From 4f8ced1f6bc9da67f51324756326c73e794ac87a Mon Sep 17 00:00:00 2001 From: Charles Gagnon Date: Mon, 2 Dec 2019 15:54:33 -0800 Subject: [PATCH 01/19] More dangling promise cleanup (#8518) * More dangling promise cleanup * return void * Function to async * Fix a couple missed promises --- src/sql/workbench/browser/actions.ts | 4 +- .../contrib/tasks/browser/tasksView.ts | 2 +- .../contrib/tasks/common/tasksAction.ts | 4 +- .../browser/accountManagementService.ts | 5 +- .../backup/browser/backupUiService.ts | 46 +++++++------- .../common/capabilitiesServiceImpl.ts | 5 +- .../browser/connectionDialogService.ts | 60 +++++++++---------- .../connectionManagementService.test.ts | 51 ++++++++-------- 8 files changed, 84 insertions(+), 93 deletions(-) diff --git a/src/sql/workbench/browser/actions.ts b/src/sql/workbench/browser/actions.ts index d2c17b4dc857..2a5e8dcd0d00 100644 --- a/src/sql/workbench/browser/actions.ts +++ b/src/sql/workbench/browser/actions.ts @@ -84,7 +84,7 @@ export class ConfigureDashboardAction extends Task { }); } - runTask(accessor: ServicesAccessor): Promise { - return accessor.get(IOpenerService).open(URI.parse(ConfigureDashboardAction.configHelpUri)).then(); + async runTask(accessor: ServicesAccessor): Promise { + accessor.get(IOpenerService).open(URI.parse(ConfigureDashboardAction.configHelpUri)); } } diff --git a/src/sql/workbench/contrib/tasks/browser/tasksView.ts b/src/sql/workbench/contrib/tasks/browser/tasksView.ts index f345121c2d12..c5da9603beb2 100644 --- a/src/sql/workbench/contrib/tasks/browser/tasksView.ts +++ b/src/sql/workbench/contrib/tasks/browser/tasksView.ts @@ -97,7 +97,7 @@ export class TaskHistoryView extends Disposable { } private updateTask(task: TaskNode): void { - this._tree.refresh(task); + this._tree.refresh(task).catch(err => errors.onUnexpectedError(err)); } public refreshTree(): void { diff --git a/src/sql/workbench/contrib/tasks/common/tasksAction.ts b/src/sql/workbench/contrib/tasks/common/tasksAction.ts index 2a3898ceb6b6..15ad481016f7 100644 --- a/src/sql/workbench/contrib/tasks/common/tasksAction.ts +++ b/src/sql/workbench/contrib/tasks/common/tasksAction.ts @@ -57,12 +57,12 @@ export class ScriptAction extends Action { super(id, label); } - public run(element: TaskNode): Promise { + public async run(element: TaskNode): Promise { if (element instanceof TaskNode) { if (element.script && element.script !== '') { this._queryEditorService.newSqlEditor(element.script); } } - return Promise.resolve(true); + return true; } } diff --git a/src/sql/workbench/services/accountManagement/browser/accountManagementService.ts b/src/sql/workbench/services/accountManagement/browser/accountManagementService.ts index ae0552f66d6e..2725bf8c7e1e 100644 --- a/src/sql/workbench/services/accountManagement/browser/accountManagementService.ts +++ b/src/sql/workbench/services/accountManagement/browser/accountManagementService.ts @@ -22,6 +22,7 @@ import { IOpenerService } from 'vs/platform/opener/common/opener'; import { URI } from 'vs/base/common/uri'; import { firstIndex } from 'vs/base/common/arrays'; import { values } from 'vs/base/common/collections'; +import { onUnexpectedError } from 'vs/base/common/errors'; export class AccountManagementService implements IAccountManagementService { // CONSTANTS /////////////////////////////////////////////////////////// @@ -314,8 +315,8 @@ export class AccountManagementService implements IAccountManagementService { * Copy the user code to the clipboard and open a browser to the verification URI */ public copyUserCodeAndOpenBrowser(userCode: string, uri: string): void { - this._clipboardService.writeText(userCode); - this._openerService.open(URI.parse(uri)); + this._clipboardService.writeText(userCode).catch(err => onUnexpectedError(err)); + this._openerService.open(URI.parse(uri)).catch(err => onUnexpectedError(err)); } // SERVICE MANAGEMENT METHODS ////////////////////////////////////////// diff --git a/src/sql/workbench/services/backup/browser/backupUiService.ts b/src/sql/workbench/services/backup/browser/backupUiService.ts index ff54132668b6..6c972702c897 100644 --- a/src/sql/workbench/services/backup/browser/backupUiService.ts +++ b/src/sql/workbench/services/backup/browser/backupUiService.ts @@ -59,7 +59,7 @@ export class BackupUiService implements IBackupUiService { } } - public showBackupDialog(connection: IConnectionProfile): Promise { + public async showBackupDialog(connection: IConnectionProfile): Promise { let self = this; self._connectionUri = ConnectionUtils.generateUri(connection); self._currentProvider = connection.providerName; @@ -79,31 +79,27 @@ export class BackupUiService implements IBackupUiService { } let backupOptions = this.getOptions(this._currentProvider); - return new Promise((resolve) => { - let uri = this._connectionManagementService.getConnectionUri(connection) - + ProviderConnectionInfo.idSeparator - + ConnectionUtils.ConnectionUriBackupIdAttributeName - + ProviderConnectionInfo.nameValueSeparator - + BackupUiService._connectionUniqueId; - - this._connectionUri = uri; - - BackupUiService._connectionUniqueId++; - - // Create connection if needed - if (!this._connectionManagementService.isConnected(uri)) { - this._connectionManagementService.connect(connection, uri).then(() => { - this._onShowBackupEvent.fire({ connection: connection, ownerUri: uri }); - }); - } + let uri = this._connectionManagementService.getConnectionUri(connection) + + ProviderConnectionInfo.idSeparator + + ConnectionUtils.ConnectionUriBackupIdAttributeName + + ProviderConnectionInfo.nameValueSeparator + + BackupUiService._connectionUniqueId; - if (backupOptions) { - (backupDialog as OptionsDialog).open(backupOptions, self._optionValues); - } else { - (backupDialog as BackupDialog).open(connection); - } - resolve(void 0); - }); + this._connectionUri = uri; + + BackupUiService._connectionUniqueId++; + + if (backupOptions) { + (backupDialog as OptionsDialog).open(backupOptions, self._optionValues); + } else { + (backupDialog as BackupDialog).open(connection); + } + + // Create connection if needed + if (!this._connectionManagementService.isConnected(uri)) { + await this._connectionManagementService.connect(connection, uri); + this._onShowBackupEvent.fire({ connection: connection, ownerUri: uri }); + } } public onShowBackupDialog() { diff --git a/src/sql/workbench/services/capabilities/common/capabilitiesServiceImpl.ts b/src/sql/workbench/services/capabilities/common/capabilitiesServiceImpl.ts index 1a79f82ae486..2577803cf7da 100644 --- a/src/sql/workbench/services/capabilities/common/capabilitiesServiceImpl.ts +++ b/src/sql/workbench/services/capabilities/common/capabilitiesServiceImpl.ts @@ -18,6 +18,7 @@ import { IConnectionProviderRegistry, Extensions as ConnectionExtensions } from import { ICapabilitiesService, ProviderFeatures, clientCapabilities, ConnectionProviderProperties } from 'sql/platform/capabilities/common/capabilitiesService'; import { find } from 'vs/base/common/arrays'; import { entries } from 'sql/base/common/collections'; +import { onUnexpectedError } from 'vs/base/common/errors'; const connectionRegistry = Registry.as(ConnectionExtensions.ConnectionProviderContributions); @@ -71,7 +72,7 @@ export class CapabilitiesService extends Disposable implements ICapabilitiesServ extensionService.whenInstalledExtensionsRegistered().then(() => { this.cleanupProviders(); - }); + }).catch(err => onUnexpectedError(err)); _storageService.onWillSaveState(() => this.shutdown()); @@ -85,7 +86,7 @@ export class CapabilitiesService extends Disposable implements ICapabilitiesServ let id = extension.contributes[connectionProvider].providerId; delete this.capabilities.connectionProviderCache[id]; } - }); + }).catch(err => onUnexpectedError(err)); })); } diff --git a/src/sql/workbench/services/connection/browser/connectionDialogService.ts b/src/sql/workbench/services/connection/browser/connectionDialogService.ts index 7971c14accb2..6a9837839e23 100644 --- a/src/sql/workbench/services/connection/browser/connectionDialogService.ts +++ b/src/sql/workbench/services/connection/browser/connectionDialogService.ts @@ -30,6 +30,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { CmsConnectionController } from 'sql/workbench/services/connection/browser/cmsConnectionController'; import { entries } from 'sql/base/common/collections'; import { find } from 'vs/base/common/arrays'; +import { onUnexpectedError } from 'vs/base/common/errors'; export interface IConnectionValidateResult { isValid: boolean; @@ -181,12 +182,12 @@ export class ConnectionDialogService implements IConnectionDialogService { profile.savePassword = true; } - this.handleDefaultOnConnect(params, profile); + this.handleDefaultOnConnect(params, profile).catch(err => onUnexpectedError(err)); } else { profile.serverName = trim(profile.serverName); - this._connectionManagementService.addSavedPassword(profile).then(connectionWithPassword => { - this.handleDefaultOnConnect(params, connectionWithPassword); - }); + this._connectionManagementService.addSavedPassword(profile).then(async (connectionWithPassword) => { + await this.handleDefaultOnConnect(params, connectionWithPassword); + }).catch(err => onUnexpectedError(err)); } } } @@ -219,14 +220,14 @@ export class ConnectionDialogService implements IConnectionDialogService { } } - private handleDefaultOnConnect(params: INewConnectionParams, connection: IConnectionProfile): Thenable { + private async handleDefaultOnConnect(params: INewConnectionParams, connection: IConnectionProfile): Promise { if (this.ignoreNextConnect) { this._connectionDialog.resetConnection(); this._connectionDialog.close(); this.ignoreNextConnect = false; this._connecting = false; this._dialogDeferredPromise.resolve(connection); - return Promise.resolve(); + return; } let fromEditor = params && params.connectionType === ConnectionType.editor; let isTemporaryConnection = params && params.connectionType === ConnectionType.temporary; @@ -242,7 +243,8 @@ export class ConnectionDialogService implements IConnectionDialogService { showFirewallRuleOnError: true }; - return this._connectionManagementService.connectAndSaveProfile(connection, uri, options, params && params.input).then(connectionResult => { + try { + const connectionResult = await this._connectionManagementService.connectAndSaveProfile(connection, uri, options, params && params.input); this._connecting = false; if (connectionResult && connectionResult.connected) { this._connectionDialog.close(); @@ -255,11 +257,11 @@ export class ConnectionDialogService implements IConnectionDialogService { this._connectionDialog.resetConnection(); this.showErrorDialog(Severity.Error, this._connectionErrorTitle, connectionResult.errorMessage, connectionResult.callStack); } - }).catch(err => { + } catch (err) { this._connecting = false; this._connectionDialog.resetConnection(); this.showErrorDialog(Severity.Error, this._connectionErrorTitle, err); - }); + } } private get uiController(): IConnectionComponentController { @@ -339,7 +341,7 @@ export class ConnectionDialogService implements IConnectionDialogService { this._model = this.createModel(connectionWithPassword); this.uiController.fillInConnectionInputs(this._model); - }); + }).catch(err => onUnexpectedError(err)); this._connectionDialog.updateProvider(this._providerNameToDisplayNameMap[connectionInfo.providerName]); } @@ -381,12 +383,9 @@ export class ConnectionDialogService implements IConnectionDialogService { return newProfile; } - private showDialogWithModel(): Promise { - return new Promise((resolve, reject) => { - this.updateModelServerCapabilities(this._inputModel); - this.doShowDialog(this._params); - resolve(null); - }); + private async showDialogWithModel(): Promise { + this.updateModelServerCapabilities(this._inputModel); + await this.doShowDialog(this._params); } public openDialogAndWait( @@ -438,7 +437,7 @@ export class ConnectionDialogService implements IConnectionDialogService { }); } - private doShowDialog(params: INewConnectionParams): Promise { + private async doShowDialog(params: INewConnectionParams): Promise { if (!this._connectionDialog) { this._connectionDialog = this._instantiationService.createInstance(ConnectionDialogWidget, this._providerDisplayNames, this._providerNameToDisplayNameMap[this._model.providerName], this._providerNameToDisplayNameMap); this._connectionDialog.onCancel(() => { @@ -457,12 +456,10 @@ export class ConnectionDialogService implements IConnectionDialogService { this._connectionDialog.newConnectionParams = params; this._connectionDialog.updateProvider(this._providerNameToDisplayNameMap[this._currentProviderType]); - return new Promise(() => { - const recentConnections: ConnectionProfile[] = this._connectionManagementService.getRecentConnections(params.providers); - this._connectionDialog.open(recentConnections.length > 0); - this.uiController.focusOnOpen(); - recentConnections.forEach(conn => conn.dispose()); - }); + const recentConnections: ConnectionProfile[] = this._connectionManagementService.getRecentConnections(params.providers); + await this._connectionDialog.open(recentConnections.length > 0); + this.uiController.focusOnOpen(); + recentConnections.forEach(conn => conn.dispose()); } private showErrorDialog(severity: Severity, headerTitle: string, message: string, messageDetails?: string): void { @@ -477,16 +474,15 @@ export class ConnectionDialogService implements IConnectionDialogService { localize('kerberosHelpLink', "Help configuring Kerberos is available at {0}", helpLink), localize('kerberosKinit', "If you have previously connected you may need to re-run kinit.") ].join('\r\n'); - actions.push(new Action('Kinit', 'Run kinit', null, true, () => { + actions.push(new Action('Kinit', 'Run kinit', null, true, async () => { this._connectionDialog.close(); - this._clipboardService.writeText('kinit\r'); - this._commandService.executeCommand('workbench.action.terminal.focus').then(resolve => { - // setTimeout to allow for terminal Instance to load. - setTimeout(() => { - return this._commandService.executeCommand('workbench.action.terminal.paste'); - }, 10); - }).then(resolve => null, reject => null); - return null; + await this._clipboardService.writeText('kinit\r'); + await this._commandService.executeCommand('workbench.action.terminal.focus'); + // setTimeout to allow for terminal Instance to load. + setTimeout(() => { + return this._commandService.executeCommand('workbench.action.terminal.paste'); + }, 10); + return; })); } diff --git a/src/sql/workbench/services/connection/test/browser/connectionManagementService.test.ts b/src/sql/workbench/services/connection/test/browser/connectionManagementService.test.ts index bdf574757bfa..aa759564637f 100644 --- a/src/sql/workbench/services/connection/test/browser/connectionManagementService.test.ts +++ b/src/sql/workbench/services/connection/test/browser/connectionManagementService.test.ts @@ -214,35 +214,32 @@ suite('SQL ConnectionManagementService tests', () => { } - function connect(uri: string, options?: IConnectionCompletionOptions, fromDialog?: boolean, connection?: IConnectionProfile, error?: string, errorCode?: number, errorCallStack?: string): Promise { + async function connect(uri: string, options?: IConnectionCompletionOptions, fromDialog?: boolean, connection?: IConnectionProfile, error?: string, errorCode?: number, errorCallStack?: string): Promise { let connectionToUse = connection ? connection : connectionProfile; - return new Promise((resolve, reject) => { - let id = connectionToUse.getOptionsKey(); - let defaultUri = 'connection:' + (id ? id : connectionToUse.serverName + ':' + connectionToUse.databaseName); - connectionManagementService.onConnectionRequestSent(() => { - let info: azdata.ConnectionInfoSummary = { - connectionId: error ? undefined : 'id', - connectionSummary: { - databaseName: connectionToUse.databaseName, - serverName: connectionToUse.serverName, - userName: connectionToUse.userName - }, - errorMessage: error, - errorNumber: errorCode, - messages: errorCallStack, - ownerUri: uri ? uri : defaultUri, - serverInfo: undefined - }; - connectionManagementService.onConnectionComplete(0, info); - }); - connectionManagementService.cancelConnectionForUri(uri).then(() => { - if (fromDialog) { - resolve(connectionManagementService.connectAndSaveProfile(connectionToUse, uri, options)); - } else { - resolve(connectionManagementService.connect(connectionToUse, uri, options)); - } - }); + let id = connectionToUse.getOptionsKey(); + let defaultUri = 'connection:' + (id ? id : connectionToUse.serverName + ':' + connectionToUse.databaseName); + connectionManagementService.onConnectionRequestSent(() => { + let info: azdata.ConnectionInfoSummary = { + connectionId: error ? undefined : 'id', + connectionSummary: { + databaseName: connectionToUse.databaseName, + serverName: connectionToUse.serverName, + userName: connectionToUse.userName + }, + errorMessage: error, + errorNumber: errorCode, + messages: errorCallStack, + ownerUri: uri ? uri : defaultUri, + serverInfo: undefined + }; + connectionManagementService.onConnectionComplete(0, info); }); + await connectionManagementService.cancelConnectionForUri(uri); + if (fromDialog) { + return connectionManagementService.connectAndSaveProfile(connectionToUse, uri, options); + } else { + return connectionManagementService.connect(connectionToUse, uri, options); + } } test('showConnectionDialog should open the dialog with default type given no parameters', done => { From d358cdac1e964c636fe88163a67566ed280a2ad9 Mon Sep 17 00:00:00 2001 From: Charles Gagnon Date: Tue, 3 Dec 2019 09:04:33 -0800 Subject: [PATCH 02/19] Add tests for declarative table component (#8499) --- .../api/extHostModelView.test.ts | 686 ++++++++++-------- 1 file changed, 398 insertions(+), 288 deletions(-) diff --git a/src/sql/workbench/test/electron-browser/api/extHostModelView.test.ts b/src/sql/workbench/test/electron-browser/api/extHostModelView.test.ts index 00153d6c5035..942390cd927d 100644 --- a/src/sql/workbench/test/electron-browser/api/extHostModelView.test.ts +++ b/src/sql/workbench/test/electron-browser/api/extHostModelView.test.ts @@ -9,7 +9,7 @@ import * as azdata from 'azdata'; import { ExtHostModelView } from 'sql/workbench/api/common/extHostModelView'; import { MainThreadModelViewShape } from 'sql/workbench/api/common/sqlExtHost.protocol'; import { IMainContext } from 'vs/workbench/api/common/extHost.protocol'; -import { IComponentShape, IItemConfig, ComponentEventType, IComponentEventArgs, ModelComponentTypes } from 'sql/workbench/api/common/sqlExtHostTypes'; +import { IComponentShape, IItemConfig, ComponentEventType, IComponentEventArgs, ModelComponentTypes, DeclarativeDataType } from 'sql/workbench/api/common/sqlExtHostTypes'; import { TitledFormItemLayout } from 'sql/workbench/browser/modelComponents/formContainer.component'; import { assign } from 'vs/base/common/objects'; @@ -28,9 +28,10 @@ suite('ExtHostModelView Validation Tests', () => { let validText = 'valid'; let widgetId = 'widget_id'; let handle = 1; - // let viewInitialized: Deferred; + let mainContext: IMainContext; - setup(done => { + // Common setup for all extHostModelView tests + setup(() => { // Set up the MainThreadModelViewShape proxy mockProxy = Mock.ofInstance({ $registerProvider: (id: string) => undefined, @@ -44,335 +45,444 @@ suite('ExtHostModelView Validation Tests', () => { dispose: () => undefined, $validate: (handle: number, componentId: string) => undefined }, MockBehavior.Loose); - let mainContext = { + mainContext = { getProxy: proxyType => mockProxy.object }; mockProxy.setup(x => x.$initializeModel(It.isAny(), It.isAny())).returns(() => Promise.resolve()); mockProxy.setup(x => x.$registerEvent(It.isAny(), It.isAny())).returns(() => Promise.resolve()); mockProxy.setup(x => x.$setProperties(It.isAny(), It.isAny(), It.isAny())).returns(() => Promise.resolve()); - // Register a model view of an input box and drop down box inside a form container inside a flex container extHostModelView = new ExtHostModelView(mainContext, undefined); - extHostModelView.$registerProvider(widgetId, async view => { - modelView = view; - inputBox = view.modelBuilder.inputBox() - .withValidation(component => component.value === validText) - .component(); - let dropDownBox = view.modelBuilder.dropDown().component(); - let formContainer = view.modelBuilder.formContainer() - .withItems([inputBox, dropDownBox]) - .component(); - let flexContainer = view.modelBuilder.flexContainer() - .withItems([formContainer]) - .component(); - await view.initializeModel(flexContainer); - done(); - }, undefined); - - extHostModelView.$registerWidget(handle, widgetId, undefined, undefined); }); - test('The custom validation output of a component gets set when it is initialized', done => { - extHostModelView.$runCustomValidations(handle, inputBox.id).then(valid => { - try { - assert.equal(valid, false, 'Empty input box did not validate as false'); + // Set of general tests using a couple of common components + suite('Basic', () => { + setup(done => { + extHostModelView.$registerProvider(widgetId, async view => { + modelView = view; + inputBox = view.modelBuilder.inputBox() + .withValidation(component => component.value === validText) + .component(); + let dropDownBox = view.modelBuilder.dropDown().component(); + let formContainer = view.modelBuilder.formContainer() + .withItems([inputBox, dropDownBox]) + .component(); + let flexContainer = view.modelBuilder.flexContainer() + .withItems([formContainer]) + .component(); + await view.initializeModel(flexContainer); done(); - } catch (err) { - done(err); - } - }, err => done(err)); - }); + }, undefined); - test('The custom validation output of a component changes if its value changes', done => { - inputBox.value = validText; - extHostModelView.$runCustomValidations(handle, inputBox.id).then(valid => { - try { - assert.equal(valid, true, 'Valid input box did not validate as valid'); - done(); - } catch (err) { - done(err); - } - }, err => done(err)); - }); + extHostModelView.$registerWidget(handle, widgetId, undefined, undefined); + }); - test('The custom validation output of a component changes after a PropertiesChanged event', done => { - extHostModelView.$handleEvent(handle, inputBox.id, { - args: { - 'value': validText - }, - eventType: ComponentEventType.PropertiesChanged - } as IComponentEventArgs); - extHostModelView.$runCustomValidations(handle, inputBox.id).then(valid => { - try { - assert.equal(valid, true, 'Valid input box did not validate as valid after PropertiesChanged event'); - done(); - } catch (err) { - done(err); - } - }, err => done(err)); - }); + test('The custom validation output of a component gets set when it is initialized', done => { + extHostModelView.$runCustomValidations(handle, inputBox.id).then(valid => { + try { + assert.equal(valid, false, 'Empty input box did not validate as false'); + done(); + } catch (err) { + done(err); + } + }, err => done(err)); + }); - test('The validity of a component is set by main thread validationChanged events', () => { - assert.equal(inputBox.valid, true, 'Component validity is true by default'); - extHostModelView.$handleEvent(handle, inputBox.id, { - eventType: ComponentEventType.validityChanged, - args: false + test('The custom validation output of a component changes if its value changes', done => { + inputBox.value = validText; + extHostModelView.$runCustomValidations(handle, inputBox.id).then(valid => { + try { + assert.equal(valid, true, 'Valid input box did not validate as valid'); + done(); + } catch (err) { + done(err); + } + }, err => done(err)); }); - assert.equal(inputBox.valid, false, 'Input box did not update validity to false based on the validityChanged event'); - extHostModelView.$handleEvent(handle, inputBox.id, { - eventType: ComponentEventType.validityChanged, - args: true + + test('The custom validation output of a component changes after a PropertiesChanged event', done => { + extHostModelView.$handleEvent(handle, inputBox.id, { + args: { + 'value': validText + }, + eventType: ComponentEventType.PropertiesChanged + } as IComponentEventArgs); + extHostModelView.$runCustomValidations(handle, inputBox.id).then(valid => { + try { + assert.equal(valid, true, 'Valid input box did not validate as valid after PropertiesChanged event'); + done(); + } catch (err) { + done(err); + } + }, err => done(err)); }); - assert.equal(inputBox.valid, true, 'Input box did not update validity to true based on the validityChanged event'); - }); - test('Main thread validityChanged events cause component to fire validity changed events', () => { - let validityFromEvent: boolean = undefined; - inputBox.onValidityChanged(valid => validityFromEvent = valid); - extHostModelView.$handleEvent(handle, inputBox.id, { - eventType: ComponentEventType.validityChanged, - args: false + test('The validity of a component is set by main thread validationChanged events', () => { + assert.equal(inputBox.valid, true, 'Component validity is true by default'); + extHostModelView.$handleEvent(handle, inputBox.id, { + eventType: ComponentEventType.validityChanged, + args: false + }); + assert.equal(inputBox.valid, false, 'Input box did not update validity to false based on the validityChanged event'); + extHostModelView.$handleEvent(handle, inputBox.id, { + eventType: ComponentEventType.validityChanged, + args: true + }); + assert.equal(inputBox.valid, true, 'Input box did not update validity to true based on the validityChanged event'); }); - assert.equal(validityFromEvent, false, 'Main thread validityChanged event did not cause component to fire its own event'); - }); - test('Setting a form component as required initializes the model with the component required', () => { - mockProxy.setup(x => x.$addToContainer(It.isAny(), It.isAny(), It.isAny(), undefined)).returns(() => Promise.resolve()); + test('Main thread validityChanged events cause component to fire validity changed events', () => { + let validityFromEvent: boolean = undefined; + inputBox.onValidityChanged(valid => validityFromEvent = valid); + extHostModelView.$handleEvent(handle, inputBox.id, { + eventType: ComponentEventType.validityChanged, + args: false + }); + assert.equal(validityFromEvent, false, 'Main thread validityChanged event did not cause component to fire its own event'); + }); - // Set up the input component with required initially set to false - let inputComponent = modelView.modelBuilder.inputBox().component(); - inputComponent.required = false; + test('Setting a form component as required initializes the model with the component required', () => { + mockProxy.setup(x => x.$addToContainer(It.isAny(), It.isAny(), It.isAny(), undefined)).returns(() => Promise.resolve()); + + // Set up the input component with required initially set to false + let inputComponent = modelView.modelBuilder.inputBox().component(); + inputComponent.required = false; + + // If I build a form that sets the input component as required + let inputFormComponent: azdata.FormComponent = { + component: inputComponent, + title: 'test_input', + required: true + }; + let requiredFormContainer = modelView.modelBuilder.formContainer().withFormItems([inputFormComponent]).component(); + modelView.initializeModel(requiredFormContainer); + + // Then the input component is sent to the main thread with required set to true + mockProxy.verify(x => x.$initializeModel(It.isAny(), It.is(rootComponent => { + return rootComponent.itemConfigs.length === 1 && rootComponent.itemConfigs[0].componentShape.id === inputComponent.id && rootComponent.itemConfigs[0].componentShape.properties['required'] === true; + })), Times.once()); + }); - // If I build a form that sets the input component as required - let inputFormComponent: azdata.FormComponent = { - component: inputComponent, - title: 'test_input', - required: true - }; - let requiredFormContainer = modelView.modelBuilder.formContainer().withFormItems([inputFormComponent]).component(); - modelView.initializeModel(requiredFormContainer); + test('Form component groups are handled correctly by adding each item in the group and a label to the form', () => { + // Set up the mock proxy to save the component that gets initialized so that it can be verified + let rootComponent: IComponentShape; + mockProxy.setup(x => x.$initializeModel(It.isAny(), It.isAny())).callback((handle, componentShape) => rootComponent = componentShape); + mockProxy.setup(x => x.$addToContainer(It.isAny(), It.isAny(), It.isAny(), undefined)).returns(() => Promise.resolve()); + + // Set up the form with a top level component and a group + let topLevelList = modelView.modelBuilder.listBox().component(); + let groupInput = modelView.modelBuilder.inputBox().component(); + let groupDropdown = modelView.modelBuilder.dropDown().component(); + + let topLevelInputFormComponent: azdata.FormComponent = { component: topLevelList, title: 'top_level_input' }; + let groupInputFormComponent: azdata.FormComponent = { component: groupInput, title: 'group_input' }; + let groupDropdownFormComponent: azdata.FormComponent = { component: groupDropdown, title: 'group_dropdown' }; + + let groupTitle = 'group_title'; + + // Give the group a default layout and add one just for the input component too + let defaultLayout: azdata.FormItemLayout = { + horizontal: true + }; + let groupInputLayout: azdata.FormItemLayout = { + horizontal: false + }; + + // If I build a form that has a group with a default layout where one item in the group has its own layout + let formContainer = modelView.modelBuilder.formContainer().withFormItems([ + topLevelInputFormComponent, + { + components: [ + assign(groupInputFormComponent, { layout: groupInputLayout }), + groupDropdownFormComponent + ], + title: groupTitle + } + ], defaultLayout).component(); + modelView.initializeModel(formContainer); + + // Then all the items plus a group label are added and have the correct layouts + assert.equal(rootComponent.itemConfigs.length, 4); + let listBoxConfig = rootComponent.itemConfigs[0]; + let groupLabelConfig = rootComponent.itemConfigs[1]; + let inputBoxConfig = rootComponent.itemConfigs[2]; + let dropdownConfig = rootComponent.itemConfigs[3]; + + // Verify that the correct items were added + assert.equal(listBoxConfig.componentShape.type, ModelComponentTypes.ListBox, `Unexpected ModelComponentType. Expected ListBox but got ${ModelComponentTypes[listBoxConfig.componentShape.type]}`); + assert.equal(groupLabelConfig.componentShape.type, ModelComponentTypes.Text, `Unexpected ModelComponentType. Expected Text but got ${ModelComponentTypes[groupLabelConfig.componentShape.type]}`); + assert.equal(inputBoxConfig.componentShape.type, ModelComponentTypes.InputBox, `Unexpected ModelComponentType. Expected InputBox but got ${ModelComponentTypes[inputBoxConfig.componentShape.type]}`); + assert.equal(dropdownConfig.componentShape.type, ModelComponentTypes.DropDown, `Unexpected ModelComponentType. Expected DropDown but got ${ModelComponentTypes[dropdownConfig.componentShape.type]}`); + + // Verify that the group title was set up correctly + assert.equal(groupLabelConfig.componentShape.properties['value'], groupTitle, `Unexpected title. Expected ${groupTitle} but got ${groupLabelConfig.componentShape.properties['value']}`); + assert.equal((groupLabelConfig.config as TitledFormItemLayout).isGroupLabel, true, `Unexpected value for isGroupLabel. Expected true but got ${(groupLabelConfig.config as TitledFormItemLayout).isGroupLabel}`); + + // Verify that the components' layouts are correct + assert.equal((listBoxConfig.config as azdata.FormItemLayout).horizontal, defaultLayout.horizontal, `Unexpected layout for listBoxConfig. Expected defaultLayout.horizontal but got ${(listBoxConfig.config as azdata.FormItemLayout).horizontal}`); + assert.equal((inputBoxConfig.config as azdata.FormItemLayout).horizontal, groupInputLayout.horizontal, `Unexpected layout for inputBoxConfig. Expected groupInputLayout.horizontal but got ${(inputBoxConfig.config as azdata.FormItemLayout).horizontal}`); + assert.equal((dropdownConfig.config as azdata.FormItemLayout).horizontal, defaultLayout.horizontal, `Unexpected layout for dropdownConfig. Expected defaultLayout.horizontal but got ${(dropdownConfig.config as azdata.FormItemLayout).horizontal}`); + }); - // Then the input component is sent to the main thread with required set to true - mockProxy.verify(x => x.$initializeModel(It.isAny(), It.is(rootComponent => { - return rootComponent.itemConfigs.length === 1 && rootComponent.itemConfigs[0].componentShape.id === inputComponent.id && rootComponent.itemConfigs[0].componentShape.properties['required'] === true; - })), Times.once()); - }); + test('Inserting and removing components from a container should work correctly', () => { + mockProxy.setup(x => x.$initializeModel(It.isAny(), It.isAny())); + mockProxy.setup(x => x.$addToContainer(It.isAny(), It.isAny(), It.isAny(), undefined)).returns(() => Promise.resolve()); + mockProxy.setup(x => x.$addToContainer(It.isAny(), It.isAny(), It.isAny(), It.isAny())).returns(() => Promise.resolve()); + mockProxy.setup(x => x.$removeFromContainer(It.isAny(), It.isAny(), It.isAny())).returns(() => Promise.resolve()); + // Set up the form with a top level component and a group + let listBox = modelView.modelBuilder.listBox().component(); + let inputBox = modelView.modelBuilder.inputBox().component(); + let dropDown = modelView.modelBuilder.dropDown().component(); + + let flex = modelView.modelBuilder.flexContainer().withItems([listBox, inputBox]).component(); + modelView.initializeModel(flex); + + const itemConfigs: InternalItemConfig[] = (flex as IWithItemConfig).itemConfigs; + assert.equal(itemConfigs.length, 2, `Unexpected number of items in list. Expected 2, got ${itemConfigs.length} ${JSON.stringify(itemConfigs)}`); + flex.insertItem(dropDown, 1); + assert.equal(itemConfigs.length, 3, `Unexpected number of items in list. Expected 3, got ${itemConfigs.length} ${JSON.stringify(itemConfigs)}`); + assert.equal(itemConfigs[1].toIItemConfig().componentShape.type, ModelComponentTypes.DropDown, `Unexpected ModelComponentType. Expected DropDown but got ${ModelComponentTypes[itemConfigs[1].toIItemConfig().componentShape.type]}`); + flex.removeItem(listBox); + assert.equal(itemConfigs.length, 2, `Unexpected number of items in list. Expected 2, got ${itemConfigs.length} ${JSON.stringify(itemConfigs)}`); + assert.equal(itemConfigs[0].toIItemConfig().componentShape.type, ModelComponentTypes.DropDown, `Unexpected ModelComponentType. Expected DropDown but got ${ModelComponentTypes[itemConfigs[0].toIItemConfig().componentShape.type]}`); + }); - test('Form component groups are handled correctly by adding each item in the group and a label to the form', () => { - // Set up the mock proxy to save the component that gets initialized so that it can be verified - let rootComponent: IComponentShape; - mockProxy.setup(x => x.$initializeModel(It.isAny(), It.isAny())).callback((handle, componentShape) => rootComponent = componentShape); - mockProxy.setup(x => x.$addToContainer(It.isAny(), It.isAny(), It.isAny(), undefined)).returns(() => Promise.resolve()); + test('Inserting component give negative number fails', () => { + mockProxy.setup(x => x.$initializeModel(It.isAny(), It.isAny())).callback((handle, componentShape) => { }); + mockProxy.setup(x => x.$addToContainer(It.isAny(), It.isAny(), It.isAny(), undefined)).returns(() => Promise.resolve()); + mockProxy.setup(x => x.$addToContainer(It.isAny(), It.isAny(), It.isAny(), It.isAny())).returns(() => Promise.resolve()); + mockProxy.setup(x => x.$removeFromContainer(It.isAny(), It.isAny(), It.isAny())).returns(() => Promise.resolve()); - // Set up the form with a top level component and a group - let topLevelList = modelView.modelBuilder.listBox().component(); - let groupInput = modelView.modelBuilder.inputBox().component(); - let groupDropdown = modelView.modelBuilder.dropDown().component(); + // Set up the form with a top level component and a group + let listBox = modelView.modelBuilder.listBox().component(); + let inputBox = modelView.modelBuilder.inputBox().component(); + let dropDown = modelView.modelBuilder.dropDown().component(); - let topLevelInputFormComponent: azdata.FormComponent = { component: topLevelList, title: 'top_level_input' }; - let groupInputFormComponent: azdata.FormComponent = { component: groupInput, title: 'group_input' }; - let groupDropdownFormComponent: azdata.FormComponent = { component: groupDropdown, title: 'group_dropdown' }; + let flex = modelView.modelBuilder.flexContainer().withItems([listBox, inputBox]).component(); + modelView.initializeModel(flex); - let groupTitle = 'group_title'; + const itemConfigs: InternalItemConfig[] = (flex as IWithItemConfig).itemConfigs; + assert.equal(itemConfigs.length, 2, `Unexpected number of items in list. Expected 2, got ${itemConfigs.length} ${JSON.stringify(itemConfigs)}`); + assert.throws(() => flex.insertItem(dropDown, -1), `Didn't get expected exception when calling insertItem with invalid index -1`); + }); - // Give the group a default layout and add one just for the input component too - let defaultLayout: azdata.FormItemLayout = { - horizontal: true - }; - let groupInputLayout: azdata.FormItemLayout = { - horizontal: false - }; + test('Inserting component give wrong number fails', () => { + mockProxy.setup(x => x.$initializeModel(It.isAny(), It.isAny())).callback((handle, componentShape) => { }); + mockProxy.setup(x => x.$addToContainer(It.isAny(), It.isAny(), It.isAny(), undefined)).returns(() => Promise.resolve()); + mockProxy.setup(x => x.$addToContainer(It.isAny(), It.isAny(), It.isAny(), It.isAny())).returns(() => Promise.resolve()); + mockProxy.setup(x => x.$removeFromContainer(It.isAny(), It.isAny(), It.isAny())).returns(() => Promise.resolve()); - // If I build a form that has a group with a default layout where one item in the group has its own layout - let formContainer = modelView.modelBuilder.formContainer().withFormItems([ - topLevelInputFormComponent, - { - components: [ - assign(groupInputFormComponent, { layout: groupInputLayout }), - groupDropdownFormComponent - ], - title: groupTitle - } - ], defaultLayout).component(); - modelView.initializeModel(formContainer); - - // Then all the items plus a group label are added and have the correct layouts - assert.equal(rootComponent.itemConfigs.length, 4); - let listBoxConfig = rootComponent.itemConfigs[0]; - let groupLabelConfig = rootComponent.itemConfigs[1]; - let inputBoxConfig = rootComponent.itemConfigs[2]; - let dropdownConfig = rootComponent.itemConfigs[3]; - - // Verify that the correct items were added - assert.equal(listBoxConfig.componentShape.type, ModelComponentTypes.ListBox, `Unexpected ModelComponentType. Expected ListBox but got ${ModelComponentTypes[listBoxConfig.componentShape.type]}`); - assert.equal(groupLabelConfig.componentShape.type, ModelComponentTypes.Text, `Unexpected ModelComponentType. Expected Text but got ${ModelComponentTypes[groupLabelConfig.componentShape.type]}`); - assert.equal(inputBoxConfig.componentShape.type, ModelComponentTypes.InputBox, `Unexpected ModelComponentType. Expected InputBox but got ${ModelComponentTypes[inputBoxConfig.componentShape.type]}`); - assert.equal(dropdownConfig.componentShape.type, ModelComponentTypes.DropDown, `Unexpected ModelComponentType. Expected DropDown but got ${ModelComponentTypes[dropdownConfig.componentShape.type]}`); - - // Verify that the group title was set up correctly - assert.equal(groupLabelConfig.componentShape.properties['value'], groupTitle, `Unexpected title. Expected ${groupTitle} but got ${groupLabelConfig.componentShape.properties['value']}`); - assert.equal((groupLabelConfig.config as TitledFormItemLayout).isGroupLabel, true, `Unexpected value for isGroupLabel. Expected true but got ${(groupLabelConfig.config as TitledFormItemLayout).isGroupLabel}`); - - // Verify that the components' layouts are correct - assert.equal((listBoxConfig.config as azdata.FormItemLayout).horizontal, defaultLayout.horizontal, `Unexpected layout for listBoxConfig. Expected defaultLayout.horizontal but got ${(listBoxConfig.config as azdata.FormItemLayout).horizontal}`); - assert.equal((inputBoxConfig.config as azdata.FormItemLayout).horizontal, groupInputLayout.horizontal, `Unexpected layout for inputBoxConfig. Expected groupInputLayout.horizontal but got ${(inputBoxConfig.config as azdata.FormItemLayout).horizontal}`); - assert.equal((dropdownConfig.config as azdata.FormItemLayout).horizontal, defaultLayout.horizontal, `Unexpected layout for dropdownConfig. Expected defaultLayout.horizontal but got ${(dropdownConfig.config as azdata.FormItemLayout).horizontal}`); - }); + // Set up the form with a top level component and a group + let listBox = modelView.modelBuilder.listBox().component(); + let inputBox = modelView.modelBuilder.inputBox().component(); + let dropDown = modelView.modelBuilder.dropDown().component(); - test('Inserting and removing components from a container should work correctly', () => { - // Set up the mock proxy to save the component that gets initialized so that it can be verified - mockProxy.setup(x => x.$initializeModel(It.isAny(), It.isAny())); - mockProxy.setup(x => x.$addToContainer(It.isAny(), It.isAny(), It.isAny(), undefined)).returns(() => Promise.resolve()); - mockProxy.setup(x => x.$addToContainer(It.isAny(), It.isAny(), It.isAny(), It.isAny())).returns(() => Promise.resolve()); - mockProxy.setup(x => x.$removeFromContainer(It.isAny(), It.isAny(), It.isAny())).returns(() => Promise.resolve()); - - // Set up the form with a top level component and a group - let listBox = modelView.modelBuilder.listBox().component(); - let inputBox = modelView.modelBuilder.inputBox().component(); - let dropDown = modelView.modelBuilder.dropDown().component(); - - let flex = modelView.modelBuilder.flexContainer().withItems([listBox, inputBox]).component(); - modelView.initializeModel(flex); - - const itemConfigs: InternalItemConfig[] = (flex as IWithItemConfig).itemConfigs; - assert.equal(itemConfigs.length, 2, `Unexpected number of items in list. Expected 2, got ${itemConfigs.length} ${JSON.stringify(itemConfigs)}`); - flex.insertItem(dropDown, 1); - assert.equal(itemConfigs.length, 3, `Unexpected number of items in list. Expected 3, got ${itemConfigs.length} ${JSON.stringify(itemConfigs)}`); - assert.equal(itemConfigs[1].toIItemConfig().componentShape.type, ModelComponentTypes.DropDown, `Unexpected ModelComponentType. Expected DropDown but got ${ModelComponentTypes[itemConfigs[1].toIItemConfig().componentShape.type]}`); - flex.removeItem(listBox); - assert.equal(itemConfigs.length, 2, `Unexpected number of items in list. Expected 2, got ${itemConfigs.length} ${JSON.stringify(itemConfigs)}`); - assert.equal(itemConfigs[0].toIItemConfig().componentShape.type, ModelComponentTypes.DropDown, `Unexpected ModelComponentType. Expected DropDown but got ${ModelComponentTypes[itemConfigs[0].toIItemConfig().componentShape.type]}`); - }); + let flex = modelView.modelBuilder.flexContainer().withItems([listBox, inputBox]).component(); + modelView.initializeModel(flex); - test('Inserting component give negative number fails', () => { - mockProxy.setup(x => x.$initializeModel(It.isAny(), It.isAny())).callback((handle, componentShape) => { }); - mockProxy.setup(x => x.$addToContainer(It.isAny(), It.isAny(), It.isAny(), undefined)).returns(() => Promise.resolve()); - mockProxy.setup(x => x.$addToContainer(It.isAny(), It.isAny(), It.isAny(), It.isAny())).returns(() => Promise.resolve()); - mockProxy.setup(x => x.$removeFromContainer(It.isAny(), It.isAny(), It.isAny())).returns(() => Promise.resolve()); + const itemConfigs: InternalItemConfig[] = (flex as IWithItemConfig).itemConfigs; + assert.equal(itemConfigs.length, 2, `Unexpected number of items in list. Expected 2, got ${itemConfigs.length} ${JSON.stringify(itemConfigs)}`); + assert.throws(() => flex.insertItem(dropDown, 10), `Didn't get expected exception when calling insertItem with invalid index 10`); + }); - // Set up the form with a top level component and a group - let listBox = modelView.modelBuilder.listBox().component(); - let inputBox = modelView.modelBuilder.inputBox().component(); - let dropDown = modelView.modelBuilder.dropDown().component(); + test('Inserting component give end of the list succeeds', () => { + mockProxy.setup(x => x.$initializeModel(It.isAny(), It.isAny())).callback((handle, componentShape) => { }); + mockProxy.setup(x => x.$addToContainer(It.isAny(), It.isAny(), It.isAny(), undefined)).returns(() => Promise.resolve()); + mockProxy.setup(x => x.$addToContainer(It.isAny(), It.isAny(), It.isAny(), It.isAny())).returns(() => Promise.resolve()); + mockProxy.setup(x => x.$removeFromContainer(It.isAny(), It.isAny(), It.isAny())).returns(() => Promise.resolve()); - let flex = modelView.modelBuilder.flexContainer().withItems([listBox, inputBox]).component(); - modelView.initializeModel(flex); + // Set up the form with a top level component and a group + let listBox = modelView.modelBuilder.listBox().component(); + let inputBox = modelView.modelBuilder.inputBox().component(); + let dropDown = modelView.modelBuilder.dropDown().component(); - const itemConfigs: InternalItemConfig[] = (flex as IWithItemConfig).itemConfigs; - assert.equal(itemConfigs.length, 2, `Unexpected number of items in list. Expected 2, got ${itemConfigs.length} ${JSON.stringify(itemConfigs)}`); - assert.throws(() => flex.insertItem(dropDown, -1), `Didn't get expected exception when calling insertItem with invalid index -1`); - }); + let flex = modelView.modelBuilder.flexContainer().withItems([listBox, inputBox]).component(); + modelView.initializeModel(flex); - test('Inserting component give wrong number fails', () => { - mockProxy.setup(x => x.$initializeModel(It.isAny(), It.isAny())).callback((handle, componentShape) => { }); - mockProxy.setup(x => x.$addToContainer(It.isAny(), It.isAny(), It.isAny(), undefined)).returns(() => Promise.resolve()); - mockProxy.setup(x => x.$addToContainer(It.isAny(), It.isAny(), It.isAny(), It.isAny())).returns(() => Promise.resolve()); - mockProxy.setup(x => x.$removeFromContainer(It.isAny(), It.isAny(), It.isAny())).returns(() => Promise.resolve()); + const itemConfigs: InternalItemConfig[] = (flex as IWithItemConfig).itemConfigs; + assert.equal(itemConfigs.length, 2, `Unexpected number of items in list. Expected 2, got ${itemConfigs.length} ${JSON.stringify(itemConfigs)}`); + flex.insertItem(dropDown, 2); + assert.equal(itemConfigs.length, 3, `Unexpected number of items in list. Expected 3, got ${itemConfigs.length} ${JSON.stringify(itemConfigs)}`); + }); - // Set up the form with a top level component and a group - let listBox = modelView.modelBuilder.listBox().component(); - let inputBox = modelView.modelBuilder.inputBox().component(); - let dropDown = modelView.modelBuilder.dropDown().component(); + test('Removing a component that does not exist does not fail', () => { + // Set up the mock proxy to save the component that gets initialized so that it can be verified + mockProxy.setup(x => x.$initializeModel(It.isAny(), It.isAny())); + mockProxy.setup(x => x.$addToContainer(It.isAny(), It.isAny(), It.isAny(), undefined)).returns(() => Promise.resolve()); + mockProxy.setup(x => x.$addToContainer(It.isAny(), It.isAny(), It.isAny(), It.isAny())).returns(() => Promise.resolve()); + + // Set up the form with a top level component and a group + let listBox = modelView.modelBuilder.listBox().component(); + let inputBox = modelView.modelBuilder.inputBox().component(); + let dropDown = modelView.modelBuilder.dropDown().component(); + + let flex = modelView.modelBuilder.flexContainer().withItems([listBox, inputBox]).component(); + modelView.initializeModel(flex); + + const itemConfigs: InternalItemConfig[] = (flex as IWithItemConfig).itemConfigs; + let result = flex.removeItem(dropDown); + assert.equal(result, false); + assert.equal(itemConfigs.length, 2, `Unexpected number of items in list. Expected 2, got ${itemConfigs.length} ${JSON.stringify(itemConfigs)}`); + assert.equal(itemConfigs[0].toIItemConfig().componentShape.type, ModelComponentTypes.ListBox, `Unexpected ModelComponentType. Expected ListBox but got ${ModelComponentTypes[itemConfigs[0].toIItemConfig().componentShape.type]}`); + }); - let flex = modelView.modelBuilder.flexContainer().withItems([listBox, inputBox]).component(); - modelView.initializeModel(flex); - const itemConfigs: InternalItemConfig[] = (flex as IWithItemConfig).itemConfigs; - assert.equal(itemConfigs.length, 2, `Unexpected number of items in list. Expected 2, got ${itemConfigs.length} ${JSON.stringify(itemConfigs)}`); - assert.throws(() => flex.insertItem(dropDown, 10), `Didn't get expected exception when calling insertItem with invalid index 10`); + test('Inserting and removing component in a form should work correctly', () => { + mockProxy.setup(x => x.$initializeModel(It.isAny(), It.isAny())).callback((handle, componentShape) => { }); + mockProxy.setup(x => x.$addToContainer(It.isAny(), It.isAny(), It.isAny(), undefined)).returns(() => Promise.resolve()); + mockProxy.setup(x => x.$addToContainer(It.isAny(), It.isAny(), It.isAny(), It.isAny())).returns(() => Promise.resolve()); + mockProxy.setup(x => x.$removeFromContainer(It.isAny(), It.isAny(), It.isAny())).returns(() => Promise.resolve()); + + // Set up the form with a top level component and a group + let listBox = modelView.modelBuilder.listBox().component(); + let inputBox = modelView.modelBuilder.inputBox().component(); + let dropDown = modelView.modelBuilder.dropDown().component(); + let checkBox = modelView.modelBuilder.checkBox().component(); + + let groupItems: azdata.FormComponentGroup = { + title: 'Group', + components: [{ + title: 'Drop Down', + component: dropDown + }, { + title: 'Check Box', + component: checkBox + }] + }; + let listBoxFormItem: azdata.FormComponent = { + title: 'List Box', + component: listBox + }; + let inputBoxFormItem: azdata.FormComponent = { + title: 'Input Box', + component: inputBox + }; + + let formBuilder = modelView.modelBuilder.formContainer(); + formBuilder.addFormItem(listBoxFormItem); + let form = formBuilder.component(); + modelView.initializeModel(formBuilder.component()); + + const itemConfigs: InternalItemConfig[] = (form as IWithItemConfig).itemConfigs; + assert.equal(itemConfigs.length, 1); + formBuilder.insertFormItem(inputBoxFormItem, 0); + assert.equal(itemConfigs.length, 2, `Unexpected number of items in list. Expected 2, got ${itemConfigs.length} ${JSON.stringify(itemConfigs)}`); + assert.equal(itemConfigs[0].toIItemConfig().componentShape.type, ModelComponentTypes.InputBox, `Unexpected ModelComponentType. Expected InputBox but got ${ModelComponentTypes[itemConfigs[0].toIItemConfig().componentShape.type]}`); + formBuilder.insertFormItem(groupItems, 0); + assert.equal(itemConfigs.length, 5, `Unexpected number of items in list. Expected 5, got ${itemConfigs.length} ${JSON.stringify(itemConfigs)}`); + formBuilder.removeFormItem(listBoxFormItem); + assert.equal(itemConfigs.length, 4, `Unexpected number of items in list. Expected 4, got ${itemConfigs.length} ${JSON.stringify(itemConfigs)}`); + formBuilder.removeFormItem(groupItems); + assert.equal(itemConfigs.length, 1, `Unexpected number of items in list. Expected 1, got ${itemConfigs.length} ${JSON.stringify(itemConfigs)}`); + formBuilder.addFormItem(listBoxFormItem); + assert.equal(itemConfigs.length, 2, `Unexpected number of items in list. Expected 2, got ${itemConfigs.length} ${JSON.stringify(itemConfigs)}`); + assert.equal(itemConfigs[1].toIItemConfig().componentShape.type, ModelComponentTypes.ListBox, `Unexpected ModelComponentType. Expected ListBox but got ${ModelComponentTypes[itemConfigs[1].toIItemConfig().componentShape.type]}`); + }); }); - test('Inserting component give end of the list succeeds', () => { - mockProxy.setup(x => x.$initializeModel(It.isAny(), It.isAny())).callback((handle, componentShape) => { }); - mockProxy.setup(x => x.$addToContainer(It.isAny(), It.isAny(), It.isAny(), undefined)).returns(() => Promise.resolve()); - mockProxy.setup(x => x.$addToContainer(It.isAny(), It.isAny(), It.isAny(), It.isAny())).returns(() => Promise.resolve()); - mockProxy.setup(x => x.$removeFromContainer(It.isAny(), It.isAny(), It.isAny())).returns(() => Promise.resolve()); + suite('Declarative table', () => { + setup(done => { + mockProxy.setup(x => x.$addToContainer(It.isAny(), It.isAny(), It.isAny(), undefined)).returns(() => Promise.resolve()); + mockProxy.setup(x => x.$addToContainer(It.isAny(), It.isAny(), It.isAny(), It.isAny())).returns(() => Promise.resolve()); + mockProxy.setup(x => x.$removeFromContainer(It.isAny(), It.isAny(), It.isAny())).returns(() => Promise.resolve()); - // Set up the form with a top level component and a group - let listBox = modelView.modelBuilder.listBox().component(); - let inputBox = modelView.modelBuilder.inputBox().component(); - let dropDown = modelView.modelBuilder.dropDown().component(); + extHostModelView = new ExtHostModelView(mainContext, undefined); + extHostModelView.$registerProvider(widgetId, async view => { + modelView = view; + done(); + }, undefined); - let flex = modelView.modelBuilder.flexContainer().withItems([listBox, inputBox]).component(); - modelView.initializeModel(flex); + extHostModelView.$registerWidget(handle, widgetId, undefined, undefined); + }); - const itemConfigs: InternalItemConfig[] = (flex as IWithItemConfig).itemConfigs; - assert.equal(itemConfigs.length, 2, `Unexpected number of items in list. Expected 2, got ${itemConfigs.length} ${JSON.stringify(itemConfigs)}`); - flex.insertItem(dropDown, 2); - assert.equal(itemConfigs.length, 3, `Unexpected number of items in list. Expected 3, got ${itemConfigs.length} ${JSON.stringify(itemConfigs)}`); - }); + test('initialized with no data has correct properties', async () => { + const declarativeTable = createDeclarativeTable(modelView, DeclarativeDataType.string, undefined); - test('Removing a component that does not exist does not fail', () => { - // Set up the mock proxy to save the component that gets initialized so that it can be verified - mockProxy.setup(x => x.$initializeModel(It.isAny(), It.isAny())); - mockProxy.setup(x => x.$addToContainer(It.isAny(), It.isAny(), It.isAny(), undefined)).returns(() => Promise.resolve()); - mockProxy.setup(x => x.$addToContainer(It.isAny(), It.isAny(), It.isAny(), It.isAny())).returns(() => Promise.resolve()); - - // Set up the form with a top level component and a group - let listBox = modelView.modelBuilder.listBox().component(); - let inputBox = modelView.modelBuilder.inputBox().component(); - let dropDown = modelView.modelBuilder.dropDown().component(); - - let flex = modelView.modelBuilder.flexContainer().withItems([listBox, inputBox]).component(); - modelView.initializeModel(flex); - - const itemConfigs: InternalItemConfig[] = (flex as IWithItemConfig).itemConfigs; - let result = flex.removeItem(dropDown); - assert.equal(result, false); - assert.equal(itemConfigs.length, 2, `Unexpected number of items in list. Expected 2, got ${itemConfigs.length} ${JSON.stringify(itemConfigs)}`); - assert.equal(itemConfigs[0].toIItemConfig().componentShape.type, ModelComponentTypes.ListBox, `Unexpected ModelComponentType. Expected ListBox but got ${ModelComponentTypes[itemConfigs[0].toIItemConfig().componentShape.type]}`); - }); + await modelView.initializeModel(declarativeTable); + mockProxy.verify(x => x.$initializeModel(It.isAny(), It.is(rootComponent => { + return rootComponent.id === declarativeTable.id && + rootComponent.properties && + rootComponent.properties.data && + rootComponent.properties.data.length === 0; + })), Times.once()); + }); - test('Inserting and removing component in a form should work correctly', () => { - mockProxy.setup(x => x.$initializeModel(It.isAny(), It.isAny())).callback((handle, componentShape) => { }); - mockProxy.setup(x => x.$addToContainer(It.isAny(), It.isAny(), It.isAny(), undefined)).returns(() => Promise.resolve()); - mockProxy.setup(x => x.$addToContainer(It.isAny(), It.isAny(), It.isAny(), It.isAny())).returns(() => Promise.resolve()); - mockProxy.setup(x => x.$removeFromContainer(It.isAny(), It.isAny(), It.isAny())).returns(() => Promise.resolve()); - - // Set up the form with a top level component and a group - let listBox = modelView.modelBuilder.listBox().component(); - let inputBox = modelView.modelBuilder.inputBox().component(); - let dropDown = modelView.modelBuilder.dropDown().component(); - let checkBox = modelView.modelBuilder.checkBox().component(); - - let groupItems: azdata.FormComponentGroup = { - title: 'Group', - components: [{ - title: 'Drop Down', - component: dropDown - }, { - title: 'Check Box', - component: checkBox - }] - }; - let listBoxFormItem: azdata.FormComponent = { - title: 'List Box', - component: listBox - }; - let inputBoxFormItem: azdata.FormComponent = { - title: 'Input Box', - component: inputBox - }; + test('initialized with string data has correct properties', async () => { + const testData = 'myData'; + const declarativeTable = createDeclarativeTable(modelView, DeclarativeDataType.component, [testData]); - let formBuilder = modelView.modelBuilder.formContainer(); - formBuilder.addFormItem(listBoxFormItem); - let form = formBuilder.component(); - modelView.initializeModel(formBuilder.component()); - - const itemConfigs: InternalItemConfig[] = (form as IWithItemConfig).itemConfigs; - assert.equal(itemConfigs.length, 1); - formBuilder.insertFormItem(inputBoxFormItem, 0); - assert.equal(itemConfigs.length, 2, `Unexpected number of items in list. Expected 2, got ${itemConfigs.length} ${JSON.stringify(itemConfigs)}`); - assert.equal(itemConfigs[0].toIItemConfig().componentShape.type, ModelComponentTypes.InputBox, `Unexpected ModelComponentType. Expected InputBox but got ${ModelComponentTypes[itemConfigs[0].toIItemConfig().componentShape.type]}`); - formBuilder.insertFormItem(groupItems, 0); - assert.equal(itemConfigs.length, 5, `Unexpected number of items in list. Expected 5, got ${itemConfigs.length} ${JSON.stringify(itemConfigs)}`); - formBuilder.removeFormItem(listBoxFormItem); - assert.equal(itemConfigs.length, 4, `Unexpected number of items in list. Expected 4, got ${itemConfigs.length} ${JSON.stringify(itemConfigs)}`); - formBuilder.removeFormItem(groupItems); - assert.equal(itemConfigs.length, 1, `Unexpected number of items in list. Expected 1, got ${itemConfigs.length} ${JSON.stringify(itemConfigs)}`); - formBuilder.addFormItem(listBoxFormItem); - assert.equal(itemConfigs.length, 2, `Unexpected number of items in list. Expected 2, got ${itemConfigs.length} ${JSON.stringify(itemConfigs)}`); - assert.equal(itemConfigs[1].toIItemConfig().componentShape.type, ModelComponentTypes.ListBox, `Unexpected ModelComponentType. Expected ListBox but got ${ModelComponentTypes[itemConfigs[1].toIItemConfig().componentShape.type]}`); + await modelView.initializeModel(declarativeTable); + + mockProxy.verify(x => x.$initializeModel(It.isAny(), It.is(rootComponent => { + return rootComponent.id === declarativeTable.id && + rootComponent.properties && + rootComponent.properties.data && + rootComponent.properties.data[0][0] === testData; + })), Times.once()); + }); + + test('initialized with component data converts to id', async () => { + const button = modelView.modelBuilder.button().component(); + const declarativeTable = createDeclarativeTable(modelView, DeclarativeDataType.component, [button]); + + await modelView.initializeModel(declarativeTable); + + // Components are expected to be converted into their ID before being sent across the proxy + mockProxy.verify(x => x.$initializeModel(It.isAny(), It.is(rootComponent => { + return rootComponent.id === declarativeTable.id && + rootComponent.properties && + rootComponent.properties.data && + rootComponent.properties.data[0][0] === button.id; + })), Times.once()); + }); + + test('when added to container with component data converts to id', async () => { + const button = modelView.modelBuilder.button().component(); + + const declarativeTable = createDeclarativeTable(modelView, DeclarativeDataType.component, [button]); + + const container = modelView.modelBuilder.divContainer().component(); + container.addItem(declarativeTable); + + await modelView.initializeModel(declarativeTable); + + // Components are expected to be converted into their ID before being sent across the proxy + mockProxy.verify(x => x.$initializeModel(It.isAny(), It.is(rootComponent => { + return rootComponent.id === declarativeTable.id && + rootComponent.properties && + rootComponent.properties.data && + rootComponent.properties.data[0][0] === button.id; + })), Times.once()); + mockProxy.verify(x => x.$addToContainer(It.isAny(), It.isAny(), It.is(item => { + return item.componentShape.id === declarativeTable.id && + item.componentShape.properties && + item.componentShape.properties.data && + item.componentShape.properties.data[0][0] === button.id; + }), undefined), Times.once()); + }); }); }); + +/** + * Helper function that creates a simple declarative table. Supports just a single column + * of data. + * @param modelView The ModelView used to create the component + * @param data The rows of data + */ +function createDeclarativeTable(modelView: azdata.ModelView, dataType: DeclarativeDataType, data?: any[]): azdata.DeclarativeTableComponent { + return modelView.modelBuilder.declarativeTable() + .withProperties( + { + columns: [ + { + displayName: 'TestColumn', + valueType: dataType, + isReadOnly: true, + width: 25 + } + ], + data: data ? [data] : [] + } + ).component(); +} From 6a4e4fc07bc5c1b0d648f0e7e91980235999e4e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbyn=C4=9Bk=20Sailer?= Date: Tue, 3 Dec 2019 19:07:16 +0100 Subject: [PATCH 03/19] LOC CHECKIN | Microsoft/azuredatastudio master | 20191203 (#8533) --- resources/xlf/de/azurecore.de.xlf | 16 + resources/xlf/de/big-data-cluster.de.xlf | 302 +- resources/xlf/de/sql.de.xlf | 5574 ++++++++++++++++- resources/xlf/es/azurecore.es.xlf | 16 + resources/xlf/es/big-data-cluster.es.xlf | 302 +- resources/xlf/es/sql.es.xlf | 5574 ++++++++++++++++- resources/xlf/fr/azurecore.fr.xlf | 16 + resources/xlf/fr/big-data-cluster.fr.xlf | 302 +- resources/xlf/fr/sql.fr.xlf | 5574 ++++++++++++++++- resources/xlf/it/azurecore.it.xlf | 16 + resources/xlf/it/big-data-cluster.it.xlf | 302 +- resources/xlf/it/schema-compare.it.xlf | 6 +- resources/xlf/it/sql.it.xlf | 5574 ++++++++++++++++- resources/xlf/ja/azurecore.ja.xlf | 16 + resources/xlf/ja/big-data-cluster.ja.xlf | 302 +- resources/xlf/ja/schema-compare.ja.xlf | 4 +- resources/xlf/ja/sql.ja.xlf | 5574 ++++++++++++++++- resources/xlf/ko/azurecore.ko.xlf | 16 + resources/xlf/ko/big-data-cluster.ko.xlf | 302 +- resources/xlf/ko/sql.ko.xlf | 5573 +++++++++++++++- resources/xlf/pt-br/azurecore.pt-BR.xlf | 16 + .../xlf/pt-br/big-data-cluster.pt-BR.xlf | 302 +- resources/xlf/pt-br/sql.pt-BR.xlf | 5573 +++++++++++++++- resources/xlf/ru/azurecore.ru.xlf | 16 + resources/xlf/ru/big-data-cluster.ru.xlf | 302 +- resources/xlf/ru/sql.ru.xlf | 5574 ++++++++++++++++- resources/xlf/zh-hans/azurecore.zh-Hans.xlf | 16 + .../xlf/zh-hans/big-data-cluster.zh-Hans.xlf | 302 +- resources/xlf/zh-hans/sql.zh-Hans.xlf | 5574 ++++++++++++++++- resources/xlf/zh-hant/azurecore.zh-Hant.xlf | 16 + .../xlf/zh-hant/big-data-cluster.zh-Hant.xlf | 302 +- .../xlf/zh-hant/schema-compare.zh-Hant.xlf | 62 +- resources/xlf/zh-hant/sql.zh-Hant.xlf | 5573 +++++++++++++++- 33 files changed, 57953 insertions(+), 1036 deletions(-) diff --git a/resources/xlf/de/azurecore.de.xlf b/resources/xlf/de/azurecore.de.xlf index 71f349cef59c..8b06ee3deab3 100644 --- a/resources/xlf/de/azurecore.de.xlf +++ b/resources/xlf/de/azurecore.de.xlf @@ -200,4 +200,20 @@ + + + + SQL Managed Instances + Verwaltete SQL-Instanzen + + + + + + + Azure Database for PostgreSQL Servers + Azure Database for PostgreSQL-Server + + + \ No newline at end of file diff --git a/resources/xlf/de/big-data-cluster.de.xlf b/resources/xlf/de/big-data-cluster.de.xlf index 4ebc3502167f..e3daf2143dec 100644 --- a/resources/xlf/de/big-data-cluster.de.xlf +++ b/resources/xlf/de/big-data-cluster.de.xlf @@ -38,6 +38,14 @@ Delete Mount Eingebundenes Volume löschen + + Big Data Cluster + Big Data-Cluster + + + Ignore SSL verification errors against SQL Server Big Data Cluster endpoints such as HDFS, Spark, and Controller if true + SSL-Verifizierungsfehler für SQL Server Big Data Cluster-Endpunkte wie HDFS, Spark und Controller ignorieren, falls der Wert "true" ist + @@ -58,26 +66,86 @@ Deleting Wird gelöscht - - Waiting For Deletion - Warten auf Löschung - Deleted Gelöscht + + Applying Upgrade + Upgrade wird durchgeführt + Upgrading Upgrade wird durchgeführt - - Waiting For Upgrade - Warten auf Upgrade + + Applying Managed Upgrade + Verwaltetes Upgrade wird angewendet. + + + Managed Upgrading + Verwaltetes Upgraden + + + Rollback + Rollback + + + Rollback In Progress + Rollback wird durchgeführt + + + Rollback Complete + Rollback abgeschlossen Error Fehler + + Creating Secrets + Geheimnisse werden erstellt + + + Waiting For Secrets + Auf Geheimnisse warten + + + Creating Groups + Gruppen werden erstellt + + + Waiting For Groups + Auf Gruppen warten + + + Creating Resources + Ressourcen werden erstellt + + + Waiting For Resources + Auf Ressourcen warten + + + Creating Kerberos Delegation Setup + Setup für Kerberos-Delegierung erfolgt + + + Waiting For Kerberos Delegation Setup + Auf Setup der Kerberos-Delegierung warten + + + Waiting For Deletion + Warten auf Löschung + + + Waiting For Upgrade + Warten auf Upgrade + + + Upgrade Paused + Upgrade pausiert + Running Wird ausgeführt @@ -162,6 +230,78 @@ Unhealthy Fehlerhaft + + Unexpected error retrieving BDC Endpoints: {0} + Unerwarteter Fehler beim Abrufen von BDC-Endpunkten: {0} + + + + + + + Basic + Standard + + + Windows Authentication + Windows-Authentifizierung + + + Login to controller failed + Die Anmeldung beim Controller ist fehlgeschlagen. + + + Login to controller failed: {0} + Fehler beim Anmelden beim Controller: {0} + + + Username is required + Benutzername erforderlich + + + Password is required + Kennwort erforderlich + + + url + URL + + + username + Benutzername + + + password + Kennwort + + + Cluster Management URL + Clusterverwaltungs-URL + + + Authentication type + Authentifizierungstyp + + + Username + Benutzername + + + Password + Kennwort + + + Cluster Connection + Clusterverbindung + + + OK + OK + + + Cancel + Abbrechen + @@ -200,6 +340,22 @@ + + + + Connect to Controller (preview) + Verbindung mit Controller herstellen (Vorschau) + + + + + + + View Details + Details anzeigen + + + @@ -207,8 +363,8 @@ Es wurden keine Informationen zum Controllerendpunkt gefunden. - Big Data Cluster Dashboard - - Big-Data-Cluster-Dashboard – + Big Data Cluster Dashboard (preview) - + Big Data-Cluster-Dashboard (Vorschau) – Yes @@ -263,8 +419,8 @@ Kennwort erforderlich - Add New Controller - Neuen Controller hinzufügen + Add New Controller (preview) + Neuen Controller hinzufügen (Vorschau) url @@ -283,8 +439,8 @@ Kennwort speichern - URL - URL + Cluster Management URL + Clusterverwaltungs-URL Authentication type @@ -319,7 +475,7 @@ Problem beheben - Big data cluster overview + Big Data Cluster overview Ãœberblick über Big-Data-Cluster @@ -362,18 +518,18 @@ Metrics and Logs Metriken und Protokolle - - Metrics - Metriken + + Node Metrics + Knotenmetriken + + + SQL Metrics + SQL-Metriken Logs Protokolle - - View Details - Details anzeigen - @@ -422,31 +578,35 @@ Endpoint Endpunkt - - View Details - Details anzeigen + + Unexpected error retrieving BDC Endpoints: {0} + Unerwarteter Fehler beim Abrufen von BDC-Endpunkten: {0} + + + The dashboard requires a connection. Please click retry to enter your credentials. + Das Dashboard erfordert eine Verbindung. Klicken Sie auf "Wiederholen", um Ihre Anmeldeinformationen einzugeben. + + + Unexpected error occurred: {0} + Unerwarteter Fehler: {0} Copy Kopieren + + Endpoint '{0}' copied to clipboard + Endpunkt "{0}" in Zwischenablage kopiert + - - Basic - Standard - - - Windows Authentication - Windows-Authentifizierung - Mount Configuration Konfiguration für das eingebundene Volume - + HDFS Path HDFS-Pfad @@ -454,22 +614,6 @@ Bad formatting of credentials at {0} Fehlerhafte Formatierung von Anmeldeinformationen in {0}. - - Login to controller failed - Die Anmeldung beim Controller ist fehlgeschlagen. - - - Login to controller failed: {0} - Fehler beim Anmelden beim Controller: {0} - - - Username is required - Benutzername erforderlich - - - Password is required - Kennwort erforderlich - Mounting HDFS folder on path {0} HDFS-Ordner wird im Pfad {0} eingebunden @@ -494,58 +638,30 @@ Unknown error occurred during the mount process Unbekannter Fehler während des Einbindungsprozesses - - url - URL - - - username - Benutzername - - - password - Kennwort - - - URL - URL - - - Authentication type - Authentifizierungstyp - - - Username - Benutzername - - - Password - Kennwort - - - Cluster Connection - Clusterverbindung - - - OK - OK - - - Cancel - Abbrechen - - Mount HDFS Folder - HDFS-Ordner einbinden + Mount HDFS Folder (preview) + HDFS-Ordner einbinden (Vorschau) + + + Path to a new (non-existing) directory which you want to associate with the mount + Pfad zu einem neuen (nicht vorhandenen) Verzeichnis, das Sie dem eingebundenen Volume zuordnen möchten - + Remote URI Remote-URI - + + The URI to the remote data source. Example for ADLS: abfs://fs@saccount.dfs.core.windows.net/ + Der URI für die Remotedatenquelle, Beispiel für ADLS: abfs://fs@saccount.dfs.core.windows.net/ + + Credentials Anmeldeinformationen + + Mount credentials for authentication to remote data source for reads + Anmeldeinformationen für die Authentifizierung bei der Remotedatenquelle für Lesevorgänge einbinden + Refresh Mount Eingebundenes Volume aktualisieren diff --git a/resources/xlf/de/sql.de.xlf b/resources/xlf/de/sql.de.xlf index d600b5a6475c..a21db4b1554e 100644 --- a/resources/xlf/de/sql.de.xlf +++ b/resources/xlf/de/sql.de.xlf @@ -1,14 +1,5574 @@  - + - - SQL Language Basics - SQL-Sprachgrundlagen + + Copying images is not supported + Das Kopieren von Bildern wird nicht unterstützt. - - Provides syntax highlighting and bracket matching in SQL files. - Bietet Syntaxhervorhebung und Klammernpaare in SQL-Dateien. + + + + + + Get Started + Erste Schritte + + + Show Getting Started + Zeige "Erste Schritte" + + + Getting &&Started + Erste &&Schritte + && denotes a mnemonic + + + + + + + QueryHistory + Abfrageverlauf + + + Whether Query History capture is enabled. If false queries executed will not be captured. + Gibt an, ob die Abfragenverlaufserfassung aktiviert ist. Wenn falsche Abfragen ausgeführt werden, werden diese nicht erfasst. + + + View + Sicht + + + Query History + Abfrageverlauf + + + &&Query History + &&Abfrageverlauf + && denotes a mnemonic + + + + + + + Connecting: {0} + Verbindung wird hergestellt: {0} + + + Running command: {0} + Befehl wird ausgeführt: {0} + + + Opening new query: {0} + Neue Abfrage wird geöffnet: {0} + + + Cannot connect as no server information was provided + Keine Verbindung möglich, da keine Serverinformationen bereitgestellt wurden + + + Could not open URL due to error {0} + Die URL konnte aufgrund des Fehlers {0} nicht geöffnet werden. + + + This will connect to server {0} + Dadurch wird eine Verbindung zum Server {0} hergestellt. + + + Are you sure you want to connect? + Möchten Sie die Verbindung wirklich herstellen? + + + &&Open + &&Öffnen + + + Connecting query file + Abfragedatei wird verbunden + + + + + + + Error + Fehler + + + Warning + Warnung + + + Info + Info + + + + + + + Saving results into different format disabled for this data provider. + Das Speichern von Ergebnissen in einem anderen Format ist für diesen Datenanbieter deaktiviert. + + + Cannot serialize data as no provider has been registered + Daten können nicht serialisiert werden, da kein Anbieter registriert wurde. + + + Serialization failed with an unknown error + Unbekannter Fehler bei der Serialisierung + + + + + + + Connection is required in order to interact with adminservice + Für eine Interaktion mit dem Adminservice ist eine Verbindung erforderlich + + + No Handler Registered + Kein Handler registriert + + + + + + + Select a file + Datei auswählen + + + + + + + Disconnect + Trennen + + + Refresh + Aktualisieren + + + + + + + Results Grid and Messages + Ergebnisraster und Meldungen + + + Controls the font family. + Steuert die Schriftfamilie. + + + Controls the font weight. + Steuert die Schriftbreite. + + + Controls the font size in pixels. + Legt die Schriftgröße in Pixeln fest. + + + Controls the letter spacing in pixels. + Legt den Abstand der Buchstaben in Pixeln fest. + + + Controls the row height in pixels + Legt die Zeilenhöhe in Pixeln fest + + + Controls the cell padding in pixels + Legt den Zellenabstand in Pixeln fest + + + Auto size the columns width on inital results. Could have performance problems with large number of columns or large cells + Spaltenbreite automatisch an die ursprünglichen Ergebnisse anpassen. Kann bei einer großen Anzahl von Spalten oder großen Zellen zu Leistungsproblemen führen + + + The maximum width in pixels for auto-sized columns + Die maximale Breite in Pixeln für Spalten mit automatischer Größe + + + + + + + {0} in progress tasks + {0} Aufgaben werden ausgeführt + + + View + Sicht + + + Tasks + Tasks + + + &&Tasks + &&Tasks + && denotes a mnemonic + + + + + + + Connections + Verbindungen + + + View + Sicht + + + Database Connections + Datenbank-Verbindungen + + + data source connections + Datenquellenverbindungen + + + data source groups + Datenquellengruppen + + + Startup Configuration + Startkonfiguration + + + True for the Servers view to be shown on launch of Azure Data Studio default; false if the last opened view should be shown + TRUE für die Serveransicht, die beim Start von Azure Data Studio standardmäßig angezeigt werden soll; FALSE, wenn die letzte geöffnete Ansicht angezeigt werden soll + + + + + + + The maximum number of recently used connections to store in the connection list. + Die maximale Anzahl der zuletzt verwendeten Verbindungen in der Verbindungsliste. + + + Default SQL Engine to use. This drives default language provider in .sql files and the default to use when creating a new connection. Valid option is currently MSSQL + Zu verwendende Standard-SQL-Engine. Dies bestimmt den Standardsprachanbieter in .sql Dateien und die Standardeinstellungen für neue Verbindungen. Gültige Option ist derzeit MSSQL + + + Attempt to parse the contents of the clipboard when the connection dialog is opened or a paste is performed. + Versuch, die Inhalte der Zwischenablage zu analysieren, wenn das Dialogfeld "Verbindung" geöffnet ist oder ein Element eingefügt wird. + + + + + + + Server Group color palette used in the Object Explorer viewlet. + Farbpalette für die Servergruppe, die im Objekt-Explorer-Viewlet verwendet wird. + + + Auto-expand Server Groups in the Object Explorer viewlet. + Servergruppen im Objekt-Explorer-Viewlet automatisch erweitern. + + + + + + + Preview Features + Previewfunktionen + + + Enable unreleased preview features + Nicht veröffentlichte Previewfunktionen aktivieren + + + Show connect dialog on startup + Verbindungsdialogfeld beim Start anzeigen + + + Obsolete API Notification + Veraltete API-Benachrichtigung + + + Enable/disable obsolete API usage notification + Veraltete API-Nutzungsbenachrichtigung aktivieren/deaktivieren + + + + + + + Problems + Probleme + + + + + + + Identifier of the account type + Kennung des Kontotyps + + + (Optional) Icon which is used to represent the accpunt in the UI. Either a file path or a themable configuration + (Optional) Symbol, das zur Darstellung des Kontos an der Benutzeroberfläche verwendet wird. Entweder einen Dateipfad oder eine bearbeitbare Konfiguration + + + Icon path when a light theme is used + Symbol-Pfad, wenn ein helles Theme verwendet wird + + + Icon path when a dark theme is used + Symbol-Pfad, wenn ein dunkles Farbschema verwendet wird + + + Contributes icons to account provider. + Stellt Symbole für den Kontoanbieter zur Verfügung. + + + + + + + Indicates data property of a data set for a chart. + Zeigt die Daten-Eigenschaft eines Datensatzes für ein Diagramm an. + + + + + + + Minimum value of the y axis + Minimalwert der Y-Achse + + + Maximum value of the y axis + Maximalwert der y-Achse + + + Label for the y axis + Bezeichnung für die y-Achse + + + Minimum value of the x axis + Minimalwert der X-Achse + + + Maximum value of the x axis + Maximalwert der x-Achse + + + Label for the x axis + Bezeichnung für die x-Achse + + + + + + + Displays the results in a simple table + Stellt die Ergebnisse in einer einfachen Tabelle dar + + + + + + + Displays an image, for example one returned by an R query using ggplot2 + Zeigt ein Bild an, zum Beispiel eins, das über eine R-Abfrage und mithilfe von ggplot2 erzeugt wurde + + + What format is expected - is this a JPEG, PNG or other format? + Welches Format wird erwartet - ist dieses ein JPEG, PNG oder ein anderes Format? + + + Is this encoded as hex, base64 or some other format? + Ist dies als Hex, base64 oder einem anderen Format kodiert? + + + + + + + For each column in a resultset, displays the value in row 0 as a count followed by the column name. Supports '1 Healthy', '3 Unhealthy' for example, where 'Healthy' is the column name and 1 is the value in row 1 cell 1 + Zeigt für jede Spalte in einem Resultset den Wert in Zeile 0 als Zählung gefolgt vom Spaltennamen an. Unterstützt beispielsweise "1 Healthy" oder "3 Unhealthy", wobei "Healthy" dem Spaltennamen und einem dem Wert in Zeile 1/Spalte 1 entspricht. + + + + + + + Manage + Verwalten + + + Dashboard + Dashboard + + + + + + + The webview that will be displayed in this tab. + Die Webview, die auf dieser Registerkarte angezeigt wird. + + + + + + + The controlhost that will be displayed in this tab. + Der Steuerelementhost, der auf dieser Registerkarte angezeigt wird. + + + + + + + The list of widgets that will be displayed in this tab. + Die Liste der Widgets, die auf dieser Registerkarte angezeigt wird. + + + The list of widgets is expected inside widgets-container for extension. + Die Liste der Widgets wird im Widgetscontainer für die Erweiterung erwartet. + + + + + + + The list of widgets or webviews that will be displayed in this tab. + Die Liste der Widgets oder Webviews, die auf dieser Registerkarte angezeigt wird. + + + widgets or webviews are expected inside widgets-container for extension. + Widgets oder Webansichten werden im Widgetcontainer für die Erweiterung erwartet. + + + + + + + Unique identifier for this container. + Eindeutiger Bezeichner für diesen Container. + + + The container that will be displayed in the tab. + Der Container, der auf der Registerkarte angezeigt wird. + + + Contributes a single or multiple dashboard containers for users to add to their dashboard. + Trägt einen oder mehrere Dashboardcontainer bei, in die Benutzer ihre Dashboards hinzufügen können. + + + No id in dashboard container specified for extension. + Im Dashboardcontainer für die Erweiterung wurde keine ID angegeben. + + + No container in dashboard container specified for extension. + Für die Erweiterung wurde kein Container im Dashboardcontainer angegeben. + + + Exactly 1 dashboard container must be defined per space. + Es muss genau 1 Dashboardcontainer pro Bereich definiert sein. + + + Unknown container type defines in dashboard container for extension. + Im Dashboardcontainer für die Erweiterung wurde ein unbekannter Containertyp definiert. + + + + + + + The model-backed view that will be displayed in this tab. + Die modellgestützte Ansicht, die auf dieser Registerkarte angezeigt wird. + + + + + + + Unique identifier for this nav section. Will be passed to the extension for any requests. + Eindeutiger Bezeichner für diesen Navigationsbereich. Wird für alle Anfragen an die Erweiterung übergeben. + + + (Optional) Icon which is used to represent this nav section in the UI. Either a file path or a themeable configuration + (Optional) Symbol, das zur Darstellung dieses Navigationsbereichs der Benutzeroberfläche verwendet wird. Erfordert einen Dateipfad oder eine Konfiguration, der ein Design zugeordnet werden kann + + + Icon path when a light theme is used + Symbol-Pfad, wenn ein helles Theme verwendet wird + + + Icon path when a dark theme is used + Symbol-Pfad, wenn ein dunkles Farbschema verwendet wird + + + Title of the nav section to show the user. + Der Titel des Navigationsbereichs, der dem Benutzer angezeigt werden soll. + + + The container that will be displayed in this nav section. + Der Container, der in diesem Navigationsbereich angezeigt wird. + + + The list of dashboard containers that will be displayed in this navigation section. + Die Liste der Dashboard-Container, die in diesem Navigationsabschnitt angezeigt wird. + + + property `icon` can be omitted or must be either a string or a literal like `{dark, light}` + Die Eigenschaft "icon" kann ausgelassen werden oder muss eine Zeichenfolge oder ein Literal wie "{dark, light}" sein. + + + No title in nav section specified for extension. + Für die Erweiterung wurde kein Titel im Navigationsbereich angegeben. + + + No container in nav section specified for extension. + Für die Anwendung wurde kein Container im Navigationsbereich angegeben. + + + Exactly 1 dashboard container must be defined per space. + Es muss genau 1 Dashboardcontainer pro Bereich definiert sein. + + + NAV_SECTION within NAV_SECTION is an invalid container for extension. + NAV_SECTION in NAV_SECTION ist ein ungültiger Container für die Erweiterung. + + + + + + + Backup + Sicherung + + + + + + + Unique identifier for this tab. Will be passed to the extension for any requests. + Eindeutiger Bezeichner für diese Registerkarte. Wird für alle Anfragen an die Erweiterung übergeben werden. + + + Title of the tab to show the user. + Titel der Registerkarte, die dem Benutzer angezeigt wird. + + + Description of this tab that will be shown to the user. + Beschreibung dieser Registerkarte, die dem Benutzer angezeigt werden. + + + Condition which must be true to show this item + Eine Bedingung, die TRUE sein muss, damit dieses Element angezeigt wird. + + + Defines the connection types this tab is compatible with. Defaults to 'MSSQL' if not set + Definiert die Verbindungstypen, mit denen diese Registerkarte kompatibel ist. Der Standardwert ist "MSSQL", wenn kein anderer Wert festgelegt wird. + + + The container that will be displayed in this tab. + Der Container, der auf dieser Registerkarte angezeigt wird. + + + Whether or not this tab should always be shown or only when the user adds it. + Legt fest, ob diese Registerkarte immer angezeigt werden soll oder nur, wenn der Benutzer sie hinzufügt. + + + Whether or not this tab should be used as the Home tab for a connection type. + Legt fest, ob diese Registerkarte als Startseite für einen Verbindungstyp verwendet werden soll. + + + Contributes a single or multiple tabs for users to add to their dashboard. + Trägt eine oder mehrere Registerkarten bei, die Benutzer ihren Dashboards hinzufügen können. + + + No title specified for extension. + Für die Erweiterung wurde kein Titel angegeben. + + + No description specified to show. + Keine Beschreibung vorhanden. + + + No container specified for extension. + Für die Erweiterung wurde kein Container angegeben. + + + Exactly 1 dashboard container must be defined per space + Pro Umgebung muss genau 1 Dashboard Container definiert werden + + + + + + + Restore + Wiederherstellen + + + Restore + Wiederherstellen + + + + + + + Cannot expand as the required connection provider '{0}' was not found + Erweiterung nicht möglich, weil der erforderliche Verbindungsanbieter "{0}" nicht gefunden wurde. + + + User canceled + Vom Benutzer abgebrochen + + + Firewall dialog canceled + Firewalldialogfeld abgebrochen + + + + + + + 1 or more tasks are in progress. Are you sure you want to quit? + Eine oder mehrere Aufgaben werden ausgeführt. Wollen Sie wirklich abbrechen? + + + Yes + Ja + + + No + Nein + + + + + + + Connection is required in order to interact with JobManagementService + Für die Interaktion mit JobManagementService ist eine Verbindung erforderlich. + + + No Handler Registered + Kein Handler registriert + + + + + + + An error occured while loading the file browser. + Fehler beim Laden des Datei-Browser. + + + File browser error + Datei-Browser-Fehler + + + + + + + Notebook Editor + Notebook-Editor + + + New Notebook + Neues Notebook + + + New Notebook + Neues Notebook + + + SQL kernel: stop Notebook execution when error occurs in a cell. + SQL-Kernel: Notebook-Ausführung beenden, wenn ein Fehler in einer Zelle auftritt + + + + + + + Script as Create + Skript für Create erstellen + + + Script as Drop + Skripterstellung als Drop + + + Select Top 1000 + Select Top 1000 + + + Script as Execute + Skript als Ausführung + + + Script as Alter + Skript als Änderung + + + Edit Data + Daten bearbeiten + + + Select Top 1000 + Select Top 1000 + + + Script as Create + Skript für Create erstellen + + + Script as Execute + Skript als Ausführung + + + Script as Alter + Skript als Änderung + + + Script as Drop + Skripterstellung als Drop + + + Refresh + Aktualisieren + + + + + + + Connection error + Verbindungsfehler + + + Connection failed due to Kerberos error. + Verbindung aufgrund eines Kerberos-Fehlers fehlgeschlagen. + + + Help configuring Kerberos is available at {0} + Unter {0} finden Sie Hilfestellungen zur Konfiguration von Kerberos. + + + If you have previously connected you may need to re-run kinit. + Wenn Sie zuvor eine Verbindung hergestellt haben, müssen Sie "kinit" möglicherweise erneut ausführen. + + + + + + + Refresh account was canceled by the user + Die Kontoaktualisierung wurde durch den Benutzer abgebrochen. + + + + + + + Specifies view templates + Legt Ansichtsvorlagen fest + + + Specifies session templates + Spezifiziert Sitzungsvorlagen + + + Profiler Filters + Profilerfilter + + + + + + + Toggle Query History + Abfrageverlauf umschalten + + + Delete + Löschen + + + Clear All History + Gesamten Verlauf löschen + + + Open Query + Abfrage öffnen + + + Run Query + Abfrage ausführen + + + Toggle Query History capture + Abfrageverlaufserfassung umschalten + + + Pause Query History Capture + Abfragenverlaufserfassung pausieren + + + Start Query History Capture + Abfrageverlaufserfassung starten + + + + + + + Preview features are required in order for extensions to be fully supported and for some actions to be available. Would you like to enable preview features? + Previewfunktionen sind erforderlich, damit Erweiterungen vollständig unterstützt werden und bestimmte Aktionen verfügbar sind. Möchten sie Previewfunktionen aktivieren? + + + Yes + Ja + + + No + Nein + + + No, don't show again + Nein, nicht mehr anzeigen + + + + + + + Commit row failed: + Committen der Zeile fehlgeschlagen: + + + Started executing query "{0}" + Ausführen der Abfrage "{0}" gestartet + + + Update cell failed: + Aktualisieren der Zelle gescheitert: + + + + + + + Failed to create Object Explorer session + Fehler beim Erstellen der Objekt-Explorer-Sitzung + + + Multiple errors: + Mehrere Fehler: + + + + + + + No URI was passed when creating a notebook manager + Bei der Erstellung eines Notebook-Managers wurde kein URI übergeben. + + + Notebook provider does not exist + Der Notebook-Anbieter existiert nicht. + + + + + + + Query Results + Abfrageergebnisse + + + Query Editor + Abfrage-Editor + + + New Query + Neue Abfrage + + + [Optional] When true, column headers are included when saving results as CSV + [Optional] Bei "Ja" werden die Spaltenüberschriften beim Speichern der Ergebnisse als CSV mit ausgegeben + + + [Optional] The custom delimiter to use between values when saving as CSV + [Optional] Das zu verwendende benutzerdefinierte Trennzeichen zwischen Werten beim Speichern als CSV + + + [Optional] Character(s) used for seperating rows when saving results as CSV + [Optional] Zeichen zur Trennung von Zeilen beim Speichern der Ergebnisse als CSV + + + [Optional] Character used for enclosing text fields when saving results as CSV + [Optional] Zeichen, das für das Umschließen von Textfeldern verwendet wird, wenn die Ergebnisse im CSV-Format gespeichert werden. + + + [Optional] File encoding used when saving results as CSV + [Optional] Verwendete Codierung, wenn die Ergebnisse als CSV-Datei gespeichert werden + + + Enable results streaming; contains few minor visual issues + Streamen der Ergebnisse aktivieren; weist einige geringfügige Darstellungsprobleme auf + + + [Optional] When true, XML output will be formatted when saving results as XML + [Optional] Wenn "true", wird die XML-Ausgabe formatiert, wenn die Ergebnisse im XML-Format gespeichert werden. + + + [Optional] File encoding used when saving results as XML + [Optional] Verwendete Dateicodierung, wenn Ergebnisse im XML-Format gespeichert werden + + + [Optional] Configuration options for copying results from the Results View + [Optional] Konfigurations-Optionen für das Kopieren von Ergebnissen aus der Ergebnisansicht + + + [Optional] Configuration options for copying multi-line results from the Results View + [Optional] Konfigurationsoptionen zum Kopieren mehrzeiliger Ergebnisse aus der Ergebnisansicht. + + + [Optional] Should execution time be shown for individual batches + [Optional] Soll die Ausführungszeit für einzelne Aufrufe angezeigt werden + + + [Optional] the default chart type to use when opening Chart Viewer from a Query Results + [Optional] Standard-Diagrammtyp, wenn Chart Viewer für ein Abfrageergebnis geöffnet wird + + + Tab coloring will be disabled + Einfärbung der Registerkarten wird deaktiviert + + + The top border of each editor tab will be colored to match the relevant server group + Der obere Rand jedes Registerkarten Editor Reiters wird entsprechend der jeweiligen Server-Gruppe eingefärbt. + + + Each editor tab's background color will match the relevant server group + Die Hintergrundfarbe der Editor-Registerkarten entspricht der jeweiligen Servergruppe. + + + Controls how to color tabs based on the server group of their active connection + Steuert die Farbe von Registerkarten anhand der Servergruppe der aktiven Verbindung + + + Controls whether to show the connection info for a tab in the title. + Legt fest, ob die Verbindungsinformationen für eine Registerkarte im Titel angezeigt werden. + + + Prompt to save generated SQL files + Aufforderung zum Speichern generierter SQL-Dateien + + + Should IntelliSense be enabled + Soll IntelliSense aktiviert werden + + + Should IntelliSense error checking be enabled + Soll IntelliSense Fehlerüberprüfung aktiviert werden + + + Should IntelliSense suggestions be enabled + Sollen IntelliSense Vorschläge aktiviert werden + + + Should IntelliSense quick info be enabled + Soll IntelliSense Schnell-Info aktiviert werden + + + Should IntelliSense suggestions be lowercase + Sollen IntelliSense Vorschläge in Kleinbuchstaben sein + + + Maximum number of rows to return before the server stops processing your query. + Maximale Anzahl von Zeilen, die zurückgegeben werden sollen, bevor der Server die Verarbeitung Ihrer Abfrage beendet. + + + Maximum size of text and ntext data returned from a SELECT statement + Maximale Größe von text- und ntext-Daten, die von einer SELECT-Anweisung zurückgegeben werden + + + An execution time-out of 0 indicates an unlimited wait (no time-out) + Ein Timeoutwert von 0 für die Ausführung kennzeichnet einen unbegrenzten Wartevorgang (kein Timeout). + + + Enable SET NOCOUNT option + Set NOCOUNT-Option aktivieren + + + Enable SET NOEXEC option + SET NOEXEC-Option aktivieren + + + Enable SET PARSEONLY option + SET PARSEONLY-Option aktivieren + + + Enable SET ARITHABORT option + Set ARITHABORT-Option aktivieren + + + Enable SET STATISTICS TIME option + SET STATISTICS TIME-Option aktivieren + + + Enable SET STATISTICS IO option + Set STATISTICS IO-Option aktivieren + + + Enable SET XACT_ABORT ON option + SET XACT_ABORT ON-Option aktivieren + + + Enable SET TRANSACTION ISOLATION LEVEL option + SET TRANSACTION ISOLATION LEVEL-Option aktivieren + + + Enable SET DEADLOCK_PRIORITY option + SET DEADLOCK_PRIORITY-Option aktivieren + + + Enable SET LOCK TIMEOUT option (in milliseconds) + SET LOCK TIMEOUT-Option aktivieren (in Millisekunden) + + + Enable SET QUERY_GOVERNOR_COST_LIMIT + SET QUERY_GOVERNOR_COST_LIMIT aktivieren + + + Enable SET ANSI_DEFAULTS + "SET ANSI_DEFAULTS" aktivieren + + + Enable SET QUOTED_IDENTIFIER + "SET QUOTED_IDENTIFIER" aktivieren + + + Enable SET ANSI_NULL_DFLT_ON + SET ANSI_NULL_DFLT_ON aktivieren + + + Enable SET IMPLICIT_TRANSACTIONS + "SET IMPLICIT_TRANSACTIONS" aktivieren + + + Enable SET CURSOR_CLOSE_ON_COMMIT + SET CURSOR_CLOSE_ON_COMMIT aktivieren + + + Enable SET ANSI_PADDING + SET ANSI_PADDING aktivieren + + + Enable SET ANSI_WARNINGS + SET ANSI_WARNINGS aktivieren + + + Enable SET ANSI_NULLS + SET ANSI_NULLS aktivieren + + + Set keybinding workbench.action.query.shortcut{0} to run the shortcut text as a procedure call. Any selected text in the query editor will be passed as a parameter + Tastenkürzel workbench.action.query.shortcut{0} festlegen, um den Verknüpfungstext als Prozeduraufruf auszuführen. Beliebiger ausgewählter Text im Abfrage-Editor wird als Parameter übergeben. + + + + + + + Common id for the provider + Allgemeine ID für den Anbieter + + + Display Name for the provider + Anzeigename für den Anbieter + + + Icon path for the server type + Symbolpfad für Servertyp + + + Options for connection + Verbindungsoptionen + + + + + + + OK + OK + + + Close + Schließen + + + Copy details + Details kopieren + + + + + + + Add server group + Servergruppe hinzufügen + + + Edit server group + Server-Gruppe bearbeiten + + + + + + + Error adding account + Fehler beim Hinzufügen des Kontos + + + Firewall rule error + Firewall-Regel Fehler + + + + + + + Some of the loaded extensions are using obsolete APIs, please find the detailed information in the Console tab of Developer Tools window + Einige der geladenen Erweiterungen verwenden veraltete APIs. Ausführliche Informationen finden Sie auf der Registerkarte "Konsole" des Fensters "Entwicklertools". + + + Don't Show Again + Nicht mehr anzeigen + + + + + + + Toggle Tasks + Aufgaben umschalten + + + + + + + Show Connections + Verbindungen anzeigen + + + Servers + Server + + + + + + + Identifier of the view. Use this to register a data provider through `vscode.window.registerTreeDataProviderForView` API. Also to trigger activating your extension by registering `onView:${id}` event to `activationEvents`. + Bezeichner der Ansicht. Damit können Sie einen Datenanbieter über die API "vscode.window.registerTreeDataProviderForView" registrieren. Er dient auch zum Aktivieren Ihrer Erweiterung, indem Sie das Ereignis "onView:${id}" für "activationEvents" registrieren. + + + The human-readable name of the view. Will be shown + Der visuell lesbare Name der Ansicht. Wird angezeigt + + + Condition which must be true to show this view + Bedingung, die zum Anzeigen dieser Ansicht erfüllt sein muss + + + Contributes views to the editor + Trägt Ansichten zum Editor bei. + + + Contributes views to Data Explorer container in the Activity bar + Trägt Ansichten zum Data Explorer-Container in der Aktivitätsleiste bei + + + Contributes views to contributed views container + Trägt Ansichten zum Container mit beigetragenen Ansichten bei + + + View container '{0}' does not exist and all views registered to it will be added to 'Data Explorer'. + Der Anzeigecontainer "{0}" ist nicht vorhanden, und alle registrierten Ansichten werden "Data Explorer" hinzugefügt. + + + Cannot register multiple views with same id `{0}` in the view container `{1}` + Es können nicht mehrere Ansichten mit derselben ID {0} im Ansichtscontainer "{1}" registriert werden. + + + A view with id `{0}` is already registered in the view container `{1}` + Eine Ansicht mit der ID {0} ist im Ansichtscontainer "{1}" bereits registriert. + + + views must be an array + Ansichten müssen ein Array sein. + + + property `{0}` is mandatory and must be of type `string` + Die Eigenschaft "{0}" ist erforderlich. Sie muss vom Typ "string" sein. + + + property `{0}` can be omitted or must be of type `string` + Die Eigenschaft "{0}" kann ausgelassen werden oder muss vom Typ "string" sein. + + + + + + + Connection Status + Verbindungsstatus + + + + + + + Manage + Verwalten + + + Show Details + Details anzeigen + + + Learn How To Configure The Dashboard + Einführung in die Konfiguration des Dashboards + + + + + + + Widget used in the dashboards + In den Dashboards verwendetes Widget + + + + + + + Displays results of a query as a chart on the dashboard + Zeigt die Ergebnisse einer Abfrage als Diagramm auf dem Dashboard + + + Maps 'column name' -> color. for example add 'column1': red to ensure this column uses a red color + Ordnet den Spaltennamen einer Farbe zu (z. B. 'column1': red, damit die Farbe Rot in dieser Spalte verwendet wird) + + + Indicates preferred position and visibility of the chart legend. These are the column names from your query, and map to the label of each chart entry + Zeigt bevorzugte Position und Sichtbarkeit der Legende des Diagramms. Dies sind die Spaltennamen aus der Abfrage und sind der Bezeichnung der einzelnen Diagrammeinträge zugeordnet. + + + If dataDirection is horizontal, setting this to true uses the first columns value for the legend. + Wenn dataDirection horizontal ausgewählt ist, werden die Werte aus der ersten Spalte als Legende verwendet, sobald diese Einstellung auf True gesetzt wird. + + + If dataDirection is vertical, setting this to true will use the columns names for the legend. + Wenn DataDirection vertikal ist und diese Einstellung auf True gesetzt ist, werden für die Legende die Spaltennamen verwendt. + + + Defines whether the data is read from a column (vertical) or a row (horizontal). For time series this is ignored as direction must be vertical. + Legt fest, ob die Daten aus einer Spalte (vertikal) oder eine Zeile (horizontal) gelesen werden. Für Zeitreihen wird dies ignoriert, da die Richtung vertikal sein muss. + + + If showTopNData is set, showing only top N data in the chart. + Wenn showTopNData festgelegt ist, werden nur die obersten n Daten im Diagramm angezeigt. + + + + + + + Condition which must be true to show this item + Eine Bedingung, die TRUE sein muss, damit dieses Element angezeigt wird. + + + The title of the container + Titel des Containers + + + The row of the component in the grid + Die Zeile der Komponente im Raster + + + The rowspan of the component in the grid. Default value is 1. Use '*' to set to number of rows in the grid. + Die RowSpan-Eigenschaft der Komponente im Raster. Der Standardwert ist 1. Verwenden Sie "*", um die Anzahl der Zeilen im Raster festzulegen. + + + The column of the component in the grid + Die Spalte der Komponente im Raster + + + The colspan of the component in the grid. Default value is 1. Use '*' to set to number of columns in the grid. + Der ColSpan-Wert der Komponente im Raster. Der Standardwert ist 1. Verwenden Sie "*", um die Anzahl der Spalten im Raster festzulegen. + + + Unique identifier for this tab. Will be passed to the extension for any requests. + Eindeutiger Bezeichner für diese Registerkarte. Wird für alle Anfragen an die Erweiterung übergeben werden. + + + Extension tab is unknown or not installed. + Die Registerkarte "Erweiterung" ist unbekannt oder nicht installiert. + + + + + + + Enable or disable the properties widget + Aktivieren Sie oder deaktivieren Sie das Eigenschaften-widget + + + Property values to show + Zu zeigende Eigenschaftswerte + + + Display name of the property + Anzeigename der Eigenschaft + + + Value in the Database Info Object + Wert im Datenbank-Info-Objekt + + + Specify specific values to ignore + Spezifische zu ignorierende Werte angeben + + + Recovery Model + Wiederherstellungsmodell + + + Last Database Backup + Letzte Datenbanksicherung + + + Last Log Backup + Letzte Protokollsicherung + + + Compatibility Level + Kompatibilitätsgrad + + + Owner + Besitzer + + + Customizes the database dashboard page + Passt das Datenbank-Dashboard an + + + Customizes the database dashboard tabs + Passt die Datenbank-Dashboard-Registerkarten an + + + + + + + Enable or disable the properties widget + Aktivieren Sie oder deaktivieren Sie das Eigenschaften-widget + + + Property values to show + Zu zeigende Eigenschaftswerte + + + Display name of the property + Anzeigename der Eigenschaft + + + Value in the Server Info Object + Wert im Server-Info-Objekt + + + Version + Version + + + Edition + Edition + + + Computer Name + Computername + + + OS Version + Betriebssystemversion + + + Customizes the server dashboard page + Passt die Dashboardseite des Servers an + + + Customizes the Server dashboard tabs + Passt die Server Dashboard Registerkarten an + + + + + + + Manage + Verwalten + + + + + + + Widget used in the dashboards + In den Dashboards verwendetes Widget + + + Widget used in the dashboards + In den Dashboards verwendetes Widget + + + Widget used in the dashboards + In den Dashboards verwendetes Widget + + + + + + + Adds a widget that can query a server or database and display the results in multiple ways - as a chart, summarized count, and more + Fügt eine Widget hinzu, das einen Server oder eine Datenbank abfragen und die Ergebnisse anzeigen kann - als ein Diagramm, Zähler und mehr + + + Unique Identifier used for caching the results of the insight. + Eindeutiger Bezeichner, der zum Zwischenspeichern der Ergebnisse der Einsicht verwendet wird + + + SQL query to run. This should return exactly 1 resultset. + Auszuführende SQL-Abfrage. Diese sollte genau 1 Resultset zurückgeben. + + + [Optional] path to a file that contains a query. Use if 'query' is not set + Optionaler Pfad zu einer Datei, die eine Abfrage enthält. Verwenden Sie diesen, wenn "query" nicht festgelegt ist. + + + [Optional] Auto refresh interval in minutes, if not set, there will be no auto refresh + [Optional] Intervall für die automatische Aktualisierung in Minuten. Wenn kein Wert festgelegt wird, wird keine automatische Aktualisierung durchgeführt. + + + Which actions to use + Zu verwendende Aktionen + + + Target database for the action; can use the format '${ columnName }' to use a data driven column name. + Zieldatenbank für die Aktion – kann das Format ${ columnName } verwenden, um einen datengesteuerten Spaltennamen zu nutzen. + + + Target server for the action; can use the format '${ columnName }' to use a data driven column name. + Zielserver für die Aktion – kann das Format ${ columnName } verwenden, um einen datengesteuerten Spaltennamen zu nutzen. + + + Target user for the action; can use the format '${ columnName }' to use a data driven column name. + Zielbenutzer für die Aktion; kann das Format ${ columnName } verwenden, um einen datengesteuerten Spaltennamen zu nutzen. + + + Identifier of the insight + Kennung des insight + + + Contributes insights to the dashboard palette. + Liefert Einblicke in die Dashboard-Palette. + + + + + + + Loading + Laden + + + Loading completed + Ladevorgang abgeschlossen + + + + + + + Defines a property to show on the dashboard + Definiert eine Eigenschaft, die auf dem Dashboard angezeigt werden soll + + + What value to use as a label for the property + Welcher Wert soll als Kennzeichnung für die Eigenschaft verwendet werden + + + What value in the object to access for the value + Wert des Objekts, auf den für den Wert zugegriffen werden soll + + + Specify values to be ignored + Werte angeben, die ignoriert werden sollen + + + Default value to show if ignored or no value + Standardwert, der angezeigt werden soll, wenn der Wert ignoriert wird oder kein Wert angegeben ist + + + A flavor for defining dashboard properties + Ein Eindruck für das Festlegen von Dashboard-Eigenschaften + + + Id of the flavor + Flavor-ID + + + Condition to use this flavor + Voraussetzung für die Verwendung dieser Variante + + + Field to compare to + Vergleichsfeld + + + Which operator to use for comparison + Vergleichsoperator auswählen + + + Value to compare the field to + Vergleichswert + + + Properties to show for database page + Eigenschaften für Datenbank-Seite zeigen + + + Properties to show for server page + Anzuzeigende Eigenschaften für die Seite "Server" + + + Defines that this provider supports the dashboard + Legt fest, dass dieser Anbieter das Dashboard unterstützt + + + Provider id (ex. MSSQL) + Anbieter-ID (z.B. MSSQL) + + + Property values to show on dashboard + Eigenschaftswerte, die auf dem Dashboard angezeigt werden sollen + + + + + + + Backup + Sicherung + + + You must enable preview features in order to use backup + Sie müssen die Previewfunktionen aktivieren, um die Sicherung verwenden zu können. + + + Backup command is not supported for Azure SQL databases. + Der Sicherungsbefehl wird für Azure SQL-Datenbank nicht unterstützt. + + + Backup command is not supported in Server Context. Please select a Database and try again. + Der Sicherungsbefehl wird im Serverkontext nicht unterstützt. Wählen Sie eine Datenbank aus, und versuchen Sie ac noch mal. + + + + + + + Restore + Wiederherstellen + + + You must enable preview features in order to use restore + Sie müssen Previewfunktionen aktivieren, um die Wiederherstellung zu verwenden. + + + Restore command is not supported for Azure SQL databases. + Der Wiederherstellungsbefehl wird für Azure SQL-Datenbank nicht unterstützt. + + + + + + + disconnected + Getrennt + + + + + + + Server Groups + Servergruppen + + + OK + OK + + + Cancel + Abbrechen + + + Server group name + Servergruppen-Name + + + Group name is required. + Gruppenname ist erforderlich. + + + Group description + Gruppenbeschreibung + + + Group color + Gruppenfarbe + + + + + + + Extension + Erweiterung + + + + + + + OK + OK + + + Cancel + Abbrechen + + + + + + + Open dashboard extensions + Dashboarderweiterungen öffnen + + + OK + OK + + + Cancel + Abbrechen + + + No dashboard extensions are installed at this time. Go to Extension Manager to explore recommended extensions. + Aktuell sind keine Dashboard-Erweiterungen installiert. Erkunden Sie empfohlene Erweiterungen im Erweiterungs-Manager. + + + + + + + Selected path + Ausgewählter Pfad + + + Files of type + Dateien des Typs + + + OK + OK + + + Discard + Verwerfen + + + + + + + No Connection Profile was passed to insights flyout + Es wurde kein Verbindungsprofil an das Insights-Flyout übergeben + + + Insights error + Insights-Fehler + + + There was an error reading the query file: + Es gab einen Fehler beim Lesen der Datei: + + + There was an error parsing the insight config; could not find query array/string or queryfile + Fehler beim Analysieren der Konfiguration für Einblicke; Abfragearray/Abfragezeichenfolge oder Abfragedatei konnte nicht gefunden werden + + + + + + + Clear List + Liste löschen + + + Recent connections list cleared + Liste der letzten Verbindungen gelöscht + + + Yes + Ja + + + No + Nein + + + Are you sure you want to delete all the connections from the list? + Sind Sie sicher, dass Sie alle Verbindungen aus der Liste löschen möchten? + + + Yes + Ja + + + No + Nein + + + Delete + Löschen + + + Get Current Connection String + Aktuelle Verbindungszeichenfolge abrufen + + + Connection string not available + Verbindungszeichenfolge nicht verfügbar + + + No active connection available + Keine aktive Verbindung verfügbar + + + + + + + Refresh + Aktualisieren + + + Disconnect + Trennen + + + New Connection + Neue Verbindung + + + New Server Group + Neue Servergruppe + + + Edit Server Group + Server-Gruppe bearbeiten + + + Show Active Connections + Aktive Verbindungen anzeigen + + + Show All Connections + Alle Verbindungen anzeigen + + + Recent Connections + Letzte Verbindungen + + + Delete Connection + Verbindung löschen + + + Delete Group + Gruppe löschen + + + + + + + Edit Data Session Failed To Connect + Bearbeiten der Daten der nicht wiederherstellbaren Session + + + + + + + Profiler + Profiler + + + Not connected + Nicht verbunden + + + XEvent Profiler Session stopped unexpectedly on the server {0}. + Die XEvent-Profiler-Sitzung wurde auf dem Server {0} unerwartet beendet. + + + Error while starting new session + Fehler beim Starten der neuen Sitzung + + + The XEvent Profiler session for {0} has lost events. + Die XEvent-Profiler-Sitzung für {0} hat Ereignisse verloren. + + + Would you like to stop the running XEvent session? + Möchten Sie die laufende XEvent-Sitzung beenden? + + + Yes + Ja + + + No + Nein + + + Cancel + Abbrechen + + + + + + + Invalid value + Ungültiger Wert. + + + {0}. {1} + {0}. {1} + + + + + + + blank + leer + + + + + + + Error displaying Plotly graph: {0} + Fehler beim Anzeigen des Plotly-Graphen: {0} + + + + + + + No {0} renderer could be found for output. It has the following MIME types: {1} + Für die Ausgabe wurde kein {0}-Renderer gefunden. Folgende MIME-Typen sind enthalten: {1} + + + (safe) + (sicher) + + + + + + + Item + Objekt + + + Value + Wert + + + Property + Eigenschaft + + + Value + Wert + + + Insights + Einblicke + + + Items + Elemente + + + Item Details + Details zum Element + + + + + + + Error adding account + Fehler beim Hinzufügen des Kontos + + + + + + + Cannot start auto OAuth. An auto OAuth is already in progress. + Auto OAuth kann nicht gestartet werden. Eine automatische OAuth läuft bereits. + + + + + + + Sort by event + Sortieren nach Ereignis + + + Sort by column + Nach Spalte sortieren + + + Profiler + Profiler + + + OK + OK + + + Cancel + Abbrechen + + + + + + + Clear all + Alle löschen + + + Apply + Anwenden + + + OK + OK + + + Cancel + Abbrechen + + + Filters + Filter + + + Remove this clause + Diese Klausel entfernen + + + Save Filter + Filter speichern + + + Load Filter + Filter laden + + + Add a clause + Klausel hinzufügen + + + Field + Feld + + + Operator + Operator + + + Value + Wert + + + Is Null + Ist NULL + + + Is Not Null + Ist nicht NULL + + + Contains + enthält + + + Not Contains + Enthält nicht + + + Starts With + Beginnt mit + + + Not Starts With + Beginnt nicht mit + + + + + + + Double-click to edit + Doppelklicken zum Bearbeiten + + + + + + + Select Top 1000 + Select Top 1000 + + + Script as Execute + Skript als Ausführung + + + Script as Alter + Skript als Änderung + + + Edit Data + Daten bearbeiten + + + Script as Create + Skript für Create erstellen + + + Script as Drop + Skripterstellung als Drop + + + + + + + No queries to display. + Keine Abfragen zum Anzeigen verfügbar + + + Query History + Abfrageverlauf + QueryHistory + + + + + + + Failed to get Azure account token for connection + Fehler beim Abrufen des Azure-Kontotokens für die Verbindung + + + Connection Not Accepted + Verbindung nicht akzeptiert + + + Yes + Ja + + + No + Nein + + + Are you sure you want to cancel this connection? + Sind Sie sicher, dass Sie diese Verbindung abbrechen möchten? + + + + + + + Started executing query at + Ausführung der Abfrage gestartet ab + + + Line {0} + Zeile {0} + + + Canceling the query failed: {0} + Abbruch der Abfrage fehlgeschlagen: {0} + + + Started saving results to + Speichern von Ergebnissen in gestartet in + + + Failed to save results. + Fehler beim Speichern der Ergebnisse. + + + Successfully saved results to + Ergebnisse erfolgreich gespeichert nach + + + Executing query... + Abfrage wird ausgeführt... + + + Maximize + Maximieren + + + Restore + Wiederherstellen + + + Save as CSV + Als CSV speichern + + + Save as JSON + Als JSON speichern + + + Save as Excel + Speichern als Excel + + + Save as XML + Im XML-Format speichern + + + View as Chart + Als Diagramm anzeigen + + + Visualize + Visualisieren + + + Results + Ergebnisse + + + Executing query + Abfrage wird ausgeführt + + + Messages + Meldungen + + + Total execution time: {0} + Gesamte Ausführungszeit: {0} + + + Save results command cannot be used with multiple selections. + Der Befehl "Ergebnisse speichern" kann nicht mit Mehrfachauswahlen verwendet werden. + + + + + + + Identifier of the notebook provider. + Bezeichner des Notebook-Anbieters. + + + What file extensions should be registered to this notebook provider + Zu registrierende Erweiterungen für diesen Notebook-Anbieter + + + What kernels should be standard with this notebook provider + Standardkernels für diesen Notebook-Anbieter + + + Contributes notebook providers. + Trägt Notebook-Anbieter bei. + + + Name of the cell magic, such as '%%sql'. + Name der Zellenmagie, z. B. %%sql. + + + The cell language to be used if this cell magic is included in the cell + Die Zellensprache, die verwendet werden soll, wenn diese Zellen-Magic in der Zelle enthalten ist. + + + Optional execution target this magic indicates, for example Spark vs SQL + Optionales Ausführungsziel, das vom Magic-Befehl angegeben wird, z.B. Spark oder SQL + + + Optional set of kernels this is valid for, e.g. python3, pyspark, sql + Optionaler Kernelsatz, auf den dies zutrifft, z. B. python3, pyspark oder sql + + + Contributes notebook language. + Trägt die Notebook-Sprache bei. + + + + + + + SQL + SQL + + + + + + + F5 shortcut key requires a code cell to be selected. Please select a code cell to run. + Zur Verwendung der F5-Tastenkombination muss eine Codezelle ausgewählt sein. Wählen Sie eine Codezelle für die Ausführung aus. + + + Clear result requires a code cell to be selected. Please select a code cell to run. + Zum Löschen des Ergebnisses muss eine Zelle ausgewählt sein. Wählen Sie eine Codezelle für die Ausführung aus. + + + + + + + Save As CSV + Als CSV speichern + + + Save As JSON + Als JSON speichern + + + Save As Excel + Speichern als Excel + + + Save As XML + Im XML-Format speichern + + + Copy + Kopieren + + + Copy With Headers + Mit Headern kopieren + + + Select All + Alle auswählen + + + + + + + Max Rows: + Max. Zeilen: + + + + + + + Select View + Ansicht auswählen + + + Select Session + Sitzung auswählen + + + Select Session: + Sitzung auswählen: + + + Select View: + Ansicht auswählen: + + + Text + Text + + + Label + Bezeichnung + + + Value + Wert + + + Details + Details + + + + + + + Copy failed with error {0} + Fehler beim Kopieren: {0} + + + + + + + New Query + Neue Abfrage + + + Run + Starten + + + Cancel + Abbrechen + + + Explain + Erklären + + + Actual + Tatsächlich + + + Disconnect + Trennen + + + Change Connection + Verbindung ändern + + + Connect + Verbinden + + + Enable SQLCMD + SQLCMD aktivieren + + + Disable SQLCMD + SQLCMD deaktivieren + + + Select Database + Datenbank auswählen + + + Select Database Toggle Dropdown + Dropdownmenü zum Auswählen der Datenbank + + + Failed to change database + Fehler beim Wechseln der Datenbank + + + Failed to change database {0} + Fehler beim Wechseln der Datenbank {0} + + + + + + + Connection + Verbindung + + + Connection type + Verbindungstyp + + + Recent Connections + Letzte Verbindungen + + + Saved Connections + Gespeicherte Verbindungen + + + Connection Details + Verbindungsdetails + + + Connect + Verbinden + + + Cancel + Abbrechen + + + No recent connection + Keine aktuelle Verbindung + + + No saved connection + Keine gespeicherten Verbindungen + + + + + + + OK + OK + + + Close + Schließen + + + + + + + Loading kernels... + Kernels werden geladen... + + + Changing kernel... + Kernel ändern... + + + Kernel: + Kernel: + + + Attach To: + Anfügen an: + + + Loading contexts... + Kontexte werden geladen... + + + Add New Connection + Neue Verbindung hinzufügen + + + Select Connection + Verbindung auswählen + + + localhost + localhost + + + Trusted + Vertrauenswürdig + + + Not Trusted + Nicht vertrauenswürdig + + + Notebook is already trusted. + Das Notebook wird bereits als vertrauenswürdig eingestuft. + + + Collapse Cells + Zellen reduzieren + + + Expand Cells + Zellen erweitern + + + No Kernel + Kein Kernel + + + None + NONE + + + New Notebook + Neues Notebook + + + + + + + Time Elapsed + Verstrichene Zeit + + + Row Count + Zeilenanzahl + + + {0} rows + {0} Zeilen + + + Executing query... + Abfrage wird ausgeführt... + + + Execution Status + Ausführungsstatus + + + + + + + No task history to display. + Kein Aufgabenverlauf zur Anzeige vorhanden. + + + Task history + Aufgabenverlauf + TaskHistory + + + Task error + Aufgabenfehler + + + + + + + Choose SQL Language + Wählen Sie SQL-Sprache + + + Change SQL language provider + SQL-Sprachanbieter ändern + + + SQL Language Flavor + SQL-Sprachvariante + + + Change SQL Engine Provider + SQL Engine Provider ändern + + + A connection using engine {0} exists. To change please disconnect or change connection + Es besteht eine Verbindung über die Engine {0}. Um die Verbindung zu ändern, trennen oder wechseln Sie bitte die Verbindung. + + + No text editor active at this time + Derzeit ist kein Texteditor aktiv + + + Select SQL Language Provider + Sprachanbieter für SQL auswählen + + + + + + + All files + Alle Dateien + + + + + + + File browser tree + Datei-Browser-Baumstruktur + FileBrowserTree + + + + + + + From + Von + + + To + An + + + Create new firewall rule + Neue Firewall-Regel erstellen + + + OK + OK + + + Cancel + Abbrechen + + + Your client IP address does not have access to the server. Sign in to an Azure account and create a new firewall rule to enable access. + Ihre Client-IP-Adresse besitzt keinen Zugriff auf den Server. Melden Sie sich bei einem Azure-Konto an, und erstellen Sie eine neue Firewallregel, um den Zugriff zu ermöglichen. + + + Learn more about firewall settings + Erfahren Sie mehr über Firewall-Einstellungen + + + Azure account + Azure-Konto + + + Firewall rule + Firewall-Regel + + + Add my client IP + Meine Client-IP hinzufügen + + + Add my subnet IP range + Meinen Subnetz-IP-Bereich hinzufügen + + + + + + + You need to refresh the credentials for this account. + Sie müssen die Anmeldeinformationen für dieses Konto aktualisieren. + + + + + + + Could not find query file at any of the following paths : + {0} + Die Abfragedatei konnte in folgenden Pfad nicht gefunden werden: +{0} + + + + + + + Add an account + Konto hinzufügen + + + Remove account + Konto entfernen + + + Are you sure you want to remove '{0}'? + Sind Sie sicher, dass Sie "{0}" entfernen möchten? + + + Yes + Ja + + + No + Nein + + + Failed to remove account + Fehler beim Entfernen des Kontos + + + Apply Filters + Filter anwenden + + + Reenter your credentials + Geben Sie Ihre Anmeldeinformationen erneut ein + + + There is no account to refresh + Es ist kein Konto zur Aktualisierung vorhanden + + + + + + + Focus on Current Query + Fokus für die aktuelle Abfrage + + + Run Query + Abfrage ausführen + + + Run Current Query + Aktuelle Abfrage ausführen + + + Run Current Query with Actual Plan + Aktuelle Abfrage mit aktuellem Plan ausführen + + + Cancel Query + Abfrage abbrechen + + + Refresh IntelliSense Cache + IntelliSense Cache aktualisieren + + + Toggle Query Results + Abfrageergebnisse umschalten + + + Editor parameter is required for a shortcut to be executed + Editor-Parameter ist erforderlich um ein Tastenkürzel auszuführen + + + Parse Query + Abfrage analysieren + + + Commands completed successfully + Befehle erfolgreich ausgeführt + + + Command failed: + Befehl fehlgeschlagen: + + + Please connect to a server + Stellen Sie eine Verbindung mit einem Server her. + + + + + + + Chart cannot be displayed with the given data + Das Diagramm kann mit den vorhandenen Daten nicht angezeigt werden. + + + + + + + The index {0} is invalid. + Der Index {0} ist ungültig. + + + + + + + no data available + Keine Daten verfügbar + + + + + + + Information + Informationen + + + Warning + Warnung + + + Error + Fehler + + + Show Details + Details anzeigen + + + Copy + Kopieren + + + Close + Schließen + + + Back + zurück + + + Hide Details + Details ausblenden + + + + + + + is required. + ist erforderlich. + + + Invalid input. Numeric value expected. + Ungültige Eingabe. Numerischer Wert erwartet. + + + + + + + Execution failed due to an unexpected error: {0} {1} + Ausführung ist aufgrund eines unerwarteten Fehlers fehlgeschlagen: {0} {1} + + + Total execution time: {0} + Gesamte Ausführungszeit: {0} + + + Started executing query at Line {0} + Die Ausführung der Abfrage wurde in Zeile {0} begonnen. + + + Initialize edit data session failed: + Fehler beim Initialisieren der Datenbearbeitungssitzung: + + + Batch execution time: {0} + Batch-Ausführungszeit: {0} + + + Copy failed with error {0} + Fehler beim Kopieren: {0} + + + + + + + Error: {0} + Fehler: {0} + + + Warning: {0} + Warnung: {0} + + + Info: {0} + Info: {0} + + + + + + + Copy Cell + Zelle kopieren + + + + + + + Backup file path + Backup-Datei-Pfad + + + Target database + Zieldatenbank + + + Restore database + Datenbank wiederherstellen + + + Restore database + Datenbank wiederherstellen + + + Database + Datenbank + + + Backup file + Backup-Datei + + + Restore + Wiederherstellen + + + Cancel + Abbrechen + + + Script + Skript + + + Source + Quelle + + + Restore from + Wiederherstellen von + + + Backup file path is required. + Sie müssen einen Dateipfad für die Sicherung angeben. + + + Please enter one or more file paths separated by commas + Geben Sie einen oder mehrere Dateipfade (durch Kommas getrennt) ein. + + + Database + Datenbank + + + Destination + Ziel + + + Select Database Toggle Dropdown + Dropdownmenü zum Auswählen der Datenbank + + + Restore to + Wiederherstellen nach + + + Restore plan + Wiederherstellungplan + + + Backup sets to restore + Wiederherzustellende Sicherungssätze + + + Restore database files as + Wiederherstellen der Datenbank-Dateien als + + + Restore database file details + Wiederherstellen der Datenbankdatei-Details + + + Logical file Name + Logischer Dateiname + + + File type + Dateityp + + + Original File Name + Ursprünglicher Dateiname + + + Restore as + Wiederherstellen als + + + Restore options + Wiederherstellungsoptionen + + + Tail-Log backup + Protokollfragmentsicherung + + + Server connections + Serververbindungen + + + General + Allgemein + + + Files + Dateien + + + Options + Optionen + + + + + + + Copy & Open + Kopieren und öffnen + + + Cancel + Abbrechen + + + User code + Benutzercode + + + Website + Webseite + + + + + + + Done + Fertig + + + Cancel + Abbrechen + + + + + + + Must be an option from the list + Muss eine Option aus der Liste sein + + + Toggle dropdown + Dropdownmenü umschalten + + + + + + + Select/Deselect All + Alle auswählen/abwählen + + + checkbox checked + Kontrollkästchen aktiviert + + + checkbox unchecked + Kontrollkästchen deaktiviert + + + + + + + modelview code editor for view model. + Modellansichts-Code-Editor für Ansichtsmodell. + + + + + + + succeeded + erfolgreich + + + failed + fehlgeschlagen + + + + + + + Server Description (optional) + Serverbeschreibung (optional) + + + + + + + Advanced Properties + Erweiterte Eigenschaften + + + Discard + Verwerfen + + + + + + + Linked accounts + Verknüpfte Konten + + + Close + Schließen + + + There is no linked account. Please add an account. + Es ist kein verknüpftes Konto vorhanden. Bitte fügen Sie ein Konto hinzu. + + + Add an account + Konto hinzufügen + + + + + + + nbformat v{0}.{1} not recognized + nbformat v{0}.{1} nicht erkannt + + + This file does not have a valid notebook format + Die Datei weist kein gültiges Notebook-Format auf. + + + Cell type {0} unknown + Unbekannter Zellentyp {0} + + + Output type {0} not recognized + Der Ausgabetyp {0} wurde nicht erkannt. + + + Data for {0} is expected to be a string or an Array of strings + Es wird erwartet, dass die Daten für {0} als Zeichenfolgen oder Array von Zeichenfolgen vorliegen. + + + Output type {0} not recognized + Der Ausgabetyp {0} wurde nicht erkannt. + + + + + + + Profiler editor for event text. Readonly + Profiler-Editor für Ereignistext. Nur lesend + + + + + + + Run Cells Before + Zellen ausführen vor: + + + Run Cells After + Zellen ausführen nach: + + + Insert Code Before + Code einfügen vor + + + Insert Code After + Code einfügen nach + + + Insert Text Before + Text einfügen vor: + + + Insert Text After + Text einfügen nach: + + + Collapse Cell + Zelle reduzieren + + + Expand Cell + Zelle erweitern + + + Clear Result + Ergebnis löschen + + + Delete + Löschen + + + + + + + No script was returned when calling select script on object + Beim Aufruf des ausgewählten Skripts im Objekt wurde kein Skript zurückgegeben. + + + Select + Auswählen + + + Create + Erstellen + + + Insert + Einfügen + + + Update + Aktualisieren + + + Delete + Löschen + + + No script was returned when scripting as {0} on object {1} + Beim scripten von {0} auf Objekt {1} wurde kein Script zurückgegeben + + + Scripting Failed + Scripterstellung ist fehlgeschlagen + + + No script was returned when scripting as {0} + Es wurde kein Skript zurückgegeben als mit {0} geskriptet wurde + + + + + + + Recent Connections + Letzte Verbindungen + + + Servers + Server + + + + + + + No Kernel + Kein Kernel + + + Cannot run cells as no kernel has been configured + Zellen können nicht ausgeführt werden, da kein Kernel konfiguriert wurde. + + + Error + Fehler + + + + + + + Azure Data Studio + Azure Data Studio + + + Start + Start + + + New connection + Neue Verbindung + + + New query + Neue Abfrage + + + New notebook + Neues Notebook + + + Open file + Datei öffnen + + + Open file + Datei öffnen + + + Deploy + Bereitstellen + + + Deploy SQL Server… + SQL Server bereitstellen... + + + Recent + Zuletzt verwendet + + + More... + Weitere... + + + No recent folders + Keine kürzlich verwendeten Ordner + + + Help + Hilfe + + + Getting started + Erste Schritte + + + Documentation + Dokumentation + + + Report issue or feature request + Problem melden oder Feature anfragen + + + GitHub repository + GitHub-Repository + + + Release notes + Anmerkungen zu dieser Version + + + Show welcome page on startup + Willkommensseite beim Start anzeigen + + + Customize + Anpassen + + + Extensions + Erweiterungen + + + Download extensions that you need, including the SQL Server Admin pack and more + Laden Sie die von Ihnen benötigten Erweiterungen herunter, darunter das SQL Server-Verwaltungspaket und mehr. + + + Keyboard Shortcuts + Tastenkombinationen + + + Find your favorite commands and customize them + Bevorzugte Befehle finden und anpassen + + + Color theme + Farbdesign + + + Make the editor and your code look the way you love + Passen Sie das Aussehen des Editors und Ihres Codes an Ihre Wünsche an. + + + Learn + Lernen + + + Find and run all commands + Alle Befehle suchen und ausführen + + + Rapidly access and search commands from the Command Palette ({0}) + Ãœber die Befehlspalette ({0}) schnell auf Befehle zugreifen und nach Befehlen suchen + + + Discover what's new in the latest release + Neuerungen in der aktuellen Version erkunden + + + New monthly blog posts each month showcasing our new features + Monatliche Vorstellung neuer Features in Blogbeiträgen + + + Follow us on Twitter + Folgen Sie uns auf Twitter + + + Keep up to date with how the community is using Azure Data Studio and to talk directly with the engineers. + Informieren Sie sich darüber, wie die Community Azure Data Studio verwendet, und kommunizieren Sie direkt mit den Technikern. + + + + + + + succeeded + erfolgreich + + + failed + fehlgeschlagen + + + in progress + In Bearbeitung + + + not started + Nicht gestartet + + + canceled + abgebrochen + + + canceling + Wird abgebrochen + + + + + + + Run + Starten + + + Dispose Edit Failed With Error: + "Dispose Edit" ist mit Fehler fehlgeschlagen: + + + Stop + Stopp + + + Show SQL Pane + SQL-Bereich anzeigen + + + Close SQL Pane + SQL-Bereich schließen + + + + + + + Connect + Verbinden + + + Disconnect + Trennen + + + Start + Start + + + New Session + Neue Sitzung + + + Pause + Pause + + + Resume + Fortsetzen + + + Stop + Stopp + + + Clear Data + Daten löschen + + + Auto Scroll: On + Automatisches Scrollen: Ein + + + Auto Scroll: Off + Automatisches Scrollen: Aus + + + Toggle Collapsed Panel + Ausgeblendeten Bereich umschalten + + + Edit Columns + Spalten bearbeiten + + + Find Next String + Nächste Zeichenfolge suchen + + + Find Previous String + Vorhergehende Zeichenfolge suchen + + + Launch Profiler + Profiler starten + + + Filter… + Filtern... + + + Clear Filter + Filter löschen + + + + + + + Events (Filtered): {0}/{1} + Ereignisse (Gefiltert): {0}/{1} + + + Events: {0} + Ereignisse: {0} + + + Event Count + Ereignisanzahl + + + + + + + Save As CSV + Als CSV speichern + + + Save As JSON + Als JSON speichern + + + Save As Excel + Speichern als Excel + + + Save As XML + Im XML-Format speichern + + + Save to file is not supported by the backing data source + Das Speichern in einer Datei wird von der zugrunde liegenden Datenquelle nicht unterstützt. + + + Copy + Kopieren + + + Copy With Headers + Mit Headern kopieren + + + Select All + Alle auswählen + + + Copy + Kopieren + + + Copy All + Alles kopieren + + + Maximize + Maximieren + + + Restore + Wiederherstellen + + + Chart + Diagramm + + + Visualizer + Visualisierer + + + + + + + The extension "{0}" is using sqlops module which has been replaced by azdata module, the sqlops module will be removed in a future release. + Die Erweiterung "{0}" verwendet das sqlops-Modul, das durch das azdata-Modul ersetzt wurde. Das sqlops-Modul wird in einer zukünftigen Version entfernt. + + + + + + + Table header background color + Hintergrundfarbe der Tabellenüberschrift + + + Table header foreground color + Vordergrundfarbe der Tabellenüberschrift + + + Disabled Input box background. + Deaktiviertes Eingabefeld Hintergrund. + + + Disabled Input box foreground. + Deaktiviertes Eingabefeld Vordergrund. + + + Button outline color when focused. + Konturfarbe der Schaltfläche, wenn markiert. + + + Disabled checkbox foreground. + Vordergrund für deaktivierte Kontrollkästchen. + + + List/Table background color for the selected and focus item when the list/table is active + Hintergrundfarbe von Listen/Tabellen für das ausgewählte oder fokussierte Element, wenn die Liste/Tabelle aktiv ist + + + SQL Agent Table background color. + Hintergrundfarbe der SQL Agent-Tabelle. + + + SQL Agent table cell background color. + Hintergrundfarbe der Tabellenzelle für SQL-Agent. + + + SQL Agent table hover background color. + Hintergrundfarbe für Zeigen auf SQL Agent-Tabelle. + + + SQL Agent heading background color. + Hintergrundfarbe von SQL-Agent-Ãœberschriften. + + + SQL Agent table cell border color. + Rahmenfarbe einer SQL-Agent-Tabellenzelle. + + + Results messages error color. + Farbe für Fehler bei Ergebnismeldung. + + + + + + + Choose Results File + Ergebnisdatei auswählen + + + CSV (Comma delimited) + CSV (durch Trennzeichen getrennte Datei) + + + JSON + JSON + + + Excel Workbook + Excel-Arbeitsmappe + + + XML + XML + + + Plain Text + Nur-Text + + + Open file location + Dateispeicherort öffnen + + + Open file + Datei öffnen + + + + + + + Backup name + Backup-Name + + + Recovery model + Wiederherstellungsmodell + + + Backup type + Backup-Typ + + + Backup files + Sicherungsdateien + + + Algorithm + Algorithmus + + + Certificate or Asymmetric key + Zertifikat oder asymmetrische Schlüssel + + + Media + Medien + + + Backup to the existing media set + Sicherung auf den vorhandenen Mediensatz + + + Backup to a new media set + Sicherung auf einen neuen Mediensatz + + + Append to the existing backup set + An vorhandenem Backup-Satz anhängen + + + Overwrite all existing backup sets + Alle vorhandenen Sicherungssätze überschreiben + + + New media set name + Name des neuen Mediensatzes + + + New media set description + Neue Mediensatz-Beschreibung + + + Perform checksum before writing to media + Prüfsumme vor dem Schreiben auf Medium berechnen + + + Verify backup when finished + Sicherung nach Abschluss überprüfen + + + Continue on error + Bei Fehler fortsetzen + + + Expiration + Ablauf + + + Set backup retain days + Beibehaltung für die Sicherung in Tagen angeben + + + Copy-only backup + Nur-Kopie-Sicherung + + + Advanced Configuration + Erweiterte Einstellungen + + + Compression + Komprimierung + + + Set backup compression + Sicherungskomprimierung festlegen + + + Encryption + Verschlüsselung + + + Transaction log + Transaktionsprotokoll + + + Truncate the transaction log + Transaktionsprotokoll abschneiden + + + Backup the tail of the log + Sicherung des Protokollfragments + + + Reliability + Zuverlässigkeit + + + Media name is required + Name des Mediums ist erforderlich + + + No certificate or asymmetric key is available + Kein Zertifikat oder asymmetrischer Schlüssel steht zur Verfügung + + + Add a file + Datei hinzufügen + + + Remove files + Dateien entfernen + + + Invalid input. Value must be greater than or equal 0. + Ungültige Eingabe. Wert muss größer oder gleich 0 sein. + + + Script + Skript + + + Backup + Sicherung + + + Cancel + Abbrechen + + + Only backup to file is supported + Nur das Sichern in Datei(en) ist unterstützt + + + Backup file path is required + Pfad für Backup-Datei erforderlich + + + + + + + Results + Ergebnisse + + + Messages + Meldungen + + + + + + + There is no data provider registered that can provide view data. + Es ist kein Datenanbieter registriert, der Ansichtsdaten bereitstellen kann. + + + Collapse All + Alle zuklappen + + + + + + + Home + Start + + + + + + + The "{0}" section has invalid content. Please contact extension owner. + Der Abschnitt "{0}" ist ungültig. Bitte kontaktieren Sie den Besitzer der Erweiterung. + + + + + + + Jobs + Aufträge + + + Notebooks + Notebooks + + + Alerts + Warnungen + + + Proxies + Proxys + + + Operators + Operatoren + + + + + + + Loading + Laden + + + + + + + SERVER DASHBOARD + SERVER-DASHBOARD + + + + + + + DATABASE DASHBOARD + DATENBANKDASHBOARD + + + + + + + Edit + Bearbeiten + + + Exit + Beenden + + + Refresh + Aktualisieren + + + Toggle More + Mehr umschalten + + + Delete Widget + Widget löschen + + + Click to unpin + Zum Lösen klicken + + + Click to pin + Klicken zum Anheften + + + Open installed features + Installierte Features öffnen + + + Collapse + Reduzieren + + + Expand + Erweitern + + + + + + + Steps + Schritte + + + + + + + StdIn: + Stdin: + + + + + + + Add code + Code hinzufügen + + + Add text + Text hinzufügen + + + Create File + Datei erstellen + + + Could not display contents: {0} + Inhalt konnte nicht angezeigt werden: {0} + + + Please install the SQL Server 2019 extension to run cells. + Installieren Sie die SQL Server 2019-Erweiterung, um Zellen auszuführen. + + + Install Extension + Erweiterung installieren + + + Code + Code + + + Text + Text + + + Run Cells + Zellen ausführen + + + Clear Results + Ergebnisse löschen + + + < Previous + < Zurück + + + Next > + Weiter > + + + cell with URI {0} was not found in this model + Die Zelle mit dem URI {0} wurde in diesem Modell nicht gefunden. + + + Run Cells failed - See error in output of the currently selected cell for more information. + Fehler beim Ausführen von Zellen: Weitere Informationen finden Sie im Fehler in der Ausgabe der aktuell ausgewählten Zelle. + + + + + + + Click on + Klicken Sie auf + + + + Code + + Code + + + or + Oder + + + + Text + + Text + + + to add a code or text cell + Hinzufügen von Code oder Textzelle + + + + + + + Database + Datenbank + + + Files and filegroups + Dateien und Dateigruppen + + + Full + Vollständig + + + Differential + Differenziell + + + Transaction Log + Transaktionsprotokoll + + + Disk + Datenträger + + + Url + URL + + + Use the default server setting + Standardservereinstellung verwenden + + + Compress backup + Sicherung komprimieren + + + Do not compress backup + Sicherung nicht komprimieren + + + Server Certificate + Serverzertifikat + + + Asymmetric Key + Asymmetrischer Schlüssel + + + Backup Files + Sicherungsdateien + + + All Files + Alle Dateien + + + + + + + No connections found. + Keine Verbindungen gefunden. + + + Add Connection + Verbindung hinzufügen + + + + + + + Failed to change database + Fehler beim Wechseln der Datenbank + + + + + + + Name + Name + + + Email Address + E-Mail-Adresse + + + Enabled + Aktiviert + + + + + + + Name + Name + + + Last Occurrence + Letztes Vorkommen + + + Enabled + Aktiviert + + + Delay Between Responses (in secs) + Verzögerung zwischen Antworten (in Sek.) + + + Category Name + Kategoriename + + + + + + + Account Name + Kontoname + + + Credential Name + Name der Anmeldeinformationen + + + Description + Beschreibung + + + Enabled + Aktiviert + + + + + + + Unable to load dashboard properties + Die Dashboardeigenschaften können nicht geladen werden. + + + + + + + Search by name of type (a:, t:, v:, f:, or sp:) + Suche nach Name des Typs (a:, t:, v:, f: oder sp:) + + + Search databases + Datenbanken suchen + + + Unable to load objects + Objekte konnten nicht geladen werden + + + Unable to load databases + Datenbanken konnten nicht geladen werden + + + + + + + Auto Refresh: OFF + Automatische Aktualisierung: AUS + + + Last Updated: {0} {1} + Letzte Aktualisierung: {0} {1} + + + No results to show + Keine anzuzeigende Ergebnisse + + + + + + + No {0}renderer could be found for output. It has the following MIME types: {1} + Für die Ausgabe wurde kein {0}-Renderer gefunden. Folgende MIME-Typen sind enthalten: {1} + + + safe + sicher + + + No component could be found for selector {0} + Für Selektor "{0}" wurde keine Komponente gefunden. + + + Error rendering component: {0} + Fehler beim Rendern der Komponente: {0} + + + + + + + Connected to + Verbunden mit + + + Disconnected + Getrennt + + + Unsaved Connections + Nicht gespeicherte Verbindungen + + + + + + + Delete Row + Zeile löschen + + + Revert Current Row + Aktuelle Zeile zurücksetzen + + + + + + + Step ID + Schritt-ID + + + Step Name + Schrittname + + + Message + Nachricht + + + + + + + XML Showplan + XML-Showplan + + + Results grid + Ergebnisraster + + + + + + + Please select active cell and try again + Wählen Sie eine aktive Zelle aus, und versuchen Sie es noch einmal. + + + Run cell + Zelle ausführen + + + Cancel execution + Ausführung abbrechen + + + Error on last run. Click to run again + Fehler bei der letzten Ausführung. Klicken Sie, um den Vorgang zu wiederholen. + + + + + + + Add an account... + Konto hinzufügen... + + + <Default> + <Standard> + + + Loading... + Wird geladen... + + + Server group + Servergruppe + + + <Default> + <Standard> + + + Add new group... + Neue Gruppe hinzufügen... + + + <Do not save> + <Nicht speichern> + + + {0} is required. + '{0}' ist erforderlich. + + + {0} will be trimmed. + {0} wird gekürzt. + + + Remember password + Kennwort speichern + + + Account + Konto + + + Refresh account credentials + Anmeldeinformationen des Kontos aktualisieren + + + Azure AD tenant + Azure AD-Mandant + + + Select Database Toggle Dropdown + Dropdownmenü zum Auswählen der Datenbank + + + Name (optional) + Name (optional) + + + Advanced... + Erweitert ... + + + You must select an account + Sie müssen ein Konto auswählen. + + + + + + + Cancel + Abbrechen + + + The task is failed to cancel. + Die Aufgabe konnte nicht abgebrochen werden. + + + Script + Skript + + + + + + + Date Created: + Erstellungsdatum: + + + Notebook Error: + Notebook-Fehler: + + + Job Error: + Auftragsfehler: + + + Pinned + Angeheftet + + + Recent Runs + Aktuelle Ausführungen + + + Past Runs + Vergangene Ausführungen + + + + + + + No tree view with id '{0}' registered. + Kein Treeviw mit der id '{0}' registriert. + + + + + + + Loading... + Wird geladen... + + + + + + + Dashboard Tabs ({0}) + Dashboardregisterkarten ({0}) + + + Id + ID + + + Title + Titel + + + Description + Beschreibung + + + Dashboard Insights ({0}) + Dashboardeinblicke ({0}) + + + Id + ID + + + Name + Name + + + When + Zeitpunkt + + + + + + + Chart + Diagramm + + + + + + + Operation + Vorgang + + + Object + Objekt + + + Est Cost + Geschätzte Kosten + + + Est Subtree Cost + Geschätzte Kosten des Subtrees + + + Actual Rows + Tatsächliche Zeilen + + + Est Rows + Zeilen ca. + + + Actual Executions + Tatsächliche Ausführungen + + + Est CPU Cost + Geschätzte CPU-Kosten + + + Est IO Cost + Geschätzte E/A Kosten + + + Parallel + Parallel + + + Actual Rebinds + Tatsächliche erneute Bindungen + + + Est Rebinds + geschätzte Zurückspulvorgänge + + + Actual Rewinds + Tatsächliche Zurückspulvorgänge + + + Est Rewinds + Geschätzte Rückläufe + + + Partitioned + Partitioniert + + + Top Operations + Top-Operationen + + + + + + + Query Plan + Abfrage-Plan + + + + + + + Could not find component for type {0} + Die Komponente für den Typ {0} wurde nicht gefunden. + + + + + + + A NotebookProvider with valid providerId must be passed to this method + Eine NotebookProvider-Klasse mit gültigem providerId-Wert muss an diese Methode übergeben werden. + + + + + + + A NotebookProvider with valid providerId must be passed to this method + Eine NotebookProvider-Klasse mit gültigem providerId-Wert muss an diese Methode übergeben werden. + + + no notebook provider found + Es wurde kein Notebook-Anbieter gefunden. + + + No Manager found + Es wurde kein Manager gefunden. + + + Notebook Manager for notebook {0} does not have a server manager. Cannot perform operations on it + Der Notebook-Manager für das Notebook {0} enthält keinen Server-Manager. Es können keine Aktionen durchgeführt werden. + + + Notebook Manager for notebook {0} does not have a content manager. Cannot perform operations on it + Der Notebook-Manager für das Notebook {0} enthält keinen Inhalts-Manager. Es können keine Aktionen durchgeführt werden. + + + Notebook Manager for notebook {0} does not have a session manager. Cannot perform operations on it + Der Notebook-Manager für das Notebook {0} enthält keinen Sitzungs-Manager. Es können keine Aktionen durchgeführt werden. + + + + + + + SQL kernel error + SQL-Kernelfehler + + + A connection must be chosen to run notebook cells + Sie müssen eine Verbindung auswählen, um Notebook-Zellen auszuführen. + + + Displaying Top {0} rows. + Die obersten {0} Zeilen werden angezeigt. + + + + + + + Show Recommendations + Empfehlungen anzeigen + + + Install Extensions + Erweiterungen installieren + + + + + + + Name + Name + + + Last Run + Letzte Ausführung + + + Next Run + Nächste Ausführung + + + Enabled + Aktiviert + + + Status + Status + + + Category + Kategorie + + + Runnable + Ausführbar + + + Schedule + Zeitplan + + + Last Run Outcome + Ergebnis der letzten Ausführung + + + Previous Runs + Vorherigen Ausführungen + + + No Steps available for this job. + Für diesen Auftrag sind keine Schritte verfügbar. + + + Error: + Fehler: + + + + + + + Find + Suchen + + + Find + Suchen + + + Previous match + Vorheriger Treffer + + + Next match + Nächste Ãœbereinstimmung + + + Close + Schließen + + + Your search returned a large number of results, only the first 999 matches will be highlighted. + Bei Ihrer Suche wurden sehr viele Ergebnisse ermittelt. Nur die ersten 999 Treffer werden hervorgehoben. + + + {0} of {1} + {0} von {1} + + + No Results + Keine Ergebnisse + + + + + + + Run Query + Abfrage ausführen + + + + + + + Done + Fertig + + + Cancel + Abbrechen + + + Generate script + Skript generieren + + + Next + Weiter + + + Previous + Zurück + + + + + + + A server group with the same name already exists. + Eine Servergruppe mit dem gleichen Namen ist bereits vorhanden. + + + + + + + {0} is an unknown container. + {0} ist ein unbekannter Container. + + + + + + + Loading Error... + Fehler beim Laden... + + + + + + + Failed + fehlgeschlagen + + + Succeeded + erfolgreich + + + Retry + Erneut versuchen + + + Cancelled + Abgebrochen + + + In Progress + In Bearbeitung + + + Status Unknown + Unbekannter Status + + + Executing + Ausführen + + + Waiting for Thread + Auf Thread warten + + + Between Retries + Zwischen Wiederholungen + + + Idle + Im Leerlauf + + + Suspended + Angehalten + + + [Obsolete] + [Veraltet] + + + Yes + Ja + + + No + Nein + + + Not Scheduled + Nicht geplant + + + Never Run + Nie ausführen + + + + + + + Name + Name + + + Target Database + Zieldatenbank + + + Last Run + Letzte Ausführung + + + Next Run + Nächste Ausführung + + + Status + Status + + + Last Run Outcome + Ergebnis der letzten Ausführung + + + Previous Runs + Vorherigen Ausführungen + + + No Steps available for this job. + Für diesen Auftrag sind keine Schritte verfügbar. + + + Error: + Fehler: + + + Notebook Error: + Notebook-Fehler: + + + + + + + Home + Start + + + No connection information could be found for this dashboard + Für dieses Dashboard wurden keine Verbindungsinformationen gefunden + + + + + + + Data + Daten + + + Connection + Verbindung + + + Query + Abfrage + + + Notebook + Notebook + + + SQL + SQL + + + Microsoft SQL Server + Microsoft SQL Server + + + Dashboard + Dashboard + + + Profiler + Profiler + + + + + + + Close + Schließen + + + + + + + Success + Erfolg + + + Error + Fehler + + + Refresh + Aktualisieren + + + New Job + Neuer Auftrag + + + Run + Starten + + + : The job was successfully started. + : Der Auftrag wurde erfolgreich gestartet. + + + Stop + Stopp + + + : The job was successfully stopped. + : Der Auftrag wurde erfolgreich beendet. + + + Edit Job + Auftrag bearbeiten + + + Open + Eröffnungskurs + + + Delete Job + Auftrag löschen + + + Are you sure you'd like to delete the job '{0}'? + Möchten Sie den Auftrag {0} wirklich löschen? + + + Could not delete job '{0}'. +Error: {1} + Auftrag "{0}" konnte nicht gelöscht werden. +Fehler: {1} + + + The job was successfully deleted + Der Auftrag wurde erfolgreich gelöscht. + + + New Step + Neuer Schritt + + + Delete Step + Schritt löschen + + + Are you sure you'd like to delete the step '{0}'? + Sind Sie sicher, dass Sie den Schritt "{0}" löschen möchten? + + + Could not delete step '{0}'. +Error: {1} + Schritt "{0}" konnte nicht gelöscht werden. +Fehler: {1} + + + The job step was successfully deleted + Der Auftragsschritt wurde erfolgreich gelöscht. + + + New Alert + Neue Warnung + + + Edit Alert + Warnung bearbeiten + + + Delete Alert + Warnung löschen + + + Cancel + Abbrechen + + + Are you sure you'd like to delete the alert '{0}'? + Sind Sie sicher, dass Sie die Warnung "{0}" löschen möchten? + + + Could not delete alert '{0}'. +Error: {1} + Warnung "{0}" konnte nicht gelöscht werden. +Fehler: {1} + + + The alert was successfully deleted + Die Warnung wurde erfolgreich gelöscht. + + + New Operator + Neuer Operator + + + Edit Operator + Operator bearbeiten + + + Delete Operator + Operator löschen + + + Are you sure you'd like to delete the operator '{0}'? + Sind Sie sicher, dass Sie den Operator "{0}" löschen möchten? + + + Could not delete operator '{0}'. +Error: {1} + Operator '{0}' konnte nicht gelöscht werden. +Fehler: {1} + + + The operator was deleted successfully + Der Operator wurde erfolgreich gelöscht. + + + New Proxy + Neuer Proxy + + + Edit Proxy + Proxy bearbeiten + + + Delete Proxy + Proxy löschen + + + Are you sure you'd like to delete the proxy '{0}'? + Sind Sie sicher, dass Sie den Proxy "{0}" löschen möchten? + + + Could not delete proxy '{0}'. +Error: {1} + Proxy "{0}" konnte nicht gelöscht werden. +Fehler: {1} + + + The proxy was deleted successfully + Der Proxy wurde erfolgreich gelöscht. + + + New Notebook Job + Neuer Notebook-Auftrag + + + Edit + Bearbeiten + + + Open Template Notebook + Vorlagennotebook öffnen + + + Delete + Löschen + + + Are you sure you'd like to delete the notebook '{0}'? + Möchten Sie das Notebook "{0}" wirklich löschen? + + + Could not delete notebook '{0}'. +Error: {1} + Notebook "{0}" konnte nicht gelöscht werden. +Fehler: {1} + + + The notebook was successfully deleted + Das Notebook wurde erfolgreich gelöscht. + + + Pin + Anheften + + + Delete + Löschen + + + Unpin + Lösen + + + Rename + umbenennen + + + Open Latest Run + Letzte Ausführung öffnen + + + + + + + Please select a connection to run cells for this kernel + Wählen Sie eine Verbindung zum Ausführen von Zellen für diesen Kernel aus. + + + Failed to delete cell. + Fehler beim Löschen der Zelle. + + + Failed to change kernel. Kernel {0} will be used. Error was: {1} + Kernel konnte nicht geändert werden. Der Kernel {0} wird verwendet. Fehler: {1} + + + Failed to change kernel due to error: {0} + Kernel konnte aufgrund des folgenden Fehlers nicht geändert werden: {0} + + + Changing context failed: {0} + Ändern des Kontexts fehlgeschlagen: {0} + + + Could not start session: {0} + Sitzung konnte nicht gestartet werden: {0} + + + A client session error occurred when closing the notebook: {0} + Beim Schließen des Notebooks {0} ist ein Fehler mit der Clientsitzung aufgetreten. + + + Can't find notebook manager for provider {0} + Der Notebook-Manager für den Anbieter {0} kann nicht gefunden werden. + + + + + + + An error occurred while starting the notebook session + Fehler beim Starten der Notebook-Sitzung + + + Server did not start for unknown reason + Der Server wurde aus unbekannten Gründen nicht gestartet. + + + Kernel {0} was not found. The default kernel will be used instead. + Der Kernel {0} wurde nicht gefunden. Der Standardkernel wird stattdessen verwendet. + + + + + + + Unknown component type. Must use ModelBuilder to create objects + Unbekannter Komponententyp: Zum Erstellen von Objekten muss ModelBuilder verwendet werden. + + + The index {0} is invalid. + Der Index {0} ist ungültig. + + + Unkown component configuration, must use ModelBuilder to create a configuration object + Unbekannte Komponentenkonfiguration, um ein Konfigurations-Objekt zu erstellen muss der ModelBuilder verwendet werden + + + + + + + Horizontal Bar + Horizontale Leiste + + + Bar + Balken + + + Line + Linie + + + Pie + Kreis + + + Scatter + Punkt + + + Time Series + Zeitreihe + + + Image + Bild + + + Count + Anzahl + + + Table + Table + + + Doughnut + Ring + + + + + + + OK + OK + + + Clear + Löschen + + + Cancel + Abbrechen + + + + + + + Cell execution cancelled + Zellausführung abgebrochen + + + Query execution was canceled + Die Ausführung der Abfrage wurde abgebrochen. + + + The session for this notebook is not yet ready + Die Sitzung für dieses Notebook ist noch nicht bereit. + + + The session for this notebook will start momentarily + Die Sitzung für dieses Notebook wird in Kürze gestartet. + + + No kernel is available for this notebook + Für dieses Notebook ist kein Kernel verfügbar. + + + + + + + Select Connection + Verbindung auswählen + + + localhost + localhost + + + + + + + Data Direction + Datenrichtung + + + Vertical + Vertikal + + + Horizontal + Horizontal + + + Use column names as labels + Spaltennamen als Bezeichnungen verwenden + + + Use first column as row label + Erste Spalte als Zeilenbezeichnung verwenden + + + Legend Position + Legendenposition + + + Y Axis Label + Bezeichnung der Y-Achse + + + Y Axis Minimum Value + Minimalwert der Y-Achse + + + Y Axis Maximum Value + Maximalwert der Y-Achse + + + X Axis Label + X-Achsenbeschriftung + + + X Axis Minimum Value + Minimalwert der X-Achse + + + X Axis Maximum Value + Maximalwert der X-Achse + + + X Axis Minimum Date + Minimaldatum der X-Achse + + + X Axis Maximum Date + Maximaldatum der X-Achse + + + Data Type + Datentyp + + + Number + Anzahl + + + Point + Punkt + + + Chart Type + Diagrammtyp + + + Encoding + Codierung + + + Image Format + Bildformat + + + + + + + Create Insight + Einblick erstellen + + + Cannot create insight as the active editor is not a SQL Editor + Einsicht kann nicht erstellt werden, weil der aktive Editor kein SQL-Editor ist + + + My-Widget + My-Widget + + + Copy as image + Als Bild kopieren + + + Could not find chart to save + Zu speicherndes Diagramm konnte nicht gefunden werden + + + Save as image + Als Bild speichern + + + PNG + PNG + + + Saved Chart to path: {0} + Diagramm in Pfad gespeichert: {0} + + + + + + + Changing editor types on unsaved files is unsupported + Das Ändern von Editor-Typen für nicht gespeicherte Dateien wird nicht unterstützt. + + + + + + + Table does not contain a valid image + Die Tabelle enthält kein gültiges Bild. + + + + + + + Series {0} + Reihe {0} diff --git a/resources/xlf/es/azurecore.es.xlf b/resources/xlf/es/azurecore.es.xlf index e5d64fc0ea57..59b73fca0fbf 100644 --- a/resources/xlf/es/azurecore.es.xlf +++ b/resources/xlf/es/azurecore.es.xlf @@ -200,4 +200,20 @@ + + + + SQL Managed Instances + Instancias administradas de SQL + + + + + + + Azure Database for PostgreSQL Servers + Servidores de Azure Database for PostgreSQL + + + \ No newline at end of file diff --git a/resources/xlf/es/big-data-cluster.es.xlf b/resources/xlf/es/big-data-cluster.es.xlf index 36911add00d1..36271ead1554 100644 --- a/resources/xlf/es/big-data-cluster.es.xlf +++ b/resources/xlf/es/big-data-cluster.es.xlf @@ -38,6 +38,14 @@ Delete Mount Eliminar montaje + + Big Data Cluster + Clúster de macrodatos + + + Ignore SSL verification errors against SQL Server Big Data Cluster endpoints such as HDFS, Spark, and Controller if true + Ignorar los errores de verificación SSL en los puntos de conexión del clúster de macrodatos de SQL Server, como HDFS, Spark y Controller, si es true + @@ -58,26 +66,86 @@ Deleting Eliminando - - Waiting For Deletion - Esperando la eliminación - Deleted Eliminado + + Applying Upgrade + Aplicando actualización + Upgrading Actualizando - - Waiting For Upgrade - Esperando la actualización + + Applying Managed Upgrade + Aplicando la actualización administrada + + + Managed Upgrading + Actualización administrada + + + Rollback + Reversión + + + Rollback In Progress + Reversión en curso + + + Rollback Complete + Reversión finalizada Error Error + + Creating Secrets + Creando secretos + + + Waiting For Secrets + Esperando secretos + + + Creating Groups + Creando grupos + + + Waiting For Groups + Esperando grupos + + + Creating Resources + Creando recursos + + + Waiting For Resources + Esperando recursos + + + Creating Kerberos Delegation Setup + Creación de la configuración de delegación Kerberos + + + Waiting For Kerberos Delegation Setup + Esperando la configuración de la delegación Kerberos + + + Waiting For Deletion + Esperando la eliminación + + + Waiting For Upgrade + Esperando la actualización + + + Upgrade Paused + Actualización en pausa + Running En ejecución @@ -162,6 +230,78 @@ Unhealthy Incorrecto + + Unexpected error retrieving BDC Endpoints: {0} + Error inesperado al recuperar puntos de conexión de BDC: {0} + + + + + + + Basic + Basic + + + Windows Authentication + Autenticación de Windows + + + Login to controller failed + Error al iniciar sesión en el controlador + + + Login to controller failed: {0} + Error al iniciar sesión en el controlador: {0} + + + Username is required + Se requiere nombre de usuario + + + Password is required + Se requiere contraseña + + + url + URL + + + username + Nombre de usuario + + + password + Contraseña + + + Cluster Management URL + Dirección URL de administración de clústeres + + + Authentication type + Tipo de autenticación + + + Username + Nombre de usuario + + + Password + Contraseña + + + Cluster Connection + Conexión de clúster + + + OK + Aceptar + + + Cancel + Cancelar + @@ -200,6 +340,22 @@ + + + + Connect to Controller (preview) + Conectar al controlador (versión preliminar) + + + + + + + View Details + Ver detalles + + + @@ -207,8 +363,8 @@ No se encontró información del punto de conexión del controlador - Big Data Cluster Dashboard - - Panel del clúster de macrodatos + Big Data Cluster Dashboard (preview) - + Panel de control del clúster de macrodatos (versión preliminar) - Yes @@ -263,8 +419,8 @@ Se requiere contraseña - Add New Controller - Agregar nuevo controlador + Add New Controller (preview) + Agregar nuevo controlador (versión preliminar) url @@ -283,8 +439,8 @@ Recordar contraseña - URL - URL + Cluster Management URL + Dirección URL de administración de clústeres Authentication type @@ -319,7 +475,7 @@ Solucionar problemas - Big data cluster overview + Big Data Cluster overview Información general del clúster de macrodatos @@ -362,18 +518,18 @@ Metrics and Logs Métricas y registros - - Metrics - Métricas + + Node Metrics + Métricas de nodo + + + SQL Metrics + Métricas SQL Logs Registros - - View Details - Ver detalles - @@ -422,31 +578,35 @@ Endpoint Extremo - - View Details - Ver detalles + + Unexpected error retrieving BDC Endpoints: {0} + Error inesperado al recuperar puntos de conexión de BDC: {0} + + + The dashboard requires a connection. Please click retry to enter your credentials. + El panel requiere una conexión. Haga clic en Reintentar para escribir sus credenciales. + + + Unexpected error occurred: {0} + Error inesperado: {0} Copy Copiar + + Endpoint '{0}' copied to clipboard + Punto de conexión "{0}" copiado en el Portapapeles + - - Basic - Basic - - - Windows Authentication - Autenticación de Windows - Mount Configuration Configuración del montaje - + HDFS Path Ruta HDFS @@ -454,22 +614,6 @@ Bad formatting of credentials at {0} Formato incorrecto de las credenciales en {0} - - Login to controller failed - Error al iniciar sesión en el controlador - - - Login to controller failed: {0} - Error al iniciar sesión en el controlador: {0} - - - Username is required - Se requiere nombre de usuario - - - Password is required - Se requiere contraseña - Mounting HDFS folder on path {0} Montaje de la carpeta HDFS en la ruta de acceso {0} @@ -494,58 +638,30 @@ Unknown error occurred during the mount process Error desconocido durante el proceso de montaje - - url - URL - - - username - Nombre de usuario - - - password - Contraseña - - - URL - URL - - - Authentication type - Tipo de autenticación - - - Username - Nombre de usuario - - - Password - Contraseña - - - Cluster Connection - Conexión de clúster - - - OK - Aceptar - - - Cancel - Cancelar - - Mount HDFS Folder - Montar carpeta HDFS + Mount HDFS Folder (preview) + Montar carpeta HDFS (versión preliminar) + + + Path to a new (non-existing) directory which you want to associate with the mount + Ruta de acceso a un nuevo directorio (no existente) que desea asociar al montaje - + Remote URI URI remoto - + + The URI to the remote data source. Example for ADLS: abfs://fs@saccount.dfs.core.windows.net/ + El URI del origen de datos remoto. Ejemplo de ADLS: abfs://fs@saccount.dfs.core.windows.net/ + + Credentials Credenciales + + Mount credentials for authentication to remote data source for reads + Credenciales de montaje para la autenticación en el origen de datos remoto para lecturas + Refresh Mount Actualizar montaje diff --git a/resources/xlf/es/sql.es.xlf b/resources/xlf/es/sql.es.xlf index 32bc1d5df29c..1e3457f214b6 100644 --- a/resources/xlf/es/sql.es.xlf +++ b/resources/xlf/es/sql.es.xlf @@ -1,14 +1,5574 @@  - + - - SQL Language Basics - Conceptos básicos del lenguaje SQL + + Copying images is not supported + No se admite la copia de imágenes - - Provides syntax highlighting and bracket matching in SQL files. - Proporciona resaltado de sintaxis y correspondencia de corchetes en archivos de SQL. + + + + + + Get Started + Iniciar + + + Show Getting Started + Ver introducción + + + Getting &&Started + I&&ntroducción + && denotes a mnemonic + + + + + + + QueryHistory + Historial de consultas + + + Whether Query History capture is enabled. If false queries executed will not be captured. + Si la captura del historial de consultas está habilitada. De no ser así, las consultas ejecutadas no se capturarán. + + + View + Vista + + + Query History + Historial de consultas + + + &&Query History + &&Historial de consultas + && denotes a mnemonic + + + + + + + Connecting: {0} + Conexión: {0} + + + Running command: {0} + Ejecutando comando: {0} + + + Opening new query: {0} + Abriendo nueva consulta: {0} + + + Cannot connect as no server information was provided + No se puede conectar ya que no se proporcionó información del servidor + + + Could not open URL due to error {0} + No se pudo abrir la dirección URL debido a un error {0} + + + This will connect to server {0} + Esto se conectará al servidor {0} + + + Are you sure you want to connect? + ¿Seguro que desea conectarse? + + + &&Open + &&Abrir + + + Connecting query file + Conectando con el archivo de consulta + + + + + + + Error + Error + + + Warning + Advertencia + + + Info + Información + + + + + + + Saving results into different format disabled for this data provider. + El guardado de resultados en diferentes formatos se ha deshabilitado para este proveedor de datos. + + + Cannot serialize data as no provider has been registered + No se pueden serializar datos ya que no se ha registrado ningún proveedor + + + Serialization failed with an unknown error + Error desconocido en la serialización + + + + + + + Connection is required in order to interact with adminservice + Se necesita la conexión para interactuar con el servicio de administración + + + No Handler Registered + Ningún controlador registrado + + + + + + + Select a file + Seleccione un archivo + + + + + + + Disconnect + Desconectar + + + Refresh + Actualizar + + + + + + + Results Grid and Messages + Cuadrícula y mensajes de resultados + + + Controls the font family. + Controla la familia de fuentes. + + + Controls the font weight. + Controla el grosor de la fuente. + + + Controls the font size in pixels. + Controla el tamaño de fuente en píxeles. + + + Controls the letter spacing in pixels. + Controla el espacio entre letras en pixels. + + + Controls the row height in pixels + Controla la altura de la fila en píxeles + + + Controls the cell padding in pixels + Controla el relleno de la celda en píxeles + + + Auto size the columns width on inital results. Could have performance problems with large number of columns or large cells + Redimensione automáticamente el ancho de las columnas en los resultados iniciales. Podría tener problemas de rendimiento con un gran número de columnas o celdas grandes + + + The maximum width in pixels for auto-sized columns + El máximo ancho en píxeles de las columnas de tamaño automático + + + + + + + {0} in progress tasks + {0} tareas en progreso + + + View + Vista + + + Tasks + Tareas + + + &&Tasks + &&Tareas + && denotes a mnemonic + + + + + + + Connections + Conexiones + + + View + Vista + + + Database Connections + Conexiones de base de datos + + + data source connections + Conexiones de fuentes de datos + + + data source groups + grupos de origen de datos + + + Startup Configuration + Configuración de inicio + + + True for the Servers view to be shown on launch of Azure Data Studio default; false if the last opened view should be shown + Verdadero para que la vista de servidores aparezcan en la apertura por defecto de Azure Data Studio; Falso si la último vista abierta debe mostrarse + + + + + + + The maximum number of recently used connections to store in the connection list. + El número máximo de conexiones recientemente utilizadas para almacenar en la lista de conexiones. + + + Default SQL Engine to use. This drives default language provider in .sql files and the default to use when creating a new connection. Valid option is currently MSSQL + Motor de SQL predeterminado a utilizar. Esto indica el proveedor de idioma predeterminado en archivos .sql y el predeterminado a utilizar al crear una nueva conexión. La opción válida es actualmente MSSQL + + + Attempt to parse the contents of the clipboard when the connection dialog is opened or a paste is performed. + Intente analizar el contenido del portapapeles cuando se abre el cuadro de diálogo de conexión o se realiza un pegado. + + + + + + + Server Group color palette used in the Object Explorer viewlet. + Paleta de colores del grupo de servidores utilizada en el viewlet del Explorador de objetos. + + + Auto-expand Server Groups in the Object Explorer viewlet. + Auto expandir Grupo de Servidores en el Explorador de Objetos + + + + + + + Preview Features + Características en versión preliminar + + + Enable unreleased preview features + Habilitar las características de la versión preliminar sin publicar + + + Show connect dialog on startup + Mostrar diálogo de conexión al inicio + + + Obsolete API Notification + Notificación de API obsoleta + + + Enable/disable obsolete API usage notification + Activar/desactivar la notificación de uso de API obsoleta + + + + + + + Problems + Problemas + + + + + + + Identifier of the account type + Identificador del tipo de cuenta + + + (Optional) Icon which is used to represent the accpunt in the UI. Either a file path or a themable configuration + (Opcional) El icono que se usa para representar el comando en la UI. Ya sea una ruta de acceso al archivo o una configuración con temas + + + Icon path when a light theme is used + Ruta del ícono cuando un tema light es usado + + + Icon path when a dark theme is used + Ruta de icono cuando se utiliza un tema oscuro + + + Contributes icons to account provider. + Contribuye los iconos al proveedor de la cuenta. + + + + + + + Indicates data property of a data set for a chart. + Indica la propiedad data de un conjunto de datos para un gráfico. + + + + + + + Minimum value of the y axis + Valor mínimo del eje y + + + Maximum value of the y axis + Valor máximo del eje y + + + Label for the y axis + Etiqueta para el eje y + + + Minimum value of the x axis + Valor mínimo del eje x + + + Maximum value of the x axis + Valor máximo del eje x + + + Label for the x axis + Etiqueta para el eje x + + + + + + + Displays the results in a simple table + Muestra los resultados en una tabla simple + + + + + + + Displays an image, for example one returned by an R query using ggplot2 + Muestra una imagen, por ejemplo uno devuelto por una consulta R con ggplot2 + + + What format is expected - is this a JPEG, PNG or other format? + ¿Qué formato se espera - es un archivo JPEG, PNG u otro formato? + + + Is this encoded as hex, base64 or some other format? + ¿Está codificado como hexadecimal, base64 o algún otro formato? + + + + + + + For each column in a resultset, displays the value in row 0 as a count followed by the column name. Supports '1 Healthy', '3 Unhealthy' for example, where 'Healthy' is the column name and 1 is the value in row 1 cell 1 + Para cada columna de un conjunto de resultados, muestra el valor de la fila 0 como un recuento seguido del nombre de la columna. Admite "1 Healthy", "3 Unhealthy", por ejemplo, donde "Healthy" es el nombre de la columna y 1 es el valor de la fila 1, celda 1 + + + + + + + Manage + Administrar + + + Dashboard + Panel + + + + + + + The webview that will be displayed in this tab. + La vista WebView que se mostrará en esta ficha. + + + + + + + The controlhost that will be displayed in this tab. + El controlhost que se mostrará en esta pestaña. + + + + + + + The list of widgets that will be displayed in this tab. + La lista de widgets que se muestran en esta ficha. + + + The list of widgets is expected inside widgets-container for extension. + La lista de widgets se espera dentro de widgets-container para la extensión. + + + + + + + The list of widgets or webviews that will be displayed in this tab. + La lista de widgets o webviews que se muestran en esta ficha. + + + widgets or webviews are expected inside widgets-container for extension. + Se esperan widgets o webviews en el contenedor de widgets para la extensión. + + + + + + + Unique identifier for this container. + Identificador único para este contenedor. + + + The container that will be displayed in the tab. + El contenedor que se mostrará en la ficha. + + + Contributes a single or multiple dashboard containers for users to add to their dashboard. + Contribuye con uno o múltiples contenedores de tablero para que los usuarios los adicionen en su propio tablero. + + + No id in dashboard container specified for extension. + No se ha especificado ningún identificador en el contenedor de paneles para la extensión. + + + No container in dashboard container specified for extension. + No hay ningún contenedor en el contenedor de paneles especificado para la extensión. + + + Exactly 1 dashboard container must be defined per space. + Debe definirse exactamente 1 contenedor de paneles por espacio. + + + Unknown container type defines in dashboard container for extension. + Tipo de contenedor desconocido definido en contenedor de paneles para la extensión. + + + + + + + The model-backed view that will be displayed in this tab. + La vista respaldada por el modelo que se visualiza en esta solapa. + + + + + + + Unique identifier for this nav section. Will be passed to the extension for any requests. + Identificador único para esta sección de navegación. Se pasará a la extensión de las solicitudes. + + + (Optional) Icon which is used to represent this nav section in the UI. Either a file path or a themeable configuration + (opcional) Icono que se utiliza para representar esta sección de navegación en la IU. Una ruta de archivo o una configuración temática + + + Icon path when a light theme is used + Ruta del ícono cuando un tema light es usado + + + Icon path when a dark theme is used + Ruta de icono cuando se utiliza un tema oscuro + + + Title of the nav section to show the user. + Título de la sección de navegación para mostrar al usuario. + + + The container that will be displayed in this nav section. + El contenedor que se mostrará en esta sección de navegación. + + + The list of dashboard containers that will be displayed in this navigation section. + La lista de contenedores de panel de instrumentos que se muestran en esta sección de navegación. + + + property `icon` can be omitted or must be either a string or a literal like `{dark, light}` + la propiedad "icon" puede omitirse o debe ser una cadena o un literal como "{dark, light}" + + + No title in nav section specified for extension. + No hay título en la sección de navegación especificada para la extensión. + + + No container in nav section specified for extension. + No hay contenedores en la sección de navegación especificada para la extensión. + + + Exactly 1 dashboard container must be defined per space. + Debe definirse exactamente 1 contenedor de paneles por espacio. + + + NAV_SECTION within NAV_SECTION is an invalid container for extension. + NAV_SECTION en NAV_SECTION es un contenedor no válido para la extensión. + + + + + + + Backup + Copia de seguridad + + + + + + + Unique identifier for this tab. Will be passed to the extension for any requests. + Identificador único para esta ficha. Se pasará a la extensión para cualquier solicitud. + + + Title of the tab to show the user. + Título de la pestaña para mostrar al usuario. + + + Description of this tab that will be shown to the user. + Descripción de esta pestaña que será mostrada al usuario. + + + Condition which must be true to show this item + Condición que se debe cumplir para mostrar este elemento + + + Defines the connection types this tab is compatible with. Defaults to 'MSSQL' if not set + Define los tipos de conexión con los que esta pestaña es compatible. El valor predeterminado es "MSSQL" si no se establece + + + The container that will be displayed in this tab. + El contenedor que será mostrado en esta pestaña. + + + Whether or not this tab should always be shown or only when the user adds it. + Si esta ficha se debe mostrar siempre, o sólo cuando el usuario la agrega. + + + Whether or not this tab should be used as the Home tab for a connection type. + Si esta pestaña se debe usar o no como la pestaña Inicio para un tipo de conexión. + + + Contributes a single or multiple tabs for users to add to their dashboard. + Aporta una o varias pestañas que los usuarios pueden agregar a su panel. + + + No title specified for extension. + No se ha especificado ningún título para la extensión. + + + No description specified to show. + No se ha especificado ninguna descripción para mostrar. + + + No container specified for extension. + Ningún contenedor especificado para la extensión. + + + Exactly 1 dashboard container must be defined per space + Debe definirse exactamente 1 contenedor de paneles por espacio + + + + + + + Restore + Restaurar + + + Restore + Restaurar + + + + + + + Cannot expand as the required connection provider '{0}' was not found + No se puede expandir porque no se encontró el proveedor de conexiones necesario "{0}" + + + User canceled + Usuario cancelado + + + Firewall dialog canceled + Diálogo de firewall cancelado + + + + + + + 1 or more tasks are in progress. Are you sure you want to quit? + 1 o más tareas están en curso. ¿Está seguro que desea salir? + + + Yes + Sí + + + No + No + + + + + + + Connection is required in order to interact with JobManagementService + Se necesita conexión para interactuar con JobManagementService + + + No Handler Registered + Ningún controlador registrado + + + + + + + An error occured while loading the file browser. + Se ha producido un error al cargar el navegador de archivos. + + + File browser error + Error del navegador de archivos + + + + + + + Notebook Editor + Editor de cuadernos + + + New Notebook + Nuevo Notebook + + + New Notebook + Nuevo Notebook + + + SQL kernel: stop Notebook execution when error occurs in a cell. + Kernel SQL: detenga la ejecución del cuaderno cuando se produzca un error en una celda. + + + + + + + Script as Create + Escribir como Crear + + + Script as Drop + Script como borrar + + + Select Top 1000 + Seleccionar el Top 1000 + + + Script as Execute + Secuencia de comandos, ejecutar + + + Script as Alter + Generar script como Alter + + + Edit Data + Editar datos + + + Select Top 1000 + Seleccionar el Top 1000 + + + Script as Create + Escribir como Crear + + + Script as Execute + Secuencia de comandos, ejecutar + + + Script as Alter + Generar script como Alter + + + Script as Drop + Script como borrar + + + Refresh + Actualizar + + + + + + + Connection error + Error de conexión + + + Connection failed due to Kerberos error. + Error en la conexión debido a un error de Kerberos. + + + Help configuring Kerberos is available at {0} + Ayuda para configurar Kerberos está disponible en {0} + + + If you have previously connected you may need to re-run kinit. + Si se ha conectado anteriormente puede que necesite volver a ejecutar kinit. + + + + + + + Refresh account was canceled by the user + El usuario canceló la actualización de la cuenta + + + + + + + Specifies view templates + Especifica las plantillas de la vista + + + Specifies session templates + Especifica plantillas de sesión + + + Profiler Filters + Filtros de Profiler + + + + + + + Toggle Query History + Alternar el historial de consultas + + + Delete + Eliminar + + + Clear All History + Borrar todo el historial + + + Open Query + Abrir consulta + + + Run Query + Ejecutar consulta + + + Toggle Query History capture + Alternar captura del historial de consultas + + + Pause Query History Capture + Pausar la captura del historial de consultas + + + Start Query History Capture + Iniciar la captura del historial de consultas + + + + + + + Preview features are required in order for extensions to be fully supported and for some actions to be available. Would you like to enable preview features? + Las características de versión preliminar se necesitan para que las extensiones sean plenamente compatibles y algunas acciones estén disponibles. ¿Desea habilitar las características de versión preliminar? + + + Yes + Sí + + + No + No + + + No, don't show again + No, no volver a mostrar + + + + + + + Commit row failed: + Someter el error de fila: + + + Started executing query "{0}" + Comenzó a ejecutar la consulta "{0}" + + + Update cell failed: + Error en la celda de actualización: + + + + + + + Failed to create Object Explorer session + Error al crear la sesión del explorador de objetos + + + Multiple errors: + Varios errores: + + + + + + + No URI was passed when creating a notebook manager + No se pasó ninguna URI al crear el administrador de cuadernos + + + Notebook provider does not exist + El proveedor de cuadernos no existe + + + + + + + Query Results + Resultados de consultas + + + Query Editor + Editor de consultas + + + New Query + Nueva consulta + + + [Optional] When true, column headers are included when saving results as CSV + [Opcional] Cuando es verdadero, los encabezados de columna se incluyen al guardar resultados como CSV + + + [Optional] The custom delimiter to use between values when saving as CSV + [Opcional] El delimitador personalizado a utilizar entre los valores al guardar como CSV + + + [Optional] Character(s) used for seperating rows when saving results as CSV + [Opcional] Caracter(es) utilizado(s) para separación de filas al guardar resultados como CSV + + + [Optional] Character used for enclosing text fields when saving results as CSV + [Opcional] Carácter que se utiliza para incluir campos de texto al guardar resultados como CSV + + + [Optional] File encoding used when saving results as CSV + [Opcional] Codificación de archivo utilizada al guardar resultados como CSV + + + Enable results streaming; contains few minor visual issues + Permitir resultados streaming; contiene algunos defectos visuales menores + + + [Optional] When true, XML output will be formatted when saving results as XML + [Opcional] Cuando es true, se dará formato a la salida XML al guardar resultados como XML + + + [Optional] File encoding used when saving results as XML + [Opcional] Se utiliza la codificación de archivo al guardar resultados como XML + + + [Optional] Configuration options for copying results from the Results View + [Opcional] Opciones de configuración para copiar resultados desde el punto de vista de resultados + + + [Optional] Configuration options for copying multi-line results from the Results View + [Opcional] Opciones de configuración para copiar resultados de varias líneas de la vista de resultados + + + [Optional] Should execution time be shown for individual batches + [Opcional] Si el tiempo de ejecución se mostrará para lotes individuales + + + [Optional] the default chart type to use when opening Chart Viewer from a Query Results + [Opcional] El tipo de gráfico predeterminado para usar al abrir el visor de gráficos desde los resultados de una consulta + + + Tab coloring will be disabled + Se deshabilitará el coloreado de las pestañas + + + The top border of each editor tab will be colored to match the relevant server group + El borde superior de cada pestaña del editor se coloreará para que coincida con el grupo de servidores correspondiente + + + Each editor tab's background color will match the relevant server group + Cada color de fondo de la ficha Editor coincidirá con el grupo de servidores pertinente + + + Controls how to color tabs based on the server group of their active connection + Controla cómo colorear las pestañas basadas en el grupo de servidor de la conexión activa + + + Controls whether to show the connection info for a tab in the title. + Controla si se muestra la información de conexión para una pestaña en el título. + + + Prompt to save generated SQL files + Solicitud para guardar los archivos SQL generados + + + Should IntelliSense be enabled + IntelliSense debe habilitarse + + + Should IntelliSense error checking be enabled + Debe habilitarse la comprobación de errores de IntelliSense + + + Should IntelliSense suggestions be enabled + Debe habilitar las sugerencias de IntelliSense + + + Should IntelliSense quick info be enabled + Si se habilita la información rápida de IntelliSense + + + Should IntelliSense suggestions be lowercase + Las sugerencias de IntelliSense deberían ser minúsculas + + + Maximum number of rows to return before the server stops processing your query. + Número máximo de filas para devolver antes de que el servidor deje de procesar la consulta. + + + Maximum size of text and ntext data returned from a SELECT statement + Tamaño máximo del texto y datos de ntext devueltos por una instrucción SELECT + + + An execution time-out of 0 indicates an unlimited wait (no time-out) + Un tiempo de espera de ejecución de 0 indica una espera ilimitada (sin tiempo de espera) + + + Enable SET NOCOUNT option + Habilitar la opción SET NOCOUNT + + + Enable SET NOEXEC option + Habilitar la opción SET NOEXEC + + + Enable SET PARSEONLY option + Habilitar la opción SET PARSEONLY + + + Enable SET ARITHABORT option + Habilitar la opción SET ARITHABORT + + + Enable SET STATISTICS TIME option + Habilitar la opción SET STATISTICS TIME + + + Enable SET STATISTICS IO option + Habilitar la opción SET STATISTICS IO + + + Enable SET XACT_ABORT ON option + Habilitar la opción SET XACT_ABORT ON + + + Enable SET TRANSACTION ISOLATION LEVEL option + Habilitar la opción SET TRANSACTION ISOLATION LEVEL + + + Enable SET DEADLOCK_PRIORITY option + Habilitar la opción SET DEADLOCK_PRIORITY + + + Enable SET LOCK TIMEOUT option (in milliseconds) + Habilitar la opción SET LOCK TIMEOUT (en milisegundos) + + + Enable SET QUERY_GOVERNOR_COST_LIMIT + Habilitar SET QUERY_GOVERNOR_COST_LIMIT + + + Enable SET ANSI_DEFAULTS + Habilitar SET ANSI_DEFAULTS + + + Enable SET QUOTED_IDENTIFIER + Habilitar SET QUOTED_IDENTIFIER + + + Enable SET ANSI_NULL_DFLT_ON + Habilitar SET ANSI_NULL_DFLT_ON + + + Enable SET IMPLICIT_TRANSACTIONS + Habilitar SET IMPLICIT_TRANSACTIONS + + + Enable SET CURSOR_CLOSE_ON_COMMIT + Habilitar SET CURSOR_CLOSE_ON_COMMIT + + + Enable SET ANSI_PADDING + Habilitar SET ANSI_PADDING + + + Enable SET ANSI_WARNINGS + Habilitar SET ANSI_WARNINGS + + + Enable SET ANSI_NULLS + Habilitar SET ANSI_NULLS + + + Set keybinding workbench.action.query.shortcut{0} to run the shortcut text as a procedure call. Any selected text in the query editor will be passed as a parameter + Establece los puntos clave workbench.action.query.shortcut{0} para ejecutar el acceso directo del texto como un procedimiento almacenado. Caulquier texto seleccionado en el editor de consultas será pasado como un parámetro. + + + + + + + Common id for the provider + Identificador común para el proveedor + + + Display Name for the provider + Nombre para mostrar del proveedor + + + Icon path for the server type + Ruta de acceso al icono para el tipo de servidor + + + Options for connection + Opciones para la conexión + + + + + + + OK + Aceptar + + + Close + Cerrar + + + Copy details + Copiar detalles + + + + + + + Add server group + Añadir Grupo de servidores + + + Edit server group + Editar grupo de servidores + + + + + + + Error adding account + Error al agregar cuenta + + + Firewall rule error + Error de regla de Firewall + + + + + + + Some of the loaded extensions are using obsolete APIs, please find the detailed information in the Console tab of Developer Tools window + Algunas de las extensiones cargadas utilizan API obsoletas, consulte la información detallada en la pestaña Consola de la ventana Herramientas de desarrollo + + + Don't Show Again + No mostrar de nuevo + + + + + + + Toggle Tasks + Alternar tareas + + + + + + + Show Connections + Mostrar conexiones + + + Servers + Servidores + + + + + + + Identifier of the view. Use this to register a data provider through `vscode.window.registerTreeDataProviderForView` API. Also to trigger activating your extension by registering `onView:${id}` event to `activationEvents`. + Identificador de la vista. Úselo para registrar un proveedor de datos mediante la API "vscode.window.registerTreeDataProviderForView". También para desencadenar la activación de su extensión al registrar el evento "onView:${id}" en "activationEvents". + + + The human-readable name of the view. Will be shown + Nombre de la vista en lenguaje natural. Será mostrado + + + Condition which must be true to show this view + Condición que se debe cumplir para mostrar esta vista + + + Contributes views to the editor + Aporta vistas al editor + + + Contributes views to Data Explorer container in the Activity bar + Aporta vistas al contenedor del Explorador de datos en la barra de la actividad + + + Contributes views to contributed views container + Contribuye vistas al contenedor de vistas aportadas + + + View container '{0}' does not exist and all views registered to it will be added to 'Data Explorer'. + El contenedor de vista "{0}" no existe y todas las vistas registradas en él se agregarán al "Explorador de datos". + + + Cannot register multiple views with same id `{0}` in the view container `{1}` + No pueden registrar múltiples vistas con el mismo identificador "{0}" en el contenedor de vistas "{1}" + + + A view with id `{0}` is already registered in the view container `{1}` + Una vista con el identificador "{0}" ya está registrada en el contenedor de vista "{1}" + + + views must be an array + Las vistas deben ser una matriz + + + property `{0}` is mandatory and must be of type `string` + la propiedad `{0}` es obligatoria y debe ser de tipo "string" + + + property `{0}` can be omitted or must be of type `string` + la propiedad `{0}` se puede omitir o debe ser de tipo "string" + + + + + + + Connection Status + Estado de conexión + + + + + + + Manage + Administrar + + + Show Details + Mostrar detalles + + + Learn How To Configure The Dashboard + Aprenda a configurar el panel + + + + + + + Widget used in the dashboards + Widget utilizado en los paneles + + + + + + + Displays results of a query as a chart on the dashboard + Muestra los resultados de una consulta como un gráfico en el panel de información + + + Maps 'column name' -> color. for example add 'column1': red to ensure this column uses a red color + Asigne "nombre de columna" -> color. Por ejemplo, agregue "column1": red para asegurarse de que esta utilice un color rojo + + + Indicates preferred position and visibility of the chart legend. These are the column names from your query, and map to the label of each chart entry + Indica la posición y visibilidad preferidas de la leyenda del gráfico. Estos son los nombres de columna de la consulta y se asignan a la etiqueta de cada entrada de gráfico + + + If dataDirection is horizontal, setting this to true uses the first columns value for the legend. + Si dataDirection es horizontal, al establecerlo como verdadero se utilizará los valores de las primeras columnas para la leyenda. + + + If dataDirection is vertical, setting this to true will use the columns names for the legend. + Si dataDirection es vertical, al configurarlo como verdadero utilizará los valores de las primeras columnas para la leyenda. + + + Defines whether the data is read from a column (vertical) or a row (horizontal). For time series this is ignored as direction must be vertical. + Define si se leen los datos de una fila (horizontal) o una columna (vertical). Para series de tiempo esto se omite la dirección debe ser vertical. + + + If showTopNData is set, showing only top N data in the chart. + Si se establece showTopNData, se mostrarán solo los primeros N datos en la tabla. + + + + + + + Condition which must be true to show this item + Condición que se debe cumplir para mostrar este elemento + + + The title of the container + El título del contenedor + + + The row of the component in the grid + La fila del componente en la cuadrícula + + + The rowspan of the component in the grid. Default value is 1. Use '*' to set to number of rows in the grid. + El intervalo de fila del componente en la cuadrícula. El valor predeterminado es 1. Use "*" para establecer el número de filas en la cuadrícula. + + + The column of the component in the grid + La columna del componente en la cuadrícula + + + The colspan of the component in the grid. Default value is 1. Use '*' to set to number of columns in the grid. + El colspan del componente en la cuadrícula. El valor predeterminado es 1. Use "*" para definir el número de columnas en la cuadrícula. + + + Unique identifier for this tab. Will be passed to the extension for any requests. + Identificador único para esta ficha. Se pasará a la extensión para cualquier solicitud. + + + Extension tab is unknown or not installed. + La pestaña extensión es desconocida o no está instalada. + + + + + + + Enable or disable the properties widget + Activar o desactivar el widget de propiedades + + + Property values to show + Valores de la propiedad a mostrar + + + Display name of the property + Nombre de la propiedad + + + Value in the Database Info Object + Valor en el objeto de la información de la base de datos + + + Specify specific values to ignore + Especificar valores específicos que ignorar + + + Recovery Model + Modelo de recuperación + + + Last Database Backup + Última copia de seguridad de la base de datos + + + Last Log Backup + Último backup del log + + + Compatibility Level + Nivel de compatibilidad + + + Owner + Propietario + + + Customizes the database dashboard page + Personaliza la página del panel de base de datos + + + Customizes the database dashboard tabs + Personaliza las pestañas de consola de base de datos + + + + + + + Enable or disable the properties widget + Activar o desactivar el widget de propiedades + + + Property values to show + Valores de la propiedad a mostrar + + + Display name of the property + Nombre de la propiedad + + + Value in the Server Info Object + Valor en el objeto de información de servidor + + + Version + Versión + + + Edition + Edición + + + Computer Name + Nombre del equipo + + + OS Version + Versión del sistema operativo + + + Customizes the server dashboard page + Personaliza la página de panel de servidor + + + Customizes the Server dashboard tabs + Personaliza las pestañas de consola de servidor + + + + + + + Manage + Administrar + + + + + + + Widget used in the dashboards + Widget utilizado en los paneles + + + Widget used in the dashboards + Widget utilizado en los paneles + + + Widget used in the dashboards + Widget utilizado en los paneles + + + + + + + Adds a widget that can query a server or database and display the results in multiple ways - as a chart, summarized count, and more + Añade un widget que puede consultar un servidor o base de datos y mostrar los resultados de múltiples maneras, como un gráfico o una cuenta de resumen, entre otras. + + + Unique Identifier used for caching the results of the insight. + Identificador único utilizado para almacenar en caché los resultados de la información. + + + SQL query to run. This should return exactly 1 resultset. + Consulta SQL para ejecutar. Esto debería devolver exactamente un conjunto de datos. + + + [Optional] path to a file that contains a query. Use if 'query' is not set + [opcional] Ruta de acceso a un archivo que contiene una consulta. Utilícelo si "query" no está establecido + + + [Optional] Auto refresh interval in minutes, if not set, there will be no auto refresh + [Opcional] Intervalo de actualización automática en minutos, si no se establece, no habrá actualización automática + + + Which actions to use + Qué acciones utilizar + + + Target database for the action; can use the format '${ columnName }' to use a data driven column name. + Base de datos de destino para la acción; puede usar el formato "${ columnName }" para usar un nombre de columna controlado por datos. + + + Target server for the action; can use the format '${ columnName }' to use a data driven column name. + Servidor de destino para la acción; puede usar el formato "'${ columnName }" para usar un nombre de columna controlado por datos. + + + Target user for the action; can use the format '${ columnName }' to use a data driven column name. + Usuario de destino para la acción; puede usar el formato "${ columnName }" para usar un nombre de columna controlado por datos. + + + Identifier of the insight + Identificador de la información + + + Contributes insights to the dashboard palette. + Aporta conocimientos a la paleta del panel. + + + + + + + Loading + Cargando + + + Loading completed + Carga completada + + + + + + + Defines a property to show on the dashboard + Define una propiedad para mostrar en el tablero + + + What value to use as a label for the property + Qué valor utilizar como etiqueta de la propiedad + + + What value in the object to access for the value + ¿Qué valor tiene el objeto para acceder por el valor? + + + Specify values to be ignored + Especificar los valores a ser ignorados + + + Default value to show if ignored or no value + Valor predeterminado para mostrar si se omite o no hay valor + + + A flavor for defining dashboard properties + Estilo para definir propiedades en un panel + + + Id of the flavor + ID del estilo + + + Condition to use this flavor + Condición para utilizar este tipo + + + Field to compare to + Campo para comparar con + + + Which operator to use for comparison + Operador a utilizar para la comparación + + + Value to compare the field to + Valor con que comparar el campo + + + Properties to show for database page + Propiedades a mostrar por página de base de datos + + + Properties to show for server page + Propiedades a mostrar de la página de servidor + + + Defines that this provider supports the dashboard + Define que este proveedor admite el panel de información + + + Provider id (ex. MSSQL) + Id de proveedor (por ejemplo MSSQL) + + + Property values to show on dashboard + Valores de las propiedades a mostrar en el tablero + + + + + + + Backup + Copia de seguridad + + + You must enable preview features in order to use backup + Debe habilitar las características de la versión preliminar para utilizar la copia de seguridad + + + Backup command is not supported for Azure SQL databases. + No se admite el comando de copia de seguridad para bases de datos de Azure SQL. + + + Backup command is not supported in Server Context. Please select a Database and try again. + No se admite el comando de copia de seguridad en el contexto del servidor. Seleccione una base de datos y vuelva a intentarlo. + + + + + + + Restore + Restaurar + + + You must enable preview features in order to use restore + Debe habilitar las características de versión preliminar para utilizar la restauración + + + Restore command is not supported for Azure SQL databases. + No se admite el comando de restauración para bases de datos de Azure SQL. + + + + + + + disconnected + Desconectado + + + + + + + Server Groups + Grupos de servidores + + + OK + Aceptar + + + Cancel + Cancelar + + + Server group name + Nombre del grupo de servidores + + + Group name is required. + Se requiere el nombre del grupo. + + + Group description + Descripción del grupo + + + Group color + Color de grupo + + + + + + + Extension + Extensión + + + + + + + OK + Aceptar + + + Cancel + Cancelar + + + + + + + Open dashboard extensions + Abrir extensiones de panel + + + OK + Aceptar + + + Cancel + Cancelar + + + No dashboard extensions are installed at this time. Go to Extension Manager to explore recommended extensions. + No hay extensiones de panel instaladas en este momento. Vaya a Extension Manager para explorar las extensiones recomendadas. + + + + + + + Selected path + Ruta seleccionada + + + Files of type + Archivos de tipo + + + OK + Aceptar + + + Discard + Descartar + + + + + + + No Connection Profile was passed to insights flyout + No se pasó ningún perfil de conexión al menú desplegable de información + + + Insights error + Error de insights + + + There was an error reading the query file: + Hubo un error leyendo el archivo de consulta: + + + There was an error parsing the insight config; could not find query array/string or queryfile + Error al analizar la configuración de la perspectiva; no se pudo encontrar la matriz/cadena de consulta o el archivo de consulta + + + + + + + Clear List + Borrar lista + + + Recent connections list cleared + Lista de conexiones recientes borrada + + + Yes + Sí + + + No + No + + + Are you sure you want to delete all the connections from the list? + ¿Está seguro que desea eliminar todas las conexiones de la lista? + + + Yes + Sí + + + No + No + + + Delete + Eliminar + + + Get Current Connection String + Obtener la cadena de conexión actual + + + Connection string not available + La cadena de conexión no está disponible + + + No active connection available + Ninguna conexión activa disponible + + + + + + + Refresh + Actualizar + + + Disconnect + Desconectar + + + New Connection + Nueva conexión + + + New Server Group + Nuevo grupo de servidores + + + Edit Server Group + Editar grupo de servidores + + + Show Active Connections + Mostrar conexiones activas + + + Show All Connections + Mostrar todas las conexiones + + + Recent Connections + Conexiones recientes + + + Delete Connection + Eliminar conexión + + + Delete Group + Eliminar grupo + + + + + + + Edit Data Session Failed To Connect + Editar datos de sesión no se pudo conectar + + + + + + + Profiler + Analizador + + + Not connected + No conectado + + + XEvent Profiler Session stopped unexpectedly on the server {0}. + La sesión de XEvent Profiler se detuvo inesperadamente en el servidor {0}. + + + Error while starting new session + Error al iniciar nueva sesión + + + The XEvent Profiler session for {0} has lost events. + La sesión de XEvent Profiler para {0} tiene eventos perdidos. + + + Would you like to stop the running XEvent session? + ¿Desea detener la sesión de XEvent en ejecución? + + + Yes + Sí + + + No + No + + + Cancel + Cancelar + + + + + + + Invalid value + Valor no válido + + + {0}. {1} + {0}. {1} + + + + + + + blank + vacío + + + + + + + Error displaying Plotly graph: {0} + Error al mostrar el gráfico de Plotly: {0} + + + + + + + No {0} renderer could be found for output. It has the following MIME types: {1} + No se ha encontrado ningún representador de {0} para la salida. Tiene los siguientes tipos MIME: {1} + + + (safe) + (seguro) + + + + + + + Item + Artículo + + + Value + Valor + + + Property + Propiedad + + + Value + Valor + + + Insights + Puntos de vista + + + Items + Artículos + + + Item Details + Detalles del artículo + + + + + + + Error adding account + Error al agregar cuenta + + + + + + + Cannot start auto OAuth. An auto OAuth is already in progress. + No se puede iniciar auto OAuth. Un auto OAuth ya está en progreso. + + + + + + + Sort by event + Ordenar por evento + + + Sort by column + Ordenar por columna + + + Profiler + Analizador + + + OK + Aceptar + + + Cancel + Cancelar + + + + + + + Clear all + Borrar todo + + + Apply + Aplicar + + + OK + Aceptar + + + Cancel + Cancelar + + + Filters + Filtros + + + Remove this clause + Quitar esta cláusula + + + Save Filter + Guardar filtro + + + Load Filter + Cargar filtro + + + Add a clause + Agregar una cláusula + + + Field + Campo + + + Operator + Operador + + + Value + Valor + + + Is Null + Es Null + + + Is Not Null + No es Null + + + Contains + Contiene + + + Not Contains + No contiene + + + Starts With + Comienza Por + + + Not Starts With + No comienza con + + + + + + + Double-click to edit + Haga doble clic para editar + + + + + + + Select Top 1000 + Seleccionar el Top 1000 + + + Script as Execute + Secuencia de comandos, ejecutar + + + Script as Alter + Generar script como Alter + + + Edit Data + Editar datos + + + Script as Create + Escribir como Crear + + + Script as Drop + Script como borrar + + + + + + + No queries to display. + No hay consultas que mostrar. + + + Query History + Historial de consultas + QueryHistory + + + + + + + Failed to get Azure account token for connection + Error al obtener el token de cuenta de Azure para conexión + + + Connection Not Accepted + Conexión no aceptada + + + Yes + Sí + + + No + No + + + Are you sure you want to cancel this connection? + ¿Está seguro que desea cancelar esta conexión? + + + + + + + Started executing query at + Comenzó la ejecución de la consulta en + + + Line {0} + Línea {0} + + + Canceling the query failed: {0} + Error al cancelar la consulta: {0} + + + Started saving results to + Comenzó a guardar los resultados en + + + Failed to save results. + Error al guardar los resultados. + + + Successfully saved results to + Correctamente guardados los resultados a + + + Executing query... + Ejecutando consulta... + + + Maximize + Maximizar + + + Restore + Restaurar + + + Save as CSV + Guardar como CSV + + + Save as JSON + Guardar como JSON + + + Save as Excel + Guardar como Excel + + + Save as XML + Guardar como XML + + + View as Chart + Ver como gráfico + + + Visualize + Visualizar + + + Results + Resultados + + + Executing query + Ejecutando consulta + + + Messages + Mensajes + + + Total execution time: {0} + Tiempo total de ejecución: {0} + + + Save results command cannot be used with multiple selections. + El comando Guardar resultados no puede ser usado con selecciones múltiples + + + + + + + Identifier of the notebook provider. + Identificador del proveedor del cuaderno. + + + What file extensions should be registered to this notebook provider + Extensiones de archivo que deben estar registradas en este proveedor de cuadernos + + + What kernels should be standard with this notebook provider + Qué núcleos deben ser estándar con este proveedor de cuadernos + + + Contributes notebook providers. + Aporta proveedores de cuadernos. + + + Name of the cell magic, such as '%%sql'. + Nombre de la magia de celda, como "%%sql". + + + The cell language to be used if this cell magic is included in the cell + El lenguaje de celda que se usará si este magic de celda se incluye en la celda + + + Optional execution target this magic indicates, for example Spark vs SQL + Objetivo de ejecución opcional indicado por este magic, por ejemplo Spark vs SQL + + + Optional set of kernels this is valid for, e.g. python3, pyspark, sql + Conjunto opcional de kernels para los que esto es válido, por ejemplo python3, pyspark, sql + + + Contributes notebook language. + Aporta el lenguaje del cuaderno. + + + + + + + SQL + SQL + + + + + + + F5 shortcut key requires a code cell to be selected. Please select a code cell to run. + La tecla de método abreviado F5 requiere que se seleccione una celda de código. Seleccione una para ejecutar. + + + Clear result requires a code cell to be selected. Please select a code cell to run. + Para borrar el resultado es necesario seleccionar una celda de código. Seleccione una para ejecutar. + + + + + + + Save As CSV + Guardar como CSV + + + Save As JSON + Guardar como JSON + + + Save As Excel + Guardar como Excel + + + Save As XML + Guardar como XML + + + Copy + Copiar + + + Copy With Headers + Copiar con encabezados + + + Select All + Seleccionar todo + + + + + + + Max Rows: + Máximo de filas: + + + + + + + Select View + Seleccionar vista + + + Select Session + Seleccione sesión + + + Select Session: + Seleccionar sesión: + + + Select View: + Seleccionar vista: + + + Text + Texto + + + Label + Etiqueta + + + Value + Valor + + + Details + Detalles + + + + + + + Copy failed with error {0} + Error de copia {0} + + + + + + + New Query + Nueva consulta + + + Run + Ejecutar + + + Cancel + Cancelar + + + Explain + Explicar + + + Actual + Actual + + + Disconnect + Desconectar + + + Change Connection + Cambiar conexión + + + Connect + Conectar + + + Enable SQLCMD + Habilitar SQLCMD + + + Disable SQLCMD + Desactivar SQLCMD + + + Select Database + Seleccione la base de datos + + + Select Database Toggle Dropdown + Seleccionar menú desplegable para alternar base de datos + + + Failed to change database + Error al cambiar la base de datos + + + Failed to change database {0} + No se pudo cambiar de base de datos {0} + + + + + + + Connection + Conexión + + + Connection type + Tipo de conexión + + + Recent Connections + Conexiones recientes + + + Saved Connections + Conexiones guardadas + + + Connection Details + Detalles de conexión + + + Connect + Conectar + + + Cancel + Cancelar + + + No recent connection + Ninguna conexión reciente + + + No saved connection + Ninguna conexión guardada + + + + + + + OK + Aceptar + + + Close + Cerrar + + + + + + + Loading kernels... + Cargando kernels... + + + Changing kernel... + Cambiando kernel... + + + Kernel: + Kernel: + + + Attach To: + Adjuntar a: + + + Loading contexts... + Cargando contextos... + + + Add New Connection + Agregar nueva conexión + + + Select Connection + Seleccionar conexión + + + localhost + localhost + + + Trusted + De confianza + + + Not Trusted + No de confianza + + + Notebook is already trusted. + El cuaderno ya es de confianza. + + + Collapse Cells + Contraer celdas + + + Expand Cells + Expandir celdas + + + No Kernel + Sin kernel + + + None + NONE + + + New Notebook + Nuevo Notebook + + + + + + + Time Elapsed + Tiempo transcurrido + + + Row Count + Recuento de filas + + + {0} rows + {0} filas + + + Executing query... + Ejecutando consulta... + + + Execution Status + Estado de ejecución + + + + + + + No task history to display. + No hay un historial de tareas para mostrar. + + + Task history + Historial de tareas + TaskHistory + + + Task error + Error de la tarea + + + + + + + Choose SQL Language + Elegir lenguaje SQL + + + Change SQL language provider + Cambiar proveedor de lenguaje SQL + + + SQL Language Flavor + Tipo de lenguaje SQL + + + Change SQL Engine Provider + Cambiar el proveedor de SQL Engine + + + A connection using engine {0} exists. To change please disconnect or change connection + Existe una conexión mediante el motor {0}. Para cambiar, por favor desconecte o cambie la conexión + + + No text editor active at this time + Ningún editor de texto activo en este momento + + + Select SQL Language Provider + Seleccione proveedor de lenguaje de SQL + + + + + + + All files + Todos los archivos + + + + + + + File browser tree + Ãrbol explorador de archivos + FileBrowserTree + + + + + + + From + Desde + + + To + Para + + + Create new firewall rule + Crear nueva regla de firewall + + + OK + Aceptar + + + Cancel + Cancelar + + + Your client IP address does not have access to the server. Sign in to an Azure account and create a new firewall rule to enable access. + Su dirección IP no tiene acceso al servidor. Inicie sesión con una cuenta de Azure y cree una regla en el firewall para permitir el acceso. + + + Learn more about firewall settings + Más información sobre configuración de firewall + + + Azure account + Cuenta de Azure + + + Firewall rule + Regla de Firewall + + + Add my client IP + Agregar mi IP de cliente + + + Add my subnet IP range + Añadir mi rango IP de subred + + + + + + + You need to refresh the credentials for this account. + Debe actualizar las credenciales para esta cuenta. + + + + + + + Could not find query file at any of the following paths : + {0} + No se pudo encontrar el archivo de consulta en ninguna de las siguientes rutas: + {0} + + + + + + + Add an account + Agregar una cuenta + + + Remove account + Eliminar cuenta + + + Are you sure you want to remove '{0}'? + ¿Está seguro que desea eliminar '{0}'? + + + Yes + Sí + + + No + No + + + Failed to remove account + Error al quitar cuenta + + + Apply Filters + Aplicar filtros + + + Reenter your credentials + Vuelva a introducir sus credenciales + + + There is no account to refresh + No hay ninguna cuenta para actualizar + + + + + + + Focus on Current Query + Centrarse en la consulta actual + + + Run Query + Ejecutar consulta + + + Run Current Query + Ejecutar la consulta actual + + + Run Current Query with Actual Plan + Ejecutar la consulta actual con el plan actual + + + Cancel Query + Cancelar consulta + + + Refresh IntelliSense Cache + Actualizar Cache de IntelliSense + + + Toggle Query Results + Alternar resultados de la consulta + + + Editor parameter is required for a shortcut to be executed + Parámetro de editor es necesario para un acceso directo a ejecutar + + + Parse Query + Analizar consulta + + + Commands completed successfully + Comandos completados correctamente + + + Command failed: + Error del comando: + + + Please connect to a server + Conéctese a un servidor + + + + + + + Chart cannot be displayed with the given data + No se puede mostrar el gráfico con los datos proporcionados + + + + + + + The index {0} is invalid. + El índice {0} no es válido. + + + + + + + no data available + no hay datos disponibles + + + + + + + Information + Información + + + Warning + Advertencia + + + Error + Error + + + Show Details + Mostrar detalles + + + Copy + Copiar + + + Close + Cerrar + + + Back + Atrás + + + Hide Details + Ocultar detalles + + + + + + + is required. + se requiere. + + + Invalid input. Numeric value expected. + Entrada no válida. Se espera un valor numérico. + + + + + + + Execution failed due to an unexpected error: {0} {1} + Ejecución falló debido a un error inesperado: {0} {1} + + + Total execution time: {0} + Tiempo total de ejecución: {0} + + + Started executing query at Line {0} + Comenzó a ejecutar la consulta en la línea {0} + + + Initialize edit data session failed: + Error al inicializar la sesión de edición de datos: + + + Batch execution time: {0} + Tiempo de ejecución por lotes: {0} + + + Copy failed with error {0} + Error de copia {0} + + + + + + + Error: {0} + Error: {0} + + + Warning: {0} + Advertencia: {0} + + + Info: {0} + Información: {0} + + + + + + + Copy Cell + Copiar celda + + + + + + + Backup file path + Ruta del archivo de copia de seguridad + + + Target database + Base de datos destino + + + Restore database + Restaurar base de datos + + + Restore database + Restaurar base de datos + + + Database + Base de datos + + + Backup file + Archivo de respaldo + + + Restore + Restaurar + + + Cancel + Cancelar + + + Script + Script + + + Source + ORIGEN + + + Restore from + Restaurar desde + + + Backup file path is required. + Se requiere la ruta de acceso del archivo de copia de seguridad. + + + Please enter one or more file paths separated by commas + Introduzca una o más rutas de archivo separadas por comas + + + Database + Base de datos + + + Destination + Destino + + + Select Database Toggle Dropdown + Seleccionar menú desplegable para alternar base de datos + + + Restore to + Restaurar a + + + Restore plan + Plan de recuperación + + + Backup sets to restore + Grupos de copias de seguridad a restaurar + + + Restore database files as + Restaurar archivos de base de datos como + + + Restore database file details + Restaurar detalles del archivo de base de datos + + + Logical file Name + Nombre lógico de archivo + + + File type + Tipo de archivo + + + Original File Name + Nombre de archivo original + + + Restore as + Restaurar como + + + Restore options + Opciones de restaurar + + + Tail-Log backup + Copia de seguridad del registro de cola + + + Server connections + Conexiones de servidor + + + General + General + + + Files + Archivos + + + Options + Opciones + + + + + + + Copy & Open + Copiar y Abrir + + + Cancel + Cancelar + + + User code + Código de usuario + + + Website + Sitio web + + + + + + + Done + hecho + + + Cancel + Cancelar + + + + + + + Must be an option from the list + Debe ser una opción de la lista + + + Toggle dropdown + Alternar menú desplegable + + + + + + + Select/Deselect All + Seleccionar o deseleccionar todos + + + checkbox checked + casilla seleccionada + + + checkbox unchecked + casilla de verificación desactivada + + + + + + + modelview code editor for view model. + Editor de código de modelview para modelo de vista. + + + + + + + succeeded + Se realizó correctamente + + + failed + fallido + + + + + + + Server Description (optional) + Descripción del servidor (opcional) + + + + + + + Advanced Properties + Propiedades avanzadas + + + Discard + Descartar + + + + + + + Linked accounts + Cuentas vinculadas + + + Close + Cerrar + + + There is no linked account. Please add an account. + No hay ninguna cuenta vinculada. Agregue una cuenta. + + + Add an account + Agregar una cuenta + + + + + + + nbformat v{0}.{1} not recognized + nbformat v{0}. {1} no reconocido + + + This file does not have a valid notebook format + Este archivo no tiene un formato válido de cuaderno + + + Cell type {0} unknown + Celda de tipo {0} desconocido + + + Output type {0} not recognized + Tipo de salida {0} no reconocido + + + Data for {0} is expected to be a string or an Array of strings + Se espera que los datos para {0} sean una cadena o una matriz de cadenas + + + Output type {0} not recognized + Tipo de salida {0} no reconocido + + + + + + + Profiler editor for event text. Readonly + Editor de Profiler para el texto del evento. Sólo lectura + + + + + + + Run Cells Before + Ejecutar celdas antes + + + Run Cells After + Ejecutar celdas después + + + Insert Code Before + Insertar el código antes de + + + Insert Code After + Insertar código después de + + + Insert Text Before + Insertar texto antes + + + Insert Text After + Insertar texto después + + + Collapse Cell + Contraer celda + + + Expand Cell + Expandir celda + + + Clear Result + Borrar resultado + + + Delete + Eliminar + + + + + + + No script was returned when calling select script on object + El script no fue devuelto cuando se llama a seleccionar script en el objeto + + + Select + Seleccionar + + + Create + Crear + + + Insert + Insertar + + + Update + Actualizar + + + Delete + Eliminar + + + No script was returned when scripting as {0} on object {1} + No se devolvió ningún script al escribir como {0} en el objeto {1} + + + Scripting Failed + Error de secuencias de comandos + + + No script was returned when scripting as {0} + No se devolvió ningún script al interpretado como {0} + + + + + + + Recent Connections + Conexiones recientes + + + Servers + Servidores + + + + + + + No Kernel + Sin kernel + + + Cannot run cells as no kernel has been configured + No se pueden ejecutar las celdas porque no se ha configurado ningún kernel + + + Error + Error + + + + + + + Azure Data Studio + Azure Data Studio + + + Start + Inicio + + + New connection + Nueva conexión + + + New query + Nueva consulta + + + New notebook + Nuevo Notebook + + + Open file + Abrir archivo + + + Open file + Abrir archivo + + + Deploy + Implementar + + + Deploy SQL Server… + Implementar SQL Server... + + + Recent + Reciente + + + More... + Más... + + + No recent folders + No hay ninguna carpeta reciente + + + Help + Ayuda + + + Getting started + Inicio + + + Documentation + Documentación + + + Report issue or feature request + Notificar problema o solicitud de características + + + GitHub repository + Repositorio de GitHub + + + Release notes + Notas de la versión + + + Show welcome page on startup + Mostrar página principal al inicio + + + Customize + Personalizar + + + Extensions + Extensiones + + + Download extensions that you need, including the SQL Server Admin pack and more + Descargue las extensiones que necesita, incluido el paquete de administración de SQL Server y mucho más + + + Keyboard Shortcuts + Métodos abreviados de teclado + + + Find your favorite commands and customize them + Encuentre sus comandos favoritos y personalícelos + + + Color theme + Tema de color + + + Make the editor and your code look the way you love + Modifique a su gusto la apariencia del editor y el código + + + Learn + Más información + + + Find and run all commands + Find and run all commands + + + Rapidly access and search commands from the Command Palette ({0}) + Acceda rápidamente y busque comandos desde la Paleta de Comandos ({0}) + + + Discover what's new in the latest release + Descubra las novedades de esta última versión + + + New monthly blog posts each month showcasing our new features + Nuevas entradas del blob mensuales que muestra nuestras nuevas características + + + Follow us on Twitter + Síganos en Twitter + + + Keep up to date with how the community is using Azure Data Studio and to talk directly with the engineers. + Manténgase al día sobre cómo la comunidad usa Azure Data Studio y hable directamente con los ingenieros. + + + + + + + succeeded + Se realizó correctamente + + + failed + fallido + + + in progress + En curso + + + not started + no se inició + + + canceled + cancelado + + + canceling + Cancelando + + + + + + + Run + Ejecutar + + + Dispose Edit Failed With Error: + Eliminar edición fallida con error: + + + Stop + Detener + + + Show SQL Pane + Mostrar panel de SQL + + + Close SQL Pane + Cerrar el panel SQL + + + + + + + Connect + Conectar + + + Disconnect + Desconectar + + + Start + Inicio + + + New Session + Nueva sesión + + + Pause + Pausa + + + Resume + Reanudar + + + Stop + Detener + + + Clear Data + Borrar los datos + + + Auto Scroll: On + Desplazamiento automático: activado + + + Auto Scroll: Off + Desplazamiento automático: desactivado + + + Toggle Collapsed Panel + Alternar el Panel contraído + + + Edit Columns + Editar columnas + + + Find Next String + Buscar la cadena siguiente + + + Find Previous String + Buscar la cadena anterior + + + Launch Profiler + Iniciar el generador de perfiles + + + Filter… + Filtrar... + + + Clear Filter + Borrar filtro + + + + + + + Events (Filtered): {0}/{1} + Eventos (filtrados): {0}/{1} + + + Events: {0} + Eventos: {0} + + + Event Count + Recuento de eventos + + + + + + + Save As CSV + Guardar como CSV + + + Save As JSON + Guardar como JSON + + + Save As Excel + Guardar como Excel + + + Save As XML + Guardar como XML + + + Save to file is not supported by the backing data source + El origen de datos de respaldo no admite la opción Guardar en archivo + + + Copy + Copiar + + + Copy With Headers + Copiar con encabezados + + + Select All + Seleccionar todo + + + Copy + Copiar + + + Copy All + Copiar todo + + + Maximize + Maximizar + + + Restore + Restaurar + + + Chart + Tabla + + + Visualizer + Visualizador + + + + + + + The extension "{0}" is using sqlops module which has been replaced by azdata module, the sqlops module will be removed in a future release. + La extensión "{0}" está utilizando el módulo sqlops que ha sido reemplazado por el módulo azdata, el módulo sqlops se eliminará en una versión futura. + + + + + + + Table header background color + Color de fondo de encabezado de tabla + + + Table header foreground color + Color de primer plano de encabezado de tabla + + + Disabled Input box background. + Fondo de Input box deshabilitado. + + + Disabled Input box foreground. + Se ha deshabilitado el cuadro de entrada en primer plano. + + + Button outline color when focused. + Contorno del botón remarcado cuando se enfoca + + + Disabled checkbox foreground. + Primer plano de la casilla de verificación deshabilitado. + + + List/Table background color for the selected and focus item when the list/table is active + Color de fondo de la lista/tabla para el elemento seleccionado y de enfoque cuando la lista/tabla está activa + + + SQL Agent Table background color. + Color de fondo de la tabla del Agente SQL. + + + SQL Agent table cell background color. + Color de fondo de celda de la tabla del Agente SQL. + + + SQL Agent table hover background color. + Color de fondo al mantener el mouse de la tabla del Agente SQL. + + + SQL Agent heading background color. + Color de fondo del encabezado del Agente SQL. + + + SQL Agent table cell border color. + Color del borde de la celda de la tabla del Agente SQL. + + + Results messages error color. + Color de error de mensajes de resultados. + + + + + + + Choose Results File + Seleccionar Archivo de Resultados + + + CSV (Comma delimited) + CSV (delimitado por comas) + + + JSON + JSON + + + Excel Workbook + Libro de Excel + + + XML + XML + + + Plain Text + Texto sin formato + + + Open file location + Abrir ubicación del archivo + + + Open file + Abrir archivo + + + + + + + Backup name + Nombre de copia de seguridad + + + Recovery model + Modelo de recuperación + + + Backup type + Tipo de copia de seguridad + + + Backup files + Archivos de copia de seguridad + + + Algorithm + Algoritmo + + + Certificate or Asymmetric key + Certificado o llave asimétrica + + + Media + Multimedia + + + Backup to the existing media set + Realizar la copia de seguridad sobre el conjunto de medios existente + + + Backup to a new media set + Realizar copia de seguridad en un nuevo conjunto de medios + + + Append to the existing backup set + Añadir al conjunto de copia de seguridad existente + + + Overwrite all existing backup sets + Sobrescribir todos los conjuntos de copia de seguridad existentes + + + New media set name + Nuevo nombre de conjunto de medios + + + New media set description + Nueva descripción del conjunto de medios + + + Perform checksum before writing to media + Realizar la suma de comprobación antes de escribir en los medios + + + Verify backup when finished + Comprobar copia de seguridad cuando haya terminado + + + Continue on error + Seguir en caso de error + + + Expiration + Expiración + + + Set backup retain days + Configure los días de conservación de la copia de seguridad + + + Copy-only backup + Respaldo solo de copia de seguridad + + + Advanced Configuration + Configuración avanzada + + + Compression + Compresión + + + Set backup compression + Establecer la compresión de copia de seguridad + + + Encryption + Cifrado + + + Transaction log + Registro de transacciones + + + Truncate the transaction log + Truncar el registro de transacciones + + + Backup the tail of the log + Hacer copia de seguridad de la cola del registro + + + Reliability + Fiabilidad + + + Media name is required + Nombre del Medio es requerido + + + No certificate or asymmetric key is available + No hay certificado o clave asimétrica disponible + + + Add a file + Agregar un archivo + + + Remove files + Eliminar archivos + + + Invalid input. Value must be greater than or equal 0. + Entrada no válida. Valor debe ser igual o mayor que 0. + + + Script + Script + + + Backup + Copia de seguridad + + + Cancel + Cancelar + + + Only backup to file is supported + Únicamente respaldo a archivo es soportado + + + Backup file path is required + La ruta del archivo de copia de seguridad es obligatoria + + + + + + + Results + Resultados + + + Messages + Mensajes + + + + + + + There is no data provider registered that can provide view data. + No hay ningún proveedor de datos registrado que pueda proporcionar datos de la vista. + + + Collapse All + Contraer todo + + + + + + + Home + Página de inicio + + + + + + + The "{0}" section has invalid content. Please contact extension owner. + La sección "{0}" tiene contenido no válido. Póngase en contacto con el propietario de la extensión. + + + + + + + Jobs + Trabajos + + + Notebooks + Notebooks + + + Alerts + Alertas + + + Proxies + Servidores proxy + + + Operators + Operadores + + + + + + + Loading + Cargando + + + + + + + SERVER DASHBOARD + CONSOLA DE SERVIDOR + + + + + + + DATABASE DASHBOARD + Panel de base de datos + + + + + + + Edit + Editar + + + Exit + Salir + + + Refresh + Actualizar + + + Toggle More + Alternar más + + + Delete Widget + Eliminar Widget + + + Click to unpin + Haga clic para liberar + + + Click to pin + Haga clic para anclar + + + Open installed features + Abrir funciones instaladas + + + Collapse + Contraer + + + Expand + Expandir + + + + + + + Steps + Pasos + + + + + + + StdIn: + Stdin: + + + + + + + Add code + Agregar código + + + Add text + Agregar texto + + + Create File + Crear archivo + + + Could not display contents: {0} + No se pudo mostrar contenido: {0} + + + Please install the SQL Server 2019 extension to run cells. + Instale la extensión de SQL Server 2019 para ejecutar celdas. + + + Install Extension + Instalar extensión + + + Code + Código + + + Text + Texto + + + Run Cells + Ejecutar celdas + + + Clear Results + Borrar resultados + + + < Previous + < Anterior + + + Next > + Siguiente > + + + cell with URI {0} was not found in this model + no se encontró la celda con URI {0} en este modelo + + + Run Cells failed - See error in output of the currently selected cell for more information. + Error al ejecutar las celdas. Para más información, vea el error en la salida de la celda seleccionada actualmente. + + + + + + + Click on + Haga clic en + + + + Code + + Código + + + or + O + + + + Text + + Texto + + + to add a code or text cell + para agregar una celda de código o texto + + + + + + + Database + Base de datos + + + Files and filegroups + Archivos y grupos de archivos + + + Full + Completa + + + Differential + Diferencial + + + Transaction Log + Registro de transacciones + + + Disk + Disco + + + Url + URL + + + Use the default server setting + Utilizar la configuración del servidor predeterminada + + + Compress backup + Comprimir copia de seguridad + + + Do not compress backup + No comprimir copia de seguridad + + + Server Certificate + Certificado de servidor + + + Asymmetric Key + Clave asimétrica + + + Backup Files + Archivos de copia de seguridad + + + All Files + Todos los archivos + + + + + + + No connections found. + No se ha encontrado una conexión. + + + Add Connection + Agregar conexión + + + + + + + Failed to change database + Error al cambiar la base de datos + + + + + + + Name + Nombre + + + Email Address + Dirección de correo electrónico + + + Enabled + Habilitado + + + + + + + Name + Nombre + + + Last Occurrence + Última aparición + + + Enabled + Habilitado + + + Delay Between Responses (in secs) + Retraso entre las respuestas (en segundos) + + + Category Name + Nombre de la categoría + + + + + + + Account Name + Nombre de la cuenta + + + Credential Name + Nombre de credencial + + + Description + Descripción + + + Enabled + Habilitado + + + + + + + Unable to load dashboard properties + No se puede cargar las propiedades del panel + + + + + + + Search by name of type (a:, t:, v:, f:, or sp:) + Búsqueda por nombre de tipo (a:, t:, v:, f: o sp:) + + + Search databases + Buscar en las bases de datos + + + Unable to load objects + No se pueden cargar objetos + + + Unable to load databases + No se pueden cargar las bases de datos + + + + + + + Auto Refresh: OFF + Actualización automática: OFF + + + Last Updated: {0} {1} + Última actualización: {0} {1} + + + No results to show + No hay resultados para mostrar + + + + + + + No {0}renderer could be found for output. It has the following MIME types: {1} + No se ha encontrado ningún representador de {0} para la salida. Tiene los siguientes tipos MIME: {1} + + + safe + Seguro + + + No component could be found for selector {0} + No se encontró un componente para el selector {0} + + + Error rendering component: {0} + Error al representar el componente: {0} + + + + + + + Connected to + Conectado a + + + Disconnected + Desconectado + + + Unsaved Connections + Conexiones sin guardar + + + + + + + Delete Row + Eliminar fila + + + Revert Current Row + Revertir la fila actual + + + + + + + Step ID + Id. de paso + + + Step Name + Nombre del paso + + + Message + Mensaje + + + + + + + XML Showplan + Plan de presentación XML + + + Results grid + Cuadrícula de resultados + + + + + + + Please select active cell and try again + Seleccione la celda activa y vuelva a intentarlo + + + Run cell + Ejecutar celda + + + Cancel execution + Cancelar ejecución + + + Error on last run. Click to run again + Error en la última ejecución. Haga clic para volver a ejecutar + + + + + + + Add an account... + Agregar una cuenta... + + + <Default> + <Predeterminado> + + + Loading... + Cargando... + + + Server group + Grupo de servidores + + + <Default> + <Predeterminado> + + + Add new group... + Agregar nuevo grupo... + + + <Do not save> + <No guardar> + + + {0} is required. + {0} es necesario. + + + {0} will be trimmed. + {0} se recortará. + + + Remember password + Recordar contraseña + + + Account + Cuenta + + + Refresh account credentials + Actualizar credenciales de la cuenta + + + Azure AD tenant + Inquilino de Azure AD + + + Select Database Toggle Dropdown + Seleccionar menú desplegable para alternar base de datos + + + Name (optional) + Nombre (opcional) + + + Advanced... + Avanzado... + + + You must select an account + Debe seleccionar una cuenta + + + + + + + Cancel + Cancelar + + + The task is failed to cancel. + La tarea no se cancelará. + + + Script + Script + + + + + + + Date Created: + Fecha de creación: + + + Notebook Error: + Error del cuaderno: + + + Job Error: + Error de trabajo: + + + Pinned + Fijado + + + Recent Runs + Ejecuciones recientes + + + Past Runs + Ejecuciones pasadas + + + + + + + No tree view with id '{0}' registered. + No se ha registrado ninga vista del árbol con id '{0}'. + + + + + + + Loading... + Cargando... + + + + + + + Dashboard Tabs ({0}) + Pestañas del panel ({0}) + + + Id + ID. + + + Title + Título + + + Description + Descripción + + + Dashboard Insights ({0}) + Información del panel ({0}) + + + Id + ID. + + + Name + Nombre + + + When + Cuando + + + + + + + Chart + Tabla + + + + + + + Operation + Operación + + + Object + Objeto + + + Est Cost + Costo estimado + + + Est Subtree Cost + Estimación de coste del subárbol + + + Actual Rows + Filas actuales + + + Est Rows + Filas estimadas + + + Actual Executions + Ejecuciones reales + + + Est CPU Cost + Costo estimado de CPU + + + Est IO Cost + Costo estimado de IO + + + Parallel + Paralelo + + + Actual Rebinds + Reenlaces reales + + + Est Rebinds + Reenlaces estimados + + + Actual Rewinds + 'Rewinds' reales + + + Est Rewinds + Rebobinados estimados + + + Partitioned + Particionado + + + Top Operations + Operaciones principales + + + + + + + Query Plan + Plan de consulta + + + + + + + Could not find component for type {0} + No pudo encontrar el componente para el tipo {0} + + + + + + + A NotebookProvider with valid providerId must be passed to this method + Un NotebookProvider con providerId valido se debe pasar a este método + + + + + + + A NotebookProvider with valid providerId must be passed to this method + Un NotebookProvider con providerId valido se debe pasar a este método + + + no notebook provider found + no se encontró ningún proveedor de cuadernos + + + No Manager found + No se encontró ningún administrador + + + Notebook Manager for notebook {0} does not have a server manager. Cannot perform operations on it + La instancia de Notebook Manager para el cuaderno {0} no tiene un administrador de servidores. No puede realizar operaciones en él + + + Notebook Manager for notebook {0} does not have a content manager. Cannot perform operations on it + Notebook Manager para el cuaderno {0} no tiene un administrador de contenidos. No puede realizar operaciones en él + + + Notebook Manager for notebook {0} does not have a session manager. Cannot perform operations on it + La instancia de Notebook Manager para el cuaderno {0} no tiene un administrador de sesión. No se pueden realizar operaciones en él. + + + + + + + SQL kernel error + Error del kernel SQL + + + A connection must be chosen to run notebook cells + Se debe elegir una conexión para ejecutar celdas de cuaderno + + + Displaying Top {0} rows. + Mostrando las primeras {0} filas. + + + + + + + Show Recommendations + Mostrar recomendaciones + + + Install Extensions + Instalar extensiones + + + + + + + Name + Nombre + + + Last Run + Última ejecución + + + Next Run + Próxima ejecución + + + Enabled + Habilitado + + + Status + Estado + + + Category + Categoría + + + Runnable + Se puede ejecutar + + + Schedule + Programación + + + Last Run Outcome + Último resultado ejecutado + + + Previous Runs + Ejecuciones anteriores + + + No Steps available for this job. + No hay pasos disponibles para este trabajo. + + + Error: + Error: + + + + + + + Find + Buscar + + + Find + Buscar + + + Previous match + Coincidencia anterior + + + Next match + Próxima coincidencia + + + Close + Cerrar + + + Your search returned a large number of results, only the first 999 matches will be highlighted. + La búsqueda ha devuelto un gran número de resultados, se resaltarán solo las primeras 999 coincidencias. + + + {0} of {1} + {0} de {1} + + + No Results + No hay resultados + + + + + + + Run Query + Ejecutar consulta + + + + + + + Done + hecho + + + Cancel + Cancelar + + + Generate script + Generar secuencia de comandos + + + Next + Siguiente + + + Previous + Anterior + + + + + + + A server group with the same name already exists. + Ya existe un grupo de servidores con el mismo nombre. + + + + + + + {0} is an unknown container. + {0} es un contenedor desconocido. + + + + + + + Loading Error... + Error de carga... + + + + + + + Failed + fallido + + + Succeeded + Se realizó correctamente + + + Retry + Reintentar + + + Cancelled + Cancelado + + + In Progress + En curso + + + Status Unknown + Estado desconocido + + + Executing + En ejecución + + + Waiting for Thread + Esperando subproceso + + + Between Retries + Entre reintentos + + + Idle + Inactivo + + + Suspended + Suspendido + + + [Obsolete] + [Obsoleto] + + + Yes + Sí + + + No + No + + + Not Scheduled + No programado + + + Never Run + No ejecutar nunca + + + + + + + Name + Nombre + + + Target Database + Base de datos destino + + + Last Run + Última ejecución + + + Next Run + Próxima ejecución + + + Status + Estado + + + Last Run Outcome + Último resultado ejecutado + + + Previous Runs + Ejecuciones anteriores + + + No Steps available for this job. + No hay pasos disponibles para este trabajo. + + + Error: + Error: + + + Notebook Error: + Error del cuaderno + + + + + + + Home + Página de inicio + + + No connection information could be found for this dashboard + No se encontró información de conexión para este panel + + + + + + + Data + Datos + + + Connection + Conexión + + + Query + Consulta + + + Notebook + Bloc de notas + + + SQL + SQL + + + Microsoft SQL Server + Microsoft SQL Server + + + Dashboard + Panel + + + Profiler + Analizador + + + + + + + Close + Cerrar + + + + + + + Success + Éxito + + + Error + Error + + + Refresh + Actualizar + + + New Job + Nuevo trabajo + + + Run + Ejecutar + + + : The job was successfully started. + : El trabajo se inició correctamente. + + + Stop + Detener + + + : The job was successfully stopped. + : El trabajo se detuvo correctamente. + + + Edit Job + Editar trabajo + + + Open + Abrir + + + Delete Job + Eliminar trabajo + + + Are you sure you'd like to delete the job '{0}'? + ¿Sguro que desea borrar el trabajo "{0}"? + + + Could not delete job '{0}'. +Error: {1} + No se pudo eliminar el trabajo "{0}". +Error: {1} + + + The job was successfully deleted + El trabajo se eliminó correctamente + + + New Step + Nuevo paso + + + Delete Step + Eliminar paso + + + Are you sure you'd like to delete the step '{0}'? + ¿Seguro que desea borrar el paso "{0}"? + + + Could not delete step '{0}'. +Error: {1} + No se puede eliminar el paso "{0}". +Error: {1} + + + The job step was successfully deleted + El paso del trabajo se eliminó correctamente + + + New Alert + Nueva alerta + + + Edit Alert + Editar alerta + + + Delete Alert + Eliminar alerta + + + Cancel + Cancelar + + + Are you sure you'd like to delete the alert '{0}'? + ¿Seguro que desea borrar la alerta "{0}"? + + + Could not delete alert '{0}'. +Error: {1} + No se pudo eliminar alerta "{0}". +Error: {1} + + + The alert was successfully deleted + La alerta se eliminó correctamente + + + New Operator + Nuevo operador + + + Edit Operator + Operador de edición + + + Delete Operator + Eliminar operador + + + Are you sure you'd like to delete the operator '{0}'? + ¿Realmente desea eliminar el operador "{0}"? + + + Could not delete operator '{0}'. +Error: {1} + No se pudo eliminar el operador "{0}". +Error: {1} + + + The operator was deleted successfully + El operador se eliminó correctamente + + + New Proxy + Nuevo proxy + + + Edit Proxy + Editar Proxy + + + Delete Proxy + Eliminar Proxy + + + Are you sure you'd like to delete the proxy '{0}'? + ¿Está seguro que desea borrar al proxy '{0}'? + + + Could not delete proxy '{0}'. +Error: {1} + No se pudo eliminar el proxy "{0}". +Error: {1} + + + The proxy was deleted successfully + El proxy se eliminó correctamente + + + New Notebook Job + Nuevo trabajo de cuaderno + + + Edit + Editar + + + Open Template Notebook + Abrir cuaderno de plantilla + + + Delete + Eliminar + + + Are you sure you'd like to delete the notebook '{0}'? + ¿Seguro que desea eliminar el cuaderno "{0}"? + + + Could not delete notebook '{0}'. +Error: {1} + No se pudo eliminar el cuaderno "{0}". +Error: {1} + + + The notebook was successfully deleted + El cuaderno se eliminó correctamente + + + Pin + Anclar + + + Delete + Eliminar + + + Unpin + Desanclar + + + Rename + Cambiar nombre + + + Open Latest Run + Abrir última ejecución + + + + + + + Please select a connection to run cells for this kernel + Seleccione una conexión para ejecutar celdas para este kernel + + + Failed to delete cell. + No se pudo eliminar la celda. + + + Failed to change kernel. Kernel {0} will be used. Error was: {1} + Error al cambiar de kernel. Se utilizará el kernel {0}. Error: {1} + + + Failed to change kernel due to error: {0} + No se pudo cambiar el kernel debido al error: {0} + + + Changing context failed: {0} + Error en el cambio de contexto: {0} + + + Could not start session: {0} + No se pudo iniciar sesión: {0} + + + A client session error occurred when closing the notebook: {0} + Se ha producido un error en la sesión del cliente al cerrar el cuaderno: {0} + + + Can't find notebook manager for provider {0} + No puede encontrar el administrador de cuadernos para el proveedor {0} + + + + + + + An error occurred while starting the notebook session + Error al iniciar la sesión del cuaderno + + + Server did not start for unknown reason + El servidor no se pudo iniciar por una razón desconocida + + + Kernel {0} was not found. The default kernel will be used instead. + No se encontró el kernel {0}. En su lugar, se utilizará el kernel predeterminado. + + + + + + + Unknown component type. Must use ModelBuilder to create objects + Tipo de componente desconocido. Debe utilizar ModelBuilder para crear objetos + + + The index {0} is invalid. + El índice {0} no es válido. + + + Unkown component configuration, must use ModelBuilder to create a configuration object + Configuración del componente desconocida, debe usar ModelBuilder para crear un objeto de configuración + + + + + + + Horizontal Bar + Barra horizontal + + + Bar + Barras + + + Line + Líneas + + + Pie + Circular + + + Scatter + Dispersión + + + Time Series + Serie temporal + + + Image + Imagen + + + Count + Recuento + + + Table + Tabla + + + Doughnut + Anillo + + + + + + + OK + Aceptar + + + Clear + Borrar + + + Cancel + Cancelar + + + + + + + Cell execution cancelled + Ejecución de celdas cancelada + + + Query execution was canceled + Se canceló la ejecución de la consulta + + + The session for this notebook is not yet ready + La sesión para este cuaderno aún no está lista + + + The session for this notebook will start momentarily + La sesión para este cuaderno comenzará momentáneamente + + + No kernel is available for this notebook + No hay ningún kernel disponible para este cuaderno + + + + + + + Select Connection + Seleccionar conexión + + + localhost + localhost + + + + + + + Data Direction + Dirección de datos + + + Vertical + Vertical + + + Horizontal + Horizontal + + + Use column names as labels + Usar nombres de columna como etiquetas + + + Use first column as row label + Usar la primera columna como etiqueta de fila + + + Legend Position + Posición de la leyenda + + + Y Axis Label + Etiqueta de eje Y + + + Y Axis Minimum Value + Valor mínimo del eje Y + + + Y Axis Maximum Value + Valor máximo del eje Y + + + X Axis Label + Etiqueta del eje X + + + X Axis Minimum Value + Valor mínimo del eje X + + + X Axis Maximum Value + Valor máximo del eje X + + + X Axis Minimum Date + Fecha mínima del eje X + + + X Axis Maximum Date + Fecha máxima para el eje X + + + Data Type + Tipo de datos + + + Number + Número + + + Point + Punto + + + Chart Type + Tipo de gráfico + + + Encoding + codificación + + + Image Format + Formato de imagen + + + + + + + Create Insight + Crear Insight + + + Cannot create insight as the active editor is not a SQL Editor + No se puede crear la perspectiva porque el editor activo no es un Editor de SQL + + + My-Widget + Mi Widget + + + Copy as image + Copiar como imagen + + + Could not find chart to save + No se pudo encontrar el gráfico a guardar + + + Save as image + Guardar como imagen + + + PNG + Png + + + Saved Chart to path: {0} + Gráfico guardado en ruta: {0} + + + + + + + Changing editor types on unsaved files is unsupported + No se admite el cambio de tipos de editor en archivos no guardados + + + + + + + Table does not contain a valid image + La tabla no contiene una imagen válida + + + + + + + Series {0} + Serie {0} diff --git a/resources/xlf/fr/azurecore.fr.xlf b/resources/xlf/fr/azurecore.fr.xlf index 98a2af32052c..5a203ecfedf8 100644 --- a/resources/xlf/fr/azurecore.fr.xlf +++ b/resources/xlf/fr/azurecore.fr.xlf @@ -200,4 +200,20 @@ + + + + SQL Managed Instances + SQL Managed Instances + + + + + + + Azure Database for PostgreSQL Servers + Base de données Azure pour les serveurs PostgreSQL + + + \ No newline at end of file diff --git a/resources/xlf/fr/big-data-cluster.fr.xlf b/resources/xlf/fr/big-data-cluster.fr.xlf index 292485036ca1..9d893c0d3e2e 100644 --- a/resources/xlf/fr/big-data-cluster.fr.xlf +++ b/resources/xlf/fr/big-data-cluster.fr.xlf @@ -38,6 +38,14 @@ Delete Mount Supprimer le montage + + Big Data Cluster + Cluster Big Data + + + Ignore SSL verification errors against SQL Server Big Data Cluster endpoints such as HDFS, Spark, and Controller if true + Ignorer les erreurs de vérification SSL sur les points de terminaison de cluster Big Data SQL Server de type HDFS, Spark et Contrôleur si la valeur est true + @@ -58,26 +66,86 @@ Deleting Suppression - - Waiting For Deletion - En attente de suppression - Deleted Supprimé + + Applying Upgrade + Application de la mise à niveau + Upgrading Mise à niveau - - Waiting For Upgrade - En attente de mise à niveau + + Applying Managed Upgrade + Application de la mise à niveau gérée + + + Managed Upgrading + Mise à niveau gérée + + + Rollback + restaurer + + + Rollback In Progress + Restauration en cours + + + Rollback Complete + Restauration effectuée Error Erreur + + Creating Secrets + Création de secrets + + + Waiting For Secrets + En attente des secrets + + + Creating Groups + Création de groupes + + + Waiting For Groups + Attente de groupes + + + Creating Resources + Création de ressources + + + Waiting For Resources + En attente des ressources + + + Creating Kerberos Delegation Setup + Création de la configuration de délégation Kerberos + + + Waiting For Kerberos Delegation Setup + En attente de configuration de la délégation Kerberos + + + Waiting For Deletion + En attente de suppression + + + Waiting For Upgrade + En attente de mise à niveau + + + Upgrade Paused + Mise à niveau suspendue + Running En cours d'exécution @@ -162,6 +230,78 @@ Unhealthy Non sain + + Unexpected error retrieving BDC Endpoints: {0} + Erreur inattendue pendant la récupération des points de terminaison BDC : {0} + + + + + + + Basic + De base + + + Windows Authentication + Authentification Windows + + + Login to controller failed + La connexion au contrôleur a échoué + + + Login to controller failed: {0} + La connexion au contrôleur a échoué : {0} + + + Username is required + Nom d'utilisateur obligatoire + + + Password is required + Mot de passe obligatoire + + + url + URL + + + username + Nom d'utilisateur + + + password + Mot de passe + + + Cluster Management URL + URL de gestion de cluster + + + Authentication type + Type d'authentification  + + + Username + Nom d'utilisateur + + + Password + Mot de passe + + + Cluster Connection + Connexion du cluster + + + OK + OK + + + Cancel + Annuler + @@ -200,6 +340,22 @@ + + + + Connect to Controller (preview) + Se connecter au contrôleur (préversion) + + + + + + + View Details + Voir les détails + + + @@ -207,8 +363,8 @@ Informations de point de terminaison du contrôleur introuvables - Big Data Cluster Dashboard - - Tableau de bord du cluster Big Data - + Big Data Cluster Dashboard (preview) - + Tableau de bord de cluster Big Data (préversion) - Yes @@ -263,8 +419,8 @@ Mot de passe obligatoire - Add New Controller - Ajouter un nouveau contrôleur + Add New Controller (preview) + Ajouter un nouveau contrôleur (préversion) url @@ -283,8 +439,8 @@ Se souvenir du mot de passe - URL - URL + Cluster Management URL + URL de gestion de cluster Authentication type @@ -319,7 +475,7 @@ Résoudre les problèmes - Big data cluster overview + Big Data Cluster overview Vue d'ensemble du cluster Big Data @@ -362,18 +518,18 @@ Metrics and Logs Métriques et journaux - - Metrics - Métriques + + Node Metrics + Métriques de noeud + + + SQL Metrics + Métriques SQL Logs Journaux - - View Details - Voir les détails - @@ -422,31 +578,35 @@ Endpoint Point de terminaison - - View Details - Voir les détails + + Unexpected error retrieving BDC Endpoints: {0} + Erreur inattendue pendant la récupération des points de terminaison BDC : {0} + + + The dashboard requires a connection. Please click retry to enter your credentials. + Le tableau de bord nécessite une connexion. Cliquez sur Réessayer pour entrer vos informations d'identification. + + + Unexpected error occurred: {0} + Erreur inattendue : {0} Copy Copier + + Endpoint '{0}' copied to clipboard + Point de terminaison '{0}' copié dans le Presse-papiers + - - Basic - De base - - - Windows Authentication - Authentification Windows - Mount Configuration Configuration du montage - + HDFS Path Chemin HDFS @@ -454,22 +614,6 @@ Bad formatting of credentials at {0} Mise en forme incorrecte des informations d'identification sur {0} - - Login to controller failed - La connexion au contrôleur a échoué - - - Login to controller failed: {0} - La connexion au contrôleur a échoué : {0} - - - Username is required - Le nom d'utilisateur est obligatoire - - - Password is required - Mot de passe obligatoire - Mounting HDFS folder on path {0} Montage du dossier HDFS sur le chemin {0} @@ -494,58 +638,30 @@ Unknown error occurred during the mount process Une erreur inconnue s'est produite pendant le processus de montage - - url - URL - - - username - Nom d'utilisateur - - - password - Mot de passe - - - URL - URL - - - Authentication type - Type d'authentification  - - - Username - Nom d'utilisateur - - - Password - Mot de passe - - - Cluster Connection - Connexion du cluster - - - OK - OK - - - Cancel - Annuler - - Mount HDFS Folder - Monter le dossier HDFS + Mount HDFS Folder (preview) + Monter le dossier HDFS (préversion) + + + Path to a new (non-existing) directory which you want to associate with the mount + Chemin d'un nouveau répertoire (non existant) à associer au montage - + Remote URI URI distant - + + The URI to the remote data source. Example for ADLS: abfs://fs@saccount.dfs.core.windows.net/ + URI de la source de données distante. Exemple pour ADLS : abfs://fs@saccount.dfs.core.windows.net/ + + Credentials Informations d'identification + + Mount credentials for authentication to remote data source for reads + Informations d'identification de montage pour l'authentification auprès de la source de données distante pour les lectures + Refresh Mount Actualiser le montage diff --git a/resources/xlf/fr/sql.fr.xlf b/resources/xlf/fr/sql.fr.xlf index f6d23f92b6f5..f3568a6d068b 100644 --- a/resources/xlf/fr/sql.fr.xlf +++ b/resources/xlf/fr/sql.fr.xlf @@ -1,14 +1,5574 @@  - + - - SQL Language Basics - Concepts de base du langage SQL + + Copying images is not supported + La copie d'images n'est pas prise en charge - - Provides syntax highlighting and bracket matching in SQL files. - Fournit la coloration syntaxique et la correspondance des crochets dans les fichiers SQL. + + + + + + Get Started + Mise en route + + + Show Getting Started + Voir la mise en route + + + Getting &&Started + Bien &&démarrer + && denotes a mnemonic + + + + + + + QueryHistory + QueryHistory + + + Whether Query History capture is enabled. If false queries executed will not be captured. + Indique si la capture de l'historique des requêtes est activée. Si la valeur est false, les requêtes exécutées ne sont pas capturées. + + + View + Afficher + + + Query History + Historique des requêtes + + + &&Query History + &&Historique des requêtes + && denotes a mnemonic + + + + + + + Connecting: {0} + Connexion : {0} + + + Running command: {0} + Exécution de la commande : {0} + + + Opening new query: {0} + Ouverture d'une nouvelle requête : {0} + + + Cannot connect as no server information was provided + Connexion impossible, car aucune information du serveur n'a été fournie + + + Could not open URL due to error {0} + Impossible d'ouvrir l'URL en raison de l'erreur {0} + + + This will connect to server {0} + Cette opération établit une connexion au serveur {0} + + + Are you sure you want to connect? + Voulez-vous vraiment vous connecter ? + + + &&Open + &&Ouvrir + + + Connecting query file + Connexion du fichier de requête + + + + + + + Error + Erreur + + + Warning + Avertissement + + + Info + Info + + + + + + + Saving results into different format disabled for this data provider. + L'enregistrement des résultats dans un format différent est désactivé pour ce fournisseur de données. + + + Cannot serialize data as no provider has been registered + Impossible de sérialiser les données, car aucun fournisseur n'est inscrit + + + Serialization failed with an unknown error + La sérialisation a échoué avec une erreur inconnue + + + + + + + Connection is required in order to interact with adminservice + La connexion est nécessaire pour interagir avec adminservice + + + No Handler Registered + Aucun gestionnaire enregistré + + + + + + + Select a file + Sélectionnez un fichier + + + + + + + Disconnect + Déconnecter + + + Refresh + Actualiser + + + + + + + Results Grid and Messages + Grille de résultats et messages + + + Controls the font family. + Contrôle de la famille de polices. + + + Controls the font weight. + Contrôle l'épaisseur de police. + + + Controls the font size in pixels. + Contrôle la taille de police en pixels. + + + Controls the letter spacing in pixels. + Contrôle l'espacement des lettres en pixels. + + + Controls the row height in pixels + Contrôle la hauteur de ligne en pixels + + + Controls the cell padding in pixels + Contrôle l'espacement entre les cellules en pixels + + + Auto size the columns width on inital results. Could have performance problems with large number of columns or large cells + Dimensionnez automatiquement la largeur de colonne en fonction des résultats initiaux. Problèmes de performance possibles avec les grands nombres de colonnes ou les grandes cellules + + + The maximum width in pixels for auto-sized columns + Largeur maximale en pixels des colonnes dimensionnées automatiquement + + + + + + + {0} in progress tasks + {0} tâches en cours + + + View + Afficher + + + Tasks + Tâches + + + &&Tasks + &&Tâches + && denotes a mnemonic + + + + + + + Connections + Connexions + + + View + Afficher + + + Database Connections + Connexions de base de données + + + data source connections + connexions de source de données + + + data source groups + groupes de source de données + + + Startup Configuration + Configuration de démarrage + + + True for the Servers view to be shown on launch of Azure Data Studio default; false if the last opened view should be shown + True pour afficher la vue des serveurs au lancement d'Azure Data Studio par défaut ; false si la dernière vue ouverte doit être affichée + + + + + + + The maximum number of recently used connections to store in the connection list. + Le nombre maximal de connexions récemment utilisées à stocker dans la liste de connexions. + + + Default SQL Engine to use. This drives default language provider in .sql files and the default to use when creating a new connection. Valid option is currently MSSQL + Moteur SQL par défaut à utiliser. Cela définit le fournisseur de langage par défaut dans les fichiers .sql et la valeur par défaut lorsque vous créez une nouvelle connexion. Une option valide est actuellement MSSQL + + + Attempt to parse the contents of the clipboard when the connection dialog is opened or a paste is performed. + Tentez d'analyser le contenu du presse-papiers quand la boîte de dialogue de connexion est ouverte ou qu'une opération de collage est effectuée. + + + + + + + Server Group color palette used in the Object Explorer viewlet. + Palette de couleurs du groupe de serveurs utilisée dans le viewlet Explorateur d’objets. + + + Auto-expand Server Groups in the Object Explorer viewlet. + Développez automatiquement les groupes de serveurs dans la viewlet Explorateur d'objets. + + + + + + + Preview Features + Fonctionnalités en préversion + + + Enable unreleased preview features + Activer les fonctionnalités en préversion non publiées + + + Show connect dialog on startup + Afficher la boîte de dialogue de connexion au démarrage + + + Obsolete API Notification + Notification d'API obsolète + + + Enable/disable obsolete API usage notification + Activer/désactiver la notification d'utilisation d'une API obsolète + + + + + + + Problems + Problèmes + + + + + + + Identifier of the account type + Identificateur du type de compte + + + (Optional) Icon which is used to represent the accpunt in the UI. Either a file path or a themable configuration + (Facultatif) L'icône qui est utilisée pour représenter le compte dans l’interface utilisateur. Peut être soit chemin de fichier, soit une configuration de thème + + + Icon path when a light theme is used + Chemin de l’icône quand un thème clair est utilisé + + + Icon path when a dark theme is used + Chemin de l’icône quand un thème sombre est utilisé + + + Contributes icons to account provider. + Icônes de contribution au fournisseur de compte. + + + + + + + Indicates data property of a data set for a chart. + Indique la propriété d'une donnée d’un jeu de données pour un graphique. + + + + + + + Minimum value of the y axis + Valeur minimale de l'axe Y + + + Maximum value of the y axis + Valeur maximale de l’axe y + + + Label for the y axis + Étiquette pour l’axe y + + + Minimum value of the x axis + Valeur minimale de l’axe des x + + + Maximum value of the x axis + Valeur maximale de l’axe des x + + + Label for the x axis + Étiquette de l’axe des x + + + + + + + Displays the results in a simple table + Affiche les résultats dans un tableau simple + + + + + + + Displays an image, for example one returned by an R query using ggplot2 + Affiche une image, par exemple celle retournée par une requête de type R utilisant "ggplot2" + + + What format is expected - is this a JPEG, PNG or other format? + Quel format est attendu - est-ce un JPEG, PNG ou autre format ? + + + Is this encoded as hex, base64 or some other format? + Cela est-il encodé en hexadécimal, base64 ou un autre format? + + + + + + + For each column in a resultset, displays the value in row 0 as a count followed by the column name. Supports '1 Healthy', '3 Unhealthy' for example, where 'Healthy' is the column name and 1 is the value in row 1 cell 1 + Pour chaque colonne d'un ensemble de résultats, affiche la valeur de la ligne 0 sous forme de chiffre suivi du nom de la colonne. Prend en charge '1 Healthy', '3 Unhealthy', par exemple, où 'Healthy' est le nom de la colonne et 1 est la valeur de la ligne 1, cellule 1 + + + + + + + Manage + Gérer + + + Dashboard + Tableau de bord + + + + + + + The webview that will be displayed in this tab. + La Webview qui s’affichera dans cet onglet. + + + + + + + The controlhost that will be displayed in this tab. + Controlhost qui sera affiché sous cet onglet. + + + + + + + The list of widgets that will be displayed in this tab. + La liste des widgets qui s’afficheront dans cet onglet. + + + The list of widgets is expected inside widgets-container for extension. + La liste des widgets est attendue à l’intérieur du conteneur de widgets pour l'extension. + + + + + + + The list of widgets or webviews that will be displayed in this tab. + La liste des widgets ou webviews qui s’afficheront dans cet onglet. + + + widgets or webviews are expected inside widgets-container for extension. + les widgets ou les webviews doivent être dans un conteneur de widgets pour l'extension. + + + + + + + Unique identifier for this container. + Identificateur unique pour ce conteneur. + + + The container that will be displayed in the tab. + Le conteneur qui sera affiché dans l’onglet. + + + Contributes a single or multiple dashboard containers for users to add to their dashboard. + Contribue à un ou plusieurs conteneurs de tableau de bord pour que les utilisateurs l'ajoutent à leur tableau de bord. + + + No id in dashboard container specified for extension. + Aucun id de conteneur de tableau de bord spécifié pour l’extension. + + + No container in dashboard container specified for extension. + Aucun conteneur dans le conteneur de tableaux de bord spécifié pour l'extension. + + + Exactly 1 dashboard container must be defined per space. + 1 seul conteneur de tableaux de bord doit être défini par espace. + + + Unknown container type defines in dashboard container for extension. + Type de conteneur inconnu défini dans le conteneur de tableau de bord pour l’extension. + + + + + + + The model-backed view that will be displayed in this tab. + Vue de modèles qui s'affiche sous cet onglet. + + + + + + + Unique identifier for this nav section. Will be passed to the extension for any requests. + Identificateur unique pour cette section de navigation. Sera transmis à l’extension pour toutes les demandes. + + + (Optional) Icon which is used to represent this nav section in the UI. Either a file path or a themeable configuration + (Facultatif) Icône utilisée pour représenter cette section nav dans l’interface utilisateur. Soit un chemin de fichier, soit une configuration personnalisable + + + Icon path when a light theme is used + Chemin de l’icône quand un thème clair est utilisé + + + Icon path when a dark theme is used + Chemin de l’icône quand un thème sombre est utilisé + + + Title of the nav section to show the user. + Titre de la section de navigation à afficher à l’utilisateur. + + + The container that will be displayed in this nav section. + Le conteneur qui sera affiché dans cette section de navigation. + + + The list of dashboard containers that will be displayed in this navigation section. + La liste des conteneurs de tableau de bord qui s’afficheront dans cette section de navigation. + + + property `icon` can be omitted or must be either a string or a literal like `{dark, light}` + la propriété 'icon' peut être omise, ou bien elle doit être une chaîne ou un littéral tel que '{dark, light}' + + + No title in nav section specified for extension. + Aucun titre dans la section de navigation spécifiée pour l'extension. + + + No container in nav section specified for extension. + Aucun conteneur dans la section de navigation n'a été spécifié pour l’extension. + + + Exactly 1 dashboard container must be defined per space. + 1 seul conteneur de tableaux de bord doit être défini par espace. + + + NAV_SECTION within NAV_SECTION is an invalid container for extension. + NAV_SECTION dans NAV_SECTION n'est pas un conteneur valide pour l'extension. + + + + + + + Backup + Sauvegarde + + + + + + + Unique identifier for this tab. Will be passed to the extension for any requests. + Identificateur unique pour cet onglet. Sera transmis à l’extension pour toutes les demandes. + + + Title of the tab to show the user. + Titre de l’onglet à montrer à l’utilisateur. + + + Description of this tab that will be shown to the user. + Description de cet onglet qui sera affiché à l’utilisateur. + + + Condition which must be true to show this item + Condition qui doit être vraie pour afficher cet élément + + + Defines the connection types this tab is compatible with. Defaults to 'MSSQL' if not set + Définit les types de connexion avec lesquels cet onglet est compatible. La valeur par défaut est 'MSSQL' si aucune valeur n'est définie + + + The container that will be displayed in this tab. + Le conteneur qui s’affichera dans cet onglet. + + + Whether or not this tab should always be shown or only when the user adds it. + Indique si cet onglet devrait toujours apparaître ou uniquement lorsque l’utilisateur l'ajoute. + + + Whether or not this tab should be used as the Home tab for a connection type. + Indique si cet onglet doit être utilisé comme onglet d'accueil pour un type de connexion. + + + Contributes a single or multiple tabs for users to add to their dashboard. + Fournit un onglet simple ou multiple que les utilisateurs peuvent ajouter à leur tableau de bord. + + + No title specified for extension. + Aucun titre spécifié pour l’extension. + + + No description specified to show. + Aucune description spécifiée à afficher. + + + No container specified for extension. + Aucun conteneur spécifié pour l’extension. + + + Exactly 1 dashboard container must be defined per space + 1 seul conteneur de tableaux de bord doit être défini par espace + + + + + + + Restore + Restaurer + + + Restore + Restaurer + + + + + + + Cannot expand as the required connection provider '{0}' was not found + Développement impossible car le fournisseur de connexion requis ('{0}') est introuvable + + + User canceled + Opération annulée par l'utilisateur + + + Firewall dialog canceled + Boîte de dialogue de pare-feu annulée + + + + + + + 1 or more tasks are in progress. Are you sure you want to quit? + 1 ou plusieurs tâches sont en cours. Êtes-vous sûr de vouloir quitter ? + + + Yes + Oui + + + No + Non + + + + + + + Connection is required in order to interact with JobManagementService + Une connexion est nécessaire pour interagir avec JobManagementService + + + No Handler Registered + Aucun gestionnaire enregistré + + + + + + + An error occured while loading the file browser. + Une erreur s’est produite lors du chargement de l’Explorateur de fichiers. + + + File browser error + Erreur du navigateur de fichier + + + + + + + Notebook Editor + Éditeur de notebooks + + + New Notebook + Nouveau notebook + + + New Notebook + Nouveau notebook + + + SQL kernel: stop Notebook execution when error occurs in a cell. + Noyau SQL : arrêtez l'exécution du notebook quand l'erreur se produit dans une cellule. + + + + + + + Script as Create + Script de création + + + Script as Drop + Scripter comme Abandonner + + + Select Top 1000 + Sélectionnez les 1000 premiers + + + Script as Execute + Scripter comme Exécuter + + + Script as Alter + Script Alter + + + Edit Data + Modifier les données + + + Select Top 1000 + Sélectionnez les 1000 premiers + + + Script as Create + Script de création + + + Script as Execute + Scripter comme Exécuter + + + Script as Alter + Script Alter + + + Script as Drop + Scripter comme Abandonner + + + Refresh + Actualiser + + + + + + + Connection error + Erreur de connexion + + + Connection failed due to Kerberos error. + La connexion a échoué en raison d'une erreur Kerberos. + + + Help configuring Kerberos is available at {0} + L'aide pour configurer Kerberos est disponible sur {0} + + + If you have previously connected you may need to re-run kinit. + Si vous vous êtes déjà connecté, vous devez peut-être réexécuter kinit. + + + + + + + Refresh account was canceled by the user + L'actualisation du compte a été annulée par l'utilisateur + + + + + + + Specifies view templates + Spécifie des modèles de vue + + + Specifies session templates + Spécifie les modèles de session + + + Profiler Filters + Filtres du profileur + + + + + + + Toggle Query History + Activer/désactiver l'historique des requêtes + + + Delete + Supprimer + + + Clear All History + Effacer tout l'historique + + + Open Query + Ouvrir la requête + + + Run Query + Exécutez la requête + + + Toggle Query History capture + Activer/désactiver la capture de l'historique des requêtes + + + Pause Query History Capture + Suspendre la capture de l'historique des requêtes + + + Start Query History Capture + Démarrer la capture de l'historique des requêtes + + + + + + + Preview features are required in order for extensions to be fully supported and for some actions to be available. Would you like to enable preview features? + Les fonctionnalités en préversion sont nécessaires pour la prise en charge complète des extensions et la disponibilité de certaines actions. Voulez-vous activer les fonctionnalités en préversion ? + + + Yes + Oui + + + No + Non + + + No, don't show again + Non, ne plus afficher + + + + + + + Commit row failed: + La validation de l'enregistrement a échoué: + + + Started executing query "{0}" + La requête "{0}" a démarré son exécution + + + Update cell failed: + Échec de la mise à jour de la cellule : + + + + + + + Failed to create Object Explorer session + Echec de la création d'une session de l’Explorateur d’objets + + + Multiple errors: + Plusieurs erreurs : + + + + + + + No URI was passed when creating a notebook manager + Aucun URI passé pendant la création d'un gestionnaire de notebooks + + + Notebook provider does not exist + Le fournisseur de notebooks n'existe pas + + + + + + + Query Results + Résultats de la requête + + + Query Editor + Éditeur de requêtes + + + New Query + Nouvelle requête + + + [Optional] When true, column headers are included when saving results as CSV + [Facultatif] Lorsque la valeur est true, les en-têtes de colonnes sont inclus lors de l’enregistrement des résultats en CSV + + + [Optional] The custom delimiter to use between values when saving as CSV + [Facultatif] Délimiteur personnalisé à utiliser entre les valeurs durant l'enregistrement au format CSV + + + [Optional] Character(s) used for seperating rows when saving results as CSV + [Facultatif] Caractère(s) utilisé(s) pour séparer les lignes durant l’enregistrement des résultats au format CSV + + + [Optional] Character used for enclosing text fields when saving results as CSV + [Facultatif] Caractère utilisé pour englober les champs "Texte" lors de l’enregistrement des résultats au format CSV + + + [Optional] File encoding used when saving results as CSV + [Facultatif] Encodage de fichier utilisé durant l'enregistrement des résultats au format CSV + + + Enable results streaming; contains few minor visual issues + Activer le streaming des résultats, contient quelques problèmes visuels mineurs + + + [Optional] When true, XML output will be formatted when saving results as XML + [Facultatif] Quand la valeur est true, la sortie XML est mise en forme pendant l'enregistrement des résultats au format XML + + + [Optional] File encoding used when saving results as XML + [Facultatif] Encodage de fichier utilisé lors de l'enregistrement des résultats au format XML + + + [Optional] Configuration options for copying results from the Results View + [Facultatif] Options de configuration pour la copie des résultats à partir de la Vue Résultats + + + [Optional] Configuration options for copying multi-line results from the Results View + [Facultatif] Options de configuration pour la copie de résultats multi-lignes depuis la vue Résultats + + + [Optional] Should execution time be shown for individual batches + [Facultatif] Le temps d’exécution doit-il être indiqué pour chaque lot + + + [Optional] the default chart type to use when opening Chart Viewer from a Query Results + [Optionnel] le type de graphique par défaut à utiliser lors de l’ouverture de la visionneuse graphique à partir d’un résultat de requête + + + Tab coloring will be disabled + La coloration des onglets sera désactivée + + + The top border of each editor tab will be colored to match the relevant server group + La bordure supérieure de chaque onglet Éditeur sera colorée pour correspondre au groupe concerné + + + Each editor tab's background color will match the relevant server group + La couleur de fond de chaque onglet d'édition correspondra au groupe de serveurs concerné + + + Controls how to color tabs based on the server group of their active connection + Contrôle comment coloriser les onglets en fonction du groupe de serveurs de leur connexion active + + + Controls whether to show the connection info for a tab in the title. + Contrôle s'il faut montrer les informations de connexion d'un onglet dans le titre. + + + Prompt to save generated SQL files + Inviter à enregistrer les fichiers SQL générés + + + Should IntelliSense be enabled + L'IntelliSense doit-il être activé + + + Should IntelliSense error checking be enabled + La vérification des erreurs à travers IntelliSense doit-elle être activée + + + Should IntelliSense suggestions be enabled + Spécifie si les suggestions IntelliSense doivent être activées + + + Should IntelliSense quick info be enabled + Spécifie si les infos rapides IntelliSense doivent être activées + + + Should IntelliSense suggestions be lowercase + Spécifie si les suggestions IntelliSense doivent être en minuscules + + + Maximum number of rows to return before the server stops processing your query. + Nombre maximum de lignes à retourner avant que le serveur cesse de traiter votre requête. + + + Maximum size of text and ntext data returned from a SELECT statement + Taille maximale des données TEXT et NTEXT retournées à partir d'une instruction SELECT + + + An execution time-out of 0 indicates an unlimited wait (no time-out) + Un délai d'exécution de 0 indique une attente illimitée (aucun délai d'expiration) + + + Enable SET NOCOUNT option + Activer l'option SET NOCOUNT + + + Enable SET NOEXEC option + Activer l'option SET NOEXEC + + + Enable SET PARSEONLY option + Activer l'option SET PARSEONLY + + + Enable SET ARITHABORT option + Activer l'option SET ARITHABORT + + + Enable SET STATISTICS TIME option + Activer l'option SET STATISTICS TIME + + + Enable SET STATISTICS IO option + Activer l'option SET STATISTICS IO + + + Enable SET XACT_ABORT ON option + Activer l'option SET XACT-ABORT ON + + + Enable SET TRANSACTION ISOLATION LEVEL option + Activer l'option SET TRANSACTION ISOLATION LEVEL + + + Enable SET DEADLOCK_PRIORITY option + Activer l'option SET DEADLOCK_PRIORITY + + + Enable SET LOCK TIMEOUT option (in milliseconds) + Activer l'option SET LOCK TIMEOUT (en millisecondes) + + + Enable SET QUERY_GOVERNOR_COST_LIMIT + Activer SET QUERY_GOVERNOR_COST_LIMIT + + + Enable SET ANSI_DEFAULTS + Activer SET ANSI_DEFAULTS + + + Enable SET QUOTED_IDENTIFIER + Activer SET QUOTED_IDENTIFIER + + + Enable SET ANSI_NULL_DFLT_ON + Activer SET ANSI_NULL_DFLT_ON + + + Enable SET IMPLICIT_TRANSACTIONS + Activer SET IMPLICIT_TRANSACTIONS + + + Enable SET CURSOR_CLOSE_ON_COMMIT + Activer SET CURSOR_CLOSE_ON_COMMIT + + + Enable SET ANSI_PADDING + Activer SET ANSI_PADDING + + + Enable SET ANSI_WARNINGS + Activer SET ANSI_WARNINGS + + + Enable SET ANSI_NULLS + Activer SET ANSI_NULLS + + + Set keybinding workbench.action.query.shortcut{0} to run the shortcut text as a procedure call. Any selected text in the query editor will be passed as a parameter + Défini le raccourci clavier workbench.action.query.shortcut{0} pour exécuter le texte de raccourci comme un appel de procédure. Tout texte sélectionné dans l’éditeur de requête sera passé en paramètre + + + + + + + Common id for the provider + ID courant du fournisseur + + + Display Name for the provider + Nom complet du fournisseur + + + Icon path for the server type + Chemin de l'icône pour le type de serveur + + + Options for connection + Options de connexion + + + + + + + OK + OK + + + Close + Fermer + + + Copy details + Copier les détails + + + + + + + Add server group + Ajouter le groupe de serveurs + + + Edit server group + Modifier le groupe de serveurs + + + + + + + Error adding account + Erreur lors de l'ajout du compte + + + Firewall rule error + Erreur de règle du pare-feu + + + + + + + Some of the loaded extensions are using obsolete APIs, please find the detailed information in the Console tab of Developer Tools window + Certaines des extensions chargées utilisent des API obsolètes, recherchez les informations détaillées sous l'onglet Console de la fenêtre Outils de développement + + + Don't Show Again + Ne plus afficher + + + + + + + Toggle Tasks + Activer/désactiver des tâches + + + + + + + Show Connections + Afficher les connexions + + + Servers + Serveurs + + + + + + + Identifier of the view. Use this to register a data provider through `vscode.window.registerTreeDataProviderForView` API. Also to trigger activating your extension by registering `onView:${id}` event to `activationEvents`. + Identificateur de la vue. Utilisez-le pour inscrire un fournisseur de données au moyen de l'API 'vscode.window.registerTreeDataProviderForView', ainsi que pour déclencher l'activation de votre extension en inscrivant l'événement 'onView:${id}' dans 'activationEvents'. + + + The human-readable name of the view. Will be shown + Nom de la vue, contrôlable de visu. Affiché + + + Condition which must be true to show this view + Condition qui doit être vraie pour afficher cette vue + + + Contributes views to the editor + Ajoute des vues à l'éditeur + + + Contributes views to Data Explorer container in the Activity bar + Ajoute des vues au conteneur Explorateur de données dans la barre d'activités + + + Contributes views to contributed views container + Les vues contribuent au conteneur de vues contributives + + + View container '{0}' does not exist and all views registered to it will be added to 'Data Explorer'. + Le conteneur de vues '{0}' n'existe pas et toutes les vues qui y sont inscrites sont ajoutées à 'Explorateur de données'. + + + Cannot register multiple views with same id `{0}` in the view container `{1}` + Impossible d'inscrire plusieurs vues avec le même ID '{0}' dans le conteneur de vues '{1}' + + + A view with id `{0}` is already registered in the view container `{1}` + Une vue avec l'ID '{0}' est déjà inscrite dans le conteneur de vues '{1}' + + + views must be an array + les vues doivent figurer dans un tableau + + + property `{0}` is mandatory and must be of type `string` + la propriété '{0}' est obligatoire et doit être de type 'string' + + + property `{0}` can be omitted or must be of type `string` + La propriété '{0}' peut être omise ou doit être de type 'string' + + + + + + + Connection Status + État de la connexion + + + + + + + Manage + Gérer + + + Show Details + Afficher les détails + + + Learn How To Configure The Dashboard + Découvrir comment configurer le tableau de bord + + + + + + + Widget used in the dashboards + Widget utilisé dans les tableaux de bord + + + + + + + Displays results of a query as a chart on the dashboard + Affiche les résultats d’une requête sous forme de diagramme sur le tableau de bord + + + Maps 'column name' -> color. for example add 'column1': red to ensure this column uses a red color + Mappe 'nom de colonne' -> couleur. Par exemple, ajouter 'colonne1': rouge pour que la colonne utilise la couleur rouge + + + Indicates preferred position and visibility of the chart legend. These are the column names from your query, and map to the label of each chart entry + Indique la position préférée et la visibilité de la légende du graphique. Ce sont les noms des colonnes de votre requête reliés à l’étiquette de chaque entrée du tableau + + + If dataDirection is horizontal, setting this to true uses the first columns value for the legend. + Si la direction des données est horizontale, définir cette valeur à "true" utilisera la valeur des premières colonnes pour la légende. + + + If dataDirection is vertical, setting this to true will use the columns names for the legend. + Si dataDirection est verticale, mettre ceci à vrai fera que les noms de colonnes seront utilisés pour la légende. + + + Defines whether the data is read from a column (vertical) or a row (horizontal). For time series this is ignored as direction must be vertical. + Définit si les données sont lues dans une colonne (verticale) ou une ligne (horizontale). Pour les séries chronologiques, ceci est ignoré car la direction doit être verticale. + + + If showTopNData is set, showing only top N data in the chart. + Si showTopNData est défini, affiche seulement les N premières données dans le graphique. + + + + + + + Condition which must be true to show this item + Condition qui doit être vraie pour afficher cet élément + + + The title of the container + Titre du conteneur + + + The row of the component in the grid + La ligne de l’élément dans la grille + + + The rowspan of the component in the grid. Default value is 1. Use '*' to set to number of rows in the grid. + Rowspan du composant dans la grille. La valeur par défaut est 1. Utilisez '*' pour le définir sur le nombre de lignes dans la grille. + + + The column of the component in the grid + Colonne du composant dans la grille + + + The colspan of the component in the grid. Default value is 1. Use '*' to set to number of columns in the grid. + Colspan du composant dans la grille. La valeur par défaut est 1. Utilisez '*' pour le définir sur le nombre de colonnes dans la grille. + + + Unique identifier for this tab. Will be passed to the extension for any requests. + Identificateur unique pour cet onglet. Sera transmis à l’extension pour toutes les demandes. + + + Extension tab is unknown or not installed. + L'onglet d’extension est inconnu ou non installé. + + + + + + + Enable or disable the properties widget + Activer ou désactiver le widget de propriétés + + + Property values to show + Valeurs de propriété à afficher + + + Display name of the property + Nom d'affichage de la propriété + + + Value in the Database Info Object + Valeur de l’objet d’informations de base de données + + + Specify specific values to ignore + Spécifiez des valeurs spécifiques à ignorer + + + Recovery Model + Mode de récupération + + + Last Database Backup + Dernière sauvegarde de base de données + + + Last Log Backup + Dernière sauvegarde du journal + + + Compatibility Level + Niveau de compatibilité + + + Owner + Propriétaire + + + Customizes the database dashboard page + Personnalise la page "Tableau de bord de la base de données" + + + Customizes the database dashboard tabs + Personnalise les onglets du tableau de bord de base de données + + + + + + + Enable or disable the properties widget + Activer ou désactiver le widget de propriétés + + + Property values to show + Valeurs de propriété à afficher + + + Display name of the property + Nom d'affichage de la propriété + + + Value in the Server Info Object + Valeur de l’objet Server Info + + + Version + Version + + + Edition + Edition + + + Computer Name + Nom de l'ordinateur + + + OS Version + Version du système d'exploitation + + + Customizes the server dashboard page + Personnalise la page de tableau de bord du serveur + + + Customizes the Server dashboard tabs + Personnalise les onglets du tableau de bord du Serveur + + + + + + + Manage + Gérer + + + + + + + Widget used in the dashboards + Widget utilisé dans les tableaux de bord + + + Widget used in the dashboards + Widget utilisé dans les tableaux de bord + + + Widget used in the dashboards + Widget utilisé dans les tableaux de bord + + + + + + + Adds a widget that can query a server or database and display the results in multiple ways - as a chart, summarized count, and more + Ajoute un widget qui peut interroger un serveur ou une base de données et afficher les résultats de plusieurs façons - tel qu'un graphique, un compte résumé, ou autre... + + + Unique Identifier used for caching the results of the insight. + Identificateur unique utilisé pour mettre en cache les résultats de l'insight. + + + SQL query to run. This should return exactly 1 resultset. + Requête SQL à exécuter. Cela devrait renvoyer exactement 1 jeu de résultat. + + + [Optional] path to a file that contains a query. Use if 'query' is not set + [Facultatif] chemin d'un fichier contenant une requête. Utilisez-le si 'query' n'est pas défini. + + + [Optional] Auto refresh interval in minutes, if not set, there will be no auto refresh + [Facultatif] Intervalle d'actualisation automatique en minutes, si la valeur n'est pas définie il n'y a pas d'actualisation automatique + + + Which actions to use + Quelles actions utiliser + + + Target database for the action; can use the format '${ columnName }' to use a data driven column name. + Base de données cible pour l'action; on peut utiliser le format '${ columnName }' pour utiliser un nom de colonne défini par les données. + + + Target server for the action; can use the format '${ columnName }' to use a data driven column name. + Serveur cible de l'action. Peut utiliser le format '${ columnName } pour utiliser un nom de colonne piloté par les données. + + + Target user for the action; can use the format '${ columnName }' to use a data driven column name. + Utilisateur cible de l'action. Peut utiliser le format '${ columnName } pour utiliser un nom de colonne piloté par les données. + + + Identifier of the insight + Identificateur de aperçu + + + Contributes insights to the dashboard palette. + Contribue à des insights de la palette de tableau de bord. + + + + + + + Loading + Chargement + + + Loading completed + Chargement effectué + + + + + + + Defines a property to show on the dashboard + Définit une propriété à afficher sur le tableau de bord + + + What value to use as a label for the property + Quelle est la valeur à utiliser comme étiquette pour la propriété + + + What value in the object to access for the value + Quelle valeur dans les objets pour accéder à la valeur + + + Specify values to be ignored + Spécifiez des valeurs à ignorer + + + Default value to show if ignored or no value + Valeur par défaut à afficher si ignoré ou aucune valeur + + + A flavor for defining dashboard properties + Une option pour définir les propriétés de tableau de bord + + + Id of the flavor + Id de la préférence + + + Condition to use this flavor + Condition pour utiliser cette saveur + + + Field to compare to + Champ à comparer à + + + Which operator to use for comparison + Quel opérateur utiliser pour la comparaison + + + Value to compare the field to + Valeur avec laquelle comparer le champ + + + Properties to show for database page + Propriétés à afficher pour la page de base de données + + + Properties to show for server page + Propriétés à afficher pour la page du serveur + + + Defines that this provider supports the dashboard + Définit que ce fournisseur prend en charge le tableau de bord + + + Provider id (ex. MSSQL) + Id du fournisseur (p. ex. MSSQL) + + + Property values to show on dashboard + Valeurs de propriétés à afficher sur le tableau de bord + + + + + + + Backup + Sauvegarde + + + You must enable preview features in order to use backup + Vous devez activer les fonctionnalités en préversion pour utiliser la sauvegarde + + + Backup command is not supported for Azure SQL databases. + La commande de sauvegarde n'est pas prise en charge pour les bases de données Azure SQL. + + + Backup command is not supported in Server Context. Please select a Database and try again. + La commande de sauvegarde n'est pas prise en charge dans le contexte serveur. Sélectionnez une base de données et réessayez. + + + + + + + Restore + Restaurer + + + You must enable preview features in order to use restore + Vous devez activer les fonctionnalités en préversion pour utiliser la restauration + + + Restore command is not supported for Azure SQL databases. + La commande de restauration n'est pas prise en charge pour les bases de données Azure SQL. + + + + + + + disconnected + Déconnecté + + + + + + + Server Groups + Groupes de serveurs + + + OK + OK + + + Cancel + Annuler + + + Server group name + Nom de groupe serveur + + + Group name is required. + Le nom du groupe est nécessaire. + + + Group description + Description du groupe + + + Group color + Couleur de groupe + + + + + + + Extension + Extension + + + + + + + OK + OK + + + Cancel + Annuler + + + + + + + Open dashboard extensions + Ouvrir les extensions de tableau de bord + + + OK + OK + + + Cancel + Annuler + + + No dashboard extensions are installed at this time. Go to Extension Manager to explore recommended extensions. + Aucune extension de tableau de bord n'est actuellement installée. Accédez au gestionnaire d'extensions pour explorer les extensions recommandées. + + + + + + + Selected path + Chemin sélectionné + + + Files of type + Fichiers de type + + + OK + OK + + + Discard + Annuler + + + + + + + No Connection Profile was passed to insights flyout + Aucun profil de connexion n'a été passé au menu volant d'insights + + + Insights error + Erreur d'aperçus + + + There was an error reading the query file: + Il y avait une erreur à la lecture du fichier de requête : + + + There was an error parsing the insight config; could not find query array/string or queryfile + Une erreur s'est produite lors de l'analyse de la configuration de l'aperçu ; Il n’a pas été trouvé de tableau/chaîne de requête ou de fichier de requête + + + + + + + Clear List + Effacer la liste + + + Recent connections list cleared + Liste des connexions récentes supprimée + + + Yes + Oui + + + No + Non + + + Are you sure you want to delete all the connections from the list? + Êtes-vous sûr de vouloir supprimer toutes les connexions de la liste ? + + + Yes + Oui + + + No + Non + + + Delete + Supprimer + + + Get Current Connection String + Obtenir la chaîne de connexion actuelle + + + Connection string not available + Chaîne de connexion non disponible + + + No active connection available + Aucune connexion active disponible + + + + + + + Refresh + Actualiser + + + Disconnect + Déconnecter + + + New Connection + Nouvelle connexion + + + New Server Group + Nouveau groupe de serveurs + + + Edit Server Group + Modifier le groupe de serveurs + + + Show Active Connections + Voir les connexions actives + + + Show All Connections + Afficher toutes les connexions + + + Recent Connections + Dernières connexions + + + Delete Connection + Supprimer la connexion + + + Delete Group + Supprimer le groupe + + + + + + + Edit Data Session Failed To Connect + Échec de connexion de la session de modification des données + + + + + + + Profiler + Profileur + + + Not connected + Non connecté + + + XEvent Profiler Session stopped unexpectedly on the server {0}. + La session XEvent Profiler a été arrêtée de manière inattendue sur le serveur {0}. + + + Error while starting new session + Erreur au démarrage d'une nouvelle session + + + The XEvent Profiler session for {0} has lost events. + La session XEvent Profiler pour {0} a des événements perdus. + + + Would you like to stop the running XEvent session? + Voulez-vous arrêter la session XEvent en cours d’exécution ? + + + Yes + Oui + + + No + Non + + + Cancel + Annuler + + + + + + + Invalid value + Valeur non valide + + + {0}. {1} + {0}. {1} + + + + + + + blank + vide + + + + + + + Error displaying Plotly graph: {0} + Erreur lors de l'affichage du graphe Plotly : {0} + + + + + + + No {0} renderer could be found for output. It has the following MIME types: {1} + Aucun renderer {0} n'a été trouvé pour la sortie. Types MIME : {1} + + + (safe) + (sécurisé) + + + + + + + Item + Élément + + + Value + Valeur + + + Property + propriété + + + Value + Valeur + + + Insights + Aperçus + + + Items + Éléments + + + Item Details + Détails de l'élément + + + + + + + Error adding account + Erreur lors de l'ajout du compte + + + + + + + Cannot start auto OAuth. An auto OAuth is already in progress. + On ne peut pas démarrer une "OAuth auto". Une "OAuth auto" est déjà en cours. + + + + + + + Sort by event + Trier par événement + + + Sort by column + Trier par colonne + + + Profiler + Profileur + + + OK + OK + + + Cancel + Annuler + + + + + + + Clear all + Effacer tout + + + Apply + Appliquer + + + OK + OK + + + Cancel + Annuler + + + Filters + Filtres + + + Remove this clause + Supprimer cette clause + + + Save Filter + Enregistrer le filtre + + + Load Filter + Charger le filtre + + + Add a clause + Ajouter une clause + + + Field + Champ + + + Operator + Opérateur + + + Value + Valeur + + + Is Null + Est Null + + + Is Not Null + N'est pas Null + + + Contains + Contient + + + Not Contains + Ne contient pas + + + Starts With + Commence par + + + Not Starts With + Ne commence pas par + + + + + + + Double-click to edit + Double-cliquer pour modifier + + + + + + + Select Top 1000 + Sélectionnez les 1000 premiers + + + Script as Execute + Scripter comme Exécuter + + + Script as Alter + Script Alter + + + Edit Data + Modifier les données + + + Script as Create + Script de création + + + Script as Drop + Scripter comme Abandonner + + + + + + + No queries to display. + Aucune requête à afficher. + + + Query History + Historique des requêtes + QueryHistory + + + + + + + Failed to get Azure account token for connection + L'obtention d'un jeton de compte Azure pour la connexion a échoué + + + Connection Not Accepted + Connexion non acceptée + + + Yes + Oui + + + No + Non + + + Are you sure you want to cancel this connection? + Êtes-vous certain de vouloir annuler cette connexion ? + + + + + + + Started executing query at + Démarrage de l’exécution de la requête à + + + Line {0} + Ligne {0} + + + Canceling the query failed: {0} + Échec de l’annulation de la requête : {0} + + + Started saving results to + Démarrage de l’enregistrement des résultats pour + + + Failed to save results. + Impossible d’enregistrer les résultats. + + + Successfully saved results to + Résultats enregistrés avec succès sur + + + Executing query... + Exécution de la requête en cours... + + + Maximize + Maximiser + + + Restore + Restaurer + + + Save as CSV + Enregistrer au format CSV + + + Save as JSON + Enregistrer au format JSON + + + Save as Excel + Enregistrer au format Excel + + + Save as XML + Enregistrer au format XML + + + View as Chart + Vue sous forme de graphique + + + Visualize + Visualiser + + + Results + Résultats + + + Executing query + Requête en cours d'exécution + + + Messages + Messages + + + Total execution time: {0} + Temps d’exécution total : {0} + + + Save results command cannot be used with multiple selections. + La commande d'enregistrement des résultats ne peut pas être utilisée avec des sélections multiples. + + + + + + + Identifier of the notebook provider. + Identificateur du fournisseur de notebooks. + + + What file extensions should be registered to this notebook provider + Extensions de fichier devant être inscrites dans ce fournisseur de notebooks + + + What kernels should be standard with this notebook provider + Noyaux devant être standard avec ce fournisseur de notebooks + + + Contributes notebook providers. + Ajoute des fournisseurs de notebooks. + + + Name of the cell magic, such as '%%sql'. + Nom de la cellule magique, par exemple, '%%sql'. + + + The cell language to be used if this cell magic is included in the cell + Langage de cellule à utiliser si cette commande magique est incluse dans la cellule + + + Optional execution target this magic indicates, for example Spark vs SQL + Cible d'exécution facultative que cette commande magique indique, par exemple, Spark vs. SQL + + + Optional set of kernels this is valid for, e.g. python3, pyspark, sql + Ensemble facultatif de noyaux, valable, par exemple, pour python3, pyspark, sql + + + Contributes notebook language. + Ajoute le langage du notebook. + + + + + + + SQL + SQL + + + + + + + F5 shortcut key requires a code cell to be selected. Please select a code cell to run. + La touche de raccourci F5 nécessite la sélection d'une cellule de code. Sélectionnez une cellule de code à exécuter. + + + Clear result requires a code cell to be selected. Please select a code cell to run. + L'effacement du résultat nécessite la sélection d'une cellule de code. Sélectionnez une cellule de code à exécuter. + + + + + + + Save As CSV + Enregistrer au format CSV + + + Save As JSON + Enregistrer au format JSON + + + Save As Excel + Enregistrer au format Excel + + + Save As XML + Enregistrer au format XML + + + Copy + Copier + + + Copy With Headers + Copier avec en-têtes + + + Select All + Tout sélectionner + + + + + + + Max Rows: + Nombre maximum de lignes : + + + + + + + Select View + Sélectionner une vue + + + Select Session + Sélectionner une session + + + Select Session: + Sélectionner une session : + + + Select View: + Sélectionner la vue : + + + Text + Texte + + + Label + Étiquette + + + Value + Valeur + + + Details + Détails + + + + + + + Copy failed with error {0} + Échec de la copie avec l'erreur {0} + + + + + + + New Query + Nouvelle requête + + + Run + Exécuter + + + Cancel + Annuler + + + Explain + Expliquer + + + Actual + Actuel + + + Disconnect + Déconnecter + + + Change Connection + Changer la connexion + + + Connect + Connecter + + + Enable SQLCMD + Activer SQLCMD + + + Disable SQLCMD + Désactiver SQLCMD + + + Select Database + Sélectionnez la base de données + + + Select Database Toggle Dropdown + Sélectionnez la liste déroulante de bases de données + + + Failed to change database + Echec lors du changement de base de données + + + Failed to change database {0} + Echec du changement de base de données {0} + + + + + + + Connection + Connexion + + + Connection type + Type de connexion + + + Recent Connections + Dernières connexions + + + Saved Connections + Connexions enregistrées + + + Connection Details + Détails de la connexion + + + Connect + Connecter + + + Cancel + Annuler + + + No recent connection + Aucune connexion récente + + + No saved connection + Aucune connexion enregistrée + + + + + + + OK + OK + + + Close + Fermer + + + + + + + Loading kernels... + Chargement de noyaux... + + + Changing kernel... + Changement du noyau... + + + Kernel: + Noyau : + + + Attach To: + Joindre à : + + + Loading contexts... + Chargement des contextes... + + + Add New Connection + Ajouter une nouvelle connexion + + + Select Connection + Sélectionner une connexion + + + localhost + localhost + + + Trusted + Approuvé + + + Not Trusted + Non approuvé + + + Notebook is already trusted. + Le notebook est déjà approuvé. + + + Collapse Cells + Réduire les cellules + + + Expand Cells + Développer les cellules + + + No Kernel + Pas de noyau + + + None + Aucun(e) + + + New Notebook + Nouveau notebook + + + + + + + Time Elapsed + Temps écoulé + + + Row Count + Nombre de lignes + + + {0} rows + {0} lignes + + + Executing query... + Exécution de la requête en cours... + + + Execution Status + État de l'exécution + + + + + + + No task history to display. + Aucun historique des tâches à afficher. + + + Task history + Historique de la tâche + TaskHistory + + + Task error + Erreur de la tâche + + + + + + + Choose SQL Language + Choisir le langage SQL + + + Change SQL language provider + Changer de fournisseur de langage SQL + + + SQL Language Flavor + Saveur de langage SQL + + + Change SQL Engine Provider + Changer le fournisseur de moteur SQL + + + A connection using engine {0} exists. To change please disconnect or change connection + Une connexion utilisant le moteur {0} existe déjà. Pour changer, veuillez vous déconnecter ou modifier la connexion. + + + No text editor active at this time + Aucun éditeur de texte actif en ce moment + + + Select SQL Language Provider + Sélectionner le fournisseur de langage SQL + + + + + + + All files + Tous les fichiers + + + + + + + File browser tree + Arborescence du navigateur de fichiers + FileBrowserTree + + + + + + + From + De + + + To + Vers + + + Create new firewall rule + Créer une nouvelle règle de pare-feu + + + OK + OK + + + Cancel + Annuler + + + Your client IP address does not have access to the server. Sign in to an Azure account and create a new firewall rule to enable access. + Votre adresse IP cliente n'a pas accès au serveur. Connectez-vous à un compte Azure et créez une règle de pare-feu pour autoriser l'accès. + + + Learn more about firewall settings + En savoir plus sur les paramètres du pare-feu + + + Azure account + Compte Azure + + + Firewall rule + Règle de pare-feu + + + Add my client IP + Ajouter l'adresse IP de mon client + + + Add my subnet IP range + Ajouter ma plage d'adresses de sous-réseau IP + + + + + + + You need to refresh the credentials for this account. + Vous devez actualiser les informations d’identification pour ce compte. + + + + + + + Could not find query file at any of the following paths : + {0} + Fichier de requête introuvable dans les chemins suivants : + {0} + + + + + + + Add an account + Ajouter un compte + + + Remove account + Supprimer le compte + + + Are you sure you want to remove '{0}'? + Êtes-vous sûr de vouloir supprimer '{0}' ? + + + Yes + Oui + + + No + Non + + + Failed to remove account + Échec de suppression du compte + + + Apply Filters + Appliquer des filtres + + + Reenter your credentials + Entrez à nouveau vos informations d’identification + + + There is no account to refresh + Il n’y a aucun compte à actualiser + + + + + + + Focus on Current Query + Se concentrer sur la requête en cours + + + Run Query + Exécutez la requête + + + Run Current Query + Exécutez la requête en cours + + + Run Current Query with Actual Plan + Exécutez la requête en cours avec le Plan réel + + + Cancel Query + Annuler la requête + + + Refresh IntelliSense Cache + Actualiser le Cache IntelliSense + + + Toggle Query Results + Activer/désactiver les résultats de requête + + + Editor parameter is required for a shortcut to be executed + Le paramètre de l’éditeur est nécessaire pour qu'un raccourci soit exécuter + + + Parse Query + Analyser la requête + + + Commands completed successfully + Commandes exécutées + + + Command failed: + Echec de la commande : + + + Please connect to a server + Connectez-vous à un serveur + + + + + + + Chart cannot be displayed with the given data + Le graphique ne peut pas être affiché avec les données fournies + + + + + + + The index {0} is invalid. + L'index {0} n'est pas valide. + + + + + + + no data available + aucune donnée disponible + + + + + + + Information + Informations + + + Warning + Avertissement + + + Error + Erreur + + + Show Details + Afficher les détails + + + Copy + Copier + + + Close + Fermer + + + Back + Précédent + + + Hide Details + Masquer les détails + + + + + + + is required. + est nécessaire. + + + Invalid input. Numeric value expected. + Entrée incorrecte. Valeur numérique attendue. + + + + + + + Execution failed due to an unexpected error: {0} {1} + L’exécution a échoué en raison d’une erreur inattendue : {0} {1} + + + Total execution time: {0} + Temps d’exécution total : {0} + + + Started executing query at Line {0} + L'exécution de la requête a démarré à la ligne {0} + + + Initialize edit data session failed: + Échec d'initialisation de la session de modification des données : + + + Batch execution time: {0} + Temps d’exécution par lots : {0} + + + Copy failed with error {0} + Échec de la copie avec l'erreur {0} + + + + + + + Error: {0} + Erreur : {0} + + + Warning: {0} + Avertissement : {0} + + + Info: {0} + Info : {0} + + + + + + + Copy Cell + Copier la cellule + + + + + + + Backup file path + Chemin du fichier de sauvegarde + + + Target database + Base de données cible + + + Restore database + Restaurer la base de données + + + Restore database + Restaurer la base de données + + + Database + Base de données  + + + Backup file + Fichier de sauvegarde + + + Restore + Restaurer + + + Cancel + Annuler + + + Script + Script + + + Source + source + + + Restore from + Restaurer à partir de + + + Backup file path is required. + Le chemin du fichier de sauvegarde est requis. + + + Please enter one or more file paths separated by commas + Veuillez saisir un ou plusieurs chemins d’accès séparés par des virgules + + + Database + Base de données  + + + Destination + Destination + + + Select Database Toggle Dropdown + Sélectionnez la liste déroulante de bases de données + + + Restore to + Restaurer vers + + + Restore plan + Plan de restauration + + + Backup sets to restore + Jeux de sauvegarde à restaurer + + + Restore database files as + Restaurer les fichiers de base de données en tant que + + + Restore database file details + Restaurer les détails du fichier de base de données + + + Logical file Name + Nom de fichier logique + + + File type + Type de fichier  + + + Original File Name + Nom de fichier initial + + + Restore as + Restaurer en tant que + + + Restore options + Options de restauration + + + Tail-Log backup + Sauvegarde de la fin du journal de transaction + + + Server connections + Connexions serveur + + + General + Général + + + Files + Fichiers + + + Options + Options + + + + + + + Copy & Open + Copier et ouvrir + + + Cancel + Annuler + + + User code + Code utilisateur + + + Website + Site Web + + + + + + + Done + Terminé + + + Cancel + Annuler + + + + + + + Must be an option from the list + Doit être une option dans la liste + + + Toggle dropdown + Activer/désactiver la liste déroulante + + + + + + + Select/Deselect All + Sélectionner/désélectionner tout + + + checkbox checked + case cochée + + + checkbox unchecked + case décochée + + + + + + + modelview code editor for view model. + éditeur de code modelview pour le modèle d’affichage. + + + + + + + succeeded + Succès + + + failed + Échec + + + + + + + Server Description (optional) + Description du serveur (facultatif) + + + + + + + Advanced Properties + Propriétés avancées + + + Discard + Annuler + + + + + + + Linked accounts + Comptes liés + + + Close + Fermer + + + There is no linked account. Please add an account. + Aucun compte lié. Ajoutez un compte. + + + Add an account + Ajouter un compte + + + + + + + nbformat v{0}.{1} not recognized + nbformat v{0}.{1} non reconnu + + + This file does not have a valid notebook format + Ce fichier n'a pas un format de notebook valide + + + Cell type {0} unknown + Type de cellule {0} inconnu + + + Output type {0} not recognized + Type de sortie {0} non reconnu + + + Data for {0} is expected to be a string or an Array of strings + Les données de {0} doivent être une chaîne ou un tableau de chaînes + + + Output type {0} not recognized + Type de sortie {0} non reconnu + + + + + + + Profiler editor for event text. Readonly + Editeur de profiler pour le texte d'événement. En lecture uniquement. + + + + + + + Run Cells Before + Exécuter les cellules avant + + + Run Cells After + Exécuter les cellules après + + + Insert Code Before + Insérer le code avant + + + Insert Code After + Insérer le code après + + + Insert Text Before + Insérer le texte avant + + + Insert Text After + Insérer le texte après + + + Collapse Cell + Réduire la cellule + + + Expand Cell + Développer la cellule + + + Clear Result + Effacer le résultat + + + Delete + Supprimer + + + + + + + No script was returned when calling select script on object + Aucun script n'a été retourné lors de l’appel de script select sur l’objet + + + Select + Sélectionner + + + Create + Créer + + + Insert + Insérer + + + Update + Mettre à jour + + + Delete + Supprimer + + + No script was returned when scripting as {0} on object {1} + Aucun script n'a été retournée lors de la création du script {0} sur l'objet {1} + + + Scripting Failed + Échec de l’écriture de scripts + + + No script was returned when scripting as {0} + Aucun script n'a été retourné lors de la création du script {0} + + + + + + + Recent Connections + Dernières connexions + + + Servers + Serveurs + + + + + + + No Kernel + Pas de noyau + + + Cannot run cells as no kernel has been configured + Impossible d'exécuter les cellules, car aucun noyau n'est configuré + + + Error + Erreur + + + + + + + Azure Data Studio + Azure Data Studio + + + Start + Démarrer + + + New connection + Nouvelle connexion + + + New query + Nouvelle requête + + + New notebook + Nouveau notebook + + + Open file + Ouvrir un fichier + + + Open file + Ouvrir un fichier + + + Deploy + Déployer + + + Deploy SQL Server… + Déployer SQL Server... + + + Recent + Récent + + + More... + Plus... + + + No recent folders + Aucun dossier récent + + + Help + Aide + + + Getting started + Commencer + + + Documentation + Documentation + + + Report issue or feature request + Signaler un problème ou demander une nouvelle fonctionnalité + + + GitHub repository + Dépôt GitHub + + + Release notes + Notes de publication + + + Show welcome page on startup + Afficher la page d'accueil au démarrage + + + Customize + Personnaliser + + + Extensions + Extensions + + + Download extensions that you need, including the SQL Server Admin pack and more + Téléchargez les extensions dont vous avez besoin, notamment le pack d'administration SQL Server + + + Keyboard Shortcuts + Raccourcis clavier + + + Find your favorite commands and customize them + Trouvez vos commandes préférées et personnalisez-les + + + Color theme + Thème de couleur + + + Make the editor and your code look the way you love + Personnalisez l'apparence de l'éditeur et de votre code + + + Learn + Apprendre + + + Find and run all commands + Rechercher et exécuter toutes les commandes + + + Rapidly access and search commands from the Command Palette ({0}) + La palette de commandes ({0}) permet d'accéder rapidement aux commandes pour en rechercher une + + + Discover what's new in the latest release + Découvrez les nouveautés de la dernière version + + + New monthly blog posts each month showcasing our new features + Nouveaux billets de blog mensuels mettant en avant les nouvelles fonctionnalités + + + Follow us on Twitter + Suivez-nous sur Twitter + + + Keep up to date with how the community is using Azure Data Studio and to talk directly with the engineers. + Tenez-vous au courant de la façon dont la communauté utilise Azure Data Studio et discutez directement avec les ingénieurs. + + + + + + + succeeded + Succès + + + failed + Échec + + + in progress + en cours + + + not started + pas démarré + + + canceled + Annulé + + + canceling + annulation + + + + + + + Run + Exécuter + + + Dispose Edit Failed With Error: + La modification (de la disposition) a échoué avec l'erreur : + + + Stop + Arrêter + + + Show SQL Pane + Afficher le volet SQL + + + Close SQL Pane + Fermer le volet SQL + + + + + + + Connect + Connecter + + + Disconnect + Déconnecter + + + Start + Démarrer + + + New Session + Nouvelle session + + + Pause + Pause + + + Resume + Reprendre + + + Stop + Arrêter + + + Clear Data + Effacer les données + + + Auto Scroll: On + Défilement automatique : activé + + + Auto Scroll: Off + Défilement Automatique : Désactivé + + + Toggle Collapsed Panel + Développer/Réduire le panneau + + + Edit Columns + Modifier les colonnes + + + Find Next String + Trouvez la chaîne suivante + + + Find Previous String + Trouvez la chaîne précédente + + + Launch Profiler + Lancer le profileur + + + Filter… + Filtrer... + + + Clear Filter + Effacer le filtre + + + + + + + Events (Filtered): {0}/{1} + Événements (filtrés) : {0}/{1} + + + Events: {0} + Événements : {0} + + + Event Count + Nombre d'événements + + + + + + + Save As CSV + Enregistrer au format CSV + + + Save As JSON + Enregistrer au format JSON + + + Save As Excel + Enregistrer au format Excel + + + Save As XML + Enregistrer au format XML + + + Save to file is not supported by the backing data source + L'enregistrement dans un fichier n'est pas pris en charge par la source de données de stockage + + + Copy + Copier + + + Copy With Headers + Copier avec en-têtes + + + Select All + Tout sélectionner + + + Copy + Copier + + + Copy All + Copier tout + + + Maximize + Maximiser + + + Restore + Restaurer + + + Chart + Graphique + + + Visualizer + Visualiseur + + + + + + + The extension "{0}" is using sqlops module which has been replaced by azdata module, the sqlops module will be removed in a future release. + L'extension "{0}" utilise le module sqlops qui a été remplacé par le module azdata, le module sqlops sera supprimé dans une future version. + + + + + + + Table header background color + Couleur d’arrière-plan de l'entete du tableau + + + Table header foreground color + Couleur de premier plan d'en-tête de table + + + Disabled Input box background. + Arrière plan d'une zone de saisie désactivée. + + + Disabled Input box foreground. + Premier plan de la zone d'entrée désactivée. + + + Button outline color when focused. + Couleur du contour de bouton quand il a le focus. + + + Disabled checkbox foreground. + Premier plan de case à cocher désactivé. + + + List/Table background color for the selected and focus item when the list/table is active + Couleur d’arrière-plan de la liste/table pour les éléments sélectionnés et qui ont le focus quand la liste/table est active + + + SQL Agent Table background color. + Couleur d’arrière-plan de la table SQL Agent. + + + SQL Agent table cell background color. + Couleur d'arrière-plan des cellules de la table SQL Agent. + + + SQL Agent table hover background color. + Couleur d’arrière-plan de survol de la table SQL Agent. + + + SQL Agent heading background color. + Couleur d’arrière-plan de l'en-tête de SQL Agent. + + + SQL Agent table cell border color. + Couleur de bordure des cellules de la table SQL Agent. + + + Results messages error color. + Couleurs d'erreur des messages de résultat. + + + + + + + Choose Results File + Choisissez le fichier de résultats + + + CSV (Comma delimited) + CSV (valeurs séparées par des virgules) + + + JSON + JSON + + + Excel Workbook + Classeur Excel + + + XML + XML + + + Plain Text + Texte brut + + + Open file location + Ouvrir l'emplacement du fichier + + + Open file + Ouvrir un fichier + + + + + + + Backup name + Nom de la sauvegarde + + + Recovery model + Mode de récupération + + + Backup type + Type de sauvegarde + + + Backup files + Fichiers de sauvegarde + + + Algorithm + Algorithme + + + Certificate or Asymmetric key + Certificat ou clé asymétrique + + + Media + Support + + + Backup to the existing media set + Sauvegarder dans le jeu de media existant + + + Backup to a new media set + Sauvegarder sur un nouveau jeu de sauvegarde + + + Append to the existing backup set + Ajouter au jeu de sauvegarde existant + + + Overwrite all existing backup sets + Remplacer tous les jeux de sauvegarde existants + + + New media set name + Nouveau nom de jeu de supports + + + New media set description + Description du nouveau support de sauvegarde + + + Perform checksum before writing to media + Effectuer la somme de contrôle avant d’écrire sur le support + + + Verify backup when finished + Vérifier la sauvegarde après son achèvement + + + Continue on error + Continuer en cas d’erreur + + + Expiration + Expiration  + + + Set backup retain days + Définir le délai de rétention en jours + + + Copy-only backup + Sauvegarde en mode copie seule + + + Advanced Configuration + Configuration avancée + + + Compression + Compression + + + Set backup compression + Activer la compression de la sauvegarde + + + Encryption + Chiffrement + + + Transaction log + Journal des transactions + + + Truncate the transaction log + Tronquer le journal des transactions + + + Backup the tail of the log + Sauvegarder la fin du journal + + + Reliability + Fiabilité + + + Media name is required + Un nom de média est nécessaire + + + No certificate or asymmetric key is available + Aucun certificat ou clé asymétrique n'est disponible + + + Add a file + Ajouter un fichier + + + Remove files + Supprimer les fichiers + + + Invalid input. Value must be greater than or equal 0. + Entrée incorrecte. La valeur doit être supérieure ou égale à 0. + + + Script + Script + + + Backup + Sauvegarde + + + Cancel + Annuler + + + Only backup to file is supported + Seule la sauvegarde sur fichier est supportée + + + Backup file path is required + Le chemin du fichier de sauvegarde est nécessaire + + + + + + + Results + Résultats + + + Messages + Messages + + + + + + + There is no data provider registered that can provide view data. + Il n’y a pas de fournisseur de données enregistré qui peut fournir des données de vue. + + + Collapse All + Réduire tout + + + + + + + Home + Accueil + + + + + + + The "{0}" section has invalid content. Please contact extension owner. + La section « {0} » a un contenu non valide. Veuillez contacter le propriétaire de l'extension. + + + + + + + Jobs + Travaux + + + Notebooks + Notebooks + + + Alerts + Alertes + + + Proxies + Proxies + + + Operators + Opérateurs + + + + + + + Loading + Chargement + + + + + + + SERVER DASHBOARD + TABLEAU DE BORD SERVEUR + + + + + + + DATABASE DASHBOARD + TABLEAU DE BORD DE BASE DE DONNÉES + + + + + + + Edit + Modifier + + + Exit + Quitter + + + Refresh + Actualiser + + + Toggle More + Activer/désactiver plus + + + Delete Widget + Supprimer le Widget + + + Click to unpin + Cliquer pour supprimer + + + Click to pin + Cliquer pour épingler + + + Open installed features + Ouvrir les fonctionnalités installées + + + Collapse + Réduire + + + Expand + Développer + + + + + + + Steps + Étapes + + + + + + + StdIn: + StdIn : + + + + + + + Add code + Ajouter du code + + + Add text + Ajouter du texte + + + Create File + Créer un fichier + + + Could not display contents: {0} + Impossible d'afficher le contenu : {0} + + + Please install the SQL Server 2019 extension to run cells. + Installez l'extension SQL Server 2019 pour exécuter les cellules. + + + Install Extension + Installer l'extension + + + Code + Code + + + Text + Texte + + + Run Cells + Exécuter les cellules + + + Clear Results + Effacer les résultats + + + < Previous + < Précédent + + + Next > + Suivant > + + + cell with URI {0} was not found in this model + cellule avec l'URI {0} introuvable dans ce modèle + + + Run Cells failed - See error in output of the currently selected cell for more information. + Échec de l'exécution des cellules. Pour plus d'informations, consultez l'erreur dans la sortie de la cellule actuellement sélectionnée. + + + + + + + Click on + Cliquer sur + + + + Code + + Code + + + or + Ou + + + + Text + + Texte + + + to add a code or text cell + pour ajouter une cellule de code ou de texte + + + + + + + Database + Base de données  + + + Files and filegroups + Fichiers et groupes de fichiers + + + Full + Full + + + Differential + Différentielle + + + Transaction Log + Journal des transactions + + + Disk + Disque + + + Url + URL + + + Use the default server setting + Utiliser le paramètre du serveur par défaut + + + Compress backup + Compresser la sauvegarde + + + Do not compress backup + Ne pas compresser la sauvegarde + + + Server Certificate + Certificat du serveur + + + Asymmetric Key + Clé asymétrique + + + Backup Files + Fichiers de sauvegarde + + + All Files + Tous les fichiers + + + + + + + No connections found. + Aucune connexion trouvée. + + + Add Connection + Ajouter une connexion + + + + + + + Failed to change database + Echec lors du changement de base de données + + + + + + + Name + Nom + + + Email Address + Adresse de messagerie + + + Enabled + Activé + + + + + + + Name + Nom + + + Last Occurrence + Dernière occurrence + + + Enabled + Activé + + + Delay Between Responses (in secs) + Délai entre les réponses (en secondes) + + + Category Name + Nom de la catégorie + + + + + + + Account Name + Nom du compte + + + Credential Name + Nom d'identification + + + Description + Description + + + Enabled + Activé + + + + + + + Unable to load dashboard properties + Impossible de charger les propriétés du tableau de bord + + + + + + + Search by name of type (a:, t:, v:, f:, or sp:) + Recherche par nom de type (a:, t:, v:, f: ou sp :) + + + Search databases + Rechercher les bases de données + + + Unable to load objects + Impossible de charger les objets + + + Unable to load databases + Impossible de charger les bases de données + + + + + + + Auto Refresh: OFF + Actualisation automatique : désactivée + + + Last Updated: {0} {1} + Dernière mise à jour : {0} {1} + + + No results to show + Aucun résultat à afficher + + + + + + + No {0}renderer could be found for output. It has the following MIME types: {1} + Aucun renderer {0} n'a été trouvé pour la sortie. Types MIME : {1} + + + safe + sécurisé + + + No component could be found for selector {0} + Aucun composant n'a été trouvé pour le sélecteur {0} + + + Error rendering component: {0} + Erreur de rendu du composant : {0} + + + + + + + Connected to + Connecté à + + + Disconnected + Déconnecté + + + Unsaved Connections + Connexions non enregistrées + + + + + + + Delete Row + Supprimer la ligne + + + Revert Current Row + Annuler la ligne actuelle + + + + + + + Step ID + ID de l'étape + + + Step Name + Nom de l'étape + + + Message + Message + + + + + + + XML Showplan + Showplan XML + + + Results grid + Grille de résultats + + + + + + + Please select active cell and try again + Sélectionnez la cellule active et réessayez + + + Run cell + Exécuter la cellule + + + Cancel execution + Annuler l'exécution + + + Error on last run. Click to run again + Erreur de la dernière exécution. Cliquer pour réexécuter + + + + + + + Add an account... + Ajouter un compte... + + + <Default> + <Par défaut> + + + Loading... + Chargement en cours... + + + Server group + Groupe de serveurs + + + <Default> + <Par défaut> + + + Add new group... + Ajouter un nouveau groupe... + + + <Do not save> + <Ne pas enregistrer> + + + {0} is required. + {0} est requis. + + + {0} will be trimmed. + {0} est tronqué. + + + Remember password + Se souvenir du mot de passe + + + Account + Compte + + + Refresh account credentials + Actualiser les informations d'identification du compte + + + Azure AD tenant + Locataire Azure AD + + + Select Database Toggle Dropdown + Sélectionnez la liste déroulante de bases de données + + + Name (optional) + Nom (facultatif) + + + Advanced... + Avancé... + + + You must select an account + Vous devez sélectionner un compte + + + + + + + Cancel + Annuler + + + The task is failed to cancel. + L'annulation de la tâche a échoué. + + + Script + Script + + + + + + + Date Created: + Date de création : + + + Notebook Error: + Erreur de notebook : + + + Job Error: + Erreur de travail : + + + Pinned + Épinglé + + + Recent Runs + Exécutions récentes + + + Past Runs + Exécutions précédentes + + + + + + + No tree view with id '{0}' registered. + Aucune arborescence avec l'ID « {0} » n'est enregistrée. + + + + + + + Loading... + Chargement en cours... + + + + + + + Dashboard Tabs ({0}) + Onglets de tableau de bord ({0}) + + + Id + ID + + + Title + Titre + + + Description + Description + + + Dashboard Insights ({0}) + Tableau de bord Insights ({0}) + + + Id + ID + + + Name + Nom + + + When + Quand + + + + + + + Chart + Graphique + + + + + + + Operation + Opération + + + Object + Objet + + + Est Cost + Coût estimé + + + Est Subtree Cost + Coût estimé de la sous-arborescence + + + Actual Rows + Lignes actuelles + + + Est Rows + Nombre de lignes estimées + + + Actual Executions + Exécutions actuelles + + + Est CPU Cost + Coût processeur estimé + + + Est IO Cost + Coût Estimé des E/S + + + Parallel + Parallèle + + + Actual Rebinds + Reliaisons actuelles + + + Est Rebinds + Nouvelles liaisons estimées + + + Actual Rewinds + Retourner réellement en arrière + + + Est Rewinds + Rembobinages estimés + + + Partitioned + Partitionné + + + Top Operations + Top opérations + + + + + + + Query Plan + Plan de requête + + + + + + + Could not find component for type {0} + Composant introuvable pour le type {0} + + + + + + + A NotebookProvider with valid providerId must be passed to this method + Vous devez passer à cette méthode un NotebookProvider avec un providerId valide + + + + + + + A NotebookProvider with valid providerId must be passed to this method + Vous devez passer à cette méthode un NotebookProvider avec un providerId valide + + + no notebook provider found + aucun fournisseur de notebooks + + + No Manager found + Aucun gestionnaire + + + Notebook Manager for notebook {0} does not have a server manager. Cannot perform operations on it + Notebook Manager pour le notebook {0} n'a pas de gestionnaire de serveur. Impossible d'y effectuer des opérations + + + Notebook Manager for notebook {0} does not have a content manager. Cannot perform operations on it + Notebook Manager pour le notebook {0} n'a pas de gestionnaire de contenu. Impossible d'y effectuer des opérations + + + Notebook Manager for notebook {0} does not have a session manager. Cannot perform operations on it + Notebook Manager pour le notebook {0} n'a pas de gestionnaire de session. Impossible d'y exécuter des opérations + + + + + + + SQL kernel error + Erreur du noyau SQL + + + A connection must be chosen to run notebook cells + Vous devez choisir une connexion pour exécuter des cellules de notebook + + + Displaying Top {0} rows. + Affichage des {0} premières lignes. + + + + + + + Show Recommendations + Afficher les recommandations + + + Install Extensions + Installer les extensions + + + + + + + Name + Nom + + + Last Run + Dernière exécution + + + Next Run + Prochaine exécution + + + Enabled + Activé + + + Status + État  + + + Category + Catégorie + + + Runnable + Exécutable + + + Schedule + Planification + + + Last Run Outcome + Résultats de la dernière exécution + + + Previous Runs + Exécutions précédentes + + + No Steps available for this job. + Aucune étape disponible pour ce travail. + + + Error: + Erreur : + + + + + + + Find + Rechercher + + + Find + Rechercher + + + Previous match + Correspondance précédente + + + Next match + Prochaine correspondance + + + Close + Fermer + + + Your search returned a large number of results, only the first 999 matches will be highlighted. + Votre recherche a retourné un grand nombre de résultats, seuls les 999 premières correspondances sont mises en surbrillance. + + + {0} of {1} + {0} sur {1} + + + No Results + Aucun résultat + + + + + + + Run Query + Exécutez la requête + + + + + + + Done + Terminé + + + Cancel + Annuler + + + Generate script + Générer le script + + + Next + Suivant + + + Previous + Précédent + + + + + + + A server group with the same name already exists. + Un groupe de serveurs avec le même nom existe déjà. + + + + + + + {0} is an unknown container. + {0} est un conteneur inconnu. + + + + + + + Loading Error... + Erreur de chargement... + + + + + + + Failed + Échec + + + Succeeded + Succès + + + Retry + Réessayer + + + Cancelled + Annulé + + + In Progress + en cours + + + Status Unknown + État inconnu + + + Executing + Exécution + + + Waiting for Thread + En attente du thread + + + Between Retries + Entre les tentatives + + + Idle + Inactif + + + Suspended + Suspendu + + + [Obsolete] + [Obsolète] + + + Yes + Oui + + + No + Non + + + Not Scheduled + Non planifié + + + Never Run + Ne jamais exécuter + + + + + + + Name + Nom + + + Target Database + Base de données cible + + + Last Run + Dernière exécution + + + Next Run + Prochaine exécution + + + Status + État  + + + Last Run Outcome + Résultats de la dernière exécution + + + Previous Runs + Exécutions précédentes + + + No Steps available for this job. + Aucune étape disponible pour ce travail. + + + Error: + Erreur : + + + Notebook Error: + Erreur de notebook : + + + + + + + Home + Accueil + + + No connection information could be found for this dashboard + Aucune information de connexion n'a pu être trouvée pour ce tableau de bord + + + + + + + Data + Données + + + Connection + Connexion + + + Query + Requête + + + Notebook + Notebook + + + SQL + SQL + + + Microsoft SQL Server + Microsoft SQL Server + + + Dashboard + Tableau de bord + + + Profiler + Profileur + + + + + + + Close + Fermer + + + + + + + Success + Succès + + + Error + Erreur + + + Refresh + Actualiser + + + New Job + Nouveau travail + + + Run + Exécuter + + + : The job was successfully started. + : Le travail a été démarré. + + + Stop + Arrêter + + + : The job was successfully stopped. + : Le travail a été arrêté. + + + Edit Job + Modifier le travail + + + Open + Ouvrir  + + + Delete Job + Supprimer le travail + + + Are you sure you'd like to delete the job '{0}'? + Voulez-vous vraiment supprimer le travail « {0} » ? + + + Could not delete job '{0}'. +Error: {1} + Impossible de supprimer le travail « {0} ». +Erreur : {1} + + + The job was successfully deleted + Le travail a été supprimé + + + New Step + Nouvelle étape + + + Delete Step + Supprimer l'étape + + + Are you sure you'd like to delete the step '{0}'? + Voulez-vous vraiment supprimer l'étape '{0}' ? + + + Could not delete step '{0}'. +Error: {1} + Impossible de supprimer l'étape '{0}'. +Erreur : {1} + + + The job step was successfully deleted + L'étape de travail a été supprimée + + + New Alert + Nouvelle alerte + + + Edit Alert + Modifier l'alerte + + + Delete Alert + Supprimer l'alerte + + + Cancel + Annuler + + + Are you sure you'd like to delete the alert '{0}'? + Voulez-vous vraiment supprimer l’alerte « {0} » ? + + + Could not delete alert '{0}'. +Error: {1} + Impossible de supprimer l'alerte « {0} ». +Erreur : {1} + + + The alert was successfully deleted + L'alerte a été supprimée + + + New Operator + Nouvel opérateur + + + Edit Operator + Modifier l'opérateur + + + Delete Operator + Supprimer l'opérateur + + + Are you sure you'd like to delete the operator '{0}'? + Voulez-vous vraiment supprimer l'opérateur '{0}' ? + + + Could not delete operator '{0}'. +Error: {1} + Impossible de supprimer l'opérateur '{0}'. +Erreur : {1} + + + The operator was deleted successfully + L'opérateur a été supprimé + + + New Proxy + Nouveau proxy + + + Edit Proxy + Modifier le proxy + + + Delete Proxy + Supprimer le proxy + + + Are you sure you'd like to delete the proxy '{0}'? + Êtes-vous sûr de vouloir supprimer le proxy « {0} » ? + + + Could not delete proxy '{0}'. +Error: {1} + Impossible de supprimer le proxy '{0}'. +Erreur : {1} + + + The proxy was deleted successfully + Le proxy a été supprimé + + + New Notebook Job + Nouveau travail de notebook + + + Edit + Modifier + + + Open Template Notebook + Ouvrir le modèle de notebook + + + Delete + Supprimer + + + Are you sure you'd like to delete the notebook '{0}'? + Voulez-vous vraiment supprimer le notebook '{0}' ? + + + Could not delete notebook '{0}'. +Error: {1} + Impossible de supprimer le notebook '{0}'. +Erreur : {1} + + + The notebook was successfully deleted + Le notebook a été supprimé + + + Pin + Épingler + + + Delete + Supprimer + + + Unpin + Détacher + + + Rename + Renommer + + + Open Latest Run + Ouvrir la dernière exécution + + + + + + + Please select a connection to run cells for this kernel + Sélectionnez une connexion pour exécuter les cellules pour ce noyau + + + Failed to delete cell. + La suppression de la cellule a échoué. + + + Failed to change kernel. Kernel {0} will be used. Error was: {1} + Le changement de noyau a échoué. Le noyau {0} est utilisé. Erreur : {1} + + + Failed to change kernel due to error: {0} + Le changement de noyau a échoué en raison de l'erreur : {0} + + + Changing context failed: {0} + Le changement de contexte a échoué : {0} + + + Could not start session: {0} + Impossible de démarrer la session : {0} + + + A client session error occurred when closing the notebook: {0} + Une erreur de session du client s'est produite pendant la fermeture du notebook : {0} + + + Can't find notebook manager for provider {0} + Gestionnaire de notebook introuvable pour le fournisseur {0} + + + + + + + An error occurred while starting the notebook session + Une erreur s'est produite au démarrage d'une session de notebook + + + Server did not start for unknown reason + Le serveur n'a pas démarré pour une raison inconnue + + + Kernel {0} was not found. The default kernel will be used instead. + Noyau {0} introuvable. Le noyau par défaut est utilisé à la place. + + + + + + + Unknown component type. Must use ModelBuilder to create objects + Type de composant inconnu. Doit utiliser ModelBuilder pour créer des objets + + + The index {0} is invalid. + L'index {0} n'est pas valide. + + + Unkown component configuration, must use ModelBuilder to create a configuration object + Configuration des composants inconnue, vous devez utiliser ModelBuilder pour créer un objet de configuration + + + + + + + Horizontal Bar + Histogramme horizontal + + + Bar + Histogramme + + + Line + Ligne + + + Pie + Camembert + + + Scatter + Nuage de points + + + Time Series + Time Series + + + Image + Image + + + Count + Nombre + + + Table + Table  + + + Doughnut + Anneau + + + + + + + OK + OK + + + Clear + Effacer + + + Cancel + Annuler + + + + + + + Cell execution cancelled + Exécution de la cellule annulée + + + Query execution was canceled + L'exécution de la requête a été annulée + + + The session for this notebook is not yet ready + La session de ce notebook n'est pas encore prête + + + The session for this notebook will start momentarily + La session de ce notebook va commencer dans un instant + + + No kernel is available for this notebook + Aucun noyau disponible pour ce notebook + + + + + + + Select Connection + Sélectionner une connexion + + + localhost + localhost + + + + + + + Data Direction + Direction des données + + + Vertical + Vertical + + + Horizontal + Horizontal + + + Use column names as labels + Utiliser les noms de colonne comme étiquettes + + + Use first column as row label + Utilisez la première colonne comme étiquette de ligne + + + Legend Position + Position de la légende + + + Y Axis Label + Étiquette de l’axe Y + + + Y Axis Minimum Value + Valeur minimale de l’axe Y + + + Y Axis Maximum Value + Valeur maximale de l’axe Y + + + X Axis Label + Étiquette de l’axe X + + + X Axis Minimum Value + Valeur minimale de l'axe X + + + X Axis Maximum Value + Valeur maximale de l'axe X + + + X Axis Minimum Date + Date minimale sur l'axe X + + + X Axis Maximum Date + Date maximale sur l'axe X + + + Data Type + Type de données + + + Number + Nombre + + + Point + Point + + + Chart Type + Type de graphique + + + Encoding + encodage + + + Image Format + Format d'image + + + + + + + Create Insight + Créer un Insight + + + Cannot create insight as the active editor is not a SQL Editor + Impossible de créer un aperçu car l’éditeur actif n’est pas un éditeur SQL + + + My-Widget + Mon-Widget + + + Copy as image + Copier en tant qu'image + + + Could not find chart to save + Le graphique à sauvegarder n'a pas été trouvé + + + Save as image + Enregistrer comme image + + + PNG + PNG + + + Saved Chart to path: {0} + Graphique enregistré au chemin d’accès : {0} + + + + + + + Changing editor types on unsaved files is unsupported + Le changement des types d'éditeur pour les fichiers non enregistrés n'est pas pris en charge + + + + + + + Table does not contain a valid image + Le tableau ne contient pas d'image valide + + + + + + + Series {0} + Série {0} diff --git a/resources/xlf/it/azurecore.it.xlf b/resources/xlf/it/azurecore.it.xlf index 271a74eb32cf..241d2bf98b3b 100644 --- a/resources/xlf/it/azurecore.it.xlf +++ b/resources/xlf/it/azurecore.it.xlf @@ -200,4 +200,20 @@ + + + + SQL Managed Instances + Istanze gestite di SQL + + + + + + + Azure Database for PostgreSQL Servers + Server di Database di Azure per PostgreSQL + + + \ No newline at end of file diff --git a/resources/xlf/it/big-data-cluster.it.xlf b/resources/xlf/it/big-data-cluster.it.xlf index e9de4bee700b..eb890a269ecd 100644 --- a/resources/xlf/it/big-data-cluster.it.xlf +++ b/resources/xlf/it/big-data-cluster.it.xlf @@ -38,6 +38,14 @@ Delete Mount Elimina montaggio + + Big Data Cluster + Cluster Big Data + + + Ignore SSL verification errors against SQL Server Big Data Cluster endpoints such as HDFS, Spark, and Controller if true + Se è true, ignora gli errori di verifica SSL in endpoint Cluster Big Data di SQL Server, come HDFS, Spark e Controller + @@ -58,26 +66,86 @@ Deleting In fase di eliminazione - - Waiting For Deletion - In attesa di eliminazione - Deleted Eliminato + + Applying Upgrade + Applicazione dell'aggiornamento + Upgrading In fase di aggiornamento - - Waiting For Upgrade - In attesa dell'aggiornamento + + Applying Managed Upgrade + Applicazione dell'aggiornamento gestito + + + Managed Upgrading + Aggiornamento gestito + + + Rollback + Rollback + + + Rollback In Progress + Rollback in corso + + + Rollback Complete + Rollback completato Error Errore + + Creating Secrets + Creazione dei segreti + + + Waiting For Secrets + In attesa dei segreti + + + Creating Groups + Creazione dei gruppi + + + Waiting For Groups + In attesa dei gruppi + + + Creating Resources + Creazione delle risorse + + + Waiting For Resources + In attesa delle risorse + + + Creating Kerberos Delegation Setup + Creazione della configurazione per la delega Kerberos + + + Waiting For Kerberos Delegation Setup + In attesa della configurazione per la delega Kerberos + + + Waiting For Deletion + In attesa di eliminazione + + + Waiting For Upgrade + In attesa dell'aggiornamento + + + Upgrade Paused + Aggiornamento sospeso + Running In esecuzione @@ -162,6 +230,78 @@ Unhealthy Non integro + + Unexpected error retrieving BDC Endpoints: {0} + Si è verificato un errore imprevisto durante il recupero degli endpoint BDC: {0} + + + + + + + Basic + Di base + + + Windows Authentication + Autenticazione di Windows + + + Login to controller failed + Accesso al controller non riuscito + + + Login to controller failed: {0} + Accesso al controller non riuscito: {0} + + + Username is required + Il nome utente è obbligatorio + + + Password is required + La password è obbligatoria + + + url + URL + + + username + Nome utente + + + password + Password + + + Cluster Management URL + URL di Gestione cluster + + + Authentication type + Tipo di autenticazione + + + Username + Nome utente + + + Password + Password + + + Cluster Connection + Connessione cluster + + + OK + OK + + + Cancel + Annulla + @@ -200,6 +340,22 @@ + + + + Connect to Controller (preview) + Connetti a Controller (anteprima) + + + + + + + View Details + Visualizza dettagli + + + @@ -207,8 +363,8 @@ Le informazioni sull'endpoint del controller non sono state trovate - Big Data Cluster Dashboard - - Dashboard del cluster Big Data - + Big Data Cluster Dashboard (preview) - + Dashboard di Cluster Big Data (anteprima) - Yes @@ -263,8 +419,8 @@ La password è obbligatoria - Add New Controller - Aggiungi nuovo controller + Add New Controller (preview) + Aggiungi nuovo controller (anteprima) url @@ -283,8 +439,8 @@ Ricorda password - URL - URL + Cluster Management URL + URL di Gestione cluster Authentication type @@ -319,7 +475,7 @@ Risoluzione dei problemi - Big data cluster overview + Big Data Cluster overview Panoramica cluster Big Data @@ -362,18 +518,18 @@ Metrics and Logs Metriche e log - - Metrics - Metriche + + Node Metrics + Metriche del nodo + + + SQL Metrics + Metriche di SQL Logs Log - - View Details - Visualizza dettagli - @@ -422,31 +578,35 @@ Endpoint Endpoint - - View Details - Visualizza dettagli + + Unexpected error retrieving BDC Endpoints: {0} + Si è verificato un errore imprevisto durante il recupero degli endpoint BDC: {0} + + + The dashboard requires a connection. Please click retry to enter your credentials. + Il dashboard richiede una connessione. Fare clic su Riprova per immettere le credenziali. + + + Unexpected error occurred: {0} + Si è verificato un errore imprevisto: {0} Copy Copia + + Endpoint '{0}' copied to clipboard + Endpoint '{0}' copiato negli Appunti + - - Basic - Di base - - - Windows Authentication - Autenticazione di Windows - Mount Configuration Configurazione montaggio - + HDFS Path Percorso HDFS @@ -454,22 +614,6 @@ Bad formatting of credentials at {0} Formattazione non valida delle credenziali alla posizione {0} - - Login to controller failed - Accesso al controller non riuscito - - - Login to controller failed: {0} - Accesso al controller non riuscito: {0} - - - Username is required - Il nome utente è obbligatorio - - - Password is required - La password è obbligatoria - Mounting HDFS folder on path {0} Montaggio della cartella HDFS nel percorso {0} @@ -494,58 +638,30 @@ Unknown error occurred during the mount process Si è verificato un errore sconosciuto durante il processo di montaggio - - url - URL - - - username - Nome utente - - - password - Password - - - URL - URL - - - Authentication type - Tipo di autenticazione - - - Username - Nome utente - - - Password - Password - - - Cluster Connection - Connessione cluster - - - OK - OK - - - Cancel - Annulla - - Mount HDFS Folder - Monta cartella HDFS + Mount HDFS Folder (preview) + Monta cartella HDFS (anteprima) + + + Path to a new (non-existing) directory which you want to associate with the mount + Percorso di una nuova directory non esistente da associare al montaggio - + Remote URI URI del repository remoto - + + The URI to the remote data source. Example for ADLS: abfs://fs@saccount.dfs.core.windows.net/ + URI dell'origine dati remota. Esempio per ADLS: abfs://fs@saccount.dfs.core.windows.net/ + + Credentials Credenziali + + Mount credentials for authentication to remote data source for reads + Credenziali di montaggio per l'autenticazione all'origine dati remota per operazioni di lettura + Refresh Mount Aggiorna montaggio diff --git a/resources/xlf/it/schema-compare.it.xlf b/resources/xlf/it/schema-compare.it.xlf index 4959ac53d2a5..bf062727febd 100644 --- a/resources/xlf/it/schema-compare.it.xlf +++ b/resources/xlf/it/schema-compare.it.xlf @@ -892,15 +892,15 @@ Specifies whether differences in the column collations should be ignored or updated when you publish to a database. - Specifica se le differenze nelle regole di confronto colonna devono essere ignorate o aggiornate quando si pubblicano aggiornamenti in un database. + Specifica se le differenze nelle regole di confronto colonna devono essere ignorate o aggiornate quando si esegue la pubblicazione in un database. Specifies whether differences in the Authorizer should be ignored or updated when you publish to a database. - Specifica se le differenze nel provider di autorizzazioni devono essere ignorate o aggiornate quando si pubblicano aggiornamenti in un database. + Specifica se le differenze nel provider di autorizzazioni devono essere ignorate o aggiornate quando si esegue la pubblicazione in un database. Specifies whether differences in the ANSI NULLS setting should be ignored or updated when you publish to a database. - Specifica se le differenze nell'impostazione ANSI NULLS devono essere ignorate o aggiornate quando si pubblicano aggiornamenti in un database. + Specifica se le differenze nell'impostazione ANSI NULLS devono essere ignorate o aggiornate quando si esegue la pubblicazione in un database. Automatically provides a default value when updating a table that contains data with a column that does not allow null values. diff --git a/resources/xlf/it/sql.it.xlf b/resources/xlf/it/sql.it.xlf index eb5898e520b5..ee1dc0f484f8 100644 --- a/resources/xlf/it/sql.it.xlf +++ b/resources/xlf/it/sql.it.xlf @@ -1,14 +1,5574 @@  - + - - SQL Language Basics - Nozioni di base del linguaggio SQL + + Copying images is not supported + La copia delle immagini non è supportata - - Provides syntax highlighting and bracket matching in SQL files. - Offre la sottolineatura delle sintassi e il match delle parentesi nei file SQL. + + + + + + Get Started + Per iniziare + + + Show Getting Started + Visualizza Guida introduttiva + + + Getting &&Started + &&Introduzione + && denotes a mnemonic + + + + + + + QueryHistory + Cronologia query + + + Whether Query History capture is enabled. If false queries executed will not be captured. + Indica se l'acquisizione della cronologia query è abilitata. Se è false, le query eseguite non verranno acquisite. + + + View + Vista + + + Query History + Cronologia query + + + &&Query History + &&Cronologia query + && denotes a mnemonic + + + + + + + Connecting: {0} + Collegamento: {0} + + + Running command: {0} + Esecuzione del comando: {0} + + + Opening new query: {0} + Apertura della nuova query: {0} + + + Cannot connect as no server information was provided + Non è possibile connettersi perché non sono state fornite informazioni sul server + + + Could not open URL due to error {0} + Non è stato possibile aprire l'URL a causa dell'errore {0} + + + This will connect to server {0} + Verrà eseguita la connessione al server {0} + + + Are you sure you want to connect? + Connettersi? + + + &&Open + &&Apri + + + Connecting query file + Connessione del file di query + + + + + + + Error + Errore + + + Warning + Avviso + + + Info + Info + + + + + + + Saving results into different format disabled for this data provider. + Il salvataggio dei risultati in un formato diverso è disabilitato per questo provider di dati. + + + Cannot serialize data as no provider has been registered + Non è possibile serializzare i dati perché non è stato registrato alcun provider + + + Serialization failed with an unknown error + La serializzazione non è riuscita e si verificato un errore sconosciuto + + + + + + + Connection is required in order to interact with adminservice + E' necessaria una connessione per interagire con adminservice + + + No Handler Registered + Non ci sono gestori registrati + + + + + + + Select a file + Seleziona file + + + + + + + Disconnect + Disconnetti + + + Refresh + Aggiorna + + + + + + + Results Grid and Messages + Messaggi e griglia dei risultati + + + Controls the font family. + Controlla la famiglia di caratteri. + + + Controls the font weight. + Controlla lo spessore del carattere. + + + Controls the font size in pixels. + Controlla le dimensioni del carattere in pixel. + + + Controls the letter spacing in pixels. + Controlla la spaziatura tra le lettere in pixel. + + + Controls the row height in pixels + Controlla l'altezza delle righe in pixel + + + Controls the cell padding in pixels + Controlla la spaziatura interna celle in pixel + + + Auto size the columns width on inital results. Could have performance problems with large number of columns or large cells + Ridimensiona automaticamente la larghezza delle colonne sui risultati iniziali. Potrebbero verificarsi problemi di prestazioni con un numero elevato di colonne o celle di grandi dimensioni + + + The maximum width in pixels for auto-sized columns + Larghezza massima in pixel per colonne con ridimensionamento automatico + + + + + + + {0} in progress tasks + {0} attività in corso + + + View + Vista + + + Tasks + Attività + + + &&Tasks + &&Attività + && denotes a mnemonic + + + + + + + Connections + Connessioni + + + View + Vista + + + Database Connections + Connessioni di database + + + data source connections + connessioni a origini dati + + + data source groups + gruppi di origine dati + + + Startup Configuration + Configurazione di avvio + + + True for the Servers view to be shown on launch of Azure Data Studio default; false if the last opened view should be shown + È true se la visualizzazione Server deve essere visualizata all'avvio di Azure Data Studio (impostazione predefinita); è false se deve essere visualizzata l'ultima visualizzazione aperta + + + + + + + The maximum number of recently used connections to store in the connection list. + Il numero massimo di connessioni utilizzate di recente da memorizzare nell'elenco connessioni. + + + Default SQL Engine to use. This drives default language provider in .sql files and the default to use when creating a new connection. Valid option is currently MSSQL + Motore SQL predefinito da utilizzare. Quest'ultimo stabilisce la lingua predefinita nel file .sql e il valore predefinito da utilizzare quando si crea una nuova connessione. L'opzione valida è attualmente MSSQL + + + Attempt to parse the contents of the clipboard when the connection dialog is opened or a paste is performed. + Prova ad analizzare il contenuto degli appunti quando si apre la finestra di dialogo di connessione o si esegue un'operazione Incolla. + + + + + + + Server Group color palette used in the Object Explorer viewlet. + Tavolozza dei colori del gruppo di server usata nel viewlet Esplora oggetti. + + + Auto-expand Server Groups in the Object Explorer viewlet. + Auto-espandi i Gruppi di Server nella viewlet di Esplora oggetti. + + + + + + + Preview Features + Funzionalità in anteprima + + + Enable unreleased preview features + Abilita le funzionalità in anteprima non rilasciate + + + Show connect dialog on startup + Mostra la finestra di dialogo di connessione all'avvio + + + Obsolete API Notification + Notifica API obsolete + + + Enable/disable obsolete API usage notification + Abilita/disabilita la notifica di utilizzo API obsolete + + + + + + + Problems + Problemi + + + + + + + Identifier of the account type + Identificatore del tipo di account + + + (Optional) Icon which is used to represent the accpunt in the UI. Either a file path or a themable configuration + (Opzionale) Icona utilizzata per rappresentare l'account nell'interfaccia utente. Un percorso di file o una configurazione themable + + + Icon path when a light theme is used + Percorso dell'icona quando è utilizzato un tema chiaro + + + Icon path when a dark theme is used + Percorso dell'icona quando si utilizza un tema scuro + + + Contributes icons to account provider. + Fornisce icone al provider dell'account. + + + + + + + Indicates data property of a data set for a chart. + Indica la proprietà dati di un set di dati per un grafico. + + + + + + + Minimum value of the y axis + Valore minimo dell'asse Y + + + Maximum value of the y axis + Valore massimo dell'asse y + + + Label for the y axis + Etichetta per l'asse y + + + Minimum value of the x axis + Valore minimo dell'asse X + + + Maximum value of the x axis + Valore massimo dell'asse x + + + Label for the x axis + Etichetta per l'asse x + + + + + + + Displays the results in a simple table + Visualizza i risultati in una tabella semplice + + + + + + + Displays an image, for example one returned by an R query using ggplot2 + Visualizza un'immagine, ad esempio una restituita da una query R che utilizza ggplot2 + + + What format is expected - is this a JPEG, PNG or other format? + Quale formato è previsto - JPEG, PNG o altro formato? + + + Is this encoded as hex, base64 or some other format? + È codificato come hex, base64 o altro formato? + + + + + + + For each column in a resultset, displays the value in row 0 as a count followed by the column name. Supports '1 Healthy', '3 Unhealthy' for example, where 'Healthy' is the column name and 1 is the value in row 1 cell 1 + Per ogni colonna in un set di risultati mostra il valore nella riga 0 sotto forma di numero seguito dal nome della colonna. Ad esempio '1 Attivi', '3 Disabilitati', dove 'Attivi' è il nome della colonna e 1 è il valore presente a riga 1 cella 1 + + + + + + + Manage + Gestisci + + + Dashboard + Dashboard + + + + + + + The webview that will be displayed in this tab. + Webview che verrà visualizzata in questa scheda. + + + + + + + The controlhost that will be displayed in this tab. + Controlhost che verrà visualizzato in questa scheda. + + + + + + + The list of widgets that will be displayed in this tab. + Elenco dei widget che verranno mostrati in questa scheda. + + + The list of widgets is expected inside widgets-container for extension. + L'elenco dei widget è previsto all'interno del contenitore dei widget-contenitore per estensione. + + + + + + + The list of widgets or webviews that will be displayed in this tab. + Elenco di widget o webview che verranno visualizzati in questa scheda. + + + widgets or webviews are expected inside widgets-container for extension. + i widget o le webview devono trovarsi nel contenitore dei widget per l'estensione. + + + + + + + Unique identifier for this container. + Identificatore univoco per questo contenitore. + + + The container that will be displayed in the tab. + Contenitore che sarà mostrato nella scheda. + + + Contributes a single or multiple dashboard containers for users to add to their dashboard. + Fornisce agli utenti uno o più dashboard container da aggiungere alle propria dashboard. + + + No id in dashboard container specified for extension. + Nel contenitore del dashboard non è stato specificato alcun ID per l'estensione. + + + No container in dashboard container specified for extension. + Nel contenitore del dashboard non è stato specificato alcun contenitore per l'estensione. + + + Exactly 1 dashboard container must be defined per space. + Per ogni spazio è necessario definire un solo contenitore di dashboard. + + + Unknown container type defines in dashboard container for extension. + Nel contenitore del dashboard è stato definito un tipo di contenitore sconosciuto per l'estensione. + + + + + + + The model-backed view that will be displayed in this tab. + La view model-backend che verrà visualizzata in questa scheda. + + + + + + + Unique identifier for this nav section. Will be passed to the extension for any requests. + Identificatore univoco per questa sezione di navigazione. Verrà passato all'estensione per eventuali richieste. + + + (Optional) Icon which is used to represent this nav section in the UI. Either a file path or a themeable configuration + (Opzionale) Icona utilizzata per rappresentare la sezione di spostamento nell'interfaccia utente. Un percorso di file o una configurazione di applicazione del tema + + + Icon path when a light theme is used + Percorso dell'icona quando è utilizzato un tema chiaro + + + Icon path when a dark theme is used + Percorso dell'icona quando si utilizza un tema scuro + + + Title of the nav section to show the user. + Titolo della sezione nav da mostrare all'utente. + + + The container that will be displayed in this nav section. + Contenitore che sarrà visualizzato in questa sezione di navigazione. + + + The list of dashboard containers that will be displayed in this navigation section. + L'elenco dei contenitori di dashboard che verrà visualizzato in questa sezione di navigazione. + + + property `icon` can be omitted or must be either a string or a literal like `{dark, light}` + la proprietà `icon` può essere omessa o deve essere una stringa o un valore letterale come `{dark, light}` + + + No title in nav section specified for extension. + Nessun titolo nella sezione nav specificato per l'estensione. + + + No container in nav section specified for extension. + Nella sezione nav non è stato specificato alcun contenitore per l'estensione. + + + Exactly 1 dashboard container must be defined per space. + Per ogni spazio è necessario definire un solo contenitore di dashboard. + + + NAV_SECTION within NAV_SECTION is an invalid container for extension. + NAV_SECTION all'interno di un altro NAV_SECTION non è un contenitore valido per l'estensione. + + + + + + + Backup + Backup + + + + + + + Unique identifier for this tab. Will be passed to the extension for any requests. + Identificatore univoco per questa scheda. Verrà passato all'estensione per eventuali richieste. + + + Title of the tab to show the user. + Titolo della scheda da mostrare all'utente. + + + Description of this tab that will be shown to the user. + Descrizione scheda da mostrare all'utente. + + + Condition which must be true to show this item + Condizione che deve essere vera per mostrare questo elemento + + + Defines the connection types this tab is compatible with. Defaults to 'MSSQL' if not set + Definisce i tipi di connessione con cui è compatibile questa scheda. Se non viene impostato, il valore predefinito è 'MSSQL' + + + The container that will be displayed in this tab. + Il contenitore che verrà visualizzato in questa scheda. + + + Whether or not this tab should always be shown or only when the user adds it. + Se questa scheda deve essere mostrata sempre oppure solo quando l'utente la aggiunge. + + + Whether or not this tab should be used as the Home tab for a connection type. + Indica se questa scheda deve essere usata come scheda iniziale per un tipo di connessione. + + + Contributes a single or multiple tabs for users to add to their dashboard. + Rende disponibili una o più schede agli utenti per l'aggiunta alla propria dashboard. + + + No title specified for extension. + Non è stato specificato alcun titolo per l'estensione. + + + No description specified to show. + Nessuna descrizione specificata da mostrare. + + + No container specified for extension. + Non è stato specificato alcun contenitore per l'estensione. + + + Exactly 1 dashboard container must be defined per space + È necessario definire esattamente un contenitore dashboard per ogni spazio + + + + + + + Restore + Ripristina + + + Restore + Ripristina + + + + + + + Cannot expand as the required connection provider '{0}' was not found + Non è possibile eseguire l'espansione perché il provider di connessione richiesto '{0}' non è stato trovato + + + User canceled + Operazione annullata dall'utente + + + Firewall dialog canceled + Finestra di dialogo del firewall annullata + + + + + + + 1 or more tasks are in progress. Are you sure you want to quit? + Una o più attività sono in corso. Sicuro di voler abbandonare? + + + Yes + Sì + + + No + No + + + + + + + Connection is required in order to interact with JobManagementService + Per interagire con JobManagementService, è richiesta la connessione + + + No Handler Registered + Non ci sono gestori registrati + + + + + + + An error occured while loading the file browser. + Si è verificato un errore durante il caricamento del browser di file. + + + File browser error + Errore del File Browser + + + + + + + Notebook Editor + Editor di notebook + + + New Notebook + Nuovo notebook + + + New Notebook + Nuovo notebook + + + SQL kernel: stop Notebook execution when error occurs in a cell. + Kernel SQL: arresta l'esecuzione di Notebook quando si verifica un errore in una cella. + + + + + + + Script as Create + Genera script Create + + + Script as Drop + Crea script come DROP + + + Select Top 1000 + Select Top 1000 + + + Script as Execute + Genera Script di Execute + + + Script as Alter + Genera script di Alter + + + Edit Data + Modifica Dati + + + Select Top 1000 + Select Top 1000 + + + Script as Create + Genera script Create + + + Script as Execute + Genera Script di Execute + + + Script as Alter + Genera script di Alter + + + Script as Drop + Crea script come DROP + + + Refresh + Aggiorna + + + + + + + Connection error + Errore di connessione + + + Connection failed due to Kerberos error. + Connessione non riuscita a causa di un errore di Kerberos. + + + Help configuring Kerberos is available at {0} + Le informazioni sulla configurazione di Kerberos sono disponibili all'indirizzo {0} + + + If you have previously connected you may need to re-run kinit. + Se si è già eseguita la connessione, può essere necessario eseguire di nuovo kinit. + + + + + + + Refresh account was canceled by the user + L'aggiornamento dell'account è stato annullato dall'utente + + + + + + + Specifies view templates + Specifica i modelli di view + + + Specifies session templates + Specifica i modelli di sessione + + + Profiler Filters + Filtri profiler + + + + + + + Toggle Query History + Attiva/disattiva cronologia query + + + Delete + Elimina + + + Clear All History + Cancella tutta la cronologia + + + Open Query + Apri query + + + Run Query + Esegui Query + + + Toggle Query History capture + Attiva/disattiva acquisizione della cronologia query + + + Pause Query History Capture + Sospendi acquisizione della cronologia query + + + Start Query History Capture + Avvia acquisizione della cronologia query + + + + + + + Preview features are required in order for extensions to be fully supported and for some actions to be available. Would you like to enable preview features? + Il supporto completo delle estensioni e alcune azioni sono disponibili solo con le funzionalità in anteprima. Abilitare le funzionalità in anteprima? + + + Yes + Sì + + + No + No + + + No, don't show again + Non visualizzare più questo messaggio + + + + + + + Commit row failed: + Commit di riga non riuscito: + + + Started executing query "{0}" + Esecuzione query "{0}" avviata + + + Update cell failed: + Aggiornamento cella non riuscito: + + + + + + + Failed to create Object Explorer session + Impossibile creare la sessione di Esplora oggetti + + + Multiple errors: + Più errori: + + + + + + + No URI was passed when creating a notebook manager + Non è stato passato alcun URI durante la creazione di un gestore di notebook + + + Notebook provider does not exist + Il provider di notebook non esiste + + + + + + + Query Results + Risultati query + + + Query Editor + Editor di query + + + New Query + Nuova query + + + [Optional] When true, column headers are included when saving results as CSV + [Facoltativo] Quando è 'true', le intestazioni di colonna sono incluse quando si salvano i risultati come file CSV + + + [Optional] The custom delimiter to use between values when saving as CSV + [Facoltativo] Delimitatore personalizzato da usare tra i valori durante il salvataggio in formato CSV + + + [Optional] Character(s) used for seperating rows when saving results as CSV + [Facoltativo] Caratteri usati per delimitare le righe durante il salvataggio dei risultati in formato CSV + + + [Optional] Character used for enclosing text fields when saving results as CSV + [Facoltativo] Carattere usato per racchiudere i campi di testo durante il salvataggio dei risultati in formato CSV + + + [Optional] File encoding used when saving results as CSV + [Facoltativo] Codifica del file utilizzata per il salvataggio dei risultati in formato CSV + + + Enable results streaming; contains few minor visual issues + Abilita lo streaming dei risultati. Contiene alcuni problemi minori relativi a oggetti visivi + + + [Optional] When true, XML output will be formatted when saving results as XML + [Facoltativo] Quando è true, l'output XML verrà formattato durante il salvataggio dei risultati in formato XML + + + [Optional] File encoding used when saving results as XML + [Facoltativo] Codifica di file usata durante il salvataggio dei risultati in formato XML + + + [Optional] Configuration options for copying results from the Results View + [Facoltativo] Opzioni di configurazione per copiare i risultati dalla vista dei risultati + + + [Optional] Configuration options for copying multi-line results from the Results View + [Facoltativo] Opzioni di configurazione per la copia di risultati multi-linea dalla vista dei risultati + + + [Optional] Should execution time be shown for individual batches + [Facoltativo] Il tempo di esecuzione deve essere mostrato per singoli batch + + + [Optional] the default chart type to use when opening Chart Viewer from a Query Results + [Facoltativo] tipo di grafico predefinito da utilizzare quando si apre il visualizzatore grafico dai risultati di una Query + + + Tab coloring will be disabled + La colorazione delle schede verrà disabilitata + + + The top border of each editor tab will be colored to match the relevant server group + Il bordo superiore di ogni scheda sarà colorato con il colore del relativo gruppo di server + + + Each editor tab's background color will match the relevant server group + Il colore di sfondo di ogni tab di editor corrisponderà con il rispettivo gruppo di server + + + Controls how to color tabs based on the server group of their active connection + Controlla la colorazione delle schede in base al gruppo di server della connessione attiva + + + Controls whether to show the connection info for a tab in the title. + Controlla se visualizzare le informazioni sulla connessione per una scheda nel titolo. + + + Prompt to save generated SQL files + Richiedi di salvare i file SQL generati + + + Should IntelliSense be enabled + IntelliSense deve essere abilitato? + + + Should IntelliSense error checking be enabled + Il controllo errori di IntelliSense dovrebbe essere abilitato + + + Should IntelliSense suggestions be enabled + I suggerimenti dell'IntelliSense dovrebbero essere abilitati + + + Should IntelliSense quick info be enabled + Le informazioni rapide di IntelliSense dovrebbero essere abilitate + + + Should IntelliSense suggestions be lowercase + I suggerimenti di IntelliSense devono essere in lettere minuscole? + + + Maximum number of rows to return before the server stops processing your query. + Numero massimo di righe da restituire prima che il server interrompa l'elaborazione della query. + + + Maximum size of text and ntext data returned from a SELECT statement + Dimensioni massime dei dati di tipo text e ntext restituiti da un'istruzione SELECT + + + An execution time-out of 0 indicates an unlimited wait (no time-out) + L'impostazione del valore 0 per il timeout di esecuzione indica un'attesa illimitata (nessun timeout) + + + Enable SET NOCOUNT option + Abilita l'opzione SET NOCOUNT + + + Enable SET NOEXEC option + Abilita l'opzione SET NOEXEC + + + Enable SET PARSEONLY option + Abilita l'opzione SET PARSEONLY + + + Enable SET ARITHABORT option + Abilita l'opzione SET ARITHABORT + + + Enable SET STATISTICS TIME option + Abilita l'opzione SET STATISTICS TIME + + + Enable SET STATISTICS IO option + Abilita L'opzione SET STATISTICS IO + + + Enable SET XACT_ABORT ON option + Abilita l'opzione SET XACT_ABORT ON + + + Enable SET TRANSACTION ISOLATION LEVEL option + Abilita l'opzione SET TRANSACTION ISOLATION LEVEL + + + Enable SET DEADLOCK_PRIORITY option + Abilita l'opzione SET DEADLOCK_PRIORITY + + + Enable SET LOCK TIMEOUT option (in milliseconds) + Abilita l'opzione SET LOCK TIMEOUT (in millisecondi) + + + Enable SET QUERY_GOVERNOR_COST_LIMIT + Abilita SET QUERY_GOVERNOR_COST_LIMIT + + + Enable SET ANSI_DEFAULTS + Abilita SET ANSI_DEFAULTS + + + Enable SET QUOTED_IDENTIFIER + Abilita SET QUOTED_IDENTIFIER + + + Enable SET ANSI_NULL_DFLT_ON + Abilita SET ANSI_NULL_DFLT_ON + + + Enable SET IMPLICIT_TRANSACTIONS + Abilita SET IMPLICIT_TRANSACTIONS + + + Enable SET CURSOR_CLOSE_ON_COMMIT + Abilita SET CURSOR_CLOSE_ON_COMMIT + + + Enable SET ANSI_PADDING + Abilita SET ANSI_PADDING + + + Enable SET ANSI_WARNINGS + Abilita SET ANSI_WARNINGS + + + Enable SET ANSI_NULLS + Abilita SET ANSI_NULLS + + + Set keybinding workbench.action.query.shortcut{0} to run the shortcut text as a procedure call. Any selected text in the query editor will be passed as a parameter + Impostare la combinazione di tasti workbench.action.query.shortcut{0} per eseguire il testo come una chiamata a procedura. Qualsiasi testo selezionato nell'editor di query verrà passato come parametro + + + + + + + Common id for the provider + ID comune del provider + + + Display Name for the provider + Nome di visualizzazione per il provider + + + Icon path for the server type + Percorso dell'icona per il tipo di server + + + Options for connection + Opzioni per la connessione + + + + + + + OK + OK + + + Close + Chiudi + + + Copy details + Copia dettagli + + + + + + + Add server group + Aggiungi gruppo di server + + + Edit server group + Modifica il gruppo di server + + + + + + + Error adding account + Errore durante l'aggiunta dell'account + + + Firewall rule error + Errore della regola del firewall + + + + + + + Some of the loaded extensions are using obsolete APIs, please find the detailed information in the Console tab of Developer Tools window + Alcune delle estensioni caricate usano API obsolete. Per informazioni dettagliate, vedere la scheda Console della finestra Strumenti di sviluppo + + + Don't Show Again + Non visualizzare più questo messaggio + + + + + + + Toggle Tasks + Attiva/disattiva attività + + + + + + + Show Connections + Mostra connessioni + + + Servers + Server + + + + + + + Identifier of the view. Use this to register a data provider through `vscode.window.registerTreeDataProviderForView` API. Also to trigger activating your extension by registering `onView:${id}` event to `activationEvents`. + Identificatore della vista. Utilizzare questo per registrare un provider di dati tramite l'API 'vscode.window.registerTreeDataProviderForView'. Anche per innescare l'attivazione dell'estensione tramite la registrazione dell'evento 'onView: ${id}' a 'activationEvents'. + + + The human-readable name of the view. Will be shown + Il nome della visualizzazione. Verrà mostrato + + + Condition which must be true to show this view + Condizione che deve essere vera per mostrare questa visualizzazione + + + Contributes views to the editor + Contribuisce visualizzazioni all'editor + + + Contributes views to Data Explorer container in the Activity bar + Aggiunge come contributo visualizzazioni al contenitore Esplora dati nella barra attività + + + Contributes views to contributed views container + Aggiunge come contributo visualizzazioni al contenitore delle visualizzazioni aggiunto come contributo + + + View container '{0}' does not exist and all views registered to it will be added to 'Data Explorer'. + Il contenitore di visualizzazioni '{0}' non esiste e tutte le visualizzazioni registrate verranno aggiunte a 'Esplora dati'. + + + Cannot register multiple views with same id `{0}` in the view container `{1}` + Non è possibile registrare più visualizzazioni con stesso ID `{0}` nel contenitore delle visualizzazioni `{1}` + + + A view with id `{0}` is already registered in the view container `{1}` + Una visualizzazione con ID `{0}` è già registrata nel contenitore delle visualizzazioni `{1}` + + + views must be an array + Visualizzazioni devono essere una matrice + + + property `{0}` is mandatory and must be of type `string` + la proprietà `{0}` è obbligatoria e deve essere di tipo `string` + + + property `{0}` can be omitted or must be of type `string` + la proprietà `{0}` può essere omessa o deve essere di tipo `string` + + + + + + + Connection Status + Stato connessione + + + + + + + Manage + Gestisci + + + Show Details + Mostra dettagli + + + Learn How To Configure The Dashboard + Informazioni su come configurare il dashboard + + + + + + + Widget used in the dashboards + Widget usato nelle dashboard + + + + + + + Displays results of a query as a chart on the dashboard + Visualizza i risultati di una query come grafico sulla dashboard + + + Maps 'column name' -> color. for example add 'column1': red to ensure this column uses a red color + Esegue il mapping di 'nome colonna' al colore. Ad esempio, aggiungere 'column1': red se si vuole usare il colore rosso per questa colonna + + + Indicates preferred position and visibility of the chart legend. These are the column names from your query, and map to the label of each chart entry + Indica la posizione preferita e la visibilità della legenda del grafico. Questi sono i nomi di colonna estratti dalla query e mappati con l'etichetta di ogni voce del grafico + + + If dataDirection is horizontal, setting this to true uses the first columns value for the legend. + Se dataDirection è orizzontale, impostando true il primo valore di ogni colonna sarà usato per la legenda. + + + If dataDirection is vertical, setting this to true will use the columns names for the legend. + Se dataDirection è verticale, impostando true saranno utilizzati i nomi colonna per la legenda. + + + Defines whether the data is read from a column (vertical) or a row (horizontal). For time series this is ignored as direction must be vertical. + Definisce se i dati vengono letti da una colonna (verticale) o da una riga (orizzontale). Per la serie temporale la direzione è ignorata poiché deve essere verticale. + + + If showTopNData is set, showing only top N data in the chart. + Se è impostato showTopNData, vengono visualizzati solo i primi N dati del grafico. + + + + + + + Condition which must be true to show this item + Condizione che deve essere vera per mostrare questo elemento + + + The title of the container + Titolo del contenitore + + + The row of the component in the grid + Riga del componente nella griglia + + + The rowspan of the component in the grid. Default value is 1. Use '*' to set to number of rows in the grid. + rowspan del componente nella griglia. Il valore predefinito è 1. Usare '*' per impostare sul numero di righe della griglia. + + + The column of the component in the grid + Colonna del componente nella griglia + + + The colspan of the component in the grid. Default value is 1. Use '*' to set to number of columns in the grid. + colspan del componente nella griglia. Il valore predefinito è 1. Usare '*' per impostare sul numero di colonne della griglia. + + + Unique identifier for this tab. Will be passed to the extension for any requests. + Identificatore univoco per questa scheda. Verrà passato all'estensione per eventuali richieste. + + + Extension tab is unknown or not installed. + Scheda di estensione sconosciuta o non installata. + + + + + + + Enable or disable the properties widget + Abilitare o disabilitare il widget delle proprietà + + + Property values to show + Valori da mostrare per la proprietà + + + Display name of the property + Nome da mostrare per la proprietà + + + Value in the Database Info Object + Valore nell'oggetto Database Info + + + Specify specific values to ignore + Indica i valori specifici da ignorare + + + Recovery Model + Modello di ripristino + + + Last Database Backup + Ultimo Backup del Database + + + Last Log Backup + Ultimo Backup del Log + + + Compatibility Level + Livello di compatibilità + + + Owner + Proprietario + + + Customizes the database dashboard page + Personalizza la pagina della dashboard del database + + + Customizes the database dashboard tabs + Personalizza le schede della database dashboard + + + + + + + Enable or disable the properties widget + Abilitare o disabilitare il widget delle proprietà + + + Property values to show + Valori da mostrare per la proprietà + + + Display name of the property + Nome da mostrare per la proprietà + + + Value in the Server Info Object + Valore nell'oggetto informazioni server + + + Version + Versione + + + Edition + Edizione + + + Computer Name + Nome computer + + + OS Version + Versione OS + + + Customizes the server dashboard page + Personalizza la pagina della server dashboard + + + Customizes the Server dashboard tabs + Personalizza le schede della Server dashboard + + + + + + + Manage + Gestisci + + + + + + + Widget used in the dashboards + Widget usato nelle dashboard + + + Widget used in the dashboards + Widget usato nelle dashboard + + + Widget used in the dashboards + Widget usato nelle dashboard + + + + + + + Adds a widget that can query a server or database and display the results in multiple ways - as a chart, summarized count, and more + Aggiunge un widget in grado di interrogare un server o un database e di visualizzare i risultati in vari modi - come ad esempio un grafico, un conteggio totalizzato e altro ancora + + + Unique Identifier used for caching the results of the insight. + Identificatore univoco usato per memorizzare nella cache i risultati delle informazioni dettagliate. + + + SQL query to run. This should return exactly 1 resultset. + Query SQL da eseguire. Dovrebbe restituire esattamente 1 solo set di risultati. + + + [Optional] path to a file that contains a query. Use if 'query' is not set + [Facoltativo] percorso di un file contenente una query. Usare se 'query' non è impostato + + + [Optional] Auto refresh interval in minutes, if not set, there will be no auto refresh + [Facoltativo] Intervallo di aggiornamento automatico in minuti. Se non è impostato, non verrà eseguito alcun aggiornamento automatico + + + Which actions to use + Azioni da utilizzare + + + Target database for the action; can use the format '${ columnName }' to use a data driven column name. + Database di destinazione per l'azione. È possibile usare il formato '${ columnName }' per specificare un nome di colonna basata sui dati. + + + Target server for the action; can use the format '${ columnName }' to use a data driven column name. + Server di destinazione per l'azione. È possibile usare il formato '${ columnName }' per specificare un nome di colonna basata sui dati. + + + Target user for the action; can use the format '${ columnName }' to use a data driven column name. + Utente di destinazione per l'azione. È possibile usare il formato '${ columnName }' per specificare un nome di colonna basata sui dati. + + + Identifier of the insight + Identificatore dell'insight + + + Contributes insights to the dashboard palette. + Fornisce insight per la dashboard palette. + + + + + + + Loading + Caricamento + + + Loading completed + Caricamento completato + + + + + + + Defines a property to show on the dashboard + Definisce una proprietà da mostrare sulla dashboard + + + What value to use as a label for the property + Indica il valore da usare come etichetta per la proprietà + + + What value in the object to access for the value + Quale valore nell'oggetto per accedere al valore + + + Specify values to be ignored + Specifica i valori da ignorare + + + Default value to show if ignored or no value + Valore predefinito da mostrare se ignorato o senza valore + + + A flavor for defining dashboard properties + Una nuova operatività per la definizione di proprietà dashboard + + + Id of the flavor + Identificativo dell'operatività + + + Condition to use this flavor + Condizione per utilizzare questa operatività + + + Field to compare to + Campo da confrontare + + + Which operator to use for comparison + Quale operatore utilizzare per il confronto + + + Value to compare the field to + Valore con cui confrontare il campo + + + Properties to show for database page + Proprietà da visualizzare per la pagina di database + + + Properties to show for server page + Proprietà da visualizzare per la pagina server + + + Defines that this provider supports the dashboard + Definisce che questo provider supporta la dashboard + + + Provider id (ex. MSSQL) + Id del provider (es. MSSQL) + + + Property values to show on dashboard + Valori delle proprietà per visualizzare la dashboard + + + + + + + Backup + Backup + + + You must enable preview features in order to use backup + Per usare il backup, è necessario abilitare le funzionalità in anteprima + + + Backup command is not supported for Azure SQL databases. + Il comando Backup non e' supportato per i database Azure SQL. + + + Backup command is not supported in Server Context. Please select a Database and try again. + Il comando di backup non è supportato nel contesto server. Selezionare un database e riprovare. + + + + + + + Restore + Ripristina + + + You must enable preview features in order to use restore + Per usare il ripristino, è necessario abilitare le funzionalità in anteprima + + + Restore command is not supported for Azure SQL databases. + Il comando di ripristino non è supportato per i database SQL di Azure. + + + + + + + disconnected + Disconnesso + + + + + + + Server Groups + Gruppi di server + + + OK + OK + + + Cancel + Annulla + + + Server group name + Nome del gruppo di server + + + Group name is required. + È neccessario indicare il nome del gruppo. + + + Group description + Descrizione del gruppo + + + Group color + Colore del gruppo + + + + + + + Extension + Estensione + + + + + + + OK + OK + + + Cancel + Annulla + + + + + + + Open dashboard extensions + Apri le estensioni del dashboard + + + OK + OK + + + Cancel + Annulla + + + No dashboard extensions are installed at this time. Go to Extension Manager to explore recommended extensions. + Al momento non sono installate estensioni del dashboard. Passare a Gestione estensioni per esplorare le estensioni consigliate. + + + + + + + Selected path + Percorso selezionato + + + Files of type + File di tipo + + + OK + OK + + + Discard + Scarta + + + + + + + No Connection Profile was passed to insights flyout + Nessun profilo di connessione passato all'insight flyout + + + Insights error + Errore Insight + + + There was an error reading the query file: + Si è verificato un errore durante la lettura del file di query + + + There was an error parsing the insight config; could not find query array/string or queryfile + C'è stato un errore nel parsing del file config di insight; non è stato possibile trovare l'array/stringa della query o il file della query + + + + + + + Clear List + Cancella elenco + + + Recent connections list cleared + Cancellato l'elenco connessioni recenti + + + Yes + Sì + + + No + No + + + Are you sure you want to delete all the connections from the list? + Sei sicuro di voler eliminare tutte le connessioni dalla lista? + + + Yes + Sì + + + No + No + + + Delete + Elimina + + + Get Current Connection String + Ottieni la stringa di connessione corrente + + + Connection string not available + La stringa di connessione non è disponibile + + + No active connection available + Non sono disponibili connessioni attive + + + + + + + Refresh + Aggiorna + + + Disconnect + Disconnetti + + + New Connection + Nuova connessione + + + New Server Group + Nuovo gruppo di Server + + + Edit Server Group + Modifica il gruppo di server + + + Show Active Connections + Mostra connessioni attive + + + Show All Connections + Mostra tutte le connessioni + + + Recent Connections + Connessioni recenti + + + Delete Connection + Elimina Connessione + + + Delete Group + Elimina gruppo + + + + + + + Edit Data Session Failed To Connect + La modifica dei dati della sessione non è riuscita ad effettuare la connessione. + + + + + + + Profiler + Profiler + + + Not connected + Non connesso + + + XEvent Profiler Session stopped unexpectedly on the server {0}. + La sessione del profiler XEvent è stata arrestata in modo imprevisto nel server {0}. + + + Error while starting new session + Si è verificato un errore durante l'avvio della nuova sessione + + + The XEvent Profiler session for {0} has lost events. + La sessione del profiler XEvent per {0} ha perso eventi. + + + Would you like to stop the running XEvent session? + Arrestare la sessione di XEvent in esecuzione? + + + Yes + Sì + + + No + No + + + Cancel + Annulla + + + + + + + Invalid value + Valore non valido + + + {0}. {1} + {0}. {1} + + + + + + + blank + vuota + + + + + + + Error displaying Plotly graph: {0} + Errore durante la visualizzazione del grafo Plotly: {0} + + + + + + + No {0} renderer could be found for output. It has the following MIME types: {1} + Non è stato possibile trovare alcun renderer {0} per l'output. Include i tipi MIME seguenti: {1} + + + (safe) + (sicuro) + + + + + + + Item + Elemento + + + Value + Valore + + + Property + Proprietà. + + + Value + Valore + + + Insights + Insights + + + Items + Elementi + + + Item Details + Dettagli elemento + + + + + + + Error adding account + Errore durante l'aggiunta dell'account + + + + + + + Cannot start auto OAuth. An auto OAuth is already in progress. + Impossibile avviare auto OAuth. Un auto OAuth è già in corso. + + + + + + + Sort by event + Ordina per evento + + + Sort by column + Ordina per colonna + + + Profiler + Profiler + + + OK + OK + + + Cancel + Annulla + + + + + + + Clear all + Cancella tutto + + + Apply + Applica + + + OK + OK + + + Cancel + Annulla + + + Filters + Filtri + + + Remove this clause + Rimuovi questa clausola + + + Save Filter + Salva filtro + + + Load Filter + Carica filtro + + + Add a clause + Aggiungi una clausola + + + Field + campo + + + Operator + Operatore + + + Value + Valore + + + Is Null + È Null + + + Is Not Null + Non è Null + + + Contains + Contiene + + + Not Contains + Non contiene + + + Starts With + Inizia con + + + Not Starts With + Non inizia con + + + + + + + Double-click to edit + Fare doppio clic per modificare + + + + + + + Select Top 1000 + Select Top 1000 + + + Script as Execute + Genera Script di Execute + + + Script as Alter + Genera script di Alter + + + Edit Data + Modifica Dati + + + Script as Create + Genera script Create + + + Script as Drop + Crea script come DROP + + + + + + + No queries to display. + Non ci sono query da visualizzare. + + + Query History + Cronologia query + QueryHistory + + + + + + + Failed to get Azure account token for connection + Non è stato possibile recuperare il token dell'account di Azure per la connessione + + + Connection Not Accepted + Connessione non accettata + + + Yes + Sì + + + No + No + + + Are you sure you want to cancel this connection? + Sei sicuro di voler chiudere questa connessione? + + + + + + + Started executing query at + Esecuzione query iniziata a + + + Line {0} + Riga {0} + + + Canceling the query failed: {0} + Errore durante l'annullamento della query: {0} + + + Started saving results to + Avviato il salvataggio dei risultati in + + + Failed to save results. + Impossibile salvare i risultati. + + + Successfully saved results to + Risultati salvati correttamente su + + + Executing query... + Esecuzione query... + + + Maximize + Massimizza + + + Restore + Ripristina + + + Save as CSV + Salva come CSV + + + Save as JSON + Salva come JSON + + + Save as Excel + Salva come Excel + + + Save as XML + Salva come XML + + + View as Chart + Visualizza come Grafico + + + Visualize + Visualizza + + + Results + Risultati + + + Executing query + Esecuzione query in corso + + + Messages + Messaggi + + + Total execution time: {0} + Tempo di esecuzione totale: {0} + + + Save results command cannot be used with multiple selections. + Non è possibile salvare i risultati del comando con selezioni multiple. + + + + + + + Identifier of the notebook provider. + Identificatore del provider di notebook. + + + What file extensions should be registered to this notebook provider + Indica le estensioni di file da registrare in questo provider di notebook + + + What kernels should be standard with this notebook provider + Indica i kernel che devono essere standard con questo provider di notebook + + + Contributes notebook providers. + Aggiunge come contributo i provider di notebook. + + + Name of the cell magic, such as '%%sql'. + Nome del comando magic per la cella, ad esempio '%%sql'. + + + The cell language to be used if this cell magic is included in the cell + Lingua da usare per la cella se questo comando magic è incluso nella cella + + + Optional execution target this magic indicates, for example Spark vs SQL + Destinazione di esecuzione facoltativa indicata da questo comando magic, ad esempio Spark rispetto a SQL + + + Optional set of kernels this is valid for, e.g. python3, pyspark, sql + Set facoltativo di kernel per cui è valida questa impostazione, ad esempio python3, pyspark, sql + + + Contributes notebook language. + Aggiunge come contributo la lingua del notebook. + + + + + + + SQL + SQL + + + + + + + F5 shortcut key requires a code cell to be selected. Please select a code cell to run. + Con il tasto di scelta rapida F5 è richiesta la selezione di una cella di codice. Selezionare una cella di codice da eseguire. + + + Clear result requires a code cell to be selected. Please select a code cell to run. + Con Cancella risultati è richiesta la selezione di una cella di codice. Selezionare una cella di codice da eseguire. + + + + + + + Save As CSV + Salva come CSV + + + Save As JSON + Salva come JSON + + + Save As Excel + Salva come Excel + + + Save As XML + Salva come XML + + + Copy + Copia + + + Copy With Headers + Copia con intestazioni + + + Select All + Seleziona tutto + + + + + + + Max Rows: + Numero massimo di righe: + + + + + + + Select View + Seleziona visualizzazione + + + Select Session + Seleziona sessione + + + Select Session: + Seleziona sessione: + + + Select View: + Seleziona visualizzazione: + + + Text + Testo + + + Label + Etichetta + + + Value + Valore + + + Details + Dettagli + + + + + + + Copy failed with error {0} + La copia non è riuscita. Errore: {0} + + + + + + + New Query + Nuova query + + + Run + Esegui + + + Cancel + Annulla + + + Explain + Spiega + + + Actual + Effettivo + + + Disconnect + Disconnetti + + + Change Connection + Cambia connessione + + + Connect + Connetti + + + Enable SQLCMD + Abilita SQLCMD + + + Disable SQLCMD + Disabilita SQLCMD + + + Select Database + Seleziona Database + + + Select Database Toggle Dropdown + Menu a tendina per la selezione del Database + + + Failed to change database + Impossibile cambiare database + + + Failed to change database {0} + Impossibile modificare il database {0} + + + + + + + Connection + Connessione + + + Connection type + Tipo connessione + + + Recent Connections + Connessioni recenti + + + Saved Connections + Connessioni salvate + + + Connection Details + Dettagli connessione + + + Connect + Connetti + + + Cancel + Annulla + + + No recent connection + Nessuna connessione recente + + + No saved connection + Nessuna connessione salvata + + + + + + + OK + OK + + + Close + Chiudi + + + + + + + Loading kernels... + Caricamento dei kernel... + + + Changing kernel... + Modifica del kernel... + + + Kernel: + Kernel: + + + Attach To: + Collega a: + + + Loading contexts... + Caricamento dei contesti... + + + Add New Connection + Aggiungi nuova connessione + + + Select Connection + Seleziona connessione + + + localhost + localhost + + + Trusted + Attendibile + + + Not Trusted + Non attendibile + + + Notebook is already trusted. + Il notebook è già attendibile. + + + Collapse Cells + Comprimi celle + + + Expand Cells + Espandi celle + + + No Kernel + Nessun kernel + + + None + Nessuno + + + New Notebook + Nuovo notebook + + + + + + + Time Elapsed + Tempo trascorso + + + Row Count + Conteggio righe + + + {0} rows + {0} righe + + + Executing query... + Esecuzione query... + + + Execution Status + Stato esecuzione + + + + + + + No task history to display. + Nessuna cronologia delle attività da visualizzare. + + + Task history + Cronologia delle attività + TaskHistory + + + Task error + Errore Task + + + + + + + Choose SQL Language + Scegli il Linguaggio SQL + + + Change SQL language provider + Cambia provider del linguaggio SQL + + + SQL Language Flavor + Versione del linguaggio SQL + + + Change SQL Engine Provider + Cambia il provider di SQL Engine + + + A connection using engine {0} exists. To change please disconnect or change connection + Esiste una connessione che utilizza il motore {0}. Per cambiare si prega di disconnetersi o cambiare connessione + + + No text editor active at this time + Nessun editor di testo attivo in questo momento + + + Select SQL Language Provider + Selezionare il Provider del linguaggio SQL + + + + + + + All files + Tutti i file + + + + + + + File browser tree + Navigazione file + FileBrowserTree + + + + + + + From + Da + + + To + A + + + Create new firewall rule + Crea nuova regola firewall + + + OK + OK + + + Cancel + Annulla + + + Your client IP address does not have access to the server. Sign in to an Azure account and create a new firewall rule to enable access. + Il tuo indirizzo IP client non è abilitato per l'accesso al server. Accedi ad un account Azure e crea una regola del firewall che lo abiliti. + + + Learn more about firewall settings + Ulteriori informazioni sulle impostazioni del firewall + + + Azure account + Account Azure + + + Firewall rule + Regola del firewall + + + Add my client IP + Aggiungi l'indirizzo IP del mio client + + + Add my subnet IP range + Aggiungi intervallo di IP della mia sottorete + + + + + + + You need to refresh the credentials for this account. + È necessario aggiornare le credenziali per questo account. + + + + + + + Could not find query file at any of the following paths : + {0} + Non è stato possibile trovare i file di query nei percorso seguenti: + {0} + + + + + + + Add an account + Aggiungi un account + + + Remove account + Rimuovi account + + + Are you sure you want to remove '{0}'? + Sicuro di voler rimuovere '{0}'? + + + Yes + Sì + + + No + No + + + Failed to remove account + Non è stato possibile rimuovere l'account + + + Apply Filters + Applica filtri + + + Reenter your credentials + Immettere nuovamente le credenziali + + + There is no account to refresh + Nessun account da aggiornare + + + + + + + Focus on Current Query + Stato attivo su query corrente + + + Run Query + Esegui Query + + + Run Current Query + Esegui la query corrente + + + Run Current Query with Actual Plan + Esegui query corrente con piano effettivo + + + Cancel Query + Annulla Query + + + Refresh IntelliSense Cache + Agggiorna la cache di IntelliSense + + + Toggle Query Results + Mostra/Nascondi Risultati Query + + + Editor parameter is required for a shortcut to be executed + Il parametro Editor è necessario affinchè un collegamento possa essere eseguito + + + Parse Query + Analizza la query + + + Commands completed successfully + Comandi completati con successo + + + Command failed: + Comando non riuscito: + + + Please connect to a server + Connettersi a un server + + + + + + + Chart cannot be displayed with the given data + Non è possibile visualizzare il grafico con i dati specificati + + + + + + + The index {0} is invalid. + L'indice {0} non è valido. + + + + + + + no data available + dati non disponibili + + + + + + + Information + Informazioni + + + Warning + Avviso + + + Error + Errore + + + Show Details + Mostra dettagli + + + Copy + Copia + + + Close + Chiudi + + + Back + Indietro + + + Hide Details + Nascondi dettagli + + + + + + + is required. + obbligatorio. + + + Invalid input. Numeric value expected. + Input non valido. Valore numerico previsto. + + + + + + + Execution failed due to an unexpected error: {0} {1} + Esecuzione non riuscita a causa di un errore imprevisto: {0} {1} + + + Total execution time: {0} + Tempo di esecuzione totale: {0} + + + Started executing query at Line {0} + L'esecuzione della query a riga {0} è stata avviata + + + Initialize edit data session failed: + Inizializzazione della sessione di modifica dati non riuscita: + + + Batch execution time: {0} + Tempo di esecuzione del batch: {0} + + + Copy failed with error {0} + La copia non è riuscita. Errore: {0} + + + + + + + Error: {0} + Errore: {0} + + + Warning: {0} + Avviso: {0} + + + Info: {0} + Info: {0} + + + + + + + Copy Cell + Copia cella + + + + + + + Backup file path + Percorso del file di backup + + + Target database + Database di destinazione + + + Restore database + Ripristina il database + + + Restore database + Ripristina il database + + + Database + Database + + + Backup file + File di backup + + + Restore + Ripristina + + + Cancel + Annulla + + + Script + Script + + + Source + ORIGINE + + + Restore from + Ripristinare da + + + Backup file path is required. + È necessario specificare un percorso per il file di backup. + + + Please enter one or more file paths separated by commas + Inserisci uno o più percorsi di file separati da virgole + + + Database + Database + + + Destination + Destinazione + + + Select Database Toggle Dropdown + Menu a tendina per la selezione del Database + + + Restore to + Ripristina a + + + Restore plan + Piano di ripristino + + + Backup sets to restore + Set di backup da ripristinare + + + Restore database files as + Ripristina i file di database come + + + Restore database file details + Dettagli del file di ripristino del database + + + Logical file Name + Nome logico del file + + + File type + Tipo di file + + + Original File Name + Nome del File originale + + + Restore as + Ripristina come + + + Restore options + Opzioni di ripristino + + + Tail-Log backup + Tail-Log backup + + + Server connections + Connessioni server + + + General + Generale + + + Files + File + + + Options + Opzioni + + + + + + + Copy & Open + Copia & Apri + + + Cancel + Annulla + + + User code + Codice utente + + + Website + Sito Web + + + + + + + Done + Operazione completata + + + Cancel + Annulla + + + + + + + Must be an option from the list + Deve essere un'opzione dall'elenco + + + Toggle dropdown + Toggle a discesa + + + + + + + Select/Deselect All + Seleziona/deseleziona tutto + + + checkbox checked + casella di controllo selezionata + + + checkbox unchecked + casella di controllo deselezionata + + + + + + + modelview code editor for view model. + Editor del codice modelview per il modello di visualizzazione. + + + + + + + succeeded + riuscito + + + failed + non riuscito + + + + + + + Server Description (optional) + Descrizione server (facoltativa) + + + + + + + Advanced Properties + Proprietà avanzate + + + Discard + Scarta + + + + + + + Linked accounts + Account collegati + + + Close + Chiudi + + + There is no linked account. Please add an account. + Non sono presenti account collegati. Aggiungere un account. + + + Add an account + Aggiungi un account + + + + + + + nbformat v{0}.{1} not recognized + nbformat v{0}.{1} non riconosciuto + + + This file does not have a valid notebook format + Questo file non include un formato di notebook valido + + + Cell type {0} unknown + Il tipo di cella {0} è sconosciuto + + + Output type {0} not recognized + Il tipo di output {0} non è stato riconosciuto + + + Data for {0} is expected to be a string or an Array of strings + I dati per {0} devono essere una stringa o una matrice di stringhe + + + Output type {0} not recognized + Il tipo di output {0} non è stato riconosciuto + + + + + + + Profiler editor for event text. Readonly + Editor del Profiler per il testo dell'evento. Sola Lettura + + + + + + + Run Cells Before + Esegui celle prima di + + + Run Cells After + Esegui celle dopo + + + Insert Code Before + Inserisci codice prima + + + Insert Code After + Inserisci codice dopo + + + Insert Text Before + Inserisci testo prima di + + + Insert Text After + Inserisci testo dopo + + + Collapse Cell + Comprimi cella + + + Expand Cell + Espandi cella + + + Clear Result + Cancella risultato + + + Delete + Elimina + + + + + + + No script was returned when calling select script on object + Nessuno script è stato restituito nell'eseguire script sull'oggetto + + + Select + Seleziona + + + Create + Crea + + + Insert + Insert + + + Update + Aggiorna + + + Delete + Elimina + + + No script was returned when scripting as {0} on object {1} + Nessuno script è stato restituito da "Script come {0}" per l'oggetto {1} + + + Scripting Failed + Generazione Script non riuscita + + + No script was returned when scripting as {0} + Nessuno script è stato restituito nel generare lo script di {0} + + + + + + + Recent Connections + Connessioni recenti + + + Servers + Server + + + + + + + No Kernel + Nessun kernel + + + Cannot run cells as no kernel has been configured + Non è possibile eseguire le celle perché non è stato configurato alcun kernel + + + Error + Errore + + + + + + + Azure Data Studio + Azure Data Studio + + + Start + Avvia + + + New connection + Nuova connessione + + + New query + Nuova query + + + New notebook + Nuovo notebook + + + Open file + Apri file + + + Open file + Apri file + + + Deploy + Distribuzione + + + Deploy SQL Server… + Distribuisci SQL Server... + + + Recent + Recenti + + + More... + Altro... + + + No recent folders + Non ci sono cartelle recenti + + + Help + Guida + + + Getting started + Introduzione + + + Documentation + Documentazione + + + Report issue or feature request + Segnala problema o invia richiesta di funzionalità + + + GitHub repository + Repository GitHub + + + Release notes + Note sulla versione + + + Show welcome page on startup + Mostra la pagina iniziale all'avvio + + + Customize + Personalizza + + + Extensions + Estensioni + + + Download extensions that you need, including the SQL Server Admin pack and more + È possibile scaricare le estensioni necessarie, tra cui il pacchetto di amministrazione di SQL Server + + + Keyboard Shortcuts + Tasti di scelta rapida + + + Find your favorite commands and customize them + Consente di trovare i comandi preferiti e personalizzarli + + + Color theme + Tema colori + + + Make the editor and your code look the way you love + Tutto quel che serve per configurare editor e codice nel modo desiderato + + + Learn + Impara + + + Find and run all commands + Trova ed esegui tutti i comandi + + + Rapidly access and search commands from the Command Palette ({0}) + Accesso e ricerca rapida di comandi dal riquadro comandi ({0}) + + + Discover what's new in the latest release + Novità della versione più recente + + + New monthly blog posts each month showcasing our new features + Ogni mese nuovi post di blog che illustrano le nuove funzionalità + + + Follow us on Twitter + Seguici su Twitter + + + Keep up to date with how the community is using Azure Data Studio and to talk directly with the engineers. + È possibile tenersi aggiornati sull'utilizzo di Azure Data Studio nella community e parlare direttamente con i tecnici. + + + + + + + succeeded + riuscito + + + failed + non riuscito + + + in progress + In corso + + + not started + non avviato + + + canceled + annullato + + + canceling + annullamento in corso + + + + + + + Run + Esegui + + + Dispose Edit Failed With Error: + Modifica del metodo Dispose non riuscita. Errore: + + + Stop + Arresta + + + Show SQL Pane + Visualizza il riquadro SQL + + + Close SQL Pane + Chiudi riquadro SQL + + + + + + + Connect + Connetti + + + Disconnect + Disconnetti + + + Start + Avvia + + + New Session + Nuova sessione + + + Pause + Pausa + + + Resume + Riprendi + + + Stop + Arresta + + + Clear Data + Cancella dati + + + Auto Scroll: On + Scorrimento automatico: On + + + Auto Scroll: Off + Scorrimento automatico: disattivato + + + Toggle Collapsed Panel + Attiva/disattiva pannello compresso + + + Edit Columns + Modifica colonne + + + Find Next String + Trova la stringa successiva + + + Find Previous String + Trova la stringa precedente + + + Launch Profiler + Avvia profiler + + + Filter… + Filtro... + + + Clear Filter + Cancella filtro + + + + + + + Events (Filtered): {0}/{1} + Eventi (filtrati): {0}/{1} + + + Events: {0} + Eventi: {0} + + + Event Count + Conteggio eventi + + + + + + + Save As CSV + Salva come CSV + + + Save As JSON + Salva come JSON + + + Save As Excel + Salva come Excel + + + Save As XML + Salva come XML + + + Save to file is not supported by the backing data source + Il salvataggio nel file non è supportato dall'origine dei dati di supporto + + + Copy + Copia + + + Copy With Headers + Copia con intestazioni + + + Select All + Seleziona tutto + + + Copy + Copia + + + Copy All + Copia tutti + + + Maximize + Massimizza + + + Restore + Ripristina + + + Chart + Grafico + + + Visualizer + Visualizzatore + + + + + + + The extension "{0}" is using sqlops module which has been replaced by azdata module, the sqlops module will be removed in a future release. + L'estensione "{0}" usa il modulo sqlops che è stato sostituito dal modulo azdata. Il modulo sqlops verrà rimosso in una versione futura. + + + + + + + Table header background color + Colore di sfondo intestazione tabella + + + Table header foreground color + Colore di primo piano intestazione tabella + + + Disabled Input box background. + Sfondo della casella di Input disabilitata. + + + Disabled Input box foreground. + Colore di primo piano per il campo di input disabilitato. + + + Button outline color when focused. + Colore di contorno del pulsante quando è selezionato. + + + Disabled checkbox foreground. + Primo piano della casella di controllo disabilitato. + + + List/Table background color for the selected and focus item when the list/table is active + Colore di sfondo dell'elenco o della tabella per l'elemento selezionato e con stato attivo quando l'elenco o la tabella è attiva + + + SQL Agent Table background color. + Colore di sfondo della tabella di SQL Agent. + + + SQL Agent table cell background color. + Colore di sfondo delle celle della tabella di SQL Agent. + + + SQL Agent table hover background color. + Colore di sfondo di tabella al passaggio del mouse per SQL Server Agent. + + + SQL Agent heading background color. + Colore di sfondo dell'intestazione di SQL Agent. + + + SQL Agent table cell border color. + Colore del bordo delle celle della tabella di SQL Agent. + + + Results messages error color. + Colore dell'errore nei messaggi dei risultati. + + + + + + + Choose Results File + Scegli file dei risultati + + + CSV (Comma delimited) + CSV (delimitati da virgole) + + + JSON + JSON + + + Excel Workbook + Cartella di lavoro di Excel + + + XML + XML + + + Plain Text + Testo normale + + + Open file location + Apri percorso file + + + Open file + Apri file + + + + + + + Backup name + Nome backup + + + Recovery model + Modello di ripristino + + + Backup type + Tipo di backup + + + Backup files + File di backup + + + Algorithm + Algoritmo + + + Certificate or Asymmetric key + Certificato o Chiave Asimmetrica + + + Media + Media + + + Backup to the existing media set + Effettua il backup su supporto esistente + + + Backup to a new media set + Backup su un nuovo set di supporti + + + Append to the existing backup set + Appendi a set di backup esistente + + + Overwrite all existing backup sets + Sovrascrivi tutti i set di backup esistenti + + + New media set name + Nuovo nome del media set + + + New media set description + Nuova descrizione per il media-set + + + Perform checksum before writing to media + Esegui checksum prima di aggiornare il supporto + + + Verify backup when finished + Verifica backup al termine + + + Continue on error + Continua in caso di errore + + + Expiration + Scadenza + + + Set backup retain days + Giorni di mantenimento del set di backup + + + Copy-only backup + Backup di sola copia + + + Advanced Configuration + Configurazione avanzata + + + Compression + Compressione + + + Set backup compression + Imposta compressione backup + + + Encryption + Crittografia + + + Transaction log + Log delle transazioni + + + Truncate the transaction log + Tronca il log delle transazioni + + + Backup the tail of the log + Esegui backup della parte finale del registro + + + Reliability + Affidabilità + + + Media name is required + Nome del supporto richiesto + + + No certificate or asymmetric key is available + Nessun certificato o chiave asimmetrica disponibile + + + Add a file + Aggiungi file + + + Remove files + Rimuovi i file + + + Invalid input. Value must be greater than or equal 0. + Input non valido. Il valore deve essere maggiore o uguale 0. + + + Script + Script + + + Backup + Backup + + + Cancel + Annulla + + + Only backup to file is supported + È supportato solo il backup su file + + + Backup file path is required + Percorso file di backup necessario + + + + + + + Results + Risultati + + + Messages + Messaggi + + + + + + + There is no data provider registered that can provide view data. + Non ci sono data provider registrati che possono fornire una visualizzazione dei dati. + + + Collapse All + Comprimi tutto + + + + + + + Home + Home + + + + + + + The "{0}" section has invalid content. Please contact extension owner. + Il contenuto della sezione "{0}" non è valido. Contattare il proprietario dell'estensione. + + + + + + + Jobs + Processi + + + Notebooks + Notebooks + + + Alerts + Avvisi + + + Proxies + Proxy + + + Operators + Operatori + + + + + + + Loading + Caricamento + + + + + + + SERVER DASHBOARD + SERVER DASHBOARD + + + + + + + DATABASE DASHBOARD + DATABASE DASHBOARD + + + + + + + Edit + Modifica + + + Exit + Esci + + + Refresh + Aggiorna + + + Toggle More + Attiva/disattiva di più + + + Delete Widget + Elimina Widget + + + Click to unpin + Fare clic per sbloccare + + + Click to pin + Click per bloccare (pin) + + + Open installed features + Mostra funzionalità installate + + + Collapse + Comprimi + + + Expand + Espandi + + + + + + + Steps + Passaggi + + + + + + + StdIn: + Stdin: + + + + + + + Add code + Aggiungi codice + + + Add text + Aggiungi testo + + + Create File + Crea file + + + Could not display contents: {0} + Non è stato possibile visualizzare il contenuto: {0} + + + Please install the SQL Server 2019 extension to run cells. + Installare l'estensione di SQL Server 2019 per eseguire le celle. + + + Install Extension + Installa estensione + + + Code + Codice + + + Text + Testo + + + Run Cells + Esegui celle + + + Clear Results + Cancella risultati + + + < Previous + < Indietro + + + Next > + Avanti > + + + cell with URI {0} was not found in this model + la cella con URI {0} non è stata trovata in questo modello + + + Run Cells failed - See error in output of the currently selected cell for more information. + Comando Esegui celle non riuscito. Per altre informazioni, vedere l'errore nell'output della cella attualmente selezionata. + + + + + + + Click on + Fare clic su + + + + Code + + Codice + + + or + oppure + + + + Text + + Testo + + + to add a code or text cell + per aggiungere una cella di testo o codice + + + + + + + Database + Database + + + Files and filegroups + File e filegroup + + + Full + Completo + + + Differential + Differenziale + + + Transaction Log + Log delle transazioni + + + Disk + Disco + + + Url + URL + + + Use the default server setting + Usa l'impostazione predefinita del server + + + Compress backup + Comprimi backup + + + Do not compress backup + Non comprimere il backup + + + Server Certificate + Certificato server + + + Asymmetric Key + Chiave asimmetrica + + + Backup Files + File di backup + + + All Files + Tutti i file + + + + + + + No connections found. + Non sono state trovate connessioni. + + + Add Connection + Aggiungi Connessione + + + + + + + Failed to change database + Impossibile cambiare database + + + + + + + Name + Nome + + + Email Address + Indirizzo email + + + Enabled + Abilitato + + + + + + + Name + Nome + + + Last Occurrence + Ultima occorrenza + + + Enabled + Abilitato + + + Delay Between Responses (in secs) + Ritardo tra le risposte (in sec) + + + Category Name + Nome categoria + + + + + + + Account Name + Nome dell'account + + + Credential Name + Nome della credenziale + + + Description + Descrizione + + + Enabled + Abilitato + + + + + + + Unable to load dashboard properties + Non è possibile caricare le proprietà del dashboard + + + + + + + Search by name of type (a:, t:, v:, f:, or sp:) + Ricerca per nome della tipologia (a:, t:, v:, f:, o sp:) + + + Search databases + Cerca database + + + Unable to load objects + Impossibile caricare gli oggetti + + + Unable to load databases + Impossibile caricare i database + + + + + + + Auto Refresh: OFF + Aggiornamento automatico: disattivato + + + Last Updated: {0} {1} + Ultimo aggiornamento: {0} {1} + + + No results to show + Nessun risultato da mostrare + + + + + + + No {0}renderer could be found for output. It has the following MIME types: {1} + Non è stato possibile trovare alcun renderer {0} per l'output. Include i tipi MIME seguenti: {1} + + + safe + sicuro + + + No component could be found for selector {0} + Non è stato possibile trovare alcun componente per il selettore {0} + + + Error rendering component: {0} + Errore durante il rendering del componente: {0} + + + + + + + Connected to + Connesso a + + + Disconnected + Disconnesso + + + Unsaved Connections + Connessioni non salvate + + + + + + + Delete Row + Elimina riga + + + Revert Current Row + Ripristina la riga corrente + + + + + + + Step ID + ID passaggio + + + Step Name + Nome del passaggio + + + Message + Messaggio + + + + + + + XML Showplan + Showplan XML + + + Results grid + Griglia dei risultati + + + + + + + Please select active cell and try again + Selezionare la cella attiva e riprovare + + + Run cell + Esegui cella + + + Cancel execution + Annulla esecuzione + + + Error on last run. Click to run again + Errore durante l'ultima esecuzione. Fare clic per ripetere l'esecuzione + + + + + + + Add an account... + Aggiungi un account... + + + <Default> + <Predefinito> + + + Loading... + Caricamento... + + + Server group + Gruppo di server + + + <Default> + <Predefinito> + + + Add new group... + Aggiungi nuovo gruppo... + + + <Do not save> + <Non salvare> + + + {0} is required. + {0} è obbligatorio. + + + {0} will be trimmed. + {0} verrà tagliato. + + + Remember password + Ricorda password + + + Account + Account + + + Refresh account credentials + Aggiorna credenziali dell'account + + + Azure AD tenant + Tenant di Azure AD + + + Select Database Toggle Dropdown + Menu a tendina per la selezione del Database + + + Name (optional) + Nome (facoltativo) + + + Advanced... + Avanzate... + + + You must select an account + È necessario selezionare un account + + + + + + + Cancel + Annulla + + + The task is failed to cancel. + La cancellazione dell'attività non è andata a buon fine. + + + Script + Script + + + + + + + Date Created: + Data di creazione: + + + Notebook Error: + Errore del notebook: + + + Job Error: + Errore del processo: + + + Pinned + Aggiunta + + + Recent Runs + Esecuzioni recenti + + + Past Runs + Esecuzioni precedenti + + + + + + + No tree view with id '{0}' registered. + Nessuna visualizzazione di struttura ad albero con ID '{0}' registrata. + + + + + + + Loading... + Caricamento... + + + + + + + Dashboard Tabs ({0}) + Schede del dashboard ({0}) + + + Id + ID + + + Title + Titolo + + + Description + Descrizione + + + Dashboard Insights ({0}) + Informazioni dettagliate dashboard ({0}) + + + Id + ID + + + Name + Nome + + + When + Quando + + + + + + + Chart + Grafico + + + + + + + Operation + Operazione + + + Object + Oggetto + + + Est Cost + Costo Stimato + + + Est Subtree Cost + Costo Stim. Sottoalbero + + + Actual Rows + Righe effettive + + + Est Rows + Righe stimate + + + Actual Executions + Esecuzioni effettive + + + Est CPU Cost + Costo Stimato di CPU + + + Est IO Cost + Costo stimato IO + + + Parallel + Parallelo + + + Actual Rebinds + Rebind Effettivi + + + Est Rebinds + Rebind Stimati + + + Actual Rewinds + Rewind effettivi + + + Est Rewinds + Riavvolge + + + Partitioned + Partizionato + + + Top Operations + Operazioni principali + + + + + + + Query Plan + Piano di esecuzione della query + + + + + + + Could not find component for type {0} + Non è stato possibile trovare il componente per il tipo {0} + + + + + + + A NotebookProvider with valid providerId must be passed to this method + È necessario passare a questo metodo un elemento NotebookProvider con providerId valido + + + + + + + A NotebookProvider with valid providerId must be passed to this method + È necessario passare a questo metodo un elemento NotebookProvider con providerId valido + + + no notebook provider found + non è stato trovato alcun provider di notebook + + + No Manager found + Non è stato trovato alcun gestore + + + Notebook Manager for notebook {0} does not have a server manager. Cannot perform operations on it + Il gestore del notebook {0} non include un gestore di server. Non è possibile eseguirvi operazioni + + + Notebook Manager for notebook {0} does not have a content manager. Cannot perform operations on it + Il gestore del notebook {0} non include un gestore di contenuti. Non è possibile eseguirvi operazioni + + + Notebook Manager for notebook {0} does not have a session manager. Cannot perform operations on it + Il gestore del notebook {0} non include un gestore di sessioni. Non è possibile eseguirvi operazioni + + + + + + + SQL kernel error + Errore del kernel SQL + + + A connection must be chosen to run notebook cells + Per eseguire le celle del notebook, è necessario scegliere una connessione + + + Displaying Top {0} rows. + Visualizzazione delle prime {0} righe. + + + + + + + Show Recommendations + Mostra gli elementi consigliati + + + Install Extensions + Installa estensioni + + + + + + + Name + Nome + + + Last Run + Ultima esecuzione + + + Next Run + Prossima esecuzione + + + Enabled + Abilitato + + + Status + Stato + + + Category + Categoria + + + Runnable + Eseguibile + + + Schedule + Pianificazione + + + Last Run Outcome + Risultato ultima esecuzione + + + Previous Runs + Esecuzioni precedenti + + + No Steps available for this job. + Non sono disponibili passaggi per questo processo. + + + Error: + Errore: + + + + + + + Find + Trova + + + Find + Trova + + + Previous match + Corrispondenza precedente + + + Next match + Trova successivo + + + Close + Chiudi + + + Your search returned a large number of results, only the first 999 matches will be highlighted. + La ricerca ha restituito un numero elevato di risultati. Verranno evidenziate solo le prime 999 corrispondenze. + + + {0} of {1} + {0} di {1} + + + No Results + Nessun risultato + + + + + + + Run Query + Esegui Query + + + + + + + Done + Operazione completata + + + Cancel + Annulla + + + Generate script + Genera lo script + + + Next + Avanti + + + Previous + Indietro + + + + + + + A server group with the same name already exists. + Esiste già un gruppo di server con lo stesso nome. + + + + + + + {0} is an unknown container. + {0} è un contenitore sconosciuto. + + + + + + + Loading Error... + Errore di caricamento... + + + + + + + Failed + non riuscito + + + Succeeded + riuscito + + + Retry + Riprova + + + Cancelled + annullato + + + In Progress + In corso + + + Status Unknown + Stato sconosciuto + + + Executing + In esecuzione + + + Waiting for Thread + In attesa del thread + + + Between Retries + Tra i tentativi + + + Idle + Inattivo + + + Suspended + Sospeso + + + [Obsolete] + [Obsoleto] + + + Yes + Sì + + + No + No + + + Not Scheduled + Non pianificata + + + Never Run + Non eseguire mai + + + + + + + Name + Nome + + + Target Database + Database di destinazione + + + Last Run + Ultima esecuzione + + + Next Run + Prossima esecuzione + + + Status + Stato + + + Last Run Outcome + Risultato ultima esecuzione + + + Previous Runs + Esecuzioni precedenti + + + No Steps available for this job. + Non sono disponibili passaggi per questo processo. + + + Error: + Errore: + + + Notebook Error: + Errore del notebook: + + + + + + + Home + Home + + + No connection information could be found for this dashboard + Impossibile trovare informazioni di connessione per questa dashboard + + + + + + + Data + Dati + + + Connection + Connessione + + + Query + Query + + + Notebook + Blocco appunti + + + SQL + SQL + + + Microsoft SQL Server + Microsoft SQL Server + + + Dashboard + Dashboard + + + Profiler + Profiler + + + + + + + Close + Chiudi + + + + + + + Success + Esito positivo + + + Error + Errore + + + Refresh + Aggiorna + + + New Job + Nuovo processo + + + Run + Esegui + + + : The job was successfully started. + : il processo è stato avviato. + + + Stop + Arresta + + + : The job was successfully stopped. + : il processo è stato arrestato. + + + Edit Job + Modifica processo + + + Open + Apri + + + Delete Job + Elimina processo + + + Are you sure you'd like to delete the job '{0}'? + Eliminare il processo '{0}'? + + + Could not delete job '{0}'. +Error: {1} + Impossibile eliminare il processo '{0}'. +Errore: {1} + + + The job was successfully deleted + Il processo è stato eliminato + + + New Step + Nuovo passaggio + + + Delete Step + Elimina passaggio + + + Are you sure you'd like to delete the step '{0}'? + Eliminare il passaggio '{0}'? + + + Could not delete step '{0}'. +Error: {1} + Non è stato possibile eliminare il passaggio '{0}'. +Errore: {1} + + + The job step was successfully deleted + Il passaggio del processo è stato eliminato + + + New Alert + Nuovo avviso + + + Edit Alert + Modifica avviso + + + Delete Alert + Elimina avviso + + + Cancel + Annulla + + + Are you sure you'd like to delete the alert '{0}'? + Eliminare l'avviso '{0}'? + + + Could not delete alert '{0}'. +Error: {1} + Non è stato possibile eliminare l'avviso '{0}'. +Errore: {1} + + + The alert was successfully deleted + L'avviso è stato eliminato + + + New Operator + Nuovo Operatore + + + Edit Operator + Modifica operatore + + + Delete Operator + Elimina operatore + + + Are you sure you'd like to delete the operator '{0}'? + Eliminare l'operatore '{0}'? + + + Could not delete operator '{0}'. +Error: {1} + Non è stato possibile eliminare l'operatore '{0}'. +Errore: {1} + + + The operator was deleted successfully + L'operatore è stato eliminato + + + New Proxy + Nuovo proxy + + + Edit Proxy + Modifica Proxy + + + Delete Proxy + Elimina proxy + + + Are you sure you'd like to delete the proxy '{0}'? + Eliminare il proxy '{0}'? + + + Could not delete proxy '{0}'. +Error: {1} + Non è stato possibile eliminare il proxy '{0}'. +Errore: {1} + + + The proxy was deleted successfully + Il proxy è stato eliminato + + + New Notebook Job + Nuovo processo Notebook + + + Edit + Modifica + + + Open Template Notebook + Apri notebook modello + + + Delete + Elimina + + + Are you sure you'd like to delete the notebook '{0}'? + Eliminare il notebook '{0}'? + + + Could not delete notebook '{0}'. +Error: {1} + Non è stato possibile eliminare il notebook '{0}'. +Errore: {1} + + + The notebook was successfully deleted + Il notebook è stato eliminato + + + Pin + Aggiungi + + + Delete + Elimina + + + Unpin + Rimuovi + + + Rename + Rinomina + + + Open Latest Run + Apri ultima esecuzione + + + + + + + Please select a connection to run cells for this kernel + Selezionare una connessione per eseguire le celle per questo kernel + + + Failed to delete cell. + Non è stato possibile eliminare la cella. + + + Failed to change kernel. Kernel {0} will be used. Error was: {1} + Non è stato possibile modificare il kernel. Verrà usato il kernel {0}. Errore: {1} + + + Failed to change kernel due to error: {0} + Non è stato possibile modificare il kernel a causa dell'errore: {0} + + + Changing context failed: {0} + La modifica del contesto non è riuscita: {0} + + + Could not start session: {0} + Non è stato possibile avviare la sessione: {0} + + + A client session error occurred when closing the notebook: {0} + Si è verificato un errore della sessione client durante la chiusura del notebook: {0} + + + Can't find notebook manager for provider {0} + Non è possibile trovare il gestore di notebook per il provider {0} + + + + + + + An error occurred while starting the notebook session + Si è verificato un errore durante l'avvio di della sessione del notebook + + + Server did not start for unknown reason + Il server non è stato avviato per motivi sconosciuti + + + Kernel {0} was not found. The default kernel will be used instead. + Il kernel {0} non è stato trovato. Verrà usato il kernel predefinito. + + + + + + + Unknown component type. Must use ModelBuilder to create objects + Tipo di componente sconosciuto. Per creare oggetti, è necessario usare ModelBuilder + + + The index {0} is invalid. + L'indice {0} non è valido. + + + Unkown component configuration, must use ModelBuilder to create a configuration object + La configurazione del componente è sconosciuta. È necessario usare ModelBuilder per creare un oggetto di configurazione + + + + + + + Horizontal Bar + A barre orizzontale + + + Bar + A barre + + + Line + A linee + + + Pie + A torta + + + Scatter + A dispersione + + + Time Series + Serie temporale + + + Image + Immagine + + + Count + Conteggio + + + Table + Tabella + + + Doughnut + Ad anello + + + + + + + OK + OK + + + Clear + Cancella + + + Cancel + Annulla + + + + + + + Cell execution cancelled + Esecuzione delle celle annullata + + + Query execution was canceled + L'esecuzione della query è stata annullata + + + The session for this notebook is not yet ready + La sessione per questo notebook non è ancora pronta + + + The session for this notebook will start momentarily + La sessione per questo notebook verrà avviata tra qualche istante + + + No kernel is available for this notebook + Non è disponibile alcun kernel per questo notebook + + + + + + + Select Connection + Seleziona connessione + + + localhost + localhost + + + + + + + Data Direction + Direzione dei dati + + + Vertical + Verticale + + + Horizontal + Orizzontale + + + Use column names as labels + Usa i nomi di colonna come etichette + + + Use first column as row label + Utilizza la prima colonna come etichetta di riga + + + Legend Position + Posizione Legenda + + + Y Axis Label + Etichetta asse Y + + + Y Axis Minimum Value + Valore minimo dell'asse Y + + + Y Axis Maximum Value + Valore massimo dell'asse Y + + + X Axis Label + Etichetta asse X + + + X Axis Minimum Value + Valore minimo asse X + + + X Axis Maximum Value + Valore massimo dell'asse X + + + X Axis Minimum Date + Data minima asse X + + + X Axis Maximum Date + Data massima asse X + + + Data Type + Tipo di dato + + + Number + Numero + + + Point + Punto + + + Chart Type + Tipo di grafico + + + Encoding + Codifica + + + Image Format + Formato immagine + + + + + + + Create Insight + Crea Insight + + + Cannot create insight as the active editor is not a SQL Editor + Impossibile creare l'insight perchè l'editor attivo non è un Editor SQL + + + My-Widget + My-Widget + + + Copy as image + Copia come immagine + + + Could not find chart to save + Impossibile trovare un grafico da salvare + + + Save as image + Salva come immagine + + + PNG + PNG + + + Saved Chart to path: {0} + Grafico salvato in: {0} + + + + + + + Changing editor types on unsaved files is unsupported + La modifica dei tipi di editor su file non salvati non è supportata + + + + + + + Table does not contain a valid image + La tabella non contiene un'immagine valida + + + + + + + Series {0} + Serie {0} diff --git a/resources/xlf/ja/azurecore.ja.xlf b/resources/xlf/ja/azurecore.ja.xlf index e430c3f2d7cd..c646878111f2 100644 --- a/resources/xlf/ja/azurecore.ja.xlf +++ b/resources/xlf/ja/azurecore.ja.xlf @@ -200,4 +200,20 @@ + + + + SQL Managed Instances + SQL マãƒãƒ¼ã‚¸ãƒ‰ インスタンス + + + + + + + Azure Database for PostgreSQL Servers + Azure Database for PostgreSQL サーãƒãƒ¼ + + + \ No newline at end of file diff --git a/resources/xlf/ja/big-data-cluster.ja.xlf b/resources/xlf/ja/big-data-cluster.ja.xlf index d08b13e87ff7..614f8b77a47f 100644 --- a/resources/xlf/ja/big-data-cluster.ja.xlf +++ b/resources/xlf/ja/big-data-cluster.ja.xlf @@ -38,6 +38,14 @@ Delete Mount マウントã®å‰Šé™¤ + + Big Data Cluster + ビッグ データ クラスター + + + Ignore SSL verification errors against SQL Server Big Data Cluster endpoints such as HDFS, Spark, and Controller if true + HDFSã€Sparkã€ã‚³ãƒ³ãƒˆãƒ­ãƒ¼ãƒ©ãƒ¼ãªã©ã® SQL Server ビッグ データ クラスター エンドãƒã‚¤ãƒ³ãƒˆã«å¯¾ã™ã‚‹ SSL 検証エラーを無視ã™ã‚‹ (true ã®å ´åˆ) + @@ -58,26 +66,86 @@ Deleting 削除中 - - Waiting For Deletion - 削除ã®å¾…機中 - Deleted 削除済㿠+ + Applying Upgrade + アップグレードã®é©ç”¨ + Upgrading アップグレード中 - - Waiting For Upgrade - アップグレードã®å¾…機中 + + Applying Managed Upgrade + マãƒãƒ¼ã‚¸ãƒ‰ アップグレードã®é©ç”¨ä¸­ + + + Managed Upgrading + マãƒãƒ¼ã‚¸ãƒ‰ アップグレード + + + Rollback + ロールãƒãƒƒã‚¯ + + + Rollback In Progress + ロールãƒãƒƒã‚¯ãŒé€²è¡Œä¸­ + + + Rollback Complete + ロールãƒãƒƒã‚¯ã®å®Œäº† Error エラー + + Creating Secrets + シークレットã®ä½œæˆä¸­ + + + Waiting For Secrets + シークレットã®å¾…機中 + + + Creating Groups + グループã®ä½œæˆä¸­ + + + Waiting For Groups + グループã®å¾…機中 + + + Creating Resources + リソースã®ä½œæˆä¸­ + + + Waiting For Resources + リソースã®å¾…機中 + + + Creating Kerberos Delegation Setup + Kerberos 委任セットアップã®ä½œæˆä¸­ + + + Waiting For Kerberos Delegation Setup + Kerberos 委任ã®ã‚»ãƒƒãƒˆã‚¢ãƒƒãƒ—ã‚’å¾…æ©Ÿã—ã¦ã„ã¾ã™ + + + Waiting For Deletion + 削除ã®å¾…機中 + + + Waiting For Upgrade + アップグレードã®å¾…機中 + + + Upgrade Paused + アップグレードãŒä¸€æ™‚åœæ­¢ã—ã¾ã—㟠+ Running 実行ã—ã¦ã„ã¾ã™ @@ -162,6 +230,78 @@ Unhealthy 異常 + + Unexpected error retrieving BDC Endpoints: {0} + BDC エンドãƒã‚¤ãƒ³ãƒˆã®å–得中ã«äºˆæœŸã—ãªã„エラーãŒç™ºç”Ÿã—ã¾ã—ãŸ: {0} + + + + + + + Basic + 基本 + + + Windows Authentication + Windows èªè¨¼ + + + Login to controller failed + コントローラーã¸ã®ãƒ­ã‚°ã‚¤ãƒ³ã«å¤±æ•—ã—ã¾ã—㟠+ + + Login to controller failed: {0} + コントローラーã¸ã®ãƒ­ã‚°ã‚¤ãƒ³ã«å¤±æ•—ã—ã¾ã—ãŸ: {0} + + + Username is required + ユーザーåãŒå¿…è¦ã§ã™ + + + Password is required + パスワードãŒå¿…è¦ã§ã™ + + + url + URL + + + username + ユーザーå + + + password + パスワード + + + Cluster Management URL + クラスター管ç†ã® URL + + + Authentication type + èªè¨¼ã®ç¨®é¡ž + + + Username + ユーザーå + + + Password + パスワード + + + Cluster Connection + クラスター接続 + + + OK + OK + + + Cancel + キャンセル + @@ -200,6 +340,22 @@ + + + + Connect to Controller (preview) + コントローラーã¸ã®æŽ¥ç¶š (プレビュー) + + + + + + + View Details + 詳細ã®è¡¨ç¤º + + + @@ -207,8 +363,8 @@ コントローラー エンドãƒã‚¤ãƒ³ãƒˆæƒ…å ±ãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“ã§ã—㟠- Big Data Cluster Dashboard - - ビッグ データ クラスター ダッシュボード - + Big Data Cluster Dashboard (preview) - + ビッグ データ クラスター ダッシュボード (プレビュー) - Yes @@ -263,8 +419,8 @@ パスワードãŒå¿…è¦ã§ã™ - Add New Controller - æ–°ã—ã„コントローラーを追加ã™ã‚‹ + Add New Controller (preview) + æ–°ã—ã„コントローラーã®è¿½åŠ  (プレビュー) url @@ -283,8 +439,8 @@ パスワードを記録ã™ã‚‹ - URL - URL + Cluster Management URL + クラスター管ç†ã® URL Authentication type @@ -319,7 +475,7 @@ トラブルシューティング - Big data cluster overview + Big Data Cluster overview ビッグ データ クラスターã®æ¦‚è¦ @@ -362,18 +518,18 @@ Metrics and Logs メトリックスã¨ãƒ­ã‚° - - Metrics - メトリックス + + Node Metrics + ノード メトリックス + + + SQL Metrics + SQL 指標 Logs ログ - - View Details - 詳細ã®è¡¨ç¤º - @@ -422,31 +578,35 @@ Endpoint エンドãƒã‚¤ãƒ³ãƒˆ - - View Details - 詳細ã®è¡¨ç¤º + + Unexpected error retrieving BDC Endpoints: {0} + BDC エンドãƒã‚¤ãƒ³ãƒˆã®å–得中ã«äºˆæœŸã—ãªã„エラーãŒç™ºç”Ÿã—ã¾ã—ãŸ: {0} + + + The dashboard requires a connection. Please click retry to enter your credentials. + ダッシュボードã«ã¯æŽ¥ç¶šãŒå¿…è¦ã§ã™ã€‚[å†è©¦è¡Œ] をクリックã—ã¦è³‡æ ¼æƒ…報を入力ã—ã¦ãã ã•ã„。 + + + Unexpected error occurred: {0} + 予期ã—ãªã„エラーãŒç™ºç”Ÿã—ã¾ã—ãŸ: {0} Copy コピー + + Endpoint '{0}' copied to clipboard + エンドãƒã‚¤ãƒ³ãƒˆ '{0}' ãŒã‚¯ãƒªãƒƒãƒ—ボードã«ã‚³ãƒ”ーã•ã‚Œã¾ã—㟠+ - - Basic - 基本 - - - Windows Authentication - Windows èªè¨¼ - Mount Configuration ãƒžã‚¦ãƒ³ãƒˆæ§‹æˆ - + HDFS Path HDFS パス @@ -454,22 +614,6 @@ Bad formatting of credentials at {0} {0} ã§ã®è³‡æ ¼æƒ…å ±ã®æ›¸å¼è¨­å®šãŒæ­£ã—ãã‚ã‚Šã¾ã›ã‚“ - - Login to controller failed - コントローラーã¸ã®ãƒ­ã‚°ã‚¤ãƒ³ã«å¤±æ•—ã—ã¾ã—㟠- - - Login to controller failed: {0} - コントローラーã¸ã®ãƒ­ã‚°ã‚¤ãƒ³ã«å¤±æ•—ã—ã¾ã—ãŸ: {0} - - - Username is required - ユーザーåãŒå¿…è¦ã§ã™ - - - Password is required - パスワードãŒå¿…è¦ã§ã™ - Mounting HDFS folder on path {0} パス {0} 上㫠HDFS フォルダーをマウントã—ã¦ã„ã¾ã™ @@ -494,58 +638,30 @@ Unknown error occurred during the mount process マウント プロセス中ã«ä¸æ˜Žãªã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—㟠- - url - URL - - - username - ユーザーå - - - password - パスワード - - - URL - URL - - - Authentication type - èªè¨¼ã®ç¨®é¡ž - - - Username - ユーザーå - - - Password - パスワード - - - Cluster Connection - クラスター接続 - - - OK - OK - - - Cancel - キャンセル - - Mount HDFS Folder - HDFS フォルダーã®ãƒžã‚¦ãƒ³ãƒˆ + Mount HDFS Folder (preview) + HDFS フォルダーã®ãƒžã‚¦ãƒ³ãƒˆ (プレビュー) + + + Path to a new (non-existing) directory which you want to associate with the mount + マウントã«é–¢é€£ä»˜ã‘ã‚‹æ–°ã—ã„ (存在ã—ãªã„) ディレクトリã¸ã®ãƒ‘ス - + Remote URI リモート URI - + + The URI to the remote data source. Example for ADLS: abfs://fs@saccount.dfs.core.windows.net/ + リモート データ ソースã¸ã® URI。ADLS ã®ä¾‹: abfs://fs@saccount.dfs.core.windows.net/ + + Credentials 資格情報 + + Mount credentials for authentication to remote data source for reads + èªè¨¼ç”¨ã®è³‡æ ¼æƒ…報をリモート データ ソースã«ãƒžã‚¦ãƒ³ãƒˆã—ã¦èª­ã¿å–りを行ㆠ+ Refresh Mount マウントã®æ›´æ–° diff --git a/resources/xlf/ja/schema-compare.ja.xlf b/resources/xlf/ja/schema-compare.ja.xlf index abcba43b547d..b38e3a5b80ae 100644 --- a/resources/xlf/ja/schema-compare.ja.xlf +++ b/resources/xlf/ja/schema-compare.ja.xlf @@ -756,7 +756,7 @@ Specifies that publish should always drop and re-create an assembly if there is a difference instead of issuing an ALTER ASSEMBLY statement - アセンブリã«ç›¸é•ãŒã‚ã‚‹å ´åˆã€ç™ºè¡Œã§ã¯ ALTER ASSEMBLY ステートメントを発行ã™ã‚‹ã®ã§ã¯ãªãã€å¸¸ã«ã‚¢ã‚»ãƒ³ãƒ–リを削除ã—ã¦ä½œæˆã—ç›´ã™ã“ã¨ã‚’指定ã—ã¾ã™ã€‚ + アセンブリã«ç›¸é•ãŒã‚ã‚‹å ´åˆã€å…¬é–‹ã§ã¯ ALTER ASSEMBLY ステートメントを発行ã™ã‚‹ã®ã§ã¯ãªãã€å¸¸ã«ã‚¢ã‚»ãƒ³ãƒ–リを削除ã—ã¦ä½œæˆã—ç›´ã™ã“ã¨ã‚’指定ã—ã¾ã™ã€‚ Specifies whether transactional statements should be used where possible when you publish to a database. @@ -924,7 +924,7 @@ Specifies whether differences in table column order should be ignored or updated when you publish to a database. - データベースã«ç™ºè¡Œã™ã‚‹ã¨ãã«ã€ãƒ†ãƒ¼ãƒ–ルã®åˆ—ã®é †åºã®é•ã„を無視ã™ã‚‹ã‹ã€æ›´æ–°ã™ã‚‹ã‹ã‚’指定ã—ã¾ã™ã€‚ + データベースã«å…¬é–‹ã™ã‚‹ã¨ãã«ã€ãƒ†ãƒ¼ãƒ–ルã®åˆ—ã®é †åºã®é•ã„を無視ã™ã‚‹ã‹ã€æ›´æ–°ã™ã‚‹ã‹ã‚’指定ã—ã¾ã™ã€‚ diff --git a/resources/xlf/ja/sql.ja.xlf b/resources/xlf/ja/sql.ja.xlf index 225cd334bfd6..8b13d1a611fa 100644 --- a/resources/xlf/ja/sql.ja.xlf +++ b/resources/xlf/ja/sql.ja.xlf @@ -1,14 +1,5574 @@  - + - - SQL Language Basics - SQL ã®åŸºæœ¬è¨€èªžã‚µãƒãƒ¼ãƒˆ + + Copying images is not supported + イメージã®ã‚³ãƒ”ーã¯ã‚µãƒãƒ¼ãƒˆã•ã‚Œã¦ã„ã¾ã›ã‚“ - - Provides syntax highlighting and bracket matching in SQL files. - SQL ファイル内ã§æ§‹æ–‡ãƒã‚¤ãƒ©ã‚¤ãƒˆã€ã‹ã£ã“一致をæä¾›ã—ã¾ã™ã€‚ + + + + + + Get Started + 開始ã™ã‚‹ + + + Show Getting Started + 概è¦ã®è¡¨ç¤º + + + Getting &&Started + 作業ã®é–‹å§‹(&&S) + && denotes a mnemonic + + + + + + + QueryHistory + クエリ履歴 + + + Whether Query History capture is enabled. If false queries executed will not be captured. + クエリ履歴キャプãƒãƒ£ãŒæœ‰åŠ¹ã‹ã©ã†ã‹ã€‚false ã®å ´åˆã€å®Ÿè¡Œã•ã‚ŒãŸã‚¯ã‚¨ãƒªã¯ã‚­ãƒ£ãƒ—ãƒãƒ£ã•ã‚Œã¾ã›ã‚“。 + + + View + ビュー + + + Query History + クエリ履歴 + + + &&Query History + クエリ履歴(&&Q) + && denotes a mnemonic + + + + + + + Connecting: {0} + 接続: {0} + + + Running command: {0} + 実行中ã®ã‚³ãƒžãƒ³ãƒ‰: {0} + + + Opening new query: {0} + æ–°ã—ã„クエリを開ã„ã¦ã„ã¾ã™: {0} + + + Cannot connect as no server information was provided + サーãƒãƒ¼æƒ…å ±ãŒæä¾›ã•ã‚Œã¦ã„ãªã„ãŸã‚接続ã§ãã¾ã›ã‚“ + + + Could not open URL due to error {0} + エラー {0} ã«ã‚ˆã‚Š URL ã‚’é–‹ãã“ã¨ãŒã§ãã¾ã›ã‚“ã§ã—㟠+ + + This will connect to server {0} + ã“ã‚Œã«ã‚ˆã‚Šã€ã‚µãƒ¼ãƒãƒ¼ {0} ã«æŽ¥ç¶šã•ã‚Œã¾ã™ + + + Are you sure you want to connect? + 接続ã—ã¾ã™ã‹? + + + &&Open + é–‹ã(&&O) + + + Connecting query file + クエリ ファイルã¸ã®æŽ¥ç¶š + + + + + + + Error + エラー + + + Warning + 警告 + + + Info + 情報 + + + + + + + Saving results into different format disabled for this data provider. + ã“ã®ãƒ‡ãƒ¼ã‚¿ プロãƒã‚¤ãƒ€ãƒ¼ã«å¯¾ã—ã¦ç„¡åŠ¹ã«ãªã£ã¦ã„る別ã®å½¢å¼ã§çµæžœã‚’ä¿å­˜ã—ã¦ã„ã¾ã™ã€‚ + + + Cannot serialize data as no provider has been registered + プロãƒã‚¤ãƒ€ãƒ¼ãŒç™»éŒ²ã•ã‚Œã¦ã„ãªã„ãŸã‚ã€ãƒ‡ãƒ¼ã‚¿ã‚’シリアル化ã§ãã¾ã›ã‚“ + + + Serialization failed with an unknown error + ä¸æ˜Žãªã‚¨ãƒ©ãƒ¼ã«ã‚ˆã‚Šã‚·ãƒªã‚¢ãƒ«åŒ–ã«å¤±æ•—ã—ã¾ã—㟠+ + + + + + + Connection is required in order to interact with adminservice + 管ç†ã‚µãƒ¼ãƒ“スã¨ã®å¯¾è©±ã«ã¯æŽ¥ç¶šãŒå¿…è¦ + + + No Handler Registered + 登録ã•ã‚Œã¦ã„ã‚‹ãƒãƒ³ãƒ‰ãƒ©ãƒ¼ãŒã‚ã‚Šã¾ã›ã‚“。 + + + + + + + Select a file + ファイルをé¸æŠž + + + + + + + Disconnect + 切断 + + + Refresh + 最新ã®æƒ…å ±ã«æ›´æ–° + + + + + + + Results Grid and Messages + çµæžœã‚°ãƒªãƒƒãƒ‰ã¨ãƒ¡ãƒƒã‚»ãƒ¼ã‚¸ + + + Controls the font family. + フォント ファミリを制御ã—ã¾ã™ã€‚ + + + Controls the font weight. + フォントã®å¤ªã•ã‚’制御ã—ã¾ã™ã€‚ + + + Controls the font size in pixels. + フォント サイズ (ピクセルå˜ä½) を制御ã—ã¾ã™ã€‚ + + + Controls the letter spacing in pixels. + 文字間隔 (ピクセルå˜ä½) を制御ã—ã¾ã™ã€‚ + + + Controls the row height in pixels + ピクセルå˜ä½ã§è¡Œã®é«˜ã•ã‚’制御ã™ã‚‹ + + + Controls the cell padding in pixels + セル内ã®ã‚¹ãƒšãƒ¼ã‚¹ã‚’ピクセルå˜ä½ã§åˆ¶å¾¡ã—ã¾ã™ + + + Auto size the columns width on inital results. Could have performance problems with large number of columns or large cells + 最åˆã®çµæžœã«ã¤ã„ã¦ã€åˆ—ã®å¹…を自動サイズ設定ã—ã¾ã™ã€‚多数ã®åˆ—ãŒã‚ã‚‹å ´åˆã‚„大ãã„セルãŒã‚ã‚‹å ´åˆã€ãƒ‘フォーマンスã®å•é¡ŒãŒç™ºç”Ÿã™ã‚‹å¯èƒ½æ€§ãŒã‚ã‚Šã¾ã™ + + + The maximum width in pixels for auto-sized columns + 自動サイズ設定ã•ã‚Œã‚‹åˆ—ã®æœ€å¤§å¹… (ピクセルå˜ä½) + + + + + + + {0} in progress tasks + {0} 個ã®ã‚¿ã‚¹ã‚¯ãŒé€²è¡Œä¸­ã§ã™ + + + View + ビュー + + + Tasks + タスク + + + &&Tasks + タスク(&&T) + && denotes a mnemonic + + + + + + + Connections + 接続 + + + View + ビュー + + + Database Connections + データベース接続 + + + data source connections + データ ソース接続 + + + data source groups + データソース グループ + + + Startup Configuration + スタートアップã®æ§‹æˆ + + + True for the Servers view to be shown on launch of Azure Data Studio default; false if the last opened view should be shown + Azure Data Studio ã®èµ·å‹•æ™‚ã«ã‚µãƒ¼ãƒãƒ¼ ビューを表示ã™ã‚‹å ´åˆã«ã¯ true (既定)。最後ã«é–‹ã„ãŸãƒ“ューを表示ã™ã‚‹å ´åˆã«ã¯ false + + + + + + + The maximum number of recently used connections to store in the connection list. + 接続リストã«æ ¼ç´ã™ã‚‹æœ€è¿‘使用ã•ã‚ŒãŸæŽ¥ç¶šã®æœ€å¤§æ•°ã€‚ + + + Default SQL Engine to use. This drives default language provider in .sql files and the default to use when creating a new connection. Valid option is currently MSSQL + 使用ã™ã‚‹æ—¢å®šã® SQL エンジン。.sql ファイル内ã®æ—¢å®šã®è¨€èªžãƒ—ロãƒã‚¤ãƒ€ãƒ¼ãŒé¸æŠžã•ã‚Œã€æ–°ã—ã„接続ãŒä½œæˆã•ã‚Œã‚‹ã¨ãã«æ—¢å®šã§ä½¿ç”¨ã•ã‚Œã¾ã™ã€‚有効ãªã‚ªãƒ—ションã¯ç¾æ™‚点㧠MSSQL ã§ã™ + + + Attempt to parse the contents of the clipboard when the connection dialog is opened or a paste is performed. + 接続ダイアログãŒé–‹ã„ãŸã‚Šã€è§£æžãŒå®Ÿè¡Œã•ã‚ŒãŸã‚Šã™ã‚‹ã¨ãã«ã‚¯ãƒªãƒƒãƒ—ボードã®å†…容ã®è§£æžã‚’試行ã—ã¾ã™ã€‚ + + + + + + + Server Group color palette used in the Object Explorer viewlet. + オブジェクト エクスプ ローラー viewlet ã§ä½¿ç”¨ã™ã‚‹ã‚µãƒ¼ãƒãƒ¼ グループ カラー パレット。 + + + Auto-expand Server Groups in the Object Explorer viewlet. + オブジェクト エクスプローラー Viewlet ã®è‡ªå‹•å±•é–‹ã‚µãƒ¼ãƒãƒ¼ グループ。 + + + + + + + Preview Features + プレビュー機能 + + + Enable unreleased preview features + 未リリースã®ãƒ—レビュー機能を有効ã«ã™ã‚‹ + + + Show connect dialog on startup + 起動時ã«æŽ¥ç¶šãƒ€ã‚¤ã‚¢ãƒ­ã‚°ã‚’表示 + + + Obsolete API Notification + å¤ã„ API 通知 + + + Enable/disable obsolete API usage notification + å¤ã„ API 使用状æ³é€šçŸ¥ã‚’有効ã¾ãŸã¯ç„¡åŠ¹ã«ã™ã‚‹ + + + + + + + Problems + å•é¡Œ + + + + + + + Identifier of the account type + アカウントã®ç¨®é¡žã®è­˜åˆ¥å­ + + + (Optional) Icon which is used to represent the accpunt in the UI. Either a file path or a themable configuration + (çœç•¥å¯èƒ½) UI ã® accpunt を表ã™ãŸã‚ã«ä½¿ç”¨ã™ã‚‹ã‚¢ã‚¤ã‚³ãƒ³ã€‚ファイル パスã¾ãŸã¯ãƒ†ãƒ¼ãƒžè¨­å®šå¯èƒ½ãªæ§‹æˆã®ã„ãšã‚Œã‹ã§ã™ + + + Icon path when a light theme is used + 明るã„テーマを使用ã—ãŸå ´åˆã®ã‚¢ã‚¤ã‚³ãƒ³ã®ãƒ‘ス + + + Icon path when a dark theme is used + æš—ã„テーマã§ä½¿ç”¨ã™ã‚‹ã‚¢ã‚¤ã‚³ãƒ³ã®ãƒ‘ス + + + Contributes icons to account provider. + アカウント プロãƒã‚¤ãƒ€ãƒ¼ã«ã‚¢ã‚¤ã‚³ãƒ³ã‚’æä¾›ã—ã¾ã™ã€‚ + + + + + + + Indicates data property of a data set for a chart. + グラフã®ãƒ‡ãƒ¼ã‚¿ セットã®ãƒ‡ãƒ¼ã‚¿ã®ãƒ—ロパティを示ã—ã¾ã™ã€‚ + + + + + + + Minimum value of the y axis + Y 軸ã®æœ€å°å€¤ + + + Maximum value of the y axis + Y 軸ã®æœ€å¤§å€¤ + + + Label for the y axis + Y 軸ã®ãƒ©ãƒ™ãƒ« + + + Minimum value of the x axis + X 軸ã®æœ€å°å€¤ + + + Maximum value of the x axis + X 軸ã®æœ€å¤§å€¤ + + + Label for the x axis + X 軸ã®ãƒ©ãƒ™ãƒ« + + + + + + + Displays the results in a simple table + å˜ç´”ãªãƒ†ãƒ¼ãƒ–ルã«çµæžœã‚’表示 + + + + + + + Displays an image, for example one returned by an R query using ggplot2 + イメージを表示ã—ã¾ã™ã€‚例 : ggplot2 を使用ã—㦠R クエリã«ã‚ˆã£ã¦è¿”ã•ã‚ŒãŸã‚¤ãƒ¡ãƒ¼ã‚¸ + + + What format is expected - is this a JPEG, PNG or other format? + å¿…è¦ãªå½¢å¼ã¯ä½•ã§ã™ã‹ã€‚ã“ã‚Œã¯ã€JPEGã€PNGã€ã¾ãŸã¯ãã®ä»–ã®å½¢å¼ã§ã™ã‹? + + + Is this encoded as hex, base64 or some other format? + ã“れ㯠16 進数ã€base64 ã¾ãŸã¯ã€ä»–ã®å½¢å¼ã§ã‚¨ãƒ³ã‚³ãƒ¼ãƒ‰ã•ã‚Œã¦ã„ã¾ã™ã‹ï¼Ÿ + + + + + + + For each column in a resultset, displays the value in row 0 as a count followed by the column name. Supports '1 Healthy', '3 Unhealthy' for example, where 'Healthy' is the column name and 1 is the value in row 1 cell 1 + çµæžœã‚»ãƒƒãƒˆã®å„列ã«ã¤ã„ã¦ã€è¡Œ 0 ã®å€¤ã‚’カウントã¨ã—ã¦è¡¨ç¤ºã—ã€ãã®å¾Œã«åˆ—åãŒç¶šãã¾ã™ã€‚ãŸã¨ãˆã°ã€'1 正常'ã€'3 異常' をサãƒãƒ¼ãƒˆã—ã¾ã™ã€‚ã“ã“ã§ã€'正常' ã¯åˆ—åã€1 ã¯è¡Œ 1 ã®ã‚»ãƒ« 1 ã®å€¤ã§ã™ã€‚ + + + + + + + Manage + ç®¡ç† + + + Dashboard + ダッシュボード + + + + + + + The webview that will be displayed in this tab. + ã“ã®ã‚¿ãƒ–ã§è¡¨ç¤ºã•ã‚Œã‚‹ã‚¦ã‚§ãƒ–ビューã§ã™ã€‚ + + + + + + + The controlhost that will be displayed in this tab. + ã“ã®ã‚¿ãƒ–ã§è¡¨ç¤ºã•ã‚Œã‚‹ controlhost。 + + + + + + + The list of widgets that will be displayed in this tab. + ã“ã®ã‚¿ãƒ–ã«è¡¨ç¤ºã•ã‚Œã‚‹ã‚¦ã‚£ã‚¸ã‚§ãƒƒãƒˆã®ä¸€è¦§ã€‚ + + + The list of widgets is expected inside widgets-container for extension. + æ‹¡å¼µã«ã¯ã€ã‚¦ã‚£ã‚¸ã‚§ãƒƒãƒˆ コンテナー内ã«ã‚¦ã‚£ã‚¸ã‚§ãƒƒãƒˆã®ä¸€è¦§ãŒå¿…è¦ã§ã™ã€‚ + + + + + + + The list of widgets or webviews that will be displayed in this tab. + ã“ã®ã‚¿ãƒ–ã§è¡¨ç¤ºã•ã‚Œã‚‹ã‚¦ã‚£ã‚¸ã‚§ãƒƒãƒˆã¾ãŸã¯ã‚¦ã‚§ãƒ–ビューã®ãƒªã‚¹ãƒˆã§ã™ã€‚ + + + widgets or webviews are expected inside widgets-container for extension. + ウィジェットや Web ビューãŒæ‹¡å¼µæ©Ÿèƒ½ç”¨ã®ã‚¦ã‚£ã‚¸ã‚§ãƒƒãƒˆ コンテナー内ã«å¿…è¦ã§ã™ã€‚ + + + + + + + Unique identifier for this container. + ã“ã®ã‚³ãƒ³ãƒ†ãƒŠãƒ¼ã®ä¸€æ„ã®è­˜åˆ¥å­ã€‚ + + + The container that will be displayed in the tab. + タブã«è¡¨ç¤ºã•ã‚Œã‚‹ã‚³ãƒ³ãƒ†ãƒŠãƒ¼ã€‚ + + + Contributes a single or multiple dashboard containers for users to add to their dashboard. + å˜ä¸€ã¾ãŸã¯ã€è¤‡æ•°ã®ãƒ€ãƒƒã‚·ãƒ¥ ボード コンテナーをæä¾›ã—ã€ãƒ¦ãƒ¼ã‚¶ãƒ¼ãŒãƒ€ãƒƒã‚·ãƒ¥ãƒœãƒ¼ãƒ‰ã«è¿½åŠ ã§ãるよã†ã«ã—ã¾ã™ã€‚ + + + No id in dashboard container specified for extension. + 拡張用ã«æŒ‡å®šã•ã‚ŒãŸãƒ€ãƒƒã‚·ãƒ¥ãƒœãƒ¼ãƒ‰ コンテナー㫠ID ãŒã‚ã‚Šã¾ã›ã‚“。 + + + No container in dashboard container specified for extension. + ダッシュボードコンテナーã«æ‹¡å¼µã§æŒ‡å®šã•ã‚ŒãŸã‚³ãƒ³ãƒ†ãƒŠãƒ¼ã¯ã‚ã‚Šã¾ã›ã‚“。 + + + Exactly 1 dashboard container must be defined per space. + 空間ã”ã¨ã«ãƒ€ãƒƒã‚·ãƒ¥ãƒœãƒ¼ãƒ‰ コンテナーを 1 ã¤ã ã‘定義ã™ã‚‹å¿…è¦ãŒã‚ã‚Šã¾ã™ã€‚ + + + Unknown container type defines in dashboard container for extension. + 拡張用ã«ãƒ€ãƒƒã‚·ãƒ¥ãƒœãƒ¼ãƒ‰ コンテナーã§å®šç¾©ã•ã‚Œã¦ã„るコンテナーã®ç¨®é¡žãŒä¸æ˜Žã§ã™ã€‚ + + + + + + + The model-backed view that will be displayed in this tab. + ã“ã®ã‚¿ãƒ–ã«è¡¨ç¤ºã•ã‚Œã‚‹ãƒ¢ãƒ‡ãƒ« ãƒãƒƒã‚­ãƒ³ã‚° ビュー。 + + + + + + + Unique identifier for this nav section. Will be passed to the extension for any requests. + ã“ã®ãƒŠãƒ“ゲーション セクションã®ä¸€æ„ã®è­˜åˆ¥å­ã€‚拡張機能ã®ã™ã¹ã¦ã®è¦æ±‚ã«æ¸¡ã•ã‚Œã¾ã™ã€‚ + + + (Optional) Icon which is used to represent this nav section in the UI. Either a file path or a themeable configuration + (オプション)UI ã§ã“ã®ãƒŠãƒ“ゲーションセクションを表ã™ãŸã‚ã«ä½¿ç”¨ã•ã‚Œã‚‹ã‚¢ã‚¤ã‚³ãƒ³ã€‚ファイル パスã¾ãŸã¯ãƒ†ãƒ¼ãƒžã‚’設定 + + + Icon path when a light theme is used + 明るã„テーマを使用ã—ãŸå ´åˆã®ã‚¢ã‚¤ã‚³ãƒ³ã®ãƒ‘ス + + + Icon path when a dark theme is used + æš—ã„テーマã§ä½¿ç”¨ã™ã‚‹ã‚¢ã‚¤ã‚³ãƒ³ã®ãƒ‘ス + + + Title of the nav section to show the user. + ユーザーã«è¡¨ç¤ºã™ã‚‹ãƒŠãƒ“ゲーション セクションã®ã‚¿ã‚¤ãƒˆãƒ«ã€‚ + + + The container that will be displayed in this nav section. + ã“ã®ãƒŠãƒ“ゲーション セクションã«è¡¨ç¤ºã•ã‚Œã‚‹ã‚³ãƒ³ãƒ†ãƒŠãƒ¼ã§ã™ã€‚ + + + The list of dashboard containers that will be displayed in this navigation section. + ã“ã®ãƒŠãƒ“ゲーション セクションã«è¡¨ç¤ºã•ã‚Œã‚‹ãƒ€ãƒƒã‚·ãƒ¥ãƒœãƒ¼ãƒ‰ コンテナーã®ä¸€è¦§ã€‚ + + + property `icon` can be omitted or must be either a string or a literal like `{dark, light}` + プロパティ `icon` ã¯çœç•¥ã™ã‚‹ã‹ã€`{dark, light}` ãªã©ã®æ–‡å­—列ã¾ãŸã¯ãƒªãƒ†ãƒ©ãƒ«ã«ã™ã‚‹å¿…è¦ãŒã‚ã‚Šã¾ã™ + + + No title in nav section specified for extension. + æ‹¡å¼µã«æŒ‡å®šã•ã‚ŒãŸãƒŠãƒ“ゲーション セクションã«ã‚¿ã‚¤ãƒˆãƒ«ãŒã‚ã‚Šã¾ã›ã‚“。 + + + No container in nav section specified for extension. + 拡張用ã®ã‚³ãƒ³ãƒ†ãƒŠãƒ¼ãŒãƒŠãƒ“ゲーション セクションã«ã‚ã‚Šã¾ã›ã‚“。 + + + Exactly 1 dashboard container must be defined per space. + 空間ã”ã¨ã«ãƒ€ãƒƒã‚·ãƒ¥ãƒœãƒ¼ãƒ‰ コンテナーを 1 ã¤ã ã‘定義ã™ã‚‹å¿…è¦ãŒã‚ã‚Šã¾ã™ã€‚ + + + NAV_SECTION within NAV_SECTION is an invalid container for extension. + æ‹¡å¼µã«é–¢ã—㦠NAV_SECTION 内㮠NAV_SECTION ã¯ç„¡åŠ¹ãªã‚³ãƒ³ãƒ†ãƒŠãƒ¼ã§ã™ã€‚ + + + + + + + Backup + ãƒãƒƒã‚¯ã‚¢ãƒƒãƒ— + + + + + + + Unique identifier for this tab. Will be passed to the extension for any requests. + ã“ã®ã‚¿ãƒ–ã®ä¸€æ„ã®è­˜åˆ¥å­ã€‚拡張機能ã®ã™ã¹ã¦ã®è¦æ±‚ã«æ¸¡ã•ã‚Œã¾ã™ã€‚ + + + Title of the tab to show the user. + ユーザーã«è¡¨ç¤ºã™ã‚‹ã‚¿ãƒ–ã®ã‚¿ã‚¤ãƒˆãƒ«ã€‚ + + + Description of this tab that will be shown to the user. + ã“ã®ã‚¿ãƒ–ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã«è¡¨ç¤ºã•ã‚Œã‚‹èª¬æ˜Žã€‚ + + + Condition which must be true to show this item + ã“ã®é …目を表示ã™ã‚‹ãŸã‚ã« true ã«ã™ã‚‹å¿…è¦ãŒã‚ã‚‹æ¡ä»¶ + + + Defines the connection types this tab is compatible with. Defaults to 'MSSQL' if not set + ã“ã®ã‚¿ãƒ–ã¨äº’æ›æ€§ã®ã‚る接続ã®ç¨®é¡žã‚’定義ã—ã¾ã™ã€‚設定ã•ã‚Œã¦ã„ãªã„å ´åˆã€æ—¢å®šå€¤ã¯ 'MSSQL' ã«è¨­å®šã•ã‚Œã¾ã™ã€‚ + + + The container that will be displayed in this tab. + ã“ã®ã‚¿ãƒ–ã«è¡¨ç¤ºã•ã‚Œã‚‹ã‚³ãƒ³ãƒ†ãƒŠãƒ¼ã€‚ + + + Whether or not this tab should always be shown or only when the user adds it. + ã“ã®ã‚¿ãƒ–を常ã«è¡¨ç¤ºã™ã‚‹ã‹ã€ã¾ãŸã¯ãƒ¦ãƒ¼ã‚¶ãƒ¼ãŒè¿½åŠ ã—ãŸå ´åˆã«ã®ã¿è¡¨ç¤ºã™ã‚‹ã‹ã©ã†ã‹ã€‚ + + + Whether or not this tab should be used as the Home tab for a connection type. + ã“ã®ã‚¿ãƒ–を接続ã®ç¨®é¡žã® [ホーム] タブã¨ã—ã¦ä½¿ç”¨ã™ã‚‹ã‹ã©ã†ã‹ã€‚ + + + Contributes a single or multiple tabs for users to add to their dashboard. + ユーザーãŒãƒ€ãƒƒã‚·ãƒ¥ãƒœãƒ¼ãƒ‰ã«è¿½åŠ ã™ã‚‹ 1 ã¤ã¾ãŸã¯ã€è¤‡æ•°ã®ã‚¿ãƒ–ã‚’æä¾›ã—ã¾ã™ã€‚ + + + No title specified for extension. + 拡張機能ã«ã‚¿ã‚¤ãƒˆãƒ«ãŒæŒ‡å®šã•ã‚Œã¦ã„ã¾ã›ã‚“。 + + + No description specified to show. + 表示ã™ã‚‹ã‚ˆã†æŒ‡å®šã•ã‚ŒãŸèª¬æ˜Žã¯ã‚ã‚Šã¾ã›ã‚“。 + + + No container specified for extension. + 拡張機能ã«ã‚³ãƒ³ãƒ†ãƒŠãƒ¼ãŒæŒ‡å®šã•ã‚Œã¦ã„ã¾ã›ã‚“。 + + + Exactly 1 dashboard container must be defined per space + 空間ã”ã¨ã«ãƒ€ãƒƒã‚·ãƒ¥ãƒœãƒ¼ãƒ‰ コンテナーを 1 ã¤ã ã‘定義ã™ã‚‹å¿…è¦ãŒã‚ã‚Šã¾ã™ + + + + + + + Restore + 復元 + + + Restore + 復元 + + + + + + + Cannot expand as the required connection provider '{0}' was not found + å¿…è¦ãªæŽ¥ç¶šãƒ—ロãƒã‚¤ãƒ€ãƒ¼ '{0}' ãŒè¦‹ã¤ã‹ã‚‰ãªã„ãŸã‚ã€å±•é–‹ã§ãã¾ã›ã‚“ + + + User canceled + ユーザーã«ã‚ˆã‚‹å–り消㗠+ + + Firewall dialog canceled + ファイアウォール ダイアログãŒå–り消ã•ã‚Œã¾ã—㟠+ + + + + + + 1 or more tasks are in progress. Are you sure you want to quit? + 1 ã¤ä»¥ä¸Šã®ã‚¿ã‚¹ã‚¯ã‚’実行中ã§ã™ã€‚終了ã—ã¦ã‚‚よã‚ã—ã„ã§ã™ã‹? + + + Yes + ã¯ã„ + + + No + ã„ã„㈠+ + + + + + + Connection is required in order to interact with JobManagementService + JobManagementService ã¨å¯¾è©±ã™ã‚‹ã«ã¯æŽ¥ç¶šãŒå¿…è¦ã§ã™ + + + No Handler Registered + 登録ã•ã‚Œã¦ã„ã‚‹ãƒãƒ³ãƒ‰ãƒ©ãƒ¼ãŒã‚ã‚Šã¾ã›ã‚“。 + + + + + + + An error occured while loading the file browser. + ファイル ブラウザーã®èª­ã¿è¾¼ã¿ä¸­ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚ + + + File browser error + ファイル ブラウザーã®ã‚¨ãƒ©ãƒ¼ + + + + + + + Notebook Editor + ノートブック エディター + + + New Notebook + æ–°ã—ã„ノートブック + + + New Notebook + æ–°ã—ã„ノートブック + + + SQL kernel: stop Notebook execution when error occurs in a cell. + SQL カーãƒãƒ«: セルã§ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ãŸã¨ãã« Notebook ã®å®Ÿè¡Œã‚’åœæ­¢ã—ã¾ã™ã€‚ + + + + + + + Script as Create + 作æˆã™ã‚‹ã¨ãã®ã‚¹ã‚¯ãƒªãƒ—ト + + + Script as Drop + ドロップã™ã‚‹ã¨ãã®ã‚¹ã‚¯ãƒªãƒ—ト + + + Select Top 1000 + トップ 1000 ã‚’é¸æŠžã™ã‚‹ + + + Script as Execute + スクリプトを実行 + + + Script as Alter + 変更ã™ã‚‹ã¨ãã®ã‚¹ã‚¯ãƒªãƒ—ト + + + Edit Data + データã®ç·¨é›† + + + Select Top 1000 + トップ 1000 ã‚’é¸æŠžã™ã‚‹ + + + Script as Create + 作æˆã™ã‚‹ã¨ãã®ã‚¹ã‚¯ãƒªãƒ—ト + + + Script as Execute + スクリプトを実行 + + + Script as Alter + 変更ã™ã‚‹ã¨ãã®ã‚¹ã‚¯ãƒªãƒ—ト + + + Script as Drop + ドロップã™ã‚‹ã¨ãã®ã‚¹ã‚¯ãƒªãƒ—ト + + + Refresh + 最新ã®æƒ…å ±ã«æ›´æ–° + + + + + + + Connection error + 接続エラー + + + Connection failed due to Kerberos error. + 接続ã¯ã€Kerberos エラーã®ãŸã‚失敗ã—ã¾ã—ãŸã€‚ + + + Help configuring Kerberos is available at {0} + Kerberos を構æˆã™ã‚‹ãŸã‚ã®ãƒ˜ãƒ«ãƒ—ã‚’ {0} ã§ç¢ºèªã§ãã¾ã™ + + + If you have previously connected you may need to re-run kinit. + 以å‰ã«æŽ¥ç¶šã—ãŸå ´åˆã¯ã€kinit ã‚’å†å®Ÿè¡Œã—ãªã‘ã‚Œã°ãªã‚‰ãªã„å ´åˆãŒã‚ã‚Šã¾ã™ã€‚ + + + + + + + Refresh account was canceled by the user + ユーザーãŒã‚¢ã‚«ã‚¦ãƒ³ãƒˆã®æ›´æ–°ã‚’キャンセルã—ã¾ã—㟠+ + + + + + + Specifies view templates + ビュー テンプレートを指定ã™ã‚‹ + + + Specifies session templates + セッション テンプレートを指定 + + + Profiler Filters + プロファイラー フィルター + + + + + + + Toggle Query History + クエリ履歴ã®åˆ‡ã‚Šæ›¿ãˆ + + + Delete + 削除 + + + Clear All History + ã™ã¹ã¦ã®å±¥æ­´ã‚’クリア + + + Open Query + クエリを開ã + + + Run Query + クエリã®å®Ÿè¡Œ + + + Toggle Query History capture + クエリ履歴キャプãƒãƒ£ã®åˆ‡ã‚Šæ›¿ãˆ + + + Pause Query History Capture + クエリ履歴ã®ã‚­ãƒ£ãƒ—ãƒãƒ£ã®ä¸€æ™‚åœæ­¢ + + + Start Query History Capture + クエリ履歴キャプãƒãƒ£ã®é–‹å§‹ + + + + + + + Preview features are required in order for extensions to be fully supported and for some actions to be available. Would you like to enable preview features? + 拡張機能を完全ã«ã‚µãƒãƒ¼ãƒˆã—ã€ã„ãã¤ã‹ã®ã‚¢ã‚¯ã‚·ãƒ§ãƒ³ã‚’使用å¯èƒ½ã«ã™ã‚‹ã«ã¯ã€ãƒ—レビュー機能ãŒå¿…è¦ã§ã™ã€‚プレビュー機能を有効ã«ã—ã¾ã™ã‹? + + + Yes + ã¯ã„ + + + No + ã„ã„㈠+ + + No, don't show again + ã„ã„ãˆã€ä»Šå¾Œã¯è¡¨ç¤ºã—ãªã„ + + + + + + + Commit row failed: + è¡Œã®ã‚³ãƒŸãƒƒãƒˆã«å¤±æ•—ã—ã¾ã—ãŸ: + + + Started executing query "{0}" + クエリ「{0}ã€ã®å®Ÿè¡Œã‚’開始ã—ã¾ã—㟠+ + + Update cell failed: + セルã®æ›´æ–°ãŒå¤±æ•—ã—ã¾ã—ãŸã€‚ + + + + + + + Failed to create Object Explorer session + オブジェクト エクスプローラー セッションを作æˆã§ãã¾ã›ã‚“ã§ã—㟠+ + + Multiple errors: + 複数ã®ã‚¨ãƒ©ãƒ¼: + + + + + + + No URI was passed when creating a notebook manager + ノートブック マãƒãƒ¼ã‚¸ãƒ£ãƒ¼ã‚’作æˆã™ã‚‹ã¨ãã« URI ãŒæ¸¡ã•ã‚Œã¾ã›ã‚“ã§ã—㟠+ + + Notebook provider does not exist + ノートブック プロãƒã‚¤ãƒ€ãƒ¼ãŒå­˜åœ¨ã—ã¾ã›ã‚“ + + + + + + + Query Results + クエリçµæžœ + + + Query Editor + クエリ エディター + + + New Query + æ–°ã—ã„クエリ + + + [Optional] When true, column headers are included when saving results as CSV + [オプション] true ã®å ´åˆã€CSV ã¨ã—ã¦çµæžœã‚’ä¿å­˜ã™ã‚‹éš›ã«åˆ—ヘッダーãŒå«ã¾ã‚Œã¾ã™ã€‚ + + + [Optional] The custom delimiter to use between values when saving as CSV + [çœç•¥å¯èƒ½] CSV ã¨ã—ã¦ä¿å­˜ã™ã‚‹ã¨ãã«å€¤ã®é–“ã«ä½¿ç”¨ã™ã‚‹ç‹¬è‡ªã®åŒºåˆ‡ã‚Šè¨˜å· + + + [Optional] Character(s) used for seperating rows when saving results as CSV + [çœç•¥å¯èƒ½] CSV ã¨ã—ã¦ä¿å­˜ã™ã‚‹ã¨ãã«è¡Œã‚’区切るãŸã‚ã«ä½¿ç”¨ã•ã‚Œã‚‹æ–‡å­— + + + [Optional] Character used for enclosing text fields when saving results as CSV + [çœç•¥å¯èƒ½] çµæžœã‚’ CSV ã¨ã—ã¦ä¿å­˜ã™ã‚‹ã¨ãã«ã€ãƒ†ã‚­ã‚¹ãƒˆ フィールドを囲むãŸã‚ã«ä½¿ç”¨ã™ã‚‹æ–‡å­— + + + [Optional] File encoding used when saving results as CSV + [çœç•¥å¯èƒ½] çµæžœã‚’ CSV ã¨ã—ã¦ä¿å­˜ã™ã‚‹ã¨ãã«ä½¿ç”¨ã™ã‚‹ãƒ•ã‚¡ã‚¤ãƒ« エンコーディング + + + Enable results streaming; contains few minor visual issues + çµæžœã®ã‚¹ãƒˆãƒªãƒ¼ãƒŸãƒ³ã‚°ã‚’有効ã«ã—ã¾ã™ã€‚視覚上ã®å°ã•ãªå•é¡ŒãŒã„ãã¤ã‹ã‚ã‚Šã¾ã™ + + + [Optional] When true, XML output will be formatted when saving results as XML + [オプション] trueã®å ´åˆã€XML å½¢å¼ã§çµæžœã‚’ä¿å­˜ã™ã‚‹ã¨ XML 出力ã«ãƒ•ã‚©ãƒ¼ãƒžãƒƒãƒˆã•ã‚Œã¾ã™ã€‚ + + + [Optional] File encoding used when saving results as XML + [オプション] XML ã¨ã—ã¦çµæžœã‚’ä¿å­˜ã™ã‚‹ã¨ãã«é©ç”¨ã•ã‚Œã‚‹ãƒ•ã‚¡ã‚¤ãƒ« エンコーディング + + + [Optional] Configuration options for copying results from the Results View + [オプション]çµæžœã‚’çµæžœãƒ“ューã‹ã‚‰ã‚³ãƒ”ーã™ã‚‹ãŸã‚ã®æ§‹æˆã‚ªãƒ—ション + + + [Optional] Configuration options for copying multi-line results from the Results View + [オプション]複数行ã®çµæžœã‚’çµæžœãƒ“ューã‹ã‚‰ã‚³ãƒ”ーã™ã‚‹ãŸã‚ã®æ§‹æˆã‚ªãƒ—ション + + + [Optional] Should execution time be shown for individual batches + [オプション]å„ãƒãƒƒãƒã®å®Ÿè¡Œæ™‚間を表示ã™ã‚‹ã‹ + + + [Optional] the default chart type to use when opening Chart Viewer from a Query Results + [çœç•¥å¯èƒ½] クエリçµæžœã‹ã‚‰ã‚°ãƒ©ãƒ• ビューアーを開ãã¨ãã«ä½¿ç”¨ã™ã‚‹æ—¢å®šã®ã‚°ãƒ©ãƒ•ã®ç¨®é¡ž + + + Tab coloring will be disabled + タブã®è‰²æŒ‡å®šã¯ç„¡åŠ¹ã«ãªã‚Šã¾ã™ + + + The top border of each editor tab will be colored to match the relevant server group + å„エディター タブã®ä¸Šéƒ¨å¢ƒç•Œç·šã¯ã€é–¢é€£ã™ã‚‹ã‚µãƒ¼ãƒãƒ¼ グループã¨åŒã˜è‰²ãŒä»˜ãã¾ã™ã€‚ + + + Each editor tab's background color will match the relevant server group + å„エディター タブã®èƒŒæ™¯è‰²ã¯é–¢é€£ã™ã‚‹ã‚µãƒ¼ãƒãƒ¼ グループã¨åŒã˜ã«ãªã‚Šã¾ã™ + + + Controls how to color tabs based on the server group of their active connection + アクティブãªæŽ¥ç¶šã®ã‚µãƒ¼ãƒãƒ¼ã‚°ãƒ«ãƒ¼ãƒ—ã«åŸºã¥ãタブã«è‰²ã‚’付ã‘る方法を制御ã—ã¾ã™ã€‚ + + + Controls whether to show the connection info for a tab in the title. + タイトルã«ã‚¿ãƒ–ã®æŽ¥ç¶šæƒ…報を表示ã™ã‚‹ã‹ã©ã†ã‹ã‚’制御ã—ã¾ã™ã€‚ + + + Prompt to save generated SQL files + 生æˆã•ã‚ŒãŸ SQL ファイルをä¿å­˜ã™ã‚‹ã‹ã©ã†ã‹ã‚’確èªã™ã‚‹ + + + Should IntelliSense be enabled + IntelliSense を有効ã«ã™ã‚‹ã‹ + + + Should IntelliSense error checking be enabled + IntelliSense エラー ãƒã‚§ãƒƒã‚¯ã‚’有効ã«ã™ã‚‹ã‹ + + + Should IntelliSense suggestions be enabled + IntelliSense æ案を有効ã«ã™ã‚‹ã‹ + + + Should IntelliSense quick info be enabled + IntelliSense クイック ヒントを有効ã«ã™ã‚‹ã‹ + + + Should IntelliSense suggestions be lowercase + IntelliSense æ案をå°æ–‡å­—ã™ã‚‹ã‹ + + + Maximum number of rows to return before the server stops processing your query. + サーãƒãƒ¼ãŒã‚¯ã‚¨ãƒªã®å‡¦ç†ã‚’åœæ­¢ã™ã‚‹å‰ã«è¿”ã™è¡Œã®æœ€å¤§æ•°ã€‚ + + + Maximum size of text and ntext data returned from a SELECT statement + SELECT ステートメントã‹ã‚‰è¿”ã•ã‚Œã‚‹ãƒ†ã‚­ã‚¹ãƒˆãŠã‚ˆã³ ntext データã®æœ€å¤§ã‚µã‚¤ã‚º + + + An execution time-out of 0 indicates an unlimited wait (no time-out) + 実行タイムアウト㌠0 ã®å ´åˆã¯ã€ç„¡åˆ¶é™ã®å¾…æ©Ÿ (タイムアウトãªã—) を示ã—ã¾ã™ã€‚ + + + Enable SET NOCOUNT option + SET NOCOUNT オプションを有効ã«ã™ã‚‹ + + + Enable SET NOEXEC option + SET NOEXEC オプションを有効ã«ã™ã‚‹ + + + Enable SET PARSEONLY option + SET PARSEONLY オプションを有効ã«ã™ã‚‹ + + + Enable SET ARITHABORT option + SET ARITHABORT オプションを有効ã«ã™ã‚‹ + + + Enable SET STATISTICS TIME option + SET STATISTICS TIME オプションを有効ã«ã™ã‚‹ + + + Enable SET STATISTICS IO option + SET STATISTICS IO オプションを有効ã«ã™ã‚‹ + + + Enable SET XACT_ABORT ON option + SET XACT_ABORT ON オプションを有効ã«ã™ã‚‹ + + + Enable SET TRANSACTION ISOLATION LEVEL option + SET TRANSACTION ISOLATION LEVEL オプションを有効ã«ã™ã‚‹ + + + Enable SET DEADLOCK_PRIORITY option + SET DEADLOCK_PRIORITY オプションを有効ã«ã™ã‚‹ + + + Enable SET LOCK TIMEOUT option (in milliseconds) + SET LOCK TIMEOUT オプションを有効ã«ã™ã‚‹ (ミリ秒å˜ä½) + + + Enable SET QUERY_GOVERNOR_COST_LIMIT + SET QUERY_GOVERNOR_COST_LIMITを有効ã«ã™ã‚‹ + + + Enable SET ANSI_DEFAULTS + SET ANSI_DEFAULTS を有効ã«ã™ã‚‹ + + + Enable SET QUOTED_IDENTIFIER + SET QUOTED_IDENTIFIER を有効ã«ã™ã‚‹ + + + Enable SET ANSI_NULL_DFLT_ON + SET ANSI_NULL_DFLT_ON を有効ã«ã™ã‚‹ + + + Enable SET IMPLICIT_TRANSACTIONS + SET IMPLICIT_TRANSACTIONS を有効ã«ã™ã‚‹ + + + Enable SET CURSOR_CLOSE_ON_COMMIT + SET CURSOR_CLOSE_ON_COMMIT を有効ã«ã™ã‚‹ + + + Enable SET ANSI_PADDING + SET ANSI_PADDING を有効ã«ã™ã‚‹ + + + Enable SET ANSI_WARNINGS + SET ANSI_WARNINGS を有効ã«ã™ã‚‹ + + + Enable SET ANSI_NULLS + SET ANSI_NULLS を有効ã«ã™ã‚‹ + + + Set keybinding workbench.action.query.shortcut{0} to run the shortcut text as a procedure call. Any selected text in the query editor will be passed as a parameter + ショートカット テキストをプロシージャ呼ã³å‡ºã—ã¨ã—ã¦å®Ÿè¡Œã™ã‚‹ã«ã¯ã‚­ãƒ¼ãƒã‚¤ãƒ³ãƒ‰ workbench.action.query.shortcut{0} を設定ã—ã¾ã™ã€‚クエリ エディターã§é¸æŠžã—ãŸãƒ†ã‚­ã‚¹ãƒˆã¯ãƒ‘ラメーターã¨ã—ã¦æ¸¡ã•ã‚Œã¾ã™ + + + + + + + Common id for the provider + プロãƒã‚¤ãƒ€ãƒ¼ã®å…±é€š ID + + + Display Name for the provider + プロãƒã‚¤ãƒ€ãƒ¼ã®è¡¨ç¤ºå + + + Icon path for the server type + サーãƒãƒ¼ã®ç¨®é¡žã®ã‚¢ã‚¤ã‚³ãƒ³ パス + + + Options for connection + 接続ã®ã‚ªãƒ—ション + + + + + + + OK + OK + + + Close + é–‰ã˜ã‚‹ + + + Copy details + コピーã®è©³ç´° + + + + + + + Add server group + サーãƒãƒ¼ グループã®è¿½åŠ  + + + Edit server group + サーãƒãƒ¼ グループã®ç·¨é›† + + + + + + + Error adding account + アカウントã®è¿½åŠ ã‚¨ãƒ©ãƒ¼ + + + Firewall rule error + ファイアウォール ルールã®ã‚¨ãƒ©ãƒ¼ + + + + + + + Some of the loaded extensions are using obsolete APIs, please find the detailed information in the Console tab of Developer Tools window + 読ã¿è¾¼ã¾ã‚ŒãŸæ‹¡å¼µæ©Ÿèƒ½ã®ä¸€éƒ¨ã¯å¤ã„ API を使用ã—ã¦ã„ã¾ã™ã€‚[開発者ツール] ウィンドウ㮠[コンソール] タブã§è©³ç´°æƒ…報を確èªã—ã¦ãã ã•ã„。 + + + Don't Show Again + 今後表示ã—ãªã„ + + + + + + + Toggle Tasks + タスクã®åˆ‡ã‚Šæ›¿ãˆ + + + + + + + Show Connections + 接続ã®è¡¨ç¤º + + + Servers + サーãƒãƒ¼ + + + + + + + Identifier of the view. Use this to register a data provider through `vscode.window.registerTreeDataProviderForView` API. Also to trigger activating your extension by registering `onView:${id}` event to `activationEvents`. + ビューã®è­˜åˆ¥å­ã€‚`vscode.window.registerTreeDataProviderForView` API を介ã—ã¦ãƒ‡ãƒ¼ã‚¿ プロãƒã‚¤ãƒ€ãƒ¼ã‚’登録ã™ã‚‹ã«ã¯ã€ã“れを使用ã—ã¾ã™ã€‚ã¾ãŸã€`onView:${id}` イベントを `activationEvents` ã«ç™»éŒ²ã™ã‚‹ã“ã¨ã«ã‚ˆã£ã¦ã€æ‹¡å¼µæ©Ÿèƒ½ã®ã‚¢ã‚¯ãƒ†ã‚£ãƒ–化をトリガーã™ã‚‹ãŸã‚ã«ã‚‚使用ã§ãã¾ã™ã€‚ + + + The human-readable name of the view. Will be shown + ビューã®åˆ¤èª­ã§ãã‚‹åå‰ã€‚表示ã•ã‚Œã¾ã™ + + + Condition which must be true to show this view + ã“ã®ãƒ“ューを表示ã™ã‚‹ãŸã‚ã«æº€ãŸã™å¿…è¦ãŒã‚ã‚‹æ¡ä»¶ + + + Contributes views to the editor + ビューをエディターã«æä¾›ã—ã¾ã™ + + + Contributes views to Data Explorer container in the Activity bar + アクティビティ ãƒãƒ¼ã®ãƒ‡ãƒ¼ã‚¿ エクスプローラー コンテナーã«ãƒ“ューをæä¾›ã—ã¾ã™ + + + Contributes views to contributed views container + コントリビューション ビュー コンテナã«ãƒ“ューをæä¾›ã—ã¾ã™ + + + View container '{0}' does not exist and all views registered to it will be added to 'Data Explorer'. + ビュー コンテナー '{0}' ã¯å­˜åœ¨ã—ãªã„ãŸã‚ã€ãã‚Œã«ç™»éŒ²ã•ã‚Œã¦ã„ã‚‹ã™ã¹ã¦ã®ãƒ“ュー㯠'データ エクスプローラー' ã«è¿½åŠ ã•ã‚Œã¾ã™ã€‚ + + + Cannot register multiple views with same id `{0}` in the view container `{1}` + ビュー コンテナ '{1}'ã«åŒã˜ id '{0}' ã®ãƒ“ューを複数登録ã§ãã¾ã›ã‚“ + + + A view with id `{0}` is already registered in the view container `{1}` + ビュー id `{0}` ã¯ãƒ“ュー コンテナー `{1}` ã«æ—¢ã«ç™»éŒ²ã•ã‚Œã¦ã„ã¾ã™ + + + views must be an array + ビューã¯é…列ã«ã™ã‚‹å¿…è¦ãŒã‚ã‚Šã¾ã™ + + + property `{0}` is mandatory and must be of type `string` + プロパティ `{0}` ã¯å¿…é ˆã§ã€åž‹ `string` ã§ãªã‘ã‚Œã°ãªã‚Šã¾ã›ã‚“ + + + property `{0}` can be omitted or must be of type `string` + `{0}` プロパティã¯çœç•¥ã™ã‚‹ã‹ã€`string` åž‹ã«ã™ã‚‹å¿…è¦ãŒã‚ã‚Šã¾ã™ + + + + + + + Connection Status + 接続状態 + + + + + + + Manage + ç®¡ç† + + + Show Details + 詳細ã®è¡¨ç¤º + + + Learn How To Configure The Dashboard + ダッシュボードã®æ§‹æˆæ–¹æ³•ã«ã¤ã„㦠+ + + + + + + Widget used in the dashboards + ダッシュ ボードã§ä½¿ç”¨ã•ã‚Œã¦ã„るウィジェット + + + + + + + Displays results of a query as a chart on the dashboard + ダッシュボードã®ã‚°ãƒ©ãƒ•ã¨ã—ã¦ã‚¯ã‚¨ãƒªçµæžœã‚’表示ã—ã¾ã™ã€‚ + + + Maps 'column name' -> color. for example add 'column1': red to ensure this column uses a red color + '列å' -> 色をマップã—ã¾ã™ã€‚ãŸã¨ãˆã°ã€'column1': 赤を追加ã—ã¦ã€ã“ã®åˆ—ã§èµ¤è‰²ãŒä½¿ç”¨ã•ã‚Œã‚‹ã‚ˆã†ã«ã—ã¾ã™ + + + Indicates preferred position and visibility of the chart legend. These are the column names from your query, and map to the label of each chart entry + グラフã®å‡¡ä¾‹ã®å„ªå…ˆã•ã‚Œã‚‹ä½ç½®ã¨è¡¨ç¤ºè¨­å®šã‚’示ã—ã¾ã™ã€‚ã“ã‚Œã¯ã‚¯ã‚¨ãƒªã«åŸºã¥ã列åã§ã€å„グラフ項目ã®ãƒ©ãƒ™ãƒ«ã«ãƒžãƒƒãƒ—ã•ã‚Œã¾ã™ + + + If dataDirection is horizontal, setting this to true uses the first columns value for the legend. + dataDirection ãŒæ¨ªã®å ´åˆã€true ã«è¨­å®šã™ã‚‹ã¨å‡¡ä¾‹ã«æœ€åˆã®åˆ—値ãŒä½¿ç”¨ã•ã‚Œã¾ã™ã€‚ + + + If dataDirection is vertical, setting this to true will use the columns names for the legend. + dataDirectionãŒåž‚ç›´ã®å ´åˆã€è¨­å®šã‚’ true ã«ã™ã‚‹ã¨å‡¡ä¾‹ã®åˆ—åãŒä½¿ç”¨ã•ã‚Œã¾ã™ã€‚ + + + Defines whether the data is read from a column (vertical) or a row (horizontal). For time series this is ignored as direction must be vertical. + 列 (åž‚ç›´æ–¹å‘) ã¾ãŸã¯è¡Œ (水平方å‘) ã‹ã‚‰ãƒ‡ãƒ¼ã‚¿ã‚’読ã¿å–ã‚‹ã‹ã©ã†ã‹ã‚’定義ã—ã¾ã™ã€‚時系列ã®å ´åˆã€æ–¹å‘ã¯åž‚ç›´ã«ã™ã‚‹å¿…è¦ãŒã‚ã‚‹ãŸã‚ã€ã“ã‚Œã¯ç„¡è¦–ã•ã‚Œã¾ã™ã€‚ + + + If showTopNData is set, showing only top N data in the chart. + showTopNData を設定ã™ã‚‹ã¨ã€ã‚°ãƒ©ãƒ•ã®ä¸Šä½ N 個ã®ãƒ‡ãƒ¼ã‚¿ã®ã¿ãŒè¡¨ç¤ºã•ã‚Œã¾ã™ã€‚ + + + + + + + Condition which must be true to show this item + ã“ã®é …目を表示ã™ã‚‹ãŸã‚ã« true ã«ã™ã‚‹å¿…è¦ãŒã‚ã‚‹æ¡ä»¶ + + + The title of the container + コンテナーã®ã‚¿ã‚¤ãƒˆãƒ« + + + The row of the component in the grid + グリッド内ã®ã‚³ãƒ³ãƒãƒ¼ãƒãƒ³ãƒˆã®è¡Œ + + + The rowspan of the component in the grid. Default value is 1. Use '*' to set to number of rows in the grid. + グリッド内ã®ã‚³ãƒ³ãƒãƒ¼ãƒãƒ³ãƒˆã® rowspan。既定値㯠1 ã§ã™ã€‚グリッド内ã®è¡Œæ•°ã‚’設定ã™ã‚‹ã«ã¯ '*' を使用ã—ã¾ã™ã€‚ + + + The column of the component in the grid + グリッド内ã®ã‚³ãƒ³ãƒãƒ¼ãƒãƒ³ãƒˆã®åˆ— + + + The colspan of the component in the grid. Default value is 1. Use '*' to set to number of columns in the grid. + グリッドã®ã‚³ãƒ³ãƒãƒ¼ãƒãƒ³ãƒˆã® colspan。既定値㯠1 ã§ã™ã€‚グリッドã®åˆ—数を設定ã™ã‚‹ã«ã¯ '*' を使用ã—ã¾ã™ã€‚ + + + Unique identifier for this tab. Will be passed to the extension for any requests. + ã“ã®ã‚¿ãƒ–ã®ä¸€æ„ã®è­˜åˆ¥å­ã€‚拡張機能ã®ã™ã¹ã¦ã®è¦æ±‚ã«æ¸¡ã•ã‚Œã¾ã™ã€‚ + + + Extension tab is unknown or not installed. + 拡張機能タブãŒä¸æ˜Žã¾ãŸã¯ã‚¤ãƒ³ã‚¹ãƒˆãƒ¼ãƒ«ã•ã‚Œã¦ã„ã¾ã›ã‚“。 + + + + + + + Enable or disable the properties widget + プロパティã®ã‚¦ã‚£ã‚¸ã‚§ãƒƒãƒˆã‚’有効ã¾ãŸã¯ç„¡åŠ¹ã«ã™ã‚‹ + + + Property values to show + プロパティã®å€¤ã‚’表示 + + + Display name of the property + プロパティã®è¡¨ç¤ºå + + + Value in the Database Info Object + データベース情報オブジェクトã®å€¤ + + + Specify specific values to ignore + 無視ã™ã‚‹ç‰¹å®šã®å€¤ã‚’指定ã—ã¾ã™ + + + Recovery Model + 復旧モデル + + + Last Database Backup + å‰å›žã®ãƒ‡ãƒ¼ã‚¿ãƒ™ãƒ¼ã‚¹ ãƒãƒƒã‚¯ã‚¢ãƒƒãƒ— + + + Last Log Backup + 最終ログ ãƒãƒƒã‚¯ã‚¢ãƒƒãƒ— + + + Compatibility Level + 互æ›æ€§ãƒ¬ãƒ™ãƒ« + + + Owner + 所有者 + + + Customizes the database dashboard page + データベース ダッシュボード ページをカスタマイズã™ã‚‹ + + + Customizes the database dashboard tabs + データベース ダッシュボード タブをカスタマイズã™ã‚‹ + + + + + + + Enable or disable the properties widget + プロパティã®ã‚¦ã‚£ã‚¸ã‚§ãƒƒãƒˆã‚’有効ã¾ãŸã¯ç„¡åŠ¹ã«ã™ã‚‹ + + + Property values to show + プロパティã®å€¤ã‚’表示 + + + Display name of the property + プロパティã®è¡¨ç¤ºå + + + Value in the Server Info Object + サーãƒãƒ¼æƒ…報オブジェクトã®å€¤ + + + Version + ãƒãƒ¼ã‚¸ãƒ§ãƒ³ + + + Edition + エディション + + + Computer Name + コンピューターå + + + OS Version + OS ãƒãƒ¼ã‚¸ãƒ§ãƒ³ + + + Customizes the server dashboard page + サーãƒãƒ¼ ダッシュ ボード ページをカスタマイズã—ã¾ã™ã€‚ + + + Customizes the Server dashboard tabs + サーãƒãƒ¼ ダッシュボードã®ã‚¿ãƒ–をカスタマイズã—ã¾ã™ + + + + + + + Manage + ç®¡ç† + + + + + + + Widget used in the dashboards + ダッシュ ボードã§ä½¿ç”¨ã•ã‚Œã¦ã„るウィジェット + + + Widget used in the dashboards + ダッシュ ボードã§ä½¿ç”¨ã•ã‚Œã¦ã„るウィジェット + + + Widget used in the dashboards + ダッシュ ボードã§ä½¿ç”¨ã•ã‚Œã¦ã„るウィジェット + + + + + + + Adds a widget that can query a server or database and display the results in multiple ways - as a chart, summarized count, and more + サーãƒãƒ¼ã¾ãŸã¯ãƒ‡ãƒ¼ã‚¿ãƒ™ãƒ¼ã‚¹ã‚’クエリã—ã¦ã€ãã®çµæžœã‚’グラフや集計ã®ã‚«ã‚¦ãƒ³ãƒˆãªã©ã®è¤‡æ•°ã®æ–¹æ³•ã§è¡¨ç¤ºã§ãるウィジェットを追加ã—ã¾ã™ + + + Unique Identifier used for caching the results of the insight. + インサイトã®çµæžœã‚’キャッシュã™ã‚‹ãŸã‚ã«ä½¿ç”¨ã•ã‚Œã‚‹ä¸€æ„ã®è­˜åˆ¥å­ã€‚ + + + SQL query to run. This should return exactly 1 resultset. + 実行ã™ã‚‹ SQL クエリ。返ã™çµæžœã‚»ãƒƒãƒˆã¯ 1 ã¤ã®ã¿ã§ãªã‘ã‚Œã°ãªã‚Šã¾ã›ã‚“。 + + + [Optional] path to a file that contains a query. Use if 'query' is not set + [çœç•¥å¯èƒ½] クエリをå«ã‚€ãƒ•ã‚¡ã‚¤ãƒ«ã¸ã®ãƒ‘ス。'クエリ' ãŒè¨­å®šã•ã‚Œã¦ã„ãªã„å ´åˆã«ä½¿ç”¨ã—ã¾ã™ + + + [Optional] Auto refresh interval in minutes, if not set, there will be no auto refresh + [çœç•¥å¯èƒ½] 分å˜ä½ã®è‡ªå‹•æ›´æ–°é–“隔。設定ã—ãªã„ã¨ã€è‡ªå‹•æ›´æ–°ã•ã‚Œã¾ã›ã‚“ + + + Which actions to use + 使用ã™ã‚‹ã‚¢ã‚¯ã‚·ãƒ§ãƒ³ + + + Target database for the action; can use the format '${ columnName }' to use a data driven column name. + アクションã®ã‚¿ãƒ¼ã‚²ãƒƒãƒˆ ãƒ‡ãƒ¼ã‚¿ãƒ™ãƒ¼ã‚¹ã€‚å½¢å¼ '${ columnName }' を使用ã—ã¦ã€ãƒ‡ãƒ¼ã‚¿ ドリブン列åを使用ã§ãã¾ã™ã€‚ + + + Target server for the action; can use the format '${ columnName }' to use a data driven column name. + アクションã®ã‚¿ãƒ¼ã‚²ãƒƒãƒˆ サーãƒãƒ¼ã€‚å½¢å¼ '${ columnName }' を使用ã—ã¦ã€ãƒ‡ãƒ¼ã‚¿ ドリブン列åを使用ã§ãã¾ã™ã€‚ + + + Target user for the action; can use the format '${ columnName }' to use a data driven column name. + アクションã®å¯¾è±¡ãƒ¦ãƒ¼ã‚¶ãƒ¼ã€‚å½¢å¼ '${ columnName }' を使用ã—ã¦ã€ãƒ‡ãƒ¼ã‚¿ ドリブン列åを使用ã§ãã¾ã™ã€‚ + + + Identifier of the insight + 分æžæƒ…å ±ã®è­˜åˆ¥å­ + + + Contributes insights to the dashboard palette. + ダッシュボード パレットã«åˆ†æžæƒ…報をæä¾›ã—ã¾ã™ã€‚ + + + + + + + Loading + 読ã¿è¾¼ã¿ä¸­ + + + Loading completed + 読ã¿è¾¼ã¿å®Œäº† + + + + + + + Defines a property to show on the dashboard + ダッシュ ボードã«è¡¨ç¤ºã™ã‚‹ãƒ—ロパティを定義ã—ã¾ã™ + + + What value to use as a label for the property + プロパティã®ãƒ©ãƒ™ãƒ«ã¨ã—ã¦ä½¿ç”¨ã™ã‚‹å€¤ + + + What value in the object to access for the value + 値ã«ã‚¢ã‚¯ã‚»ã‚¹ã™ã‚‹ãŸã‚ã®ã‚ªãƒ–ジェクトã®å€¤ + + + Specify values to be ignored + 無視ã•ã‚Œã‚‹å€¤ã‚’指定ã—ã¾ã™ã€‚ + + + Default value to show if ignored or no value + 無視ã•ã‚Œã‚‹ã‹å€¤ãŒç„¡ã„å ´åˆã«è¡¨ç¤ºã•ã‚Œã‚‹æ—¢å®šå€¤ã§ã™ã€‚ + + + A flavor for defining dashboard properties + デッシュボード プロパティを定義ã™ã‚‹ãŸã‚ã®æ§‹æˆ + + + Id of the flavor + フレーãƒãƒ¼ã® ID + + + Condition to use this flavor + ã“ã®ãƒ•ãƒ¬ãƒ¼ãƒãƒ¼ã‚’使用ã™ã‚‹æ¡ä»¶ + + + Field to compare to + 比較ã™ã‚‹ãƒ•ã‚£ãƒ¼ãƒ«ãƒ‰ + + + Which operator to use for comparison + 比較ã«ä½¿ç”¨ã™ã‚‹æ¼”ç®—å­ + + + Value to compare the field to + フィールドを比較ã™ã‚‹å€¤ + + + Properties to show for database page + データベース ページã«è¡¨ç¤ºã™ã‚‹ãƒ—ロパティ + + + Properties to show for server page + サーãƒãƒ¼ ページã«è¡¨ç¤ºã™ã‚‹ãƒ—ロパティ + + + Defines that this provider supports the dashboard + ã“ã®ãƒ—ロãƒã‚¤ãƒ€ãƒ¼ãŒãƒ€ãƒƒã‚·ãƒ¥ãƒœãƒ¼ãƒ‰ã‚’サãƒãƒ¼ãƒˆã™ã‚‹ã“ã¨ã‚’定義ã—ã¾ã™ + + + Provider id (ex. MSSQL) + プロãƒã‚¤ãƒ€ãƒ¼ id (例: MSSQL) + + + Property values to show on dashboard + ダッシュ ボードã«è¡¨ç¤ºã™ã‚‹ãƒ—ロパティã®å€¤ + + + + + + + Backup + ãƒãƒƒã‚¯ã‚¢ãƒƒãƒ— + + + You must enable preview features in order to use backup + ãƒãƒƒã‚¯ã‚¢ãƒƒãƒ—を使用ã™ã‚‹ã«ã¯ãƒ—レビュー機能を有効ã«ã™ã‚‹å¿…è¦ãŒã‚ã‚Šã¾ã™ã€‚ + + + Backup command is not supported for Azure SQL databases. + Azure SQL データベースã§ã¯ãƒãƒƒã‚¯ã‚¢ãƒƒãƒ— コマンドã¯ã‚µãƒãƒ¼ãƒˆã•ã‚Œã¦ã„ã¾ã›ã‚“。 + + + Backup command is not supported in Server Context. Please select a Database and try again. + ãƒãƒƒã‚¯ã‚¢ãƒƒãƒ— コマンドã¯ã€ã‚µãƒ¼ãƒãƒ¼ コンテキストã§ã¯ã‚µãƒãƒ¼ãƒˆã•ã‚Œã¦ã„ã¾ã›ã‚“。データベースをé¸æŠžã—ã¦ã€ã‚‚ã†ä¸€åº¦ãŠè©¦ã—ãã ã•ã„。 + + + + + + + Restore + 復元 + + + You must enable preview features in order to use restore + 復元を使用ã™ã‚‹ã«ã¯ã€ãƒ—レビュー機能を有効ã«ã™ã‚‹å¿…è¦ãŒã‚ã‚Šã¾ã™ã€‚ + + + Restore command is not supported for Azure SQL databases. + Azure SQL データベースã§ã¯ã€å¾©å…ƒã‚³ãƒžãƒ³ãƒ‰ã¯ã‚µãƒãƒ¼ãƒˆã•ã‚Œã¦ã„ã¾ã›ã‚“。 + + + + + + + disconnected + 切断 + + + + + + + Server Groups + サーãƒãƒ¼ グループ + + + OK + OK + + + Cancel + キャンセル + + + Server group name + サーãƒãƒ¼ グループå + + + Group name is required. + グループåãŒå¿…è¦ã§ã™ã€‚ + + + Group description + グループã®èª¬æ˜Ž + + + Group color + グループã®è‰² + + + + + + + Extension + æ‹¡å¼µå­ + + + + + + + OK + OK + + + Cancel + キャンセル + + + + + + + Open dashboard extensions + ダッシュボードã®æ‹¡å¼µæ©Ÿèƒ½ã‚’é–‹ã + + + OK + OK + + + Cancel + キャンセル + + + No dashboard extensions are installed at this time. Go to Extension Manager to explore recommended extensions. + ã¾ã ãƒ€ãƒƒã‚·ãƒ¥ãƒœãƒ¼ãƒ‰ã®æ‹¡å¼µæ©Ÿèƒ½ãŒã‚¤ãƒ³ã‚¹ãƒˆãƒ¼ãƒ«ã•ã‚Œã¦ã„ã¾ã›ã‚“。拡張機能マãƒãƒ¼ã‚¸ãƒ£ãƒ¼ã«ç§»å‹•ã—ã€æŽ¨å¥¨ã•ã‚Œã‚‹æ‹¡å¼µæ©Ÿèƒ½ã‚’ã”確èªãã ã•ã„。 + + + + + + + Selected path + é¸æŠžã•ã‚ŒãŸãƒ‘ス + + + Files of type + ファイルã®ç¨®é¡ž + + + OK + OK + + + Discard + 破棄 + + + + + + + No Connection Profile was passed to insights flyout + 分æžæƒ…å ±ãƒãƒƒãƒ—アップã«æ¸¡ã•ã‚ŒãŸæŽ¥ç¶šãƒ—ロファイルã¯ã‚ã‚Šã¾ã›ã‚“ã§ã—㟠+ + + Insights error + 分æžæƒ…報エラー + + + There was an error reading the query file: + クエリ ファイルã®èª­ã¿å–り中ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸ: + + + There was an error parsing the insight config; could not find query array/string or queryfile + 洞察ã®è¨­å®šã®è§£æžä¸­ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚クエリé…列/文字ã¾ãŸã¯ã€ã‚¯ã‚¨ãƒª ファイルを見ã¤ã‘ã‚‹ã“ã¨ãŒã§ãã¾ã›ã‚“。 + + + + + + + Clear List + リストã®ã‚¯ãƒªã‚¢ + + + Recent connections list cleared + 接続履歴をクリアã—ã¾ã—㟠+ + + Yes + ã¯ã„ + + + No + ã„ã„㈠+ + + Are you sure you want to delete all the connections from the list? + 一覧ã‹ã‚‰ã™ã¹ã¦ã®æŽ¥ç¶šã‚’削除ã—ã¦ã‚ˆã‚ã—ã„ã§ã™ã‹? + + + Yes + ã¯ã„ + + + No + ã„ã„㈠+ + + Delete + 削除 + + + Get Current Connection String + ç¾åœ¨ã®æŽ¥ç¶šæ–‡å­—列をå–å¾—ã™ã‚‹ + + + Connection string not available + 接続文字列ã¯ä½¿ç”¨ã§ãã¾ã›ã‚“ + + + No active connection available + 使用ã§ãるアクティブãªæŽ¥ç¶šãŒã‚ã‚Šã¾ã›ã‚“ + + + + + + + Refresh + 最新ã®æƒ…å ±ã«æ›´æ–° + + + Disconnect + 切断 + + + New Connection + æ–°ã—ã„接続 + + + New Server Group + æ–°ã—ã„サーãƒãƒ¼ グループ + + + Edit Server Group + サーãƒãƒ¼ グループã®ç·¨é›† + + + Show Active Connections + アクティブãªæŽ¥ç¶šã‚’表示 + + + Show All Connections + ã™ã¹ã¦ã®æŽ¥ç¶šã‚’表示 + + + Recent Connections + 最近ã®æŽ¥ç¶š + + + Delete Connection + 接続ã®å‰Šé™¤ + + + Delete Group + グループã®å‰Šé™¤ + + + + + + + Edit Data Session Failed To Connect + 編集データ セッションã®æŽ¥ç¶šã«å¤±æ•—ã—ã¾ã—㟠+ + + + + + + Profiler + プロファイラー + + + Not connected + 接続ã•ã‚Œã¦ã„ã¾ã›ã‚“ + + + XEvent Profiler Session stopped unexpectedly on the server {0}. + サーãƒãƒ¼ {0} 㧠XEvent プロファイラー セッションãŒäºˆæœŸã›ãšåœæ­¢ã—ã¾ã—ãŸã€‚ + + + Error while starting new session + æ–°ã—ã„セッションを開始中ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—㟠+ + + The XEvent Profiler session for {0} has lost events. + {0} ã® XEvent プロファイラー セッションã®ã‚¤ãƒ™ãƒ³ãƒˆãŒå¤±ã‚ã‚Œã¾ã—ãŸã€‚ + + + Would you like to stop the running XEvent session? + 実行中㮠XEvent セッションをåœæ­¢ã—ã¾ã™ã‹? + + + Yes + ã¯ã„ + + + No + ã„ã„㈠+ + + Cancel + キャンセル + + + + + + + Invalid value + 無効ãªå€¤ + + + {0}. {1} + {0}。{1} + + + + + + + blank + 空白 + + + + + + + Error displaying Plotly graph: {0} + Plotly グラフã®è¡¨ç¤ºã‚¨ãƒ©ãƒ¼: {0} + + + + + + + No {0} renderer could be found for output. It has the following MIME types: {1} + {0} レンダラーãŒå‡ºåŠ›ç”¨ã«è¦‹ã¤ã‹ã‚Šã¾ã›ã‚“ã§ã—ãŸã€‚次㮠MIME ã®ç¨®é¡žãŒã‚ã‚Šã¾ã™: {1}。 + + + (safe) + (安全) + + + + + + + Item + é …ç›® + + + Value + 値 + + + Property + プロパティ + + + Value + 値 + + + Insights + 洞察 + + + Items + アイテム + + + Item Details + アイテムã®è©³ç´° + + + + + + + Error adding account + アカウントã®è¿½åŠ ã‚¨ãƒ©ãƒ¼ + + + + + + + Cannot start auto OAuth. An auto OAuth is already in progress. + 自動 OAuth を開始ã§ãã¾ã›ã‚“。自動 OAuth ã¯æ—¢ã«é€²è¡Œä¸­ã§ã™ã€‚ + + + + + + + Sort by event + イベントã§ä¸¦ã³æ›¿ãˆ + + + Sort by column + 列ã§ä¸¦ã¹æ›¿ãˆ + + + Profiler + プロファイラー + + + OK + OK + + + Cancel + キャンセル + + + + + + + Clear all + ã™ã¹ã¦ã‚¯ãƒªã‚¢ + + + Apply + é©ç”¨ + + + OK + OK + + + Cancel + キャンセル + + + Filters + フィルター + + + Remove this clause + ã“ã®å¥ã‚’削除ã™ã‚‹ + + + Save Filter + フィルターをä¿å­˜ã™ã‚‹ + + + Load Filter + フィルターã®èª­ã¿è¾¼ã¿ + + + Add a clause + å¥ã‚’追加ã™ã‚‹ + + + Field + フィールド + + + Operator + æ¼”ç®—å­ + + + Value + 値 + + + Is Null + Null ã§ã‚ã‚‹ + + + Is Not Null + Null ã§ãªã„ + + + Contains + 次ã®å€¤ã‚’å«ã‚€ + + + Not Contains + å«ã¾ãªã„ + + + Starts With + 次ã§å§‹ã¾ã‚‹ + + + Not Starts With + 次ã§å§‹ã¾ã‚‰ãªã„ + + + + + + + Double-click to edit + ダブルクリックã—ã¦ç·¨é›† + + + + + + + Select Top 1000 + トップ 1000 ã‚’é¸æŠžã™ã‚‹ + + + Script as Execute + スクリプトを実行 + + + Script as Alter + 変更ã™ã‚‹ã¨ãã®ã‚¹ã‚¯ãƒªãƒ—ト + + + Edit Data + データã®ç·¨é›† + + + Script as Create + 作æˆã™ã‚‹ã¨ãã®ã‚¹ã‚¯ãƒªãƒ—ト + + + Script as Drop + ドロップã™ã‚‹ã¨ãã®ã‚¹ã‚¯ãƒªãƒ—ト + + + + + + + No queries to display. + 表示ã™ã‚‹ã‚¯ã‚¨ãƒªãŒã‚ã‚Šã¾ã›ã‚“。 + + + Query History + クエリ履歴 + QueryHistory + + + + + + + Failed to get Azure account token for connection + 接続用㮠Azure アカウント トークンã®å–å¾—ã«å¤±æ•—ã—ã¾ã—㟠+ + + Connection Not Accepted + 接続ãŒè¨±å¯ã•ã‚Œã¦ã„ã¾ã›ã‚“ + + + Yes + ã¯ã„ + + + No + ã„ã„㈠+ + + Are you sure you want to cancel this connection? + ã“ã®æŽ¥ç¶šã‚’キャンセルã—ã¦ã‚‚よã‚ã—ã„ã§ã™ã‹? + + + + + + + Started executing query at + クエリã®å®Ÿè¡Œã‚’開始ã—ã¾ã—ãŸã€‚ + + + Line {0} + è¡Œ {0} + + + Canceling the query failed: {0} + 失敗ã—ãŸã‚¯ã‚¨ãƒªã®ã‚­ãƒ£ãƒ³ã‚»ãƒ«ä¸­: {0} + + + Started saving results to + 以下ã®å ´æ‰€ã¸ã®çµæžœã®ä¿å­˜ã‚’開始ã—ã¾ã—ãŸ: + + + Failed to save results. + çµæžœã‚’ä¿å­˜ã§ãã¾ã›ã‚“ã§ã—ãŸã€‚ + + + Successfully saved results to + çµæžœãŒæ­£å¸¸ã«ä¿å­˜ã•ã‚Œã¾ã—㟠+ + + Executing query... + クエリを実行ã—ã¦ã„ã¾ã™... + + + Maximize + 最大化 + + + Restore + 復元 + + + Save as CSV + CSV ã¨ã—ã¦ä¿å­˜ + + + Save as JSON + JSON ã¨ã—ã¦ä¿å­˜ + + + Save as Excel + Excel ã¨ã—ã¦ä¿å­˜ + + + Save as XML + XML ã¨ã—ã¦ä¿å­˜ + + + View as Chart + グラフã¨ã—ã¦è¡¨ç¤º + + + Visualize + 視覚化 + + + Results + çµæžœ + + + Executing query + クエリを実行中 + + + Messages + メッセージ + + + Total execution time: {0} + ç·å®Ÿè¡Œæ™‚é–“: {0} + + + Save results command cannot be used with multiple selections. + çµæžœã®ä¿å­˜ã‚³ãƒžãƒ³ãƒ‰ã¯ã€è¤‡æ•°ãŒé¸æŠžã•ã‚ŒãŸçŠ¶æ…‹ã§ã¯ä½¿ç”¨ã§ãã¾ã›ã‚“。 + + + + + + + Identifier of the notebook provider. + ノートブック プロãƒã‚¤ãƒ€ãƒ¼ã®è­˜åˆ¥å­ã€‚ + + + What file extensions should be registered to this notebook provider + ã“ã®ãƒŽãƒ¼ãƒˆãƒ–ック プロãƒã‚¤ãƒ€ãƒ¼ã«ã©ã®ãƒ•ã‚¡ã‚¤ãƒ«æ‹¡å¼µå­ã‚’登録ã™ã‚‹å¿…è¦ãŒã‚ã‚‹ã‹ + + + What kernels should be standard with this notebook provider + ã“ã®ãƒŽãƒ¼ãƒˆãƒ–ック プロãƒã‚¤ãƒ€ãƒ¼ã¸ã®æ¨™æº–装備ãŒå¿…è¦ãªã‚«ãƒ¼ãƒãƒ« + + + Contributes notebook providers. + ノートブック プロãƒã‚¤ãƒ€ãƒ¼ã‚’æä¾›ã—ã¾ã™ã€‚ + + + Name of the cell magic, such as '%%sql'. + '%%sql' ãªã©ã®ã‚»ãƒ« マジックã®åå‰ã€‚ + + + The cell language to be used if this cell magic is included in the cell + ã“ã®ã‚»ãƒ« マジックãŒã‚»ãƒ«ã«å«ã¾ã‚Œã‚‹å ´åˆã«ä½¿ç”¨ã•ã‚Œã‚‹ã‚»ãƒ«ã®è¨€èªž + + + Optional execution target this magic indicates, for example Spark vs SQL + Spark vs SQL ãªã©ã€ã“ã®ãƒžã‚¸ãƒƒã‚¯ãŒç¤ºã™ã‚ªãƒ—ションã®å®Ÿè¡Œå¯¾è±¡ + + + Optional set of kernels this is valid for, e.g. python3, pyspark, sql + ã“ã‚ŒãŒæœ‰åŠ¹ãªã‚«ãƒ¼ãƒãƒ«ã®ã‚ªãƒ—ションã®ã‚»ãƒƒãƒˆ (python3ã€pysparkã€sql ãªã©) + + + Contributes notebook language. + ノートブックã®è¨€èªžã‚’æä¾›ã—ã¾ã™ã€‚ + + + + + + + SQL + SQL + + + + + + + F5 shortcut key requires a code cell to be selected. Please select a code cell to run. + F5 ショートカット キーをé¸æŠžã™ã‚‹ã«ã¯ã€ã‚³ãƒ¼ãƒ‰ セルãŒå¿…è¦ã§ã™ã€‚実行ã™ã‚‹ã‚³ãƒ¼ãƒ‰ セルをé¸æŠžã—ã¦ãã ã•ã„。 + + + Clear result requires a code cell to be selected. Please select a code cell to run. + çµæžœã‚’クリアã™ã‚‹ã«ã¯ã€ã‚³ãƒ¼ãƒ‰ セルをé¸æŠžã™ã‚‹å¿…è¦ãŒã‚ã‚Šã¾ã™ã€‚実行ã™ã‚‹ã‚³ãƒ¼ãƒ‰ セルをé¸æŠžã—ã¦ãã ã•ã„。 + + + + + + + Save As CSV + CSV ã¨ã—ã¦ä¿å­˜ + + + Save As JSON + JSON ã¨ã—ã¦ä¿å­˜ + + + Save As Excel + Excel ã¨ã—ã¦ä¿å­˜ + + + Save As XML + XML ã¨ã—ã¦ä¿å­˜ + + + Copy + コピー + + + Copy With Headers + ヘッダー付ãã§ã‚³ãƒ”ー + + + Select All + ã™ã¹ã¦ã‚’é¸æŠž + + + + + + + Max Rows: + 最大行数: + + + + + + + Select View + ビューã®é¸æŠž + + + Select Session + セッションã®é¸æŠž + + + Select Session: + セッションをé¸æŠž: + + + Select View: + ビューをé¸æŠž: + + + Text + テキスト + + + Label + ラベル + + + Value + 値 + + + Details + 詳細 + + + + + + + Copy failed with error {0} + エラー {0} ã§ã‚³ãƒ”ーã«å¤±æ•—ã—ã¾ã—㟠+ + + + + + + New Query + æ–°ã—ã„クエリ + + + Run + 実行 + + + Cancel + キャンセル + + + Explain + 説明 + + + Actual + 実際 + + + Disconnect + 切断 + + + Change Connection + 接続ã®å¤‰æ›´ + + + Connect + 接続 + + + Enable SQLCMD + SQLCMD を有効ã«ã™ã‚‹ + + + Disable SQLCMD + SQLCMD を無効ã«ã™ã‚‹ + + + Select Database + データベースã®é¸æŠž + + + Select Database Toggle Dropdown + データベースã®é¸æŠžã®åˆ‡ã‚Šæ›¿ãˆãƒ‰ãƒ­ãƒƒãƒ—ダウン + + + Failed to change database + データベースを変更ã§ãã¾ã›ã‚“ã§ã—ãŸã€‚ + + + Failed to change database {0} + データベース {0} ã®å¤‰æ›´ã«å¤±æ•—ã—ã¾ã—㟠+ + + + + + + Connection + 接続 + + + Connection type + 接続ã®ç¨®é¡ž + + + Recent Connections + 最近ã®æŽ¥ç¶š + + + Saved Connections + ä¿å­˜ã•ã‚ŒãŸæŽ¥ç¶š + + + Connection Details + 接続ã®è©³ç´° + + + Connect + 接続 + + + Cancel + キャンセル + + + No recent connection + 最近ã®æŽ¥ç¶šã¯ã‚ã‚Šã¾ã›ã‚“ + + + No saved connection + ä¿å­˜ã•ã‚ŒãŸæŽ¥ç¶šã¯ã‚ã‚Šã¾ã›ã‚“ + + + + + + + OK + OK + + + Close + é–‰ã˜ã‚‹ + + + + + + + Loading kernels... + カーãƒãƒ«ã‚’読ã¿è¾¼ã‚“ã§ã„ã¾ã™... + + + Changing kernel... + カーãƒãƒ«ã‚’変更ã—ã¦ã„ã¾ã™... + + + Kernel: + カーãƒãƒ«: + + + Attach To: + 接続先: + + + Loading contexts... + コンテキストを読ã¿è¾¼ã‚“ã§ã„ã¾ã™... + + + Add New Connection + æ–°ã—ã„接続を追加 + + + Select Connection + 接続ã®é¸æŠž + + + localhost + localhost + + + Trusted + 信頼済㿠+ + + Not Trusted + ä¿¡é ¼ã•ã‚Œã¦ã„ã¾ã›ã‚“ + + + Notebook is already trusted. + ノートブックã¯æ—¢ã«ä¿¡é ¼ã•ã‚Œã¦ã„ã¾ã™ã€‚ + + + Collapse Cells + セルã®æŠ˜ã‚ŠãŸãŸã¿ + + + Expand Cells + セルã®å±•é–‹ + + + No Kernel + カーãƒãƒ«ãªã— + + + None + ãªã— + + + New Notebook + æ–°ã—ã„ノートブック + + + + + + + Time Elapsed + 経éŽæ™‚é–“ + + + Row Count + 行数 + + + {0} rows + {0} è¡Œ + + + Executing query... + クエリを実行ã—ã¦ã„ã¾ã™... + + + Execution Status + 実行状態 + + + + + + + No task history to display. + 表示ã™ã‚‹ã‚¿ã‚¹ã‚¯å±¥æ­´ãŒã‚ã‚Šã¾ã›ã‚“。 + + + Task history + タスクã®å±¥æ­´ + TaskHistory + + + Task error + タスク エラー + + + + + + + Choose SQL Language + SQL 言語ã®é¸æŠž + + + Change SQL language provider + SQL 言語プロãƒã‚¤ãƒ€ãƒ¼ã®å¤‰æ›´ + + + SQL Language Flavor + SQL 言語ã®ç¨®é¡ž + + + Change SQL Engine Provider + SQL エンジン プロãƒã‚¤ãƒ€ãƒ¼ã®å¤‰æ›´ + + + A connection using engine {0} exists. To change please disconnect or change connection + エンジン {0} を使用ã—ã¦ã„る接続ãŒå­˜åœ¨ã—ã¾ã™ã€‚変更ã™ã‚‹ã«ã¯ã€åˆ‡æ–­ã™ã‚‹ã‹ã€æŽ¥ç¶šã‚’変更ã—ã¦ãã ã•ã„ + + + No text editor active at this time + ã“ã®æ™‚点ã§ã‚¢ã‚¯ãƒ†ã‚£ãƒ–ãªãƒ†ã‚­ã‚¹ãƒˆ エディターã¯ã‚ã‚Šã¾ã›ã‚“ + + + Select SQL Language Provider + SQL 言語プロãƒã‚¤ãƒ€ãƒ¼ã‚’é¸æŠžã—ã¾ã™ã€‚ + + + + + + + All files + ã™ã¹ã¦ã®ãƒ•ã‚¡ã‚¤ãƒ« + + + + + + + File browser tree + ファイル ブラウザー ツリー + FileBrowserTree + + + + + + + From + 開始 + + + To + To + + + Create new firewall rule + æ–°ã—ã„ファイアウォールルールã®ä½œæˆ + + + OK + OK + + + Cancel + キャンセル + + + Your client IP address does not have access to the server. Sign in to an Azure account and create a new firewall rule to enable access. + ã“ã®ã‚¯ãƒ©ã‚¤ã‚¢ãƒ³ãƒˆ IP アドレスã§ã¯ã‚µãƒ¼ãƒãƒ¼ã«ã‚¢ã‚¯ã‚»ã‚¹ã§ãã¾ã›ã‚“。アクセスã§ãるよã†ã«ã™ã‚‹ã«ã¯ã€Azure アカウントã«ã‚µã‚¤ãƒ³ã‚¤ãƒ³ã—ã€æ–°ã—ã„ファイアウォールè¦å‰‡ã‚’作æˆã—ã¾ã™ã€‚ + + + Learn more about firewall settings + ファイアウォール設定ã®è©³ç´° + + + Azure account + Azure アカウント + + + Firewall rule + ファイアウォールè¦å‰‡ + + + Add my client IP + 自分ã®ã‚¯ãƒ©ã‚¤ã‚¢ãƒ³ãƒˆ IP アドレスを追加 + + + Add my subnet IP range + サブãƒãƒƒãƒˆ IP 範囲を追加 + + + + + + + You need to refresh the credentials for this account. + ã“ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã®è³‡æ ¼æƒ…報を更新ã™ã‚‹å¿…è¦ãŒã‚ã‚Šã¾ã™ã€‚ + + + + + + + Could not find query file at any of the following paths : + {0} + クエリ ファイルãŒã€æ¬¡ã®ã©ã®ãƒ‘スã«ã‚‚見ã¤ã‹ã‚Šã¾ã›ã‚“ã§ã—ãŸ: +{0} + + + + + + + Add an account + アカウントを追加ã—ã¾ã™ + + + Remove account + アカウントを削除ã—ã¾ã™ã€‚ + + + Are you sure you want to remove '{0}'? + '{0}' を削除ã—ã¦ã‚ˆã‚ã—ã„ã§ã™ã‹? + + + Yes + ã¯ã„ + + + No + ã„ã„㈠+ + + Failed to remove account + アカウントを削除ã§ãã¾ã›ã‚“ã§ã—ãŸã€‚ + + + Apply Filters + フィルターã®é©ç”¨ + + + Reenter your credentials + 資格情報をå†å…¥åŠ›ã—ã¦ãã ã•ã„ + + + There is no account to refresh + æ›´æ–°ã™ã‚‹ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã¯ã‚ã‚Šã¾ã›ã‚“ + + + + + + + Focus on Current Query + ç¾åœ¨ã®ã‚¯ã‚¨ãƒªã«ãƒ•ã‚©ãƒ¼ã‚«ã‚¹ã‚’移動ã™ã‚‹ + + + Run Query + クエリã®å®Ÿè¡Œ + + + Run Current Query + ç¾åœ¨ã®ã‚¯ã‚¨ãƒªã‚’実行 + + + Run Current Query with Actual Plan + 実際ã®ãƒ—ランã§ç¾åœ¨ã®ã‚¯ã‚¨ãƒªã‚’実行 + + + Cancel Query + クエリã®ã‚­ãƒ£ãƒ³ã‚»ãƒ« + + + Refresh IntelliSense Cache + IntelliSenseキャッシュã®æ›´æ–° + + + Toggle Query Results + クエリçµæžœã®åˆ‡ã‚Šæ›¿ãˆ + + + Editor parameter is required for a shortcut to be executed + ショートカットを実行ã™ã‚‹ã«ã¯ã‚¨ãƒ‡ã‚£ã‚¿ãƒ¼ パラメーターãŒå¿…è¦ã§ã™ + + + Parse Query + クエリã®è§£æž + + + Commands completed successfully + コマンドãŒæ­£å¸¸ã«å®Œäº†ã—ã¾ã—㟠+ + + Command failed: + コマンドã«å¤±æ•—ã—ã¾ã—ãŸ: + + + Please connect to a server + サーãƒãƒ¼ã«æŽ¥ç¶šã—ã¦ãã ã•ã„ + + + + + + + Chart cannot be displayed with the given data + 指定ã•ã‚ŒãŸãƒ‡ãƒ¼ã‚¿ã®ã‚°ãƒ©ãƒ•ã‚’表示ã§ãã¾ã›ã‚“ + + + + + + + The index {0} is invalid. + インデックス {0} ãŒç„¡åŠ¹ã§ã™ã€‚ + + + + + + + no data available + 利用ã§ãるデータãŒã‚ã‚Šã¾ã›ã‚“ + + + + + + + Information + 情報 + + + Warning + 警告 + + + Error + エラー + + + Show Details + 詳細ã®è¡¨ç¤º + + + Copy + コピー + + + Close + é–‰ã˜ã‚‹ + + + Back + 戻る + + + Hide Details + 詳細を表示ã—ãªã„ + + + + + + + is required. + å¿…é ˆã§ã™ã€‚ + + + Invalid input. Numeric value expected. + 入力ãŒç„¡åŠ¹ã§ã™ã€‚ 数値ãŒå¿…è¦ã§ã™ã€‚ + + + + + + + Execution failed due to an unexpected error: {0} {1} + 予期ã—ãªã„エラーã«ã‚ˆã‚Šå®Ÿè¡ŒãŒå¤±æ•—ã—ã¾ã—ãŸ: {0} {1} + + + Total execution time: {0} + ç·å®Ÿè¡Œæ™‚é–“: {0} + + + Started executing query at Line {0} + è¡Œ {0} ã§ã®ã‚¯ã‚¨ãƒªã®å®Ÿè¡ŒãŒé–‹å§‹ã•ã‚Œã¾ã—㟠+ + + Initialize edit data session failed: + データ セッションã®ç·¨é›†ã‚’åˆæœŸåŒ–ã§ãã¾ã›ã‚“ã§ã—ãŸ: + + + Batch execution time: {0} + ãƒãƒƒãƒå®Ÿè¡Œæ™‚é–“: {0} + + + Copy failed with error {0} + エラー {0} ã§ã‚³ãƒ”ーã«å¤±æ•—ã—ã¾ã—㟠+ + + + + + + Error: {0} + エラー: {0} + + + Warning: {0} + 警告: {0} + + + Info: {0} + 情報: {0} + + + + + + + Copy Cell + セルをコピー + + + + + + + Backup file path + ãƒãƒƒã‚¯ã‚¢ãƒƒãƒ— ファイルã®ãƒ‘ス + + + Target database + ターゲット データベース + + + Restore database + データベースを復元 + + + Restore database + データベースを復元 + + + Database + データベース + + + Backup file + ãƒãƒƒã‚¯ã‚¢ãƒƒãƒ— ファイル + + + Restore + 復元 + + + Cancel + キャンセル + + + Script + スクリプト + + + Source + SOURCE + + + Restore from + 復元元 + + + Backup file path is required. + ãƒãƒƒã‚¯ã‚¢ãƒƒãƒ— ファイルã®ãƒ‘スãŒå¿…è¦ã§ã™ã€‚ + + + Please enter one or more file paths separated by commas + 1ã¤ã¾ãŸã¯è¤‡æ•°ã®ãƒ•ã‚¡ã‚¤ãƒ«ãƒ‘スをコンマã§åŒºåˆ‡ã£ã¦å…¥åŠ›ã—ã¦ãã ã•ã„。 + + + Database + データベース + + + Destination + å…ˆ + + + Select Database Toggle Dropdown + データベースã®é¸æŠžã®åˆ‡ã‚Šæ›¿ãˆãƒ‰ãƒ­ãƒƒãƒ—ダウン + + + Restore to + 次ã®å ´æ‰€ã¸å¾©å…ƒ: + + + Restore plan + 復元計画 + + + Backup sets to restore + 復元ã™ã‚‹ãƒãƒƒã‚¯ã‚¢ãƒƒãƒ— セット + + + Restore database files as + データベース ファイルをåå‰ã‚’付ã‘ã¦å¾©å…ƒ + + + Restore database file details + データベース ファイルã®è©³ç´°ã‚’復元ã™ã‚‹ + + + Logical file Name + è«–ç†ãƒ•ã‚¡ã‚¤ãƒ«å + + + File type + ファイルタイプ + + + Original File Name + å…ƒã®ãƒ•ã‚¡ã‚¤ãƒ«å + + + Restore as + 復元先 + + + Restore options + 復元オプション + + + Tail-Log backup + ログ末尾ã®ãƒãƒƒã‚¯ã‚¢ãƒƒãƒ— + + + Server connections + サーãƒãƒ¼ã®æŽ¥ç¶š + + + General + 全般 + + + Files + ファイル + + + Options + オプション + + + + + + + Copy & Open + コピーã—ã¦é–‹ã + + + Cancel + キャンセル + + + User code + ユーザー コード + + + Website + Web サイト + + + + + + + Done + 完了 + + + Cancel + キャンセル + + + + + + + Must be an option from the list + 一覧ã‹ã‚‰ã‚ªãƒ—ションをé¸æŠžã™ã‚‹å¿…è¦ãŒã‚ã‚Šã¾ã™ + + + Toggle dropdown + ドロップダウンã®åˆ‡ã‚Šæ›¿ãˆ + + + + + + + Select/Deselect All + ã™ã¹ã¦é¸æŠž/é¸æŠžè§£é™¤ + + + checkbox checked + ãƒã‚§ãƒƒã‚¯ãƒœãƒƒã‚¯ã‚¹ãŒã‚ªãƒ³ + + + checkbox unchecked + ãƒã‚§ãƒƒã‚¯ãƒœãƒƒã‚¯ã‚¹ãŒã‚ªãƒ• + + + + + + + modelview code editor for view model. + ビュー モデルã®ãƒ¢ãƒ¼ãƒ‰ãƒ“ュー コード エディター。 + + + + + + + succeeded + æˆåŠŸ + + + failed + 失敗 + + + + + + + Server Description (optional) + サーãƒãƒ¼ã®èª¬æ˜Ž (オプション) + + + + + + + Advanced Properties + 高度ãªãƒ—ロパティ + + + Discard + 破棄 + + + + + + + Linked accounts + リンクã•ã‚Œã¦ã„るアカウント + + + Close + é–‰ã˜ã‚‹ + + + There is no linked account. Please add an account. + リンクã•ã‚Œã¦ã„るアカウントã¯ã‚ã‚Šã¾ã›ã‚“。アカウントを追加ã—ã¦ãã ã•ã„。 + + + Add an account + アカウントを追加ã—ã¾ã™ + + + + + + + nbformat v{0}.{1} not recognized + nbformat v{0}.{1} ãŒèªè­˜ã•ã‚Œã¾ã›ã‚“ + + + This file does not have a valid notebook format + ã“ã®ãƒ•ã‚¡ã‚¤ãƒ«ã¯æœ‰åŠ¹ãªãƒŽãƒ¼ãƒˆãƒ–ックã®å½¢å¼ã§ã¯ã‚ã‚Šã¾ã›ã‚“ + + + Cell type {0} unknown + セルã®ç¨®é¡ž {0} ãŒä¸æ˜Ž + + + Output type {0} not recognized + 出力ã®ç¨®é¡ž {0} ã‚’èªè­˜ã§ãã¾ã›ã‚“ + + + Data for {0} is expected to be a string or an Array of strings + {0} ã®ãƒ‡ãƒ¼ã‚¿ã¯ã€æ–‡å­—列ã¾ãŸã¯æ–‡å­—列ã®é…列ã§ã‚ã‚‹å¿…è¦ãŒã‚ã‚Šã¾ã™ã€‚ + + + Output type {0} not recognized + 出力ã®ç¨®é¡ž {0} ã‚’èªè­˜ã§ãã¾ã›ã‚“ + + + + + + + Profiler editor for event text. Readonly + イベントテキストã®ãƒ—ロファイラーエディター。読ã¿å–り専用 + + + + + + + Run Cells Before + å‰ã«ã‚»ãƒ«ã‚’実行ã™ã‚‹ + + + Run Cells After + ã“ã®ã‚»ãƒ«ä»¥é™ã‚’実行ã™ã‚‹ + + + Insert Code Before + å‰ã«ã‚³ãƒ¼ãƒ‰ã‚’挿入 + + + Insert Code After + 後ã«ã‚³ãƒ¼ãƒ‰ã‚’挿入 + + + Insert Text Before + å‰ã«ãƒ†ã‚­ã‚¹ãƒˆã‚’挿入ã™ã‚‹ + + + Insert Text After + 後ã‚ã«ãƒ†ã‚­ã‚¹ãƒˆã‚’挿入ã™ã‚‹ + + + Collapse Cell + セルã®æŠ˜ã‚ŠãŸãŸã¿ + + + Expand Cell + セルã®å±•é–‹ + + + Clear Result + çµæžœã®ã‚¯ãƒªã‚¢ + + + Delete + 削除 + + + + + + + No script was returned when calling select script on object + オブジェクトã§ã‚¹ã‚¯ãƒªãƒ—トã®é¸æŠžã‚’呼ã³å‡ºã—ãŸã¨ãã«ã‚¹ã‚¯ãƒªãƒ—トãŒè¿”ã•ã‚Œã¾ã›ã‚“ã§ã—㟠+ + + Select + é¸æŠž + + + Create + ä½œæˆ + + + Insert + 挿入 + + + Update + æ›´æ–° + + + Delete + 削除 + + + No script was returned when scripting as {0} on object {1} + オブジェクト {1} 㧠{0} ã¨ã—ã¦ã‚¹ã‚¯ãƒªãƒ—トを作æˆã—ãŸã¨ãã«è¿”ã•ã‚ŒãŸã‚¹ã‚¯ãƒªãƒ—トã¯ã‚ã‚Šã¾ã›ã‚“ + + + Scripting Failed + スクリプト作æˆã«å¤±æ•—ã—ã¾ã—㟠+ + + No script was returned when scripting as {0} + {0} ã¨ã—ã¦ã‚¹ã‚¯ãƒªãƒ—ト化ã—ãŸã¨ãã«ã€ã‚¹ã‚¯ãƒªãƒ—トãŒè¿”ã•ã‚Œã¾ã›ã‚“ã§ã—㟠+ + + + + + + Recent Connections + 最近ã®æŽ¥ç¶š + + + Servers + サーãƒãƒ¼ + + + + + + + No Kernel + カーãƒãƒ«ãªã— + + + Cannot run cells as no kernel has been configured + カーãƒãƒ«ãŒæ§‹æˆã•ã‚Œã¦ã„ãªã„ãŸã‚ã€ã‚»ãƒ«ã‚’実行ã§ãã¾ã›ã‚“ + + + Error + エラー + + + + + + + Azure Data Studio + Azure Data Studio + + + Start + 開始 + + + New connection + æ–°ã—ã„接続 + + + New query + æ–°ã—ã„クエリ + + + New notebook + æ–°ã—ã„ノートブック + + + Open file + ファイルを開ã + + + Open file + ファイルを開ã + + + Deploy + 展開 + + + Deploy SQL Server… + SQL Server ã®å±•é–‹... + + + Recent + 最近 + + + More... + ãã®ä»–... + + + No recent folders + 最近使用ã—ãŸãƒ•ã‚©ãƒ«ãƒ€ãƒ¼ãªã— + + + Help + ヘルプ + + + Getting started + ã¯ã˜ã‚ã« + + + Documentation + ドキュメント + + + Report issue or feature request + å•é¡Œã‚’報告ã™ã‚‹ã‹æ©Ÿèƒ½ã®ãƒªã‚¯ã‚¨ã‚¹ãƒˆã‚’é€ä¿¡ã™ã‚‹ + + + GitHub repository + GitHub リãƒã‚¸ãƒˆãƒª + + + Release notes + リリース ノート + + + Show welcome page on startup + 起動時ã«ã‚¦ã‚§ãƒ«ã‚«ãƒ  ページを表示 + + + Customize + カスタマイズ + + + Extensions + 拡張機能 + + + Download extensions that you need, including the SQL Server Admin pack and more + SQL Server 管ç†ãƒ‘ックãªã©ã€å¿…è¦ãªæ‹¡å¼µæ©Ÿèƒ½ã‚’ダウンロードã™ã‚‹ + + + Keyboard Shortcuts + キーボード ショートカット + + + Find your favorite commands and customize them + ãŠæ°—ã«å…¥ã‚Šã®ã‚³ãƒžãƒ³ãƒ‰ã‚’見ã¤ã‘ã¦ã‚«ã‚¹ã‚¿ãƒžã‚¤ã‚ºã™ã‚‹ + + + Color theme + é…色テーマ + + + Make the editor and your code look the way you love + エディターã¨ã‚³ãƒ¼ãƒ‰ã®å¤–観を自由ã«è¨­å®šã—ã¾ã™ + + + Learn + 学㶠+ + + Find and run all commands + ã™ã¹ã¦ã®ã‚³ãƒžãƒ³ãƒ‰ã®æ¤œç´¢ã¨å®Ÿè¡Œ + + + Rapidly access and search commands from the Command Palette ({0}) + コマンド パレットã‹ã‚‰ã‚³ãƒžãƒ³ãƒ‰ã‚’検索ã—ã¦ã™ã°ã‚„ãアクセスã—ã¾ã™ ({0}) + + + Discover what's new in the latest release + 最新リリースã®æ–°æ©Ÿèƒ½ã‚’見る + + + New monthly blog posts each month showcasing our new features + 毎月新機能を紹介ã™ã‚‹æ–°ã—ã„ブログ記事 + + + Follow us on Twitter + Twitter ã§ãƒ•ã‚©ãƒ­ãƒ¼ã™ã‚‹ + + + Keep up to date with how the community is using Azure Data Studio and to talk directly with the engineers. + コミュニティãŒã©ã®ã‚ˆã†ã«Azure Data Studioを使用ã—ã¦ã„ã‚‹ã®ã‹ã«ã¤ã„ã¦ã®æœ€æ–°æƒ…報を把æ¡ã—ã€ã‚¨ãƒ³ã‚¸ãƒ‹ã‚¢ã¨ç›´æŽ¥è©±ã—åˆã†ãŸã‚ã§ã™ã€‚ + + + + + + + succeeded + æˆåŠŸ + + + failed + 失敗 + + + in progress + 進行中 + + + not started + 未開始 + + + canceled + キャンセル + + + canceling + キャンセル + + + + + + + Run + 実行 + + + Dispose Edit Failed With Error: + 編集内容ã®ç ´æ£„ãŒã‚¨ãƒ©ãƒ¼ãŒå¤±æ•—ã—ã¾ã—ãŸã€‚ + + + Stop + åœæ­¢ + + + Show SQL Pane + SQL ペインã®è¡¨ç¤º + + + Close SQL Pane + SQL ペインを閉ã˜ã‚‹ + + + + + + + Connect + 接続 + + + Disconnect + 切断 + + + Start + 開始 + + + New Session + æ–°ã—ã„セッション + + + Pause + 一時åœæ­¢ + + + Resume + å†é–‹ + + + Stop + åœæ­¢ + + + Clear Data + データã®ã‚¯ãƒªã‚¢ + + + Auto Scroll: On + 自動スクロール: オン + + + Auto Scroll: Off + 自動スクロール: オフ + + + Toggle Collapsed Panel + 折りãŸãŸã‚“ã ãƒ‘ãƒãƒ«ã‚’切り替ãˆã‚‹ + + + Edit Columns + 列ã®ç·¨é›† + + + Find Next String + 次ã®æ–‡å­—列を検索 + + + Find Previous String + å‰ã®æ–‡å­—列を検索ã—ã¾ã™ + + + Launch Profiler + プロファイラーを起動 + + + Filter… + フィルター... + + + Clear Filter + フィルターã®ã‚¯ãƒªã‚¢ + + + + + + + Events (Filtered): {0}/{1} + イベント (フィルター処ç†æ¸ˆã¿): {0}/{1} + + + Events: {0} + イベント: {0} + + + Event Count + イベント数 + + + + + + + Save As CSV + CSV ã¨ã—ã¦ä¿å­˜ + + + Save As JSON + JSON ã¨ã—ã¦ä¿å­˜ + + + Save As Excel + Excel ã¨ã—ã¦ä¿å­˜ + + + Save As XML + XML ã¨ã—ã¦ä¿å­˜ + + + Save to file is not supported by the backing data source + ãƒãƒƒã‚­ãƒ³ã‚° データ ソースã§ãƒ•ã‚¡ã‚¤ãƒ«ã®ä¿å­˜ã¯ã‚µãƒãƒ¼ãƒˆã•ã‚Œã¦ã„ã¾ã›ã‚“ + + + Copy + コピー + + + Copy With Headers + ヘッダー付ãã§ã‚³ãƒ”ー + + + Select All + ã™ã¹ã¦ã‚’é¸æŠž + + + Copy + コピー + + + Copy All + ã™ã¹ã¦ã‚³ãƒ”ー + + + Maximize + 最大化 + + + Restore + 復元 + + + Chart + グラフ + + + Visualizer + ビジュアライザー + + + + + + + The extension "{0}" is using sqlops module which has been replaced by azdata module, the sqlops module will be removed in a future release. + 拡張機能 "{0}" 㯠azdata モジュールã«ç½®ãæ›ãˆã‚‰ã‚ŒãŸ sqlops モジュールを使用ã—ã¦ã„ã¾ã™ãŒã€sqlops モジュールã¯å°†æ¥ã®ãƒªãƒªãƒ¼ã‚¹ã§å‰Šé™¤ã•ã‚Œã‚‹äºˆå®šã§ã™ã€‚ + + + + + + + Table header background color + 表ã®ãƒ˜ãƒƒãƒ€ãƒ¼ã®èƒŒæ™¯è‰² + + + Table header foreground color + テーブル ヘッダーã®å‰æ™¯è‰² + + + Disabled Input box background. + 入力ボックスã®èƒŒæ™¯ãŒç„¡åŠ¹ã«ã•ã‚Œã¾ã—ãŸã€‚ + + + Disabled Input box foreground. + 無効ãªå…¥åŠ›ãƒœãƒƒã‚¯ã‚¹ã®å‰æ™¯è‰²ã€‚ + + + Button outline color when focused. + フォーカスã—ãŸã¨ãã®ãƒœã‚¿ãƒ³ã®å¤–æž ã®è‰²ã€‚ + + + Disabled checkbox foreground. + ãƒã‚§ãƒƒã‚¯ãƒœãƒƒã‚¯ã‚¹ã®å‰æ™¯ã‚’無効ã«ã—ã¾ã—ãŸã€‚ + + + List/Table background color for the selected and focus item when the list/table is active + リスト/テーブルãŒã‚¢ã‚¯ãƒ†ã‚£ãƒ–ãªã¨ãã«é¸æŠžã—ãŸé …ç›®ã¨ãƒ•ã‚©ãƒ¼ã‚«ã‚¹ã®ã‚ã‚‹é …ç›®ã®ãƒªã‚¹ãƒˆ/テーブル背景色 + + + SQL Agent Table background color. + SQL エージェントã®ãƒ†ãƒ¼ãƒ–ル背景色。 + + + SQL Agent table cell background color. + SQL エージェント テーブル セルã®èƒŒæ™¯è‰²ã€‚ + + + SQL Agent table hover background color. + SQL エージェント テーブル ホãƒãƒ¼ã®èƒŒæ™¯è‰²ã€‚ + + + SQL Agent heading background color. + SQL エージェントã®è¦‹å‡ºã—背景色。 + + + SQL Agent table cell border color. + SQL エージェントã®ãƒ†ãƒ¼ãƒ–ル セルã®ç½«ç·šè‰²ã€‚ + + + Results messages error color. + çµæžœãƒ¡ãƒƒã‚»ãƒ¼ã‚¸ã®ã‚¨ãƒ©ãƒ¼ã®è‰²ã€‚ + + + + + + + Choose Results File + çµæžœãƒ•ã‚¡ã‚¤ãƒ«ã‚’é¸æŠžã—ã¾ã™ã€‚ + + + CSV (Comma delimited) + CSV (カンマ区切り) + + + JSON + JSON + + + Excel Workbook + Excel ブック + + + XML + XML + + + Plain Text + プレーンテキスト + + + Open file location + ファイルã®å ´æ‰€ã‚’é–‹ã + + + Open file + ファイルを開ã + + + + + + + Backup name + ãƒãƒƒã‚¯ã‚¢ãƒƒãƒ—å + + + Recovery model + 復旧モデル + + + Backup type + ãƒãƒƒã‚¯ã‚¢ãƒƒãƒ—ã®ç¨®é¡ž + + + Backup files + ãƒãƒƒã‚¯ã‚¢ãƒƒãƒ— ファイル + + + Algorithm + アルゴリズム + + + Certificate or Asymmetric key + 証明書ã¾ãŸã¯éžå¯¾ç§°ã‚­ãƒ¼ + + + Media + メディア + + + Backup to the existing media set + 既存ã®ãƒ¡ãƒ‡ã‚£ã‚¢ セットã¸ã®ãƒãƒƒã‚¯ã‚¢ãƒƒãƒ— + + + Backup to a new media set + æ–°ã—ã„メディア セットã«ãƒãƒƒã‚¯ã‚¢ãƒƒãƒ— + + + Append to the existing backup set + 既存ã®ãƒãƒƒã‚¯ã‚¢ãƒƒãƒ— セットã«è¿½åŠ  + + + Overwrite all existing backup sets + 既存ã®ã™ã¹ã¦ã®ãƒãƒƒã‚¯ã‚¢ãƒƒãƒ— セットを上書ãã™ã‚‹ + + + New media set name + æ–°ã—ã„メディア セットå + + + New media set description + æ–°ã—ã„メディア セットã®èª¬æ˜Ž + + + Perform checksum before writing to media + メディアã«æ›¸ã込むå‰ã«ãƒã‚§ãƒƒã‚¯ã‚µãƒ ã‚’行ㆠ+ + + Verify backup when finished + 完了時ã«ãƒãƒƒã‚¯ã‚¢ãƒƒãƒ—を検証ã—ã¾ã™ã€‚ + + + Continue on error + エラー時ã«ç¶šè¡Œ + + + Expiration + æœ‰åŠ¹æœŸé™ + + + Set backup retain days + ãƒãƒƒã‚¯ã‚¢ãƒƒãƒ—ã®ä¿æŒæ—¥æ•° + + + Copy-only backup + コピーã®ã¿ã®ãƒãƒƒã‚¯ã‚¢ãƒƒãƒ— + + + Advanced Configuration + 高度ãªæ§‹æˆ + + + Compression + 圧縮 + + + Set backup compression + ãƒãƒƒã‚¯ã‚¢ãƒƒãƒ—ã®åœ§ç¸®ã®è¨­å®š + + + Encryption + æš—å·åŒ– + + + Transaction log + トランザクション ログ + + + Truncate the transaction log + トランザクション ログã®åˆ‡ã‚Šæ¨ã¦ + + + Backup the tail of the log + ログ末尾ã®ãƒãƒƒã‚¯ã‚¢ãƒƒãƒ— + + + Reliability + 信頼性 + + + Media name is required + メディアåãŒå¿…è¦ã§ã™ã€‚ + + + No certificate or asymmetric key is available + 証明書ã¾ãŸã¯éžå¯¾ç§°ã‚­ãƒ¼ã¯ä½¿ç”¨ã§ãã¾ã›ã‚“ + + + Add a file + ファイルã®è¿½åŠ  + + + Remove files + ファイルを削除 + + + Invalid input. Value must be greater than or equal 0. + 入力ãŒç„¡åŠ¹ã§ã™ã€‚値㯠0 以上ã§ãªã‘ã‚Œã°ãªã‚Šã¾ã›ã‚“。 + + + Script + スクリプト + + + Backup + ãƒãƒƒã‚¯ã‚¢ãƒƒãƒ— + + + Cancel + キャンセル + + + Only backup to file is supported + ファイルã¸ã®ãƒãƒƒã‚¯ã‚¢ãƒƒãƒ—ã®ã¿ãŒã‚µãƒãƒ¼ãƒˆã•ã‚Œã¦ã„ã¾ã™ã€‚ + + + Backup file path is required + ãƒãƒƒã‚¯ã‚¢ãƒƒãƒ— ファイルã®ãƒ‘スãŒå¿…è¦ã§ã™ã€‚ + + + + + + + Results + çµæžœ + + + Messages + メッセージ + + + + + + + There is no data provider registered that can provide view data. + ビュー データをæä¾›ã§ãるデータ プロãƒã‚¤ãƒ€ãƒ¼ãŒç™»éŒ²ã•ã‚Œã¦ã„ã¾ã›ã‚“。 + + + Collapse All + ã™ã¹ã¦æŠ˜ã‚ŠãŸãŸã‚“ã§è¡¨ç¤ºã—ã¾ã™ã€‚ + + + + + + + Home + ホーム + + + + + + + The "{0}" section has invalid content. Please contact extension owner. + "{0}" セクションã«ç„¡åŠ¹ãªã‚³ãƒ³ãƒ†ãƒ³ãƒ„ãŒã‚ã‚Šã¾ã™ã€‚機能拡張ã®æ‰€æœ‰è€…ã«ãŠå•ã„åˆã‚ã›ãã ã•ã„。 + + + + + + + Jobs + ジョブ + + + Notebooks + ノートブック + + + Alerts + 警告 + + + Proxies + プロキシ + + + Operators + æ¼”ç®—å­ + + + + + + + Loading + 読ã¿è¾¼ã¿ä¸­ + + + + + + + SERVER DASHBOARD + サーãƒãƒ¼ ダッシュボード + + + + + + + DATABASE DASHBOARD + データベース ダッシュボード + + + + + + + Edit + 編集 + + + Exit + 終了 + + + Refresh + 最新ã®æƒ…å ±ã«æ›´æ–° + + + Toggle More + 詳細ã«åˆ‡ã‚Šæ›¿ãˆã‚‹ + + + Delete Widget + ウィジェットã®å‰Šé™¤ + + + Click to unpin + クリックã—ã¦ãƒ”ン留ã‚を外ã—ã¾ã™ + + + Click to pin + クリックã—ã¦ãƒ”ン留ã‚ã—ã¾ã™ + + + Open installed features + インストールã•ã‚Œã¦ã„る機能を開ã + + + Collapse + 折りãŸãŸã¿ + + + Expand + 展開 + + + + + + + Steps + ステップ + + + + + + + StdIn: + Stdin: + + + + + + + Add code + コードã®è¿½åŠ  + + + Add text + テキストã®è¿½åŠ  + + + Create File + ファイルã®ä½œæˆ + + + Could not display contents: {0} + コンテンツを表示ã§ãã¾ã›ã‚“ã§ã—ãŸ: {0} + + + Please install the SQL Server 2019 extension to run cells. + セルを実行ã™ã‚‹ã«ã¯ã€SQL Server 2019 拡張機能をインストールã—ã¦ãã ã•ã„。 + + + Install Extension + 拡張機能ã®ã‚¤ãƒ³ã‚¹ãƒˆãƒ¼ãƒ« + + + Code + コード + + + Text + テキスト + + + Run Cells + セルã®å®Ÿè¡Œ + + + Clear Results + çµæžœã‚’クリア + + + < Previous + < å‰ã¸ + + + Next > + 次㸠> + + + cell with URI {0} was not found in this model + URI {0} ã‚’å«ã‚€ã‚»ãƒ«ã¯ã€ã“ã®ãƒ¢ãƒ‡ãƒ«ã§ã¯è¦‹ã¤ã‹ã‚Šã¾ã›ã‚“ã§ã—㟠+ + + Run Cells failed - See error in output of the currently selected cell for more information. + セルã®å®Ÿè¡Œã«å¤±æ•—ã—ã¾ã—ãŸã€‚詳細ã«ã¤ã„ã¦ã¯ã€ç¾åœ¨é¸æŠžã•ã‚Œã¦ã„るセルã®å‡ºåŠ›ã®ã‚¨ãƒ©ãƒ¼ã‚’ã”覧ãã ã•ã„。 + + + + + + + Click on + 次をクリック + + + + Code + + コード + + + or + OR + + + + Text + + テキスト + + + to add a code or text cell + コードã¾ãŸã¯ãƒ†ã‚­ã‚¹ãƒˆã®ã‚»ãƒ«ã‚’追加ã™ã‚‹ãŸã‚ + + + + + + + Database + データベース + + + Files and filegroups + ファイルã¨ãƒ•ã‚¡ã‚¤ãƒ« グループ + + + Full + 完全 + + + Differential + 差分 + + + Transaction Log + トランザクション ログ + + + Disk + ディスク + + + Url + URL + + + Use the default server setting + 既定ã®ã‚µãƒ¼ãƒãƒ¼è¨­å®šã‚’使用ã™ã‚‹ + + + Compress backup + ãƒãƒƒã‚¯ã‚¢ãƒƒãƒ—ã®åœ§ç¸® + + + Do not compress backup + ãƒãƒƒã‚¯ã‚¢ãƒƒãƒ—を圧縮ã—ãªã„ + + + Server Certificate + サーãƒãƒ¼è¨¼æ˜Žæ›¸ + + + Asymmetric Key + éžå¯¾ç§°ã‚­ãƒ¼ + + + Backup Files + ãƒãƒƒã‚¯ã‚¢ãƒƒãƒ— ファイル + + + All Files + ã™ã¹ã¦ã®ãƒ•ã‚¡ã‚¤ãƒ« + + + + + + + No connections found. + 接続ãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“ã§ã—ãŸã€‚ + + + Add Connection + 接続ã®è¿½åŠ  + + + + + + + Failed to change database + データベースを変更ã§ãã¾ã›ã‚“ã§ã—ãŸã€‚ + + + + + + + Name + åå‰ + + + Email Address + é›»å­ãƒ¡ãƒ¼ãƒ« アドレス + + + Enabled + 有効 + + + + + + + Name + åå‰ + + + Last Occurrence + 最後ã®ç™ºç”Ÿ + + + Enabled + 有効 + + + Delay Between Responses (in secs) + 応答ã®é–“ã®é…延 (秒) + + + Category Name + カテゴリå + + + + + + + Account Name + アカウントå + + + Credential Name + 資格情報å + + + Description + 説明 + + + Enabled + 有効 + + + + + + + Unable to load dashboard properties + ダッシュボードã®ãƒ—ロパティを読ã¿è¾¼ã‚€ã“ã¨ãŒã§ãã¾ã›ã‚“。 + + + + + + + Search by name of type (a:, t:, v:, f:, or sp:) + åž‹ã®åå‰ã§æ¤œç´¢ã™ã‚‹ (a:ã€t:ã€v:ã€f:ã€sp:) + + + Search databases + 検索データベース + + + Unable to load objects + オブジェクトを読ã¿è¾¼ã‚€ã“ã¨ãŒã§ãã¾ã›ã‚“ + + + Unable to load databases + データベースを読ã¿è¾¼ã‚ã¾ã›ã‚“ + + + + + + + Auto Refresh: OFF + 自動更新: オフ + + + Last Updated: {0} {1} + 最終更新日: {0} {1} + + + No results to show + 表示ã™ã‚‹çµæžœãŒã‚ã‚Šã¾ã›ã‚“。 + + + + + + + No {0}renderer could be found for output. It has the following MIME types: {1} + {0} レンダラーãŒå‡ºåŠ›ç”¨ã«è¦‹ã¤ã‹ã‚Šã¾ã›ã‚“ã§ã—ãŸã€‚次㮠MIME タイプ {1} ãŒã‚ã‚Šã¾ã™ã€‚ + + + safe + 安全 + + + No component could be found for selector {0} + セレクター {0} ã®ã‚³ãƒ³ãƒãƒ¼ãƒãƒ³ãƒˆãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“ã§ã—㟠+ + + Error rendering component: {0} + コンãƒãƒ¼ãƒãƒ³ãƒˆã®ãƒ¬ãƒ³ãƒ€ãƒªãƒ³ã‚° エラー: {0} + + + + + + + Connected to + 接続先 + + + Disconnected + 切断 + + + Unsaved Connections + ä¿å­˜ã•ã‚Œã¦ã„ãªã„接続 + + + + + + + Delete Row + 行を削除 + + + Revert Current Row + ç¾åœ¨ã®è¡Œã‚’å…ƒã«æˆ»ã™ + + + + + + + Step ID + ステップ ID + + + Step Name + ステップå + + + Message + メッセージ + + + + + + + XML Showplan + XML プラン表示 + + + Results grid + çµæžœã‚°ãƒªãƒƒãƒ‰ + + + + + + + Please select active cell and try again + アクティブãªã‚»ãƒ«ã‚’é¸æŠžã—ã¦ã€ã‚‚ã†ä¸€åº¦ãŠè©¦ã—ãã ã•ã„ + + + Run cell + セルã®å®Ÿè¡Œ + + + Cancel execution + 実行ã®ã‚­ãƒ£ãƒ³ã‚»ãƒ« + + + Error on last run. Click to run again + 最後ã®å®Ÿè¡Œã§ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚ã‚‚ã†ä¸€åº¦å®Ÿè¡Œã™ã‚‹ã«ã¯ã‚¯ãƒªãƒƒã‚¯ã—ã¦ãã ã•ã„ + + + + + + + Add an account... + アカウントã®è¿½åŠ ... + + + <Default> + <既定> + + + Loading... + 読ã¿è¾¼ã‚“ã§ã„ã¾ã™... + + + Server group + サーãƒãƒ¼ グループ + + + <Default> + <既定> + + + Add new group... + æ–°ã—ã„グループを追加... + + + <Do not save> + <ä¿å­˜ã—ãªã„> + + + {0} is required. + {0} ãŒå¿…è¦ã§ã™ã€‚ + + + {0} will be trimmed. + {0} ãŒãƒˆãƒªãƒŸãƒ³ã‚°ã•ã‚Œã¾ã™ã€‚ + + + Remember password + パスワードを記録ã™ã‚‹ + + + Account + アカウント + + + Refresh account credentials + アカウントã®è³‡æ ¼æƒ…報を更新 + + + Azure AD tenant + Azure AD テナント + + + Select Database Toggle Dropdown + データベースã®é¸æŠžã®åˆ‡ã‚Šæ›¿ãˆãƒ‰ãƒ­ãƒƒãƒ—ダウン + + + Name (optional) + åå‰ (オプション) + + + Advanced... + 詳細... + + + You must select an account + アカウントをé¸æŠžã™ã‚‹å¿…è¦ãŒã‚ã‚Šã¾ã™ + + + + + + + Cancel + キャンセル + + + The task is failed to cancel. + タスクをキャンセルã§ãã¾ã›ã‚“ã§ã—ãŸã€‚ + + + Script + スクリプト + + + + + + + Date Created: + 作æˆæ—¥: + + + Notebook Error: + ノートブック エラー: + + + Job Error: + ジョブ エラー: + + + Pinned + ピン留゠+ + + Recent Runs + 最近ã®å®Ÿè¡Œ + + + Past Runs + éŽåŽ»ã®å®Ÿè¡Œ + + + + + + + No tree view with id '{0}' registered. + ID '{0}' ã®ãƒ„リー ビューã¯ç™»éŒ²ã•ã‚Œã¦ã„ã¾ã›ã‚“。 + + + + + + + Loading... + 読ã¿è¾¼ã‚“ã§ã„ã¾ã™... + + + + + + + Dashboard Tabs ({0}) + ダッシュボード タブ ({0}) + + + Id + ID + + + Title + タイトル + + + Description + 説明 + + + Dashboard Insights ({0}) + ダッシュボード分æžæƒ…å ± ({0}) + + + Id + ID + + + Name + åå‰ + + + When + ã„㤠+ + + + + + + Chart + グラフ + + + + + + + Operation + æ“作 + + + Object + オブジェクト + + + Est Cost + 推定コスト + + + Est Subtree Cost + サブ ツリーã®ã‚³ã‚¹ãƒˆäºˆæ¸¬ + + + Actual Rows + 実際ã®è¡Œæ•° + + + Est Rows + 推定行数 + + + Actual Executions + 実際ã®å®Ÿè¡Œ + + + Est CPU Cost + 推定 CPU コスト + + + Est IO Cost + 推定 IO コスト + + + Parallel + 並列 + + + Actual Rebinds + 実際ã®å†ãƒã‚¤ãƒ³ãƒ‰æ•° + + + Est Rebinds + å†ãƒã‚¤ãƒ³ãƒ‰ã®æŽ¨å®šæ•° + + + Actual Rewinds + 実際ã®å·»ã戻ã—æ•° + + + Est Rewinds + å·»ã戻ã—ã®æŽ¨å®šå›žæ•° + + + Partitioned + パーティション分割 + + + Top Operations + トップæ“作 + + + + + + + Query Plan + クエリ プラン + + + + + + + Could not find component for type {0} + åž‹ {0} ã®ã‚³ãƒ³ãƒãƒ¼ãƒãƒ³ãƒˆãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“ã§ã—㟠+ + + + + + + A NotebookProvider with valid providerId must be passed to this method + 有効㪠providerId ã‚’æŒã¤ NotebookProvider ã‚’ã“ã®ãƒ¡ã‚½ãƒƒãƒ‰ã«æ¸¡ã™å¿…è¦ãŒã‚ã‚Šã¾ã™ + + + + + + + A NotebookProvider with valid providerId must be passed to this method + 有効㪠providerId ã‚’æŒã¤ NotebookProvider ã‚’ã“ã®ãƒ¡ã‚½ãƒƒãƒ‰ã«æ¸¡ã™å¿…è¦ãŒã‚ã‚Šã¾ã™ + + + no notebook provider found + ノートブック プロãƒã‚¤ãƒ€ãƒ¼ãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“ + + + No Manager found + マãƒãƒ¼ã‚¸ãƒ£ãƒ¼ãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“ + + + Notebook Manager for notebook {0} does not have a server manager. Cannot perform operations on it + ノートブック {0} ã® Notebook Manager ã«ã‚µãƒ¼ãƒãƒ¼ マãƒãƒ¼ã‚¸ãƒ£ãƒ¼ãŒã‚ã‚Šã¾ã›ã‚“。ãã‚Œã«å¯¾ã—ã¦æ“作を実行ã§ãã¾ã›ã‚“ + + + Notebook Manager for notebook {0} does not have a content manager. Cannot perform operations on it + ノートブック {0} ã® Notebook Manager ã«ã‚³ãƒ³ãƒ†ãƒ³ãƒ„ マãƒãƒ¼ã‚¸ãƒ£ãƒ¼ãŒã‚ã‚Šã¾ã›ã‚“。ãã‚Œã«å¯¾ã—ã¦æ“作を実行ã§ãã¾ã›ã‚“ + + + Notebook Manager for notebook {0} does not have a session manager. Cannot perform operations on it + ノートブック {0} ã® Notebook Manager ã«ã‚»ãƒƒã‚·ãƒ§ãƒ³ マãƒãƒ¼ã‚¸ãƒ£ãƒ¼ãŒã‚ã‚Šã¾ã›ã‚“。ãã‚Œã«å¯¾ã—ã¦æ“作を実行ã§ãã¾ã›ã‚“ + + + + + + + SQL kernel error + SQL カーãƒãƒ« エラー + + + A connection must be chosen to run notebook cells + ノートブックã®ã‚»ãƒ«ã‚’実行ã™ã‚‹ã«ã¯ã€æŽ¥ç¶šã‚’é¸æŠžã™ã‚‹å¿…è¦ãŒã‚ã‚Šã¾ã™ + + + Displaying Top {0} rows. + ä¸Šä½ {0} 行を表示ã—ã¦ã„ã¾ã™ã€‚ + + + + + + + Show Recommendations + 推奨事項を表示 + + + Install Extensions + 拡張機能ã®ã‚¤ãƒ³ã‚¹ãƒˆãƒ¼ãƒ« + + + + + + + Name + åå‰ + + + Last Run + 最後ã®å®Ÿè¡Œ + + + Next Run + 次回ã®å®Ÿè¡Œ + + + Enabled + 有効 + + + Status + 状態 + + + Category + カテゴリ + + + Runnable + 実行å¯èƒ½ + + + Schedule + スケジュール + + + Last Run Outcome + 最終実行ã®çµæžœ + + + Previous Runs + 以å‰ã®å®Ÿè¡Œ + + + No Steps available for this job. + ã“ã®ã‚¸ãƒ§ãƒ–ã«åˆ©ç”¨ã§ãるステップã¯ã‚ã‚Šã¾ã›ã‚“。 + + + Error: + エラー: + + + + + + + Find + 検索 + + + Find + 検索 + + + Previous match + å‰ã®æ¤œç´¢çµæžœ + + + Next match + 次ã®ä¸€è‡´é …ç›® + + + Close + é–‰ã˜ã‚‹ + + + Your search returned a large number of results, only the first 999 matches will be highlighted. + 検索ã§å¤šæ•°ã®çµæžœãŒè¿”ã•ã‚Œã¾ã—ãŸã€‚最åˆã® 999 件ã®ã¿å¼·èª¿è¡¨ç¤ºã•ã‚Œã¾ã™ã€‚ + + + {0} of {1} + {0} / {1} 件 + + + No Results + çµæžœã¯ã‚ã‚Šã¾ã›ã‚“。 + + + + + + + Run Query + クエリã®å®Ÿè¡Œ + + + + + + + Done + 完了 + + + Cancel + キャンセル + + + Generate script + スクリプトã®ç”Ÿæˆ + + + Next + 次㸠+ + + Previous + å‰ã¸ + + + + + + + A server group with the same name already exists. + åŒã˜åå‰ã®ã‚µãƒ¼ãƒãƒ¼ グループãŒæ—¢ã«å­˜åœ¨ã—ã¾ã™ã€‚ + + + + + + + {0} is an unknown container. + {0} ã¯ä¸æ˜Žãªã‚³ãƒ³ãƒ†ãƒŠãƒ¼ã§ã™ã€‚ + + + + + + + Loading Error... + 読ã¿è¾¼ã¿ã‚¨ãƒ©ãƒ¼... + + + + + + + Failed + 失敗 + + + Succeeded + æˆåŠŸ + + + Retry + å†è©¦è¡Œ + + + Cancelled + å–り消ã•ã‚Œã¾ã—㟠+ + + In Progress + 進行中 + + + Status Unknown + 状態ãŒä¸æ˜Žã§ã™ + + + Executing + 実行中 + + + Waiting for Thread + スレッドを待機ã—ã¦ã„ã¾ã™ + + + Between Retries + å†è©¦è¡Œé–“éš” + + + Idle + アイドル状態 + + + Suspended + 中断 + + + [Obsolete] + [å¤ã„] + + + Yes + ã¯ã„ + + + No + ã„ã„㈠+ + + Not Scheduled + スケジュールãŒè¨­å®šã•ã‚Œã¦ã„ã¾ã›ã‚“ + + + Never Run + 実行ã—ãªã„ + + + + + + + Name + åå‰ + + + Target Database + ターゲット データベース + + + Last Run + 最後ã®å®Ÿè¡Œ + + + Next Run + 次回ã®å®Ÿè¡Œ + + + Status + 状態 + + + Last Run Outcome + 最終実行ã®çµæžœ + + + Previous Runs + 以å‰ã®å®Ÿè¡Œ + + + No Steps available for this job. + ã“ã®ã‚¸ãƒ§ãƒ–ã«åˆ©ç”¨ã§ãるステップã¯ã‚ã‚Šã¾ã›ã‚“。 + + + Error: + エラー: + + + Notebook Error: + ノートブック エラー: + + + + + + + Home + ホーム + + + No connection information could be found for this dashboard + ã“ã®ãƒ€ãƒƒã‚·ãƒ¥ ボードã«ã¯æŽ¥ç¶šæƒ…å ±ãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“ã§ã—㟠+ + + + + + + Data + データ + + + Connection + 接続 + + + Query + クエリ + + + Notebook + ノートブック + + + SQL + SQL + + + Microsoft SQL Server + Microsoft SQL Server + + + Dashboard + ダッシュボード + + + Profiler + プロファイラー + + + + + + + Close + é–‰ã˜ã‚‹ + + + + + + + Success + æˆåŠŸ + + + Error + エラー + + + Refresh + 最新ã®æƒ…å ±ã«æ›´æ–° + + + New Job + æ–°è¦ã‚¸ãƒ§ãƒ– + + + Run + 実行 + + + : The job was successfully started. + : ジョブãŒæ­£å¸¸ã«é–‹å§‹ã•ã‚Œã¾ã—ãŸã€‚ + + + Stop + åœæ­¢ + + + : The job was successfully stopped. + : ジョブãŒæ­£å¸¸ã«åœæ­¢ã•ã‚Œã¾ã—ãŸã€‚ + + + Edit Job + ジョブã®ç·¨é›† + + + Open + é–‹ã + + + Delete Job + ジョブã®å‰Šé™¤ + + + Are you sure you'd like to delete the job '{0}'? + ジョブ '{0}' を削除ã—ã¦ã‚ˆã‚ã—ã„ã§ã™ã‹ã€‚ + + + Could not delete job '{0}'. +Error: {1} + ジョブ '{0}' を削除ã§ãã¾ã›ã‚“ã§ã—ãŸã€‚ +エラー: {1} + + + The job was successfully deleted + ジョブã¯æ­£å¸¸ã«å‰Šé™¤ã•ã‚Œã¾ã—㟠+ + + New Step + æ–°ã—ã„ステップ + + + Delete Step + ステップã®å‰Šé™¤ + + + Are you sure you'd like to delete the step '{0}'? + ステップ '{0}' を削除ã—ã¾ã™ã‹? + + + Could not delete step '{0}'. +Error: {1} + ステップ '{0}' を削除ã§ãã¾ã›ã‚“ã§ã—ãŸã€‚ +エラー: {1} + + + The job step was successfully deleted + ジョブ ステップãŒæ­£å¸¸ã«å‰Šé™¤ã•ã‚Œã¾ã—㟠+ + + New Alert + æ–°ã—ã„警告 + + + Edit Alert + 警告ã®ç·¨é›† + + + Delete Alert + 警告を削除 + + + Cancel + キャンセル + + + Are you sure you'd like to delete the alert '{0}'? + 警告 '{0}' を削除ã—ã¦ã‚‚よã‚ã—ã„ã§ã™ã‹ï¼Ÿ + + + Could not delete alert '{0}'. +Error: {1} + 警告 '{0}' を削除ã§ãã¾ã›ã‚“ã§ã—ãŸã€‚ +エラー: {1} + + + The alert was successfully deleted + アラートã¯æ­£å¸¸ã«å‰Šé™¤ã•ã‚Œã¾ã—㟠+ + + New Operator + æ–°ã—ã„æ¼”ç®—å­ + + + Edit Operator + 演算å­ã®ç·¨é›† + + + Delete Operator + 演算å­ã®å‰Šé™¤ + + + Are you sure you'd like to delete the operator '{0}'? + æ¼”ç®—å­ '{0}' を削除ã—ã¦ã‚ˆã‚ã—ã„ã§ã™ã‹? + + + Could not delete operator '{0}'. +Error: {1} + æ¼”ç®—å­ '{0}' を削除ã§ãã¾ã›ã‚“ã§ã—ãŸã€‚ +エラー: {1} + + + The operator was deleted successfully + 演算å­ãŒæ­£å¸¸ã«å‰Šé™¤ã•ã‚Œã¾ã—㟠+ + + New Proxy + æ–°ã—ã„プロキシ + + + Edit Proxy + プロキシã®ç·¨é›† + + + Delete Proxy + プロキシã®å‰Šé™¤ + + + Are you sure you'd like to delete the proxy '{0}'? + プロキシ '{0}' を削除ã—ã¦ã‚ˆã‚ã—ã„ã§ã™ã‹? + + + Could not delete proxy '{0}'. +Error: {1} + プロキシ '{0}' を削除ã§ãã¾ã›ã‚“ã§ã—ãŸã€‚ +エラー: {1} + + + The proxy was deleted successfully + プロキシãŒæ­£å¸¸ã«å‰Šé™¤ã•ã‚Œã¾ã—㟠+ + + New Notebook Job + æ–°ã—ã„ノートブック ジョブ + + + Edit + 編集 + + + Open Template Notebook + テンプレート ノートブックを開ã + + + Delete + 削除 + + + Are you sure you'd like to delete the notebook '{0}'? + ノートブック '{0}' を削除ã—ã¾ã™ã‹? + + + Could not delete notebook '{0}'. +Error: {1} + ノートブック '{0}' を削除ã§ãã¾ã›ã‚“ã§ã—ãŸã€‚ +エラー: {1} + + + The notebook was successfully deleted + ノートブックãŒæ­£å¸¸ã«å‰Šé™¤ã•ã‚Œã¾ã—㟠+ + + Pin + ピン留゠+ + + Delete + 削除 + + + Unpin + ピン留ã‚を外㙠+ + + Rename + åå‰ã®å¤‰æ›´ + + + Open Latest Run + 最新ã®å®Ÿè¡Œã‚’é–‹ã + + + + + + + Please select a connection to run cells for this kernel + ã“ã®ã‚«ãƒ¼ãƒãƒ«ã®ã‚»ãƒ«ã‚’実行ã™ã‚‹ãŸã‚ã€æŽ¥ç¶šã‚’é¸æŠžã—ã¦ãã ã•ã„ + + + Failed to delete cell. + セルã®å‰Šé™¤ã«å¤±æ•—ã—ã¾ã—ãŸã€‚ + + + Failed to change kernel. Kernel {0} will be used. Error was: {1} + カーãƒãƒ«ã®å¤‰æ›´ã«å¤±æ•—ã—ã¾ã—ãŸã€‚カーãƒãƒ« {0} ãŒä½¿ç”¨ã•ã‚Œã¾ã™ã€‚エラー: {1} + + + Failed to change kernel due to error: {0} + 次ã®ã‚¨ãƒ©ãƒ¼ã«ã‚ˆã‚Šã€ã‚«ãƒ¼ãƒãƒ«ã®å¤‰æ›´ã«å¤±æ•—ã—ã¾ã—ãŸ: {0} + + + Changing context failed: {0} + コンテキストã®å¤‰æ›´ã«å¤±æ•—ã—ã¾ã—ãŸ: {0} + + + Could not start session: {0} + セッションを開始ã§ãã¾ã›ã‚“ã§ã—ãŸ: {0} + + + A client session error occurred when closing the notebook: {0} + ノートブックを閉ã˜ã‚‹ã¨ãã«ã‚¯ãƒ©ã‚¤ã‚¢ãƒ³ãƒˆ セッション エラーãŒç™ºç”Ÿã—ã¾ã—ãŸ: {0} + + + Can't find notebook manager for provider {0} + プロãƒã‚¤ãƒ€ãƒ¼ {0} ã®ãƒŽãƒ¼ãƒˆãƒ–ック マãƒãƒ¼ã‚¸ãƒ£ãƒ¼ãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“ + + + + + + + An error occurred while starting the notebook session + ノートブック セッションã®é–‹å§‹ä¸­ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚ + + + Server did not start for unknown reason + ä¸æ˜Žãªç†ç”±ã«ã‚ˆã‚Šã‚µãƒ¼ãƒãƒ¼ãŒèµ·å‹•ã—ã¾ã›ã‚“ã§ã—㟠+ + + Kernel {0} was not found. The default kernel will be used instead. + カーãƒãƒ« {0} ãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“ã§ã—ãŸã€‚既定ã®ã‚«ãƒ¼ãƒãƒ«ãŒä»£ã‚ã‚Šã«ä½¿ç”¨ã•ã‚Œã¾ã™ã€‚ + + + + + + + Unknown component type. Must use ModelBuilder to create objects + ä¸æ˜Žãªã‚³ãƒ³ãƒãƒ¼ãƒãƒ³ãƒˆã®ç¨®é¡žã§ã™ã€‚オブジェクトを作æˆã™ã‚‹ã«ã¯ã€ModelBuilder を使用ã™ã‚‹å¿…è¦ãŒã‚ã‚Šã¾ã™ + + + The index {0} is invalid. + インデックス {0} ãŒç„¡åŠ¹ã§ã™ã€‚ + + + Unkown component configuration, must use ModelBuilder to create a configuration object + ä¸æ˜Žãªã‚³ãƒ³ãƒãƒ¼ãƒãƒ³ãƒˆæ§‹æˆã§ã™ã€‚ModelBuilder を使用ã—ã¦æ§‹æˆã‚ªãƒ–ジェクトを作æˆã™ã‚‹å¿…è¦ãŒã‚ã‚Šã¾ã™ + + + + + + + Horizontal Bar + æ°´å¹³ãƒãƒ¼ + + + Bar + ãƒãƒ¼ + + + Line + ç·š + + + Pie + 円 + + + Scatter + 散布 + + + Time Series + 時系列 + + + Image + イメージ + + + Count + カウント + + + Table + テーブル + + + Doughnut + ドーナツ + + + + + + + OK + OK + + + Clear + クリア + + + Cancel + キャンセル + + + + + + + Cell execution cancelled + セルã®å®Ÿè¡ŒãŒå–り消ã•ã‚Œã¾ã—㟠+ + + Query execution was canceled + クエリã®å®Ÿè¡ŒãŒã‚­ãƒ£ãƒ³ã‚»ãƒ«ã•ã‚Œã¾ã—㟠+ + + The session for this notebook is not yet ready + ã“ã®ãƒŽãƒ¼ãƒˆãƒ–ックã®ã‚»ãƒƒã‚·ãƒ§ãƒ³ã¯æº–å‚™ãŒã¾ã æ•´ã£ã¦ã„ã¾ã›ã‚“ + + + The session for this notebook will start momentarily + ã“ã®ãƒŽãƒ¼ãƒˆãƒ–ックã®ã‚»ãƒƒã‚·ãƒ§ãƒ³ãŒé–“ã‚‚ãªã開始ã•ã‚Œã¾ã™ + + + No kernel is available for this notebook + ã“ã®ãƒŽãƒ¼ãƒˆãƒ–ックã§ä½¿ç”¨ã§ãるカーãƒãƒ«ã¯ã‚ã‚Šã¾ã›ã‚“ + + + + + + + Select Connection + 接続ã®é¸æŠž + + + localhost + localhost + + + + + + + Data Direction + データã®æ–¹å‘ + + + Vertical + åž‚ç›´ + + + Horizontal + æ°´å¹³æ–¹å‘ + + + Use column names as labels + 列åをラベルã¨ã—ã¦ä½¿ç”¨ + + + Use first column as row label + 行ラベルã¨ã—ã¦å…ˆé ­è¡Œã‚’使用 + + + Legend Position + 凡例ã®ä½ç½® + + + Y Axis Label + Y 軸ã®ãƒ©ãƒ™ãƒ« + + + Y Axis Minimum Value + Y 軸ã®æœ€å°å€¤ + + + Y Axis Maximum Value + Y 軸ã®æœ€å¤§å€¤ + + + X Axis Label + X 軸ラベル + + + X Axis Minimum Value + X 軸ã®æœ€å°å€¤ + + + X Axis Maximum Value + X 軸ã®æœ€å¤§å€¤ + + + X Axis Minimum Date + X 軸ã®æœ€å°æ—¥ä»˜ + + + X Axis Maximum Date + X 軸ã®æœ€å¤§æ—¥ä»˜ + + + Data Type + データ型 + + + Number + 数値 + + + Point + ãƒã‚¤ãƒ³ãƒˆ + + + Chart Type + グラフã®ç¨®é¡ž + + + Encoding + エンコード + + + Image Format + ç”»åƒã®å½¢å¼ + + + + + + + Create Insight + æ´žå¯Ÿã‚’ä½œæˆ + + + Cannot create insight as the active editor is not a SQL Editor + アクティブãªã‚¨ãƒ‡ã‚£ã‚¿ãƒ¼ãŒ SQL エディターã§ã¯ãªã„ãŸã‚ã€ã‚¤ãƒ³ã‚µã‚¤ãƒˆã‚’作æˆã§ãã¾ã›ã‚“ + + + My-Widget + マイ ウィジェット + + + Copy as image + イメージã¨ã—ã¦ã‚³ãƒ”ー + + + Could not find chart to save + ä¿å­˜ã™ã‚‹ã‚°ãƒ©ãƒ•ãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“ã§ã—㟠+ + + Save as image + ç”»åƒã¨ã—ã¦ä¿å­˜ + + + PNG + PNG + + + Saved Chart to path: {0} + パス : {0} ã«ä¿å­˜ã•ã‚ŒãŸã‚°ãƒ©ãƒ• + + + + + + + Changing editor types on unsaved files is unsupported + ä¿å­˜ã•ã‚Œã¦ã„ãªã„ファイルã§ã®ç·¨é›†è€…ã®ç¨®é¡žã®å¤‰æ›´ã¯ã‚µãƒãƒ¼ãƒˆã•ã‚Œã¦ã„ã¾ã›ã‚“ + + + + + + + Table does not contain a valid image + テーブルã«æœ‰åŠ¹ãªã‚¤ãƒ¡ãƒ¼ã‚¸ãŒå«ã¾ã‚Œã¦ã„ã¾ã›ã‚“ + + + + + + + Series {0} + シリーズ {0} diff --git a/resources/xlf/ko/azurecore.ko.xlf b/resources/xlf/ko/azurecore.ko.xlf index 6739f4794e5d..d1a2635c0d44 100644 --- a/resources/xlf/ko/azurecore.ko.xlf +++ b/resources/xlf/ko/azurecore.ko.xlf @@ -200,4 +200,20 @@ + + + + SQL Managed Instances + SQL Managed Instances + + + + + + + Azure Database for PostgreSQL Servers + Azure Database for PostgreSQL 서버 + + + \ No newline at end of file diff --git a/resources/xlf/ko/big-data-cluster.ko.xlf b/resources/xlf/ko/big-data-cluster.ko.xlf index 22d7a7a320a8..5e60163ff578 100644 --- a/resources/xlf/ko/big-data-cluster.ko.xlf +++ b/resources/xlf/ko/big-data-cluster.ko.xlf @@ -38,6 +38,14 @@ Delete Mount 탑재 ì‚­ì œ + + Big Data Cluster + ë¹… ë°ì´í„° í´ëŸ¬ìŠ¤í„° + + + Ignore SSL verification errors against SQL Server Big Data Cluster endpoints such as HDFS, Spark, and Controller if true + Trueì¸ ê²½ìš° HDFS, Spark ë° Controller와 ê°™ì€ SQL Server ë¹… ë°ì´í„° í´ëŸ¬ìŠ¤í„° 엔드í¬ì¸íŠ¸ì— 대한 SSL í™•ì¸ ì˜¤ë¥˜ 무시 + @@ -58,26 +66,86 @@ Deleting 삭제하는 중 - - Waiting For Deletion - ì‚­ì œ 대기 중 - Deleted ì‚­ì œ + + Applying Upgrade + 업그레ì´ë“œë¥¼ ì ìš©í•˜ëŠ” 중 + Upgrading 업그레ì´ë“œí•˜ëŠ” 중 - - Waiting For Upgrade - 업그레ì´ë“œ 대기 중 + + Applying Managed Upgrade + 관리ë˜ëŠ” 업그레ì´ë“œë¥¼ ì ìš©í•˜ëŠ” 중 + + + Managed Upgrading + 관리ë˜ëŠ” 업그레ì´ë“œ + + + Rollback + 롤백 + + + Rollback In Progress + 롤백 진행 중 + + + Rollback Complete + 롤백 완료 Error 오류 + + Creating Secrets + ë¹„ë°€ì„ ë§Œë“œëŠ” 중 + + + Waiting For Secrets + ë¹„ë°€ì„ ê¸°ë‹¤ë¦¬ëŠ” 중 + + + Creating Groups + ê·¸ë£¹ì„ ë§Œë“œëŠ” 중 + + + Waiting For Groups + 그룹 대기 중 + + + Creating Resources + 리소스를 만드는 중 + + + Waiting For Resources + 리소스 대기 중 + + + Creating Kerberos Delegation Setup + Kerberos 위임 ì„¤ì •ì„ ë§Œë“œëŠ” 중 + + + Waiting For Kerberos Delegation Setup + Kerberos 위임 설정 대기 중 + + + Waiting For Deletion + ì‚­ì œ 대기 중 + + + Waiting For Upgrade + 업그레ì´ë“œ 대기 중 + + + Upgrade Paused + 업그레ì´ë“œë¥¼ ì¼ì‹œ 중지함 + Running 실행 @@ -162,6 +230,78 @@ Unhealthy ë¹„ì •ìƒ + + Unexpected error retrieving BDC Endpoints: {0} + BDC 엔드í¬ì¸íŠ¸ë¥¼ 검색하는 ë™ì•ˆ 예기치 ì•Šì€ ì˜¤ë¥˜ ë°œìƒ: {0} + + + + + + + Basic + 기본 + + + Windows Authentication + Windows ì¸ì¦ + + + Login to controller failed + ì»¨íŠ¸ë¡¤ëŸ¬ì— ë¡œê·¸ì¸í•˜ì§€ 못함 + + + Login to controller failed: {0} + ì»¨íŠ¸ë¡¤ëŸ¬ì— ë¡œê·¸ì¸í•˜ì§€ 못함: {0} + + + Username is required + ì‚¬ìš©ìž ì´ë¦„ì€ í•„ìˆ˜ìž„ + + + Password is required + 암호는 필수임 + + + url + URL + + + username + ì‚¬ìš©ìž ì´ë¦„ + + + password + 암호 + + + Cluster Management URL + í´ëŸ¬ìŠ¤í„° 관리 URL + + + Authentication type + ì¸ì¦ 유형 + + + Username + ì‚¬ìš©ìž ì´ë¦„ + + + Password + 암호 + + + Cluster Connection + í´ëŸ¬ìŠ¤í„° ì—°ê²° + + + OK + í™•ì¸ + + + Cancel + 취소 + @@ -200,6 +340,22 @@ + + + + Connect to Controller (preview) + ì»¨íŠ¸ë¡¤ëŸ¬ì— ì—°ê²°(미리 보기) + + + + + + + View Details + 세부 ì •ë³´ 보기 + + + @@ -207,8 +363,8 @@ 컨트롤러 엔드í¬ì¸íŠ¸ 정보를 ì°¾ì„ ìˆ˜ ì—†ìŒ - Big Data Cluster Dashboard - - ë¹… ë°ì´í„° í´ëŸ¬ìŠ¤í„° 대시보드 - + Big Data Cluster Dashboard (preview) - + ë¹… ë°ì´í„° í´ëŸ¬ìŠ¤í„° 대시보드(미리 보기) - Yes @@ -263,8 +419,8 @@ 암호는 필수임 - Add New Controller - 새 컨트롤러 추가 + Add New Controller (preview) + 새 컨트롤러 추가(미리 보기) url @@ -283,8 +439,8 @@ 암호 기억 - URL - URL + Cluster Management URL + í´ëŸ¬ìŠ¤í„° 관리 URL Authentication type @@ -319,7 +475,7 @@ 문제 í•´ê²° - Big data cluster overview + Big Data Cluster overview ë¹… ë°ì´í„° í´ëŸ¬ìŠ¤í„° 개요 @@ -362,18 +518,18 @@ Metrics and Logs 메트릭 ë° ë¡œê·¸ - - Metrics - 메트릭 + + Node Metrics + 노드 메트릭 + + + SQL Metrics + SQL 메트릭 Logs 로그 - - View Details - 세부 ì •ë³´ 보기 - @@ -422,31 +578,35 @@ Endpoint ëì  - - View Details - 세부 ì •ë³´ 보기 + + Unexpected error retrieving BDC Endpoints: {0} + BDC 엔드í¬ì¸íŠ¸ë¥¼ 검색하는 ë™ì•ˆ 예기치 ì•Šì€ ì˜¤ë¥˜ê°€ ë°œìƒí–ˆìŠµë‹ˆë‹¤. {0} + + + The dashboard requires a connection. Please click retry to enter your credentials. + ëŒ€ì‹œë³´ë“œì— ì—°ê²°ì´ í•„ìš”í•©ë‹ˆë‹¤. ìžê²© ì¦ëª…ì„ ìž…ë ¥í•˜ë ¤ë©´ 다시 ì‹œë„를 í´ë¦­í•˜ì„¸ìš”. + + + Unexpected error occurred: {0} + 예기치 ì•Šì€ ì˜¤ë¥˜ê°€ ë°œìƒí–ˆìŠµë‹ˆë‹¤. {0} Copy 복사 + + Endpoint '{0}' copied to clipboard + 엔드í¬ì¸íŠ¸ '{0}'ì´(ê°€) í´ë¦½ë³´ë“œì— ë³µì‚¬ë¨ + - - Basic - 기본 - - - Windows Authentication - Windows ì¸ì¦ - Mount Configuration 탑재 구성 - + HDFS Path HDFS 경로 @@ -454,22 +614,6 @@ Bad formatting of credentials at {0} {0}ì— ìžˆëŠ” ìžê²© ì¦ëª… 형ì‹ì´ ìž˜ëª»ë¨ - - Login to controller failed - ì»¨íŠ¸ë¡¤ëŸ¬ì— ë¡œê·¸ì¸í•˜ì§€ 못함 - - - Login to controller failed: {0} - ì»¨íŠ¸ë¡¤ëŸ¬ì— ë¡œê·¸ì¸í•˜ì§€ 못함: {0} - - - Username is required - ì‚¬ìš©ìž ì´ë¦„ì€ í•„ìˆ˜ìž„ - - - Password is required - 암호는 필수임 - Mounting HDFS folder on path {0} 경로 {0}ì— HDFS í´ë”를 탑재하는 중 @@ -494,58 +638,30 @@ Unknown error occurred during the mount process 탑재 프로세스 ì¤‘ì— ì•Œ 수 없는 오류가 ë°œìƒí–ˆìŠµë‹ˆë‹¤. - - url - URL - - - username - ì‚¬ìš©ìž ì´ë¦„ - - - password - 암호 - - - URL - URL - - - Authentication type - ì¸ì¦ 유형 - - - Username - ì‚¬ìš©ìž ì´ë¦„ - - - Password - 암호 - - - Cluster Connection - í´ëŸ¬ìŠ¤í„° ì—°ê²° - - - OK - í™•ì¸ - - - Cancel - 취소 - - Mount HDFS Folder - HDFS í´ë” 탑재 + Mount HDFS Folder (preview) + HDFS í´ë” 마운트(미리 보기) + + + Path to a new (non-existing) directory which you want to associate with the mount + 마운트와 연결하려는 새(존재하지 않는) 디렉터리 경로 - + Remote URI ì›ê²© URI - + + The URI to the remote data source. Example for ADLS: abfs://fs@saccount.dfs.core.windows.net/ + ì›ê²© ë°ì´í„° ì›ë³¸ì— 대한 URI입니다. ADLSì˜ ì˜ˆ: abfs://fs@saccount.dfs.core.windows.net/ + + Credentials ìžê²© ì¦ëª… + + Mount credentials for authentication to remote data source for reads + ì½ê¸° 위해 ì›ê²© ë°ì´í„° ì›ë³¸ì— ì¸ì¦ì„ 위한 ìžê²© ì¦ëª… 마운트 + Refresh Mount 탑재 새로 고침 diff --git a/resources/xlf/ko/sql.ko.xlf b/resources/xlf/ko/sql.ko.xlf index 10c41bb8184f..8f05eea258a6 100644 --- a/resources/xlf/ko/sql.ko.xlf +++ b/resources/xlf/ko/sql.ko.xlf @@ -1,14 +1,5573 @@  - + - - SQL Language Basics - SQL 언어 기본 사항 + + Copying images is not supported + ì´ë¯¸ì§€ 복사가 지ì›ë˜ì§€ 않습니다. - - Provides syntax highlighting and bracket matching in SQL files. - SQL 파ì¼ì—ì„œ 구문 ê°•ì¡° 표시 ë° ê´„í˜¸ ì¼ì¹˜ë¥¼ 제공합니다. + + + + + + Get Started + 시작하기 + + + Show Getting Started + 시작 보기 + + + Getting &&Started + 시작(&&S) + && denotes a mnemonic + + + + + + + QueryHistory + QueryHistory + + + Whether Query History capture is enabled. If false queries executed will not be captured. + 쿼리 ê¸°ë¡ ìº¡ì²˜ê°€ 사용하ë„ë¡ ì„¤ì •ë˜ì–´ 있는지 여부입니다. falseì´ë©´ ì‹¤í–‰ëœ ì¿¼ë¦¬ê°€ 캡처ë˜ì§€ 않습니다. + + + View + 보기 + + + Query History + 쿼리 ê¸°ë¡ + + + &&Query History + &&쿼리 ê¸°ë¡ + && denotes a mnemonic + + + + + + + Connecting: {0} + {0}ì— ì—°ê²°í•˜ëŠ” 중 + + + Running command: {0} + 명령 {0} 실행 중 + + + Opening new query: {0} + 새 쿼리 열기: {0} + + + Cannot connect as no server information was provided + 서버 ì •ë³´ê°€ 제공ë˜ì§€ 않았으므로 ì—°ê²°í•  수 없습니다. + + + Could not open URL due to error {0} + {0} 오류로 ì¸í•´ URLì„ ì—´ 수 없습니다. + + + This will connect to server {0} + ì´ë ‡ê²Œ 하면 서버 {0}ì— ì—°ê²°ë©ë‹ˆë‹¤. + + + Are you sure you want to connect? + 연결하시겠습니까? + + + &&Open + 열기(&&O) + + + Connecting query file + 쿼리 íŒŒì¼ ì—°ê²° 중 + + + + + + + Error + 오류 + + + Warning + 경고 + + + Info + ì •ë³´ + + + + + + + Saving results into different format disabled for this data provider. + ì´ ë°ì´í„° 공급ìžì— 대해 사용하지 ì•Šë„ë¡ ì„¤ì •í•œ 다른 형ì‹ìœ¼ë¡œ 결과를 저장하고 있습니다. + + + Cannot serialize data as no provider has been registered + 공급ìžê°€ 등ë¡ë˜ì§€ ì•Šì€ ê²½ìš° ë°ì´í„°ë¥¼ serializeí•  수 없습니다. + + + Serialization failed with an unknown error + ì•Œ 수 없는 오류로 ì¸í•´ serialize 실패 + + + + + + + Connection is required in order to interact with adminservice + adminservice와 ìƒí˜¸ 작용하려면 ì—°ê²°ì´ í•„ìš”í•©ë‹ˆë‹¤. + + + No Handler Registered + 등ë¡ëœ 처리기 ì—†ìŒ + + + + + + + Select a file + íŒŒì¼ ì„ íƒ + + + + + + + Disconnect + ì—°ê²° ëŠê¸° + + + Refresh + 새로 고침 + + + + + + + Results Grid and Messages + ê²°ê³¼ í‘œ ë° ë©”ì‹œì§€ + + + Controls the font family. + 글꼴 패밀리를 제어합니다. + + + Controls the font weight. + 글꼴 ë‘께를 조정합니다. + + + Controls the font size in pixels. + 픽셀 단위로 글꼴 í¬ê¸°ë¥¼ 제어합니다. + + + Controls the letter spacing in pixels. + 픽셀 단위로 ë¬¸ìž ê°„ê²©ì„ ì œì–´ 합니다. + + + Controls the row height in pixels + 픽셀단위로 í–‰ ë†’ì´ ì œì–´ + + + Controls the cell padding in pixels + ì…€ 안쪽 ì—¬ë°±ì„ í”½ì…€ 단위로 제어합니다. + + + Auto size the columns width on inital results. Could have performance problems with large number of columns or large cells + 초기 ê²°ê³¼ì˜ ì—´ 너비를 ìžë™ìœ¼ë¡œ 조정합니다. ì—´ 개수가 많거나 ì…€ì´ í¬ë©´ 성능 문제가 ë°œìƒí•  수 있습니다. + + + The maximum width in pixels for auto-sized columns + ìžë™ìœ¼ë¡œ í¬ê¸°ê°€ ì¡°ì •ë˜ëŠ” ì—´ì˜ ìµœëŒ€ 너비(픽셀) + + + + + + + {0} in progress tasks + 진행 ì¤‘ì¸ ìž‘ì—… {0} ê°œ + + + View + 보기 + + + Tasks + ìž‘ì—… + + + &&Tasks + ìž‘ì—…(&&T) + && denotes a mnemonic + + + + + + + Connections + ì—°ê²° + + + View + 보기 + + + Database Connections + ë°ì´í„°ë² ì´ìŠ¤ ì—°ê²° + + + data source connections + ë°ì´í„° ì›ë³¸ ì—°ê²° + + + data source groups + ë°ì´í„° 소스 그룹 + + + Startup Configuration + 시작 구성 + + + True for the Servers view to be shown on launch of Azure Data Studio default; false if the last opened view should be shown + Azure Data Studio 시작 ì‹œ 서버 보기를 표시하려면 True(기본값), 마지막으로 ì—° 보기를 표시하려면 False + + + + + + + The maximum number of recently used connections to store in the connection list. + ìµœê·¼ì— ì‚¬ìš©í•œ ì—°ê²°ì„ ì—°ê²° 목ë¡ì— 저장할 최대 수입니다. + + + Default SQL Engine to use. This drives default language provider in .sql files and the default to use when creating a new connection. Valid option is currently MSSQL + 사용할 기본 SQL 엔진. ì´ëŠ” .sql 파ì¼ì˜ 기본 언어 공급ìžë¥¼ 구ë™í•˜ê³  새 ì—°ê²°ì„ ë§Œë“¤ ë•Œ 기본값으로 사용합니다. 유효한 ì˜µì…˜ì€ í˜„ìž¬ MSSQL입니다. + + + Attempt to parse the contents of the clipboard when the connection dialog is opened or a paste is performed. + ì—°ê²° 대화 ìƒìžê°€ 열리거나 붙여넣기가 수행ë˜ë©´ í´ë¦½ë³´ë“œì˜ ë‚´ìš© 구문 분ì„ì„ ì‹œë„합니다. + + + + + + + Server Group color palette used in the Object Explorer viewlet. + 개체 íƒìƒ‰ê¸° 보기ì—ì„œ 사용ë˜ëŠ” 서버 그룹 ìƒ‰ìƒ í‘œ. + + + Auto-expand Server Groups in the Object Explorer viewlet. + 개체 íƒìƒ‰ê¸° ë·°ë ›ì—ì„œ 서버 ê·¸ë£¹ì„ ìžë™ 확장합니다. + + + + + + + Preview Features + 미리 보기 기능 + + + Enable unreleased preview features + 릴리스ë˜ì§€ ì•Šì€ ë¯¸ë¦¬ 보기 기능 사용 + + + Show connect dialog on startup + 시작 ì‹œ ì—°ê²° 대화 ìƒìž 표시 + + + Obsolete API Notification + 사용ë˜ì§€ 않는 API 알림 + + + Enable/disable obsolete API usage notification + 사용ë˜ì§€ 않는 API 사용 알림 사용/사용 안 함 + + + + + + + Problems + 문제 + + + + + + + Identifier of the account type + 계정유형 ì‹ë³„ìž + + + (Optional) Icon which is used to represent the accpunt in the UI. Either a file path or a themable configuration + (옵션) UIì—ì„œ ê³„ì •ì„ ë‚˜íƒ€ë‚´ëŠ” ë° ì‚¬ìš©ë˜ëŠ” ì•„ì´ì½˜. íŒŒì¼ ê²½ë¡œ ë˜ëŠ” 변경할 수 있는 구성 + + + Icon path when a light theme is used + ë°ì€ 테마를 사용하는 ê²½ìš°ì˜ ì•„ì´ì½˜ 경로 + + + Icon path when a dark theme is used + ì–´ë‘ìš´ 테마를 사용할 ë•Œ ì•„ì´ì½˜ 경로 + + + Contributes icons to account provider. + 계정 공급ìžì—게 ì•„ì´ì½˜ì„ 제공합니다. + + + + + + + Indicates data property of a data set for a chart. + ì°¨íŠ¸ì— ëŒ€í•œ ë°ì´í„° 집합 ë°ì´í„° ì†ì„±ì„ 나타냅니다. + + + + + + + Minimum value of the y axis + y축 최소값 + + + Maximum value of the y axis + yì¶•ì˜ ìµœëŒ“ê°’ + + + Label for the y axis + y 축 ë ˆì´ë¸” + + + Minimum value of the x axis + X ì¶•ì˜ ìµœì†Œê°’ + + + Maximum value of the x axis + x축 최댓값 + + + Label for the x axis + x축 ë ˆì´ë¸” + + + + + + + Displays the results in a simple table + í…Œì´ë¸”ì— ê²°ê³¼ë¥¼ 표시합니다. + + + + + + + Displays an image, for example one returned by an R query using ggplot2 + ì´ë¯¸ì§€ë¥¼ 표시합니다. 예: ggplot2를 사용하여 R 쿼리ì—ì„œ 반환한 ì´ë¯¸ì§€ + + + What format is expected - is this a JPEG, PNG or other format? + ì–´ë–¤ í¬ë§·ì´ 예ìƒë˜ë‚˜ìš”? - JPEG, PNG í˜¹ì€ ë‹¤ë¥¸ í¬ë§·ì¸ê°€ìš”? + + + Is this encoded as hex, base64 or some other format? + 16 진수, base64 ë˜ëŠ” 다른 형ì‹ìœ¼ë¡œ ì¸ì½”딩ë˜ì–´ 있습니까? + + + + + + + For each column in a resultset, displays the value in row 0 as a count followed by the column name. Supports '1 Healthy', '3 Unhealthy' for example, where 'Healthy' is the column name and 1 is the value in row 1 cell 1 + ê²°ê³¼ ì„¸íŠ¸ì˜ ê° ì—´ì— ëŒ€í•´ í–‰ 0ì˜ ê°’ì„ ê°œìˆ˜ë¡œ 표시하고 ê·¸ 다ìŒì— ì—´ ì´ë¦„ì„ í‘œì‹œí•©ë‹ˆë‹¤. 예를 들어, 'Healthy'ê°€ ì—´ ì´ë¦„ì´ê³  1ì´ í–‰ 1, ì…€ 1ì˜ ê°’ì´ë©´ '1 Healthy', '3 Unhealthy'ê°€ 지ì›ë©ë‹ˆë‹¤. + + + + + + + Manage + 관리 + + + Dashboard + 대시보드 + + + + + + + The webview that will be displayed in this tab. + ì´ íƒ­ì— í‘œì‹œí•  webview. + + + + + + + The controlhost that will be displayed in this tab. + ì´ íƒ­ì— í‘œì‹œë  controlhost입니다. + + + + + + + The list of widgets that will be displayed in this tab. + ì´ íƒ­ì— í‘œì‹œë˜ëŠ” 위젯 목ë¡ìž…니다. + + + The list of widgets is expected inside widgets-container for extension. + 위젯 목ë¡ì€ í™•ìž¥ì„ ìœ„í•œ 위젯 컨테ì´ë„ˆ ì•ˆì— ìžˆì„ ê²ƒìž…ë‹ˆë‹¤. + + + + + + + The list of widgets or webviews that will be displayed in this tab. + ì´ íƒ­ì— í‘œì‹œë˜ëŠ” 위젯 ë˜ëŠ” 웹 보기 목ë¡ìž…니다. + + + widgets or webviews are expected inside widgets-container for extension. + 위젯ì´ë‚˜ 웹뷰는 í™•ìž¥ì„ ìœ„í•´ 위젯-컨테ì´ë„ˆ ë‚´ë¶€ì— ìžˆì–´ì•¼ 합니다. + + + + + + + Unique identifier for this container. + ì´ ì»¨í…Œì´ë„ˆì˜ 고유 ì‹ë³„ìžìž…니다. + + + The container that will be displayed in the tab. + 컨테ì´ë„ˆëŠ” íƒ­ì— í‘œì‹œ ë©ë‹ˆë‹¤. + + + Contributes a single or multiple dashboard containers for users to add to their dashboard. + 사용ìžìš© 대시보드 추가를 위한 ë‹¨ì¼ ë˜ëŠ” 다중 대시보드 기고 + + + No id in dashboard container specified for extension. + í™•ìž¥ì— ëŒ€í•´ ì§€ì •ëœ ëŒ€ì‹œë³´ë“œ 컨테ì´ë„ˆì— IDê°€ 없습니다. + + + No container in dashboard container specified for extension. + 대시보드 컨테ì´ë„ˆì— í™•ìž¥ì— ëŒ€í•´ ì§€ì •ëœ ì»¨í…Œì´ë„ˆê°€ 없습니다. + + + Exactly 1 dashboard container must be defined per space. + 공간당 정확하게 1ê°œì˜ ëŒ€ì‹œë³´ë“œ 컨테ì´ë„ˆê°€ ì •ì˜ë˜ì–´ì•¼ 합니다. + + + Unknown container type defines in dashboard container for extension. + 확장용 대시보드 컨테ì´ë„ˆì— ì •ì˜ëœ ì•Œ 수 없는 컨테ì´ë„ˆ 형ì‹ìž…니다. + + + + + + + The model-backed view that will be displayed in this tab. + ì´ íƒ­ì— í‘œì‹œë  ëª¨ë¸ ì§€ì› ë³´ê¸°ìž…ë‹ˆë‹¤. + + + + + + + Unique identifier for this nav section. Will be passed to the extension for any requests. + ì´ íƒìƒ‰ ì˜ì—­ì— 대한 고유 ì‹ë³„ìž ìž…ë‹ˆë‹¤. 모든 확장 ìš”ì²­ì— ëŒ€í•´ 전달ë©ë‹ˆë‹¤. + + + (Optional) Icon which is used to represent this nav section in the UI. Either a file path or a themeable configuration + (ì„ íƒ ì‚¬í•­) UIì—ì„œ íƒìƒ‰ ì„¹ì…˜ì„ ë‚˜íƒ€ë‚´ëŠ” ì•„ì´ì½˜ìž…니다. íŒŒì¼ ê²½ë¡œë¥¼ 지정하거나 테마 êµ¬ì„±ì„ ì´ìš©í•  수 있습니다. + + + Icon path when a light theme is used + ë°ì€ 테마를 사용하는 ê²½ìš°ì˜ ì•„ì´ì½˜ 경로 + + + Icon path when a dark theme is used + ì–´ë‘ìš´ 테마를 사용할 ë•Œ ì•„ì´ì½˜ 경로 + + + Title of the nav section to show the user. + 사용ìžì—게 표시 í•  íƒìƒ‰ ì„¹ì…˜ì˜ ì œëª©. + + + The container that will be displayed in this nav section. + ì´ íƒìƒ‰ ì„¹ì…˜ì— í‘œì‹œë  ì»¨í…Œì´ë„ˆìž…니다. + + + The list of dashboard containers that will be displayed in this navigation section. + ì´ íƒìƒ‰ ì„¹ì…˜ì— í‘œì‹œë  ëŒ€ì‹œ ë³´ë“œ 컨테ì´ë„ˆ 목ë¡ìž…니다. + + + property `icon` can be omitted or must be either a string or a literal like `{dark, light}` + 'icon' ì†ì„±ì€ ìƒëžµë  수 있거나, '{dark, light}' ê°™ì€ ë¬¸ìžì—´ ë˜ëŠ” 리터럴ì´ì–´ì•¼ 합니다. + + + No title in nav section specified for extension. + í™•ìž¥ì„ ìœ„í•´ ì§€ì •ëœ íƒìƒ‰ ì„¹ì…˜ì˜ ì œëª©ì´ ì—†ìŠµë‹ˆë‹¤. + + + No container in nav section specified for extension. + íƒìƒ‰ ì„¹ì…˜ì— í™•ìž¥ì— ëŒ€í•´ ì§€ì •ëœ ì»¨í…Œì´ë„ˆê°€ 없습니다. + + + Exactly 1 dashboard container must be defined per space. + 공간당 정확하게 1ê°œì˜ ëŒ€ì‹œë³´ë“œ 컨테ì´ë„ˆê°€ ì •ì˜ë˜ì–´ì•¼ 합니다. + + + NAV_SECTION within NAV_SECTION is an invalid container for extension. + NAV_SECTION ë‚´ì˜ NAV_SECTIONì€ í™•ìž¥ìš©ìœ¼ë¡œ 유효하지 ì•Šì€ ì»¨í…Œì´ë„ˆìž…니다. + + + + + + + Backup + 백업 + + + + + + + Unique identifier for this tab. Will be passed to the extension for any requests. + ì´ íƒ­ì— ëŒ€ í•œ 고유 ì‹ë³„ìžìž…니다. í™•ìž¥ì— ëª¨ë“  ìš”ì²­ì— ëŒ€í•´ 전달 ë©ë‹ˆë‹¤. + + + Title of the tab to show the user. + 사용ìžë¥¼ 표시할 íƒ­ì˜ ì œëª©ìž…ë‹ˆë‹¤. + + + Description of this tab that will be shown to the user. + íƒ­ì˜ ê¸°ëŠ¥ 설명입니다. ì´ íƒ­ì€ ì‚¬ìš©ìžì—게 표시ë©ë‹ˆë‹¤. + + + Condition which must be true to show this item + ì´ í•­ëª©ì´ í‘œì‹œ ë˜ê¸° 위해 true여야 하는 ì¡°ê±´ + + + Defines the connection types this tab is compatible with. Defaults to 'MSSQL' if not set + ì´ íƒ­ê³¼ 호환ë˜ëŠ” ì—°ê²° ìœ í˜•ì„ ì •ì˜í•©ë‹ˆë‹¤. 설정하지 않으면 ê¸°ë³¸ê°’ì€ 'MSSQL'입니다. + + + The container that will be displayed in this tab. + ì´ íƒ­ì— í‘œì‹œë  ì»¨í…Œì´ë„ˆ. + + + Whether or not this tab should always be shown or only when the user adds it. + ì´ íƒ­ì„ í•­ìƒ í‘œì‹œí• ì§€, 사용ìžê°€ 추가할 때만 표시할지 여부입니다. + + + Whether or not this tab should be used as the Home tab for a connection type. + ì—°ê²° 형ì‹ì— 대해 ì´ íƒ­ì´ í™ˆ 탭으로 사용ë˜ëŠ”지 여부입니다. + + + Contributes a single or multiple tabs for users to add to their dashboard. + 사용ìžìš© íƒ­ì„ í•˜ë‚˜ ë˜ëŠ” 여러 ê°œ 제공하여 ëŒ€ì‹œë³´ë“œì— ì¶”ê°€í•©ë‹ˆë‹¤. + + + No title specified for extension. + í™•ìž¥ì„ ìœ„í•´ ì§€ì •ëœ ì œëª©ì´ ì—†ìŠµë‹ˆë‹¤. + + + No description specified to show. + 표시 í•  ì„¤ëª…ì´ ì—†ìŠµë‹ˆë‹¤. + + + No container specified for extension. + í™•ìž¥ì„ ìœ„í•´ ì§€ì •ëœ ì»¨í…Œì´ë„ˆê°€ 없습니다. + + + Exactly 1 dashboard container must be defined per space + 공간마다 정확하게 1ê°œì˜ ëŒ€ì‹œë³´ë“œ 컨테ì´ë„ˆë¥¼ ì •ì˜í•´ì•¼ 합니다. + + + + + + + Restore + ë³µì› + + + Restore + ë³µì› + + + + + + + Cannot expand as the required connection provider '{0}' was not found + 필요한 ì—°ê²° ê³µê¸‰ìž '{0}'ì„(를) ì°¾ì„ ìˆ˜ 없으므로 확장할 수 없습니다. + + + User canceled + 사용ìžê°€ 취소함 + + + Firewall dialog canceled + 방화벽 대화 ìƒìžê°€ ì·¨ì†Œë¨ + + + + + + + 1 or more tasks are in progress. Are you sure you want to quit? + 1ê°œ ì´ìƒì˜ ìž‘ì—…ì´ ì§„í–‰ 중입니다. ê·¸ëž˜ë„ ìž‘ì—…ì„ ì¢…ë£Œí•˜ì‹œê² ìŠµë‹ˆê¹Œ? + + + Yes + 예 + + + No + 아니요 + + + + + + + Connection is required in order to interact with JobManagementService + JobManagementService와 ìƒí˜¸ 작용하려면 ì—°ê²°ì´ í•„ìš”í•©ë‹ˆë‹¤. + + + No Handler Registered + 등ë¡ëœ 처리기 ì—†ìŒ + + + + + + + An error occured while loading the file browser. + íŒŒì¼ ë¸Œë¼ìš°ì €ë¥¼ 로드 하는 ë™ì•ˆ 오류가 ë°œìƒ í–ˆìŠµë‹ˆë‹¤. + + + File browser error + íŒŒì¼ ë¸Œë¼ìš°ì € 오류 + + + + + + + Notebook Editor + Notebook 편집기 + + + New Notebook + 새 ë…¸íŠ¸ë¶ + + + New Notebook + 새 ë…¸íŠ¸ë¶ + + + SQL kernel: stop Notebook execution when error occurs in a cell. + SQL 커ë„: ì…€ì—ì„œ 오류가 ë°œìƒí•˜ë©´ Notebook ì‹¤í–‰ì„ ì¤‘ì§€í•©ë‹ˆë‹¤. + + + + + + + Script as Create + Createë¡œ 스í¬ë¦½íŠ¸ + + + Script as Drop + Drop 스í¬ë¦½íŠ¸ + + + Select Top 1000 + Select Top 1000 + + + Script as Execute + 실행 스í¬ë¦½íŠ¸ + + + Script as Alter + Alterë¡œ 스í¬ë¦½íŠ¸ + + + Edit Data + ë°ì´í„° 편집 + + + Select Top 1000 + Select Top 1000 + + + Script as Create + Createë¡œ 스í¬ë¦½íŠ¸ + + + Script as Execute + 실행 스í¬ë¦½íŠ¸ + + + Script as Alter + Alterë¡œ 스í¬ë¦½íŠ¸ + + + Script as Drop + Drop 스í¬ë¦½íŠ¸ + + + Refresh + 새로 고침 + + + + + + + Connection error + ì—°ê²° 오류 + + + Connection failed due to Kerberos error. + Kerberos 오류로 ì¸í•´ ì—°ê²°ì´ ì‹¤íŒ¨í–ˆìŠµë‹ˆë‹¤. + + + Help configuring Kerberos is available at {0} + Kerberos 구성 ë„움ë§ì€ {0}ì— ìžˆìŠµë‹ˆë‹¤. + + + If you have previously connected you may need to re-run kinit. + ì´ì „ì— ì—°ê²°ëœ ê²½ìš° 다시 kinit를 실행해야 í•  수 있습니다. + + + + + + + Refresh account was canceled by the user + 사용ìžê°€ 계정 새로 ê³ ì¹¨ì„ ì·¨ì†Œí–ˆìŠµë‹ˆë‹¤. + + + + + + + Specifies view templates + ë·° 템플릿 지정 + + + Specifies session templates + 세션 템플릿 지정 + + + Profiler Filters + 프로파ì¼ëŸ¬ í•„í„° + + + + + + + Toggle Query History + 쿼리 ê¸°ë¡ ì „í™˜ + + + Delete + ì‚­ì œ + + + Clear All History + 모든 ê¸°ë¡ ì§€ìš°ê¸° + + + Open Query + 쿼리 열기 + + + Run Query + 쿼리 실행 + + + Toggle Query History capture + 쿼리 ê¸°ë¡ ìº¡ì²˜ 전환 + + + Pause Query History Capture + 쿼리 ê¸°ë¡ ìº¡ì²˜ ì¼ì‹œ 중지 + + + Start Query History Capture + 쿼리 ê¸°ë¡ ìº¡ì²˜ 시작 + + + + + + + Preview features are required in order for extensions to be fully supported and for some actions to be available. Would you like to enable preview features? + í™•ìž¥ì„ ì™„ì „ížˆ 지ì›í•˜ê³  ì¼ë¶€ ìž‘ì—…ì„ ì‚¬ìš©í•  수 있ë„ë¡ í•˜ë ¤ë©´ 미리 보기 ê¸°ëŠ¥ì´ í•„ìš”í•©ë‹ˆë‹¤. 미리 보기 ê¸°ëŠ¥ì„ ì‚¬ìš©í•˜ë„ë¡ ì„¤ì •í•˜ì‹œê² ìŠµë‹ˆê¹Œ? + + + Yes + 예 + + + No + 아니요 + + + No, don't show again + 아니요, 다시 표시하지 않습니다. + + + + + + + Commit row failed: + row 커밋 실패 + + + Started executing query "{0}" + 쿼리 "{0}" 실행 시작 + + + Update cell failed: + ì…€ ì—…ë°ì´íŠ¸ 실패: + + + + + + + Failed to create Object Explorer session + 개체 íƒìƒ‰ê¸° ì„¸ì…˜ì„ ë§Œë“¤ì§€ 못했습니다. + + + Multiple errors: + 여러 오류: + + + + + + + No URI was passed when creating a notebook manager + ë…¸íŠ¸ë¶ ê´€ë¦¬ìžë¥¼ 만들 ë•Œ URIê°€ 전달ë˜ì§€ 않았습니다. + + + Notebook provider does not exist + ë…¸íŠ¸ë¶ ê³µê¸‰ìžê°€ 존재하지 않습니다. + + + + + + + Query Results + 쿼리 ê²°ê³¼ + + + Query Editor + 쿼리 편집기 + + + New Query + 새 쿼리 + + + [Optional] When true, column headers are included when saving results as CSV + [옵션] trueë¡œ 설정하면, 결과를 CSVë¡œ 저장할 ë•Œ ì—´ì˜ ë¨¸ë¦¬ê¸€ì´ í¬í•¨ë¨ + + + [Optional] The custom delimiter to use between values when saving as CSV + [ì„ íƒ ì‚¬í•­] CSVë¡œ 저장할 ë•Œ ê°’ 사ì´ì— 사용하는 ì‚¬ìš©ìž ì§€ì • 구분 기호 + + + [Optional] Character(s) used for seperating rows when saving results as CSV + [옵션] 결과를 CSVë¡œ 저장할 ë•Œ í–‰ì„ êµ¬ë¶„í•˜ëŠ” ë° ì‚¬ìš©ë˜ëŠ” ë¬¸ìž + + + [Optional] Character used for enclosing text fields when saving results as CSV + [옵션]결과를 CSVë¡œ 저장할 ë•Œ í…스트 필드를 묶는 ë° ì‚¬ìš©ë˜ëŠ” ë¬¸ìž + + + [Optional] File encoding used when saving results as CSV + [옵션] 결과를 CSVë¡œ 저장할 ë•Œ 사용할 íŒŒì¼ ì¸ì½”딩 + + + Enable results streaming; contains few minor visual issues + ê²°ê³¼ 스트리ë°ì„ 사용합니다. 몇 가지 사소한 ì‹œê°ì  문제가 있습니다. + + + [Optional] When true, XML output will be formatted when saving results as XML + [ì„ íƒ ì‚¬í•­] trueì´ë©´ 결과를 XMLë¡œ 저장할 ë•Œ XML ì¶œë ¥ì— í˜•ì‹ì´ 지정ë©ë‹ˆë‹¤. + + + [Optional] File encoding used when saving results as XML + [ì„ íƒ ì‚¬í•­] XMLë¡œ 결과를 저장할 ë•Œ 사용ë˜ëŠ” íŒŒì¼ ì¸ì½”딩 + + + [Optional] Configuration options for copying results from the Results View + [옵션] ê²°ê³¼ ë·°ì—ì„œ 결과를 ë³µì‚¬ì— ëŒ€í•œ 구성 옵션 + + + [Optional] Configuration options for copying multi-line results from the Results View + [옵션] ê²°ê³¼ ë·°ì—ì„œ 여러 ì¤„ì„ í¬í•¨í•œ 결과를 ë³µì‚¬í• ì§€ì— ëŒ€í•œ 구성 옵션 + + + [Optional] Should execution time be shown for individual batches + [옵션] 개별 ì¼ê´„ ì²˜ë¦¬ì— ëŒ€í•œ 실행시간 표시 여부 + + + [Optional] the default chart type to use when opening Chart Viewer from a Query Results + [ì„ íƒ ì‚¬í•­] 쿼리 ê²°ê³¼ì—ì„œ 차트 뷰어를 ì—´ ë•Œ 사용할 기본 차트 유형 + + + Tab coloring will be disabled + 탭 색칠 비활성화 + + + The top border of each editor tab will be colored to match the relevant server group + ê° íŽ¸ì§‘ê¸° íƒ­ì˜ ìƒë‹¨ í…Œë‘리는 관련 서버 그룹과 ì¼ì¹˜í•˜ë„ë¡ ì¹ í•´ì§‘ë‹ˆë‹¤. + + + Each editor tab's background color will match the relevant server group + ê° íŽ¸ì§‘ê¸° íƒ­ì˜ ë°°ê²½ìƒ‰ì´ ê´€ë ¨ 서버 그룹과 ì¼ì¹˜í•©ë‹ˆë‹¤. + + + Controls how to color tabs based on the server group of their active connection + 활성 ì—°ê²°ì˜ ì„œë²„ ê·¸ë£¹ì„ ê¸°ë°˜ìœ¼ë¡œ 탭 ìƒ‰ìƒ ì§€ì • 방법 제어 + + + Controls whether to show the connection info for a tab in the title. + ì œëª©ì— ìžˆëŠ” íƒ­ì— ì—°ê²° 정보를 표시할지 여부를 제어합니다. + + + Prompt to save generated SQL files + ìƒì„±ëœ SQL íŒŒì¼ ì €ìž¥ 여부 묻기 + + + Should IntelliSense be enabled + IntelliSenseì˜ ì‚¬ìš© 여부 + + + Should IntelliSense error checking be enabled + IntelliSense 오류 ê²€ì‚¬ì˜ ì‚¬ìš© 여부 + + + Should IntelliSense suggestions be enabled + IntelliSense ì œì•ˆì˜ ì‚¬ìš© 여부 + + + Should IntelliSense quick info be enabled + IntelliSense ìš”ì•½ì •ë³´ì˜ ì‚¬ìš© 여부 + + + Should IntelliSense suggestions be lowercase + IntelliSense ì œì•ˆì„ ì†Œë¬¸ìžë¡œ 표시 여부 + + + Maximum number of rows to return before the server stops processing your query. + 서버가 쿼리 처리를 중지하기 ì „ì— ë°˜í™˜í•  최대 í–‰ 수입니다. + + + Maximum size of text and ntext data returned from a SELECT statement + SELECT 문ì—ì„œ 반환ë˜ëŠ” text ë° ntext ë°ì´í„°ì˜ 최대 í¬ê¸° + + + An execution time-out of 0 indicates an unlimited wait (no time-out) + 실행 시간 ì œí•œì´ 0ì´ë©´ 무제한 대기(시간 제한 ì—†ìŒ)를 나타냅니다. + + + Enable SET NOCOUNT option + SET NOCOUNT 옵션 사용 + + + Enable SET NOEXEC option + SET NOEXEC 옵션 사용 + + + Enable SET PARSEONLY option + SET PARSEONLY 옵션 사용 + + + Enable SET ARITHABORT option + SET ARITHABORT 옵션 사용 + + + Enable SET STATISTICS TIME option + SET STATISTICS TIME 옵션 사용 + + + Enable SET STATISTICS IO option + SET STATISTICS IO 옵션 사용 + + + Enable SET XACT_ABORT ON option + SET XACT_ABORT ON 옵션 사용 + + + Enable SET TRANSACTION ISOLATION LEVEL option + SET TRANSACTION ISOLATION LEVEL 옵션 사용 + + + Enable SET DEADLOCK_PRIORITY option + SET DEADLOCK_PRIORITY 옵션 사용 + + + Enable SET LOCK TIMEOUT option (in milliseconds) + SET LOCK TIMEOUT 옵션 사용(밀리초) + + + Enable SET QUERY_GOVERNOR_COST_LIMIT + SET QUERY_GOVERNOR_COST_LIMIT 사용 + + + Enable SET ANSI_DEFAULTS + SET ANSI_DEFAULTS 사용 + + + Enable SET QUOTED_IDENTIFIER + SET QUOTED_IDENTIFIER 사용 + + + Enable SET ANSI_NULL_DFLT_ON + SET ANSI_NULL_DFLT_ON 사용 + + + Enable SET IMPLICIT_TRANSACTIONS + SET IMPLICIT_TRANSACTIONS 사용 + + + Enable SET CURSOR_CLOSE_ON_COMMIT + SET CURSOR_CLOSE_ON_COMMIT 사용 + + + Enable SET ANSI_PADDING + SET ANSI_PADDING 사용 + + + Enable SET ANSI_WARNINGS + SET ANSI_WARNINGS 사용 + + + Enable SET ANSI_NULLS + SET ANSI_NULLS 사용 + + + Set keybinding workbench.action.query.shortcut{0} to run the shortcut text as a procedure call. Any selected text in the query editor will be passed as a parameter + 바로 가기 í…스트를 프로 시저 호출로 실행하려면 키 ë°”ì¸ë”© workbench.action.query.shortcut{0}ì„ ì„¤ì • 하십시오. 쿼리 편집기ì—ì„œ ì„ íƒí•œ í…스트가 매개 변수로 전달ë©ë‹ˆë‹¤. + + + + + + + Common id for the provider + 공급ìžìš© ì¼ë°˜ ID + + + Display Name for the provider + 공급ìžì˜ 표시 ì´ë¦„ + + + Icon path for the server type + 서버 ìœ í˜•ì˜ ì•„ì´ì½˜ 경로 + + + Options for connection + ì—°ê²° 옵션 + + + + + + + OK + í™•ì¸ + + + Close + 닫기 + + + Copy details + 세부 ë‚´ìš© 복사 + + + + + + + Add server group + 서버 그룹 추가 + + + Edit server group + 서버 그룹 편집 + + + + + + + Error adding account + 계정 추가 오류 + + + Firewall rule error + 방화벽 규칙 오류 + + + + + + + Some of the loaded extensions are using obsolete APIs, please find the detailed information in the Console tab of Developer Tools window + ë¡œë“œëœ í™•ìž¥ 중 ì¼ë¶€ëŠ” 사용ë˜ì§€ 않는 API를 사용하고 있습니다. ê°œë°œìž ë„구 ì°½ì˜ ì½˜ì†” 탭ì—ì„œ ìžì„¸í•œ 정보를 확ì¸í•˜ì„¸ìš”. + + + Don't Show Again + 다시 표시 안 함 + + + + + + + Toggle Tasks + ìž‘ì—… 토글 + + + + + + + Show Connections + ì—°ê²° 표시 + + + Servers + 서버 + + + + + + + Identifier of the view. Use this to register a data provider through `vscode.window.registerTreeDataProviderForView` API. Also to trigger activating your extension by registering `onView:${id}` event to `activationEvents`. + ë·°ì˜ ì‹ë³„ìžìž…니다. 'vscode.window.registerTreeDataProviderForView` API를 통해 ë°ì´í„° 공급ìžë¥¼ 등ë¡í•˜ëŠ” ë° ì‚¬ìš©í•©ë‹ˆë‹¤. `onView:${id}` ì´ë²¤íŠ¸ë¥¼ `activationEvents`ì— ë“±ë¡í•˜ì—¬ 확장 활성화를 트리거하는 ë°ì—ë„ ì‚¬ìš©í•©ë‹ˆë‹¤. + + + The human-readable name of the view. Will be shown + 사용ìžê°€ ì½ì„ 수 있는 ë·° ì´ë¦„입니다. 표시ë©ë‹ˆë‹¤. + + + Condition which must be true to show this view + ì´ ë³´ê¸°ë¥¼ 표시하기 위해 true여야 하는 조건입니다. + + + Contributes views to the editor + 뷰를 ì—ë””í„°ì— ì ìš©í•©ë‹ˆë‹¤. + + + Contributes views to Data Explorer container in the Activity bar + ìž‘ì—… ë§‰ëŒ€ì˜ Data Explorer 컨테ì´ë„ˆì— 보기 제공 + + + Contributes views to contributed views container + 뷰를 ê¸°ì—¬ëœ ë·° 컨테ì´ë„ˆì— 기여합니다. + + + View container '{0}' does not exist and all views registered to it will be added to 'Data Explorer'. + ë·° 컨테ì´ë„ˆ '{0}'ì´(ê°€) 없으며 해당 ë·° 컨테ì´ë„ˆì— 등ë¡ëœ 모든 ë·°ê°€ 'Data Explorer'ì— ì¶”ê°€ë©ë‹ˆë‹¤. + + + Cannot register multiple views with same id `{0}` in the view container `{1}` + ë·° 컨테ì´ë„ˆ '{1}'ì— ë™ì¼í•œ ID '{0}'(으)ë¡œ 여러 ê°œì˜ ë·°ë¥¼ 등ë¡í•  수 없습니다. + + + A view with id `{0}` is already registered in the view container `{1}` + IDê°€ '{0}'ì¸ ë·°ê°€ ë·° 컨테ì´ë„ˆ '{1}'ì— ì´ë¯¸ 등ë¡ë˜ì–´ 있습니다. + + + views must be an array + 뷰는 ë°°ì—´ì´ì–´ì•¼ 합니다. + + + property `{0}` is mandatory and must be of type `string` + ì†ì„± `{0}`ì€(는) 필수ì´ë©° `string` 형ì‹ì´ì–´ì•¼ 합니다. + + + property `{0}` can be omitted or must be of type `string` + ì†ì„± `{0}`ì€(는) ìƒëžµí•  수 있으며 `string` 형ì‹ì´ì–´ì•¼ 합니다. + + + + + + + Connection Status + ì—°ê²° ìƒíƒœ + + + + + + + Manage + 관리 + + + Show Details + 세부 ì •ë³´ 표시 + + + Learn How To Configure The Dashboard + 대시보드를 구성하는 방법 알아보기 + + + + + + + Widget used in the dashboards + ëŒ€ì‹œë³´ë“œì— ì‚¬ìš©ë˜ëŠ” 위젯 + + + + + + + Displays results of a query as a chart on the dashboard + ëŒ€ì‹œë³´ë“œì˜ ì°¨íŠ¸ë¡œ 쿼리 결과를 표시 합니다. + + + Maps 'column name' -> color. for example add 'column1': red to ensure this column uses a red color + 'column name' -> color를 매핑합니다. 예를 들어 ì´ ì—´ì— ë¹¨ê°„ìƒ‰ì„ ì‚¬ìš©í•˜ë„ë¡ í•˜ë ¤ë©´ 'column1': red를 추가합니다. + + + Indicates preferred position and visibility of the chart legend. These are the column names from your query, and map to the label of each chart entry + 차트 ë²”ë¡€ì˜ ê¸°ë³¸ 위치 ë° í‘œì‹œ 여부를 나타냅니다. ì¿¼ë¦¬ì˜ ì—´ ì´ë¦„ì´ë©°, ê° ì°¨íŠ¸ í•­ëª©ì˜ ë ˆì´ë¸”ì— ë§¤í•‘ë©ë‹ˆë‹¤. + + + If dataDirection is horizontal, setting this to true uses the first columns value for the legend. + dataDirectionì´ ê°€ë¡œì¸ ê²½ìš°, ì´ ê°’ì„ trueë¡œ 설정하면 ë²”ë¡€ì˜ ì²« 번째 ì—´ ê°’ì„ ì‚¬ìš©í•©ë‹ˆë‹¤. + + + If dataDirection is vertical, setting this to true will use the columns names for the legend. + dataDirection ê°’ì´ vertical ì¸ ê²½ìš°, ì´ê²ƒì„ trueë¡œ 설정하면 ë²”ë¡€ì— ì—´ ì´ë¦„ì„ ì‚¬ìš©í•©ë‹ˆë‹¤. + + + Defines whether the data is read from a column (vertical) or a row (horizontal). For time series this is ignored as direction must be vertical. + ë°ì´í„°ë¥¼ ì—´(수ì§)ì—ì„œ ì½ì–´ì˜¬ì§€ í–‰(수í‰)ì—ì„œ ì½ì–´ì˜¬ì§€ ì •ì˜í•©ë‹ˆë‹¤. 시계열ì—서는 수ì§ì´ì–´ì•¼ 하므로 ë°©í–¥ ê°’ì€ ë¬´ì‹œë©ë‹ˆë‹¤. + + + If showTopNData is set, showing only top N data in the chart. + showTopNDataê°€ ì„¤ì •ëœ ê²½ìš° ì°¨íŠ¸ì— ìƒìœ„ Nê°œì˜ ë°ì´í„°ë§Œ 표시합니다. + + + + + + + Condition which must be true to show this item + ì´ í•­ëª©ì´ í‘œì‹œ ë˜ê¸° 위해 true여야 하는 ì¡°ê±´ + + + The title of the container + 컨테ì´ë„ˆì˜ 제목 + + + The row of the component in the grid + í‘œ ì•ˆì˜ êµ¬ì„± í–‰ + + + The rowspan of the component in the grid. Default value is 1. Use '*' to set to number of rows in the grid. + í‘œì— ìžˆëŠ” 구성 ìš”ì†Œì˜ rowspan입니다. ê¸°ë³¸ê°’ì€ 1입니다. '*'ì„ ì‚¬ìš©í•˜ì—¬ í‘œì˜ í–‰ 수를 설정합니다. + + + The column of the component in the grid + ê·¸ë¦¬ë“œì˜ ì»´í¼ë„ŒíŠ¸ 열입니다. + + + The colspan of the component in the grid. Default value is 1. Use '*' to set to number of columns in the grid. + í‘œ 구성 ìš”ì†Œì¸ colspan입니다. ê¸°ë³¸ê°’ì€ 1입니다. í‘œì˜ ì—´ 갯수를 설정 하려면 '*'ì„ ì´ìš©í•˜ì‹­ì‹œì˜¤. + + + Unique identifier for this tab. Will be passed to the extension for any requests. + ì´ íƒ­ì— ëŒ€ í•œ 고유 ì‹ë³„ìžìž…니다. í™•ìž¥ì— ëª¨ë“  ìš”ì²­ì— ëŒ€í•´ 전달 ë©ë‹ˆë‹¤. + + + Extension tab is unknown or not installed. + 확장 íƒ­ì€ ì•Œ 수 없거나 설치 ë˜ì§€ 않았습니다. + + + + + + + Enable or disable the properties widget + ì†ì„± ìœ„ì ¯ì„ í™œì„±í™” ë˜ëŠ” 비활성화합니다. + + + Property values to show + 표시할 ì†ì„± ê°’ + + + Display name of the property + ì†ì„±ì˜ 표시 ì´ë¦„ + + + Value in the Database Info Object + ë°ì´í„°ë² ì´ìŠ¤ ì •ë³´ ê°œì²´ì˜ ê°’ + + + Specify specific values to ignore + 무시할 특정 ê°’ 지정 + + + Recovery Model + 복구 ëª¨ë¸ + + + Last Database Backup + 마지막 ë°ì´í„°ë² ì´ìŠ¤ 백업 + + + Last Log Backup + 마지막 로그 백업 + + + Compatibility Level + 호환성 수준 + + + Owner + ì†Œìœ ìž + + + Customizes the database dashboard page + ë°ì´í„°ë² ì´ìŠ¤ 대시보드 페ì´ì§€ë¥¼ 수정합니다 + + + Customizes the database dashboard tabs + ë°ì´í„°ë² ì´ìŠ¤ 대시 ë³´ë“œ íƒ­ì„ ì‚¬ìš©ìž ì •ì˜í•©ë‹ˆë‹¤. + + + + + + + Enable or disable the properties widget + ì†ì„± ìœ„ì ¯ì„ í™œì„±í™” ë˜ëŠ” 비활성화합니다. + + + Property values to show + 표시할 ì†ì„± ê°’ + + + Display name of the property + ì†ì„±ì˜ 표시 ì´ë¦„ + + + Value in the Server Info Object + 서버 ì •ë³´ ê°œì²´ì˜ ê°’ + + + Version + 버전 + + + Edition + ì—디션 + + + Computer Name + 컴퓨터 ì´ë¦„ + + + OS Version + OS 버전 + + + Customizes the server dashboard page + 서버 대시보드 페ì´ì§€ ì‚¬ìš©ìž ì •ì˜ + + + Customizes the Server dashboard tabs + 서버 대시보드 탭 ì‚¬ìš©ìž ì§€ì •í•˜ê¸° + + + + + + + Manage + 관리 + + + + + + + Widget used in the dashboards + ëŒ€ì‹œë³´ë“œì— ì‚¬ìš©ë˜ëŠ” 위젯 + + + Widget used in the dashboards + ëŒ€ì‹œë³´ë“œì— ì‚¬ìš©ë˜ëŠ” 위젯 + + + Widget used in the dashboards + ëŒ€ì‹œë³´ë“œì— ì‚¬ìš©ë˜ëŠ” 위젯 + + + + + + + Adds a widget that can query a server or database and display the results in multiple ways - as a chart, summarized count, and more + 서버 ë˜ëŠ” ë°ì´í„°ë² ì´ìŠ¤ë¥¼ 쿼리하고 여러 가지 방법(차트, 요약 개수 등)으로 결과를 표시할 수 있는 ìœ„ì ¯ì„ ì¶”ê°€í•©ë‹ˆë‹¤. + + + Unique Identifier used for caching the results of the insight. + ì¸ì‚¬ì´íŠ¸ì˜ 결과를 ìºì‹±í•˜ëŠ” ë° ì‚¬ìš©ë˜ëŠ” 고유 ì‹ë³„ìžìž…니다. + + + SQL query to run. This should return exactly 1 resultset. + 실행할 SQL 쿼리입니다. 정확하게 1ê°œì˜ ê²°ê³¼ ì§‘í•©ì„ ë°˜í™˜í•´ì•¼ 합니다. + + + [Optional] path to a file that contains a query. Use if 'query' is not set + [ì„ íƒ ì‚¬í•­] 쿼리가 í¬í•¨ëœ 파ì¼ì˜ 경로입니다. '쿼리'를 설정하지 ì•Šì€ ê²½ìš°ì— ì‚¬ìš©í•©ë‹ˆë‹¤. + + + [Optional] Auto refresh interval in minutes, if not set, there will be no auto refresh + [옵션] ìžë™ 새로 고침 간격 (분), 설정 ë˜ì§€ ì•Šì€ ê²½ìš° ìžë™ 새로 고침 ì—†ìŒ. + + + Which actions to use + 사용할 ìž‘ì—… + + + Target database for the action; can use the format '${ columnName }' to use a data driven column name. + ìž‘ì—…ì˜ ëŒ€ìƒ ë°ì´í„°ë² ì´ìŠ¤ìž…니다. '${ columnName }' 형ì‹ì„ 사용하여 ë°ì´í„° 기반 ì—´ ì´ë¦„ì„ ì‚¬ìš©í•  수 있습니다. + + + Target server for the action; can use the format '${ columnName }' to use a data driven column name. + ìž‘ì—…ì— ëŒ€í•œ ëŒ€ìƒ ì„œë²„; '${ columnName }' 형ì‹ì„ 사용하여 ë°ì´í„° 기반 ì—´ ì´ë¦„ì„ ì‚¬ìš©í•  수 있습니다. + + + Target user for the action; can use the format '${ columnName }' to use a data driven column name. + ìž‘ì—…ì— ëŒ€í•œ ëŒ€ìƒ ì‚¬ìš©ìž; '${ columnName }' 형ì‹ì„ 사용하여 ë°ì´í„° 기반 ì—´ ì´ë¦„ì„ ì‚¬ìš©í•  수 있습니다. + + + Identifier of the insight + ì¸ì‚¬ì´íŠ¸ì˜ ì‹ë³„ìž + + + Contributes insights to the dashboard palette. + 대시보드 íŒ”ë ˆíŠ¸ì— ì¸ì‚¬ì´íŠ¸ë¥¼ 제공합니다. + + + + + + + Loading + 로드 + + + Loading completed + 로드 완료 + + + + + + + Defines a property to show on the dashboard + ëŒ€ì‹œë³´ë“œì— í‘œì‹œ ì†ì„±ì„ ì •ì˜ í•©ë‹ˆë‹¤. + + + What value to use as a label for the property + ì†ì„±ì— 대한 ë ˆì´ë¸”ë¡œ 사용할 ê°’ + + + What value in the object to access for the value + ê°’ì— ëŒ€í•´ 액세스하는 ê°œì²´ì˜ ê°’ + + + Specify values to be ignored + 무시 í•  ê°’ 지정 + + + Default value to show if ignored or no value + 무시ë˜ê±°ë‚˜ ê°’ì´ ì—†ëŠ” 경우 표시할 기본값 + + + A flavor for defining dashboard properties + 대시보드 ì†ì„±ì„ ì •ì˜í•˜ê¸° 위한 특성 + + + Id of the flavor + íŠ¹ì„±ì˜ ID + + + Condition to use this flavor + ì´ ì„ íƒì„ 사용하는 ì¡°ê±´ + + + Field to compare to + 비êµí•  í•„ë“œ + + + Which operator to use for comparison + 비êµë¥¼ 위해 사용할 ì—°ì‚°ìž + + + Value to compare the field to + 필드를 비êµí•  ëŒ€ìƒ ê°’ + + + Properties to show for database page + ë°ì´í„°ë² ì´ìŠ¤ 페ì´ì§€ 표시 ì†ì„± + + + Properties to show for server page + 서버 페ì´ì§€ì— 표시할 ì†ì„± + + + Defines that this provider supports the dashboard + ì´ ê³µê¸‰ìžê°€ 대시보드를 지ì›í•¨ì„ ì •ì˜í•©ë‹ˆë‹¤. + + + Provider id (ex. MSSQL) + ê³µê¸‰ìž id (예: MSSQL) + + + Property values to show on dashboard + ëŒ€ì‹œë³´ë“œì— í‘œì‹œí•  ì†ì„± ê°’ + + + + + + + Backup + 백업 + + + You must enable preview features in order to use backup + ë°±ì—…ì„ ì‚¬ìš©í•˜ë ¤ë©´ 미리 보기 ê¸°ëŠ¥ì„ ì‚¬ìš©í•˜ë„ë¡ ì„¤ì •í•´ì•¼ 합니다. + + + Backup command is not supported for Azure SQL databases. + Azure SQL Databaseì— ëŒ€í•´ 백업 ëª…ë ¹ì´ ì§€ì›ë˜ì§€ 않습니다. + + + Backup command is not supported in Server Context. Please select a Database and try again. + 서버 컨í…스트ì—ì„œ 백업 ëª…ë ¹ì´ ì§€ì›ë˜ì§€ 않습니다. ë°ì´í„°ë² ì´ìŠ¤ë¥¼ ì„ íƒí•˜ê³  다시 ì‹œë„하세요. + + + + + + + Restore + ë³µì› + + + You must enable preview features in order to use restore + ë³µì›ì„ 사용하려면 미리 보기 ê¸°ëŠ¥ì„ ì‚¬ìš©í•˜ë„ë¡ ì„¤ì •í•´ì•¼ 합니다. + + + Restore command is not supported for Azure SQL databases. + Azure SQL Databaseì— ëŒ€í•´ ë³µì› ëª…ë ¹ì´ ì§€ì›ë˜ì§€ 않습니다. + + + + + + + disconnected + ì—°ê²° ëŠê¹€ + + + + + + + Server Groups + 서버 그룹 + + + OK + í™•ì¸ + + + Cancel + 취소 + + + Server group name + 서버 그룹명 + + + Group name is required. + 그룹 ì´ë¦„ì´ í•„ìš”í•©ë‹ˆë‹¤. + + + Group description + 그룹 설명 + + + Group color + 그룹 ìƒ‰ìƒ + + + + + + + Extension + 확장 + + + + + + + OK + í™•ì¸ + + + Cancel + 취소 + + + + + + + Open dashboard extensions + 대시보드 확장 열기 + + + OK + í™•ì¸ + + + Cancel + 취소 + + + No dashboard extensions are installed at this time. Go to Extension Manager to explore recommended extensions. + 현재 ì„¤ì¹˜ëœ ëŒ€ì‹œë³´ë“œ í™•ìž¥ì´ ì—†ìŠµë‹ˆë‹¤. 확장 관리ìžë¡œ 가서 추천 í™•ìž¥ì„ íƒìƒ‰í•˜ì„¸ìš”. + + + + + + + Selected path + ì„ íƒí•œ 경로 + + + Files of type + íŒŒì¼ í˜•ì‹ + + + OK + í™•ì¸ + + + Discard + í기 + + + + + + + No Connection Profile was passed to insights flyout + ì¸ì‚¬ì´íŠ¸ 플ë¼ì´ì•„ì›ƒì— ì—°ê²° í”„ë¡œí•„ì´ ì „ë‹¬ë˜ì§€ 않았습니다. + + + Insights error + ì¸ì‚¬ì´íŠ¸ 오류 + + + There was an error reading the query file: + 쿼리 파ì¼ì„ ì½ëŠ” 중 오류가 ë°œìƒí–ˆìŠµë‹ˆë‹¤. + + + There was an error parsing the insight config; could not find query array/string or queryfile + ì¸ì‚¬ì´íŠ¸ 구성 분ì„ì— ì˜¤ë¥˜ê°€ ë°œìƒ í–ˆìŠµë‹ˆë‹¤; 쿼리 ë°°ì—´/문ìžì—´ì´ë‚˜ 쿼리파ì¼ì„ ì°¾ì„ ìˆ˜ 없습니다. + + + + + + + Clear List + ëª©ë¡ ì§€ìš°ê¸° + + + Recent connections list cleared + 최근 ì—°ê²° 목ë¡ì„ 삭제했습니다. + + + Yes + 예 + + + No + 아니요 + + + Are you sure you want to delete all the connections from the list? + 목ë¡ì—ì„œ 모든 ì—°ê²°ì„ ì‚­ì œí•˜ì‹œê² ìŠµë‹ˆê¹Œ? + + + Yes + 예 + + + No + 아니요 + + + Delete + ì‚­ì œ + + + Get Current Connection String + 현재 ì—°ê²° 문ìžì—´ 얻기 + + + Connection string not available + ì—°ê²° 문ìžì—´ì„ 사용할 수 없습니다 + + + No active connection available + 사용 가능한 활성 ì—°ê²°ì´ ì—†ìŠµë‹ˆë‹¤ + + + + + + + Refresh + 새로 고침 + + + Disconnect + ì—°ê²° ëŠê¸° + + + New Connection + 새 ì—°ê²° + + + New Server Group + 새 서버 그룹 + + + Edit Server Group + 서버 그룹 편집 + + + Show Active Connections + 활성 ì—°ê²° 표시 + + + Show All Connections + 모든 ì—°ê²° 표시 + + + Recent Connections + 최근 ì—°ê²° + + + Delete Connection + ì—°ê²° ì‚­ì œ + + + Delete Group + 그룹 ì‚­ì œ + + + + + + + Edit Data Session Failed To Connect + 연결실패한 ë°ì´íƒ€ 세션 편집 + + + + + + + Profiler + 프로파ì¼ëŸ¬ + + + Not connected + ì—°ê²°ë˜ì§€ ì•ŠìŒ + + + XEvent Profiler Session stopped unexpectedly on the server {0}. + {0} 서버ì—ì„œ XEvent 프로파ì¼ëŸ¬ ì„¸ì…˜ì´ ì˜ˆê¸°ì¹˜ 않게 중지ë˜ì—ˆìŠµë‹ˆë‹¤. + + + Error while starting new session + 새로운 ì„¸ì…˜ì„ ì‹œìž‘ 하는 ë™ì•ˆ 오류가 ë°œìƒ í–ˆìŠµë‹ˆë‹¤ + + + The XEvent Profiler session for {0} has lost events. + {0}ì˜ XEvent 프로파ì¼ëŸ¬ 세션ì—ì„œ ì´ë²¤íŠ¸ê°€ ì†ì‹¤ë˜ì—ˆìŠµë‹ˆë‹¤. + + + Would you like to stop the running XEvent session? + 실행 ì¤‘ì¸ XEvent ì„¸ì…˜ì„ ì¤‘ì§€í•˜ì‹œê² ìŠµë‹ˆê¹Œ? + + + Yes + 예 + + + No + 아니요 + + + Cancel + 취소 + + + + + + + Invalid value + ìž˜ëª»ëœ ê°’ + + + {0}. {1} + {0}. {1} + + + + + + + blank + 비어 ìžˆìŒ + + + + + + + Error displaying Plotly graph: {0} + 플롯 그래프 표시 오류: {0} + + + + + + + No {0} renderer could be found for output. It has the following MIME types: {1} + ì¶œë ¥ì˜ {0} ë Œë”러를 ì°¾ì„ ìˆ˜ 없습니다. MIME 형ì‹ì€ {1}입니다. + + + (safe) + (안전) + + + + + + + Item + 항목 + + + Value + ê°’ + + + Property + ì†ì„± + + + Value + ê°’ + + + Insights + ì¸ì‚¬ì´íŠ¸ + + + Items + 항목 + + + Item Details + 항목 세부 ì •ë³´ + + + + + + + Error adding account + 계정 추가 오류 + + + + + + + Cannot start auto OAuth. An auto OAuth is already in progress. + ìžë™ OAuth를 시작할 수 없습니다. ìžë™ OAuthê°€ ì´ë¯¸ 진행 중입니다. + + + + + + + Sort by event + ì´ë²¤íŠ¸ë¡œ ì •ë ¬ + + + Sort by column + ì—´ ì •ë ¬ + + + Profiler + 프로파ì¼ëŸ¬ + + + OK + í™•ì¸ + + + Cancel + 취소 + + + + + + + Clear all + ëª¨ë‘ ì§€ìš°ê¸° + + + Apply + ì ìš© + + + OK + í™•ì¸ + + + Cancel + 취소 + + + Filters + í•„í„° + + + Remove this clause + ì´ ì ˆ 제거 + + + Save Filter + í•„í„° 저장 + + + Load Filter + í•„í„° 로드 + + + Add a clause + ì ˆ 추가 + + + Field + í•„ë“œ + + + Operator + ìš´ì˜ìž + + + Value + ê°’ + + + Is Null + Nullìž„ + + + Is Not Null + Null 아님 + + + Contains + í¬í•¨ + + + Not Contains + í¬í•¨í•˜ì§€ ì•ŠìŒ + + + Starts With + 다ìŒìœ¼ë¡œ 시작함 + + + Not Starts With + 다ìŒìœ¼ë¡œ 시작하지 ì•ŠìŒ + + + + + + + Double-click to edit + 편집하려면 ë‘ ë²ˆ í´ë¦­í•˜ì„¸ìš”. + + + + + + + Select Top 1000 + Select Top 1000 + + + Script as Execute + 실행 스í¬ë¦½íŠ¸ + + + Script as Alter + Alterë¡œ 스í¬ë¦½íŠ¸ + + + Edit Data + ë°ì´í„° 편집 + + + Script as Create + Createë¡œ 스í¬ë¦½íŠ¸ + + + Script as Drop + Drop 스í¬ë¦½íŠ¸ + + + + + + + No queries to display. + 표시할 쿼리가 없습니다. + + + Query History + 쿼리 ê¸°ë¡ + QueryHistory + + + + + + + Failed to get Azure account token for connection + ì—°ê²°ì„ ìœ„í•´ Azure 계정 토í°ì„ 가져오지 못했습니다. + + + Connection Not Accepted + ì—°ê²°ì´ í—ˆìš©ë˜ì§€ 않습니다 + + + Yes + 예 + + + No + 아니요 + + + Are you sure you want to cancel this connection? + ì´ ì—°ê²°ì„ ì·¨ì†Œí•˜ì‹œê² ìŠµë‹ˆê¹Œ? + + + + + + + Started executing query at + 쿼리 실행 시작: + + + Line {0} + 줄 {0} + + + Canceling the query failed: {0} + 쿼리 취소 실패: {0} + + + Started saving results to + ê²°ê³¼ 저장 시작: + + + Failed to save results. + ê²°ê³¼ ì €ìž¥ì— ì‹¤íŒ¨í•˜ì˜€ìŠµë‹ˆë‹¤. + + + Successfully saved results to + ê²°ê³¼ 저장 완료: + + + Executing query... + 쿼리를 실행하는 중... + + + Maximize + 최대화 + + + Restore + ë³µì› + + + Save as CSV + CSVë¡œ 저장 + + + Save as JSON + JSON으로 저장 + + + Save as Excel + Excelë¡œ 저장 + + + Save as XML + XMLë¡œ 저장 + + + View as Chart + 차트 보기 + + + Visualize + ì‹œê°í™” + + + Results + ê²°ê³¼ + + + Executing query + 쿼리 실행 + + + Messages + 메시지 + + + Total execution time: {0} + ì´ ì‹¤í–‰ 시간: {0} + + + Save results command cannot be used with multiple selections. + ê²°ê³¼ 저장 ëª…ë ¹ì€ ë‹¤ì¤‘ ì„ íƒì„ 사용할 수 없습니다. + + + + + + + Identifier of the notebook provider. + ë…¸íŠ¸ë¶ ê³µê¸‰ìžì˜ ì‹ë³„ìžìž…니다. + + + What file extensions should be registered to this notebook provider + ì´ ë…¸íŠ¸ë¶ ê³µê¸‰ìžì— 등ë¡í•´ì•¼ 하는 íŒŒì¼ í™•ìž¥ëª… + + + What kernels should be standard with this notebook provider + ì´ ë…¸íŠ¸ë¶ ê³µê¸‰ìžì—ì„œ 표준ì´ì–´ì•¼ 하는 ì»¤ë„ + + + Contributes notebook providers. + ë…¸íŠ¸ë¶ ê³µê¸‰ìžë¥¼ 제공합니다. + + + Name of the cell magic, such as '%%sql'. + '%%sql'ê³¼(와) ê°™ì€ ì…€ 매ì§ì˜ ì´ë¦„입니다. + + + The cell language to be used if this cell magic is included in the cell + ì´ ì…€ 매ì§ì´ ì…€ì— í¬í•¨ë˜ëŠ” 경우 사용ë˜ëŠ” ì…€ 언어 + + + Optional execution target this magic indicates, for example Spark vs SQL + ì´ ë§¤ì§ì´ 나타내는 ì„ íƒì  실행 대ìƒ(예: Spark와 SQL) + + + Optional set of kernels this is valid for, e.g. python3, pyspark, sql + ì„ íƒì  ì»¤ë„ ì„¸íŠ¸ë¡œ, python3, pyspark, sql ë“±ì— ìœ íš¨í•©ë‹ˆë‹¤. + + + Contributes notebook language. + ë…¸íŠ¸ë¶ ì–¸ì–´ë¥¼ 제공합니다. + + + + + + + SQL + SQL + + + + + + + F5 shortcut key requires a code cell to be selected. Please select a code cell to run. + F5 바로 가기 키를 사용하려면 코드 ì…€ì„ ì„ íƒí•´ì•¼ 합니다. 실행할 코드 ì…€ì„ ì„ íƒí•˜ì„¸ìš”. + + + Clear result requires a code cell to be selected. Please select a code cell to run. + 명확한 결과를 얻으려면 코드 ì…€ì„ ì„ íƒí•´ì•¼ 합니다. 실행할 코드 ì…€ì„ ì„ íƒí•˜ì„¸ìš”. + + + + + + + Save As CSV + CSVë¡œ 저장 + + + Save As JSON + JSON으로 저장 + + + Save As Excel + Excelë¡œ 저장 + + + Save As XML + XMLë¡œ 저장 + + + Copy + 복사 + + + Copy With Headers + 복사(머리글 í¬í•¨) + + + Select All + ëª¨ë‘ ì„ íƒ + + + + + + + Max Rows: + 최대 í–‰: + + + + + + + Select View + 보기 ì„ íƒ + + + Select Session + 세션 ì„ íƒ + + + Select Session: + 세션 ì„ íƒ: + + + Select View: + 보기 ì„ íƒ: + + + Text + í…스트 + + + Label + ë ˆì´ë¸” + + + Value + ê°’ + + + Details + 세부 ì •ë³´ + + + + + + + Copy failed with error {0} + {0} 오류를 나타내며 복사 실패 + + + + + + + New Query + 새 쿼리 + + + Run + 실행 + + + Cancel + 취소 + + + Explain + 설명 + + + Actual + 실제 + + + Disconnect + ì—°ê²° ëŠê¸° + + + Change Connection + ì—°ê²° 변경 + + + Connect + ì—°ê²° + + + Enable SQLCMD + SQLCMD 사용 + + + Disable SQLCMD + SQLCMD 사용 안 함 + + + Select Database + ë°ì´í„°ë² ì´ìŠ¤ ì„ íƒ + + + Select Database Toggle Dropdown + ë°ì´í„°ë² ì´ìŠ¤ 토글 ë“œë¡­ë‹¤ìš´ì„ ì„ íƒí•˜ì‹­ì‹œì˜¤ + + + Failed to change database + ë°ì´í„°ë² ì´ìŠ¤ë¥¼ 변경하지 못했습니다. + + + Failed to change database {0} + ë°ì´í„°ë² ì´ìŠ¤ {0}를 변경 하지 못했습니다. + + + + + + + Connection + ì—°ê²° + + + Connection type + ì—°ê²° 유형 + + + Recent Connections + 최근 ì—°ê²° + + + Saved Connections + ì €ìž¥ëœ ì—°ê²° + + + Connection Details + ì—°ê²° ì •ë³´ + + + Connect + ì—°ê²° + + + Cancel + 취소 + + + No recent connection + 최근 ì—°ê²° ì—†ìŒ + + + No saved connection + ì €ìž¥ëœ ì—°ê²° ì—†ìŒ + + + + + + + OK + í™•ì¸ + + + Close + 닫기 + + + + + + + Loading kernels... + 커ë„ì„ ë¡œë“œí•˜ëŠ” 중... + + + Changing kernel... + 커ë„ì„ ë³€ê²½í•˜ëŠ” 중... + + + Kernel: + 커ë„: + + + Attach To: + ì—°ê²° 대ìƒ: + + + Loading contexts... + 컨í…스트를 로드하는 중... + + + Add New Connection + 새 ì—°ê²° 추가 + + + Select Connection + ì—°ê²° ì„ íƒ + + + localhost + localhost + + + Trusted + ë†’ì€ ì‹ ë¢°ì„± + + + Not Trusted + 신뢰할 수 ì—†ìŒ + + + Notebook is already trusted. + 노트ë¶ì„ ì´ë¯¸ 신뢰할 수 있습니다. + + + Collapse Cells + ì…€ 축소 + + + Expand Cells + ì…€ 확장 + + + No Kernel + ì»¤ë„ ì•„ë‹˜ + + + None + None + + + New Notebook + 새 ë…¸íŠ¸ë¶ + + + + + + + Time Elapsed + 경과 시간 + + + Row Count + í–‰ 개수 + + + {0} rows + {0}ê°œì˜ í–‰ + + + Executing query... + 쿼리를 실행하는 중... + + + Execution Status + 실행 ìƒíƒœ + + + + + + + No task history to display. + 표시할 ìž‘ì—… 기ë¡ì´ 없습니다. + + + Task history + ìž‘ì—… ê¸°ë¡ + TaskHistory + + + Task error + ìž‘ì—… 오류 + + + + + + + Choose SQL Language + SQL 언어 ì„ íƒ + + + Change SQL language provider + SQL 언어 ê³µê¸‰ìž ë³€ê²½ + + + SQL Language Flavor + SQL 언어 빌드 버전 + + + Change SQL Engine Provider + SQL 엔진 ê³µê¸‰ìž ë³€ê²½ + + + A connection using engine {0} exists. To change please disconnect or change connection + {0} ì—”ì§„ì„ ì‚¬ìš©í•œ ì—°ê²°ì´ ì¡´ìž¬í•©ë‹ˆë‹¤. ë³€ê²½ì„ ìœ„í•´ì„œ ì—°ê²°ì„ ë³€ê²½í•˜ê±°ë‚˜ 취소하십시오. + + + No text editor active at this time + 현재 활성 í…스트 편집기 ì—†ìŒ + + + Select SQL Language Provider + SQL 공급ìžë¥¼ ì„ íƒí•˜ì„¸ìš” + + + + + + + All files + 모든 íŒŒì¼ + + + + + + + File browser tree + íŒŒì¼ ë¸Œë¼ìš°ì € 트리 + FileBrowserTree + + + + + + + From + 보낸 사람 + + + To + 받는 사람 + + + Create new firewall rule + 새 방화벽 규칙 만들기 + + + OK + í™•ì¸ + + + Cancel + 취소 + + + Your client IP address does not have access to the server. Sign in to an Azure account and create a new firewall rule to enable access. + í´ë¼ì´ì–¸íŠ¸ IP 주소ì—는 해당 ì„œë²„ì— ì•¡ì„¸ìŠ¤í•  ê¶Œí•œì´ ì—†ìŠµë‹ˆë‹¤. 액세스하려면 Azure ê³„ì •ì— ë¡œê·¸ì¸í•˜ê³  새 방화벽 ê·œì¹™ì„ ë§Œë“œì„¸ìš”. + + + Learn more about firewall settings + 방화벽 ì„¤ì •ì— ëŒ€í•œ ìžì„¸í•œ ì •ë³´ + + + Azure account + Azure 계정 + + + Firewall rule + 방화벽 규칙 + + + Add my client IP + ë‚´ í´ë¼ì´ì–¸íŠ¸ IP를 추가 + + + Add my subnet IP range + ë‚´ 서브넷 IP 범위를 추가 + + + + + + + You need to refresh the credentials for this account. + ì´ ê³„ì •ì˜ ìžê²© ì¦ëª…ì„ ìƒˆë¡œ ê³ ì³ì•¼í•©ë‹ˆë‹¤. + + + + + + + Could not find query file at any of the following paths : + {0} + ë‹¤ìŒ ê²½ë¡œì—ì„œ 쿼리 파ì¼ì„ ì°¾ì„ ìˆ˜ 없습니다. {0} + + + + + + + Add an account + 계정 추가 + + + Remove account + 계정 제거 + + + Are you sure you want to remove '{0}'? + '{0}'ì„ ì œê±°í•˜ì‹œê² ìŠµë‹ˆê¹Œ? + + + Yes + 예 + + + No + 아니요 + + + Failed to remove account + ê³„ì •ì„ ì œê±°í•˜ì§€ 못했습니다. + + + Apply Filters + í•„í„° ì ìš© + + + Reenter your credentials + ìžê²© ì¦ëª… 다시 ìž…ë ¥ + + + There is no account to refresh + 갱신할 ê³„ì •ì´ ì—†ìŠµë‹ˆë‹¤. + + + + + + + Focus on Current Query + 현재 ì¿¼ë¦¬ì— í¬ì»¤ìŠ¤ + + + Run Query + 쿼리 실행 + + + Run Current Query + 현재 쿼리 실행 + + + Run Current Query with Actual Plan + 실제 실행 계íšê³¼ 함께 현재 쿼리 실행 + + + Cancel Query + 쿼리 취소 + + + Refresh IntelliSense Cache + IntelliSense ìºì‹œë¥¼ 새로 고침 + + + Toggle Query Results + 쿼리 ê²°ê³¼ 전환 + + + Editor parameter is required for a shortcut to be executed + 단축키를 실행하려면 편집기 매개변수가 필요합니다 + + + Parse Query + 쿼리 구문 ë¶„ì„ + + + Commands completed successfully + ëª…ë ¹ì´ ì„±ê³µì ìœ¼ë¡œ 완료ë˜ì—ˆìŠµë‹ˆë‹¤. + + + Command failed: + 명령 실패: + + + Please connect to a server + ì„œë²„ì— ì—°ê²°í•˜ì„¸ìš”. + + + + + + + Chart cannot be displayed with the given data + 제공한 ë°ì´í„°ë¥¼ 사용하여 차트를 표시할 수 없습니다. + + + + + + + The index {0} is invalid. + ì¸ë±ìŠ¤ {0}ì´(ê°€) 잘못ë˜ì—ˆìŠµë‹ˆë‹¤. + + + + + + + no data available + 사용할 수 있는 ë°ì´í„°ê°€ 없습니다. + + + + + + + Information + ì •ë³´ + + + Warning + 경고 + + + Error + 오류 + + + Show Details + 세부 ì •ë³´ 표시 + + + Copy + 복사 + + + Close + 닫기 + + + Back + 뒤로 + + + Hide Details + 세부 ì •ë³´ 숨기기 + + + + + + + is required. + ê°€ í•„ìš” 합니다. + + + Invalid input. Numeric value expected. + ìž…ë ¥ì´ ìž˜ëª»ë˜ì—ˆìŠµë‹ˆë‹¤. ìˆ«ìž ê°’ì´ í•„ìš”í•©ë‹ˆë‹¤. + + + + + + + Execution failed due to an unexpected error: {0} {1} + 예기치 ì•Šì€ ì˜¤ë¥˜ë¡œ ì¸í•´ 실행하지 못했습니다. {0} {1} + + + Total execution time: {0} + ì´ ì‹¤í–‰ 시간: {0} + + + Started executing query at Line {0} + {0}번째 줄ì—ì„œ 쿼리 ì‹¤í–‰ì´ ì‹œìž‘ë¨ + + + Initialize edit data session failed: + 편집 ë°ì´í„° 세션 초기화 실패: + + + Batch execution time: {0} + ì¼ê´„ 처리 실행 시간: {0} + + + Copy failed with error {0} + {0} 오류를 나타내며 복사 실패 + + + + + + + Error: {0} + 오류: {0} + + + Warning: {0} + 경고: {0} + + + Info: {0} + ì •ë³´: {0} + + + + + + + Copy Cell + ì…€ 복사 + + + + + + + Backup file path + 백업 íŒŒì¼ ê²½ë¡œ + + + Target database + ëŒ€ìƒ ë°ì´í„°ë² ì´ìŠ¤ + + + Restore database + ë°ì´í„°ë² ì´ìŠ¤ ë³µì› + + + Restore database + ë°ì´í„°ë² ì´ìŠ¤ ë³µì› + + + Database + ë°ì´í„°ë² ì´ìŠ¤ + + + Backup file + 백업 íŒŒì¼ + + + Restore + ë³µì› + + + Cancel + 취소 + + + Script + 스í¬ë¦½íŠ¸ + + + Source + 소스 + + + Restore from + ë³µì›í•˜ê¸° + + + Backup file path is required. + 백업 파ì¼ì˜ 경로가 필요합니다. + + + Please enter one or more file paths separated by commas + 하나 ì´ìƒì˜ íŒŒì¼ ê²½ë¡œë¥¼ 쉼표로 구분하여 입력하세요. + + + Database + ë°ì´í„°ë² ì´ìŠ¤ + + + Destination + ëŒ€ìƒ + + + Select Database Toggle Dropdown + ë°ì´í„°ë² ì´ìŠ¤ 토글 ë“œë¡­ë‹¤ìš´ì„ ì„ íƒí•˜ì‹­ì‹œì˜¤ + + + Restore to + ë³µì› ìœ„ì¹˜ + + + Restore plan + ë³µì› ê³„íš + + + Backup sets to restore + ë³µì›í•  백업 세트 + + + Restore database files as + 으로 ë°ì´í„°ë² ì´ìŠ¤ 파ì¼ì„ ë³µì› + + + Restore database file details + ë³µì› ë°ì´í„°ë² ì´ìŠ¤ íŒŒì¼ ì„¸ë¶€ 사항 + + + Logical file Name + ë…¼ë¦¬ì  íŒŒì¼ ì´ë¦„ + + + File type + íŒŒì¼ í˜•ì‹ + + + Original File Name + ì›ëž˜ íŒŒì¼ ì´ë¦„ + + + Restore as + 으로 ë³µì› + + + Restore options + ë³µì› ì˜µì…˜ + + + Tail-Log backup + ë¹„ìƒ ë¡œê·¸ 백업 + + + Server connections + 서버 ì—°ê²° + + + General + ì¼ë°˜ + + + Files + íŒŒì¼ + + + Options + 옵션 + + + + + + + Copy & Open + 복사 ë° ì—´ê¸° + + + Cancel + 취소 + + + User code + ì‚¬ìš©ìž ì½”ë“œ + + + Website + 웹사ì´íŠ¸ + + + + + + + Done + 완료 + + + Cancel + 취소 + + + + + + + Must be an option from the list + 목ë¡ì— 있는 옵션ì´ì–´ì•¼ 합니다. + + + Toggle dropdown + 토글 메뉴 + + + + + + + Select/Deselect All + ëª¨ë‘ ì„ íƒ/í•´ì œ + + + checkbox checked + 확ì¸ëž€ì„ ì„ íƒí•¨ + + + checkbox unchecked + 확ì¸ëž€ì„ ì„ íƒ ì·¨ì†Œí•¨ + + + + + + + modelview code editor for view model. + view model ìš© modelview 코드 편집기. + + + + + + + succeeded + 성공 + + + failed + 실패 + + + + + + + Server Description (optional) + 서버 설명(ì„ íƒ ì‚¬í•­) + + + + + + + Advanced Properties + 고급 ì†ì„± + + + Discard + í기 + + + + + + + Linked accounts + ì—°ê²° ëœ ê³„ì • + + + Close + 닫기 + + + There is no linked account. Please add an account. + ì—°ê²°ëœ ê³„ì •ì´ ì—†ìŠµë‹ˆë‹¤. ê³„ì •ì„ ì¶”ê°€í•˜ì„¸ìš”. + + + Add an account + 계정 추가 + + + + + + + nbformat v{0}.{1} not recognized + nbformat v{0}.{1}ì´(ê°€) ì¸ì‹ë˜ì§€ 않습니다. + + + This file does not have a valid notebook format + ì´ íŒŒì¼ì— 유효한 ë…¸íŠ¸ë¶ í˜•ì‹ì´ 없습니다. + + + Cell type {0} unknown + ì•Œ 수 없는 ì…€ í˜•ì‹ {0} + + + Output type {0} not recognized + {0} 출력 형ì‹ì„ ì¸ì‹í•  수 없습니다. + + + Data for {0} is expected to be a string or an Array of strings + {0}ì˜ ë°ì´í„°ëŠ” 문ìžì—´ ë˜ëŠ” 문ìžì—´ì˜ ë°°ì—´ì´ì–´ì•¼ 합니다. + + + Output type {0} not recognized + {0} 출력 형ì‹ì„ ì¸ì‹í•  수 없습니다. + + + + + + + Profiler editor for event text. Readonly + ì´ë²¤íŠ¸ í…ìŠ¤íŠ¸ì˜ í”„ë¡œíŒŒì¼ëŸ¬ 편집기. ì½ê¸° ì „ìš© + + + + + + + Run Cells Before + ì´ì „ì— ì…€ 실행 + + + Run Cells After + ë‹¤ìŒ ì´í›„ì— ì…€ 실행 + + + Insert Code Before + ì•žì— ì½”ë“œ 삽입 + + + Insert Code After + 다ìŒì— 코드 삽입 + + + Insert Text Before + ì•žì— í…스트 삽입 + + + Insert Text After + ë’¤ì— í…스트 삽입 + + + Collapse Cell + ì…€ 축소 + + + Expand Cell + ì…€ 확장 + + + Clear Result + ê²°ê³¼ 지우기 + + + Delete + ì‚­ì œ + + + + + + + No script was returned when calling select script on object + ê°ì²´ì—ì„œ select 스í¬ë¦½íŠ¸ë¥¼ 호출 í•  ë•Œ 스í¬ë¦½íŠ¸ê°€ 반환ë˜ì§€ 않았습니다 + + + Select + ì„ íƒ + + + Create + 만들기 + + + Insert + 삽입 + + + Update + ì—…ë°ì´íŠ¸ + + + Delete + ì‚­ì œ + + + No script was returned when scripting as {0} on object {1} + 개체 {1}를 {0} (으)ë¡œ 스í¬ë¦½íŒ… 했지만, 스í¬ë¦½íŠ¸ê°€ 반환ë˜ì§€ 않았습니다. + + + Scripting Failed + 스í¬ë¦½íŠ¸ 실패 + + + No script was returned when scripting as {0} + {0}ì˜ ê²°ê³¼ë¡œ ë°˜í™˜ëœ ìŠ¤í¬ë¦½íŠ¸ê°€ 없습니다. + + + + + + + Recent Connections + 최근 ì—°ê²° + + + Servers + 서버 + + + + + + + No Kernel + ì»¤ë„ ì•„ë‹˜ + + + Cannot run cells as no kernel has been configured + 커ë„ì´ êµ¬ì„±ë˜ì§€ 않았기 ë•Œë¬¸ì— ì…€ì„ ì‹¤í–‰í•  수 없습니다. + + + Error + 오류 + + + + + + + Azure Data Studio + Azure Data Studio + + + Start + 시작 + + + New connection + 새 ì—°ê²° + + + New query + 새 쿼리 + + + New notebook + 새 ë…¸íŠ¸ë¶ + + + Open file + íŒŒì¼ ì—´ê¸° + + + Open file + íŒŒì¼ ì—´ê¸° + + + Deploy + ë°°í¬ + + + Deploy SQL Server… + SQL Server ë°°í¬... + + + Recent + 최근 항목 + + + More... + ìžì„¸ížˆ... + + + No recent folders + 최근 í´ë” ì—†ìŒ + + + Help + ë„ì›€ë§ + + + Getting started + 시작 + + + Documentation + 문서 + + + Report issue or feature request + 문제 ë˜ëŠ” 기능 요청 ë³´ê³  + + + GitHub repository + GitHub 리í¬ì§€í† ë¦¬ + + + Release notes + 릴리스 ì •ë³´ + + + Show welcome page on startup + 시작 ì‹œ 시작 페ì´ì§€ 표시 + + + Customize + ì‚¬ìš©ìž ì§€ì • + + + Extensions + 확장 + + + Download extensions that you need, including the SQL Server Admin pack and more + SQL Server ê´€ë¦¬ìž íŒ© ë“±ì„ í¬í•¨í•˜ì—¬ 필요한 확장 다운로드 + + + Keyboard Shortcuts + 바로 가기 키(&&K) + + + Find your favorite commands and customize them + ì¦ê²¨ì°¾ëŠ” ëª…ë ¹ì„ ì°¾ì•„ ì‚¬ìš©ìž ì§€ì • + + + Color theme + 색 테마 + + + Make the editor and your code look the way you love + 편집기 ë° ì½”ë“œê°€ 좋아하는 ë°©ì‹ìœ¼ë¡œ 표시ë˜ê²Œ 만들기 + + + Learn + 알아보기 + + + Find and run all commands + 모든 명령 찾기 ë° ì‹¤í–‰ + + + Rapidly access and search commands from the Command Palette ({0}) + 명령 팔레트({0})ì—ì„œ 빠른 액세스 ë° ëª…ë ¹ 검색 + + + Discover what's new in the latest release + 최신 ë¦´ë¦¬ìŠ¤ì˜ ìƒˆë¡œìš´ 기능 알아보기 + + + New monthly blog posts each month showcasing our new features + 매월 새로운 ê¸°ëŠ¥ì„ ë³´ì—¬ì£¼ëŠ” 새로운 월간 블로그 게시물 + + + Follow us on Twitter + Twitterì—ì„œ 팔로우 + + + Keep up to date with how the community is using Azure Data Studio and to talk directly with the engineers. + 커뮤니티가 Azure Data Studio를 사용하고 엔지니어와 ì§ì ‘ 대화하는 ë°©ë²•ì„ ìµœì‹  ìƒíƒœë¡œ 유지할 수 있습니다. + + + + + + + succeeded + 성공 + + + failed + 실패 + + + in progress + 진행 중 + + + not started + 시작 ë˜ì§€ ì•ŠìŒ + + + canceled + ì·¨ì†Œë¨ + + + canceling + 취소하는 중 + + + + + + + Run + 실행 + + + Dispose Edit Failed With Error: + 오류와 함께 실패한 편집 ë‚´ìš©ì„ ì‚­ì œí•©ë‹ˆë‹¤. + + + Stop + 중지 + + + Show SQL Pane + SQL ì°½ 표시 + + + Close SQL Pane + SQL ì°½ 닫기 + + + + + + + Connect + ì—°ê²° + + + Disconnect + ì—°ê²° ëŠê¸° + + + Start + 시작 + + + New Session + 새 세션 + + + Pause + ì¼ì‹œ 중지 + + + Resume + 재개 + + + Stop + 중지 + + + Clear Data + ë°ì´í„° 초기화 + + + Auto Scroll: On + ìžë™ 스í¬ë¡¤ : 사용 + + + Auto Scroll: Off + ìžë™ 스í¬ë¡¤ : ë„기 + + + Toggle Collapsed Panel + ì¶•ì†Œëœ íŒ¨ë„ë¡œ 전환 + + + Edit Columns + ì—´ 편집 + + + Find Next String + ë‹¤ìŒ ë¬¸ìžì—´ 찾기 + + + Find Previous String + ì´ì „ 문ìžì—´ 찾기 + + + Launch Profiler + 프로파ì¼ëŸ¬ 시작 + + + Filter… + í•„í„°... + + + Clear Filter + í•„í„° 지우기 + + + + + + + Events (Filtered): {0}/{1} + ì´ë²¤íŠ¸(í•„í„°ë§ë¨): {0}/{1} + + + Events: {0} + ì´ë²¤íŠ¸: {0} + + + Event Count + ì´ë²¤íŠ¸ 수 + + + + + + + Save As CSV + CSVë¡œ 저장 + + + Save As JSON + JSON으로 저장 + + + Save As Excel + Excelë¡œ 저장 + + + Save As XML + XMLë¡œ 저장 + + + Save to file is not supported by the backing data source + 백업 ë°ì´í„° 소스ì—서는 파ì¼ë¡œ ì €ìž¥ì´ ì§€ì›ë˜ì§€ 않습니다. + + + Copy + 복사 + + + Copy With Headers + 복사(머리글 í¬í•¨) + + + Select All + ëª¨ë‘ ì„ íƒ + + + Copy + 복사 + + + Copy All + ëª¨ë‘ ë³µì‚¬ + + + Maximize + 최대화 + + + Restore + ë³µì› + + + Chart + 차트 + + + Visualizer + ì‹œê°í™” ë„우미 + + + + + + + The extension "{0}" is using sqlops module which has been replaced by azdata module, the sqlops module will be removed in a future release. + 확장 "{0}"ì€(는) azdata 모듈로 ëŒ€ì²´ëœ sqlops ëª¨ë“ˆì„ ì‚¬ìš©í•˜ê³  있으며 sqlops ëª¨ë“ˆì€ í–¥í›„ 릴리스ì—ì„œ ì œê±°ë  ì˜ˆì •ìž…ë‹ˆë‹¤. + + + + + + + Table header background color + í…Œì´ë¸” í—¤ë” ë°°ê²½ìƒ‰ + + + Table header foreground color + í…Œì´ë¸” í—¤ë” ì „ê²½ìƒ‰ + + + Disabled Input box background. + ìž…ë ¥ìƒìž ë°°ê²½ 사용안함. + + + Disabled Input box foreground. + 사용할 수 없는 ì „ë©´ ìž…ë ¥ ìƒìžìž…니다. + + + Button outline color when focused. + ì„ íƒí•œ ë²„íŠ¼ì˜ ìœ¤ê³½ì„  색. + + + Disabled checkbox foreground. + ë¹„í™œì„±í™”ëœ í™•ì¸ëž€ 전경입니다. + + + List/Table background color for the selected and focus item when the list/table is active + 목ë¡/표가 활성화ë˜ì—ˆì„ ë•Œ ì„ íƒí•˜ê³  í¬ì»¤ì‹±ëœ í•­ëª©ì— ëŒ€í•œ 목ë¡/í‘œ 배경색 + + + SQL Agent Table background color. + SQL ì—ì´ì „트 í…Œì´ë¸” 배경색입니다. + + + SQL Agent table cell background color. + SQL ì—ì´ì „트 í…Œì´ë¸” ì…€ 배경색입니다. + + + SQL Agent table hover background color. + SQL ì—ì´ì „트 í…Œì´ë¸” 호버 배경색. + + + SQL Agent heading background color. + SQL ì—ì´ì „트 머리글 배경색입니다. + + + SQL Agent table cell border color. + SQL ì—ì´ì „트 í…Œì´ë¸” ì…€ í…Œë‘리 색입니다. + + + Results messages error color. + ê²°ê³¼ 메시지 오류 색입니다. + + + + + + + Choose Results File + ê²°ê³¼ íŒŒì¼ ì„ íƒ + + + CSV (Comma delimited) + CSV (쉼표로 구분) + + + JSON + JSON + + + Excel Workbook + Excel 통합 문서 + + + XML + XML + + + Plain Text + ì¼ë°˜ í…스트 + + + Open file location + íŒŒì¼ ìœ„ì¹˜ 열기 + + + Open file + íŒŒì¼ ì—´ê¸° + + + + + + + Backup name + 백업 ì´ë¦„ + + + Recovery model + 복구 ëª¨ë¸ + + + Backup type + 백업 유형 + + + Backup files + 백업 íŒŒì¼ + + + Algorithm + 알고리즘 + + + Certificate or Asymmetric key + ì¸ì¦ì„œ ë˜ëŠ” 비대칭 키 + + + Media + 미디어 + + + Backup to the existing media set + 기존 미디어 ì„¸íŠ¸ì— ë°±ì—… + + + Backup to a new media set + 새 미디어 ì„¸íŠ¸ì— ë°±ì—… + + + Append to the existing backup set + 기존 백업 ì„¸íŠ¸ì— ì¶”ê°€ + + + Overwrite all existing backup sets + 모든 기존 백업 세트 ë®ì–´ì“°ê¸° + + + New media set name + 새 미디어 세트 ì´ë¦„ + + + New media set description + 새 미디어 세트 설명 + + + Perform checksum before writing to media + ë¯¸ë””ì–´ì— ì“°ê¸° ì „ì— ì²´í¬ì„¬ 수행 + + + Verify backup when finished + 완료ë˜ë©´ 백업 í™•ì¸ + + + Continue on error + 오류 ë°œìƒ ì‹œ ê³„ì† + + + Expiration + 만료 + + + Set backup retain days + 백업 유지 ì¼ìˆ˜ 설정 + + + Copy-only backup + 복사 ì „ìš© 백업 + + + Advanced Configuration + 고급 구성 + + + Compression + 압축 + + + Set backup compression + 백업 압축 설정 + + + Encryption + 암호화 + + + Transaction log + 트랜잭션 로그 + + + Truncate the transaction log + 트랜잭션 로그 잘ë¼ë‚´ê¸° + + + Backup the tail of the log + ë¹„ìƒ ë¡œê·¸ 백업 + + + Reliability + 안정성 + + + Media name is required + 미디어 ì´ë¦„ì´ í•„ìš”í•©ë‹ˆë‹¤. + + + No certificate or asymmetric key is available + 사용 가능한 ì¸ì¦ì„œ ë˜ëŠ” 비대칭 키가 없습니다 + + + Add a file + íŒŒì¼ ì¶”ê°€ + + + Remove files + íŒŒì¼ ì œê±° + + + Invalid input. Value must be greater than or equal 0. + ìž˜ëª»ëœ ìž…ë ¥ìž…ë‹ˆë‹¤. ê°’ì€ 0ê³¼ 같거나 ë” ì»¤ì•¼ 합니다. + + + Script + 스í¬ë¦½íŠ¸ + + + Backup + 백업 + + + Cancel + 취소 + + + Only backup to file is supported + íŒŒì¼ ë°±ì—…ë§Œ ì§€ì› + + + Backup file path is required + 백업 íŒŒì¼ ê²½ë¡œê°€ 필요합니다. + + + + + + + Results + ê²°ê³¼ + + + Messages + 메시지 + + + + + + + There is no data provider registered that can provide view data. + 등ë¡ëœ ë°ì´í„° 공급ìžê°€ ì—†ì´ ë³´ê¸° ë°ì´í„°ë¥¼ 제공할 수 없습니다. + + + Collapse All + ëª¨ë‘ ì¶•ì†Œ + + + + + + + Home + 홈 + + + + + + + The "{0}" section has invalid content. Please contact extension owner. + "{0}" ì„¹ì…˜ì— ìœ íš¨í•˜ì§€ ì•Šì€ ë‚´ìš©ì´ ìžˆìŠµë‹ˆë‹¤. 확장 소유ìžì—게 ë¬¸ì˜ í•˜ì‹œê¸° ë°”ëžë‹ˆë‹¤. + + + + + + + Jobs + ìž‘ì—… + + + Notebooks + ë…¸íŠ¸ë¶ + + + Alerts + 경고 + + + Proxies + 프ë¡ì‹œ + + + Operators + ìš´ì˜ìž + + + + + + + Loading + 로드 + + + + + + + SERVER DASHBOARD + 서버 대시보드 + + + + + + + DATABASE DASHBOARD + ë°ì´í„°ë² ì´ìŠ¤ 대시보드 + + + + + + + Edit + 편집 + + + Exit + ë내기 + + + Refresh + 새로 고침 + + + Toggle More + 추가 설정 + + + Delete Widget + 위젯 ì‚­ì œ + + + Click to unpin + í´ë¦­í•˜ì—¬ ê³ ì • í•´ì œ + + + Click to pin + 고정하려면 í´ë¦­ + + + Open installed features + ì„¤ì¹˜ëœ ê¸°ëŠ¥ë“¤ 열기 + + + Collapse + Collapse + + + Expand + 확장 + + + + + + + Steps + 단계 + + + + + + + StdIn: + StdIn: + + + + + + + Add code + 코드 추가 + + + Add text + í…스트 추가 + + + Create File + íŒŒì¼ ë§Œë“¤ê¸° + + + Could not display contents: {0} + ë‚´ìš©ì„ í‘œì‹œí•  수 없습니다. {0} + + + Please install the SQL Server 2019 extension to run cells. + ì…€ì„ ì‹¤í–‰í•˜ë ¤ë©´ SQL Server 2019 í™•ìž¥ì„ ì„¤ì¹˜í•˜ì„¸ìš”. + + + Install Extension + 확장 설치 + + + Code + 코드 + + + Text + í…스트 + + + Run Cells + ì…€ 실행 + + + Clear Results + ê²°ê³¼ 지우기 + + + < Previous + < ì´ì „ + + + Next > + ë‹¤ìŒ > + + + cell with URI {0} was not found in this model + ì´ ëª¨ë¸ì—ì„œ URI {0}ì´(ê°€) í¬í•¨ëœ ì…€ì„ ì°¾ì„ ìˆ˜ 없습니다. + + + Run Cells failed - See error in output of the currently selected cell for more information. + ì…€ 실행 실패 - ìžì„¸í•œ ë‚´ìš©ì€ í˜„ìž¬ ì„ íƒí•œ ì…€ì˜ ì¶œë ¥ 오류를 참조하세요. + + + + + + + Click on + í´ë¦­ + + + + Code + + 코드 + + + or + ë˜ëŠ” + + + + Text + + í…스트 + + + to add a code or text cell + 코드 ë˜ëŠ” í…스트 ì…€ 추가 + + + + + + + Database + ë°ì´í„°ë² ì´ìŠ¤ + + + Files and filegroups + íŒŒì¼ ë° íŒŒì¼ ê·¸ë£¹ + + + Full + ì „ì²´ + + + Differential + 차등 + + + Transaction Log + 트랜잭션 로그 + + + Disk + ë””ìŠ¤í¬ + + + Url + URL + + + Use the default server setting + 기본 서버 설정 사용 + + + Compress backup + 백업 압축 + + + Do not compress backup + 백업 압축 안 함 + + + Server Certificate + 서버 ì¸ì¦ì„œ + + + Asymmetric Key + 비대칭 키 + + + Backup Files + 백업 íŒŒì¼ + + + All Files + 모든 íŒŒì¼ + + + + + + + No connections found. + ì—°ê²°ì´ ì—†ìŠµë‹ˆë‹¤. + + + Add Connection + ì—°ê²° 추가 + + + + + + + Failed to change database + ë°ì´í„°ë² ì´ìŠ¤ë¥¼ 변경하지 못했습니다. + + + + + + + Name + ì´ë¦„ + + + Email Address + ì „ìž ë©”ì¼ ì£¼ì†Œ + + + Enabled + 사용 + + + + + + + Name + ì´ë¦„ + + + Last Occurrence + 마지막 ë°œìƒ + + + Enabled + 사용 + + + Delay Between Responses (in secs) + ì‘답 ê°„ 지연(ì´ˆ) + + + Category Name + 범주 ì´ë¦„ + + + + + + + Account Name + 계정 ì´ë¦„ + + + Credential Name + ìžê²© ì¦ëª… ì´ë¦„ + + + Description + 설명 + + + Enabled + 사용 + + + + + + + Unable to load dashboard properties + 대시보드 ì†ì„±ì„ 로드할 수 없습니다. + + + + + + + Search by name of type (a:, t:, v:, f:, or sp:) + 형ì‹ì˜ ì´ë¦„으로 검색 (a:, t:, v:, f: ë˜ëŠ” sp:) + + + Search databases + ë°ì´í„°ë² ì´ìŠ¤ 검색 + + + Unable to load objects + 개체를 로드할 수 없습니다. + + + Unable to load databases + ë°ì´í„°ë² ì´ìŠ¤ë¥¼ 로드할 수 없습니다 + + + + + + + Auto Refresh: OFF + ìžë™ 새로 고침: êº¼ì§ + + + Last Updated: {0} {1} + 최종 ì—…ë°ì´íŠ¸: {0} {1} + + + No results to show + 표시할 결과가 없습니다 + + + + + + + No {0}renderer could be found for output. It has the following MIME types: {1} + ì¶œë ¥ì˜ {0} ë Œë”러를 ì°¾ì„ ìˆ˜ 없습니다. MIME 형ì‹ì´ {1}입니다. + + + safe + 안전 + + + No component could be found for selector {0} + ì„ íƒê¸° {0}ì— ëŒ€í•œ 구성 요소를 ì°¾ì„ ìˆ˜ 없습니다. + + + Error rendering component: {0} + 오류 ë Œë”ë§ êµ¬ì„± 요소: {0} + + + + + + + Connected to + ì— ì—°ê²° + + + Disconnected + ì—°ê²° ëŠê¹€ + + + Unsaved Connections + 저장 ë˜ì§€ ì•Šì€ ì—°ê²° + + + + + + + Delete Row + í–‰ ì‚­ì œ + + + Revert Current Row + 현재 í–‰ ë˜ëŒë¦¬ê¸° + + + + + + + Step ID + 단계 ID + + + Step Name + 단계 ì´ë¦„ + + + Message + 메시지 + + + + + + + XML Showplan + XML 실행 ê³„íš + + + Results grid + ê²°ê³¼ í‘œ + + + + + + + Please select active cell and try again + 활성 ì…€ì„ ì„ íƒí•˜ê³  다시 ì‹œë„하세요. + + + Run cell + ì…€ 실행 + + + Cancel execution + 실행 취소 + + + Error on last run. Click to run again + 마지막 실행 ì‹œ 오류가 ë°œìƒí–ˆìŠµë‹ˆë‹¤. 다시 실행하려면 í´ë¦­í•˜ì„¸ìš”. + + + + + + + Add an account... + 계정 추가... + + + <Default> + < 기본 > + + + Loading... + 로드 중... + + + Server group + 서버 그룹 + + + <Default> + < 기본 > + + + Add new group... + 새 그룹 추가... + + + <Do not save> + <저장 안 함> + + + {0} is required. + {0}ì´(ê°€) 필요합니다. + + + {0} will be trimmed. + {0}ê°€ 잘립니다. + + + Remember password + 암호 기억 + + + Account + 계정 + + + Refresh account credentials + 계정 ìžê²© ì¦ëª… 새로 고침 + + + Azure AD tenant + Azure AD 테넌트 + + + Select Database Toggle Dropdown + ë°ì´í„°ë² ì´ìŠ¤ 토글 ë“œë¡­ë‹¤ìš´ì„ ì„ íƒí•˜ì‹­ì‹œì˜¤ + + + Name (optional) + ì´ë¦„(ì„ íƒ ì‚¬í•­) + + + Advanced... + 고급... + + + You must select an account + ê³„ì •ì„ ì„ íƒí•´ì•¼ 합니다. + + + + + + + Cancel + 취소 + + + The task is failed to cancel. + ìž‘ì—…ì„ ì·¨ì†Œí•˜ì§€ 못했습니다. + + + Script + 스í¬ë¦½íŠ¸ + + + + + + + Date Created: + 만든 날짜: + + + Notebook Error: + Notebook 오류: + + + Job Error: + ìž‘ì—… 오류: + + + Pinned + ê³ ì • + + + Recent Runs + 최근 실행 + + + Past Runs + 지난 실행 + + + + + + + No tree view with id '{0}' registered. + IDê°€ '{0}'ì¸ ë“±ë¡ëœ 트리 ë·°ê°€ 없습니다. + + + + + + + Loading... + 로드 중... + + + + + + + Dashboard Tabs ({0}) + 대시보드 탭 ({0}) + + + Id + ID + + + Title + 제목 + + + Description + 설명 + + + Dashboard Insights ({0}) + 대시보드 ì¸ì‚¬ì´íŠ¸({0}) + + + Id + ID + + + Name + ì´ë¦„ + + + When + 언제 + + + + + + + Chart + 차트 + + + + + + + Operation + ìž‘ì—… + + + Object + 개체 + + + Est Cost + ì˜ˆìƒ ë¹„ìš© + + + Est Subtree Cost + ì˜ˆìƒ í•˜ìœ„ 트리 비용 + + + Actual Rows + 실제 í–‰ + + + Est Rows + Est í–‰ + + + Actual Executions + 실제 실행 + + + Est CPU Cost + 예측 CPU 비용 + + + Est IO Cost + ì˜ˆìƒ ìž…ì¶œë ¥ 비용 + + + Parallel + 병렬 + + + Actual Rebinds + 실제 리바ì¸ë”© + + + Est Rebinds + Est 다시 ë°”ì¸ë”© + + + Actual Rewinds + 실제 ë˜ê°ê¸° + + + Est Rewinds + ì˜ˆìƒ ë˜ê°ê¸° 수 + + + Partitioned + 분할 + + + Top Operations + ìƒìœ„ ìž‘ì—… + + + + + + + Query Plan + 쿼리 실행 ê³„íš + + + + + + + Could not find component for type {0} + {0} 형ì‹ì˜ 구성 요소를 ì°¾ì„ ìˆ˜ 없습니다. + + + + + + + A NotebookProvider with valid providerId must be passed to this method + 유효한 providerIdê°€ 있는 NotebookProvider를 ì´ ë©”ì„œë“œì— ì „ë‹¬í•´ì•¼ 합니다. + + + + + + + A NotebookProvider with valid providerId must be passed to this method + 유효한 providerIdê°€ 있는 NotebookProvider를 ì´ ë©”ì„œë“œì— ì „ë‹¬í•´ì•¼ 합니다. + + + no notebook provider found + ë…¸íŠ¸ë¶ ê³µê¸‰ìžê°€ 없습니다. + + + No Manager found + 관리ìžë¥¼ ì°¾ì„ ìˆ˜ 없습니다. + + + Notebook Manager for notebook {0} does not have a server manager. Cannot perform operations on it + {0} 노트ë¶ì˜ ë…¸íŠ¸ë¶ ê´€ë¦¬ìžì— 서버 관리ìžê°€ 없습니다. ìž‘ì—…ì„ ìˆ˜í–‰í•  수 없습니다. + + + Notebook Manager for notebook {0} does not have a content manager. Cannot perform operations on it + {0} 노트ë¶ì˜ ë…¸íŠ¸ë¶ ê´€ë¦¬ìžì— 콘í…츠 관리ìžê°€ 없습니다. ìž‘ì—…ì„ ìˆ˜í–‰í•  수 없습니다. + + + Notebook Manager for notebook {0} does not have a session manager. Cannot perform operations on it + {0} 노트ë¶ì˜ ë…¸íŠ¸ë¶ ê´€ë¦¬ìžì— 세션 관리ìžê°€ 없습니다. ìž‘ì—…ì„ ìˆ˜í–‰í•  수 없습니다. + + + + + + + SQL kernel error + SQL ì»¤ë„ ì˜¤ë¥˜ + + + A connection must be chosen to run notebook cells + ë…¸íŠ¸ë¶ ì…€ì„ ì‹¤í–‰í•˜ë ¤ë©´ ì—°ê²°ì„ ì„ íƒí•´ì•¼ 합니다. + + + Displaying Top {0} rows. + ìƒìœ„ {0}ê°œ í–‰ì„ í‘œì‹œí•©ë‹ˆë‹¤. + + + + + + + Show Recommendations + 권장 사항 표시 + + + Install Extensions + 확장 설치 + + + + + + + Name + ì´ë¦„ + + + Last Run + 마지막 실행 + + + Next Run + ë‹¤ìŒ ì‹¤í–‰ + + + Enabled + 사용 + + + Status + ìƒíƒœ + + + Category + 범주 + + + Runnable + 실행 가능 + + + Schedule + ì¼ì • + + + Last Run Outcome + 마지막 실행 ê²°ê³¼ + + + Previous Runs + ì´ì „ 실행 + + + No Steps available for this job. + ì´ ìž‘ì—…ì— ì‚¬ìš©í•  수 있는 단계가 없습니다. + + + Error: + 오류: + + + + + + + Find + 찾기 + + + Find + 찾기 + + + Previous match + ì´ì „ ì¼ì¹˜ + + + Next match + ë‹¤ìŒ ì¼ì¹˜ 항목 + + + Close + 닫기 + + + Your search returned a large number of results, only the first 999 matches will be highlighted. + ë§Žì€ ìˆ˜ì˜ ê²€ìƒ‰ 결과가 반환ë˜ì—ˆìŠµë‹ˆë‹¤. ì²˜ìŒ 999ê°œì˜ ì¼ì¹˜ 항목만 ê°•ì¡° 표시ë©ë‹ˆë‹¤. + + + {0} of {1} + {1}ì˜ {0} + + + No Results + ê²°ê³¼ ì—†ìŒ + + + + + + + Run Query + 쿼리 실행 + + + + + + + Done + 완료 + + + Cancel + 취소 + + + Generate script + 스í¬ë¦½íŠ¸ ìƒì„± + + + Next + ë‹¤ìŒ + + + Previous + ì´ì „ + + + + + + + A server group with the same name already exists. + ê°™ì€ ì´ë¦„ì˜ ì„œë²„ ê·¸ë£¹ì´ ì´ë¯¸ 있습니다. + + + + + + + {0} is an unknown container. + {0}는 ì•Œ 수 없는 컨테ì´ë„ˆìž…니다. + + + + + + + Loading Error... + 불러오기 오류... + + + + + + + Failed + 실패 + + + Succeeded + 성공 + + + Retry + ìž¬ì‹œë„ + + + Cancelled + 취소 + + + In Progress + 진행 중 + + + Status Unknown + ìƒíƒœ ì•Œ 수 ì—†ìŒ + + + Executing + 실행 중 + + + Waiting for Thread + 스레드 대기 중 + + + Between Retries + 다시 ì‹œë„ ëŒ€ê¸° 중 + + + Idle + 유휴 ìƒíƒœ + + + Suspended + ì¼ì‹œ ì¤‘ì§€ë¨ + + + [Obsolete] + [ì´ì „ ë°©ì‹] + + + Yes + 예 + + + No + 아니요 + + + Not Scheduled + 예약ë˜ì§€ ì•ŠìŒ + + + Never Run + 실행 안 함 + + + + + + + Name + ì´ë¦„ + + + Target Database + ëŒ€ìƒ ë°ì´í„°ë² ì´ìŠ¤ + + + Last Run + 마지막 실행 + + + Next Run + ë‹¤ìŒ ì‹¤í–‰ + + + Status + ìƒíƒœ + + + Last Run Outcome + 마지막 실행 ê²°ê³¼ + + + Previous Runs + ì´ì „ 실행 + + + No Steps available for this job. + ì´ ìž‘ì—…ì— ì‚¬ìš©í•  수 있는 단계가 없습니다. + + + Error: + 오류: + + + Notebook Error: + Notebook 오류: + + + + + + + Home + 홈 + + + No connection information could be found for this dashboard + ì´ ëŒ€ì‹œë³´ë“œì— ëŒ€í•œ ì—°ê²° 정보를 ì°¾ì„ ìˆ˜ 없습니다. + + + + + + + Data + ë°ì´í„° + + + Connection + ì—°ê²° + + + Query + 쿼리 + + + Notebook + Notebook + + + SQL + SQL + + + Microsoft SQL Server + Microsoft SQL Server + + + Dashboard + 대시보드 + + + Profiler + 프로파ì¼ëŸ¬ + + + + + + + Close + 닫기 + + + + + + + Success + 성공 + + + Error + 오류 + + + Refresh + 새로 고침 + + + New Job + 새 ìž‘ì—… + + + Run + 실행 + + + : The job was successfully started. + ìž‘ì—…ì´ ì‹œìž‘ë˜ì—ˆìŠµë‹ˆë‹¤. + + + Stop + 중지 + + + : The job was successfully stopped. + : ìž‘ì—…ì´ ì„±ê³µì ìœ¼ë¡œ 중지 ë˜ì—ˆìŠµë‹ˆë‹¤. + + + Edit Job + ìž‘ì—… 편집 + + + Open + 열기 + + + Delete Job + ìž‘ì—… ì‚­ì œ + + + Are you sure you'd like to delete the job '{0}'? + ìž‘ì—… '{0}'ì„ ì •ë§ ì‚­ì œí•˜ê² ìŠµë‹ˆê¹Œ? + + + Could not delete job '{0}'. +Error: {1} + ìž‘ì—… '{0}' ì„ ì‚­ì œí•  수 없습니다. +오류: {1} + + + The job was successfully deleted + ìž‘ì—…ì´ ì‚­ì œë˜ì—ˆìŠµë‹ˆë‹¤. + + + New Step + 새로운 단계 + + + Delete Step + 단계 ì‚­ì œ + + + Are you sure you'd like to delete the step '{0}'? + '{0}' 단계를 삭제하시겠습니까? + + + Could not delete step '{0}'. +Error: {1} + '{0}' 단계를 삭제할 수 없습니다. +오류: {1} + + + The job step was successfully deleted + ìž‘ì—… 단계가 ì‚­ì œë˜ì—ˆìŠµë‹ˆë‹¤. + + + New Alert + 새 경고 + + + Edit Alert + 경고 편집 + + + Delete Alert + ì‚­ì œ 경고 + + + Cancel + 취소 + + + Are you sure you'd like to delete the alert '{0}'? + '{0}' 경고를 삭제하시겠습니까? + + + Could not delete alert '{0}'. +Error: {1} + '{0}' 경고를 삭제할 수 없습니다. +오류: {1} + + + The alert was successfully deleted + 경고가 ì‚­ì œë˜ì—ˆìŠµë‹ˆë‹¤. + + + New Operator + 새 ìš´ì˜ìž + + + Edit Operator + ì—°ì‚°ìž íŽ¸ì§‘ + + + Delete Operator + ì—°ì‚°ìž ì‚­ì œ + + + Are you sure you'd like to delete the operator '{0}'? + ì—°ì‚°ìž '{0}'ì„ (를) ì‚­ì œ 하시겠습니까? + + + Could not delete operator '{0}'. +Error: {1} + ì—°ì‚°ìž '{0}'를 삭제할 수 없습니다. +오류: {1} + + + The operator was deleted successfully + ì—°ì‚°ìžê°€ ì‚­ì œë˜ì—ˆìŠµë‹ˆë‹¤. + + + New Proxy + 새 프ë¡ì‹œ + + + Edit Proxy + 프ë¡ì‹œ 편집 + + + Delete Proxy + 프ë¡ì‹œ ì‚­ì œ + + + Are you sure you'd like to delete the proxy '{0}'? + ë‹¹ì‹ ì€ ì •ë§ë¡œ 프ë¡ì‹œ '{0}'를 삭제하시겠습니까? + + + Could not delete proxy '{0}'. +Error: {1} + 프ë¡ì‹œ '{0}'를 삭제할 수 없습니다. +오류: {1} + + + The proxy was deleted successfully + 프ë¡ì‹œê°€ ì‚­ì œë˜ì—ˆìŠµë‹ˆë‹¤. + + + New Notebook Job + 새 Notebook ìž‘ì—… + + + Edit + 편집 + + + Open Template Notebook + 템플릿 Notebook 열기 + + + Delete + ì‚­ì œ + + + Are you sure you'd like to delete the notebook '{0}'? + Notebook '{0}'ì„(를) 삭제하시겠습니까? + + + Could not delete notebook '{0}'. +Error: {1} + Notebook '{0}'ì„(를) 삭제할 수 없습니다. +오류: {1} + + + The notebook was successfully deleted + Notebookì„ ì‚­ì œí–ˆìŠµë‹ˆë‹¤. + + + Pin + ê³ ì • + + + Delete + ì‚­ì œ + + + Unpin + ê³ ì • í•´ì œ + + + Rename + ì´ë¦„ 바꾸기 + + + Open Latest Run + 최신 실행 열기 + + + + + + + Please select a connection to run cells for this kernel + ì´ ì»¤ë„ì— ëŒ€í•´ ì…€ì„ ì‹¤í–‰í•˜ë ¤ë©´ ì—°ê²°ì„ ì„ íƒí•˜ì„¸ìš”. + + + Failed to delete cell. + ì…€ì„ ì‚­ì œí•˜ì§€ 못했습니다. + + + Failed to change kernel. Kernel {0} will be used. Error was: {1} + 커ë„ì„ ë³€ê²½í•˜ì§€ 못했습니다. {0} 커ë„ì´ ì‚¬ìš©ë©ë‹ˆë‹¤. 오류: {1} + + + Failed to change kernel due to error: {0} + 오류로 ì¸í•´ 커ë„ì„ ë³€ê²½í•˜ì§€ 못했습니다. {0} + + + Changing context failed: {0} + 컨í…스트 변경 실패: {0} + + + Could not start session: {0} + ì„¸ì…˜ì„ ì‹œìž‘í•˜ì§€ 못했습니다. {0} + + + A client session error occurred when closing the notebook: {0} + 노트ë¶ì„ 닫는 ë™ì•ˆ í´ë¼ì´ì–¸íŠ¸ 세션 오류가 ë°œìƒí–ˆìŠµë‹ˆë‹¤. {0} + + + Can't find notebook manager for provider {0} + {0} 공급ìžì˜ ë…¸íŠ¸ë¶ ê´€ë¦¬ìžë¥¼ ì°¾ì„ ìˆ˜ 없습니다. + + + + + + + An error occurred while starting the notebook session + ë…¸íŠ¸ë¶ ì„¸ì…˜ì„ ì‹œìž‘í•˜ëŠ” ë™ì•ˆ 오류가 ë°œìƒí–ˆìŠµë‹ˆë‹¤. + + + Server did not start for unknown reason + ì•Œ 수 없는 ì´ìœ ë¡œ 서버가 시작하지 않았습니다. + + + Kernel {0} was not found. The default kernel will be used instead. + {0} 커ë„ì„ ì°¾ì„ ìˆ˜ 없습니다. 대신 기본 커ë„ì´ ì‚¬ìš©ë©ë‹ˆë‹¤. + + + + + + + Unknown component type. Must use ModelBuilder to create objects + ì•Œ 수 없는 구성 요소 유형입니다. ModelBuilder를 사용하여 개체를 만들어야 합니다. + + + The index {0} is invalid. + ì¸ë±ìŠ¤ {0}ì´(ê°€) 잘못ë˜ì—ˆìŠµë‹ˆë‹¤. + + + Unkown component configuration, must use ModelBuilder to create a configuration object + ì•Œ 수 없는 구성 요소 구성입니다. ModelBuilder를 사용하여 구성 개체를 만들어야 합니다. + + + + + + + Horizontal Bar + 가로 막대 + + + Bar + 막대 + + + Line + 줄 + + + Pie + ì›í˜• + + + Scatter + 분산형 + + + Time Series + 시계열 + + + Image + ì´ë¯¸ì§€ + + + Count + 개수 + + + Table + í…Œì´ë¸” + + + Doughnut + ë„넛형 + + + + + + + OK + í™•ì¸ + + + Clear + 지우기 + + + Cancel + 취소 + + + + + + + Cell execution cancelled + ì…€ ì‹¤í–‰ì´ ì·¨ì†Œë¨ + + + Query execution was canceled + 쿼리 ì‹¤í–‰ì´ ì·¨ì†Œë˜ì—ˆìŠµë‹ˆë‹¤. + + + The session for this notebook is not yet ready + ì´ ë…¸íŠ¸ë¶ì˜ ì„¸ì…˜ì´ ì•„ì§ ì¤€ë¹„ë˜ì§€ 않았습니다. + + + The session for this notebook will start momentarily + ì´ ë…¸íŠ¸ë¶ì˜ ì„¸ì…˜ì´ ê³§ 시작ë©ë‹ˆë‹¤. + + + No kernel is available for this notebook + ì´ ë…¸íŠ¸ë¶ì— 사용할 수 있는 커ë„ì´ ì—†ìŠµë‹ˆë‹¤. + + + + + + + Select Connection + ì—°ê²° ì„ íƒ + + + localhost + localhost + + + + + + + Data Direction + ë°ì´í„° ë°©í–¥ + + + Vertical + ìˆ˜ì§ + + + Horizontal + 가로 + + + Use column names as labels + ì—´ ì´ë¦„ì„ ë ˆì´ë¸”ë¡œ 사용 + + + Use first column as row label + í–‰ ë ˆì´ë¸”ë¡œ 첫 번째 ì—´ 사용 + + + Legend Position + 범례 위치 + + + Y Axis Label + Y 축 ë ˆì´ë¸” + + + Y Axis Minimum Value + Y 축 최소 ê°’ + + + Y Axis Maximum Value + Y 축 최댓값 + + + X Axis Label + X 축 ë ˆì´ë¸” + + + X Axis Minimum Value + X 축 최소 ê°’ + + + X Axis Maximum Value + X 축 최대값 + + + X Axis Minimum Date + X축 최소 날짜 + + + X Axis Maximum Date + X축 최대 날짜 + + + Data Type + ë°ì´í„° 타입 + + + Number + 번호 + + + Point + í¬ì¸íŠ¸ + + + Chart Type + 차트 종류 + + + Encoding + ì¸ì½”딩 + + + Image Format + ì´ë¯¸ì§€ í˜•ì‹ + + + + + + + Create Insight + ì¸ì‚¬ì´íŠ¸ 만들기 + + + Cannot create insight as the active editor is not a SQL Editor + 활성 편집기가 SQL 편집기가 아니기 ë•Œë¬¸ì— ì¸ì‚¬ì´íŠ¸ë¥¼ 만들 수 없습니다 + + + My-Widget + ë‚´ 위젯 + + + Copy as image + ì´ë¯¸ì§€ë¡œ 복사 + + + Could not find chart to save + 저장할 차트를 ì°¾ì„ ìˆ˜ 없습니다. + + + Save as image + ì´ë¯¸ì§€ë¡œ 저장 + + + PNG + Png + + + Saved Chart to path: {0} + ë‹¤ìŒ ê²½ë¡œì— ì°¨íŠ¸ê°€ 저장ë¨: {0} + + + + + + + Changing editor types on unsaved files is unsupported + 저장ë˜ì§€ ì•Šì€ íŒŒì¼ì— 대한 편집기 유형 ë³€ê²½ì€ ì§€ì›ë˜ì§€ 않습니다. + + + + + + + Table does not contain a valid image + í…Œì´ë¸”ì— ìœ íš¨í•œ ì´ë¯¸ì§€ê°€ í¬í•¨ë˜ì–´ 있지 않습니다. + + + + + + + Series {0} + 시리즈 {0} diff --git a/resources/xlf/pt-br/azurecore.pt-BR.xlf b/resources/xlf/pt-br/azurecore.pt-BR.xlf index 3d28075e9e7d..016e94c00426 100644 --- a/resources/xlf/pt-br/azurecore.pt-BR.xlf +++ b/resources/xlf/pt-br/azurecore.pt-BR.xlf @@ -200,4 +200,20 @@ + + + + SQL Managed Instances + Instâncias Gerenciadas do SQL + + + + + + + Azure Database for PostgreSQL Servers + Banco de Dados do Azure para Servidores PostgreSQL + + + \ No newline at end of file diff --git a/resources/xlf/pt-br/big-data-cluster.pt-BR.xlf b/resources/xlf/pt-br/big-data-cluster.pt-BR.xlf index a3f86dd9f743..870df1cc4a0e 100644 --- a/resources/xlf/pt-br/big-data-cluster.pt-BR.xlf +++ b/resources/xlf/pt-br/big-data-cluster.pt-BR.xlf @@ -38,6 +38,14 @@ Delete Mount Excluir a Montagem + + Big Data Cluster + Cluster de Big Data + + + Ignore SSL verification errors against SQL Server Big Data Cluster endpoints such as HDFS, Spark, and Controller if true + Ignorar os erros de verificação do SSL em relação aos pontos de extremidade do Cluster de Big Data do SQL Server, como o HDFS, o Spark e o Controlador, se for true + @@ -58,26 +66,86 @@ Deleting Excluindo - - Waiting For Deletion - Aguardando a Exclusão - Deleted Excluído + + Applying Upgrade + Aplicando a Atualização + Upgrading Atualizando - - Waiting For Upgrade - Aguardando o Upgrade + + Applying Managed Upgrade + Aplicando a Atualização Gerenciada + + + Managed Upgrading + Atualização gerenciada + + + Rollback + reverter + + + Rollback In Progress + Reversão em andamento + + + Rollback Complete + Reversão Concluída Error Erro + + Creating Secrets + Criando segredos + + + Waiting For Secrets + Esperando os Segredos + + + Creating Groups + Criando Grupos + + + Waiting For Groups + Aguardando Grupos + + + Creating Resources + Criando Recursos + + + Waiting For Resources + Esperando Recursos + + + Creating Kerberos Delegation Setup + Criando a Configuração de Delegação do Kerberos + + + Waiting For Kerberos Delegation Setup + Esperando a Configuração da Delegação do Kerberos + + + Waiting For Deletion + Aguardando a Exclusão + + + Waiting For Upgrade + Aguardando o Upgrade + + + Upgrade Paused + Atualização em Pausa + Running Em execução @@ -162,6 +230,78 @@ Unhealthy Não íntegro + + Unexpected error retrieving BDC Endpoints: {0} + Erro inesperado ao recuperar os Pontos de Extremidade do BDC: {0} + + + + + + + Basic + Básico + + + Windows Authentication + Autenticação do Windows + + + Login to controller failed + Falha ao entrar no controlador + + + Login to controller failed: {0} + Falha ao entrar no controlador: {0} + + + Username is required + O nome de usuário é necessário + + + Password is required + A senha é necessária + + + url + URL + + + username + Nome do Usuário + + + password + Senha + + + Cluster Management URL + URL de gerenciamento de cluster + + + Authentication type + Tipo de autenticação + + + Username + Nome do Usuário + + + Password + Senha + + + Cluster Connection + Conexão de cluster + + + OK + OK + + + Cancel + Cancelar + @@ -200,6 +340,22 @@ + + + + Connect to Controller (preview) + Conecte-se ao Controlador (versão prévia) + + + + + + + View Details + Ver os Detalhes + + + @@ -207,8 +363,8 @@ As informações do ponto de extremidade do controlador não foram encontradas - Big Data Cluster Dashboard - - Painel do Cluster de Big Sata – + Big Data Cluster Dashboard (preview) - + Painel de Cluster de Big Data (versão prévia) – Yes @@ -263,8 +419,8 @@ A senha é necessária - Add New Controller - Adicionar novo controlador + Add New Controller (preview) + Adicionar um Novo Controlador (versão prévia) url @@ -283,8 +439,8 @@ Lembrar senha - URL - URL + Cluster Management URL + URL de gerenciamento de cluster Authentication type @@ -319,7 +475,7 @@ Solucionar problemas - Big data cluster overview + Big Data Cluster overview Visão geral do cluster de Big data @@ -362,18 +518,18 @@ Metrics and Logs Métricas e logs - - Metrics - Métricas + + Node Metrics + Métricas de nó + + + SQL Metrics + Métricas do SQL Logs Logs - - View Details - Ver detalhes - @@ -422,31 +578,35 @@ Endpoint Ponto de Extremidade - - View Details - Ver os Detalhes + + Unexpected error retrieving BDC Endpoints: {0} + Erro inesperado ao recuperar os Pontos de Extremidade BDC: {0} + + + The dashboard requires a connection. Please click retry to enter your credentials. + O painel requer uma conexão. Clique em tentar novamente para inserir suas credenciais. + + + Unexpected error occurred: {0} + Erro inesperado: {0} Copy Copiar + + Endpoint '{0}' copied to clipboard + Ponto de extremidade '{0}' copiado para a área de transferência + - - Basic - Básico - - - Windows Authentication - Autenticação do Windows - Mount Configuration Configuração da montagem - + HDFS Path Caminho do HDFS @@ -454,22 +614,6 @@ Bad formatting of credentials at {0} Formatação incorreta de credenciais em {0} - - Login to controller failed - Falha ao entrar no controlador - - - Login to controller failed: {0} - Falha ao entrar no controlador: {0} - - - Username is required - O nome de usuário é necessário - - - Password is required - A senha é necessária - Mounting HDFS folder on path {0} Montando a pasta do HDFS no caminho {0} @@ -494,58 +638,30 @@ Unknown error occurred during the mount process Erro desconhecido durante o processo de montagem - - url - URL - - - username - Nome do Usuário - - - password - Senha - - - URL - URL - - - Authentication type - Tipo de autenticação - - - Username - Nome do Usuário - - - Password - Senha - - - Cluster Connection - Conexão de cluster - - - OK - OK - - - Cancel - Cancelar - - Mount HDFS Folder - Montar a Pasta do HDFS + Mount HDFS Folder (preview) + Montar a Pasta do HDFS (versão prévia) + + + Path to a new (non-existing) directory which you want to associate with the mount + Caminho para um novo diretório (não existente) que você deseja associar com a montagem - + Remote URI URI remoto - + + The URI to the remote data source. Example for ADLS: abfs://fs@saccount.dfs.core.windows.net/ + O URI da fonte de dados remota. Exemplo para o ADLS: abfs://fs@saccount.dfs.core.windows.net/ + + Credentials Credenciais + + Mount credentials for authentication to remote data source for reads + Montar as credenciais para autenticação na fonte de dados remota para leituras + Refresh Mount Atualizar a Montagem diff --git a/resources/xlf/pt-br/sql.pt-BR.xlf b/resources/xlf/pt-br/sql.pt-BR.xlf index ed777f268b2d..ea80014db331 100644 --- a/resources/xlf/pt-br/sql.pt-BR.xlf +++ b/resources/xlf/pt-br/sql.pt-BR.xlf @@ -1,14 +1,5573 @@  - + - - SQL Language Basics - Noções Básicas Sobre a Linguagem SQL + + Copying images is not supported + Não há suporte para copiar imagens - - Provides syntax highlighting and bracket matching in SQL files. - Fornece realce de sintaxe e correspondência de colchetes nos arquivos SQL. + + + + + + Get Started + Iniciar + + + Show Getting Started + Mostrar como começar + + + Getting &&Started + I&&ntrodução + && denotes a mnemonic + + + + + + + QueryHistory + QueryHistory + + + Whether Query History capture is enabled. If false queries executed will not be captured. + Se a captura do Histórico de Consultas está habilitada. Se for false, as consultas executadas não serão capturadas. + + + View + Visão + + + Query History + Histórico de Consultas + + + &&Query History + &&Histórico de Consultas + && denotes a mnemonic + + + + + + + Connecting: {0} + Conectando: {0} + + + Running command: {0} + Comando em execução: {0} + + + Opening new query: {0} + Abrindo a nova consulta: {0} + + + Cannot connect as no server information was provided + Não é possível se conectar, pois não foi fornecida nenhuma informação do servidor + + + Could not open URL due to error {0} + Não foi possível abrir a URL devido ao erro {0} + + + This will connect to server {0} + Isso será conectado ao servidor {0} + + + Are you sure you want to connect? + Tem certeza de que quer se conectar? + + + &&Open + &&Abrir + + + Connecting query file + Conectando o arquivo de consulta + + + + + + + Error + Erro + + + Warning + Aviso + + + Info + Informação + + + + + + + Saving results into different format disabled for this data provider. + O salvamento de resultados em diferentes formatos está desabilitado para este provedor de dados. + + + Cannot serialize data as no provider has been registered + Não foi possível serializar os dados, pois não foi registrado nenhum provedor + + + Serialization failed with an unknown error + Falha na serialização com um erro desconhecido + + + + + + + Connection is required in order to interact with adminservice + Conexão é necessário para interagir com adminservice + + + No Handler Registered + Nenhum manipulador registrado + + + + + + + Select a file + Selecione um arquivo + + + + + + + Disconnect + Desconectar-se + + + Refresh + Atualizar + + + + + + + Results Grid and Messages + Grade de resultados e mensagens + + + Controls the font family. + Controla a família de fontes. + + + Controls the font weight. + Controles de espessura da fonte. + + + Controls the font size in pixels. + Controla o tamanho da fonte em pixels. + + + Controls the letter spacing in pixels. + Controla o espaçamento de letras, em pixels + + + Controls the row height in pixels + Controle da altura da linha em pixels + + + Controls the cell padding in pixels + Controla o enchimento em pixels da célula + + + Auto size the columns width on inital results. Could have performance problems with large number of columns or large cells + Dimensionar automaticamente a largura de colunas nos resultados iniciais. Pode haver problemas de desempenho com um grande número de colunas ou com células grandes + + + The maximum width in pixels for auto-sized columns + A largura máxima em pixels para colunas dimensionadas automaticamente + + + + + + + {0} in progress tasks + {0} tarefas em andamento + + + View + Visão + + + Tasks + Tarefas + + + &&Tasks + &&Tarefas + && denotes a mnemonic + + + + + + + Connections + Conexões + + + View + Visão + + + Database Connections + Conexões de banco de dados + + + data source connections + conexões de fonte de dados + + + data source groups + grupos de fonte de dados + + + Startup Configuration + Configuração de inicialização + + + True for the Servers view to be shown on launch of Azure Data Studio default; false if the last opened view should be shown + Verdadeiro para a exibição Servidores a ser mostrada na inicialização do padrão do Azure Data Studio; falso se a última visualização aberta deve ser mostrada + + + + + + + The maximum number of recently used connections to store in the connection list. + O número máximo de conexões usados recentemente para armazenar na lista de conexão. + + + Default SQL Engine to use. This drives default language provider in .sql files and the default to use when creating a new connection. Valid option is currently MSSQL + Mecanismo de SQL padrão para uso. Isso leva o provedor de idioma padrão em arquivos .sql e o padrão para usar ao criar uma nova conexão. Opção válida atualmente é MSSQL + + + Attempt to parse the contents of the clipboard when the connection dialog is opened or a paste is performed. + Tentativa de analisar o conteúdo da área de transferência quando a caixa de diálogo conexão é aberta ou a cópia é executada. + + + + + + + Server Group color palette used in the Object Explorer viewlet. + Paleta de cores do grupo de servidores usada no explorador de objetos. + + + Auto-expand Server Groups in the Object Explorer viewlet. + Expanda automaticamente grupos de servidores no explorador de objetos. + + + + + + + Preview Features + Funcionalidades em Versão Prévia + + + Enable unreleased preview features + Habilitar funcionalidades de versão prévia não liberadas + + + Show connect dialog on startup + Mostrar caixa de diálogo de conexão na inicialização + + + Obsolete API Notification + Notificação de API obsoleta + + + Enable/disable obsolete API usage notification + Habilitar/desabilitar a notificação de uso de API obsoleta + + + + + + + Problems + Problemas + + + + + + + Identifier of the account type + Identificador do tipo de conta + + + (Optional) Icon which is used to represent the accpunt in the UI. Either a file path or a themable configuration + (Opcional) Ãcone que é usado para representar o accpunt na interface do usuário. Um caminho de arquivo ou uma configuração tematizáveis + + + Icon path when a light theme is used + Caminho do ícone quando um tema leve é usado + + + Icon path when a dark theme is used + Caminho de ícone quando é usado um tema escuro + + + Contributes icons to account provider. + Contribui ícones para provedor de conta. + + + + + + + Indicates data property of a data set for a chart. + Indica a propriedade de um conjunto de dados para um gráfico. + + + + + + + Minimum value of the y axis + Valor mínimo do eixo y + + + Maximum value of the y axis + Valor máximo do eixo y + + + Label for the y axis + Rótulo para o eixo y + + + Minimum value of the x axis + Valor mínimo do eixo x + + + Maximum value of the x axis + Valor máximo do eixo x + + + Label for the x axis + Rótulo para o eixo x + + + + + + + Displays the results in a simple table + Exibe os resultados em uma tabela simples + + + + + + + Displays an image, for example one returned by an R query using ggplot2 + Exibe uma imagem, por exemplo, um retornado por uma consulta R usando o ggplot2 + + + What format is expected - is this a JPEG, PNG or other format? + Qual o formato que é esperado - este é um JPEG, PNG ou outro formato? + + + Is this encoded as hex, base64 or some other format? + Isso é codificado como hex, base64 ou algum outro formato? + + + + + + + For each column in a resultset, displays the value in row 0 as a count followed by the column name. Supports '1 Healthy', '3 Unhealthy' for example, where 'Healthy' is the column name and 1 is the value in row 1 cell 1 + Para cada coluna em um conjunto de resultados, exibe o valor na linha 0 como uma contagem seguida pelo nome da coluna. Dá suporte para '1 Ãntegro', '3 Não Ãntegro', por exemplo, em que 'Ãntegro' é o nome da coluna e 1 é o valor na célula 1 da linha 1 + + + + + + + Manage + Gerenciar + + + Dashboard + Painel + + + + + + + The webview that will be displayed in this tab. + O webview que será exibido nesta aba. + + + + + + + The controlhost that will be displayed in this tab. + O host de controle que será exibido nessa guia. + + + + + + + The list of widgets that will be displayed in this tab. + A lista de widgets que serão exibidos nesta aba. + + + The list of widgets is expected inside widgets-container for extension. + A lista de widgets é esperada dentro de contêiner de widgets para a extensão. + + + + + + + The list of widgets or webviews that will be displayed in this tab. + A lista de widgets ou webviews que serão exibidos nesta aba. + + + widgets or webviews are expected inside widgets-container for extension. + Widgets ou webviews são esperados dentro da extensão de widgets-container. + + + + + + + Unique identifier for this container. + Identificador exclusivo para este recipiente. + + + The container that will be displayed in the tab. + O contêiner que será exibido na guia. + + + Contributes a single or multiple dashboard containers for users to add to their dashboard. + Contribui um único ou vários recipientes de painel para os usuários para adicionar ao seu painel de controle. + + + No id in dashboard container specified for extension. + Nenhuma identificação no contêiner do Dashboard especificado para extensão. + + + No container in dashboard container specified for extension. + Nenhum container especificado no dashboard de extensões. + + + Exactly 1 dashboard container must be defined per space. + Exatamente 1 contêiner do Dashboard deve ser definido por espaço. + + + Unknown container type defines in dashboard container for extension. + Tipo de container desconhecido definido no dashboard para extensões. + + + + + + + The model-backed view that will be displayed in this tab. + O modo de exibição de modelo-backed que será exibido nesta guia. + + + + + + + Unique identifier for this nav section. Will be passed to the extension for any requests. + Identificador exclusivo para esta seção de nav. Será passado para a extensão para quaisquer solicitações. + + + (Optional) Icon which is used to represent this nav section in the UI. Either a file path or a themeable configuration + (Opcional) Ãcone usado para representar essa seção de navegação na interface do usuário. Um caminho de arquivo ou uma configuração com tema + + + Icon path when a light theme is used + Caminho do ícone quando um tema leve é usado + + + Icon path when a dark theme is used + Caminho de ícone quando é usado um tema escuro + + + Title of the nav section to show the user. + Título da seção de nav para mostrar ao usuário. + + + The container that will be displayed in this nav section. + O contêiner que será exibido nesta seção de nav. + + + The list of dashboard containers that will be displayed in this navigation section. + A lista de recipientes de painel de controle que será exibido nesta seção de navegação. + + + property `icon` can be omitted or must be either a string or a literal like `{dark, light}` + Propriedade 'ícone' pode ser omitida ou deve ser uma sequência de caracteres ou um literal como '{escuro, luz}' + + + No title in nav section specified for extension. + Nenhum título na seção NAV especificado para extensão. + + + No container in nav section specified for extension. + Não foi especificado nenhum contêiner na seção nav para a extensão. + + + Exactly 1 dashboard container must be defined per space. + Exatamente 1 contêiner do Dashboard deve ser definido por espaço. + + + NAV_SECTION within NAV_SECTION is an invalid container for extension. + NAV_SECTION dentro NAV_SECTION é um contêiner inválido para a extensão. + + + + + + + Backup + Backup + + + + + + + Unique identifier for this tab. Will be passed to the extension for any requests. + Identificador exclusivo para este guia. Será passado para a extensão para quaisquer solicitações. + + + Title of the tab to show the user. + Título da guia para mostrar ao usuário. + + + Description of this tab that will be shown to the user. + Descrição desta página que será mostrado ao usuário. + + + Condition which must be true to show this item + Condição deve ser verdadeira para mostrar este item + + + Defines the connection types this tab is compatible with. Defaults to 'MSSQL' if not set + Define os tipos de conexão com os quais essa guia é compatível. O padrão será 'MSSQL' se essa opção não for definida + + + The container that will be displayed in this tab. + O contêiner que será exibido nesta aba. + + + Whether or not this tab should always be shown or only when the user adds it. + Se essa guia deve ou não ser sempre exibida ou apenas quando o usuário a adiciona. + + + Whether or not this tab should be used as the Home tab for a connection type. + Esta guia pode ou não ser utilizada como guia de início para uma determinado tipo de conexão. + + + Contributes a single or multiple tabs for users to add to their dashboard. + Contribuir com uma única ou várias guias para que os usuários possam adicionar ao painel de controle. + + + No title specified for extension. + Nenhum título especificado para extensão. + + + No description specified to show. + Nenhuma descrição especificada para mostrar. + + + No container specified for extension. + Nenhum container especificado para a extensão. + + + Exactly 1 dashboard container must be defined per space + Exatamente 1 contêiner de painel deve ser definido por espaço + + + + + + + Restore + Restaurar + + + Restore + Restaurar + + + + + + + Cannot expand as the required connection provider '{0}' was not found + Não é possível expandir, pois o provedor de conexão necessário '{0}' não foi encontrado + + + User canceled + Usuário cancelado + + + Firewall dialog canceled + Diálogo do Firewall cancelado + + + + + + + 1 or more tasks are in progress. Are you sure you want to quit? + 1 ou mais tarefas estão em andamento. Tem certeza que deseja sair? + + + Yes + Sim + + + No + Não + + + + + + + Connection is required in order to interact with JobManagementService + A conexão é necessária para interagir com JobManagementService + + + No Handler Registered + Nenhum manipulador registrado + + + + + + + An error occured while loading the file browser. + Ocorreu um erro ao carregar o navegador de arquivos. + + + File browser error + Erro do navegador de arquivo + + + + + + + Notebook Editor + Editor de Notebooks + + + New Notebook + Novo Notebook + + + New Notebook + Novo Notebook + + + SQL kernel: stop Notebook execution when error occurs in a cell. + Kernel do SQL: parar a execução do Notebook quando ocorrer erro em uma célula. + + + + + + + Script as Create + Script de criação + + + Script as Drop + Script apagado + + + Select Top 1000 + Selecione Top 1000 + + + Script as Execute + Script executar como + + + Script as Alter + Script como Alter + + + Edit Data + Editar dados + + + Select Top 1000 + Selecione Top 1000 + + + Script as Create + Script de criação + + + Script as Execute + Script executar como + + + Script as Alter + Script como Alter + + + Script as Drop + Script apagado + + + Refresh + Atualizar + + + + + + + Connection error + Erro de Conexão + + + Connection failed due to Kerberos error. + Conexão falhou devido a um erro de Kerberos. + + + Help configuring Kerberos is available at {0} + Ajuda para configurar o Kerberos está disponível em {0} + + + If you have previously connected you may need to re-run kinit. + Se você conectou previamente você pode precisar de re-Run kinit. + + + + + + + Refresh account was canceled by the user + A conta de atualização foi cancelada pelo usuário + + + + + + + Specifies view templates + Especifica os templates de view + + + Specifies session templates + Especifica modelos de sessão + + + Profiler Filters + Filtros do profiler + + + + + + + Toggle Query History + Ativar/desativar o Histórico de Consultas + + + Delete + Excluir + + + Clear All History + Limpar Todo o Histórico + + + Open Query + Abrir a Consulta + + + Run Query + Executar consulta + + + Toggle Query History capture + Ativar/desativar a captura de Histórico de Consultas + + + Pause Query History Capture + Pausar a Captura de Histórico de Consultas + + + Start Query History Capture + Iniciar a captura do histórico de consultas + + + + + + + Preview features are required in order for extensions to be fully supported and for some actions to be available. Would you like to enable preview features? + Funcionalidades beta são necessárias para que extensões sejam totalmente compatíveis e para que algumas ações fiquem disponíveis. Você gostaria de habilitar as funcionalidades beta? + + + Yes + Sim + + + No + Não + + + No, don't show again + Não, não mostrar novamente + + + + + + + Commit row failed: + A linha de confirmação falhou: + + + Started executing query "{0}" + Começou a executar consulta "{0}" + + + Update cell failed: + Falha na célula de atualização: + + + + + + + Failed to create Object Explorer session + Falha ao criar sessão do Object Explorer + + + Multiple errors: + Múltiplos erros: + + + + + + + No URI was passed when creating a notebook manager + Nenhum URI foi passado ao criar um gerenciador de notebooks + + + Notebook provider does not exist + O provedor de notebooks não existe + + + + + + + Query Results + Resultados da Consulta + + + Query Editor + Editor de consultas + + + New Query + Nova consulta + + + [Optional] When true, column headers are included when saving results as CSV + [Opcional] Quando verdadeiro, os cabeçalhos de coluna são incluídos ao salvar os resultados como CSV + + + [Optional] The custom delimiter to use between values when saving as CSV + [Opcional] Delimitador personalizado para ser usado entre valores ao ser salvo como CSV + + + [Optional] Character(s) used for seperating rows when saving results as CSV + [Opcional] Caractere (s) usado para separar linhas ao salvar os resultados como CSV + + + [Optional] Character used for enclosing text fields when saving results as CSV + [Opcional] Caractere usado para delimitador de campos de texto ao salvar os resultados como CSV + + + [Optional] File encoding used when saving results as CSV + [Opcional] Arquivo de codificação usado ao salvar os resultados como CSV + + + Enable results streaming; contains few minor visual issues + Habilitar streaming de resultados; contém alguns problemas visuais menores + + + [Optional] When true, XML output will be formatted when saving results as XML + [Opcional] Quando verdadeiro, a saída XML será formatada ao salvar resultados como XML + + + [Optional] File encoding used when saving results as XML + [Opcional] Arquivo de codificação usado ao salvar os resultados como XML + + + [Optional] Configuration options for copying results from the Results View + [Opcional] Opções de configuração para copiar os resultados da exibição de resultados + + + [Optional] Configuration options for copying multi-line results from the Results View + [Opcional] Opções de configuração para copiar os resultados de várias linhas do modo de exibição de resultados + + + [Optional] Should execution time be shown for individual batches + [Opcional] Tempo de execução deve ser mostrado para lotes individuais + + + [Optional] the default chart type to use when opening Chart Viewer from a Query Results + [Opcional] o tipo de gráfico padrão para usar ao abrir o visualizador gráfico dos resultados da consulta + + + Tab coloring will be disabled + Coloração de guia será desativada + + + The top border of each editor tab will be colored to match the relevant server group + Borda superior de cada guia editor irá ser colorida para combinar com o grupo de servidor relevantes + + + Each editor tab's background color will match the relevant server group + Cor de fundo de cada editor do guia irá corresponder o grupo relevante no servidor + + + Controls how to color tabs based on the server group of their active connection + Controla como colorir as abas com base no grupo de servidores de sua conexão ativa + + + Controls whether to show the connection info for a tab in the title. + Controla se deseja mostrar a informação de conexão para uma guia no título. + + + Prompt to save generated SQL files + Pedir para salvar arquivos SQL gerados + + + Should IntelliSense be enabled + O IntelliSense deve ser habilitado + + + Should IntelliSense error checking be enabled + Verificação de erros de IntelliSense devem ser habilitados? + + + Should IntelliSense suggestions be enabled + Sugestões de IntelliSense devem ser habilitados? + + + Should IntelliSense quick info be enabled + Devem ser habilitada para IntelliSense informações rápidas + + + Should IntelliSense suggestions be lowercase + IntelliSense sugestões devem ser em letras minúsculas + + + Maximum number of rows to return before the server stops processing your query. + Número máximo de linhas a serem retornadas antes que o servidor pare de processar sua consulta. + + + Maximum size of text and ntext data returned from a SELECT statement + Tamanho máximo de dados text e ntext retornados de uma instrução SELECT + + + An execution time-out of 0 indicates an unlimited wait (no time-out) + Um tempo limite de execução de 0 indica uma espera ilimitada (sem tempo limite) + + + Enable SET NOCOUNT option + Ativar opção SET NOCOUNT + + + Enable SET NOEXEC option + Habilitar a opção SET NOEXEC + + + Enable SET PARSEONLY option + Habilitar a opção SET PARSEONLY + + + Enable SET ARITHABORT option + Habilitar a opção SET ARITHABORT + + + Enable SET STATISTICS TIME option + Ative a opção SET STATISTICS TIME + + + Enable SET STATISTICS IO option + Habilitar a opção SET STATISTICS IO + + + Enable SET XACT_ABORT ON option + Habilitar a opção SET XACT_ABORT ON + + + Enable SET TRANSACTION ISOLATION LEVEL option + Habilitar a opção SET TRANSACTION ISOLATION LEVEL + + + Enable SET DEADLOCK_PRIORITY option + Habilitar a opção SET DEADLOCK_PRIORITY + + + Enable SET LOCK TIMEOUT option (in milliseconds) + Habilitar a opção SET LOCK TIMEOUT (em milissegundos) + + + Enable SET QUERY_GOVERNOR_COST_LIMIT + Habilitar SET QUERY_GOVERNOR_COST_LIMIT + + + Enable SET ANSI_DEFAULTS + Habilitar SET ANSI_DEFAULTS + + + Enable SET QUOTED_IDENTIFIER + Habilitar SET QUOTED_IDENTIFIER + + + Enable SET ANSI_NULL_DFLT_ON + Habilitar SET ANSI_NULL_DFLT_ON + + + Enable SET IMPLICIT_TRANSACTIONS + Ativar SET IMPLICIT_TRANSACTIONS + + + Enable SET CURSOR_CLOSE_ON_COMMIT + Habilitar SET CURSOR_CLOSE_ON_COMMIT + + + Enable SET ANSI_PADDING + Habilitar SET ANSI_PADDING + + + Enable SET ANSI_WARNINGS + Habilitar SET ANSI_WARNINGS + + + Enable SET ANSI_NULLS + Habilitar SET ANSI_NULLS + + + Set keybinding workbench.action.query.shortcut{0} to run the shortcut text as a procedure call. Any selected text in the query editor will be passed as a parameter + Defina keybinding workbench.action.query.shortcut {0} para executar o texto do atalho como uma chamada de procedimento. Qualquer texto selecionado no editor de consultas será passado como um parâmetro + + + + + + + Common id for the provider + Id comum para o provider + + + Display Name for the provider + Nome de exibição do provedor + + + Icon path for the server type + Caminho do ícone para o tipo de servidor + + + Options for connection + Opções de conexão + + + + + + + OK + OK + + + Close + Fechar + + + Copy details + Copiar detalhes + + + + + + + Add server group + Adicionar grupo de servidores + + + Edit server group + Editar grupo de servidores + + + + + + + Error adding account + Erro ao adicionar conta + + + Firewall rule error + Erro de regra de firewall + + + + + + + Some of the loaded extensions are using obsolete APIs, please find the detailed information in the Console tab of Developer Tools window + Algumas das extensões carregadas estão usando APIs obsoletas. Encontre as informações detalhadas na guia Console da janela Ferramentas do Desenvolvedor + + + Don't Show Again + Não mostrar novamente + + + + + + + Toggle Tasks + Alternar tarefas + + + + + + + Show Connections + Mostrar conexões + + + Servers + Servidores + + + + + + + Identifier of the view. Use this to register a data provider through `vscode.window.registerTreeDataProviderForView` API. Also to trigger activating your extension by registering `onView:${id}` event to `activationEvents`. + Identificador da visualização. Use isto para registrar um provedor de dados através da API 'vscode.window.registerTreeDataProviderForView'. Também para ativar sua extensão registrando o evento 'onView: ${id}' para 'activationEvents'. + + + The human-readable name of the view. Will be shown + O nome legível da visualização. Será mostrado + + + Condition which must be true to show this view + Condição que deve ser verdadeira para mostrar esta visualização + + + Contributes views to the editor + Contribui visualizações ao editor + + + Contributes views to Data Explorer container in the Activity bar + Contribui com vistas ao contêiner do Data Explorer na barra de atividade + + + Contributes views to contributed views container + Contribui com visualizações ao contêiner de contribuições de visualizações + + + View container '{0}' does not exist and all views registered to it will be added to 'Data Explorer'. + O contêiner de exibição '{0}' não existe e todas as exibições registradas para ele serão adicionadas ao 'Data Explorer'. + + + Cannot register multiple views with same id `{0}` in the view container `{1}` + Não é possível registrar vários modos de exibição com a mesma id '{0}' no contêiner de exibição '{1}' + + + A view with id `{0}` is already registered in the view container `{1}` + Uma exibição com id '{0}' já está registrada no contêiner de exibição '{1}' + + + views must be an array + visualizações devem ser uma matriz + + + property `{0}` is mandatory and must be of type `string` + a propriedade `{0}` é obrigatória e deve ser do tipo `string` + + + property `{0}` can be omitted or must be of type `string` + a propriedade `{0}` é opcional ou deve ser do tipo `string` + + + + + + + Connection Status + Status da conexão + + + + + + + Manage + Gerenciar + + + Show Details + Mostrar detalhes + + + Learn How To Configure The Dashboard + Aprenda a Configurar o Painel + + + + + + + Widget used in the dashboards + Widget usado nos painéis de controle + + + + + + + Displays results of a query as a chart on the dashboard + Exibe os resultados de uma consulta como um gráfico no painel + + + Maps 'column name' -> color. for example add 'column1': red to ensure this column uses a red color + Mapeia 'nome de coluna' -> cor. por exemplo, adicione 'column1': red para garantir que essa coluna use uma cor vermelha + + + Indicates preferred position and visibility of the chart legend. These are the column names from your query, and map to the label of each chart entry + Indica a posição preferida e a visibilidade da legenda do gráfico. Estes são os nomes das colunas da sua consulta e mapeados para o rótulo de cada entrada do gráfico + + + If dataDirection is horizontal, setting this to true uses the first columns value for the legend. + Se dataDirection é horizontal, alterar essa configuração para true usa o valor das primeiras colunas para a legenda. + + + If dataDirection is vertical, setting this to true will use the columns names for the legend. + Se dataDirection for vertical, definindo como verdadeiro usará os nomes das colunas para a legenda. + + + Defines whether the data is read from a column (vertical) or a row (horizontal). For time series this is ignored as direction must be vertical. + Define se os dados são lidos a partir de uma coluna (vertical) ou uma linha (horizontal). Para séries temporais, isso é ignorado, pois a direção deve ser vertical. + + + If showTopNData is set, showing only top N data in the chart. + Se showTopNData for definido, mostre somente os dados top N no gráfico. + + + + + + + Condition which must be true to show this item + Condição deve ser verdadeira para mostrar este item + + + The title of the container + O título do container + + + The row of the component in the grid + A linha do componente no grid + + + The rowspan of the component in the grid. Default value is 1. Use '*' to set to number of rows in the grid. + O rowspan do componente no grid. O valor padrão é 1. Use '*' para definir o número de linhas no grid. + + + The column of the component in the grid + A coluna do componente na grade + + + The colspan of the component in the grid. Default value is 1. Use '*' to set to number of columns in the grid. + O colspan do component no grid. O valor padrão é 1. Use '*'para definir a quantidade de colunas no grid. + + + Unique identifier for this tab. Will be passed to the extension for any requests. + Identificador exclusivo para este guia. Será passado para a extensão para quaisquer solicitações. + + + Extension tab is unknown or not installed. + Guia de extensão é desconhecida ou não está instalado. + + + + + + + Enable or disable the properties widget + Habilitar ou desabilitar o widget Propriedades + + + Property values to show + Valores de propriedade para mostrar + + + Display name of the property + Nome para exibição da propriedade + + + Value in the Database Info Object + Valor no objeto de banco de dados de informação + + + Specify specific values to ignore + Especificar valores específicos para ignorar + + + Recovery Model + Modo de Recuperação + + + Last Database Backup + Último Backup de banco de dados + + + Last Log Backup + Último Backup de Log + + + Compatibility Level + Nível de compatibilidade + + + Owner + Proprietário + + + Customizes the database dashboard page + Personaliza a página do painel de banco de dados + + + Customizes the database dashboard tabs + Personaliza as guias do painel de controle de banco de dados + + + + + + + Enable or disable the properties widget + Habilitar ou desabilitar o widget Propriedades + + + Property values to show + Valores de propriedade para mostrar + + + Display name of the property + Nome para exibição da propriedade + + + Value in the Server Info Object + Valor no objeto de informação do servidor + + + Version + Versão + + + Edition + Edição + + + Computer Name + Nome do Computador + + + OS Version + Versão do sistema operacional + + + Customizes the server dashboard page + Personaliza a página de painel de controle do servidor + + + Customizes the Server dashboard tabs + Personaliza as guias do painel de controle de servidor + + + + + + + Manage + Gerenciar + + + + + + + Widget used in the dashboards + Widget usado nos painéis de controle + + + Widget used in the dashboards + Widget usado nos painéis de controle + + + Widget used in the dashboards + Widget usado nos painéis de controle + + + + + + + Adds a widget that can query a server or database and display the results in multiple ways - as a chart, summarized count, and more + Adiciona um widget que pode consultar um servidor ou banco de dados e exibir os resultados de várias maneiras - como um gráfico, resumida contagem e muito mais + + + Unique Identifier used for caching the results of the insight. + Identificador Exclusivo usado para armazenar em cache os resultados do insight. + + + SQL query to run. This should return exactly 1 resultset. + Consulta SQL para executar. Isso deve retornar exatamente 1 resultset. + + + [Optional] path to a file that contains a query. Use if 'query' is not set + [Opcional] caminho para um arquivo que contém uma consulta. Use se a 'query' não for definida + + + [Optional] Auto refresh interval in minutes, if not set, there will be no auto refresh + [Opcional] Auto intervalo de atualização em minutos, se não estiver definido, não haverá atualização automática + + + Which actions to use + Quais ações para usar + + + Target database for the action; can use the format '${ columnName }' to use a data driven column name. + Banco de dados de destino da ação. É possível usar o formato '${ columnName }' para usar um nome de coluna controlado por dados. + + + Target server for the action; can use the format '${ columnName }' to use a data driven column name. + O servidor de destino da ação. É possível usar o formato '${ columnName }' para usar um nome de coluna controlado por dados. + + + Target user for the action; can use the format '${ columnName }' to use a data driven column name. + Usuário alvo para a ação. É possível usar o formato '${ columnName }' para usar um nome de coluna controlado por dados. + + + Identifier of the insight + Identificador da percepção + + + Contributes insights to the dashboard palette. + Contribuir com ideias para a paleta do painel de controle. + + + + + + + Loading + Carregando + + + Loading completed + Carregamento concluído + + + + + + + Defines a property to show on the dashboard + Define uma propriedade para mostrar no painel de controle + + + What value to use as a label for the property + Qual o valor a ser usado como um rótulo para a propriedade + + + What value in the object to access for the value + Qual o valor do objeto para acessar o valor + + + Specify values to be ignored + Especifique valores para ser ignorado. + + + Default value to show if ignored or no value + Valor padrão para mostrar se ignorado ou nenhum valor + + + A flavor for defining dashboard properties + Um sabor para definir as propriedades do painel de controle + + + Id of the flavor + Identificação do Flavor + + + Condition to use this flavor + Condição para usar o flavor + + + Field to compare to + Campo para comparar a + + + Which operator to use for comparison + Qual operador usar para comparação + + + Value to compare the field to + Valor para comparar o campo para + + + Properties to show for database page + Propriedades para mostrar a página de banco de dados + + + Properties to show for server page + Propriedades para mostrar para a página do servidor + + + Defines that this provider supports the dashboard + Define que este provedor oferece suporte para o painel de controle + + + Provider id (ex. MSSQL) + Id do provedor (ex. MSSQL) + + + Property values to show on dashboard + Valores de propriedade a serem exibidos no painel de controle + + + + + + + Backup + Backup + + + You must enable preview features in order to use backup + Você precisa habilitar as funcionalidades em versão prévia para poder utilizar o backup + + + Backup command is not supported for Azure SQL databases. + O comando Backup não é compatível com bancos de dados SQL do Azure. + + + Backup command is not supported in Server Context. Please select a Database and try again. + O comando backup não é compatível no contexto do servidor. Selecione um banco de dados e tente novamente. + + + + + + + Restore + Restaurar + + + You must enable preview features in order to use restore + Você precisa habilitar recursos em versão prévia para poder utilizar a restauração + + + Restore command is not supported for Azure SQL databases. + O comando Restore não é compatível com bancos de dados SQL do Azure. + + + + + + + disconnected + Desconectado + + + + + + + Server Groups + Grupos de servidores + + + OK + OK + + + Cancel + Cancelar + + + Server group name + Nome do grupo de servidores + + + Group name is required. + Nome do grupo é necessário. + + + Group description + Descrição do grupo + + + Group color + Grupo cor + + + + + + + Extension + Extensão + + + + + + + OK + OK + + + Cancel + Cancelar + + + + + + + Open dashboard extensions + Abrir extensões do dashboard + + + OK + OK + + + Cancel + Cancelar + + + No dashboard extensions are installed at this time. Go to Extension Manager to explore recommended extensions. + Não há extensões de painel para serem instaladas neste momento. Vá para o Gerenciador de Extensões para explorar extensões recomendadas. + + + + + + + Selected path + Caminho selecionado + + + Files of type + Tipo de Arquivos + + + OK + OK + + + Discard + Descarte + + + + + + + No Connection Profile was passed to insights flyout + Sem perfil de Conexão foi passado para o submenu de ideias + + + Insights error + Erro de ideias + + + There was an error reading the query file: + Houve um erro ao ler o arquivo de consulta: + + + There was an error parsing the insight config; could not find query array/string or queryfile + Ocorreu um erro ao analisar a configuração de uma visão; Não foi possível encontrar a consulta vetor/string ou arquivo de consulta + + + + + + + Clear List + Limpar lista + + + Recent connections list cleared + Lista de conexões recentes limpa + + + Yes + Sim + + + No + Não + + + Are you sure you want to delete all the connections from the list? + Você tem certeza que deseja excluir todas as conexões da lista? + + + Yes + Sim + + + No + Não + + + Delete + Excluir + + + Get Current Connection String + Obter a String de Conexão atual + + + Connection string not available + Connection String não disponível + + + No active connection available + Nenhuma conexão ativa disponível + + + + + + + Refresh + Atualizar + + + Disconnect + Desconectar-se + + + New Connection + Nova Conexão + + + New Server Group + Novo Server Group + + + Edit Server Group + Editar grupo de servidores + + + Show Active Connections + Mostrar conexões ativas + + + Show All Connections + Mostrar Todas as Conexões + + + Recent Connections + Conexões recentes + + + Delete Connection + Excluir a Conexão + + + Delete Group + Excluir grupo + + + + + + + Edit Data Session Failed To Connect + Editar dados de sessão falhou ao conectar + + + + + + + Profiler + Profiler + + + Not connected + Não conectado + + + XEvent Profiler Session stopped unexpectedly on the server {0}. + Sessão do Profiler XEvent parou inesperadamente no servidor {0}. + + + Error while starting new session + Erro enquanto se inicia uma nova sessão. + + + The XEvent Profiler session for {0} has lost events. + A sessão de XEvent Profiler para {0} perdeu eventos. + + + Would you like to stop the running XEvent session? + Gostaria de parar a execução da sessão XEvent? + + + Yes + Sim + + + No + Não + + + Cancel + Cancelar + + + + + + + Invalid value + Valor inválido + + + {0}. {1} + {0}. {1} + + + + + + + blank + branco + + + + + + + Error displaying Plotly graph: {0} + Erro ao exibir o gráfico Plotly: {0} + + + + + + + No {0} renderer could be found for output. It has the following MIME types: {1} + Nenhum renderizador de {0} pode ser encontrado para a saída. Ele tem os seguintes tipos MIME: {1} + + + (safe) + (seguro) + + + + + + + Item + Item + + + Value + Valor + + + Property + Propriedade. + + + Value + Valor + + + Insights + Insights + + + Items + Itens + + + Item Details + Detalhes do item + + + + + + + Error adding account + Erro ao adicionar conta + + + + + + + Cannot start auto OAuth. An auto OAuth is already in progress. + Não é possível iniciar o auto OAuth. Um auto OAuth já está em andamento. + + + + + + + Sort by event + Ordenar por evento + + + Sort by column + Ordenar por coluna + + + Profiler + Profiler + + + OK + OK + + + Cancel + Cancelar + + + + + + + Clear all + Limpar tudo + + + Apply + Aplicar + + + OK + OK + + + Cancel + Cancelar + + + Filters + Filtros + + + Remove this clause + Remover esta cláusula + + + Save Filter + Salvar filtro + + + Load Filter + Carregar filtro + + + Add a clause + Adicionar uma cláusula + + + Field + Campo + + + Operator + Operador + + + Value + Valor + + + Is Null + É nulo + + + Is Not Null + Não é nulo + + + Contains + Contém + + + Not Contains + Não contém + + + Starts With + Começa com + + + Not Starts With + Não começa com + + + + + + + Double-click to edit + Clique duas vezes para editar + + + + + + + Select Top 1000 + Selecione Top 1000 + + + Script as Execute + Script executar como + + + Script as Alter + Script como Alter + + + Edit Data + Editar dados + + + Script as Create + Script de criação + + + Script as Drop + Script apagado + + + + + + + No queries to display. + Não há nenhuma consulta a ser exibida. + + + Query History + Histórico de consultas + QueryHistory + + + + + + + Failed to get Azure account token for connection + Falha ao obter o token de conta do Azure para a conexão + + + Connection Not Accepted + Conexão não aceita + + + Yes + Sim + + + No + Não + + + Are you sure you want to cancel this connection? + Tem certeza que deseja cancelar esta conexão? + + + + + + + Started executing query at + Iniciada a execução de consulta em + + + Line {0} + Linha {0} + + + Canceling the query failed: {0} + Cancelamento da consulta falhou: {0} + + + Started saving results to + Iniciado o salvamento de resultados em + + + Failed to save results. + Falha ao salvar os resultados. + + + Successfully saved results to + Os resultados foram salvos com sucesso em + + + Executing query... + Executando consulta ... + + + Maximize + Maximizar + + + Restore + Restaurar + + + Save as CSV + Salve como CSV + + + Save as JSON + Salvar como JSON + + + Save as Excel + Salvar como Excel + + + Save as XML + Salvar como XML + + + View as Chart + Ver como gráfico + + + Visualize + Visualizar + + + Results + Resultados + + + Executing query + Executando consulta + + + Messages + Mensagens + + + Total execution time: {0} + Tempo total de execução: {0} + + + Save results command cannot be used with multiple selections. + O comando de salvar resultados não pode ser utilizado com múltiplas seleções + + + + + + + Identifier of the notebook provider. + Identificador do provedor de bloco de notas. + + + What file extensions should be registered to this notebook provider + Quais extensões de arquivo devem ser registradas para este provedor de notebook + + + What kernels should be standard with this notebook provider + Quais kernels devem ser padrão com este provedor de notebook + + + Contributes notebook providers. + Contribui com provedores de notebooks. + + + Name of the cell magic, such as '%%sql'. + Nome do magic da célula, como '%%sql'. + + + The cell language to be used if this cell magic is included in the cell + A linguagem de célula a ser usada se esta magia de célula está incluída na célula + + + Optional execution target this magic indicates, for example Spark vs SQL + Destino de execução opcional que essa mágica indica, por exemplo, Spark versus SQL + + + Optional set of kernels this is valid for, e.g. python3, pyspark, sql + Conjunto opcional de kerneis que é válido para, por exemplo, python3, pyspark, SQL + + + Contributes notebook language. + Contribui a linguagem do notebook. + + + + + + + SQL + SQL + + + + + + + F5 shortcut key requires a code cell to be selected. Please select a code cell to run. + A tecla de atalho F5 requer que uma célula de código seja selecionada. Selecione uma célula de código para executar. + + + Clear result requires a code cell to be selected. Please select a code cell to run. + Limpar o resultado requer que uma célula de código seja selecionada. Por favor, selecione uma célula de código para executar. + + + + + + + Save As CSV + Salve como CSV + + + Save As JSON + Salvar como JSON + + + Save As Excel + Salvar como Excel + + + Save As XML + Salvar como XML + + + Copy + Copiar + + + Copy With Headers + Copie com cabeçalhos + + + Select All + Selecionar tudo + + + + + + + Max Rows: + Máximo de linhas: + + + + + + + Select View + Selecione a Exibição + + + Select Session + Selecionar Sessão + + + Select Session: + Selecione a sessão: + + + Select View: + Selecione a Exibição: + + + Text + Texto + + + Label + Rótulo + + + Value + Valor + + + Details + Detalhes + + + + + + + Copy failed with error {0} + Falha na cópia com o erro {0} + + + + + + + New Query + Nova consulta + + + Run + Executar + + + Cancel + Cancelar + + + Explain + Explicar + + + Actual + Real + + + Disconnect + Desconectar-se + + + Change Connection + Alterar a Conexão + + + Connect + Conectar-se + + + Enable SQLCMD + Habilitar o SQLCMD + + + Disable SQLCMD + Desabilitar o SQLCMD + + + Select Database + Selecione o banco de dados + + + Select Database Toggle Dropdown + Selecione a lista suspensa de bancos de dados disponíveis + + + Failed to change database + Falha ao mudar o banco de dados + + + Failed to change database {0} + Falha ao alterar o banco de dados {0} + + + + + + + Connection + Conexão + + + Connection type + Tipo de Conexão + + + Recent Connections + Conexões recentes + + + Saved Connections + Conexões salvas + + + Connection Details + Detalhes da Conexão + + + Connect + Conectar-se + + + Cancel + Cancelar + + + No recent connection + Nenhuma conexão recente + + + No saved connection + Nenhuma conexão salva + + + + + + + OK + OK + + + Close + Fechar + + + + + + + Loading kernels... + Carregando kernels... + + + Changing kernel... + Alterando o kernel... + + + Kernel: + Kernel: + + + Attach To: + Anexar à: + + + Loading contexts... + Carregando contextos... + + + Add New Connection + Adicionar nova conexão + + + Select Connection + Selecione a conexão + + + localhost + localhost + + + Trusted + Confiável + + + Not Trusted + Não confiável + + + Notebook is already trusted. + O notebook já é confiável. + + + Collapse Cells + Recolher as Células + + + Expand Cells + Expandir as células + + + No Kernel + Nenhum Kernel + + + None + NENHUM + + + New Notebook + Novo Notebook + + + + + + + Time Elapsed + Tempo decorrido + + + Row Count + Contagem de Linhas + + + {0} rows + {0} linhas + + + Executing query... + Executando consulta ... + + + Execution Status + Status de execução + + + + + + + No task history to display. + Nenhum histórico de tarefas a ser exibido. + + + Task history + Tarefa de história + TaskHistory + + + Task error + Erro de tarefa + + + + + + + Choose SQL Language + Escolha a linguagem SQL + + + Change SQL language provider + Alterar provedor de idiomas SQL + + + SQL Language Flavor + Tipo de Linguagem SQL + + + Change SQL Engine Provider + Alterar o provedor de mecanismo SQL + + + A connection using engine {0} exists. To change please disconnect or change connection + Existe uma conexão usando o mecanismo {0}. Para alterar, por favor desconecte ou altere a conexão + + + No text editor active at this time + Nenhum editor de texto ativo neste momento + + + Select SQL Language Provider + Selecione o provedor de linguagem SQL + + + + + + + All files + Todos os Arquivos + + + + + + + File browser tree + Ãrvore de navegador de arquivo + FileBrowserTree + + + + + + + From + De + + + To + Para + + + Create new firewall rule + Criar nova regra de firewall + + + OK + OK + + + Cancel + Cancelar + + + Your client IP address does not have access to the server. Sign in to an Azure account and create a new firewall rule to enable access. + Seu endereço IP de cliente não tem acesso ao servidor. Acesse uma conta Azure e criar uma nova regra de firewall para permitir o acesso. + + + Learn more about firewall settings + Saiba mais sobre as configurações do firewall + + + Azure account + Conta do Microsoft Azure + + + Firewall rule + Regra de firewall + + + Add my client IP + Adicionar o meu IP do cliente + + + Add my subnet IP range + Adicionar a minha faixa de IP de sub-rede + + + + + + + You need to refresh the credentials for this account. + Você precisa atualizar as credenciais para esta conta. + + + + + + + Could not find query file at any of the following paths : + {0} + Não foi possível encontrar o arquivo de consulta em nenhum dos seguintes caminhos: {0} + + + + + + + Add an account + Adicionar uma conta + + + Remove account + Remover conta + + + Are you sure you want to remove '{0}'? + Tem certeza que deseja remover '{0}'? + + + Yes + Sim + + + No + Não + + + Failed to remove account + Falha ao remover conta + + + Apply Filters + Aplicar filtros + + + Reenter your credentials + Digite novamente suas credenciais + + + There is no account to refresh + Não há nenhuma conta para atualizar + + + + + + + Focus on Current Query + Foco na consulta atual + + + Run Query + Executar consulta + + + Run Current Query + Executar consulta atual + + + Run Current Query with Actual Plan + Executar a consulta atual com o plano real + + + Cancel Query + Cancelar a consulta + + + Refresh IntelliSense Cache + Atualizar Cache do IntelliSense + + + Toggle Query Results + Alternar resultados da consulta + + + Editor parameter is required for a shortcut to be executed + Editor de parâmetro é necessário para um atalho a ser executado + + + Parse Query + Analisar a consulta + + + Commands completed successfully + Comandos concluídos com sucesso + + + Command failed: + Falha no comando: + + + Please connect to a server + Por favor, conecte-se a um servidor + + + + + + + Chart cannot be displayed with the given data + Gráfico não pode ser exibido com os dados fornecidos + + + + + + + The index {0} is invalid. + O índice {0} é inválido. + + + + + + + no data available + não há nenhum dado disponível + + + + + + + Information + Informações + + + Warning + Aviso + + + Error + Erro + + + Show Details + Mostrar detalhes + + + Copy + Copiar + + + Close + Fechar + + + Back + voltar + + + Hide Details + Ocultar detalhes + + + + + + + is required. + é necessário. + + + Invalid input. Numeric value expected. + Entrada inválida. Valor numérico esperado + + + + + + + Execution failed due to an unexpected error: {0} {1} + Execução falhou devido a um erro inesperado: {0} {1} + + + Total execution time: {0} + Tempo total de execução: {0} + + + Started executing query at Line {0} + Execução da consulta iniciada na linha {0} + + + Initialize edit data session failed: + Falha ao inicializar a sessão de edição de dados: + + + Batch execution time: {0} + Tempo de execução em lote: {0} + + + Copy failed with error {0} + Falha na cópia com o erro {0} + + + + + + + Error: {0} + Erro: {0} + + + Warning: {0} + Aviso: {0} + + + Info: {0} + Info: {0} + + + + + + + Copy Cell + Copie a célula + + + + + + + Backup file path + Caminho do arquivo de backup + + + Target database + Banco de Dados de destino + + + Restore database + Restaurar o banco de dados + + + Restore database + Restaurar o banco de dados + + + Database + Banco de dados + + + Backup file + Arquivo de backup + + + Restore + Restaurar + + + Cancel + Cancelar + + + Script + Script + + + Source + Fonte + + + Restore from + Restaurar a partir de + + + Backup file path is required. + Caminho do arquivo de backup é necessário. + + + Please enter one or more file paths separated by commas + Por favor, digite um ou mais caminhos de arquivo separados por vírgulas + + + Database + Banco de dados + + + Destination + Destino + + + Select Database Toggle Dropdown + Selecione a lista suspensa de bancos de dados disponíveis + + + Restore to + Restaurar para + + + Restore plan + Restaurar o plano + + + Backup sets to restore + Conjuntos de backup para restaurar + + + Restore database files as + Restaurar arquivos de banco de dados como + + + Restore database file details + Restaurar os detalhes do arquivo de banco de dados + + + Logical file Name + Nome do arquivo lógico + + + File type + Tipo de arquivo + + + Original File Name + Nome do arquivo original + + + Restore as + Restaurar como + + + Restore options + Opções de restauração + + + Tail-Log backup + Backup de Tail-Log + + + Server connections + Conexões de servidor + + + General + Geral + + + Files + Arquivos + + + Options + Opções + + + + + + + Copy & Open + Copiar & Abrir + + + Cancel + Cancelar + + + User code + Código do usuário + + + Website + Web site + + + + + + + Done + concluído + + + Cancel + Cancelar + + + + + + + Must be an option from the list + Deve ser uma opção da lista + + + Toggle dropdown + Alternar menu suspenso + + + + + + + Select/Deselect All + Seleccionar/desmarcar tudo + + + checkbox checked + caixa de seleção marcada + + + checkbox unchecked + caixa de seleção desmarcada + + + + + + + modelview code editor for view model. + Editor de código modelview para modelo de exibição. + + + + + + + succeeded + sucedeu + + + failed + falhou + + + + + + + Server Description (optional) + Descrição do servidor (opcional) + + + + + + + Advanced Properties + Propriedades avançadas + + + Discard + Descarte + + + + + + + Linked accounts + Contas vinculadas + + + Close + Fechar + + + There is no linked account. Please add an account. + Não há nenhuma conta vinculada. Por favor, adicione uma conta. + + + Add an account + Adicionar uma conta + + + + + + + nbformat v{0}.{1} not recognized + nbformat v{0}.{1} não reconhecido + + + This file does not have a valid notebook format + Este arquivo não tem um formato de notebook válido + + + Cell type {0} unknown + Tipo de célula {0} desconhecido + + + Output type {0} not recognized + Tipo de saída {0} não reconhecido + + + Data for {0} is expected to be a string or an Array of strings + Espera-se que os dados de {0} sejam uma string ou uma matriz de strings + + + Output type {0} not recognized + Tipo de saída {0} não reconhecido + + + + + + + Profiler editor for event text. Readonly + Editor do Profiler para o texto do evento. ReadOnly + + + + + + + Run Cells Before + Executar células antes de + + + Run Cells After + Executar células após + + + Insert Code Before + Inserir o código antes de + + + Insert Code After + Inserir o código depois de + + + Insert Text Before + Inserir texto antes + + + Insert Text After + Inserir texto após + + + Collapse Cell + Recolher a Célula + + + Expand Cell + Expandir a Célula + + + Clear Result + Limpar Resultado + + + Delete + Excluir + + + + + + + No script was returned when calling select script on object + Nenhum script foi retornado ao chamar o script de selecionar objeto + + + Select + Selecione + + + Create + Criar + + + Insert + Inserir + + + Update + Atualização + + + Delete + Excluir + + + No script was returned when scripting as {0} on object {1} + Nenhum script foi retornado durante o script como {0} no objeto {1} + + + Scripting Failed + Script falhou + + + No script was returned when scripting as {0} + Nenhum script foi retornado quando scripts como {0} + + + + + + + Recent Connections + Conexões recentes + + + Servers + Servidores + + + + + + + No Kernel + Nenhum Kernel + + + Cannot run cells as no kernel has been configured + Não é possível executar células porque nenhum kernel foi configurado + + + Error + Erro + + + + + + + Azure Data Studio + Azure Data Studio + + + Start + Início + + + New connection + Nova Conexão + + + New query + Nova consulta + + + New notebook + Novo Notebook + + + Open file + Abrir arquivo + + + Open file + Abrir arquivo + + + Deploy + Implantar + + + Deploy SQL Server… + Implantar o SQL Server... + + + Recent + Recente + + + More... + Mais... + + + No recent folders + Não há pastas recentes + + + Help + Ajuda + + + Getting started + Introdução + + + Documentation + Documentation + + + Report issue or feature request + Relatar problema ou solicitação de recurso + + + GitHub repository + Repositório GitHub + + + Release notes + Notas de Versão + + + Show welcome page on startup + Mostrar a página de boas-vindas na inicialização + + + Customize + Personalizar + + + Extensions + Extensões + + + Download extensions that you need, including the SQL Server Admin pack and more + Baixe as extensões necessárias, incluindo o pacote de Administrador do SQL Server e muito mais + + + Keyboard Shortcuts + Atalhos de Teclado + + + Find your favorite commands and customize them + Encontre seus comandos favoritos e personalize-os + + + Color theme + Tema de Cores + + + Make the editor and your code look the way you love + Fazer o editor e seu código parecer do jeito que você gosta + + + Learn + Aprender + + + Find and run all commands + Encontrar e executar todos os comandos + + + Rapidly access and search commands from the Command Palette ({0}) + Comandos de acesso rápido e de pesquisa da paleta de comando ({0}) + + + Discover what's new in the latest release + Descubra o que há de novo na versão mais recente + + + New monthly blog posts each month showcasing our new features + Novas postagens mensais do blog apresentando nossos novos recursos + + + Follow us on Twitter + Siga-nos no Twitter + + + Keep up to date with how the community is using Azure Data Studio and to talk directly with the engineers. + Mantenha-se atualizado com a forma como a Comunidade está usando o Azure Data Studio e converse diretamente com os engenheiros. + + + + + + + succeeded + sucedeu + + + failed + falhou + + + in progress + Em andamento + + + not started + não iniciado + + + canceled + cancelado + + + canceling + cancelando + + + + + + + Run + Executar + + + Dispose Edit Failed With Error: + Editar Descarte Falhou com o Erro: + + + Stop + Pare + + + Show SQL Pane + Mostrar painel de SQL + + + Close SQL Pane + Fechar painel SQL + + + + + + + Connect + Conectar-se + + + Disconnect + Desconectar-se + + + Start + Início + + + New Session + Nova sessão + + + Pause + Pausa + + + Resume + Resumo + + + Stop + Pare + + + Clear Data + Limpar dados + + + Auto Scroll: On + Auto rolagem: Habilitado + + + Auto Scroll: Off + Auto Scroll: Desligado + + + Toggle Collapsed Panel + Alternar o painel recolhido + + + Edit Columns + Editar colunas + + + Find Next String + Encontrar a próxima sequência de caracteres + + + Find Previous String + Encontrar a sequência anterior + + + Launch Profiler + Executar Profiler + + + Filter… + Filtro... + + + Clear Filter + Limpar Filtro + + + + + + + Events (Filtered): {0}/{1} + Eventos (Filtrados): {0}/{1} + + + Events: {0} + Eventos: {0} + + + Event Count + Contagem de eventos + + + + + + + Save As CSV + Salve como CSV + + + Save As JSON + Salvar como JSON + + + Save As Excel + Salvar como Excel + + + Save As XML + Salvar como XML + + + Save to file is not supported by the backing data source + Salvar em arquivo não é uma ação compatível com a fonte de dados de backup + + + Copy + Copiar + + + Copy With Headers + Copie com cabeçalhos + + + Select All + Selecionar tudo + + + Copy + Copiar + + + Copy All + Copiar todos + + + Maximize + Maximizar + + + Restore + Restaurar + + + Chart + Gráfico + + + Visualizer + Visualizador + + + + + + + The extension "{0}" is using sqlops module which has been replaced by azdata module, the sqlops module will be removed in a future release. + A extensão "{0}" está usando o módulo sqlops que foi substituído pelo módulo azdata. O módulo sqlops será removido em uma versão futura. + + + + + + + Table header background color + Cor de fundo do cabeçalho de tabela + + + Table header foreground color + Cor de primeiro plano de cabeçalho de tabela + + + Disabled Input box background. + Fundo de caixa de entrada desativado. + + + Disabled Input box foreground. + Caixa de entrada em primeiro plano desativada. + + + Button outline color when focused. + Cor de contorno do botão quando possui o foco. + + + Disabled checkbox foreground. + Primeiro plano da caixa de seleção desabilitado. + + + List/Table background color for the selected and focus item when the list/table is active + Lista/tabela de cor de fundo para o selecionado e item de foco quando a lista/tabela está ativa + + + SQL Agent Table background color. + Cor de fundo da tabela do SQL Agent. + + + SQL Agent table cell background color. + Cor de fundo célula de tabela SQL Agent. + + + SQL Agent table hover background color. + Cor de fundo para tabela no SQL Agent. + + + SQL Agent heading background color. + Cor de fundo do título do SQL Agent. + + + SQL Agent table cell border color. + Cor da borda da célula na tabela SQL Agent. + + + Results messages error color. + Cor de erro das mensagens de resultados. + + + + + + + Choose Results File + Escolha o arquivo de resultados + + + CSV (Comma delimited) + CSV (separado por vírgula) + + + JSON + JSON + + + Excel Workbook + Pasta de trabalho do Excel + + + XML + XML + + + Plain Text + Texto Sem Formatação + + + Open file location + Abrir arquivo de localização + + + Open file + Abrir arquivo + + + + + + + Backup name + Nome do backup + + + Recovery model + Modo de Recuperação + + + Backup type + Tipo de backup + + + Backup files + Arquivos de backup + + + Algorithm + Algoritmo de + + + Certificate or Asymmetric key + Chave de certificado ou assimétrica + + + Media + Mídia + + + Backup to the existing media set + Backup para o conjunto de mídia existente + + + Backup to a new media set + Backup em um novo conjunto de mídias + + + Append to the existing backup set + Acrescentar ao conjunto de backup existente + + + Overwrite all existing backup sets + Substituir todos os conjuntos de backup existentes + + + New media set name + Novo nome do conjunto de mídia + + + New media set description + Nova descrição do conjunto de mídia + + + Perform checksum before writing to media + Realize o checksum antes de gravar na mídia + + + Verify backup when finished + Verificar backup quando terminar + + + Continue on error + Continuar no erro + + + Expiration + Expiração + + + Set backup retain days + Defina os dias de retenção de backup + + + Copy-only backup + Backup somente cópia + + + Advanced Configuration + Configuração avançada + + + Compression + Compressão + + + Set backup compression + Definir a compactação de backup + + + Encryption + Criptografia + + + Transaction log + Log de transações + + + Truncate the transaction log + Truncar o log de transações + + + Backup the tail of the log + Backup do final do log + + + Reliability + Confiabilidade + + + Media name is required + Nome da mídia é necessária + + + No certificate or asymmetric key is available + Nenhum certificado ou chave assimétrica está disponível + + + Add a file + Adicione um arquivo + + + Remove files + Remover arquivos + + + Invalid input. Value must be greater than or equal 0. + Entrada inválida. O valor deve ser maior ou igual a 0. + + + Script + Script + + + Backup + Backup + + + Cancel + Cancelar + + + Only backup to file is supported + Apenas backup em arquivo é suportado. + + + Backup file path is required + Caminho do arquivo de backup é necessário + + + + + + + Results + Resultados + + + Messages + Mensagens + + + + + + + There is no data provider registered that can provide view data. + Não há dados provedor registrado que pode fornecer dados de exibição. + + + Collapse All + Recolher tudo + + + + + + + Home + Casa + + + + + + + The "{0}" section has invalid content. Please contact extension owner. + A seção "{0}" tem conteúdo inválido. Entre em contato com o proprietário da extensão. + + + + + + + Jobs + Trabalhos + + + Notebooks + Notebooks + + + Alerts + Alertas + + + Proxies + Proxies + + + Operators + Operadores + + + + + + + Loading + Carregando + + + + + + + SERVER DASHBOARD + PAINEL DO SERVIDOR + + + + + + + DATABASE DASHBOARD + PAINEL DE BANCO DE DADOS + + + + + + + Edit + Editar + + + Exit + Sair + + + Refresh + Atualizar + + + Toggle More + Alternar entre mais + + + Delete Widget + Excluir o Widget + + + Click to unpin + Clique para Desafixar + + + Click to pin + Clique para fixar + + + Open installed features + Recursos instalados abertos + + + Collapse + Recolher + + + Expand + Expandir + + + + + + + Steps + Passos + + + + + + + StdIn: + Stdin: + + + + + + + Add code + Adicionar código + + + Add text + Adicionar texto + + + Create File + Criar arquivo + + + Could not display contents: {0} + Não foi possível exibir o conteúdo: {0} + + + Please install the SQL Server 2019 extension to run cells. + Instale a extensão do SQL Server 2019 para executar células. + + + Install Extension + Instalar Extensão + + + Code + Código + + + Text + Texto + + + Run Cells + Executar células + + + Clear Results + Limpar Resultados + + + < Previous + < Anterior + + + Next > + Próximo > + + + cell with URI {0} was not found in this model + a célula com o URI {0} não foi encontrada neste modelo + + + Run Cells failed - See error in output of the currently selected cell for more information. + Falha ao executar células – confira o erro na saída da célula selecionada no momento para obter mais informações. + + + + + + + Click on + Clique em + + + + Code + + Código + + + or + Ou + + + + Text + + Texto + + + to add a code or text cell + para adicionar uma célula de texto ou código + + + + + + + Database + Banco de dados + + + Files and filegroups + Arquivos e grupos de arquivos + + + Full + Completo + + + Differential + Diferencial + + + Transaction Log + Log de transações + + + Disk + Disco + + + Url + URL + + + Use the default server setting + Usar a configuração padrão do servidor + + + Compress backup + Compactar o backup + + + Do not compress backup + Não compactar o backup + + + Server Certificate + Certificado do Servidor + + + Asymmetric Key + Chave Assimétrica + + + Backup Files + Arquivos de backup + + + All Files + Todos os Arquivos + + + + + + + No connections found. + Nenhuma conexão encontrada. + + + Add Connection + Adicionar Conexão + + + + + + + Failed to change database + Falha ao mudar o banco de dados + + + + + + + Name + Nome + + + Email Address + Endereço de e-mail + + + Enabled + Habilitado + + + + + + + Name + Nome + + + Last Occurrence + Última ocorrência + + + Enabled + Habilitado + + + Delay Between Responses (in secs) + Atraso entre as respostas (em segundos) + + + Category Name + Nome da categoria + + + + + + + Account Name + Nome da conta + + + Credential Name + Nome da credencial + + + Description + Descrição + + + Enabled + Habilitado + + + + + + + Unable to load dashboard properties + Não é possível carregar as propriedades do painel + + + + + + + Search by name of type (a:, t:, v:, f:, or sp:) + Pesquisar pelo nome do tipo (a:, t:, v:, f:, or sp:) + + + Search databases + Bancos de dados de pesquisa + + + Unable to load objects + Não é possível carregar objetos + + + Unable to load databases + Não é possível carregar bancos de dados + + + + + + + Auto Refresh: OFF + Atualização Automática: DESABILITADA + + + Last Updated: {0} {1} + Ultima atualização: {0} {1} + + + No results to show + Nenhum resultado para mostrar + + + + + + + No {0}renderer could be found for output. It has the following MIME types: {1} + Não foi possível encontrar nenhum {0}renderizador para saída. Ele tem os seguintes tipos MIME: {1} + + + safe + Seguro + + + No component could be found for selector {0} + Não foi possível encontrar nenhum componente para o seletor {0} + + + Error rendering component: {0} + Erro ao renderizar o componente: {0} + + + + + + + Connected to + Conectado a + + + Disconnected + Desconectado + + + Unsaved Connections + Conexões não salvas + + + + + + + Delete Row + Excluir linha + + + Revert Current Row + Desfazer a linha atual + + + + + + + Step ID + ID da Etapa + + + Step Name + Nome da etapa + + + Message + Mensagem + + + + + + + XML Showplan + Plano de execução XML + + + Results grid + Grade de resultados + + + + + + + Please select active cell and try again + Por favor, selecione a célula ativa e tente novamente + + + Run cell + Executar célula + + + Cancel execution + Cancelar a execução + + + Error on last run. Click to run again + Erro na última execução. Clique para executar novamente + + + + + + + Add an account... + Adicione uma conta... + + + <Default> + <Padrão> + + + Loading... + Carregando... + + + Server group + Grupo de servidores + + + <Default> + <Padrão> + + + Add new group... + Adicionar novo grupo... + + + <Do not save> + <Não Salvar> + + + {0} is required. + {0} é necessária. + + + {0} will be trimmed. + {0} será removido. + + + Remember password + Lembrar senha + + + Account + Conta + + + Refresh account credentials + Atualizar as credenciais de conta + + + Azure AD tenant + Locatário do Azure AD + + + Select Database Toggle Dropdown + Selecione a lista suspensa de bancos de dados disponíveis + + + Name (optional) + Nome (opcional) + + + Advanced... + Avançada... + + + You must select an account + Você precisa selecionar uma conta + + + + + + + Cancel + Cancelar + + + The task is failed to cancel. + A tarefa falhou ao ser cancelada. + + + Script + Script + + + + + + + Date Created: + Data de Criação: + + + Notebook Error: + Erro do Notebook: + + + Job Error: + Erro de trabalho: + + + Pinned + Fixo + + + Recent Runs + Execuções recentes + + + Past Runs + Execuções anteriores + + + + + + + No tree view with id '{0}' registered. + Não há exibição de árvore com id '{0}' registrado. + + + + + + + Loading... + Carregando... + + + + + + + Dashboard Tabs ({0}) + Painel de abas ({0}) + + + Id + ID + + + Title + Título + + + Description + Descrição + + + Dashboard Insights ({0}) + Painel de Insights ({0}) + + + Id + ID + + + Name + Nome + + + When + Quando + + + + + + + Chart + Gráfico + + + + + + + Operation + Operação + + + Object + Objeto + + + Est Cost + Custo est + + + Est Subtree Cost + Custo est da subárvore + + + Actual Rows + Linhas atuais + + + Est Rows + Linhas de est + + + Actual Executions + Execuções reais + + + Est CPU Cost + Custo de CPU est + + + Est IO Cost + Custo de IO est + + + Parallel + Paralelo + + + Actual Rebinds + Reassociações reais + + + Est Rebinds + Est reassociações + + + Actual Rewinds + Actual Rewinds + + + Est Rewinds + Retroceder + + + Partitioned + Particionado + + + Top Operations + Operações principais + + + + + + + Query Plan + Plano de consulta + + + + + + + Could not find component for type {0} + Não foi possível localizar componente para tipo {0} + + + + + + + A NotebookProvider with valid providerId must be passed to this method + Um NotebookProvider com providerId válido precisa ser passado para este método + + + + + + + A NotebookProvider with valid providerId must be passed to this method + Um NotebookProvider com providerId válido precisa ser passado para este método + + + no notebook provider found + nenhum provedor de notebook encontrado + + + No Manager found + Nenhum Gerenciador encontrado + + + Notebook Manager for notebook {0} does not have a server manager. Cannot perform operations on it + O Notebook Manager para o notebook {0} não tem um gerenciador de servidores. Não é possível executar operações nele + + + Notebook Manager for notebook {0} does not have a content manager. Cannot perform operations on it + O gerenciador de notebooks do notebook {0} não tem um gerenciador de conteúdo. Não é possível executar operações nele + + + Notebook Manager for notebook {0} does not have a session manager. Cannot perform operations on it + O gerenciador de notebooks do notebook {0} não tem um gerenciador de sessão. Não é possível executar operações nele + + + + + + + SQL kernel error + Erro do kernel SQL + + + A connection must be chosen to run notebook cells + Uma conexão precisa ser escolhida para executar as células do notebook + + + Displaying Top {0} rows. + Exibindo as Primeiras {0} linhas. + + + + + + + Show Recommendations + Mostrar Recomendações + + + Install Extensions + Instalar Extensões + + + + + + + Name + Nome + + + Last Run + Última execução + + + Next Run + Próxima Execução + + + Enabled + Habilitado + + + Status + Status + + + Category + Categoria + + + Runnable + Executável + + + Schedule + Agenda + + + Last Run Outcome + Resultado da última execução + + + Previous Runs + Execuções anteriores + + + No Steps available for this job. + Nenhum passo disponível para este job. + + + Error: + Erro: + + + + + + + Find + Encontrar + + + Find + Encontrar + + + Previous match + Correspondência anterior + + + Next match + Próxima correspondência + + + Close + Fechar + + + Your search returned a large number of results, only the first 999 matches will be highlighted. + Sua pesquisa retornou um grande número de resultados, apenas as primeiras 999 correspondências serão destacadas. + + + {0} of {1} + {0} de {1} + + + No Results + Nenhum resultado + + + + + + + Run Query + Executar consulta + + + + + + + Done + concluído + + + Cancel + Cancelar + + + Generate script + Gerar script + + + Next + Próximo + + + Previous + Anterior + + + + + + + A server group with the same name already exists. + Um grupo de servidores com o mesmo nome já existe. + + + + + + + {0} is an unknown container. + {0} é um container desconhecido. + + + + + + + Loading Error... + Erro de carregamento... + + + + + + + Failed + falhou + + + Succeeded + sucedeu + + + Retry + Tentar Novamente + + + Cancelled + Cancelada + + + In Progress + Em andamento + + + Status Unknown + Status desconhecido + + + Executing + Executando + + + Waiting for Thread + Esperando pela Thread + + + Between Retries + Entre tentativas + + + Idle + Ocioso + + + Suspended + Suspenso + + + [Obsolete] + [Obsoleto] + + + Yes + Sim + + + No + Não + + + Not Scheduled + Não agendado + + + Never Run + Nunca executar + + + + + + + Name + Nome + + + Target Database + Banco de Dados de destino + + + Last Run + Última execução + + + Next Run + Próxima Execução + + + Status + Status + + + Last Run Outcome + Resultado da última execução + + + Previous Runs + Execuções anteriores + + + No Steps available for this job. + Nenhum passo disponível para este job. + + + Error: + Erro: + + + Notebook Error: + Erro do Notebook: + + + + + + + Home + Casa + + + No connection information could be found for this dashboard + Nenhuma informação de conexão pôde ser encontrada para este painel + + + + + + + Data + Dados + + + Connection + Conexão + + + Query + Consulta + + + Notebook + Notebook + + + SQL + SQL + + + Microsoft SQL Server + Microsoft SQL Server + + + Dashboard + Painel + + + Profiler + Profiler + + + + + + + Close + Fechar + + + + + + + Success + Sucesso + + + Error + Erro + + + Refresh + Atualizar + + + New Job + Novo job + + + Run + Executar + + + : The job was successfully started. + O job foi iniciado com êxito. + + + Stop + Pare + + + : The job was successfully stopped. + : O trabalho foi interrompido com êxito. + + + Edit Job + Editar job + + + Open + Aberta + + + Delete Job + Excluir Job + + + Are you sure you'd like to delete the job '{0}'? + Tem certeza de que gostaria de excluir o job '{0}'? + + + Could not delete job '{0}'. +Error: {1} + Não foi possível excluir o job '{0}'. +Erro: {1} + + + The job was successfully deleted + O trabalho foi excluído com êxito + + + New Step + Nova etapa + + + Delete Step + Excluir Etapa + + + Are you sure you'd like to delete the step '{0}'? + Tem certeza de que gostaria de excluir a etapa '{0}'? + + + Could not delete step '{0}'. +Error: {1} + Não foi possível excluir a etapa '{0}'. +Erro: {1} + + + The job step was successfully deleted + A etapa do trabalho foi excluída com êxito + + + New Alert + Novo alerta + + + Edit Alert + Editar alerta + + + Delete Alert + Apagar alerta + + + Cancel + Cancelar + + + Are you sure you'd like to delete the alert '{0}'? + Tem certeza que deseja apagar o alerta '{0}'? + + + Could not delete alert '{0}'. +Error: {1} + Não foi possível excluir alerta '{0}'. +Erro: {1} + + + The alert was successfully deleted + O alerta foi excluído com êxito + + + New Operator + Novo operador + + + Edit Operator + Editar o operador + + + Delete Operator + Excluir operador + + + Are you sure you'd like to delete the operator '{0}'? + Você tem certeza que gostaria de excluir o operador '{0}'? + + + Could not delete operator '{0}'. +Error: {1} + Não foi possível excluir o operador '{0}'. +Erro: {1} + + + The operator was deleted successfully + O operador foi excluído com êxito + + + New Proxy + Novo Proxy + + + Edit Proxy + Editar proxy + + + Delete Proxy + Excluir proxy + + + Are you sure you'd like to delete the proxy '{0}'? + Tem certeza de que você gostaria de excluir o proxy '{0}'? + + + Could not delete proxy '{0}'. +Error: {1} + Não foi possível excluir o proxy '{0}'. +Erro: {1} + + + The proxy was deleted successfully + O proxy foi excluído com êxito + + + New Notebook Job + Novo Trabalho de Notebook + + + Edit + Editar + + + Open Template Notebook + Abrir o Notebook do Modelo + + + Delete + Excluir + + + Are you sure you'd like to delete the notebook '{0}'? + Tem certeza de que deseja excluir o notebook '{0}'? + + + Could not delete notebook '{0}'. +Error: {1} + Não foi possível excluir o notebook '{0}'. +Erro: {1} + + + The notebook was successfully deleted + O notebook foi excluído com sucesso + + + Pin + Fixar + + + Delete + Excluir + + + Unpin + Desafixar + + + Rename + Renomear + + + Open Latest Run + Abrir a Última Execução + + + + + + + Please select a connection to run cells for this kernel + Selecione uma conexão para executar células para este kernel + + + Failed to delete cell. + Falha ao excluir a célula. + + + Failed to change kernel. Kernel {0} will be used. Error was: {1} + Falha ao alterar o kernel. O kernel {0} será usado. O erro foi: {1} + + + Failed to change kernel due to error: {0} + Falha ao alterar o kernel devido a um erro: {0} + + + Changing context failed: {0} + Falha ao alterar o contexto: {0} + + + Could not start session: {0} + Não foi possível iniciar a sessão: {0} + + + A client session error occurred when closing the notebook: {0} + Ocorreu um erro de sessão de cliente ao fechar o notebook: {0} + + + Can't find notebook manager for provider {0} + Não é possível encontrar o gerenciador de notebooks do provedor {0} + + + + + + + An error occurred while starting the notebook session + Ocorreu um erro ao iniciar a sessão do notebook + + + Server did not start for unknown reason + O servidor não foi iniciado por uma razão desconhecida + + + Kernel {0} was not found. The default kernel will be used instead. + O kernel {0} não foi encontrado. O kernel padrão será usado em seu lugar. + + + + + + + Unknown component type. Must use ModelBuilder to create objects + Tipo de componente desconhecido. É necessário usar o ModelBuilder para criar objetos + + + The index {0} is invalid. + O índice {0} é inválido. + + + Unkown component configuration, must use ModelBuilder to create a configuration object + Componente de configuração desconhecida, use ModelBuilder para criar um objeto de configuração. + + + + + + + Horizontal Bar + Barra horizontal + + + Bar + Barra + + + Line + Linha + + + Pie + Pizza + + + Scatter + Dispersão + + + Time Series + Séries temporais + + + Image + Imagem + + + Count + Contagem + + + Table + Tabela + + + Doughnut + Rosca + + + + + + + OK + OK + + + Clear + Limpar + + + Cancel + Cancelar + + + + + + + Cell execution cancelled + Execução da célula cancelada + + + Query execution was canceled + A execução da consulta foi cancelada. + + + The session for this notebook is not yet ready + A sessão para este notebook ainda não está pronta + + + The session for this notebook will start momentarily + A sessão para este notebook vai começar em breve + + + No kernel is available for this notebook + Nenhum kernel está disponível para este notebook + + + + + + + Select Connection + Selecione a conexão + + + localhost + localhost + + + + + + + Data Direction + Direção de dados + + + Vertical + Vertical + + + Horizontal + Horizontal + + + Use column names as labels + Use nomes de colunas como rótulos + + + Use first column as row label + Usar a primeira coluna como rótulo de linha + + + Legend Position + Posição da legenda + + + Y Axis Label + Rótulo de eixo Y + + + Y Axis Minimum Value + Valor mínimo do eixo Y + + + Y Axis Maximum Value + Valor máximo do eixo Y + + + X Axis Label + Rótulo do eixo X + + + X Axis Minimum Value + Valor mínimo do eixo X + + + X Axis Maximum Value + Valor máximo para o eixo X + + + X Axis Minimum Date + Data Mínima do Eixo X + + + X Axis Maximum Date + Data Máxima do Eixo X + + + Data Type + Tipo de dados + + + Number + Número + + + Point + Ponto + + + Chart Type + Tipo de gráfico + + + Encoding + codificação + + + Image Format + Formato de imagem + + + + + + + Create Insight + Criar um insighit + + + Cannot create insight as the active editor is not a SQL Editor + Não é possível criar informações como o editor ativo não é um Editor SQL + + + My-Widget + Meu Widget + + + Copy as image + Copiar como imagem + + + Could not find chart to save + Não foi possível encontrar o gráfico para salvar + + + Save as image + Salvar como imagem + + + PNG + PNG + + + Saved Chart to path: {0} + Gráfico Salvo no caminho: {0} + + + + + + + Changing editor types on unsaved files is unsupported + Não há suporte para alterar os tipos de editor em arquivos não salvos + + + + + + + Table does not contain a valid image + A tabela não contém uma imagem válida + + + + + + + Series {0} + Série {0} diff --git a/resources/xlf/ru/azurecore.ru.xlf b/resources/xlf/ru/azurecore.ru.xlf index 7ddfa7617580..23f050740bca 100644 --- a/resources/xlf/ru/azurecore.ru.xlf +++ b/resources/xlf/ru/azurecore.ru.xlf @@ -200,4 +200,20 @@ + + + + SQL Managed Instances + УправлÑемые ÑкземплÑры SQL + + + + + + + Azure Database for PostgreSQL Servers + База данных Azure Ð´Ð»Ñ Ñерверов PostgreSQL + + + \ No newline at end of file diff --git a/resources/xlf/ru/big-data-cluster.ru.xlf b/resources/xlf/ru/big-data-cluster.ru.xlf index a60f43164d1f..9182aef747fa 100644 --- a/resources/xlf/ru/big-data-cluster.ru.xlf +++ b/resources/xlf/ru/big-data-cluster.ru.xlf @@ -38,6 +38,14 @@ Delete Mount Удалить подключение + + Big Data Cluster + КлаÑтер больших данных + + + Ignore SSL verification errors against SQL Server Big Data Cluster endpoints such as HDFS, Spark, and Controller if true + ЕÑли Ñтот параметр имеет значение true, игнорировать ошибки проверки SSL в отношении конечных точек клаÑтера больших данных SQL Server, таких как HDFS, Spark и Controller + @@ -58,26 +66,86 @@ Deleting Удаление - - Waiting For Deletion - Ожидание ÑƒÐ´Ð°Ð»ÐµÐ½Ð¸Ñ - Deleted Удалить + + Applying Upgrade + Применение Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ + Upgrading Идет обновление - - Waiting For Upgrade - Ожидание Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ + + Applying Managed Upgrade + Применение управлÑемого Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ + + + Managed Upgrading + УправлÑемое обновление + + + Rollback + откат + + + Rollback In Progress + ВыполнÑетÑÑ Ð¾Ñ‚ÐºÐ°Ñ‚ + + + Rollback Complete + Откат завершен Error Ошибка + + Creating Secrets + Создание Ñекретов + + + Waiting For Secrets + Ожидание Ñекретов + + + Creating Groups + Создание групп + + + Waiting For Groups + Ожидание групп + + + Creating Resources + Создание реÑурÑов + + + Waiting For Resources + Ожидание реÑурÑов + + + Creating Kerberos Delegation Setup + Создание наÑтройки Ð´ÐµÐ»ÐµÐ³Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Kerberos + + + Waiting For Kerberos Delegation Setup + Ожидание наÑтройки Ð´ÐµÐ»ÐµÐ³Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Kerberos + + + Waiting For Deletion + Ожидание ÑƒÐ´Ð°Ð»ÐµÐ½Ð¸Ñ + + + Waiting For Upgrade + Ожидание Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ + + + Upgrade Paused + Обновление приоÑтановлено + Running Работает @@ -162,6 +230,78 @@ Unhealthy ÐеработоÑпоÑобный + + Unexpected error retrieving BDC Endpoints: {0} + При получении конечных точек BDC возникла Ð½ÐµÐ¾Ð¶Ð¸Ð´Ð°Ð½Ð½Ð°Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ°: {0} + + + + + + + Basic + Обычный + + + Windows Authentication + Проверка подлинноÑти Windows. + + + Login to controller failed + Ðе удалоÑÑŒ войти на контроллер + + + Login to controller failed: {0} + Ошибка входа на контроллер: {0} + + + Username is required + ТребуетÑÑ Ð¸Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ + + + Password is required + ТребуетÑÑ Ð¿Ð°Ñ€Ð¾Ð»ÑŒ + + + url + URL-Ð°Ð´Ñ€ÐµÑ + + + username + Ð˜Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ + + + password + Пароль + + + Cluster Management URL + URL-Ð°Ð´Ñ€ÐµÑ ÑƒÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ ÐºÐ»Ð°Ñтером + + + Authentication type + Тип проверки подлинноÑти + + + Username + Ð˜Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ + + + Password + Пароль + + + Cluster Connection + Подключение клаÑтера + + + OK + ОК + + + Cancel + Отмена + @@ -200,6 +340,22 @@ + + + + Connect to Controller (preview) + Подключение к контроллеру (Ð¿Ñ€ÐµÐ´Ð²Ð°Ñ€Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ð²ÐµÑ€ÑиÑ) + + + + + + + View Details + ПоÑмотреть подробную информацию + + + @@ -207,8 +363,8 @@ Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ конечных точках контроллера не найдена - Big Data Cluster Dashboard - - Панель мониторинга клаÑтера больших данных - + Big Data Cluster Dashboard (preview) - + Панель мониторинга клаÑтера больших данных (Ð¿Ñ€ÐµÐ´Ð²Ð°Ñ€Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ð²ÐµÑ€ÑиÑ) - Yes @@ -263,8 +419,8 @@ ТребуетÑÑ Ð¿Ð°Ñ€Ð¾Ð»ÑŒ - Add New Controller - Добавление нового контроллера + Add New Controller (preview) + Добавление нового контроллера (Ð¿Ñ€ÐµÐ´Ð²Ð°Ñ€Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ð²ÐµÑ€ÑиÑ) url @@ -283,8 +439,8 @@ Запомнить пароль - URL - URL-Ð°Ð´Ñ€ÐµÑ + Cluster Management URL + URL-Ð°Ð´Ñ€ÐµÑ ÑƒÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ ÐºÐ»Ð°Ñтером Authentication type @@ -319,7 +475,7 @@ УÑтранение неполадок - Big data cluster overview + Big Data Cluster overview Обзор клаÑтера больших данных @@ -362,18 +518,18 @@ Metrics and Logs Метрики и журналы - - Metrics - Метрики + + Node Metrics + Метрики узла + + + SQL Metrics + Метрики SQL Logs Журналы - - View Details - ПоÑмотреть подробную информацию - @@ -422,31 +578,35 @@ Endpoint ÐšÐ¾Ð½ÐµÑ‡Ð½Ð°Ñ Ñ‚Ð¾Ñ‡ÐºÐ° - - View Details - ПоÑмотреть подробную информацию + + Unexpected error retrieving BDC Endpoints: {0} + При получении конечных точек BDC произошла Ð½ÐµÐ¾Ð¶Ð¸Ð´Ð°Ð½Ð½Ð°Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ°: {0} + + + The dashboard requires a connection. Please click retry to enter your credentials. + Ðеобходимо указать параметры ÑÐ¾ÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ Ð´Ð»Ñ Ð¿Ð°Ð½ÐµÐ»Ð¸ мониторинга. Ðажмите "Повторить", чтобы ввеÑти Ñвои учетные данные. + + + Unexpected error occurred: {0} + Произошла Ð½ÐµÐ¾Ð¶Ð¸Ð´Ð°Ð½Ð½Ð°Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ°: {0} Copy Копирование + + Endpoint '{0}' copied to clipboard + ÐšÐ¾Ð½ÐµÑ‡Ð½Ð°Ñ Ñ‚Ð¾Ñ‡ÐºÐ° "{0}" Ñкопирована в буфер обмена + - - Basic - Обычный - - - Windows Authentication - Проверка подлинноÑти Windows. - Mount Configuration ÐšÐ¾Ð½Ñ„Ð¸Ð³ÑƒÑ€Ð°Ñ†Ð¸Ñ Ð¿Ð¾Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ - + HDFS Path Путь HDFS @@ -454,22 +614,6 @@ Bad formatting of credentials at {0} Ðеправильное форматирование учетных данных в {0} - - Login to controller failed - Ðе удалоÑÑŒ войти на контроллер - - - Login to controller failed: {0} - Ошибка входа на контроллер: {0} - - - Username is required - ТребуетÑÑ Ð¸Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ - - - Password is required - ТребуетÑÑ Ð¿Ð°Ñ€Ð¾Ð»ÑŒ - Mounting HDFS folder on path {0} Подключение папки HDFS по пути {0} @@ -494,58 +638,30 @@ Unknown error occurred during the mount process ÐеизвеÑÑ‚Ð½Ð°Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ° в процеÑÑе Ð¿Ð¾Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ - - url - URL-Ð°Ð´Ñ€ÐµÑ - - - username - Ð˜Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ - - - password - Пароль - - - URL - URL-Ð°Ð´Ñ€ÐµÑ - - - Authentication type - Тип проверки подлинноÑти - - - Username - Ð˜Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ - - - Password - Пароль - - - Cluster Connection - Подключение клаÑтера - - - OK - ОК - - - Cancel - Отмена - - Mount HDFS Folder - Подключить папку HDFS + Mount HDFS Folder (preview) + Подключение папки HDFS (Ð¿Ñ€ÐµÐ´Ð²Ð°Ñ€Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ð²ÐµÑ€ÑиÑ) + + + Path to a new (non-existing) directory which you want to associate with the mount + Путь к новому (неÑущеÑтвующему) каталогу, который вы хотите ÑвÑзать Ñ Ñ‚Ð¾Ñ‡ÐºÐ¾Ð¹ Ð¿Ð¾Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ - + Remote URI Удаленный URI - + + The URI to the remote data source. Example for ADLS: abfs://fs@saccount.dfs.core.windows.net/ + URI удаленного иÑточника данных. Ðапример, Ð´Ð»Ñ ADLS: abfs://fs@saccount.dfs.core.windows.net/ + + Credentials Учетные данные + + Mount credentials for authentication to remote data source for reads + Учетные данные Ð¿Ð¾Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ Ð´Ð»Ñ Ð¿Ñ€Ð¾Ð²ÐµÑ€ÐºÐ¸ подлинноÑти Ð´Ð»Ñ Ñ‡Ñ‚ÐµÐ½Ð¸Ñ ÑƒÐ´Ð°Ð»ÐµÐ½Ð½Ð¾Ð³Ð¾ иÑточника данных + Refresh Mount Обновить подключение diff --git a/resources/xlf/ru/sql.ru.xlf b/resources/xlf/ru/sql.ru.xlf index c4d0f2c1b420..ae304b62834e 100644 --- a/resources/xlf/ru/sql.ru.xlf +++ b/resources/xlf/ru/sql.ru.xlf @@ -1,14 +1,5574 @@  - + - - SQL Language Basics - ОÑновы Ñзыка SQL + + Copying images is not supported + Копирование изображений не поддерживаетÑÑ - - Provides syntax highlighting and bracket matching in SQL files. - ПредоÑтавлÑет подÑветку ÑинтакÑиÑа и выделение парных Ñкобок в файлах SQL. + + + + + + Get Started + Ðачать работу + + + Show Getting Started + Показать "ПриÑÑ‚ÑƒÐ¿Ð°Ñ Ðº работе" + + + Getting &&Started + ПриÑÑ‚ÑƒÐ¿Ð°Ñ Ðº Ñ€&&аботе + && denotes a mnemonic + + + + + + + QueryHistory + QueryHistory + + + Whether Query History capture is enabled. If false queries executed will not be captured. + Включено ли ведение иÑтории запроÑов. ЕÑли Ñтот параметр имеет значение false, выполнÑемые запроÑÑ‹ не будут запиÑыватьÑÑ Ð² иÑтории. + + + View + Вид + + + Query History + ИÑÑ‚Ð¾Ñ€Ð¸Ñ Ð·Ð°Ð¿Ñ€Ð¾Ñов + + + &&Query History + &&ИÑÑ‚Ð¾Ñ€Ð¸Ñ Ð·Ð°Ð¿Ñ€Ð¾Ñов + && denotes a mnemonic + + + + + + + Connecting: {0} + Подключение: {0} + + + Running command: {0} + ВыполнÑÐµÐ¼Ð°Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð°: {0} + + + Opening new query: {0} + Открытие нового запроÑа: {0} + + + Cannot connect as no server information was provided + Ðе удаетÑÑ Ð²Ñ‹Ð¿Ð¾Ð»Ð½Ð¸Ñ‚ÑŒ подключение, Ñ‚.к. Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ Ñервере не указана + + + Could not open URL due to error {0} + Ðе удалоÑÑŒ открыть URL-Ð°Ð´Ñ€ÐµÑ Ð¸Ð·-за ошибки {0} + + + This will connect to server {0} + Будет выполнено подключение к Ñерверу {0} + + + Are you sure you want to connect? + Ð’Ñ‹ дейÑтвительно хотите выполнить Ñоединение? + + + &&Open + &&Открыть + + + Connecting query file + Файл Ñ Ð·Ð°Ð¿Ñ€Ð¾Ñом на подключение + + + + + + + Error + Ошибка + + + Warning + Предупреждение + + + Info + Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ + + + + + + + Saving results into different format disabled for this data provider. + Сохранение результатов в другом формате отключено Ð´Ð»Ñ Ñтого поÑтавщика данных. + + + Cannot serialize data as no provider has been registered + Ðе удаетÑÑ Ñериализировать данные, так как ни один поÑтавщик не зарегиÑтрирован + + + Serialization failed with an unknown error + Ðе удалоÑÑŒ выполнить Ñериализацию. Произошла неизвеÑÑ‚Ð½Ð°Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ° + + + + + + + Connection is required in order to interact with adminservice + ТребуетÑÑ Ð¿Ð¾Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ðµ Ð´Ð»Ñ Ð²Ð·Ð°Ð¸Ð¼Ð¾Ð´ÐµÐ¹ÑÑ‚Ð²Ð¸Ñ Ñ adminservice + + + No Handler Registered + Ðет зарегиÑтрированного обработчика + + + + + + + Select a file + Выберите файл + + + + + + + Disconnect + Отключить + + + Refresh + Обновить + + + + + + + Results Grid and Messages + Сетка результатов и ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ + + + Controls the font family. + ОпределÑет ÑемейÑтво шрифтов. + + + Controls the font weight. + УправлÑет наÑыщенноÑтью шрифта. + + + Controls the font size in pixels. + УправлÑет размером шрифта в пикÑелÑÑ…. + + + Controls the letter spacing in pixels. + УправлÑет интервалом между буквами в пикÑелÑÑ…. + + + Controls the row height in pixels + ОпределÑет выÑоту Ñтроки в пикÑелах + + + Controls the cell padding in pixels + ОпределÑет поле Ñчейки в пикÑелах + + + Auto size the columns width on inital results. Could have performance problems with large number of columns or large cells + ÐвтоматичеÑки уÑтанавливать ширину Ñтолбцов на оÑнове начальных результатов. При наличии большого чиÑла Ñтолбцов или широких Ñчеек возможны проблемы Ñ Ð¿Ñ€Ð¾Ð¸Ð·Ð²Ð¾Ð´Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð¾Ñтью + + + The maximum width in pixels for auto-sized columns + МакÑÐ¸Ð¼Ð°Ð»ÑŒÐ½Ð°Ñ ÑˆÐ¸Ñ€Ð¸Ð½Ð° в пикÑелÑÑ… Ð´Ð»Ñ Ñтолбцов, размер которых определÑетÑÑ Ð°Ð²Ñ‚Ð¾Ð¼Ð°Ñ‚Ð¸Ñ‡ÐµÑки + + + + + + + {0} in progress tasks + {0} выполнÑющихÑÑ Ð·Ð°Ð´Ð°Ñ‡ + + + View + Вид + + + Tasks + Задачи + + + &&Tasks + &&Задачи + && denotes a mnemonic + + + + + + + Connections + Ð¡Ð¾ÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ + + + View + Вид + + + Database Connections + ÐŸÐ¾Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ Ðº базе данных + + + data source connections + Ð¿Ð¾Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ Ð¸Ñточника данных + + + data source groups + группы иÑточника данных + + + Startup Configuration + ÐšÐ¾Ð½Ñ„Ð¸Ð³ÑƒÑ€Ð°Ñ†Ð¸Ñ Ð·Ð°Ð¿ÑƒÑка + + + True for the Servers view to be shown on launch of Azure Data Studio default; false if the last opened view should be shown + Задайте значение true, чтобы при запуÑке Azure Data Studio отображалоÑÑŒ предÑтавление "Серверы"; задайте значение false, чтобы при запуÑке Azure Data Studio отображалоÑÑŒ поÑледнее открытое предÑтавление + + + + + + + The maximum number of recently used connections to store in the connection list. + МакÑимальное количеÑтво недавно иÑпользованных Ñоединений Ð´Ð»Ñ Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð² ÑпиÑке подключений. + + + Default SQL Engine to use. This drives default language provider in .sql files and the default to use when creating a new connection. Valid option is currently MSSQL + Ядро СУБД, иÑпользуемое по умолчанию. Оно определÑет поÑтавщик Ñзыка по умолчанию в файлах .sql и иÑпользуетÑÑ Ð¿Ð¾ умолчанию при Ñоздании новых подключений. ДопуÑтимое значение: MSSQL + + + Attempt to parse the contents of the clipboard when the connection dialog is opened or a paste is performed. + Попытка проанализировать Ñодержимое буфера обмена, когда открыто диалоговое окно Ð¿Ð¾Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ Ð¸Ð»Ð¸ выполнÑетÑÑ Ð²Ñтавка. + + + + + + + Server Group color palette used in the Object Explorer viewlet. + Ð¦Ð²ÐµÑ‚Ð¾Ð²Ð°Ñ Ð¿Ð°Ð»Ð¸Ñ‚Ñ€Ð° группы Ñерверов, иÑпользуемых в предÑтавлении ÐžÐ±Ð¾Ð·Ñ€ÐµÐ²Ð°Ñ‚ÐµÐ»Ñ Ð¾Ð±ÑŠÐµÐºÑ‚Ð¾Ð². + + + Auto-expand Server Groups in the Object Explorer viewlet. + ÐвтоматичеÑки разворачивать группы Ñерверов в предÑтавлении ÐžÐ±Ð¾Ð·Ñ€ÐµÐ²Ð°Ñ‚ÐµÐ»Ñ Ð¾Ð±ÑŠÐµÐºÑ‚Ð¾Ð². + + + + + + + Preview Features + Функции предварительной верÑии + + + Enable unreleased preview features + Включить невыпущенные функции предварительной верÑии + + + Show connect dialog on startup + Показывать диалоговое окно Ð¿Ð¾Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¸ запуÑке + + + Obsolete API Notification + Уведомление об уÑтаревших API + + + Enable/disable obsolete API usage notification + Включить/отключить уведомление об иÑпользовании уÑтаревших API + + + + + + + Problems + Проблемы + + + + + + + Identifier of the account type + Идентификатор типа учетной запиÑи + + + (Optional) Icon which is used to represent the accpunt in the UI. Either a file path or a themable configuration + (ÐеобÑзательно) Значок, который иÑпользуетÑÑ Ð´Ð»Ñ Ð¿Ñ€ÐµÐ´ÑÑ‚Ð°Ð²Ð»ÐµÐ½Ð¸Ñ ÑƒÑ‡ÐµÑ‚Ð½Ð¾Ð¹ запиÑи в пользовательÑком интерфейÑе. Путь к файлу или ÐºÐ¾Ð½Ñ„Ð¸Ð³ÑƒÑ€Ð°Ñ†Ð¸Ñ Ñ Ð²Ð¾Ð·Ð¼Ð¾Ð¶Ð½Ð¾Ñтью Ð¿Ñ€Ð¸Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ñ‚ÐµÐ¼ + + + Icon path when a light theme is used + Путь к значку, когда иÑпользуетÑÑ ÑÐ²ÐµÑ‚Ð»Ð°Ñ Ñ‚ÐµÐ¼Ð° + + + Icon path when a dark theme is used + Путь к значку, когда иÑпользуетÑÑ Ñ‚ÐµÐ¼Ð½Ð°Ñ Ñ‚ÐµÐ¼Ð° + + + Contributes icons to account provider. + Передает значки поÑтавщику учетной запиÑи. + + + + + + + Indicates data property of a data set for a chart. + ОпределÑет ÑвойÑтво данных Ð´Ð»Ñ Ð½Ð°Ð±Ð¾Ñ€Ð° данных диаграммы. + + + + + + + Minimum value of the y axis + Минимальное значение Ð´Ð»Ñ Ð¾Ñи y + + + Maximum value of the y axis + МакÑимальное значение по оÑи y + + + Label for the y axis + Метка Ð´Ð»Ñ Ð¾Ñи y + + + Minimum value of the x axis + Минимальное значение по оÑи x + + + Maximum value of the x axis + МакÑимальное значение по оÑи x + + + Label for the x axis + Метка Ð´Ð»Ñ Ð¾Ñи x + + + + + + + Displays the results in a simple table + Отображает результаты в виде проÑтой таблицы + + + + + + + Displays an image, for example one returned by an R query using ggplot2 + Отображает изображение, например, возвращенное Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ запроÑа R, Ñ Ð¸Ñпользованием ggplot2 + + + What format is expected - is this a JPEG, PNG or other format? + Каков ожидаемый формат изображениÑ: JPEG, PNG или другой формат? + + + Is this encoded as hex, base64 or some other format? + ИÑпользуетÑÑ Ð»Ð¸ кодировка hex, base64 или другой формат кодировки? + + + + + + + For each column in a resultset, displays the value in row 0 as a count followed by the column name. Supports '1 Healthy', '3 Unhealthy' for example, where 'Healthy' is the column name and 1 is the value in row 1 cell 1 + Ð”Ð»Ñ ÐºÐ°Ð¶Ð´Ð¾Ð³Ð¾ Ñтолбца в наборе результатов в Ñтроке 0 отображаетÑÑ Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ðµ, предÑтавлÑющее Ñобой чиÑло, за которым Ñледует название Ñтолбца. Ðапример, '1 Healthy', '3 Unhealthy', где 'Healthy'— название Ñтолбца, а 1 — значение в Ñтроке 1 Ñчейки 1 + + + + + + + Manage + Управление + + + Dashboard + Панель мониторинга + + + + + + + The webview that will be displayed in this tab. + Веб предÑтавление, которое будет отображатьÑÑ Ð² Ñтой вкладке. + + + + + + + The controlhost that will be displayed in this tab. + Узел управлениÑ, который будет отображатьÑÑ Ð½Ð° Ñтой вкладке. + + + + + + + The list of widgets that will be displayed in this tab. + СпиÑок виджетов, которые будут отображатьÑÑ Ð½Ð° Ñтой вкладке. + + + The list of widgets is expected inside widgets-container for extension. + СпиÑок мини-приложений, которые должны находитьÑÑ Ð²Ð½ÑƒÑ‚Ñ€Ð¸ контейнера мини-приложений Ð´Ð»Ñ Ñ€Ð°ÑширениÑ. + + + + + + + The list of widgets or webviews that will be displayed in this tab. + СпиÑок виджетов или webviews, которые будут отображатьÑÑ Ð² Ñтой вкладке. + + + widgets or webviews are expected inside widgets-container for extension. + мини-Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð¸Ð»Ð¸ веб-предÑтавлениÑ, которые должны размещатьÑÑ Ð² контейнере мини-приложений раÑширениÑ. + + + + + + + Unique identifier for this container. + Уникальный идентификатор Ñтого контейнера. + + + The container that will be displayed in the tab. + Контейнер, который будет отображатьÑÑ Ð² Ñтой вкладке. + + + Contributes a single or multiple dashboard containers for users to add to their dashboard. + ДобавлÑет один или неÑколько контейнеров панелей мониторинга, которые пользователи могут добавить на Ñвои панели мониторинга. + + + No id in dashboard container specified for extension. + Идентификатор контейнера панели мониторинга Ð´Ð»Ñ Ñ€Ð°ÑÑˆÐ¸Ñ€ÐµÐ½Ð¸Ñ Ð½Ðµ указан. + + + No container in dashboard container specified for extension. + Ð’ контейнере панели мониторинга Ð´Ð»Ñ Ñ€Ð°ÑÑˆÐ¸Ñ€ÐµÐ½Ð¸Ñ Ð½Ðµ указан контейнер. + + + Exactly 1 dashboard container must be defined per space. + Ð”Ð»Ñ ÐºÐ°Ð¶Ð´Ð¾Ð³Ð¾ проÑтранÑтва необходимо определить ровно один контейнер панели мониторинга. + + + Unknown container type defines in dashboard container for extension. + Ð’ контейнере панели мониторинга Ð´Ð»Ñ Ñ€Ð°ÑÑˆÐ¸Ñ€ÐµÐ½Ð¸Ñ ÑƒÐºÐ°Ð·Ð°Ð½ неизвеÑтный тип контейнера. + + + + + + + The model-backed view that will be displayed in this tab. + ПредÑтавление на оÑнове модели, которое будет отображатьÑÑ Ð² Ñтой вкладке. + + + + + + + Unique identifier for this nav section. Will be passed to the extension for any requests. + Уникальный идентификатор Ð´Ð»Ñ Ñтой Ñекции навигации. Будет передан раÑширению Ð´Ð»Ñ Ð»ÑŽÐ±Ñ‹Ñ… запроÑов. + + + (Optional) Icon which is used to represent this nav section in the UI. Either a file path or a themeable configuration + (ÐеобÑзательно) Значок, который иÑпользуетÑÑ Ð´Ð»Ñ Ð¿Ñ€ÐµÐ´ÑÑ‚Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ñ€Ð°Ð·Ð´ÐµÐ»Ð° навигации в пользовательÑком интерфейÑе. Путь к файлу или к конфигурации Ñ Ð²Ð¾Ð·Ð¼Ð¾Ð¶Ð½Ð¾Ñтью иÑÐ¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ Ñ‚ÐµÐ¼ + + + Icon path when a light theme is used + Путь к значку, когда иÑпользуетÑÑ ÑÐ²ÐµÑ‚Ð»Ð°Ñ Ñ‚ÐµÐ¼Ð° + + + Icon path when a dark theme is used + Путь к значку, когда иÑпользуетÑÑ Ñ‚ÐµÐ¼Ð½Ð°Ñ Ñ‚ÐµÐ¼Ð° + + + Title of the nav section to show the user. + Ðазвание навигационной Ñекции Ð´Ð»Ñ Ð¾Ñ‚Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»ÑŽ. + + + The container that will be displayed in this nav section. + Контейнер, который будет отображатьÑÑ Ð² разделе навигации. + + + The list of dashboard containers that will be displayed in this navigation section. + СпиÑок контейнеров панелей мониторинга, отображаемых в Ñтом разделе. + + + property `icon` can be omitted or must be either a string or a literal like `{dark, light}` + СвойÑтво icon может быть пропущено или должно быть Ñтрокой или литералом, например "{dark, light}" + + + No title in nav section specified for extension. + Заголовок в разделе навигации Ð´Ð»Ñ Ñ€Ð°ÑÑˆÐ¸Ñ€ÐµÐ½Ð¸Ñ Ð½Ðµ указан. + + + No container in nav section specified for extension. + Ð”Ð»Ñ Ñ€Ð°ÑÑˆÐ¸Ñ€ÐµÐ½Ð¸Ñ Ð½Ðµ указан контейнер в разделе навигации. + + + Exactly 1 dashboard container must be defined per space. + Ð”Ð»Ñ ÐºÐ°Ð¶Ð´Ð¾Ð³Ð¾ проÑтранÑтва необходимо определить ровно один контейнер панели мониторинга. + + + NAV_SECTION within NAV_SECTION is an invalid container for extension. + NAV_SECTION в NAV_SECTION ÑвлÑетÑÑ Ð½ÐµÐ´Ð¾Ð¿ÑƒÑтимым контейнером Ð´Ð»Ñ Ñ€Ð°ÑширениÑ. + + + + + + + Backup + Резервное копирование + + + + + + + Unique identifier for this tab. Will be passed to the extension for any requests. + Уникальный идентификатор Ñтой вкладки. Будет передаватьÑÑ Ñ€Ð°Ñширению при выполнении любых запроÑов. + + + Title of the tab to show the user. + Ðазвание вкладки, отображаемое Ð´Ð»Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ. + + + Description of this tab that will be shown to the user. + ОпиÑание Ñтой вкладки, которое будет показано пользователю. + + + Condition which must be true to show this item + УÑловие, которое должно иметь значение True, чтобы отображалÑÑ Ñтот Ñлемент + + + Defines the connection types this tab is compatible with. Defaults to 'MSSQL' if not set + ОпределÑет типы Ñоединений, Ñ ÐºÐ¾Ñ‚Ð¾Ñ€Ñ‹Ð¼Ð¸ ÑовмеÑтима Ñта вкладка. ЕÑли не задано, иÑпользуетÑÑ Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ðµ по умолчанию "MSSQL". + + + The container that will be displayed in this tab. + Контейнер, который будет отображатьÑÑ Ð² Ñтой вкладке. + + + Whether or not this tab should always be shown or only when the user adds it. + Будет ли Ñта вкладка отображатьÑÑ Ð²Ñегда или только при добавлении её пользователем. + + + Whether or not this tab should be used as the Home tab for a connection type. + Следует ли иÑпользовать Ñту вкладку в качеÑтве вкладки "ГлавнаÑ" Ð´Ð»Ñ Ñ‚Ð¸Ð¿Ð° подключениÑ. + + + Contributes a single or multiple tabs for users to add to their dashboard. + ДобавлÑет одну или неÑколько вкладок Ð´Ð»Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»ÐµÐ¹, которые будут добавлены на их панель мониторинга. + + + No title specified for extension. + Заголовок раÑÑˆÐ¸Ñ€ÐµÐ½Ð¸Ñ Ð½Ðµ указан. + + + No description specified to show. + ОпиÑание не указано. + + + No container specified for extension. + Ðе выбран контейнер Ð´Ð»Ñ Ñ€Ð°ÑширениÑ. + + + Exactly 1 dashboard container must be defined per space + Ð”Ð»Ñ ÐºÐ°Ð¶Ð´Ð¾Ð³Ð¾ проÑтранÑтва необходимо определить ровно один контейнер панели мониторинга + + + + + + + Restore + ВоÑÑтановить + + + Restore + ВоÑÑтановить + + + + + + + Cannot expand as the required connection provider '{0}' was not found + Ðе удаетÑÑ Ð²Ñ‹Ð¿Ð¾Ð»Ð½Ð¸Ñ‚ÑŒ раÑширение, так как требуемый поÑтавщик Ð¿Ð¾Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ "{0}" не найден + + + User canceled + ДейÑтвие отменено пользователем + + + Firewall dialog canceled + Диалоговое окно брандмауÑра отменено + + + + + + + 1 or more tasks are in progress. Are you sure you want to quit? + ВыполнÑетÑÑ Ð¾Ð´Ð½Ð° или неÑколько задач. Ð’Ñ‹ дейÑтвительно хотите завершить работу? + + + Yes + Да + + + No + Ðет + + + + + + + Connection is required in order to interact with JobManagementService + ТребуетÑÑ Ð¿Ð¾Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ðµ Ð´Ð»Ñ Ð²Ð·Ð°Ð¸Ð¼Ð¾Ð´ÐµÐ¹ÑÑ‚Ð²Ð¸Ñ Ñ JobManagementService + + + No Handler Registered + Ðет зарегиÑтрированного обработчика + + + + + + + An error occured while loading the file browser. + Произошла ошибка при загрузке браузера файлов. + + + File browser error + Ошибка браузера файлов + + + + + + + Notebook Editor + Редактор запиÑной книжки + + + New Notebook + Создать запиÑную книжку + + + New Notebook + Создать запиÑную книжку + + + SQL kernel: stop Notebook execution when error occurs in a cell. + Ядро SQL: оÑтановить выполнение запиÑной книжки при возникновении ошибки в Ñчейке. + + + + + + + Script as Create + Выполнение ÑÑ†ÐµÐ½Ð°Ñ€Ð¸Ñ Ð² режиме ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ + + + Script as Drop + Сценарий как удаление + + + Select Top 1000 + Выберите первые 1000 + + + Script as Execute + Выполнение ÑÑ†ÐµÐ½Ð°Ñ€Ð¸Ñ Ð² режиме Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ + + + Script as Alter + Выполнение ÑÑ†ÐµÐ½Ð°Ñ€Ð¸Ñ Ð² режиме Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ + + + Edit Data + Редактировать данные + + + Select Top 1000 + Выберите первые 1000 + + + Script as Create + Выполнение ÑÑ†ÐµÐ½Ð°Ñ€Ð¸Ñ Ð² режиме ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ + + + Script as Execute + Выполнение ÑÑ†ÐµÐ½Ð°Ñ€Ð¸Ñ Ð² режиме Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ + + + Script as Alter + Выполнение ÑÑ†ÐµÐ½Ð°Ñ€Ð¸Ñ Ð² режиме Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ + + + Script as Drop + Сценарий как удаление + + + Refresh + Обновить + + + + + + + Connection error + Ошибка Ð¿Ð¾Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ + + + Connection failed due to Kerberos error. + Сбой Ð¿Ð¾Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ Ð¸Ð·-за ошибок Kerberos. + + + Help configuring Kerberos is available at {0} + Справка по наÑтройке Kerberos доÑтупна на Ñтранице {0} + + + If you have previously connected you may need to re-run kinit. + ЕÑли вы подключалиÑÑŒ ранее, может потребоватьÑÑ Ð·Ð°Ð¿ÑƒÑтить kinit повторно. + + + + + + + Refresh account was canceled by the user + Обновление учетной запиÑи было отменено пользователем + + + + + + + Specifies view templates + Задает шаблоны предÑÑ‚Ð°Ð²Ð»ÐµÐ½Ð¸Ñ + + + Specifies session templates + ОпределÑет шаблоны ÑеÑÑии + + + Profiler Filters + Фильтры профилировщика + + + + + + + Toggle Query History + Включить или отключить иÑторию запроÑов + + + Delete + Удалить + + + Clear All History + ОчиÑтить вÑÑŽ иÑторию + + + Open Query + Открыть Ð·Ð°Ð¿Ñ€Ð¾Ñ + + + Run Query + Выполнить Ð·Ð°Ð¿Ñ€Ð¾Ñ + + + Toggle Query History capture + Включить или отключить ведение иÑтории запроÑов + + + Pause Query History Capture + ПриоÑтановить ведение иÑтории запроÑов + + + Start Query History Capture + Ðачать ведение иÑтории запроÑов + + + + + + + Preview features are required in order for extensions to be fully supported and for some actions to be available. Would you like to enable preview features? + Функции предварительной верÑии требуютÑÑ Ð´Ð»Ñ Ð¿Ð¾Ð»Ð½Ð¾Ð¹ поддержки раÑширений и Ð´Ð»Ñ Ð¾Ð±ÐµÑÐ¿ÐµÑ‡ÐµÐ½Ð¸Ñ Ð´Ð¾ÑтупноÑти некоторых дейÑтвий. Ð’Ñ‹ хотите включить функции предварительной верÑии? + + + Yes + Да + + + No + Ðет + + + No, don't show again + Больше не показывать + + + + + + + Commit row failed: + Ðе удалоÑÑŒ зафикÑировать Ñтроку: + + + Started executing query "{0}" + Ðачато выполнение запроÑа "{0}" + + + Update cell failed: + Сбой Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ Ñчейки: + + + + + + + Failed to create Object Explorer session + Ðе удалоÑÑŒ Ñоздать ÑеÑÑию Object Explorer + + + Multiple errors: + ÐеÑколько ошибок: + + + + + + + No URI was passed when creating a notebook manager + При Ñоздании диÑпетчера запиÑных книжек не был передан URI + + + Notebook provider does not exist + ПоÑтавщик запиÑных книжек не ÑущеÑтвует + + + + + + + Query Results + Результаты запроÑа + + + Query Editor + Редактор запроÑов + + + New Query + Создать Ð·Ð°Ð¿Ñ€Ð¾Ñ + + + [Optional] When true, column headers are included when saving results as CSV + [ÐеобÑзательно] ЕÑли Ñтот параметр имеет значение true, то при Ñохранении результата в файле CSV в файл будут включены заголовки Ñтолбцов + + + [Optional] The custom delimiter to use between values when saving as CSV + [ÐеобÑзательно] ПользовательÑкий разделитель значений при Ñохранении в формате CSV + + + [Optional] Character(s) used for seperating rows when saving results as CSV + [ÐеобÑзательно] Символы, иÑпользуемые Ð´Ð»Ñ Ñ€Ð°Ð·Ð´ÐµÐ»ÐµÐ½Ð¸Ñ Ñтрок при Ñохранении результатов в файле CSV + + + [Optional] Character used for enclosing text fields when saving results as CSV + [ÐеобÑзательно] Символ, иÑпользуемый Ð´Ð»Ñ Ð¾Ð±Ñ€Ð°Ð¼Ð»ÐµÐ½Ð¸Ñ Ñ‚ÐµÐºÑтовых полей при Ñохранении результатов в CSV + + + [Optional] File encoding used when saving results as CSV + [ÐеобÑзательно] Кодировка файла, иÑÐ¿Ð¾Ð»ÑŒÐ·ÑƒÐµÐ¼Ð°Ñ Ð¿Ñ€Ð¸ Ñохранении результатов в формате CSV + + + Enable results streaming; contains few minor visual issues + Включить потоковую передачу результатов; имеетÑÑ Ð½ÐµÑколько небольших проблем Ñ Ð¾Ñ‚Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸ÐµÐ¼ + + + [Optional] When true, XML output will be formatted when saving results as XML + [Опционально] ЕÑли Ñтот параметр имеет значение true, выходные данные XML будут отформатированы при Ñохранении результатов в виде XML + + + [Optional] File encoding used when saving results as XML + [ÐеобÑзательно] Кодировка файла, ÐºÐ¾Ñ‚Ð¾Ñ€Ð°Ñ Ð¸ÑпользуетÑÑ Ð¿Ñ€Ð¸ Ñохранении результатов в виде XML + + + [Optional] Configuration options for copying results from the Results View + [ÐеобÑзательно] Параметры конфигурации Ð´Ð»Ñ ÐºÐ¾Ð¿Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ñ€ÐµÐ·ÑƒÐ»ÑŒÑ‚Ð°Ñ‚Ð¾Ð² из предÑÑ‚Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ñ€ÐµÐ·ÑƒÐ»ÑŒÑ‚Ð°Ñ‚Ð¾Ð² + + + [Optional] Configuration options for copying multi-line results from the Results View + [ÐеобÑзательно] Параметры конфигурации Ð´Ð»Ñ ÐºÐ¾Ð¿Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¼Ð½Ð¾Ð³Ð¾Ñтрочных результатов из предÑÑ‚Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ñ€ÐµÐ·ÑƒÐ»ÑŒÑ‚Ð°Ñ‚Ð¾Ð² + + + [Optional] Should execution time be shown for individual batches + [Опционально] Должно ли Ð²Ñ€ÐµÐ¼Ñ Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ Ð¾Ñ‚Ð¾Ð±Ñ€Ð°Ð¶Ð°Ñ‚ÑŒÑÑ Ð´Ð»Ñ Ð¾Ñ‚Ð´ÐµÐ»ÑŒÐ½Ñ‹Ñ… пакетов + + + [Optional] the default chart type to use when opening Chart Viewer from a Query Results + [ÐеобÑзательно] тип диаграммы по умолчанию, иÑпользуемый при открытии ÑредÑтва проÑмотра диаграмм из результатов запроÑа + + + Tab coloring will be disabled + Цветовое выделение вкладок будет отключено + + + The top border of each editor tab will be colored to match the relevant server group + ВерхнÑÑ Ð³Ñ€Ð°Ð½Ð¸Ñ†Ð° каждой вкладки редактора будет окрашена в цвет ÑоответÑтвующей группы Ñерверов + + + Each editor tab's background color will match the relevant server group + Цвет фона каждой вкладки редактора будет ÑоответÑтвовать ÑвÑзанной группе Ñерверов + + + Controls how to color tabs based on the server group of their active connection + УправлÑет тем, как определÑÑŽÑ‚ÑÑ Ñ†Ð²ÐµÑ‚Ð° вкладок на оÑнове группы Ñерверов активного Ð¿Ð¾Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ + + + Controls whether to show the connection info for a tab in the title. + УправлÑет тем, Ñледует ли отображать ÑÐ²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¾ подключении Ð´Ð»Ñ Ð²ÐºÐ»Ð°Ð´ÐºÐ¸ в заголовке. + + + Prompt to save generated SQL files + Запрашивать Ñохранение Ñозданных файлов SQL + + + Should IntelliSense be enabled + Должен ли быть включен IntelliSense + + + Should IntelliSense error checking be enabled + Должна ли быть включена проверка ошибок IntelliSense + + + Should IntelliSense suggestions be enabled + Должны ли быть включены подÑказки IntelliSense + + + Should IntelliSense quick info be enabled + Должны ли быть включены краткие ÑÐ²ÐµÐ´ÐµÐ½Ð¸Ñ IntelliSense + + + Should IntelliSense suggestions be lowercase + Следует ли подÑказкам IntelliSense иÑпользовать Ñтрочные буквы + + + Maximum number of rows to return before the server stops processing your query. + МакÑимальное чиÑло Ñтрок, которые будут возвращены перед тем, как Ñервер переÑтанет обрабатывать ваш запроÑ. + + + Maximum size of text and ntext data returned from a SELECT statement + МакÑимальный размер текÑта и данных ntext, возвращаемых инÑтрукцией SELECT + + + An execution time-out of 0 indicates an unlimited wait (no time-out) + Ð’Ñ€ÐµÐ¼Ñ Ð¾Ð¶Ð¸Ð´Ð°Ð½Ð¸Ñ Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ, равное 0, указывает на неограниченное Ð²Ñ€ÐµÐ¼Ñ Ð¾Ð¶Ð¸Ð´Ð°Ð½Ð¸Ñ + + + Enable SET NOCOUNT option + УÑтановить параметр SET NOCOUNT + + + Enable SET NOEXEC option + УÑтановить параметр SET NOEXEC + + + Enable SET PARSEONLY option + УÑтановить параметр SET PARSEONLY + + + Enable SET ARITHABORT option + УÑтановить параметр SET ARITHABORT + + + Enable SET STATISTICS TIME option + УÑтановить параметр SET STATISTICS TIME + + + Enable SET STATISTICS IO option + УÑтановить параметр SET STATISTICS IO + + + Enable SET XACT_ABORT ON option + УÑтановить параметр SET XACT_ABORT ON + + + Enable SET TRANSACTION ISOLATION LEVEL option + УÑтановить параметр SET TRANSACTION ISOLATION LEVEL + + + Enable SET DEADLOCK_PRIORITY option + УÑтановить параметр SET DEADLOCK_PRIORITY + + + Enable SET LOCK TIMEOUT option (in milliseconds) + УÑтановить параметр SET LOCK TIMEOUT (в миллиÑекундах) + + + Enable SET QUERY_GOVERNOR_COST_LIMIT + УÑтановить параметр SET QUERY_GOVERNOR_COST_LIMIT + + + Enable SET ANSI_DEFAULTS + УÑтановить параметр SET ANSI_DEFAULTS + + + Enable SET QUOTED_IDENTIFIER + УÑтановить параметр SET QUOTED_IDENTIFIER + + + Enable SET ANSI_NULL_DFLT_ON + УÑтановить параметр SET ANSI_NULL_DFLT_ON + + + Enable SET IMPLICIT_TRANSACTIONS + УÑтановить параметр SET IMPLICIT_TRANSACTIONS + + + Enable SET CURSOR_CLOSE_ON_COMMIT + УÑтановить параметр SET CURSOR_CLOSE_ON_COMMIT + + + Enable SET ANSI_PADDING + УÑтановить параметр SET ANSI_PADDING + + + Enable SET ANSI_WARNINGS + УÑтановить параметр SET ANSI_WARNINGS + + + Enable SET ANSI_NULLS + Включить SET ANSI_NULLS + + + Set keybinding workbench.action.query.shortcut{0} to run the shortcut text as a procedure call. Any selected text in the query editor will be passed as a parameter + УÑтановка ÑÐ¾Ñ‡ÐµÑ‚Ð°Ð½Ð¸Ñ ÐºÐ»Ð°Ð²Ð¸Ñˆ workbench.action.query.shortcut{0} Ð´Ð»Ñ Ð·Ð°Ð¿ÑƒÑка текÑта Ñрлыка при вызове процедуры. Ð’ качеÑтве параметра будет передан любой выбранный текÑÑ‚ в редакторе запроÑов + + + + + + + Common id for the provider + Общий идентификатор поÑтавщика + + + Display Name for the provider + Отображаемое Ð¸Ð¼Ñ Ð¿Ð¾Ñтавщика + + + Icon path for the server type + Путь к значку Ð´Ð»Ñ Ñ‚Ð¸Ð¿Ð° Ñервера + + + Options for connection + Параметры Ð¿Ð¾Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ + + + + + + + OK + ОК + + + Close + Закрыть + + + Copy details + Скопировать ÑÐ²ÐµÐ´ÐµÐ½Ð¸Ñ + + + + + + + Add server group + Добавить группу Ñерверов + + + Edit server group + Редактировать группу Ñерверов + + + + + + + Error adding account + Ошибка при добавлении учетной запиÑи + + + Firewall rule error + Ошибка правила брандмауÑра + + + + + + + Some of the loaded extensions are using obsolete APIs, please find the detailed information in the Console tab of Developer Tools window + Ðекоторые из загруженных раÑширений иÑпользуют уÑтаревшие API. Подробные ÑÐ²ÐµÐ´ÐµÐ½Ð¸Ñ Ñм. на вкладке "КонÑоль" окна "ИнÑтрументы разработчика" + + + Don't Show Again + Больше не показывать + + + + + + + Toggle Tasks + Переключить задачи + + + + + + + Show Connections + Показать Ð¿Ð¾Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ + + + Servers + Серверы + + + + + + + Identifier of the view. Use this to register a data provider through `vscode.window.registerTreeDataProviderForView` API. Also to trigger activating your extension by registering `onView:${id}` event to `activationEvents`. + Идентификатор предÑтавлениÑ. ИÑпользуйте его Ð´Ð»Ñ Ñ€ÐµÐ³Ð¸Ñтрации поÑтавщика данных Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ API-интерфейÑа "vscode.window.registerTreeDataProviderForView", а также Ð´Ð»Ñ Ð°ÐºÑ‚Ð¸Ð²Ð°Ñ†Ð¸Ð¸ раÑÑˆÐ¸Ñ€ÐµÐ½Ð¸Ñ Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ региÑтрации ÑÐ¾Ð±Ñ‹Ñ‚Ð¸Ñ "onView:${id}" в "activationEvents". + + + The human-readable name of the view. Will be shown + ПонÑтное Ð¸Ð¼Ñ Ð¿Ñ€ÐµÐ´ÑтавлениÑ. Будет отображатьÑÑ Ð½Ð° Ñкране + + + Condition which must be true to show this view + УÑловие, которое должно иметь значение 'true', чтобы отображалоÑÑŒ Ñто предÑтавление + + + Contributes views to the editor + ДобавлÑет предÑÑ‚Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð² редактор + + + Contributes views to Data Explorer container in the Activity bar + ДобавлÑет предÑÑ‚Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð² контейнер ÐžÐ±Ð¾Ð·Ñ€ÐµÐ²Ð°Ñ‚ÐµÐ»Ñ Ð´Ð°Ð½Ð½Ñ‹Ñ… на панели дейÑтвий + + + Contributes views to contributed views container + ДобавлÑет предÑÑ‚Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð² контейнер добавленных предÑтавлений + + + View container '{0}' does not exist and all views registered to it will be added to 'Data Explorer'. + ПредÑтавление контейнера '{0}' не ÑущеÑтвует, и вÑе предÑтавлениÑ, зарегиÑтрированные в нем, будут добавлены в обозреватель данных. + + + Cannot register multiple views with same id `{0}` in the view container `{1}` + Ðе удаетÑÑ Ð·Ð°Ñ€ÐµÐ³Ð¸Ñтрировать неÑколько предÑтавлений Ñ Ð¾Ð´Ð¸Ð½Ð°ÐºÐ¾Ð²Ñ‹Ð¼ идентификатором '{0}' в контейнере предÑÑ‚Ð°Ð²Ð»ÐµÐ½Ð¸Ñ '{1}' + + + A view with id `{0}` is already registered in the view container `{1}` + ПредÑтавление Ñ Ð¸Ð´ÐµÐ½Ñ‚Ð¸Ñ„Ð¸ÐºÐ°Ñ‚Ð¾Ñ€Ð¾Ð¼ '{0}' уже зарегиÑтрировано в контейнере предÑÑ‚Ð°Ð²Ð»ÐµÐ½Ð¸Ñ '{1}' + + + views must be an array + предÑÑ‚Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð´Ð¾Ð»Ð¶Ð½Ñ‹ быть маÑÑивом + + + property `{0}` is mandatory and must be of type `string` + ÑвойÑтво "{0}" ÑвлÑетÑÑ Ð¾Ð±Ñзательным и должно иметь тип string + + + property `{0}` can be omitted or must be of type `string` + ÑвойÑтво "{0}" может быть опущено или должно иметь тип string + + + + + + + Connection Status + СоÑтоÑние ÑÐ¾ÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ + + + + + + + Manage + Управление + + + Show Details + Показать подробные ÑÐ²ÐµÐ´ÐµÐ½Ð¸Ñ + + + Learn How To Configure The Dashboard + Ð¡Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¾ наÑтройке панели мониторинга + + + + + + + Widget used in the dashboards + Мини-приложение, иÑпользуемое на панелÑÑ… мониторинга + + + + + + + Displays results of a query as a chart on the dashboard + Отображает результаты запроÑа в виде диаграммы на панели мониторинга + + + Maps 'column name' -> color. for example add 'column1': red to ensure this column uses a red color + Задает ÑопоÑтавление 'Ð¸Ð¼Ñ Ñтолбца' -> цвет. Ðапример, добавьте "'column1': red", чтобы задать Ð´Ð»Ñ Ñтого Ñтолбца краÑный цвет + + + Indicates preferred position and visibility of the chart legend. These are the column names from your query, and map to the label of each chart entry + Указывает предпочтительное положение и видимоÑÑ‚ÑŒ уÑловных обозначений диаграммы. Это имена Ñтолбцов из вашего запроÑа и карта Ð´Ð»Ñ ÑопоÑÑ‚Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ñ ÐºÐ°Ð¶Ð´Ð¾Ð¹ запиÑью диаграммы + + + If dataDirection is horizontal, setting this to true uses the first columns value for the legend. + ЕÑли направление данные (dataDirection) горизонтальное, то при указании Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ true Ð´Ð»Ñ Ñтого параметра Ð´Ð»Ñ ÑƒÑловных обозначений будут иÑпользоватьÑÑ Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ Ð² первом Ñтолбце. + + + If dataDirection is vertical, setting this to true will use the columns names for the legend. + ЕÑли dataDirection равно vertical и Ñто значение равно true, то Ð´Ð»Ñ ÑƒÑловных обозначений будут иÑпользованы имена Ñтолбцов. + + + Defines whether the data is read from a column (vertical) or a row (horizontal). For time series this is ignored as direction must be vertical. + ОпределÑет, ÑчитываютÑÑ Ð»Ð¸ данные из Ñтолбцов (по вертикали) или из Ñтрок (по горизонтали). Ð”Ð»Ñ Ð²Ñ€ÐµÐ¼ÐµÐ½Ð½Ñ‹Ñ… Ñ€Ñдов Ñто игнорируетÑÑ, так как Ð´Ð»Ñ Ð½Ð¸Ñ… вÑегда иÑпользуетÑÑ Ð½Ð°Ð¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ðµ по вертикали. + + + If showTopNData is set, showing only top N data in the chart. + ЕÑли параметр showTopNData уÑтановлен, отображаютÑÑ Ñ‚Ð¾Ð»ÑŒÐºÐ¾ первые N данных на диаграмме. + + + + + + + Condition which must be true to show this item + УÑловие, которое должно иметь значение True, чтобы отображалÑÑ Ñтот Ñлемент + + + The title of the container + Ðазвание контейнера + + + The row of the component in the grid + Строке компонента в Ñетке + + + The rowspan of the component in the grid. Default value is 1. Use '*' to set to number of rows in the grid. + Ðтрибут rowspan компонента Ñетки. Значение по умолчанию — 1. ИÑпользуйте значение '*', чтобы задать количеÑтво Ñтрок в Ñетке. + + + The column of the component in the grid + Столбец компонента в Ñетке + + + The colspan of the component in the grid. Default value is 1. Use '*' to set to number of columns in the grid. + Ðтрибут colspan компонента Ñетки. Значение по умолчанию — 1. ИÑпользуйте значение '*', чтобы задать количеÑтво Ñтолбцов в Ñетке. + + + Unique identifier for this tab. Will be passed to the extension for any requests. + Уникальный идентификатор Ñтой вкладки. Будет передаватьÑÑ Ñ€Ð°Ñширению при выполнении любых запроÑов. + + + Extension tab is unknown or not installed. + Вкладка «раÑширение» неизвеÑтна или не уÑтановлена. + + + + + + + Enable or disable the properties widget + Включение или отключение виджета ÑвойÑтв + + + Property values to show + Ð—Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ ÑвойÑтв Ð´Ð»Ñ Ð¾Ñ‚Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ + + + Display name of the property + Отображаемое Ð¸Ð¼Ñ ÑвойÑтва + + + Value in the Database Info Object + Значение в объекте информации базы данных + + + Specify specific values to ignore + Укажите конкретные значениÑ, которые нужно пропуÑтить + + + Recovery Model + Recovery Model + + + Last Database Backup + ПоÑледнее резервное копирование базы данных + + + Last Log Backup + ПоÑледнÑÑ Ñ€ÐµÐ·ÐµÑ€Ð²Ð½Ð°Ñ ÐºÐ¾Ð¿Ð¸Ñ Ð¶ÑƒÑ€Ð½Ð°Ð»Ð° + + + Compatibility Level + Compatibility Level + + + Owner + Владелец + + + Customizes the database dashboard page + ÐаÑтраивает Ñтраницу панели мониторинга базы данных + + + Customizes the database dashboard tabs + ÐаÑтраивает вкладки панели мониторинга базы данных + + + + + + + Enable or disable the properties widget + Включение или отключение виджета ÑвойÑтв + + + Property values to show + Ð—Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ ÑвойÑтв Ð´Ð»Ñ Ð¾Ñ‚Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ + + + Display name of the property + Отображаемое Ð¸Ð¼Ñ ÑвойÑтва + + + Value in the Server Info Object + Значение в объекте Ñведений о Ñервере + + + Version + ВерÑÐ¸Ñ + + + Edition + ВыпуÑк + + + Computer Name + Ð˜Ð¼Ñ ÐºÐ¾Ð¼Ð¿ÑŒÑŽÑ‚ÐµÑ€Ð° + + + OS Version + ВерÑÐ¸Ñ ÐžÐ¡ + + + Customizes the server dashboard page + ÐаÑтраивает Ñтраницу панели мониторинга Ñервера + + + Customizes the Server dashboard tabs + ÐаÑтраивает вкладки панели мониторинга Ñервера + + + + + + + Manage + Управление + + + + + + + Widget used in the dashboards + Мини-приложение, иÑпользуемое на панелÑÑ… мониторинга + + + Widget used in the dashboards + Мини-приложение, иÑпользуемое на панелÑÑ… мониторинга + + + Widget used in the dashboards + Мини-приложение, иÑпользуемое на панелÑÑ… мониторинга + + + + + + + Adds a widget that can query a server or database and display the results in multiple ways - as a chart, summarized count, and more + ДобавлÑет мини-приложение, которое может опрашивать Ñервер или базу данных и отображать результаты различными ÑпоÑобами — в виде диаграмм, Ñводных данных и Ñ‚.д. + + + Unique Identifier used for caching the results of the insight. + Уникальный идентификатор, иÑпользуемый Ð´Ð»Ñ ÐºÑÑˆÐ¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ñ€ÐµÐ·ÑƒÐ»ÑŒÑ‚Ð°Ñ‚Ð¾Ð² анализа. + + + SQL query to run. This should return exactly 1 resultset. + SQL-Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð´Ð»Ñ Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ. Он должен возвратить ровно один набор данных. + + + [Optional] path to a file that contains a query. Use if 'query' is not set + [ÐеобÑзательно] путь к файлу, который Ñодержит запроÑ. ИÑпользуйте, еÑли параметр "query" не уÑтановлен + + + [Optional] Auto refresh interval in minutes, if not set, there will be no auto refresh + [ÐеобÑзательно] Интервал автоматичеÑкого Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ Ð² минутах. ЕÑли значение не задано, то автоматичеÑкое обновление не будет выполнÑÑ‚ÑŒÑÑ. + + + Which actions to use + ИÑпользуемые дейÑÑ‚Ð²Ð¸Ñ + + + Target database for the action; can use the format '${ columnName }' to use a data driven column name. + Ð¦ÐµÐ»ÐµÐ²Ð°Ñ Ð±Ð°Ð·Ð° данных Ð´Ð»Ñ Ñтого дейÑтвиÑ; чтобы указать Ð¸Ð¼Ñ Ñтолбца на оÑнове данных, можно иÑпользовать формат "${ columnName }". + + + Target server for the action; can use the format '${ columnName }' to use a data driven column name. + Целевой Ñервер Ð´Ð»Ñ Ñтого дейÑтвиÑ; чтобы указать Ð¸Ð¼Ñ Ñтолбца на оÑнове данных, можно иÑпользовать формат "${ columnName }". + + + Target user for the action; can use the format '${ columnName }' to use a data driven column name. + Целевой пользователь Ð´Ð»Ñ Ñтого дейÑтвиÑ; чтобы указать Ð¸Ð¼Ñ Ñтолбца на оÑнове данных, можно иÑпользовать формат "${ columnName }". + + + Identifier of the insight + Идентификатор аналитичеÑких данных + + + Contributes insights to the dashboard palette. + ДобавлÑет аналитичеÑкие ÑÐ²ÐµÐ´ÐµÐ½Ð¸Ñ Ð½Ð° палитру панели мониторинга. + + + + + + + Loading + Загрузка + + + Loading completed + Загрузка завершена + + + + + + + Defines a property to show on the dashboard + ОпределÑет ÑвойÑтво Ð´Ð»Ñ Ð¾Ñ‚Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ Ð½Ð° панели мониторинга + + + What value to use as a label for the property + Какое значение иÑпользовать в качеÑтве метки Ð´Ð»Ñ ÑвойÑтва + + + What value in the object to access for the value + Значение объекта, иÑпользуемое Ð´Ð»Ñ Ð´Ð¾Ñтупа к значению + + + Specify values to be ignored + Укажите игнорируемые Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ + + + Default value to show if ignored or no value + Значение по умолчанию, которое отображаетÑÑ Ð² том Ñлучае, еÑли значение игнорируетÑÑ Ð¸Ð»Ð¸ не указано. + + + A flavor for defining dashboard properties + ОÑобые ÑвойÑтва панели мониторинга + + + Id of the flavor + Идентификатор варианта Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ + + + Condition to use this flavor + УÑловие иÑÐ¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ Ñтого варианта Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ + + + Field to compare to + Поле Ð´Ð»Ñ ÑÑ€Ð°Ð²Ð½ÐµÐ½Ð¸Ñ + + + Which operator to use for comparison + Какой оператор иÑпользовать Ð´Ð»Ñ ÑÑ€Ð°Ð²Ð½ÐµÐ½Ð¸Ñ + + + Value to compare the field to + Значение Ð´Ð»Ñ ÑÑ€Ð°Ð²Ð½ÐµÐ½Ð¸Ñ Ð¿Ð¾Ð»Ñ + + + Properties to show for database page + СвойÑтва Ð´Ð»Ñ Ð¾Ñ‚Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ Ð½Ð° Ñтранице базы данных + + + Properties to show for server page + Отображаемые ÑвойÑтва Ð´Ð»Ñ Ñтраницы Ñервера + + + Defines that this provider supports the dashboard + ОпределÑет, что Ñтот поÑтавщик поддерживает панель мониторинга + + + Provider id (ex. MSSQL) + Идентификатор поÑтавщика (пример: MSSQL) + + + Property values to show on dashboard + Ð—Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ ÑвойÑтва Ð´Ð»Ñ Ð¾Ñ‚Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ Ð½Ð° панели мониторинга + + + + + + + Backup + Резервное копирование + + + You must enable preview features in order to use backup + Ð”Ð»Ñ Ð¸ÑÐ¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ Ñ€ÐµÐ·ÐµÑ€Ð²Ð½Ð¾Ð³Ð¾ ÐºÐ¾Ð¿Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ð½ÐµÐ¾Ð±Ñ…Ð¾Ð´Ð¸Ð¼Ð¾ включить функции предварительного проÑмотра + + + Backup command is not supported for Azure SQL databases. + Команда резервного ÐºÐ¾Ð¿Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ð½Ðµ поддерживаетÑÑ Ð´Ð»Ñ Ð±Ð°Ð· данных SQL Azure. + + + Backup command is not supported in Server Context. Please select a Database and try again. + Команда резервного ÐºÐ¾Ð¿Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ð½Ðµ поддерживаетÑÑ Ð² контекÑте Ñервера. Выберите базу данных и повторите попытку. + + + + + + + Restore + ВоÑÑтановить + + + You must enable preview features in order to use restore + Чтобы иÑпользовать воÑÑтановление, необходимо включить функции предварительного проÑмотра + + + Restore command is not supported for Azure SQL databases. + Команда воÑÑÑ‚Ð°Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ Ð½Ðµ поддерживаетÑÑ Ð´Ð»Ñ Ð±Ð°Ð· данных SQL Azure. + + + + + + + disconnected + Отключен + + + + + + + Server Groups + Группы Ñерверов + + + OK + ОК + + + Cancel + Отмена + + + Server group name + Ð˜Ð¼Ñ Ð³Ñ€ÑƒÐ¿Ð¿Ñ‹ Ñерверов + + + Group name is required. + Ðеобходимо указать Ð¸Ð¼Ñ Ð³Ñ€ÑƒÐ¿Ð¿Ñ‹. + + + Group description + ОпиÑание группы + + + Group color + Цвет группы + + + + + + + Extension + РаÑширение + + + + + + + OK + ОК + + + Cancel + Отмена + + + + + + + Open dashboard extensions + Открыть раÑÑˆÐ¸Ñ€ÐµÐ½Ð¸Ñ Ð¿Ð°Ð½ÐµÐ»Ð¸ мониторинга + + + OK + ОК + + + Cancel + Отмена + + + No dashboard extensions are installed at this time. Go to Extension Manager to explore recommended extensions. + РаÑÑˆÐ¸Ñ€ÐµÐ½Ð¸Ñ Ð¿Ð°Ð½ÐµÐ»Ð¸ мониторинга не уÑтановлены. Чтобы проÑмотреть рекомендуемые раÑширениÑ, перейдите в ДиÑпетчер раÑширений. + + + + + + + Selected path + Выбранный путь + + + Files of type + Файлы данного типа + + + OK + ОК + + + Discard + Отменить + + + + + + + No Connection Profile was passed to insights flyout + Профиль Ð¿Ð¾Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ Ð½Ðµ был передан во вÑплывающий Ñлемент аналитичеÑких данных + + + Insights error + Ошибка аналитичеÑких данных + + + There was an error reading the query file: + Произошла ошибка при чтении файла запроÑа: + + + There was an error parsing the insight config; could not find query array/string or queryfile + Произошла ошибка при ÑинтакÑичеÑком анализе конфигурации аналитичеÑких данных; не удалоÑÑŒ найти маÑÑив/Ñтроку запроÑа или файл запроÑа + + + + + + + Clear List + ОчиÑтить ÑпиÑок + + + Recent connections list cleared + CпиÑок поÑледних подключений очищен + + + Yes + Да + + + No + Ðет + + + Are you sure you want to delete all the connections from the list? + Ð’Ñ‹ уверены, что хотите удалить вÑе ÑÐ¾ÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ Ð¸Ð· ÑпиÑка? + + + Yes + Да + + + No + Ðет + + + Delete + Удалить + + + Get Current Connection String + Получить текущую Ñтроку Ð¿Ð¾Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ + + + Connection string not available + Строка Ð¿Ð¾Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ Ð½ÐµÐ´Ð¾Ñтупна + + + No active connection available + Ðктивные Ð¿Ð¾Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ Ð¾Ñ‚ÑутÑтвуют + + + + + + + Refresh + Обновить + + + Disconnect + Отключить + + + New Connection + Создание ÑÐ¾ÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ + + + New Server Group + ÐÐ¾Ð²Ð°Ñ Ð³Ñ€ÑƒÐ¿Ð¿Ð° Ñерверов + + + Edit Server Group + Редактировать группу Ñерверов + + + Show Active Connections + Показать активные ÑÐ¾ÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ + + + Show All Connections + Показать вÑе Ð¿Ð¾Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ + + + Recent Connections + ПоÑледние ÑÐ¾ÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ + + + Delete Connection + Удалить подключение + + + Delete Group + Удалить группу + + + + + + + Edit Data Session Failed To Connect + Ðе удалоÑÑŒ подключитьÑÑ Ðº ÑеанÑу Ñ€ÐµÐ´Ð°ÐºÑ‚Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ð´Ð°Ð½Ð½Ñ‹Ñ… + + + + + + + Profiler + Профилировщик + + + Not connected + Ðет ÑÐ¾ÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ + + + XEvent Profiler Session stopped unexpectedly on the server {0}. + Ð¡ÐµÐ°Ð½Ñ Ð¿Ñ€Ð¾Ñ„Ð¸Ð»Ð¸Ñ€Ð¾Ð²Ñ‰Ð¸ÐºÐ° XEvent был неожиданно оÑтановлен на Ñервере {0}. + + + Error while starting new session + Ошибка при запуÑке нового ÑеанÑа + + + The XEvent Profiler session for {0} has lost events. + Ð’ ÑеанÑе профилировщика XEvent Ð´Ð»Ñ {0} были потерÑны ÑобытиÑ. + + + Would you like to stop the running XEvent session? + Ð’Ñ‹ хотите оÑтановить запущенный ÑÐµÐ°Ð½Ñ XEvent? + + + Yes + Да + + + No + Ðет + + + Cancel + Отмена + + + + + + + Invalid value + ÐедопуÑтимое значение + + + {0}. {1} + {0}.{1} + + + + + + + blank + пуÑтой + + + + + + + Error displaying Plotly graph: {0} + Ðе удалоÑÑŒ отобразить диаграмму Plotly: {0} + + + + + + + No {0} renderer could be found for output. It has the following MIME types: {1} + Ðе удаетÑÑ Ð½Ð°Ð¹Ñ‚Ð¸ отриÑовщик {0} Ð´Ð»Ñ Ð²Ñ‹Ñ…Ð¾Ð´Ð½Ñ‹Ñ… данных. Он Ñодержит Ñледующие типы MIME: {1} + + + (safe) + (безопаÑный) + + + + + + + Item + Элемент + + + Value + Значение + + + Property + СвойÑтво + + + Value + Значение + + + Insights + ÐналитичеÑкие данные + + + Items + Элементы + + + Item Details + Ð¡Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¾Ð± Ñлементе + + + + + + + Error adding account + Ошибка при добавлении учетной запиÑи + + + + + + + Cannot start auto OAuth. An auto OAuth is already in progress. + Ðе удаетÑÑ Ð·Ð°Ð¿ÑƒÑтить автоматичеÑкую авторизацию OAuth. Она уже выполнÑетÑÑ. + + + + + + + Sort by event + Сортировка по ÑобытиÑм + + + Sort by column + Сортировка по Ñтолбцу + + + Profiler + Профилировщик + + + OK + ОК + + + Cancel + Отмена + + + + + + + Clear all + ОчиÑтить вÑе + + + Apply + Применить + + + OK + ОК + + + Cancel + Отмена + + + Filters + Фильтры + + + Remove this clause + Удалить Ñто предложение + + + Save Filter + Сохранить фильтр + + + Load Filter + Загрузить фильтр + + + Add a clause + Добавить предложение + + + Field + Поле + + + Operator + Оператор + + + Value + Значение + + + Is Null + Имеет значение Null + + + Is Not Null + Ðе имеет значение Null + + + Contains + Содержит + + + Not Contains + Ðе Ñодержит + + + Starts With + ÐачинаетÑÑ Ñ + + + Not Starts With + Ðе начинаетÑÑ Ñ + + + + + + + Double-click to edit + Дважды щелкните, чтобы изменить + + + + + + + Select Top 1000 + Выберите первые 1000 + + + Script as Execute + Выполнение ÑÑ†ÐµÐ½Ð°Ñ€Ð¸Ñ Ð² режиме Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ + + + Script as Alter + Выполнение ÑÑ†ÐµÐ½Ð°Ñ€Ð¸Ñ Ð² режиме Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ + + + Edit Data + Редактировать данные + + + Script as Create + Выполнение ÑÑ†ÐµÐ½Ð°Ñ€Ð¸Ñ Ð² режиме ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ + + + Script as Drop + Сценарий как удаление + + + + + + + No queries to display. + Ðет запроÑов Ð´Ð»Ñ Ð¾Ñ‚Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ. + + + Query History + ИÑÑ‚Ð¾Ñ€Ð¸Ñ Ð·Ð°Ð¿Ñ€Ð¾Ñов + QueryHistory + + + + + + + Failed to get Azure account token for connection + Ðе удалоÑÑŒ получить маркер учетной запиÑи Azure Ð´Ð»Ñ Ð¿Ð¾Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ + + + Connection Not Accepted + Подключение не принÑто + + + Yes + Да + + + No + Ðет + + + Are you sure you want to cancel this connection? + Ð’Ñ‹ дейÑтвительно хотите отменить Ñто подключение? + + + + + + + Started executing query at + Ðачало Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ Ð·Ð°Ð¿Ñ€Ð¾Ñа + + + Line {0} + Строка {0} + + + Canceling the query failed: {0} + Ошибка при отмене запроÑа: {0} + + + Started saving results to + Ðачало ÑÐ¾Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ñ€ÐµÐ·ÑƒÐ»ÑŒÑ‚Ð°Ñ‚Ð¾Ð² в + + + Failed to save results. + Ðе удалоÑÑŒ Ñохранить результаты. + + + Successfully saved results to + Результаты уÑпешно Ñохранены в + + + Executing query... + Выполнение запроÑа... + + + Maximize + Развернуть + + + Restore + ВоÑÑтановить + + + Save as CSV + Сохранить в формате CSV + + + Save as JSON + Сохранить в формате JSON + + + Save as Excel + Сохранить в файле Excel + + + Save as XML + Сохранить как XML + + + View as Chart + ПоÑмотреть в виде диаграммы + + + Visualize + Визуализировать + + + Results + Результаты + + + Executing query + Выполнение запроÑа + + + Messages + Ð¡Ð¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ + + + Total execution time: {0} + Общее Ð²Ñ€ÐµÐ¼Ñ Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ: {0} + + + Save results command cannot be used with multiple selections. + Команда ÑÐ¾Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ñ€ÐµÐ·ÑƒÐ»ÑŒÑ‚Ð°Ñ‚Ð¾Ð² не может быть иÑпользована при множеÑтвенном выборе. + + + + + + + Identifier of the notebook provider. + Идентификатор поÑтавщика запиÑных книжек. + + + What file extensions should be registered to this notebook provider + РаÑÑˆÐ¸Ñ€ÐµÐ½Ð¸Ñ Ñ„Ð°Ð¹Ð»Ð¾Ð², которые должны быть зарегиÑтрированы Ð´Ð»Ñ Ñтого поÑтавщика запиÑных книжек + + + What kernels should be standard with this notebook provider + Какие Ñдра Ñледует иÑпользовать в качеÑтве Ñтандартных Ð´Ð»Ñ Ñтого поÑтавщика запиÑных книжек + + + Contributes notebook providers. + ДобавлÑет поÑтавщиков запиÑных книжек. + + + Name of the cell magic, such as '%%sql'. + Ðазвание магичеÑкой команды Ñчейки, например, '%%sql'. + + + The cell language to be used if this cell magic is included in the cell + Язык Ñчейки, который будет иÑпользоватьÑÑ, еÑли Ñта магичеÑÐºÐ°Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð° Ñчейки включена в Ñчейку + + + Optional execution target this magic indicates, for example Spark vs SQL + ÐеобÑÐ·Ð°Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ñ†ÐµÐ»ÑŒ выполнениÑ, ÑƒÐºÐ°Ð·Ð°Ð½Ð½Ð°Ñ Ð² Ñтой магичеÑкой команде, например, Spark vs SQL + + + Optional set of kernels this is valid for, e.g. python3, pyspark, sql + ÐеобÑзательный набор Ñдер, Ð´Ð»Ñ ÐºÐ¾Ñ‚Ð¾Ñ€Ñ‹Ñ… Ñто дейÑтвительно, например, python3, pyspark, sql + + + Contributes notebook language. + ОпределÑет Ñзык запиÑной книжки. + + + + + + + SQL + SQL + + + + + + + F5 shortcut key requires a code cell to be selected. Please select a code cell to run. + При нажатии клавиши F5 необходимо выбрать Ñчейку кода. Выберите Ñчейку кода Ð´Ð»Ñ Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ. + + + Clear result requires a code cell to be selected. Please select a code cell to run. + Ð”Ð»Ñ Ð¿Ð¾Ð»ÑƒÑ‡ÐµÐ½Ð¸Ñ Ñ‡ÐµÑ‚ÐºÐ¾Ð³Ð¾ результата требуетÑÑ Ð²Ñ‹Ð±Ñ€Ð°Ñ‚ÑŒ Ñчейку кода. Выберите Ñчейку кода Ð´Ð»Ñ Ð·Ð°Ð¿ÑƒÑка. + + + + + + + Save As CSV + Сохранить в формате CSV + + + Save As JSON + Сохранить в формате JSON + + + Save As Excel + Сохранить в файле Excel + + + Save As XML + Сохранить как XML + + + Copy + Копирование + + + Copy With Headers + Копировать Ñ Ð·Ð°Ð³Ð¾Ð»Ð¾Ð²ÐºÐ°Ð¼Ð¸ + + + Select All + Выбрать вÑе + + + + + + + Max Rows: + МакÑимальное чиÑло Ñтрок: + + + + + + + Select View + Выберите предÑтавление + + + Select Session + Выберите ÑÐµÐ°Ð½Ñ + + + Select Session: + Выберите ÑеанÑ: + + + Select View: + Выберите предÑтавление: + + + Text + ТекÑÑ‚ + + + Label + Этикетка + + + Value + Значение + + + Details + Подробные ÑÐ²ÐµÐ´ÐµÐ½Ð¸Ñ + + + + + + + Copy failed with error {0} + Ðе удалоÑÑŒ выполнить копирование. Ошибка: {0} + + + + + + + New Query + Создать Ð·Ð°Ð¿Ñ€Ð¾Ñ + + + Run + ЗапуÑк + + + Cancel + Отмена + + + Explain + ОбъÑÑнить + + + Actual + ДейÑтвительное значение + + + Disconnect + Отключить + + + Change Connection + Изменить Ñоединение + + + Connect + ПодключитьÑÑ + + + Enable SQLCMD + Включить SQLCMD + + + Disable SQLCMD + Отключить SQLCMD + + + Select Database + Выберите базу данных + + + Select Database Toggle Dropdown + РаÑкрывающийÑÑ ÑпиÑок Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ Ð±Ð°Ð·Ñ‹ данных + + + Failed to change database + Ðе удалоÑÑŒ изменить базу данных + + + Failed to change database {0} + Ðе удалоÑÑŒ изменить базу данных {0} + + + + + + + Connection + Подключение + + + Connection type + Тип Ð¿Ð¾Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ + + + Recent Connections + ПоÑледние ÑÐ¾ÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ + + + Saved Connections + Сохраненные Ð¿Ð¾Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ + + + Connection Details + Ð¡Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¾ подключении + + + Connect + ПодключитьÑÑ + + + Cancel + Отмена + + + No recent connection + Ðедавние ÑÐ¾ÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ Ð¾Ñ‚ÑутÑтвуют + + + No saved connection + Сохраненные Ð¿Ð¾Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ Ð¾Ñ‚ÑутÑтвуют + + + + + + + OK + ОК + + + Close + Закрыть + + + + + + + Loading kernels... + Загрузка Ñдер... + + + Changing kernel... + Изменение Ñдра... + + + Kernel: + Ядро: + + + Attach To: + ПриÑоединитьÑÑ Ðº: + + + Loading contexts... + Загрузка контекÑтов... + + + Add New Connection + Добавить новое подключение + + + Select Connection + Выберите подключение + + + localhost + localhost + + + Trusted + Доверенный + + + Not Trusted + Ðе доверенный + + + Notebook is already trusted. + ЗапиÑÐ½Ð°Ñ ÐºÐ½Ð¸Ð¶ÐºÐ° уже ÑвлÑетÑÑ Ð´Ð¾Ð²ÐµÑ€ÐµÐ½Ð½Ð¾Ð¹. + + + Collapse Cells + Свернуть Ñчейки + + + Expand Cells + Развернуть Ñчейки + + + No Kernel + Ðет Ñдра + + + None + NONE + + + New Notebook + Создать запиÑную книжку + + + + + + + Time Elapsed + Прошедшее Ð²Ñ€ÐµÐ¼Ñ + + + Row Count + ЧиÑло Ñтрок + + + {0} rows + {0} Ñтрок + + + Executing query... + Выполнение запроÑа... + + + Execution Status + СоÑтоÑние Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ + + + + + + + No task history to display. + Журнал задач Ð´Ð»Ñ Ð¾Ñ‚Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ Ð¾Ñ‚ÑутÑтвует. + + + Task history + ИÑÑ‚Ð¾Ñ€Ð¸Ñ Ð·Ð°Ð´Ð°Ñ‡ + TaskHistory + + + Task error + Ошибка Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ Ð·Ð°Ð´Ð°Ñ‡Ð¸ + + + + + + + Choose SQL Language + Выберите Ñзык SQL + + + Change SQL language provider + Изменить поÑтавщика Ñзыка SQL + + + SQL Language Flavor + Вариант Ñзыка SQL + + + Change SQL Engine Provider + Изменить поÑтавщика SQL + + + A connection using engine {0} exists. To change please disconnect or change connection + Подключение Ñ Ð¸Ñпользованием {0} уже ÑущеÑтвует. Чтобы изменить его, пожалуйÑта отключите или Ñмените ÑущеÑтвующее подключение. + + + No text editor active at this time + Ðктивные текÑтовые редакторы отÑутÑтвуют + + + Select SQL Language Provider + Выберите поÑтавщика Ñзыка SQL + + + + + + + All files + Ð’Ñе файлы + + + + + + + File browser tree + Дерево Ð¾Ð±Ð¾Ð·Ñ€ÐµÐ²Ð°Ñ‚ÐµÐ»Ñ Ñ„Ð°Ð¹Ð»Ð¾Ð² + FileBrowserTree + + + + + + + From + С + + + To + До + + + Create new firewall rule + Создание нового правила брандмауÑра + + + OK + ОК + + + Cancel + Отмена + + + Your client IP address does not have access to the server. Sign in to an Azure account and create a new firewall rule to enable access. + Ваш клиентÑкий IP-Ð°Ð´Ñ€ÐµÑ Ð½Ðµ имеет доÑтупа к Ñерверу. Войдите в учетную запиÑÑŒ Azure и Ñоздайте новое правило брандмауÑра, чтобы разрешить доÑтуп. + + + Learn more about firewall settings + Дополнительные ÑÐ²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¾ параметрах брандмауÑра + + + Azure account + Ð£Ñ‡ÐµÑ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ Azure + + + Firewall rule + Правило брандмауÑра + + + Add my client IP + Добавить мой клиентÑкий IP + + + Add my subnet IP range + Добавить диапазон IP-адреÑов моей подÑети + + + + + + + You need to refresh the credentials for this account. + Вам необходимо обновить учетные данные Ð´Ð»Ñ Ñтой учетной запиÑи. + + + + + + + Could not find query file at any of the following paths : + {0} + Ðе удалоÑÑŒ найти файл запроÑа по любому из Ñледующих путей: + {0} + + + + + + + Add an account + Добавить учетную запиÑÑŒ + + + Remove account + Удаление учетной запиÑи + + + Are you sure you want to remove '{0}'? + Ð’Ñ‹ дейÑтвительно хотите удалить '{0}'? + + + Yes + Да + + + No + Ðет + + + Failed to remove account + Ðе удалоÑÑŒ удалить учетную запиÑÑŒ + + + Apply Filters + Применить фильтры + + + Reenter your credentials + Повторно введите учетные данные + + + There is no account to refresh + Учетные запиÑи Ð´Ð»Ñ Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ Ð¾Ñ‚ÑутÑтвуют + + + + + + + Focus on Current Query + Ð¤Ð¾ÐºÑƒÑ Ð½Ð° текущем запроÑе + + + Run Query + Выполнить Ð·Ð°Ð¿Ñ€Ð¾Ñ + + + Run Current Query + Выполнить текущий Ð·Ð°Ð¿Ñ€Ð¾Ñ + + + Run Current Query with Actual Plan + Выполнить текущий Ð·Ð°Ð¿Ñ€Ð¾Ñ Ñ Ñ„Ð°ÐºÑ‚Ð¸Ñ‡ÐµÑким планом + + + Cancel Query + Отменить Ð·Ð°Ð¿Ñ€Ð¾Ñ + + + Refresh IntelliSense Cache + Обновить кÑш IntelliSense + + + Toggle Query Results + Переключить результаты запроÑа + + + Editor parameter is required for a shortcut to be executed + Ð”Ð»Ñ Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ Ñрлыка требуетÑÑ Ð¿Ð°Ñ€Ð°Ð¼ÐµÑ‚Ñ€ редактора. + + + Parse Query + СинтакÑичеÑкий анализ запроÑа + + + Commands completed successfully + Команды уÑпешно выполнены + + + Command failed: + Ðе удалоÑÑŒ выполнить команду: + + + Please connect to a server + ПодключитеÑÑŒ к Ñерверу + + + + + + + Chart cannot be displayed with the given data + Ðе удаетÑÑ Ð¾Ñ‚Ð¾Ð±Ñ€Ð°Ð·Ð¸Ñ‚ÑŒ диаграмму Ñ ÑƒÐºÐ°Ð·Ð°Ð½Ð½Ñ‹Ð¼Ð¸ данными. + + + + + + + The index {0} is invalid. + Ð˜Ð½Ð´ÐµÐºÑ {0} ÑвлÑетÑÑ Ð½ÐµÐ´Ð¾Ð¿ÑƒÑтимым. + + + + + + + no data available + данные недоÑтупны + + + + + + + Information + Ð¡Ð²ÐµÐ´ÐµÐ½Ð¸Ñ + + + Warning + Предупреждение + + + Error + Ошибка + + + Show Details + Показать подробные ÑÐ²ÐµÐ´ÐµÐ½Ð¸Ñ + + + Copy + Копирование + + + Close + Закрыть + + + Back + Ðазад + + + Hide Details + Скрыть подробные ÑÐ²ÐµÐ´ÐµÐ½Ð¸Ñ + + + + + + + is required. + требуетÑÑ. + + + Invalid input. Numeric value expected. + Введены недопуÑтимые данные. ОжидаетÑÑ Ñ‡Ð¸Ñловое значение. + + + + + + + Execution failed due to an unexpected error: {0} {1} + Сбой Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ Ð¸Ð·-за непредвиденной ошибки: {0} {1} + + + Total execution time: {0} + Общее Ð²Ñ€ÐµÐ¼Ñ Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ: {0} + + + Started executing query at Line {0} + Ðачато выполнение запроÑа в Ñтроке {0} + + + Initialize edit data session failed: + Ðе удалоÑÑŒ инициализировать ÑÐµÐ°Ð½Ñ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð´Ð°Ð½Ð½Ñ‹Ñ…: + + + Batch execution time: {0} + Ð’Ñ€ÐµÐ¼Ñ Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ Ð¿Ð°ÐºÐµÑ‚Ð°: {0} + + + Copy failed with error {0} + Ðе удалоÑÑŒ выполнить копирование. Ошибка: {0} + + + + + + + Error: {0} + Ошибка: {0} + + + Warning: {0} + Предупреждение: {0} + + + Info: {0} + ИнформациÑ: {0} + + + + + + + Copy Cell + Копировать Ñчейку + + + + + + + Backup file path + Путь к файлу резервной копии + + + Target database + Ð¦ÐµÐ»ÐµÐ²Ð°Ñ Ð±Ð°Ð·Ð° данных + + + Restore database + ВоÑÑтановление базы данных + + + Restore database + ВоÑÑтановление базы данных + + + Database + База данных + + + Backup file + Файл резервной копии + + + Restore + ВоÑÑтановить + + + Cancel + Отмена + + + Script + Скрипт + + + Source + ИÑÑ…Ð¾Ð´Ð½Ð°Ñ Ð±Ð°Ð·Ð° данных + + + Restore from + ВоÑÑтановить из + + + Backup file path is required. + ТребуетÑÑ Ð¿ÑƒÑ‚ÑŒ к файлу резервной копии. + + + Please enter one or more file paths separated by commas + ПожалуйÑта, введите один или неÑколько путей файлов, разделенных запÑтыми + + + Database + База данных + + + Destination + Ðазначение + + + Select Database Toggle Dropdown + РаÑкрывающийÑÑ ÑпиÑок Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ Ð±Ð°Ð·Ñ‹ данных + + + Restore to + ВоÑÑтановление + + + Restore plan + План воÑÑÑ‚Ð°Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ + + + Backup sets to restore + Резервные наборы данных Ð´Ð»Ñ Ð²Ð¾ÑÑÑ‚Ð°Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ + + + Restore database files as + ВоÑÑтановить файлы базы данных как + + + Restore database file details + ВоÑÑтановление Ñведений о файле базы данных + + + Logical file Name + Ð˜Ð¼Ñ Ð»Ð¾Ð³Ð¸Ñ‡ÐµÑкого файла + + + File type + Тип файла + + + Original File Name + ИÑходное Ð¸Ð¼Ñ Ñ„Ð°Ð¹Ð»Ð° + + + Restore as + ВоÑÑтановить как + + + Restore options + Параметры воÑÑÑ‚Ð°Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ + + + Tail-Log backup + Ð ÐµÐ·ÐµÑ€Ð²Ð½Ð°Ñ ÐºÐ¾Ð¿Ð¸Ñ Ð·Ð°ÐºÐ»ÑŽÑ‡Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð¾Ð³Ð¾ фрагмента журнала + + + Server connections + Ð¡Ð¾ÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ Ñ Ñервером + + + General + Общее + + + Files + Файлы + + + Options + Параметры + + + + + + + Copy & Open + Копировать и Открыть + + + Cancel + Отмена + + + User code + Код Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ + + + Website + Веб-Ñайт + + + + + + + Done + ДоговорилиÑÑŒ + + + Cancel + Отмена + + + + + + + Must be an option from the list + Ðеобходимо выбрать вариант из ÑпиÑка + + + Toggle dropdown + Переключить раÑкрывающийÑÑ ÑпиÑок + + + + + + + Select/Deselect All + Выбрать вÑе/отменить выбор + + + checkbox checked + флажок уÑтановлен + + + checkbox unchecked + флажок ÑнÑÑ‚ + + + + + + + modelview code editor for view model. + редактор кода в предÑтавлении модели Ð´Ð»Ñ Ð¼Ð¾Ð´ÐµÐ»Ð¸ предÑтавлениÑ. + + + + + + + succeeded + уÑпешно + + + failed + Ошибка + + + + + + + Server Description (optional) + ОпиÑание Ñервера (необÑзательно) + + + + + + + Advanced Properties + Дополнительные ÑвойÑтва + + + Discard + Отменить + + + + + + + Linked accounts + СвÑзанные учетные запиÑи + + + Close + Закрыть + + + There is no linked account. Please add an account. + СвÑÐ·Ð°Ð½Ð½Ð°Ñ ÑƒÑ‡ÐµÑ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ не ÑущеÑтвует. Добавьте учетную запиÑÑŒ. + + + Add an account + Добавить учетную запиÑÑŒ + + + + + + + nbformat v{0}.{1} not recognized + Формат nbformat v{0}.{1} не раÑпознан + + + This file does not have a valid notebook format + Формат Ñтого файла не ÑоответÑтвует допуÑтимому формату запиÑной книжки + + + Cell type {0} unknown + ÐеизвеÑтный тип Ñчейки {0} + + + Output type {0} not recognized + Тип выходных данных {0} не раÑпознан + + + Data for {0} is expected to be a string or an Array of strings + Данные Ð´Ð»Ñ {0} должны предÑтавлÑÑ‚ÑŒ Ñобой Ñтроку или маÑÑив Ñтрок + + + Output type {0} not recognized + Тип выходных данных {0} не раÑпознан + + + + + + + Profiler editor for event text. Readonly + Редактор профилировщика Ð´Ð»Ñ Ñ‚ÐµÐºÑта ÑобытиÑ. Только Ð´Ð»Ñ Ñ‡Ñ‚ÐµÐ½Ð¸Ñ. + + + + + + + Run Cells Before + ВыполнÑÑ‚ÑŒ Ñчейки перед + + + Run Cells After + ВыполнÑÑ‚ÑŒ Ñчейки поÑле + + + Insert Code Before + Ð’Ñтавить код перед + + + Insert Code After + Ð’Ñтавить код поÑле + + + Insert Text Before + Ð’Ñтавить текÑÑ‚ перед + + + Insert Text After + Ð’Ñтавить текÑÑ‚ поÑле + + + Collapse Cell + Свернуть Ñчейку + + + Expand Cell + Развернуть Ñчейку + + + Clear Result + ОчиÑтить результаты + + + Delete + Удалить + + + + + + + No script was returned when calling select script on object + Ðе было получено ни одного ÑÑ†ÐµÐ½Ð°Ñ€Ð¸Ñ Ð¿Ñ€Ð¸ вызове метода выбора ÑÑ†ÐµÐ½Ð°Ñ€Ð¸Ñ Ð´Ð»Ñ Ð¾Ð±ÑŠÐµÐºÑ‚Ð° + + + Select + Выбрать + + + Create + Создать + + + Insert + Insert + + + Update + Обновить + + + Delete + Удалить + + + No script was returned when scripting as {0} on object {1} + При выполнении ÑÑ†ÐµÐ½Ð°Ñ€Ð¸Ñ Ð² режиме {0} Ð´Ð»Ñ Ð¾Ð±ÑŠÐµÐºÑ‚Ð° {1} не было возвращено ни одного ÑценариÑ. + + + Scripting Failed + Ðе удалоÑÑŒ Ñоздать Ñценарий + + + No script was returned when scripting as {0} + При выполнении ÑÑ†ÐµÐ½Ð°Ñ€Ð¸Ñ Ð² режиме {0} не было возвращено ни одного ÑÑ†ÐµÐ½Ð°Ñ€Ð¸Ñ + + + + + + + Recent Connections + ПоÑледние ÑÐ¾ÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ + + + Servers + Серверы + + + + + + + No Kernel + Ðет Ñдра + + + Cannot run cells as no kernel has been configured + Ðе удаетÑÑ Ð·Ð°Ð¿ÑƒÑтить Ñчейки, так как Ñдро не наÑтроено + + + Error + Ошибка + + + + + + + Azure Data Studio + Azure Data Studio + + + Start + ЗапуÑтить + + + New connection + Создание ÑÐ¾ÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ + + + New query + Создать Ð·Ð°Ð¿Ñ€Ð¾Ñ + + + New notebook + Создать запиÑную книжку + + + Open file + Открыть файл + + + Open file + Открыть файл + + + Deploy + Развертывание. + + + Deploy SQL Server… + Развернуть SQL Server… + + + Recent + ПоÑледние + + + More... + Подробнее... + + + No recent folders + Ðет поÑледних папок. + + + Help + Справка + + + Getting started + ПриÑÑ‚ÑƒÐ¿Ð°Ñ Ðº работе + + + Documentation + Ð”Ð¾ÐºÑƒÐ¼ÐµÐ½Ñ‚Ð°Ñ†Ð¸Ñ + + + Report issue or feature request + Сообщить о проблеме или отправить Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð½Ð° добавление новой возможноÑти + + + GitHub repository + Репозиторий GitHub + + + Release notes + Заметки о выпуÑке + + + Show welcome page on startup + Отображать Ñтраницу приветÑÑ‚Ð²Ð¸Ñ Ð¿Ñ€Ð¸ запуÑке + + + Customize + ÐаÑтроить + + + Extensions + РаÑÑˆÐ¸Ñ€ÐµÐ½Ð¸Ñ + + + Download extensions that you need, including the SQL Server Admin pack and more + Скачайте необходимые раÑширениÑ, Ð²ÐºÐ»ÑŽÑ‡Ð°Ñ Ð¿Ð°ÐºÐµÑ‚ админиÑÑ‚Ñ€Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ SQL Server и другие. + + + Keyboard Shortcuts + Ð¡Ð¾Ñ‡ÐµÑ‚Ð°Ð½Ð¸Ñ ÐºÐ»Ð°Ð²Ð¸Ñˆ + + + Find your favorite commands and customize them + Ðайдите любимые команды и наÑтройте их + + + Color theme + Ð¦Ð²ÐµÑ‚Ð¾Ð²Ð°Ñ Ñ‚ÐµÐ¼Ð° + + + Make the editor and your code look the way you love + ÐаÑтройте редактор и код удобным образом. + + + Learn + Подробнее + + + Find and run all commands + Ðайти и выполнить вÑе команды. + + + Rapidly access and search commands from the Command Palette ({0}) + БыÑтро обращайтеÑÑŒ к командам и выполнÑйте поиÑк по командам Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ палитры команд ({0}) + + + Discover what's new in the latest release + ОзнакомьтеÑÑŒ Ñ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñми в поÑледнем выпуÑке + + + New monthly blog posts each month showcasing our new features + ЕжемеÑÑчные запиÑи в блоге Ñ Ð¾Ð±Ð·Ð¾Ñ€Ð¾Ð¼ новых функций + + + Follow us on Twitter + Следите за нашими новоÑÑ‚Ñми в Twitter + + + Keep up to date with how the community is using Azure Data Studio and to talk directly with the engineers. + Будьте в курÑе того, как ÑообщеÑтво иÑпользует Azure Data Studio, и общайтеÑÑŒ Ñ Ñ‚ÐµÑ…Ð½Ð¸Ñ‡ÐµÑкими ÑпециалиÑтами напрÑмую. + + + + + + + succeeded + уÑпешно + + + failed + Ошибка + + + in progress + ВыполнÑетÑÑ + + + not started + Ðе запущена + + + canceled + Отмененный + + + canceling + выполнÑетÑÑ Ð¾Ñ‚Ð¼ÐµÐ½Ð° + + + + + + + Run + ЗапуÑк + + + Dispose Edit Failed With Error: + Ðе удалоÑÑŒ отменить изменениÑ. Ошибка: + + + Stop + ОÑтановить + + + Show SQL Pane + Показать панель SQL + + + Close SQL Pane + Закрыть панель SQL + + + + + + + Connect + ПодключитьÑÑ + + + Disconnect + Отключить + + + Start + ЗапуÑтить + + + New Session + Создание ÑеанÑа + + + Pause + ПриоÑтановить + + + Resume + Возобновить + + + Stop + ОÑтановить + + + Clear Data + ОчиÑтить данные + + + Auto Scroll: On + ÐвтоматичеÑÐºÐ°Ñ Ð¿Ñ€Ð¾ÐºÑ€ÑƒÑ‚ÐºÐ°: Вкл + + + Auto Scroll: Off + ÐвтоматичеÑÐºÐ°Ñ Ð¿Ñ€Ð¾ÐºÑ€ÑƒÑ‚ÐºÐ°: Выкл + + + Toggle Collapsed Panel + Переключить Ñвернутую панель + + + Edit Columns + Редактирование Ñтолбцов + + + Find Next String + Ðайти Ñледующую Ñтроку + + + Find Previous String + Ðайти предыдущую Ñтроку + + + Launch Profiler + ЗапуÑтить профилировщик + + + Filter… + Фильтр... + + + Clear Filter + ОчиÑтить фильтр + + + + + + + Events (Filtered): {0}/{1} + Ð¡Ð¾Ð±Ñ‹Ñ‚Ð¸Ñ (отфильтровано): {0}/{1} + + + Events: {0} + СобытиÑ: {0} + + + Event Count + ЧиÑло Ñобытий + + + + + + + Save As CSV + Сохранить в формате CSV + + + Save As JSON + Сохранить в формате JSON + + + Save As Excel + Сохранить в файле Excel + + + Save As XML + Сохранить как XML + + + Save to file is not supported by the backing data source + Сохранение в файл не поддерживаетÑÑ Ñ€ÐµÐ·ÐµÑ€Ð²Ð½Ñ‹Ð¼ иÑточником данных + + + Copy + Копирование + + + Copy With Headers + Копировать Ñ Ð·Ð°Ð³Ð¾Ð»Ð¾Ð²ÐºÐ°Ð¼Ð¸ + + + Select All + Выбрать вÑе + + + Copy + Копирование + + + Copy All + Копировать вÑе + + + Maximize + Развернуть + + + Restore + ВоÑÑтановить + + + Chart + Диаграмма + + + Visualizer + Визуализатор + + + + + + + The extension "{0}" is using sqlops module which has been replaced by azdata module, the sqlops module will be removed in a future release. + РаÑширение "{0}" иÑпользует модуль sqlops, который был заменен модулем azdata. Модуль sqlops будет удален в одном из будущих выпуÑков. + + + + + + + Table header background color + Цвет фона заголовка таблицы + + + Table header foreground color + Цвет переднего плана заголовка таблицы + + + Disabled Input box background. + Фоновый цвет отключенного Ð¿Ð¾Ð»Ñ Ð²Ð²Ð¾Ð´Ð°. + + + Disabled Input box foreground. + Цвет переднего плана отключенного Ð¿Ð¾Ð»Ñ Ð²Ð²Ð¾Ð´Ð°. + + + Button outline color when focused. + Цвет контура кнопки в фокуÑе. + + + Disabled checkbox foreground. + Цвет переднего плана отключенного флажка. + + + List/Table background color for the selected and focus item when the list/table is active + Цвет фоны ÑпиÑка или таблицы Ð´Ð»Ñ Ð²Ñ‹Ð±Ñ€Ð°Ð½Ð½Ð¾Ð³Ð¾ Ñлемента и Ñлемента, на котором находитÑÑ Ñ„Ð¾ÐºÑƒÑ, когда ÑпиÑок или таблица активны + + + SQL Agent Table background color. + Цвет фона таблицы Ð´Ð»Ñ Ð°Ð³ÐµÐ½Ñ‚Ð° SQL. + + + SQL Agent table cell background color. + Цвет фона Ñчейки таблицы Ð´Ð»Ñ Ð°Ð³ÐµÐ½Ñ‚Ð° SQL. + + + SQL Agent table hover background color. + Цвет фона таблицы агента SQL при наведении. + + + SQL Agent heading background color. + Цвет фона заголовка агента SQL. + + + SQL Agent table cell border color. + Цвет границы Ñчейки таблицы Ð´Ð»Ñ Ð°Ð³ÐµÐ½Ñ‚Ð° SQL. + + + Results messages error color. + Цвет Ñообщений об ошибках в результатах. + + + + + + + Choose Results File + Выберите файл результатов + + + CSV (Comma delimited) + CSV (Ñ Ñ€Ð°Ð·Ð´ÐµÐ»ÐµÐ½Ð¸ÐµÐ¼ запÑтыми) + + + JSON + JSON + + + Excel Workbook + Книга Excel + + + XML + XML + + + Plain Text + ПроÑтой текÑÑ‚ + + + Open file location + РаÑположение Ð´Ð»Ñ Ð¾Ñ‚ÐºÑ€Ñ‹Ñ‚Ð¸Ñ Ñ„Ð°Ð¹Ð»Ð° + + + Open file + Открыть файл + + + + + + + Backup name + Ð˜Ð¼Ñ Ñ€ÐµÐ·ÐµÑ€Ð²Ð½Ð¾Ð¹ копии + + + Recovery model + Recovery Model + + + Backup type + Тип резервной копии + + + Backup files + Резервные файлы + + + Algorithm + Ðлгоритм + + + Certificate or Asymmetric key + Сертификат или аÑимметричный ключ + + + Media + ÐоÑитель + + + Backup to the existing media set + Создать резервную копию в ÑущеÑтвующем наборе ноÑителей + + + Backup to a new media set + Создать резервную копию на новом наборе ноÑителей + + + Append to the existing backup set + Добавить к ÑущеÑтвующему резервному набору данных + + + Overwrite all existing backup sets + ПерезапиÑать вÑе ÑущеÑтвующие резервные наборы данных + + + New media set name + Ð˜Ð¼Ñ Ð½Ð¾Ð²Ð¾Ð³Ð¾ набора ноÑителей + + + New media set description + ОпиÑание нового набора ноÑителей + + + Perform checksum before writing to media + РаÑÑчитать контрольную Ñумму перед запиÑью на ноÑитель + + + Verify backup when finished + Проверить резервную копию поÑле Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð¸Ñ + + + Continue on error + Продолжать при ошибке + + + Expiration + ИÑтечение Ñрока + + + Set backup retain days + УÑтановить Ð²Ñ€ÐµÐ¼Ñ Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ñ€ÐµÐ·ÐµÑ€Ð²Ð½Ð¾Ð¹ копии в днÑÑ… + + + Copy-only backup + Только архивное копирование + + + Advanced Configuration + РаÑÑˆÐ¸Ñ€ÐµÐ½Ð½Ð°Ñ ÐºÐ¾Ð½Ñ„Ð¸Ð³ÑƒÑ€Ð°Ñ†Ð¸Ñ + + + Compression + Сжатие + + + Set backup compression + ÐаÑтройка ÑÐ¶Ð°Ñ‚Ð¸Ñ Ñ€ÐµÐ·ÐµÑ€Ð²Ð½Ð¾Ð¹ копии + + + Encryption + Шифрование + + + Transaction log + Журнал транзакций + + + Truncate the transaction log + Обрежьте журнал транзакций + + + Backup the tail of the log + Резервное копирование заключительного фрагмента журнала + + + Reliability + ÐадежноÑÑ‚ÑŒ + + + Media name is required + ТребуетÑÑ Ð¸Ð¼Ñ Ð½Ð¾ÑÐ¸Ñ‚ÐµÐ»Ñ + + + No certificate or asymmetric key is available + Ðет доÑтупных Ñертификатов или аÑÑиметричных ключей. + + + Add a file + Добавить файл + + + Remove files + Удаление файлов + + + Invalid input. Value must be greater than or equal 0. + Ðекорректные данные. Значение должно быть больше или равно 0. + + + Script + Скрипт + + + Backup + Резервное копирование + + + Cancel + Отмена + + + Only backup to file is supported + ПоддерживаетÑÑ Ñ‚Ð¾Ð»ÑŒÐºÐ¾ резервное копирование в файл + + + Backup file path is required + ТребуетÑÑ Ð¿ÑƒÑ‚ÑŒ к файлу резервной копии + + + + + + + Results + Результаты + + + Messages + Ð¡Ð¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ + + + + + + + There is no data provider registered that can provide view data. + ОтÑутÑтвует зарегиÑтрированный поÑтавщик данных, который может предоÑтавить ÑÐ²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¾ проÑмотрах. + + + Collapse All + Свернуть вÑе + + + + + + + Home + ДомашнÑÑ Ñтраница + + + + + + + The "{0}" section has invalid content. Please contact extension owner. + Ð’ разделе "{0}" имеетÑÑ Ð½ÐµÐ´Ð¾Ð¿ÑƒÑтимое Ñодержание. СвÑжитеÑÑŒ Ñ Ð²Ð»Ð°Ð´ÐµÐ»ÑŒÑ†ÐµÐ¼ раÑширениÑ. + + + + + + + Jobs + Ð—Ð°Ð´Ð°Ð½Ð¸Ñ + + + Notebooks + ЗапиÑные книжки + + + Alerts + ÐŸÑ€ÐµÐ´ÑƒÐ¿Ñ€ÐµÐ¶Ð´ÐµÐ½Ð¸Ñ + + + Proxies + ПрокÑи-Ñерверы + + + Operators + Операторы + + + + + + + Loading + Загрузка + + + + + + + SERVER DASHBOARD + ПÐÐЕЛЬ МОÐИТОРИÐГРСЕРВЕРР+ + + + + + + DATABASE DASHBOARD + ПÐÐЕЛЬ МОÐИТОРИÐГРБÐЗЫ ДÐÐÐЫХ + + + + + + + Edit + Изменить + + + Exit + Выход + + + Refresh + Обновить + + + Toggle More + Переключить еще + + + Delete Widget + Удалить мини-приложение + + + Click to unpin + Щелкните, чтобы открепить + + + Click to pin + Щелкните, чтобы закрепить + + + Open installed features + Открыть уÑтановленные компоненты + + + Collapse + Collapse + + + Expand + Развернуть + + + + + + + Steps + Шаги + + + + + + + StdIn: + Стандартный поток ввода: + + + + + + + Add code + Добавить код + + + Add text + Добавить текÑÑ‚ + + + Create File + Создать файл + + + Could not display contents: {0} + Ðе удалоÑÑŒ отобразить Ñодержимое: {0} + + + Please install the SQL Server 2019 extension to run cells. + Ð”Ð»Ñ Ð·Ð°Ð¿ÑƒÑка Ñчеек уÑтановите раÑширение SQL Server 2019. + + + Install Extension + УÑтановить раÑширение + + + Code + Код + + + Text + ТекÑÑ‚ + + + Run Cells + ЗапуÑтить Ñчейки + + + Clear Results + ОчиÑтить результаты + + + < Previous + < Ðазад + + + Next > + Далее > + + + cell with URI {0} was not found in this model + Ñчейка Ñ URI {0} не найдена в Ñтой модели + + + Run Cells failed - See error in output of the currently selected cell for more information. + Ðе удалоÑÑŒ выполнить Ñчейки. Дополнительные ÑÐ²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¾Ð± ошибке Ñм. в выходных данных текущей выбранной Ñчейки. + + + + + + + Click on + Щелкните + + + + Code + Добавить код + + + or + Или + + + + Text + Добавить текÑÑ‚ + + + to add a code or text cell + Ð´Ð»Ñ Ð´Ð¾Ð±Ð°Ð²Ð»ÐµÐ½Ð¸Ñ ÐºÐ¾Ð´Ð° или текÑтовой Ñчейки + + + + + + + Database + База данных + + + Files and filegroups + Файлы и файловые группы + + + Full + Полное + + + Differential + РазноÑтное + + + Transaction Log + Журнал транзакций + + + Disk + ДиÑк + + + Url + URL-Ð°Ð´Ñ€ÐµÑ + + + Use the default server setting + ИÑпользовать параметр Ñервера по умолчанию + + + Compress backup + Сжимать резервные копии + + + Do not compress backup + Ðе Ñжимать резервные копии + + + Server Certificate + Сертификат Ñервера + + + Asymmetric Key + Asymmetric Key + + + Backup Files + Резервные файлы + + + All Files + Ð’Ñе файлы + + + + + + + No connections found. + ÐŸÐ¾Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ Ð½Ðµ найдены. + + + Add Connection + Добавить подключение + + + + + + + Failed to change database + Ðе удалоÑÑŒ изменить базу данных + + + + + + + Name + Ð˜Ð¼Ñ + + + Email Address + ÐÐ´Ñ€ÐµÑ Ñлектронной почты + + + Enabled + Включено + + + + + + + Name + Ð˜Ð¼Ñ + + + Last Occurrence + ПоÑледнее поÑвление + + + Enabled + Включено + + + Delay Between Responses (in secs) + Задержка между ответами (в Ñекундах) + + + Category Name + Ð˜Ð¼Ñ ÐºÐ°Ñ‚ÐµÐ³Ð¾Ñ€Ð¸Ð¸ + + + + + + + Account Name + Ð˜Ð¼Ñ ÑƒÑ‡ÐµÑ‚Ð½Ð¾Ð¹ запиÑи + + + Credential Name + Ð˜Ð¼Ñ ÑƒÑ‡ÐµÑ‚Ð½Ñ‹Ñ… данных + + + Description + ОпиÑание + + + Enabled + Включено + + + + + + + Unable to load dashboard properties + Ðе удалоÑÑŒ загрузить ÑвойÑтва панели мониторинга + + + + + + + Search by name of type (a:, t:, v:, f:, or sp:) + ПоиÑк по имени типа (a:, t:, v:, f: или sp:) + + + Search databases + ПоиÑк базы данных + + + Unable to load objects + Ðе удаетÑÑ Ð·Ð°Ð³Ñ€ÑƒÐ·Ð¸Ñ‚ÑŒ объекты + + + Unable to load databases + Ðе удалоÑÑŒ загрузить базы данных + + + + + + + Auto Refresh: OFF + ÐвтоматичеÑкое обновление: выключено + + + Last Updated: {0} {1} + ПоÑледнее обновление: {0} {1} + + + No results to show + Результаты Ð´Ð»Ñ Ð¾Ñ‚Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ Ð¾Ñ‚ÑутÑтвуют + + + + + + + No {0}renderer could be found for output. It has the following MIME types: {1} + Ðе удаетÑÑ Ð½Ð°Ð¹Ñ‚Ð¸ отриÑовщик {0} Ð´Ð»Ñ Ð²Ñ‹Ñ…Ð¾Ð´Ð½Ñ‹Ñ… данных. Он Ñодержит Ñледующие типы MIME: {1} + + + safe + безопаÑный + + + No component could be found for selector {0} + Ðе удалоÑÑŒ найти компонент Ð´Ð»Ñ Ñелектора {0} + + + Error rendering component: {0} + Ошибка при отриÑовке компонента: {0} + + + + + + + Connected to + Подключен к + + + Disconnected + Отключен + + + Unsaved Connections + ÐеÑохраненные ÑÐ¾ÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ + + + + + + + Delete Row + Удалить Ñтроку + + + Revert Current Row + Отменить Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð² текущей Ñтроке + + + + + + + Step ID + Идентификатор шага + + + Step Name + Ð˜Ð¼Ñ ÑˆÐ°Ð³Ð° + + + Message + Сообщение + + + + + + + XML Showplan + XML Showplan + + + Results grid + Сетка результатов + + + + + + + Please select active cell and try again + Выберите активную Ñчейку и повторите попытку + + + Run cell + ЗапуÑтить Ñчейку + + + Cancel execution + Отменить выполнение + + + Error on last run. Click to run again + Ошибка при поÑледнем запуÑке. Щелкните, чтобы запуÑтить повторно + + + + + + + Add an account... + Добавить учетную запиÑÑŒ... + + + <Default> + <По умолчанию> + + + Loading... + Загрузка... + + + Server group + Группа Ñерверов + + + <Default> + <По умолчанию> + + + Add new group... + Добавить группу... + + + <Do not save> + <Ðе ÑохранÑÑ‚ÑŒ> + + + {0} is required. + {0} ÑвлÑетÑÑ Ð¾Ð±Ñзательным. + + + {0} will be trimmed. + {0} будет обрезан. + + + Remember password + Запомнить пароль + + + Account + Ð£Ñ‡ÐµÑ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ + + + Refresh account credentials + Обновите учетные данные учетной запиÑи + + + Azure AD tenant + Клиент Azure AD + + + Select Database Toggle Dropdown + РаÑкрывающийÑÑ ÑпиÑок Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ Ð±Ð°Ð·Ñ‹ данных + + + Name (optional) + Ð˜Ð¼Ñ (необÑзательно) + + + Advanced... + Дополнительно... + + + You must select an account + Ðеобходимо выбрать учетную запиÑÑŒ + + + + + + + Cancel + Отмена + + + The task is failed to cancel. + Ошибка при отмене задачи. + + + Script + Скрипт + + + + + + + Date Created: + Дата ÑозданиÑ: + + + Notebook Error: + Ошибка запиÑной книжки: + + + Job Error: + Ошибка заданиÑ: + + + Pinned + Закреплено + + + Recent Runs + ПоÑледние запуÑки + + + Past Runs + Предыдущие запуÑки + + + + + + + No tree view with id '{0}' registered. + ОтÑутÑтвует зарегиÑтрированное предÑтавление в виде дерева Ñ Ð¸Ð´ÐµÐ½Ñ‚Ð¸Ñ„Ð¸ÐºÐ°Ñ‚Ð¾Ñ€Ð¾Ð¼ '{0}'. + + + + + + + Loading... + Загрузка... + + + + + + + Dashboard Tabs ({0}) + Вкладки панели мониторинга ({0}) + + + Id + Идентификатор + + + Title + Ðазвание + + + Description + ОпиÑание + + + Dashboard Insights ({0}) + ÐналитичеÑкие данные панели мониторинга ({0}) + + + Id + Идентификатор + + + Name + Ð˜Ð¼Ñ + + + When + Когда + + + + + + + Chart + Диаграмма + + + + + + + Operation + ÐžÐ¿ÐµÑ€Ð°Ñ†Ð¸Ñ + + + Object + Объект + + + Est Cost + Оценка ÑтоимоÑти + + + Est Subtree Cost + Оценка ÑтоимоÑти поддерева + + + Actual Rows + ФактичеÑких Ñтрок + + + Est Rows + Оценка Ñтрок + + + Actual Executions + ФактичеÑкое выполнение + + + Est CPU Cost + Приблизительные раÑходы на ЦП + + + Est IO Cost + Приблизительные затраты на операции ввода/вывода + + + Parallel + Параллельный + + + Actual Rebinds + ФактичеÑкое чиÑло повторных привÑзок + + + Est Rebinds + Приблизительное чиÑло повторных привÑзок + + + Actual Rewinds + ФактичеÑкое чиÑло ÑброÑов на начало + + + Est Rewinds + Приблизительное чиÑло возвратов + + + Partitioned + Секционированный + + + Top Operations + ОÑновные операции + + + + + + + Query Plan + План запроÑа + + + + + + + Could not find component for type {0} + Ðе удалоÑÑŒ найти компонент Ð´Ð»Ñ Ñ‚Ð¸Ð¿Ð° {0} + + + + + + + A NotebookProvider with valid providerId must be passed to this method + Ð’ качеÑтве параметра Ñтого метода необходимо указать NotebookProvider Ñ Ð´ÐµÐ¹Ñтвительным providerId + + + + + + + A NotebookProvider with valid providerId must be passed to this method + Ð’ качеÑтве параметра Ñтого метода необходимо указать NotebookProvider Ñ Ð´ÐµÐ¹Ñтвительным providerId + + + no notebook provider found + поÑтавщики запиÑных книжек не найдены + + + No Manager found + ДиÑпетчер не найден + + + Notebook Manager for notebook {0} does not have a server manager. Cannot perform operations on it + Notebook Manager Ð´Ð»Ñ Ð·Ð°Ð¿Ð¸Ñной книжки {0} не Ñодержит диÑпетчера Ñерверов. Ðевозможно выполнить операции над ним + + + Notebook Manager for notebook {0} does not have a content manager. Cannot perform operations on it + Notebook Manager Ð´Ð»Ñ Ð·Ð°Ð¿Ð¸Ñной книжки {0} не включает диÑпетчер Ñодержимого. Ðевозможно выполнить операции над ним + + + Notebook Manager for notebook {0} does not have a session manager. Cannot perform operations on it + Notebook Manager Ð´Ð»Ñ Ð·Ð°Ð¿Ð¸Ñной книжки {0} не Ñодержит диÑпетчер ÑеанÑа. Ðевозможно выполнить дейÑÑ‚Ð²Ð¸Ñ Ð½Ð°Ð´ ним + + + + + + + SQL kernel error + Ошибка Ñдра SQL + + + A connection must be chosen to run notebook cells + Ð”Ð»Ñ Ð·Ð°Ð¿ÑƒÑка Ñчеек запиÑной книжки необходимо выбрать подключение + + + Displaying Top {0} rows. + ОтображаютÑÑ Ð¿ÐµÑ€Ð²Ñ‹Ðµ {0} Ñтрок. + + + + + + + Show Recommendations + Показать рекомендации + + + Install Extensions + УÑтановить раÑÑˆÐ¸Ñ€ÐµÐ½Ð¸Ñ + + + + + + + Name + Ð˜Ð¼Ñ + + + Last Run + ПоÑледний запуÑк + + + Next Run + Следующий запуÑк + + + Enabled + Включено + + + Status + Ð¡Ñ‚Ð°Ñ‚ÑƒÑ + + + Category + ÐšÐ°Ñ‚ÐµÐ³Ð¾Ñ€Ð¸Ñ + + + Runnable + Готово к запуÑку + + + Schedule + РаÑпиÑание + + + Last Run Outcome + Результат поÑледнего запуÑка + + + Previous Runs + Предыдущие запуÑки + + + No Steps available for this job. + Шаги Ð´Ð»Ñ Ñтого Ð·Ð°Ð´Ð°Ð½Ð¸Ñ Ð½ÐµÐ´Ð¾Ñтупны. + + + Error: + Ошибка: + + + + + + + Find + Ðайти + + + Find + Ðайти + + + Previous match + Предыдущее ÑоответÑтвие + + + Next match + Следующее ÑоответÑтвие + + + Close + Закрыть + + + Your search returned a large number of results, only the first 999 matches will be highlighted. + Ð’ результате поиÑка было получено Ñлишком большое чиÑло результатов. Будут показаны только первые 999 результатов. + + + {0} of {1} + {0} из {1} + + + No Results + Результаты отÑутÑтвуют + + + + + + + Run Query + Выполнить Ð·Ð°Ð¿Ñ€Ð¾Ñ + + + + + + + Done + ДоговорилиÑÑŒ + + + Cancel + Отмена + + + Generate script + Создать Ñценарий + + + Next + Далее + + + Previous + Ðазад + + + + + + + A server group with the same name already exists. + Группа Ñерверов Ñ Ñ‚Ð°ÐºÐ¸Ð¼ именем уже ÑущеÑтвует. + + + + + + + {0} is an unknown container. + {0} ÑвлÑетÑÑ Ð½ÐµÐ¸Ð·Ð²ÐµÑтным контейнером. + + + + + + + Loading Error... + Ошибка загрузки... + + + + + + + Failed + Ошибка + + + Succeeded + уÑпешно + + + Retry + Повторить попытку + + + Cancelled + Отменено + + + In Progress + ВыполнÑетÑÑ + + + Status Unknown + СоÑтоÑние неизвеÑтно + + + Executing + Выполнение + + + Waiting for Thread + Ожидание потока + + + Between Retries + Между попытками + + + Idle + БездейÑтвие + + + Suspended + ПриоÑтановлено + + + [Obsolete] + [УÑтаревший] + + + Yes + Да + + + No + Ðет + + + Not Scheduled + Ðе запланировано + + + Never Run + Ðикогда не запуÑкать + + + + + + + Name + Ð˜Ð¼Ñ + + + Target Database + Ð¦ÐµÐ»ÐµÐ²Ð°Ñ Ð±Ð°Ð·Ð° данных + + + Last Run + ПоÑледний запуÑк + + + Next Run + Следующий запуÑк + + + Status + Ð¡Ñ‚Ð°Ñ‚ÑƒÑ + + + Last Run Outcome + Результат поÑледнего запуÑка + + + Previous Runs + Предыдущие запуÑки + + + No Steps available for this job. + Шаги Ð´Ð»Ñ Ñтого Ð·Ð°Ð´Ð°Ð½Ð¸Ñ Ð½ÐµÐ´Ð¾Ñтупны. + + + Error: + Ошибка: + + + Notebook Error: + Ошибка запиÑной книжки: + + + + + + + Home + ДомашнÑÑ Ñтраница + + + No connection information could be found for this dashboard + Ðе удалоÑÑŒ найти ÑÐ²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¾ подключении Ð´Ð»Ñ Ñтой панели мониторинга + + + + + + + Data + Данные + + + Connection + Подключение + + + Query + Ð—Ð°Ð¿Ñ€Ð¾Ñ + + + Notebook + ЗапиÑÐ½Ð°Ñ ÐºÐ½Ð¸Ð¶ÐºÐ° + + + SQL + SQL + + + Microsoft SQL Server + Microsoft SQL Server + + + Dashboard + Панель мониторинга + + + Profiler + Профилировщик + + + + + + + Close + Закрыть + + + + + + + Success + УÑпех + + + Error + Ошибка + + + Refresh + Обновить + + + New Job + Создание Ð·Ð°Ð´Ð°Ð½Ð¸Ñ + + + Run + ЗапуÑк + + + : The job was successfully started. + : задание уÑпешно запущено. + + + Stop + ОÑтановить + + + : The job was successfully stopped. + : задание уÑпешно оÑтановлено. + + + Edit Job + Изменение Ð·Ð°Ð´Ð°Ð½Ð¸Ñ + + + Open + Открыто + + + Delete Job + Удалить задание + + + Are you sure you'd like to delete the job '{0}'? + Ð’Ñ‹ дейÑтвительно хотите удалить задание "{0}"? + + + Could not delete job '{0}'. +Error: {1} + Ðе удалоÑÑŒ удалить задание '{0}'. +Ошибка: {1} + + + The job was successfully deleted + Задание уÑпешно удалено + + + New Step + Ðовый шаг + + + Delete Step + Удалить шаг + + + Are you sure you'd like to delete the step '{0}'? + Ð’Ñ‹ дейÑтвительно хотите удалить шаг "{0}"? + + + Could not delete step '{0}'. +Error: {1} + Ðе удалоÑÑŒ удалить шаг "{0}". +Ошибка: {1} + + + The job step was successfully deleted + Шаг Ð·Ð°Ð´Ð°Ð½Ð¸Ñ Ð±Ñ‹Ð» уÑпешно удален + + + New Alert + Создание Ð¿Ñ€ÐµÐ´ÑƒÐ¿Ñ€ÐµÐ¶Ð´ÐµÐ½Ð¸Ñ + + + Edit Alert + Изменить оповещение + + + Delete Alert + Удалить предупреждение + + + Cancel + Отмена + + + Are you sure you'd like to delete the alert '{0}'? + Ð’Ñ‹ дейÑтвительно хотите удалить предупреждение "{0}"? + + + Could not delete alert '{0}'. +Error: {1} + Ðе удалоÑÑŒ удалить оповещение "{0}". +Ошибка: {1} + + + The alert was successfully deleted + Предупреждение уÑпешно удалено + + + New Operator + Создание оператора + + + Edit Operator + Оператор Ñ€ÐµÐ´Ð°ÐºÑ‚Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ + + + Delete Operator + Оператор ÑƒÐ´Ð°Ð»ÐµÐ½Ð¸Ñ + + + Are you sure you'd like to delete the operator '{0}'? + Ð’Ñ‹ дейÑтвительно хотите удалить оператор "{0}"? + + + Could not delete operator '{0}'. +Error: {1} + Ðе удалоÑÑŒ удалить оператор "{0}". +Ошибка: {1} + + + The operator was deleted successfully + Оператор уÑпешно удален + + + New Proxy + Ðовый прокÑи-Ñервер + + + Edit Proxy + Изменить прокÑи-Ñервер + + + Delete Proxy + Удалить прокÑи-Ñервер + + + Are you sure you'd like to delete the proxy '{0}'? + Ð’Ñ‹ дейÑтвительно хотите удалить прокÑи-Ñервер "{0}"? + + + Could not delete proxy '{0}'. +Error: {1} + Ðе удалоÑÑŒ удалить прокÑи-Ñервер "{0}". +Ошибка: {1} + + + The proxy was deleted successfully + ПрокÑи-Ñервер был уÑпешно удален + + + New Notebook Job + Ðовое задание запиÑной книжки + + + Edit + Изменить + + + Open Template Notebook + Открыть запиÑную книжку шаблона + + + Delete + Удалить + + + Are you sure you'd like to delete the notebook '{0}'? + Ð’Ñ‹ уверены, что хотите удалить запиÑную книжку "{0}"? + + + Could not delete notebook '{0}'. +Error: {1} + Ðе удалоÑÑŒ удалить запиÑную книжку '{0}'. +Ошибка: {1} + + + The notebook was successfully deleted + ЗапиÑÐ½Ð°Ñ ÐºÐ½Ð¸Ð¶ÐºÐ° уÑпешно удалена + + + Pin + Закрепить + + + Delete + Удалить + + + Unpin + Открепить + + + Rename + Переименование + + + Open Latest Run + Открытый поÑледний запуÑк + + + + + + + Please select a connection to run cells for this kernel + Выберите подключение, на котором будут выполнÑÑ‚ÑŒÑÑ Ñчейки Ð´Ð»Ñ Ñтого Ñдра + + + Failed to delete cell. + Ðе удалоÑÑŒ удалить Ñчейку. + + + Failed to change kernel. Kernel {0} will be used. Error was: {1} + Ðе удалоÑÑŒ изменить Ñдро. Будет иÑпользоватьÑÑ Ñдро {0}. Ошибка: {1} + + + Failed to change kernel due to error: {0} + Ðе удалоÑÑŒ изменить Ñдро из-за ошибки: {0} + + + Changing context failed: {0} + Ðе удалоÑÑŒ изменить контекÑÑ‚: {0} + + + Could not start session: {0} + Ðе удалоÑÑŒ запуÑтить ÑеанÑ: {0} + + + A client session error occurred when closing the notebook: {0} + При закрытии запиÑной книжки произошла ошибка ÑеанÑа клиента: {0} + + + Can't find notebook manager for provider {0} + Ðе удаетÑÑ Ð½Ð°Ð¹Ñ‚Ð¸ диÑпетчер запиÑных книжек Ð´Ð»Ñ Ð¿Ð¾Ñтавщика {0} + + + + + + + An error occurred while starting the notebook session + Произошла ошибка во Ð²Ñ€ÐµÐ¼Ñ Ð·Ð°Ð¿ÑƒÑка ÑеанÑа запиÑной книжки + + + Server did not start for unknown reason + Ðе удалоÑÑŒ запуÑтить Ñервер по неизвеÑтной причине + + + Kernel {0} was not found. The default kernel will be used instead. + Ядро {0} не найдено. Будет иÑпользоватьÑÑ Ñдро по умолчанию. + + + + + + + Unknown component type. Must use ModelBuilder to create objects + ÐеизвеÑтный тип компонента. Ð”Ð»Ñ ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ð¾Ð±ÑŠÐµÐºÑ‚Ð¾Ð² необходимо иÑпользовать ModelBuilder + + + The index {0} is invalid. + Ð˜Ð½Ð´ÐµÐºÑ {0} ÑвлÑетÑÑ Ð½ÐµÐ´Ð¾Ð¿ÑƒÑтимым. + + + Unkown component configuration, must use ModelBuilder to create a configuration object + ÐеизвеÑÑ‚Ð½Ð°Ñ ÐºÐ¾Ð½Ñ„Ð¸Ð³ÑƒÑ€Ð°Ñ†Ð¸Ñ ÐºÐ¾Ð¼Ð¿Ð¾Ð½ÐµÐ½Ñ‚Ð°. Ðеобходимо иÑпользовать ModelBuilder Ð´Ð»Ñ ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ð¾Ð±ÑŠÐµÐºÑ‚Ð° конфигурации + + + + + + + Horizontal Bar + Ð“Ð¾Ñ€Ð¸Ð·Ð¾Ð½Ñ‚Ð°Ð»ÑŒÐ½Ð°Ñ Ð»Ð¸Ð½ÐµÐ¹Ñ‡Ð°Ñ‚Ð°Ñ Ð´Ð¸Ð°Ð³Ñ€Ð°Ð¼Ð¼Ð° + + + Bar + Ð›Ð¸Ð½ÐµÐ¹Ñ‡Ð°Ñ‚Ð°Ñ Ð´Ð¸Ð°Ð³Ñ€Ð°Ð¼Ð¼Ð° + + + Line + График + + + Pie + ÐšÑ€ÑƒÐ³Ð¾Ð²Ð°Ñ Ð´Ð¸Ð°Ð³Ñ€Ð°Ð¼Ð¼Ð° + + + Scatter + Ð¢Ð¾Ñ‡ÐµÑ‡Ð½Ð°Ñ Ð´Ð¸Ð°Ð³Ñ€Ð°Ð¼Ð¼Ð° + + + Time Series + Временной Ñ€Ñд + + + Image + Образ + + + Count + ПодÑчет + + + Table + Таблицы + + + Doughnut + ÐšÑ€ÑƒÐ³Ð¾Ð²Ð°Ñ Ð´Ð¸Ð°Ð³Ñ€Ð°Ð¼Ð¼Ð° + + + + + + + OK + ОК + + + Clear + Ð¡Ð±Ñ€Ð¾Ñ + + + Cancel + Отмена + + + + + + + Cell execution cancelled + Выполнение Ñчейки отменено + + + Query execution was canceled + Выполнение запроÑа отменено + + + The session for this notebook is not yet ready + Ð¡ÐµÐ°Ð½Ñ Ð´Ð»Ñ Ñтой запиÑной книжки еще не готов + + + The session for this notebook will start momentarily + Ð¡ÐµÐ°Ð½Ñ Ð´Ð»Ñ Ñтой запиÑной книжки ÑÐµÐ¹Ñ‡Ð°Ñ Ð½Ð°Ñ‡Ð½ÐµÑ‚ÑÑ + + + No kernel is available for this notebook + Ядро Ð´Ð»Ñ Ñтой запиÑной книжки недоÑтупно + + + + + + + Select Connection + Выберите подключение + + + localhost + localhost + + + + + + + Data Direction + Ðаправление передачи данных + + + Vertical + По вертикали + + + Horizontal + По горизонтали + + + Use column names as labels + ИÑпользовать имена Ñтолбцов в качеÑтве меток + + + Use first column as row label + ИÑпользовать первый Ñтолбец в качеÑтве метки Ñтроки + + + Legend Position + РаÑположение уÑловных обозначений + + + Y Axis Label + Метка оÑи Y + + + Y Axis Minimum Value + Минимальное значение по оÑи Y + + + Y Axis Maximum Value + МакÑимальное значение по оÑи Y + + + X Axis Label + Метка Ð´Ð»Ñ Ð¾Ñи x + + + X Axis Minimum Value + Минимальное значение по оÑи x + + + X Axis Maximum Value + МакÑимальное значение по оÑи x + + + X Axis Minimum Date + ÐœÐ¸Ð½Ð¸Ð¼Ð°Ð»ÑŒÐ½Ð°Ñ Ð´Ð°Ñ‚Ð° по оÑи x + + + X Axis Maximum Date + МакÑÐ¸Ð¼Ð°Ð»ÑŒÐ½Ð°Ñ Ð´Ð°Ñ‚Ð° по оÑи x + + + Data Type + Тип данных + + + Number + ЧиÑло + + + Point + Точка + + + Chart Type + Тип диаграммы + + + Encoding + Кодировка + + + Image Format + Формат Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ + + + + + + + Create Insight + Создать предÑтавление + + + Cannot create insight as the active editor is not a SQL Editor + Ðе удаетÑÑ Ñоздать аналитичеÑкие данные, так как активным редактором не ÑвлÑетÑÑ Ñ€ÐµÐ´Ð°ÐºÑ‚Ð¾Ñ€ SQL + + + My-Widget + Мое мини-приложение + + + Copy as image + Копировать как изображение + + + Could not find chart to save + Ðе удалоÑÑŒ найти диаграмму Ð´Ð»Ñ ÑÐ¾Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ + + + Save as image + Сохранить как изображение + + + PNG + Png + + + Saved Chart to path: {0} + Диаграмма Ñохранена по Ñледующему пути: {0} + + + + + + + Changing editor types on unsaved files is unsupported + Изменение типов редакторов Ð´Ð»Ñ Ð½ÐµÑохраненных файлов не поддерживаетÑÑ + + + + + + + Table does not contain a valid image + Таблица не Ñодержит допуÑтимого Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ + + + + + + + Series {0} + Серии {0} diff --git a/resources/xlf/zh-hans/azurecore.zh-Hans.xlf b/resources/xlf/zh-hans/azurecore.zh-Hans.xlf index 94e9aea09cec..49e35bfe7050 100644 --- a/resources/xlf/zh-hans/azurecore.zh-Hans.xlf +++ b/resources/xlf/zh-hans/azurecore.zh-Hans.xlf @@ -200,4 +200,20 @@ + + + + SQL Managed Instances + SQL 托管实例 + + + + + + + Azure Database for PostgreSQL Servers + 用于 PostgreSQL æœåŠ¡å™¨çš„ Azure æ•°æ®åº“ + + + \ No newline at end of file diff --git a/resources/xlf/zh-hans/big-data-cluster.zh-Hans.xlf b/resources/xlf/zh-hans/big-data-cluster.zh-Hans.xlf index 4c2f8ffa918f..8cd7938d51ec 100644 --- a/resources/xlf/zh-hans/big-data-cluster.zh-Hans.xlf +++ b/resources/xlf/zh-hans/big-data-cluster.zh-Hans.xlf @@ -38,6 +38,14 @@ Delete Mount 删除装载 + + Big Data Cluster + 大数æ®ç¾¤é›† + + + Ignore SSL verification errors against SQL Server Big Data Cluster endpoints such as HDFS, Spark, and Controller if true + 如果为 true,则忽略针对 SQL Server 大数æ®ç¾¤é›†ç»ˆç»“点(如 HDFSã€Spark 和控制器)çš„ SSL 验è¯é”™è¯¯ + @@ -58,26 +66,86 @@ Deleting 正在删除 - - Waiting For Deletion - 等待删除 - Deleted 已删除 + + Applying Upgrade + 正在应用å‡çº§ + Upgrading 正在å‡çº§ - - Waiting For Upgrade - 正在等待å‡çº§ + + Applying Managed Upgrade + 应用托管å‡çº§ + + + Managed Upgrading + 托管å‡çº§ + + + Rollback + 回滚 + + + Rollback In Progress + 回滚正在进行中 + + + Rollback Complete + å›žæ»šå®Œæˆ Error 错误 + + Creating Secrets + 正在创建密钥 + + + Waiting For Secrets + 等待密钥 + + + Creating Groups + 正在创建组 + + + Waiting For Groups + 正在等待组 + + + Creating Resources + æ­£åœ¨åˆ›å»ºèµ„æº + + + Waiting For Resources + æ­£åœ¨ç­‰å¾…èµ„æº + + + Creating Kerberos Delegation Setup + 创建 Kerberos 代ç†è®¾ç½® + + + Waiting For Kerberos Delegation Setup + 创建 Kerberos 代ç†è®¾ç½® + + + Waiting For Deletion + 等待删除 + + + Waiting For Upgrade + 正在等待å‡çº§ + + + Upgrade Paused + å‡çº§å·²æš‚åœ + Running 正在è¿è¡Œ @@ -162,6 +230,78 @@ Unhealthy è¿è¡Œä¸æ­£å¸¸ + + Unexpected error retrieving BDC Endpoints: {0} + 检索 BDC 终结点时出现æ„外错误: {0} + + + + + + + Basic + 基本 + + + Windows Authentication + Windows èº«ä»½éªŒè¯ + + + Login to controller failed + 未能登录到控制器 + + + Login to controller failed: {0} + 未能登录到控制器: {0} + + + Username is required + 用户å是必需的 + + + Password is required + 密ç ä¸ºå¿…填项 + + + url + URL + + + username + 用户å + + + password + å¯†ç  + + + Cluster Management URL + ç¾¤é›†ç®¡ç† URL + + + Authentication type + 身份验è¯ç±»åž‹ + + + Username + 用户å + + + Password + å¯†ç  + + + Cluster Connection + 群集连接 + + + OK + 确定 + + + Cancel + å–消 + @@ -200,6 +340,22 @@ + + + + Connect to Controller (preview) + 连接到控制器(预览) + + + + + + + View Details + æŸ¥çœ‹è¯¦ç»†ä¿¡æ¯ + + + @@ -207,8 +363,8 @@ 找ä¸åˆ°æŽ§åˆ¶å™¨ç»ˆç»“ç‚¹ä¿¡æ¯ - Big Data Cluster Dashboard - - 大数æ®ç¾¤é›†ä»ªè¡¨æ¿ - + Big Data Cluster Dashboard (preview) - + 大数æ®ç¾¤é›†ä»ªè¡¨æ¿ï¼ˆé¢„览) - Yes @@ -263,8 +419,8 @@ 密ç ä¸ºå¿…填项 - Add New Controller - 添加新的控制器 + Add New Controller (preview) + 添加新控制器(预览) url @@ -283,8 +439,8 @@ è®°ä½å¯†ç  - URL - URL + Cluster Management URL + ç¾¤é›†ç®¡ç† URL Authentication type @@ -319,7 +475,7 @@ 疑难解答 - Big data cluster overview + Big Data Cluster overview 大数æ®ç¾¤é›†æ¦‚è¿° @@ -362,18 +518,18 @@ Metrics and Logs 指标和日志 - - Metrics - 指标 + + Node Metrics + 节点指标 + + + SQL Metrics + SQL 指标 Logs 日志 - - View Details - æŸ¥çœ‹è¯¦ç»†ä¿¡æ¯ - @@ -422,31 +578,35 @@ Endpoint 终结点 - - View Details - æŸ¥çœ‹è¯¦ç»†ä¿¡æ¯ + + Unexpected error retrieving BDC Endpoints: {0} + 检索 BDC 终结点时出现æ„外错误: {0} + + + The dashboard requires a connection. Please click retry to enter your credentials. + 仪表æ¿éœ€è¦è¿žæŽ¥ã€‚请å•å‡»â€œé‡è¯•â€ä»¥è¾“入凭æ®ã€‚ + + + Unexpected error occurred: {0} + å‘生æ„外错误: {0} Copy å¤åˆ¶ + + Endpoint '{0}' copied to clipboard + 终结点“{0}â€å·²å¤åˆ¶åˆ°å‰ªè´´æ¿ + - - Basic - 基本 - - - Windows Authentication - Windows èº«ä»½éªŒè¯ - Mount Configuration 装载é…ç½® - + HDFS Path HDFS 路径 @@ -454,22 +614,6 @@ Bad formatting of credentials at {0} {0} 处凭æ®æ ¼å¼é”™è¯¯ - - Login to controller failed - 未能登录到控制器 - - - Login to controller failed: {0} - 未能登录到控制器: {0} - - - Username is required - 用户å是必需的 - - - Password is required - 密ç ä¸ºå¿…填项 - Mounting HDFS folder on path {0} 正在路径 {0} 上装载 HDFS 文件夹 @@ -494,58 +638,30 @@ Unknown error occurred during the mount process 装载过程中å‘生未知错误 - - url - URL - - - username - 用户å - - - password - å¯†ç  - - - URL - URL - - - Authentication type - 身份验è¯ç±»åž‹ - - - Username - 用户å - - - Password - å¯†ç  - - - Cluster Connection - 群集连接 - - - OK - 确定 - - - Cancel - å–消 - - Mount HDFS Folder - 装载 HDFS 文件夹 + Mount HDFS Folder (preview) + 装载 HDFS 文件夹(预览) + + + Path to a new (non-existing) directory which you want to associate with the mount + è¦ä¸Žè£…载关è”çš„æ–°(ä¸å­˜åœ¨)目录的路径 - + Remote URI 远程 URI - + + The URI to the remote data source. Example for ADLS: abfs://fs@saccount.dfs.core.windows.net/ + 到远程数æ®æºçš„ URI。ADLS 示例: abfs://fs@saccount.dfs.core.windows.net/ + + Credentials å‡­æ® + + Mount credentials for authentication to remote data source for reads + 将身份验è¯å‡­æ®è£…载到远程数æ®æºè¿›è¡Œè¯»å– + Refresh Mount 刷新装载 diff --git a/resources/xlf/zh-hans/sql.zh-Hans.xlf b/resources/xlf/zh-hans/sql.zh-Hans.xlf index cc52477941df..4d5628138278 100644 --- a/resources/xlf/zh-hans/sql.zh-Hans.xlf +++ b/resources/xlf/zh-hans/sql.zh-Hans.xlf @@ -1,14 +1,5574 @@  - + - - SQL Language Basics - SQL 语言基础功能 + + Copying images is not supported + ä¸æ”¯æŒå¤åˆ¶å›¾åƒ - - Provides syntax highlighting and bracket matching in SQL files. - 在 SQL 文件中æ供语法高亮和括å·åŒ¹é…功能。 + + + + + + Get Started + 入门 + + + Show Getting Started + 显示入门 + + + Getting &&Started + 入门(&&S) + && denotes a mnemonic + + + + + + + QueryHistory + 查询历å²è®°å½• + + + Whether Query History capture is enabled. If false queries executed will not be captured. + 是å¦å¯ç”¨æŸ¥è¯¢åŽ†å²è®°å½•æ•èŽ·ã€‚若为 false,则ä¸æ•èŽ·æ‰€æ‰§è¡Œçš„查询。 + + + View + 视图 + + + Query History + 查询历å²è®°å½• + + + &&Query History + 查询历å²è®°å½•(&&Q) + && denotes a mnemonic + + + + + + + Connecting: {0} + 正在连接: {0} + + + Running command: {0} + 正在è¿è¡Œå‘½ä»¤: {0} + + + Opening new query: {0} + 打开新查询: {0} + + + Cannot connect as no server information was provided + 无法连接,因为未æä¾›æœåŠ¡å™¨ä¿¡æ¯ + + + Could not open URL due to error {0} + 由于错误 {0} 而无法打开 URL + + + This will connect to server {0} + 这将连接到æœåŠ¡å™¨ {0} + + + Are you sure you want to connect? + 确定è¦è¿žæŽ¥å—? + + + &&Open + 打开(&&O) + + + Connecting query file + 正在连接查询文件 + + + + + + + Error + 错误 + + + Warning + 警告 + + + Info + ä¿¡æ¯ + + + + + + + Saving results into different format disabled for this data provider. + 正在将结果ä¿å­˜ä¸ºæ­¤æ•°æ®æ供程åºç¦ç”¨çš„其他格å¼ã€‚ + + + Cannot serialize data as no provider has been registered + 无法åºåˆ—化数æ®ï¼Œå› ä¸ºå°šæœªæ³¨å†Œä»»ä½•æä¾›ç¨‹åº + + + Serialization failed with an unknown error + 出现未知错误,åºåˆ—化失败 + + + + + + + Connection is required in order to interact with adminservice + 为了与 adminservice 交互, 需è¦è¿žæŽ¥ + + + No Handler Registered + 未注册处ç†ç¨‹åº + + + + + + + Select a file + 选择文件 + + + + + + + Disconnect + 断开连接 + + + Refresh + 刷新 + + + + + + + Results Grid and Messages + ç»“æžœç½‘æ ¼å’Œæ¶ˆæ¯ + + + Controls the font family. + 控制字体系列。 + + + Controls the font weight. + 控制字体粗细。 + + + Controls the font size in pixels. + 以åƒç´ ä¸ºå•ä½æŽ§åˆ¶å­—体大å°ã€‚ + + + Controls the letter spacing in pixels. + 控制以åƒç´ ä¸ºå•ä½çš„å­—æ¯é—´è·ã€‚ + + + Controls the row height in pixels + 以åƒç´ ä¸ºå•ä½æŽ§åˆ¶è¡Œé«˜ + + + Controls the cell padding in pixels + 控制å•å…ƒæ ¼è¾¹è·(以åƒç´ ä¸ºå•ä½) + + + Auto size the columns width on inital results. Could have performance problems with large number of columns or large cells + 自动调整åˆå§‹ç»“果上的列宽度。如果存在大é‡çš„列或大型å•å…ƒæ ¼ï¼Œåˆ™å¯èƒ½å‡ºçŽ°æ€§èƒ½é—®é¢˜ + + + The maximum width in pixels for auto-sized columns + 自动调整大å°çš„列的最大宽度(以åƒç´ ä¸ºå•ä½) + + + + + + + {0} in progress tasks + {0} 正在执行任务 + + + View + 视图 + + + Tasks + 任务 + + + &&Tasks + 任务(&&T) + && denotes a mnemonic + + + + + + + Connections + 连接 + + + View + 视图 + + + Database Connections + æ•°æ®åº“连接 + + + data source connections + æ•°æ®æºè¿žæŽ¥ + + + data source groups + æ•°æ®æºç»„ + + + Startup Configuration + å¯åŠ¨é…ç½® + + + True for the Servers view to be shown on launch of Azure Data Studio default; false if the last opened view should be shown + 如果è¦åœ¨ Azure Data Studio å¯åŠ¨æ—¶é»˜è®¤æ˜¾ç¤ºæœåŠ¡å™¨è§†å›¾ï¼Œåˆ™ä¸º True;如果应显示上次打开的视图,则为 false + + + + + + + The maximum number of recently used connections to store in the connection list. + 连接列表中ä¿å­˜çš„最近使用连接的最大值 + + + Default SQL Engine to use. This drives default language provider in .sql files and the default to use when creating a new connection. Valid option is currently MSSQL + è¦ä½¿ç”¨çš„默认SQL引擎。这将驱动.sql文件中的默认语言æ供程åºï¼Œä»¥åŠåˆ›å»ºæ–°è¿žæŽ¥æ—¶ä½¿ç”¨çš„默认语言æ供程åºã€‚有效选项目å‰æ˜¯MSSQL + + + Attempt to parse the contents of the clipboard when the connection dialog is opened or a paste is performed. + 在打开连接对è¯æ¡†æˆ–执行粘贴时å°è¯•è§£æžå‰ªè´´æ¿çš„内容。 + + + + + + + Server Group color palette used in the Object Explorer viewlet. + 在对象资æºç®¡ç†å™¨ viewlet 中使用的æœåŠ¡å™¨ç»„调色æ¿ã€‚ + + + Auto-expand Server Groups in the Object Explorer viewlet. + 在对象资æºç®¡ç†å™¨ viewlet 中自动展开æœåŠ¡å™¨ç»„。 + + + + + + + Preview Features + 预览功能 + + + Enable unreleased preview features + å¯ç”¨æœªå‘布的预览功能 + + + Show connect dialog on startup + 在å¯åŠ¨æ—¶æ˜¾ç¤ºè¿žæŽ¥å¯¹è¯æ¡† + + + Obsolete API Notification + 过时的 API 通知 + + + Enable/disable obsolete API usage notification + å¯ç”¨/ç¦ç”¨è¿‡æ—¶çš„ API 使用通知 + + + + + + + Problems + 问题 + + + + + + + Identifier of the account type + å¸æˆ·ç±»åž‹çš„标识符 + + + (Optional) Icon which is used to represent the accpunt in the UI. Either a file path or a themable configuration + (å¯é€‰ï¼‰ç”¨äºŽè¡¨ç¤ºUI中的accpunt的图标。文件路径或å¯é…ç½®é…ç½® + + + Icon path when a light theme is used + 使用浅色主题时的图标路径 + + + Icon path when a dark theme is used + 使用暗主题时的图标路径 + + + Contributes icons to account provider. + 将图标贡献给账å·æ供者。 + + + + + + + Indicates data property of a data set for a chart. + 指示图表数æ®é›†çš„æ•°æ®å±žæ€§ã€‚ + + + + + + + Minimum value of the y axis + Y 轴的最å°å€¼ + + + Maximum value of the y axis + y 轴的最大值 + + + Label for the y axis + Y 轴的标签 + + + Minimum value of the x axis + X 轴的最å°å€¼ + + + Maximum value of the x axis + X 轴的最大值 + + + Label for the x axis + x 轴标签 + + + + + + + Displays the results in a simple table + 在简å•è¡¨ä¸­æ˜¾ç¤ºç»“æžœ + + + + + + + Displays an image, for example one returned by an R query using ggplot2 + 显示图åƒï¼Œä¾‹å¦‚使用 ggplot2 时由 R æŸ¥è¯¢è¿”å›žçš„å›¾åƒ + + + What format is expected - is this a JPEG, PNG or other format? + 期望什么格å¼-这是 JPEGã€PNG 还是其他格å¼ï¼Ÿ + + + Is this encoded as hex, base64 or some other format? + 此编ç ä¸ºå六进制ã€base64 还是其他格å¼ï¼Ÿ + + + + + + + For each column in a resultset, displays the value in row 0 as a count followed by the column name. Supports '1 Healthy', '3 Unhealthy' for example, where 'Healthy' is the column name and 1 is the value in row 1 cell 1 + 对于结果集中的æ¯ä¸€åˆ—,将行 0 中的值显示为计数,åŽè·Ÿåˆ—å称。例如支æŒâ€œ1 正常â€ã€â€œ3 ä¸æ­£å¸¸â€ï¼Œå…¶ä¸­â€œæ­£å¸¸â€æ˜¯åˆ—å称,1 表示第 1 è¡Œå•å…ƒæ ¼ 1 中的值 + + + + + + + Manage + ç®¡ç† + + + Dashboard + ä»ªè¡¨æ¿ + + + + + + + The webview that will be displayed in this tab. + 将在此选项å¡ä¸­æ˜¾ç¤ºçš„webview。 + + + + + + + The controlhost that will be displayed in this tab. + 将在此选项å¡ä¸­æ˜¾ç¤º controlhost。 + + + + + + + The list of widgets that will be displayed in this tab. + 将在此选项å¡ä¸­æ˜¾ç¤ºçš„å°éƒ¨ä»¶åˆ—表。 + + + The list of widgets is expected inside widgets-container for extension. + 控件容器里的控件列表应该是用于扩展的 + + + + + + + The list of widgets or webviews that will be displayed in this tab. + 将在此选项å¡ä¸­æ˜¾ç¤ºçš„å°ç»„件或 Web 视图的列表。 + + + widgets or webviews are expected inside widgets-container for extension. + å°éƒ¨ä»¶æˆ– webviews 在å°éƒ¨ä»¶ä¸­è¢«æœŸæœ›ä¸ºæ‰©å±•çš„容器。 + + + + + + + Unique identifier for this container. + 此容器的唯一标识符。 + + + The container that will be displayed in the tab. + 将在选项å¡ä¸­æ˜¾ç¤ºçš„容器。 + + + Contributes a single or multiple dashboard containers for users to add to their dashboard. + 为用户æ供一个或多个仪表æ¿å®¹å™¨ï¼Œä¾›å…¶æ·»åŠ åˆ°å…¶ä»ªè¡¨æ¿ä¸­ã€‚ + + + No id in dashboard container specified for extension. + 用于扩展的仪表æ¿å®¹å™¨ä¸­æ²¡æœ‰æŒ‡å®šId。 + + + No container in dashboard container specified for extension. + 为扩展指定的仪表æ¿å®¹å™¨ä¸­æ²¡æœ‰å®¹å™¨ã€‚ + + + Exactly 1 dashboard container must be defined per space. + æ¯ä¸ªç©ºé—´åªèƒ½å®šä¹‰ 1 个仪表æ¿å®¹å™¨ã€‚ + + + Unknown container type defines in dashboard container for extension. + 在仪表æ¿å®¹å™¨ä¸­ä¸ºæ‰©å±•å®šä¹‰äº†æœªçŸ¥çš„容器类型。 + + + + + + + The model-backed view that will be displayed in this tab. + 将在此选项å¡ä¸­æ˜¾ç¤ºçš„模型支æŒçš„视图。 + + + + + + + Unique identifier for this nav section. Will be passed to the extension for any requests. + 此导航部分的唯一标识符。将被传递给任何请求的扩展å。 + + + (Optional) Icon which is used to represent this nav section in the UI. Either a file path or a themeable configuration + (å¯é€‰) 用于在UI 中表示此导航节的图标。应为文件路径或å¯ä¸»ä½“化的é…ç½® + + + Icon path when a light theme is used + 使用浅色主题时的图标路径 + + + Icon path when a dark theme is used + 使用暗主题时的图标路径 + + + Title of the nav section to show the user. + è¦å‘用户显示的 "导航" 部分的标题。 + + + The container that will be displayed in this nav section. + 容器将显示在此导航节中。 + + + The list of dashboard containers that will be displayed in this navigation section. + 将在此导航部分中显示的仪表æ¿å®¹å™¨çš„列表。 + + + property `icon` can be omitted or must be either a string or a literal like `{dark, light}` + 属性 "icon" å¯ä»¥çœç•¥, 或者必须是字符串或文字, 如 "{dark, light}" + + + No title in nav section specified for extension. + 未在“导航â€éƒ¨åˆ†ä¸­ä¸ºæ‰©å±•æŒ‡å®šæ ‡é¢˜ã€‚ + + + No container in nav section specified for extension. + 未在“导航â€éƒ¨åˆ†ä¸­ä¸ºæ‰©å±•æŒ‡å®šæ ‡é¢˜ã€‚ + + + Exactly 1 dashboard container must be defined per space. + æ¯ä¸ªç©ºé—´åªèƒ½å®šä¹‰ 1 个仪表æ¿å®¹å™¨ã€‚ + + + NAV_SECTION within NAV_SECTION is an invalid container for extension. + NAV_SECTION 内的 NAV_SECTION 是一个无效的扩展容器。 + + + + + + + Backup + 备份 + + + + + + + Unique identifier for this tab. Will be passed to the extension for any requests. + 此选项å¡çš„唯一标识符。将被传递给任何请求的扩展å。 + + + Title of the tab to show the user. + 显示用户的选项å¡çš„标题。 + + + Description of this tab that will be shown to the user. + 将显示给用户的此选项å¡çš„说明。 + + + Condition which must be true to show this item + 显示此项目时æ¡ä»¶å¿…须为 true + + + Defines the connection types this tab is compatible with. Defaults to 'MSSQL' if not set + 定义此选项å¡å…¼å®¹çš„连接类型。如果未设置,则默认为 "MSSQL" + + + The container that will be displayed in this tab. + 将在此选项å¡ä¸­æ˜¾ç¤ºçš„容器。 + + + Whether or not this tab should always be shown or only when the user adds it. + 是å¦åº”始终显示此选项å¡æˆ–仅当用户添加时显示。 + + + Whether or not this tab should be used as the Home tab for a connection type. + 此选项å¡æ˜¯å¦åº”用作连接类型的“开始â€é€‰é¡¹å¡ã€‚ + + + Contributes a single or multiple tabs for users to add to their dashboard. + 为用户æ供一个或多个选项å¡ä»¥æ·»åŠ åˆ°å…¶ä»ªè¡¨æ¿ä¸­ã€‚ + + + No title specified for extension. + 没有为扩展指定标题。 + + + No description specified to show. + 未指定è¦æ˜¾ç¤ºçš„说明。 + + + No container specified for extension. + 没有为扩展指定容器。 + + + Exactly 1 dashboard container must be defined per space + æ¯ä¸ªç©ºé—´å¿…须定义一个完整的仪表æ¿å®¹å™¨ + + + + + + + Restore + 还原 + + + Restore + 还原 + + + + + + + Cannot expand as the required connection provider '{0}' was not found + 无法展开,因为找ä¸åˆ°æ‰€éœ€çš„连接æ供程åºâ€œ{0}†+ + + User canceled + 用户已å–消 + + + Firewall dialog canceled + 防ç«å¢™å¯¹è¯æ¡†å·²å–消 + + + + + + + 1 or more tasks are in progress. Are you sure you want to quit? + 一个或多个任务正在è¿è¡Œä¸­ã€‚确定è¦é€€å‡ºå—? + + + Yes + 是 + + + No + å¦ + + + + + + + Connection is required in order to interact with JobManagementService + 需è¦è¿žæŽ¥æ‰èƒ½ä¸Ž JobManagementService 交互 + + + No Handler Registered + 未注册处ç†ç¨‹åº + + + + + + + An error occured while loading the file browser. + 加载文件æµè§ˆå™¨æ—¶å‡ºé”™ã€‚ + + + File browser error + 文件æµè§ˆå™¨é”™è¯¯ + + + + + + + Notebook Editor + 笔记本编辑器 + + + New Notebook + 新笔记本 + + + New Notebook + 新笔记本 + + + SQL kernel: stop Notebook execution when error occurs in a cell. + SQL 内核: 在å•å…ƒæ ¼ä¸­å‘生错误时åœæ­¢ç¬”记本执行。 + + + + + + + Script as Create + ç”Ÿæˆ Create 脚本 + + + Script as Drop + 生æˆDROP脚本 + + + Select Top 1000 + é€‰æ‹©å‰ 1000 项 + + + Script as Execute + 作为执行的脚本 + + + Script as Alter + 生æˆAlter脚本 + + + Edit Data + ç¼–è¾‘æ•°æ® + + + Select Top 1000 + é€‰æ‹©å‰ 1000 项 + + + Script as Create + ç”Ÿæˆ Create 脚本 + + + Script as Execute + 作为执行的脚本 + + + Script as Alter + 生æˆAlter脚本 + + + Script as Drop + 生æˆDROP脚本 + + + Refresh + 刷新 + + + + + + + Connection error + 连接错误 + + + Connection failed due to Kerberos error. + 由于 Kerberos 错误,连接失败。 + + + Help configuring Kerberos is available at {0} + å¯åœ¨ {0} 处获å–有关é…ç½® Kerberos 的帮助 + + + If you have previously connected you may need to re-run kinit. + 如果您之å‰å·²è¿žæŽ¥è¿‡ï¼Œåˆ™å¯èƒ½éœ€è¦é‡æ–°è¿è¡Œkinit。 + + + + + + + Refresh account was canceled by the user + 用户已å–消刷新å¸æˆ· + + + + + + + Specifies view templates + æŒ‡å®šè§†å›¾æ¨¡æ¿ + + + Specifies session templates + 指定会è¯æ¨¡æ¿ + + + Profiler Filters + 探查器筛选器 + + + + + + + Toggle Query History + 切æ¢æŸ¥è¯¢åŽ†å²è®°å½• + + + Delete + 删除 + + + Clear All History + 清除所有历å²è®°å½• + + + Open Query + 打开查询 + + + Run Query + è¿è¡ŒæŸ¥è¯¢ + + + Toggle Query History capture + 切æ¢æŸ¥è¯¢åŽ†å²è®°å½•æ•èŽ· + + + Pause Query History Capture + æš‚åœæŸ¥è¯¢åŽ†å²è®°å½•æ•èŽ· + + + Start Query History Capture + 开始查询历å²è®°å½•æ•èŽ· + + + + + + + Preview features are required in order for extensions to be fully supported and for some actions to be available. Would you like to enable preview features? + 为了完全支æŒæ‰©å±•å’Œæä¾›æŸäº›æ“作,需è¦é¢„览功能。是å¦è¦å¯ç”¨é¢„览功能? + + + Yes + 是 + + + No + å¦ + + + No, don't show again + å¦ï¼Œä¸å†æ˜¾ç¤º + + + + + + + Commit row failed: + æ交行失败: + + + Started executing query "{0}" + 已开始执行查询 "{0}" + + + Update cell failed: + å•å…ƒæ ¼æ›´æ–°å¤±è´¥: + + + + + + + Failed to create Object Explorer session + 未能创建对象资æºç®¡ç†å™¨ä¼šè¯ + + + Multiple errors: + 多个错误: + + + + + + + No URI was passed when creating a notebook manager + 创建笔记本管ç†å™¨æ—¶æœªä¼ é€’ URI + + + Notebook provider does not exist + 笔记本æ供程åºä¸å­˜åœ¨ + + + + + + + Query Results + 查询结果 + + + Query Editor + 查询编辑器 + + + New Query + 新建查询 + + + [Optional] When true, column headers are included when saving results as CSV + [å¯é€‰] 当为 "true" 时,将在ä¿å­˜ç»“果为 CSV 时包å«åˆ—标题 + + + [Optional] The custom delimiter to use between values when saving as CSV + [å¯é€‰] ä¿å­˜ä¸º CSV 时使用的自定义分隔符 + + + [Optional] Character(s) used for seperating rows when saving results as CSV + [å¯é€‰] 将结果ä¿å­˜ä¸ºCSV文件时,用于分离行的字符 (s) + + + [Optional] Character used for enclosing text fields when saving results as CSV + [å¯é€‰]将结果ä¿å­˜ä¸ºCSV时用于å°é—­æ–‡æœ¬å­—段的字符 + + + [Optional] File encoding used when saving results as CSV + [å¯é€‰]将结果ä¿å­˜ä¸º CSV æ—¶ä½¿ç”¨çš„æ–‡ä»¶ç¼–ç  + + + Enable results streaming; contains few minor visual issues + å¯ç”¨ç»“æžœæµï¼›åŒ…å«å°‘é‡è½»å¾®çš„å¯è§†åŒ–问题 + + + [Optional] When true, XML output will be formatted when saving results as XML + [å¯é€‰] 如果为 true, 则在将结果å¦å­˜ä¸º XML 时设置 XML è¾“å‡ºçš„æ ¼å¼ + + + [Optional] File encoding used when saving results as XML + [å¯é€‰] 将结果å¦å­˜ä¸º XML æ—¶ä½¿ç”¨çš„æ–‡ä»¶ç¼–ç  + + + [Optional] Configuration options for copying results from the Results View + [å¯é€‰] 从结果视图å¤åˆ¶ç»“果的é…置选项 + + + [Optional] Configuration options for copying multi-line results from the Results View + [å¯é€‰] 用于从结果视图中å¤åˆ¶å¤šè¡Œç»“果的é…置选项 + + + [Optional] Should execution time be shown for individual batches + [å¯é€‰] 是å¦æ˜¾ç¤ºæ¯ä¸ªæ‰¹å¤„ç†çš„执行时间 + + + [Optional] the default chart type to use when opening Chart Viewer from a Query Results + [å¯é€‰] 从查询结果打开图表查看器时è¦ä½¿ç”¨çš„默认图表类型 + + + Tab coloring will be disabled + å°†ç¦ç”¨é€‰é¡¹å¡ç€è‰² + + + The top border of each editor tab will be colored to match the relevant server group + æ¯ä¸ªç¼–辑器选项å¡çš„上边框将被ç€è‰²ä»¥åŒ¹é…相关的æœåŠ¡å™¨ç»„ + + + Each editor tab's background color will match the relevant server group + æ¯ä¸ªç¼–辑器选项å¡çš„背景颜色将与相关的æœåŠ¡å™¨ç»„åŒ¹é… + + + Controls how to color tabs based on the server group of their active connection + 控制如何根æ®æ´»åŠ¨è¿žæŽ¥çš„æœåŠ¡å™¨ç»„为选项å¡ç€è‰² + + + Controls whether to show the connection info for a tab in the title. + 控制是å¦æ˜¾ç¤ºæ ‡é¢˜ä¸­é€‰é¡¹å¡çš„连接信æ¯ã€‚ + + + Prompt to save generated SQL files + æ示ä¿å­˜ç”Ÿæˆçš„ SQL 文件 + + + Should IntelliSense be enabled + 是å¦åº”å¯ç”¨æ™ºèƒ½æ„ŸçŸ¥ + + + Should IntelliSense error checking be enabled + 是å¦å¼€å¯IntelliSense错误检查 + + + Should IntelliSense suggestions be enabled + 是å¦åº”å¯ç”¨ IntelliSense + + + Should IntelliSense quick info be enabled + 是å¦åº”å¯ç”¨ IntelliSense å¿«é€Ÿä¿¡æ¯ + + + Should IntelliSense suggestions be lowercase + 是å¦å°å†™æ˜¾ç¤ºIntelliSense建议 + + + Maximum number of rows to return before the server stops processing your query. + 在æœåŠ¡å™¨åœæ­¢å¤„ç†æŸ¥è¯¢ä¹‹å‰è¦è¿”回的最大行数。 + + + Maximum size of text and ntext data returned from a SELECT statement + 从 SELECT 语å¥è¿”回的 text å’Œ ntext æ•°æ®çš„大å°ä¸Šé™ + + + An execution time-out of 0 indicates an unlimited wait (no time-out) + 如果执行超时为 0,则表示无é™ç­‰å¾…(无超时) + + + Enable SET NOCOUNT option + å¯ç”¨ SET NOCOUNT 选项 + + + Enable SET NOEXEC option + å¯ç”¨ SET NOEXEC 选项 + + + Enable SET PARSEONLY option + å¯ç”¨ SET PARSEONLY 选项 + + + Enable SET ARITHABORT option + å¯ç”¨ SET ARITHABORT 选项 + + + Enable SET STATISTICS TIME option + å¯ç”¨ SET STATISTICS TIME 选项 + + + Enable SET STATISTICS IO option + å¯ç”¨ SET STATISTICS IO 选项 + + + Enable SET XACT_ABORT ON option + å¯ç”¨ SET XACT_ABORT ON 选项 + + + Enable SET TRANSACTION ISOLATION LEVEL option + å¯ç”¨ SET TRANSACTION ISOLATION LEVEL 选项 + + + Enable SET DEADLOCK_PRIORITY option + å¯ç”¨ SET DEADLOCK_PRIORITY 选项 + + + Enable SET LOCK TIMEOUT option (in milliseconds) + å¯ç”¨ SET LOCK TIMEOUT 选项(以毫秒为å•ä½) + + + Enable SET QUERY_GOVERNOR_COST_LIMIT + å¯ç”¨ SET QUERY_GOVERNOR_COST_LIMIT + + + Enable SET ANSI_DEFAULTS + å¯ç”¨ SET ANSI_DEFAULTS + + + Enable SET QUOTED_IDENTIFIER + å¯ç”¨ SET QUOTED_IDENTIFIER + + + Enable SET ANSI_NULL_DFLT_ON + å¯ç”¨ SET ANSI_NULL_DFLT_ON + + + Enable SET IMPLICIT_TRANSACTIONS + å¯ç”¨ SET IMPLICIT_TRANSACTIONS + + + Enable SET CURSOR_CLOSE_ON_COMMIT + å¯ç”¨ SET CURSOR_CLOSE_ON_COMMIT + + + Enable SET ANSI_PADDING + å¯ç”¨ SET ANSI_PADDING + + + Enable SET ANSI_WARNINGS + å¯ç”¨ SET ANSI_WARNINGS + + + Enable SET ANSI_NULLS + å¯ç”¨ SET ANSI_NULLS + + + Set keybinding workbench.action.query.shortcut{0} to run the shortcut text as a procedure call. Any selected text in the query editor will be passed as a parameter + 设置 keybinding 工作å°. æ“作. 查询. å¿«æ·æ–¹å¼ {0} 将快æ·æ–¹å¼æ–‡æœ¬ä½œä¸ºè¿‡ç¨‹è°ƒç”¨è¿è¡Œã€‚查询编辑器中的任何选定文本都将作为å‚数传递 + + + + + + + Common id for the provider + æ供程åºçš„公共 id + + + Display Name for the provider + æ供程åºçš„显示å称 + + + Icon path for the server type + æœåŠ¡å™¨ç±»åž‹çš„图标路径 + + + Options for connection + 连接选项 + + + + + + + OK + 确定 + + + Close + 关闭 + + + Copy details + å¤åˆ¶è¯¦ç»†ä¿¡æ¯ + + + + + + + Add server group + 添加æœåŠ¡å™¨ç»„ + + + Edit server group + 编辑æœåŠ¡å™¨ç»„ + + + + + + + Error adding account + 添加å¸æˆ·æ—¶å‡ºé”™ + + + Firewall rule error + 防ç«å¢™è§„则错误 + + + + + + + Some of the loaded extensions are using obsolete APIs, please find the detailed information in the Console tab of Developer Tools window + æŸäº›åŠ è½½çš„扩展正在使用过时的 API,请在开å‘人员工具的“控制å°â€é€‰é¡¹å¡ä¸­æ‰¾åˆ°è¯¦ç»†ä¿¡æ¯ + + + Don't Show Again + ä¸å†æ˜¾ç¤º + + + + + + + Toggle Tasks + 切æ¢ä»»åŠ¡ + + + + + + + Show Connections + 显示连接 + + + Servers + æœåŠ¡å™¨ + + + + + + + Identifier of the view. Use this to register a data provider through `vscode.window.registerTreeDataProviderForView` API. Also to trigger activating your extension by registering `onView:${id}` event to `activationEvents`. + 视图的标识符。使用标识符通过“vscode.window.registerTreeDataProviderForView†API 注册数æ®æ供程åºã€‚åŒæ—¶å°†â€œonView:${id}â€äº‹ä»¶æ³¨å†Œåˆ°â€œactivationEventsâ€ä»¥æ¿€æ´»ä½ çš„扩展。 + + + The human-readable name of the view. Will be shown + 人类å¯è¯»çš„视图å称。将会被显示 + + + Condition which must be true to show this view + 显示此视图必须为真的æ¡ä»¶ + + + Contributes views to the editor + å‘编辑器æ供视图 + + + Contributes views to Data Explorer container in the Activity bar + å‘活动æ ä¸­çš„æ•°æ®èµ„æºç®¡ç†å™¨å®¹å™¨è´¡çŒ®è§†å›¾ + + + Contributes views to contributed views container + 在扩展æ供的视图容器中æ供视图。 + + + View container '{0}' does not exist and all views registered to it will be added to 'Data Explorer'. + 视图容器“{0}â€ä¸å­˜åœ¨ï¼Œå‘其注册的所有视图都将添加到“数æ®èµ„æºç®¡ç†å™¨â€ä¸­ã€‚ + + + Cannot register multiple views with same id `{0}` in the view container `{1}` + 无法在视图容器“{1}â€ä¸­æ³¨å†Œå¤šä¸ªå…·æœ‰ç›¸åŒ ID (“{0}â€) 的视图 + + + A view with id `{0}` is already registered in the view container `{1}` + 视图容器“{1}â€ä¸­å·²æ³¨å†Œæœ‰ ID 为“{0}â€çš„视图 + + + views must be an array + 视图必须为数组 + + + property `{0}` is mandatory and must be of type `string` + 属性“{0}â€æ˜¯å¿…需的,其类型必须是“字符串†+ + + property `{0}` can be omitted or must be of type `string` + 属性“{0}â€å¯ä»¥çœç•¥ï¼Œå¦åˆ™å…¶ç±»åž‹å¿…须是 "string" + + + + + + + Connection Status + è¿žæŽ¥çŠ¶æ€ + + + + + + + Manage + ç®¡ç† + + + Show Details + æ˜¾ç¤ºè¯¦ç»†ä¿¡æ¯ + + + Learn How To Configure The Dashboard + 了解如何é…ç½®ä»ªè¡¨æ¿ + + + + + + + Widget used in the dashboards + 仪表æ¿ä¸­ä½¿ç”¨çš„å°ç»„件 + + + + + + + Displays results of a query as a chart on the dashboard + 将查询结果显示为仪表æ¿ä¸Šçš„图表 + + + Maps 'column name' -> color. for example add 'column1': red to ensure this column uses a red color + 地图“列å称â€->“颜色â€ã€‚例如,添加“列1â€: 红色以确ä¿æ­¤åˆ—显示为红色 + + + Indicates preferred position and visibility of the chart legend. These are the column names from your query, and map to the label of each chart entry + 表示图表图例的首选ä½ç½®å’Œå¯è§æ€§ã€‚这些是查询中的列å称,并映射到æ¯ä¸ªå›¾è¡¨æ¡ç›®çš„标签 + + + If dataDirection is horizontal, setting this to true uses the first columns value for the legend. + 如果 dataDirection 是水平的, 则将其设置为 true 将使用图例的第一列值。 + + + If dataDirection is vertical, setting this to true will use the columns names for the legend. + 如果dataDirection是垂直的,则将其设置为true将使用图例的列å称。 + + + Defines whether the data is read from a column (vertical) or a row (horizontal). For time series this is ignored as direction must be vertical. + 定义无论是从列(垂直)还是从行(水平)读å–æ•°æ®ã€‚对于时间åºåˆ—,这都将被忽略,因为方å‘必须是垂直的。 + + + If showTopNData is set, showing only top N data in the chart. + 如果设置了 showTopNData,则åªåœ¨å›¾è¡¨ä¸­æ˜¾ç¤ºå‰ N 个数æ®ã€‚ + + + + + + + Condition which must be true to show this item + 显示此项目时æ¡ä»¶å¿…须为 true + + + The title of the container + 容器的标题 + + + The row of the component in the grid + 网格中的组件行 + + + The rowspan of the component in the grid. Default value is 1. Use '*' to set to number of rows in the grid. + 网格中组件的行跨度。默认值为 1。使用 "*" 设置为网格中的行数。 + + + The column of the component in the grid + 网格中组件的列 + + + The colspan of the component in the grid. Default value is 1. Use '*' to set to number of columns in the grid. + 网格中组件的列跨è·ã€‚默认值为 1。使用 "*" 设置网格中的列数。 + + + Unique identifier for this tab. Will be passed to the extension for any requests. + 此选项å¡çš„唯一标识符。将被传递给任何请求的扩展å。 + + + Extension tab is unknown or not installed. + "扩展" 选项å¡æœªçŸ¥æˆ–未安装。 + + + + + + + Enable or disable the properties widget + å¯ç”¨æˆ–ç¦ç”¨å±žæ€§å°ç»„件 + + + Property values to show + è¦æ˜¾ç¤ºçš„属性值 + + + Display name of the property + 属性的显示å称 + + + Value in the Database Info Object + æ•°æ®åº“ä¿¡æ¯å¯¹è±¡ä¸­çš„值 + + + Specify specific values to ignore + 指定è¦å¿½ç•¥çš„特定值 + + + Recovery Model + æ¢å¤æ¨¡åž‹ + + + Last Database Backup + 上次数æ®åº“备份 + + + Last Log Backup + 上次日志备份 + + + Compatibility Level + 兼容级别 + + + Owner + 所有者 + + + Customizes the database dashboard page + 自定义数æ®åº“仪表æ¿é¡µé¢ + + + Customizes the database dashboard tabs + 自定义数æ®åº“仪表æ¿é€‰é¡¹å¡ + + + + + + + Enable or disable the properties widget + å¯ç”¨æˆ–ç¦ç”¨å±žæ€§å°ç»„件 + + + Property values to show + è¦æ˜¾ç¤ºçš„属性值 + + + Display name of the property + 属性的显示å称 + + + Value in the Server Info Object + æœåŠ¡å™¨ä¿¡æ¯å¯¹è±¡ä¸­çš„值 + + + Version + 版本 + + + Edition + 版 + + + Computer Name + 计算机å + + + OS Version + OS 版本 + + + Customizes the server dashboard page + 自定义æœåŠ¡å™¨ä»ªè¡¨æ¿é¡µé¢ + + + Customizes the Server dashboard tabs + 自定义æœåŠ¡å™¨ä»ªè¡¨æ¿é€‰é¡¹å¡ + + + + + + + Manage + ç®¡ç† + + + + + + + Widget used in the dashboards + 仪表æ¿ä¸­ä½¿ç”¨çš„å°ç»„件 + + + Widget used in the dashboards + 仪表æ¿ä¸­ä½¿ç”¨çš„å°ç»„件 + + + Widget used in the dashboards + 仪表æ¿ä¸­ä½¿ç”¨çš„å°ç»„件 + + + + + + + Adds a widget that can query a server or database and display the results in multiple ways - as a chart, summarized count, and more + 添加一个å¯ä»¥æŸ¥è¯¢æœåŠ¡å™¨æˆ–æ•°æ®åº“并以多ç§æ–¹å¼æ˜¾ç¤ºç»“果的å°éƒ¨ä»¶, 如图表ã€æ±‡æ€»è®¡æ•°ç­‰ã€‚ + + + Unique Identifier used for caching the results of the insight. + 用于缓存è§è§£ç»“果的唯一标识符。 + + + SQL query to run. This should return exactly 1 resultset. + è¦è¿è¡Œçš„ SQL 查询。这åªèƒ½è¿”回 1 个结果集。 + + + [Optional] path to a file that contains a query. Use if 'query' is not set + [å¯é€‰]包å«æŸ¥è¯¢çš„文件的 路径。未设置“查询â€æ—¶ä½¿ç”¨ + + + [Optional] Auto refresh interval in minutes, if not set, there will be no auto refresh + [å¯é€‰] 自动刷新间隔(分钟数);如果未设置,则ä¸è‡ªåŠ¨åˆ·æ–° + + + Which actions to use + è¦ä½¿ç”¨çš„æ“作 + + + Target database for the action; can use the format '${ columnName }' to use a data driven column name. + æ“作的目标数æ®åº“ï¼›å¯æŒ‰æ ¼å¼ "${ columnName }" 使用数æ®é©±åŠ¨çš„列å。 + + + Target server for the action; can use the format '${ columnName }' to use a data driven column name. + æ“作的目标æœåŠ¡å™¨ï¼›å¯æŒ‰æ ¼å¼ "${ columnName }" 使用数æ®é©±åŠ¨çš„列å。 + + + Target user for the action; can use the format '${ columnName }' to use a data driven column name. + æ“作的目标用户;å¯æŒ‰ "${ columnName }" çš„æ ¼å¼ä½¿ç”¨æ•°æ®é©±åŠ¨çš„列å。 + + + Identifier of the insight + 智能è§è§£çš„标识符 + + + Contributes insights to the dashboard palette. + å‘仪表æ¿è°ƒè‰²æ¿æä¾›è§è§£ã€‚ + + + + + + + Loading + 正在加载 + + + Loading completed + åŠ è½½å·²å®Œæˆ + + + + + + + Defines a property to show on the dashboard + 定义è¦åœ¨ä»ªè¡¨æ¿ä¸Šæ˜¾ç¤ºçš„属性 + + + What value to use as a label for the property + 用作属性标签的值 + + + What value in the object to access for the value + 对象中è¦é’ˆå¯¹è¯¥å€¼è®¿é—®çš„值 + + + Specify values to be ignored + 指定è¦å¿½ç•¥çš„值 + + + Default value to show if ignored or no value + 在忽略或没有值时显示的默认值 + + + A flavor for defining dashboard properties + 用于定义仪表æ¿å±žæ€§çš„风格 + + + Id of the flavor + 风格的 ID + + + Condition to use this flavor + 使用这ç§é£Žæ ¼çš„æ¡ä»¶ + + + Field to compare to + è¦æ¯”较的字段 + + + Which operator to use for comparison + 用于比较的è¿ç®—符 + + + Value to compare the field to + 用于和字段比较的值 + + + Properties to show for database page + 用于显示于数æ®åº“页的属性 + + + Properties to show for server page + è¦æ˜¾ç¤ºçš„用于æœåŠ¡å™¨é¡µçš„属性 + + + Defines that this provider supports the dashboard + 定义此æ供程åºæ”¯æŒä»ªè¡¨æ¿ + + + Provider id (ex. MSSQL) + æä¾›ç¨‹åº ID (如 MSSQL) + + + Property values to show on dashboard + è¦åœ¨ä»ªè¡¨æ¿ä¸Šæ˜¾ç¤ºçš„属性值 + + + + + + + Backup + 备份 + + + You must enable preview features in order to use backup + å¿…é¡»å¯ç”¨é¢„览功能æ‰èƒ½ä½¿ç”¨å¤‡ä»½ + + + Backup command is not supported for Azure SQL databases. + Azure SQL æ•°æ®åº“ä¸æ”¯æŒå¤‡ä»½å‘½ä»¤ã€‚ + + + Backup command is not supported in Server Context. Please select a Database and try again. + æœåŠ¡å™¨ä¸Šä¸‹æ–‡ä¸­ä¸æ”¯æŒå¤‡ä»½å‘½ä»¤ã€‚请选择数æ®åº“,然åŽé‡è¯•ã€‚ + + + + + + + Restore + 还原 + + + You must enable preview features in order to use restore + å¿…é¡»å¯ç”¨é¢„览功能æ‰èƒ½ä½¿ç”¨è¿˜åŽŸ + + + Restore command is not supported for Azure SQL databases. + Azure SQL æ•°æ®åº“ä¸æ”¯æŒè¿˜åŽŸå‘½ä»¤ã€‚ + + + + + + + disconnected + 已断开连接 + + + + + + + Server Groups + æœåŠ¡å™¨ç»„ + + + OK + 确定 + + + Cancel + å–消 + + + Server group name + æœåŠ¡å™¨ç»„å称 + + + Group name is required. + 组å称是必需的。 + + + Group description + 组æè¿° + + + Group color + 组颜色 + + + + + + + Extension + 扩展 + + + + + + + OK + 确定 + + + Cancel + å–消 + + + + + + + Open dashboard extensions + 打开仪表æ¿æ‰©å±• + + + OK + 确定 + + + Cancel + å–消 + + + No dashboard extensions are installed at this time. Go to Extension Manager to explore recommended extensions. + 此时没有安装仪表æ¿æ‰©å±•ã€‚转到扩展管ç†å™¨æ¥æœç´¢æŽ¨è的扩展。 + + + + + + + Selected path + 选定的路径 + + + Files of type + 文件类型 + + + OK + 确定 + + + Discard + 放弃 + + + + + + + No Connection Profile was passed to insights flyout + 未将连接é…置文件传递给è§è§£å¼¹å‡ºçª—å£ + + + Insights error + 洞察错误 + + + There was an error reading the query file: + 读å–查询文件时出错: + + + There was an error parsing the insight config; could not find query array/string or queryfile + 分æžæ´žå¯Ÿé…置时出错。找ä¸åˆ°æŸ¥è¯¢æ•°ç»„/字符串或查询文件 + + + + + + + Clear List + 清除列表 + + + Recent connections list cleared + 已清空最新连接列表 + + + Yes + 是 + + + No + å¦ + + + Are you sure you want to delete all the connections from the list? + 确定è¦åˆ é™¤åˆ—表中的所有连接å—? + + + Yes + 是 + + + No + å¦ + + + Delete + 删除 + + + Get Current Connection String + 获å–当å‰è¿žæŽ¥å­—符串 + + + Connection string not available + 连接字符串ä¸å¯ç”¨ + + + No active connection available + 没有å¯ç”¨çš„活动连接 + + + + + + + Refresh + 刷新 + + + Disconnect + 断开连接 + + + New Connection + 新建连接 + + + New Server Group + æ–°æœåŠ¡å™¨ç»„ + + + Edit Server Group + 编辑æœåŠ¡å™¨ç»„ + + + Show Active Connections + 显示活动连接 + + + Show All Connections + 显示所有连接 + + + Recent Connections + 最近的连接 + + + Delete Connection + 删除连接 + + + Delete Group + 删除组 + + + + + + + Edit Data Session Failed To Connect + 编辑数æ®ä¼šè¯æ— æ³•è¿žæŽ¥ + + + + + + + Profiler + 分æžå™¨ + + + Not connected + 未连接 + + + XEvent Profiler Session stopped unexpectedly on the server {0}. + XEvent 分æžå™¨ä¼šè¯åœ¨æœåŠ¡å™¨ {0} 上æ„外åœæ­¢ã€‚ + + + Error while starting new session + å¯åŠ¨æ–°ä¼šè¯æ—¶å‡ºé”™ + + + The XEvent Profiler session for {0} has lost events. + {0} çš„ XEvent 分æžä¼šè¯ç¼ºå¤±äº‹ä»¶ã€‚ + + + Would you like to stop the running XEvent session? + 是å¦è¦åœæ­¢æ­£åœ¨è¿è¡Œçš„ XEvent 会è¯ï¼Ÿ + + + Yes + 是 + + + No + å¦ + + + Cancel + å–消 + + + + + + + Invalid value + 值无效 + + + {0}. {1} + {0}. {1} + + + + + + + blank + 空白 + + + + + + + Error displaying Plotly graph: {0} + 显示 Plotly 图形时出错: {0} + + + + + + + No {0} renderer could be found for output. It has the following MIME types: {1} + 找ä¸åˆ° {0} 呈现器用于输出。它具有以下 MIME 类型: {1} + + + (safe) + (安全) + + + + + + + Item + 项 + + + Value + 值 + + + Property + 属性 + + + Value + 值 + + + Insights + è§è§£ + + + Items + 项目 + + + Item Details + 项目详情 + + + + + + + Error adding account + 添加å¸æˆ·æ—¶å‡ºé”™ + + + + + + + Cannot start auto OAuth. An auto OAuth is already in progress. + 无法å¯åŠ¨è‡ªåŠ¨ OAuth。自动 OAuth 已在进行中。 + + + + + + + Sort by event + æŒ‰äº‹ä»¶æŽ’åº + + + Sort by column + æŒ‰åˆ—æŽ’åº + + + Profiler + 分æžå™¨ + + + OK + 确定 + + + Cancel + å–消 + + + + + + + Clear all + 全部清除 + + + Apply + 应用 + + + OK + 确定 + + + Cancel + å–消 + + + Filters + 筛选器 + + + Remove this clause + 删除此å­å¥ + + + Save Filter + ä¿å­˜ç­›é€‰å™¨ + + + Load Filter + 加载筛选器 + + + Add a clause + 添加å­å¥ + + + Field + 字段 + + + Operator + è¿ç®—符 + + + Value + 值 + + + Is Null + 为 null + + + Is Not Null + ä¸ä¸º Null + + + Contains + åŒ…å« + + + Not Contains + ä¸åŒ…å« + + + Starts With + 开始于 + + + Not Starts With + ä¸å¼€å§‹äºŽ + + + + + + + Double-click to edit + åŒå‡»ä»¥ç¼–辑 + + + + + + + Select Top 1000 + é€‰æ‹©å‰ 1000 项 + + + Script as Execute + 作为执行的脚本 + + + Script as Alter + 生æˆAlter脚本 + + + Edit Data + ç¼–è¾‘æ•°æ® + + + Script as Create + ç”Ÿæˆ Create 脚本 + + + Script as Drop + 生æˆDROP脚本 + + + + + + + No queries to display. + 没有è¦æ˜¾ç¤ºçš„查询。 + + + Query History + 查询历å²è®°å½• + QueryHistory + + + + + + + Failed to get Azure account token for connection + æœªèƒ½èŽ·å– Azure å¸æˆ·ä»¤ç‰Œç”¨äºŽè¿žæŽ¥ + + + Connection Not Accepted + è¿žæŽ¥æœªè¢«æŽ¥å— + + + Yes + 是 + + + No + å¦ + + + Are you sure you want to cancel this connection? + 您确定è¦å–消此连接å—? + + + + + + + Started executing query at + 开始执行查询于 + + + Line {0} + 第 {0} è¡Œ + + + Canceling the query failed: {0} + å–消查询失败: {0} + + + Started saving results to + 已开始将结果ä¿å­˜åˆ° + + + Failed to save results. + ä¿å­˜ç»“果失败。 + + + Successfully saved results to + æˆåŠŸå°†ç»“æžœä¿å­˜åˆ° + + + Executing query... + 正在执行查询... + + + Maximize + 最大化 + + + Restore + 还原 + + + Save as CSV + å¦å­˜ä¸º CSV + + + Save as JSON + å¦å­˜ä¸º JSON + + + Save as Excel + å¦å­˜ä¸º Excel + + + Save as XML + å¦å­˜ä¸º XML + + + View as Chart + 图表视图 + + + Visualize + å¯è§†åŒ– + + + Results + 结果集 + + + Executing query + 执行查询 + + + Messages + æ¶ˆæ¯ + + + Total execution time: {0} + 执行时间总计: {0} + + + Save results command cannot be used with multiple selections. + 多选时ä¸èƒ½ä½¿ç”¨ä¿å­˜ç»“果命令。 + + + + + + + Identifier of the notebook provider. + 笔记本æ供程åºçš„标识符。 + + + What file extensions should be registered to this notebook provider + 应å‘此笔记本æ供程åºæ³¨å†Œå“ªäº›æ–‡ä»¶æ‰©å±•å + + + What kernels should be standard with this notebook provider + 此笔记本æ供程åºåº”æ ‡é…哪些内核 + + + Contributes notebook providers. + 贡献笔记本æ供程åºã€‚ + + + Name of the cell magic, such as '%%sql'. + å•å…ƒæ ¼æ•ˆæžœçš„å称,如 "%%sql"。 + + + The cell language to be used if this cell magic is included in the cell + å•å…ƒæ ¼ä¸­åŒ…å«æ­¤å•å…ƒæ ¼é­”法时è¦ä½¿ç”¨çš„å•å…ƒæ ¼è¯­è¨€ + + + Optional execution target this magic indicates, for example Spark vs SQL + 此魔法指示的å¯é€‰æ‰§è¡Œç›®æ ‡ï¼Œä¾‹å¦‚ Spark å’Œ SQL + + + Optional set of kernels this is valid for, e.g. python3, pyspark, sql + å¯é€‰å†…核集,这适用于 python3ã€pyspark å’Œ sql ç­‰ + + + Contributes notebook language. + 贡献笔记本语言。 + + + + + + + SQL + Sql + + + + + + + F5 shortcut key requires a code cell to be selected. Please select a code cell to run. + 需è¦é€‰æ‹©ä»£ç å•å…ƒæ ¼æ‰èƒ½ä½¿ç”¨ F5 å¿«æ·é”®ã€‚请选择è¦è¿è¡Œçš„代ç å•å…ƒæ ¼ã€‚ + + + Clear result requires a code cell to be selected. Please select a code cell to run. + 需è¦é€‰æ‹©ä»£ç å•å…ƒæ‰èƒ½æ¸…除结果。请选择è¦è¿è¡Œçš„代ç å•å…ƒã€‚ + + + + + + + Save As CSV + å¦å­˜ä¸º CSV + + + Save As JSON + å¦å­˜ä¸º JSON + + + Save As Excel + å¦å­˜ä¸º Excel + + + Save As XML + å¦å­˜ä¸º XML + + + Copy + å¤åˆ¶ + + + Copy With Headers + è¿žåŒæ ‡é¢˜ä¸€èµ·å¤åˆ¶ + + + Select All + 选择全部 + + + + + + + Max Rows: + 最大行数: + + + + + + + Select View + 选择视图 + + + Select Session + é€‰æ‹©ä¼šè¯ + + + Select Session: + 选择会è¯: + + + Select View: + 选择视图: + + + Text + 文本 + + + Label + 标签 + + + Value + 值 + + + Details + 细节 + + + + + + + Copy failed with error {0} + å¤åˆ¶å¤±è´¥ï¼Œå‡ºçŽ°é”™è¯¯ {0} + + + + + + + New Query + 新建查询 + + + Run + è¿è¡Œ + + + Cancel + å–消 + + + Explain + 解释 + + + Actual + 实际为 + + + Disconnect + 断开连接 + + + Change Connection + 更改连接 + + + Connect + 连接 + + + Enable SQLCMD + å¯ç”¨ SQLCMD + + + Disable SQLCMD + ç¦ç”¨ SQLCMD + + + Select Database + 选择数æ®åº“ + + + Select Database Toggle Dropdown + “选择数æ®åº“â€åˆ‡æ¢ä¸‹æ‹‰åˆ—表 + + + Failed to change database + 未能更改数æ®åº“ + + + Failed to change database {0} + 更改数æ®åº“ {0} 失败 + + + + + + + Connection + 连接 + + + Connection type + 连接类型 + + + Recent Connections + 最近的连接 + + + Saved Connections + ä¿å­˜çš„连接 + + + Connection Details + è¿žæŽ¥è¯¦ç»†ä¿¡æ¯ + + + Connect + 连接 + + + Cancel + å–消 + + + No recent connection + 没有最近的连接 + + + No saved connection + 没有ä¿å­˜çš„连接 + + + + + + + OK + 确定 + + + Close + 关闭 + + + + + + + Loading kernels... + 正在加载内核中… + + + Changing kernel... + 正在更改内核... + + + Kernel: + 内核: + + + Attach To: + 附加到: + + + Loading contexts... + 正在加载上下文… + + + Add New Connection + 添加新连接 + + + Select Connection + 选择连接 + + + localhost + 本地 主机 + + + Trusted + ä¿¡ä»» + + + Not Trusted + ä¸å—ä¿¡ä»» + + + Notebook is already trusted. + 笔记本已å—信任。 + + + Collapse Cells + 折å å•å…ƒæ ¼ + + + Expand Cells + 展开å•å…ƒæ ¼ + + + No Kernel + 无内核 + + + None + 没有 + + + New Notebook + 新笔记本 + + + + + + + Time Elapsed + å ç”¨çš„时间 + + + Row Count + 行计数 + + + {0} rows + {0} è¡Œ + + + Executing query... + 正在执行查询... + + + Execution Status + æ‰§è¡ŒçŠ¶æ€ + + + + + + + No task history to display. + 没有è¦æ˜¾ç¤ºçš„任务历å²è®°å½•ã€‚ + + + Task history + 任务历å²è®°å½• + TaskHistory + + + Task error + 任务错误 + + + + + + + Choose SQL Language + 选择 SQL 语言 + + + Change SQL language provider + 更改 SQL 语言æä¾›ç¨‹åº + + + SQL Language Flavor + SQL 语言风格 + + + Change SQL Engine Provider + 更改 SQL 引擎æä¾›ç¨‹åº + + + A connection using engine {0} exists. To change please disconnect or change connection + 使用引擎 {0} 的连接已存在。若è¦æ›´æ”¹, 请先断开或更改连接 + + + No text editor active at this time + 此时没有活动的文本编辑器 + + + Select SQL Language Provider + 选择 SQL 语言æä¾›ç¨‹åº + + + + + + + All files + 所有文件 + + + + + + + File browser tree + 文件æµè§ˆæ ‘ + FileBrowserTree + + + + + + + From + 从 + + + To + 到 + + + Create new firewall rule + 创建新的防ç«å¢™è§„则 + + + OK + 确定 + + + Cancel + å–消 + + + Your client IP address does not have access to the server. Sign in to an Azure account and create a new firewall rule to enable access. + 您的客户端 IP æ— æƒè®¿é—®æœåŠ¡å™¨ã€‚请登录 Azure å¸æˆ·å¹¶æ–°å»ºä¸€ä¸ªé˜²ç«å¢™è§„则以å¯ç”¨è®¿é—®æƒé™ã€‚ + + + Learn more about firewall settings + 了解更多有关防ç«å¢™è®¾ç½®çš„ä¿¡æ¯ + + + Azure account + Azure å¸æˆ· + + + Firewall rule + 防ç«å¢™è§„则 + + + Add my client IP + 添加我的客户端 IP + + + Add my subnet IP range + 添加我的å­ç½‘ IP 范围 + + + + + + + You need to refresh the credentials for this account. + 您需è¦ä¸ºæ­¤ç”¨æˆ·é‡æ–°èŽ·å–凭æ®ã€‚ + + + + + + + Could not find query file at any of the following paths : + {0} + 在下述所有路径中都找ä¸åˆ°æŸ¥è¯¢æ–‡ä»¶: + {0} + + + + + + + Add an account + 添加一个账户 + + + Remove account + 删除å¸æˆ· + + + Are you sure you want to remove '{0}'? + 确实è¦åˆ é™¤ "{0}" å—? + + + Yes + 是 + + + No + å¦ + + + Failed to remove account + 未能删除å¸æˆ· + + + Apply Filters + 应用筛选器 + + + Reenter your credentials + é‡æ–°è¾“å…¥æ‚¨çš„å‡­æ® + + + There is no account to refresh + 没有è¦åˆ·æ–°çš„å¸æˆ· + + + + + + + Focus on Current Query + 关注当å‰æŸ¥è¯¢ + + + Run Query + è¿è¡ŒæŸ¥è¯¢ + + + Run Current Query + è¿è¡Œå½“å‰æŸ¥è¯¢ + + + Run Current Query with Actual Plan + 使用实际计划è¿è¡Œå½“å‰æŸ¥è¯¢ + + + Cancel Query + å–消查询 + + + Refresh IntelliSense Cache + 刷新智能感知缓存 + + + Toggle Query Results + 切æ¢æŸ¥è¯¢ç»“æžœ + + + Editor parameter is required for a shortcut to be executed + è¦æ‰§è¡Œçš„å¿«æ·æ–¹å¼éœ€è¦ç¼–辑器å‚æ•° + + + Parse Query + 解æžæŸ¥è¯¢ + + + Commands completed successfully + 命令已æˆåŠŸå®Œæˆ + + + Command failed: + 命令失败: + + + Please connect to a server + 请连接到æœåŠ¡å™¨ + + + + + + + Chart cannot be displayed with the given data + 无法用给定的数æ®æ˜¾ç¤ºå›¾è¡¨ + + + + + + + The index {0} is invalid. + 索引 {0} 无效。 + + + + + + + no data available + 没有å¯ç”¨çš„æ•°æ® + + + + + + + Information + ä¿¡æ¯ + + + Warning + 警告 + + + Error + 错误 + + + Show Details + æ˜¾ç¤ºè¯¦ç»†ä¿¡æ¯ + + + Copy + å¤åˆ¶ + + + Close + 关闭 + + + Back + 上一步 + + + Hide Details + éšè—è¯¦ç»†ä¿¡æ¯ + + + + + + + is required. + 是必需的。 + + + Invalid input. Numeric value expected. + 输入无效。 期待输入的是数值。 + + + + + + + Execution failed due to an unexpected error: {0} {1} + 由于æ„外错误,执行失败: {0} {1} + + + Total execution time: {0} + 执行时间总计: {0} + + + Started executing query at Line {0} + 已开始在第 {0} 行执行查询 + + + Initialize edit data session failed: + “编辑数æ®â€ä¼šè¯åˆå§‹åŒ–失败: + + + Batch execution time: {0} + 批处ç†æ‰§è¡Œæ—¶é—´: {0} + + + Copy failed with error {0} + å¤åˆ¶å¤±è´¥ï¼Œå‡ºçŽ°é”™è¯¯ {0} + + + + + + + Error: {0} + 错误: {0} + + + Warning: {0} + 警告: {0} + + + Info: {0} + ä¿¡æ¯: {0} + + + + + + + Copy Cell + å¤åˆ¶å•å…ƒæ ¼ + + + + + + + Backup file path + 备份文件路径 + + + Target database + 目标数æ®åº“ + + + Restore database + 还原数æ®åº“ + + + Restore database + 还原数æ®åº“ + + + Database + æ•°æ®åº“ + + + Backup file + 备份文件 + + + Restore + 还原 + + + Cancel + å–消 + + + Script + 脚本 + + + Source + SOURCE + + + Restore from + 还原自 + + + Backup file path is required. + 备份文件路径是必需的。 + + + Please enter one or more file paths separated by commas + 请输入一个或多个用逗å·åˆ†éš”的文件路径 + + + Database + æ•°æ®åº“ + + + Destination + 目标 + + + Select Database Toggle Dropdown + “选择数æ®åº“â€åˆ‡æ¢ä¸‹æ‹‰åˆ—表 + + + Restore to + 还原到 + + + Restore plan + 还原计划 + + + Backup sets to restore + è¦æ¢å¤çš„备份集 + + + Restore database files as + 将数æ®åº“文件还原为 + + + Restore database file details + 还原数æ®åº“æ–‡ä»¶è¯¦ç»†ä¿¡æ¯ + + + Logical file Name + 逻辑文件å + + + File type + 文件类型 + + + Original File Name + 原始文件å + + + Restore as + 还原为 + + + Restore options + 还原选项 + + + Tail-Log backup + 尾日志备份 + + + Server connections + æœåŠ¡å™¨è¿žæŽ¥ + + + General + 常规 + + + Files + 文件 + + + Options + 选项 + + + + + + + Copy & Open + å¤åˆ¶å¹¶æ‰“å¼€ + + + Cancel + å–消 + + + User code + ç”¨æˆ·ä»£ç  + + + Website + 网站 + + + + + + + Done + å·²å®Œæˆ + + + Cancel + å–消 + + + + + + + Must be an option from the list + 必须是列表中的选项 + + + Toggle dropdown + 切æ¢ä¸‹æ‹‰åˆ—表 + + + + + + + Select/Deselect All + 全选/å–消æƒé™ + + + checkbox checked + 选中å¤é€‰æ¡† + + + checkbox unchecked + å¤é€‰æ¡†æœªé€‰ä¸­ + + + + + + + modelview code editor for view model. + 用于视图模型的模型视图代ç ç¼–辑器。 + + + + + + + succeeded + æˆåŠŸ + + + failed + 失败 + + + + + + + Server Description (optional) + æœåŠ¡å™¨è¯´æ˜Ž(å¯é€‰) + + + + + + + Advanced Properties + 高级属性 + + + Discard + 放弃 + + + + + + + Linked accounts + 链接å¸æˆ· + + + Close + 关闭 + + + There is no linked account. Please add an account. + 没有链接的å¸æˆ·ã€‚请添加一个å¸æˆ·ã€‚ + + + Add an account + 添加一个账户 + + + + + + + nbformat v{0}.{1} not recognized + 无法识别 nbformat v{0}.{1} + + + This file does not have a valid notebook format + æ­¤æ–‡ä»¶æ²¡æœ‰æœ‰æ•ˆçš„ç¬”è®°æœ¬æ ¼å¼ + + + Cell type {0} unknown + å•å…ƒæ ¼ç±»åž‹ {0} 未知 + + + Output type {0} not recognized + 无法识别输出类型 {0} + + + Data for {0} is expected to be a string or an Array of strings + {0} çš„æ•°æ®åº”为字符串或字符串数组 + + + Output type {0} not recognized + 无法识别输出类型 {0} + + + + + + + Profiler editor for event text. Readonly + 用于事件文本的分æžå™¨ç¼–辑器。åªè¯» + + + + + + + Run Cells Before + 在...之å‰è¿è¡Œå•å…ƒæ ¼ + + + Run Cells After + 在...åŽè¿è¡Œå•å…ƒæ ¼ + + + Insert Code Before + 在...之å‰æ’å…¥ä»£ç  + + + Insert Code After + 在...之åŽæ’å…¥ä»£ç  + + + Insert Text Before + 在...之å‰æ’入文本 + + + Insert Text After + 在...之åŽæ’入文本 + + + Collapse Cell + 折å å•å…ƒæ ¼ + + + Expand Cell + 展开å•å…ƒæ ¼ + + + Clear Result + 清除结果 + + + Delete + 删除 + + + + + + + No script was returned when calling select script on object + 在对象上调用select脚本时,没有返回脚本 + + + Select + 选择 + + + Create + 创建 + + + Insert + æ’å…¥ + + + Update + æ›´æ–° + + + Delete + 删除 + + + No script was returned when scripting as {0} on object {1} + 当在对象 {1} 上è¿è¡Œè„šæœ¬ {0} 时没有返回任何脚本 + + + Scripting Failed + 脚本编写失败 + + + No script was returned when scripting as {0} + 脚本为 {0} 时未返回任何脚本 + + + + + + + Recent Connections + 最近的连接 + + + Servers + æœåŠ¡å™¨ + + + + + + + No Kernel + 无内核 + + + Cannot run cells as no kernel has been configured + 无法è¿è¡Œå•å…ƒæ ¼ï¼Œå› ä¸ºå°šæœªé…置内核 + + + Error + 错误 + + + + + + + Azure Data Studio + Azure Data Studio + + + Start + å¯åŠ¨ + + + New connection + 新建连接 + + + New query + 新建查询 + + + New notebook + 新笔记本 + + + Open file + 打开文件 + + + Open file + 打开文件 + + + Deploy + 部署 + + + Deploy SQL Server… + 部署 SQL Server… + + + Recent + 最近 + + + More... + 更多... + + + No recent folders + 无最近使用文件夹 + + + Help + 帮助 + + + Getting started + 开始使用 + + + Documentation + Documentation + + + Report issue or feature request + 报告问题或功能请求 + + + GitHub repository + GitHub 存储库 + + + Release notes + å‘行说明 + + + Show welcome page on startup + å¯åŠ¨æ—¶æ˜¾ç¤ºæ¬¢è¿Žé¡µ + + + Customize + 自定义 + + + Extensions + 扩展 + + + Download extensions that you need, including the SQL Server Admin pack and more + 下载所需的扩展,包括 SQL Server 管ç†åŒ…等等 + + + Keyboard Shortcuts + 键盘快æ·æ–¹å¼ + + + Find your favorite commands and customize them + 查找你喜欢的命令并将其自定义 + + + Color theme + 颜色主题 + + + Make the editor and your code look the way you love + 使编辑器和代ç å‘ˆçŽ°ä½ å–œæ¬¢çš„外观 + + + Learn + 学习 + + + Find and run all commands + 查找并è¿è¡Œæ‰€æœ‰å‘½ä»¤ + + + Rapidly access and search commands from the Command Palette ({0}) + 使用命令é¢æ¿å¿«é€Ÿè®¿é—®å’Œæœç´¢å‘½ä»¤ ({0}) + + + Discover what's new in the latest release + 了解最新版本中的新增功能 + + + New monthly blog posts each month showcasing our new features + æ¯æœˆæŽ¨å‡ºæ–°çš„月度åšå®¢æ–‡ç« ï¼Œå…¶ä¸­å±•ç¤ºäº†æˆ‘们的新功能 + + + Follow us on Twitter + 在 Twitter 上关注我们 + + + Keep up to date with how the community is using Azure Data Studio and to talk directly with the engineers. + 跟进了解社区如何使用 Azure Data Studio 并与工程师直接交谈。 + + + + + + + succeeded + æˆåŠŸ + + + failed + 失败 + + + in progress + 正在进行 + + + not started + 未å¯åŠ¨ + + + canceled + å·²å–消 + + + canceling + 正在å–消 + + + + + + + Run + è¿è¡Œ + + + Dispose Edit Failed With Error: + 处ç†ç¼–辑失败, 出现错误: + + + Stop + åœæ­¢ + + + Show SQL Pane + 显示 SQL 窗格 + + + Close SQL Pane + 关闭 SQL 窗格 + + + + + + + Connect + 连接 + + + Disconnect + 断开连接 + + + Start + å¯åŠ¨ + + + New Session + æ–°ä¼šè¯ + + + Pause + æš‚åœ + + + Resume + æ¢å¤ + + + Stop + åœæ­¢ + + + Clear Data + æ¸…é™¤æ•°æ® + + + Auto Scroll: On + 自动滚动:开 + + + Auto Scroll: Off + 自动滚动: 关闭 + + + Toggle Collapsed Panel + 切æ¢æŠ˜å é¢æ¿ + + + Edit Columns + 编辑列 + + + Find Next String + 查找下一个字符串 + + + Find Previous String + 查找上一个字符串 + + + Launch Profiler + å¯åŠ¨åˆ†æžå™¨ + + + Filter… + 筛选器... + + + Clear Filter + 清除筛选器 + + + + + + + Events (Filtered): {0}/{1} + 事件(已筛选): {0}/{1} + + + Events: {0} + 事件: {0} + + + Event Count + 事件计数 + + + + + + + Save As CSV + å¦å­˜ä¸º CSV + + + Save As JSON + å¦å­˜ä¸º JSON + + + Save As Excel + å¦å­˜ä¸º Excel + + + Save As XML + å¦å­˜ä¸º XML + + + Save to file is not supported by the backing data source + 备份数æ®æºä¸æ”¯æŒä¿å­˜åˆ°æ–‡ä»¶ + + + Copy + å¤åˆ¶ + + + Copy With Headers + è¿žåŒæ ‡é¢˜ä¸€èµ·å¤åˆ¶ + + + Select All + 选择全部 + + + Copy + å¤åˆ¶ + + + Copy All + 全部å¤åˆ¶ + + + Maximize + 最大化 + + + Restore + 还原 + + + Chart + 图表 + + + Visualizer + å¯è§†åŒ–工具 + + + + + + + The extension "{0}" is using sqlops module which has been replaced by azdata module, the sqlops module will be removed in a future release. + 扩展“{0}â€ä½¿ç”¨çš„是已被 azdata 模å—替æ¢çš„ sqlops 模å—,sqlops 模å—将在未æ¥çš„版本中被删除。 + + + + + + + Table header background color + 表头背景色 + + + Table header foreground color + 表头å‰æ™¯è‰² + + + Disabled Input box background. + ç¦ç”¨è¾“入框背景。 + + + Disabled Input box foreground. + å‰å°ç¦ç”¨è¾“入框。 + + + Button outline color when focused. + èšç„¦æ—¶æŒ‰é’®è½®å»“颜色。 + + + Disabled checkbox foreground. + å·²ç¦ç”¨å¤é€‰æ¡†å‰æ™¯ã€‚ + + + List/Table background color for the selected and focus item when the list/table is active + 当列表/表处于活动状æ€æ—¶æ‰€é€‰å’Œç„¦ç‚¹é¡¹çš„列表/表背景色 + + + SQL Agent Table background color. + sql 代ç†è¡¨èƒŒæ™¯è‰²ã€‚ + + + SQL Agent table cell background color. + SQL 代ç†è¡¨å•å…ƒæ ¼çš„背景色。 + + + SQL Agent table hover background color. + SQL 代ç†è¡¨æ‚¬åœèƒŒæ™¯é¢œè‰²ã€‚ + + + SQL Agent heading background color. + sql 代ç†æ ‡é¢˜èƒŒæ™¯è‰²ã€‚ + + + SQL Agent table cell border color. + SQL 代ç†è¡¨çš„å•å…ƒæ ¼è¾¹æ¡†é¢œè‰²ã€‚ + + + Results messages error color. + 错误结果消æ¯é¢œè‰²ã€‚ + + + + + + + Choose Results File + 选择结果文件 + + + CSV (Comma delimited) + CSV (以逗å·åˆ†éš”) + + + JSON + JSON + + + Excel Workbook + Excel 工作簿 + + + XML + XML + + + Plain Text + 纯文本 + + + Open file location + 打开文件所在ä½ç½® + + + Open file + 打开文件 + + + + + + + Backup name + 备份å称 + + + Recovery model + æ¢å¤æ¨¡åž‹ + + + Backup type + 备份类型 + + + Backup files + 备份文件 + + + Algorithm + 算法 + + + Certificate or Asymmetric key + è¯ä¹¦æˆ–éžå¯¹ç§°å¯†é’¥ + + + Media + 媒体 + + + Backup to the existing media set + 备份到已有的媒体集 + + + Backup to a new media set + 备份到新的媒体集 + + + Append to the existing backup set + 追加到现有备份集 + + + Overwrite all existing backup sets + 覆盖所有现有备份集 + + + New media set name + 新媒体集å称 + + + New media set description + 新建媒体集说明 + + + Perform checksum before writing to media + 在写入介质å‰æ‰§è¡Œå’Œæ ¡éªŒ + + + Verify backup when finished + 完æˆåŽéªŒè¯å¤‡ä»½ + + + Continue on error + 错误å‘生时继续 + + + Expiration + 过期 + + + Set backup retain days + 设置备份ä¿ç•™å¤©æ•° + + + Copy-only backup + ä»…å¤åˆ¶å¤‡ä»½ + + + Advanced Configuration + 高级é…ç½® + + + Compression + 压缩 + + + Set backup compression + 设置备份压缩 + + + Encryption + 加密 + + + Transaction log + 事务日志 + + + Truncate the transaction log + 截断事务日志 + + + Backup the tail of the log + 备份日志的末尾 + + + Reliability + å¯é æ€§ + + + Media name is required + 需è¦åª’体å称 + + + No certificate or asymmetric key is available + 没有å¯ç”¨çš„è¯ä¹¦æˆ–éžå¯¹ç§°å¯†é’¥ + + + Add a file + 添加文件 + + + Remove files + 删除文件 + + + Invalid input. Value must be greater than or equal 0. + 输入无效。值必须大于或等于0。 + + + Script + 脚本 + + + Backup + 备份 + + + Cancel + å–消 + + + Only backup to file is supported + åªæ”¯æŒå¤‡ä»½åˆ°æ–‡ä»¶ + + + Backup file path is required + 备份文件路径是必需的 + + + + + + + Results + 结果集 + + + Messages + æ¶ˆæ¯ + + + + + + + There is no data provider registered that can provide view data. + 当å‰æ²¡æœ‰å¯æ供视图数æ®çš„æä¾›æ¥æºè¿›è¡Œæ³¨å†Œã€‚ + + + Collapse All + å…¨éƒ¨æŠ˜å  + + + + + + + Home + 主页 + + + + + + + The "{0}" section has invalid content. Please contact extension owner. + “{0}â€éƒ¨åˆ†å…·æœ‰æ— æ•ˆå†…容。请与扩展所有者è”系。 + + + + + + + Jobs + 作业 + + + Notebooks + 笔记本 + + + Alerts + 警报 + + + Proxies + ä»£ç† + + + Operators + è¿ç®—符 + + + + + + + Loading + 正在加载 + + + + + + + SERVER DASHBOARD + æœåŠ¡å™¨ä»ªè¡¨æ¿ + + + + + + + DATABASE DASHBOARD + æ•°æ®åº“ä»ªè¡¨æ¿ + + + + + + + Edit + 编辑 + + + Exit + 退出 + + + Refresh + 刷新 + + + Toggle More + 切æ¢æ›´å¤š + + + Delete Widget + 删除å°éƒ¨ä»¶ + + + Click to unpin + å•å‡»ä»¥å–消固定 + + + Click to pin + å•å‡»ä»¥é”定 + + + Open installed features + 打开已安装的功能 + + + Collapse + æŠ˜å  + + + Expand + 扩展 + + + + + + + Steps + 步骤 + + + + + + + StdIn: + StdIn: + + + + + + + Add code + æ·»åŠ ä»£ç  + + + Add text + 添加文本 + + + Create File + 创建文件 + + + Could not display contents: {0} + 无法显示内容: {0} + + + Please install the SQL Server 2019 extension to run cells. + 请安装 SQL Server 2019 扩展以è¿è¡Œå•å…ƒæ ¼ã€‚ + + + Install Extension + 安装扩展 + + + Code + ä»£ç  + + + Text + 文本 + + + Run Cells + è¿è¡Œå•å…ƒæ ¼ + + + Clear Results + 清除结果 + + + < Previous + < 上一个 + + + Next > + 下一个 > + + + cell with URI {0} was not found in this model + 在此模型中找ä¸åˆ°å…·æœ‰ URI {0} çš„å•å…ƒæ ¼ + + + Run Cells failed - See error in output of the currently selected cell for more information. + å•å…ƒæ ¼è¿è¡Œå¤±è´¥ - 有关详细信æ¯ï¼Œè¯·å‚阅当å‰æ‰€é€‰å•å…ƒæ ¼è¾“出中的错误。 + + + + + + + Click on + å•å‡» + + + + Code + + ä»£ç  + + + or + 或 + + + + Text + + 文本 + + + to add a code or text cell + 添加代ç æˆ–文本å•å…ƒæ ¼ + + + + + + + Database + æ•°æ®åº“ + + + Files and filegroups + 文件和文件组 + + + Full + 完全 + + + Differential + 差异 + + + Transaction Log + 事务日志 + + + Disk + ç£ç›˜ + + + Url + URL + + + Use the default server setting + 使用默认æœåŠ¡å™¨è®¾ç½® + + + Compress backup + 压缩备份 + + + Do not compress backup + ä¸åŽ‹ç¼©å¤‡ä»½ + + + Server Certificate + æœåŠ¡å™¨è¯ä¹¦ + + + Asymmetric Key + éžå¯¹ç§°å¯†é’¥ + + + Backup Files + 备份文件 + + + All Files + 所有文件 + + + + + + + No connections found. + 未找到连接。 + + + Add Connection + 添加连接 + + + + + + + Failed to change database + 未能更改数æ®åº“ + + + + + + + Name + å称 + + + Email Address + 电å­é‚®ä»¶åœ°å€ + + + Enabled + å·²å¯ç”¨ + + + + + + + Name + å称 + + + Last Occurrence + 上次å‘生 + + + Enabled + å·²å¯ç”¨ + + + Delay Between Responses (in secs) + å“应之间的延迟(秒) + + + Category Name + 类别å称 + + + + + + + Account Name + å¸æˆ·å + + + Credential Name + 凭æ®å称 + + + Description + 说明 + + + Enabled + å·²å¯ç”¨ + + + + + + + Unable to load dashboard properties + 无法加载仪表æ¿å±žæ€§ + + + + + + + Search by name of type (a:, t:, v:, f:, or sp:) + 按类型å称æœç´¢ (a:, t:, v:, f:, 或 sp:) + + + Search databases + æœç´¢æ•°æ®åº“ + + + Unable to load objects + 无法加载对象 + + + Unable to load databases + 无法加载数æ®åº“ + + + + + + + Auto Refresh: OFF + 自动刷新: å…³ + + + Last Updated: {0} {1} + 最近更新: {0} {1} + + + No results to show + 没有è¦æ˜¾ç¤ºçš„结果 + + + + + + + No {0}renderer could be found for output. It has the following MIME types: {1} + 找ä¸åˆ° {0} 呈现器用于输出。它具有以下 MIME 类型: {1} + + + safe + 安全 + + + No component could be found for selector {0} + 找ä¸åˆ°é€‰æ‹©å™¨ {0} 的组件 + + + Error rendering component: {0} + 渲染组件时出错: {0} + + + + + + + Connected to + 连接到 + + + Disconnected + 已断开连接 + + + Unsaved Connections + 未ä¿å­˜çš„连接 + + + + + + + Delete Row + 删除行 + + + Revert Current Row + 还原当å‰è¡Œ + + + + + + + Step ID + 步骤 ID + + + Step Name + 步骤å称 + + + Message + æ¶ˆæ¯ + + + + + + + XML Showplan + XML 显示计划 + + + Results grid + 结果网格 + + + + + + + Please select active cell and try again + 请选择活动å•å…ƒæ ¼ï¼Œç„¶åŽé‡è¯• + + + Run cell + è¿è¡Œå•å…ƒæ ¼ + + + Cancel execution + å–消执行 + + + Error on last run. Click to run again + 上次è¿è¡Œæ—¶å‡ºé”™ã€‚请å•å‡»ä»¥é‡æ–°è¿è¡Œ + + + + + + + Add an account... + 添加å¸æˆ·... + + + <Default> + <默认值> + + + Loading... + 正在加载... + + + Server group + æœåŠ¡å™¨ç»„ + + + <Default> + <默认值> + + + Add new group... + 添加新组… + + + <Do not save> + <ä¸ä¿å­˜> + + + {0} is required. + {0} 是必需的。 + + + {0} will be trimmed. + 将修剪 {0}。 + + + Remember password + è®°ä½å¯†ç  + + + Account + å¸æˆ· + + + Refresh account credentials + 刷新å¸æˆ·å‡­æ® + + + Azure AD tenant + Azure AD 租户 + + + Select Database Toggle Dropdown + “选择数æ®åº“â€åˆ‡æ¢ä¸‹æ‹‰åˆ—表 + + + Name (optional) + å称(å¯é€‰) + + + Advanced... + 高级... + + + You must select an account + 必须选择一个å¸æˆ· + + + + + + + Cancel + å–消 + + + The task is failed to cancel. + 任务å–消失败。 + + + Script + 脚本 + + + + + + + Date Created: + 创建日期: + + + Notebook Error: + 笔记本错误: + + + Job Error: + 作业错误: + + + Pinned + 已固定 + + + Recent Runs + 最近è¿è¡Œ + + + Past Runs + å·²è¿è¡Œ + + + + + + + No tree view with id '{0}' registered. + 没有注册 id 为 "{0}" 的树视图。 + + + + + + + Loading... + 正在加载... + + + + + + + Dashboard Tabs ({0}) + 仪表æ¿é€‰é¡¹å¡({0}) + + + Id + ID + + + Title + 标题 + + + Description + 说明 + + + Dashboard Insights ({0}) + 仪表æ¿è§è§£({0}) + + + Id + ID + + + Name + å称 + + + When + 时间 + + + + + + + Chart + 图表 + + + + + + + Operation + æ“作 + + + Object + 对象 + + + Est Cost + 预计消耗 + + + Est Subtree Cost + 估计å­æ ‘æˆæœ¬ + + + Actual Rows + 实际行数 + + + Est Rows + 估计行数 + + + Actual Executions + 实际执行 + + + Est CPU Cost + CPU æˆæœ¬ + + + Est IO Cost + 预计 IO æˆæœ¬ + + + Parallel + 并行 + + + Actual Rebinds + 实际的é‡æ–°ç»‘定 + + + Est Rebinds + 评估é‡æ–°ç»‘定 + + + Actual Rewinds + 实际åŽé€€ + + + Est Rewinds + Est åŽé€€ + + + Partitioned + 分区 + + + Top Operations + 顶部æ“作 + + + + + + + Query Plan + 查询计划 + + + + + + + Could not find component for type {0} + 找ä¸åˆ°ç±»åž‹ {0} 的组件 + + + + + + + A NotebookProvider with valid providerId must be passed to this method + å¿…é¡»å‘此方法传递具有有效 providerId 的记事本æä¾›ç¨‹åº + + + + + + + A NotebookProvider with valid providerId must be passed to this method + å¿…é¡»å‘此方法传递具有有效 providerId 的记事本æä¾›ç¨‹åº + + + no notebook provider found + 找ä¸åˆ°ç¬”记本æä¾›ç¨‹åº + + + No Manager found + 找ä¸åˆ°ç®¡ç†å™¨ + + + Notebook Manager for notebook {0} does not have a server manager. Cannot perform operations on it + 笔记本 {0} 的笔记本管ç†å™¨æ²¡æœ‰æœåŠ¡å™¨ç®¡ç†å™¨ã€‚无法对其执行æ“作 + + + Notebook Manager for notebook {0} does not have a content manager. Cannot perform operations on it + 笔记本 {0} 的笔记本管ç†å™¨æ²¡æœ‰å†…容管ç†å™¨ã€‚无法对其执行æ“作 + + + Notebook Manager for notebook {0} does not have a session manager. Cannot perform operations on it + 笔记本 {0} 的笔记本管ç†å™¨æ²¡æœ‰ä¼šè¯ç®¡ç†å™¨ã€‚无法对其执行æ“作 + + + + + + + SQL kernel error + SQL 内核错误 + + + A connection must be chosen to run notebook cells + 必须选择连接æ‰èƒ½è¿è¡Œç¬”记本å•å…ƒ + + + Displaying Top {0} rows. + æ­£åœ¨æ˜¾ç¤ºå‰ {0} 行。 + + + + + + + Show Recommendations + 显示建议 + + + Install Extensions + 安装扩展 + + + + + + + Name + å称 + + + Last Run + 上次è¿è¡Œ + + + Next Run + 下次è¿è¡Œ + + + Enabled + å·²å¯ç”¨ + + + Status + çŠ¶æ€ + + + Category + 类别 + + + Runnable + å¯è¿è¡Œ + + + Schedule + 计划 + + + Last Run Outcome + 上次è¿è¡Œç»“æžœ + + + Previous Runs + 之å‰çš„è¿è¡Œ + + + No Steps available for this job. + 没有å¯ç”¨äºŽæ­¤ä½œä¸šçš„步骤。 + + + Error: + 错误: + + + + + + + Find + 查找 + + + Find + 查找 + + + Previous match + 上一个匹é…项 + + + Next match + 下一个匹é…项 + + + Close + 关闭 + + + Your search returned a large number of results, only the first 999 matches will be highlighted. + ä½ çš„æœç´¢è¿”回了大é‡ç»“果,将仅çªå‡ºæ˜¾ç¤ºå‰ 999 个匹é…项。 + + + {0} of {1} + {1} 中的 {0} + + + No Results + 无结果 + + + + + + + Run Query + è¿è¡ŒæŸ¥è¯¢ + + + + + + + Done + å·²å®Œæˆ + + + Cancel + å–消 + + + Generate script + 生æˆè„šæœ¬ + + + Next + 下一个 + + + Previous + 上一个 + + + + + + + A server group with the same name already exists. + 已存在åŒåçš„æœåŠ¡å™¨ç»„。 + + + + + + + {0} is an unknown container. + {0} 是未知容器。 + + + + + + + Loading Error... + 加载错误... + + + + + + + Failed + 失败 + + + Succeeded + æˆåŠŸ + + + Retry + é‡è¯• + + + Cancelled + å·²å–消 + + + In Progress + 正在进行 + + + Status Unknown + 状æ€æœªçŸ¥ + + + Executing + 正在执行 + + + Waiting for Thread + 等待线程 + + + Between Retries + é‡è¯•ä¹‹é—´ + + + Idle + 空闲 + + + Suspended + æš‚åœ + + + [Obsolete] + [废弃] + + + Yes + 是 + + + No + å¦ + + + Not Scheduled + 未计划 + + + Never Run + 从ä¸è¿è¡Œ + + + + + + + Name + å称 + + + Target Database + 目标数æ®åº“ + + + Last Run + 上次è¿è¡Œ + + + Next Run + 下次è¿è¡Œ + + + Status + çŠ¶æ€ + + + Last Run Outcome + 上次è¿è¡Œç»“æžœ + + + Previous Runs + 之å‰çš„è¿è¡Œ + + + No Steps available for this job. + 没有å¯ç”¨äºŽæ­¤ä½œä¸šçš„步骤。 + + + Error: + 错误: + + + Notebook Error: + 笔记本错误: + + + + + + + Home + 主页 + + + No connection information could be found for this dashboard + 找ä¸åˆ°æ­¤ä»ªè¡¨æ¿çš„è¿žæŽ¥ä¿¡æ¯ + + + + + + + Data + æ•°æ® + + + Connection + 连接 + + + Query + 查询 + + + Notebook + 笔记本 + + + SQL + Sql + + + Microsoft SQL Server + Microsoft SQL Server + + + Dashboard + ä»ªè¡¨æ¿ + + + Profiler + 分æžå™¨ + + + + + + + Close + 关闭 + + + + + + + Success + æˆåŠŸ + + + Error + 错误 + + + Refresh + 刷新 + + + New Job + 新建作业 + + + Run + è¿è¡Œ + + + : The job was successfully started. + : 作业已æˆåŠŸå¯åŠ¨ã€‚ + + + Stop + åœæ­¢ + + + : The job was successfully stopped. + : 作业已æˆåŠŸåœæ­¢ã€‚ + + + Edit Job + 编辑作业 + + + Open + 打开 + + + Delete Job + 删除作业 + + + Are you sure you'd like to delete the job '{0}'? + 确定è¦åˆ é™¤ä½œä¸šâ€œ{0}â€å—? + + + Could not delete job '{0}'. +Error: {1} + 无法删除作业 "{0}"。 +错误: {1} + + + The job was successfully deleted + 作业已æˆåŠŸåˆ é™¤ + + + New Step + 新步骤 + + + Delete Step + 删除步骤 + + + Are you sure you'd like to delete the step '{0}'? + 确定è¦åˆ é™¤æ­¥éª¤â€œ{0}â€å—? + + + Could not delete step '{0}'. +Error: {1} + 无法删除步骤“{0}â€ã€‚ +错误: {1} + + + The job step was successfully deleted + 作业步骤已æˆåŠŸåˆ é™¤ + + + New Alert + 新警报 + + + Edit Alert + 编辑警报 + + + Delete Alert + 删除警报 + + + Cancel + å–消 + + + Are you sure you'd like to delete the alert '{0}'? + 您确定è¦åˆ é™¤è­¦æŠ¥ "{0}" å—? + + + Could not delete alert '{0}'. +Error: {1} + 无法删除警报 "{0}"。 +错误: {1} + + + The alert was successfully deleted + 警报已æˆåŠŸåˆ é™¤ + + + New Operator + 新建æ“作符 + + + Edit Operator + 编辑æ“作员 + + + Delete Operator + 删除è¿ç®—符 + + + Are you sure you'd like to delete the operator '{0}'? + 确定è¦åˆ é™¤è¿ç®—符“{0}â€å—? + + + Could not delete operator '{0}'. +Error: {1} + "无法删除è¿ç®—符“{0}â€ã€‚ +错误: {1}" + + + The operator was deleted successfully + å·²æˆåŠŸåˆ é™¤è¿ç®—符 + + + New Proxy + æ–°å»ºä»£ç† + + + Edit Proxy + ç¼–è¾‘ä»£ç† + + + Delete Proxy + 删除代ç†æœåŠ¡å™¨ + + + Are you sure you'd like to delete the proxy '{0}'? + 确定è¦åˆ é™¤ä»£ç†â€œ{0}â€å—? + + + Could not delete proxy '{0}'. +Error: {1} + æ— æ³•åˆ é™¤ä»£ç† "{0}"。 +错误: {1} + + + The proxy was deleted successfully + 代ç†å·²æˆåŠŸåˆ é™¤ + + + New Notebook Job + 新建笔记本作业 + + + Edit + 编辑 + + + Open Template Notebook + 打开模æ¿ç¬”记本 + + + Delete + 删除 + + + Are you sure you'd like to delete the notebook '{0}'? + 确定è¦åˆ é™¤ç¬”记本“{0}â€å—? + + + Could not delete notebook '{0}'. +Error: {1} + 无法删除笔记本“{0}â€ã€‚ +错误: {1} + + + The notebook was successfully deleted + 笔记本已æˆåŠŸåˆ é™¤ + + + Pin + 固定 + + + Delete + 删除 + + + Unpin + å–消固定 + + + Rename + é‡å‘½å + + + Open Latest Run + 打开最新è¿è¡Œ + + + + + + + Please select a connection to run cells for this kernel + 请选择一个连接æ¥è¿è¡Œæ­¤å†…核的å•å…ƒ + + + Failed to delete cell. + 未能删除å•å…ƒæ ¼ã€‚ + + + Failed to change kernel. Kernel {0} will be used. Error was: {1} + 未能更改内核。将使用内核 {0}。错误为: {1} + + + Failed to change kernel due to error: {0} + 由于错误而未能更改内核: {0} + + + Changing context failed: {0} + 更改上下文失败: {0} + + + Could not start session: {0} + 无法å¯åŠ¨ä¼šè¯: {0} + + + A client session error occurred when closing the notebook: {0} + 关闭笔记本时å‘生客户端会è¯é”™è¯¯: {0} + + + Can't find notebook manager for provider {0} + 找ä¸åˆ°æä¾›ç¨‹åº {0} 的笔记本管ç†å™¨ + + + + + + + An error occurred while starting the notebook session + å¯åŠ¨ç¬”记本会è¯æ—¶å‡ºé”™ + + + Server did not start for unknown reason + 由于未知原因,æœåŠ¡å™¨æœªå¯åŠ¨ + + + Kernel {0} was not found. The default kernel will be used instead. + 找ä¸åˆ°å†…æ ¸ {0}。将改为使用默认内核。 + + + + + + + Unknown component type. Must use ModelBuilder to create objects + 未知组件类型。必须使用 ModelBuilder 创建对象 + + + The index {0} is invalid. + 索引 {0} 无效。 + + + Unkown component configuration, must use ModelBuilder to create a configuration object + 组件é…置未知,必须使用 ModelBuilder æ¥åˆ›å»ºé…置对象 + + + + + + + Horizontal Bar + æ°´å¹³æ¡ + + + Bar + æ  + + + Line + è¡Œ + + + Pie + 饼图 + + + Scatter + 散点图 + + + Time Series + æ—¶åº + + + Image + 图片 + + + Count + 计数 + + + Table + 表 + + + Doughnut + 饼图 + + + + + + + OK + 确定 + + + Clear + 清除 + + + Cancel + å–消 + + + + + + + Cell execution cancelled + å•å…ƒæ ¼æ‰§è¡Œå·²å–消 + + + Query execution was canceled + 查询执行已å–消 + + + The session for this notebook is not yet ready + 此笔记本的会è¯å°šæœªå‡†å¤‡å¥½ + + + The session for this notebook will start momentarily + 此笔记本的会è¯å°†æš‚æ—¶å¯åŠ¨ + + + No kernel is available for this notebook + 此笔记本没有å¯ç”¨çš„内核 + + + + + + + Select Connection + 选择连接 + + + localhost + 本地 主机 + + + + + + + Data Direction + æ•°æ®æ–¹å‘ + + + Vertical + åž‚ç›´ + + + Horizontal + æ°´å¹³ + + + Use column names as labels + 将列å用作标签 + + + Use first column as row label + 使用第一列作为行标签 + + + Legend Position + 图例ä½ç½® + + + Y Axis Label + Y 轴标签 + + + Y Axis Minimum Value + Y 轴最å°å€¼ + + + Y Axis Maximum Value + Y 轴最大值 + + + X Axis Label + X 轴标签 + + + X Axis Minimum Value + X 轴最å°å€¼ + + + X Axis Maximum Value + X 轴最大值 + + + X Axis Minimum Date + X 轴最å°æ—¥æœŸ + + + X Axis Maximum Date + X 轴最大日期 + + + Data Type + æ•°æ®ç±»åž‹ + + + Number + ç¼–å· + + + Point + 点 + + + Chart Type + 图表类型 + + + Encoding + ç¼–ç  + + + Image Format + 图åƒæ ¼å¼ + + + + + + + Create Insight + 创建洞察力 + + + Cannot create insight as the active editor is not a SQL Editor + 无法创建è§è§£ï¼Œå› ä¸ºæ´»åŠ¨ç¼–辑器ä¸æ˜¯ SQL 编辑器 + + + My-Widget + 我的å°ç»„件 + + + Copy as image + å¤åˆ¶ä¸ºå›¾åƒ + + + Could not find chart to save + 找ä¸åˆ°è¦ä¿å­˜çš„图表 + + + Save as image + å¦å­˜ä¸ºå›¾åƒ + + + PNG + PNG + + + Saved Chart to path: {0} + ä¿å­˜çš„图表到路径: {0} + + + + + + + Changing editor types on unsaved files is unsupported + ä¸æ”¯æŒæ›´æ”¹æœªä¿å­˜æ–‡ä»¶çš„编辑器类型 + + + + + + + Table does not contain a valid image + è¡¨æ ¼ä¸­æ²¡æœ‰ä»»ä½•æœ‰æ•ˆçš„å›¾åƒ + + + + + + + Series {0} + 系列 {0} diff --git a/resources/xlf/zh-hant/azurecore.zh-Hant.xlf b/resources/xlf/zh-hant/azurecore.zh-Hant.xlf index 652d31102a32..0736aa31f88e 100644 --- a/resources/xlf/zh-hant/azurecore.zh-Hant.xlf +++ b/resources/xlf/zh-hant/azurecore.zh-Hant.xlf @@ -200,4 +200,20 @@ + + + + SQL Managed Instances + SQL å—控執行個體 + + + + + + + Azure Database for PostgreSQL Servers + é©ç”¨æ–¼ PostgreSQL 伺æœå™¨çš„ Azure 資料庫 + + + \ No newline at end of file diff --git a/resources/xlf/zh-hant/big-data-cluster.zh-Hant.xlf b/resources/xlf/zh-hant/big-data-cluster.zh-Hant.xlf index da67c5cad07b..4537c84f4885 100644 --- a/resources/xlf/zh-hant/big-data-cluster.zh-Hant.xlf +++ b/resources/xlf/zh-hant/big-data-cluster.zh-Hant.xlf @@ -38,6 +38,14 @@ Delete Mount 刪除è£è¼‰ + + Big Data Cluster + å·¨é‡è³‡æ–™å¢é›† + + + Ignore SSL verification errors against SQL Server Big Data Cluster endpoints such as HDFS, Spark, and Controller if true + 若為 Trueï¼Œå‰‡å¿½ç•¥å° SQL Server å·¨é‡è³‡æ–™å¢é›†ç«¯é»ž (例如 HDFSã€Spark åŠæŽ§åˆ¶å™¨) 所產生的 SSL 驗證錯誤 + @@ -58,26 +66,86 @@ Deleting 正在刪除 - - Waiting For Deletion - 等待刪除 - Deleted 已刪除 + + Applying Upgrade + 正在套用å‡ç´š + Upgrading 正在å‡ç´š - - Waiting For Upgrade - 正在等待å‡ç´š + + Applying Managed Upgrade + 正在套用å—控å‡ç´š + + + Managed Upgrading + å—控å‡ç´š + + + Rollback + 復原 + + + Rollback In Progress + 正在復原 + + + Rollback Complete + å›žå¾©å®Œæˆ Error 錯誤 + + Creating Secrets + 正在建立祕密 + + + Waiting For Secrets + 正在等待秘密 + + + Creating Groups + 正在建立群組 + + + Waiting For Groups + 正在等待群組 + + + Creating Resources + æ­£åœ¨å»ºç«‹è³‡æº + + + Waiting For Resources + æ­£åœ¨ç­‰å¾…è³‡æº + + + Creating Kerberos Delegation Setup + 正在建立 Kerberos 委派設定 + + + Waiting For Kerberos Delegation Setup + 正在等待 Kerberos 委派設定 + + + Waiting For Deletion + 等待刪除 + + + Waiting For Upgrade + 正在等待å‡ç´š + + + Upgrade Paused + å‡ç´šå·²æš«åœ + Running 正在執行 @@ -162,6 +230,78 @@ Unhealthy ç‹€æ³ä¸è‰¯ + + Unexpected error retrieving BDC Endpoints: {0} + æ“·å– BDC 端點時,發生未é æœŸçš„的錯誤: {0} + + + + + + + Basic + 基本 + + + Windows Authentication + Windows é©—è­‰ + + + Login to controller failed + 無法登入控制站 + + + Login to controller failed: {0} + 無法登入控制器: {0} + + + Username is required + 需è¦ä½¿ç”¨è€…å稱 + + + Password is required + 需è¦å¯†ç¢¼ + + + url + URL + + + username + 使用者å稱 + + + password + 密碼 + + + Cluster Management URL + å¢é›†ç®¡ç† URL + + + Authentication type + 驗證類型 + + + Username + 使用者å稱 + + + Password + 密碼 + + + Cluster Connection + å¢é›†é€£ç·š + + + OK + 確定 + + + Cancel + å–消 + @@ -200,6 +340,22 @@ + + + + Connect to Controller (preview) + 連線到控制器 (é è¦½) + + + + + + + View Details + 檢視詳細資料 + + + @@ -207,8 +363,8 @@ 找ä¸åˆ°æŽ§åˆ¶ç«™ç«¯é»žè³‡è¨Š - Big Data Cluster Dashboard - - å·¨é‡è³‡æ–™å¢é›†å„€è¡¨æ¿ - + Big Data Cluster Dashboard (preview) - + å·¨é‡è³‡æ–™å¢é›†å„€è¡¨æ¿ (é è¦½) - Yes @@ -263,8 +419,8 @@ 需è¦å¯†ç¢¼ - Add New Controller - 新增控制項 + Add New Controller (preview) + 新增控制器 (é è¦½) url @@ -283,8 +439,8 @@ 記ä½å¯†ç¢¼ - URL - URL + Cluster Management URL + å¢é›†ç®¡ç† URL Authentication type @@ -319,7 +475,7 @@ 疑難排解 - Big data cluster overview + Big Data Cluster overview å·¨é‡è³‡æ–™å¢é›†æ¦‚觀 @@ -362,18 +518,18 @@ Metrics and Logs 計é‡èˆ‡è¨˜éŒ„ - - Metrics - è¨ˆé‡ + + Node Metrics + ç¯€é»žè¨ˆé‡ + + + SQL Metrics + SQL è¨ˆé‡ Logs 記錄 - - View Details - 檢視詳細資料 - @@ -422,31 +578,35 @@ Endpoint 端點 - - View Details - 檢視詳細資料 + + Unexpected error retrieving BDC Endpoints: {0} + æ“·å– BDC 端點時,發生未é æœŸçš„錯誤: {0} + + + The dashboard requires a connection. Please click retry to enter your credentials. + 儀表æ¿éœ€è¦é€£ç·šã€‚請按一下 [é‡è©¦],以輸入您的èªè­‰ã€‚ + + + Unexpected error occurred: {0} + 發生未é æœŸçš„錯誤: {0} Copy 複製 + + Endpoint '{0}' copied to clipboard + 已將端點 "{0}" 複製到剪貼簿 + - - Basic - 基本 - - - Windows Authentication - Windows é©—è­‰ - Mount Configuration è£è¼‰çµ„æ…‹ - + HDFS Path HDFS 路徑 @@ -454,22 +614,6 @@ Bad formatting of credentials at {0} ä½æ–¼ {0} çš„èªè­‰æ ¼å¼éŒ¯èª¤ - - Login to controller failed - 無法登入控制站 - - - Login to controller failed: {0} - 無法登入控制器: {0} - - - Username is required - 需è¦ä½¿ç”¨è€…å稱 - - - Password is required - 需è¦å¯†ç¢¼ - Mounting HDFS folder on path {0} 正在路徑 {0} 上è£è¼‰ HDFS 資料夾 @@ -494,58 +638,30 @@ Unknown error occurred during the mount process è£è¼‰éŽç¨‹ä¸­ç™¼ç”ŸæœªçŸ¥éŒ¯èª¤ - - url - URL - - - username - 使用者å稱 - - - password - 密碼 - - - URL - URL - - - Authentication type - 驗證類型 - - - Username - 使用者å稱 - - - Password - 密碼 - - - Cluster Connection - å¢é›†é€£ç·š - - - OK - 確定 - - - Cancel - å–消 - - Mount HDFS Folder - è£è¼‰ HDFS 資料夾 + Mount HDFS Folder (preview) + 掛接 HDFS 資料夾 (é è¦½) + + + Path to a new (non-existing) directory which you want to associate with the mount + è¦é€£çµåˆ°æŽ›æŽ¥ä¹‹æ–° (ä¸å­˜åœ¨) 目錄的路徑 - + Remote URI é ç«¯ URI - + + The URI to the remote data source. Example for ADLS: abfs://fs@saccount.dfs.core.windows.net/ + é ç«¯è³‡æ–™ä¾†æºçš„ URI。ADLS 範例: abfs://fs@saccount.dfs.core.windows.net/ + + Credentials èªè­‰ + + Mount credentials for authentication to remote data source for reads + 將驗證用èªè­‰æŽ›æŽ¥è‡³é ç«¯è³‡æ–™ä¾†æºä»¥ä¾›è®€å– + Refresh Mount é‡æ–°æ•´ç†è£è¼‰ diff --git a/resources/xlf/zh-hant/schema-compare.zh-Hant.xlf b/resources/xlf/zh-hant/schema-compare.zh-Hant.xlf index 981799192002..9e79c7d1da95 100644 --- a/resources/xlf/zh-hant/schema-compare.zh-Hant.xlf +++ b/resources/xlf/zh-hant/schema-compare.zh-Hant.xlf @@ -624,11 +624,11 @@ Specifies whether differences in the table options will be ignored or updated when you publish to a database. - 指定當您發行至資料庫時,將忽略或更新資料表é¸é …的差異。 + 指定當您發佈至資料庫時,è¦å¿½ç•¥æˆ–更新資料表é¸é …的差異。 Specifies whether differences in the semi-colons between T-SQL statements will be ignored or updated when you publish to a database. - 指定當您發行至資料庫時,應該忽略或更新 T-SQL 陳述å¼é–“之分號的差異。 + 指定當您發佈至資料庫時,應該忽略或更新 T-SQL 陳述å¼é–“之分號的差異。 Specifies whether differences in the amount of time that SQL Server retains the route in the routing table should be ignored or updated when you publish to a database. @@ -636,11 +636,11 @@ Specifies whether differences in the role membership of logins should be ignored or updated when you publish to a database. - 指定當您發行至資料庫時,應該忽略或更新登入之角色æˆå“¡è³‡æ ¼çš„差異。 + 指定當您發佈至資料庫時,應該忽略或更新登入之角色æˆå“¡è³‡æ ¼çš„差異。 Specifies whether differences in the quoted identifiers setting should be ignored or updated when you publish to a database. - 指定當您發行至資料庫時,應該忽略或更新引號識別碼設定的差異。 + 指定當您發佈至資料庫時,應該忽略或更新引號識別碼設定的差異。 Specifies whether permissions should be ignored. @@ -648,7 +648,7 @@ Specifies whether differences in partition schemes and functions should be ignored or updated when you publish to a database. - 指定當您發行至資料庫時,應該忽略或更新資料分割é…置和函數的差異。 + 指定當您發佈至資料庫時,應該忽略或更新資料分割é…置和函數的差異。 Specifies whether an object's placement on a partition scheme should be ignored or updated when you publish to a database. @@ -656,39 +656,39 @@ Specifies whether the not for replication settings should be ignored or updated when you publish to a database. - 指定當您發行至資料庫時,應該忽略或更新ä¸å¯è¤‡å¯«è¨­å®šã€‚ + 指定當您發佈至資料庫時,è¦å¿½ç•¥æˆ–æ›´æ–°ä¸å¯è¤‡å¯«è¨­å®šã€‚ Specifies whether differences in the security identification number (SID) should be ignored or updated when you publish to a database. - 指定當您發行至資料庫時,應該忽略或更新安全性識別碼 (SID) 的差異。 + 指定當您發佈至資料庫時,應該忽略或更新安全性識別碼 (SID) 的差異。 Specifies whether differences in the lock hints on indexes should be ignored or updated when you publish to a database. - 指定當您發行至資料庫時,應該忽略或更新索引之鎖定æ示的差異。 + 指定當您發佈至資料庫時,應該忽略或更新索引之鎖定æ示的差異。 Specifies whether differences in the casing of keywords should be ignored or updated when you publish to a database. - 指定當您發行至資料庫時,應該忽略或更新關éµå­—之大å°å¯«çš„差異。 + 指定當您發佈至資料庫時,應該忽略或更新關éµå­—之大å°å¯«çš„差異。 Specifies whether differences in the index padding should be ignored or updated when you publish to a database. - 指定當您發行至資料庫時,應該忽略或更新索引填補的差異。 + 指定當您發佈至資料庫時,è¦å¿½ç•¥æˆ–更新索引填補的差異。 Specifies whether differences in the index options should be ignored or updated when you publish to a database. - 指定當您發行至資料庫時,應該忽略或更新索引é¸é …的差異。 + 指定當您發佈至資料庫時,是å¦è¦å¿½ç•¥æˆ–更新索引é¸é …的差異。 Specifies whether differences in the increment for an identity column should be ignored or updated when you publish to a database. - 指定當您發行至資料庫時,應該忽略或更新識別欄ä½ä¹‹å¢žé‡çš„差異。 + 指定當您發佈至資料庫時,應該忽略或更新識別欄ä½ä¹‹å¢žé‡çš„差異。 Specifies whether differences in the seed for an identity column should be ignored or updated when you publish updates to a database. - 指定當您發行更新至資料庫時,應該忽略或更新識別欄ä½ä¹‹ç¨®å­çš„差異。 + 指定當您發佈更新至資料庫時,應該忽略或更新識別欄ä½ä¹‹ç¨®å­çš„差異。 Specifies whether differences in the user settings objects will be ignored or updated when you publish to a database. - 指定當您發行至資料庫時,將忽略或更新使用者設定物件的差異。 + 指定當您發佈至資料庫時,是å¦è¦å¿½ç•¥æˆ–更新使用者設定物件的差異。 Specifies whether differences in the file path for the full-text catalog should be ignored or whether a warning should be issued when you publish to a database. @@ -696,7 +696,7 @@ Specifies whether differences in white space will be ignored or updated when you publish to a database. - 指定當您發行至資料庫時,將忽略或更新空白字元的差異。 + 指定當您發佈至資料庫時,è¦å¿½ç•¥æˆ–更新空白字元的差異。 Specifies whether differences in the value of the WITH NOCHECK clause for foreign keys will be ignored or updated when you publish to a database. @@ -716,7 +716,7 @@ Include refresh statements at the end of the publish script. - 在發行指令碼的çµå°¾åŒ…å«é‡æ–°æ•´ç†é™³è¿°å¼ã€‚ + 在發佈指令碼的çµå°¾åŒ…å«é‡æ–°æ•´ç†é™³è¿°å¼ã€‚ At the end of publish all of the constraints will be verified as one set, avoiding data errors caused by a check or foreign key constraint in the middle of publish. If set to False, your constraints will be published without checking the corresponding data. @@ -732,15 +732,15 @@ Specifies whether target database properties should be set or updated as part of the publish action. - 指定是å¦æ‡‰è©²åœ¨ç™¼è¡Œå‹•ä½œä¸­è¨­å®šæˆ–更新目標資料庫屬性。 + 指定是å¦è¦åœ¨åŸ·è¡Œç™¼ä½ˆå‹•ä½œæ™‚,設定或更新目標資料庫屬性。 Specifies whether differences in the database compatibility should be ignored or updated when you publish to a database. - 指定當您發行至資料庫時,應該忽略或更新資料庫相容性的差異。 + 指定當您發佈至資料庫時,è¦å¿½ç•¥æˆ–更新資料庫相容性的差異。 Specifies whether differences in the database collation should be ignored or updated when you publish to a database. - 指定當您發行至資料庫時,應該忽略或更新資料庫定åºçš„差異。 + 指定當您發佈至資料庫時,è¦å¿½ç•¥æˆ–更新資料庫定åºçš„差異。 Specifies whether DeploymentPlanExecutor contributors should be run when other operations are executed. @@ -760,11 +760,11 @@ Specifies whether transactional statements should be used where possible when you publish to a database. - 指定當您發行至資料庫時,是å¦æ‡‰è©²ç›¡å¯èƒ½ä½¿ç”¨äº¤æ˜“陳述å¼ã€‚ + 指定當您發佈至資料庫時,是å¦æ‡‰è©²ç›¡å¯èƒ½åœ°ä½¿ç”¨äº¤æ˜“陳述å¼ã€‚ Include all composite elements as part of a single publish operation. - 將所有複åˆé …目包å«åœ¨å–®ä¸€ç™¼è¡Œä½œæ¥­ä¸­ã€‚ + 將所有複åˆé …目加入單一發佈作業中。 Do not block data motion on a table which has Row Level Security if this property is set to true. Default is false. @@ -784,7 +784,7 @@ Specifies whether differences in the placement of objects in FILEGROUPs should be ignored or updated when you publish to a database. - 指定當您發行至資料庫時,應該忽略或更新 FILEGROUP 中物件ä½ç½®çš„差異。 + 指定當您發佈至資料庫時,應該忽略或更新 FILEGROUP 中物件ä½ç½®çš„差異。 Specifies whether objects that are replicated are identified during verification. @@ -820,7 +820,7 @@ Specifies that the publish episode should be terminated if there is a possibility of data loss resulting from the publish operation. - 指定如果發行作業å¯èƒ½å°Žè‡´è³‡æ–™éºå¤±ï¼Œå°±æ‡‰è©²çµ‚止發行事件。 + 指定如果發佈作業å¯èƒ½å°Žè‡´è³‡æ–™éºå¤±ï¼Œå°±æ‡‰è©²çµ‚止發佈事件。 Backups the database before deploying any changes. @@ -852,7 +852,7 @@ Specifies whether differences in the paths for files and log files should be ignored or updated when you publish to a database. - 指定當您發行至資料庫時,應該忽略或更新檔案和記錄檔之路徑的差異。 + 指定當您發佈至資料庫時,應該忽略或更新檔案和記錄檔之路徑的差異。 Specifies whether extended properties should be ignored. @@ -868,7 +868,7 @@ Specifies whether differences in the default schema should be ignored or updated when you publish to a database. - 指定當您發行至資料庫時,應該忽略或更新é è¨­çµæ§‹æ述的差異。 + 指定當您發佈至資料庫時,是å¦æ‡‰å¿½ç•¥æˆ–æ›´æ–°é è¨­çµæ§‹æ述中的差異。 Specifies whether differences in the enabled or disabled state of Data Definition Language (DDL) triggers should be ignored or updated when you publish to a database. @@ -880,7 +880,7 @@ Specifies whether differences in the file path for the cryptographic provider should be ignored or updated when you publish to a database. - 指定當您發行至資料庫時,應該忽略或更新密碼編譯æ供者之檔案路徑的差異。 + 指定當您發佈至資料庫時,應該忽略或更新密碼編譯æ供者之檔案路徑的差異。 Specifies whether checks should be performed before publishing that will stop the publish action if issues are present that might block successful publishing. For example, your publish action might stop if you have foreign keys on the target database that do not exist in the database project, and that will cause errors when you publish. @@ -888,19 +888,19 @@ Specifies whether differences in the comments should be ignored or updated when you publish to a database. - 指定當您發行至資料庫時,應該忽略或更新註解的差異。 + 指定當您發佈至資料庫時,è¦å¿½ç•¥æˆ–更新註解的差異。 Specifies whether differences in the column collations should be ignored or updated when you publish to a database. - 指定當您發行至資料庫時,應該忽略或更新資料行定åºçš„差異。 + 指定當您發佈至資料庫時,è¦å¿½ç•¥æˆ–更新資料行定åºçš„差異。 Specifies whether differences in the Authorizer should be ignored or updated when you publish to a database. - 指定當您發行至資料庫時,應該忽略或更新授權者的差異。 + 指定當您發佈至資料庫時,è¦å¿½ç•¥æˆ–更新授權者的差異。 Specifies whether differences in the ANSI NULLS setting should be ignored or updated when you publish to a database. - 指定當您發行至資料庫時,應該忽略或更新 ANSI NULLS 設定的差異。 + 指定當您發佈至資料庫時,應該忽略或更新 ANSI NULLS 設定的差異。 Automatically provides a default value when updating a table that contains data with a column that does not allow null values. @@ -924,7 +924,7 @@ Specifies whether differences in table column order should be ignored or updated when you publish to a database. - 指定當您發行至資料庫時,是å¦æ‡‰ç•¥éŽæˆ–更新資料表資料行順åºçš„差異。 + 指定當您發佈至資料庫時,è¦ç•¥éŽæˆ–更新資料表資料行順åºçš„差異。 diff --git a/resources/xlf/zh-hant/sql.zh-Hant.xlf b/resources/xlf/zh-hant/sql.zh-Hant.xlf index 02cfb0b83361..8c0d0621df1a 100644 --- a/resources/xlf/zh-hant/sql.zh-Hant.xlf +++ b/resources/xlf/zh-hant/sql.zh-Hant.xlf @@ -1,14 +1,5573 @@  - + - - SQL Language Basics - SQL 語言基本知識 + + Copying images is not supported + ä¸æ”¯æ´è¤‡è£½æ˜ åƒ - - Provides syntax highlighting and bracket matching in SQL files. - 為 SQL 檔案æ供語法醒目æ示與括弧å°æ‡‰åŠŸèƒ½ã€‚ + + + + + + Get Started + å…¥é–€æŒ‡å— + + + Show Getting Started + é¡¯ç¤ºå…¥é–€æŒ‡å— + + + Getting &&Started + 開始使用(&&S) + && denotes a mnemonic + + + + + + + QueryHistory + 查詢歷å²è¨˜éŒ„ + + + Whether Query History capture is enabled. If false queries executed will not be captured. + 是å¦å•Ÿç”¨äº†æŸ¥è©¢æ­·å²è¨˜éŒ„æ“·å–。若未啟用,將ä¸æœƒæ“·å–已經執行的查詢。 + + + View + 檢視 + + + Query History + 查詢歷å²è¨˜éŒ„ + + + &&Query History + 查詢歷å²è¨˜éŒ„(&&Q) + && denotes a mnemonic + + + + + + + Connecting: {0} + 正在連線: {0} + + + Running command: {0} + 正在執行命令: {0} + + + Opening new query: {0} + 正在開啟新查詢: {0} + + + Cannot connect as no server information was provided + 因為未æ供伺æœå™¨è³‡è¨Šï¼Œæ‰€ä»¥ç„¡æ³•é€£ç·šï¼Œ + + + Could not open URL due to error {0} + 因為發生錯誤 {0},導致無法開啟 URL + + + This will connect to server {0} + 這會連線到伺æœå™¨ {0} + + + Are you sure you want to connect? + 確定è¦é€£ç·šå—Ž? + + + &&Open + é–‹å•Ÿ(&&O) + + + Connecting query file + 正在連線到查詢檔案 + + + + + + + Error + 錯誤 + + + Warning + 警告 + + + Info + 資訊 + + + + + + + Saving results into different format disabled for this data provider. + 正在將çµæžœå„²å­˜ç‚ºå…¶ä»–æ ¼å¼ï¼Œä½†æ­¤è³‡æ–™æ供者已åœç”¨è©²æ ¼å¼ã€‚ + + + Cannot serialize data as no provider has been registered + 因為未註冊任何æ供者,所以無法åºåˆ—化資料 + + + Serialization failed with an unknown error + 因為發生未知錯誤,導致åºåˆ—化失敗 + + + + + + + Connection is required in order to interact with adminservice + 必需連線以便與 adminservice 互動 + + + No Handler Registered + 未註冊處ç†å¸¸å¼ + + + + + + + Select a file + é¸æ“‡æª”案 + + + + + + + Disconnect + 中斷連線 + + + Refresh + é‡æ–°æ•´ç† + + + + + + + Results Grid and Messages + çµæžœæ–¹æ ¼èˆ‡è¨Šæ¯ + + + Controls the font family. + 控制字型家æ—。 + + + Controls the font weight. + 控制字型粗細。 + + + Controls the font size in pixels. + æŽ§åˆ¶å­—åž‹å¤§å° (åƒç´ )。 + + + Controls the letter spacing in pixels. + 控制字æ¯é–“è· (åƒç´ )。 + + + Controls the row height in pixels + 控制列高 (åƒç´ ) + + + Controls the cell padding in pixels + 控制儲存格填補值 (åƒç´ ) + + + Auto size the columns width on inital results. Could have performance problems with large number of columns or large cells + 自動調整åˆå§‹çµæžœçš„欄寬大å°ã€‚大é‡æ¬„ä½æˆ–大型儲存格å¯èƒ½æœƒé€ æˆæ•ˆèƒ½å•é¡Œ + + + The maximum width in pixels for auto-sized columns + 自動調整大å°ä¹‹æ¬„ä½çš„å¯¬åº¦ä¸Šé™ (åƒç´ ) + + + + + + + {0} in progress tasks + {0} 正在執行的工作 + + + View + 檢視 + + + Tasks + 工作 + + + &&Tasks + 工作(&&T) + && denotes a mnemonic + + + + + + + Connections + 連接 + + + View + 檢視 + + + Database Connections + 資料庫連接 + + + data source connections + 資料來æºé€£æŽ¥ + + + data source groups + 資料來æºç¾¤çµ„ + + + Startup Configuration + å•Ÿå‹•é…ç½® + + + True for the Servers view to be shown on launch of Azure Data Studio default; false if the last opened view should be shown + é è¨­ç‚ºåœ¨ Azure Data Studio 啟動時è¦é¡¯ç¤ºçš„伺æœå™¨æª¢è¦–å³ç‚º True; 如應顯示上次開啟的檢視則為 false + + + + + + + The maximum number of recently used connections to store in the connection list. + 連接列表內最近使用的最大連çµæ•¸ã€‚ + + + Default SQL Engine to use. This drives default language provider in .sql files and the default to use when creating a new connection. Valid option is currently MSSQL + 使用é è¨­ SQL 引擎。在 .sql 檔案é è¨­é©…å‹•çš„é è¨­èªžè¨€æ供者,以åŠå»ºç«‹æ–°é€£æŽ¥æ™‚使用的é è¨­è¨­å®šã€‚有效é¸é …ç›®å‰ç‚º MSSQL + + + Attempt to parse the contents of the clipboard when the connection dialog is opened or a paste is performed. + 嘗試在連線å°è©±æ–¹å¡Šé–‹å•Ÿæˆ–已執行貼上時剖æžå‰ªè²¼ç°¿çš„內容。 + + + + + + + Server Group color palette used in the Object Explorer viewlet. + 在物件總管 viewlet 中使用的伺æœå™¨ç¾¤çµ„調色盤。 + + + Auto-expand Server Groups in the Object Explorer viewlet. + 物件總管 viewlet 中的自動展開伺æœå™¨ç¾¤çµ„。 + + + + + + + Preview Features + é è¦½åŠŸèƒ½ + + + Enable unreleased preview features + 啟用未發佈的é è¦½åŠŸèƒ½ + + + Show connect dialog on startup + 啟動時顯示連線å°è©±æ–¹å¡Š + + + Obsolete API Notification + 淘汰 API 通知 + + + Enable/disable obsolete API usage notification + 啟用/åœç”¨ä½¿ç”¨æ·˜æ±°çš„ API 通知 + + + + + + + Problems + å•é¡Œ + + + + + + + Identifier of the account type + 帳戶類型的識別碼 + + + (Optional) Icon which is used to represent the accpunt in the UI. Either a file path or a themable configuration + (é¸ç”¨) 用於 UI 中表示 accpunt 的圖示。任一檔案路徑或主題化的設定。 + + + Icon path when a light theme is used + 使用亮色主題時的圖示路徑 + + + Icon path when a dark theme is used + 使用暗色主題時的圖åƒè·¯å¾‘ + + + Contributes icons to account provider. + è²¢ç»åœ–示給帳戶供應者。 + + + + + + + Indicates data property of a data set for a chart. + 指定圖表資料集的資料屬性。 + + + + + + + Minimum value of the y axis + y 軸的最å°å€¼ + + + Maximum value of the y axis + y 軸的最大值 + + + Label for the y axis + Y 軸的標籤 + + + Minimum value of the x axis + X 軸的最å°å€¼ + + + Maximum value of the x axis + X 軸的最大值 + + + Label for the x axis + X 軸標籤 + + + + + + + Displays the results in a simple table + 在簡單表格中顯示çµæžœ + + + + + + + Displays an image, for example one returned by an R query using ggplot2 + 顯示圖åƒï¼Œ 如使用 ggplot2 R查詢返回的圖åƒã€‚ + + + What format is expected - is this a JPEG, PNG or other format? + é æœŸæ ¼å¼æ˜¯ä»€éº¼ - 是 JPEG, PNG 或其他格å¼? + + + Is this encoded as hex, base64 or some other format? + 此編碼為å六進ä½ã€base64 或是其他格å¼ï¼Ÿ + + + + + + + For each column in a resultset, displays the value in row 0 as a count followed by the column name. Supports '1 Healthy', '3 Unhealthy' for example, where 'Healthy' is the column name and 1 is the value in row 1 cell 1 + 為çµæžœé›†ä¸­çš„æ¯ä¸€å€‹è³‡æ–™è¡Œï¼Œåœ¨è³‡æ–™åˆ— 0 中先顯示計數值,å†é¡¯ç¤ºè³‡æ–™è¡Œå稱。 ä¾‹å¦‚ï¼Œæ”¯æ´ '1 Healthy','3 Unhealthy',其中 'Healthy' 為資料行å稱; 1 為資料列 1 中儲存格 1 的值 + + + + + + + Manage + ç®¡ç† + + + Dashboard + å„€è¡¨æ¿ + + + + + + + The webview that will be displayed in this tab. + 顯示在此索引標籤的 webview。 + + + + + + + The controlhost that will be displayed in this tab. + 會在此索引標籤中顯示的 controlhost。 + + + + + + + The list of widgets that will be displayed in this tab. + 顯示在此索引標籤中的å°å·¥å…·æ¸…單。 + + + The list of widgets is expected inside widgets-container for extension. + 延伸模組的 widgets-container 中應有 widgets 的清單。 + + + + + + + The list of widgets or webviews that will be displayed in this tab. + 顯示在此索引標籤的å°å·¥å…·æˆ– webviews 的清單。 + + + widgets or webviews are expected inside widgets-container for extension. + 延伸模組的 widgets-container 中應有 widgets 或 webviews。 + + + + + + + Unique identifier for this container. + 此容器的唯一識別碼。 + + + The container that will be displayed in the tab. + 顯示於索引標籤的容器。 + + + Contributes a single or multiple dashboard containers for users to add to their dashboard. + æ供一個或多個索引標籤,讓使用者å¯å¢žåŠ è‡³å…¶å„€è¡¨æ¿ä¸­ã€‚ + + + No id in dashboard container specified for extension. + 為延伸模組指定的儀表æ¿å®¹å™¨ä¸­æ²’有任何識別碼。 + + + No container in dashboard container specified for extension. + 為延伸模組指定的儀表æ¿å®¹å™¨ä¸­æ²’有任何容器。 + + + Exactly 1 dashboard container must be defined per space. + 至少須為æ¯å€‹ç©ºé–“定義剛好 1 個儀表æ¿å®¹å™¨ã€‚ + + + Unknown container type defines in dashboard container for extension. + 延伸模組的儀表æ¿å®¹å™¨ä¸­æœ‰ä¸æ˜Žçš„容器類型定義。 + + + + + + + The model-backed view that will be displayed in this tab. + 將在此索引標籤中顯示的 model-backed 檢視。 + + + + + + + Unique identifier for this nav section. Will be passed to the extension for any requests. + 導覽å€çš„唯一識別碼。將傳éžçµ¦ä»»ä½•è¦æ±‚的擴充功能。 + + + (Optional) Icon which is used to represent this nav section in the UI. Either a file path or a themeable configuration + (é¸ç”¨) 用於 UI 中表示導覽å€çš„圖示。任一檔案路徑或主題化的設定。 + + + Icon path when a light theme is used + 使用亮色主題時的圖示路徑 + + + Icon path when a dark theme is used + 使用暗色主題時的圖åƒè·¯å¾‘ + + + Title of the nav section to show the user. + 顯示給使用者的導覽å€æ¨™é¡Œã€‚ + + + The container that will be displayed in this nav section. + 顯示在此導覽å€çš„容器。 + + + The list of dashboard containers that will be displayed in this navigation section. + 在導覽å€é¡¯ç¤ºå„€è¡¨æ¿å®¹å™¨æ¸…單。 + + + property `icon` can be omitted or must be either a string or a literal like `{dark, light}` + 屬性 `icon` å¯ä»¥çœç•¥ï¼Œå¦å‰‡å¿…須為字串或類似 `{dark, light}` 的常值 + + + No title in nav section specified for extension. + 為延伸模組指定的導覽å€æ®µä¸­æ²’有標題。 + + + No container in nav section specified for extension. + 為延伸模組指定的導覽å€æ®µä¸­æ²’有任何容器。 + + + Exactly 1 dashboard container must be defined per space. + 至少須為æ¯å€‹ç©ºé–“定義剛好 1 個儀表æ¿å®¹å™¨ã€‚ + + + NAV_SECTION within NAV_SECTION is an invalid container for extension. + NAV_SECTION 中的 NAV_SECTION å°å»¶ä¼¸æ¨¡çµ„而言是無效的容器。 + + + + + + + Backup + 備份 + + + + + + + Unique identifier for this tab. Will be passed to the extension for any requests. + 此索引標籤的唯一識別碼。將傳éžçµ¦ä»»ä½•è¦æ±‚的擴充功能。 + + + Title of the tab to show the user. + 顯示給使用者的索引標籤的標題。 + + + Description of this tab that will be shown to the user. + 此索引標籤的æ述將顯示給使用者。 + + + Condition which must be true to show this item + 必須為 true 以顯示此項目的æ¢ä»¶ + + + Defines the connection types this tab is compatible with. Defaults to 'MSSQL' if not set + 定義與此索引標籤相容的連線類型。若未設定,則é è¨­å€¼ç‚º 'MSSQL' + + + The container that will be displayed in this tab. + 將在此索引標籤中顯示的容器。 + + + Whether or not this tab should always be shown or only when the user adds it. + 此索引標籤是å¦æ‡‰ç¸½æ˜¯é¡¯ç¤ºæˆ–僅當使用者增加時。 + + + Whether or not this tab should be used as the Home tab for a connection type. + 是å¦æ‡‰å°‡æ­¤ç´¢å¼•æ¨™ç±¤ç”¨ä½œé€£ç·šé¡žåž‹çš„ [首é ] 索引標籤。 + + + Contributes a single or multiple tabs for users to add to their dashboard. + æ供一個或多個索引標籤,讓使用者å¯å¢žåŠ è‡³å…¶å„€è¡¨æ¿ä¸­ã€‚ + + + No title specified for extension. + 未為延伸模組指定標題。 + + + No description specified to show. + 未指定任何è¦é¡¯ç¤ºçš„說明。 + + + No container specified for extension. + 未為延伸模組指定任何容器。 + + + Exactly 1 dashboard container must be defined per space + æ¯å€‹ç©ºé–“必須明確定義一個儀表æ¿å®¹å™¨ + + + + + + + Restore + 還原 + + + Restore + 還原 + + + + + + + Cannot expand as the required connection provider '{0}' was not found + 因為找ä¸åˆ°å¿…è¦çš„連線æ供者 ‘{0}’,所以無法展開 + + + User canceled + å·²å–消使用者 + + + Firewall dialog canceled + 防ç«ç‰†å°è©±æ–¹å¡Šå·²å–消 + + + + + + + 1 or more tasks are in progress. Are you sure you want to quit? + 1 個或更多的工作正在進行中。是å¦ç¢ºå¯¦è¦é€€å‡ºï¼Ÿ + + + Yes + 是 + + + No + å¦ + + + + + + + Connection is required in order to interact with JobManagementService + 需è¦é€£ç·šæ‰èƒ½èˆ‡ JobManagementService 互動 + + + No Handler Registered + 未註冊處ç†å¸¸å¼ + + + + + + + An error occured while loading the file browser. + 載入檔案ç€è¦½å™¨æ™‚發生錯誤。 + + + File browser error + 檔案ç€è¦½å™¨éŒ¯èª¤ + + + + + + + Notebook Editor + 筆記本編輯器 + + + New Notebook + 新增 Notebook + + + New Notebook + 新增 Notebook + + + SQL kernel: stop Notebook execution when error occurs in a cell. + SQL 核心: 當儲存格發生錯誤時,åœæ­¢åŸ·è¡Œç­†è¨˜æœ¬ã€‚ + + + + + + + Script as Create + 建立指令碼 + + + Script as Drop + 編寫指令碼為 Drop + + + Select Top 1000 + é¸å–å‰ 1000 + + + Script as Execute + 作為指令碼執行 + + + Script as Alter + 修改指令碼 + + + Edit Data + 編輯資料 + + + Select Top 1000 + é¸å–å‰ 1000 + + + Script as Create + 建立指令碼 + + + Script as Execute + 作為指令碼執行 + + + Script as Alter + 修改指令碼 + + + Script as Drop + 編寫指令碼為 Drop + + + Refresh + é‡æ–°æ•´ç† + + + + + + + Connection error + 連接錯誤 + + + Connection failed due to Kerberos error. + 因為 Kerberos 錯誤導致連接失敗。 + + + Help configuring Kerberos is available at {0} + 您å¯æ–¼ {0} å–得設定 Kerberos 的說明 + + + If you have previously connected you may need to re-run kinit. + 如果您之å‰æ›¾é€£ç·šï¼Œå‰‡å¯èƒ½éœ€è¦é‡æ–°åŸ·è¡Œ kinit。 + + + + + + + Refresh account was canceled by the user + 使用者已å–消é‡æ–°æ•´ç†å¸³æˆ¶ + + + + + + + Specifies view templates + 指定檢視範本 + + + Specifies session templates + 指定工作階段範本 + + + Profiler Filters + Profiler ç¯©é¸ + + + + + + + Toggle Query History + 切æ›æŸ¥è©¢æ­·å²è¨˜éŒ„ + + + Delete + 刪除 + + + Clear All History + 清除所有歷å²è¨˜éŒ„ + + + Open Query + 開啟查詢 + + + Run Query + 執行查詢 + + + Toggle Query History capture + 切æ›æŸ¥è©¢æ­·å²è¨˜éŒ„æ“·å– + + + Pause Query History Capture + æš«åœæŸ¥è©¢æ­·å²è¨˜éŒ„æ“·å– + + + Start Query History Capture + 開始查詢歷å²è¨˜éŒ„æ“·å– + + + + + + + Preview features are required in order for extensions to be fully supported and for some actions to be available. Would you like to enable preview features? + 需è¦é è¦½åŠŸèƒ½æ‰èƒ½å®Œæ•´æ”¯æ´å»¶ä¼¸æ¨¡çµ„和使用部分動作。è¦å•Ÿç”¨é è¦½åŠŸèƒ½å—Ž? + + + Yes + 是 + + + No + å¦ + + + No, don't show again + ä¸ï¼Œä¸è¦å†é¡¯ç¤º + + + + + + + Commit row failed: + æ交行失敗: + + + Started executing query "{0}" + 已開始執行查詢 "{0}" + + + Update cell failed: + 更新儲存格失敗: + + + + + + + Failed to create Object Explorer session + 建立物件總管工作階段失敗 + + + Multiple errors: + 多個錯誤: + + + + + + + No URI was passed when creating a notebook manager + 建立筆記本管ç†å“¡æ™‚未傳éžä»»ä½• URI + + + Notebook provider does not exist + 筆記本æ供者ä¸å­˜åœ¨ + + + + + + + Query Results + 查詢çµæžœ + + + Query Editor + 查詢編輯器 + + + New Query + 新增查詢 + + + [Optional] When true, column headers are included when saving results as CSV + [é¸ç”¨] 設定為 true,當å¦å­˜çµæžœç‚º CSV 時包å«è¡Œæ¨™é¡Œ + + + [Optional] The custom delimiter to use between values when saving as CSV + [é¸æ“‡æ€§] å¦å­˜ç‚º CSV 時è¦åœ¨å€¼ä¹‹é–“使用的自訂分隔符號 + + + [Optional] Character(s) used for seperating rows when saving results as CSV + [å¯é¸] å°‡çµæžœå¦å­˜ç‚º CSV 時使用斷行字元 + + + [Optional] Character used for enclosing text fields when saving results as CSV + [é¸æ“‡æ€§] å¦å­˜çµæžœç‚º CSV 時è¦ç”¨æ–¼æ‹¬ä½æ–‡å­—欄ä½çš„å­—å…ƒ + + + [Optional] File encoding used when saving results as CSV + [é¸æ“‡æ€§] å¦å­˜çµæžœç‚º CSV 時è¦ä½¿ç”¨çš„檔案編碼 + + + Enable results streaming; contains few minor visual issues + 啟用çµæžœä¸²æµ; 包å«äº›è¨±è¼•å¾®è¦–覺效果å•é¡Œ + + + [Optional] When true, XML output will be formatted when saving results as XML + [é¸æ“‡æ€§] 若為 true,則會於將çµæžœå„²å­˜å›ž XML 時將輸出格å¼åŒ– + + + [Optional] File encoding used when saving results as XML + [é¸æ“‡æ€§] å°‡çµæžœå„²å­˜ç‚º XML 時使用檔案編碼 + + + [Optional] Configuration options for copying results from the Results View + [é¸ç”¨] 從çµæžœæª¢è¦–內複製çµæžœçš„組態é¸é … + + + [Optional] Configuration options for copying multi-line results from the Results View + [é¸ç”¨] 從çµæžœæª¢è¦–內複製多行çµæžœçš„組態é¸é … + + + [Optional] Should execution time be shown for individual batches + [é¸ç”¨] 是å¦é¡¯ç¤ºå€‹åˆ¥æ‰¹æ¬¡çš„執行時間 + + + [Optional] the default chart type to use when opening Chart Viewer from a Query Results + [é¸ç”¨] 從查詢çµæžœé–‹å•Ÿåœ–表檢視器時使用é è¨­åœ–表類型 + + + Tab coloring will be disabled + 索引標籤著色將被ç¦ç”¨ + + + The top border of each editor tab will be colored to match the relevant server group + 在æ¯å€‹ç·¨è¼¯å™¨ç´¢å¼•æ¨™ç±¤çš„上邊框著上符åˆç›¸é—œçš„伺æœå™¨ç¾¤çµ„çš„é¡è‰² + + + Each editor tab's background color will match the relevant server group + æ¯å€‹ç·¨è¼¯å™¨ç´¢å¼•æ¨™ç±¤çš„背景é¡è‰²å°‡èˆ‡ç›¸é—œçš„伺æœå™¨ç¾¤çµ„相符 + + + Controls how to color tabs based on the server group of their active connection + ä¾æ“šé€£æŽ¥çš„伺æœå™¨ç¾¤çµ„控制索引é ç±¤å¦‚何著色 + + + Controls whether to show the connection info for a tab in the title. + 控制是å¦è¦åœ¨æ¨™é¡Œä¸­é¡¯ç¤ºç´¢å¼•æ¨™ç±¤çš„連線資訊。 + + + Prompt to save generated SQL files + æ示儲存產生的 SQL 檔案 + + + Should IntelliSense be enabled + 是å¦å•Ÿç”¨IntelliSense + + + Should IntelliSense error checking be enabled + 是å¦å•Ÿç”¨ IntelliSense 錯誤檢查 + + + Should IntelliSense suggestions be enabled + 是å¦å•Ÿç”¨ IntelliSense 建議 + + + Should IntelliSense quick info be enabled + 是å¦å•Ÿç”¨ IntelliSense 快速諮詢 + + + Should IntelliSense suggestions be lowercase + 是å¦è¨­å®š IntelliSense 建議為å°å¯« + + + Maximum number of rows to return before the server stops processing your query. + è¦åœ¨ä¼ºæœå™¨åœæ­¢è™•ç†æŸ¥è©¢å‰å‚³å›žçš„資料列數上é™ã€‚ + + + Maximum size of text and ntext data returned from a SELECT statement + SELECT 陳述å¼æ‰€å‚³å›ž text 與 Ntext 資料的大å°ä¸Šé™ + + + An execution time-out of 0 indicates an unlimited wait (no time-out) + 執行逾時為 0 表示無é™ç­‰å€™ (ä¸é€¾æ™‚) + + + Enable SET NOCOUNT option + 啟用 SET NOCOUNT é¸é … + + + Enable SET NOEXEC option + 啟用 SET NOEXEC é¸é … + + + Enable SET PARSEONLY option + 啟用 SET PARSEONLY é¸é … + + + Enable SET ARITHABORT option + 啟用 SET ARITHABORT é¸é … + + + Enable SET STATISTICS TIME option + 啟用 SET STATISTICS TIME é¸é … + + + Enable SET STATISTICS IO option + 啟用 SET STATISTICS IO é¸é … + + + Enable SET XACT_ABORT ON option + 啟用 SET XACT_ABORT ON é¸é … + + + Enable SET TRANSACTION ISOLATION LEVEL option + 啟用 SET TRANSACTION ISOLATION LEVEL é¸é … + + + Enable SET DEADLOCK_PRIORITY option + 啟用 SET DEADLOCK_PRIORITY é¸é … + + + Enable SET LOCK TIMEOUT option (in milliseconds) + 啟用 SET LOCK TIMEOUT é¸é … (毫秒) + + + Enable SET QUERY_GOVERNOR_COST_LIMIT + 啟用 SET QUERY_GOVERNOR_COST_LIMIT + + + Enable SET ANSI_DEFAULTS + 啟用 SET ANSI_DEFAULTS + + + Enable SET QUOTED_IDENTIFIER + 啟用 SET QUOTED_IDENTIFIER + + + Enable SET ANSI_NULL_DFLT_ON + 啟用 SET ANSI_NULL_DFLT_ON + + + Enable SET IMPLICIT_TRANSACTIONS + 啟用 SET IMPLICIT_TRANSACTIONS + + + Enable SET CURSOR_CLOSE_ON_COMMIT + 啟用 SET CURSOR_CLOSE_ON_COMMIT + + + Enable SET ANSI_PADDING + 啟用 SET ANSI_PADDING + + + Enable SET ANSI_WARNINGS + 啟用 SET ANSI_WARNINGS + + + Enable SET ANSI_NULLS + 啟用 SET ANSI_NULLS + + + Set keybinding workbench.action.query.shortcut{0} to run the shortcut text as a procedure call. Any selected text in the query editor will be passed as a parameter + 設置按éµç¶å®š workbench.action.query.shortcut{0} 以執行快速éµæ–‡å­—作為程åºå‘¼å«ã€‚任何é¸æ“‡çš„文字在查詢編輯器中將作為åƒæ•¸å‚³éž + + + + + + + Common id for the provider + æ供者的通用識別碼。 + + + Display Name for the provider + æ供者的顯示å稱 + + + Icon path for the server type + 伺æœå™¨é¡žåž‹çš„圖示路徑 + + + Options for connection + 連線的é¸é … + + + + + + + OK + 確定 + + + Close + 關閉 + + + Copy details + 複製詳細資訊 + + + + + + + Add server group + 新增伺æœå™¨ç¾¤çµ„ + + + Edit server group + 編輯伺æœå™¨ç¾¤çµ„ + + + + + + + Error adding account + 新增帳戶時發生錯誤 + + + Firewall rule error + 防ç«ç‰†è¦å‰‡éŒ¯èª¤ + + + + + + + Some of the loaded extensions are using obsolete APIs, please find the detailed information in the Console tab of Developer Tools window + 載入的延伸模組中,有一些使用淘汰的 API。請åƒé–± [開發人員工具] 視窗的 [主控å°] 索引標籤中的詳細資訊 + + + Don't Show Again + ä¸è¦å†é¡¯ç¤º + + + + + + + Toggle Tasks + 切æ›å·¥ä½œ + + + + + + + Show Connections + 顯示連線 + + + Servers + 伺æœå™¨ + + + + + + + Identifier of the view. Use this to register a data provider through `vscode.window.registerTreeDataProviderForView` API. Also to trigger activating your extension by registering `onView:${id}` event to `activationEvents`. + 檢視的識別碼。請使用此識別碼é€éŽ `vscode.window.registerTreeDataProviderForView` API 登錄資料æ供者。並藉由將 `onView:${id}` 事件登錄至 `activationEvents` 以觸發啟用您的延伸模組。 + + + The human-readable name of the view. Will be shown + 使用人性化顯示å稱. + + + Condition which must be true to show this view + 必須為 true 以顯示此檢視的æ¢ä»¶ + + + Contributes views to the editor + æä¾›æ„見給編輯者 + + + Contributes views to Data Explorer container in the Activity bar + 將檢視æ供到活動列中的資料總管容器 + + + Contributes views to contributed views container + æ供檢視給åƒèˆ‡æª¢è¦–容器 + + + View container '{0}' does not exist and all views registered to it will be added to 'Data Explorer'. + 檢視容器 '{0}' 並ä¸å­˜åœ¨ï¼Œä¸”所有å‘其註冊的檢視都將新增至「資料總管ã€ã€‚ + + + Cannot register multiple views with same id `{0}` in the view container `{1}` + 無法在檢視容器 '{1}' 中以åŒä¸€å€‹è­˜åˆ¥ç¢¼ '{0}' 註冊多個檢視 + + + A view with id `{0}` is already registered in the view container `{1}` + 已在檢視容器 '{1}' 中註冊識別碼為 '{0}' 的檢視 + + + views must be an array + 項目必須為陣列 + + + property `{0}` is mandatory and must be of type `string` + 屬性 '{0}' 為強制項目且必須屬於 `string` é¡žåž‹ + + + property `{0}` can be omitted or must be of type `string` + 屬性 `{0}` å¯ä»¥çœç•¥æˆ–必須屬於 `string` é¡žåž‹ + + + + + + + Connection Status + 連線狀態 + + + + + + + Manage + ç®¡ç† + + + Show Details + 顯示詳細資訊 + + + Learn How To Configure The Dashboard + äº†è§£å¦‚ä½•è¨­å®šå„€è¡¨æ¿ + + + + + + + Widget used in the dashboards + 儀表æ¿ä¸­ä½¿ç”¨çš„å°å·¥å…· + + + + + + + Displays results of a query as a chart on the dashboard + 將查詢çµæžœä»¥åœ–表方å¼é¡¯ç¤ºåœ¨å„€è¡¨æ¿ä¸Š + + + Maps 'column name' -> color. for example add 'column1': red to ensure this column uses a red color + å°æ‡‰ 'column name' -> 色彩。例如,新增 'column1': red å¯ç¢ºä¿è³‡æ–™è¡Œä½¿ç”¨ç´…色 + + + Indicates preferred position and visibility of the chart legend. These are the column names from your query, and map to the label of each chart entry + 指定圖表圖例的優先ä½ç½®å’Œå¯è¦‹åº¦ã€‚這些是您查詢中的欄ä½å稱, 並å°æ‡‰åˆ°æ¯å€‹åœ–表項目的標籤 + + + If dataDirection is horizontal, setting this to true uses the first columns value for the legend. + è‹¥ dataDirection 是水平的,設置為 true 時則使用第一個欄ä½å€¼ç‚ºå…¶åœ–例。 + + + If dataDirection is vertical, setting this to true will use the columns names for the legend. + è‹¥ dataDirection 是垂直的,設置為 true 時則使用欄ä½å稱為其圖例。 + + + Defines whether the data is read from a column (vertical) or a row (horizontal). For time series this is ignored as direction must be vertical. + 定義是å¦å¾žè¡Œ (åž‚ç›´) 或列 (æ°´å¹³) 讀å–資料。å°æ–¼æ™‚é–“åºåˆ—, 當呈ç¾æ–¹å‘為垂直時會被忽略。 + + + If showTopNData is set, showing only top N data in the chart. + 如已設定 showTopNDataï¼Œå‰‡åƒ…é¡¯ç¤ºåœ–è¡¨ä¸­çš„å‰ N 個資料。 + + + + + + + Condition which must be true to show this item + 必須為 true 以顯示此項目的æ¢ä»¶ + + + The title of the container + 容器的標題 + + + The row of the component in the grid + 格線中元件的列 + + + The rowspan of the component in the grid. Default value is 1. Use '*' to set to number of rows in the grid. + 網格中元件的 rowspan。é è¨­å€¼ç‚º 1。使用 '*' å³å¯è¨­ç‚ºç¶²æ ¼ä¸­çš„列數。 + + + The column of the component in the grid + ç¶²æ ¼ä¸­å…ƒä»¶çš„æ¬„ä½ + + + The colspan of the component in the grid. Default value is 1. Use '*' to set to number of columns in the grid. + 網格內元件的 colspan。é è¨­å€¼ç‚º 1。使用 '*' å³å¯è¨­ç‚ºç¶²æ ¼ä¸­çš„欄ä½æ•¸ã€‚ + + + Unique identifier for this tab. Will be passed to the extension for any requests. + 此索引標籤的唯一識別碼。將傳éžçµ¦ä»»ä½•è¦æ±‚的擴充功能。 + + + Extension tab is unknown or not installed. + 未知的擴充功能索引標籤或未安è£ã€‚ + + + + + + + Enable or disable the properties widget + 啟用或ç¦ç”¨å±¬æ€§å°å·¥å…· + + + Property values to show + 顯示屬性值 + + + Display name of the property + 顯示屬性的å稱 + + + Value in the Database Info Object + 資料庫資訊物件中的值 + + + Specify specific values to ignore + 指定è¦å¿½ç•¥çš„特定值 + + + Recovery Model + å¾©åŽŸæ¨¡å¼ + + + Last Database Backup + 上次資料庫備份 + + + Last Log Backup + 上次日誌備份 + + + Compatibility Level + 相容層級 + + + Owner + æ“有者 + + + Customizes the database dashboard page + 自訂 "資料庫儀表æ¿" é  + + + Customizes the database dashboard tabs + 自訂資料庫儀表æ¿ç´¢å¼•æ¨™ç±¤ + + + + + + + Enable or disable the properties widget + 啟用或ç¦ç”¨å±¬æ€§å°å·¥å…· + + + Property values to show + 顯示屬性值 + + + Display name of the property + 顯示屬性的å稱 + + + Value in the Server Info Object + 伺æœå™¨è³‡è¨Šç‰©ä»¶ä¸­çš„值 + + + Version + 版本 + + + Edition + 版本 + + + Computer Name + 電腦å稱 + + + OS Version + 作業系統版本 + + + Customizes the server dashboard page + 自訂伺æœå™¨å„€è¡¨æ¿é é¢ + + + Customizes the Server dashboard tabs + 自訂伺æœå™¨å„€è¡¨æ¿ç´¢å¼•æ¨™ç±¤ + + + + + + + Manage + ç®¡ç† + + + + + + + Widget used in the dashboards + 儀表æ¿ä¸­ä½¿ç”¨çš„å°å·¥å…· + + + Widget used in the dashboards + 儀表æ¿ä¸­ä½¿ç”¨çš„å°å·¥å…· + + + Widget used in the dashboards + 儀表æ¿ä¸­ä½¿ç”¨çš„å°å·¥å…· + + + + + + + Adds a widget that can query a server or database and display the results in multiple ways - as a chart, summarized count, and more + 加入一個å¯æŸ¥è©¢ä¼ºæœå™¨æˆ–資料庫並以多種方å¼å‘ˆç¾çµæžœçš„å°å·¥å…·ï¼Œå¦‚圖表ã€è¨ˆæ•¸ç¸½çµç­‰ã€‚ + + + Unique Identifier used for caching the results of the insight. + 用於快å–見解çµæžœçš„唯一識別碼。 + + + SQL query to run. This should return exactly 1 resultset. + è¦åŸ·è¡Œçš„ SQL 查詢。這僅會回傳一個çµæžœé›†ã€‚ + + + [Optional] path to a file that contains a query. Use if 'query' is not set + [é¸æ“‡æ€§] 包å«æŸ¥è©¢ä¹‹æª”案的路徑。這會在未設定 'query' 時使用 + + + [Optional] Auto refresh interval in minutes, if not set, there will be no auto refresh + [é¸æ“‡æ€§] 自動é‡æ–°æ•´ç†é–“éš” (分é˜),如未設定,就ä¸æœƒè‡ªå‹•é‡æ–°æ•´ç† + + + Which actions to use + è¦ä½¿ç”¨çš„動作 + + + Target database for the action; can use the format '${ columnName }' to use a data driven column name. + 此動作的目標資料庫; å¯ä½¿ç”¨æ ¼å¼ '${ columnName }',以使用資料驅動的資料行å稱。 + + + Target server for the action; can use the format '${ columnName }' to use a data driven column name. + 此動作的目標伺æœå™¨; å¯ä½¿ç”¨æ ¼å¼ '${ columnName }',以使用資料驅動的資料列å稱。 + + + Target user for the action; can use the format '${ columnName }' to use a data driven column name. + 請指定執行此動作的使用者; å¯ä½¿ç”¨æ ¼å¼ '${ columnName }',以使用資料驅動的資料行å稱。 + + + Identifier of the insight + Insight 的識別碼 + + + Contributes insights to the dashboard palette. + è²¢ç» insights 至儀表æ¿é¸æ“‡å€ã€‚ + + + + + + + Loading + 正在載入 + + + Loading completed + 已完æˆè¼‰å…¥ + + + + + + + Defines a property to show on the dashboard + 定義顯示於儀表æ¿ä¸Šçš„屬性 + + + What value to use as a label for the property + åšç‚ºå±¬æ€§æ¨™ç±¤çš„值 + + + What value in the object to access for the value + è¦å­˜å–何值於該物件中 + + + Specify values to be ignored + 指定è¦å¿½ç•¥çš„值 + + + Default value to show if ignored or no value + 如果忽略或沒有值,則顯示é è¨­å€¼ + + + A flavor for defining dashboard properties + 定義儀表æ¿å±¬æ€§é¡žåˆ¥ + + + Id of the flavor + 類別的 id + + + Condition to use this flavor + 使用此類別的æ¢ä»¶ + + + Field to compare to + è¦æ¯”è¼ƒçš„æ¬„ä½ + + + Which operator to use for comparison + 用於比較的é‹ç®—å­ + + + Value to compare the field to + 用於比較該欄ä½çš„值 + + + Properties to show for database page + 顯示資料庫é çš„屬性 + + + Properties to show for server page + 顯示伺æœå™¨é çš„屬性 + + + Defines that this provider supports the dashboard + 定義此æ供者支æ´å„€è¡¨æ¿ + + + Provider id (ex. MSSQL) + æ供者 id (ex. MSSQL) + + + Property values to show on dashboard + 在儀表æ¿ä¸Šé¡¯ç¤ºçš„屬性值 + + + + + + + Backup + 備份 + + + You must enable preview features in order to use backup + 您必須啟用é è¦½åŠŸèƒ½æ‰èƒ½ä½¿ç”¨å‚™ä»½ + + + Backup command is not supported for Azure SQL databases. + Azure SQL 資料庫ä¸æ”¯æ´å‚™ä»½å‘½ä»¤ã€‚ + + + Backup command is not supported in Server Context. Please select a Database and try again. + 伺æœå™¨å…§å®¹ä¸­ä¸æ”¯æ´å‚™ä»½å‘½ä»¤ã€‚è«‹é¸å–資料庫並å†è©¦ä¸€æ¬¡ã€‚ + + + + + + + Restore + 還原 + + + You must enable preview features in order to use restore + 您必須啟用é è¦½åŠŸèƒ½æ‰èƒ½ä½¿ç”¨é‚„原 + + + Restore command is not supported for Azure SQL databases. + Azure SQL 資料庫ä¸æ”¯æ´é‚„原命令。 + + + + + + + disconnected + 已中斷連線 + + + + + + + Server Groups + 伺æœå™¨ç¾¤çµ„ + + + OK + 確定 + + + Cancel + å–消 + + + Server group name + 伺æœå™¨ç¾¤çµ„å稱 + + + Group name is required. + 需è¦ç¾¤çµ„å稱。 + + + Group description + 群組æè¿° + + + Group color + 群組é¡è‰² + + + + + + + Extension + 延伸模組 + + + + + + + OK + 確定 + + + Cancel + å–消 + + + + + + + Open dashboard extensions + 開啟儀表æ¿å»¶ä¼¸æ¨¡çµ„ + + + OK + 確定 + + + Cancel + å–消 + + + No dashboard extensions are installed at this time. Go to Extension Manager to explore recommended extensions. + ç›®å‰æ²’有安è£ä»»ä½•å„€è¡¨æ¿å»¶ä¼¸æ¨¡çµ„。請å‰å¾€å»¶ä¼¸æ¨¡çµ„管ç†å“¡æŽ¢ç´¢å»ºè­°çš„延伸模組。 + + + + + + + Selected path + é¸æ“‡çš„路徑 + + + Files of type + 檔案類型 + + + OK + 確定 + + + Discard + æ¨æ£„ + + + + + + + No Connection Profile was passed to insights flyout + 沒有連接設定傳éžçµ¦ insights 彈出å¼è¦–窗 + + + Insights error + Insights 錯誤 + + + There was an error reading the query file: + 讀å–查詢檔案時發生錯誤: + + + There was an error parsing the insight config; could not find query array/string or queryfile + è§£æž insight 設定時發生錯誤。找ä¸åˆ°æŸ¥è©¢é™£åˆ—/字串或 queryfile + + + + + + + Clear List + 清除清單 + + + Recent connections list cleared + 最近的連接清單已清除 + + + Yes + 是 + + + No + å¦ + + + Are you sure you want to delete all the connections from the list? + 您確定è¦åˆªé™¤æ¸…單中的所有連接嗎? + + + Yes + 是 + + + No + å¦ + + + Delete + 刪除 + + + Get Current Connection String + å–å¾—ç›®å‰çš„連接字串 + + + Connection string not available + 連接字串無法使用 + + + No active connection available + 沒有å¯ç”¨çš„有效連線 + + + + + + + Refresh + é‡æ–°æ•´ç† + + + Disconnect + 中斷連線 + + + New Connection + 新增連接 + + + New Server Group + 新伺æœå™¨ç¾¤çµ„ + + + Edit Server Group + 編輯伺æœå™¨ç¾¤çµ„ + + + Show Active Connections + 顯示使用中的連接 + + + Show All Connections + 顯示所有連線 + + + Recent Connections + 最近的連接 + + + Delete Connection + 刪除連接 + + + Delete Group + 刪除群組 + + + + + + + Edit Data Session Failed To Connect + 編輯資料工作階段連接失敗 + + + + + + + Profiler + 分æžå™¨ + + + Not connected + 未連線 + + + XEvent Profiler Session stopped unexpectedly on the server {0}. + 伺æœå™¨ {0} 上的 XEvent 分æžå·¥å…·å·¥ä½œéšŽæ®µæ„外åœæ­¢ã€‚ + + + Error while starting new session + 啟動新的工作階段時發生錯誤 + + + The XEvent Profiler session for {0} has lost events. + {0} çš„ XEvent 分æžå·¥å…·å·¥ä½œéšŽæ®µéºå¤±äº‹ä»¶ã€‚ + + + Would you like to stop the running XEvent session? + è¦åœæ­¢æ­£åœ¨åŸ·è¡Œçš„ XEvent 工作階段嗎? + + + Yes + 是 + + + No + å¦ + + + Cancel + å–消 + + + + + + + Invalid value + 值無效 + + + {0}. {1} + {0}. {1} + + + + + + + blank + 空白 + + + + + + + Error displaying Plotly graph: {0} + 顯示 Plotly 圖表時發生錯誤: {0} + + + + + + + No {0} renderer could be found for output. It has the following MIME types: {1} + 找ä¸åˆ°è¼¸å‡ºçš„ {0} 轉譯器。其有下列 MIME é¡žåž‹: {1} + + + (safe) + (安全) + + + + + + + Item + é …ç›® + + + Value + 值 + + + Property + 屬性 + + + Value + 值 + + + Insights + Insights + + + Items + é …ç›® + + + Item Details + 項目詳細資訊 + + + + + + + Error adding account + 新增帳戶時發生錯誤 + + + + + + + Cannot start auto OAuth. An auto OAuth is already in progress. + 無法啟動自動 OAuth。自動 OAuth 已在進行中。 + + + + + + + Sort by event + æŒ‰äº‹ä»¶æŽ’åº + + + Sort by column + ä¾è³‡æ–™è¡ŒæŽ’åº + + + Profiler + 分æžå™¨ + + + OK + 確定 + + + Cancel + å–消 + + + + + + + Clear all + 全部清除 + + + Apply + 套用 + + + OK + 確定 + + + Cancel + å–消 + + + Filters + ç¯©é¸ + + + Remove this clause + 移除此å­å¥ + + + Save Filter + å„²å­˜ç¯©é¸ + + + Load Filter + è¼‰å…¥ç¯©é¸ + + + Add a clause + 新增å­å¥ + + + Field + æ¬„ä½ + + + Operator + é‹ç®—å­ + + + Value + 值 + + + Is Null + 為 Null + + + Is Not Null + éž Null + + + Contains + åŒ…å« + + + Not Contains + ä¸åŒ…å« + + + Starts With + 開始於 + + + Not Starts With + ä¸ä»¥ä¸‹åˆ—項目開始 + + + + + + + Double-click to edit + 按兩下å³å¯ç·¨è¼¯ + + + + + + + Select Top 1000 + é¸å–å‰ 1000 + + + Script as Execute + 作為指令碼執行 + + + Script as Alter + 修改指令碼 + + + Edit Data + 編輯資料 + + + Script as Create + 建立指令碼 + + + Script as Drop + 編寫指令碼為 Drop + + + + + + + No queries to display. + 沒有查詢å¯é¡¯ç¤ºã€‚ + + + Query History + 查詢歷å²è¨˜éŒ„ + QueryHistory + + + + + + + Failed to get Azure account token for connection + 無法å–得連線的 Azure å¸³æˆ¶æ¬Šæ– + + + Connection Not Accepted + é€£æŽ¥æœªè¢«æŽ¥å— + + + Yes + 是 + + + No + å¦ + + + Are you sure you want to cancel this connection? + 您確定è¦å–消此連接嗎? + + + + + + + Started executing query at + 已開始執行查詢在 + + + Line {0} + 第 {0} è¡Œ + + + Canceling the query failed: {0} + å–消查詢失敗︰ {0} + + + Started saving results to + 開始儲存çµæžœ + + + Failed to save results. + 無法儲存çµæžœã€‚ + + + Successfully saved results to + æˆåŠŸå„²å­˜çµæžœè‡³ + + + Executing query... + 執行查詢中... + + + Maximize + 最大化 + + + Restore + 還原 + + + Save as CSV + å¦å­˜ç‚º CSV + + + Save as JSON + å¦å­˜ç‚º JSON + + + Save as Excel + å¦å­˜ç‚º Excel + + + Save as XML + å¦å­˜ç‚º xml + + + View as Chart + 以圖表查看 + + + Visualize + 視覺化 + + + Results + çµæžœ + + + Executing query + 執行查詢 + + + Messages + è¨Šæ¯ + + + Total execution time: {0} + 總執行時間: {0} + + + Save results command cannot be used with multiple selections. + 儲存çµæžœæŒ‡ä»¤ç„¡æ³•ä½¿ç”¨æ–¼å¤šé‡é¸æ“‡ä¸­ + + + + + + + Identifier of the notebook provider. + 筆記本æ供者的識別碼。 + + + What file extensions should be registered to this notebook provider + 應å‘此筆記本æ供者註冊的檔案副檔å + + + What kernels should be standard with this notebook provider + 應為此筆記本æ供者之標準的核心 + + + Contributes notebook providers. + æ供筆記本æ供者。 + + + Name of the cell magic, such as '%%sql'. + 儲存格 Magic çš„å稱,例如 '%%sql'。 + + + The cell language to be used if this cell magic is included in the cell + 儲存格中包å«æ­¤å„²å­˜æ ¼ magic 時,è¦ä½¿ç”¨çš„儲存格語言 + + + Optional execution target this magic indicates, for example Spark vs SQL + 這個 magic 指示的é¸æ“‡æ€§åŸ·è¡Œç›®æ¨™ï¼Œä¾‹å¦‚ Spark vs SQL + + + Optional set of kernels this is valid for, e.g. python3, pyspark, sql + é¸æ“‡æ€§æ ¸å¿ƒé›†é©ç”¨æ–¼åƒæ˜¯ python3ã€pysparkã€sql 等等 + + + Contributes notebook language. + è²¢ç»ç­†è¨˜æœ¬èªžè¨€ã€‚ + + + + + + + SQL + Sql + + + + + + + F5 shortcut key requires a code cell to be selected. Please select a code cell to run. + F5 快速éµéœ€è¦é¸å–程å¼ç¢¼è³‡æ–™æ ¼ã€‚è«‹é¸å–è¦åŸ·è¡Œçš„程å¼ç¢¼è³‡æ–™æ ¼ã€‚ + + + Clear result requires a code cell to be selected. Please select a code cell to run. + 清除çµæžœéœ€è¦é¸å–程å¼ç¢¼è³‡æ–™æ ¼ã€‚è«‹é¸å–è¦åŸ·è¡Œçš„程å¼ç¢¼è³‡æ–™æ ¼ã€‚ + + + + + + + Save As CSV + å¦å­˜ç‚º CSV + + + Save As JSON + å¦å­˜ç‚º JSON + + + Save As Excel + å¦å­˜ç‚º Excel + + + Save As XML + å¦å­˜ç‚º xml + + + Copy + 複製 + + + Copy With Headers + 隨標頭一åŒè¤‡è£½ + + + Select All + å…¨é¸ + + + + + + + Max Rows: + 最大行數: + + + + + + + Select View + é¸å–檢視 + + + Select Session + é¸å–工作階段 + + + Select Session: + é¸å–工作階段: + + + Select View: + é¸å–檢視: + + + Text + 文字 + + + Label + 標籤 + + + Value + 值 + + + Details + 詳細資料 + + + + + + + Copy failed with error {0} + 複製失敗。錯誤: {0} + + + + + + + New Query + 新增查詢 + + + Run + 執行 + + + Cancel + å–消 + + + Explain + 解釋 + + + Actual + 實際 + + + Disconnect + 中斷連線 + + + Change Connection + 變更連接 + + + Connect + 連接 + + + Enable SQLCMD + 啟用 SQLCMD + + + Disable SQLCMD + åœç”¨ SQLCMD + + + Select Database + é¸æ“‡è³‡æ–™åº« + + + Select Database Toggle Dropdown + é¸å–資料庫切æ›ä¸‹æ‹‰å¼æ¸…å–® + + + Failed to change database + 變更資料庫失敗 + + + Failed to change database {0} + 無法變更資料庫 {0} + + + + + + + Connection + 連接 + + + Connection type + 連接類型 + + + Recent Connections + 最近的連接 + + + Saved Connections + 已儲存的連接 + + + Connection Details + 連接詳細資料 + + + Connect + 連接 + + + Cancel + å–消 + + + No recent connection + 沒有最近使用的連接 + + + No saved connection + 沒有儲存的連接 + + + + + + + OK + 確定 + + + Close + 關閉 + + + + + + + Loading kernels... + 正在載入核心... + + + Changing kernel... + 正在變更核心... + + + Kernel: + 核心: + + + Attach To: + 附加至: + + + Loading contexts... + 正在載入內容... + + + Add New Connection + 新增連線 + + + Select Connection + é¸å–連線 + + + localhost + localhost + + + Trusted + å—ä¿¡ä»» + + + Not Trusted + ä¸å—ä¿¡ä»» + + + Notebook is already trusted. + 筆記本已å—信任。 + + + Collapse Cells + 折疊儲存格 + + + Expand Cells + 展開儲存格 + + + No Kernel + 沒有核心 + + + None + ç„¡ + + + New Notebook + 新增 Notebook + + + + + + + Time Elapsed + 已耗用時間 + + + Row Count + 資料列計數 + + + {0} rows + {0} 個資料列 + + + Executing query... + 執行查詢中... + + + Execution Status + 執行狀態 + + + + + + + No task history to display. + 沒有工作歷程記錄å¯é¡¯ç¤ºã€‚ + + + Task history + 工作歷å²ç´€éŒ„ + TaskHistory + + + Task error + 工作錯誤 + + + + + + + Choose SQL Language + é¸æ“‡ SQL 語言 + + + Change SQL language provider + 變更 SQL 語言æ供者 + + + SQL Language Flavor + SQL 語言的特色 + + + Change SQL Engine Provider + 變更 SQL 引擎æ供者 + + + A connection using engine {0} exists. To change please disconnect or change connection + 使用引擎 {0} 的連接已存在。若è¦è®Šæ›´ï¼Œè«‹ä¸­æ–·é€£æŽ¥æˆ–變更連接 + + + No text editor active at this time + 此時無使用中的文字編輯器 + + + Select SQL Language Provider + é¸æ“‡ SQL 語言æ供者 + + + + + + + All files + 所有檔案 + + + + + + + File browser tree + 樹狀çµæ§‹æª”案ç€è¦½å™¨ + FileBrowserTree + + + + + + + From + 從 + + + To + 至 + + + Create new firewall rule + 建立新的防ç«ç‰†è¦å‰‡ + + + OK + 確定 + + + Cancel + å–消 + + + Your client IP address does not have access to the server. Sign in to an Azure account and create a new firewall rule to enable access. + 您的用戶端 IP ä½å€ç„¡æ³•è¨ªå•ä¼ºæœå™¨ã€‚登錄到 Azure 帳戶並創建新的防ç«ç‰†è¦å‰‡ä»¥å•Ÿç”¨å­˜å–權é™ã€‚ + + + Learn more about firewall settings + 學習更多有關防ç«ç‰†è¨­å®š + + + Azure account + Azure 帳戶 + + + Firewall rule + 防ç«ç‰†è¦å‰‡ + + + Add my client IP + 加入我的用戶端 IP + + + Add my subnet IP range + 新增我的å­ç¶²è·¯ IP ç¯„åœ + + + + + + + You need to refresh the credentials for this account. + 您需è¦æ›´æ–°æ­¤å¸³è™Ÿé©—證資訊。 + + + + + + + Could not find query file at any of the following paths : + {0} + 無法在以下任一路徑找到查詢檔案 : + {0} + + + + + + + Add an account + 新增帳戶 + + + Remove account + 移除帳號 + + + Are you sure you want to remove '{0}'? + 您確定è¦ç§»é™¤ '{0}' 嗎? + + + Yes + 是 + + + No + å¦ + + + Failed to remove account + 移除帳戶失敗 + + + Apply Filters + å¥—ç”¨ç¯©é¸ + + + Reenter your credentials + é‡æ–°è¼¸å…¥æ‚¨çš„驗證資訊 + + + There is no account to refresh + 沒有帳戶è¦é‡æ–°æ•´ç† + + + + + + + Focus on Current Query + èšç„¦æ–¼ç›®å‰çš„查詢 + + + Run Query + 執行查詢 + + + Run Current Query + 執行目å‰æŸ¥è©¢ + + + Run Current Query with Actual Plan + 使用實際計畫執行目å‰çš„查詢 + + + Cancel Query + å–消查詢 + + + Refresh IntelliSense Cache + æ›´æ–° IntelliSense å¿«å– + + + Toggle Query Results + 開啟查詢çµæžœ + + + Editor parameter is required for a shortcut to be executed + è¦åŸ·è¡Œçš„æ·å¾‘需è¦ç·¨è¼¯å™¨åƒæ•¸ + + + Parse Query + 剖æžæŸ¥è©¢ + + + Commands completed successfully + å·²æˆåŠŸå®Œæˆå‘½ä»¤ + + + Command failed: + 命令失敗: + + + Please connect to a server + 請連線至伺æœå™¨ + + + + + + + Chart cannot be displayed with the given data + 無法以指定的資料顯示圖表 + + + + + + + The index {0} is invalid. + 索引 {0} 無效。 + + + + + + + no data available + 沒有資料å¯ç”¨ + + + + + + + Information + 資訊 + + + Warning + 警告 + + + Error + 錯誤 + + + Show Details + 顯示詳細資訊 + + + Copy + 複製 + + + Close + 關閉 + + + Back + ä¸Šä¸€é  + + + Hide Details + éš±è—詳細資料 + + + + + + + is required. + 是必è¦çš„。 + + + Invalid input. Numeric value expected. + 輸入無效。é æœŸç‚ºæ•¸å­—。 + + + + + + + Execution failed due to an unexpected error: {0} {1} + 由於æ„外錯誤導致執行失敗: {0} {1} + + + Total execution time: {0} + 總執行時間: {0} + + + Started executing query at Line {0} + 已於第 {0} 行開始執行查詢 + + + Initialize edit data session failed: + åˆå§‹åŒ–編輯資料工作階段失敗: + + + Batch execution time: {0} + 批次執行時間: {0} + + + Copy failed with error {0} + 複製失敗。錯誤: {0} + + + + + + + Error: {0} + 錯誤: {0} + + + Warning: {0} + 警告: {0} + + + Info: {0} + 資訊: {0} + + + + + + + Copy Cell + 複製儲存格 + + + + + + + Backup file path + 備份檔案路徑 + + + Target database + 目標資料庫 + + + Restore database + 還原資料庫 + + + Restore database + 還原資料庫 + + + Database + 資料庫 + + + Backup file + 備份檔案 + + + Restore + 還原 + + + Cancel + å–消 + + + Script + 指令碼 + + + Source + ä¾†æº + + + Restore from + 還原自 + + + Backup file path is required. + 需è¦å‚™ä»½æª”案路徑。 + + + Please enter one or more file paths separated by commas + 請輸入一個或多個用逗號分隔的檔案路徑 + + + Database + 資料庫 + + + Destination + 目的地 + + + Select Database Toggle Dropdown + é¸å–資料庫切æ›ä¸‹æ‹‰å¼æ¸…å–® + + + Restore to + 還原到 + + + Restore plan + 還原計劃 + + + Backup sets to restore + è¦é‚„原的備份組 + + + Restore database files as + 將資料庫檔案還原為 + + + Restore database file details + 還原資料庫檔詳細資訊 + + + Logical file Name + é‚輯檔案å稱 + + + File type + 檔案類型 + + + Original File Name + 原檔案å稱 + + + Restore as + 還原為 + + + Restore options + 還原é¸é … + + + Tail-Log backup + çµå°¾è¨˜éŒ„備份 + + + Server connections + 伺æœå™¨é€£æŽ¥ + + + General + 一般 + + + Files + 檔案 + + + Options + é¸é … + + + + + + + Copy & Open + 複製並開啟 + + + Cancel + å–消 + + + User code + 使用者指令碼 + + + Website + 網站 + + + + + + + Done + å®Œæˆ + + + Cancel + å–消 + + + + + + + Must be an option from the list + 必須是清單中的é¸é … + + + Toggle dropdown + 切æ›ä¸‹æ‹‰æ¸…å–® + + + + + + + Select/Deselect All + é¸æ“‡/å–消全部 + + + checkbox checked + å·²é¸å–æ ¸å–方塊 + + + checkbox unchecked + 未é¸å–æ ¸å–方塊 + + + + + + + modelview code editor for view model. + 檢視模型的 modelview 程å¼ç¢¼ç·¨è¼¯å™¨ã€‚ + + + + + + + succeeded + å·²æˆåŠŸ + + + failed + 失敗 + + + + + + + Server Description (optional) + 伺æœå™¨æè¿° (é¸ç”¨) + + + + + + + Advanced Properties + 進階屬性 + + + Discard + æ¨æ£„ + + + + + + + Linked accounts + 連çµå¸³æˆ¶ + + + Close + 關閉 + + + There is no linked account. Please add an account. + 沒有任何已連çµå¸³æˆ¶ã€‚請新增帳戶。 + + + Add an account + 新增帳戶 + + + + + + + nbformat v{0}.{1} not recognized + 無法識別 nbformat v{0}.{1} + + + This file does not have a valid notebook format + 檔案ä¸å…·æœ‰æ•ˆçš„ç­†è¨˜æœ¬æ ¼å¼ + + + Cell type {0} unknown + 儲存格類型 {0} ä¸æ˜Ž + + + Output type {0} not recognized + 無法識別輸出類型 {0} + + + Data for {0} is expected to be a string or an Array of strings + {0} 的資料應為字串或字串的陣列 + + + Output type {0} not recognized + 無法識別輸出類型 {0} + + + + + + + Profiler editor for event text. Readonly + 事件文字的分æžç·¨è¼¯å™¨ã€‚唯讀 + + + + + + + Run Cells Before + 在之å‰åŸ·è¡Œè³‡æ–™æ ¼ + + + Run Cells After + 在之後執行資料格 + + + Insert Code Before + 在å‰é¢æ’入程å¼ç¢¼ + + + Insert Code After + 在之後æ’入程å¼ç¢¼ + + + Insert Text Before + 在å‰é¢æ’入文字 + + + Insert Text After + 在後é¢æ’入文字 + + + Collapse Cell + 折疊儲存格 + + + Expand Cell + 展開儲存格 + + + Clear Result + 清除çµæžœ + + + Delete + 刪除 + + + + + + + No script was returned when calling select script on object + 在物件上呼å«é¸å–的指令碼時沒有回傳任何指令碼 + + + Select + é¸æ“‡ + + + Create + 建立 + + + Insert + æ’å…¥ + + + Update + æ›´æ–° + + + Delete + 刪除 + + + No script was returned when scripting as {0} on object {1} + 在物件 {1} 指令碼為 {0} 時無回傳任何指令 + + + Scripting Failed + 指令碼失敗 + + + No script was returned when scripting as {0} + 指令碼為 {0} 時無回傳任何指令 + + + + + + + Recent Connections + 最近的連接 + + + Servers + 伺æœå™¨ + + + + + + + No Kernel + 沒有核心 + + + Cannot run cells as no kernel has been configured + 因為未設定任何核心,所以無法執行儲存格 + + + Error + 錯誤 + + + + + + + Azure Data Studio + Azure Data Studio + + + Start + 開始 + + + New connection + 新增連接 + + + New query + 新增查詢 + + + New notebook + 新增 Notebook + + + Open file + 開啟檔案 + + + Open file + 開啟檔案 + + + Deploy + 部署 + + + Deploy SQL Server… + 部署 SQL Server… + + + Recent + 最近使用 + + + More... + 更多... + + + No recent folders + 沒有最近使用的資料夾 + + + Help + 說明 + + + Getting started + 開始使用 + + + Documentation + 文件 + + + Report issue or feature request + 回報å•é¡Œæˆ–功能è¦æ±‚ + + + GitHub repository + GitHub 存放庫 + + + Release notes + 版本資訊 + + + Show welcome page on startup + 啟動時顯示歡迎é é¢ + + + Customize + 自訂 + + + Extensions + 延伸模組 + + + Download extensions that you need, including the SQL Server Admin pack and more + 下載所需延伸模組,包括 SQL Server 系統管ç†å“¡å¥—件等 + + + Keyboard Shortcuts + éµç›¤å¿«é€Ÿéµ(&&K) + + + Find your favorite commands and customize them + 尋找您最愛的命令並予以自訂 + + + Color theme + 色彩佈景主題 + + + Make the editor and your code look the way you love + 將編輯器åŠæ‚¨çš„程å¼ç¢¼è¨­å®šæˆæ‚¨å–œæ„›çš„外觀 + + + Learn + 深入了解 + + + Find and run all commands + 尋找åŠåŸ·è¡Œæ‰€æœ‰å‘½ä»¤ + + + Rapidly access and search commands from the Command Palette ({0}) + 從命令é¸æ“‡å€å¿«é€Ÿå­˜å–åŠæœå°‹å‘½ä»¤ ({0}) + + + Discover what's new in the latest release + 探索最新版本中的新功能 + + + New monthly blog posts each month showcasing our new features + æ¯æœˆå±•ç¤ºæˆ‘們新功能的新æ¯æœˆéƒ¨è½æ ¼æ–‡ç«  + + + Follow us on Twitter + 跟隨我們的 Twitter + + + Keep up to date with how the community is using Azure Data Studio and to talk directly with the engineers. + 掌æ¡ç¤¾ç¾¤ä½¿ç”¨ Azure Data Studio 之方å¼çš„最新消æ¯ï¼Œä¸¦ç›´æŽ¥æ´½è©¢å·¥ç¨‹å¸«ã€‚ + + + + + + + succeeded + å·²æˆåŠŸ + + + failed + 失敗 + + + in progress + 進行中 + + + not started + 未啟動 + + + canceled + å·²å–消 + + + canceling + å–消中 + + + + + + + Run + 執行 + + + Dispose Edit Failed With Error: + 處ç†ç·¨è¼¯å¤±æ•—, 出ç¾éŒ¯èª¤: + + + Stop + åœæ­¢ + + + Show SQL Pane + 顯示 sql 窗格 + + + Close SQL Pane + 關閉 SQL 窗格 + + + + + + + Connect + 連接 + + + Disconnect + 中斷連線 + + + Start + 開始 + + + New Session + 新增工作階段 + + + Pause + æš«åœ + + + Resume + 繼續 + + + Stop + åœæ­¢ + + + Clear Data + 清除資料 + + + Auto Scroll: On + 自動æ²å‹•: é–‹å•Ÿ + + + Auto Scroll: Off + 自動æ²å‹•: 關閉 + + + Toggle Collapsed Panel + 切æ›æŠ˜ç–Šé¢æ¿ + + + Edit Columns + ç·¨è¼¯æ¬„ä½ + + + Find Next String + 尋找下一個字串 + + + Find Previous String + 尋找å‰ä¸€å€‹å­—串 + + + Launch Profiler + 啟動分æžå·¥å…· + + + Filter… + 篩é¸... + + + Clear Filter + æ¸…é™¤ç¯©é¸ + + + + + + + Events (Filtered): {0}/{1} + 事件 (已篩é¸): {0}/{1} + + + Events: {0} + 事件: {0} + + + Event Count + 事件計數 + + + + + + + Save As CSV + å¦å­˜ç‚º CSV + + + Save As JSON + å¦å­˜ç‚º JSON + + + Save As Excel + å¦å­˜ç‚º Excel + + + Save As XML + å¦å­˜ç‚º xml + + + Save to file is not supported by the backing data source + 回溯資料來æºä¸æ”¯æ´å„²å­˜ç‚ºæª”案 + + + Copy + 複製 + + + Copy With Headers + 隨標頭一åŒè¤‡è£½ + + + Select All + å…¨é¸ + + + Copy + 複製 + + + Copy All + 全部複製 + + + Maximize + 最大化 + + + Restore + 還原 + + + Chart + 圖表 + + + Visualizer + 視覺化檢視 + + + + + + + The extension "{0}" is using sqlops module which has been replaced by azdata module, the sqlops module will be removed in a future release. + 延伸模組 "{0}" 正在使用 sqlops 模組,而該模組已由 azdata 模組å–代,並將從後續的版本中移除。 + + + + + + + Table header background color + 資料表標題背景é¡è‰² + + + Table header foreground color + 資料表標頭的å‰æ™¯è‰²å½© + + + Disabled Input box background. + ç¦ç”¨è¼¸å…¥æ¡†èƒŒæ™¯ã€‚ + + + Disabled Input box foreground. + ç¦ç”¨è¼¸å…¥æ¡†å‰æ™¯ã€‚ + + + Button outline color when focused. + èšç„¦æ™‚按鈕輪廓é¡è‰²ã€‚ + + + Disabled checkbox foreground. + å·²åœç”¨æ ¸å–方塊å‰æ™¯ã€‚ + + + List/Table background color for the selected and focus item when the list/table is active + 當清單/表格處於使用狀態時,所é¸é …目與èšç„¦é …目的清單/表格背景色彩 + + + SQL Agent Table background color. + SQL Agent 資料表背景色彩。 + + + SQL Agent table cell background color. + SQL Agent 表格儲存格背景色彩。 + + + SQL Agent table hover background color. + SQL Agent 表格暫留背景色彩。 + + + SQL Agent heading background color. + SQL Agent 標題背景色彩。 + + + SQL Agent table cell border color. + SQL Agent 表格儲存格邊框色彩。 + + + Results messages error color. + çµæžœè¨Šæ¯éŒ¯èª¤è‰²å½©ã€‚ + + + + + + + Choose Results File + é¸æ“‡çµæžœæª”案 + + + CSV (Comma delimited) + CSV (逗點分隔) + + + JSON + JSON + + + Excel Workbook + excel æ´»é ç°¿ + + + XML + XML + + + Plain Text + 純文字 + + + Open file location + 開啟檔案ä½ç½® + + + Open file + 開啟檔案 + + + + + + + Backup name + 備份å稱 + + + Recovery model + å¾©åŽŸæ¨¡å¼ + + + Backup type + 備份類型 + + + Backup files + 備份檔案 + + + Algorithm + 演算法 + + + Certificate or Asymmetric key + 憑證或éžå°ç¨±é‡‘é‘° + + + Media + 媒體 + + + Backup to the existing media set + 備份到ç¾æœ‰çš„媒體集 + + + Backup to a new media set + 備份到新媒體集 + + + Append to the existing backup set + 附加至ç¾æœ‰çš„備份組 + + + Overwrite all existing backup sets + 覆寫所有ç¾æœ‰çš„備份集 + + + New media set name + 新媒體集å稱 + + + New media set description + 新媒體集說明 + + + Perform checksum before writing to media + 在寫入媒體å‰åŸ·è¡Œæª¢æŸ¥ç¢¼ + + + Verify backup when finished + 完æˆå¾Œé©—證備份 + + + Continue on error + 錯誤時ä»ç¹¼çºŒ + + + Expiration + 逾期 + + + Set backup retain days + 設置備份ä¿ç•™å¤©æ•¸ + + + Copy-only backup + 僅複製備份 + + + Advanced Configuration + 進階設置 + + + Compression + 壓縮 + + + Set backup compression + 設定備份壓縮 + + + Encryption + 加密 + + + Transaction log + 交易記錄 + + + Truncate the transaction log + 截斷交易記錄 + + + Backup the tail of the log + 備份最後的日誌內容 + + + Reliability + å¯é æ€§ + + + Media name is required + 需è¦åª’é«”å稱 + + + No certificate or asymmetric key is available + 沒有憑證或éžå°ç¨±é‡‘é‘°å¯ç”¨ + + + Add a file + 增加檔案 + + + Remove files + 移除檔案 + + + Invalid input. Value must be greater than or equal 0. + 輸入無效。值必須大於或等於 0。 + + + Script + 指令碼 + + + Backup + 備份 + + + Cancel + å–消 + + + Only backup to file is supported + 僅支æ´å‚™ä»½åˆ°æª”案 + + + Backup file path is required + 需è¦å‚™ä»½æª”案路徑 + + + + + + + Results + çµæžœ + + + Messages + è¨Šæ¯ + + + + + + + There is no data provider registered that can provide view data. + 沒有任何已註冊的資料æ供者å¯æ供檢視資料。 + + + Collapse All + 全部摺疊 + + + + + + + Home + é¦–é  + + + + + + + The "{0}" section has invalid content. Please contact extension owner. + "{0}" å€æ®µæœ‰ç„¡æ•ˆçš„內容。請連絡延伸模組æ“有者。 + + + + + + + Jobs + 作業 + + + Notebooks + Notebooks + + + Alerts + 警示 + + + Proxies + Proxy + + + Operators + é‹ç®—å­ + + + + + + + Loading + 正在載入 + + + + + + + SERVER DASHBOARD + 伺æœå™¨å„€è¡¨æ¿ + + + + + + + DATABASE DASHBOARD + è³‡æ–™åº«å„€è¡¨æ¿ + + + + + + + Edit + 編輯 + + + Exit + çµæŸ + + + Refresh + é‡æ–°æ•´ç† + + + Toggle More + 切æ›æ›´å¤š + + + Delete Widget + 刪除å°å·¥å…· + + + Click to unpin + 點擊以å–æ¶ˆé‡˜é¸ + + + Click to pin + 按一下以固定 + + + Open installed features + 開啟已安è£çš„功能 + + + Collapse + 摺疊 + + + Expand + 展開 + + + + + + + Steps + 步驟 + + + + + + + StdIn: + Stdin: + + + + + + + Add code + 新增程å¼ç¢¼ + + + Add text + 新增文字 + + + Create File + 建立檔案 + + + Could not display contents: {0} + 無法顯示內容: {0} + + + Please install the SQL Server 2019 extension to run cells. + è«‹å®‰è£ SQL Server 2019 延伸模組以執行資料格。 + + + Install Extension + 安è£å»¶ä¼¸æ¨¡çµ„ + + + Code + 程å¼ç¢¼ + + + Text + 文字 + + + Run Cells + 執行儲存格 + + + Clear Results + 清除çµæžœ + + + < Previous + < 上一步 + + + Next > + 下一步 > + + + cell with URI {0} was not found in this model + 無法在此模型中找到 URI 為 {0} 的儲存格 + + + Run Cells failed - See error in output of the currently selected cell for more information. + 執行資料格失敗 - 如需詳細資訊,請åƒé–±ç›®å‰æ‰€é¸è³‡æ–™æ ¼ä¹‹è¼¸å‡ºä¸­çš„錯誤。 + + + + + + + Click on + 按一下 + + + + Code + + 程å¼ç¢¼ + + + or + 或 + + + + Text + + 文字 + + + to add a code or text cell + 新增程å¼ç¢¼æˆ–文字儲存格 + + + + + + + Database + 資料庫 + + + Files and filegroups + 檔案與檔案群組 + + + Full + Full + + + Differential + 差異 + + + Transaction Log + 交易記錄 + + + Disk + ç£ç¢Ÿ + + + Url + URL + + + Use the default server setting + 使用é è¨­ä¼ºæœå™¨è¨­å®š + + + Compress backup + 壓縮備份 + + + Do not compress backup + ä¸è¦å£“縮備份 + + + Server Certificate + 伺æœå™¨æ†‘è­‰ + + + Asymmetric Key + éžå°ç¨±é‡‘é‘° + + + Backup Files + 備份檔案 + + + All Files + 所有檔案 + + + + + + + No connections found. + 找ä¸åˆ°é€£ç·šã€‚ + + + Add Connection + 加入連接 + + + + + + + Failed to change database + 變更資料庫失敗 + + + + + + + Name + å稱 + + + Email Address + é›»å­éƒµä»¶åœ°å€ + + + Enabled + 啟用 + + + + + + + Name + å稱 + + + Last Occurrence + 上次發生 + + + Enabled + 啟用 + + + Delay Between Responses (in secs) + å›žæ‡‰ä¹‹é–“çš„å»¶é² (秒) + + + Category Name + 類別å稱 + + + + + + + Account Name + 帳戶å稱 + + + Credential Name + èªè­‰å稱 + + + Description + æè¿° + + + Enabled + 啟用 + + + + + + + Unable to load dashboard properties + 無法載入儀表æ¿å±¬æ€§ + + + + + + + Search by name of type (a:, t:, v:, f:, or sp:) + 按類型å稱æœç´¢ (a:, t:, v:, f: 或 sp:) + + + Search databases + æœå°‹è³‡æ–™åº« + + + Unable to load objects + 無法載入物件 + + + Unable to load databases + 無法載入資料庫 + + + + + + + Auto Refresh: OFF + 自動é‡æ–°æ•´ç†: 關閉 + + + Last Updated: {0} {1} + 最近更新: {0} {1} + + + No results to show + 沒有å¯é¡¯ç¤ºçš„çµæžœ + + + + + + + No {0}renderer could be found for output. It has the following MIME types: {1} + 找ä¸åˆ°è¼¸å‡ºçš„ {0} 轉譯器。其有下列 MIME é¡žåž‹: {1} + + + safe + 安全 + + + No component could be found for selector {0} + 找ä¸åˆ°é¸å–器 {0} 的元件 + + + Error rendering component: {0} + 轉譯元件時發生錯誤: {0} + + + + + + + Connected to + 已連接到 + + + Disconnected + 已中斷連線 + + + Unsaved Connections + æœªå„²å­˜çš„é€£çµ + + + + + + + Delete Row + 刪除行 + + + Revert Current Row + 還原目å‰çš„列 + + + + + + + Step ID + 步驟識別碼 + + + Step Name + 步驟å稱 + + + Message + è¨Šæ¯ + + + + + + + XML Showplan + XML 執行程åºè¡¨ + + + Results grid + çµæžœæ–¹æ ¼ + + + + + + + Please select active cell and try again + è«‹é¸å–作用儲存格並å†è©¦ä¸€æ¬¡ + + + Run cell + 執行儲存格 + + + Cancel execution + å–消執行 + + + Error on last run. Click to run again + 上一個執行發生錯誤。按一下å³å¯é‡æ–°åŸ·è¡Œ + + + + + + + Add an account... + 新增帳戶... + + + <Default> + <é è¨­> + + + Loading... + 正在載入... + + + Server group + 伺æœå™¨ç¾¤çµ„ + + + <Default> + <é è¨­> + + + Add new group... + 新增群組... + + + <Do not save> + <ä¸è¦å„²å­˜> + + + {0} is required. + {0} 為必è¦é …。 + + + {0} will be trimmed. + {0} å°‡å—到修剪。 + + + Remember password + 記ä½å¯†ç¢¼ + + + Account + 帳戶 + + + Refresh account credentials + é‡æ–°æ•´ç†å¸³æˆ¶èªè­‰ + + + Azure AD tenant + Azure AD 租用戶 + + + Select Database Toggle Dropdown + é¸å–資料庫切æ›ä¸‹æ‹‰å¼æ¸…å–® + + + Name (optional) + å稱 (é¸æ“‡æ€§) + + + Advanced... + 進階... + + + You must select an account + 您必須é¸å–帳戶 + + + + + + + Cancel + å–消 + + + The task is failed to cancel. + 工作無法å–消。 + + + Script + 指令碼 + + + + + + + Date Created: + 建立日期: + + + Notebook Error: + 筆記本錯誤: + + + Job Error: + 作業錯誤: + + + Pinned + å·²é‡˜é¸ + + + Recent Runs + 最近的執行 + + + Past Runs + éŽåŽ»çš„執行 + + + + + + + No tree view with id '{0}' registered. + 未註冊識別碼為 '{0}' 的樹狀檢視。 + + + + + + + Loading... + 正在載入... + + + + + + + Dashboard Tabs ({0}) + 儀表æ¿ç´¢å¼•æ¨™ç±¤ ({0}) + + + Id + 識別碼 + + + Title + 標題 + + + Description + æè¿° + + + Dashboard Insights ({0}) + 儀表æ¿è¦‹è§£ ({0}) + + + Id + 識別碼 + + + Name + å稱 + + + When + 當 + + + + + + + Chart + 圖表 + + + + + + + Operation + æ“作 + + + Object + 物件 + + + Est Cost + 估計æˆæœ¬ + + + Est Subtree Cost + 估計的å­æ¨¹æˆæœ¬ + + + Actual Rows + 實際行數 + + + Est Rows + 估計行數 + + + Actual Executions + 實際執行 + + + Est CPU Cost + 估計 CPU æˆæœ¬ + + + Est IO Cost + 估計 IO æˆæœ¬ + + + Parallel + 並排 + + + Actual Rebinds + 實際é‡æ–°ç¶å®š + + + Est Rebinds + 估計é‡æ–°ç¹«çµ + + + Actual Rewinds + 實際倒轉 + + + Est Rewinds + 估計倒轉 + + + Partitioned + 已分割 + + + Top Operations + 最å‰å¹¾é …æ“作 + + + + + + + Query Plan + 查詢計劃 + + + + + + + Could not find component for type {0} + 找ä¸åˆ°é¡žåž‹ {0} 的元件 + + + + + + + A NotebookProvider with valid providerId must be passed to this method + 必須將具有有效 providerId çš„ NotebookProvider 傳éžçµ¦æ­¤æ–¹æ³• + + + + + + + A NotebookProvider with valid providerId must be passed to this method + 必須將具有有效 providerId çš„ NotebookProvider 傳éžçµ¦æ­¤æ–¹æ³• + + + no notebook provider found + 找ä¸åˆ°ä»»ä½•ç­†è¨˜æœ¬æ供者 + + + No Manager found + 找ä¸åˆ°ç®¡ç†å“¡ + + + Notebook Manager for notebook {0} does not have a server manager. Cannot perform operations on it + 筆記本 {0} 的筆記本管ç†å“¡æ²’有伺æœå™¨ç®¡ç†å“¡ã€‚無法å°å…¶åŸ·è¡Œä½œæ¥­ + + + Notebook Manager for notebook {0} does not have a content manager. Cannot perform operations on it + 筆記本 {0} 的筆記本管ç†å“¡æ²’有內容管ç†å“¡ã€‚無法å°å…¶åŸ·è¡Œä½œæ¥­ + + + Notebook Manager for notebook {0} does not have a session manager. Cannot perform operations on it + 筆記本 {0} 的筆記本管ç†å“¡æ²’有工作階段管ç†å“¡ã€‚無法å°å…¶åŸ·è¡Œä½œæ¥­ + + + + + + + SQL kernel error + SQL 核心錯誤 + + + A connection must be chosen to run notebook cells + å¿…é ˆé¸æ“‡é€£ç·šä¾†åŸ·è¡Œç­†è¨˜æœ¬å„²å­˜æ ¼ + + + Displaying Top {0} rows. + ç›®å‰é¡¯ç¤ºå‰ {0} 列。 + + + + + + + Show Recommendations + 顯示建議 + + + Install Extensions + 安è£å»¶ä¼¸æ¨¡çµ„ + + + + + + + Name + å稱 + + + Last Run + 上一個執行 + + + Next Run + 下一個執行 + + + Enabled + 啟用 + + + Status + 狀態 + + + Category + 分類 + + + Runnable + å¯åŸ·è¡Œ + + + Schedule + 排程 + + + Last Run Outcome + 上次執行çµæžœ + + + Previous Runs + 上一個執行 + + + No Steps available for this job. + 沒有任何此作業å¯ç”¨çš„步驟。 + + + Error: + 錯誤︰ + + + + + + + Find + 尋找 + + + Find + 尋找 + + + Previous match + 上一個符åˆé …ç›® + + + Next match + 下一個符åˆé …ç›® + + + Close + 關閉 + + + Your search returned a large number of results, only the first 999 matches will be highlighted. + 您的æœå°‹å‚³å›žäº†å¤§é‡çµæžœï¼Œåªæœƒå°‡å‰ 999 個相符項目醒目æ示。 + + + {0} of {1} + {1} çš„ {0} + + + No Results + 查無çµæžœ + + + + + + + Run Query + 執行查詢 + + + + + + + Done + å®Œæˆ + + + Cancel + å–消 + + + Generate script + 產生指令碼 + + + Next + 下一個 + + + Previous + 上一個 + + + + + + + A server group with the same name already exists. + 伺æœå™¨ç¾¤çµ„å稱已經存在。 + + + + + + + {0} is an unknown container. + {0} 是ä¸æ˜Žå®¹å™¨ã€‚ + + + + + + + Loading Error... + 正在載入錯誤... + + + + + + + Failed + 失敗 + + + Succeeded + å·²æˆåŠŸ + + + Retry + é‡è©¦ + + + Cancelled + å·²å–消 + + + In Progress + 進行中 + + + Status Unknown + 狀態ä¸æ˜Ž + + + Executing + 正在執行 + + + Waiting for Thread + 正在等待執行緒 + + + Between Retries + 在é‡è©¦ä¹‹é–“ + + + Idle + é–’ç½® + + + Suspended + æš«åœ + + + [Obsolete] + [已淘汰] + + + Yes + 是 + + + No + å¦ + + + Not Scheduled + 未排程 + + + Never Run + 從未執行 + + + + + + + Name + å稱 + + + Target Database + 目標資料庫 + + + Last Run + 上一個執行 + + + Next Run + 下一個執行 + + + Status + 狀態 + + + Last Run Outcome + 上次執行çµæžœ + + + Previous Runs + 上一個執行 + + + No Steps available for this job. + 沒有任何此作業å¯ç”¨çš„步驟。 + + + Error: + 錯誤︰ + + + Notebook Error: + 筆記本錯誤: + + + + + + + Home + é¦–é  + + + No connection information could be found for this dashboard + 此儀表æ¿ä¸Šæ‰¾ä¸åˆ°é€£æŽ¥è³‡è¨Š + + + + + + + Data + 資料 + + + Connection + 連接 + + + Query + 查詢 + + + Notebook + Notebook + + + SQL + Sql + + + Microsoft SQL Server + Microsoft SQL Server + + + Dashboard + å„€è¡¨æ¿ + + + Profiler + 分æžå™¨ + + + + + + + Close + 關閉 + + + + + + + Success + æˆåŠŸ + + + Error + 錯誤 + + + Refresh + é‡æ–°æ•´ç† + + + New Job + 新工作 + + + Run + 執行 + + + : The job was successfully started. + : å·²æˆåŠŸå•Ÿå‹•ä½œæ¥­ã€‚ + + + Stop + åœæ­¢ + + + : The job was successfully stopped. + : å·²æˆåŠŸåœæ­¢ä½œæ¥­ã€‚ + + + Edit Job + 編輯作業 + + + Open + é–‹å•Ÿ + + + Delete Job + 刪除作業 + + + Are you sure you'd like to delete the job '{0}'? + 確定è¦åˆªé™¤ä½œæ¥­ '{0}' å—Ž? + + + Could not delete job '{0}'. +Error: {1} + 無法刪除作業 '{0}'。 +錯誤: {1} + + + The job was successfully deleted + å·²æˆåŠŸåˆªé™¤ä½œæ¥­ + + + New Step + 新增步驟 + + + Delete Step + 刪除步驟 + + + Are you sure you'd like to delete the step '{0}'? + 確定è¦åˆªé™¤æ­¥é©Ÿ '{0}' å—Ž? + + + Could not delete step '{0}'. +Error: {1} + 無法刪除步驟 '{0}'。 +錯誤: {1} + + + The job step was successfully deleted + å·²æˆåŠŸåˆªé™¤ä½œæ¥­æ­¥é©Ÿ + + + New Alert + 新增警示 + + + Edit Alert + 編輯警示 + + + Delete Alert + 刪除警示 + + + Cancel + å–消 + + + Are you sure you'd like to delete the alert '{0}'? + 確定è¦åˆªé™¤è­¦ç¤º '{0}' å—Ž? + + + Could not delete alert '{0}'. +Error: {1} + 無法刪除警示 '{0}'。錯誤: {1} + + + The alert was successfully deleted + å·²æˆåŠŸåˆªé™¤è­¦ç¤º + + + New Operator + 新增é‹ç®—å­ + + + Edit Operator + 編輯é‹ç®—å­ + + + Delete Operator + 刪除é‹ç®—å­ + + + Are you sure you'd like to delete the operator '{0}'? + 確定è¦åˆªé™¤é‹ç®—å­ "{0}" å—Ž? + + + Could not delete operator '{0}'. +Error: {1} + 無法刪除é‹ç®—å­ '{0}'。 +錯誤:{1} + + + The operator was deleted successfully + å·²æˆåŠŸåˆªé™¤é‹ç®—å­ + + + New Proxy + 新增 Proxy + + + Edit Proxy + 編輯 Proxy + + + Delete Proxy + 刪除 Proxy + + + Are you sure you'd like to delete the proxy '{0}'? + 確定è¦åˆªé™¤ Proxy '{0}' å—Ž? + + + Could not delete proxy '{0}'. +Error: {1} + 無法刪除 Proxy '{0}'。 +錯誤: {1} + + + The proxy was deleted successfully + å·²æˆåŠŸåˆªé™¤ Proxy + + + New Notebook Job + 新增筆記本作業 + + + Edit + 編輯 + + + Open Template Notebook + 開啟範本筆記本 + + + Delete + 刪除 + + + Are you sure you'd like to delete the notebook '{0}'? + 確定è¦åˆªé™¤ç­†è¨˜æœ¬ '{0}' å—Ž? + + + Could not delete notebook '{0}'. +Error: {1} + 無法刪除筆記本 '{0}'。 +錯誤: {1} + + + The notebook was successfully deleted + å·²æˆåŠŸåˆªé™¤ç­†è¨˜æœ¬ + + + Pin + é‡˜é¸ + + + Delete + 刪除 + + + Unpin + å–æ¶ˆé‡˜é¸ + + + Rename + é‡æ–°å‘½å + + + Open Latest Run + é–‹å•Ÿæœ€æ–°çš„å›žåˆ + + + + + + + Please select a connection to run cells for this kernel + è«‹é¸å–è¦ç‚ºæ­¤æ ¸å¿ƒåŸ·è¡Œè³‡æ–™æ ¼çš„連線 + + + Failed to delete cell. + 無法刪除儲存格。 + + + Failed to change kernel. Kernel {0} will be used. Error was: {1} + 無法變更核心。將會使用核心 {0}。錯誤為: {1} + + + Failed to change kernel due to error: {0} + 因為錯誤所以無法變更核心: {0} + + + Changing context failed: {0} + 無法變更內容: {0} + + + Could not start session: {0} + 無法啟動工作階段: {0} + + + A client session error occurred when closing the notebook: {0} + 關閉筆記本時發生用戶端工作階段錯誤: {0} + + + Can't find notebook manager for provider {0} + 找ä¸åˆ°æ供者 {0} 的筆記本管ç†å“¡ + + + + + + + An error occurred while starting the notebook session + 啟動筆記本工作階段時發生錯誤 + + + Server did not start for unknown reason + 伺æœå™¨å› ç‚ºä¸æ˜ŽåŽŸå› è€Œæœªå•Ÿå‹• + + + Kernel {0} was not found. The default kernel will be used instead. + 找ä¸åˆ°æ ¸å¿ƒ {0}。會改用é è¨­æ ¸å¿ƒã€‚ + + + + + + + Unknown component type. Must use ModelBuilder to create objects + 未知元件類型。必須使用 ModelBuilder 建立物件 + + + The index {0} is invalid. + 索引 {0} 無效。 + + + Unkown component configuration, must use ModelBuilder to create a configuration object + 元件設定ä¸æ˜Žï¼Œå¿…須使用 ModelBuilder æ‰èƒ½å»ºç«‹è¨­å®šç‰©ä»¶ + + + + + + + Horizontal Bar + 水平軸 + + + Bar + æ©«æ¢åœ– + + + Line + 折線圖 + + + Pie + 圓形圖 + + + Scatter + 散佈圖 + + + Time Series + 時間åºåˆ— + + + Image + å½±åƒ + + + Count + 計數 + + + Table + 資料表 + + + Doughnut + 環圈圖 + + + + + + + OK + 確定 + + + Clear + 清除 + + + Cancel + å–消 + + + + + + + Cell execution cancelled + å·²å–消資料格執行 + + + Query execution was canceled + å·²å–消執行查詢 + + + The session for this notebook is not yet ready + 這個筆記本的工作階段尚未就緒 + + + The session for this notebook will start momentarily + 這個筆記本的工作階段å³å°‡å•Ÿå‹• + + + No kernel is available for this notebook + 沒有任何å¯ä¾›æ­¤ç­†è¨˜æœ¬ä½¿ç”¨çš„核心 + + + + + + + Select Connection + é¸å–連線 + + + localhost + localhost + + + + + + + Data Direction + è³‡æ–™æ–¹å‘ + + + Vertical + åž‚ç›´ + + + Horizontal + æ°´å¹³ + + + Use column names as labels + 使用欄ä½å稱作為標籤 + + + Use first column as row label + 使用第一欄作為列標籤 + + + Legend Position + 圖例ä½ç½® + + + Y Axis Label + y 軸標籤 + + + Y Axis Minimum Value + y 軸最å°å€¼ + + + Y Axis Maximum Value + y 軸最大值 + + + X Axis Label + X 軸標籤 + + + X Axis Minimum Value + X 軸最å°å€¼ + + + X Axis Maximum Value + X 軸最大值 + + + X Axis Minimum Date + X 軸最å°æ—¥æœŸ + + + X Axis Maximum Date + X 軸最大日期 + + + Data Type + 資料型態 + + + Number + 數字 + + + Point + 點 + + + Chart Type + 圖表類型 + + + Encoding + 編碼 + + + Image Format + 映åƒæ ¼å¼ + + + + + + + Create Insight + 建立 Insight + + + Cannot create insight as the active editor is not a SQL Editor + 啟用的編輯器ä¸æ˜¯ SQL 編輯器,無法建立 insight + + + My-Widget + 我的å°å·¥å…· + + + Copy as image + è¤‡è£½ç‚ºåœ–åƒ + + + Could not find chart to save + 找ä¸åˆ°è¦å„²å­˜çš„圖表 + + + Save as image + å¦å­˜ç‚ºåœ–åƒ + + + PNG + PNG + + + Saved Chart to path: {0} + 已儲存圖表到路徑: {0} + + + + + + + Changing editor types on unsaved files is unsupported + 無法變更尚未儲存之檔案的編輯器類型 + + + + + + + Table does not contain a valid image + 資料表ä¸å«æœ‰æ•ˆçš„æ˜ åƒ + + + + + + + Series {0} + 系列 {0} From c93ea20b757b7310fbe28372968fa0f7c1c01065 Mon Sep 17 00:00:00 2001 From: Anthony Dresser Date: Tue, 3 Dec 2019 10:09:20 -0800 Subject: [PATCH 04/19] fix uri string for converting inputs (#8530) --- src/sql/workbench/contrib/query/browser/query.contribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sql/workbench/contrib/query/browser/query.contribution.ts b/src/sql/workbench/contrib/query/browser/query.contribution.ts index 57db8768b5b9..f1cff45c6ef9 100644 --- a/src/sql/workbench/contrib/query/browser/query.contribution.ts +++ b/src/sql/workbench/contrib/query/browser/query.contribution.ts @@ -60,7 +60,7 @@ Registry.as(EditorInputFactoryExtensions.EditorInpu Registry.as(LanguageAssociationExtensions.LanguageAssociations) .registerLanguageAssociation('sql', (accessor, editor) => { const instantiationService = accessor.get(IInstantiationService); - const queryResultsInput = instantiationService.createInstance(QueryResultsInput, editor.getResource().toString()); + const queryResultsInput = instantiationService.createInstance(QueryResultsInput, editor.getResource().toString(true)); if (editor instanceof FileEditorInput) { return instantiationService.createInstance(FileQueryEditorInput, '', editor, queryResultsInput); } else if (editor instanceof UntitledEditorInput) { From 82c60a23c0f01cfc78b8782cb935f8ae3e5364f6 Mon Sep 17 00:00:00 2001 From: Arvind Ranasaria Date: Tue, 3 Dec 2019 10:18:09 -0800 Subject: [PATCH 05/19] Pull integration test connection string variables from secret store (#8529) * Pull test secrets from Secret store * removing redundant steps: from copy/paste * fix indentation * fix env: sections for test secrets --- .../win32/sql-product-build-win32.yml | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/build/azure-pipelines/win32/sql-product-build-win32.yml b/build/azure-pipelines/win32/sql-product-build-win32.yml index f32c7540bf63..1c002d8d230a 100644 --- a/build/azure-pipelines/win32/sql-product-build-win32.yml +++ b/build/azure-pipelines/win32/sql-product-build-win32.yml @@ -104,6 +104,13 @@ steps: condition: and(succeeded(), eq(variables['RUN_UNSTABLE_TESTS'], 'true')) displayName: Run unstable tests + - task: AzureKeyVault@1 + displayName: 'Azure Key Vault: SqlToolsSecretStore' + inputs: + azureSubscription: 'ClientToolsInfra_670062 (88d5392f-a34f-4769-b405-f597fc533613)' + KeyVaultName: SqlToolsSecretStore + SecretsFilter: 'ads-integration-test-azure-server,ads-integration-test-azure-server-password,ads-integration-test-azure-server-username,ads-integration-test-bdc-server,ads-integration-test-bdc-server-password,ads-integration-test-bdc-server-username,ads-integration-test-standalone-server,ads-integration-test-standalone-server-password,ads-integration-test-standalone-server-username' + - powershell: | . build/azure-pipelines/win32/exec.ps1 $ErrorActionPreference = "Stop" @@ -111,6 +118,16 @@ steps: continueOnError: true condition: and(succeeded(), eq(variables['RUN_TESTS'], 'true')) displayName: Run stable tests + env: + BDC_BACKEND_USERNAME: $(ads-integration-test-bdc-server-username) + BDC_BACKEND_PWD: $(ads-integration-test-bdc-server-password) + BDC_BACKEND_HOSTNAME: $(ads-integration-test-bdc-server) + STANDALONE_SQL_USERNAME: $(ads-integration-test-standalone-server-username) + STANDALONE_SQL_PWD: $(ads-integration-test-standalone-server-password) + STANDALONE_SQL: $(ads-integration-test-standalone-server) + AZURE_SQL_USERNAME: $(ads-integration-test-azure-server-username) + AZURE_SQL_PWD: $(ads-integration-test-azure-server-password) + AZURE_SQL: $(ads-integration-test-azure-server) - powershell: | . build/azure-pipelines/win32/exec.ps1 @@ -122,6 +139,15 @@ steps: env: ADS_TEST_GREP: (.*@REL@|integration test setup) ADS_TEST_INVERT_GREP: 0 + BDC_BACKEND_USERNAME: $(ads-integration-test-bdc-server-username) + BDC_BACKEND_PWD: $(ads-integration-test-bdc-server-password) + BDC_BACKEND_HOSTNAME: $(ads-integration-test-bdc-server) + STANDALONE_SQL_USERNAME: $(ads-integration-test-standalone-server-username) + STANDALONE_SQL_PWD: $(ads-integration-test-standalone-server-password) + STANDALONE_SQL: $(ads-integration-test-standalone-server) + AZURE_SQL_USERNAME: $(ads-integration-test-azure-server-username) + AZURE_SQL_PWD: $(ads-integration-test-azure-server-password) + AZURE_SQL: $(ads-integration-test-azure-server) - powershell: | . build/azure-pipelines/win32/exec.ps1 @@ -130,6 +156,16 @@ steps: continueOnError: true condition: and(succeeded(), eq(variables['RUN_UNSTABLE_TESTS'], 'true')) displayName: Run unstable integration tests + env: + BDC_BACKEND_USERNAME: $(ads-integration-test-bdc-server-username) + BDC_BACKEND_PWD: $(ads-integration-test-bdc-server-password) + BDC_BACKEND_HOSTNAME: $(ads-integration-test-bdc-server) + STANDALONE_SQL_USERNAME: $(ads-integration-test-standalone-server-username) + STANDALONE_SQL_PWD: $(ads-integration-test-standalone-server-password) + STANDALONE_SQL: $(ads-integration-test-standalone-server) + AZURE_SQL_USERNAME: $(ads-integration-test-azure-server-username) + AZURE_SQL_PWD: $(ads-integration-test-azure-server-password) + AZURE_SQL: $(ads-integration-test-azure-server) - task: SFP.build-tasks.custom-build-task-1.EsrpCodeSigning@1 displayName: 'Sign out code' From b38b53b6583407c7e206518e395315594631f733 Mon Sep 17 00:00:00 2001 From: Alan Ren Date: Tue, 3 Dec 2019 13:26:00 -0800 Subject: [PATCH 06/19] accessible radio card (#8514) * accessible radio card group * set radio card group width * address comments * address comments 2 * fix the profile card not being focused issue --- .../pages/deploymentProfilePage.ts | 202 ++++++++---------- .../src/ui/resourceTypePickerDialog.ts | 71 +++--- src/sql/azdata.proposed.d.ts | 36 ++++ .../workbench/api/common/extHostModelView.ts | 67 ++++++ .../workbench/api/common/sqlExtHostTypes.ts | 3 +- .../modelComponents/componentWithIconBase.ts | 47 +--- .../components.contribution.ts | 5 + .../browser/modelComponents/iconUtils.ts | 54 +++++ .../browser/modelComponents/media/card.css | 30 ++- .../radioCardGroup.component.html | 31 +++ .../radioCardGroup.component.ts | 176 +++++++++++++++ 11 files changed, 517 insertions(+), 205 deletions(-) create mode 100644 src/sql/workbench/browser/modelComponents/iconUtils.ts create mode 100644 src/sql/workbench/browser/modelComponents/radioCardGroup.component.html create mode 100644 src/sql/workbench/browser/modelComponents/radioCardGroup.component.ts diff --git a/extensions/resource-deployment/src/ui/deployClusterWizard/pages/deploymentProfilePage.ts b/extensions/resource-deployment/src/ui/deployClusterWizard/pages/deploymentProfilePage.ts index 57aa46ea072d..9540b8946dad 100644 --- a/extensions/resource-deployment/src/ui/deployClusterWizard/pages/deploymentProfilePage.ts +++ b/extensions/resource-deployment/src/ui/deployClusterWizard/pages/deploymentProfilePage.ts @@ -5,38 +5,49 @@ import * as azdata from 'azdata'; import * as nls from 'vscode-nls'; -import { DeployClusterWizard } from '../deployClusterWizard'; -import { WizardPageBase } from '../../wizardPageBase'; -import * as VariableNames from '../constants'; -import { createFlexContainer } from '../../modelViewUtils'; import { BdcDeploymentType } from '../../../interfaces'; import { BigDataClusterDeploymentProfile } from '../../../services/bigDataClusterDeploymentProfile'; +import { createFlexContainer } from '../../modelViewUtils'; +import { WizardPageBase } from '../../wizardPageBase'; +import * as VariableNames from '../constants'; +import { DeployClusterWizard } from '../deployClusterWizard'; const localize = nls.loadMessageBundle(); export class DeploymentProfilePage extends WizardPageBase { - private _cards: azdata.CardComponent[] = []; - private _cardContainer: azdata.FlexContainer | undefined; + private _profiles: BigDataClusterDeploymentProfile[] = []; + private _cardContainer: azdata.RadioCardGroupComponent | undefined; private _loadingComponent: azdata.LoadingComponent | undefined; - private _view: azdata.ModelView | undefined; constructor(wizard: DeployClusterWizard) { - super(localize('deployCluster.summaryPageTitle', "Deployment configuration template"), - localize('deployCluster.summaryPageDescription', "Select the target configuration template"), wizard); + super(localize('deployCluster.summaryPageTitle', "Deployment configuration profile"), + localize('deployCluster.summaryPageDescription', "Select the target configuration profile"), wizard); } public initialize(): void { - this.pageObject.registerContent((view: azdata.ModelView) => { - this._view = view; - this._cardContainer = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'row', flexWrap: 'wrap' }).component(); + this.pageObject.registerContent(async (view: azdata.ModelView): Promise => { + this._cardContainer = view.modelBuilder.radioCardGroup().withProperties({ + cards: [], + cardWidth: '240px', + cardHeight: '340px', + ariaLabel: localize('deploymentDialog.deploymentOptions', "Deployment options"), + width: '1000px' + }).component(); + this.wizard.registerDisposable(this._cardContainer.onSelectionChanged((profileName) => { + const selectedProfile = this._profiles.find(p => profileName === p.profileName); + this.wizard.wizardObject.message = { text: '' }; + if (selectedProfile) { + this.setModelValuesByProfile(selectedProfile); + } + })); const hintText = view.modelBuilder.text().withProperties({ value: localize('deployCluster.ProfileHintText', "Note: The settings of the deployment profile can be customized in later steps.") }).component(); const container = createFlexContainer(view, [this._cardContainer, hintText], false); this._loadingComponent = view.modelBuilder.loadingComponent().withItem(container).withProperties({ loading: true, - loadingText: localize('deployCluster.loadingProfiles', "Loading deployment profiles"), - loadingCompletedText: localize('deployCluster.loadingProfilesCompleted', "Loading deployment profiles completed"), + loadingText: localize('deployCluster.loadingProfiles', "Loading profiles"), + loadingCompletedText: localize('deployCluster.loadingProfilesCompleted', "Loading profiles completed"), showText: true }).component(); let formBuilder = view.modelBuilder.formContainer().withFormItems( @@ -51,99 +62,74 @@ export class DeploymentProfilePage extends WizardPageBase { } ).withLayout({ width: '100%', height: '100%' }); const form = formBuilder.withLayout({ width: '100%' }).component(); - this.loadCards().then(() => { - this._loadingComponent!.loading = false; - }, (error) => { - this.wizard.wizardObject.message = { - level: azdata.window.MessageLevel.Error, - text: localize('deployCluster.loadProfileFailed', "Failed to load the deployment profiles: {0}", error.message) - }; - this._loadingComponent!.loading = false; - }); - return view.initializeModel(form); + await view.initializeModel(form); + await this.loadCards(); }); } - private createProfileCard(profile: BigDataClusterDeploymentProfile, view: azdata.ModelView): azdata.CardComponent { - const descriptions: azdata.CardDescriptionItem[] = [{ - label: localize('deployCluster.serviceLabel', "Service"), - value: localize('deployCluster.instancesLabel', "Instances"), - fontWeight: 'bold' - }, { - label: localize('deployCluster.masterPoolLabel', "SQL Server Master"), - value: profile.sqlServerReplicas.toString() - }, { - label: localize('deployCluster.computePoolLable', "Compute"), - value: profile.computeReplicas.toString() - }, { - label: localize('deployCluster.dataPoolLabel', "Data"), - value: profile.dataReplicas.toString() - }, { - label: localize('deployCluster.hdfsLabel', "HDFS + Spark"), - value: profile.hdfsReplicas.toString() - }, { - label: '' // line separator - }, { - label: localize('deployCluster.storageSize', "Storage size"), - value: localize('deployCluster.gbPerInstance', "GB per Instance"), - fontWeight: 'bold' - }, { - label: localize('deployCluster.defaultDataStorage', "Data storage"), - value: profile.controllerDataStorageSize.toString() - }, { - label: localize('deployCluster.defaultLogStorage', "Log storage"), - value: profile.controllerLogsStorageSize.toString() - }, { - label: '' // line separator - }, { - label: localize('deployCluster.features', "Features"), - value: '', - fontWeight: 'bold' - }, { - label: localize('deployCluster.basicAuthentication', "Basic authentication"), - value: '' - }]; + private createProfileCard(profile: BigDataClusterDeploymentProfile): azdata.RadioCard { + const scaleDescription: azdata.RadioCardDescription = { + ariaLabel: localize('deployCluster.scaleDescription', "Scale description"), + labelHeader: localize('deployCluster.serviceLabel', "Service"), + valueHeader: localize('deployCluster.instancesLabel', "Instances"), + contents: [ + { + label: localize('deployCluster.masterPoolLabel', "SQL Server Master"), + value: profile.sqlServerReplicas.toString() + }, + { + label: localize('deployCluster.computePoolLable', "Compute"), + value: profile.computeReplicas.toString() + }, + { + label: localize('deployCluster.dataPoolLabel', "Data"), + value: profile.dataReplicas.toString() + }, { + label: localize('deployCluster.hdfsLabel', "HDFS + Spark"), + value: profile.hdfsReplicas.toString() + }] + }; + const storageDescription: azdata.RadioCardDescription = { + ariaLabel: localize('deployCluster.storageDescription', "Storage description"), + labelHeader: localize('deployCluster.storageSize', "Storage size"), + valueHeader: localize('deployCluster.gbPerInstance', "GB per Instance"), + contents: [ + { + label: localize('deployCluster.defaultDataStorage', "Data storage"), + value: profile.controllerDataStorageSize.toString() + }, { + label: localize('deployCluster.defaultLogStorage', "Log storage"), + value: profile.controllerLogsStorageSize.toString() + } + ] + }; + + const featureDescription: azdata.RadioCardDescription = { + ariaLabel: localize('deployCluster.featureDescription', "Feature description"), + labelHeader: localize('deployCluster.features', "Features"), + contents: [ + { + label: localize('deployCluster.basicAuthentication', "Basic authentication") + } + ] + }; if (profile.activeDirectorySupported) { - descriptions.push({ - label: localize('deployCluster.activeDirectoryAuthentication', "Active Directory authentication"), - value: '' + featureDescription.contents.push({ + label: localize('deployCluster.activeDirectoryAuthentication', "Active Directory authentication") }); } if (profile.sqlServerReplicas > 1) { - descriptions.push({ - label: localize('deployCluster.hadr', "High Availability"), - value: '' + featureDescription.contents.push({ + label: localize('deployCluster.hadr', "High Availability") }); } - const card = view.modelBuilder.card().withProperties({ - cardType: azdata.CardType.VerticalButton, + return { + id: profile.profileName, label: profile.profileName, - descriptions: descriptions, - width: '240px', - height: '320px', - }).component(); - this._cards.push(card); - this.wizard.registerDisposable(card.onCardSelectedChanged(() => { - if (card.selected) { - this.wizard.wizardObject.message = { text: '' }; - this.setModelValuesByProfile(profile); - // clear the selected state of the previously selected card - this._cards.forEach(c => { - if (c !== card) { - c.selected = false; - } - }); - } else { - // keep the selected state if no other card is selected - if (this._cards.filter(c => { return c !== card && c.selected; }).length === 0) { - card.selected = true; - } - } - })); - - return card; + descriptions: [scaleDescription, storageDescription, featureDescription] + }; } private setModelValuesByProfile(selectedProfile: BigDataClusterDeploymentProfile): void { @@ -174,20 +160,20 @@ export class DeploymentProfilePage extends WizardPageBase { this.wizard.model.selectedProfile = selectedProfile; } - private loadCards(): Promise { - return this.wizard.azdataService.getDeploymentProfiles(this.wizard.deploymentType).then((profiles: BigDataClusterDeploymentProfile[]) => { + private async loadCards(): Promise { + try { + this._profiles = await this.wizard.azdataService.getDeploymentProfiles(this.wizard.deploymentType); const defaultProfile: string = this.getDefaultProfile(); - - profiles.forEach(profile => { - const card = this.createProfileCard(profile, this._view!); - if (profile.profileName === defaultProfile) { - card.selected = true; - card.focus(); - this.setModelValuesByProfile(profile); - } - this._cardContainer!.addItem(card, { flex: '0 0 auto' }); - }); - }); + this._cardContainer!.cards = this._profiles.map(profile => this.createProfileCard(profile)); + this._loadingComponent!.loading = false; + this._cardContainer!.selectedCardId = defaultProfile; + } catch (error) { + this.wizard.wizardObject.message = { + level: azdata.window.MessageLevel.Error, + text: localize('deployCluster.loadProfileFailed', "Failed to load the deployment profiles: {0}", error.message) + }; + this._loadingComponent!.loading = false; + } } public onEnter() { diff --git a/extensions/resource-deployment/src/ui/resourceTypePickerDialog.ts b/extensions/resource-deployment/src/ui/resourceTypePickerDialog.ts index fd9256d6c9cf..5eaeee8c04da 100644 --- a/extensions/resource-deployment/src/ui/resourceTypePickerDialog.ts +++ b/extensions/resource-deployment/src/ui/resourceTypePickerDialog.ts @@ -17,19 +17,17 @@ const localize = nls.loadMessageBundle(); export class ResourceTypePickerDialog extends DialogBase { private toolRefreshTimestamp: number = 0; private _selectedResourceType: ResourceType; - private _resourceTypeCards: azdata.CardComponent[] = []; private _view!: azdata.ModelView; private _resourceDescriptionLabel!: azdata.TextComponent; private _optionsContainer!: azdata.FlexContainer; private _toolsTable!: azdata.TableComponent; - private _cardResourceTypeMap: Map = new Map(); + private _cardGroup!: azdata.RadioCardGroupComponent; private _optionDropDownMap: Map = new Map(); private _toolsLoadingComponent!: azdata.LoadingComponent; private _agreementContainer!: azdata.DivContainer; private _agreementCheckboxChecked: boolean = false; private _installToolButton: azdata.window.Button; private _tools: ITool[] = []; - private _cardsContainer!: azdata.FlexContainer; constructor( private toolsService: IToolsService, @@ -61,10 +59,30 @@ export class ResourceTypePickerDialog extends DialogBase { tab.registerContent((view: azdata.ModelView) => { const tableWidth = 1126; this._view = view; - this.resourceTypeService.getResourceTypes().sort((a: ResourceType, b: ResourceType) => { + const resourceTypes = this.resourceTypeService.getResourceTypes().sort((a: ResourceType, b: ResourceType) => { return (a.displayIndex || Number.MAX_VALUE) - (b.displayIndex || Number.MAX_VALUE); - }).forEach(resourceType => this.addCard(resourceType)); - this._cardsContainer = view.modelBuilder.flexContainer().withItems(this._resourceTypeCards, { flex: '0 0 auto', CSSStyles: { 'margin-bottom': '10px' } }).withLayout({ flexFlow: 'row' }).component(); + }); + this._cardGroup = view.modelBuilder.radioCardGroup().withProperties({ + cards: resourceTypes.map((resourceType) => { + return { + id: resourceType.name, + label: resourceType.displayName, + icon: resourceType.icon + }; + }), + iconHeight: '50px', + iconWidth: '50px', + cardWidth: '220px', + cardHeight: '180px', + ariaLabel: localize('deploymentDialog.deploymentOptions', "Deployment options"), + width: '1100px' + }).component(); + this._toDispose.push(this._cardGroup.onSelectionChanged((cardId: string) => { + const resourceType = resourceTypes.find(rt => { return rt.name === cardId; }); + if (resourceType) { + this.selectResourceType(resourceType); + } + })); this._resourceDescriptionLabel = view.modelBuilder.text().withProperties({ value: this._selectedResourceType ? this._selectedResourceType.description : undefined }).component(); this._optionsContainer = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component(); this._agreementContainer = view.modelBuilder.divContainer().component(); @@ -106,7 +124,7 @@ export class ResourceTypePickerDialog extends DialogBase { const formBuilder = view.modelBuilder.formContainer().withFormItems( [ { - component: this._cardsContainer, + component: this._cardGroup, title: '' }, { component: this._resourceDescriptionLabel, @@ -132,50 +150,15 @@ export class ResourceTypePickerDialog extends DialogBase { return view.initializeModel(form).then(() => { if (this._selectedResourceType) { - this.selectResourceType(this._selectedResourceType); + this._cardGroup.selectedCardId = this._selectedResourceType.name; } }); }); this._dialogObject.content = [tab]; } - private addCard(resourceType: ResourceType): void { - const card = this._view.modelBuilder.card().withProperties({ - cardType: azdata.CardType.VerticalButton, - iconPath: { - dark: resourceType.icon.dark, - light: resourceType.icon.light - }, - label: resourceType.displayName, - selected: (this._selectedResourceType && this._selectedResourceType.name === resourceType.name), - width: '220px', - height: '180px', - iconWidth: '50px', - iconHeight: '50px' - }).component(); - this._resourceTypeCards.push(card); - this._cardResourceTypeMap.set(resourceType.name, card); - this._toDispose.push(card.onCardSelectedChanged(() => this.selectResourceType(resourceType))); - } - private selectResourceType(resourceType: ResourceType): void { this._selectedResourceType = resourceType; - const card = this._cardResourceTypeMap.get(this._selectedResourceType.name)!; - if (card.selected) { - card.focus(); - // clear the selected state of the previously selected card - this._resourceTypeCards.forEach(c => { - if (c !== card) { - c.selected = false; - } - }); - } else { - // keep the selected state if no other card is selected - if (this._resourceTypeCards.filter(c => { return c !== card && c.selected; }).length === 0) { - card.selected = true; - } - } - this._resourceDescriptionLabel.value = resourceType.description; this._agreementCheckboxChecked = false; this._agreementContainer.clearItems(); @@ -346,7 +329,7 @@ export class ResourceTypePickerDialog extends DialogBase { } private enableUiControlsWhenNotInstalling(enabled: boolean): void { - this._cardsContainer.enabled = enabled; + this._cardGroup.enabled = enabled; this._agreementContainer.enabled = enabled; this._optionsContainer.enabled = enabled; this._dialogObject.cancelButton.enabled = enabled; diff --git a/src/sql/azdata.proposed.d.ts b/src/sql/azdata.proposed.d.ts index 84a2a5723d8f..a17b54fcccfd 100644 --- a/src/sql/azdata.proposed.d.ts +++ b/src/sql/azdata.proposed.d.ts @@ -124,6 +124,42 @@ declare module 'azdata' { OssRdbms = 2 } + export interface ModelBuilder { + radioCardGroup(): ComponentBuilder; + } + + export interface RadioCard { + id: string; + label: string; + descriptions?: RadioCardDescription[]; + icon?: string | vscode.Uri | { light: string | vscode.Uri; dark: string | vscode.Uri }; + } + + export interface RadioCardDescription { + ariaLabel: string; + labelHeader: string; + contents: RadioCardLabelValuePair[]; + valueHeader?: string; + } + + export interface RadioCardLabelValuePair { + label: string; + value?: string; + } + + export interface RadioCardGroupComponentProperties extends ComponentProperties, TitledComponentProperties { + cards: RadioCard[]; + cardWidth: string; + cardHeight: string; + iconWidth?: string; + iconHeight?: string; + selectedCardId?: string; + } + + export interface RadioCardGroupComponent extends Component, RadioCardGroupComponentProperties { + onSelectionChanged: vscode.Event; + } + export interface DeclarativeTableProperties extends ComponentProperties { } } diff --git a/src/sql/workbench/api/common/extHostModelView.ts b/src/sql/workbench/api/common/extHostModelView.ts index 636b2ce6a3a9..d24c25cbeb9f 100644 --- a/src/sql/workbench/api/common/extHostModelView.ts +++ b/src/sql/workbench/api/common/extHostModelView.ts @@ -226,6 +226,13 @@ class ModelBuilderImpl implements azdata.ModelBuilder { return builder; } + radioCardGroup(): azdata.ComponentBuilder { + let id = this.getNextComponentId(); + let builder: ComponentBuilderImpl = this.getComponentBuilder(new RadioCardGroupComponentWrapper(this._proxy, this._handle, id), id); + this._componentBuilders.set(id, builder); + return builder; + } + getComponentBuilder(component: ComponentWrapper, id: string): ComponentBuilderImpl { let componentBuilder: ComponentBuilderImpl = new ComponentBuilderImpl(component); this._componentBuilders.set(id, componentBuilder); @@ -1591,6 +1598,66 @@ class HyperlinkComponentWrapper extends ComponentWrapper implements azdata.Hyper } } +class RadioCardGroupComponentWrapper extends ComponentWrapper implements azdata.RadioCardGroupComponent { + constructor(proxy: MainThreadModelViewShape, handle: number, id: string) { + super(proxy, handle, ModelComponentTypes.RadioCardGroup, id); + this.properties = {}; + this._emitterMap.set(ComponentEventType.onDidChange, new Emitter()); + } + + public get iconWidth(): string | undefined { + return this.properties['iconWidth']; + } + + public set iconWidth(v: string | undefined) { + this.setProperty('iconWidth', v); + } + + public get iconHeight(): string | undefined { + return this.properties['iconHeight']; + } + + public set iconHeight(v: string | undefined) { + this.setProperty('iconHeight', v); + } + + public get cardWidth(): string | undefined { + return this.properties['cardWidth']; + } + + public set cardWidth(v: string | undefined) { + this.setProperty('cardWidth', v); + } + + public get cardHeight(): string | undefined { + return this.properties['cardHeight']; + } + + public set cardHeight(v: string | undefined) { + this.setProperty('cardHeight', v); + } + + public get cards(): azdata.RadioCard[] { + return this.properties['cards']; + } + public set cards(v: azdata.RadioCard[]) { + this.setProperty('cards', v); + } + + public get selectedCardId(): string | undefined { + return this.properties['selectedCardId']; + } + + public set selectedCardId(v: string | undefined) { + this.setProperty('selectedCardId', v); + } + + public get onSelectionChanged(): vscode.Event { + let emitter = this._emitterMap.get(ComponentEventType.onDidChange); + return emitter && emitter.event; + } +} + class GroupContainerComponentWrapper extends ComponentWrapper implements azdata.GroupContainer { constructor(proxy: MainThreadModelViewShape, handle: number, type: ModelComponentTypes, id: string) { super(proxy, handle, type, id); diff --git a/src/sql/workbench/api/common/sqlExtHostTypes.ts b/src/sql/workbench/api/common/sqlExtHostTypes.ts index ebf77e406555..2844c0f9b442 100644 --- a/src/sql/workbench/api/common/sqlExtHostTypes.ts +++ b/src/sql/workbench/api/common/sqlExtHostTypes.ts @@ -167,7 +167,8 @@ export enum ModelComponentTypes { DiffEditor, Dom, Hyperlink, - Image + Image, + RadioCardGroup } export enum ColumnSizingMode { diff --git a/src/sql/workbench/browser/modelComponents/componentWithIconBase.ts b/src/sql/workbench/browser/modelComponents/componentWithIconBase.ts index 4ca478dd80df..309b85ad55c5 100644 --- a/src/sql/workbench/browser/modelComponents/componentWithIconBase.ts +++ b/src/sql/workbench/browser/modelComponents/componentWithIconBase.ts @@ -4,23 +4,17 @@ *--------------------------------------------------------------------------------------------*/ import { ChangeDetectorRef, ElementRef } from '@angular/core'; - -import { IComponentDescriptor } from 'sql/workbench/browser/modelComponents/interfaces'; import * as azdata from 'azdata'; -import { URI } from 'vs/base/common/uri'; -import { IdGenerator } from 'vs/base/common/idGenerator'; -import { createCSSRule, removeCSSRulesContainingSelector, asCSSUrl } from 'vs/base/browser/dom'; import { ComponentBase } from 'sql/workbench/browser/modelComponents/componentBase'; - - -export type IUserFriendlyIcon = string | URI | { light: string | URI; dark: string | URI }; +import { createIconCssClass, IUserFriendlyIcon } from 'sql/workbench/browser/modelComponents/iconUtils'; +import { IComponentDescriptor } from 'sql/workbench/browser/modelComponents/interfaces'; +import { removeCSSRulesContainingSelector } from 'vs/base/browser/dom'; +import { URI } from 'vs/base/common/uri'; export class ItemDescriptor { constructor(public descriptor: IComponentDescriptor, public config: T) { } } -const ids = new IdGenerator('model-view-component-icon-'); - export abstract class ComponentWithIconBase extends ComponentBase { protected _iconClass: string; @@ -40,42 +34,11 @@ export abstract class ComponentWithIconBase extends ComponentBase { protected updateIcon() { if (this.iconPath && this.iconPath !== this._iconPath) { this._iconPath = this.iconPath; - if (!this._iconClass) { - this._iconClass = ids.nextId(); - } - - removeCSSRulesContainingSelector(this._iconClass); - const icon = this.getLightIconUri(this.iconPath); - const iconDark = this.getDarkIconUri(this.iconPath) || icon; - createCSSRule(`.icon.${this._iconClass}`, `background-image: ${asCSSUrl(icon)}`); - createCSSRule(`.vs-dark .icon.${this._iconClass}, .hc-black .icon.${this._iconClass}`, `background-image: ${asCSSUrl(iconDark)}`); + this._iconClass = createIconCssClass(this.iconPath, this._iconClass); this._changeRef.detectChanges(); } } - private getLightIconUri(iconPath: IUserFriendlyIcon): URI { - if (iconPath && iconPath['light']) { - return this.getIconUri(iconPath['light']); - } else { - return this.getIconUri(iconPath); - } - } - - private getDarkIconUri(iconPath: IUserFriendlyIcon): URI { - if (iconPath && iconPath['dark']) { - return this.getIconUri(iconPath['dark']); - } - return null; - } - - private getIconUri(iconPath: string | URI): URI { - if (typeof iconPath === 'string') { - return URI.file(iconPath); - } else { - return URI.revive(iconPath); - } - } - public getIconWidth(): string { return this.convertSize(this.iconWidth, '40px'); } diff --git a/src/sql/workbench/browser/modelComponents/components.contribution.ts b/src/sql/workbench/browser/modelComponents/components.contribution.ts index 281a582d74b1..cebfff409a7a 100644 --- a/src/sql/workbench/browser/modelComponents/components.contribution.ts +++ b/src/sql/workbench/browser/modelComponents/components.contribution.ts @@ -29,6 +29,7 @@ import { registerComponentType } from 'sql/platform/dashboard/browser/modelCompo import { ModelComponentTypes } from 'sql/workbench/api/common/sqlExtHostTypes'; import HyperlinkComponent from 'sql/workbench/browser/modelComponents/hyperlink.component'; import SplitViewContainer from 'sql/workbench/browser/modelComponents/splitviewContainer.component'; +import RadioCardGroup from 'sql/workbench/browser/modelComponents/radioCardGroup.component'; export const DIV_CONTAINER = 'div-container'; registerComponentType(DIV_CONTAINER, ModelComponentTypes.DivContainer, DivContainer); @@ -105,3 +106,7 @@ registerComponentType(DOM_COMPONENT, ModelComponentTypes.Dom, DomComponent); export const HYPERLINK_COMPONENT = 'hyperlink-component'; registerComponentType(HYPERLINK_COMPONENT, ModelComponentTypes.Hyperlink, HyperlinkComponent); + +export const RADIOCARDGROUP_COMPONENT = 'radiocardgroup-component'; +registerComponentType(RADIOCARDGROUP_COMPONENT, ModelComponentTypes.RadioCardGroup, RadioCardGroup); + diff --git a/src/sql/workbench/browser/modelComponents/iconUtils.ts b/src/sql/workbench/browser/modelComponents/iconUtils.ts new file mode 100644 index 000000000000..6e6c7d0d36fc --- /dev/null +++ b/src/sql/workbench/browser/modelComponents/iconUtils.ts @@ -0,0 +1,54 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { asCSSUrl, createCSSRule, removeCSSRulesContainingSelector } from 'vs/base/browser/dom'; +import { IdGenerator } from 'vs/base/common/idGenerator'; +import { URI } from 'vs/base/common/uri'; + +const ids = new IdGenerator('model-view-component-icon-'); + +export type IUserFriendlyIcon = string | URI | { light: string | URI; dark: string | URI }; + +/** + * Create a CSS class for the specified icon, if a class with the name already exists, it will be deleted first. + * @param iconPath icon specification + * @param className optional, the class name you want to reuse. + * @returns the CSS class name + */ +export function createIconCssClass(iconPath: IUserFriendlyIcon, className?: string): string { + let iconClass = className; + if (!iconClass) { + iconClass = ids.nextId(); + } + removeCSSRulesContainingSelector(iconClass); + const icon = getLightIconUri(iconPath); + const iconDark = getDarkIconUri(iconPath) || icon; + createCSSRule(`.icon.${iconClass}`, `background-image: ${asCSSUrl(icon)}`); + createCSSRule(`.vs-dark .icon.${iconClass}, .hc-black .icon.${iconClass}`, `background-image: ${asCSSUrl(iconDark)}`); + return iconClass; +} + +function getLightIconUri(iconPath: IUserFriendlyIcon): URI { + if (iconPath && iconPath['light']) { + return getIconUri(iconPath['light']); + } else { + return getIconUri(iconPath); + } +} + +function getDarkIconUri(iconPath: IUserFriendlyIcon): URI { + if (iconPath && iconPath['dark']) { + return getIconUri(iconPath['dark']); + } + return null; +} + +function getIconUri(iconPath: string | URI): URI { + if (typeof iconPath === 'string') { + return URI.file(iconPath); + } else { + return URI.revive(iconPath); + } +} diff --git a/src/sql/workbench/browser/modelComponents/media/card.css b/src/sql/workbench/browser/modelComponents/media/card.css index 4dcca366c6dc..8b67b22391dc 100644 --- a/src/sql/workbench/browser/modelComponents/media/card.css +++ b/src/sql/workbench/browser/modelComponents/media/card.css @@ -13,16 +13,7 @@ border-style: solid; text-align: left; vertical-align: top; -} - -.model-card-list-item.selected, .model-card.selected { - border-color: rgb(0, 120, 215); - box-shadow: rgba(0, 120, 215, 0.75) 0px 0px 6px; -} - -.model-card-list-item.unselected, .model-card.unselected { border-color: rgb(214, 214, 214); - box-shadow: none; } .model-card .card-content { @@ -122,7 +113,7 @@ border-radius: 50%; background-color: white; border-width: 1px; - border-color: rgb(0, 120, 215); + border-color: rgb(214, 214, 214); border-style: solid; } @@ -207,3 +198,22 @@ .model-card-list-item-description-value { float: right; } + +.card-group { + display: flex; + flex-flow: row; +} + +.model-card-description-table { + margin-bottom: 10px; +} + +.model-card-description-label-column { + text-align: left; + width: 100%; +} + +.model-card-description-value-column { + text-align: right; + white-space: nowrap; +} diff --git a/src/sql/workbench/browser/modelComponents/radioCardGroup.component.html b/src/sql/workbench/browser/modelComponents/radioCardGroup.component.html new file mode 100644 index 000000000000..3c597e52322f --- /dev/null +++ b/src/sql/workbench/browser/modelComponents/radioCardGroup.component.html @@ -0,0 +1,31 @@ +
+ +
diff --git a/src/sql/workbench/browser/modelComponents/radioCardGroup.component.ts b/src/sql/workbench/browser/modelComponents/radioCardGroup.component.ts new file mode 100644 index 000000000000..3b0a6d4bfdfc --- /dev/null +++ b/src/sql/workbench/browser/modelComponents/radioCardGroup.component.ts @@ -0,0 +1,176 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import { ChangeDetectorRef, Component, ElementRef, forwardRef, Inject, Input, OnDestroy, QueryList, ViewChildren } from '@angular/core'; +import * as azdata from 'azdata'; +import { ComponentBase } from 'sql/workbench/browser/modelComponents/componentBase'; +import { createIconCssClass } from 'sql/workbench/browser/modelComponents/iconUtils'; +import { ComponentEventType, IComponent, IComponentDescriptor, IModelStore } from 'sql/workbench/browser/modelComponents/interfaces'; +import * as DOM from 'vs/base/browser/dom'; +import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { KeyCode } from 'vs/base/common/keyCodes'; +import 'vs/css!./media/card'; +import { ILogService } from 'vs/platform/log/common/log'; + +@Component({ + templateUrl: decodeURI(require.toUrl('./radioCardGroup.component.html')) + +}) +export default class RadioCardGroup extends ComponentBase implements IComponent, OnDestroy { + @Input() descriptor: IComponentDescriptor; + @Input() modelStore: IModelStore; + @ViewChildren('cardDiv') cardElements: QueryList; + + private selectedCard: azdata.RadioCard; + private focusedCard: azdata.RadioCard; + private iconClasses: { [key: string]: string } = {}; + + constructor( + @Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef, + @Inject(forwardRef(() => ElementRef)) el: ElementRef, + @Inject(ILogService) private _logService: ILogService + ) { + super(changeRef, el); + } + + ngOnInit(): void { + this.baseInit(); + } + + setLayout(layout: any): void { + this.layout(); + } + + ngOnDestroy(): void { + Object.keys(this.iconClasses).forEach((key) => { + DOM.removeCSSRulesContainingSelector(this.iconClasses[key]); + }); + this.baseDestroy(); + } + + onKeyDown(event: KeyboardEvent): void { + if (!this.enabled || this.cards.length === 0) { + return; + } + + let e = new StandardKeyboardEvent(event); + if (e.keyCode === KeyCode.Enter || e.keyCode === KeyCode.Space) { + if (this.focusedCard && !this.selectedCard) { + this.selectCard(this.focusedCard); + } + DOM.EventHelper.stop(e, true); + } + else if (e.keyCode === KeyCode.LeftArrow || e.keyCode === KeyCode.UpArrow) { + if (this.focusedCard) { + this.selectCard(this.findPreviousCard(this.focusedCard)); + } + DOM.EventHelper.stop(e, true); + } else if (e.keyCode === KeyCode.RightArrow || e.keyCode === KeyCode.DownArrow) { + if (this.focusedCard) { + this.selectCard(this.findNextCard(this.focusedCard)); + } + DOM.EventHelper.stop(e, true); + } + } + + private findPreviousCard(currentCard: azdata.RadioCard): azdata.RadioCard { + const currentIndex = this.cards.indexOf(currentCard); + const previousCardIndex = currentIndex === 0 ? this.cards.length - 1 : currentIndex - 1; + return this.cards[previousCardIndex]; + } + + private findNextCard(currentCard: azdata.RadioCard): azdata.RadioCard { + const currentIndex = this.cards.indexOf(currentCard); + const nextCardIndex = currentIndex === this.cards.length - 1 ? 0 : currentIndex + 1; + return this.cards[nextCardIndex]; + } + + public get cards(): azdata.RadioCard[] { + return this.getPropertyOrDefault((props) => props.cards, []); + } + + public get cardWidth(): string | undefined { + return this.getPropertyOrDefault((props) => props.cardWidth, undefined); + } + + public get cardHeight(): string | undefined { + return this.getPropertyOrDefault((props) => props.cardHeight, undefined); + } + + public get iconWidth(): string | undefined { + return this.getPropertyOrDefault((props) => props.iconWidth, undefined); + } + + public get iconHeight(): string | undefined { + return this.getPropertyOrDefault((props) => props.iconHeight, undefined); + } + + public get selectedCardId(): string | undefined { + return this.getPropertyOrDefault((props) => props.selectedCardId, undefined); + } + + public getIconClass(card: azdata.RadioCard): string { + if (!this.iconClasses[card.id]) { + this.iconClasses[card.id] = `cardIcon icon ${createIconCssClass(card.icon)}`; + } + return this.iconClasses[card.id]; + } + + public setProperties(properties: { [key: string]: any }) { + super.setProperties(properties); + // This is the entry point for the extension to set the selectedCardId + if (this.selectedCardId) { + const filteredCards = this.cards.filter(c => { return c.id === this.selectedCardId; }); + if (filteredCards.length === 1) { + this.selectCard(filteredCards[0]); + } else { + this._logService.error(`There should be one and only one matching card for the giving selectedCardId, actual number: ${filteredCards.length}, selectedCardId: ${this.selectedCardId} $`); + } + } + } + + public selectCard(card: azdata.RadioCard): void { + if (!this.enabled || this.selectedCard === card || this.cards.indexOf(card) === -1) { + return; + } + this.selectedCard = card; + this._changeRef.detectChanges(); + const cardElement = this.getCardElement(this.selectedCard); + cardElement.nativeElement.focus(); + this.setPropertyFromUI((props, value) => props.selectedCardId = value, card.id); + this.fireEvent({ + eventType: ComponentEventType.onDidChange, + args: this.selectedCard.id + }); + } + + public getCardElement(card: azdata.RadioCard): ElementRef { + return this.cardElements.toArray()[this.cards.indexOf(card)]; + } + + public getTabIndex(card: azdata.RadioCard): number { + if (!this.enabled) { + return -1; + } + else if (!this.selectedCard) { + return this.cards.indexOf(card) === 0 ? 0 : -1; + } else { + return card === this.selectedCard ? 0 : -1; + } + } + + public isCardSelected(card: azdata.RadioCard): boolean { + return card === this.selectedCard; + } + + public onCardFocus(card: azdata.RadioCard): void { + this.focusedCard = card; + this._changeRef.detectChanges(); + } + + public onCardBlur(card: azdata.RadioCard): void { + this.focusedCard = undefined; + this._changeRef.detectChanges(); + } +} From 255ea1945bc278289fc1840e06c4a2c09d1f7112 Mon Sep 17 00:00:00 2001 From: Cory Rivera Date: Tue, 3 Dec 2019 16:40:11 -0800 Subject: [PATCH 07/19] Only conda install pykerberos on non-windows platforms. (#8544) --- .../notebook/src/jupyter/jupyterServerInstallation.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/extensions/notebook/src/jupyter/jupyterServerInstallation.ts b/extensions/notebook/src/jupyter/jupyterServerInstallation.ts index c56a3ff3a7db..894dcc4ef0e2 100644 --- a/extensions/notebook/src/jupyter/jupyterServerInstallation.ts +++ b/extensions/notebook/src/jupyter/jupyterServerInstallation.ts @@ -83,10 +83,8 @@ export class JupyterServerInstallation { ]; private readonly _expectedPythonPackages = this._commonPackages.concat(this._commonPipPackages); - - private readonly _expectedCondaPackages = this._commonPackages.concat([{ name: 'pykerberos', version: '1.2.1' }]); - private readonly _expectedCondaPipPackages = this._commonPipPackages; + private readonly _expectedCondaPackages: PythonPkgDetails[]; constructor(extensionPath: string, outputChannel: OutputChannel, apiWrapper: ApiWrapper, pythonInstallationPath?: string) { this.extensionPath = extensionPath; @@ -98,6 +96,12 @@ export class JupyterServerInstallation { this._usingExistingPython = JupyterServerInstallation.getExistingPythonSetting(this.apiWrapper); this._prompter = new CodeAdapter(); + + if (process.platform !== constants.winPlatform) { + this._expectedCondaPackages = this._commonPackages.concat([{ name: 'pykerberos', version: '1.2.1' }]); + } else { + this._expectedCondaPackages = this._commonPackages; + } } private async installDependencies(backgroundOperation: azdata.BackgroundOperation, forceInstall: boolean): Promise { From 7f16a4d857f62145bb8357efc5e2de5ecfc9cc07 Mon Sep 17 00:00:00 2001 From: Charles Gagnon Date: Tue, 3 Dec 2019 17:40:02 -0800 Subject: [PATCH 08/19] Fix refresh action (#8519) * Fix refresh action * Refactor to async * Fix missing bracket * Remove unused error level * Add back in error message * Fix missing service --- .../scripting/browser/scriptingActions.ts | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/src/sql/workbench/contrib/scripting/browser/scriptingActions.ts b/src/sql/workbench/contrib/scripting/browser/scriptingActions.ts index 958e638f7f3f..84da157af4d6 100644 --- a/src/sql/workbench/contrib/scripting/browser/scriptingActions.ts +++ b/src/sql/workbench/contrib/scripting/browser/scriptingActions.ts @@ -20,9 +20,12 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/browser/objectExplorerService'; import { TreeSelectionHandler } from 'sql/workbench/contrib/objectExplorer/browser/treeSelectionHandler'; import { TreeUpdateUtils } from 'sql/workbench/contrib/objectExplorer/browser/treeUpdateUtils'; -import Severity from 'vs/base/common/severity'; import { TreeNode } from 'sql/workbench/contrib/objectExplorer/common/treeNode'; import { VIEWLET_ID } from 'sql/workbench/contrib/dataExplorer/browser/dataExplorerViewlet'; +import { ILogService } from 'vs/platform/log/common/log'; +import { getErrorMessage } from 'vs/base/common/errors'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { localize } from 'vs/nls'; //#region -- Data Explorer export const SCRIPT_AS_CREATE_COMMAND_ID = 'dataExplorer.scriptAsCreate'; @@ -307,39 +310,36 @@ CommandsRegistry.registerCommand({ // Refresh Action for Scriptable objects CommandsRegistry.registerCommand({ id: OE_REFRESH_COMMAND_ID, - handler: async (accessor, args: ObjectExplorerActionsContext) => { + handler: async (accessor, args: ObjectExplorerActionsContext): Promise => { const connectionManagementService = accessor.get(IConnectionManagementService); const capabilitiesService = accessor.get(ICapabilitiesService); const objectExplorerService = accessor.get(IObjectExplorerService); - const errorMessageService = accessor.get(IErrorMessageService); + const logService = accessor.get(ILogService); + const notificationService = accessor.get(INotificationService); const connection = new ConnectionProfile(capabilitiesService, args.connectionProfile); - let treeNode: TreeNode; if (connectionManagementService.isConnected(undefined, connection)) { - treeNode = await getTreeNode(args, objectExplorerService); - if (treeNode === undefined) { - objectExplorerService.updateObjectExplorerNodes(connection.toIConnectionProfile()).then(() => { - treeNode = objectExplorerService.getObjectExplorerNode(connection); - }); + let treeNode = await getTreeNode(args, objectExplorerService); + if (!treeNode) { + await objectExplorerService.updateObjectExplorerNodes(connection.toIConnectionProfile()); + treeNode = objectExplorerService.getObjectExplorerNode(connection); + } + if (treeNode) { + const tree = objectExplorerService.getServerTreeView().tree; + try { + await tree.collapse(treeNode); + await objectExplorerService.refreshTreeNode(treeNode.getSession(), treeNode); + await tree.refresh(treeNode); + await tree.expand(treeNode); + } catch (err) { + // Display message to the user but also log the entire error to the console for the stack trace + notificationService.error(localize('refreshError', "An error occurred refreshing node '{0}': {1}", args.nodeInfo.label, getErrorMessage(err))); + logService.error(err); + } + + } else { + logService.error(`Could not find tree node for node ${args.nodeInfo.label}`); } } - const tree = objectExplorerService.getServerTreeView().tree; - if (treeNode) { - return tree.collapse(treeNode).then(() => { - return objectExplorerService.refreshTreeNode(treeNode.getSession(), treeNode).then(() => { - return tree.refresh(treeNode).then(() => { - return tree.expand(treeNode); - }, refreshError => { - return Promise.resolve(true); - }); - }, error => { - errorMessageService.showDialog(Severity.Error, '', error); - return Promise.resolve(true); - }); - }, collapseError => { - return Promise.resolve(true); - }); - } - return Promise.resolve(true); } }); //#endregion From 6cce532ca474cb3272a81c7317d759579e4c2afb Mon Sep 17 00:00:00 2001 From: Charles Gagnon Date: Tue, 3 Dec 2019 17:40:19 -0800 Subject: [PATCH 09/19] Fix wizard not displaying error messages (#8545) --- src/sql/workbench/browser/modal/modal.ts | 22 +++++++++++-------- .../services/dialog/browser/wizardModal.ts | 15 ++++++++++--- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/src/sql/workbench/browser/modal/modal.ts b/src/sql/workbench/browser/modal/modal.ts index 961068dc259e..ccfb3c2ae192 100644 --- a/src/sql/workbench/browser/modal/modal.ts +++ b/src/sql/workbench/browser/modal/modal.ts @@ -481,19 +481,23 @@ export abstract class Modal extends Disposable implements IThemable { this._messageDetail.innerText = description; } DOM.removeNode(this._messageDetail); - if (this._messageSummaryText) { - if (this._useDefaultMessageBoxLocation) { - DOM.prepend(this._modalContent, (this._messageElement)); - } - } else { - // Set the focus manually otherwise it'll escape the dialog to something behind it - this.setInitialFocusedElement(); - DOM.removeNode(this._messageElement); - } + this.messagesElementVisible = !!this._messageSummaryText; this.updateExpandMessageState(); } } + protected set messagesElementVisible(visible: boolean) { + if (visible) { + if (this._useDefaultMessageBoxLocation) { + DOM.prepend(this._modalContent, (this._messageElement)); + } + } else { + // Set the focus manually otherwise it'll escape the dialog to something behind it + this.setInitialFocusedElement(); + DOM.removeNode(this._messageElement); + } + } + /** * Set spinner element to show or hide */ diff --git a/src/sql/workbench/services/dialog/browser/wizardModal.ts b/src/sql/workbench/services/dialog/browser/wizardModal.ts index e3694de4875b..c0f217c389fc 100644 --- a/src/sql/workbench/services/dialog/browser/wizardModal.ts +++ b/src/sql/workbench/services/dialog/browser/wizardModal.ts @@ -36,6 +36,7 @@ export class WizardModal extends Modal { private _body: HTMLElement; private _pageContainer: HTMLElement; + private _mpContainer: HTMLElement; // Buttons private _previousButton: Button; @@ -130,9 +131,8 @@ export class WizardModal extends Modal { this.initializeNavigation(this._body); - const mpContainer = append(this._body, $('div.dialog-message-and-page-container')); - mpContainer.append(this._messageElement); - this._pageContainer = append(mpContainer, $('div.dialogModal-page-container')); + this._mpContainer = append(this._body, $('div.dialog-message-and-page-container')); + this._pageContainer = append(this._mpContainer, $('div.dialogModal-page-container')); this._wizard.pages.forEach(page => { this.registerPage(page); @@ -152,6 +152,15 @@ export class WizardModal extends Modal { this.updatePageNumbers(); } + protected set messagesElementVisible(visible: boolean) { + if (visible) { + this._mpContainer.prepend(this._messageElement); + } else { + // Let base class handle it + super.messagesElementVisible = false; + } + } + private updatePageNumbers(): void { this._wizard.pages.forEach((page, index) => { let dialogPane = this._dialogPanes.get(page); From ee94524ab1a4f1679c32263d6eede92b9c5b8c56 Mon Sep 17 00:00:00 2001 From: Arvind Ranasaria Date: Wed, 4 Dec 2019 09:37:58 -0800 Subject: [PATCH 10/19] Pull GITHUB_TOKEN from akv store (#8538) * Pull GITHUB_TOKEN from akv store * fixing subscription for ado-secrets * undo changes to publish-types.yml * fix subscription again --- .../azure-pipelines/darwin/continuous-build-darwin.yml | 10 ++++++++-- .../darwin/sql-product-build-darwin.yml | 1 + build/azure-pipelines/linux/continuous-build-linux.yml | 10 ++++++++-- .../azure-pipelines/linux/sql-product-build-linux.yml | 1 + build/azure-pipelines/win32/continuous-build-win32.yml | 10 ++++++++-- .../azure-pipelines/win32/sql-product-build-win32.yml | 1 + 6 files changed, 27 insertions(+), 6 deletions(-) diff --git a/build/azure-pipelines/darwin/continuous-build-darwin.yml b/build/azure-pipelines/darwin/continuous-build-darwin.yml index fea2d90f0b0c..644df3249136 100644 --- a/build/azure-pipelines/darwin/continuous-build-darwin.yml +++ b/build/azure-pipelines/darwin/continuous-build-darwin.yml @@ -7,6 +7,12 @@ steps: keyfile: '.yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock' targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules' vstsFeed: '$(build-cache)' # {{SQL CARBON EDIT}} update build cache +- task: AzureKeyVault@1 + displayName: 'Azure Key Vault: Get Secrets' + inputs: + azureSubscription: 'azuredatastudio-adointegration' + KeyVaultName: ado-secrets + SecretsFilter: 'github-distro-mixin-password' - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@3 # {{SQL CARBON EDIT}} update version inputs: versionSpec: "1.x" @@ -15,7 +21,7 @@ steps: displayName: Install Dependencies condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) env: - GITHUB_TOKEN: $(GITHUB_TOKEN) # {{SQL CARBON EDIT}} add github token + GITHUB_TOKEN: $(github-distro-mixin-password) # {{SQL CARBON EDIT}} add github token - task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1 inputs: keyfile: '.yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock' @@ -26,7 +32,7 @@ steps: yarn electron x64 displayName: Download Electron env: - GITHUB_TOKEN: $(GITHUB_TOKEN) # {{SQL CARBON EDIT}} add github token + GITHUB_TOKEN: $(github-distro-mixin-password) # {{SQL CARBON EDIT}} add github token - script: | yarn gulp hygiene --skip-tslint displayName: Run Hygiene Checks diff --git a/build/azure-pipelines/darwin/sql-product-build-darwin.yml b/build/azure-pipelines/darwin/sql-product-build-darwin.yml index f1874fb06411..857d1b0b16af 100644 --- a/build/azure-pipelines/darwin/sql-product-build-darwin.yml +++ b/build/azure-pipelines/darwin/sql-product-build-darwin.yml @@ -18,6 +18,7 @@ steps: inputs: azureSubscription: 'ClientToolsInfra_670062 (88d5392f-a34f-4769-b405-f597fc533613)' KeyVaultName: ado-secrets + SecretsFilter: 'github-distro-mixin-password' - script: | set -e diff --git a/build/azure-pipelines/linux/continuous-build-linux.yml b/build/azure-pipelines/linux/continuous-build-linux.yml index 4ad19ca4c25e..94c1b0fb1278 100644 --- a/build/azure-pipelines/linux/continuous-build-linux.yml +++ b/build/azure-pipelines/linux/continuous-build-linux.yml @@ -15,6 +15,12 @@ steps: keyfile: '.yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock' targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules' vstsFeed: '$(build-cache)' # {{SQL CARBON EDIT}} update build cache +- task: AzureKeyVault@1 + displayName: 'Azure Key Vault: Get Secrets' + inputs: + azureSubscription: 'azuredatastudio-adointegration' + KeyVaultName: ado-secrets + SecretsFilter: 'github-distro-mixin-password' - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@3 # {{SQL CARBON EDIT}} update version inputs: versionSpec: "1.x" @@ -23,7 +29,7 @@ steps: displayName: Install Dependencies condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) env: - GITHUB_TOKEN: $(GITHUB_TOKEN) # {{SQL CARBON EDIT}} add github token + GITHUB_TOKEN: $(github-distro-mixin-password) # {{SQL CARBON EDIT}} add github token - task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1 inputs: keyfile: '.yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock' @@ -34,7 +40,7 @@ steps: yarn electron x64 displayName: Download Electron env: - GITHUB_TOKEN: $(GITHUB_TOKEN) # {{SQL CARBON EDIT}} add github token + GITHUB_TOKEN: $(github-distro-mixin-password) # {{SQL CARBON EDIT}} add github token - script: | yarn gulp hygiene --skip-tslint displayName: Run Hygiene Checks diff --git a/build/azure-pipelines/linux/sql-product-build-linux.yml b/build/azure-pipelines/linux/sql-product-build-linux.yml index 9dd4bda5edbd..b256b0b171c9 100644 --- a/build/azure-pipelines/linux/sql-product-build-linux.yml +++ b/build/azure-pipelines/linux/sql-product-build-linux.yml @@ -22,6 +22,7 @@ steps: inputs: azureSubscription: 'ClientToolsInfra_670062 (88d5392f-a34f-4769-b405-f597fc533613)' KeyVaultName: ado-secrets + SecretsFilter: 'github-distro-mixin-password' - script: | set -e diff --git a/build/azure-pipelines/win32/continuous-build-win32.yml b/build/azure-pipelines/win32/continuous-build-win32.yml index 3b46d8368baa..c2c9b3b81603 100644 --- a/build/azure-pipelines/win32/continuous-build-win32.yml +++ b/build/azure-pipelines/win32/continuous-build-win32.yml @@ -9,6 +9,12 @@ steps: inputs: versionSpec: '2.x' addToPath: true +- task: AzureKeyVault@1 + displayName: 'Azure Key Vault: Get Secrets' + inputs: + azureSubscription: 'azuredatastudio-adointegration' + KeyVaultName: ado-secrets + SecretsFilter: 'github-distro-mixin-password' - task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 inputs: keyfile: '.yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock' @@ -18,7 +24,7 @@ steps: yarn --frozen-lockfile env: CHILD_CONCURRENCY: "1" - GITHUB_TOKEN: $(GITHUB_TOKEN) # {{SQL CARBON EDIT}} add github token + GITHUB_TOKEN: $(github-distro-mixin-password) # {{SQL CARBON EDIT}} add github token displayName: Install Dependencies condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) - task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1 @@ -30,7 +36,7 @@ steps: - powershell: | yarn electron env: - GITHUB_TOKEN: $(GITHUB_TOKEN) # {{SQL CARBON EDIT}} add github token + GITHUB_TOKEN: $(github-distro-mixin-password) # {{SQL CARBON EDIT}} add github token - script: | yarn gulp hygiene --skip-tslint displayName: Run Hygiene Checks diff --git a/build/azure-pipelines/win32/sql-product-build-win32.yml b/build/azure-pipelines/win32/sql-product-build-win32.yml index 1c002d8d230a..ba3b3084390b 100644 --- a/build/azure-pipelines/win32/sql-product-build-win32.yml +++ b/build/azure-pipelines/win32/sql-product-build-win32.yml @@ -21,6 +21,7 @@ steps: inputs: azureSubscription: 'ClientToolsInfra_670062 (88d5392f-a34f-4769-b405-f597fc533613)' KeyVaultName: ado-secrets + SecretsFilter: 'github-distro-mixin-password' - powershell: | . build/azure-pipelines/win32/exec.ps1 From ab94b9785ef62d02b4ee0207d4ae83b22cf5c31e Mon Sep 17 00:00:00 2001 From: Alan Ren Date: Wed, 4 Dec 2019 10:48:08 -0800 Subject: [PATCH 11/19] deprecate the card component (#8552) * deprecate the card component * add deprecated tag --- src/sql/azdata.d.ts | 3 +++ src/sql/workbench/api/common/extHostModelView.ts | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/src/sql/azdata.d.ts b/src/sql/azdata.d.ts index 70e211849036..1eb52054c31f 100644 --- a/src/sql/azdata.d.ts +++ b/src/sql/azdata.d.ts @@ -2477,6 +2477,9 @@ declare module 'azdata' { flexContainer(): FlexBuilder; splitViewContainer(): SplitViewBuilder; dom(): ComponentBuilder; + /** + * @deprecated please use radioCardGroup component. + */ card(): ComponentBuilder; inputBox(): ComponentBuilder; checkBox(): ComponentBuilder; diff --git a/src/sql/workbench/api/common/extHostModelView.ts b/src/sql/workbench/api/common/extHostModelView.ts index d24c25cbeb9f..04a00dfa976d 100644 --- a/src/sql/workbench/api/common/extHostModelView.ts +++ b/src/sql/workbench/api/common/extHostModelView.ts @@ -79,7 +79,12 @@ class ModelBuilderImpl implements azdata.ModelBuilder { return container; } + private cardDeprecationMessagePrinted = false; card(): azdata.ComponentBuilder { + if (!this.cardDeprecationMessagePrinted) { + console.warn(`Extension '${this._extension.identifier.value}' is using card component which has been replaced by radioCardGroup. the card component will be removed in a future release.`); + this.cardDeprecationMessagePrinted = true; + } let id = this.getNextComponentId(); let builder: ComponentBuilderImpl = this.getComponentBuilder(new CardWrapper(this._proxy, this._handle, id), id); this._componentBuilders.set(id, builder); From c34869c2438b7294a78fe04a452f178eb5863c26 Mon Sep 17 00:00:00 2001 From: Alan Ren Date: Wed, 4 Dec 2019 10:59:30 -0800 Subject: [PATCH 12/19] improve the install sql on windows scenario (#8559) --- .../src/services/resourceTypeService.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/extensions/resource-deployment/src/services/resourceTypeService.ts b/extensions/resource-deployment/src/services/resourceTypeService.ts index 9fca6ab56425..9790db8be906 100644 --- a/extensions/resource-deployment/src/services/resourceTypeService.ts +++ b/extensions/resource-deployment/src/services/resourceTypeService.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import * as azdata from 'azdata'; -import * as cp from 'child_process'; import { createWriteStream, promises as fs } from 'fs'; import * as https from 'https'; import * as os from 'os'; @@ -246,10 +245,10 @@ export class ResourceTypeService implements IResourceTypeService { isCancelable: false, operation: op => { op.updateStatus(azdata.TaskStatus.InProgress, localize('resourceDeployment.DownloadingText', "Downloading from: {0}", provider.downloadUrl)); - self.download(provider.downloadUrl).then((downloadedFile) => { + self.download(provider.downloadUrl).then(async (downloadedFile) => { op.updateStatus(azdata.TaskStatus.InProgress, localize('resourceDeployment.DownloadCompleteText', "Successfully downloaded: {0}", downloadedFile)); op.updateStatus(azdata.TaskStatus.InProgress, localize('resourceDeployment.LaunchingProgramText', "Launching: {0}", downloadedFile)); - cp.exec(downloadedFile); + await this.platformService.runCommand(downloadedFile, { sudo: true }); op.updateStatus(azdata.TaskStatus.Succeeded, localize('resourceDeployment.ProgramLaunchedText', "Successfully launched: {0}", downloadedFile)); }, (error) => { op.updateStatus(azdata.TaskStatus.Failed, error); @@ -285,7 +284,12 @@ export class ResourceTypeService implements IResourceTypeService { const extension = path.extname(url); const originalFileName = path.basename(url, extension); let fileName = originalFileName; - const downloadFolder = os.homedir(); + // Download it to the user's downloads folder + // and fall back to the user's homedir if it does not exist. + let downloadFolder = path.join(os.homedir(), 'Downloads'); + if (!await exists(downloadFolder)) { + downloadFolder = os.homedir(); + } let cnt = 1; while (await exists(path.join(downloadFolder, fileName + extension))) { fileName = `${originalFileName}-${cnt}`; From a7f5741608cdfe5c330059561ff7a8c4a4b438b9 Mon Sep 17 00:00:00 2001 From: Benjin Dubishar Date: Wed, 4 Dec 2019 14:44:04 -0800 Subject: [PATCH 13/19] Correcting license link on schema-compare (#8560) --- extensions/schema-compare/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/schema-compare/package.json b/extensions/schema-compare/package.json index 64c8e6394e77..5e234a9375bd 100644 --- a/extensions/schema-compare/package.json +++ b/extensions/schema-compare/package.json @@ -9,7 +9,7 @@ "vscode": "^1.25.0", "azdata": ">=1.13.0" }, - "license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/master/extensions/import/Microsoft_SQL_Server_Import_Extension_and_Tools_Import_Flat_File_Preview.docx", + "license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/master/LICENSE.txt", "icon": "images/sqlserver.png", "aiKey": "AIF-37eefaf0-8022-4671-a3fb-64752724682e", "activationEvents": [ From 9691fab91700620f8db8b01b7372ccd5f41ecbcf Mon Sep 17 00:00:00 2001 From: Cory Rivera Date: Wed, 4 Dec 2019 14:45:48 -0800 Subject: [PATCH 14/19] Add code coverage tests for cell.ts (#8564) --- .../contrib/notebook/browser/models/cell.ts | 18 +- .../browser/models/modelInterfaces.ts | 3 + .../test/electron-browser/cell.test.ts | 248 +++++++++++++++++- .../notebook/test/electron-browser/common.ts | 181 ++++++++++++- 4 files changed, 429 insertions(+), 21 deletions(-) diff --git a/src/sql/workbench/contrib/notebook/browser/models/cell.ts b/src/sql/workbench/contrib/notebook/browser/models/cell.ts index abc61231f489..46ea0841a501 100644 --- a/src/sql/workbench/contrib/notebook/browser/models/cell.ts +++ b/src/sql/workbench/contrib/notebook/browser/models/cell.ts @@ -80,7 +80,7 @@ export class CellModel implements ICellModel { } public equals(other: ICellModel) { - return other && other.id === this.id; + return other !== undefined && other.id === this.id; } public get onCollapseStateChanged(): Event { @@ -91,10 +91,6 @@ export class CellModel implements ICellModel { return this._onOutputsChanged.event; } - public get onCellModeChanged(): Event { - return this._onCellModeChanged.event; - } - public get isEditMode(): boolean { return this._isEditMode; } @@ -191,17 +187,13 @@ export class CellModel implements ICellModel { } public get notebookModel(): NotebookModel { - return this.options.notebook; + return this._options && this._options.notebook; } public set cellUri(value: URI) { this._cellUri = value; } - public get options(): ICellModelOptions { - return this._options; - } - public get cellType(): CellType { return this._cellType; } @@ -234,7 +226,7 @@ export class CellModel implements ICellModel { if (this._language) { return this._language; } - return this.options.notebook.language; + return this._options.notebook.language; } public get cellGuid(): string { @@ -370,7 +362,7 @@ export class CellModel implements ICellModel { } private async getOrStartKernel(notificationService: INotificationService): Promise { - let model = this.options.notebook; + let model = this._options.notebook; let clientSession = model && model.clientSession; if (!clientSession) { this.sendNotification(notificationService, Severity.Error, localize('notebookNotReady', "The session for this notebook is not yet ready")); @@ -516,7 +508,7 @@ export class CellModel implements ICellModel { try { let result = output as nb.IDisplayResult; if (result && result.data && result.data['text/html']) { - let model = (this as CellModel).options.notebook as NotebookModel; + let model = this._options.notebook as NotebookModel; if (model.activeConnection) { let gatewayEndpointInfo = this.getGatewayEndpoint(model.activeConnection); if (gatewayEndpointInfo) { diff --git a/src/sql/workbench/contrib/notebook/browser/models/modelInterfaces.ts b/src/sql/workbench/contrib/notebook/browser/models/modelInterfaces.ts index eb8f6fb55124..3dae44471a94 100644 --- a/src/sql/workbench/contrib/notebook/browser/models/modelInterfaces.ts +++ b/src/sql/workbench/contrib/notebook/browser/models/modelInterfaces.ts @@ -418,6 +418,8 @@ export interface INotebookModel { * @param cell New active cell */ updateActiveCell(cell: ICellModel); + + requestConnection(): Promise; } export interface NotebookContentChange { @@ -491,6 +493,7 @@ export interface ICellModel { isCollapsed: boolean; readonly onCollapseStateChanged: Event; modelContentChangedEvent: IModelContentChangedEvent; + isEditMode: boolean; } export interface FutureInternal extends nb.IFuture { diff --git a/src/sql/workbench/contrib/notebook/test/electron-browser/cell.test.ts b/src/sql/workbench/contrib/notebook/test/electron-browser/cell.test.ts index f4197b180559..1caea8e046bc 100644 --- a/src/sql/workbench/contrib/notebook/test/electron-browser/cell.test.ts +++ b/src/sql/workbench/contrib/notebook/test/electron-browser/cell.test.ts @@ -11,15 +11,21 @@ import * as objects from 'vs/base/common/objects'; import { CellTypes } from 'sql/workbench/contrib/notebook/common/models/contracts'; import { ModelFactory } from 'sql/workbench/contrib/notebook/browser/models/modelFactory'; -import { NotebookModelStub } from './common'; +import { NotebookModelStub, ClientSessionStub, KernelStub, FutureStub } from './common'; import { EmptyFuture } from 'sql/workbench/services/notebook/browser/sessionManager'; -import { ICellModel } from 'sql/workbench/contrib/notebook/browser/models/modelInterfaces'; +import { ICellModel, ICellModelOptions, IClientSession, INotebookModel } from 'sql/workbench/contrib/notebook/browser/models/modelInterfaces'; import { Deferred } from 'sql/base/common/promise'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; import { isUndefinedOrNull } from 'vs/base/common/types'; import { startsWith } from 'vs/base/common/strings'; +import { Schemas } from 'vs/base/common/network'; +import { URI } from 'vs/base/common/uri'; +import { IModelContentChangedEvent } from 'vs/editor/common/model/textModelEvents'; +import { Promise } from 'es6-promise'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; let instantiationService: IInstantiationService; @@ -590,4 +596,242 @@ suite('Cell Model', function (): void { }); }); + test('Getters and setters test', async function (): Promise { + // Code Cell + let cellData: nb.ICellContents = { + cell_type: CellTypes.Code, + source: '1+1', + outputs: [], + metadata: { language: 'python' }, + execution_count: 1 + }; + let cell = factory.createCell(cellData, undefined); + + assert.strictEqual(cell.trustedMode, false, 'Cell should not be trusted by default'); + cell.trustedMode = true; + assert.strictEqual(cell.trustedMode, true, 'Cell should be trusted after manually setting trustedMode'); + + assert.strictEqual(cell.isEditMode, true, 'Code cells should be editable by default'); + cell.isEditMode = false; + assert.strictEqual(cell.isEditMode, false, 'Cell should not be editable after manually setting isEditMode'); + + cell.hover = true; + assert.strictEqual(cell.hover, true, 'Cell should be hovered after manually setting hover=true'); + cell.hover = false; + assert.strictEqual(cell.hover, false, 'Cell should be hovered after manually setting hover=false'); + + let cellUri = URI.from({ scheme: Schemas.untitled, path: `notebook-editor-${cell.id}` }); + assert.deepStrictEqual(cell.cellUri, cellUri); + cellUri = URI.from({ scheme: Schemas.untitled, path: `test-uri-12345` }); + cell.cellUri = cellUri; + assert.deepStrictEqual(cell.cellUri, cellUri); + + assert.strictEqual(cell.language, 'python'); + + assert.strictEqual(cell.notebookModel, undefined); + + assert.strictEqual(cell.modelContentChangedEvent, undefined); + let contentChangedEvent = {}; + cell.modelContentChangedEvent = contentChangedEvent; + assert.strictEqual(cell.modelContentChangedEvent, contentChangedEvent); + + assert.strictEqual(cell.stdInVisible, false, 'Cell stdin should not be visible by default'); + cell.stdInVisible = true; + assert.strictEqual(cell.stdInVisible, true, 'Cell stdin should not be visible by default'); + + cell.loaded = true; + assert.strictEqual(cell.loaded, true, 'Cell should be loaded after manually setting loaded=true'); + cell.loaded = false; + assert.strictEqual(cell.loaded, false, 'Cell should be loaded after manually setting loaded=false'); + + assert.ok(cell.onExecutionStateChange !== undefined, 'onExecutionStateChange event should not be undefined'); + + assert.ok(cell.onLoaded !== undefined, 'onLoaded event should not be undefined'); + + // Markdown cell + cellData = { + cell_type: CellTypes.Markdown, + source: 'some *markdown*', + outputs: [], + metadata: { language: 'python' } + }; + let notebookModel = new NotebookModelStub({ + name: 'python', + version: '', + mimetype: '' + }); + + let cellOptions: ICellModelOptions = { notebook: notebookModel, isTrusted: true }; + cell = factory.createCell(cellData, cellOptions); + + assert.strictEqual(cell.isEditMode, false, 'Markdown cells should not be editable by default'); + assert.strictEqual(cell.trustedMode, true, 'Cell should be trusted when providing isTrusted=true in the cell options'); + assert.strictEqual(cell.language, 'markdown'); + assert.strictEqual(cell.notebookModel, notebookModel); + }); + + test('Equals test', async function (): Promise { + let cell = factory.createCell(undefined, undefined); + + let result = cell.equals(undefined); + assert.strictEqual(result, false, 'Cell should not be equal to undefined'); + + result = cell.equals(cell); + assert.strictEqual(result, true, 'Cell should be equal to itself'); + + let otherCell = factory.createCell(undefined, undefined); + result = cell.equals(otherCell); + assert.strictEqual(result, false, 'Cell should not be equal to a different cell'); + }); + + suite('Run Cell tests', function (): void { + let cellOptions: ICellModelOptions; + let mockClientSession: TypeMoq.Mock; + let mockNotebookModel: TypeMoq.Mock; + let mockKernel: TypeMoq.Mock; + + const codeCellContents: nb.ICellContents = { + cell_type: CellTypes.Code, + source: '1+1', + outputs: [], + metadata: { language: 'python' }, + execution_count: 1 + }; + const markdownCellContents: nb.ICellContents = { + cell_type: CellTypes.Markdown, + source: 'some *markdown*', + outputs: [], + metadata: { language: 'python' } + }; + + setup(() => { + mockKernel = TypeMoq.Mock.ofType(KernelStub); + + mockClientSession = TypeMoq.Mock.ofType(ClientSessionStub); + mockClientSession.setup(s => s.kernel).returns(() => mockKernel.object); + mockClientSession.setup(s => s.isReady).returns(() => true); + + mockNotebookModel = TypeMoq.Mock.ofType(NotebookModelStub); + mockNotebookModel.setup(m => m.clientSession).returns(() => mockClientSession.object); + mockNotebookModel.setup(m => m.updateActiveCell(TypeMoq.It.isAny())); + + cellOptions = { notebook: mockNotebookModel.object, isTrusted: true }; + }); + + test('Run markdown cell', async function (): Promise { + let cell = factory.createCell(markdownCellContents, cellOptions); + let result = await cell.runCell(); + assert.strictEqual(result, false, 'Markdown cells should not be runnable'); + }); + + test('No client session provided', async function (): Promise { + mockNotebookModel.reset(); + mockNotebookModel.setup(m => m.clientSession).returns(() => undefined); + mockNotebookModel.setup(m => m.updateActiveCell(TypeMoq.It.isAny())); + cellOptions.notebook = mockNotebookModel.object; + + let cell = factory.createCell(codeCellContents, cellOptions); + let result = await cell.runCell(); + assert.strictEqual(result, false, 'Running code cell without a client session should fail'); + }); + + test('No Kernel provided', async function (): Promise { + mockClientSession.reset(); + mockClientSession.setup(s => s.kernel).returns(() => null); + mockClientSession.setup(s => s.isReady).returns(() => true); + mockNotebookModel.reset(); + mockNotebookModel.setup(m => m.defaultKernel).returns(() => null); + mockNotebookModel.setup(m => m.clientSession).returns(() => mockClientSession.object); + mockNotebookModel.setup(m => m.updateActiveCell(TypeMoq.It.isAny())); + cellOptions.notebook = mockNotebookModel.object; + + let cell = factory.createCell(codeCellContents, cellOptions); + let result = await cell.runCell(); + assert.strictEqual(result, false, 'Running code cell without a kernel should fail'); + }); + + test('Kernel fails to connect', async function (): Promise { + mockKernel.setup(k => k.requiresConnection).returns(() => true); + mockNotebookModel.setup(m => m.requestConnection()).returns(() => Promise.resolve(false)); + + let cell = factory.createCell(codeCellContents, cellOptions); + let result = await cell.runCell(); + assert.strictEqual(result, false, 'Running code cell should fail after connection fails'); + }); + + test('Normal execute', async function (): Promise { + mockKernel.setup(k => k.requiresConnection).returns(() => false); + mockKernel.setup(k => k.requestExecute(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => { + let replyMsg: nb.IExecuteReplyMsg = { + content: { + execution_count: 1, + status: 'ok' + } + }; + + return new FutureStub(undefined, Promise.resolve(replyMsg)); + }); + + let cell = factory.createCell(codeCellContents, cellOptions); + let result = await cell.runCell(); + assert.strictEqual(result, true, 'Running normal code cell should succeed'); + }); + + test('Execute returns error status', async function (): Promise { + mockKernel.setup(k => k.requiresConnection).returns(() => false); + mockKernel.setup(k => k.requestExecute(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => { + let replyMsg: nb.IExecuteReplyMsg = { + content: { + execution_count: 1, + status: 'error' + } + }; + + return new FutureStub(undefined, Promise.resolve(replyMsg)); + }); + + let cell = factory.createCell(codeCellContents, cellOptions); + let result = await cell.runCell(); + assert.strictEqual(result, false, 'Run cell should fail if execute returns error status'); + }); + + test('Execute returns abort status', async function (): Promise { + mockKernel.setup(k => k.requiresConnection).returns(() => false); + mockKernel.setup(k => k.requestExecute(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => { + let replyMsg: nb.IExecuteReplyMsg = { + content: { + execution_count: 1, + status: 'abort' + } + }; + + return new FutureStub(undefined, Promise.resolve(replyMsg)); + }); + + let cell = factory.createCell(codeCellContents, cellOptions); + let result = await cell.runCell(); + assert.strictEqual(result, false, 'Run cell should fail if execute returns abort status'); + }); + + test('Execute throws exception', async function (): Promise { + let testMsg = 'Test message'; + mockKernel.setup(k => k.requiresConnection).returns(() => false); + mockKernel.setup(k => k.requestExecute(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => { + throw new Error(testMsg); + }); + + let actualMsg: string; + let mockNotification = TypeMoq.Mock.ofType(TestNotificationService); + mockNotification.setup(n => n.notify(TypeMoq.It.isAny())).returns(notification => { + actualMsg = notification.message; + return undefined; + }); + + let cell = factory.createCell(codeCellContents, cellOptions); + let result = await cell.runCell(mockNotification.object); + assert.strictEqual(result, true, 'Run cell should report errors via notification service'); + assert.ok(actualMsg !== undefined, 'Should have received an error notification'); + assert.strictEqual(actualMsg, testMsg); + }); + }); }); diff --git a/src/sql/workbench/contrib/notebook/test/electron-browser/common.ts b/src/sql/workbench/contrib/notebook/test/electron-browser/common.ts index 74afa99ed18e..31244a2aacd3 100644 --- a/src/sql/workbench/contrib/notebook/test/electron-browser/common.ts +++ b/src/sql/workbench/contrib/notebook/test/electron-browser/common.ts @@ -6,7 +6,7 @@ import { nb, IConnectionProfile } from 'azdata'; import { Event, Emitter } from 'vs/base/common/event'; -import { INotebookModel, ICellModel, IClientSession, IDefaultConnection, NotebookContentChange } from 'sql/workbench/contrib/notebook/browser/models/modelInterfaces'; +import { INotebookModel, ICellModel, IClientSession, IDefaultConnection, NotebookContentChange, IKernelPreference } from 'sql/workbench/contrib/notebook/browser/models/modelInterfaces'; import { NotebookChangeType, CellType } from 'sql/workbench/contrib/notebook/common/models/contracts'; import { INotebookManager, INotebookService, INotebookEditor, ILanguageMagic, INotebookProvider, INavigationProvider } from 'sql/workbench/services/notebook/browser/notebookService'; import { ISingleNotebookEditOperation } from 'sql/workbench/api/common/sqlExtHostTypes'; @@ -17,11 +17,11 @@ import { RenderMimeRegistry } from 'sql/workbench/contrib/notebook/browser/outpu export class NotebookModelStub implements INotebookModel { constructor(private _languageInfo?: nb.ILanguageInfo) { } - public trustedMode: boolean; + trustedMode: boolean; language: string; standardKernels: IStandardKernelWithProvider[]; - public get languageInfo(): nb.ILanguageInfo { + get languageInfo(): nb.ILanguageInfo { return this._languageInfo; } onCellChange(cell: ICellModel, change: NotebookChangeType): void { @@ -114,7 +114,9 @@ export class NotebookModelStub implements INotebookModel { updateActiveCell(cell: ICellModel) { throw new Error('Method not implemented.'); } - + requestConnection(): Promise { + throw new Error('Method not implemented.'); + } } export class NotebookManagerStub implements INotebookManager { @@ -125,12 +127,12 @@ export class NotebookManagerStub implements INotebookManager { } export class ServerManagerStub implements nb.ServerManager { - public onServerStartedEmitter = new Emitter(); + onServerStartedEmitter = new Emitter(); onServerStarted: Event = this.onServerStartedEmitter.event; isStarted: boolean = false; calledStart: boolean = false; calledEnd: boolean = false; - public result: Promise = undefined; + result: Promise = undefined; startServer(): Promise { this.calledStart = true; @@ -202,3 +204,170 @@ export class NotebookServiceStub implements INotebookService { throw new Error('Method not implemented.'); } } + +export class ClientSessionStub implements IClientSession { + initialize(): Promise { + throw new Error('Method not implemented.'); + } + changeKernel(options: nb.IKernelSpec, oldKernel?: nb.IKernel): Promise { + throw new Error('Method not implemented.'); + } + configureKernel(options: nb.IKernelSpec): Promise { + throw new Error('Method not implemented.'); + } + shutdown(): Promise { + throw new Error('Method not implemented.'); + } + selectKernel(): Promise { + throw new Error('Method not implemented.'); + } + restart(): Promise { + throw new Error('Method not implemented.'); + } + setPath(path: string): Promise { + throw new Error('Method not implemented.'); + } + setName(name: string): Promise { + throw new Error('Method not implemented.'); + } + setType(type: string): Promise { + throw new Error('Method not implemented.'); + } + updateConnection(connection: IConnectionProfile): Promise { + throw new Error('Method not implemented.'); + } + onKernelChanging(changeHandler: (kernel: nb.IKernelChangedArgs) => Promise): void { + throw new Error('Method not implemented.'); + } + dispose(): void { + throw new Error('Method not implemented.'); + } + get terminated(): Event { + throw new Error('Method not implemented.'); + } + get kernelChanged(): Event { + throw new Error('Method not implemented.'); + } + get statusChanged(): Event { + throw new Error('Method not implemented.'); + } + get iopubMessage(): Event { + throw new Error('Method not implemented.'); + } + get unhandledMessage(): Event { + throw new Error('Method not implemented.'); + } + get propertyChanged(): Event<'path' | 'name' | 'type'> { + throw new Error('Method not implemented.'); + } + get kernel(): nb.IKernel | null { + throw new Error('Method not implemented.'); + } + get notebookUri(): URI { + throw new Error('Method not implemented.'); + } + get name(): string { + throw new Error('Method not implemented.'); + } + get type(): string { + throw new Error('Method not implemented.'); + } + get status(): nb.KernelStatus { + throw new Error('Method not implemented.'); + } + get isReady(): boolean { + throw new Error('Method not implemented.'); + } + get ready(): Promise { + throw new Error('Method not implemented.'); + } + get kernelChangeCompleted(): Promise { + throw new Error('Method not implemented.'); + } + get kernelPreference(): IKernelPreference { + throw new Error('Method not implemented.'); + } + set kernelPreference(value: IKernelPreference) { + throw new Error('Method not implemented.'); + } + get kernelDisplayName(): string { + throw new Error('Method not implemented.'); + } + get errorMessage(): string { + throw new Error('Method not implemented.'); + } + get isInErrorState(): boolean { + throw new Error('Method not implemented.'); + } + get cachedKernelSpec(): nb.IKernelSpec { + throw new Error('Method not implemented.'); + } +} + +export class KernelStub implements nb.IKernel { + get id(): string { + throw new Error('Method not implemented.'); + } + get name(): string { + throw new Error('Method not implemented.'); + } + get supportsIntellisense(): boolean { + throw new Error('Method not implemented.'); + } + get requiresConnection(): boolean { + throw new Error('Method not implemented.'); + } + get isReady(): boolean { + throw new Error('Method not implemented.'); + } + get ready(): Thenable { + throw new Error('Method not implemented.'); + } + get info(): nb.IInfoReply { + throw new Error('Method not implemented.'); + } + getSpec(): Thenable { + throw new Error('Method not implemented.'); + } + requestExecute(content: nb.IExecuteRequest, disposeOnDone?: boolean): nb.IFuture { + throw new Error('Method not implemented.'); + } + requestComplete(content: nb.ICompleteRequest): Thenable { + throw new Error('Method not implemented.'); + } + interrupt(): Thenable { + throw new Error('Method not implemented.'); + } +} + +export class FutureStub implements nb.IFuture { + constructor(private _msg: nb.IMessage, private _done: Thenable) { + } + get msg(): nb.IMessage { + return this._msg; + } + get done(): Thenable { + return this._done; + } + setReplyHandler(handler: nb.MessageHandler): void { + return; + } + setStdInHandler(handler: nb.MessageHandler): void { + return; + } + setIOPubHandler(handler: nb.MessageHandler): void { + return; + } + registerMessageHook(hook: (msg: nb.IIOPubMessage) => boolean | Thenable): void { + return; + } + removeMessageHook(hook: (msg: nb.IIOPubMessage) => boolean | Thenable): void { + return; + } + sendInputReply(content: nb.IInputReply): void { + return; + } + dispose() { + return; + } +} From a8818ab0df6fb81d2b2bb123c73e0be94db2d6b2 Mon Sep 17 00:00:00 2001 From: Benjin Dubishar Date: Wed, 4 Dec 2019 15:02:55 -0800 Subject: [PATCH 15/19] Initial commit of Database Projects extension (#8540) * Initial commit of Database Projects extension * Removing unused references, correcting license, tabs -> spaces in package.json * cleaning up linter errors --- build/lib/extensions.js | 5 +- build/lib/extensions.ts | 5 +- extensions/sql-database-projects/.gitignore | 1 + .../sql-database-projects/.vscodeignore | 2 + extensions/sql-database-projects/README.md | 27 ++ .../images/sqlserver.png | Bin 0 -> 37585 bytes extensions/sql-database-projects/package.json | 57 ++++ .../sql-database-projects/package.nls.json | 6 + .../src/controllers/mainController.ts | 38 +++ extensions/sql-database-projects/src/main.ts | 24 ++ .../src/typings/ref.d.ts | 9 + .../sql-database-projects/tsconfig.json | 14 + extensions/sql-database-projects/yarn.lock | 256 ++++++++++++++++++ 13 files changed, 440 insertions(+), 4 deletions(-) create mode 100644 extensions/sql-database-projects/.gitignore create mode 100644 extensions/sql-database-projects/.vscodeignore create mode 100644 extensions/sql-database-projects/README.md create mode 100644 extensions/sql-database-projects/images/sqlserver.png create mode 100644 extensions/sql-database-projects/package.json create mode 100644 extensions/sql-database-projects/package.nls.json create mode 100644 extensions/sql-database-projects/src/controllers/mainController.ts create mode 100644 extensions/sql-database-projects/src/main.ts create mode 100644 extensions/sql-database-projects/src/typings/ref.d.ts create mode 100644 extensions/sql-database-projects/tsconfig.json create mode 100644 extensions/sql-database-projects/yarn.lock diff --git a/build/lib/extensions.js b/build/lib/extensions.js index 7a5d23682f02..2159e7eee428 100644 --- a/build/lib/extensions.js +++ b/build/lib/extensions.js @@ -193,7 +193,7 @@ const externalExtensions = [ // This is the list of SQL extensions which the source code is included in this repository, but // they get packaged separately. Adding extension name here, will make the build to create // a separate vsix package for the extension and the extension will be excluded from the main package. - // Any extension not included here, will be installed by default. + // Any extension not included here will be installed by default. 'admin-tool-ext-win', 'agent', 'import', @@ -203,7 +203,8 @@ const externalExtensions = [ 'schema-compare', 'cms', 'query-history', - 'liveshare' + 'liveshare', + 'database-project' ]; const builtInExtensions = process.env['VSCODE_QUALITY'] === 'stable' ? require('../builtInExtensions.json') : require('../builtInExtensions-insiders.json'); // {{SQL CARBON EDIT}} - End diff --git a/build/lib/extensions.ts b/build/lib/extensions.ts index d4de92f385ae..097a645a5f8a 100644 --- a/build/lib/extensions.ts +++ b/build/lib/extensions.ts @@ -229,7 +229,7 @@ const externalExtensions = [ // This is the list of SQL extensions which the source code is included in this repository, but // they get packaged separately. Adding extension name here, will make the build to create // a separate vsix package for the extension and the extension will be excluded from the main package. - // Any extension not included here, will be installed by default. + // Any extension not included here will be installed by default. 'admin-tool-ext-win', 'agent', 'import', @@ -239,7 +239,8 @@ const externalExtensions = [ 'schema-compare', 'cms', 'query-history', - 'liveshare' + 'liveshare', + 'database-project' ]; interface IBuiltInExtension { diff --git a/extensions/sql-database-projects/.gitignore b/extensions/sql-database-projects/.gitignore new file mode 100644 index 000000000000..dfacd4d5b489 --- /dev/null +++ b/extensions/sql-database-projects/.gitignore @@ -0,0 +1 @@ +*.vsix \ No newline at end of file diff --git a/extensions/sql-database-projects/.vscodeignore b/extensions/sql-database-projects/.vscodeignore new file mode 100644 index 000000000000..7f47d852aacf --- /dev/null +++ b/extensions/sql-database-projects/.vscodeignore @@ -0,0 +1,2 @@ +src/** +tsconfig.json diff --git a/extensions/sql-database-projects/README.md b/extensions/sql-database-projects/README.md new file mode 100644 index 000000000000..82a0cb65f4ce --- /dev/null +++ b/extensions/sql-database-projects/README.md @@ -0,0 +1,27 @@ +# Microsoft SQL Server Database Projects for Azure Data Studio + +Microsoft SQL Server Database Projects for Azure Data Studio includes: + +## Database Projects +The Database Projects extension provides a way to design, edit, and deploy schemas for SQL databases from a source controlled project. + +Please report issues and feature requests [here.](https://github.com/microsoft/azuredatastudio/issues) + +## Getting Started with Database Projects + +* Create a new database project by selecting File -> New Database Project, going to the Database Projects viewlet under Explorer, or by searching for **New Database Project** in the command palette (Ctrl + Shift + P). +* Existing database projects can be opened from File -> Open File, or via **Open Database Project** in the command palette. + +## Code of Conduct + +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. + +## Privacy Statement + +The [Microsoft Enterprise and Developer Privacy Statement](https://privacy.microsoft.com/en-us/privacystatement) describes the privacy statement of this software. + +## License + +Copyright (c) Microsoft Corporation. All rights reserved. + +Licensed under the [Source EULA](https://raw.githubusercontent.com/Microsoft/azuredatastudio/master/LICENSE.txt). diff --git a/extensions/sql-database-projects/images/sqlserver.png b/extensions/sql-database-projects/images/sqlserver.png new file mode 100644 index 0000000000000000000000000000000000000000..d884faa14a804f63aa2a9d365fc75c09c12eeeb5 GIT binary patch literal 37585 zcmeFZg;$kd*ELK^Nq2WE-Q6YK-62RycXyW{E!|yGQi7l~NJyt39n$#j^Ygyn=f2;+ z;QPjS#yC0<$2n(T``T-*x#pbfidI#YMMWk;hJu1Zm6wxJhk}BofP#X4hxiQqCdhA( z5&VPDR#H+`UQ&`m^{tb&t-Tc#6hpFwsj0f$3x+RdSf-|5#+VtA-+HP?Ma8L`2KM%T zrWoz*>m4`E%``MzC%{-AfP!Nq!u9JM>P0MM{T1@9KW!MLU0ppkHTaU8VqBSpfyvC& z^cz%T`qx*;VuxfD1~iPs-;ANm@u5i(EuEknW+)G_O}R0Clh$V?QP@!2!zPVoz*%z! zA8x*itcMq~FYl(H_?elKpH>NV-W~Cwq$Hd!I)DOEL7iDatl2d{+)B;bM6(O_Z! zXoh8GQNo4>iNFr@@Q;*8Td7(QypJO!Ksu?3Dt|4Eib6y;^G%ST= z=HjFeI95#R(+pEDD!xpLmF|5CoaioFy*_&Gdvp=E_wapBd%~nVK*SE<9AtUDh_h(WB_adr7BR%J4ruy&yD1hm=`R}#LQLw>iior;Z z#r!{C29`?rpX&ra|L;=&cUS-Stp0CU{eM1LQ3ii{x_gxwIwBtghXwaru4~=oKeB6Z zE?m#!Z-c8o1oCo1PV4fLR~eO7U*A>46Z2Nq7_?Q2KHW&-GHWZQGpI?VzckP$^;!?E z^tn2yG#iLGXx~cEpwIIvT=KtO@x&X_p%H@-L_FMmy?B!j?Gks(j<3F-8?TtK3zuams#YL|DV@AdIPk|dfM?W=d1 zzQ-TS1b!BT&VI%>sV-G~Z?4DGl>AQ-f>VNuphy&bYOmBNOYphbh!j>62|0%SMIGFZ zS%9RyEE9Kxnhu3Vy6UwRZ}%mM^5EptALYe&tqwmb*)4|3-|mc-C=L++UE>B8ct81a zc#(W6-R*SJVv|jkMFRg&Mx!1B(%^e2Pq9rQAA8>dxO#=}4HmLjKZ_DewL~V%dDr|j zxZiwNe7xC83~o(M|GThE%$v#qx2GPZ&Gp@8w9E9xs2a^uE}&yE>Tb)BK_jHXj*sC6pu;YrQ*Uigu03P2&Qu zv}z6cv#HMMSX*oMzP-0tSz|HkC>Q>HIz9}u%<{cjPs3rcQF@s!2Z?_tO=IvjGx(p{ z+UpDBT=t{cRs-HiCjdpM{(`IbU&C!xL}5lo-_p^-@r?dnXX1p)k=^w@orC%x^dUwGqYLME)H083 zl-gCHRum!dcsZlBH589)Ont{smHX%XD=V@fGYUS7Cfmo5#h2e&8Nos;kW``N$A&Tl zPYY(7Y*e(W33&5UppENI#c8%)Q+$uCIqbe(ZPk09_;De;?xjKLSm5tPo6hUN`y*bRDev`Qgz3^fC9E$tFgfrDLU#*yW7hC=>$}g4SAw8n zVd`dzCEy5~aY>BJ{vxL^+4Y|LV~wa=O3W8Ho4iPL?eEhPLw4RVsP(%qCafeqLnXrL z`aBPo`v(Gz!eq@R3&bgBi5{8eYYfVU5=e(0o-uXZrJ}T2vas_tqY8oCZxS_6 zw??1a{bG#wBqai0j=~WvIgHDkjXPzjIAT_Ft?Kbt-KSaVM4?SzVPj3_&$v3}=pr{_ zeas3>{JWVJvz;eBRka}JYRY@%{&OHGpkV4gJ$017Q%Gm3xEL2yNgGZajKycwzFu}s z6uA3Yq*bHeGW)V^xlrWk#$tyrfqbq(5eqQ~YUT7OC*2j|eSx$Y{e z{aI>rB40Wlw#oM|J@vf-C%p=!$*85VK^hER$S#sJK(an0!_^vfMl8gfRt12l8*mDf zMmdUc0Jfn~llGEI1I&(Kef>n~(3&cq{=54c> z*!^Ua%ykpeAEP+`DmkYqy}yrseD3_Uuz{v;nxeEr{raGq)gFAi9}%?1ge(ZWILmFP z{Q5)nLrHYu=C3W5_=?_iU%$Tjn{97pO+kt)ZJ&znV88mJL6dtw>d&{%rpDG`>yl&EkF|wNk0qRLUTX4GzHqc#ru6i-YnFCT}`O z-8(zMoH>uZ8%K&TZEaY2??<=%|5p1JDs+AsZ*O% zso!GPK_htFS7Y3h3x5j=!R*heB2PBC>q6U^QoA3m4*MymWdG`WaHvkf!NK#%-$zxf zX8_mJhXGl)O?6iO3D38hLgEmX}scw>yXx!RN%EDKg@ZsTqH(d;-6s{Qgi?Q#K zl|I)Ds1}y;U>)zEG_2MdtzzxwDzz$qbQ%sLi*AN&hxT$t(;!k)C22s9MAC=)Lkn&R`R_FbvB(4*mQi;g#HGDAGYJ}0Ah~na4et8Q zLj8xOX4_OXAG-f+prUv{S0)mV-Wq@|bSgGiAP&1l^u5-o%GmErdh60fF$$Mae!Z`0 zLd4*x^@pRkRUY23i&ggD+X2A_y=hRm7#iyRZb&~g6booMUQbfUpD&d7|Eg)5-TWLI z|HJ?3cCXVm@Zn5$OGr`fUx)y53NVcRIWE}%L5#ipXfd2nNF?%jsc|(WOCEhb$an^_ zXIwT4tNBa)X6Kndmoq9ux0y-*1mfRrkD>zv^bHr!r$0}f25-B4u9i8Ur`Wx9$F~Gk z)DB$g5JQK@QDqG7{R#EmSJqDidlnM^iI{$Zo!gvQpuxewSp(HT?IkvyqH2|P4c1Py zWgE;t$0}X-#w*oJ6o$AT!I3=x ztLIiGj|_u^^3R}02xj@c78uy}hRp^R8{q0VXKm+k2dS-V^gx30*?)B2+l;}&=gn(9o}C3SA)_7cze-2P5`*Gk258#!yipXuT3pb*W*C3|spo=E)F$fB(F$?-B2d&5UTO*DSBzV?@4#ty;HvbZsh z2A*Aid`9eNv-O@uud(=EF@sTK$Q?TGE6L=^AZN$~?NWiV!C7x}d|2BD0+@1@}_7o z1j9n`?1bna!y4TN^I_4{h5^eOnRuc~?Hc{cfXBN70L8S2$pgCtlP^B)Jk6e7uLV}Y zqu`Z;E0^*I_rwn1nqy1Ud|AcqukT(~VK7|O51?tp6LG79Dp4gsHm zyb6G?k2b^FwTAmo;hXvLAQkQ<5D(RYI#mg>%W=EBq73I8X88T9cQ4dVy07!@?dkKJ z7|8djtRxBhGV%OMRfNu@czZN(?r3ZWrb}L}?A?Ia(TLuI4OCjrJ^?iag1QAo)iUyc_SR3s0>|z(vW!T)(7Aw!_Na;0^PQP>=CkL-o+rp)d=k6xXv~VlfB8}wi4i@ zCJ+tq8@-oyGL76_cAa?X!++$f_%jlfxYRh{*6M1-bFD;CTEP<$kzQ0$|E zU#rb{P6dy{(zZ9}2fsG^#c6*TWx*pZ&+9K23dlH!;Y9iJ(OrL@?hp3M@_EMJ_9M}k z1ISBIrjm{kYrSC0g?p)FUK-y>8~c8LDL6chZ-RaLD*l>E844MZmNOVENKB3M$rHc` z`j?u4lks20{O^WEZTVe3meU^=K8ak-YIM3O)rrN9Lh;rF9jYt0;`2BzGuTBM8;!Rr zOkBKa!bY%|t~C;rtvijmqxODEEt$jSEi?HPfX25p3XU?PU6Xb|e3itaz8Y4#Nqzp3 z+i*LJtwhZW0d&!a`l57_U%zqftG2J85lGez10SyvjM+1I1Wzx0_7?qnh`C;4AkquX zJlUMfqh`@huxz!UFw`sh6^PtOldj4#Ebijb@i!tBTMQRdv+i#>dYcQz+iXbuJb z3YTNMPLpdjJPwN6Q?UVg@t7!7&`8ks;u_Bp4qu&7EsbXPf`%Zxp^@x(<-DwC-CYZ*A?V&Jja3!K~WqG zhoNm{s;dMoZTkmRF}D4Ay66O+(2+_9n%gKDd`?%4pj2c;t@KftnJwf4SF2q zyPU8KkpE~LGS=`vOkN-TmItj~uUNZh>(i$cc>M3&rqIpvMf_7d#PrcJ)u3-#6{-=@2{{I* zY?7obl8+I-#`-ShuOcuyn=1X9NBIFp0)DV5G8$ew z1VFh1CuLEoM8{Ecr=Hv0wCj`=0zr@JFL=Uwgl2}sY^FgjEqts~-M-hw`!193sOW=2 zNBAUT-9o7CUe5Y+%0&d zm7L6qkid3%oI2g~FgFI3R-pJE#Xi0GuePymFPZghBYGh^cjIfuBs%l9Y0+vIr!0&h+|8VS7+IN*s-ruRJ>#1CS!fw#nWQro6>GF1M@i@|7>x6QxA`|v$ zFE#Da!AnM7cO=|Ri`v4}>8s_-OJ-nva>nIgI$DSq`G;0cyZ0e2z(Q_p%)_4OA2qg-Fk7W%c$Wm{(r4_c z4_JeGQ_B43cAn_aIA$!bvA8B#kB!q~KQj)ZiQCgd`sV=b1?o?o$JSeo{{vrLs%aSTo@jZ+&61Dsm0}WmY zo)Bj{ogznP+2yrXhiu{%vftN;aghwmhz}9C`Z;C(o}CLzhOtp8w!l<-=@0(0ieM>5O@f}f_J4Bs8W9VHu06p9?vu1B)b|_vEkbAnA?lmB>?iG#H_RmFrq6qE zuQUKoJx>J(0b>}T#Dn8Owaq>&&{QXDi^(LC)zpt#F)chY5L_*Dk=J~kbNxKapU_#0CUY;+@8*GuouyyA<1E=m1H!&gVRl4Y>tGYlv@-C6z3l@;Remg)r z2A?&rS>RAXvY;I|9dz8C%G|vO*k=@Qqm+1fjank7dY@T{$G`Q?qT681M0+eAt%^LH z4xrEuG$odwo1Xsu;bc=1Cx#?`>5v(3ZZ;sb&WNc{B&9k5`os99bzhZX1rFtWCQ2Oc zk*35;tQoGfcxfuuFognq9K8r!Xn)mob1^X@1gRe#L}>?ncPIq(E%AT$M>>jhisMGn z1TbMbNIe$JhWw|h5)+J*#}*WYKX9{f_HmGJ2E8!qn4J>MH0ME*xryb&HtKM5xm2(j za!I1=C5R*8pB68u3US|bpL7ws+#V6Pyem9nxHDGKBt0@3;Y)blz5>wdxHtmocIFWD zRH`0Aa#8H}m)>=rd^vz^;)}9BY58gNf|p9D=M9YmQ`YYxVyDY$TOXUx4bd#|1cZpa z74%+tE%1IANe!>#@L0EkUZYzi7#EDjd|F*}ZstVZveAP)f9t`%!EvWPxUH6XW7h<}%_!0% zRDJ0E`MR})j@Y-ozTMedU7*~5XDEcu?cnNF?*9-AM11*U?ECT~)WRDNjIvoTS?+n#bGNyUO{JaBfZ0Zk|x z(Df6=Fgubjo5z8}-rISk{+GpFQRv(^_g~)$xf77T)MhptwX^>!v?x>}BY9v$c$umD z!C1-)T^(Ayd#Rd=wr)m6RJeX3RU|Z}kyLuP;-~LXDKAEN!@BE-vB2f@<3a~^|3s|^ zdrxXiTw|gm;)*0IQSK;Uqmh-W!Nguh$4$ z5f_K9>4=6QN0Dpl(II(B1|dgL%8KG*_yxbxkp`r1z`#3%R7Kx1%*WRUrf-H&i(u3V z=D>Phnj1McB5F|jdwg&XhW-@ljimkBL8|i{=ZZ9+*kiFkXVP{d{;p1s+bR7u~us zTDJ1dovFPkHa)h~-DE}iV~RW*Zc;9FK{r1Comg{}`zE2?WI=FI=;xysFCyyfaWEEs z(G%c2RH2_Bn2*AJ%PiRBG^h`lnP1X)SLbnFn%GyAC}KAkkO{ZZl~QB)c7(Rjr-2wV zEIP{}P<5?I za>B&H7JkAkqPRQF{^CHpEY)*4{Yve1$FFH@GB-td7J@6w1up&>T#8B1)6QdS1izAD z-I#iom$CkqGv6{C?YIm*{bbt8D@pu0T69^K5i+Ks*mLsi8v=Y2zXl=^zxD=CX7SWP z`Ln}5>~zibjXluYzO(+3`~1=5mUg#q{cP-r!yHe9`Hrxv{?GM(?^`}2a&vc*1M2cu04xuH_MnZY(V$P2f z(JcC@R80hz1As^lNbm}Kl)_y8z*g9&g{1AA%~ zy;3GypKvIq46hpo*Hq$jZ6((3%W1YOqI&_eUAlC@2!P(yx6w|MIe)te0r`ak;xNYa>#owpGHqQdzU+{d!^s0#4q zB35{m5fP{IlfoPBw{sLe^(BW`%6=KjQRuwEYhnC0$kQpoxAhj#b&-qqTODRk6QD(xe{AHhXQ`lewEX}zP~wop;j$^^;{NAmSai_4f3#prm?6_#9&X|Sw2Sdg z3w$b*$s_S!(FG|DEu7YRjAoy|P?a9x)GrqX6sHtz=WYU&rUcZu?5A|1<02uOk`>Jv))qnsbF z0Vv3%`H}oAa-4QILy#llMgoBXcb?_Fp?0;d8p#@r@!I+w9~zav%=+ATgm>nPaiI%E z`1yIP4}!)>FH32Iy&zJ`l*@o!jV0Dc`$Z0`rcJvUf0E3!2eDpO(eYN*Z8-ZO%?_rQ zPFfFdo8Qc{Ov(&=lRKd51WaKvA|C@Zyeq}NjltVIBpW3RDGe&NH;vJqR22iU&3t>O zyV*`LG5wZ3_KS^$LT2<4ghqgUmL3y3mERvvDu5)+xZE^8h?<*^|Ml8JZw`@pOL@8{%qCGA?|*tc!-=kwMewcl$QE zj0w&%4S7>u&O^U=$2XbGCJeC3f@*4j2TEY@*Tj02kLjgr*!)^eGi$;F@Rm1Xpsb>+ zb~9l`TGLKtZAcouWf>^V{WV{wGa-DP{E^0rMYiuNbLFnoPzaF>kEO-1p#%UEIpiHNNPL6%eflGLxM$^!Cz%M1w+3Moo-D`kW>=j|b& zSj@Q)&7;zI{Es!}`lef{Am(kmOBRQ;a^LBYbL2WsRdY}l?mEVg;ZX4_i8MYE0b88iEd1f0b_ zrH=}64s5tQJp(E+&$nP>-?i`pTnsq%)EGB+UHzW5{a>>c)Ri!M*)-}^+Arq-G?0H+ zY-{4ZgRAu^!2jAWYMVr1y1%hKYQ?g`_sGvzrdYuaZ=kOld@&{Df??cj)9Lg58o$i# zfUZj#;j_}x#X(hFB@pD6XezRILr_>$wJJ5)|Jm>dU{8RszTvFaUi9vfSs&VSK9agY zDOSLQG?7n7ZK@HIkX&X&jcsGu@OQDX51Nkxl4(1jEYD-f_TZ>(4bS^3^^#LdzXB%W zEu@%Lw(A;Zh)_G1#RUQZEO?m)>;&Bw(+d3A5-lq%`m_52(-1n;7kZ$(G$kfXT`G<= zX*zo6_BPto*F`ue&s3{Iy=3rJ(I+6bcK6>tk3IyHu-$UIOQqZXbc((0jKpmaRtW6s z0{_ss6b8sUlcnvfKjrcC+Xd)5rXy znZhSve5Ea58`9R^9vh!+_`}tXPch*$u4wy);Sf-K%j&Zc(Zi-EKJ4>3E;fGVs>nvm zKWTrJG$FUF*YHS_gbvdE;On%yo;y2mml?P4J^@|+0EjwV%K0xgVsM#f!;r93Rup_{ zAWXecr-%H>Ss_34KF#;t;gv4#Go_F7#=h{YUi+f~1PUqhs`h-Z_;KxBAv{7^en92J z+lIJS;qx!l$ZIy>V0VVtaI#8Q`kfJ;rHFh5YFQKv*nTwv_yL|YW*ucC@169Bige$4 zEYZS6L=QEZ)+Jr;7us=&T0Gy9b?jn|8!bodtGIXZ0!I5l5&Zo`Ai-Y2e`YCT@YDVo`)zI-jtI9(*Yp^f*otC!Wm9 zWaU#?KvaNxEnCzWODEBjOf8GoopUaXQw+91Xvcq*jh-U60GJ!bzt=_Eo-aN5Y6*Do zU^ls4MY>GvVstCz75WdjCQ85aT=UnVv`#Szk-Pw#q!o471CC&IRk8>*aM{>=caJa% zQX;|67Bag|y#nfLCIDXMLyR01^#_rr`lTW1mq6psrejS*Gd80s6`MD7jsGH5t!m|` z5f$WyAl3LvtDLF_DHkdva>xkC4=l$a8IhG6H!v>3WPYdO;o%scEQjIyOkWN5G4!@h1xZ&Xh1jie5)>I$iG@(yLeh+#5R7E~Fx- z0Y8zfkI#_HwbQ?_$Uvd4{Qeo-t_N`8H0ih6zwg+2XMnT(`;KyVXfwjRwOiW)c%c>z-fN8}?TU7)i=m&OH>z z=L6`XQ5{e+0r_u&el3ApAxp_2>=FECu9DWSNPZK7P+M$gWaL*4cDFdqFLE+o8hp1T z$czBGQ3ND1oF^x@0WmUvrRB8rpx0#0xWJnIWqTxbE(9J$F{Q&ci`S`^h}+IGCl84e zJL&Fmt;@UV4$v7K_}_4Pu)!i8Vz@VaWl=OK;&qLHi2>&0qkr@Y1U|O2j0`7`{`&aK z7|!l!`D4=;$#<88!6Bg&UO864D^R#QP<$K&i-xgd2B}RqkW`)Dz%;Awm6UueklC^A z59~3>bLso2u;|uQG@pQ0dmqbLW7Mgg6G_+w4y~cna=Y3iP~KK~X?n3rQeQDrvv!1> zC}zft=L+g{-yKvPWLhMg73#Ld0rjX78ul4MhRw{McIHZT+N)kz^x{{ZTl6tV@)QA( zeQ^5b(M-TDKAOhZwB=ul4_sC{eCR34^%u|??@D2sB7k}7E9gxLvC7p;V$4Q!M|!{) z4^~r7vWz z?=Sba$bxv~?Pmg6m;YonkJWJjju0Q$p{O{T$rQ2`s^Hcm;Rbr7Q{QnH7G)y~nSsLi z!r}abWMpZ#$z>@Qi~Q7sUw%$deD~d6eqde~n=xuy(AXdni{;h}U@F1mCO;l5qB#1| zrUM*UIx4_K@O@W=FY3=p;GZAm1^{YnPZQw3w#kvci(BwR71`C0z2nv0XvJDL_0?MlITEl7?ub$2q1bGmpnsr1e=i*TB=jy zEYWBt<&<_H(^S67U^ynl%uLTSg1Fni|bMWRmlS8_gXXi zu^S6FX=-i)sUVT3Ehrxc;AAOhk%{J|geENbpM9MVn0~gq_!(fID4z%5t*8X2z#uI$ zx|=>|plQ0n7l<(3PI6+j{q`C}e4Gl%axfY|kDdcDaVYSs-pGG=&uRNhax~S;=!|yuFSeBDQC4Ki5 zxB=wy*8@F?c^&Q3SdtMV+i6#kux|@b@nfk$kCEY`vCg($UX;KAAO|RlXArYifJN)8 z*aoeYLg`qe+Om$*Ro|0}LWFl_66^rSYU+7>3;^8~GqJ(75%Oku8eA5SL)N)*B{4({ zsx;^#qkskocsrp%Z4Zq|4HmcDH zUGU}^EkionUh)?JM3`e_DHyYD$B=T|rsa2||757YeNr%NS0Oa@32zt2vYoHZrSX1k zizy1%NeKK97;Sx)^jLxvB?{?S>7>IpaHL@fGSP!OgvpoDwjjYG14E8_rmX$i2Fm@U zBs#a_pG zj0#aPen7ZS=PGP3hatzZ7)BjuR)jC*jv^t$5AHN1)|g@a^uS?(|6!roaYewQBLr!^ zn!<6MP6RX-4cYsg6GBaW1kF)g9Ye}mMvbl# zso-fFxZ7vwWI12RVn2bO*^^8|q~(=@#%0u-Hgf-!2uByDzp`{HYL%-}T69Q-0Ksm= zpWo;5=U_=(U_&Fy+HIOCqTA0&@k2Zu=6ztsLM{!irlp0>o#7zN(5~y;OK6=B8-^<(U5F-wV*O@g=+$~C`Z3!U*FMeivN2G8>Ex+1WDeeW z-v2(5@ekrC zDTHRcD5LlYz_S_`3@*5DA-v2Q`?LkvJ`B zDD*Joa6ACZQUUBCw5u}bCm@5?(g>FQ9Hh-Kq!Mu>O&>o2IIggC9jdcpL5O-n=prtJ zcYxvR-PJFxKTnU+N`vOHTw+D#AyIVip0P;t0`CFN)M~vRjVZ{WQG{g+#g&>BDX-IA zfI)A_zw@B73ebg_?>v#rN-2$krH@YItnFftjo#>~i^H(dF9KMFVmx|t2Wp2F;yIy~ zpp%ry$x3Hixxenl=S?|7X#uwU)EKD3qVYfwnNG9;qA>bIa+&hhW%6xAE4AVosa z74qa0ADUk6&(P<{*p$2X20<$jr4x6O173bi^{tyz_TUx6g3ri%%uLcnzn%;hqsk7q zef9dKFI^G$=KCA2gORp?VX(Zzu#_w)7?4tD`sxrxa8n{(+%*r^f)CJ&T;p(ak?{3ay#B{6FBB> zV0e@s3-XpX9DhOci-5|DRlk`5(k~nUS^3=k6QWNTDteCtqZTT13*QOK6&ScsW`{0It0(8}dvGb{DDZKz7AP+nb=hk`~T z2cGs^e7HI``Mxj;1qHb@C0V%F@hRq38UnVmeQZsA9~}5%qe5O7cj@*y90CAHX@@q> zN8)$dS11%c$l7yLC{dlv2Cpy=vI~`6$ZY%z?Oup@phg-!xF8%I^TzlPXM+Tx%S0Xy z;SAG+DSrn55FGbF05=LRLDE%&#FriQrkXFty23#)z1H06%385}s%D$FFa^v)xWMgp zYK`4M@CJZ3%sH%x27o-@a--q-y^rzZf9SNEQ$hWc{SErHv@VHc8zgbEPA1( z^ZvNA=9jd;s*mHx9X#B3ztp_mrXUUxuKp?x30Qh&JGh9nAM9j%&rxF1=9pXM2T7jq zQ}8;__vj=eo-?|=Bwv$L!Z~{{WvYOTG?)%HustD1yc1G(LN(u~dTA-|W@S=g2ZIJ% zU0&!A=fz1lzOkw*-9QW_)Pyp`MDeDr2RNfbl)m21L+tn=dsK&(^6=PB42f(zA^fV7 zA-d#B**v)L%+kaztV(T5ibk%y5j2Byl3ELXx6uW5?DI9n_z$7|CKDpcbo(8OY;v< z)@z7^T||%Dx0czPS)`I&etoY&b_rY$SF#HUvrB4&TqV&Ay-}lOwB1x;c!*5{uoIW5 z9QGNtGeW$m4BwH`YJrK)5O z%1?fNMk>0q$KEHkP`hvDur$#6etRk$w-#5pmAUT^hys%CdP}2E9&?Vl8HDl_#4x%m z9|$m_s$k9^uh#^ZEM*@`B3OkLNv=lAph*K$RiPKtS%+w|$^V3i+C`FjWELF_lOE{= z%*TZ36x?(_oPSjY#fVff4duJWse2C-MQ_ClwP2~i0Im`LYaiu*Pts1JiRl6oMyI}Wjx|GWs-Ye7x5_SdHIrX@3u zPlX9_H1XLl(KdXQ&a{*86*#El_W_#j`*^#XP5MGgO^)I@jPFlYZUr>#Ns~)t7Bb+s zqdTTmL@E}a9ZY8n#f6ST`S#v4yDr_LiUaHYx(eAIim4>piSVfmY-$Vy8O)WdQCnTw zWja$%ZUD&eLF9HDo7-aOIj=y|6Q&~0*e7|!G%)>RThhl}HjM%&ubycXa*Hxfzix7S zzGHMzjIAOx)$%#Ag6tcs8CII2sd0Fd7~hu|SG1^K?4HNVmh#f`|F~J5TK5Q~(jQsR zfLXonw9cT-@c{6d?i2hQuF$EXnMvh&xg38czK+8XK~snPdC%yZycep zOm<`B;1cmV=G@=PbfFuFleTH$h!tz>Ipt~U%c#gV;?j-}< z3zFBac{TfVLlzG=XWD&QsV$rtwMJeW;m3OHw)v;tJ;xot3|;xU89o3aU_WcAzX{LW zOFYRMl+p#Y@)Su+K1$Bd40i@>@YpnA9lgB4SD=Mar&900 z7vH-)-bS;cQA5w64nohvhlqqA;H## zMU`7WDhJxt7CGF6XkoR5;RkzFISK{PHA&fW`Ps$tL^~VlL0aJ@RFdW7$ujTEIG!BJju@?@bheKw_ViC!ND4wLwpsA~s zm?Bm&Bt`V+0gJ&~cS@yYCPk@R*RuB*O=OLStdwIeUcN%4DxL8?{q=?;?}pk~DvWDA z#-U8#jvG(Hb7b?r3v&4RU+_N$=V_8ClKf7%T`~e)BZ6krSzytJY<@OmpLxzQrk+vbd(MOT~KflOZyNCfTGHam7a3g6J#>5fLCjTBZWfHxD3SEfnIjG+v zE}>_=xDDG{K}&s%+|3o^dMJ=p+{bqkGC|VA`iPfl(f#=Q#{m!y<-BW$Pk0vIC9Yh8 z;W1wik)c*nmyt*#jKR>wH+9{c)0VBNaMfVgwxJ(yLXmgHEmduxq?1-AoFn-J?ld%< z0EVq4R9J*o1kN-t@%2@v*LsrAEc_XhvmB94xP1t-On2b>C4>w9!#j!_W7QB>@1OM0#iMI zaJ8!$&S;&#>)CwFtEeXqk$*J{x2N5FKU~hujCC}1Mven>EbYzbXWZ%vx3%{&25kcK9KdvK|xEYx>Iz}C44r&;M> zxu%L;g8LD@nt8KCMJmj39p`vN+ZXlO7Pv}M#JlQUxr-m&?X=E+m68-yQr*ht-ca}fFtSP#;sQ0RFp}BbD=Ozee6eJZD)T5 zX~-tBBgwh=*k6NwUqbao=9M>XlrYt;;f8Zh!uhe*)rWT%fNL(-G4_^*SX1o)=7Wb=h#R z5VHViEr@Os!HPnJzKN72L6+hlIyeSg2ri7CJe(j2TVk-F9?6$*tXSMh3Tm@gOWDk} z>>i+{ev2-##l|&+#x7bMOv&z2U0W1R4mAAZcC!|8mf#|f-&Aw$l#QGrSHbW?FE^H< z@sMdkjPP<^Nqjmx>|b%16ckSRomDEmE)R$M`64)wE#IKUE?w8<^EfdcW-q8%1bs^O zvp5m0Ooh_aqg-@UHwcM53wpiiL^(vgaG?;IjvQF(UFfM78kD4} zhfNe4*_ETC<_QFIM*8@6vrIFd#~|gIND0dR!Jc3%l-MFyOkZb2TkQf95(l@AwwmuZ zUE@ZWMYa%GVkSH{`TNWzHg9`P;B>(qk!hEWW`RERGlPbi2-x%^xy#_ zd9*<5FXSS!NBNJb6)2=_K4>zDi8E;HYsmP||9$3WnDnzKQ|NWeaw>jW8laQNw{GGu3_lFlkI^r~$P=~+cj{kasLof+MksL#CHs^nL`~QcBWPw^Ce&o9K0rW3+V4Udy zP)_cD{_y~Sxmg{^K-YbpW`#OLT0U;^v!UIG3{+R}IIUI#L9S=Zq`~~NW;7P_{BLpH54Wm5Oc zDcLFxt5HiPs3wiXqGa3-d{`*g9MQKn$d7l;9#i>Nl8&>vww8+_QdgD}FPZ=X+c+ z`tIegfWURZVld!|JR#g8doW*DhbD5f?zZUt?;O)eRiB-gS(5+WN=t|{v==-bU>4%N zrc=qQUJbZ+fQKJsU36$$S(cZ&*d5aX_E(5RP*~re|LPoY!~Z;Ofl;IM@3SVJoWwT~Hg)u^s)&n#=GH1rJ%H!3bpKOD%6~vU2u?=z{8O;}!u&w0x2b znUVs^$d4_34?LX;b=o;Fs(8?9tO`O@R5Xy#VwMFS1*!H^zh{GV${+`PCLbW zsSg>wDo0^y@8#Y}N$;Kg*RoV0yj;JKkT)+ddAS40sT}B#-s*We;ij8$@_soUQmeof z#|_34uOKsQ43uhEnGRr}T<_{|ab|@!TKxw{OE|I|oy5kf2_RoO0N*$?C`$k)$tcm(<>(MJ5WBy(I)2rn9OWn&F7CYPY+y$Ee3kX4 zRt-Ga!=lIB^wZyw-18{N$2}uveg^^*<;eLpo>HQsb~~CdP&o{nxEH$jLV91#gI@N4 ziR#o(hB8=~Q0M~ko@eIH1)IL47(dWu; zK7JnrYmK5g=Qb7WZ$|E5Fmbtd#S-p{`|t7aFZ7`5zmT~HHWMAlXuhs?)e?bKXg`nigSgeuLCy-RM z5{1s?A~b)u0d2Gvqq7LOsA~~Os&ls1K;~_LC0xm6wDzT;3*%~C_BOKF&nrtm zg;DG=A&_UA8AMu0a-_-=IEeWnB>SnGliV;}X67;TAPZ`Mem%b*uCN?9^`t?RDMQ#d zJ3yn@0j9{2=8dBD=DI%qqc@lmodtCL)(>$5#r7;N+s&ofP@7xX^ypb;{ezi&}q)s#bzIzYQKI!m>otGXV?3tW)boJqpso zSn4X7b1(FhJ-+}S=q%78mrpSGro}oTv+9r+ z1`fEch+@c0sXLhDAuLtSJuC=Cdu#OD81N~O`P2$*ypN%{W0Gk6gO8ErgGj-@mOi@f zb*NQhx(HV~20?7>?jY$f4c?rp_nv~lbniwKzm+R}U0jKt+6xe+ zgT|x>L!dDtUGVxTAoG|r&(+Q&p+u@~mYo*rDXWw%A}>>3vAFyp1%?cH{1+)&L3^aJ zBsY;0$R1``Fz|5VqAmnvyJp6L%Gg(@tp9?d?Qm|-VHKXiEIo`&v6GRYe!E)(Y4s-% zI!C9lC!e=ic^YK%@k6XUMfBh0e^ly-G*$IdU4xOnlSANgl>cEdpqZ`4??bislN2}h z`R)i$N<)Ez=FL*K${14(?*I#dj_4xi-qnIfiMrR1<%{(fyE+gB2!corM=L!1? zEX~qOzFb|E@rOk~b8D8@`Ou0`6ZCThb|MYk!F=cPVD9S_oyKZER$8gB0l&b{V)~e< z|I^-C{#CVg@g6oHDIKD8w}7cZYO`bf8o&N0UC_Z?jIPMIG2^uk9g)CG_YV%hk(I;*TaS27fDd{u8ls+I~@ zrgXsjNmbj{eol-w?!Fnxoem7rc-Gu_m}8i=WYE{?n=a(Sui(?sB=RWI&&2N^q*Ye@ zi{bT#(MjHDfr-dq#wTc>Es%WE`Qb3=g}svJAy#WW@u*j)8hjz`{15_x-r5zyDd&Z~ zN<*LaZt(385E?Hvd^nsN|4L*!waQn)OETO_*DSwg_;{C8KM7aH zb5-HbX`Sm%_lAZo9<|q?9$qlgqy}C-IGay}Veda9T8IKW@^%@G`H8&?D6N<3A$KQ- z>I$qFRvGy|2fj@8*>z_O8P{j1NTE+ev6?|*Sbu;3EdGjmJO8C8RY7ZzPxaLtEpk6UUg4}|aAtcG z=1nUykmvM&MRQ~8=|!*brEivmd2K#-6o=x%@~deR-HK~Q0N4>(HAwvue2#})n zH9nIUz3O!XuQai$?H++Xy%ZejOw%%MDI|RI>#Rk%h`P%y$=B~cQB7i7!>$OA3_Pc= zH#DHo8c?kMdu~aQDL{wzSvg3=eQAonV@Ed#-YchZ{(j#9H<<(0*X0t5^mCtLl;K%N z@iiCq0o3Y!bh&Et;bh~)q?BAym_kjyKfy)9K$@Tim0$cNZu%?640!>TPI?J6d(#3( zU?-QY-4eS4)2Z~+{U)h~cb9u8)N{dUzck12Dd813Qb_^#s?w5}n_SH{eMJPJ7bVO9 zM8ygemM#e?tn}r8hUTv&*%+Fp^J^X1T0iN*P*m2jHzWaU1TY&C)oItwIW*s*9)cm< z2WDzD@S@|_cAe%T|RkW zAC|*p8j*NEw{%q&An$FwC44eKE*hkz?hUegI(^YqB@roaXghylj>Xmp{k8e}5|M+q zqEBdns4z-&I1G2WzCB_&8rWln(>p+PVSVv)9EC;FNuQ;83)^`kls2rT`Te@cwQU3s z^tP1(>A&#Z0lBCkb^OZ3hH+sR<=#+=0@8*s@S2^z;A}OFgtkzlpIw zes`)wIGJkv_}~#Jo(sj7EJp?q{)9$8U}|sv8EG@9m87a-^c8%(S6%m7041?$aOe9p zQeOC1%dUouU+7zAx*4frQow788*!3Hl?Vc#Z8fIw4-Z+dT`PcE&)_SS^sn``TfjTW zyfCOREYqnk`FhfgIt{cfl>7U0)o-Uki7;^_Ktl}+$5W>B>UHm=#Fnw~iBU*~Xs=^8 zFM}K9_YVqoKw6~<@CRz|pO1se(Dm?)vLtb0l0!!O;~!5>028V=>VnieZp$D@QnEG% zY6{ZXA~kvb5Jsdw-0vRYsZh<8UIQ`^XF*gjUe&*~p;R+H@%z6pYDR8$y%w;KCxo51 zeQ%@rxn3rIVAApeFU39-c)_SqrUXJIM*`NXB_~DoTruRpCi(XQ0Wd>I)JB`9?At@=0h2WVs<|>?nBIZGl8Y`?DI519w>2C|> z289r~T`Gutv_8&S!*iC(foRf706n9$e+0^Kyg$Y@r&*8p10&Ju>rl`esI|9zV%tNK zEtF5AadiF&m(Mp}wmf%h2(cHu2RH6L?G;dQN@;m<1vx^J~ z4+PJ=B`XjtO=HWPAKnF=tb(+zeqd*vH60>0D&);?99ji6DDDNgfEv1wi$m9b!@`C< z-j01rrLuUA`2&Ys1{<=V_vDq>xF9hyzqE9-t8^MBFQ`QmRKv=)rN()(GI>BZNVXQJ zrZKm0N*kc6Jd|HtY>_z|x5g*<7&zmy^;?KNhVXh!Gh4a0Y9;$*a=ZrWJgC_eiOpiq z)pNeajQF1BXQ;)x1pLJ@IK|;zDl@*7VD52k<|=T$K#wW9_q!(nCrc6My&Lh38YRI6 zY=UN>;1u_sS|K4OX8hiGyeD0omxv&VD2iWSy2g{*KJ(gO6o{nOe`biU-o;lK9bFkt z4S;Z(Q{N%*80y~QJ*?^x+a}m@0p~}2hUj+_IBVn|9xkZjfi#DsAy40rKX)U1WKcgkH*wAtLa1`2B%gV;F#B7bsO{Rhr-v(0rmoiD3<1k5`5&_g&Md{ zDM9CbwaU~rPu@A^xNlZpGImnSrO%QJr{m3Mi4+)%QC^#UM0J5Tf`zqUpG!0ZDTY<` zI|G4C!NZO9EO|`p)fdTmctCy4Ru#wGB8J5?1&vjyS9T6pH4VMhm$=*BZe)JKv>5I} zyUB@Jkl$Q81lnAu7>Q#Si~zn)E(=c&Z7fLR zFxq7@55{pZkJ6C|;L?mJ&oTF<#6oq)qh?jA&uF_UUX02@B^(acE~9mcC{n38b*D4% zidqX2Z1TmY?ScMRrJ9xau8{Fg`q#*~Z|a)Qt?x8}%tA5C$CXmDQ)0YVhJt3%A#Lss zft0Z=*%pM@OO*^|--*Ei-_JcVQo6*sK-;uehSMeb-k`h!Sbh%hq$bp~FxO7rB_Ndk z;aOvj1?sqoSMRJ(lxDs<+c^|_8St&SBZsF7(HYl93di0ym;{N=6QyV*%T z%%uV=ExOvTLD~m6Io=i50UY83SNNl!q!3no);=cH#QO*^HJwMUmx5g(m60>3EH@4< z9-+xP0Y&buebf3AtA=#hN(_#cLXr0cFVwpjIP?-t9G{tWZM9pBW%_cCn-fs&n`^PQ zh`u3I?INrKa+FjW#WeJltThTglopcW=XcO#8aQ>+*KrQ{Igu<>%QCqvYFA z#KIh|+qphgeMNUl@xmke5a{hmgzoh=>B$g{-?)Tw)vM@T%no^Brj+g z(4&3JQ6W&7N}PxWU>HwnLh`+9Q(#nKr4{DQ3E*ML=uauK^+{SjUfAh8bxHwp>IKtC z=S+}^)9dBQm_8>DuDG zc;%jf;R`dqYl~p;4GKoOmLqwy)TxT87i2;ajEtS@MJd9u-vUG+P^bm1OPXF?q*k$n zwla45Z=sfM%onf9#K^v}E~Hf`xS@-7N~j9=x+UM5Ql2y+6lkcttGNSGNY`03oq-zV zm#rs$BF#6<1NMD@JhavxG*1gzS#h4#uQ>COTAfE~oiZk1k_kt8WidMZ9U;TEe|)Uf z^G4f^B_Wb|3)F0-oPYl4DtSG(Bl_V2Xzh!d2D*|#A|e?i(w^exZmYOZ!)RuzbTKee ze}spMmAF+J%R|ve6W8C<4#zp(o(F`o)0EFAqpXuLjq-I4n*eT@^ljB42*i&!uuY8h zKz_lVPcC&$M-6edHtJnA+;4iZHi5_cbJZE*si$w=J^s1wh%B8Ho?CKy^1eKb814Q~ z7d(3D;|XrSD$L=2T7OIT5dSEQjEOkeXM|C0wGy?#re$HOgjBJcdgj0jpicqq|5fyWHhfg9dm1r<+X9V&f)7IV2= zJT`~NOEI#y&9|vSi@AIY!-~Udm0D(z5wSmNCFod;dPA@_s#)%U$m$ob2cDvyD-ruo zJZ(wExH3ZM*7C2wgZRM ztZ<#B-Di_213Q{+GLt&4O2_Xk&ym|K$wSBtFL*~U1XNSR4^Dpb=O)Xxm&R&jwN%^V zXLf)SQFt?Tll1_>=(ku({!o@m0yv^Jn+;3XZjB>hT(`DOlaX9YiAAZOZss^%Di6~r zd3ogq>43P#^V5|^_<6Z0ucp3pVqT(iaO{s)Sd3nzi+8wi5RdOGcW_!K#se5k0!R+H zZMpHMaC%jhZVywcihb3kzV*#u@i8gnlMtIZ%7eslij+bwN}AkS!#@_*@do-!6Vv!L zQ!L2!jQ_bQpYsXkdg1T^*L}>va?B~p0Jh$GmUC8bQ`-|z4cb%=elFM4+0BxqrrY9k zKxIIZ1Le|-iboD~b}zyNR^2K`)+as_UdXrk)9?GQ+zyt6F2H3Lwfmtte{fVxToseN zuush!=_*?&aHS^p>Tko;MG*7ElwEWot{v>!UDl{WWR?7-v}iu|-AhdfzNg|SFasP% z3%x^7KVs#3Muvt6WK$RM-{*7T1b|~<;OYrdW9JSkajEh=3SNvY^1}8&eifk5WqJjJ zNMyDPo|QmMlAzH*T&9`^ zV8fOMiDkrf%)wrXfI&KZ{>7S6;LFV)w)xYO+y|z&w4g#et5{Insqsu%$swCjx67C7 zp`h@+kdoldJU)TcN1}xU6x&exV&qQZN;S;D6l_Jv!jgE~pSy#KHs=O@A6kMYl;AHJ zJd5O&TGb*zTPzs?XMws#UIgNljn+6c9!WnpstGNL8-s!Qtg5Y0+Ds`O&ck^e;kh`P zlXa7HjJyMJ4(0d!>GeN$W;AfIkV&#E_{DLyXHoO^!Pew_9>$J>b1?Yz;CU;snRKT= zMn>b$_+F@GA*7FY<#@Th4xCk$fqKG_+_S)u2pVo`d&&yepY%`UNn&vSz;EBmWeOQ$ zy7VbH%3k)@RX*07vzv6O-=OLA$DnS9&;GEH%}n!S9)*Sh8pU<58`~YzWj2}Wb`acp zUhY$0o%ydtfm4E}F!TG)Jp84e?%G(fHE+gt2er*?FDU29#&$47oz9-vi*JsxnrHc-|OI=$TtAPoj+l z9g&A-|3X&`f!+7t>@M~}Cy4<*E_go=ffSU!@$$l-`fISL&B8Bl(~+9@LTU@NbKWWw zI6vYg*)-Gp1~n}G5}CUW&wI?ZJd3fSKYbk|N3ZDLi@8gnSM7Zm=sE$(4GTdSeCo9r zPrg5c$Ls0h4HTSRGM9@S_DfB+X(kzlh$6VPlLkR$2j>)&qj-D7l$$P_u1i-$zE>V* zW*hwG!_QuRWndB2730y#;WYOj3)1lFyTlzKgZ_#(fP%jc!ymbZ#aTlS`J*<6w6tJM zjZA}Q5JKWw(56uY1Z-+no?-~){4x;3broM-K^YIo1fF_=`78WA-L4j6%pxGO{FCFa?d{$#n+BC_C#jw?ekc^OIXzc z&C*r|t^-F~nzlP1%bCj^4_H>}Sd>gG8GB40$Q{6`yIDW+=|k(S=`?v_B4p`078?+V zKtv~py$9TIFQ#?l1`g^x5Hw8v!p9g#V*=<|uY0;Rl5;bXi$Ihap33TSIHxp%Qm~`h z5IctT76CN|dwi5>>Bc1Pjlmaj+gE!OsVmi|UkP*10Mh9=)xZoVPh5>0=L^)PNd3I_ z1&}W~eG&uI?zf-2Sn<%!O$y4z!|@qVx^|0vo}s6+5~5?sP+PaWow$1CZ49aU9;}Ko z_sMg8jY63(m-cb>@gd7o0ABD;NiLTPX_Ntr$r24DrQ-UM>YxY^H(K(;pDRd(X)JVk zFSEmNQPMn#-{yRqzIeBO10U{Xj5KQ5Z5&X%Hk=m~X_DHi#0TGg4-z6M`m)ym2kua% zlSWV=yYri0Og8C>7P_aVY#M0`x{p_I4+=ZUzuiH2omb&;R{4YuzfJdL|Drgw$t6xtDk7DpE@UM9H%IHRb2QOID=N7>=R#iXOa zMwWH=-%B^GMhqUa!-X6w6`oBbgyHaXGg{!n7dpoU;_%e^#s{#%)12RA7Kr(Ss#kPB ztYOLf?ZSt%mO=~?yaRkV*C=E~?vn+~ewo6N{MnuFbF%ghbm1scL=yB(T%fG3x98uu zf#(vBzt1CaoG0M&7;rjLJ)veegm3jS*e!YV(voVV_YGV(S%SRRNCqfd`d0^S> zxZPcp>P!R8&LJn*vRI8NRt(}c+*Zl&7Z@SJGrl`{Ag!~JWFRw$+8V97TCl0ic!y>v zt&xTnZd!wcMF)ILo_|moJKT;6jMz4zq(OO1)>CBBsuCvs>Gr+Cv@(sPhka53f1Vf? ztV4SQW{@LOUhvoWF1ZJHSfD;E!m#{`z zh(~$!-nWWvzIL8-a0$CGTg{Rr}n*y)kek!q3m=6%WT zDD^KBL@gQ2xc9Vi(xYl%=%1@Uv_(P<*~Va|#&8PDfwsR~?jFY_TaZbJT1T z@U4g)6kk@eDO^mO&r-!EecBXv%-ktz%+eHm%?i7SHwUkJ%LgGwMj)xpA{iPuw@dh;TKRR64_e6rJzuJUp!_f_h=Ry?{d$q(o_1f z=^LmK8V?6Q`P5^bGAt|bgWUN#2_9yaBq8HcRhNFNTQwBX`lj-!OJ9ifYd~cm`_7`3 zJ>rfYsu}I)ygg}?Myk$0c6$+U-&lE46TFN$_i@opK~@sY0lkXBdxUeg$ZoNrKz4tS zKh%O;jV`0iV?xSb#K@775Vc7s(4rt4-keX8$U#hLI&?oa)p$K3;G!9evq;FqZFxdi z1XWM^5xYf>t0S7ud80RU5A~%ZcL~_9ePab|g!|!qZxd%TWx|14TL*Z6O<+f8P&Hmw z4;4{9er1f}GRq(Ghl{!q3I6h;g9ei;-k=G#AfMJK&&RDT?Q?rz>kB+c@*y-#*$1c^ zOvIj7dvI?~jnp5Z27hV$#3!h1KwiQ<8lHgx?+O%ybQlhmOH)RJ1 zdN%#KR9QP9#S%yY1WB`Mdu=YpH=v@Pw|n%h9%6?Z0}#Hu&!1dL3c)ZgzQ;o0Zh+!) z$t-sAl`z+&;Amcc{C2T7Dc;w;9yG#BuS~5?RRJ=!bk{(2AhuU49U=WO0m0se8lrjq z(es`EJgb0ip&ySv)UjZ7CbG9PoRU}NX34-haIq;@Z1s>bAuWgs@)@IQ`4-n~FLTAB z!M=sr50Axz)oKAOvB&$NQ!hQf()0w<(qQrw9T!L)zQ(zfK~JSQ+ge;5N#hQ0L2C@g z#rE0F0Oi^ekm7gS*TLRlsj~VtTbZ>RQ&3?!K0qf6iq7IAAdj9w=gk$GhfPbNPRi1D zp%VK=Dm^JalT=3Cwd6p_k*Y(tA$Uw#P!RQ=Dc=MAe47tki{x|RdZR0tG}ZJ?u!mL< zyk-VosTxgj2BNxn6x;9gd&qNgQXrdTk*erw`~-eBjy3IULKPRipTbBJC}#2Wu05!g zlBux!;2IGYU-A>ZJ9mGnm9=L>bODkW4OVAL6UKc+nW0o0joeJ3w@@T?P1r2u0}}@* zqxdnL)G0qO!N8;cQq7e#ivGz-|ELp?SOQH!JtgLw;;*u=WiUv1F6W8)-~vvJ`?=vP z3?91oD-y8Cj^XM{IU%N1DKS9r`)y6Uh)`?ThcXN(S(<_e9Ta*|-2q2qnanCV+3a8x zkL%Zj%X&m0!D_HUm6XO4ms)Db^?)$asHR(U?XhN>gNwu2)?j}h;j$t= zG+NTx0q)48_)RZ*qrJh-Q)7=ZOj|W{0pORW>i87@%Y&j;E-wp;u+!Rg2Uy6o z8!=0xc+?n9?F{Nv+;Xc^Zl3H}S6ZfRo*3}au-=Az-!~_^*>!xep7aRljml6JN?a-m z^(Hj_zaG}9@uZoxO|Xl1+)SV;lGX$w+_(PX&vCY(RuNFUIH~)Dv=i&P*1GgZx z`1GW6H4^j8R%e-K)>`n{Ze(uc8&BPgN8x{Yt1MoMtztd!tWp+v-BaBHz zS(q?@NnXl<%2~=Z9CCWs$jZfP*d7z6dg>Tl3_h)C0Feh*ZqE5W>(}0Ul=1Dh5Q)(- zKg*(8894+hMz41BLdXWA!7tQcT85jQ4geJ?L(KwKia+E-|G6k}p(|~qd zW4#1w#pZ|TfU91E64pY{J>~~6t`bW?4;^}nBmk&4P$@&rKCFhf?R`uZDB*3~t#}+T z2f9%uWWWOuAB#YME8t}vGWk?Wb;3bdJ-bYTB0J;90CFdy*4*>`Y!d}d1U0PNb^Mdz zkt^1(R9A?j#dF8c@QE=$;)h|c|MsHll={g=;!L#Q|G;qn&@)mYwl?yF2jgk5s0ui%%g#Z%lVgGoHF0u zUrnI^8`VPPgYCo*68;CJE;LkBn?k>$pCA;#MjRBsRm!H7Pc_v^JR7=4!IKe0{r2kJ z${Y2sAFNvQV}ul-w32gUvG+nm4E4NPQE*2p2=JvRS_p9-r#@+-i_59W7~?D>Nejez zJ^&I?{A!fvZBmV!FMJKPkN+ODyA7!DWBwWf&5g9Xssp8MpYP?PuCf6c;aG#`FexR( z?aBIK@09oNprs1(C=w zi6DtGW?;zreUS@Rl_&J|trk-RQsML2gl*f5rH3n9mmXl8_bg!DZ`@%JWLThr1Gv0U z;!v;!hIpOqK!J-S+^CUo4lU+dH;5SDp8zsvrwN zZFDU#6CD;c90n|*SkK^JrW)0Hgn9r=Izz>u;a6j7Tn2A~&=IH_gg!W&_7N~1-h0x| zjRa|zi=8YAll$sXko|_=QW8aj{id3f}ga;Ke+p5C$fL;wElfAeo3H$t{e)!mAV{)!UQ|=X;bH)fvqHl*ltDD=k zVId3#U$7)#Ku8g6z`5>*&gktnYH6gvJtdTN*6&pJQ-HyrUdB2>k0ME0p1ZO*UqaflU#a%-pyg9X-g$f5yI%$e>1|^IyIz?o zuXg!&^=&Ha1w4zJI~DwLt3bWoW%Ft0(ed4qh- zItY)NK1bxWq&|%5Xe8Kg`<@J6>S)!PM{1s}%{}S#C6A$m#f>3?EiC?8>h6E{5zvb~ zFoa_py}FUDe|t_9DX$#i>x%PdFFSr`j(t07(NEj=77(gZqFY7DTp$9s$Uvd~qS>&b zwB2U5f)fu9FE(1SYl8d-X!AsgQ&g1eMK*+5!=1wRR>D-3S$uP-@!Z{IRj(4^A<8lW z30@`;21TkkUHUb;<~hmJ7y{CLw9goSuNjtTY=d4OXLszuDSeg!goRfa@X*W>2)*CM;h!^JR)gG;!g`Rp z0V79US?`EeCTv0<4|&OI6Mz zbNU{Bh%XiL&5~$B4sXe~L49{TJ*pDBj_+*rxwwhwNPcG%ni zP*q4WHMb;x@98<#NqmHMW8ekVb;eJGr<2gFGt+G&G^8NBG&zBBTfhO2y7)|eUvDw@ z4SWr9nN~H$@3;9K`oG8l&)qnG(;Q{+>H*|;zk6-94I^!RC*&`=RR;qo_iLpMC+EOu2*hj{=2&5ih3D zYp~DFN;RKh(VMcEGYi-$Aws6Hv;dsb+3?rp>)9V)7x1POLc-qw&AK_*1z6PSbSdBy zh|WRfE9y<^{=HDXmgFUG!nr$3du;4$Yt*4ZgN`1STw>vEc40E5LwF@2YaV}=9BLaL zv0OvOo+g{DF|cE`UY-JNE*k(8U+`t#F@M4or=v$uuIU35ohchS0{sulCZ+ETi1SrU zA?@T9l-x19ejWE`;jzf!;Zn*jPiad60y18^x>)re&Q{n zz&64LR9B(}8&Hr@!I@`y9(C+!`y~%-Q-g~%=SY`4+aA_sH^ffq8SN{mG%$MH2 zxyz|-b6^W&uGgg=JB&uLH zBccrhb!D5AfZH=V(tw2m2dTVaS9PmpEf2};E<2dC{0ZoA$bPOq-pc5;_mq3d=onx| zehDb1^NcgLw7UAMMI`zVeO&}5k=%vMfrS(u^a{BN;9D&&n96*F^0u$<*}J(xXDU{3 z#B>XYYYC?tE~%(4N||+?-KsR9B@@g8I5_SzRw(uV)AykdFZV@+R7Q6nT}J;n0J$C? zqPdbc^+oyD$#`$QKS4qZ$k{%J+h|Y%jo#C7xN41`lXNJ9uAu)5pR4?kB$4uG&`HZ0 z6y~{h|Clm-Ba%g*1IiZX@k5~pfX$QGv8`-9lQDlb`YX%>&tgV@7hReBC47RrHe))} zbUokX@YW!Ta6lLet)K;Txrk9`5^`9!7F}N;3%Ucn8NK_Gg z4%HYCiBGNC(UY=BbDoDvt-I;hv|B7_(0S?yFLcS}$)$48d%46JqC;hIgI@=YfzErf>v}2|uRMAbw?$!mMksC&#J^%>}OoD9v57oj&U-cPjQWBr~xWH-3<=cc>N(C1lfmwr>T<@lG~WM6yNR zh*^GD&w)rrKHr}E{fPhRXHIZ>kI`4I51>sX;qCZM*?aTG0@Aa=c-mkLyM5OjIJh$H z+WsZb(zOua8_j_iZC>B`k1p5A&@hxWv-?$z&Hy5lP)Pg@z<%kMZWkKtbcLT#ez6y? zR1KiljBQ?n3cPgeqR&)>db=c{9kM7%am9ZzAm}z|e0@kU#C4iCFAu&BJ@Iez6R8G9%KH z7zX%5E)OF`@BE}lMUpwh&CZGtOHs@9rJ?CdWXPE^hg?M)2PH&EKc`K-5`luY$K7sI{CkY~IiKq6m+>&9? zLO|^N7Hkkm@>~`psfqESF%?k3#BQGMIvXx}>!-&*5?31n6uTdn;Y=kfS?2^AE|eFi zohiSVr?d|ZbJ+cPqdcHowtcZ!AY}=(oJGz^G6K)2c`M`4hjPiDI&Q^E26fn>$ZSxu zyFLeUv+?6>CMuSeL-tD6dF92=S&ki;PLcn4Tpi)BQ(q z4o0L={tlb{2rix+bD*}IFKmS{C2)wqRc&O{$nFo6sdj8JC!4}08=&WGSNjGyjC zMjql=3C&nAR0$yaI2q22MQi_hfmAt zq?bfuvn8fTen;KP)xt=V}+B1PN5n|VaMq@C9943!b=Gc7xUC-yO6;_^bT zDRt~-87UXAc>A9Kpp6(-(N;2}r0udO?yclC=Exq824#KeZ-AQbt?@6hQJpWr!(!3Q zdS>P{YXeTXPxDT#Xr)^y_B%KYpZU@V){dDp1Bd8c~X*i%8-aXY|!P7@Q8^@I7 z51yj>`bd|gEG{0XjLXP-d0N5N<(zimr&#W$BBsc5Z!S)EO4HRTL=! zEW(TysL%0&L&2uCN%ZDWir#DTKpB)q$>19bXZSjk%HK9Dx%xq%>+zOj$lL+LoPPpl z3OWTQ9Vdj2tiVO$$jx-tDx*5m*(aJ4B2n=(p-xsNu!uRZ2c$ku5#-={p&H%)mGrrTODzm&N{Cz zz~u;~lJ2UHVS!z<$n-Snh(%=u9@v&O6VA2C%eV75jk^PWaJ4E~R0CPmIwTOHi5vcH zN?oDDabfxx3!4;{8vaV&q}ZTf{=S5Plln^CL{OXaTXeBjK0sOT0BjVuha{O&qR^?p zkvg$7iQoaC!+jchd=C`FTc_VniDgcEwhwYX?!J=v>5=;b*K0O8H=tsyPFM7B1IFlUB}tEsA|WJ-85L7Op?+z6W1*J^rD_3Xq@0U zhHFC6sL}5Ha|hY#ZqEW4BIpK)ZX4I9bw<$(a)o3LI2B%ABNp4SA&dkHEa$pT^QpQB zTo<<63SSQ6ory5YHXV#auV@_KMRrdey9Kywlr?LW)qM*v;9tr5csIzX`1#BUuXYaR zsjoc8Gc5#u&RM?-f)_j7RWg!gmV*H#j4NUo6%kERVh|avFJi7Pui2{RRZ(5m4bB${{CkR40gsCB}u(^JXvO(LZxq)+_J{YZ=iYp8Lrt9!ZRl&-z7C`lyUv{HF60 zNd4n|5Lj$|=KFEju4uoz^Tc*~M+OD(wkrHs6tq?ojBZZWyFeP3UvgdJDMG%v0hev< zq%&2ZZpU^3*`0UYYkS{%viByK;>}Sy{i(qSF1vy^U@ylJ4uixY?T9_U6|6U*eZCpyJ~IC%S~(+W0yxYD4U#!C(>3QDGfRh7xJR-<0Swd} z2T?kij)Pc2u!A@@ePxrM5cl1#=fM6r_yD;#cJR(^t*q}4s#p}zX)so2ypGqSYBASbv zOz?|Ug6=ngsL%Q^F9$3{=p1rtIBb-q!j?dZuX_nu;J`-bkQeYLdxrOhGAoA1JrajD z{4osoaWP=2*VM>x^-(?cu9XMTF561!0y>T@2J@Z|^jl0#pr zPli7S-a+%s_LDDcN}tonPhb>i9OFJn6ExOapcCWQ=NTXM^i-7>+{MHQ)^L(adzFVu z++cf}Ipe)+gW6bCm6es9wx9o@3|@k6*}G*y6kbn#=a6hy+xc6A!AncNbU8^hD|=0_ zEO;@|CWe79q*d`dgs35$9sv4I>SP~4jV|aVbs5g9WsEwld;|oxN!hT#;||Bq)3PZ! zAg_h(3lgt^G9e!y!TJ2^SAF)XIJ50O-?t^%55Io!i_QA^3E`?vm8iXXDfQhDjk+cmM^r8Va+l( z7;4IiS&f96_78QE&Y<5RgZb<0Eznrnyvxu`C>xYo&4E_JfUw+@C@`fv;)OkEI4bX0G+q{bOH8%?*)@i66{agZZm{V)DUP!6ZqV@ zbQ^4oFT4{l!eRxiCa5MsEKS!(5BJYd4Q0Y8R}Fncfyo9xXx!jLZQ_VW?XT))5V$A) zpCN)?iZ!hdMCCQ5OaiPgcuJ`tyz?~kRu?OW=F0abza+uZ2O<6Qn~sz|khm9u)=8MY zz(bu1+8=mCT7u@0g@DRt4m!OUT>f9rYA6s!fNa~bkkG;u0($Cl15Lch*4aFQSwEPcq3h@9b#U1f207T{j@5C!0@VY2b{O6gFDM15xnag4Jige^M(B38;l;h1o z(+Put62p#mZD3Ni0`enGZwBGNrx_WHuL_k9jt70FdVc}|1fWnjpxk4??TDH^1wApJ zo9kT5JN}%eOZ;afg@(Y>%)f^pZ|lakoGwXBVK)n#w;=71g%*RLZBbZ37CMj^sa*7* zKaqfW;#ndiy}S1WK91}Muz-1j+ z;nDyc;{|UK<-c=~2s#HhwJnBM;DJKY7@C3m%%Z>VjTX409%2x<|L;^0Y6i*cSv^wK$TcnntLkbmaMP$=}#e`p}IF+k$5{L$G8%4X2A`pSSV zIG*h-)R*3ZLr!b7u=u}M9sxfGOW$8Bk)nRl;Khx31|~|c(j?~peCh+~Ci-`ofc^+JbXq^Ne(v}0 z%MLzO5=w^q-#=jjKEoE37yX}4L2qlP|31>c2S6$UECD7(R!IL&S?~oRSY!16tYZJU zl~6FC(rBDd|Cw}u{dYRD;eW>QuP>6Zfp%9rTO7z2GydpwyuM`4koxbLW5V{{Ii&{~x^nXEV?D5s~v?)3G@E RTp9-alNMJHs}M2r`#&r#oSOgu literal 0 HcmV?d00001 diff --git a/extensions/sql-database-projects/package.json b/extensions/sql-database-projects/package.json new file mode 100644 index 000000000000..05f269b6c612 --- /dev/null +++ b/extensions/sql-database-projects/package.json @@ -0,0 +1,57 @@ +{ + "name": "sql-database-projects", + "displayName": "SQL Server Database Projects", + "description": "SQL Server Database Projects for Azure Data Studio designing and deploying database schemas.", + "version": "0.1.0", + "publisher": "Microsoft", + "preview": true, + "engines": { + "vscode": "^1.30.1", + "azdata": ">=1.12.0" + }, + "license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/master/LICENSE.txt", + "icon": "images/sqlserver.png", + "aiKey": "AIF-c5594e2d-38b5-4d3b-ab1b-ed5d4fe8ee40", + "activationEvents": [ + "*" + ], + "main": "./out/main", + "repository": { + "type": "git", + "url": "https://github.com/Microsoft/azuredatastudio.git" + }, + "extensionDependencies": [ + "Microsoft.mssql" + ], + "contributes": { + "commands": [ + { + "command": "sqlDatabaseProjects.new", + "title": "%sqlDatabaseProjects.new%", + "category": "%sqlDatabaseProjects.displayName%" + }, + { + "command": "sqlDatabaseProjects.open", + "title": "%sqlDatabaseProjects.open%", + "category": "%sqlDatabaseProjects.displayName%" + } + ], + "menus": { + "commandPalette": [ + { + "command": "sqlDatabaseProjects.new" + }, + { + "command": "sqlDatabaseProjects.open" + } + ] + } + }, + "dependencies": { + "vscode-nls": "^3.2.1" + }, + "devDependencies": { + "tslint": "^5.8.0", + "typescript": "^2.6.1" + } +} diff --git a/extensions/sql-database-projects/package.nls.json b/extensions/sql-database-projects/package.nls.json new file mode 100644 index 000000000000..cb5a11b152e3 --- /dev/null +++ b/extensions/sql-database-projects/package.nls.json @@ -0,0 +1,6 @@ +{ + "sqlDatabaseProjects.displayName": "Database Projects", + "sqlDatabaseProjects.description": "Design and deploy SQL database schemas", + "sqlDatabaseProjects.new": "New Database Project", + "sqlDatabaseProjects.open": "Open Database Project" +} diff --git a/extensions/sql-database-projects/src/controllers/mainController.ts b/extensions/sql-database-projects/src/controllers/mainController.ts new file mode 100644 index 000000000000..32c316ea850a --- /dev/null +++ b/extensions/sql-database-projects/src/controllers/mainController.ts @@ -0,0 +1,38 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; + +/** + * The main controller class that initializes the extension + */ +export default class MainController implements vscode.Disposable { + protected _context: vscode.ExtensionContext; + + public constructor(context: vscode.ExtensionContext) { + this._context = context; + } + + public get extensionContext(): vscode.ExtensionContext { + return this._context; + } + + public deactivate(): void { + } + + public activate(): Promise { + this.initializeDatabaseProjects(); + return Promise.resolve(true); + } + + private initializeDatabaseProjects(): void { + vscode.commands.registerCommand('sqlDatabaseProjects.new', () => { console.log('new database project called'); }); + vscode.commands.registerCommand('sqlDatabaseProjects.open', () => { console.log('open database project called'); }); + } + + public dispose(): void { + this.deactivate(); + } +} diff --git a/extensions/sql-database-projects/src/main.ts b/extensions/sql-database-projects/src/main.ts new file mode 100644 index 000000000000..002986d3242b --- /dev/null +++ b/extensions/sql-database-projects/src/main.ts @@ -0,0 +1,24 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import MainController from './controllers/mainController'; + +let controllers: MainController[] = []; + +export async function activate(context: vscode.ExtensionContext): Promise { + // Start the main controller + let mainController = new MainController(context); + controllers.push(mainController); + context.subscriptions.push(mainController); + + await mainController.activate(); +} + +export function deactivate(): void { + for (let controller of controllers) { + controller.deactivate(); + } +} diff --git a/extensions/sql-database-projects/src/typings/ref.d.ts b/extensions/sql-database-projects/src/typings/ref.d.ts new file mode 100644 index 000000000000..cfdf5dd135fa --- /dev/null +++ b/extensions/sql-database-projects/src/typings/ref.d.ts @@ -0,0 +1,9 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/// +/// +/// +/// diff --git a/extensions/sql-database-projects/tsconfig.json b/extensions/sql-database-projects/tsconfig.json new file mode 100644 index 000000000000..d912ca51339e --- /dev/null +++ b/extensions/sql-database-projects/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../shared.tsconfig.json", + "compileOnSave": true, + "compilerOptions": { + "outDir": "./out", + "lib": [ + "es6", + "es2015.promise" + ] + }, + "exclude": [ + "node_modules" + ] +} diff --git a/extensions/sql-database-projects/yarn.lock b/extensions/sql-database-projects/yarn.lock new file mode 100644 index 000000000000..251921e01764 --- /dev/null +++ b/extensions/sql-database-projects/yarn.lock @@ -0,0 +1,256 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/code-frame@^7.0.0": + version "7.5.5" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.5.5.tgz#bc0782f6d69f7b7d49531219699b988f669a8f9d" + integrity sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw== + dependencies: + "@babel/highlight" "^7.0.0" + +"@babel/highlight@^7.0.0": + version "7.5.0" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.5.0.tgz#56d11312bd9248fa619591d02472be6e8cb32540" + integrity sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ== + dependencies: + chalk "^2.0.0" + esutils "^2.0.2" + js-tokens "^4.0.0" + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +builtin-modules@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" + integrity sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8= + +chalk@^2.0.0, chalk@^2.3.0: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + +commander@^2.12.1: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +diff@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.1.tgz#0c667cb467ebbb5cea7f14f135cc2dba7780a8ff" + integrity sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q== + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +glob@^7.1.1: + version "7.1.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.13.1: + version "3.13.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" + integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +minimist@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" + integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= + +mkdirp@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" + integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= + dependencies: + minimist "0.0.8" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +path-parse@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" + integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== + +resolve@^1.3.2: + version "1.13.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.13.1.tgz#be0aa4c06acd53083505abb35f4d66932ab35d16" + integrity sha512-CxqObCX8K8YtAhOBRg+lrcdn+LK+WYOS8tSjqSFbjtrI5PnS63QPhZl4+yKfrU9tdsbMu9Anr/amegT87M9Z6w== + dependencies: + path-parse "^1.0.6" + +semver@^5.3.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +tslib@^1.8.0, tslib@^1.8.1: + version "1.10.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" + integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ== + +tslint@^5.8.0: + version "5.20.1" + resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.20.1.tgz#e401e8aeda0152bc44dd07e614034f3f80c67b7d" + integrity sha512-EcMxhzCFt8k+/UP5r8waCf/lzmeSyVlqxqMEDQE7rWYiQky8KpIBz1JAoYXfROHrPZ1XXd43q8yQnULOLiBRQg== + dependencies: + "@babel/code-frame" "^7.0.0" + builtin-modules "^1.1.1" + chalk "^2.3.0" + commander "^2.12.1" + diff "^4.0.1" + glob "^7.1.1" + js-yaml "^3.13.1" + minimatch "^3.0.4" + mkdirp "^0.5.1" + resolve "^1.3.2" + semver "^5.3.0" + tslib "^1.8.0" + tsutils "^2.29.0" + +tsutils@^2.29.0: + version "2.29.0" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.29.0.tgz#32b488501467acbedd4b85498673a0812aca0b99" + integrity sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA== + dependencies: + tslib "^1.8.1" + +typescript@^2.6.1: + version "2.9.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.9.2.tgz#1cbf61d05d6b96269244eb6a3bce4bd914e0f00c" + integrity sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w== + +vscode-nls@^3.2.1: + version "3.2.5" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.5.tgz#25520c1955108036dec607c85e00a522f247f1a4" + integrity sha512-ITtoh3V4AkWXMmp3TB97vsMaHRgHhsSFPsUdzlueSL+dRZbSNTZeOmdQv60kjCV306ghPxhDeoNUEm3+EZMuyw== + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= From f5ce7fb2a5194bb5df6db081fd6e488d11c2dc23 Mon Sep 17 00:00:00 2001 From: Anthony Dresser Date: Wed, 4 Dec 2019 19:28:22 -0800 Subject: [PATCH 16/19] Merge from vscode a5cf1da01d5db3d2557132be8d30f89c38019f6c (#8525) * Merge from vscode a5cf1da01d5db3d2557132be8d30f89c38019f6c * remove files we don't want * fix hygiene * update distro * update distro * fix hygiene * fix strict nulls * distro * distro * fix tests * fix tests * add another edit * fix viewlet icon * fix azure dialog * fix some padding * fix more padding issues --- .github/ISSUE_TEMPLATE/config.yml | 1 + .vscode/launch.json | 19 +- .vscode/settings.json | 1 + .vscode/tasks.json | 6 +- .yarnrc | 2 +- build/azure-pipelines/common/createAsset.ts | 132 + build/azure-pipelines/common/createBuild.ts | 60 + build/azure-pipelines/common/releaseBuild.ts | 70 + build/azure-pipelines/common/sync-mooncake.ts | 69 +- .../darwin/continuous-build-darwin.yml | 12 +- .../darwin/product-build-darwin.yml | 2 +- build/azure-pipelines/darwin/publish.sh | 12 +- build/azure-pipelines/distro-build.yml | 2 +- build/azure-pipelines/exploration-build.yml | 2 +- .../linux/continuous-build-linux.yml | 16 +- build/azure-pipelines/linux/frozen-check.js | 40 - .../linux/product-build-linux-multiarch.yml | 2 +- .../linux/product-build-linux.yml | 28 +- build/azure-pipelines/linux/publish.sh | 14 +- .../linux/snap-build-linux.yml | 6 +- build/azure-pipelines/product-build.yml | 4 +- build/azure-pipelines/product-compile.yml | 43 +- .../publish-types/check-version.ts | 4 +- .../publish-types/publish-types.yml | 2 +- build/azure-pipelines/release.yml | 2 +- build/azure-pipelines/sync-mooncake.yml | 2 +- .../azure-pipelines/web/product-build-web.yml | 2 +- build/azure-pipelines/web/publish.sh | 5 +- .../win32/continuous-build-win32.yml | 6 +- .../win32/product-build-win32.yml | 2 +- build/azure-pipelines/win32/publish.ps1 | 9 +- build/gulpfile.editor.js | 76 +- build/gulpfile.extensions.js | 9 +- build/gulpfile.hygiene.js | 11 +- build/gulpfile.vscode.js | 6 +- build/lib/builtInExtensions.js | 23 +- build/lib/compilation.js | 2 +- build/lib/compilation.ts | 2 +- build/lib/i18n.resources.json | 14 +- build/lib/standalone.js | 4 +- build/lib/standalone.ts | 5 +- build/lib/treeshaking.js | 16 +- build/lib/treeshaking.ts | 16 +- build/lib/util.js | 25 + build/lib/util.ts | 25 + build/lib/watch/index.js | 9 +- build/lib/watch/package.json | 5 +- build/lib/watch/yarn.lock | 1183 +----- build/monaco/monaco.d.ts.recipe | 2 +- build/monaco/monaco.usage.recipe | 23 +- build/npm/postinstall.js | 45 - build/npm/preinstall.js | 4 +- build/package.json | 9 +- build/win32/i18n/messages.de.isl | 3 +- build/win32/i18n/messages.en.isl | 3 +- build/win32/i18n/messages.es.isl | 3 +- build/win32/i18n/messages.fr.isl | 3 +- build/win32/i18n/messages.hu.isl | 3 +- build/win32/i18n/messages.it.isl | 3 +- build/win32/i18n/messages.ja.isl | 3 +- build/win32/i18n/messages.ko.isl | 3 +- build/win32/i18n/messages.pt-br.isl | 3 +- build/win32/i18n/messages.ru.isl | 3 +- build/win32/i18n/messages.tr.isl | 3 +- build/win32/i18n/messages.zh-cn.isl | 3 +- build/win32/i18n/messages.zh-tw.isl | 3 +- build/yarn.lock | 201 +- cgmanifest.json | 6 +- extensions/admin-tool-ext-win/package.json | 2 +- extensions/admin-tool-ext-win/yarn.lock | 8 +- extensions/agent/package.json | 2 +- extensions/agent/yarn.lock | 8 +- extensions/azurecore/package.json | 2 +- extensions/azurecore/yarn.lock | 7 +- extensions/cms/package.json | 2 +- extensions/cms/yarn.lock | 8 +- extensions/configuration-editing/package.json | 5 +- .../schemas/attachContainer.schema.json | 14 + .../schemas/devContainer.schema.json | 33 +- .../configuration-editing/src/extension.ts | 57 - extensions/configuration-editing/yarn.lock | 8 +- extensions/dacpac/package.json | 2 +- extensions/dacpac/yarn.lock | 8 +- extensions/extension-editing/package.json | 2 +- extensions/extension-editing/yarn.lock | 8 +- extensions/git-ui/package.json | 8 +- extensions/git-ui/src/main.ts | 4 +- extensions/git-ui/yarn.lock | 8 +- extensions/git/package.json | 206 +- extensions/git/package.nls.json | 14 +- extensions/git/src/api/api1.ts | 5 + extensions/git/src/api/git.d.ts | 2 + extensions/git/src/askpass-main.ts | 42 +- extensions/git/src/askpass.ts | 95 +- extensions/git/src/commands.ts | 351 +- extensions/git/src/contentProvider.ts | 2 +- extensions/git/src/decorationProvider.ts | 1 + extensions/git/src/encoding.ts | 2 - extensions/git/src/fileSystemProvider.ts | 199 + extensions/git/src/git.ts | 79 +- extensions/git/src/ipc/ipcClient.ts | 45 + extensions/git/src/ipc/ipcServer.ts | 106 + extensions/git/src/main.ts | 28 +- extensions/git/src/model.ts | 28 +- extensions/git/src/repository.ts | 103 +- extensions/git/src/typings/jschardet.d.ts | 11 - extensions/git/src/uri.ts | 27 +- extensions/git/src/util.ts | 19 +- extensions/git/yarn.lock | 16 +- extensions/image-preview/media/main.css | 1 + extensions/image-preview/media/main.js | 43 +- extensions/image-preview/package.json | 3 +- .../src/binarySizeStatusBarEntry.ts | 57 + extensions/image-preview/src/extension.ts | 14 +- .../image-preview/src/ownedStatusBarEntry.ts | 31 + extensions/image-preview/src/preview.ts | 51 +- .../image-preview/src/sizeStatusBarEntry.ts | 23 +- .../image-preview/src/zoomStatusBarEntry.ts | 25 +- extensions/import/package.json | 2 +- extensions/import/yarn.lock | 8 +- .../integration-tests/.vscode/launch.json | 2 +- .../client/src/jsonMain.ts | 22 +- .../json-language-features/package.json | 6 +- .../server/package.json | 13 +- .../server/src/jsonServerMain.ts | 130 +- .../json-language-features/server/yarn.lock | 104 +- extensions/json-language-features/yarn.lock | 55 +- extensions/json/syntaxes/JSON.tmLanguage.json | 4 +- extensions/liveshare/package.json | 2 +- extensions/liveshare/yarn.lock | 8 +- .../syntaxes/markdown.tmLanguage.json | 104 +- .../markdown-language-features/media/index.js | 4 +- .../media/markdown.css | 9 - .../markdown-language-features/media/pre.js | 4 +- .../markdown-language-features/package.json | 26 +- .../preview-src/index.ts | 2 +- .../preview-src/scroll-sync.ts | 47 +- .../src/commands/showPreview.ts | 9 +- .../src/extension.ts | 4 +- .../src/features/documentLinkProvider.ts | 6 +- .../src/features/preview.ts | 135 +- .../src/features/previewManager.ts | 181 +- .../markdown-language-features/src/logger.ts | 14 +- .../src/markdownEngine.ts | 11 +- .../src/test/index.ts | 2 +- .../src/util/links.ts | 2 +- .../src/util/topmostLineMonitor.ts | 21 +- .../markdown-language-features/yarn.lock | 3356 +++++------------ extensions/merge-conflict/package.json | 2 +- .../src/documentMergeConflict.ts | 4 +- extensions/merge-conflict/yarn.lock | 8 +- extensions/notebook/package.json | 2 +- extensions/notebook/yarn.lock | 8 +- extensions/package.json | 2 +- extensions/python/cgmanifest.json | 2 +- .../syntaxes/MagicPython.tmLanguage.json | 4 +- .../test-freeze-56377_py.json | 30 +- .../python/test/colorize-results/test_py.json | 138 +- extensions/resource-deployment/package.json | 2 +- .../src/services/platformService.ts | 2 +- .../resource-deployment/src/typings/ref.d.ts | 1 - extensions/resource-deployment/yarn.lock | 8 +- extensions/schema-compare/package.json | 2 +- extensions/schema-compare/yarn.lock | 8 +- extensions/search-result/README.md | 3 + .../search-result/extension.webpack.config.js | 23 +- extensions/search-result/package.json | 64 + extensions/search-result/package.nls.json | 5 + extensions/search-result/src/extension.ts | 186 + .../search-result/src/media/refresh-dark.svg | 4 + .../search-result/src/media/refresh-light.svg | 4 + .../search-result/src/typings/refs.d.ts | 5 +- .../syntaxes/searchResult.tmLanguage.json | 18 + extensions/search-result/tsconfig.json | 9 + extensions/search-result/yarn.lock | 602 +++ .../theme-abyss/themes/abyss-color-theme.json | 5 +- .../theme-defaults/themes/dark_plus.json | 3 +- extensions/theme-defaults/themes/dark_vs.json | 5 +- .../theme-defaults/themes/light_plus.json | 3 +- .../theme-defaults/themes/light_vs.json | 5 +- .../themes/monokai-color-theme.json | 61 +- extensions/vscode-colorize-tests/package.json | 10 +- .../src/colorizerTestMain.ts | 47 + extensions/vscode-colorize-tests/src/index.ts | 2 +- .../src/typings/ref.d.ts | 4 +- extensions/vscode-colorize-tests/yarn.lock | 13 +- extensions/vscode-test-resolver/package.json | 6 +- .../vscode-test-resolver/src/extension.ts | 6 +- extensions/vscode-test-resolver/yarn.lock | 8 +- extensions/yaml/package.json | 4 +- extensions/yarn.lock | 8 +- package.json | 53 +- remote/package.json | 27 +- remote/web/package.json | 11 +- remote/web/yarn.lock | 37 +- remote/yarn.lock | 133 +- scripts/code-web.js | 63 +- scripts/code.bat | 5 +- scripts/test-integration.bat | 3 +- src/bootstrap-fork.js | 21 +- src/bootstrap-window.js | 2 +- src/bootstrap.js | 51 +- src/main.js | 25 +- .../browser/ui/selectBox/media/selectBox.css | 5 + src/sql/media/actionBarLabel.css | 6 - .../platform/tasks/browser/tasksRegistry.ts | 11 +- src/sql/platform/theme/common/styler.ts | 3 +- .../api/browser/mainThreadNotebook.ts | 4 +- .../mainThreadNotebookDocumentsAndEditors.ts | 10 +- src/sql/workbench/browser/modal/modal.ts | 3 +- .../workbench/browser/modal/optionsDialog.ts | 6 +- .../modelComponents/diffeditor.component.ts | 2 +- .../modelComponents/editor.component.ts | 6 +- .../browser/modelComponents/modelViewInput.ts | 13 +- .../modelComponents/queryTextEditor.ts | 12 +- .../browser/modelComponents/tree.component.ts | 6 +- .../modelComponents/treeComponentRenderer.ts | 5 +- .../modelComponents/webview.component.ts | 2 +- .../browser/parts/views/customView.ts | 6 +- .../common/editorReplacerContribution.ts | 6 +- .../workbench/common/languageAssociation.ts | 4 +- .../contrib/accounts/browser/accountDialog.ts | 8 +- .../contrib/charts/browser/actions.ts | 4 +- .../test/electron-browser/commandLine.test.ts | 5 +- .../browser/connection.contribution.ts | 8 +- .../common/connectionProviderExtension.ts | 10 +- .../dashboardWidgetWrapper.component.ts | 4 +- .../contrib/dashboard/browser/core/actions.ts | 13 +- .../browser/widgets/explorer/explorerTree.ts | 2 +- .../browser/connectionViewletPanel.ts | 6 +- .../browser/dataExplorer.contribution.ts | 4 +- .../browser/dataExplorerViewlet.ts | 8 +- .../media/dataExplorer.contribution.css | 11 +- .../test/browser/dataExplorerViewlet.test.ts | 10 +- .../editData/browser/editDataEditor.ts | 8 +- .../contrib/editData/browser/editDataInput.ts | 13 +- .../browser/cellViews/code.component.ts | 10 +- .../notebook/browser/models/notebookInput.ts | 31 +- .../notebook/browser/notebook.contribution.ts | 6 +- .../browser/outputs/gridOutput.component.ts | 4 +- .../common/models/nodebookInputFactory.ts | 8 +- .../common/models/untitledNotebookInput.ts | 13 +- .../test/browser/notebookEditorModel.test.ts | 2 +- .../electron-browser/contentManagers.test.ts | 4 +- .../profiler/browser/profilerEditor.ts | 6 +- .../contrib/profiler/browser/profilerInput.ts | 30 +- .../browser/profilerResourceEditor.ts | 12 +- .../contrib/query/browser/gridPanel.ts | 6 +- .../contrib/query/browser/messagePanel.ts | 8 +- .../query/browser/query.contribution.ts | 26 +- .../query/common/fileQueryEditorInput.ts | 9 + .../contrib/query/common/queryEditorInput.ts | 20 +- .../contrib/query/common/queryInputFactory.ts | 8 +- .../contrib/query/common/queryResultsInput.ts | 6 - .../query/common/untitledQueryEditorInput.ts | 28 +- .../query/test/browser/queryActions.test.ts | 9 +- .../query/test/browser/queryEditor.test.ts | 7 +- .../browser/queryHistoryActions.ts | 8 +- .../electron-browser/queryHistory.ts | 4 +- .../queryPlan/common/queryPlanInput.ts | 4 +- .../tasks/browser/tasks.contribution.ts | 4 +- .../services/dialog/browser/dialogPane.ts | 2 +- .../insights/browser/insightsDialogView.ts | 8 +- .../electron-browser/insightsUtils.test.ts | 1 + .../queryEditor/browser/queryEditorService.ts | 4 +- .../releaseNotes.contribution.ts | 2 +- src/tsconfig.base.json | 1 + src/tsconfig.monaco.json | 1 - src/typings/applicationInsights.d.ts | 218 -- src/typings/applicationinsights-web.d.ts | 59 - src/typings/chokidar.d.ts | 200 - src/typings/electron.d.ts | 11 +- src/typings/http-proxy-agent.d.ts | 20 - src/typings/iconv-lite.d.ts | 18 - src/typings/jschardet.d.ts | 11 - src/typings/native-is-elevated.d.ts | 10 - src/typings/native-keymap.d.ts | 93 - src/typings/native-watchdog.d.ts | 17 - src/typings/node-pty.d.ts | 143 - src/typings/nsfw.d.ts | 41 - src/typings/onigasm-umd.d.ts | 33 - src/typings/require.d.ts | 4 +- src/typings/spdlog.d.ts | 40 - src/typings/sudo-prompt.d.ts | 13 - src/typings/v8-inspect-profiler.d.ts | 55 - src/typings/vscode-minimist.d.ts | 92 - src/typings/vscode-proxy-agent.d.ts | 6 - src/typings/vscode-ripgrep.d.ts | 3 - src/typings/vscode-sqlite3.d.ts | 115 - src/typings/vscode-textmate.d.ts | 256 -- src/typings/vscode-windows-ca-certs.d.ts | 6 - src/typings/windows-process-tree.d.ts | 63 - src/typings/xterm-addon-search.d.ts | 69 - src/typings/xterm-addon-web-links.d.ts | 71 - src/typings/xterm.d.ts | 1098 ------ src/typings/yauzl.d.ts | 49 - src/vs/base/browser/browser.ts | 17 - src/vs/base/browser/canIUse.ts | 60 + src/vs/base/browser/contextmenu.ts | 8 +- src/vs/base/browser/dom.ts | 72 +- src/vs/base/browser/fastDomNode.ts | 12 +- src/vs/base/browser/globalMouseMoveMonitor.ts | 22 +- src/vs/base/browser/markdownRenderer.ts | 22 +- src/vs/base/browser/touch.ts | 49 + .../base/browser/ui/actionbar/actionbar.css | 1 + src/vs/base/browser/ui/actionbar/actionbar.ts | 7 + .../ui/breadcrumbs/breadcrumbsWidget.css | 6 + src/vs/base/browser/ui/button/button.css | 3 +- .../browser/ui/centered/centeredViewLayout.ts | 4 +- src/vs/base/browser/ui/checkbox/checkbox.css | 13 +- src/vs/base/browser/ui/checkbox/checkbox.ts | 2 + .../ui/codiconLabel/codicon/codicon.css | 501 +-- .../ui/codiconLabel/codicon/codicon.ttf | Bin 52888 -> 47404 bytes .../browser/ui/contextview/contextview.ts | 4 +- .../base/browser/ui/countBadge/countBadge.ts | 3 +- src/vs/base/browser/ui/dialog/close-dark.svg | 3 - src/vs/base/browser/ui/dialog/close-light.svg | 3 - src/vs/base/browser/ui/dialog/dialog.css | 88 +- src/vs/base/browser/ui/dialog/dialog.ts | 23 +- src/vs/base/browser/ui/dialog/error-dark.svg | 3 - src/vs/base/browser/ui/dialog/error-light.svg | 3 - src/vs/base/browser/ui/dialog/info-dark.svg | 3 - src/vs/base/browser/ui/dialog/info-light.svg | 4 - .../base/browser/ui/dialog/pending-dark.svg | 31 - src/vs/base/browser/ui/dialog/pending-hc.svg | 31 - src/vs/base/browser/ui/dialog/pending.svg | 31 - .../base/browser/ui/dialog/warning-dark.svg | 3 - .../base/browser/ui/dialog/warning-light.svg | 4 - src/vs/base/browser/ui/dropdown/dropdown.css | 2 - src/vs/base/browser/ui/findinput/findInput.ts | 20 +- src/vs/base/browser/ui/grid/grid.ts | 2 +- src/vs/base/browser/ui/grid/gridview.ts | 106 +- .../ui/highlightedlabel/highlightedLabel.ts | 6 +- src/vs/base/browser/ui/iconLabel/iconLabel.ts | 138 +- .../base/browser/ui/iconLabel/iconlabel.css | 21 +- src/vs/base/browser/ui/inputbox/inputBox.css | 38 +- src/vs/base/browser/ui/inputbox/inputBox.ts | 7 +- src/vs/base/browser/ui/list/list.css | 68 +- src/vs/base/browser/ui/list/list.ts | 3 +- src/vs/base/browser/ui/list/listView.ts | 30 +- src/vs/base/browser/ui/list/listWidget.ts | 84 +- .../base/browser/ui/list/media/close-dark.svg | 3 - .../base/browser/ui/list/media/close-hc.svg | 3 - .../browser/ui/list/media/close-light.svg | 3 - .../browser/ui/list/media/filter-dark.svg | 3 - .../base/browser/ui/list/media/filter-hc.svg | 3 - .../browser/ui/list/media/filter-light.svg | 3 - .../browser/ui/list/media/no-filter-dark.svg | 3 - .../browser/ui/list/media/no-filter-hc.svg | 3 - .../browser/ui/list/media/no-filter-light.svg | 3 - src/vs/base/browser/ui/menu/menu.css | 32 +- src/vs/base/browser/ui/menu/menu.ts | 35 +- src/vs/base/browser/ui/menu/menubar.ts | 2 +- .../browser/ui/progressbar/progressbar.css | 17 +- .../browser/ui/scrollbar/abstractScrollbar.ts | 1 + .../browser/ui/scrollbar/scrollableElement.ts | 2 +- .../browser/ui/selectBox/selectBoxCustom.css | 12 +- .../browser/ui/selectBox/selectBoxNative.ts | 7 + .../base/browser/ui/splitview/panelview.css | 100 - src/vs/base/browser/ui/splitview/paneview.css | 101 + .../splitview/{panelview.ts => paneview.ts} | 153 +- src/vs/base/browser/ui/splitview/splitview.ts | 118 +- src/vs/base/browser/ui/toolbar/toolbar.ts | 2 +- src/vs/base/browser/ui/tree/abstractTree.ts | 85 +- src/vs/base/browser/ui/tree/asyncDataTree.ts | 289 +- .../ui/tree/compressedObjectTreeModel.ts | 22 +- src/vs/base/browser/ui/tree/dataTree.ts | 2 +- src/vs/base/browser/ui/tree/indexTreeModel.ts | 1 + .../{panelviewlet.css => paneviewlet.css} | 5 +- src/vs/base/browser/ui/tree/media/tree.css | 4 +- src/vs/base/browser/ui/tree/objectTree.ts | 31 +- .../base/browser/ui/tree/objectTreeModel.ts | 7 +- src/vs/base/browser/ui/widget.ts | 5 + src/vs/base/common/async.ts | 2 +- src/vs/base/common/buffer.ts | 345 +- src/vs/base/common/cache.ts | 1 - src/vs/base/common/color.ts | 17 + src/vs/base/common/errors.ts | 1 - src/vs/base/common/event.ts | 35 +- src/vs/base/common/filters.ts | 2 +- src/vs/base/common/iterator.ts | 19 +- src/vs/base/common/json.ts | 23 +- src/vs/base/common/jsonEdit.ts | 44 +- src/vs/base/common/jsonSchema.ts | 4 +- src/vs/base/common/lazy.ts | 5 + src/vs/base/common/lifecycle.ts | 37 - src/vs/base/common/mime.ts | 9 +- src/vs/base/common/path.ts | 4 +- src/vs/base/common/platform.ts | 6 +- src/vs/base/common/stream.ts | 487 +++ src/vs/base/common/strings.ts | 396 +- .../base/common/styler.ts} | 8 +- src/vs/base/common/types.ts | 7 + src/vs/base/common/uri.ts | 64 +- src/vs/base/node/decoder.ts | 2 +- src/vs/base/node/encoding.ts | 26 +- src/vs/base/node/languagePacks.js | 22 +- src/vs/base/node/pfs.ts | 56 +- src/vs/base/node/processes.ts | 18 +- src/vs/base/node/watcher.ts | 4 +- src/vs/base/node/zip.ts | 6 +- src/vs/base/parts/ipc/common/ipc.net.ts | 8 +- src/vs/base/parts/ipc/common/ipc.ts | 2 +- src/vs/base/parts/ipc/node/ipc.net.ts | 2 +- src/vs/base/parts/ipc/node/ipc.ts | 2 +- .../parts/quickopen/browser/quickOpenModel.ts | 5 +- .../quickopen/browser/quickOpenWidget.ts | 15 +- .../parts/storage/test/node/storage.test.ts | 2 +- src/vs/base/parts/tree/browser/tree.css | 10 +- src/vs/base/parts/tree/browser/treeView.ts | 4 +- .../browser/ui/splitview/splitview.test.ts | 8 +- .../browser/ui/tree/asyncDataTree.test.ts | 65 +- .../browser/ui/tree/indexTreeModel.test.ts | 31 + .../test/browser/ui/tree/objectTree.test.ts | 6 +- src/vs/base/test/common/event.test.ts | 63 +- src/vs/base/test/common/filters.test.ts | 10 + src/vs/base/test/common/jsonEdit.test.ts | 36 +- src/vs/base/test/common/stream.test.ts | 176 + src/vs/base/test/common/strings.test.ts | 38 + src/vs/base/test/common/uri.test.ts | 4 + src/vs/base/test/node/buffer.test.ts | 22 +- .../base/test/node/encoding/encoding.test.ts | 18 +- .../test/node/encoding/fixtures/some_file.css | 42 + .../node/encoding/fixtures/some_utf16be.css | Bin 1408 -> 1424 bytes .../node/encoding/fixtures/some_utf16le.css | Bin 1408 -> 1424 bytes .../test/node/encoding/fixtures/some_utf8.css | 6 +- src/vs/base/test/node/glob.test.ts | 6 +- src/vs/base/test/node/pfs/pfs.test.ts | 36 +- .../node/zip}/fixtures/extract.zip | Bin .../{node/test => test/node/zip}/zip.test.ts | 0 .../code/browser/workbench/workbench-dev.html | 1 + src/vs/code/browser/workbench/workbench.html | 1 + src/vs/code/browser/workbench/workbench.ts | 37 +- .../issue/issueReporterUtil.ts | 0 .../issue/issueReporterMain.ts | 20 +- .../issue/test/testReporterModel.test.ts | 2 +- .../processExplorer/media/processExplorer.css | 2 - src/vs/code/electron-browser/proxy/auth.html | 4 +- .../sharedProcess/sharedProcessMain.ts | 9 +- .../electron-browser/workbench/workbench.js | 36 +- src/vs/code/electron-main/app.ts | 33 +- src/vs/code/electron-main/main.ts | 4 +- src/vs/code/electron-main/sharedProcess.ts | 10 +- src/vs/code/electron-main/window.ts | 20 +- src/vs/code/node/cli.ts | 9 +- src/vs/code/node/paths.ts | 3 - .../test/electron-main/nativeHelpers.test.ts | 4 +- src/vs/code/test/node/argv.test.ts | 3 +- .../editor/browser/config/charWidthReader.ts | 2 +- .../editor/browser/controller/coreCommands.ts | 12 +- .../editor/browser/controller/mouseHandler.ts | 16 +- .../editor/browser/controller/mouseTarget.ts | 12 +- .../browser/controller/pointerHandler.ts | 99 +- .../browser/controller/textAreaHandler.ts | 33 +- .../browser/controller/textAreaInput.ts | 19 +- .../browser/controller/textAreaState.ts | 22 +- src/vs/editor/browser/editorBrowser.ts | 9 +- src/vs/editor/browser/editorDom.ts | 46 +- src/vs/editor/browser/editorExtensions.ts | 109 +- .../editor/browser/services/openerService.ts | 204 +- src/vs/editor/browser/view/viewImpl.ts | 67 +- .../currentLineHighlight.css | 14 +- .../currentLineHighlight.ts | 159 +- .../currentLineMarginHighlight.css | 16 - .../currentLineMarginHighlight.ts | 139 - .../viewParts/decorations/decorations.ts | 3 + .../viewParts/glyphMargin/glyphMargin.ts | 2 +- .../browser/viewParts/lines/viewLine.ts | 78 +- .../browser/viewParts/lines/viewLines.css | 7 +- .../browser/viewParts/lines/viewLines.ts | 70 +- .../editor/browser/viewParts/margin/margin.ts | 1 + .../browser/viewParts/minimap/minimap.ts | 44 +- .../viewParts/minimap/minimapCharRenderer.ts | 3 +- .../minimap/minimapCharRendererFactory.ts | 15 +- .../viewParts/minimap/minimapCharSheet.ts | 6 +- .../viewParts/minimap/minimapPreBaked.ts | 63 + .../overviewRuler/decorationsOverviewRuler.ts | 1 + .../viewParts/overviewRuler/overviewRuler.ts | 1 + .../viewParts/selections/selections.ts | 2 +- .../viewParts/viewCursors/viewCursor.ts | 11 +- .../browser/viewParts/viewZones/viewZones.ts | 106 +- .../editor/browser/widget/codeEditorWidget.ts | 8 +- .../editor/browser/widget/diffEditorWidget.ts | 20 +- src/vs/editor/browser/widget/diffNavigator.ts | 9 +- src/vs/editor/browser/widget/diffReview.ts | 6 +- .../browser/widget/media/diffReview.css | 7 +- src/vs/editor/common/commands/shiftCommand.ts | 4 +- .../common/config/commonEditorConfig.ts | 90 +- src/vs/editor/common/config/editorOptions.ts | 511 ++- src/vs/editor/common/controller/cursor.ts | 21 +- .../editor/common/controller/cursorCommon.ts | 14 +- .../editor/common/controller/cursorEvents.ts | 12 + .../common/controller/cursorTypeOperations.ts | 160 +- .../common/controller/cursorWordOperations.ts | 13 +- src/vs/editor/common/core/lineTokens.ts | 9 +- src/vs/editor/common/model.ts | 17 +- .../pieceTreeTextBuffer/pieceTreeBase.ts | 21 +- .../pieceTreeTextBufferBuilder.ts | 2 +- src/vs/editor/common/model/textModel.ts | 29 +- src/vs/editor/common/model/textModelSearch.ts | 2 +- src/vs/editor/common/model/tokensStore.ts | 569 ++- src/vs/editor/common/modes.ts | 52 +- .../common/modes/languageConfiguration.ts | 22 + .../modes/languageConfigurationRegistry.ts | 254 +- src/vs/editor/common/modes/linkComputer.ts | 4 + .../editor/common/modes/supports/onEnter.ts | 70 +- .../common/modes/textToHtmlTokenizer.ts | 4 +- .../common/services/editorSimpleWorker.ts | 28 +- .../services/editorWorkerServiceImpl.ts | 28 +- .../services/markerDecorationsServiceImpl.ts | 13 +- .../common/services/modelServiceImpl.ts | 478 ++- .../editor/common/services/resolverService.ts | 3 + .../common/standalone/standaloneEnums.ts | 3 +- .../editor/common/view/editorColorRegistry.ts | 12 + src/vs/editor/common/view/renderingContext.ts | 38 +- .../editor/common/viewLayout/linesLayout.ts | 602 ++- src/vs/editor/common/viewLayout/viewLayout.ts | 50 +- .../common/viewLayout/viewLineRenderer.ts | 2 +- .../common/viewLayout/whitespaceComputer.ts | 493 --- .../common/viewModel/splitLinesCollection.ts | 25 +- src/vs/editor/common/viewModel/viewModel.ts | 16 +- src/vs/editor/contrib/clipboard/clipboard.ts | 12 +- .../editor/contrib/codeAction/codeAction.ts | 35 +- .../contrib/codeAction/codeActionCommands.ts | 206 +- .../contrib/codeAction/codeActionMenu.ts | 195 + .../contrib/codeAction/codeActionModel.ts | 10 +- .../contrib/codeAction/codeActionTrigger.ts | 98 - .../editor/contrib/codeAction/codeActionUi.ts | 111 +- .../contrib/codeAction/codeActionWidget.ts | 91 - .../contrib/codeAction/lightBulbWidget.ts | 9 +- .../codeAction/test/codeAction.test.ts | 42 +- .../test/codeActionKeybindingResolver.test.ts | 94 + .../codeAction/test/codeActionModel.test.ts | 23 +- src/vs/editor/contrib/codeAction/types.ts | 151 + .../editor/contrib/codelens/codeLensCache.ts | 15 +- .../contrib/codelens/codelensController.ts | 66 +- .../contrib/codelens/codelensWidget.css | 7 +- .../editor/contrib/codelens/codelensWidget.ts | 194 +- .../contrib/colorPicker/colorPicker.css | 4 +- .../contrib/colorPicker/colorPickerWidget.ts | 8 +- src/vs/editor/contrib/comment/comment.ts | 26 +- .../editor/contrib/contextmenu/contextmenu.ts | 26 +- .../editor/contrib/cursorUndo/cursorUndo.ts | 31 +- .../cursorUndo/test/cursorUndo.test.ts | 63 + src/vs/editor/contrib/dnd/dnd.ts | 6 +- .../contrib/documentSymbols/outlineTree.ts | 195 +- src/vs/editor/contrib/find/findController.ts | 28 +- .../editor/contrib/find/findOptionsWidget.ts | 10 +- src/vs/editor/contrib/find/findState.ts | 10 +- src/vs/editor/contrib/find/findWidget.css | 51 +- src/vs/editor/contrib/find/findWidget.ts | 158 +- src/vs/editor/contrib/find/test/find.test.ts | 2 +- .../contrib/find/test/findController.test.ts | 30 +- .../contrib/find/test/replacePattern.test.ts | 157 +- src/vs/editor/contrib/folding/folding.ts | 2 +- src/vs/editor/contrib/folding/foldingModel.ts | 8 +- src/vs/editor/contrib/format/formatActions.ts | 4 +- .../goToDefinition/goToDefinitionCommands.ts | 505 --- src/vs/editor/contrib/gotoError/gotoError.ts | 4 +- .../contrib/gotoError/gotoErrorWidget.ts | 3 +- .../gotoError/media/gotoErrorWidget.css | 3 +- .../editor/contrib/gotoSymbol/goToCommands.ts | 781 ++++ .../goToSymbol.ts} | 27 +- .../link}/clickLinkGesture.ts | 1 - .../link/goToDefinitionAtPosition.css} | 0 .../link/goToDefinitionAtPosition.ts} | 129 +- .../peek}/referencesController.ts | 228 +- .../peek}/referencesTree.ts | 12 +- .../peek}/referencesWidget.css | 0 .../peek}/referencesWidget.ts | 116 +- .../referencesModel.ts | 136 +- .../symbolNavigation.ts} | 2 +- .../test/referencesModel.test.ts | 5 +- src/vs/editor/contrib/hover/hover.css | 15 +- src/vs/editor/contrib/hover/hover.ts | 49 +- .../editor/contrib/hover/modesContentHover.ts | 10 +- .../editor/contrib/hover/modesGlyphHover.ts | 2 +- .../editor/contrib/indentation/indentation.ts | 9 +- .../linesOperations/linesOperations.ts | 10 +- .../linesOperations/moveLinesCommand.ts | 30 +- .../linesOperations/sortLinesCommand.ts | 12 +- .../test/moveLinesCommand.test.ts | 9 +- src/vs/editor/contrib/links/getLinks.ts | 12 +- src/vs/editor/contrib/links/links.ts | 2 +- .../contrib/markdown/markdownRenderer.ts | 13 +- .../editor/contrib/multicursor/multicursor.ts | 31 +- .../contrib/parameterHints/parameterHints.css | 29 +- .../parameterHints/parameterHintsModel.ts | 2 +- .../parameterHints/parameterHintsWidget.ts | 95 +- .../media/peekViewWidget.css | 16 +- .../peekView.ts} | 67 +- .../referenceSearch/referenceSearch.ts | 290 -- src/vs/editor/contrib/rename/rename.ts | 2 +- .../editor/contrib/smartSelect/smartSelect.ts | 4 +- .../smartSelect/test/smartSelect.test.ts | 3 +- .../editor/contrib/snippet/snippetSession.ts | 28 +- .../snippet/test/snippetSession.test.ts | 2 +- .../editor/contrib/suggest/completionModel.ts | 2 +- .../editor/contrib/suggest/media/suggest.css | 13 +- src/vs/editor/contrib/suggest/suggest.ts | 26 +- .../suggest/suggestCommitCharacters.ts | 2 +- .../contrib/suggest/suggestController.ts | 214 +- src/vs/editor/contrib/suggest/suggestModel.ts | 58 +- .../suggest/suggestRangeHighlighter.ts | 125 + .../editor/contrib/suggest/suggestWidget.ts | 6 +- .../suggest/test/completionModel.test.ts | 69 +- .../test/wordOperations.test.ts | 2 +- src/vs/editor/editor.all.ts | 5 +- src/vs/editor/editor.api.ts | 2 +- .../browser/inspectTokens/inspectTokens.css | 5 +- .../browser/inspectTokens/inspectTokens.ts | 6 +- .../browser/quickOpen/quickCommand.ts | 2 +- .../browser/quickOpen/quickOutline.ts | 2 +- .../standaloneReferenceSearch.ts | 2 +- .../standalone/browser/simpleServices.ts | 17 +- .../standalone/browser/standaloneEditor.ts | 11 +- .../standalone/browser/standaloneServices.ts | 10 +- .../browser/standaloneThemeServiceImpl.ts | 9 + .../standalone/common/monarch/monarchLexer.ts | 28 +- .../test/browser/standaloneLanguages.test.ts | 9 +- .../browser/commands/shiftCommand.test.ts | 24 +- .../test/browser/controller/cursor.test.ts | 55 +- .../test/browser/controller/imeTester.ts | 2 +- .../browser/controller/textAreaState.test.ts | 2 +- .../browser/services/openerService.test.ts | 41 +- .../browser/view/minimapCharRenderer.test.ts | 4 +- .../test/browser/view/minimapFontCreator.html | 25 - .../test/browser/view/minimapFontCreator.ts | 119 - .../test/common/model/textModel.test.ts | 2 +- .../test/common/modes/linkComputer.test.ts | 7 + .../common/modes/supports/onEnter.test.ts | 9 +- .../common/modes/textToHtmlTokenizer.test.ts | 36 +- .../services/editorSimpleWorker.test.ts | 5 +- .../test/common/services/modelService.test.ts | 3 +- .../viewLayout/editorLayoutProvider.test.ts | 40 +- .../common/viewLayout/linesLayout.test.ts | 581 ++- .../viewLayout/viewLineRenderer.test.ts | 82 +- .../viewLayout/whitespaceComputer.test.ts | 558 --- .../viewModel/viewModelDecorations.test.ts | 37 +- src/vs/monaco.d.ts | 212 +- .../browser/menuEntryActionViewItem.ts | 64 +- src/vs/platform/actions/common/actions.ts | 17 +- src/vs/platform/actions/common/menuService.ts | 47 +- src/vs/platform/auth/common/auth.css | 100 + src/vs/platform/auth/common/auth.html | 35 + src/vs/platform/auth/common/auth.ts | 20 +- src/vs/platform/auth/common/authTokenIpc.ts | 4 +- .../auth/electron-browser/authServer.ts | 165 + .../auth/electron-browser/authTokenService.ts | 276 ++ .../test/node/configurationService.test.ts | 5 +- .../platform/contextkey/common/contextkey.ts | 2 +- .../contextview/browser/contextView.ts | 6 +- .../debug/common/extensionHostDebugIpc.ts | 4 +- .../diagnostics/node/diagnosticsService.ts | 4 +- src/vs/platform/dialogs/common/dialogs.ts | 22 +- .../download/common/downloadService.ts | 2 +- .../platform/driver/electron-main/driver.ts | 4 +- .../electron-main/electronMainService.ts | 22 +- src/vs/platform/electron/node/electron.ts | 4 - .../environment/common/environment.ts | 9 +- src/vs/platform/environment/node/argv.ts | 39 +- .../platform/environment/node/argvHelper.ts | 14 + .../environment/node/environmentService.ts | 9 +- .../environment/node/waitMarkerFile.ts | 28 + .../common/extensionGalleryService.ts | 25 +- .../common/extensionManagement.ts | 1 + .../node/extensionLifecycle.ts | 8 +- .../node/extensionManagementService.ts | 20 +- .../platform/extensions/common/extensions.ts | 14 +- src/vs/platform/files/common/fileService.ts | 256 +- src/vs/platform/files/common/files.ts | 119 +- src/vs/platform/files/common/io.ts | 114 + .../files/node/diskFileSystemProvider.ts | 59 +- .../node/watcher/nsfw/nsfwWatcherService.ts | 2 +- .../watcher/win32/csharpWatcherService.ts | 4 +- .../files/test/node/diskFileService.test.ts | 565 ++- .../test/node/fixtures/service/lorem.txt | 854 ++++- .../instantiation/common/extensions.ts | 4 +- .../instantiation/common/instantiation.ts | 29 +- .../common/instantiationService.ts | 2 +- .../common/abstractKeybindingService.ts | 7 +- .../platform/keybinding/common/keybinding.ts | 15 +- .../keybinding/common/keybindingResolver.ts | 4 +- .../common/abstractKeybindingService.test.ts | 4 + .../test/common/mockKeybindingService.ts | 4 + src/vs/platform/list/browser/listService.ts | 101 +- .../platform/menubar/electron-main/menubar.ts | 4 +- src/vs/platform/opener/common/opener.ts | 41 +- src/vs/platform/product/common/product.ts | 5 +- .../platform/product/common/productService.ts | 10 +- .../platform/quickinput/common/quickInput.ts | 6 +- .../remote/common/remoteAgentConnection.ts | 4 +- .../common/remoteAgentFileSystemChannel.ts | 64 +- src/vs/platform/remote/common/tunnel.ts | 8 +- .../platform/remote/common/tunnelService.ts | 8 +- src/vs/platform/request/common/request.ts | 2 +- src/vs/platform/request/node/proxy.ts | 6 +- .../platform/request/node/requestService.ts | 10 +- .../severityIcon/common/severityIcon.ts | 12 +- src/vs/platform/storage/node/storageIpc.ts | 4 +- .../browser/workbenchCommonProperties.ts | 8 +- .../telemetry/node/appInsightsAppender.ts | 2 +- .../test/browser/commonProperties.test.ts | 6 +- .../appInsightsAppender.test.ts | 10 +- src/vs/platform/theme/common/colorRegistry.ts | 14 +- src/vs/platform/theme/common/styler.ts | 13 +- src/vs/platform/theme/common/themeService.ts | 46 + .../common/tokenClassificationRegistry.ts | 395 ++ .../theme/test/common/testThemeService.ts | 8 + .../electron-main/updateService.linux.ts | 2 +- .../electron-main/updateService.win32.ts | 2 +- .../url/electron-main/electronUrlListener.ts | 8 +- .../platform/userDataSync/common/content.ts | 53 + .../userDataSync/common/extensionsSync.ts | 13 +- .../userDataSync/common/keybindingsMerge.ts | 367 ++ .../userDataSync/common/keybindingsSync.ts | 348 ++ .../userDataSync/common/keybindingsSyncIpc.ts | 45 + .../userDataSync/common/settingsSync.ts | 37 +- .../userDataSync/common/userDataAutoSync.ts | 69 + .../userDataSync/common/userDataSync.ts | 83 +- .../common/userDataSyncService.ts | 101 +- .../common/userDataSyncStoreService.ts | 39 +- .../electron-browser/userDataAutoSync.ts | 32 + .../test/common/keybindingsMerge.test.ts | 735 ++++ src/vs/platform/windows/common/windows.ts | 11 +- .../electron-main/windowsMainService.ts | 17 +- src/vs/platform/workspace/common/workspace.ts | 3 +- .../platform/workspaces/common/workspaces.ts | 12 +- .../electron-main/workspacesMainService.ts | 19 +- src/vs/vscode.d.ts | 320 +- src/vs/vscode.proposed.d.ts | 756 +++- .../api/browser/mainThreadComments.ts | 40 +- .../api/browser/mainThreadDialogs.ts | 8 +- .../api/browser/mainThreadDocuments.ts | 12 +- .../browser/mainThreadDocumentsAndEditors.ts | 6 +- .../api/browser/mainThreadFileSystem.ts | 36 +- .../mainThreadFileSystemEventService.ts | 82 +- .../api/browser/mainThreadLanguageFeatures.ts | 91 +- src/vs/workbench/api/browser/mainThreadSCM.ts | 2 +- .../api/browser/mainThreadSaveParticipant.ts | 33 +- .../api/browser/mainThreadTerminalService.ts | 25 +- .../api/browser/mainThreadTreeViews.ts | 14 +- .../workbench/api/browser/mainThreadUrls.ts | 6 +- .../api/browser/mainThreadWebview.ts | 66 +- .../workbench/api/browser/mainThreadWindow.ts | 12 +- .../api/browser/mainThreadWorkspace.ts | 25 +- .../api/browser/viewsExtensionPoint.ts | 23 +- .../api/common/configurationExtensionPoint.ts | 4 +- .../workbench/api/common/extHost.api.impl.ts | 64 +- .../workbench/api/common/extHost.protocol.ts | 107 +- .../api/common/extHostApiCommands.ts | 139 +- .../workbench/api/common/extHostCommands.ts | 17 +- .../workbench/api/common/extHostComments.ts | 48 +- .../api/common/extHostConfiguration.ts | 16 +- .../workbench/api/common/extHostCustomers.ts | 6 +- .../api/common/extHostDebugService.ts | 1059 +++++- .../api/common/extHostDecorations.ts | 6 +- .../api/common/extHostDiagnostics.ts | 5 +- .../api/common/extHostDocumentData.ts | 71 +- .../common/extHostDocumentSaveParticipant.ts | 2 +- .../workbench/api/common/extHostDocuments.ts | 3 +- .../api/common/extHostExtensionActivator.ts | 17 +- .../api/common/extHostExtensionService.ts | 42 +- .../workbench/api/common/extHostFileSystem.ts | 5 + .../common/extHostFileSystemEventService.ts | 138 +- .../api/common/extHostLanguageFeatures.ts | 363 +- .../api/common/extHostMessageService.ts | 8 +- .../workbench/api/common/extHostQuickOpen.ts | 14 +- .../api/common/extHostRequireInterceptor.ts | 15 +- src/vs/workbench/api/common/extHostSCM.ts | 3 +- src/vs/workbench/api/common/extHostSearch.ts | 119 +- src/vs/workbench/api/common/extHostTask.ts | 23 +- .../api/common/extHostTerminalService.ts | 113 +- .../workbench/api/common/extHostTreeViews.ts | 14 +- .../api/common/extHostTypeConverters.ts | 75 +- src/vs/workbench/api/common/extHostTypes.ts | 144 +- src/vs/workbench/api/common/extHostUrls.ts | 8 +- src/vs/workbench/api/common/extHostWebview.ts | 126 +- src/vs/workbench/api/common/extHostWindow.ts | 4 +- .../workbench/api/common/extHostWorkspace.ts | 102 +- .../api/common/menusExtensionPoint.ts | 11 +- .../api/common/shared/semanticTokens.ts | 113 + src/vs/workbench/api/node/extHost.services.ts | 4 +- src/vs/workbench/api/node/extHostCLIServer.ts | 9 +- .../workbench/api/node/extHostDebugService.ts | 1110 +----- .../api/node/extHostExtensionService.ts | 1 + src/vs/workbench/api/node/extHostSearch.ts | 121 +- .../workbench/api/node/extHostStoragePaths.ts | 8 +- src/vs/workbench/api/node/extHostTask.ts | 56 +- .../api/node/extHostTerminalService.ts | 8 +- src/vs/workbench/browser/actions.ts | 4 +- .../browser/actions/developerActions.ts | 8 +- .../workbench/browser/actions/helpActions.ts | 29 +- .../browser/actions/layoutActions.ts | 24 +- .../workbench/browser/actions/listCommands.ts | 4 +- .../browser/actions/navigationActions.ts | 8 +- .../browser/actions/windowActions.ts | 12 +- .../browser/actions/workspaceActions.ts | 14 +- src/vs/workbench/browser/composite.ts | 16 +- src/vs/workbench/browser/contextkeys.ts | 42 +- src/vs/workbench/browser/dnd.ts | 46 +- src/vs/workbench/browser/editor.ts | 10 +- src/vs/workbench/browser/labels.ts | 30 +- src/vs/workbench/browser/layout.ts | 113 +- src/vs/workbench/browser/media/part.css | 7 +- src/vs/workbench/browser/media/style.css | 45 +- src/vs/workbench/browser/panel.ts | 8 +- src/vs/workbench/browser/part.ts | 2 +- .../parts/activitybar/activitybarActions.ts | 80 +- .../parts/activitybar/activitybarPart.ts | 72 +- .../activitybar/media/activityaction.css | 57 +- .../activitybar/media/activitybarpart.css | 28 +- .../media/ellipsis-activity-bar.svg | 5 - .../media/settings-activity-bar.svg | 3 - .../browser/parts/compositeBarActions.ts | 22 +- .../workbench/browser/parts/compositePart.ts | 4 +- .../browser/parts/editor/baseEditor.ts | 6 +- .../browser/parts/editor/binaryEditor.ts | 4 +- .../browser/parts/editor/breadcrumbs.ts | 130 +- .../parts/editor/breadcrumbsControl.ts | 13 +- .../browser/parts/editor/breadcrumbsModel.ts | 49 +- .../browser/parts/editor/breadcrumbsPicker.ts | 21 +- .../parts/editor/editor.contribution.ts | 272 +- .../workbench/browser/parts/editor/editor.ts | 4 +- .../browser/parts/editor/editorActions.ts | 62 +- .../browser/parts/editor/editorAutoSave.ts | 86 + .../browser/parts/editor/editorCommands.ts | 10 +- .../browser/parts/editor/editorControl.ts | 1 + .../browser/parts/editor/editorDropTarget.ts | 9 +- .../browser/parts/editor/editorGroupView.ts | 193 +- .../browser/parts/editor/editorPart.ts | 30 +- .../browser/parts/editor/editorPicker.ts | 5 +- .../browser/parts/editor/editorStatus.ts | 165 +- .../parts/editor/media/close-all-dark.svg | 4 - .../parts/editor/media/close-all-light.svg | 4 - .../parts/editor/media/close-dark-alt.svg | 3 - .../editor/media/close-dirty-dark-alt.svg | 3 - .../editor/media/close-dirty-light-alt.svg | 3 - .../parts/editor/media/close-light-alt.svg | 3 - .../parts/editor/media/editorgroupview.css | 14 + .../parts/editor/media/editorstatus.css | 1 + .../parts/editor/media/next-diff-dark.svg | 3 - .../parts/editor/media/next-diff-light.svg | 3 - .../parts/editor/media/notabstitlecontrol.css | 9 + .../parts/editor/media/paragraph-dark.svg | 3 - .../editor/media/paragraph-disabled-dark.svg | 5 - .../editor/media/paragraph-disabled-light.svg | 5 - .../parts/editor/media/paragraph-light.svg | 3 - .../parts/editor/media/previous-diff-dark.svg | 3 - .../editor/media/previous-diff-light.svg | 3 - .../media/split-editor-horizontal-dark.svg | 3 - .../media/split-editor-horizontal-hc.svg | 3 - .../media/split-editor-horizontal-light.svg | 7 - .../media/split-editor-vertical-dark.svg | 3 - .../editor/media/split-editor-vertical-hc.svg | 3 - .../media/split-editor-vertical-light.svg | 7 - .../parts/editor/media/tabstitlecontrol.css | 18 +- .../parts/editor/media/titlecontrol.css | 16 +- .../parts/editor/noTabsTitleControl.ts | 24 +- .../browser/parts/editor/resourceViewer.ts | 11 +- .../browser/parts/editor/sideBySideEditor.ts | 2 +- .../browser/parts/editor/tabsTitleControl.ts | 114 +- .../browser/parts/editor/textDiffEditor.ts | 52 +- .../browser/parts/editor/textEditor.ts | 92 +- .../parts/editor/textResourceEditor.ts | 54 +- .../browser/parts/editor/titleControl.ts | 12 +- .../media/notificationsActions.css | 1 + .../notifications/media/notificationsList.css | 7 +- .../media/notificationsToasts.css | 5 +- .../parts/notifications/notificationsList.ts | 13 +- .../notifications/notificationsToasts.ts | 5 +- .../parts/panel/media/ellipsis-dark.svg | 5 - .../browser/parts/panel/media/ellipsis-hc.svg | 5 - .../parts/panel/media/ellipsis-light.svg | 5 - .../browser/parts/panel/media/panelpart.css | 39 +- .../browser/parts/panel/panelActions.ts | 16 +- .../parts/quickinput/media/quickInput.css | 7 +- .../browser/parts/quickinput/quickInput.ts | 276 +- .../parts/quickinput/quickInputActions.ts | 2 +- .../parts/quickinput/quickInputList.ts | 44 +- .../parts/quickopen/quickOpenActions.ts | 12 +- .../parts/quickopen/quickOpenController.ts | 14 +- .../parts/sidebar/media/sidebarpart.css | 22 +- .../browser/parts/sidebar/sidebarPart.ts | 2 +- .../parts/statusbar/media/statusbarpart.css | 22 +- .../browser/parts/statusbar/statusbarPart.ts | 31 +- .../parts/titlebar/media/titlebarpart.css | 30 +- .../browser/parts/titlebar/menubarControl.ts | 9 +- .../browser/parts/titlebar/titlebarPart.ts | 41 +- .../browser/parts/views/customView.ts | 40 +- .../{panelviewlet.css => paneviewlet.css} | 8 +- .../browser/parts/views/media/views.css | 17 +- .../views/{panelViewlet.ts => paneViewlet.ts} | 165 +- src/vs/workbench/browser/parts/views/views.ts | 62 +- .../browser/parts/views/viewsViewlet.ts | 196 +- src/vs/workbench/browser/quickopen.ts | 12 +- src/vs/workbench/browser/style.ts | 35 +- src/vs/workbench/browser/viewlet.ts | 21 +- src/vs/workbench/browser/web.main.ts | 42 +- .../browser/workbench.contribution.ts | 10 +- src/vs/workbench/browser/workbench.ts | 7 +- src/vs/workbench/common/activity.ts | 5 +- src/vs/workbench/common/composite.ts | 11 + .../workbench/common/configuration.ts} | 14 +- src/vs/workbench/common/contributions.ts | 9 +- src/vs/workbench/common/editor.ts | 308 +- .../common/editor/binaryEditorModel.ts | 27 +- .../common/editor/dataUriEditorInput.ts | 73 - .../common/editor/diffEditorInput.ts | 8 +- src/vs/workbench/common/editor/editorGroup.ts | 104 +- .../common/editor/resourceEditorModel.ts | 4 - .../common/editor/textEditorModel.ts | 14 +- ...torInput.ts => untitledTextEditorInput.ts} | 115 +- ...torModel.ts => untitledTextEditorModel.ts} | 47 +- src/vs/workbench/common/theme.ts | 24 +- src/vs/workbench/common/views.ts | 7 + .../backup/common/backupModelTracker.ts | 38 +- .../contrib/backup/common/backupRestorer.ts | 4 +- .../browser/callHierarchy.contribution.ts | 147 +- .../callHierarchy/browser/callHierarchy.ts | 178 +- .../browser/callHierarchyPeek.ts | 224 +- .../browser/callHierarchyTree.ts | 131 +- .../browser/media/callHierarchy.css | 11 + .../contrib/cli/node/cli.contribution.ts | 8 +- .../common/codeActions.contribution.ts | 30 + .../codeActions/common/configuration.ts | 155 + .../codeActions/common/extensionPoint.ts | 67 + .../browser/accessibility/accessibility.css | 3 +- .../browser/accessibility/accessibility.ts | 10 +- .../browser/find/simpleFindWidget.ts | 7 +- .../codeEditor/browser/inspectKeybindings.ts | 8 +- .../inspectTMScopes/inspectTMScopes.css | 1 + .../inspectTMScopes/inspectTMScopes.ts | 9 +- .../languageConfigurationExtensionPoint.ts | 25 +- .../suggestEnabledInput.css | 3 - .../suggestEnabledInput.ts | 20 +- .../codeEditor/browser/toggleMinimap.ts | 2 +- .../browser/toggleMultiCursorModifier.ts | 4 +- .../browser/toggleRenderControlCharacter.ts | 2 +- .../browser/toggleRenderWhitespace.ts | 2 +- .../codeEditor/browser/toggleWordWrap.ts | 8 +- .../browser/workbenchReferenceSearch.ts | 2 +- .../codeEditor.contribution.ts | 1 + .../electron-browser/inputClipboardActions.ts | 43 + .../electron-browser/selectionClipboard.ts | 36 +- .../contrib/comments/browser/commentNode.ts | 34 +- .../comments/browser/commentThreadWidget.ts | 64 +- .../browser/commentsEditorContribution.ts | 2 +- .../comments/browser/commentsTreeViewer.ts | 12 +- .../contrib/comments/browser/media/review.css | 10 +- .../comments/browser/simpleCommentEditor.ts | 9 +- .../contrib/customEditor/browser/commands.ts | 75 +- .../customEditor/browser/customEditorInput.ts | 133 +- .../browser/customEditorInputFactory.ts | 5 +- .../customEditor/browser/customEditors.ts | 93 +- .../customEditor/browser/extensionPoint.ts | 4 - .../browser/webviewEditor.contribution.ts | 16 +- .../customEditor/common/customEditor.ts | 56 +- .../customEditor/common/customEditorModel.ts | 157 + .../common/customEditorModelManager.ts | 54 + .../contrib/debug/browser/baseDebugView.ts | 3 +- .../browser/breakpointEditorContribution.ts | 100 +- .../contrib/debug/browser/breakpointWidget.ts | 35 +- .../contrib/debug/browser/breakpointsView.ts | 57 +- .../contrib/debug/browser/callStackView.ts | 90 +- .../debug/browser/debug.contribution.ts | 89 +- .../debug/browser/debugActionViewItems.ts | 2 +- .../contrib/debug/browser/debugActions.ts | 16 +- .../browser/debugCallStackContribution.ts | 65 +- .../contrib/debug/browser/debugCommands.ts | 107 +- .../browser/debugConfigurationManager.ts | 49 +- .../debug/browser/debugEditorActions.ts | 6 +- .../debug/browser/debugEditorContribution.ts | 4 +- .../contrib/debug/browser/debugHover.ts | 16 +- .../contrib/debug/browser/debugService.ts | 40 +- .../contrib/debug/browser/debugSession.ts | 2 +- .../contrib/debug/browser/debugToolBar.ts | 130 +- .../contrib/debug/browser/debugViewlet.ts | 48 +- .../browser/extensionHostDebugService.ts | 42 +- .../contrib/debug/browser/linkDetector.ts | 8 +- .../debug/browser/loadedScriptsView.ts | 22 +- .../contrib/debug/browser/media/add-dark.svg | 3 - .../contrib/debug/browser/media/add-hc.svg | 3 - .../contrib/debug/browser/media/add-light.svg | 3 - .../browser/media/breakpoint-conditional.svg | 3 - .../media/breakpoint-data-disabled.svg | 3 - .../media/breakpoint-data-unverified.svg | 3 - .../debug/browser/media/breakpoint-data.svg | 3 - .../browser/media/breakpoint-disabled.svg | 3 - .../media/breakpoint-function-disabled.svg | 3 - .../media/breakpoint-function-unverified.svg | 3 - .../browser/media/breakpoint-function.svg | 3 - .../debug/browser/media/breakpoint-hint.svg | 5 - .../browser/media/breakpoint-log-disabled.svg | 3 - .../media/breakpoint-log-unverified.svg | 3 - .../debug/browser/media/breakpoint-log.svg | 3 - .../browser/media/breakpoint-unsupported.svg | 3 - .../browser/media/breakpoint-unverified.svg | 3 - .../debug/browser/media/breakpoint.svg | 3 - .../debug/browser/media/breakpointWidget.css | 11 +- .../debug/browser/media/close-all-dark.svg | 4 - .../debug/browser/media/close-all-hc.svg | 4 - .../debug/browser/media/close-all-light.svg | 4 - .../debug/browser/media/configure-dark.svg | 6 - .../debug/browser/media/configure-hc.svg | 6 - .../debug/browser/media/configure-light.svg | 6 - .../debug/browser/media/console-dark.svg | 3 - .../debug/browser/media/console-hc.svg | 3 - .../debug/browser/media/console-light.svg | 3 - .../debug/browser/media/continue-dark.svg | 3 - .../debug/browser/media/continue-light.svg | 3 - .../debug/browser/media/continue-white.svg | 3 - .../browser/media/current-and-breakpoint.svg | 4 - .../debug/browser/media/current-arrow.svg | 3 - .../browser/media/debug-activity-bar.svg | 5 - .../browser/media/debug.contribution.css | 120 +- .../debug/browser/media/debugHover.css | 2 + .../debug/browser/media/debugToolBar.css | 14 +- .../debug/browser/media/debugViewlet.css | 213 +- .../debug/browser/media/disconnect-dark.svg | 3 - .../debug/browser/media/disconnect-light.svg | 3 - .../debug/browser/media/disconnect-white.svg | 3 - .../contrib/debug/browser/media/drag.svg | 8 - .../debug/browser/media/exceptionWidget.css | 1 + .../debug/browser/media/pause-dark.svg | 3 - .../debug/browser/media/pause-light.svg | 3 - .../debug/browser/media/pause-white.svg | 3 - .../contrib/debug/browser/media/repl.css | 12 + .../debug/browser/media/restart-dark.svg | 3 - .../debug/browser/media/restart-light.svg | 3 - .../debug/browser/media/restart-white.svg | 3 - .../browser/media/reverse-continue-dark.svg | 3 - .../browser/media/reverse-continue-light.svg | 3 - .../media/stackframe-and-breakpoint.svg | 4 - .../debug/browser/media/stackframe-arrow.svg | 3 - .../debug/browser/media/start-dark.svg | 3 - .../contrib/debug/browser/media/start-hc.svg | 3 - .../debug/browser/media/start-light.svg | 3 - .../debug/browser/media/step-back-dark.svg | 3 - .../debug/browser/media/step-back-light.svg | 3 - .../debug/browser/media/step-into-dark.svg | 3 - .../debug/browser/media/step-into-light.svg | 3 - .../debug/browser/media/step-into-white.svg | 3 - .../debug/browser/media/step-out-dark.svg | 3 - .../debug/browser/media/step-out-light.svg | 3 - .../debug/browser/media/step-out-white.svg | 3 - .../debug/browser/media/step-over-dark.svg | 3 - .../debug/browser/media/step-over-light.svg | 3 - .../debug/browser/media/step-over-white.svg | 3 - .../contrib/debug/browser/media/stop-dark.svg | 3 - .../debug/browser/media/stop-light.svg | 3 - .../debug/browser/media/stop-white.svg | 3 - .../browser/media/toggle-breakpoints-dark.svg | 3 - .../browser/media/toggle-breakpoints-hc.svg | 3 - .../media/toggle-breakpoints-light.svg | 3 - .../workbench/contrib/debug/browser/repl.ts | 18 +- .../contrib/debug/browser/startView.ts | 156 + .../contrib/debug/browser/variablesView.ts | 16 +- .../debug/browser/watchExpressionsView.ts | 21 +- .../debug/common/abstractDebugAdapter.ts | 6 +- .../workbench/contrib/debug/common/debug.ts | 23 +- .../contrib/debug/common/debugModel.ts | 22 +- .../contrib/debug/common/debugUtils.ts | 10 +- .../contrib/debug/common/debugViewModel.ts | 9 +- .../contrib/debug/common/replModel.ts | 14 + .../extensionHostDebugService.ts | 10 +- .../contrib/debug/node/debugAdapter.ts | 10 +- .../workbench/contrib/debug/node/terminals.ts | 63 +- .../browser/actions/expandAbbreviation.ts | 13 +- .../browser/actions/showEmmetCommands.ts | 13 +- .../experiments/common/experimentService.ts | 32 +- .../experimentService.test.ts | 6 +- .../extensions/browser/extensionEditor.ts | 116 +- .../browser/extensionTipsService.ts | 35 +- .../browser/extensions.contribution.ts | 71 +- .../extensions/browser/extensionsActions.ts | 332 +- .../browser/extensionsDependencyChecker.ts | 4 +- .../extensions/browser/extensionsViewer.ts | 5 +- .../extensions/browser/extensionsViewlet.ts | 12 +- .../extensions/browser/extensionsViews.ts | 14 +- .../extensions/browser/extensionsWidgets.ts | 8 +- .../browser/extensionsWorkbenchService.ts | 4 +- .../extensions/browser/media/clear-dark.svg | 7 - .../extensions/browser/media/clear-hc.svg | 7 - .../extensions/browser/media/clear-light.svg | 7 - .../browser/media/configure-dark.svg | 6 - .../extensions/browser/media/configure-hc.svg | 6 - .../browser/media/configure-light.svg | 6 - .../browser/media/extensionActions.css | 37 +- .../browser/media/extensionEditor.css | 11 + .../browser/media/extensions-activity-bar.svg | 3 - .../extensions/browser/media/extensions.css | 8 - .../browser/media/extensionsViewlet.css | 15 +- .../browser/media/extensionsWidgets.css | 38 +- .../extensions/browser/media/star-empty.svg | 5 - .../extensions/browser/media/star-full.svg | 3 - .../extensions/browser/media/star-half.svg | 3 - .../extensions/browser/media/star-small.svg | 10 - .../extensionProfileService.ts | 4 +- .../extensions.contribution.ts | 14 +- .../runtimeExtensionsEditor.ts | 8 +- .../extensionsActions.test.ts | 195 +- .../extensionsTipsService.test.ts | 7 +- .../files/browser/editors/binaryFileEditor.ts | 2 +- .../browser/editors/fileEditorTracker.ts | 65 +- .../files/browser/editors/textFileEditor.ts | 23 +- .../contrib/files/browser/explorerViewlet.ts | 8 +- .../files/browser/fileActions.contribution.ts | 132 +- .../contrib/files/browser/fileActions.ts | 347 +- .../contrib/files/browser/fileCommands.ts | 495 ++- .../files/browser/files.contribution.ts | 44 +- .../workbench/contrib/files/browser/files.ts | 63 +- .../files/browser/files.web.contribution.ts | 8 +- .../files/browser/media/check-dark.svg | 3 - .../files/browser/media/check-light.svg | 3 - .../files/browser/media/collapse-all-dark.svg | 4 - .../files/browser/media/collapse-all-hc.svg | 4 - .../browser/media/collapse-all-light.svg | 4 - .../files/browser/media/explorerviewlet.css | 40 +- .../files/browser/media/fileactions.css | 35 - .../browser/media/files-activity-bar.svg | 3 - .../files/browser/media/preview-dark.svg | 4 - .../files/browser/media/preview-light.svg | 4 - .../media/split-editor-horizontal-dark.svg | 3 - .../media/split-editor-horizontal-hc.svg | 3 - .../media/split-editor-horizontal-light.svg | 7 - .../media/split-editor-vertical-dark.svg | 3 - .../media/split-editor-vertical-hc.svg | 3 - .../media/split-editor-vertical-light.svg | 7 - .../contrib/files/browser/media/undo-dark.svg | 3 - .../files/browser/media/undo-light.svg | 3 - ...Handler.ts => textFileSaveErrorHandler.ts} | 22 +- .../contrib/files/browser/views/emptyView.ts | 13 +- .../files/browser/views/explorerView.ts | 218 +- .../files/browser/views/explorerViewer.ts | 455 ++- .../files/browser/views/openEditorsView.ts | 59 +- .../files/common/dirtyFilesIndicator.ts | 70 + .../contrib/files/common/dirtyFilesTracker.ts | 113 - .../files/common/editors/fileEditorInput.ts | 47 +- .../contrib/files/common/explorerModel.ts | 31 +- .../contrib/files/common/explorerService.ts | 24 +- .../workbench/contrib/files/common/files.ts | 43 +- .../electron-browser/dirtyFilesTracker.ts | 81 - .../fileActions.contribution.ts | 2 +- .../electron-browser/files.contribution.ts | 8 +- .../files/electron-browser/textFileEditor.ts | 9 +- .../test/browser/fileEditorInput.test.ts | 8 +- .../format/browser/formatActionsMultiple.ts | 16 +- .../format/browser/formatActionsNone.ts | 2 +- .../issue/browser/issue.contribution.ts | 46 + .../contrib/issue/browser/issueService.ts | 67 + .../electron-browser/issue.contribution.ts | 4 +- .../browser/localizations.contribution.ts | 2 +- .../browser/localizationsActions.ts | 6 +- .../contrib/logs/common/logs.contribution.ts | 10 +- .../electron-browser/logs.contribution.ts | 2 +- .../markers/browser/markers.contribution.ts | 11 +- .../contrib/markers/browser/markers.ts | 7 +- .../markers/browser/markersFilterOptions.ts | 22 +- .../contrib/markers/browser/markersModel.ts | 8 + .../contrib/markers/browser/markersPanel.ts | 193 +- .../markers/browser/markersPanelActions.ts | 268 +- .../markers/browser/markersTreeViewer.ts | 26 +- .../contrib/markers/browser/media/markers.css | 52 +- .../contrib/markers/browser/messages.ts | 10 +- .../outline/browser/outline.contribution.ts | 128 +- .../outline/browser/outlineNavigation.ts | 202 + .../{outlinePanel.css => outlinePane.css} | 18 +- .../{outlinePanel.ts => outlinePane.ts} | 40 +- .../contrib/output/browser/logViewer.ts | 12 +- .../output/browser/output.contribution.ts | 12 +- .../contrib/output/browser/outputActions.ts | 4 +- .../contrib/output/browser/outputPanel.ts | 8 +- .../electron-browser/startupProfiler.ts | 6 +- .../preferences/browser/keybindingWidgets.ts | 4 +- .../preferences/browser/keybindingsEditor.ts | 119 +- .../browser/keybindingsEditorContribution.ts | 9 +- .../browser/keyboardLayoutPicker.ts | 2 +- .../preferences/browser/media/preferences.css | 2 + .../browser/media/settingsEditor2.css | 30 +- .../browser/preferences.contribution.ts | 58 +- .../preferences/browser/preferencesEditor.ts | 12 +- .../browser/preferencesRenderers.ts | 4 +- .../preferences/browser/settingsEditor2.ts | 61 +- .../preferences/browser/settingsLayout.ts | 8 +- .../preferences/browser/settingsTree.ts | 51 +- .../preferences/browser/settingsWidgets.ts | 30 +- .../contrib/preferences/browser/tocTree.ts | 12 +- .../quickopen/browser/commandsHandler.ts | 17 +- .../browser/quickopen.contribution.ts | 22 +- .../browser/relauncher.contribution.ts | 16 +- .../remote/browser/explorerViewItems.ts | 114 + .../browser/help-documentation-dark.svg | 11 - .../remote/browser/help-documentation-hc.svg | 11 - .../browser/help-documentation-light.svg | 11 - .../remote/browser/help-feedback-dark.svg | 4 - .../remote/browser/help-feedback-hc.svg | 4 - .../remote/browser/help-feedback-light.svg | 4 - .../browser/help-getting-started-dark.svg | 4 - .../browser/help-getting-started-hc.svg | 4 - .../browser/help-getting-started-light.svg | 4 - .../remote/browser/help-report-issue-dark.svg | 4 - .../remote/browser/help-report-issue-hc.svg | 4 - .../browser/help-report-issue-light.svg | 4 - .../browser/help-review-issues-dark.svg | 4 - .../remote/browser/help-review-issues-hc.svg | 4 - .../browser/help-review-issues-light.svg | 4 - .../remote/browser/media/tunnelView.css} | 7 +- .../remote/browser/remote-activity-bar.svg | 13 - .../contrib/remote/browser/remote.ts | 539 ++- .../contrib/remote/browser/remoteViewlet.css | 91 +- .../contrib/remote/browser/tunnelView.ts | 757 ++++ .../remote/common/remote.contribution.ts | 23 +- .../electron-browser/remote.contribution.ts | 34 +- .../workbench/contrib/scm/browser/activity.ts | 45 +- .../contrib/scm/browser/dirtydiffDecorator.ts | 76 +- .../scm/browser/{mainPanel.ts => mainPane.ts} | 24 +- .../scm/browser/media/dirtydiffDecorator.css | 6 +- .../scm/browser/media/scm-activity-bar.svg | 10 - .../contrib/scm/browser/media/scmViewlet.css | 14 +- .../{repositoryPanel.ts => repositoryPane.ts} | 59 +- .../contrib/scm/browser/scm.contribution.ts | 13 +- .../contrib/scm/browser/scmViewlet.ts | 26 +- .../browser/media/search-activity-bar.svg | 3 - .../browser/media/search.contribution.css | 9 - .../search/browser/media/searchview.css | 10 +- .../search/browser/patternInputWidget.ts | 37 +- .../search/browser/search.contribution.ts | 83 +- .../contrib/search/browser/searchActions.ts | 92 +- .../contrib/search/browser/searchEditor.ts | 266 ++ .../contrib/search/browser/searchPanel.ts | 6 +- .../contrib/search/browser/searchView.ts | 203 +- .../contrib/search/browser/searchWidget.ts | 50 +- .../contrib/search/common/constants.ts | 4 + .../contrib/search/common/searchModel.ts | 58 +- .../search/test/common/searchModel.test.ts | 4 + .../browser/snippetCompletionProvider.ts | 20 +- .../contrib/snippets/browser/snippetsFile.ts | 21 +- .../snippets/browser/snippetsService.ts | 8 +- .../snippets/test/browser/snippetFile.test.ts | 2 +- .../test/browser/snippetsService.test.ts | 50 +- .../partsSplash.contribution.ts | 9 +- .../browser/workspaceTagsService.ts} | 6 +- .../common/workspaceTags.ts} | 4 +- .../electron-browser/tags.contribution.ts} | 6 +- .../electron-browser/workspaceTags.ts} | 16 +- .../electron-browser/workspaceTagsService.ts} | 16 +- .../test/workspaceTags.test.ts} | 6 +- .../tasks/browser/abstractTaskService.ts | 227 +- .../contrib/tasks/browser/quickOpen.ts | 4 +- .../tasks/browser/task.contribution.ts | 31 +- .../tasks/browser/terminalTaskSystem.ts | 90 +- .../contrib/tasks/common/jsonSchemaCommon.ts | 4 +- .../contrib/tasks/common/jsonSchema_v2.ts | 2 +- .../tasks/common/media/configure-dark.svg | 6 - .../tasks/common/media/configure-hc.svg | 6 - .../tasks/common/media/configure-light.svg | 6 - .../tasks/common/media/task.contribution.css | 16 - .../contrib/tasks/common/problemMatcher.ts | 6 + .../contrib/tasks/common/taskConfiguration.ts | 12 +- .../contrib/tasks/common/taskService.ts | 2 +- .../contrib/tasks/node/processTaskSystem.ts | 2 +- .../browser/addons/commandTrackerAddon.ts | 20 +- .../terminal/browser/media/scrollbar.css | 2 +- .../terminal/browser/media/terminal.css | 7 + .../contrib/terminal/browser/media/xterm.css | 1 - .../terminal/browser/terminal.contribution.ts | 197 +- .../contrib/terminal/browser/terminal.ts | 24 +- .../terminal/browser/terminalActions.ts | 44 +- .../terminal/browser/terminalInstance.ts | 140 +- .../browser/terminalInstanceService.ts | 9 + .../terminal/browser/terminalLinkHandler.ts | 44 +- .../contrib/terminal/browser/terminalPanel.ts | 26 +- .../browser/terminalProcessExtHostProxy.ts | 6 +- .../browser/terminalProcessManager.ts | 6 +- .../terminal/browser/terminalQuickOpen.ts | 4 +- .../terminal/browser/terminalService.ts | 43 +- .../terminal/browser/terminalWidgetManager.ts | 27 +- .../contrib/terminal/common/terminal.ts | 16 +- .../terminal/common/terminalDataBuffering.ts | 57 + .../terminal/common/terminalEnvironment.ts | 27 +- .../terminalInstanceService.ts | 9 + .../electron-browser/terminalNativeService.ts | 3 +- .../electron-browser/terminalRemote.ts | 2 +- .../electron-browser/windowsShellHelper.ts | 26 +- .../contrib/terminal/node/terminalProcess.ts | 8 +- .../test/common/terminalDataBuffering.test.ts | 191 + .../terminalColorRegistry.test.ts | 7 +- .../terminalLinkHandler.test.ts | 3 + .../browser/testCustomEditors.ts | 252 ++ .../themes/browser/themes.contribution.ts | 102 +- .../themes.test.contribution.ts | 3 + .../contrib/update/browser/media/markdown.css | 130 - .../update/browser/releaseNotesEditor.ts | 142 +- .../update/browser/update.contribution.ts | 28 +- .../contrib/update/browser/update.ts | 46 +- .../workbench/contrib/update/common/update.ts | 3 +- .../contrib/url/common/trustedDomains.ts | 1 + .../trustedDomainsFileSystemProvider.ts | 20 +- .../url/common/trustedDomainsValidator.ts | 90 +- .../contrib/url/common/url.contribution.ts | 7 +- .../userDataSync/browser/userDataAutoSync.ts | 35 + .../browser/userDataSync.contribution.ts | 36 +- .../userDataSync/browser/userDataSync.ts | 311 +- .../browser/userDataSyncTrigger.ts | 61 + .../userDataSync.contribution.ts | 5 +- .../contrib/watermark/browser/watermark.ts | 5 +- .../webview/browser/baseWebviewElement.ts | 9 +- .../browser/dynamicWebviewEditorOverlay.ts | 9 +- .../contrib/webview/browser/pre/fake.html | 11 + .../contrib/webview/browser/pre/index.html | 6 +- .../contrib/webview/browser/pre/main.js | 28 +- .../webview/browser/webview.contribution.ts | 4 +- .../contrib/webview/browser/webview.ts | 5 +- .../webview/browser/webviewCommands.ts | 34 +- .../webview/browser/webviewEditorInput.ts | 75 +- .../contrib/webview/browser/webviewElement.ts | 7 +- .../browser/webviewWorkbenchService.ts | 25 +- .../contrib/webview/common/resourceLoader.ts | 49 +- .../electron-browser/webview.contribution.ts | 69 +- .../electron-browser/webviewCommands.ts | 87 +- .../electron-browser/webviewElement.ts | 11 +- .../electron-browser/webviewProtocols.ts | 6 +- .../welcome/overlay/browser/welcomeOverlay.ts | 27 +- .../page/browser/welcomePage.contribution.ts | 7 +- .../welcome/page/browser/welcomePage.ts | 10 +- .../browser/walkThrough.contribution.ts | 4 +- .../walkThrough/browser/walkThroughPart.css | 1 + .../walkThrough/browser/walkThroughPart.ts | 20 +- .../electron-browser/desktop.contribution.ts | 48 +- .../electron-browser/desktop.main.ts | 10 +- src/vs/workbench/electron-browser/window.ts | 100 +- .../node/accessibilityService.ts | 16 +- .../authToken/browser}/authTokenService.ts | 60 +- .../electron-browser/authTokenService.ts | 27 +- .../services/backup/common/backup.ts | 6 - .../backup/common/backupFileService.ts | 6 +- .../backup/electron-browser/backup.ts | 12 + .../backupFileService.test.ts | 4 +- .../bulkEdit/browser/bulkEditService.ts | 27 +- .../clipboard/browser/clipboardService.ts | 23 +- .../browser/configurationService.ts | 2 +- .../common/configurationEditingService.ts | 8 +- .../common/jsonEditingService.ts | 2 +- .../configurationEditingService.test.ts | 41 +- .../configurationService.test.ts | 4 +- .../common/variableResolver.ts | 38 +- .../configurationResolverService.test.ts | 4 +- .../electron-browser/contextmenuService.ts | 9 +- .../decorations/browser/decorations.ts | 2 +- .../decorations/browser/decorationsService.ts | 80 +- .../browser/abstractFileDialogService.ts | 52 +- .../dialogs/browser/simpleFileDialog.ts | 87 +- .../electron-browser/fileDialogService.ts | 19 +- .../services/editor/browser/editorService.ts | 200 +- .../editor/common/editorGroupsService.ts | 10 +- .../services/editor/common/editorService.ts | 52 +- .../test/browser/editorGroupsService.test.ts | 78 +- .../editor/test/browser/editorService.test.ts | 97 +- .../environment/browser/environmentService.ts | 383 +- .../environment/common/environmentService.ts | 8 +- .../environmentService.ts | 37 +- .../common/extensionEnablementService.ts | 21 +- .../common/extensionManagementService.ts | 19 +- .../extensionEnablementService.test.ts | 74 +- .../browser/extensionResourceLoaderService.ts | 38 + .../common/extensionResourceLoader.ts | 21 + .../extensionResourceLoaderService.ts | 25 + .../extensions/browser/extensionService.ts | 10 +- .../extensions/browser/extensionUrlHandler.ts | 2 +- .../browser/webWorkerFileSystemProvider.ts | 5 +- .../common/extensionDescriptionRegistry.ts | 7 +- .../extensions/common/extensionDevOptions.ts | 2 +- .../common/extensionHostProcessManager.ts | 17 +- .../extensions/common/extensionsRegistry.ts | 53 +- .../extensions/common/extensionsUtil.ts | 143 +- .../services/extensions/common/rpcProtocol.ts | 59 +- .../electron-browser/extensionHost.ts | 34 +- .../electron-browser/extensionService.ts | 50 +- .../remoteExtensionManagementIpc.ts | 8 +- .../node/extensionHostProcessSetup.ts | 11 + .../extensions/node/extensionPoints.ts | 24 +- .../services/extensions/node/proxyResolver.ts | 14 +- .../extensions/test/node/rpcProtocol.test.ts | 11 + .../extensions/worker/extHost.services.ts | 10 +- .../common/filesConfigurationService.ts | 208 + .../services/history/browser/history.ts | 23 +- .../host/browser/browserHostService.ts | 2 +- .../keybinding/browser/keybindingService.ts | 79 +- .../keybinding/browser/keymapService.ts | 16 +- .../keybinding/common/keybindingEditing.ts | 2 +- .../keybindingEditing.test.ts | 14 +- .../services/label/common/labelService.ts | 8 +- .../services/layout/browser/layoutService.ts | 26 + .../mode/common/workbenchModeService.ts | 2 +- .../common/preferencesEditorInput.ts | 2 +- .../preferences/common/preferencesModels.ts | 2 +- .../common/keybindingsEditorModel.test.ts | 2 +- .../progress/browser/editorProgressService.ts | 11 - .../browser/media/progressService.css | 1 + .../progress/browser/progressIndicator.ts | 64 +- .../progress/browser/progressService.ts | 6 +- .../progress/test/progressIndicator.test.ts | 4 + .../remote/common/remoteExplorerService.ts | 236 ++ .../services/remote/node/tunnelService.ts | 55 +- .../{node => common}/fileSearchManager.ts | 3 +- .../services/search/common/replace.ts | 4 +- .../services/search/common/search.ts | 4 + .../services/search/common/searchService.ts | 16 +- .../search/common/textSearchManager.ts | 355 ++ .../services/search/node/rawSearchService.ts | 4 +- .../search/node/ripgrepTextSearchEngine.ts | 6 +- .../services/search/node/searchService.ts | 9 +- .../services/search/node/textSearchAdapter.ts | 7 +- .../services/search/node/textSearchManager.ts | 350 +- .../search/test/common/replace.test.ts | 18 +- .../test/node/textSearchManager.test.ts | 6 +- .../services/statusbar/common/statusbar.ts | 13 +- .../telemetry/browser/telemetryService.ts | 20 +- .../electron-browser/telemetryService.ts | 2 +- .../browser/abstractTextMateService.ts | 32 +- .../textMate/browser/textMateService.ts | 6 +- .../textMate/common/TMGrammarFactory.ts | 6 +- .../textMate/common/textMateService.ts | 2 +- .../electron-browser/textMateService.ts | 25 +- .../electron-browser/textMateWorker.ts | 4 +- .../browser/browserTextFileService.ts | 4 +- .../textfile/browser/textFileService.ts | 386 +- .../textfile/common/textFileEditorModel.ts | 87 +- .../common/textFileEditorModelManager.ts | 8 +- .../services/textfile/common/textfiles.ts | 98 +- .../electron-browser/nativeTextFileService.ts | 60 +- .../textfile/test/textFileEditorModel.test.ts | 87 +- .../test/textFileEditorModelManager.test.ts | 4 +- .../textfile/test/textFileService.io.test.ts | 30 +- .../textfile/test/textFileService.test.ts | 119 +- .../common/textModelResolverService.ts | 8 +- .../test/textModelResolverService.test.ts | 12 +- .../themes/browser/fileIconThemeData.ts | 4 +- .../themes/browser/workbenchThemeService.ts | 37 +- .../services/themes/common/colorThemeData.ts | 539 ++- .../themes/common/textMateScopeMatcher.ts | 134 + .../themes/common/themeCompatibility.ts | 8 +- .../themes/common/workbenchThemeService.ts | 17 +- .../tokenStyleResolving.test.ts | 324 ++ ...ervice.ts => untitledTextEditorService.ts} | 59 +- .../url/electron-browser/urlService.ts | 12 +- .../userData/common/fileUserDataProvider.ts | 16 +- .../common/inMemoryUserDataProvider.ts | 4 +- .../fileUserDataProvider.test.ts | 17 +- .../common/settingsMergeService.ts | 6 +- .../userDataSync/common/userDataSyncUtil.ts | 40 + .../workingCopy/common/workingCopyService.ts | 162 + .../test/common/workingCopyService.test.ts | 145 + .../abstractWorkspaceEditingService.ts | 60 +- .../browser/workspaceEditingService.ts | 19 +- .../workspaces/browser/workspacesService.ts | 12 +- .../workspaceEditingService.ts | 49 +- .../browser/parts/editor/baseEditor.test.ts | 12 +- .../parts/editor/breadcrumbModel.test.ts | 4 +- .../test/browser/parts/views/views.test.ts | 129 +- .../workbench/test/browser/quickopen.test.ts | 4 +- src/vs/workbench/test/browser/viewlet.test.ts | 8 +- .../common/editor/dataUriEditorInput.test.ts | 34 - .../test/common/editor/editor.test.ts | 12 +- .../test/common/editor/editorGroups.test.ts | 109 +- .../test/common/editor/editorInput.test.ts | 4 +- ...tor.test.ts => untitledTextEditor.test.ts} | 87 +- .../test/contrib/linkProtection.test.ts | 73 +- .../api/extHostApiCommands.test.ts | 69 +- .../api/extHostConfiguration.test.ts | 14 +- .../api/extHostDiagnostics.test.ts | 13 +- .../api/extHostDocumentData.test.ts | 6 +- .../extHostDocumentSaveParticipant.test.ts | 2 +- .../api/extHostFileSystemEventService.test.ts | 5 +- .../api/extHostLanguageFeatures.test.ts | 19 +- .../api/extHostSearch.test.ts | 12 +- .../api/extHostTypeConverter.test.ts | 6 +- .../electron-browser/api/extHostTypes.test.ts | 19 + .../api/extHostWebview.test.ts | 13 +- .../api/extHostWorkspace.test.ts | 106 +- .../api/mainThreadDocumentsAndEditors.test.ts | 3 +- .../api/mainThreadEditors.test.ts | 9 +- .../api/mainThreadSaveParticipant.test.ts | 3 +- .../quickopen.perf.integrationTest.ts | 7 +- .../textsearch.perf.integrationTest.ts | 7 +- .../workbench/test/workbenchTestServices.ts | 129 +- src/vs/workbench/workbench.common.main.ts | 12 +- src/vs/workbench/workbench.desktop.main.ts | 7 +- src/vs/workbench/workbench.web.api.ts | 81 +- src/vs/workbench/workbench.web.main.ts | 8 +- test/automation/.gitignore | 1 + test/automation/.npmignore | 6 + test/automation/package.json | 2 +- test/automation/src/debug.ts | 4 +- test/automation/src/editor.ts | 2 +- test/automation/src/index.ts | 2 +- test/automation/yarn.lock | 6 +- test/electron/renderer.html | 10 + test/smoke/README.md | 9 +- test/smoke/package.json | 2 +- test/smoke/src/areas/editor/editor.test.ts | 36 - .../src/areas/statusbar/statusbar.test.ts | 16 +- test/smoke/src/main.ts | 2 +- test/smoke/yarn.lock | 8 +- test/splitview/public/index.html | 10 +- yarn.lock | 325 +- 1507 files changed, 42727 insertions(+), 27284 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 build/azure-pipelines/common/createAsset.ts create mode 100644 build/azure-pipelines/common/createBuild.ts create mode 100644 build/azure-pipelines/common/releaseBuild.ts delete mode 100644 build/azure-pipelines/linux/frozen-check.js create mode 100644 extensions/git/src/fileSystemProvider.ts create mode 100644 extensions/git/src/ipc/ipcClient.ts create mode 100644 extensions/git/src/ipc/ipcServer.ts delete mode 100644 extensions/git/src/typings/jschardet.d.ts create mode 100644 extensions/image-preview/src/binarySizeStatusBarEntry.ts create mode 100644 extensions/image-preview/src/ownedStatusBarEntry.ts create mode 100644 extensions/search-result/README.md rename src/typings/yazl.d.ts => extensions/search-result/extension.webpack.config.js (60%) create mode 100644 extensions/search-result/package.json create mode 100644 extensions/search-result/package.nls.json create mode 100644 extensions/search-result/src/extension.ts create mode 100644 extensions/search-result/src/media/refresh-dark.svg create mode 100644 extensions/search-result/src/media/refresh-light.svg rename src/typings/graceful-fs.d.ts => extensions/search-result/src/typings/refs.d.ts (74%) create mode 100644 extensions/search-result/syntaxes/searchResult.tmLanguage.json create mode 100644 extensions/search-result/tsconfig.json create mode 100644 extensions/search-result/yarn.lock create mode 100644 extensions/vscode-colorize-tests/src/colorizerTestMain.ts delete mode 100644 src/typings/applicationInsights.d.ts delete mode 100644 src/typings/applicationinsights-web.d.ts delete mode 100644 src/typings/chokidar.d.ts delete mode 100644 src/typings/http-proxy-agent.d.ts delete mode 100644 src/typings/iconv-lite.d.ts delete mode 100644 src/typings/jschardet.d.ts delete mode 100644 src/typings/native-is-elevated.d.ts delete mode 100644 src/typings/native-keymap.d.ts delete mode 100644 src/typings/native-watchdog.d.ts delete mode 100644 src/typings/node-pty.d.ts delete mode 100644 src/typings/nsfw.d.ts delete mode 100644 src/typings/onigasm-umd.d.ts delete mode 100644 src/typings/spdlog.d.ts delete mode 100644 src/typings/sudo-prompt.d.ts delete mode 100644 src/typings/v8-inspect-profiler.d.ts delete mode 100644 src/typings/vscode-minimist.d.ts delete mode 100644 src/typings/vscode-proxy-agent.d.ts delete mode 100644 src/typings/vscode-ripgrep.d.ts delete mode 100644 src/typings/vscode-sqlite3.d.ts delete mode 100644 src/typings/vscode-textmate.d.ts delete mode 100644 src/typings/vscode-windows-ca-certs.d.ts delete mode 100644 src/typings/windows-process-tree.d.ts delete mode 100644 src/typings/xterm-addon-search.d.ts delete mode 100644 src/typings/xterm-addon-web-links.d.ts delete mode 100644 src/typings/xterm.d.ts delete mode 100644 src/typings/yauzl.d.ts create mode 100644 src/vs/base/browser/canIUse.ts delete mode 100644 src/vs/base/browser/ui/dialog/close-dark.svg delete mode 100644 src/vs/base/browser/ui/dialog/close-light.svg delete mode 100644 src/vs/base/browser/ui/dialog/error-dark.svg delete mode 100644 src/vs/base/browser/ui/dialog/error-light.svg delete mode 100644 src/vs/base/browser/ui/dialog/info-dark.svg delete mode 100644 src/vs/base/browser/ui/dialog/info-light.svg delete mode 100644 src/vs/base/browser/ui/dialog/pending-dark.svg delete mode 100644 src/vs/base/browser/ui/dialog/pending-hc.svg delete mode 100644 src/vs/base/browser/ui/dialog/pending.svg delete mode 100644 src/vs/base/browser/ui/dialog/warning-dark.svg delete mode 100644 src/vs/base/browser/ui/dialog/warning-light.svg delete mode 100644 src/vs/base/browser/ui/list/media/close-dark.svg delete mode 100644 src/vs/base/browser/ui/list/media/close-hc.svg delete mode 100644 src/vs/base/browser/ui/list/media/close-light.svg delete mode 100644 src/vs/base/browser/ui/list/media/filter-dark.svg delete mode 100644 src/vs/base/browser/ui/list/media/filter-hc.svg delete mode 100644 src/vs/base/browser/ui/list/media/filter-light.svg delete mode 100644 src/vs/base/browser/ui/list/media/no-filter-dark.svg delete mode 100644 src/vs/base/browser/ui/list/media/no-filter-hc.svg delete mode 100644 src/vs/base/browser/ui/list/media/no-filter-light.svg delete mode 100644 src/vs/base/browser/ui/splitview/panelview.css create mode 100644 src/vs/base/browser/ui/splitview/paneview.css rename src/vs/base/browser/ui/splitview/{panelview.ts => paneview.ts} (70%) rename src/vs/base/browser/ui/tree/media/{panelviewlet.css => paneviewlet.css} (80%) create mode 100644 src/vs/base/common/stream.ts rename src/{typings/windows-foreground-love.d.ts => vs/base/common/styler.ts} (67%) create mode 100644 src/vs/base/test/common/stream.test.ts create mode 100644 src/vs/base/test/node/encoding/fixtures/some_file.css rename src/vs/base/{node/test => test/node/zip}/fixtures/extract.zip (100%) rename src/vs/base/{node/test => test/node/zip}/zip.test.ts (100%) rename src/vs/code/{electron-browser => common}/issue/issueReporterUtil.ts (100%) delete mode 100644 src/vs/editor/browser/viewParts/currentLineMarginHighlight/currentLineMarginHighlight.css delete mode 100644 src/vs/editor/browser/viewParts/currentLineMarginHighlight/currentLineMarginHighlight.ts create mode 100644 src/vs/editor/browser/viewParts/minimap/minimapPreBaked.ts delete mode 100644 src/vs/editor/common/viewLayout/whitespaceComputer.ts create mode 100644 src/vs/editor/contrib/codeAction/codeActionMenu.ts delete mode 100644 src/vs/editor/contrib/codeAction/codeActionTrigger.ts delete mode 100644 src/vs/editor/contrib/codeAction/codeActionWidget.ts create mode 100644 src/vs/editor/contrib/codeAction/test/codeActionKeybindingResolver.test.ts create mode 100644 src/vs/editor/contrib/codeAction/types.ts create mode 100644 src/vs/editor/contrib/cursorUndo/test/cursorUndo.test.ts delete mode 100644 src/vs/editor/contrib/goToDefinition/goToDefinitionCommands.ts create mode 100644 src/vs/editor/contrib/gotoSymbol/goToCommands.ts rename src/vs/editor/contrib/{goToDefinition/goToDefinition.ts => gotoSymbol/goToSymbol.ts} (68%) rename src/vs/editor/contrib/{goToDefinition => gotoSymbol/link}/clickLinkGesture.ts (99%) rename src/vs/editor/contrib/{goToDefinition/goToDefinitionMouse.css => gotoSymbol/link/goToDefinitionAtPosition.css} (100%) rename src/vs/editor/contrib/{goToDefinition/goToDefinitionMouse.ts => gotoSymbol/link/goToDefinitionAtPosition.ts} (67%) rename src/vs/editor/contrib/{referenceSearch => gotoSymbol/peek}/referencesController.ts (52%) rename src/vs/editor/contrib/{referenceSearch => gotoSymbol/peek}/referencesTree.ts (97%) rename src/vs/editor/contrib/{referenceSearch/media => gotoSymbol/peek}/referencesWidget.css (100%) rename src/vs/editor/contrib/{referenceSearch => gotoSymbol/peek}/referencesWidget.ts (72%) rename src/vs/editor/contrib/{referenceSearch => gotoSymbol}/referencesModel.ts (78%) rename src/vs/editor/contrib/{goToDefinition/goToDefinitionResultsNavigation.ts => gotoSymbol/symbolNavigation.ts} (99%) rename src/vs/editor/contrib/{referenceSearch => gotoSymbol}/test/referencesModel.test.ts (93%) rename src/vs/editor/contrib/{referenceSearch => peekView}/media/peekViewWidget.css (87%) rename src/vs/editor/contrib/{referenceSearch/peekViewWidget.ts => peekView/peekView.ts} (61%) delete mode 100644 src/vs/editor/contrib/referenceSearch/referenceSearch.ts create mode 100644 src/vs/editor/contrib/suggest/suggestRangeHighlighter.ts delete mode 100644 src/vs/editor/test/browser/view/minimapFontCreator.html delete mode 100644 src/vs/editor/test/browser/view/minimapFontCreator.ts delete mode 100644 src/vs/editor/test/common/viewLayout/whitespaceComputer.test.ts create mode 100644 src/vs/platform/auth/common/auth.css create mode 100644 src/vs/platform/auth/common/auth.html create mode 100644 src/vs/platform/auth/electron-browser/authServer.ts create mode 100644 src/vs/platform/auth/electron-browser/authTokenService.ts create mode 100644 src/vs/platform/environment/node/waitMarkerFile.ts create mode 100644 src/vs/platform/files/common/io.ts create mode 100644 src/vs/platform/theme/common/tokenClassificationRegistry.ts create mode 100644 src/vs/platform/userDataSync/common/content.ts create mode 100644 src/vs/platform/userDataSync/common/keybindingsMerge.ts create mode 100644 src/vs/platform/userDataSync/common/keybindingsSync.ts create mode 100644 src/vs/platform/userDataSync/common/keybindingsSyncIpc.ts create mode 100644 src/vs/platform/userDataSync/common/userDataAutoSync.ts create mode 100644 src/vs/platform/userDataSync/electron-browser/userDataAutoSync.ts create mode 100644 src/vs/platform/userDataSync/test/common/keybindingsMerge.test.ts create mode 100644 src/vs/workbench/api/common/shared/semanticTokens.ts delete mode 100644 src/vs/workbench/browser/parts/activitybar/media/ellipsis-activity-bar.svg delete mode 100644 src/vs/workbench/browser/parts/activitybar/media/settings-activity-bar.svg create mode 100644 src/vs/workbench/browser/parts/editor/editorAutoSave.ts delete mode 100644 src/vs/workbench/browser/parts/editor/media/close-all-dark.svg delete mode 100644 src/vs/workbench/browser/parts/editor/media/close-all-light.svg delete mode 100644 src/vs/workbench/browser/parts/editor/media/close-dark-alt.svg delete mode 100644 src/vs/workbench/browser/parts/editor/media/close-dirty-dark-alt.svg delete mode 100644 src/vs/workbench/browser/parts/editor/media/close-dirty-light-alt.svg delete mode 100644 src/vs/workbench/browser/parts/editor/media/close-light-alt.svg delete mode 100644 src/vs/workbench/browser/parts/editor/media/next-diff-dark.svg delete mode 100644 src/vs/workbench/browser/parts/editor/media/next-diff-light.svg delete mode 100644 src/vs/workbench/browser/parts/editor/media/paragraph-dark.svg delete mode 100644 src/vs/workbench/browser/parts/editor/media/paragraph-disabled-dark.svg delete mode 100644 src/vs/workbench/browser/parts/editor/media/paragraph-disabled-light.svg delete mode 100644 src/vs/workbench/browser/parts/editor/media/paragraph-light.svg delete mode 100644 src/vs/workbench/browser/parts/editor/media/previous-diff-dark.svg delete mode 100644 src/vs/workbench/browser/parts/editor/media/previous-diff-light.svg delete mode 100644 src/vs/workbench/browser/parts/editor/media/split-editor-horizontal-dark.svg delete mode 100644 src/vs/workbench/browser/parts/editor/media/split-editor-horizontal-hc.svg delete mode 100644 src/vs/workbench/browser/parts/editor/media/split-editor-horizontal-light.svg delete mode 100644 src/vs/workbench/browser/parts/editor/media/split-editor-vertical-dark.svg delete mode 100644 src/vs/workbench/browser/parts/editor/media/split-editor-vertical-hc.svg delete mode 100644 src/vs/workbench/browser/parts/editor/media/split-editor-vertical-light.svg delete mode 100644 src/vs/workbench/browser/parts/panel/media/ellipsis-dark.svg delete mode 100644 src/vs/workbench/browser/parts/panel/media/ellipsis-hc.svg delete mode 100644 src/vs/workbench/browser/parts/panel/media/ellipsis-light.svg rename src/vs/workbench/browser/parts/views/media/{panelviewlet.css => paneviewlet.css} (72%) rename src/vs/workbench/browser/parts/views/{panelViewlet.ts => paneViewlet.ts} (71%) rename src/{typings/require-monaco.d.ts => vs/workbench/common/configuration.ts} (51%) delete mode 100644 src/vs/workbench/common/editor/dataUriEditorInput.ts rename src/vs/workbench/common/editor/{untitledEditorInput.ts => untitledTextEditorInput.ts} (56%) rename src/vs/workbench/common/editor/{untitledEditorModel.ts => untitledTextEditorModel.ts} (78%) create mode 100644 src/vs/workbench/contrib/codeActions/common/codeActions.contribution.ts create mode 100644 src/vs/workbench/contrib/codeActions/common/configuration.ts create mode 100644 src/vs/workbench/contrib/codeActions/common/extensionPoint.ts create mode 100644 src/vs/workbench/contrib/codeEditor/electron-browser/inputClipboardActions.ts create mode 100644 src/vs/workbench/contrib/customEditor/common/customEditorModel.ts create mode 100644 src/vs/workbench/contrib/customEditor/common/customEditorModelManager.ts delete mode 100644 src/vs/workbench/contrib/debug/browser/media/add-dark.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/add-hc.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/add-light.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/breakpoint-conditional.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/breakpoint-data-disabled.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/breakpoint-data-unverified.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/breakpoint-data.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/breakpoint-disabled.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/breakpoint-function-disabled.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/breakpoint-function-unverified.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/breakpoint-function.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/breakpoint-hint.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/breakpoint-log-disabled.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/breakpoint-log-unverified.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/breakpoint-log.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/breakpoint-unsupported.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/breakpoint-unverified.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/breakpoint.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/close-all-dark.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/close-all-hc.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/close-all-light.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/configure-dark.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/configure-hc.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/configure-light.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/console-dark.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/console-hc.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/console-light.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/continue-dark.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/continue-light.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/continue-white.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/current-and-breakpoint.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/current-arrow.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/debug-activity-bar.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/disconnect-dark.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/disconnect-light.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/disconnect-white.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/drag.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/pause-dark.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/pause-light.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/pause-white.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/restart-dark.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/restart-light.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/restart-white.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/reverse-continue-dark.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/reverse-continue-light.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/stackframe-and-breakpoint.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/stackframe-arrow.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/start-dark.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/start-hc.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/start-light.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/step-back-dark.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/step-back-light.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/step-into-dark.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/step-into-light.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/step-into-white.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/step-out-dark.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/step-out-light.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/step-out-white.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/step-over-dark.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/step-over-light.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/step-over-white.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/stop-dark.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/stop-light.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/stop-white.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/toggle-breakpoints-dark.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/toggle-breakpoints-hc.svg delete mode 100644 src/vs/workbench/contrib/debug/browser/media/toggle-breakpoints-light.svg create mode 100644 src/vs/workbench/contrib/debug/browser/startView.ts delete mode 100644 src/vs/workbench/contrib/extensions/browser/media/clear-dark.svg delete mode 100644 src/vs/workbench/contrib/extensions/browser/media/clear-hc.svg delete mode 100644 src/vs/workbench/contrib/extensions/browser/media/clear-light.svg delete mode 100644 src/vs/workbench/contrib/extensions/browser/media/configure-dark.svg delete mode 100644 src/vs/workbench/contrib/extensions/browser/media/configure-hc.svg delete mode 100644 src/vs/workbench/contrib/extensions/browser/media/configure-light.svg delete mode 100644 src/vs/workbench/contrib/extensions/browser/media/extensions-activity-bar.svg delete mode 100644 src/vs/workbench/contrib/extensions/browser/media/extensions.css delete mode 100644 src/vs/workbench/contrib/extensions/browser/media/star-empty.svg delete mode 100644 src/vs/workbench/contrib/extensions/browser/media/star-full.svg delete mode 100644 src/vs/workbench/contrib/extensions/browser/media/star-half.svg delete mode 100644 src/vs/workbench/contrib/extensions/browser/media/star-small.svg delete mode 100644 src/vs/workbench/contrib/files/browser/media/check-dark.svg delete mode 100644 src/vs/workbench/contrib/files/browser/media/check-light.svg delete mode 100644 src/vs/workbench/contrib/files/browser/media/collapse-all-dark.svg delete mode 100644 src/vs/workbench/contrib/files/browser/media/collapse-all-hc.svg delete mode 100644 src/vs/workbench/contrib/files/browser/media/collapse-all-light.svg delete mode 100644 src/vs/workbench/contrib/files/browser/media/files-activity-bar.svg delete mode 100644 src/vs/workbench/contrib/files/browser/media/preview-dark.svg delete mode 100644 src/vs/workbench/contrib/files/browser/media/preview-light.svg delete mode 100644 src/vs/workbench/contrib/files/browser/media/split-editor-horizontal-dark.svg delete mode 100644 src/vs/workbench/contrib/files/browser/media/split-editor-horizontal-hc.svg delete mode 100644 src/vs/workbench/contrib/files/browser/media/split-editor-horizontal-light.svg delete mode 100644 src/vs/workbench/contrib/files/browser/media/split-editor-vertical-dark.svg delete mode 100644 src/vs/workbench/contrib/files/browser/media/split-editor-vertical-hc.svg delete mode 100644 src/vs/workbench/contrib/files/browser/media/split-editor-vertical-light.svg delete mode 100644 src/vs/workbench/contrib/files/browser/media/undo-dark.svg delete mode 100644 src/vs/workbench/contrib/files/browser/media/undo-light.svg rename src/vs/workbench/contrib/files/browser/{saveErrorHandler.ts => textFileSaveErrorHandler.ts} (95%) create mode 100644 src/vs/workbench/contrib/files/common/dirtyFilesIndicator.ts delete mode 100644 src/vs/workbench/contrib/files/common/dirtyFilesTracker.ts delete mode 100644 src/vs/workbench/contrib/files/electron-browser/dirtyFilesTracker.ts create mode 100644 src/vs/workbench/contrib/issue/browser/issue.contribution.ts create mode 100644 src/vs/workbench/contrib/issue/browser/issueService.ts create mode 100644 src/vs/workbench/contrib/outline/browser/outlineNavigation.ts rename src/vs/workbench/contrib/outline/browser/{outlinePanel.css => outlinePane.css} (67%) rename src/vs/workbench/contrib/outline/browser/{outlinePanel.ts => outlinePane.ts} (95%) create mode 100644 src/vs/workbench/contrib/remote/browser/explorerViewItems.ts delete mode 100644 src/vs/workbench/contrib/remote/browser/help-documentation-dark.svg delete mode 100644 src/vs/workbench/contrib/remote/browser/help-documentation-hc.svg delete mode 100644 src/vs/workbench/contrib/remote/browser/help-documentation-light.svg delete mode 100644 src/vs/workbench/contrib/remote/browser/help-feedback-dark.svg delete mode 100644 src/vs/workbench/contrib/remote/browser/help-feedback-hc.svg delete mode 100644 src/vs/workbench/contrib/remote/browser/help-feedback-light.svg delete mode 100644 src/vs/workbench/contrib/remote/browser/help-getting-started-dark.svg delete mode 100644 src/vs/workbench/contrib/remote/browser/help-getting-started-hc.svg delete mode 100644 src/vs/workbench/contrib/remote/browser/help-getting-started-light.svg delete mode 100644 src/vs/workbench/contrib/remote/browser/help-report-issue-dark.svg delete mode 100644 src/vs/workbench/contrib/remote/browser/help-report-issue-hc.svg delete mode 100644 src/vs/workbench/contrib/remote/browser/help-report-issue-light.svg delete mode 100644 src/vs/workbench/contrib/remote/browser/help-review-issues-dark.svg delete mode 100644 src/vs/workbench/contrib/remote/browser/help-review-issues-hc.svg delete mode 100644 src/vs/workbench/contrib/remote/browser/help-review-issues-light.svg rename src/vs/{editor/browser/widget/media/tokens.css => workbench/contrib/remote/browser/media/tunnelView.css} (73%) delete mode 100644 src/vs/workbench/contrib/remote/browser/remote-activity-bar.svg create mode 100644 src/vs/workbench/contrib/remote/browser/tunnelView.ts rename src/vs/workbench/contrib/scm/browser/{mainPanel.ts => mainPane.ts} (93%) delete mode 100644 src/vs/workbench/contrib/scm/browser/media/scm-activity-bar.svg rename src/vs/workbench/contrib/scm/browser/{repositoryPanel.ts => repositoryPane.ts} (95%) delete mode 100644 src/vs/workbench/contrib/search/browser/media/search-activity-bar.svg delete mode 100644 src/vs/workbench/contrib/search/browser/media/search.contribution.css create mode 100644 src/vs/workbench/contrib/search/browser/searchEditor.ts rename src/vs/workbench/contrib/{stats/browser/workspaceStatsService.ts => tags/browser/workspaceTagsService.ts} (78%) rename src/vs/workbench/contrib/{stats/common/workspaceStats.ts => tags/common/workspaceTags.ts} (88%) rename src/vs/workbench/contrib/{stats/electron-browser/stats.contribution.ts => tags/electron-browser/tags.contribution.ts} (75%) rename src/vs/workbench/contrib/{stats/electron-browser/workspaceStats.ts => tags/electron-browser/workspaceTags.ts} (95%) rename src/vs/workbench/contrib/{stats/electron-browser/workspaceStatsService.ts => tags/electron-browser/workspaceTagsService.ts} (97%) rename src/vs/workbench/contrib/{stats/test/workspaceStats.test.ts => tags/test/workspaceTags.test.ts} (98%) delete mode 100644 src/vs/workbench/contrib/tasks/common/media/configure-dark.svg delete mode 100644 src/vs/workbench/contrib/tasks/common/media/configure-hc.svg delete mode 100644 src/vs/workbench/contrib/tasks/common/media/configure-light.svg delete mode 100644 src/vs/workbench/contrib/tasks/common/media/task.contribution.css create mode 100644 src/vs/workbench/contrib/terminal/common/terminalDataBuffering.ts create mode 100644 src/vs/workbench/contrib/terminal/test/common/terminalDataBuffering.test.ts create mode 100644 src/vs/workbench/contrib/testCustomEditors/browser/testCustomEditors.ts delete mode 100644 src/vs/workbench/contrib/update/browser/media/markdown.css create mode 100644 src/vs/workbench/contrib/userDataSync/browser/userDataAutoSync.ts create mode 100644 src/vs/workbench/contrib/userDataSync/browser/userDataSyncTrigger.ts rename src/vs/{platform/auth/common => workbench/services/authToken/browser}/authTokenService.ts (51%) create mode 100644 src/vs/workbench/services/backup/electron-browser/backup.ts rename src/vs/workbench/services/backup/test/{node => electron-browser}/backupFileService.test.ts (99%) rename src/vs/workbench/services/environment/{node => electron-browser}/environmentService.ts (65%) create mode 100644 src/vs/workbench/services/extensionResourceLoader/browser/extensionResourceLoaderService.ts create mode 100644 src/vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader.ts create mode 100644 src/vs/workbench/services/extensionResourceLoader/electron-browser/extensionResourceLoaderService.ts create mode 100644 src/vs/workbench/services/filesConfiguration/common/filesConfigurationService.ts delete mode 100644 src/vs/workbench/services/progress/browser/editorProgressService.ts create mode 100644 src/vs/workbench/services/remote/common/remoteExplorerService.ts rename src/vs/workbench/services/search/{node => common}/fileSearchManager.ts (99%) create mode 100644 src/vs/workbench/services/search/common/textSearchManager.ts create mode 100644 src/vs/workbench/services/themes/common/textMateScopeMatcher.ts create mode 100644 src/vs/workbench/services/themes/test/electron-browser/tokenStyleResolving.test.ts rename src/vs/workbench/services/untitled/common/{untitledEditorService.ts => untitledTextEditorService.ts} (80%) create mode 100644 src/vs/workbench/services/userDataSync/common/userDataSyncUtil.ts create mode 100644 src/vs/workbench/services/workingCopy/common/workingCopyService.ts create mode 100644 src/vs/workbench/services/workingCopy/test/common/workingCopyService.test.ts delete mode 100644 src/vs/workbench/test/common/editor/dataUriEditorInput.test.ts rename src/vs/workbench/test/common/editor/{untitledEditor.test.ts => untitledTextEditor.test.ts} (73%) create mode 100644 test/automation/.npmignore diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000000..3ba13e0cec6c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1 @@ +blank_issues_enabled: false diff --git a/.vscode/launch.json b/.vscode/launch.json index 971df773670d..e67212baab8b 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -67,17 +67,16 @@ "request": "launch", "name": "Launch azuredatastudio", "windows": { - "runtimeExecutable": "${workspaceFolder}/scripts/sql.bat", - "timeout": 45000 + "runtimeExecutable": "${workspaceFolder}/scripts/sql.bat" }, "osx": { - "runtimeExecutable": "${workspaceFolder}/scripts/sql.sh", - "timeout": 45000 + "runtimeExecutable": "${workspaceFolder}/scripts/sql.sh" }, "linux": { - "runtimeExecutable": "${workspaceFolder}/scripts/sql.sh", - "timeout": 45000 + "runtimeExecutable": "${workspaceFolder}/scripts/sql.sh" }, + "port": 9222, + "timeout": 20000, "env": { "VSCODE_EXTHOST_WILL_SEND_SOCKET": null }, @@ -260,6 +259,14 @@ "Attach to Main Process" ] }, + { + "name": "Debug azuredatastudio Main, Renderer & Extension Host", + "configurations": [ + "Launch azuredatastudio", + "Attach to Main Process", + "Attach to Extension Host" + ] + }, { "name": "Debug Renderer and search processes", "configurations": [ diff --git a/.vscode/settings.json b/.vscode/settings.json index 30b4aab45483..ef1f7370d26e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -39,6 +39,7 @@ ], "typescript.tsdk": "node_modules/typescript/lib", "npm.exclude": "**/extensions/**", + "npm.packageManager": "yarn", "emmet.excludeLanguages": [], "typescript.preferences.importModuleSpecifier": "non-relative", "typescript.preferences.quoteStyle": "single", diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 8e0dcabd44ae..042f5058f8c7 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -33,15 +33,15 @@ }, { "type": "npm", - "script": "strict-initialization-watch", - "label": "TS - Strict Initialization", + "script": "strict-function-types-watch", + "label": "TS - Strict Function Types", "isBackground": true, "presentation": { "reveal": "never" }, "problemMatcher": { "base": "$tsc-watch", - "owner": "typescript-strict-initialization", + "owner": "typescript-function-types", "applyTo": "allDocuments" } }, diff --git a/.yarnrc b/.yarnrc index c54f7d6d6e3a..288e7393abdc 100644 --- a/.yarnrc +++ b/.yarnrc @@ -1,3 +1,3 @@ disturl "https://atom.io/download/electron" -target "6.0.12" +target "6.1.5" runtime "electron" diff --git a/build/azure-pipelines/common/createAsset.ts b/build/azure-pipelines/common/createAsset.ts new file mode 100644 index 000000000000..7c41748f570f --- /dev/null +++ b/build/azure-pipelines/common/createAsset.ts @@ -0,0 +1,132 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import * as fs from 'fs'; +import { Readable } from 'stream'; +import * as crypto from 'crypto'; +import * as azure from 'azure-storage'; +import * as mime from 'mime'; +import { CosmosClient } from '@azure/cosmos'; + +interface Asset { + platform: string; + type: string; + url: string; + mooncakeUrl?: string; + hash: string; + sha256hash: string; + size: number; + supportsFastUpdate?: boolean; +} + +if (process.argv.length !== 6) { + console.error('Usage: node createAsset.js PLATFORM TYPE NAME FILE'); + process.exit(-1); +} + +function hashStream(hashName: string, stream: Readable): Promise { + return new Promise((c, e) => { + const shasum = crypto.createHash(hashName); + + stream + .on('data', shasum.update.bind(shasum)) + .on('error', e) + .on('close', () => c(shasum.digest('hex'))); + }); +} + +async function doesAssetExist(blobService: azure.BlobService, quality: string, blobName: string): Promise { + const existsResult = await new Promise((c, e) => blobService.doesBlobExist(quality, blobName, (err, r) => err ? e(err) : c(r))); + return existsResult.exists; +} + +async function uploadBlob(blobService: azure.BlobService, quality: string, blobName: string, file: string): Promise { + const blobOptions: azure.BlobService.CreateBlockBlobRequestOptions = { + contentSettings: { + contentType: mime.lookup(file), + cacheControl: 'max-age=31536000, public' + } + }; + + await new Promise((c, e) => blobService.createBlockBlobFromLocalFile(quality, blobName, file, blobOptions, err => err ? e(err) : c())); +} + +function getEnv(name: string): string { + const result = process.env[name]; + + if (typeof result === 'undefined') { + throw new Error('Missing env: ' + name); + } + + return result; +} + +async function main(): Promise { + const [, , platform, type, name, file] = process.argv; + const quality = getEnv('VSCODE_QUALITY'); + const commit = getEnv('BUILD_SOURCEVERSION'); + + console.log('Creating asset...'); + + const stat = await new Promise((c, e) => fs.stat(file, (err, stat) => err ? e(err) : c(stat))); + const size = stat.size; + + console.log('Size:', size); + + const stream = fs.createReadStream(file); + const [sha1hash, sha256hash] = await Promise.all([hashStream('sha1', stream), hashStream('sha256', stream)]); + + console.log('SHA1:', sha1hash); + console.log('SHA256:', sha256hash); + + const blobName = commit + '/' + name; + const storageAccount = process.env['AZURE_STORAGE_ACCOUNT_2']!; + + const blobService = azure.createBlobService(storageAccount, process.env['AZURE_STORAGE_ACCESS_KEY_2']!) + .withFilter(new azure.ExponentialRetryPolicyFilter(20)); + + const blobExists = await doesAssetExist(blobService, quality, blobName); + + if (blobExists) { + console.log(`Blob ${quality}, ${blobName} already exists, not publishing again.`); + return; + } + + console.log('Uploading blobs to Azure storage...'); + + await uploadBlob(blobService, quality, blobName, file); + + console.log('Blobs successfully uploaded.'); + + const asset: Asset = { + platform, + type, + url: `${process.env['AZURE_CDN_URL']}/${quality}/${blobName}`, + hash: sha1hash, + sha256hash, + size + }; + + // Remove this if we ever need to rollback fast updates for windows + if (/win32/.test(platform)) { + asset.supportsFastUpdate = true; + } + + console.log('Asset:', JSON.stringify(asset, null, ' ')); + + const client = new CosmosClient({ endpoint: process.env['AZURE_DOCUMENTDB_ENDPOINT']!, key: process.env['AZURE_DOCUMENTDB_MASTERKEY'] }); + const scripts = client.database('builds').container(quality).scripts; + await scripts.storedProcedure('createAsset').execute('', [commit, asset, true]); +} + +main().then(() => { + console.log('Asset successfully created'); + process.exit(0); +}, err => { + console.error(err); + process.exit(1); +}); diff --git a/build/azure-pipelines/common/createBuild.ts b/build/azure-pipelines/common/createBuild.ts new file mode 100644 index 000000000000..eecf0e945ebd --- /dev/null +++ b/build/azure-pipelines/common/createBuild.ts @@ -0,0 +1,60 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import { CosmosClient } from '@azure/cosmos'; + +if (process.argv.length !== 3) { + console.error('Usage: node createBuild.js VERSION'); + process.exit(-1); +} + +function getEnv(name: string): string { + const result = process.env[name]; + + if (typeof result === 'undefined') { + throw new Error('Missing env: ' + name); + } + + return result; +} + +async function main(): Promise { + const [, , _version] = process.argv; + const quality = getEnv('VSCODE_QUALITY'); + const commit = getEnv('BUILD_SOURCEVERSION'); + const queuedBy = getEnv('BUILD_QUEUEDBY'); + const sourceBranch = getEnv('BUILD_SOURCEBRANCH'); + const version = _version + (quality === 'stable' ? '' : `-${quality}`); + + console.log('Creating build...'); + console.log('Quality:', quality); + console.log('Version:', version); + console.log('Commit:', commit); + + const build = { + id: commit, + timestamp: (new Date()).getTime(), + version, + isReleased: false, + sourceBranch, + queuedBy, + assets: [], + updates: {} + }; + + const client = new CosmosClient({ endpoint: process.env['AZURE_DOCUMENTDB_ENDPOINT']!, key: process.env['AZURE_DOCUMENTDB_MASTERKEY'] }); + const scripts = client.database('builds').container(quality).scripts; + await scripts.storedProcedure('createBuild').execute('', [{ ...build, _partitionKey: '' }]); +} + +main().then(() => { + console.log('Build successfully created'); + process.exit(0); +}, err => { + console.error(err); + process.exit(1); +}); diff --git a/build/azure-pipelines/common/releaseBuild.ts b/build/azure-pipelines/common/releaseBuild.ts new file mode 100644 index 000000000000..e8a9835fb6d0 --- /dev/null +++ b/build/azure-pipelines/common/releaseBuild.ts @@ -0,0 +1,70 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import { CosmosClient } from '@azure/cosmos'; + +function getEnv(name: string): string { + const result = process.env[name]; + + if (typeof result === 'undefined') { + throw new Error('Missing env: ' + name); + } + + return result; +} + +interface Config { + id: string; + frozen: boolean; +} + +function createDefaultConfig(quality: string): Config { + return { + id: quality, + frozen: false + }; +} + +async function getConfig(client: CosmosClient, quality: string): Promise { + const query = `SELECT TOP 1 * FROM c WHERE c.id = "${quality}"`; + + const res = await client.database('builds').container('config').items.query(query).fetchAll(); + + if (res.resources.length === 0) { + return createDefaultConfig(quality); + } + + return res.resources[0] as Config; +} + +async function main(): Promise { + const commit = getEnv('BUILD_SOURCEVERSION'); + const quality = getEnv('VSCODE_QUALITY'); + + const client = new CosmosClient({ endpoint: process.env['AZURE_DOCUMENTDB_ENDPOINT']!, key: process.env['AZURE_DOCUMENTDB_MASTERKEY'] }); + const config = await getConfig(client, quality); + + console.log('Quality config:', config); + + if (config.frozen) { + console.log(`Skipping release because quality ${quality} is frozen.`); + return; + } + + console.log(`Releasing build ${commit}...`); + + const scripts = client.database('builds').container(quality).scripts; + await scripts.storedProcedure('releaseBuild').execute('', [commit]); +} + +main().then(() => { + console.log('Build successfully released'); + process.exit(0); +}, err => { + console.error(err); + process.exit(1); +}); diff --git a/build/azure-pipelines/common/sync-mooncake.ts b/build/azure-pipelines/common/sync-mooncake.ts index a7dea2bcfef9..97fc67c10fc4 100644 --- a/build/azure-pipelines/common/sync-mooncake.ts +++ b/build/azure-pipelines/common/sync-mooncake.ts @@ -8,7 +8,7 @@ import * as url from 'url'; import * as azure from 'azure-storage'; import * as mime from 'mime'; -import { DocumentClient, RetrievedDocument } from 'documentdb'; +import { CosmosClient } from '@azure/cosmos'; function log(...args: any[]) { console.log(...[`[${new Date().toISOString()}]`, ...args]); @@ -23,7 +23,7 @@ if (process.argv.length < 3) { process.exit(-1); } -interface Build extends RetrievedDocument { +interface Build { assets: Asset[]; } @@ -38,62 +38,20 @@ interface Asset { supportsFastUpdate?: boolean; } -function updateBuild(commit: string, quality: string, platform: string, type: string, asset: Asset): Promise { - const client = new DocumentClient(process.env['AZURE_DOCUMENTDB_ENDPOINT']!, { masterKey: process.env['AZURE_DOCUMENTDB_MASTERKEY'] }); - const collection = 'dbs/builds/colls/' + quality; - const updateQuery = { - query: 'SELECT TOP 1 * FROM c WHERE c.id = @id', - parameters: [{ name: '@id', value: commit }] - }; - - let updateTries = 0; - - function _update(): Promise { - updateTries++; - - return new Promise((c, e) => { - client.queryDocuments(collection, updateQuery).toArray((err, results) => { - if (err) { return e(err); } - if (results.length !== 1) { return e(new Error('No documents')); } - - const release = results[0]; +async function sync(commit: string, quality: string): Promise { + log(`Synchronizing Mooncake assets for ${quality}, ${commit}...`); - release.assets = [ - ...release.assets.filter((a: any) => !(a.platform === platform && a.type === type)), - asset - ]; + const client = new CosmosClient({ endpoint: process.env['AZURE_DOCUMENTDB_ENDPOINT']!, key: process.env['AZURE_DOCUMENTDB_MASTERKEY'] }); + const container = client.database('builds').container(quality); - client.replaceDocument(release._self, release, err => { - if (err && err.code === 409 && updateTries < 5) { return c(_update()); } - if (err) { return e(err); } + const query = `SELECT TOP 1 * FROM c WHERE c.id = "${commit}"`; + const res = await container.items.query(query, {}).fetchAll(); - log('Build successfully updated.'); - c(); - }); - }); - }); + if (res.resources.length !== 1) { + throw new Error(`No builds found for ${commit}`); } - return _update(); -} - -async function sync(commit: string, quality: string): Promise { - log(`Synchronizing Mooncake assets for ${quality}, ${commit}...`); - - const cosmosdb = new DocumentClient(process.env['AZURE_DOCUMENTDB_ENDPOINT']!, { masterKey: process.env['AZURE_DOCUMENTDB_MASTERKEY'] }); - const collection = `dbs/builds/colls/${quality}`; - const query = { - query: 'SELECT TOP 1 * FROM c WHERE c.id = @id', - parameters: [{ name: '@id', value: commit }] - }; - - const build = await new Promise((c, e) => { - cosmosdb.queryDocuments(collection, query).toArray((err, results) => { - if (err) { return e(err); } - if (results.length !== 1) { return e(new Error('No documents')); } - c(results[0] as Build); - }); - }); + const build = res.resources[0]; log(`Found build for ${commit}, with ${build.assets.length} assets`); @@ -140,8 +98,9 @@ async function sync(commit: string, quality: string): Promise { await new Promise((c, e) => readStream.pipe(writeStream).on('finish', c).on('error', e)); log(` Updating build in DB...`); - asset.mooncakeUrl = `${process.env['MOONCAKE_CDN_URL']}${blobPath}`; - await updateBuild(commit, quality, asset.platform, asset.type, asset); + const mooncakeUrl = `${process.env['MOONCAKE_CDN_URL']}${blobPath}`; + await container.scripts.storedProcedure('setAssetMooncakeUrl') + .execute('', [commit, asset.platform, asset.type, mooncakeUrl]); log(` Done ✔ï¸`); } catch (err) { diff --git a/build/azure-pipelines/darwin/continuous-build-darwin.yml b/build/azure-pipelines/darwin/continuous-build-darwin.yml index 644df3249136..6edf9f3392bd 100644 --- a/build/azure-pipelines/darwin/continuous-build-darwin.yml +++ b/build/azure-pipelines/darwin/continuous-build-darwin.yml @@ -1,21 +1,21 @@ steps: - task: NodeTool@0 inputs: - versionSpec: "10.15.1" + versionSpec: "12.13.0" +- task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@3 # {{SQL CARBON EDIT}} update version + inputs: + versionSpec: "1.x" - task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 inputs: keyfile: '.yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock' targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules' - vstsFeed: '$(build-cache)' # {{SQL CARBON EDIT}} update build cache + vstsFeed: 'npm-cache' # {{SQL CARBON EDIT}} update build cache - task: AzureKeyVault@1 displayName: 'Azure Key Vault: Get Secrets' inputs: azureSubscription: 'azuredatastudio-adointegration' KeyVaultName: ado-secrets SecretsFilter: 'github-distro-mixin-password' -- task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@3 # {{SQL CARBON EDIT}} update version - inputs: - versionSpec: "1.x" - script: | CHILD_CONCURRENCY=1 yarn --frozen-lockfile displayName: Install Dependencies @@ -26,7 +26,7 @@ steps: inputs: keyfile: '.yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock' targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules' - vstsFeed: '$(build-cache)' # {{SQL CARBON EDIT}} update build cache + vstsFeed: 'npm-cache' # {{SQL CARBON EDIT}} update build cache condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) - script: | yarn electron x64 diff --git a/build/azure-pipelines/darwin/product-build-darwin.yml b/build/azure-pipelines/darwin/product-build-darwin.yml index 0616a2d4dd5f..77f4af38c8e6 100644 --- a/build/azure-pipelines/darwin/product-build-darwin.yml +++ b/build/azure-pipelines/darwin/product-build-darwin.yml @@ -21,7 +21,7 @@ steps: - task: NodeTool@0 inputs: - versionSpec: "10.15.1" + versionSpec: "12.13.0" - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 inputs: diff --git a/build/azure-pipelines/darwin/publish.sh b/build/azure-pipelines/darwin/publish.sh index fa453bcaff5f..a8067a5eefb9 100755 --- a/build/azure-pipelines/darwin/publish.sh +++ b/build/azure-pipelines/darwin/publish.sh @@ -5,28 +5,20 @@ set -e zip -d ../VSCode-darwin.zip "*.pkg" # publish the build -PACKAGEJSON=`ls ../VSCode-darwin/*.app/Contents/Resources/app/package.json` -VERSION=`node -p "require(\"$PACKAGEJSON\").version"` -node build/azure-pipelines/common/publish.js \ - "$VSCODE_QUALITY" \ +node build/azure-pipelines/common/createAsset.js \ darwin \ archive \ "VSCode-darwin-$VSCODE_QUALITY.zip" \ - $VERSION \ - true \ ../VSCode-darwin.zip # package Remote Extension Host pushd .. && mv vscode-reh-darwin vscode-server-darwin && zip -Xry vscode-server-darwin.zip vscode-server-darwin && popd # publish Remote Extension Host -node build/azure-pipelines/common/publish.js \ - "$VSCODE_QUALITY" \ +node build/azure-pipelines/common/createAsset.js \ server-darwin \ archive-unsigned \ "vscode-server-darwin.zip" \ - $VERSION \ - true \ ../vscode-server-darwin.zip # publish hockeyapp symbols diff --git a/build/azure-pipelines/distro-build.yml b/build/azure-pipelines/distro-build.yml index 1d95842ced56..edf92ccfcb74 100644 --- a/build/azure-pipelines/distro-build.yml +++ b/build/azure-pipelines/distro-build.yml @@ -11,7 +11,7 @@ pr: steps: - task: NodeTool@0 inputs: - versionSpec: "10.15.1" + versionSpec: "12.13.0" - task: AzureKeyVault@1 displayName: 'Azure Key Vault: Get Secrets' diff --git a/build/azure-pipelines/exploration-build.yml b/build/azure-pipelines/exploration-build.yml index 305852528bcf..b91b01138d01 100644 --- a/build/azure-pipelines/exploration-build.yml +++ b/build/azure-pipelines/exploration-build.yml @@ -7,7 +7,7 @@ pr: none steps: - task: NodeTool@0 inputs: - versionSpec: "10.15.1" + versionSpec: "12.13.0" - task: AzureKeyVault@1 displayName: 'Azure Key Vault: Get Secrets' diff --git a/build/azure-pipelines/linux/continuous-build-linux.yml b/build/azure-pipelines/linux/continuous-build-linux.yml index 94c1b0fb1278..2a13f543bf68 100644 --- a/build/azure-pipelines/linux/continuous-build-linux.yml +++ b/build/azure-pipelines/linux/continuous-build-linux.yml @@ -9,21 +9,21 @@ steps: sudo service xvfb start - task: NodeTool@0 inputs: - versionSpec: "10.15.1" -- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 + versionSpec: "12.13.0" +- task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@3 inputs: - keyfile: '.yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock' - targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules' - vstsFeed: '$(build-cache)' # {{SQL CARBON EDIT}} update build cache + versionSpec: "1.x" - task: AzureKeyVault@1 displayName: 'Azure Key Vault: Get Secrets' inputs: azureSubscription: 'azuredatastudio-adointegration' KeyVaultName: ado-secrets SecretsFilter: 'github-distro-mixin-password' -- task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@3 # {{SQL CARBON EDIT}} update version +- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 inputs: - versionSpec: "1.x" + keyfile: '.yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock' + targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules' + vstsFeed: 'npm-cache' # {{SQL CARBON EDIT}} update build cache - script: | CHILD_CONCURRENCY=1 yarn --frozen-lockfile displayName: Install Dependencies @@ -34,7 +34,7 @@ steps: inputs: keyfile: '.yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock' targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules' - vstsFeed: '$(build-cache)' # {{SQL CARBON EDIT}} update build cache + vstsFeed: 'npm-cache' # {{SQL CARBON EDIT}} update build cache condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) - script: | yarn electron x64 diff --git a/build/azure-pipelines/linux/frozen-check.js b/build/azure-pipelines/linux/frozen-check.js deleted file mode 100644 index 76ba554f14e4..000000000000 --- a/build/azure-pipelines/linux/frozen-check.js +++ /dev/null @@ -1,40 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -'use strict'; -Object.defineProperty(exports, "__esModule", { value: true }); -const documentdb_1 = require("documentdb"); -function createDefaultConfig(quality) { - return { - id: quality, - frozen: false - }; -} -function getConfig(quality) { - const client = new documentdb_1.DocumentClient(process.env['AZURE_DOCUMENTDB_ENDPOINT'], { masterKey: process.env['AZURE_DOCUMENTDB_MASTERKEY'] }); - const collection = 'dbs/builds/colls/config'; - const query = { - query: `SELECT TOP 1 * FROM c WHERE c.id = @quality`, - parameters: [ - { name: '@quality', value: quality } - ] - }; - return new Promise((c, e) => { - client.queryDocuments(collection, query).toArray((err, results) => { - if (err && err.code !== 409) { - return e(err); - } - c(!results || results.length === 0 ? createDefaultConfig(quality) : results[0]); - }); - }); -} -getConfig(process.argv[2]) - .then(config => { - console.log(config.frozen); - process.exit(0); -}) - .catch(err => { - console.error(err); - process.exit(1); -}); diff --git a/build/azure-pipelines/linux/product-build-linux-multiarch.yml b/build/azure-pipelines/linux/product-build-linux-multiarch.yml index 4f5e39dcdf0c..68ae4ee8b67f 100644 --- a/build/azure-pipelines/linux/product-build-linux-multiarch.yml +++ b/build/azure-pipelines/linux/product-build-linux-multiarch.yml @@ -21,7 +21,7 @@ steps: - task: NodeTool@0 inputs: - versionSpec: "10.15.1" + versionSpec: "12.13.0" - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 inputs: diff --git a/build/azure-pipelines/linux/product-build-linux.yml b/build/azure-pipelines/linux/product-build-linux.yml index 0d01ba8a6083..573d7c7d4c29 100644 --- a/build/azure-pipelines/linux/product-build-linux.yml +++ b/build/azure-pipelines/linux/product-build-linux.yml @@ -21,7 +21,7 @@ steps: - task: NodeTool@0 inputs: - versionSpec: "10.15.1" + versionSpec: "12.13.0" - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 inputs: @@ -118,6 +118,32 @@ steps: displayName: Run integration tests condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) +- script: | + set -e + yarn gulp "vscode-linux-x64-build-deb" + yarn gulp "vscode-linux-x64-build-rpm" + yarn gulp "vscode-linux-x64-prepare-snap" + displayName: Build packages + +- task: SFP.build-tasks.custom-build-task-1.EsrpCodeSigning@1 + inputs: + ConnectedServiceName: 'ESRP CodeSign' + FolderPath: '.build/linux/rpm/x86_64' + Pattern: '*.rpm' + signConfigType: inlineSignParams + inlineOperation: | + [ + { + "keyCode": "CP-450779-Pgp", + "operationSetCode": "LinuxSign", + "parameters": [ ], + "toolName": "sign", + "toolVersion": "1.0" + } + ] + SessionTimeout: 120 + displayName: Codesign rpm + - script: | set -e AZURE_DOCUMENTDB_MASTERKEY="$(builds-docdb-key-readwrite)" \ diff --git a/build/azure-pipelines/linux/publish.sh b/build/azure-pipelines/linux/publish.sh index 5fc86d02d604..3da6ea3eed63 100755 --- a/build/azure-pipelines/linux/publish.sh +++ b/build/azure-pipelines/linux/publish.sh @@ -10,13 +10,11 @@ BUILD="$ROOT/$BUILDNAME" BUILD_VERSION="$(date +%s)" [ -z "$VSCODE_QUALITY" ] && TARBALL_FILENAME="code-$BUILD_VERSION.tar.gz" || TARBALL_FILENAME="code-$VSCODE_QUALITY-$BUILD_VERSION.tar.gz" TARBALL_PATH="$ROOT/$TARBALL_FILENAME" -PACKAGEJSON="$BUILD/resources/app/package.json" -VERSION=$(node -p "require(\"$PACKAGEJSON\").version") rm -rf $ROOT/code-*.tar.* (cd $ROOT && tar -czf $TARBALL_PATH $BUILDNAME) -node build/azure-pipelines/common/publish.js "$VSCODE_QUALITY" "$PLATFORM_LINUX" archive-unsigned "$TARBALL_FILENAME" "$VERSION" true "$TARBALL_PATH" +node build/azure-pipelines/common/createAsset.js "$PLATFORM_LINUX" archive-unsigned "$TARBALL_FILENAME" "$TARBALL_PATH" # Publish Remote Extension Host LEGACY_SERVER_BUILD_NAME="vscode-reh-$PLATFORM_LINUX" @@ -27,32 +25,28 @@ SERVER_TARBALL_PATH="$ROOT/$SERVER_TARBALL_FILENAME" rm -rf $ROOT/vscode-server-*.tar.* (cd $ROOT && mv $LEGACY_SERVER_BUILD_NAME $SERVER_BUILD_NAME && tar --owner=0 --group=0 -czf $SERVER_TARBALL_PATH $SERVER_BUILD_NAME) -node build/azure-pipelines/common/publish.js "$VSCODE_QUALITY" "server-$PLATFORM_LINUX" archive-unsigned "$SERVER_TARBALL_FILENAME" "$VERSION" true "$SERVER_TARBALL_PATH" +node build/azure-pipelines/common/createAsset.js "server-$PLATFORM_LINUX" archive-unsigned "$SERVER_TARBALL_FILENAME" "$SERVER_TARBALL_PATH" # Publish hockeyapp symbols node build/azure-pipelines/common/symbols.js "$VSCODE_MIXIN_PASSWORD" "$VSCODE_HOCKEYAPP_TOKEN" "x64" "$VSCODE_HOCKEYAPP_ID_LINUX64" # Publish DEB -yarn gulp "vscode-linux-x64-build-deb" PLATFORM_DEB="linux-deb-x64" DEB_ARCH="amd64" DEB_FILENAME="$(ls $REPO/.build/linux/deb/$DEB_ARCH/deb/)" DEB_PATH="$REPO/.build/linux/deb/$DEB_ARCH/deb/$DEB_FILENAME" -node build/azure-pipelines/common/publish.js "$VSCODE_QUALITY" "$PLATFORM_DEB" package "$DEB_FILENAME" "$VERSION" true "$DEB_PATH" +node build/azure-pipelines/common/createAsset.js "$PLATFORM_DEB" package "$DEB_FILENAME" "$DEB_PATH" # Publish RPM -yarn gulp "vscode-linux-x64-build-rpm" PLATFORM_RPM="linux-rpm-x64" RPM_ARCH="x86_64" RPM_FILENAME="$(ls $REPO/.build/linux/rpm/$RPM_ARCH/ | grep .rpm)" RPM_PATH="$REPO/.build/linux/rpm/$RPM_ARCH/$RPM_FILENAME" -node build/azure-pipelines/common/publish.js "$VSCODE_QUALITY" "$PLATFORM_RPM" package "$RPM_FILENAME" "$VERSION" true "$RPM_PATH" +node build/azure-pipelines/common/createAsset.js "$PLATFORM_RPM" package "$RPM_FILENAME" "$RPM_PATH" # Publish Snap -yarn gulp "vscode-linux-x64-prepare-snap" - # Pack snap tarball artifact, in order to preserve file perms mkdir -p $REPO/.build/linux/snap-tarball SNAP_TARBALL_PATH="$REPO/.build/linux/snap-tarball/snap-x64.tar.gz" diff --git a/build/azure-pipelines/linux/snap-build-linux.yml b/build/azure-pipelines/linux/snap-build-linux.yml index 047081ced04c..a530499b3130 100644 --- a/build/azure-pipelines/linux/snap-build-linux.yml +++ b/build/azure-pipelines/linux/snap-build-linux.yml @@ -1,7 +1,7 @@ steps: - task: NodeTool@0 inputs: - versionSpec: "10.15.1" + versionSpec: "12.13.0" - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 inputs: @@ -43,12 +43,10 @@ steps: # Create snap package BUILD_VERSION="$(date +%s)" SNAP_FILENAME="code-$VSCODE_QUALITY-$BUILD_VERSION.snap" - PACKAGEJSON="$(ls $SNAP_ROOT/code*/usr/share/code*/resources/app/package.json)" - VERSION=$(node -p "require(\"$PACKAGEJSON\").version") SNAP_PATH="$SNAP_ROOT/$SNAP_FILENAME" (cd $SNAP_ROOT/code-* && sudo --preserve-env snapcraft snap --output "$SNAP_PATH") # Publish snap package AZURE_DOCUMENTDB_MASTERKEY="$(builds-docdb-key-readwrite)" \ AZURE_STORAGE_ACCESS_KEY_2="$(vscode-storage-key)" \ - node build/azure-pipelines/common/publish.js "$VSCODE_QUALITY" "linux-snap-x64" package "$SNAP_FILENAME" "$VERSION" true "$SNAP_PATH" + node build/azure-pipelines/common/createAsset.js "linux-snap-x64" package "$SNAP_FILENAME" "$SNAP_PATH" diff --git a/build/azure-pipelines/product-build.yml b/build/azure-pipelines/product-build.yml index ecf47fa1cdd5..2eedaf8dce5f 100644 --- a/build/azure-pipelines/product-build.yml +++ b/build/azure-pipelines/product-build.yml @@ -67,7 +67,7 @@ jobs: - template: linux/product-build-linux-multiarch.yml - job: LinuxArm64 - condition: and(succeeded(), eq(variables['VSCODE_COMPILE_ONLY'], 'false'), eq(variables['VSCODE_BUILD_LINUX_ARM64'], 'true'), ne(variables['VSCODE_QUALITY'], 'stable')) + condition: and(succeeded(), eq(variables['VSCODE_COMPILE_ONLY'], 'false'), eq(variables['VSCODE_BUILD_LINUX_ARM64'], 'true')) pool: vmImage: 'Ubuntu-16.04' variables: @@ -118,6 +118,7 @@ jobs: - Linux - LinuxSnap - LinuxArmhf + - LinuxArm64 - LinuxAlpine - macOS steps: @@ -133,6 +134,7 @@ jobs: - Linux - LinuxSnap - LinuxArmhf + - LinuxArm64 - LinuxAlpine - LinuxWeb - macOS diff --git a/build/azure-pipelines/product-compile.yml b/build/azure-pipelines/product-compile.yml index 76e1f6a927a1..8029f8a5661b 100644 --- a/build/azure-pipelines/product-compile.yml +++ b/build/azure-pipelines/product-compile.yml @@ -12,23 +12,24 @@ steps: vstsFeed: 'npm-vscode' platformIndependent: true alias: 'Compilation' + dryRun: true - task: NodeTool@0 inputs: - versionSpec: "10.15.1" - condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true')) + versionSpec: "12.13.0" + condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true')) - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 inputs: versionSpec: "1.x" - condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true')) + condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true')) - task: AzureKeyVault@1 displayName: 'Azure Key Vault: Get Secrets' inputs: azureSubscription: 'vscode-builds-subscription' KeyVaultName: vscode - condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true')) + condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true')) - script: | set -e @@ -41,7 +42,7 @@ steps: git config user.email "vscode@microsoft.com" git config user.name "VSCode" displayName: Prepare tooling - condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true')) + condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true')) - script: | set -e @@ -49,33 +50,33 @@ steps: git fetch distro git merge $(node -p "require('./package.json').distro") displayName: Merge distro - condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true')) + condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true')) - task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 inputs: keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock' targetfolder: '**/node_modules, !**/node_modules/**/node_modules' vstsFeed: 'npm-vscode' - condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true')) + condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true')) - script: | set -e CHILD_CONCURRENCY=1 yarn --frozen-lockfile displayName: Install dependencies - condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true'), ne(variables['CacheRestored'], 'true')) + condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true'), ne(variables['CacheRestored'], 'true')) - task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1 inputs: keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock' targetfolder: '**/node_modules, !**/node_modules/**/node_modules' vstsFeed: 'npm-vscode' - condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true'), ne(variables['CacheRestored'], 'true')) + condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true'), ne(variables['CacheRestored'], 'true')) - script: | set -e yarn postinstall displayName: Run postinstall scripts - condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true'), eq(variables['CacheRestored'], 'true')) + condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true'), eq(variables['CacheRestored'], 'true')) # Mixin must run before optimize, because the CSS loader will # inline small SVGs @@ -83,7 +84,7 @@ steps: set -e node build/azure-pipelines/mixin displayName: Mix in quality - condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true')) + condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true')) - script: | set -e @@ -91,20 +92,20 @@ steps: yarn gulp tslint yarn monaco-compile-check displayName: Run hygiene, tslint and monaco compile checks - condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) - script: | set - ./build/azure-pipelines/common/extract-telemetry.sh displayName: Extract Telemetry - condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true')) + condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true')) - script: | set -e AZURE_WEBVIEW_STORAGE_ACCESS_KEY="$(vscode-webview-storage-key)" \ ./build/azure-pipelines/common/publish-webview.sh displayName: Publish Webview - condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true')) + condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true')) - script: | set -e @@ -114,14 +115,22 @@ steps: yarn gulp minify-vscode-reh yarn gulp minify-vscode-reh-web displayName: Compile - condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true')) + condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true')) - script: | set -e AZURE_STORAGE_ACCESS_KEY="$(ticino-storage-key)" \ node build/azure-pipelines/upload-sourcemaps displayName: Upload sourcemaps - condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true')) + condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true')) + +- script: | + set -e + VERSION=`node -p "require(\"./package.json\").version"` + AZURE_DOCUMENTDB_MASTERKEY="$(builds-docdb-key-readwrite)" \ + node build/azure-pipelines/common/createBuild.js $VERSION + displayName: Create build + condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true')) - task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1 inputs: @@ -130,4 +139,4 @@ steps: vstsFeed: 'npm-vscode' platformIndependent: true alias: 'Compilation' - condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true')) + condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true')) diff --git a/build/azure-pipelines/publish-types/check-version.ts b/build/azure-pipelines/publish-types/check-version.ts index 3562261a8003..3e3614a86745 100644 --- a/build/azure-pipelines/publish-types/check-version.ts +++ b/build/azure-pipelines/publish-types/check-version.ts @@ -35,9 +35,9 @@ function isValidTag(t: string) { return false; } - if (parseInt(major, 10) === NaN || parseInt(minor, 10) === NaN) { + if (isNaN(parseInt(major, 10)) || isNaN(parseInt(minor, 10))) { return false; } return true; -} \ No newline at end of file +} diff --git a/build/azure-pipelines/publish-types/publish-types.yml b/build/azure-pipelines/publish-types/publish-types.yml index 6808054b3f12..1d4ab83e1ac0 100644 --- a/build/azure-pipelines/publish-types/publish-types.yml +++ b/build/azure-pipelines/publish-types/publish-types.yml @@ -9,7 +9,7 @@ pr: none steps: - task: NodeTool@0 inputs: - versionSpec: "10.15.1" + versionSpec: "12.13.0" - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 inputs: diff --git a/build/azure-pipelines/release.yml b/build/azure-pipelines/release.yml index eee54a1d8b79..f5365b80c7e0 100644 --- a/build/azure-pipelines/release.yml +++ b/build/azure-pipelines/release.yml @@ -19,4 +19,4 @@ steps: (cd build ; yarn) AZURE_DOCUMENTDB_MASTERKEY="$(builds-docdb-key-readwrite)" \ - node build/azure-pipelines/common/release.js + node build/azure-pipelines/common/releaseBuild.js diff --git a/build/azure-pipelines/sync-mooncake.yml b/build/azure-pipelines/sync-mooncake.yml index 1fb8ffcd8865..2641830a4130 100644 --- a/build/azure-pipelines/sync-mooncake.yml +++ b/build/azure-pipelines/sync-mooncake.yml @@ -1,7 +1,7 @@ steps: - task: NodeTool@0 inputs: - versionSpec: "10.15.1" + versionSpec: "12.13.0" - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 inputs: diff --git a/build/azure-pipelines/web/product-build-web.yml b/build/azure-pipelines/web/product-build-web.yml index 46ecb39eac35..0c338203b4d6 100644 --- a/build/azure-pipelines/web/product-build-web.yml +++ b/build/azure-pipelines/web/product-build-web.yml @@ -21,7 +21,7 @@ steps: - task: NodeTool@0 inputs: - versionSpec: "10.15.1" + versionSpec: "12.13.0" - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 inputs: diff --git a/build/azure-pipelines/web/publish.sh b/build/azure-pipelines/web/publish.sh index 2c112362a256..827edc2661bf 100755 --- a/build/azure-pipelines/web/publish.sh +++ b/build/azure-pipelines/web/publish.sh @@ -7,12 +7,9 @@ ROOT="$REPO/.." WEB_BUILD_NAME="vscode-web" WEB_TARBALL_FILENAME="vscode-web.tar.gz" WEB_TARBALL_PATH="$ROOT/$WEB_TARBALL_FILENAME" -BUILD="$ROOT/$WEB_BUILD_NAME" -PACKAGEJSON="$BUILD/package.json" -VERSION=$(node -p "require(\"$PACKAGEJSON\").version") rm -rf $ROOT/vscode-web.tar.* (cd $ROOT && tar --owner=0 --group=0 -czf $WEB_TARBALL_PATH $WEB_BUILD_NAME) -node build/azure-pipelines/common/publish.js "$VSCODE_QUALITY" "web-standalone" archive-unsigned "$WEB_TARBALL_FILENAME" "$VERSION" true "$WEB_TARBALL_PATH" +node build/azure-pipelines/common/createAsset.js web-standalone archive-unsigned "$WEB_TARBALL_FILENAME" "$WEB_TARBALL_PATH" diff --git a/build/azure-pipelines/win32/continuous-build-win32.yml b/build/azure-pipelines/win32/continuous-build-win32.yml index c2c9b3b81603..499ed99099c0 100644 --- a/build/azure-pipelines/win32/continuous-build-win32.yml +++ b/build/azure-pipelines/win32/continuous-build-win32.yml @@ -1,7 +1,7 @@ steps: - task: NodeTool@0 inputs: - versionSpec: "10.15.1" + versionSpec: "12.13.0" - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@3 # {{SQL CARBON EDIT}} update version inputs: versionSpec: "1.x" @@ -19,7 +19,7 @@ steps: inputs: keyfile: '.yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock' targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules' - vstsFeed: '$(build-cache)' # {{SQL CARBON EDIT}} update build cache + vstsFeed: 'npm-cache' # {{SQL CARBON EDIT}} update build cache - powershell: | yarn --frozen-lockfile env: @@ -31,7 +31,7 @@ steps: inputs: keyfile: '.yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock' targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules' - vstsFeed: '$(build-cache)' # {{SQL CARBON EDIT}} update build cache + vstsFeed: 'npm-cache' # {{SQL CARBON EDIT}} update build cache condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) - powershell: | yarn electron diff --git a/build/azure-pipelines/win32/product-build-win32.yml b/build/azure-pipelines/win32/product-build-win32.yml index 3c95d012879d..4c9336c6c1c5 100644 --- a/build/azure-pipelines/win32/product-build-win32.yml +++ b/build/azure-pipelines/win32/product-build-win32.yml @@ -21,7 +21,7 @@ steps: - task: NodeTool@0 inputs: - versionSpec: "10.15.1" + versionSpec: "12.13.0" - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 inputs: diff --git a/build/azure-pipelines/win32/publish.ps1 b/build/azure-pipelines/win32/publish.ps1 index 86da6df4e0a4..5a22d4749cf6 100644 --- a/build/azure-pipelines/win32/publish.ps1 +++ b/build/azure-pipelines/win32/publish.ps1 @@ -23,14 +23,13 @@ exec { .\node_modules\7zip\7zip-lite\7z.exe a -tzip $ServerZip $Server -r } # get version $PackageJson = Get-Content -Raw -Path "$Build\resources\app\package.json" | ConvertFrom-Json $Version = $PackageJson.version -$Quality = "$env:VSCODE_QUALITY" $AssetPlatform = if ("$Arch" -eq "ia32") { "win32" } else { "win32-x64" } -exec { node build/azure-pipelines/common/publish.js $Quality "$AssetPlatform-archive" archive "VSCode-win32-$Arch-$Version.zip" $Version true $Zip } -exec { node build/azure-pipelines/common/publish.js $Quality "$AssetPlatform" setup "VSCodeSetup-$Arch-$Version.exe" $Version true $SystemExe } -exec { node build/azure-pipelines/common/publish.js $Quality "$AssetPlatform-user" setup "VSCodeUserSetup-$Arch-$Version.exe" $Version true $UserExe } -exec { node build/azure-pipelines/common/publish.js $Quality "server-$AssetPlatform" archive "vscode-server-win32-$Arch.zip" $Version true $ServerZip } +exec { node build/azure-pipelines/common/createAsset.js "$AssetPlatform-archive" archive "VSCode-win32-$Arch-$Version.zip" $Zip } +exec { node build/azure-pipelines/common/createAsset.js "$AssetPlatform" setup "VSCodeSetup-$Arch-$Version.exe" $SystemExe } +exec { node build/azure-pipelines/common/createAsset.js "$AssetPlatform-user" setup "VSCodeUserSetup-$Arch-$Version.exe" $UserExe } +exec { node build/azure-pipelines/common/createAsset.js "server-$AssetPlatform" archive "vscode-server-win32-$Arch.zip" $ServerZip } # publish hockeyapp symbols $hockeyAppId = if ("$Arch" -eq "ia32") { "$env:VSCODE_HOCKEYAPP_ID_WIN32" } else { "$env:VSCODE_HOCKEYAPP_ID_WIN64" } diff --git a/build/gulpfile.editor.js b/build/gulpfile.editor.js index 9054fbd83eac..83d3dc6c8568 100644 --- a/build/gulpfile.editor.js +++ b/build/gulpfile.editor.js @@ -57,7 +57,6 @@ var BUNDLED_FILE_HEADER = [ const languages = i18n.defaultLanguages.concat([]); // i18n.defaultLanguages.concat(process.env.VSCODE_QUALITY !== 'stable' ? i18n.extraLanguages : []); const extractEditorSrcTask = task.define('extract-editor-src', () => { - console.log(`If the build fails, consider tweaking shakeLevel below to a lower value.`); const apiusages = monacoapi.execute().usageContent; const extrausages = fs.readFileSync(path.join(root, 'build', 'monaco', 'monaco.usage.recipe')).toString(); standalone.extractEditor({ @@ -71,14 +70,6 @@ const extractEditorSrcTask = task.define('extract-editor-src', () => { apiusages, extrausages ], - typings: [ - 'typings/lib.ie11_safe_es6.d.ts', - 'typings/thenable.d.ts', - 'typings/es6-promise.d.ts', - 'typings/require-monaco.d.ts', - "typings/lib.es2018.promise.d.ts", - 'vs/monaco.d.ts' - ], libs: [ `lib.es5.d.ts`, `lib.dom.d.ts`, @@ -86,7 +77,8 @@ const extractEditorSrcTask = task.define('extract-editor-src', () => { ], shakeLevel: 2, // 0-Files, 1-InnerFile, 2-ClassMembers importIgnorePattern: /(^vs\/css!)|(promise-polyfill\/polyfill)/, - destRoot: path.join(root, 'out-editor-src') + destRoot: path.join(root, 'out-editor-src'), + redirects: [] }); }); @@ -137,18 +129,70 @@ const createESMSourcesAndResourcesTask = task.define('extract-editor-esm', () => }); const compileEditorESMTask = task.define('compile-editor-esm', () => { + console.log(`Launching the TS compiler at ${path.join(__dirname, '../out-editor-esm')}...`); + let result; if (process.platform === 'win32') { - const result = cp.spawnSync(`..\\node_modules\\.bin\\tsc.cmd`, { + result = cp.spawnSync(`..\\node_modules\\.bin\\tsc.cmd`, { cwd: path.join(__dirname, '../out-editor-esm') }); - console.log(result.stdout.toString()); - console.log(result.stderr.toString()); } else { - const result = cp.spawnSync(`node`, [`../node_modules/.bin/tsc`], { + result = cp.spawnSync(`node`, [`../node_modules/.bin/tsc`], { cwd: path.join(__dirname, '../out-editor-esm') }); - console.log(result.stdout.toString()); - console.log(result.stderr.toString()); + } + + console.log(result.stdout.toString()); + console.log(result.stderr.toString()); + + if (result.status !== 0) { + console.log(`The TS Compilation failed, preparing analysis folder...`); + const destPath = path.join(__dirname, '../../vscode-monaco-editor-esm-analysis'); + return util.rimraf(destPath)().then(() => { + fs.mkdirSync(destPath); + + // initialize a new repository + cp.spawnSync(`git`, [`init`], { + cwd: destPath + }); + + // build a list of files to copy + const files = util.rreddir(path.join(__dirname, '../out-editor-esm')); + + // copy files from src + for (const file of files) { + const srcFilePath = path.join(__dirname, '../src', file); + const dstFilePath = path.join(destPath, file); + if (fs.existsSync(srcFilePath)) { + util.ensureDir(path.dirname(dstFilePath)); + const contents = fs.readFileSync(srcFilePath).toString().replace(/\r\n|\r|\n/g, '\n'); + fs.writeFileSync(dstFilePath, contents); + } + } + + // create an initial commit to diff against + cp.spawnSync(`git`, [`add`, `.`], { + cwd: destPath + }); + + // create the commit + cp.spawnSync(`git`, [`commit`, `-m`, `"original sources"`, `--no-gpg-sign`], { + cwd: destPath + }); + + // copy files from esm + for (const file of files) { + const srcFilePath = path.join(__dirname, '../out-editor-esm', file); + const dstFilePath = path.join(destPath, file); + if (fs.existsSync(srcFilePath)) { + util.ensureDir(path.dirname(dstFilePath)); + const contents = fs.readFileSync(srcFilePath).toString().replace(/\r\n|\r|\n/g, '\n'); + fs.writeFileSync(dstFilePath, contents); + } + } + + console.log(`Open in VS Code the folder at '${destPath}' and you can alayze the compilation error`); + throw new Error('Standalone Editor compilation failed. If this is the build machine, simply launch `yarn run gulp editor-distro` on your machine to further analyze the compilation problem.'); + }); } }); diff --git a/build/gulpfile.extensions.js b/build/gulpfile.extensions.js index 334899a9551c..dae86fd9cb2b 100644 --- a/build/gulpfile.extensions.js +++ b/build/gulpfile.extensions.js @@ -115,7 +115,8 @@ const tasks = compilations.map(function (tsconfigFile) { const compileTask = task.define(`compile-extension:${name}`, task.series(cleanTask, () => { const pipeline = createPipeline(sqlLocalizedExtensions.includes(name), true); // {{SQL CARBON EDIT}} - const input = pipeline.tsProjectSrc(); + const nonts = gulp.src(src, srcOpts).pipe(filter(['**', '!**/*.ts'])); + const input = es.merge(nonts, pipeline.tsProjectSrc()); return input .pipe(pipeline()) @@ -124,7 +125,8 @@ const tasks = compilations.map(function (tsconfigFile) { const watchTask = task.define(`watch-extension:${name}`, task.series(cleanTask, () => { const pipeline = createPipeline(false); - const input = pipeline.tsProjectSrc(); + const nonts = gulp.src(src, srcOpts).pipe(filter(['**', '!**/*.ts'])); + const input = es.merge(nonts, pipeline.tsProjectSrc()); const watchInput = watcher(src, { ...srcOpts, ...{ readDelay: 200 } }); return watchInput @@ -134,7 +136,8 @@ const tasks = compilations.map(function (tsconfigFile) { const compileBuildTask = task.define(`compile-build-extension-${name}`, task.series(cleanTask, () => { const pipeline = createPipeline(true, true); - const input = pipeline.tsProjectSrc(); + const nonts = gulp.src(src, srcOpts).pipe(filter(['**', '!**/*.ts'])); + const input = es.merge(nonts, pipeline.tsProjectSrc()); return input .pipe(pipeline()) diff --git a/build/gulpfile.hygiene.js b/build/gulpfile.hygiene.js index 27a592b41345..e94eefb36c99 100644 --- a/build/gulpfile.hygiene.js +++ b/build/gulpfile.hygiene.js @@ -125,6 +125,7 @@ const copyrightFilter = [ '!**/*.opts', '!**/*.disabled', '!**/*.code-workspace', + '!**/*.js.map', '!**/promise-polyfill/polyfill.js', '!build/**/*.init', '!resources/linux/snap/snapcraft.yaml', @@ -194,7 +195,8 @@ const tslintBaseFilter = [ '!extensions/**/*.test.ts', '!extensions/html-language-features/server/lib/jquery.d.ts', '!extensions/big-data-cluster/src/bigDataCluster/controller/apiGenerated.ts', // {{SQL CARBON EDIT}}, - '!extensions/big-data-cluster/src/bigDataCluster/controller/tokenApiGenerated.ts' // {{SQL CARBON EDIT}}, + '!extensions/big-data-cluster/src/bigDataCluster/controller/tokenApiGenerated.ts', // {{SQL CARBON EDIT}}, + '!src/vs/workbench/services/themes/common/textMateScopeMatcher.ts' // {{SQL CARBON EDIT}} skip this because we have no plans on touching this and its not ours ]; // {{SQL CARBON EDIT}} @@ -424,7 +426,12 @@ function hygiene(some) { let input; if (Array.isArray(some) || typeof some === 'string' || !some) { - input = vfs.src(some || all, { base: '.', follow: true, allowEmpty: true }); + const options = { base: '.', follow: true, allowEmpty: true }; + if (some) { + input = vfs.src(some, options).pipe(filter(all)); // split this up to not unnecessarily filter all a second time + } else { + input = vfs.src(all, options); + } } else { input = some; } diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index f51ae290f173..40b4f8db5a73 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -91,8 +91,7 @@ const vscodeResources = [ 'out-build/vs/code/electron-browser/sharedProcess/sharedProcess.js', 'out-build/vs/code/electron-browser/issue/issueReporter.js', 'out-build/vs/code/electron-browser/processExplorer/processExplorer.js', - // {{SQL CARBON EDIT}} - 'out-build/sql/workbench/electron-browser/splashscreen/*', + 'out-build/sql/workbench/electron-browser/splashscreen/*', // {{SQL CARBON EDIT}} STart 'out-build/sql/**/*.{svg,png,cur,html}', 'out-build/sql/base/browser/ui/table/media/*.{gif,png,svg}', 'out-build/sql/base/browser/ui/checkbox/media/*.{gif,png,svg}', @@ -110,7 +109,8 @@ const vscodeResources = [ 'out-build/sql/media/objectTypes/*.svg', 'out-build/sql/media/icons/*.svg', 'out-build/sql/workbench/parts/notebook/media/**/*.svg', - 'out-build/sql/setup.js', + 'out-build/sql/setup.js', // {{SQL CARBON EDIT}} end + 'out-build/vs/platform/auth/common/auth.css', '!**/test/**' ]; diff --git a/build/lib/builtInExtensions.js b/build/lib/builtInExtensions.js index 85fa091a5b7a..31551040b924 100644 --- a/build/lib/builtInExtensions.js +++ b/build/lib/builtInExtensions.js @@ -23,6 +23,13 @@ const quality = process.env['VSCODE_QUALITY']; const builtInExtensions = quality && quality === 'stable' ? require('../builtInExtensions.json') : require('../builtInExtensions-insiders.json'); // {{SQL CARBON EDIT}} - END const controlFilePath = path.join(os.homedir(), '.vscode-oss-dev', 'extensions', 'control.json'); +const ENABLE_LOGGING = !process.env['VSCODE_BUILD_BUILTIN_EXTENSIONS_SILENCE_PLEASE']; + +function log() { + if (ENABLE_LOGGING) { + fancyLog.apply(this, arguments); + } +} function getExtensionPath(extension) { return path.join(root, '.build', 'builtInExtensions', extension.name); @@ -47,7 +54,7 @@ function isUpToDate(extension) { function syncMarketplaceExtension(extension) { if (isUpToDate(extension)) { - fancyLog(ansiColors.blue('[marketplace]'), `${extension.name}@${extension.version}`, ansiColors.green('✔︎')); + log(ansiColors.blue('[marketplace]'), `${extension.name}@${extension.version}`, ansiColors.green('✔︎')); return es.readArray([]); } @@ -56,13 +63,13 @@ function syncMarketplaceExtension(extension) { return ext.fromMarketplace(extension.name, extension.version, extension.metadata) .pipe(rename(p => p.dirname = `${extension.name}/${p.dirname}`)) .pipe(vfs.dest('.build/builtInExtensions')) - .on('end', () => fancyLog(ansiColors.blue('[marketplace]'), extension.name, ansiColors.green('✔︎'))); + .on('end', () => log(ansiColors.blue('[marketplace]'), extension.name, ansiColors.green('✔︎'))); } function syncExtension(extension, controlState) { switch (controlState) { case 'disabled': - fancyLog(ansiColors.blue('[disabled]'), ansiColors.gray(extension.name)); + log(ansiColors.blue('[disabled]'), ansiColors.gray(extension.name)); return es.readArray([]); case 'marketplace': @@ -70,15 +77,15 @@ function syncExtension(extension, controlState) { default: if (!fs.existsSync(controlState)) { - fancyLog(ansiColors.red(`Error: Built-in extension '${extension.name}' is configured to run from '${controlState}' but that path does not exist.`)); + log(ansiColors.red(`Error: Built-in extension '${extension.name}' is configured to run from '${controlState}' but that path does not exist.`)); return es.readArray([]); } else if (!fs.existsSync(path.join(controlState, 'package.json'))) { - fancyLog(ansiColors.red(`Error: Built-in extension '${extension.name}' is configured to run from '${controlState}' but there is no 'package.json' file in that directory.`)); + log(ansiColors.red(`Error: Built-in extension '${extension.name}' is configured to run from '${controlState}' but there is no 'package.json' file in that directory.`)); return es.readArray([]); } - fancyLog(ansiColors.blue('[local]'), `${extension.name}: ${ansiColors.cyan(controlState)}`, ansiColors.green('✔︎')); + log(ansiColors.blue('[local]'), `${extension.name}: ${ansiColors.cyan(controlState)}`, ansiColors.green('✔︎')); return es.readArray([]); } } @@ -97,8 +104,8 @@ function writeControlFile(control) { } function main() { - fancyLog('Syncronizing built-in extensions...'); - fancyLog(`You can manage built-in extensions with the ${ansiColors.cyan('--builtin')} flag`); + log('Syncronizing built-in extensions...'); + log(`You can manage built-in extensions with the ${ansiColors.cyan('--builtin')} flag`); const control = readControlFile(); const streams = []; diff --git a/build/lib/compilation.js b/build/lib/compilation.js index 6b65b88e2dbf..903de37a70f9 100644 --- a/build/lib/compilation.js +++ b/build/lib/compilation.js @@ -44,7 +44,7 @@ function createCompile(src, build, emitError) { const input = es.through(); const output = input .pipe(utf8Filter) - .pipe(bom()) + .pipe(bom()) // this is required to preserve BOM in test files that loose it otherwise .pipe(utf8Filter.restore) .pipe(tsFilter) .pipe(util.loadSourcemaps()) diff --git a/build/lib/compilation.ts b/build/lib/compilation.ts index 4f5a7b8e8021..640932274312 100644 --- a/build/lib/compilation.ts +++ b/build/lib/compilation.ts @@ -54,7 +54,7 @@ function createCompile(src: string, build: boolean, emitError?: boolean) { const input = es.through(); const output = input .pipe(utf8Filter) - .pipe(bom()) + .pipe(bom()) // this is required to preserve BOM in test files that loose it otherwise .pipe(utf8Filter.restore) .pipe(tsFilter) .pipe(util.loadSourcemaps()) diff --git a/build/lib/i18n.resources.json b/build/lib/i18n.resources.json index 3f8c3cec51f9..6a3f89dc4717 100644 --- a/build/lib/i18n.resources.json +++ b/build/lib/i18n.resources.json @@ -42,10 +42,18 @@ "name": "vs/workbench/contrib/callHierarchy", "project": "vscode-workbench" }, + { + "name": "vs/workbench/contrib/codeActions", + "project": "vscode-workbench" + }, { "name": "vs/workbench/contrib/comments", "project": "vscode-workbench" }, + { + "name": "vs/workbench/contrib/testCustomEditors", + "project": "vscode-workbench" + }, { "name": "vs/workbench/contrib/debug", "project": "vscode-workbench" @@ -135,7 +143,7 @@ "project": "vscode-workbench" }, { - "name": "vs/workbench/contrib/stats", + "name": "vs/workbench/contrib/tags", "project": "vscode-workbench" }, { @@ -194,6 +202,10 @@ "name": "vs/workbench/services/actions", "project": "vscode-workbench" }, + { + "name": "vs/workbench/services/authToken", + "project": "vscode-workbench" + }, { "name": "vs/workbench/services/bulkEdit", "project": "vscode-workbench" diff --git a/build/lib/standalone.js b/build/lib/standalone.js index e374a22eff53..3f5a44ae09d1 100644 --- a/build/lib/standalone.js +++ b/build/lib/standalone.js @@ -43,7 +43,9 @@ function extractEditor(options) { compilerOptions.declaration = false; compilerOptions.moduleResolution = ts.ModuleResolutionKind.Classic; options.compilerOptions = compilerOptions; - console.log(`Running with shakeLevel ${tss.toStringShakeLevel(options.shakeLevel)}`); + console.log(`Running tree shaker with shakeLevel ${tss.toStringShakeLevel(options.shakeLevel)}`); + // Take the extra included .d.ts files from `tsconfig.monaco.json` + options.typings = tsConfig.include.filter(includedFile => /\.d\.ts$/.test(includedFile)); let result = tss.shake(options); for (let fileName in result) { if (result.hasOwnProperty(fileName)) { diff --git a/build/lib/standalone.ts b/build/lib/standalone.ts index ec579ddad692..f157b7bba7ec 100644 --- a/build/lib/standalone.ts +++ b/build/lib/standalone.ts @@ -50,7 +50,10 @@ export function extractEditor(options: tss.ITreeShakingOptions & { destRoot: str options.compilerOptions = compilerOptions; - console.log(`Running with shakeLevel ${tss.toStringShakeLevel(options.shakeLevel)}`); + console.log(`Running tree shaker with shakeLevel ${tss.toStringShakeLevel(options.shakeLevel)}`); + + // Take the extra included .d.ts files from `tsconfig.monaco.json` + options.typings = (tsConfig.include).filter(includedFile => /\.d\.ts$/.test(includedFile)); let result = tss.shake(options); for (let fileName in result) { diff --git a/build/lib/treeshaking.js b/build/lib/treeshaking.js index c8bc24dbdec5..ffcffea8a869 100644 --- a/build/lib/treeshaking.js +++ b/build/lib/treeshaking.js @@ -25,17 +25,17 @@ function toStringShakeLevel(shakeLevel) { } } exports.toStringShakeLevel = toStringShakeLevel; -function printDiagnostics(diagnostics) { +function printDiagnostics(options, diagnostics) { for (const diag of diagnostics) { let result = ''; if (diag.file) { - result += `${diag.file.fileName}: `; + result += `${path.join(options.sourcesRoot, diag.file.fileName)}`; } if (diag.file && diag.start) { let location = diag.file.getLineAndCharacterOfPosition(diag.start); - result += `- ${location.line + 1},${location.character} - `; + result += `:${location.line + 1}:${location.character}`; } - result += JSON.stringify(diag.messageText); + result += ` - ` + JSON.stringify(diag.messageText); console.log(result); } } @@ -44,17 +44,17 @@ function shake(options) { const program = languageService.getProgram(); const globalDiagnostics = program.getGlobalDiagnostics(); if (globalDiagnostics.length > 0) { - printDiagnostics(globalDiagnostics); + printDiagnostics(options, globalDiagnostics); throw new Error(`Compilation Errors encountered.`); } const syntacticDiagnostics = program.getSyntacticDiagnostics(); if (syntacticDiagnostics.length > 0) { - printDiagnostics(syntacticDiagnostics); + printDiagnostics(options, syntacticDiagnostics); throw new Error(`Compilation Errors encountered.`); } const semanticDiagnostics = program.getSemanticDiagnostics(); if (semanticDiagnostics.length > 0) { - printDiagnostics(semanticDiagnostics); + printDiagnostics(options, semanticDiagnostics); throw new Error(`Compilation Errors encountered.`); } markNodes(languageService, options); @@ -358,7 +358,7 @@ function markNodes(languageService, options) { ++step; let node; if (step % 100 === 0) { - console.log(`${step}/${step + black_queue.length + gray_queue.length} (${black_queue.length}, ${gray_queue.length})`); + console.log(`Treeshaking - ${Math.floor(100 * step / (step + black_queue.length + gray_queue.length))}% - ${step}/${step + black_queue.length + gray_queue.length} (${black_queue.length}, ${gray_queue.length})`); } if (black_queue.length === 0) { for (let i = 0; i < gray_queue.length; i++) { diff --git a/build/lib/treeshaking.ts b/build/lib/treeshaking.ts index 87b6f42dc3e2..e187507c87af 100644 --- a/build/lib/treeshaking.ts +++ b/build/lib/treeshaking.ts @@ -71,17 +71,17 @@ export interface ITreeShakingResult { [file: string]: string; } -function printDiagnostics(diagnostics: ReadonlyArray): void { +function printDiagnostics(options: ITreeShakingOptions, diagnostics: ReadonlyArray): void { for (const diag of diagnostics) { let result = ''; if (diag.file) { - result += `${diag.file.fileName}: `; + result += `${path.join(options.sourcesRoot, diag.file.fileName)}`; } if (diag.file && diag.start) { let location = diag.file.getLineAndCharacterOfPosition(diag.start); - result += `- ${location.line + 1},${location.character} - `; + result += `:${location.line + 1}:${location.character}`; } - result += JSON.stringify(diag.messageText); + result += ` - ` + JSON.stringify(diag.messageText); console.log(result); } } @@ -92,19 +92,19 @@ export function shake(options: ITreeShakingOptions): ITreeShakingResult { const globalDiagnostics = program.getGlobalDiagnostics(); if (globalDiagnostics.length > 0) { - printDiagnostics(globalDiagnostics); + printDiagnostics(options, globalDiagnostics); throw new Error(`Compilation Errors encountered.`); } const syntacticDiagnostics = program.getSyntacticDiagnostics(); if (syntacticDiagnostics.length > 0) { - printDiagnostics(syntacticDiagnostics); + printDiagnostics(options, syntacticDiagnostics); throw new Error(`Compilation Errors encountered.`); } const semanticDiagnostics = program.getSemanticDiagnostics(); if (semanticDiagnostics.length > 0) { - printDiagnostics(semanticDiagnostics); + printDiagnostics(options, semanticDiagnostics); throw new Error(`Compilation Errors encountered.`); } @@ -471,7 +471,7 @@ function markNodes(languageService: ts.LanguageService, options: ITreeShakingOpt let node: ts.Node; if (step % 100 === 0) { - console.log(`${step}/${step + black_queue.length + gray_queue.length} (${black_queue.length}, ${gray_queue.length})`); + console.log(`Treeshaking - ${Math.floor(100 * step / (step + black_queue.length + gray_queue.length))}% - ${step}/${step + black_queue.length + gray_queue.length} (${black_queue.length}, ${gray_queue.length})`); } if (black_queue.length === 0) { diff --git a/build/lib/util.js b/build/lib/util.js index ea7b512e631a..79cbe51ec64c 100644 --- a/build/lib/util.js +++ b/build/lib/util.js @@ -185,6 +185,31 @@ function rimraf(dir) { return result; } exports.rimraf = rimraf; +function _rreaddir(dirPath, prepend, result) { + const entries = fs.readdirSync(dirPath, { withFileTypes: true }); + for (const entry of entries) { + if (entry.isDirectory()) { + _rreaddir(path.join(dirPath, entry.name), `${prepend}/${entry.name}`, result); + } + else { + result.push(`${prepend}/${entry.name}`); + } + } +} +function rreddir(dirPath) { + let result = []; + _rreaddir(dirPath, '', result); + return result; +} +exports.rreddir = rreddir; +function ensureDir(dirPath) { + if (fs.existsSync(dirPath)) { + return; + } + ensureDir(path.dirname(dirPath)); + fs.mkdirSync(dirPath); +} +exports.ensureDir = ensureDir; function getVersion(root) { let version = process.env['BUILD_SOURCEVERSION']; if (!version || !/^[0-9a-f]{40}$/i.test(version)) { diff --git a/build/lib/util.ts b/build/lib/util.ts index 9b63c9fd7e4d..806173699f53 100644 --- a/build/lib/util.ts +++ b/build/lib/util.ts @@ -243,6 +243,31 @@ export function rimraf(dir: string): () => Promise { return result; } +function _rreaddir(dirPath: string, prepend: string, result: string[]): void { + const entries = fs.readdirSync(dirPath, { withFileTypes: true }); + for (const entry of entries) { + if (entry.isDirectory()) { + _rreaddir(path.join(dirPath, entry.name), `${prepend}/${entry.name}`, result); + } else { + result.push(`${prepend}/${entry.name}`); + } + } +} + +export function rreddir(dirPath: string): string[] { + let result: string[] = []; + _rreaddir(dirPath, '', result); + return result; +} + +export function ensureDir(dirPath: string): void { + if (fs.existsSync(dirPath)) { + return; + } + ensureDir(path.dirname(dirPath)); + fs.mkdirSync(dirPath); +} + export function getVersion(root: string): string | undefined { let version = process.env['BUILD_SOURCEVERSION']; diff --git a/build/lib/watch/index.js b/build/lib/watch/index.js index beb7abbe42de..d07d6efdde69 100644 --- a/build/lib/watch/index.js +++ b/build/lib/watch/index.js @@ -3,14 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -const es = require('event-stream'); - - -let watch = undefined; - -if (!watch) { - watch = process.platform === 'win32' ? require('./watch-win32') : require('gulp-watch'); -} +const watch = process.platform === 'win32' ? require('./watch-win32') : require('vscode-gulp-watch'); module.exports = function () { return watch.apply(null, arguments); diff --git a/build/lib/watch/package.json b/build/lib/watch/package.json index b26f589ce0d9..d509f873e849 100644 --- a/build/lib/watch/package.json +++ b/build/lib/watch/package.json @@ -5,7 +5,8 @@ "author": "Microsoft ", "private": true, "license": "MIT", - "devDependencies": { - "gulp-watch": "5.0.1" + "devDependencies": {}, + "dependencies": { + "vscode-gulp-watch": "^5.0.2" } } diff --git a/build/lib/watch/yarn.lock b/build/lib/watch/yarn.lock index f7d5d976b1f2..3f330da1764b 100644 --- a/build/lib/watch/yarn.lock +++ b/build/lib/watch/yarn.lock @@ -2,11 +2,6 @@ # yarn lockfile v1 -abbrev@1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" - integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== - ansi-colors@1.1.0, ansi-colors@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-1.1.0.tgz#6374b4dd5d4718ff3ce27a671a3b1cad077132a9" @@ -21,11 +16,6 @@ ansi-gray@^0.1.1: dependencies: ansi-wrap "0.1.0" -ansi-regex@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" - integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= - ansi-wrap@0.1.0, ansi-wrap@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/ansi-wrap/-/ansi-wrap-0.1.0.tgz#a82250ddb0015e9a27ca82e82ea603bbfa45efaf" @@ -39,26 +29,13 @@ anymatch@^1.3.0: micromatch "^2.1.5" normalize-path "^2.0.0" -anymatch@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" - integrity sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw== - dependencies: - micromatch "^3.1.4" - normalize-path "^2.1.1" - -aproba@^1.0.3: - version "1.2.0" - resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" - integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== - -are-we-there-yet@~1.1.2: - version "1.1.4" - resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz#bb5dca382bb94f05e15194373d16fd3ba1ca110d" - integrity sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0= +anymatch@~3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142" + integrity sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg== dependencies: - delegates "^1.0.0" - readable-stream "^2.0.6" + normalize-path "^3.0.0" + picomatch "^2.0.4" arr-diff@^2.0.0: version "2.0.0" @@ -72,7 +49,7 @@ arr-diff@^4.0.0: resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA= -arr-flatten@^1.0.1, arr-flatten@^1.1.0: +arr-flatten@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== @@ -87,56 +64,15 @@ array-unique@^0.2.1: resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53" integrity sha1-odl8yvy8JiXMcPrc6zalDFiwGlM= -array-unique@^0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" - integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= - assign-symbols@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= -async-each@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" - integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== - -atob@^2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" - integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== - -balanced-match@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" - integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= - -base@^0.11.1: - version "0.11.2" - resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" - integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg== - dependencies: - cache-base "^1.0.1" - class-utils "^0.3.5" - component-emitter "^1.2.1" - define-property "^1.0.0" - isobject "^3.0.1" - mixin-deep "^1.2.0" - pascalcase "^0.1.1" - -binary-extensions@^1.0.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.10.0.tgz#9aeb9a6c5e88638aad171e167f5900abe24835d0" - integrity sha1-muuabF6IY4qtFx4Wf1kAq+JINdA= - -brace-expansion@^1.1.7: - version "1.1.8" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292" - integrity sha1-wHshHHyVLsH479Uad+8NHTmQopI= - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" +binary-extensions@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.0.0.tgz#23c0df14f6a88077f5f986c0d167ec03c3d5537c" + integrity sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow== braces@^1.8.2: version "1.8.5" @@ -147,70 +83,27 @@ braces@^1.8.2: preserve "^0.2.0" repeat-element "^1.1.2" -braces@^2.3.1, braces@^2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" - integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== - dependencies: - arr-flatten "^1.1.0" - array-unique "^0.3.2" - extend-shallow "^2.0.1" - fill-range "^4.0.0" - isobject "^3.0.1" - repeat-element "^1.1.2" - snapdragon "^0.8.1" - snapdragon-node "^2.0.1" - split-string "^3.0.2" - to-regex "^3.0.1" - -cache-base@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" - integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ== - dependencies: - collection-visit "^1.0.0" - component-emitter "^1.2.1" - get-value "^2.0.6" - has-value "^1.0.0" - isobject "^3.0.1" - set-value "^2.0.0" - to-object-path "^0.3.0" - union-value "^1.0.0" - unset-value "^1.0.0" - -chokidar@^2.0.0: - version "2.1.6" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.6.tgz#b6cad653a929e244ce8a834244164d241fa954c5" - integrity sha512-V2jUo67OKkc6ySiRpJrjlpJKl9kDuG+Xb8VgsGzb+aEouhgS1D0weyPU4lEzdAcsCAvrih2J2BqyXqHWvVLw5g== - dependencies: - anymatch "^2.0.0" - async-each "^1.0.1" - braces "^2.3.2" - glob-parent "^3.1.0" - inherits "^2.0.3" - is-binary-path "^1.0.0" - is-glob "^4.0.0" - normalize-path "^3.0.0" - path-is-absolute "^1.0.0" - readdirp "^2.2.1" - upath "^1.1.1" +braces@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +chokidar@3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.3.0.tgz#12c0714668c55800f659e262d4962a97faf554a6" + integrity sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A== + dependencies: + anymatch "~3.1.1" + braces "~3.0.2" + glob-parent "~5.1.0" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.2.0" optionalDependencies: - fsevents "^1.2.7" - -chownr@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.2.tgz#a18f1e0b269c8a6a5d3c86eb298beb14c3dd7bf6" - integrity sha512-GkfeAQh+QNy3wquu9oIZr6SS5x7wGdSgNQvD10X3r+AZr1Oys22HW8kAmDMvNg2+Dm0TeGaEuO8gFwdBXxwO8A== - -class-utils@^0.3.5: - version "0.3.6" - resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" - integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg== - dependencies: - arr-union "^3.1.0" - define-property "^0.2.5" - isobject "^3.0.0" - static-extend "^0.1.1" + fsevents "~2.1.1" clone-buffer@^1.0.0: version "1.0.0" @@ -228,9 +121,9 @@ clone-stats@^1.0.0: integrity sha1-s3gt/4u1R04Yuba/D9/ngvh3doA= clone@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.3.tgz#298d7e2231660f40c003c2ed3140decf3f53085f" - integrity sha1-KY1+IjFmD0DAA8LtMUDezz9TCF8= + version "1.0.4" + resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" + integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4= clone@^2.1.1: version "2.1.2" @@ -246,105 +139,16 @@ cloneable-readable@^1.0.0: process-nextick-args "^2.0.0" readable-stream "^2.3.5" -code-point-at@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" - integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= - -collection-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" - integrity sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA= - dependencies: - map-visit "^1.0.0" - object-visit "^1.0.0" - color-support@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== -component-emitter@^1.2.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" - integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= - -console-control-strings@^1.0.0, console-control-strings@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" - integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= - -copy-descriptor@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" - integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= - core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= -debug@^2.2.0, debug@^2.3.3: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - -debug@^3.2.6: - version "3.2.6" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" - integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== - dependencies: - ms "^2.1.1" - -decode-uri-component@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" - integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= - -deep-extend@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" - integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== - -define-property@^0.2.5: - version "0.2.5" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" - integrity sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY= - dependencies: - is-descriptor "^0.1.0" - -define-property@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" - integrity sha1-dp66rz9KY6rTr56NMEybvnm/sOY= - dependencies: - is-descriptor "^1.0.0" - -define-property@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" - integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ== - dependencies: - is-descriptor "^1.0.2" - isobject "^3.0.1" - -delegates@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" - integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= - -detect-libc@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.2.tgz#71ad5d204bf17a6a6ca8f450c61454066ef461e1" - integrity sha1-ca1dIEvxempsqPRQxhRUBm70YeE= - expand-brackets@^0.1.4: version "0.1.5" resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b" @@ -352,19 +156,6 @@ expand-brackets@^0.1.4: dependencies: is-posix-bracket "^0.1.0" -expand-brackets@^2.1.4: - version "2.1.4" - resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" - integrity sha1-t3c14xXOMPa27/D4OwQVGiJEliI= - dependencies: - debug "^2.3.3" - define-property "^0.2.5" - extend-shallow "^2.0.1" - posix-character-classes "^0.1.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - expand-range@^1.8.1: version "1.8.2" resolved "https://registry.yarnpkg.com/expand-range/-/expand-range-1.8.2.tgz#a299effd335fe2721ebae8e257ec79644fc85337" @@ -372,14 +163,7 @@ expand-range@^1.8.1: dependencies: fill-range "^2.1.0" -extend-shallow@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" - integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8= - dependencies: - is-extendable "^0.1.0" - -extend-shallow@^3.0.0, extend-shallow@^3.0.2: +extend-shallow@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" integrity sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg= @@ -394,20 +178,6 @@ extglob@^0.3.1: dependencies: is-extglob "^1.0.0" -extglob@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" - integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw== - dependencies: - array-unique "^0.3.2" - define-property "^1.0.0" - expand-brackets "^2.1.4" - extend-shallow "^2.0.1" - fragment-cache "^0.2.1" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - fancy-log@1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/fancy-log/-/fancy-log-1.3.2.tgz#f41125e3d84f2e7d89a43d06d958c8f78be16be1" @@ -423,25 +193,22 @@ filename-regex@^2.0.0: integrity sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY= fill-range@^2.1.0: - version "2.2.3" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.3.tgz#50b77dfd7e469bc7492470963699fe7a8485a723" - integrity sha1-ULd9/X5Gm8dJJHCWNpn+eoSFpyM= + version "2.2.4" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.4.tgz#eb1e773abb056dcd8df2bfdf6af59b8b3a936565" + integrity sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q== dependencies: is-number "^2.1.0" isobject "^2.0.0" - randomatic "^1.1.3" + randomatic "^3.0.0" repeat-element "^1.1.2" repeat-string "^1.5.2" -fill-range@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" - integrity sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc= +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== dependencies: - extend-shallow "^2.0.1" - is-number "^3.0.0" - repeat-string "^1.6.1" - to-regex-range "^2.1.0" + to-regex-range "^5.0.1" first-chunk-stream@^2.0.0: version "2.0.0" @@ -450,7 +217,7 @@ first-chunk-stream@^2.0.0: dependencies: readable-stream "^2.0.2" -for-in@^1.0.1, for-in@^1.0.2: +for-in@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= @@ -462,51 +229,10 @@ for-own@^0.1.4: dependencies: for-in "^1.0.1" -fragment-cache@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" - integrity sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk= - dependencies: - map-cache "^0.2.2" - -fs-minipass@^1.2.5: - version "1.2.6" - resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.6.tgz#2c5cc30ded81282bfe8a0d7c7c1853ddeb102c07" - integrity sha512-crhvyXcMejjv3Z5d2Fa9sf5xLYVCF5O1c71QxbVnbLsmYMBEvDAftewesN/HhY03YRoA7zOMxjNGrF5svGaaeQ== - dependencies: - minipass "^2.2.1" - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= - -fsevents@^1.2.7: - version "1.2.9" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.9.tgz#3f5ed66583ccd6f400b5a00db6f7e861363e388f" - integrity sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw== - dependencies: - nan "^2.12.1" - node-pre-gyp "^0.12.0" - -gauge@~2.7.3: - version "2.7.4" - resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" - integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c= - dependencies: - aproba "^1.0.3" - console-control-strings "^1.0.0" - has-unicode "^2.0.0" - object-assign "^4.1.0" - signal-exit "^3.0.0" - string-width "^1.0.1" - strip-ansi "^3.0.1" - wide-align "^1.1.0" - -get-value@^2.0.3, get-value@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" - integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= +fsevents@~2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.1.tgz#74c64e21df71721845d0c44fe54b7f56b82995a9" + integrity sha512-4FRPXWETxtigtJW/gxzEDsX1LVbPAM93VleB83kZB+ellqbHMkyt2aJfuzNLRvFPnGi6bcE5SvfxgbXPeKteJw== glob-base@^0.3.0: version "0.3.0" @@ -523,7 +249,7 @@ glob-parent@^2.0.0: dependencies: is-glob "^2.0.0" -glob-parent@^3.0.1, glob-parent@^3.1.0: +glob-parent@^3.0.1: version "3.1.0" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" integrity sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4= @@ -531,177 +257,35 @@ glob-parent@^3.0.1, glob-parent@^3.1.0: is-glob "^3.1.0" path-dirname "^1.0.0" -glob@^7.0.5: - version "7.1.2" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" - integrity sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ== +glob-parent@~5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.0.tgz#5f4c1d1e748d30cd73ad2944b3577a81b081e8c2" + integrity sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw== dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - -graceful-fs@^4.1.11: - version "4.2.0" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.0.tgz#8d8fdc73977cb04104721cb53666c1ca64cd328b" - integrity sha512-jpSvDPV4Cq/bgtpndIWbI5hmYxhQGHPC4d4cqBPb4DLniCfhJokdXhwhaDuLBGLQdvvRum/UiX6ECVIPvDXqdg== + is-glob "^4.0.1" graceful-fs@^4.1.2: - version "4.1.11" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" - integrity sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg= - -gulp-watch@5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/gulp-watch/-/gulp-watch-5.0.1.tgz#83d378752f5bfb46da023e73c17ed1da7066215d" - integrity sha512-HnTSBdzAOFIT4wmXYPDUn783TaYAq9bpaN05vuZNP5eni3z3aRx0NAKbjhhMYtcq76x4R1wf4oORDGdlrEjuog== - dependencies: - ansi-colors "1.1.0" - anymatch "^1.3.0" - chokidar "^2.0.0" - fancy-log "1.3.2" - glob-parent "^3.0.1" - object-assign "^4.1.0" - path-is-absolute "^1.0.1" - plugin-error "1.0.1" - readable-stream "^2.2.2" - slash "^1.0.0" - vinyl "^2.1.0" - vinyl-file "^2.0.0" - -has-unicode@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" - integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= - -has-value@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" - integrity sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8= - dependencies: - get-value "^2.0.3" - has-values "^0.1.4" - isobject "^2.0.0" - -has-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" - integrity sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc= - dependencies: - get-value "^2.0.6" - has-values "^1.0.0" - isobject "^3.0.0" - -has-values@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" - integrity sha1-bWHeldkd/Km5oCCJrThL/49it3E= - -has-values@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" - integrity sha1-lbC2P+whRmGab+V/51Yo1aOe/k8= - dependencies: - is-number "^3.0.0" - kind-of "^4.0.0" - -iconv-lite@^0.4.4: - version "0.4.24" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" - integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== - dependencies: - safer-buffer ">= 2.1.2 < 3" - -ignore-walk@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.1.tgz#a83e62e7d272ac0e3b551aaa82831a19b69f82f8" - integrity sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ== - dependencies: - minimatch "^3.0.4" - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2, inherits@^2.0.1, inherits@~2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= + version "4.2.3" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" + integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ== -inherits@^2.0.3: +inherits@^2.0.1, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== -ini@~1.3.0: - version "1.3.4" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.4.tgz#0537cb79daf59b59a1a517dff706c86ec039162e" - integrity sha1-BTfLedr1m1mhpRff9wbIbsA5Fi4= - -is-accessor-descriptor@^0.1.6: - version "0.1.6" - resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" - integrity sha1-qeEss66Nh2cn7u84Q/igiXtcmNY= - dependencies: - kind-of "^3.0.2" - -is-accessor-descriptor@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" - integrity sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ== - dependencies: - kind-of "^6.0.0" - -is-binary-path@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" - integrity sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg= +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== dependencies: - binary-extensions "^1.0.0" + binary-extensions "^2.0.0" is-buffer@^1.1.5: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== -is-data-descriptor@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" - integrity sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y= - dependencies: - kind-of "^3.0.2" - -is-data-descriptor@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" - integrity sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ== - dependencies: - kind-of "^6.0.0" - -is-descriptor@^0.1.0: - version "0.1.6" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" - integrity sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg== - dependencies: - is-accessor-descriptor "^0.1.6" - is-data-descriptor "^0.1.4" - kind-of "^5.0.0" - -is-descriptor@^1.0.0, is-descriptor@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" - integrity sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg== - dependencies: - is-accessor-descriptor "^1.0.0" - is-data-descriptor "^1.0.0" - kind-of "^6.0.2" - is-dotfile@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1" @@ -714,7 +298,7 @@ is-equal-shallow@^0.1.3: dependencies: is-primitive "^2.0.0" -is-extendable@^0.1.0, is-extendable@^0.1.1: +is-extendable@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= @@ -736,13 +320,6 @@ is-extglob@^2.1.0, is-extglob@^2.1.1: resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= -is-fullwidth-code-point@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" - integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= - dependencies: - number-is-nan "^1.0.0" - is-glob@^2.0.0, is-glob@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863" @@ -757,7 +334,7 @@ is-glob@^3.1.0: dependencies: is-extglob "^2.1.0" -is-glob@^4.0.0: +is-glob@^4.0.1, is-glob@~4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== @@ -771,14 +348,17 @@ is-number@^2.1.0: dependencies: kind-of "^3.0.2" -is-number@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" - integrity sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU= - dependencies: - kind-of "^3.0.2" +is-number@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-4.0.0.tgz#0026e37f5454d73e356dfe6564699867c6a7f0ff" + integrity sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ== + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== -is-plain-object@^2.0.3, is-plain-object@^2.0.4: +is-plain-object@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== @@ -800,11 +380,6 @@ is-utf8@^0.2.0: resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI= -is-windows@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" - integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== - isarray@1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" @@ -817,46 +392,27 @@ isobject@^2.0.0: dependencies: isarray "1.0.0" -isobject@^3.0.0, isobject@^3.0.1: +isobject@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= -kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: +kind-of@^3.0.2: version "3.2.2" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ= dependencies: is-buffer "^1.1.5" -kind-of@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" - integrity sha1-IIE989cSkosgc3hpGkUGb65y3Vc= - dependencies: - is-buffer "^1.1.5" - -kind-of@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" - integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== - -kind-of@^6.0.0, kind-of@^6.0.2: +kind-of@^6.0.0: version "6.0.2" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051" integrity sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA== -map-cache@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" - integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= - -map-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" - integrity sha1-7Nyo8TFE5mDxtb1B8S80edmN+48= - dependencies: - object-visit "^1.0.0" +math-random@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/math-random/-/math-random-1.0.4.tgz#5dd6943c938548267016d4e34f057583080c514c" + integrity sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A== micromatch@^2.1.5: version "2.3.11" @@ -877,198 +433,23 @@ micromatch@^2.1.5: parse-glob "^3.0.4" regex-cache "^0.4.2" -micromatch@^3.1.10, micromatch@^3.1.4: - version "3.1.10" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" - integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - braces "^2.3.1" - define-property "^2.0.2" - extend-shallow "^3.0.2" - extglob "^2.0.4" - fragment-cache "^0.2.1" - kind-of "^6.0.2" - nanomatch "^1.2.9" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.2" - -minimatch@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" - integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== - dependencies: - brace-expansion "^1.1.7" - -minimist@0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" - integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= - -minimist@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" - integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= - -minipass@^2.2.1, minipass@^2.3.5: - version "2.3.5" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.5.tgz#cacebe492022497f656b0f0f51e2682a9ed2d848" - integrity sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA== - dependencies: - safe-buffer "^5.1.2" - yallist "^3.0.0" - -minizlib@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.2.1.tgz#dd27ea6136243c7c880684e8672bb3a45fd9b614" - integrity sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA== - dependencies: - minipass "^2.2.1" - -mixin-deep@^1.2.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" - integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA== - dependencies: - for-in "^1.0.2" - is-extendable "^1.0.1" - -mkdirp@^0.5.0, mkdirp@^0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" - integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= - dependencies: - minimist "0.0.8" - -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= - -ms@^2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" - integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== - -nan@^2.12.1: - version "2.14.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" - integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== - -nanomatch@^1.2.9: - version "1.2.13" - resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" - integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA== - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - define-property "^2.0.2" - extend-shallow "^3.0.2" - fragment-cache "^0.2.1" - is-windows "^1.0.2" - kind-of "^6.0.2" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -needle@^2.2.1: - version "2.4.0" - resolved "https://registry.yarnpkg.com/needle/-/needle-2.4.0.tgz#6833e74975c444642590e15a750288c5f939b57c" - integrity sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg== - dependencies: - debug "^3.2.6" - iconv-lite "^0.4.4" - sax "^1.2.4" - -node-pre-gyp@^0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.12.0.tgz#39ba4bb1439da030295f899e3b520b7785766149" - integrity sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A== - dependencies: - detect-libc "^1.0.2" - mkdirp "^0.5.1" - needle "^2.2.1" - nopt "^4.0.1" - npm-packlist "^1.1.6" - npmlog "^4.0.2" - rc "^1.2.7" - rimraf "^2.6.1" - semver "^5.3.0" - tar "^4" - -nopt@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" - integrity sha1-0NRoWv1UFRk8jHUFYC0NF81kR00= - dependencies: - abbrev "1" - osenv "^0.1.4" - -normalize-path@^2.0.0, normalize-path@^2.0.1, normalize-path@^2.1.1: +normalize-path@^2.0.0, normalize-path@^2.0.1: version "2.1.1" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk= dependencies: remove-trailing-separator "^1.0.1" -normalize-path@^3.0.0: +normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== -npm-bundled@^1.0.1: - version "1.0.6" - resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.6.tgz#e7ba9aadcef962bb61248f91721cd932b3fe6bdd" - integrity sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g== - -npm-packlist@^1.1.6: - version "1.4.4" - resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.4.tgz#866224233850ac534b63d1a6e76050092b5d2f44" - integrity sha512-zTLo8UcVYtDU3gdeaFu2Xu0n0EvelfHDGuqtNIn5RO7yQj4H1TqNdBc/yZjxnWA0PVB8D3Woyp0i5B43JwQ6Vw== - dependencies: - ignore-walk "^3.0.1" - npm-bundled "^1.0.1" - -npmlog@^4.0.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" - integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== - dependencies: - are-we-there-yet "~1.1.2" - console-control-strings "~1.1.0" - gauge "~2.7.3" - set-blocking "~2.0.0" - -number-is-nan@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" - integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= - object-assign@^4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= -object-copy@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" - integrity sha1-fn2Fi3gb18mRpBupde04EnVOmYw= - dependencies: - copy-descriptor "^0.1.0" - define-property "^0.2.5" - kind-of "^3.0.3" - -object-visit@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" - integrity sha1-95xEk68MU3e1n+OdOV5BBC3QRbs= - dependencies: - isobject "^3.0.0" - object.omit@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa" @@ -1077,38 +458,6 @@ object.omit@^2.0.0: for-own "^0.1.4" is-extendable "^0.1.1" -object.pick@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" - integrity sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c= - dependencies: - isobject "^3.0.1" - -once@^1.3.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= - dependencies: - wrappy "1" - -os-homedir@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" - integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= - -os-tmpdir@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" - integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= - -osenv@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.4.tgz#42fe6d5953df06c8064be6f176c3d05aaaa34644" - integrity sha1-Qv5tWVPfBsgGS+bxdsPQWqqjRkQ= - dependencies: - os-homedir "^1.0.0" - os-tmpdir "^1.0.0" - parse-glob@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/parse-glob/-/parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c" @@ -1119,21 +468,21 @@ parse-glob@^3.0.4: is-extglob "^1.0.0" is-glob "^2.0.0" -pascalcase@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" - integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= - path-dirname@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" integrity sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA= -path-is-absolute@^1.0.0, path-is-absolute@^1.0.1: +path-is-absolute@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= +picomatch@^2.0.4: + version "2.1.0" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.1.0.tgz#0fd042f568d08b1ad9ff2d3ec0f0bfb3cb80e177" + integrity sha512-uhnEDzAbrcJ8R3g2fANnSuXZMBtkpSjxTTgn2LeSiQlfmq72enQJWdQllXW24MBLYnA1SBD2vfvx2o0Zw3Ielw== + pify@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" @@ -1161,11 +510,6 @@ plugin-error@1.0.1: arr-union "^3.1.0" extend-shallow "^3.0.2" -posix-character-classes@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" - integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= - preserve@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" @@ -1176,43 +520,16 @@ process-nextick-args@^2.0.0, process-nextick-args@~2.0.0: resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== -process-nextick-args@~1.0.6: - version "1.0.7" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" - integrity sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M= - -randomatic@^1.1.3: - version "1.1.7" - resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-1.1.7.tgz#c7abe9cc8b87c0baa876b19fde83fd464797e38c" - integrity sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how== - dependencies: - is-number "^3.0.0" - kind-of "^4.0.0" - -rc@^1.2.7: - version "1.2.8" - resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" - integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== - dependencies: - deep-extend "^0.6.0" - ini "~1.3.0" - minimist "^1.2.0" - strip-json-comments "~2.0.1" - -readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.2.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c" - integrity sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ== +randomatic@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-3.1.1.tgz#b776efc59375984e36c537b2f51a1f0aff0da1ed" + integrity sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw== dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~1.0.6" - safe-buffer "~5.1.1" - string_decoder "~1.0.3" - util-deprecate "~1.0.1" + is-number "^4.0.0" + kind-of "^6.0.0" + math-random "^1.0.1" -readable-stream@^2.3.5: +readable-stream@^2.0.2, readable-stream@^2.2.2, readable-stream@^2.3.5: version "2.3.6" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw== @@ -1225,14 +542,12 @@ readable-stream@^2.3.5: string_decoder "~1.1.1" util-deprecate "~1.0.1" -readdirp@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525" - integrity sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ== +readdirp@~3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.2.0.tgz#c30c33352b12c96dfb4b895421a49fd5a9593839" + integrity sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ== dependencies: - graceful-fs "^4.1.11" - micromatch "^3.1.10" - readable-stream "^2.0.2" + picomatch "^2.0.4" regex-cache@^0.4.2: version "0.4.4" @@ -1241,25 +556,17 @@ regex-cache@^0.4.2: dependencies: is-equal-shallow "^0.1.3" -regex-not@^1.0.0, regex-not@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" - integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A== - dependencies: - extend-shallow "^3.0.2" - safe-regex "^1.1.0" - remove-trailing-separator@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= repeat-element@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.2.tgz#ef089a178d1483baae4d93eb98b4f9e4e11d990a" - integrity sha1-7wiaF40Ug7quTZPrmLT55OEdmQo= + version "1.1.3" + resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce" + integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g== -repeat-string@^1.5.2, repeat-string@^1.6.1: +repeat-string@^1.5.2: version "1.6.1" resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= @@ -1274,161 +581,10 @@ replace-ext@^1.0.0: resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.0.tgz#de63128373fcbf7c3ccfa4de5a480c45a67958eb" integrity sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs= -resolve-url@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" - integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= - -ret@~0.1.10: - version "0.1.15" - resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" - integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== - -rimraf@^2.6.1: - version "2.6.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36" - integrity sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w== - dependencies: - glob "^7.0.5" - -safe-buffer@^5.1.2: - version "5.2.0" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" - integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== - safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" - integrity sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg== - -safe-regex@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" - integrity sha1-QKNmnzsHfR6UPURinhV91IAjvy4= - dependencies: - ret "~0.1.10" - -"safer-buffer@>= 2.1.2 < 3": - version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - -sax@^1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" - integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== - -semver@^5.3.0: - version "5.4.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e" - integrity sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg== - -set-blocking@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" - integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= - -set-value@^2.0.0, set-value@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" - integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== - dependencies: - extend-shallow "^2.0.1" - is-extendable "^0.1.1" - is-plain-object "^2.0.3" - split-string "^3.0.1" - -signal-exit@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" - integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= - -slash@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" - integrity sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU= - -snapdragon-node@^2.0.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" - integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw== - dependencies: - define-property "^1.0.0" - isobject "^3.0.0" - snapdragon-util "^3.0.1" - -snapdragon-util@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" - integrity sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ== - dependencies: - kind-of "^3.2.0" - -snapdragon@^0.8.1: - version "0.8.2" - resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" - integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg== - dependencies: - base "^0.11.1" - debug "^2.2.0" - define-property "^0.2.5" - extend-shallow "^2.0.1" - map-cache "^0.2.2" - source-map "^0.5.6" - source-map-resolve "^0.5.0" - use "^3.1.0" - -source-map-resolve@^0.5.0: - version "0.5.2" - resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.2.tgz#72e2cc34095543e43b2c62b2c4c10d4a9054f259" - integrity sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA== - dependencies: - atob "^2.1.1" - decode-uri-component "^0.2.0" - resolve-url "^0.2.1" - source-map-url "^0.4.0" - urix "^0.1.0" - -source-map-url@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" - integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM= - -source-map@^0.5.6: - version "0.5.7" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" - integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= - -split-string@^3.0.1, split-string@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" - integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== - dependencies: - extend-shallow "^3.0.0" - -static-extend@^0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" - integrity sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY= - dependencies: - define-property "^0.2.5" - object-copy "^0.1.0" - -string-width@^1.0.1, string-width@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" - integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= - dependencies: - code-point-at "^1.0.0" - is-fullwidth-code-point "^1.0.0" - strip-ansi "^3.0.0" - -string_decoder@~1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab" - integrity sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ== - dependencies: - safe-buffer "~5.1.0" + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== string_decoder@~1.1.1: version "1.1.1" @@ -1437,13 +593,6 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -strip-ansi@^3.0.0, strip-ansi@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" - integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= - dependencies: - ansi-regex "^2.0.0" - strip-bom-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/strip-bom-stream/-/strip-bom-stream-2.0.0.tgz#f87db5ef2613f6968aa545abfe1ec728b6a829ca" @@ -1459,86 +608,17 @@ strip-bom@^2.0.0: dependencies: is-utf8 "^0.2.0" -strip-json-comments@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" - integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= - -tar@^4: - version "4.4.10" - resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.10.tgz#946b2810b9a5e0b26140cf78bea6b0b0d689eba1" - integrity sha512-g2SVs5QIxvo6OLp0GudTqEf05maawKUxXru104iaayWA09551tFCTI8f1Asb4lPfkBr91k07iL4c11XO3/b0tA== - dependencies: - chownr "^1.1.1" - fs-minipass "^1.2.5" - minipass "^2.3.5" - minizlib "^1.2.1" - mkdirp "^0.5.0" - safe-buffer "^5.1.2" - yallist "^3.0.3" - time-stamp@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-1.1.0.tgz#764a5a11af50561921b133f3b44e618687e0f5c3" integrity sha1-dkpaEa9QVhkhsTPztE5hhofg9cM= -to-object-path@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" - integrity sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68= - dependencies: - kind-of "^3.0.2" - -to-regex-range@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" - integrity sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg= - dependencies: - is-number "^3.0.0" - repeat-string "^1.6.1" - -to-regex@^3.0.1, to-regex@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" - integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw== - dependencies: - define-property "^2.0.2" - extend-shallow "^3.0.2" - regex-not "^1.0.2" - safe-regex "^1.1.0" - -union-value@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" - integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg== - dependencies: - arr-union "^3.1.0" - get-value "^2.0.6" - is-extendable "^0.1.1" - set-value "^2.0.1" - -unset-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" - integrity sha1-g3aHP30jNRef+x5vw6jtDfyKtVk= +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== dependencies: - has-value "^0.3.1" - isobject "^3.0.0" - -upath@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/upath/-/upath-1.1.2.tgz#3db658600edaeeccbe6db5e684d67ee8c2acd068" - integrity sha512-kXpym8nmDmlCBr7nKdIx8P2jNBa+pBpIUFRnKJ4dr8htyYGJFokkr2ZvERRtUN+9SY+JqXouNgUPtv6JQva/2Q== - -urix@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" - integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= - -use@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" - integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== + is-number "^7.0.0" util-deprecate@~1.0.1: version "1.0.2" @@ -1578,19 +658,20 @@ vinyl@^2.1.0: remove-trailing-separator "^1.0.1" replace-ext "^1.0.0" -wide-align@^1.1.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.2.tgz#571e0f1b0604636ebc0dfc21b0339bbe31341710" - integrity sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w== +vscode-gulp-watch@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/vscode-gulp-watch/-/vscode-gulp-watch-5.0.2.tgz#0060ba8d091284a6fbd7e608aa318a9c1d73b840" + integrity sha512-l2v+W3iQvxpX2ny2C7eJTd+83rQXiZ85KGY0mub/QRqUxgDc+KH/EYiw4mttzIhPzVBmxrUO4RcLNbPdccg0mQ== dependencies: - string-width "^1.0.2" - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= - -yallist@^3.0.0, yallist@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.3.tgz#b4b049e314be545e3ce802236d6cd22cd91c3de9" - integrity sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A== + ansi-colors "1.1.0" + anymatch "^1.3.0" + chokidar "3.3.0" + fancy-log "1.3.2" + glob-parent "^3.0.1" + normalize-path "^3.0.0" + object-assign "^4.1.0" + path-is-absolute "^1.0.1" + plugin-error "1.0.1" + readable-stream "^2.2.2" + vinyl "^2.1.0" + vinyl-file "^2.0.0" diff --git a/build/monaco/monaco.d.ts.recipe b/build/monaco/monaco.d.ts.recipe index b45cc8b6208e..57a446cda159 100644 --- a/build/monaco/monaco.d.ts.recipe +++ b/build/monaco/monaco.d.ts.recipe @@ -43,7 +43,7 @@ declare namespace monaco { } declare namespace monaco.editor { - +#include(vs/editor/browser/widget/diffNavigator): IDiffNavigator #includeAll(vs/editor/standalone/browser/standaloneEditor;modes.=>languages.;editorCommon.=>): #include(vs/editor/standalone/common/standaloneThemeService): BuiltinTheme, IStandaloneThemeData, IColors #include(vs/editor/common/modes/supports/tokenization): ITokenThemeRule diff --git a/build/monaco/monaco.usage.recipe b/build/monaco/monaco.usage.recipe index f1a74a25e301..3c48da8d85a7 100644 --- a/build/monaco/monaco.usage.recipe +++ b/build/monaco/monaco.usage.recipe @@ -2,38 +2,17 @@ // This file is adding references to various symbols which should not be removed via tree shaking import { ServiceIdentifier } from './vs/platform/instantiation/common/instantiation'; -import { IContextViewService } from './vs/platform/contextview/browser/contextView'; -import { IHighlight } from './vs/base/parts/quickopen/browser/quickOpenModel'; -import { IWorkspaceContextService } from './vs/platform/workspace/common/workspace'; -import { IEnvironmentService } from './vs/platform/environment/common/environment'; -import { CountBadge } from './vs/base/browser/ui/countBadge/countBadge'; -import { SimpleWorkerClient, create as create1 } from './vs/base/common/worker/simpleWorker'; +import { create as create1 } from './vs/base/common/worker/simpleWorker'; import { create as create2 } from './vs/editor/common/services/editorSimpleWorker'; -import { QuickOpenWidget } from './vs/base/parts/quickopen/browser/quickOpenWidget'; -import { WorkbenchAsyncDataTree } from './vs/platform/list/browser/listService'; import { SyncDescriptor0, SyncDescriptor1, SyncDescriptor2, SyncDescriptor3, SyncDescriptor4, SyncDescriptor5, SyncDescriptor6, SyncDescriptor7, SyncDescriptor8 } from './vs/platform/instantiation/common/descriptors'; -import { DiffNavigator } from './vs/editor/browser/widget/diffNavigator'; -import { DocumentRangeFormattingEditProvider } from './vs/editor/common/modes'; import * as editorAPI from './vs/editor/editor.api'; (function () { var a: any; var b: any; - a = (b).layout; // IContextViewProvider - a = (b).getWorkspaceFolder; // IWorkspaceFolderProvider - a = (b).getWorkspace; // IWorkspaceFolderProvider - a = (b).style; // IThemable - a = (b).style; // IThemable - a = (>b).style; // IThemable - a = (b).userHome; // IUserHomeProvider - a = (b).previous; // IDiffNavigator a = (>b).type; - a = (b).start; - a = (b).end; - a = (>b).getProxyObject; // IWorkerClient a = create1; a = create2; - a = (b).extensionId; // injection madness a = (>b).ctor; diff --git a/build/npm/postinstall.js b/build/npm/postinstall.js index 5a6a2bc2a34e..86f41ba3dc72 100644 --- a/build/npm/postinstall.js +++ b/build/npm/postinstall.js @@ -73,48 +73,3 @@ yarnInstall(`build`); // node modules required for build yarnInstall('test/automation'); // node modules required for smoketest yarnInstall('test/smoke'); // node modules required for smoketest yarnInstallBuildDependencies(); // node modules for watching, specific to host node version, not electron - -// Remove the windows process tree typings as this causes duplicate identifier errors in tsc builds -const processTreeDts = path.join('node_modules', 'windows-process-tree', 'typings', 'windows-process-tree.d.ts'); -if (fs.existsSync(processTreeDts)) { - console.log('Removing windows-process-tree.d.ts'); - fs.unlinkSync(processTreeDts); -} - -function getInstalledVersion(packageName, cwd) { - const opts = {}; - if (cwd) { - opts.cwd = cwd; - } - - const result = cp.spawnSync(yarn, ['list', '--pattern', packageName], opts); - const stdout = result.stdout.toString(); - const match = stdout.match(new RegExp(packageName + '@(\\S+)')); - if (!match || !match[1]) { - throw new Error('Unexpected output from yarn list: ' + stdout); - } - - return match[1]; -} - -function assertSameVersionsBetweenFolders(packageName, otherFolder) { - const baseVersion = getInstalledVersion(packageName); - const otherVersion = getInstalledVersion(packageName, otherFolder); - - if (baseVersion !== otherVersion) { - throw new Error(`Mismatched versions installed for ${packageName}: root has ${baseVersion}, ./${otherFolder} has ${otherVersion}. These should be the same!`); - } -} - -// Check that modules in both the base package.json and remote/ have the same version installed -const requireSameVersionsInRemote = [ - 'xterm', - 'xterm-addon-search', - 'xterm-addon-web-links', - 'node-pty', - 'vscode-ripgrep' -]; - -requireSameVersionsInRemote.forEach(packageName => { - assertSameVersionsBetweenFolders(packageName, 'remote'); -}); diff --git a/build/npm/preinstall.js b/build/npm/preinstall.js index 6b1f5982b967..77f8eb52ce2b 100644 --- a/build/npm/preinstall.js +++ b/build/npm/preinstall.js @@ -7,8 +7,8 @@ let err = false; const majorNodeVersion = parseInt(/^(\d+)\./.exec(process.versions.node)[1]); -if (majorNodeVersion < 8 || majorNodeVersion >= 11) { - console.error('\033[1;31m*** Please use node >=8 and <11.\033[0;0m'); +if (majorNodeVersion < 10 || majorNodeVersion >= 13) { + console.error('\033[1;31m*** Please use node >=10 and <=12.\033[0;0m'); err = true; } diff --git a/build/package.json b/build/package.json index 50116ab2956e..289357016767 100644 --- a/build/package.json +++ b/build/package.json @@ -6,7 +6,7 @@ "@types/ansi-colors": "^3.2.0", "@types/azure": "0.9.19", "@types/debounce": "^1.0.0", - "@types/documentdb": "1.10.2", + "@types/documentdb": "^1.10.5", "@types/fancy-log": "^1.3.0", "@types/glob": "^7.1.1", "@types/gulp": "^4.0.5", @@ -44,10 +44,10 @@ "rollup": "^1.20.3", "rollup-plugin-commonjs": "^10.1.0", "rollup-plugin-node-resolve": "^5.2.0", + "service-downloader": "github:anthonydresser/service-downloader#0.1.7", "terser": "4.3.8", "tslint": "^5.9.1", - "service-downloader": "github:anthonydresser/service-downloader#0.1.7", - "typescript": "3.7.0-dev.20191017", + "typescript": "3.7.2", "vsce": "1.48.0", "vscode-telemetry-extractor": "^1.5.4", "xml2js": "^0.4.17" @@ -57,5 +57,8 @@ "watch": "tsc -p tsconfig.build.json --watch", "postinstall": "npm run compile", "npmCheckJs": "tsc --noEmit" + }, + "dependencies": { + "@azure/cosmos": "^3.4.0" } } diff --git a/build/win32/i18n/messages.de.isl b/build/win32/i18n/messages.de.isl index 755652bcdbe9..6a9f29aa9c27 100644 --- a/build/win32/i18n/messages.de.isl +++ b/build/win32/i18n/messages.de.isl @@ -5,4 +5,5 @@ AssociateWithFiles=%1 als Editor f AddToPath=Zu PATH hinzufügen (nach dem Neustart verfügbar) RunAfter=%1 nach der Installation ausführen Other=Andere: -SourceFile=%1-Quelldatei \ No newline at end of file +SourceFile=%1-Quelldatei +OpenWithCodeContextMenu=Mit %1 öffnen \ No newline at end of file diff --git a/build/win32/i18n/messages.en.isl b/build/win32/i18n/messages.en.isl index a6aab59b95a9..6535824eed67 100644 --- a/build/win32/i18n/messages.en.isl +++ b/build/win32/i18n/messages.en.isl @@ -5,4 +5,5 @@ AssociateWithFiles=Register %1 as an editor for supported file types AddToPath=Add to PATH (requires shell restart) RunAfter=Run %1 after installation Other=Other: -SourceFile=%1 Source File \ No newline at end of file +SourceFile=%1 Source File +OpenWithCodeContextMenu=Open with %1 \ No newline at end of file diff --git a/build/win32/i18n/messages.es.isl b/build/win32/i18n/messages.es.isl index e8d5af64b619..e51f099f9a13 100644 --- a/build/win32/i18n/messages.es.isl +++ b/build/win32/i18n/messages.es.isl @@ -5,4 +5,5 @@ AssociateWithFiles=Registrar %1 como editor para tipos de archivo admitidos AddToPath=Agregar a PATH (disponible después de reiniciar) RunAfter=Ejecutar %1 después de la instalación Other=Otros: -SourceFile=Archivo de origen %1 \ No newline at end of file +SourceFile=Archivo de origen %1 +OpenWithCodeContextMenu=Abrir con %1 \ No newline at end of file diff --git a/build/win32/i18n/messages.fr.isl b/build/win32/i18n/messages.fr.isl index 3e17036f8067..df1404186251 100644 --- a/build/win32/i18n/messages.fr.isl +++ b/build/win32/i18n/messages.fr.isl @@ -5,4 +5,5 @@ AssociateWithFiles=Inscrire %1 en tant qu' AddToPath=Ajouter à PATH (disponible après le redémarrage) RunAfter=Exécuter %1 après l'installation Other=Autre : -SourceFile=Fichier source %1 \ No newline at end of file +SourceFile=Fichier source %1 +OpenWithCodeContextMenu=Ouvrir avec %1 \ No newline at end of file diff --git a/build/win32/i18n/messages.hu.isl b/build/win32/i18n/messages.hu.isl index f141ad1b24ad..b64553da8e6d 100644 --- a/build/win32/i18n/messages.hu.isl +++ b/build/win32/i18n/messages.hu.isl @@ -5,4 +5,5 @@ AssociateWithFiles=%1 regisztr AddToPath=Hozzáadás a PATH-hoz (újraindítás után lesz elérhetõ) RunAfter=%1 indítása a telepítés után Other=Egyéb: -SourceFile=%1 forrásfájl \ No newline at end of file +SourceFile=%1 forrásfájl +OpenWithCodeContextMenu=Megnyitás a következõvel: %1 \ No newline at end of file diff --git a/build/win32/i18n/messages.it.isl b/build/win32/i18n/messages.it.isl index 7ad8a0622d45..08248c4ce1ba 100644 --- a/build/win32/i18n/messages.it.isl +++ b/build/win32/i18n/messages.it.isl @@ -5,4 +5,5 @@ AssociateWithFiles=Registra %1 come editor per i tipi di file supportati AddToPath=Aggiungi a PATH (disponibile dopo il riavvio) RunAfter=Esegui %1 dopo l'installazione Other=Altro: -SourceFile=File di origine %1 \ No newline at end of file +SourceFile=File di origine %1 +OpenWithCodeContextMenu=Apri con %1 \ No newline at end of file diff --git a/build/win32/i18n/messages.ja.isl b/build/win32/i18n/messages.ja.isl index 3a16aaa204e9..9675060e94af 100644 --- a/build/win32/i18n/messages.ja.isl +++ b/build/win32/i18n/messages.ja.isl @@ -5,4 +5,5 @@ AssociateWithFiles= AddToPath=PATH ‚ւ̒ljÁiÄ‹N“®Œã‚ÉŽg—p‰Â”\j RunAfter=ƒCƒ“ƒXƒg[ƒ‹Œã‚É %1 ‚ðŽÀs‚·‚é Other=‚»‚Ì‘¼: -SourceFile=%1 ƒ\[ƒX ƒtƒ@ƒCƒ‹ \ No newline at end of file +SourceFile=%1 ƒ\[ƒX ƒtƒ@ƒCƒ‹ +OpenWithCodeContextMenu=%1 ‚ÅŠJ‚­ \ No newline at end of file diff --git a/build/win32/i18n/messages.ko.isl b/build/win32/i18n/messages.ko.isl index 28860c36b633..5a510558bbd2 100644 --- a/build/win32/i18n/messages.ko.isl +++ b/build/win32/i18n/messages.ko.isl @@ -5,4 +5,5 @@ AssociateWithFiles=%1 AddToPath=PATH¿¡ Ãß°¡(´Ù½Ã ½ÃÀÛÇÑ ÈÄ »ç¿ë °¡´É) RunAfter=¼³Ä¡ ÈÄ %1 ½ÇÇà Other=±âŸ: -SourceFile=%1 ¿øº» ÆÄÀÏ \ No newline at end of file +SourceFile=%1 ¿øº» ÆÄÀÏ +OpenWithCodeContextMenu=%1(À¸)·Î ¿­±â \ No newline at end of file diff --git a/build/win32/i18n/messages.pt-br.isl b/build/win32/i18n/messages.pt-br.isl index d534637f8b61..e327e8fd1a06 100644 --- a/build/win32/i18n/messages.pt-br.isl +++ b/build/win32/i18n/messages.pt-br.isl @@ -5,4 +5,5 @@ AssociateWithFiles=Registre %1 como um editor para tipos de arquivos suportados AddToPath=Adicione em PATH (disponível após reiniciar) RunAfter=Executar %1 após a instalação Other=Outros: -SourceFile=Arquivo Fonte %1 \ No newline at end of file +SourceFile=Arquivo Fonte %1 +OpenWithCodeContextMenu=Abrir com %1 \ No newline at end of file diff --git a/build/win32/i18n/messages.ru.isl b/build/win32/i18n/messages.ru.isl index 4d834663627e..bca3b864a5f9 100644 --- a/build/win32/i18n/messages.ru.isl +++ b/build/win32/i18n/messages.ru.isl @@ -5,4 +5,5 @@ AssociateWithFiles= AddToPath=Äîáàâèòü â PATH (äîñòóïíî ïîñëå ïåðåçàãðóçêè) RunAfter=Çàïóñòèòü %1 ïîñëå óñòàíîâêè Other=Äðóãîå: -SourceFile=Èñõîäíûé ôàéë %1 \ No newline at end of file +SourceFile=Èñõîäíûé ôàéë %1 +OpenWithCodeContextMenu=Îòêðûòü ñ ïîìîùüþ %1 \ No newline at end of file diff --git a/build/win32/i18n/messages.tr.isl b/build/win32/i18n/messages.tr.isl index dc241d924c7b..b13e5e27bd2a 100644 --- a/build/win32/i18n/messages.tr.isl +++ b/build/win32/i18n/messages.tr.isl @@ -5,4 +5,5 @@ AssociateWithFiles=%1 uygulamas AddToPath=PATH'e ekle (yeniden baþlattýktan sonra kullanýlabilir) RunAfter=Kurulumdan sonra %1 uygulamasýný çalýþtýr. Other=Diðer: -SourceFile=%1 Kaynak Dosyasý \ No newline at end of file +SourceFile=%1 Kaynak Dosyasý +OpenWithCodeContextMenu=%1 Ýle Aç \ No newline at end of file diff --git a/build/win32/i18n/messages.zh-cn.isl b/build/win32/i18n/messages.zh-cn.isl index 349fc2ccc29b..8fa136f6d5a9 100644 --- a/build/win32/i18n/messages.zh-cn.isl +++ b/build/win32/i18n/messages.zh-cn.isl @@ -5,4 +5,5 @@ AssociateWithFiles= AddToPath=Ìí¼Óµ½ PATH (ÖØÆôºóÉúЧ) RunAfter=°²×°ºóÔËÐÐ %1 Other=ÆäËû: -SourceFile=%1 Ô´Îļþ \ No newline at end of file +SourceFile=%1 Ô´Îļþ +OpenWithCodeContextMenu=ͨ¹ý %1 ´ò¿ª \ No newline at end of file diff --git a/build/win32/i18n/messages.zh-tw.isl b/build/win32/i18n/messages.zh-tw.isl index 7c3f84aa131f..40c5fa92d793 100644 --- a/build/win32/i18n/messages.zh-tw.isl +++ b/build/win32/i18n/messages.zh-tw.isl @@ -5,4 +5,5 @@ AssociateWithFiles= AddToPath=¥[¤J PATH ¤¤ (­«·s±Ò°Ê«á¥Í®Ä) RunAfter=¦w¸Ë«á°õ¦æ %1 Other=¨ä¥L: -SourceFile=%1 ¨Ó·½ÀÉ®× \ No newline at end of file +SourceFile=%1 ¨Ó·½ÀÉ®× +OpenWithCodeContextMenu=¥H %1 ¶}±Ò \ No newline at end of file diff --git a/build/yarn.lock b/build/yarn.lock index a32f725051dd..e39467f9f3e1 100644 --- a/build/yarn.lock +++ b/build/yarn.lock @@ -2,6 +2,22 @@ # yarn lockfile v1 +"@azure/cosmos@^3.4.0": + version "3.4.0" + resolved "https://registry.yarnpkg.com/@azure/cosmos/-/cosmos-3.4.0.tgz#96f36a4522be23e1389d0516ea4d77e5fc153221" + integrity sha512-4ym+ezk7qBe4s7/tb6IJ5kmXE4xgEbAPbraT3382oeCRlYpGrblIZIDoWbthMCJfLyLBDX5T05Fhm18QeY1R/w== + dependencies: + "@types/debug" "^4.1.4" + debug "^4.1.1" + fast-json-stable-stringify "^2.0.0" + node-abort-controller "^1.0.4" + node-fetch "^2.6.0" + os-name "^3.1.0" + priorityqueuejs "^1.0.0" + semaphore "^1.0.5" + tslib "^1.9.3" + uuid "^3.3.2" + "@dsherret/to-absolute-glob@^2.0.2": version "2.0.2" resolved "https://registry.yarnpkg.com/@dsherret/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz#1f6475dc8bd974cea07a2daf3864b317b1dd332c" @@ -61,10 +77,15 @@ resolved "https://registry.yarnpkg.com/@types/debounce/-/debounce-1.0.0.tgz#417560200331e1bb84d72da85391102c2fcd61b7" integrity sha1-QXVgIAMx4buE1y2oU5EQLC/NYbc= -"@types/documentdb@1.10.2": - version "1.10.2" - resolved "https://registry.yarnpkg.com/@types/documentdb/-/documentdb-1.10.2.tgz#6795025cdc51577af5ed531b6f03bd44404f5350" - integrity sha512-A+AsxkjKB/mpnuNl2L/YP7azlpd/n/55rtBTTKYj203g5hSrDfv06AnN8v+pO1Qo6vCxm6JsKV/BaEBmgx4gaQ== +"@types/debug@^4.1.4": + version "4.1.5" + resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.5.tgz#b14efa8852b7768d898906613c23f688713e02cd" + integrity sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ== + +"@types/documentdb@^1.10.5": + version "1.10.5" + resolved "https://registry.yarnpkg.com/@types/documentdb/-/documentdb-1.10.5.tgz#86c6ec9be9ce07ff9fcac5ca3c17570c385d40a4" + integrity sha512-FHQV9Nc1ffrLkQxO0zFlDCRPyHZtuKmAAuJIi278COhtkKBuBRuKOzoO3JlT0yfUrivPjAzNae+gh9fS++r0Ag== dependencies: "@types/node" "*" @@ -950,6 +971,17 @@ core-util-is@1.0.2, core-util-is@~1.0.0: resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= +cross-spawn@^6.0.0: + version "6.0.5" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" + integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== + dependencies: + nice-try "^1.0.4" + path-key "^2.0.1" + semver "^5.5.0" + shebang-command "^1.2.0" + which "^1.2.9" + cryptiles@2.x.x: version "2.0.5" resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8" @@ -1031,6 +1063,13 @@ debug@^3.1.0: dependencies: ms "^2.1.1" +debug@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" + integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== + dependencies: + ms "^2.1.1" + decode-uri-component@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" @@ -1254,6 +1293,13 @@ end-of-stream@^1.0.0: dependencies: once "^1.4.0" +end-of-stream@^1.1.0: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + entities@^1.1.1, entities@~1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" @@ -1296,6 +1342,19 @@ eventemitter2@^5.0.1: resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-5.0.1.tgz#6197a095d5fb6b57e8942f6fd7eaad63a09c9452" integrity sha1-YZegldX7a1folC9v1+qtY6CclFI= +execa@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" + integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA== + dependencies: + cross-spawn "^6.0.0" + get-stream "^4.0.0" + is-stream "^1.1.0" + npm-run-path "^2.0.0" + p-finally "^1.0.0" + signal-exit "^3.0.0" + strip-eof "^1.0.0" + expand-brackets@^2.1.4: version "2.1.4" resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" @@ -1528,6 +1587,13 @@ get-stream@^2.2.0: object-assign "^4.0.1" pinkie-promise "^2.0.0" +get-stream@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" + integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== + dependencies: + pump "^3.0.0" + get-value@^2.0.3, get-value@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" @@ -2152,6 +2218,11 @@ isarray@1.0.0, isarray@~1.0.0: resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + isobject@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" @@ -2388,6 +2459,11 @@ lodash@^4.15.0, lodash@^4.17.10: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== +macos-release@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-2.3.0.tgz#eb1930b036c0800adebccd5f17bc4c12de8bb71f" + integrity sha512-OHhSbtcviqMPt7yfw5ef5aghS2jzFVKEFyCJndQt2YpSQ9qRVSEv2axSJI1paVThEu+FFGs584h/1YhxjVqajA== + magic-string@^0.25.2: version "0.25.3" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.3.tgz#34b8d2a2c7fec9d9bdf9929a3fd81d271ef35be9" @@ -2569,9 +2645,9 @@ ms@2.0.0: integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= ms@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" - integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== multimatch@^4.0.0: version "4.0.0" @@ -2627,6 +2703,21 @@ needle@^2.2.1: iconv-lite "^0.4.4" sax "^1.2.4" +nice-try@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" + integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== + +node-abort-controller@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/node-abort-controller/-/node-abort-controller-1.0.4.tgz#4095e41d58b2fae169d2f9892904d603e11c7a39" + integrity sha512-7cNtLKTAg0LrW3ViS2C7UfIzbL3rZd8L0++5MidbKqQVJ8yrH6+1VRSHl33P0ZjBTbOJd37d9EYekvHyKkB0QQ== + +node-fetch@^2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd" + integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA== + node-pre-gyp@^0.10.0: version "0.10.3" resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.10.3.tgz#3070040716afdc778747b61b6887bf78880b80fc" @@ -2676,6 +2767,13 @@ npm-packlist@^1.1.6: ignore-walk "^3.0.1" npm-bundled "^1.0.1" +npm-run-path@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" + integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8= + dependencies: + path-key "^2.0.0" + npmlog@^4.0.2: version "4.1.2" resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" @@ -2741,7 +2839,7 @@ object.pick@^1.3.0: dependencies: isobject "^3.0.1" -once@^1.3.0, once@^1.4.0: +once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= @@ -2761,6 +2859,14 @@ os-homedir@^1.0.0: resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= +os-name@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/os-name/-/os-name-3.1.0.tgz#dec19d966296e1cd62d701a5a66ee1ddeae70801" + integrity sha512-h8L+8aNjNcMpo/mAIBPn5PXCM16iyPGjHNWo6U1YO8sJTMHtEtyczI6QJnLoplswm6goopQkqc7OAnjhWcugVg== + dependencies: + macos-release "^2.2.0" + windows-release "^3.1.0" + os-tmpdir@^1.0.0, os-tmpdir@~1.0.1, os-tmpdir@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" @@ -2774,6 +2880,11 @@ osenv@^0.1.3, osenv@^0.1.4: os-homedir "^1.0.0" os-tmpdir "^1.0.0" +p-finally@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" + integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= + p-map@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.2.0.tgz#e4e94f311eabbc8633a1e79908165fca26241b6b" @@ -2813,6 +2924,11 @@ path-is-inside@^1.0.1: resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM= +path-key@^2.0.0, path-key@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" + integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= + path-parse@^1.0.5, path-parse@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" @@ -2878,7 +2994,7 @@ prettyjson@1.2.1: colors "^1.1.2" minimist "^1.2.0" -priorityqueuejs@1.0.0: +priorityqueuejs@1.0.0, priorityqueuejs@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/priorityqueuejs/-/priorityqueuejs-1.0.0.tgz#2ee4f23c2560913e08c07ce5ccdd6de3df2c5af8" integrity sha1-LuTyPCVgkT4IwHzlzN1t498sWvg= @@ -2893,6 +3009,14 @@ process-nextick-args@~2.0.0: resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" integrity sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw== +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + punycode@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" @@ -3200,11 +3324,21 @@ semaphore@1.0.5: resolved "https://registry.yarnpkg.com/semaphore/-/semaphore-1.0.5.tgz#b492576e66af193db95d65e25ec53f5f19798d60" integrity sha1-tJJXbmavGT25XWXiXsU/Xxl5jWA= +semaphore@^1.0.5: + version "1.1.0" + resolved "https://registry.yarnpkg.com/semaphore/-/semaphore-1.1.0.tgz#aaad8b86b20fe8e9b32b16dc2ee682a8cd26a8aa" + integrity sha512-O4OZEaNtkMd/K0i6js9SL+gqy0ZCBMgUvlSqHKi4IBdjhe7wB8pwztUk1BbZ1fmrvpwFrPbHzqd2w5pTcJH6LA== + semver@^5.1.0, semver@^5.3.0: version "5.6.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg== +semver@^5.5.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + "service-downloader@github:anthonydresser/service-downloader#0.1.7": version "0.1.7" resolved "https://codeload.github.com/anthonydresser/service-downloader/tar.gz/c08de456c9f1af6258061fdc524275b21c6db667" @@ -3242,6 +3376,18 @@ set-value@^2.0.0: is-plain-object "^2.0.3" split-string "^3.0.1" +shebang-command@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= + dependencies: + shebang-regex "^1.0.0" + +shebang-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= + signal-exit@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" @@ -3437,6 +3583,11 @@ strip-dirs@^2.0.0: dependencies: is-natural-number "^4.0.1" +strip-eof@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" + integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= + strip-json-comments@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" @@ -3600,6 +3751,11 @@ tslib@^1.8.0, tslib@^1.8.1: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286" integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ== +tslib@^1.9.3: + version "1.10.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" + integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ== + tslint@^5.9.1: version "5.11.0" resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.11.0.tgz#98f30c02eae3cde7006201e4c33cb08b48581eed" @@ -3650,10 +3806,10 @@ typed-rest-client@^0.9.0: tunnel "0.0.4" underscore "1.8.3" -typescript@3.7.0-dev.20191017: - version "3.7.0-dev.20191017" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.0-dev.20191017.tgz#e61440dd445edea6d7b9a699e7c5d5fbcd1906f2" - integrity sha512-Yi0lCPEN0cn9Gp8TEEkPpgKNR5SWAmx9Hmzzz+oEuivw6amURqRGynaLyFZkMA9iMsvYG5LLqhdlFO3uu5ZT/w== +typescript@3.7.2: + version "3.7.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.2.tgz#27e489b95fa5909445e9fef5ee48d81697ad18fb" + integrity sha512-ml7V7JfiN2Xwvcer+XAf2csGO1bPBdRbFCkYBczNZggrBZ9c7G3riSUeJmqEU5uOtXNPMhE3n+R4FA/3YOAWOQ== typescript@^3.0.1: version "3.5.3" @@ -3759,6 +3915,11 @@ uuid@^3.1.0: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.2.1.tgz#12c528bb9d58d0b9265d9a2f6f0fe8be17ff1f14" integrity sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA== +uuid@^3.3.2: + version "3.3.3" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.3.tgz#4568f0216e78760ee1dbf3a4d2cf53e224112866" + integrity sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ== + validator@~3.35.0: version "3.35.0" resolved "https://registry.yarnpkg.com/validator/-/validator-3.35.0.tgz#3f07249402c1fc8fc093c32c6e43d72a79cca1dc" @@ -3845,6 +4006,13 @@ vso-node-api@6.1.2-preview: typed-rest-client "^0.9.0" underscore "^1.8.3" +which@^1.2.9: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + wide-align@^1.1.0: version "1.1.3" resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" @@ -3852,6 +4020,13 @@ wide-align@^1.1.0: dependencies: string-width "^1.0.2 || 2" +windows-release@^3.1.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/windows-release/-/windows-release-3.2.0.tgz#8122dad5afc303d833422380680a79cdfa91785f" + integrity sha512-QTlz2hKLrdqukrsapKsINzqMgOUpQW268eJ0OaOpJN32h272waxR9fkB9VoWRtK7uKHG5EHJcTXQBD8XZVJkFA== + dependencies: + execa "^1.0.0" + wordwrap@~0.0.2: version "0.0.3" resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" diff --git a/cgmanifest.json b/cgmanifest.json index c78562c41d57..bc3086c31003 100644 --- a/cgmanifest.json +++ b/cgmanifest.json @@ -60,12 +60,12 @@ "git": { "name": "electron", "repositoryUrl": "https://github.com/electron/electron", - "commitHash": "1e50380fab37f407c4d357e1e30ecbc3d5a703b8" + "commitHash": "6f62f91822a80192cb711c604f1a8f1a176f328d" } }, "isOnlyProductionDependency": true, "license": "MIT", - "version": "6.0.12" + "version": "6.1.5" }, { "component": { @@ -98,7 +98,7 @@ "git": { "name": "vscode-codicons", "repositoryUrl": "https://github.com/microsoft/vscode-codicons", - "commitHash": "7f14c092f65f658cd520df3f13765efe828d0ba4" + "commitHash": "65d11e0839d0ce09faa1a159dc0b3c0bd1aa50be" } }, "license": "MIT and Creative Commons Attribution 4.0", diff --git a/extensions/admin-tool-ext-win/package.json b/extensions/admin-tool-ext-win/package.json index 48d2890915ce..6786293805f1 100644 --- a/extensions/admin-tool-ext-win/package.json +++ b/extensions/admin-tool-ext-win/package.json @@ -74,7 +74,7 @@ "vscode-nls": "^3.2.1" }, "devDependencies": { - "@types/node": "10", + "@types/node": "^12.11.7", "mocha-junit-reporter": "^1.17.0", "mocha-multi-reporters": "^1.1.7", "should": "^13.2.3", diff --git a/extensions/admin-tool-ext-win/yarn.lock b/extensions/admin-tool-ext-win/yarn.lock index 19ae10b4563b..b7aa7baad0c4 100644 --- a/extensions/admin-tool-ext-win/yarn.lock +++ b/extensions/admin-tool-ext-win/yarn.lock @@ -2,10 +2,10 @@ # yarn lockfile v1 -"@types/node@10": - version "10.17.4" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.4.tgz#8993a4fe3c4022fda66bf4ea660d615fc5659c6f" - integrity sha512-F2pgg+LcIr/elguz+x+fdBX5KeZXGUOp7TV8M0TVIrDezYLFRNt8oMTyps0VQ1kj5WGGoR18RdxnRDHXrIFHMQ== +"@types/node@^12.11.7": + version "12.12.7" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.7.tgz#01e4ea724d9e3bd50d90c11fd5980ba317d8fa11" + integrity sha512-E6Zn0rffhgd130zbCbAr/JdXfXkoOUFAKNs/rF8qnafSJ8KYaA/j3oz7dcwal+lYjLA7xvdd5J4wdYpCTlP8+w== "ads-extension-telemetry@github:Charles-Gagnon/ads-extension-telemetry#0.1.0": version "0.1.0" diff --git a/extensions/agent/package.json b/extensions/agent/package.json index ff5c21c61a4e..41ca0fca2721 100644 --- a/extensions/agent/package.json +++ b/extensions/agent/package.json @@ -82,7 +82,7 @@ }, "devDependencies": { "@types/mocha": "^5.2.5", - "@types/node": "^10.14.8", + "@types/node": "^12.11.7", "mocha": "^5.2.0", "mocha-junit-reporter": "^1.17.0", "mocha-multi-reporters": "^1.1.7", diff --git a/extensions/agent/yarn.lock b/extensions/agent/yarn.lock index 2db1288c3f43..f11b6cd39b9e 100644 --- a/extensions/agent/yarn.lock +++ b/extensions/agent/yarn.lock @@ -7,10 +7,10 @@ resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-5.2.6.tgz#b8622d50557dd155e9f2f634b7d68fd38de5e94b" integrity sha512-1axi39YdtBI7z957vdqXI4Ac25e7YihYQtJa+Clnxg1zTJEaIRbndt71O3sP4GAMgiAm0pY26/b9BrY4MR/PMw== -"@types/node@^10.14.8": - version "10.14.17" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.14.17.tgz#b96d4dd3e427382482848948041d3754d40fd5ce" - integrity sha512-p/sGgiPaathCfOtqu2fx5Mu1bcjuP8ALFg4xpGgNkcin7LwRyzUKniEHBKdcE1RPsenq5JVPIpMTJSygLboygQ== +"@types/node@^12.11.7": + version "12.12.7" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.7.tgz#01e4ea724d9e3bd50d90c11fd5980ba317d8fa11" + integrity sha512-E6Zn0rffhgd130zbCbAr/JdXfXkoOUFAKNs/rF8qnafSJ8KYaA/j3oz7dcwal+lYjLA7xvdd5J4wdYpCTlP8+w== agent-base@4, agent-base@^4.3.0: version "4.3.0" diff --git a/extensions/azurecore/package.json b/extensions/azurecore/package.json index 281dd8672df3..9203453187d1 100644 --- a/extensions/azurecore/package.json +++ b/extensions/azurecore/package.json @@ -186,7 +186,7 @@ }, "devDependencies": { "@types/mocha": "^5.2.5", - "@types/node": "^10.12.12", + "@types/node": "^12.11.7", "@types/request": "^2.48.1", "mocha": "^5.2.0", "mocha-junit-reporter": "^1.17.0", diff --git a/extensions/azurecore/yarn.lock b/extensions/azurecore/yarn.lock index b87a360ff0e8..ac683128c591 100644 --- a/extensions/azurecore/yarn.lock +++ b/extensions/azurecore/yarn.lock @@ -59,11 +59,16 @@ resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-5.2.5.tgz#8a4accfc403c124a0bafe8a9fc61a05ec1032073" integrity sha512-lAVp+Kj54ui/vLUFxsJTMtWvZraZxum3w3Nwkble2dNuV5VnPA+Mi2oGX9XYJAaIvZi3tn3cbjS/qcJXRb6Bww== -"@types/node@*", "@types/node@^10.12.12": +"@types/node@*": version "10.14.4" resolved "https://registry.yarnpkg.com/@types/node/-/node-10.14.4.tgz#1c586b991457cbb58fef51bc4e0cfcfa347714b5" integrity sha512-DT25xX/YgyPKiHFOpNuANIQIVvYEwCWXgK2jYYwqgaMrYE6+tq+DtmMwlD3drl6DJbUwtlIDnn0d7tIn/EbXBg== +"@types/node@^12.11.7": + version "12.12.7" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.7.tgz#01e4ea724d9e3bd50d90c11fd5980ba317d8fa11" + integrity sha512-E6Zn0rffhgd130zbCbAr/JdXfXkoOUFAKNs/rF8qnafSJ8KYaA/j3oz7dcwal+lYjLA7xvdd5J4wdYpCTlP8+w== + "@types/node@^8.0.47": version "8.10.45" resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.45.tgz#4c49ba34106bc7dced77ff6bae8eb6543cde8351" diff --git a/extensions/cms/package.json b/extensions/cms/package.json index d1ee19e30e4b..7eebb23b7f19 100644 --- a/extensions/cms/package.json +++ b/extensions/cms/package.json @@ -633,7 +633,7 @@ }, "devDependencies": { "@types/mocha": "^5.2.5", - "@types/node": "^10.14.8", + "@types/node": "^12.11.7", "mocha-junit-reporter": "^1.17.0", "mocha-multi-reporters": "^1.1.7", "mocha": "^5.2.0", diff --git a/extensions/cms/yarn.lock b/extensions/cms/yarn.lock index 0743d25fc7d0..06c5ec513245 100644 --- a/extensions/cms/yarn.lock +++ b/extensions/cms/yarn.lock @@ -7,10 +7,10 @@ resolved "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.5.tgz#8a4accfc403c124a0bafe8a9fc61a05ec1032073" integrity sha512-lAVp+Kj54ui/vLUFxsJTMtWvZraZxum3w3Nwkble2dNuV5VnPA+Mi2oGX9XYJAaIvZi3tn3cbjS/qcJXRb6Bww== -"@types/node@^10.14.8": - version "10.14.17" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.14.17.tgz#b96d4dd3e427382482848948041d3754d40fd5ce" - integrity sha512-p/sGgiPaathCfOtqu2fx5Mu1bcjuP8ALFg4xpGgNkcin7LwRyzUKniEHBKdcE1RPsenq5JVPIpMTJSygLboygQ== +"@types/node@^12.11.7": + version "12.12.7" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.7.tgz#01e4ea724d9e3bd50d90c11fd5980ba317d8fa11" + integrity sha512-E6Zn0rffhgd130zbCbAr/JdXfXkoOUFAKNs/rF8qnafSJ8KYaA/j3oz7dcwal+lYjLA7xvdd5J4wdYpCTlP8+w== ajv@^5.3.0: version "5.5.2" diff --git a/extensions/configuration-editing/package.json b/extensions/configuration-editing/package.json index bd10c038f8ac..5defc86cf5d9 100644 --- a/extensions/configuration-editing/package.json +++ b/extensions/configuration-editing/package.json @@ -38,7 +38,8 @@ "tasks.json", "keybindings.json", "extensions.json", - "argv.json" + "argv.json", + "profiles.json" ] } ], @@ -114,6 +115,6 @@ ] }, "devDependencies": { - "@types/node": "^10.14.8" + "@types/node": "^12.11.7" } } diff --git a/extensions/configuration-editing/schemas/attachContainer.schema.json b/extensions/configuration-editing/schemas/attachContainer.schema.json index 5f3d28abb89e..ba0df4b33dc1 100644 --- a/extensions/configuration-editing/schemas/attachContainer.schema.json +++ b/extensions/configuration-editing/schemas/attachContainer.schema.json @@ -18,6 +18,20 @@ "type": "integer" } }, + "remoteEnv": { + "type": "object", + "additionalProperties": { + "type": [ + "string", + "null" + ] + }, + "description": "Remote environment variables." + }, + "remoteUser": { + "type": "string", + "description": "The user VS Code Server will be started with. The default is the same user as the container." + }, "extensions": { "type": "array", "description": "An array of extensions that should be installed into the container.", diff --git a/extensions/configuration-editing/schemas/devContainer.schema.json b/extensions/configuration-editing/schemas/devContainer.schema.json index c24857720e65..631c58e82b5d 100644 --- a/extensions/configuration-editing/schemas/devContainer.schema.json +++ b/extensions/configuration-editing/schemas/devContainer.schema.json @@ -22,6 +22,20 @@ "$ref": "vscode://schemas/settings/machine", "description": "Machine specific settings that should be copied into the container." }, + "remoteEnv": { + "type": "object", + "additionalProperties": { + "type": [ + "string", + "null" + ] + }, + "description": "Remote environment variables." + }, + "remoteUser": { + "type": "string", + "description": "The user VS Code Server will be started with. The default is the same user as the container." + }, "postCreateCommand": { "type": [ "string", @@ -55,6 +69,21 @@ ] } }, + "containerEnv": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Container environment variables." + }, + "containerUser": { + "type": "string", + "description": "The user the container will be started with. The default is the user on the Docker image." + }, + "updateRemoteUserUID": { + "type": "boolean", + "description": "Controls whether on Linux the container's user should be updated with the local user's UID and GID. On by default." + }, "runArgs": { "type": "array", "description": "The arguments required when starting in the container.", @@ -68,7 +97,7 @@ "none", "stopContainer" ], - "description": "Action to take when VS Code is shutting down. The default is to stop the container." + "description": "Action to take when the VS Code window is closed. The default is to stop the container." }, "overrideCommand": { "type": "boolean", @@ -146,7 +175,7 @@ "none", "stopCompose" ], - "description": "Action to take when VS Code is shutting down. The default is to stop the containers." + "description": "Action to take when the VS Code window is closed. The default is to stop the containers." } }, "required": [ diff --git a/extensions/configuration-editing/src/extension.ts b/extensions/configuration-editing/src/extension.ts index f67105c60ffe..8b2574d474cc 100644 --- a/extensions/configuration-editing/src/extension.ts +++ b/extensions/configuration-editing/src/extension.ts @@ -4,23 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import { getLocation, parse, visit } from 'jsonc-parser'; -import * as path from 'path'; import * as vscode from 'vscode'; import * as nls from 'vscode-nls'; import { SettingsDocument } from './settingsDocumentHelper'; const localize = nls.loadMessageBundle(); -const fadedDecoration = vscode.window.createTextEditorDecorationType({ - light: { - color: '#757575' - }, - dark: { - color: '#878787' - } -}); - -let pendingLaunchJsonDecoration: NodeJS.Timer; - export function activate(context: vscode.ExtensionContext): void { //settings.json suggestions context.subscriptions.push(registerSettingsCompletions()); @@ -33,18 +21,6 @@ export function activate(context: vscode.ExtensionContext): void { // task.json variable suggestions context.subscriptions.push(registerVariableCompletions('**/tasks.json')); - - // launch.json decorations - context.subscriptions.push(vscode.window.onDidChangeActiveTextEditor(editor => updateLaunchJsonDecorations(editor), null, context.subscriptions)); - context.subscriptions.push(vscode.workspace.onDidChangeTextDocument(event => { - if (vscode.window.activeTextEditor && event.document === vscode.window.activeTextEditor.document) { - if (pendingLaunchJsonDecoration) { - clearTimeout(pendingLaunchJsonDecoration); - } - pendingLaunchJsonDecoration = setTimeout(() => updateLaunchJsonDecorations(vscode.window.activeTextEditor), 1000); - } - }, null, context.subscriptions)); - updateLaunchJsonDecorations(vscode.window.activeTextEditor); } function registerSettingsCompletions(): vscode.Disposable { @@ -153,39 +129,6 @@ function provideInstalledExtensionProposals(extensionsContent: IExtensionsConten return undefined; } -function updateLaunchJsonDecorations(editor: vscode.TextEditor | undefined): void { - if (!editor || path.basename(editor.document.fileName) !== 'launch.json') { - return; - } - - const ranges: vscode.Range[] = []; - let addPropertyAndValue = false; - let depthInArray = 0; - visit(editor.document.getText(), { - onObjectProperty: (property, offset, length) => { - // Decorate attributes which are unlikely to be edited by the user. - // Only decorate "configurations" if it is not inside an array (compounds have a configurations property which should not be decorated). - addPropertyAndValue = property === 'version' || property === 'type' || property === 'request' || property === 'compounds' || (property === 'configurations' && depthInArray === 0); - if (addPropertyAndValue) { - ranges.push(new vscode.Range(editor.document.positionAt(offset), editor.document.positionAt(offset + length))); - } - }, - onLiteralValue: (_value, offset, length) => { - if (addPropertyAndValue) { - ranges.push(new vscode.Range(editor.document.positionAt(offset), editor.document.positionAt(offset + length))); - } - }, - onArrayBegin: (_offset: number, _length: number) => { - depthInArray++; - }, - onArrayEnd: (_offset: number, _length: number) => { - depthInArray--; - } - }); - - editor.setDecorations(fadedDecoration, ranges); -} - vscode.languages.registerDocumentSymbolProvider({ pattern: '**/launch.json', language: 'jsonc' }, { provideDocumentSymbols(document: vscode.TextDocument, _token: vscode.CancellationToken): vscode.ProviderResult { const result: vscode.SymbolInformation[] = []; diff --git a/extensions/configuration-editing/yarn.lock b/extensions/configuration-editing/yarn.lock index 39ad23373148..d5aafed1189d 100644 --- a/extensions/configuration-editing/yarn.lock +++ b/extensions/configuration-editing/yarn.lock @@ -2,10 +2,10 @@ # yarn lockfile v1 -"@types/node@^10.14.8": - version "10.14.8" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.14.8.tgz#fe444203ecef1162348cd6deb76c62477b2cc6e9" - integrity sha512-I4+DbJEhLEg4/vIy/2gkWDvXBOOtPKV9EnLhYjMoqxcRW+TTZtUftkHktz/a8suoD5mUL7m6ReLrkPvSsCQQmw== +"@types/node@^12.11.7": + version "12.11.7" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.11.7.tgz#57682a9771a3f7b09c2497f28129a0462966524a" + integrity sha512-JNbGaHFCLwgHn/iCckiGSOZ1XYHsKFwREtzPwSGCVld1SGhOlmZw2D4ZI94HQCrBHbADzW9m4LER/8olJTRGHA== jsonc-parser@^2.1.1: version "2.1.1" diff --git a/extensions/dacpac/package.json b/extensions/dacpac/package.json index a8eb43b53e22..5b606a86291d 100644 --- a/extensions/dacpac/package.json +++ b/extensions/dacpac/package.json @@ -57,7 +57,7 @@ }, "devDependencies": { "@types/mocha": "^5.2.5", - "@types/node": "10", + "@types/node": "^12.11.7", "mocha": "^5.2.0", "mocha-junit-reporter": "^1.17.0", "mocha-multi-reporters": "^1.1.7", diff --git a/extensions/dacpac/yarn.lock b/extensions/dacpac/yarn.lock index b02627ca8064..4ababcfc7a20 100644 --- a/extensions/dacpac/yarn.lock +++ b/extensions/dacpac/yarn.lock @@ -7,10 +7,10 @@ resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-5.2.6.tgz#b8622d50557dd155e9f2f634b7d68fd38de5e94b" integrity sha512-1axi39YdtBI7z957vdqXI4Ac25e7YihYQtJa+Clnxg1zTJEaIRbndt71O3sP4GAMgiAm0pY26/b9BrY4MR/PMw== -"@types/node@10": - version "10.17.4" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.4.tgz#8993a4fe3c4022fda66bf4ea660d615fc5659c6f" - integrity sha512-F2pgg+LcIr/elguz+x+fdBX5KeZXGUOp7TV8M0TVIrDezYLFRNt8oMTyps0VQ1kj5WGGoR18RdxnRDHXrIFHMQ== +"@types/node@^12.11.7": + version "12.12.7" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.7.tgz#01e4ea724d9e3bd50d90c11fd5980ba317d8fa11" + integrity sha512-E6Zn0rffhgd130zbCbAr/JdXfXkoOUFAKNs/rF8qnafSJ8KYaA/j3oz7dcwal+lYjLA7xvdd5J4wdYpCTlP8+w== agent-base@4, agent-base@^4.3.0: version "4.3.0" diff --git a/extensions/extension-editing/package.json b/extensions/extension-editing/package.json index 50c97fb26f41..54edf5ccbdc5 100644 --- a/extensions/extension-editing/package.json +++ b/extensions/extension-editing/package.json @@ -54,6 +54,6 @@ }, "devDependencies": { "@types/markdown-it": "0.0.2", - "@types/node": "^10.14.8" + "@types/node": "^12.11.7" } } diff --git a/extensions/extension-editing/yarn.lock b/extensions/extension-editing/yarn.lock index fb98728f237d..d2f69a169d82 100644 --- a/extensions/extension-editing/yarn.lock +++ b/extensions/extension-editing/yarn.lock @@ -7,10 +7,10 @@ resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-0.0.2.tgz#5d9ad19e6e6508cdd2f2596df86fd0aade598660" integrity sha1-XZrRnm5lCM3S8llt+G/Qqt5ZhmA= -"@types/node@^10.14.8": - version "10.14.8" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.14.8.tgz#fe444203ecef1162348cd6deb76c62477b2cc6e9" - integrity sha512-I4+DbJEhLEg4/vIy/2gkWDvXBOOtPKV9EnLhYjMoqxcRW+TTZtUftkHktz/a8suoD5mUL7m6ReLrkPvSsCQQmw== +"@types/node@^12.11.7": + version "12.11.7" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.11.7.tgz#57682a9771a3f7b09c2497f28129a0462966524a" + integrity sha512-JNbGaHFCLwgHn/iCckiGSOZ1XYHsKFwREtzPwSGCVld1SGhOlmZw2D4ZI94HQCrBHbADzW9m4LER/8olJTRGHA== "@types/node@^6.0.46": version "6.0.78" diff --git a/extensions/git-ui/package.json b/extensions/git-ui/package.json index 2f1ab43f8920..b08942888ea2 100644 --- a/extensions/git-ui/package.json +++ b/extensions/git-ui/package.json @@ -7,7 +7,9 @@ "engines": { "vscode": "^1.5.0" }, - "extensionKind": "ui", + "extensionKind": [ + "ui" + ], "aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217", "enableProposedApi": true, "categories": [ @@ -23,6 +25,6 @@ "watch": "gulp watch-extension:git-ui" }, "devDependencies": { - "@types/node": "^10.14.8" + "@types/node": "^12.11.7" } -} \ No newline at end of file +} diff --git a/extensions/git-ui/src/main.ts b/extensions/git-ui/src/main.ts index 057074fae6c5..12eedaa4d83e 100644 --- a/extensions/git-ui/src/main.ts +++ b/extensions/git-ui/src/main.ts @@ -41,12 +41,12 @@ export function exec(command: string, options: cp.ExecOptions & { stdin?: string (error ? reject : resolve)({ error, stdout, stderr }); }); if (options.stdin) { - child.stdin.write(options.stdin, (err: any) => { + child.stdin!.write(options.stdin, (err: any) => { if (err) { reject(err); return; } - child.stdin.end((err: any) => { + child.stdin!.end((err: any) => { if (err) { reject(err); } diff --git a/extensions/git-ui/yarn.lock b/extensions/git-ui/yarn.lock index b23b0ac0392e..40784952b89a 100644 --- a/extensions/git-ui/yarn.lock +++ b/extensions/git-ui/yarn.lock @@ -2,7 +2,7 @@ # yarn lockfile v1 -"@types/node@^10.14.8": - version "10.14.8" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.14.8.tgz#fe444203ecef1162348cd6deb76c62477b2cc6e9" - integrity sha512-I4+DbJEhLEg4/vIy/2gkWDvXBOOtPKV9EnLhYjMoqxcRW+TTZtUftkHktz/a8suoD5mUL7m6ReLrkPvSsCQQmw== +"@types/node@^12.11.7": + version "12.11.7" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.11.7.tgz#57682a9771a3f7b09c2497f28129a0462966524a" + integrity sha512-JNbGaHFCLwgHn/iCckiGSOZ1XYHsKFwREtzPwSGCVld1SGhOlmZw2D4ZI94HQCrBHbADzW9m4LER/8olJTRGHA== diff --git a/extensions/git/package.json b/extensions/git/package.json index eafc49164393..d801ec64f97a 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -109,6 +109,24 @@ "dark": "resources/icons/dark/stage.svg" } }, + { + "command": "git.stageAllTracked", + "title": "%command.stageAllTracked%", + "category": "Git", + "icon": { + "light": "resources/icons/light/stage.svg", + "dark": "resources/icons/dark/stage.svg" + } + }, + { + "command": "git.stageAllUntracked", + "title": "%command.stageAllUntracked%", + "category": "Git", + "icon": { + "light": "resources/icons/light/stage.svg", + "dark": "resources/icons/dark/stage.svg" + } + }, { "command": "git.stageSelectedRanges", "title": "%command.stageSelectedRanges%", @@ -178,6 +196,24 @@ "dark": "resources/icons/dark/clean.svg" } }, + { + "command": "git.cleanAllTracked", + "title": "%command.cleanAllTracked%", + "category": "Git", + "icon": { + "light": "resources/icons/light/clean.svg", + "dark": "resources/icons/dark/clean.svg" + } + }, + { + "command": "git.cleanAllUntracked", + "title": "%command.cleanAllUntracked%", + "category": "Git", + "icon": { + "light": "resources/icons/light/clean.svg", + "dark": "resources/icons/dark/clean.svg" + } + }, { "command": "git.commit", "title": "%command.commit%", @@ -267,6 +303,11 @@ "title": "%command.createTag%", "category": "Git" }, + { + "command": "git.deleteTag", + "title": "%command.deleteTag%", + "category": "Git" + }, { "command": "git.fetch", "title": "%command.fetch%", @@ -396,6 +437,11 @@ "command": "git.stashApplyLatest", "title": "%command.stashApplyLatest%", "category": "Git" + }, + { + "command": "git.stashDrop", + "title": "%command.stashDrop%", + "category": "Git" } ], "menus": { @@ -440,6 +486,14 @@ "command": "git.stageAll", "when": "config.git.enabled && gitOpenRepositoryCount != 0" }, + { + "command": "git.stageAllTracked", + "when": "config.git.enabled && gitOpenRepositoryCount != 0" + }, + { + "command": "git.stageAllUntracked", + "when": "config.git.enabled && gitOpenRepositoryCount != 0" + }, { "command": "git.stageSelectedRanges", "when": "config.git.enabled && gitOpenRepositoryCount != 0" @@ -564,6 +618,10 @@ "command": "git.createTag", "when": "config.git.enabled && gitOpenRepositoryCount != 0" }, + { + "command": "git.deleteTag", + "when": "config.git.enabled && gitOpenRepositoryCount != 0" + }, { "command": "git.fetch", "when": "config.git.enabled && gitOpenRepositoryCount != 0" @@ -651,6 +709,10 @@ { "command": "git.stashApplyLatest", "when": "config.git.enabled && gitOpenRepositoryCount != 0" + }, + { + "command": "git.stashDrop", + "when": "config.git.enabled && gitOpenRepositoryCount != 0" } ], "scm/title": [ @@ -804,6 +866,11 @@ "group": "6_stash", "when": "scmProvider == git" }, + { + "command": "git.stashDrop", + "group": "6_stash", + "when": "scmProvider == git" + }, { "command": "git.showOutput", "group": "7_repository", @@ -840,22 +907,62 @@ }, { "command": "git.cleanAll", - "when": "scmProvider == git && scmResourceGroup == workingTree", + "when": "scmProvider == git && scmResourceGroup == workingTree && config.git.untrackedChanges == mixed", "group": "1_modification" }, { "command": "git.stageAll", - "when": "scmProvider == git && scmResourceGroup == workingTree", + "when": "scmProvider == git && scmResourceGroup == workingTree && config.git.untrackedChanges == mixed", "group": "1_modification" }, { "command": "git.cleanAll", - "when": "scmProvider == git && scmResourceGroup == workingTree", + "when": "scmProvider == git && scmResourceGroup == workingTree && config.git.untrackedChanges == mixed", "group": "inline" }, { "command": "git.stageAll", - "when": "scmProvider == git && scmResourceGroup == workingTree", + "when": "scmProvider == git && scmResourceGroup == workingTree && config.git.untrackedChanges == mixed", + "group": "inline" + }, + { + "command": "git.cleanAllTracked", + "when": "scmProvider == git && scmResourceGroup == workingTree && config.git.untrackedChanges != mixed", + "group": "1_modification" + }, + { + "command": "git.stageAllTracked", + "when": "scmProvider == git && scmResourceGroup == workingTree && config.git.untrackedChanges != mixed", + "group": "1_modification" + }, + { + "command": "git.cleanAllTracked", + "when": "scmProvider == git && scmResourceGroup == workingTree && config.git.untrackedChanges != mixed", + "group": "inline" + }, + { + "command": "git.stageAllTracked", + "when": "scmProvider == git && scmResourceGroup == workingTree && config.git.untrackedChanges != mixed", + "group": "inline" + }, + { + "command": "git.cleanAllUntracked", + "when": "scmProvider == git && scmResourceGroup == untracked", + "group": "1_modification" + }, + { + "command": "git.stageAllUntracked", + "when": "scmProvider == git && scmResourceGroup == untracked", + "group": "1_modification" + }, + { + "command": "git.cleanAllUntracked", + "when": "scmProvider == git && scmResourceGroup == untracked", + "group": "inline" + }, + { + "command": "git.stageAllUntracked", + "when": "scmProvider == git && scmResourceGroup == untracked", "group": "inline" } ], @@ -1031,13 +1138,63 @@ "command": "git.revealInExplorer", "when": "scmProvider == git && scmResourceGroup == workingTree", "group": "2_view" + }, + { + "command": "git.openChange", + "when": "scmProvider == git && scmResourceGroup == untracked", + "group": "navigation" + }, + { + "command": "git.openHEADFile", + "when": "scmProvider == git && scmResourceGroup == untracked", + "group": "navigation" + }, + { + "command": "git.openFile", + "when": "scmProvider == git && scmResourceGroup == untracked", + "group": "navigation" + }, + { + "command": "git.stage", + "when": "scmProvider == git && scmResourceGroup == untracked", + "group": "1_modification" + }, + { + "command": "git.clean", + "when": "scmProvider == git && scmResourceGroup == untracked && !gitFreshRepository", + "group": "1_modification" + }, + { + "command": "git.clean", + "when": "scmProvider == git && scmResourceGroup == untracked && !gitFreshRepository", + "group": "inline" + }, + { + "command": "git.stage", + "when": "scmProvider == git && scmResourceGroup == untracked", + "group": "inline" + }, + { + "command": "git.openFile2", + "when": "scmProvider == git && scmResourceGroup == untracked && config.git.showInlineOpenFileAction && config.git.openDiffOnClick", + "group": "inline0" + }, + { + "command": "git.openChange", + "when": "scmProvider == git && scmResourceGroup == untracked && config.git.showInlineOpenFileAction && !config.git.openDiffOnClick", + "group": "inline0" + }, + { + "command": "git.ignore", + "when": "scmProvider == git && scmResourceGroup == untracked", + "group": "1_modification@3" } ], "editor/title": [ { "command": "git.openFile", "group": "navigation", - "when": "config.git.enabled && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^git$|^file$/" + "when": "config.git.enabled && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^gitfs$|^file$/" }, { "command": "git.openChange", @@ -1047,44 +1204,44 @@ { "command": "git.stageSelectedRanges", "group": "2_git@1", - "when": "config.git.enabled && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^git$|^file$/" + "when": "config.git.enabled && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^gitfs$|^file$/" }, { "command": "git.unstageSelectedRanges", "group": "2_git@2", - "when": "config.git.enabled && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^git$|^file$/" + "when": "config.git.enabled && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^gitfs$|^file$/" }, { "command": "git.revertSelectedRanges", "group": "2_git@3", - "when": "config.git.enabled && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^git$|^file$/" + "when": "config.git.enabled && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^gitfs$|^file$/" } ], "editor/context": [ { "command": "git.stageSelectedRanges", "group": "2_git@1", - "when": "isInDiffRightEditor && config.git.enabled && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^git$|^file$/" + "when": "isInDiffRightEditor && config.git.enabled && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^gitfs$|^file$/" }, { "command": "git.unstageSelectedRanges", "group": "2_git@2", - "when": "isInDiffRightEditor && config.git.enabled && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^git$|^file$/" + "when": "isInDiffRightEditor && config.git.enabled && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^gitfs$|^file$/" }, { "command": "git.revertSelectedRanges", "group": "2_git@3", - "when": "isInDiffRightEditor && config.git.enabled && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^git$|^file$/" + "when": "isInDiffRightEditor && config.git.enabled && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^gitfs$|^file$/" } ], "scm/change/title": [ { "command": "git.stageChange", - "when": "originalResourceScheme == git" + "when": "originalResourceScheme == gitfs" }, { "command": "git.revertChange", - "when": "originalResourceScheme == git" + "when": "originalResourceScheme == gitfs" } ] }, @@ -1174,7 +1331,8 @@ "%config.countBadge.off%" ], "description": "%config.countBadge%", - "default": "all" + "default": "all", + "scope": "resource" }, "git.checkoutType": { "type": "string", @@ -1434,6 +1592,22 @@ ], "default": "committerdate", "description": "%config.branchSortOrder%" + }, + "git.untrackedChanges": { + "type": "string", + "enum": [ + "mixed", + "separate", + "hidden" + ], + "enumDescriptions": [ + "%config.untrackedChanges.mixed%", + "%config.untrackedChanges.separate%", + "%config.untrackedChanges.hidden%" + ], + "default": "mixed", + "description": "%config.untrackedChanges%", + "scope": "resource" } } }, @@ -1585,7 +1759,7 @@ "byline": "^5.0.0", "file-type": "^7.2.0", "iconv-lite": "^0.4.24", - "jschardet": "^1.6.0", + "jschardet": "2.1.1", "vscode-extension-telemetry": "0.1.1", "vscode-nls": "^4.0.0", "vscode-uri": "^2.0.0", @@ -1595,7 +1769,7 @@ "@types/byline": "4.2.31", "@types/file-type": "^5.2.1", "@types/mocha": "2.2.43", - "@types/node": "^10.14.8", + "@types/node": "^12.11.7", "@types/which": "^1.0.28", "mocha": "^3.2.0" } diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index 358f166058c0..5c357b40eb07 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -11,6 +11,8 @@ "command.openHEADFile": "Open File (HEAD)", "command.stage": "Stage Changes", "command.stageAll": "Stage All Changes", + "command.stageAllTracked": "Stage All Tracked Changes", + "command.stageAllUntracked": "Stage All Untracked Changes", "command.stageSelectedRanges": "Stage Selected Ranges", "command.revertSelectedRanges": "Revert Selected Ranges", "command.stageChange": "Stage Change", @@ -20,6 +22,8 @@ "command.unstageSelectedRanges": "Unstage Selected Ranges", "command.clean": "Discard Changes", "command.cleanAll": "Discard All Changes", + "command.cleanAllTracked": "Discard All Tracked Changes", + "command.cleanAllUntracked": "Discard All Untracked Changes", "command.commit": "Commit", "command.commitStaged": "Commit Staged", "command.commitEmpty": "Commit Empty", @@ -37,6 +41,7 @@ "command.renameBranch": "Rename Branch...", "command.merge": "Merge Branch...", "command.createTag": "Create Tag", + "command.deleteTag": "Delete Tag", "command.fetch": "Fetch", "command.fetchPrune": "Fetch (Prune)", "command.fetchAll": "Fetch From All Remotes", @@ -56,13 +61,14 @@ "command.publish": "Publish Branch...", "command.showOutput": "Show Git Output", "command.ignore": "Add to .gitignore", - "command.revealInExplorer": "Reveal in Explorer", + "command.revealInExplorer": "Reveal in Side Bar", "command.stashIncludeUntracked": "Stash (Include Untracked)", "command.stash": "Stash", "command.stashPop": "Pop Stash...", "command.stashPopLatest": "Pop Latest Stash", "command.stashApply": "Apply Stash...", "command.stashApplyLatest": "Apply Latest Stash", + "command.stashDrop": "Drop Stash...", "config.enabled": "Whether git is enabled.", "config.path": "Path and filename of the git executable, e.g. `C:\\Program Files\\Git\\bin\\git.exe` (Windows).", "config.autoRepositoryDetection": "Configures when repositories should be automatically detected.", @@ -119,7 +125,7 @@ "config.scanRepositories": "List of paths to search for git repositories in.", "config.showProgress": "Controls whether git actions should show progress.", "config.rebaseWhenSync": "Force git to use rebase when running the sync command.", - "config.confirmEmptyCommits": "Always confirm the creation of empty commits.", + "config.confirmEmptyCommits": "Always confirm the creation of empty commits for the 'Git: Commit Empty' command.", "config.fetchOnPull": "Fetch all branches when pulling or just the current one.", "config.pullTags": "Fetch all tags when pulling.", "config.autoStash": "Stash any changes before pulling and restore them after successful pull.", @@ -129,6 +135,10 @@ "config.openDiffOnClick": "Controls whether the diff editor should be opened when clicking a change. Otherwise the regular editor will be opened.", "config.supportCancellation": "Controls whether a notification comes up when running the Sync action, which allows the user to cancel the operation.", "config.branchSortOrder": "Controls the sort order for branches.", + "config.untrackedChanges": "Controls how untracked changes behave.", + "config.untrackedChanges.mixed": "All changes, tracked and untracked, appear together and behave equally.", + "config.untrackedChanges.separate": "Untracked changes appear separately in the Source Control view. They are also excluded from several actions.", + "config.untrackedChanges.hidden": "Untracked changes are hidden and excluded from several actions.", "colors.added": "Color for added resources.", "colors.modified": "Color for modified resources.", "colors.deleted": "Color for deleted resources.", diff --git a/extensions/git/src/api/api1.ts b/extensions/git/src/api/api1.ts index 6e47ea6eafb0..c51ae898c061 100644 --- a/extensions/git/src/api/api1.ts +++ b/extensions/git/src/api/api1.ts @@ -8,6 +8,7 @@ import { Repository as BaseRepository, Resource } from '../repository'; import { InputBox, Git, API, Repository, Remote, RepositoryState, Branch, Ref, Submodule, Commit, Change, RepositoryUIState, Status, LogOptions, APIState } from './git'; import { Event, SourceControlInputBox, Uri, SourceControl } from 'vscode'; import { mapEvent } from '../util'; +import { toGitUri } from '../uri'; class ApiInputBox implements InputBox { set value(value: string) { this._inputBox.value = value; } @@ -234,5 +235,9 @@ export class ApiImpl implements API { return this._model.repositories.map(r => new ApiRepository(r)); } + toGitUri(uri: Uri, ref: string): Uri { + return toGitUri(uri, ref); + } + constructor(private _model: Model) { } } diff --git a/extensions/git/src/api/git.d.ts b/extensions/git/src/api/git.d.ts index e8362f68c516..d0fb3b9134b4 100644 --- a/extensions/git/src/api/git.d.ts +++ b/extensions/git/src/api/git.d.ts @@ -185,6 +185,8 @@ export interface API { readonly repositories: Repository[]; readonly onDidOpenRepository: Event; readonly onDidCloseRepository: Event; + + toGitUri(uri: Uri, ref: string): Uri; } export interface GitExtension { diff --git a/extensions/git/src/askpass-main.ts b/extensions/git/src/askpass-main.ts index ec0b062ca56c..2da27b158668 100644 --- a/extensions/git/src/askpass-main.ts +++ b/extensions/git/src/askpass-main.ts @@ -3,9 +3,9 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as http from 'http'; import * as fs from 'fs'; import * as nls from 'vscode-nls'; +import { IPCClient } from './ipc/ipcClient'; const localize = nls.loadMessageBundle(); @@ -20,10 +20,6 @@ function main(argv: string[]): void { return fatal('Wrong number of arguments'); } - if (!process.env['VSCODE_GIT_ASKPASS_HANDLE']) { - return fatal('Missing handle'); - } - if (!process.env['VSCODE_GIT_ASKPASS_PIPE']) { return fatal('Missing pipe'); } @@ -33,40 +29,14 @@ function main(argv: string[]): void { } const output = process.env['VSCODE_GIT_ASKPASS_PIPE'] as string; - const socketPath = process.env['VSCODE_GIT_ASKPASS_HANDLE'] as string; const request = argv[2]; const host = argv[4].substring(1, argv[4].length - 2); - const opts: http.RequestOptions = { - socketPath, - path: '/', - method: 'POST' - }; - - const req = http.request(opts, res => { - if (res.statusCode !== 200) { - return fatal(`Bad status code: ${res.statusCode}`); - } - - const chunks: string[] = []; - res.setEncoding('utf8'); - res.on('data', (d: string) => chunks.push(d)); - res.on('end', () => { - const raw = chunks.join(''); - - try { - const result = JSON.parse(raw); - fs.writeFileSync(output, result + '\n'); - } catch (err) { - return fatal(`Error parsing response`); - } - - setTimeout(() => process.exit(0), 0); - }); - }); + const ipcClient = new IPCClient('askpass'); - req.on('error', () => fatal('Error in request')); - req.write(JSON.stringify({ request, host })); - req.end(); + ipcClient.call({ request, host }).then(res => { + fs.writeFileSync(output, res + '\n'); + setTimeout(() => process.exit(0), 0); + }).catch(err => fatal(err)); } main(process.argv); diff --git a/extensions/git/src/askpass.ts b/extensions/git/src/askpass.ts index 1ee9388c4461..14d7e7f26056 100644 --- a/extensions/git/src/askpass.ts +++ b/extensions/git/src/askpass.ts @@ -3,15 +3,10 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Disposable, window, InputBoxOptions } from 'vscode'; -import { denodeify } from './util'; +import { window, InputBoxOptions } from 'vscode'; +import { IDisposable } from './util'; import * as path from 'path'; -import * as http from 'http'; -import * as os from 'os'; -import * as fs from 'fs'; -import * as crypto from 'crypto'; - -const randomBytes = denodeify(crypto.randomBytes); +import { IIPCHandler, IIPCServer } from './ipc/ipcServer'; export interface AskpassEnvironment { GIT_ASKPASS: string; @@ -21,68 +16,21 @@ export interface AskpassEnvironment { VSCODE_GIT_ASKPASS_HANDLE?: string; } -function getIPCHandlePath(nonce: string): string { - if (process.platform === 'win32') { - return `\\\\.\\pipe\\vscode-git-askpass-${nonce}-sock`; - } +export class Askpass implements IIPCHandler { - if (process.env['XDG_RUNTIME_DIR']) { - return path.join(process.env['XDG_RUNTIME_DIR'] as string, `vscode-git-askpass-${nonce}.sock`); - } - - return path.join(os.tmpdir(), `vscode-git-askpass-${nonce}.sock`); -} - -export class Askpass implements Disposable { - - private server: http.Server; - private ipcHandlePathPromise: Promise; - private ipcHandlePath: string | undefined; - private enabled = true; - - constructor() { - this.server = http.createServer((req, res) => this.onRequest(req, res)); - this.ipcHandlePathPromise = this.setup().catch(err => { - console.error(err); - return ''; - }); - } + private disposable: IDisposable; - private async setup(): Promise { - const buffer = await randomBytes(20); - const nonce = buffer.toString('hex'); - const ipcHandlePath = getIPCHandlePath(nonce); - this.ipcHandlePath = ipcHandlePath; - - try { - this.server.listen(ipcHandlePath); - this.server.on('error', err => console.error(err)); - } catch (err) { - console.error('Could not launch git askpass helper.'); - this.enabled = false; - } - - return ipcHandlePath; + static getDisabledEnv(): AskpassEnvironment { + return { + GIT_ASKPASS: path.join(__dirname, 'askpass-empty.sh') + }; } - private onRequest(req: http.IncomingMessage, res: http.ServerResponse): void { - const chunks: string[] = []; - req.setEncoding('utf8'); - req.on('data', (d: string) => chunks.push(d)); - req.on('end', () => { - const { request, host } = JSON.parse(chunks.join('')); - - this.prompt(host, request).then(result => { - res.writeHead(200); - res.end(JSON.stringify(result)); - }, () => { - res.writeHead(500); - res.end(); - }); - }); + constructor(ipc: IIPCServer) { + this.disposable = ipc.registerHandler('askpass', this); } - private async prompt(host: string, request: string): Promise { + async handle({ request, host }: { request: string, host: string }): Promise { const options: InputBoxOptions = { password: /password/i.test(request), placeHolder: request, @@ -93,27 +41,16 @@ export class Askpass implements Disposable { return await window.showInputBox(options) || ''; } - async getEnv(): Promise { - if (!this.enabled) { - return { - GIT_ASKPASS: path.join(__dirname, 'askpass-empty.sh') - }; - } - + getEnv(): AskpassEnvironment { return { ELECTRON_RUN_AS_NODE: '1', GIT_ASKPASS: path.join(__dirname, 'askpass.sh'), VSCODE_GIT_ASKPASS_NODE: process.execPath, - VSCODE_GIT_ASKPASS_MAIN: path.join(__dirname, 'askpass-main.js'), - VSCODE_GIT_ASKPASS_HANDLE: await this.ipcHandlePathPromise + VSCODE_GIT_ASKPASS_MAIN: path.join(__dirname, 'askpass-main.js') }; } dispose(): void { - this.server.close(); - - if (this.ipcHandlePath && process.platform !== 'win32') { - fs.unlinkSync(this.ipcHandlePath); - } + this.disposable.dispose(); } -} \ No newline at end of file +} diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 0db1b8a02230..e43affda740b 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -3,19 +3,19 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Uri, commands, Disposable, window, workspace, QuickPickItem, OutputChannel, Range, WorkspaceEdit, Position, LineChange, SourceControlResourceState, TextDocumentShowOptions, ViewColumn, ProgressLocation, TextEditor, MessageOptions, WorkspaceFolder } from 'vscode'; -import { Git, CommitOptions, Stash, ForcePushMode } from './git'; -import { Repository, Resource, ResourceGroupType } from './repository'; -import { Model } from './model'; -import { toGitUri, fromGitUri } from './uri'; -import { grep, isDescendant, pathEquals } from './util'; -import { applyLineChanges, intersectDiffWithRange, toLineRanges, invertLineChange, getModifiedRange } from './staging'; -import * as path from 'path'; import { lstat, Stats } from 'fs'; import * as os from 'os'; +import * as path from 'path'; +import { commands, Disposable, LineChange, MessageOptions, OutputChannel, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder } from 'vscode'; import TelemetryReporter from 'vscode-extension-telemetry'; import * as nls from 'vscode-nls'; -import { Ref, RefType, Branch, GitErrorCodes, Status } from './api/git'; +import { Branch, GitErrorCodes, Ref, RefType, Status } from './api/git'; +import { CommitOptions, ForcePushMode, Git, Stash } from './git'; +import { Model } from './model'; +import { Repository, Resource, ResourceGroupType } from './repository'; +import { applyLineChanges, getModifiedRange, intersectDiffWithRange, invertLineChange, toLineRanges } from './staging'; +import { fromGitUri, toGitUri, isGitUri } from './uri'; +import { grep, isDescendant, pathEquals } from './util'; const localize = nls.loadMessageBundle(); @@ -99,7 +99,7 @@ class CreateBranchItem implements QuickPickItem { constructor(private cc: CommandCenter) { } - get label(): string { return localize('create branch', '$(plus) Create new branch...'); } + get label(): string { return '$(plus) ' + localize('create branch', 'Create new branch...'); } get description(): string { return ''; } get alwaysShow(): boolean { return true; } @@ -113,7 +113,7 @@ class CreateBranchFromItem implements QuickPickItem { constructor(private cc: CommandCenter) { } - get label(): string { return localize('create branch from', '$(plus) Create new branch from...'); } + get label(): string { return '$(plus) ' + localize('create branch from', 'Create new branch from...'); } get description(): string { return ''; } get alwaysShow(): boolean { return true; } @@ -136,7 +136,7 @@ class AddRemoteItem implements QuickPickItem { constructor(private cc: CommandCenter) { } - get label(): string { return localize('add remote', '$(plus) Add a new remote...'); } + get label(): string { return '$(plus) ' + localize('add remote', 'Add a new remote...'); } get description(): string { return ''; } get alwaysShow(): boolean { return true; } @@ -170,14 +170,14 @@ function command(commandId: string, options: CommandOptions = {}): Function { }; } -const ImageMimetypes = [ - 'image/png', - 'image/gif', - 'image/jpeg', - 'image/webp', - 'image/tiff', - 'image/bmp' -]; +// const ImageMimetypes = [ +// 'image/png', +// 'image/gif', +// 'image/jpeg', +// 'image/webp', +// 'image/tiff', +// 'image/bmp' +// ]; async function categorizeResourceByResolution(resources: Resource[]): Promise<{ merge: Resource[], resolved: Resource[], unresolved: Resource[], deletionConflicts: Resource[] }> { const selection = resources.filter(s => s instanceof Resource) as Resource[]; @@ -213,6 +213,12 @@ function createCheckoutItems(repository: Repository): CheckoutItem[] { return [...heads, ...tags, ...remoteHeads]; } +class TagItem implements QuickPickItem { + get label(): string { return this.ref.name ?? ''; } + get description(): string { return this.ref.commit?.substr(0, 8) ?? ''; } + constructor(readonly ref: Ref) { } +} + enum PushType { Push, PushTo, @@ -289,10 +295,10 @@ export class CommandCenter { } } else { if (resource.type !== Status.DELETED_BY_THEM) { - left = await this.getLeftResource(resource); + left = this.getLeftResource(resource); } - right = await this.getRightResource(resource); + right = this.getRightResource(resource); } const title = this.getTitle(resource); @@ -324,79 +330,40 @@ export class CommandCenter { } } - private async getURI(uri: Uri, ref: string): Promise { - const repository = this.model.getRepository(uri); - - if (!repository) { - return toGitUri(uri, ref); - } - - try { - let gitRef = ref; - - if (gitRef === '~') { - const uriString = uri.toString(); - const [indexStatus] = repository.indexGroup.resourceStates.filter(r => r.resourceUri.toString() === uriString); - gitRef = indexStatus ? '' : 'HEAD'; - } - - const { size, object } = await repository.getObjectDetails(gitRef, uri.fsPath); - const { mimetype } = await repository.detectObjectType(object); - - if (mimetype === 'text/plain') { - return toGitUri(uri, ref); - } - - if (size > 1000000) { // 1 MB - return Uri.parse(`data:;label:${path.basename(uri.fsPath)};description:${gitRef},`); - } - - if (ImageMimetypes.indexOf(mimetype) > -1) { - const contents = await repository.buffer(gitRef, uri.fsPath); - return Uri.parse(`data:${mimetype};label:${path.basename(uri.fsPath)};description:${gitRef};size:${size};base64,${contents.toString('base64')}`); - } - - return Uri.parse(`data:;label:${path.basename(uri.fsPath)};description:${gitRef},`); - - } catch (err) { - return toGitUri(uri, ref); - } - } - - private async getLeftResource(resource: Resource): Promise { + private getLeftResource(resource: Resource): Uri | undefined { switch (resource.type) { case Status.INDEX_MODIFIED: case Status.INDEX_RENAMED: case Status.INDEX_ADDED: - return this.getURI(resource.original, 'HEAD'); + return toGitUri(resource.original, 'HEAD'); case Status.MODIFIED: case Status.UNTRACKED: - return this.getURI(resource.resourceUri, '~'); + return toGitUri(resource.resourceUri, '~'); case Status.DELETED_BY_THEM: - return this.getURI(resource.resourceUri, ''); + return toGitUri(resource.resourceUri, ''); } return undefined; } - private async getRightResource(resource: Resource): Promise { + private getRightResource(resource: Resource): Uri | undefined { switch (resource.type) { case Status.INDEX_MODIFIED: case Status.INDEX_ADDED: case Status.INDEX_COPIED: case Status.INDEX_RENAMED: - return this.getURI(resource.resourceUri, ''); + return toGitUri(resource.resourceUri, ''); case Status.INDEX_DELETED: case Status.DELETED: - return this.getURI(resource.resourceUri, 'HEAD'); + return toGitUri(resource.resourceUri, 'HEAD'); case Status.DELETED_BY_US: - return this.getURI(resource.resourceUri, '~3'); + return toGitUri(resource.resourceUri, '~3'); case Status.DELETED_BY_THEM: - return this.getURI(resource.resourceUri, '~2'); + return toGitUri(resource.resourceUri, '~2'); case Status.MODIFIED: case Status.UNTRACKED: @@ -453,7 +420,7 @@ export class CommandCenter { } @command('git.clone') - async clone(url?: string): Promise { + async clone(url?: string, parentPath?: string): Promise { if (!url) { url = await window.showInputBox({ prompt: localize('repourl', "Repository URL"), @@ -473,30 +440,32 @@ export class CommandCenter { url = url.trim().replace(/^git\s+clone\s+/, ''); - const config = workspace.getConfiguration('git'); - let defaultCloneDirectory = config.get('defaultCloneDirectory') || os.homedir(); - defaultCloneDirectory = defaultCloneDirectory.replace(/^~/, os.homedir()); - - const uris = await window.showOpenDialog({ - canSelectFiles: false, - canSelectFolders: true, - canSelectMany: false, - defaultUri: Uri.file(defaultCloneDirectory), - openLabel: localize('selectFolder', "Select Repository Location") - }); + if (!parentPath) { + const config = workspace.getConfiguration('git'); + let defaultCloneDirectory = config.get('defaultCloneDirectory') || os.homedir(); + defaultCloneDirectory = defaultCloneDirectory.replace(/^~/, os.homedir()); - if (!uris || uris.length === 0) { - /* __GDPR__ - "clone" : { - "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } - } - */ - this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'no_directory' }); - return; - } + const uris = await window.showOpenDialog({ + canSelectFiles: false, + canSelectFolders: true, + canSelectMany: false, + defaultUri: Uri.file(defaultCloneDirectory), + openLabel: localize('selectFolder', "Select Repository Location") + }); - const uri = uris[0]; - const parentPath = uri.fsPath; + if (!uris || uris.length === 0) { + /* __GDPR__ + "clone" : { + "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + } + */ + this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'no_directory' }); + return; + } + + const uri = uris[0]; + parentPath = uri.fsPath; + } try { const opts = { @@ -507,7 +476,7 @@ export class CommandCenter { const repositoryPath = await window.withProgress( opts, - (progress, token) => this.git.clone(url!, parentPath, progress, token) + (progress, token) => this.git.clone(url!, parentPath!, progress, token) ); let message = localize('proposeopen', "Would you like to open the cloned repository?"); @@ -686,7 +655,7 @@ export class CommandCenter { let uris: Uri[] | undefined; if (arg instanceof Uri) { - if (arg.scheme === 'git') { + if (isGitUri(arg)) { uris = [Uri.file(fromGitUri(arg).path)]; } else if (arg.scheme === 'file') { uris = [arg]; @@ -765,7 +734,7 @@ export class CommandCenter { return; } - const HEAD = await this.getLeftResource(resource); + const HEAD = this.getLeftResource(resource); const basename = path.basename(resource.resourceUri.fsPath); const title = `${basename} (HEAD)`; @@ -866,7 +835,8 @@ export class CommandCenter { } const workingTree = selection.filter(s => s.resourceGroupType === ResourceGroupType.WorkingTree); - const scmResources = [...workingTree, ...resolved, ...unresolved]; + const untracked = selection.filter(s => s.resourceGroupType === ResourceGroupType.Untracked); + const scmResources = [...workingTree, ...untracked, ...resolved, ...unresolved]; this.outputChannel.appendLine(`git.stage.scmResources ${scmResources.length}`); if (!scmResources.length) { @@ -907,7 +877,9 @@ export class CommandCenter { } } - await repository.add([]); + const config = workspace.getConfiguration('git', Uri.file(repository.root)); + const untrackedChanges = config.get<'mixed' | 'separate' | 'hidden'>('untrackedChanges'); + await repository.add([], untrackedChanges === 'mixed' ? undefined : { update: true }); } private async _stageDeletionConflict(repository: Repository, uri: Uri): Promise { @@ -945,6 +917,24 @@ export class CommandCenter { } } + @command('git.stageAllTracked', { repository: true }) + async stageAllTracked(repository: Repository): Promise { + const resources = repository.workingTreeGroup.resourceStates + .filter(r => r.type !== Status.UNTRACKED && r.type !== Status.IGNORED); + const uris = resources.map(r => r.resourceUri); + + await repository.add(uris); + } + + @command('git.stageAllUntracked', { repository: true }) + async stageAllUntracked(repository: Repository): Promise { + const resources = [...repository.workingTreeGroup.resourceStates, ...repository.untrackedGroup.resourceStates] + .filter(r => r.type === Status.UNTRACKED || r.type === Status.IGNORED); + const uris = resources.map(r => r.resourceUri); + + await repository.add(uris); + } + @command('git.stageChange') async stageChange(uri: Uri, changes: LineChange[], index: number): Promise { const textEditor = window.visibleTextEditors.filter(e => e.document.uri.toString() === uri.toString())[0]; @@ -1090,7 +1080,7 @@ export class CommandCenter { const modifiedDocument = textEditor.document; const modifiedUri = modifiedDocument.uri; - if (modifiedUri.scheme !== 'git') { + if (!isGitUri(modifiedUri)) { return; } @@ -1131,8 +1121,8 @@ export class CommandCenter { resourceStates = [resource]; } - const scmResources = resourceStates - .filter(s => s instanceof Resource && s.resourceGroupType === ResourceGroupType.WorkingTree) as Resource[]; + const scmResources = resourceStates.filter(s => s instanceof Resource + && (s.resourceGroupType === ResourceGroupType.WorkingTree || s.resourceGroupType === ResourceGroupType.Untracked)) as Resource[]; if (!scmResources.length) { return; @@ -1189,41 +1179,11 @@ export class CommandCenter { const untrackedResources = resources.filter(r => r.type === Status.UNTRACKED || r.type === Status.IGNORED); if (untrackedResources.length === 0) { - const message = resources.length === 1 - ? localize('confirm discard all single', "Are you sure you want to discard changes in {0}?", path.basename(resources[0].resourceUri.fsPath)) - : localize('confirm discard all', "Are you sure you want to discard ALL changes in {0} files?\nThis is IRREVERSIBLE!\nYour current working set will be FOREVER LOST.", resources.length); - const yes = resources.length === 1 - ? localize('discardAll multiple', "Discard 1 File") - : localize('discardAll', "Discard All {0} Files", resources.length); - const pick = await window.showWarningMessage(message, { modal: true }, yes); - - if (pick !== yes) { - return; - } - - await repository.clean(resources.map(r => r.resourceUri)); - return; + await this._cleanTrackedChanges(repository, resources); } else if (resources.length === 1) { - const message = localize('confirm delete', "Are you sure you want to DELETE {0}?\nThis is IRREVERSIBLE!\nThis file will be FOREVER LOST.", path.basename(resources[0].resourceUri.fsPath)); - const yes = localize('delete file', "Delete file"); - const pick = await window.showWarningMessage(message, { modal: true }, yes); - - if (pick !== yes) { - return; - } - - await repository.clean(resources.map(r => r.resourceUri)); + await this._cleanUntrackedChange(repository, resources[0]); } else if (trackedResources.length === 0) { - const message = localize('confirm delete multiple', "Are you sure you want to DELETE {0} files?", resources.length); - const yes = localize('delete files', "Delete Files"); - const pick = await window.showWarningMessage(message, { modal: true }, yes); - - if (pick !== yes) { - return; - } - - await repository.clean(resources.map(r => r.resourceUri)); - + await this._cleanUntrackedChanges(repository, resources); } else { // resources.length > 1 && untrackedResources.length > 0 && trackedResources.length > 0 const untrackedMessage = untrackedResources.length === 1 ? localize('there are untracked files single', "The following untracked file will be DELETED FROM DISK if discarded: {0}.", path.basename(untrackedResources[0].resourceUri.fsPath)) @@ -1248,6 +1208,74 @@ export class CommandCenter { } } + @command('git.cleanAllTracked', { repository: true }) + async cleanAllTracked(repository: Repository): Promise { + const resources = repository.workingTreeGroup.resourceStates + .filter(r => r.type !== Status.UNTRACKED && r.type !== Status.IGNORED); + + if (resources.length === 0) { + return; + } + + await this._cleanTrackedChanges(repository, resources); + } + + @command('git.cleanAllUntracked', { repository: true }) + async cleanAllUntracked(repository: Repository): Promise { + const resources = [...repository.workingTreeGroup.resourceStates, ...repository.untrackedGroup.resourceStates] + .filter(r => r.type === Status.UNTRACKED || r.type === Status.IGNORED); + + if (resources.length === 0) { + return; + } + + if (resources.length === 1) { + await this._cleanUntrackedChange(repository, resources[0]); + } else { + await this._cleanUntrackedChanges(repository, resources); + } + } + + private async _cleanTrackedChanges(repository: Repository, resources: Resource[]): Promise { + const message = resources.length === 1 + ? localize('confirm discard all single', "Are you sure you want to discard changes in {0}?", path.basename(resources[0].resourceUri.fsPath)) + : localize('confirm discard all', "Are you sure you want to discard ALL changes in {0} files?\nThis is IRREVERSIBLE!\nYour current working set will be FOREVER LOST.", resources.length); + const yes = resources.length === 1 + ? localize('discardAll multiple', "Discard 1 File") + : localize('discardAll', "Discard All {0} Files", resources.length); + const pick = await window.showWarningMessage(message, { modal: true }, yes); + + if (pick !== yes) { + return; + } + + await repository.clean(resources.map(r => r.resourceUri)); + } + + private async _cleanUntrackedChange(repository: Repository, resource: Resource): Promise { + const message = localize('confirm delete', "Are you sure you want to DELETE {0}?\nThis is IRREVERSIBLE!\nThis file will be FOREVER LOST.", path.basename(resource.resourceUri.fsPath)); + const yes = localize('delete file', "Delete file"); + const pick = await window.showWarningMessage(message, { modal: true }, yes); + + if (pick !== yes) { + return; + } + + await repository.clean([resource.resourceUri]); + } + + private async _cleanUntrackedChanges(repository: Repository, resources: Resource[]): Promise { + const message = localize('confirm delete multiple', "Are you sure you want to DELETE {0} files?", resources.length); + const yes = localize('delete files', "Delete Files"); + const pick = await window.showWarningMessage(message, { modal: true }, yes); + + if (pick !== yes) { + return; + } + + await repository.clean(resources.map(r => r.resourceUri)); + } + private async smartCommit( repository: Repository, getCommitMessage: () => Promise, @@ -1271,7 +1299,7 @@ export class CommandCenter { if (promptToSaveFilesBeforeCommit === 'staged' || repository.indexGroup.resourceStates.length > 0) { documents = documents - .filter(d => repository.indexGroup.resourceStates.some(s => s.resourceUri.path === d.uri.fsPath)); + .filter(d => repository.indexGroup.resourceStates.some(s => pathEquals(s.resourceUri.fsPath, d.uri.fsPath))); } if (documents.length > 0) { @@ -1356,6 +1384,10 @@ export class CommandCenter { opts.all = 'tracked'; } + if (opts.all && config.get<'mixed' | 'separate' | 'hidden'>('untrackedChanges') !== 'mixed') { + opts.all = 'tracked'; + } + await repository.commit(message, opts); const postCommitCommand = config.get<'none' | 'push' | 'sync'>('postCommitCommand'); @@ -1376,6 +1408,7 @@ export class CommandCenter { const message = repository.inputBox.value; const getCommitMessage = async () => { let _message: string | undefined = message; + if (!_message) { let value: string | undefined = undefined; @@ -1400,7 +1433,7 @@ export class CommandCenter { }); } - return _message ? repository.cleanUpCommitEditMessage(_message) : _message; + return _message; }; const didCommit = await this.smartCommit(repository, getCommitMessage, opts); @@ -1485,7 +1518,7 @@ export class CommandCenter { if (commit.parents.length > 1) { const yes = localize('undo commit', "Undo merge commit"); - const result = await window.showWarningMessage(localize('merge commit', "The last commit was a merge commit. Are you sure you want to undo it?"), yes); + const result = await window.showWarningMessage(localize('merge commit', "The last commit was a merge commit. Are you sure you want to undo it?"), { modal: true }, yes); if (result !== yes) { return; @@ -1705,6 +1738,26 @@ export class CommandCenter { await repository.tag(name, message); } + @command('git.deleteTag', { repository: true }) + async deleteTag(repository: Repository): Promise { + const picks = repository.refs.filter(ref => ref.type === RefType.Tag) + .map(ref => new TagItem(ref)); + + if (picks.length === 0) { + window.showWarningMessage(localize('no tags', "This repository has no tags.")); + return; + } + + const placeHolder = localize('select a tag to delete', 'Select a tag to delete'); + const choice = await window.showQuickPick(picks, { placeHolder }); + + if (!choice) { + return; + } + + await repository.deleteTag(choice.label); + } + @command('git.fetch', { repository: true }) async fetch(repository: Repository): Promise { if (repository.remotes.length === 0) { @@ -2073,9 +2126,14 @@ export class CommandCenter { return; } + const branchName = repository.HEAD && repository.HEAD.name || ''; + + if (remotes.length === 1) { + return await repository.pushTo(remotes[0].name, branchName, true); + } + const addRemote = new AddRemoteItem(this); const picks = [...repository.remotes.map(r => ({ label: r.name, description: r.pushUrl })), addRemote]; - const branchName = repository.HEAD && repository.HEAD.name || ''; const placeHolder = localize('pick remote', "Pick a remote to publish the branch '{0}' to:", branchName); const choice = await window.showQuickPick(picks, { placeHolder }); @@ -2133,7 +2191,8 @@ export class CommandCenter { } private async _stash(repository: Repository, includeUntracked = false): Promise { - const noUnstagedChanges = repository.workingTreeGroup.resourceStates.length === 0; + const noUnstagedChanges = repository.workingTreeGroup.resourceStates.length === 0 + && (!includeUntracked || repository.untrackedGroup.resourceStates.length === 0); const noStagedChanges = repository.indexGroup.resourceStates.length === 0; if (noUnstagedChanges && noStagedChanges) { @@ -2215,6 +2274,18 @@ export class CommandCenter { await repository.applyStash(); } + @command('git.stashDrop', { repository: true }) + async stashDrop(repository: Repository): Promise { + const placeHolder = localize('pick stash to drop', "Pick a stash to drop"); + const stash = await this.pickStash(repository, placeHolder); + + if (!stash) { + return; + } + + await repository.dropStash(stash.index); + } + private async pickStash(repository: Repository, placeHolder: string): Promise { const stashes = await repository.getStashes(); @@ -2352,7 +2423,7 @@ export class CommandCenter { return undefined; } - if (uri.scheme === 'git') { + if (isGitUri(uri)) { const { path } = fromGitUri(uri); uri = Uri.file(path); } diff --git a/extensions/git/src/contentProvider.ts b/extensions/git/src/contentProvider.ts index b94847eec26c..1f394f9cd943 100644 --- a/extensions/git/src/contentProvider.ts +++ b/extensions/git/src/contentProvider.ts @@ -147,4 +147,4 @@ export class GitContentProvider { dispose(): void { this.disposables.forEach(d => d.dispose()); } -} \ No newline at end of file +} diff --git a/extensions/git/src/decorationProvider.ts b/extensions/git/src/decorationProvider.ts index 1f0a02567ae0..d3ee8bb27be4 100644 --- a/extensions/git/src/decorationProvider.ts +++ b/extensions/git/src/decorationProvider.ts @@ -113,6 +113,7 @@ class GitDecorationProvider implements DecorationProvider { this.collectSubmoduleDecorationData(newDecorations); this.collectDecorationData(this.repository.indexGroup, newDecorations); + this.collectDecorationData(this.repository.untrackedGroup, newDecorations); this.collectDecorationData(this.repository.workingTreeGroup, newDecorations); this.collectDecorationData(this.repository.mergeGroup, newDecorations); diff --git a/extensions/git/src/encoding.ts b/extensions/git/src/encoding.ts index 48df276e43b5..424f5312bbff 100644 --- a/extensions/git/src/encoding.ts +++ b/extensions/git/src/encoding.ts @@ -5,8 +5,6 @@ import * as jschardet from 'jschardet'; -jschardet.Constants.MINIMUM_THRESHOLD = 0.2; - function detectEncodingByBOM(buffer: Buffer): string | null { if (!buffer || buffer.length < 2) { return null; diff --git a/extensions/git/src/fileSystemProvider.ts b/extensions/git/src/fileSystemProvider.ts new file mode 100644 index 000000000000..3ea7558b9ad6 --- /dev/null +++ b/extensions/git/src/fileSystemProvider.ts @@ -0,0 +1,199 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { workspace, Uri, Disposable, Event, EventEmitter, window, FileSystemProvider, FileChangeEvent, FileStat, FileType, FileChangeType, FileSystemError } from 'vscode'; +import { debounce, throttle } from './decorators'; +import { fromGitUri, toGitUri } from './uri'; +import { Model, ModelChangeEvent, OriginalResourceChangeEvent } from './model'; +import { filterEvent, eventToPromise, isDescendant, pathEquals, EmptyDisposable } from './util'; + +interface CacheRow { + uri: Uri; + timestamp: number; +} + +const THREE_MINUTES = 1000 * 60 * 3; +const FIVE_MINUTES = 1000 * 60 * 5; + +export class GitFileSystemProvider implements FileSystemProvider { + + private _onDidChangeFile = new EventEmitter(); + readonly onDidChangeFile: Event = this._onDidChangeFile.event; + + private changedRepositoryRoots = new Set(); + private cache = new Map(); + private mtime = new Date().getTime(); + private disposables: Disposable[] = []; + + constructor(private model: Model) { + this.disposables.push( + model.onDidChangeRepository(this.onDidChangeRepository, this), + model.onDidChangeOriginalResource(this.onDidChangeOriginalResource, this), + workspace.registerFileSystemProvider('gitfs', this, { isReadonly: true, isCaseSensitive: true }), + workspace.registerResourceLabelFormatter({ + scheme: 'gitfs', + formatting: { + label: '${path} (git)', + separator: '/' + } + }) + ); + + setInterval(() => this.cleanup(), FIVE_MINUTES); + } + + private onDidChangeRepository({ repository }: ModelChangeEvent): void { + this.changedRepositoryRoots.add(repository.root); + this.eventuallyFireChangeEvents(); + } + + private onDidChangeOriginalResource({ uri }: OriginalResourceChangeEvent): void { + if (uri.scheme !== 'file') { + return; + } + + const gitUri = toGitUri(uri, '', { replaceFileExtension: true }); + this.mtime = new Date().getTime(); + this._onDidChangeFile.fire([{ type: FileChangeType.Changed, uri: gitUri }]); + } + + @debounce(1100) + private eventuallyFireChangeEvents(): void { + this.fireChangeEvents(); + } + + @throttle + private async fireChangeEvents(): Promise { + if (!window.state.focused) { + const onDidFocusWindow = filterEvent(window.onDidChangeWindowState, e => e.focused); + await eventToPromise(onDidFocusWindow); + } + + const events: FileChangeEvent[] = []; + + for (const { uri } of this.cache.values()) { + const fsPath = uri.fsPath; + + for (const root of this.changedRepositoryRoots) { + if (isDescendant(root, fsPath)) { + events.push({ type: FileChangeType.Changed, uri }); + break; + } + } + } + + if (events.length > 0) { + this.mtime = new Date().getTime(); + this._onDidChangeFile.fire(events); + } + + this.changedRepositoryRoots.clear(); + } + + private cleanup(): void { + const now = new Date().getTime(); + const cache = new Map(); + + for (const row of this.cache.values()) { + const { path } = fromGitUri(row.uri); + const isOpen = workspace.textDocuments + .filter(d => d.uri.scheme === 'file') + .some(d => pathEquals(d.uri.fsPath, path)); + + if (isOpen || now - row.timestamp < THREE_MINUTES) { + cache.set(row.uri.toString(), row); + } else { + // TODO: should fire delete events? + } + } + + this.cache = cache; + } + + watch(): Disposable { + return EmptyDisposable; + } + + stat(uri: Uri): FileStat { + const { submoduleOf } = fromGitUri(uri); + const repository = submoduleOf ? this.model.getRepository(submoduleOf) : this.model.getRepository(uri); + + if (!repository) { + throw FileSystemError.FileNotFound(); + } + + return { type: FileType.File, size: 0, mtime: this.mtime, ctime: 0 }; + } + + readDirectory(): Thenable<[string, FileType][]> { + throw new Error('Method not implemented.'); + } + + createDirectory(): void { + throw new Error('Method not implemented.'); + } + + async readFile(uri: Uri): Promise { + let { path, ref, submoduleOf } = fromGitUri(uri); + + if (submoduleOf) { + const repository = this.model.getRepository(submoduleOf); + + if (!repository) { + throw FileSystemError.FileNotFound(); + } + + const encoder = new TextEncoder(); + + if (ref === 'index') { + return encoder.encode(await repository.diffIndexWithHEAD(path)); + } else { + return encoder.encode(await repository.diffWithHEAD(path)); + } + } + + const repository = this.model.getRepository(uri); + + if (!repository) { + throw FileSystemError.FileNotFound(); + } + + const timestamp = new Date().getTime(); + const cacheValue: CacheRow = { uri, timestamp }; + + this.cache.set(uri.toString(), cacheValue); + + if (ref === '~') { + const fileUri = Uri.file(path); + const uriString = fileUri.toString(); + const [indexStatus] = repository.indexGroup.resourceStates.filter(r => r.resourceUri.toString() === uriString); + ref = indexStatus ? '' : 'HEAD'; + } else if (/^~\d$/.test(ref)) { + ref = `:${ref[1]}`; + } + + try { + return await repository.buffer(ref, path); + } catch (err) { + return new Uint8Array(0); + } + } + + writeFile(): void { + throw new Error('Method not implemented.'); + } + + delete(): void { + throw new Error('Method not implemented.'); + } + + rename(): void { + throw new Error('Method not implemented.'); + } + + dispose(): void { + this.disposables.forEach(d => d.dispose()); + } +} diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index ca90587eaa63..6be990e828b8 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as fs from 'fs'; +import { promises as fs, exists } from 'fs'; import * as path from 'path'; import * as os from 'os'; import * as cp from 'child_process'; @@ -11,7 +11,7 @@ import * as which from 'which'; import { EventEmitter } from 'events'; import iconv = require('iconv-lite'); import * as filetype from 'file-type'; -import { assign, groupBy, denodeify, IDisposable, toDisposable, dispose, mkdirp, readBytes, detectUnicodeEncoding, Encoding, onceEvent, splitInChunks, Limiter } from './util'; +import { assign, groupBy, IDisposable, toDisposable, dispose, mkdirp, readBytes, detectUnicodeEncoding, Encoding, onceEvent, splitInChunks, Limiter } from './util'; import { CancellationToken, Progress } from 'vscode'; import { URI } from 'vscode-uri'; import { detectEncoding } from './encoding'; @@ -22,8 +22,6 @@ import { StringDecoder } from 'string_decoder'; // https://github.com/microsoft/vscode/issues/65693 const MAX_CLI_LENGTH = 30000; -const readfile = denodeify(fs.readFile); - export interface IGit { path: string; version: string; @@ -196,13 +194,13 @@ async function exec(child: cp.ChildProcess, cancellationToken?: CancellationToke }), new Promise(c => { const buffers: Buffer[] = []; - on(child.stdout, 'data', (b: Buffer) => buffers.push(b)); - once(child.stdout, 'close', () => c(Buffer.concat(buffers))); + on(child.stdout!, 'data', (b: Buffer) => buffers.push(b)); + once(child.stdout!, 'close', () => c(Buffer.concat(buffers))); }), new Promise(c => { const buffers: Buffer[] = []; - on(child.stderr, 'data', (b: Buffer) => buffers.push(b)); - once(child.stderr, 'close', () => c(Buffer.concat(buffers).toString('utf8'))); + on(child.stderr!, 'data', (b: Buffer) => buffers.push(b)); + once(child.stderr!, 'close', () => c(Buffer.concat(buffers).toString('utf8'))); }) ]) as Promise<[number, Buffer, string]>; @@ -350,7 +348,7 @@ export class Git { let folderPath = path.join(parentPath, folderName); let count = 1; - while (count < 20 && await new Promise(c => fs.exists(folderPath, c))) { + while (count < 20 && await new Promise(c => exists(folderPath, c))) { folderName = `${baseFolderName}-${count++}`; folderPath = path.join(parentPath, folderName); } @@ -360,7 +358,7 @@ export class Git { const onSpawn = (child: cp.ChildProcess) => { const decoder = new StringDecoder('utf8'); const lineStream = new byline.LineStream({ encoding: 'utf8' }); - child.stderr.on('data', (buffer: Buffer) => lineStream.write(decoder.write(buffer))); + child.stderr!.on('data', (buffer: Buffer) => lineStream.write(decoder.write(buffer))); let totalProgress = 0; let previousProgress = 0; @@ -438,7 +436,7 @@ export class Git { } if (options.input) { - child.stdin.end(options.input, 'utf8'); + child.stdin!.end(options.input, 'utf8'); } const bufferResult = await exec(child, options.cancellationToken); @@ -763,9 +761,10 @@ export class Repository { async log(options?: LogOptions): Promise { const maxEntries = options && typeof options.maxEntries === 'number' && options.maxEntries > 0 ? options.maxEntries : 32; const args = ['log', '-' + maxEntries, `--pretty=format:${COMMIT_FORMAT}%x00%x00`]; + const gitResult = await this.run(args); if (gitResult.exitCode) { - // An empty repo. + // An empty repo return []; } @@ -882,7 +881,7 @@ export class Repository { async detectObjectType(object: string): Promise<{ mimetype: string, encoding?: string }> { const child = await this.stream(['show', object]); - const buffer = await readBytes(child.stdout, 4100); + const buffer = await readBytes(child.stdout!, 4100); try { child.kill(); @@ -1151,7 +1150,7 @@ export class Repository { async stage(path: string, data: string): Promise { const child = this.stream(['hash-object', '--stdin', '-w', '--path', path], { stdio: [null, null, null] }); - child.stdin.end(data, 'utf8'); + child.stdin!.end(data, 'utf8'); const { exitCode, stdout } = await exec(child); const hash = stdout.toString('utf8'); @@ -1163,11 +1162,12 @@ export class Repository { }); } + const treeish = await this.getCommit('HEAD').then(() => 'HEAD', () => ''); let mode: string; let add: string = ''; try { - const details = await this.getObjectDetails('HEAD', path); + const details = await this.getObjectDetails(treeish, path); mode = details.mode; } catch (err) { if (err.gitErrorCode !== GitErrorCodes.UnknownPath) { @@ -1327,6 +1327,11 @@ export class Repository { await this.run(args); } + async deleteTag(name: string): Promise { + let args = ['tag', '-d', name]; + await this.run(args); + } + async clean(paths: string[]): Promise { const pathsByGroup = groupBy(paths, p => path.dirname(p)); const groups = Object.keys(pathsByGroup).map(k => pathsByGroup[k]); @@ -1592,6 +1597,24 @@ export class Repository { } } + async dropStash(index?: number): Promise { + const args = ['stash', 'drop']; + + if (typeof index === 'number') { + args.push(`stash@{${index}}`); + } + + try { + await this.run(args); + } catch (err) { + if (/No stash found/.test(err.stderr || '')) { + err.gitErrorCode = GitErrorCodes.NoStashFound; + } + + throw err; + } + } + getStatus(limit = 5000): Promise<{ status: IFileStatus[]; didHitLimit: boolean; }> { return new Promise<{ status: IFileStatus[]; didHitLimit: boolean; }>((c, e) => { const parser = new GitStatusParser(); @@ -1618,19 +1641,19 @@ export class Repository { if (parser.status.length > limit) { child.removeListener('exit', onExit); - child.stdout.removeListener('data', onStdoutData); + child.stdout!.removeListener('data', onStdoutData); child.kill(); c({ status: parser.status.slice(0, limit), didHitLimit: true }); } }; - child.stdout.setEncoding('utf8'); - child.stdout.on('data', onStdoutData); + child.stdout!.setEncoding('utf8'); + child.stdout!.on('data', onStdoutData); const stderrData: string[] = []; - child.stderr.setEncoding('utf8'); - child.stderr.on('data', raw => stderrData.push(raw as string)); + child.stderr!.setEncoding('utf8'); + child.stderr!.on('data', raw => stderrData.push(raw as string)); child.on('error', cpErrorHandler(e)); child.on('exit', onExit); @@ -1789,18 +1812,17 @@ export class Repository { } } - cleanupCommitEditMessage(message: string): string { - //TODO: Support core.commentChar + // TODO: Support core.commentChar + stripCommitMessageComments(message: string): string { return message.replace(/^\s*#.*$\n?/gm, '').trim(); } - async getMergeMessage(): Promise { const mergeMsgPath = path.join(this.repositoryRoot, '.git', 'MERGE_MSG'); try { - const raw = await readfile(mergeMsgPath, 'utf8'); - return raw.trim(); + const raw = await fs.readFile(mergeMsgPath, 'utf8'); + return this.stripCommitMessageComments(raw); } catch { return undefined; } @@ -1823,9 +1845,8 @@ export class Repository { templatePath = path.join(this.repositoryRoot, templatePath); } - const raw = await readfile(templatePath, 'utf8'); - return raw.trim(); - + const raw = await fs.readFile(templatePath, 'utf8'); + return this.stripCommitMessageComments(raw); } catch (err) { return ''; } @@ -1848,7 +1869,7 @@ export class Repository { const gitmodulesPath = path.join(this.root, '.gitmodules'); try { - const gitmodulesRaw = await readfile(gitmodulesPath, 'utf8'); + const gitmodulesRaw = await fs.readFile(gitmodulesPath, 'utf8'); return parseGitmodules(gitmodulesRaw); } catch (err) { if (/ENOENT/.test(err.message)) { diff --git a/extensions/git/src/ipc/ipcClient.ts b/extensions/git/src/ipc/ipcClient.ts new file mode 100644 index 000000000000..54b07dd45f5a --- /dev/null +++ b/extensions/git/src/ipc/ipcClient.ts @@ -0,0 +1,45 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as http from 'http'; + +export class IPCClient { + + private ipcHandlePath: string; + + constructor(private handlerName: string) { + const ipcHandlePath = process.env['VSCODE_GIT_IPC_HANDLE']; + + if (!ipcHandlePath) { + throw new Error('Missing VSCODE_GIT_IPC_HANDLE'); + } + + this.ipcHandlePath = ipcHandlePath; + } + + call(request: any): Promise { + const opts: http.RequestOptions = { + socketPath: this.ipcHandlePath, + path: `/${this.handlerName}`, + method: 'POST' + }; + + return new Promise((c, e) => { + const req = http.request(opts, res => { + if (res.statusCode !== 200) { + return e(new Error(`Bad status code: ${res.statusCode}`)); + } + + const chunks: Buffer[] = []; + res.on('data', d => chunks.push(d)); + res.on('end', () => c(JSON.parse(Buffer.concat(chunks).toString('utf8')))); + }); + + req.on('error', err => e(err)); + req.write(JSON.stringify(request)); + req.end(); + }); + } +} diff --git a/extensions/git/src/ipc/ipcServer.ts b/extensions/git/src/ipc/ipcServer.ts new file mode 100644 index 000000000000..218a7c1a5344 --- /dev/null +++ b/extensions/git/src/ipc/ipcServer.ts @@ -0,0 +1,106 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable } from 'vscode'; +import { toDisposable } from '../util'; +import * as path from 'path'; +import * as http from 'http'; +import * as os from 'os'; +import * as fs from 'fs'; +import * as crypto from 'crypto'; + +function getIPCHandlePath(nonce: string): string { + if (process.platform === 'win32') { + return `\\\\.\\pipe\\vscode-git-ipc-${nonce}-sock`; + } + + if (process.env['XDG_RUNTIME_DIR']) { + return path.join(process.env['XDG_RUNTIME_DIR'] as string, `vscode-git-ipc-${nonce}.sock`); + } + + return path.join(os.tmpdir(), `vscode-git-ipc-${nonce}.sock`); +} + +export interface IIPCHandler { + handle(request: any): Promise; +} + +export async function createIPCServer(): Promise { + const server = http.createServer(); + const buffer = await new Promise((c, e) => crypto.randomBytes(20, (err, buf) => err ? e(err) : c(buf))); + const nonce = buffer.toString('hex'); + const ipcHandlePath = getIPCHandlePath(nonce); + + return new Promise((c, e) => { + try { + server.on('error', err => e(err)); + server.listen(ipcHandlePath); + c(new IPCServer(server, ipcHandlePath)); + } catch (err) { + e(err); + } + }); +} + +export interface IIPCServer extends Disposable { + readonly ipcHandlePath: string | undefined; + getEnv(): any; + registerHandler(name: string, handler: IIPCHandler): Disposable; +} + +class IPCServer implements IIPCServer, Disposable { + + private handlers = new Map(); + get ipcHandlePath(): string { return this._ipcHandlePath; } + + constructor(private server: http.Server, private _ipcHandlePath: string) { + this.server.on('request', this.onRequest.bind(this)); + } + + registerHandler(name: string, handler: IIPCHandler): Disposable { + this.handlers.set(`/${name}`, handler); + return toDisposable(() => this.handlers.delete(name)); + } + + private onRequest(req: http.IncomingMessage, res: http.ServerResponse): void { + if (!req.url) { + console.warn(`Request lacks url`); + return; + } + + const handler = this.handlers.get(req.url); + + if (!handler) { + console.warn(`IPC handler for ${req.url} not found`); + return; + } + + const chunks: Buffer[] = []; + req.on('data', d => chunks.push(d)); + req.on('end', () => { + const request = JSON.parse(Buffer.concat(chunks).toString('utf8')); + handler.handle(request).then(result => { + res.writeHead(200); + res.end(JSON.stringify(result)); + }, () => { + res.writeHead(500); + res.end(); + }); + }); + } + + getEnv(): any { + return { VSCODE_GIT_IPC_HANDLE: this.ipcHandlePath }; + } + + dispose(): void { + this.handlers.clear(); + this.server.close(); + + if (this._ipcHandlePath && process.platform !== 'win32') { + fs.unlinkSync(this._ipcHandlePath); + } + } +} diff --git a/extensions/git/src/main.ts b/extensions/git/src/main.ts index fdc0f8741680..5e85b3420b40 100644 --- a/extensions/git/src/main.ts +++ b/extensions/git/src/main.ts @@ -11,6 +11,7 @@ import { findGit, Git, IGit } from './git'; import { Model } from './model'; import { CommandCenter } from './commands'; import { GitContentProvider } from './contentProvider'; +import { GitFileSystemProvider } from './fileSystemProvider'; import { GitDecorations } from './decorationProvider'; import { Askpass } from './askpass'; import { toDisposable, filterEvent, eventToPromise } from './util'; @@ -18,9 +19,9 @@ import TelemetryReporter from 'vscode-extension-telemetry'; import { GitExtension } from './api/git'; import { GitProtocolHandler } from './protocolHandler'; import { GitExtensionImpl } from './api/extension'; -// {{SQL CARBON EDIT}} - remove unused imports // import * as path from 'path'; // import * as fs from 'fs'; +import { createIPCServer, IIPCServer } from './ipc/ipcServer'; const deactivateTasks: { (): Promise; }[] = []; @@ -33,10 +34,26 @@ export async function deactivate(): Promise { async function createModel(context: ExtensionContext, outputChannel: OutputChannel, telemetryReporter: TelemetryReporter, disposables: Disposable[]): Promise { const pathHint = workspace.getConfiguration('git').get('path'); const info = await findGit(pathHint, path => outputChannel.appendLine(localize('looking', "Looking for git in: {0}", path))); - const askpass = new Askpass(); - disposables.push(askpass); - const env = await askpass.getEnv(); + let env: any = {}; + let ipc: IIPCServer | undefined; + + try { + ipc = await createIPCServer(); + disposables.push(ipc); + env = { ...env, ...ipc.getEnv() }; + } catch { + // noop + } + + if (ipc) { + const askpass = new Askpass(ipc); + disposables.push(askpass); + env = { ...env, ...askpass.getEnv() }; + } else { + env = { ...env, ...Askpass.getDisabledEnv() }; + } + const git = new Git({ gitPath: info.path, version: info.version, env }); const model = new Model(git, context.globalState, outputChannel); disposables.push(model); @@ -63,6 +80,7 @@ async function createModel(context: ExtensionContext, outputChannel: OutputChann disposables.push( new CommandCenter(git, model, outputChannel, telemetryReporter), new GitContentProvider(model), + new GitFileSystemProvider(model), new GitDecorations(model), new GitProtocolHandler() ); @@ -198,4 +216,4 @@ async function checkGitVersion(_info: IGit): Promise { // await config.update('ignoreLegacyWarning', true, true); // } // {{SQL CARBON EDIT}} - End -} \ No newline at end of file +} diff --git a/extensions/git/src/model.ts b/extensions/git/src/model.ts index 064c2186429f..560c4a5d01d0 100644 --- a/extensions/git/src/model.ts +++ b/extensions/git/src/model.ts @@ -6,7 +6,7 @@ import { workspace, WorkspaceFoldersChangeEvent, Uri, window, Event, EventEmitter, QuickPickItem, Disposable, SourceControl, SourceControlResourceGroup, TextEditor, Memento, OutputChannel } from 'vscode'; import { Repository, RepositoryState } from './repository'; import { memoize, sequentialize, debounce } from './decorators'; -import { dispose, anyEvent, filterEvent, isDescendant, firstIndex } from './util'; +import { dispose, anyEvent, filterEvent, isDescendant, firstIndex, pathEquals } from './util'; import { Git } from './git'; import * as path from 'path'; import * as fs from 'fs'; @@ -240,10 +240,7 @@ export class Model { return; } - const config = workspace.getConfiguration('git'); - const ignoredRepos = new Set(config.get>('ignoredRepositories')); - - if (ignoredRepos.has(rawRoot)) { + if (this.shouldRepositoryBeIgnored(rawRoot)) { return; } @@ -261,6 +258,27 @@ export class Model { } } + private shouldRepositoryBeIgnored(repositoryRoot: string): boolean { + const config = workspace.getConfiguration('git'); + const ignoredRepos = config.get('ignoredRepositories') || []; + + for (const ignoredRepo of ignoredRepos) { + if (path.isAbsolute(ignoredRepo)) { + if (pathEquals(ignoredRepo, repositoryRoot)) { + return true; + } + } else { + for (const folder of workspace.workspaceFolders || []) { + if (pathEquals(path.join(folder.uri.fsPath, ignoredRepo), repositoryRoot)) { + return true; + } + } + } + } + + return false; + } + private open(repository: Repository): void { this.outputChannel.appendLine(`Open repository: ${repository.root}`); diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 29a8876d9923..b0bf44b165cf 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -3,17 +3,17 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Uri, Command, EventEmitter, Event, scm, SourceControl, SourceControlInputBox, SourceControlResourceGroup, SourceControlResourceState, SourceControlResourceDecorations, SourceControlInputBoxValidation, Disposable, ProgressLocation, window, workspace, WorkspaceEdit, ThemeColor, Decoration, Memento, SourceControlInputBoxValidationType, OutputChannel, LogLevel, env, ProgressOptions, CancellationToken } from 'vscode'; -import { Repository as BaseRepository, Commit, Stash, GitError, Submodule, CommitOptions, ForcePushMode } from './git'; -import { anyEvent, filterEvent, eventToPromise, dispose, find, isDescendant, IDisposable, onceEvent, EmptyDisposable, debounceEvent, combinedDisposable } from './util'; -import { memoize, throttle, debounce } from './decorators'; -import { toGitUri } from './uri'; -import { AutoFetcher } from './autofetch'; +import * as fs from 'fs'; import * as path from 'path'; +import { CancellationToken, Command, Disposable, env, Event, EventEmitter, LogLevel, Memento, OutputChannel, ProgressLocation, ProgressOptions, scm, SourceControl, SourceControlInputBox, SourceControlInputBoxValidation, SourceControlInputBoxValidationType, SourceControlResourceDecorations, SourceControlResourceGroup, SourceControlResourceState, ThemeColor, Uri, window, workspace, WorkspaceEdit, Decoration } from 'vscode'; import * as nls from 'vscode-nls'; -import * as fs from 'fs'; +import { Branch, Change, GitErrorCodes, LogOptions, Ref, RefType, Remote, Status } from './api/git'; +import { AutoFetcher } from './autofetch'; +import { debounce, memoize, throttle } from './decorators'; +import { Commit, CommitOptions, ForcePushMode, GitError, Repository as BaseRepository, Stash, Submodule } from './git'; import { StatusBarCommands } from './statusbar'; -import { Branch, Ref, Remote, RefType, GitErrorCodes, Status, LogOptions, Change } from './api/git'; +import { toGitUri } from './uri'; +import { anyEvent, combinedDisposable, debounceEvent, dispose, EmptyDisposable, eventToPromise, filterEvent, find, IDisposable, isDescendant, onceEvent } from './util'; import { IFileWatcher, watch } from './watch'; const timeout = (millis: number) => new Promise(c => setTimeout(c, millis)); @@ -33,7 +33,8 @@ export const enum RepositoryState { export const enum ResourceGroupType { Merge, Index, - WorkingTree + WorkingTree, + Untracked } export class Resource implements SourceControlResourceState { @@ -292,6 +293,7 @@ export const enum Operation { Merge = 'Merge', Ignore = 'Ignore', Tag = 'Tag', + DeleteTag = 'DeleteTag', Stash = 'Stash', CheckIgnore = 'CheckIgnore', GetObjectDetails = 'GetObjectDetails', @@ -569,6 +571,9 @@ export class Repository implements Disposable { private _workingTreeGroup: SourceControlResourceGroup; get workingTreeGroup(): GitResourceGroup { return this._workingTreeGroup as GitResourceGroup; } + private _untrackedGroup: SourceControlResourceGroup; + get untrackedGroup(): GitResourceGroup { return this._untrackedGroup as GitResourceGroup; } + private _HEAD: Branch | undefined; get HEAD(): Branch | undefined { return this._HEAD; @@ -641,6 +646,7 @@ export class Repository implements Disposable { this.mergeGroup.resourceStates = []; this.indexGroup.resourceStates = []; this.workingTreeGroup.resourceStates = []; + this.untrackedGroup.resourceStates = []; this._sourceControl.count = 0; } @@ -708,6 +714,7 @@ export class Repository implements Disposable { this._mergeGroup = this._sourceControl.createResourceGroup('merge', localize('merge changes', "MERGE CHANGES")); this._indexGroup = this._sourceControl.createResourceGroup('index', localize('staged changes', "STAGED CHANGES")); this._workingTreeGroup = this._sourceControl.createResourceGroup('workingTree', localize('changes', "CHANGES")); + this._untrackedGroup = this._sourceControl.createResourceGroup('untracked', localize('untracked changes', "UNTRACKED CHANGES")); const updateIndexGroupVisibility = () => { const config = workspace.getConfiguration('git', root); @@ -721,11 +728,16 @@ export class Repository implements Disposable { const onConfigListenerForBranchSortOrder = filterEvent(workspace.onDidChangeConfiguration, e => e.affectsConfiguration('git.branchSortOrder', root)); onConfigListenerForBranchSortOrder(this.updateModelState, this, this.disposables); + const onConfigListenerForUntracked = filterEvent(workspace.onDidChangeConfiguration, e => e.affectsConfiguration('git.untrackedChanges', root)); + onConfigListenerForUntracked(this.updateModelState, this, this.disposables); + this.mergeGroup.hideWhenEmpty = true; + this.untrackedGroup.hideWhenEmpty = true; this.disposables.push(this.mergeGroup); this.disposables.push(this.indexGroup); this.disposables.push(this.workingTreeGroup); + this.disposables.push(this.untrackedGroup); this.disposables.push(new AutoFetcher(this, globalState)); @@ -911,8 +923,8 @@ export class Repository implements Disposable { return this.run(Operation.HashObject, () => this.repository.hashObject(data)); } - async add(resources: Uri[]): Promise { - await this.run(Operation.Add, () => this.repository.add(resources.map(r => r.fsPath))); + async add(resources: Uri[], opts?: { update?: boolean }): Promise { + await this.run(Operation.Add, () => this.repository.add(resources.map(r => r.fsPath), opts)); } async rm(resources: Uri[]): Promise { @@ -957,6 +969,7 @@ export class Repository implements Disposable { const toClean: string[] = []; const toCheckout: string[] = []; const submodulesToUpdate: string[] = []; + const resourceStates = [...this.workingTreeGroup.resourceStates, ...this.untrackedGroup.resourceStates]; resources.forEach(r => { const fsPath = r.fsPath; @@ -969,7 +982,7 @@ export class Repository implements Disposable { } const raw = r.toString(); - const scmResource = find(this.workingTreeGroup.resourceStates, sr => sr.resourceUri.toString() === raw); + const scmResource = find(resourceStates, sr => sr.resourceUri.toString() === raw); if (!scmResource) { return; @@ -1021,6 +1034,10 @@ export class Repository implements Disposable { await this.run(Operation.Tag, () => this.repository.tag(name, message)); } + async deleteTag(name: string): Promise { + await this.run(Operation.DeleteTag, () => this.repository.deleteTag(name)); + } + async checkout(treeish: string): Promise { await this.run(Operation.Checkout, () => this.repository.checkout(treeish, [])); } @@ -1249,6 +1266,10 @@ export class Repository implements Disposable { return await this.run(Operation.Stash, () => this.repository.popStash(index)); } + async dropStash(index?: number): Promise { + return await this.run(Operation.Stash, () => this.repository.dropStash(index)); + } + async applyStash(index?: number): Promise { return await this.run(Operation.Stash, () => this.repository.applyStash(index)); } @@ -1257,10 +1278,6 @@ export class Repository implements Disposable { return await this.run(Operation.GetCommitTemplate, async () => this.repository.getCommitTemplate()); } - async cleanUpCommitEditMessage(editMessage: string): Promise { - return this.repository.cleanupCommitEditMessage(editMessage); - } - async ignore(files: Uri[]): Promise { return await this.run(Operation.Ignore, async () => { const ignoreFile = `${this.repository.root}${path.sep}.gitignore`; @@ -1298,7 +1315,7 @@ export class Repository implements Disposable { // https://git-scm.com/docs/git-check-ignore#git-check-ignore--z const child = this.repository.stream(['check-ignore', '-v', '-z', '--stdin'], { stdio: [null, null, null] }); - child.stdin.end(filePaths.join('\0'), 'utf8'); + child.stdin!.end(filePaths.join('\0'), 'utf8'); const onExit = (exitCode: number) => { if (exitCode === 1) { @@ -1320,12 +1337,12 @@ export class Repository implements Disposable { data += raw; }; - child.stdout.setEncoding('utf8'); - child.stdout.on('data', onStdoutData); + child.stdout!.setEncoding('utf8'); + child.stdout!.on('data', onStdoutData); let stderr: string = ''; - child.stderr.setEncoding('utf8'); - child.stderr.on('data', raw => stderr += raw); + child.stderr!.setEncoding('utf8'); + child.stderr!.on('data', raw => stderr += raw); child.on('error', reject); child.on('exit', onExit); @@ -1427,6 +1444,7 @@ export class Repository implements Disposable { private async updateModelState(): Promise { const { status, didHitLimit } = await this.repository.getStatus(); const config = workspace.getConfiguration('git'); + const scopedConfig = workspace.getConfiguration('git', Uri.file(this.repository.root)); const shouldIgnore = config.get('ignoreLimitWarning') === true; const useIcons = !config.get('decorations.enabled', true); this.isRepositoryHuge = didHitLimit; @@ -1487,17 +1505,29 @@ export class Repository implements Disposable { this._submodules = submodules!; this.rebaseCommit = rebaseCommit; + const untrackedChanges = scopedConfig.get<'mixed' | 'separate' | 'hidden'>('untrackedChanges'); const index: Resource[] = []; const workingTree: Resource[] = []; const merge: Resource[] = []; + const untracked: Resource[] = []; status.forEach(raw => { const uri = Uri.file(path.join(this.repository.root, raw.path)); - const renameUri = raw.rename ? Uri.file(path.join(this.repository.root, raw.rename)) : undefined; + const renameUri = raw.rename + ? Uri.file(path.join(this.repository.root, raw.rename)) + : undefined; switch (raw.x + raw.y) { - case '??': return workingTree.push(new Resource(ResourceGroupType.WorkingTree, uri, Status.UNTRACKED, useIcons)); - case '!!': return workingTree.push(new Resource(ResourceGroupType.WorkingTree, uri, Status.IGNORED, useIcons)); + case '??': switch (untrackedChanges) { + case 'mixed': return workingTree.push(new Resource(ResourceGroupType.WorkingTree, uri, Status.UNTRACKED, useIcons)); + case 'separate': return untracked.push(new Resource(ResourceGroupType.Untracked, uri, Status.UNTRACKED, useIcons)); + default: return undefined; + } + case '!!': switch (untrackedChanges) { + case 'mixed': return workingTree.push(new Resource(ResourceGroupType.WorkingTree, uri, Status.IGNORED, useIcons)); + case 'separate': return untracked.push(new Resource(ResourceGroupType.Untracked, uri, Status.IGNORED, useIcons)); + default: return undefined; + } case 'DD': return merge.push(new Resource(ResourceGroupType.Merge, uri, Status.BOTH_DELETED, useIcons)); case 'AU': return merge.push(new Resource(ResourceGroupType.Merge, uri, Status.ADDED_BY_US, useIcons)); case 'UD': return merge.push(new Resource(ResourceGroupType.Merge, uri, Status.DELETED_BY_THEM, useIcons)); @@ -1520,6 +1550,7 @@ export class Repository implements Disposable { case 'D': workingTree.push(new Resource(ResourceGroupType.WorkingTree, uri, Status.DELETED, useIcons, renameUri)); break; case 'A': workingTree.push(new Resource(ResourceGroupType.WorkingTree, uri, Status.INTENT_TO_ADD, useIcons, renameUri)); break; } + return undefined; }); @@ -1527,6 +1558,7 @@ export class Repository implements Disposable { this.mergeGroup.resourceStates = merge; this.indexGroup.resourceStates = index; this.workingTreeGroup.resourceStates = workingTree; + this.untrackedGroup.resourceStates = untracked; // set count badge this.setCountBadge(); @@ -1537,12 +1569,27 @@ export class Repository implements Disposable { } private setCountBadge(): void { - const countBadge = workspace.getConfiguration('git').get('countBadge'); - let count = this.mergeGroup.resourceStates.length + this.indexGroup.resourceStates.length + this.workingTreeGroup.resourceStates.length; + const config = workspace.getConfiguration('git', Uri.file(this.repository.root)); + const countBadge = config.get<'all' | 'tracked' | 'off'>('countBadge'); + const untrackedChanges = config.get<'mixed' | 'separate' | 'hidden'>('untrackedChanges'); + + let count = + this.mergeGroup.resourceStates.length + + this.indexGroup.resourceStates.length + + this.workingTreeGroup.resourceStates.length; switch (countBadge) { case 'off': count = 0; break; - case 'tracked': count = count - this.workingTreeGroup.resourceStates.filter(r => r.type === Status.UNTRACKED || r.type === Status.IGNORED).length; break; + case 'tracked': + if (untrackedChanges === 'mixed') { + count -= this.workingTreeGroup.resourceStates.filter(r => r.type === Status.UNTRACKED || r.type === Status.IGNORED).length; + } + break; + case 'all': + if (untrackedChanges === 'separate') { + count += this.untrackedGroup.resourceStates.length; + } + break; } this._sourceControl.count = count; @@ -1644,7 +1691,7 @@ export class Repository implements Disposable { const head = HEAD.name || tagName || (HEAD.commit || '').substr(0, 8); return head - + (this.workingTreeGroup.resourceStates.length > 0 ? '*' : '') + + (this.workingTreeGroup.resourceStates.length + this.untrackedGroup.resourceStates.length > 0 ? '*' : '') + (this.indexGroup.resourceStates.length > 0 ? '+' : '') + (this.mergeGroup.resourceStates.length > 0 ? '!' : ''); } diff --git a/extensions/git/src/typings/jschardet.d.ts b/extensions/git/src/typings/jschardet.d.ts deleted file mode 100644 index f252a47fd094..000000000000 --- a/extensions/git/src/typings/jschardet.d.ts +++ /dev/null @@ -1,11 +0,0 @@ -declare module 'jschardet' { - export interface IDetectedMap { - encoding: string, - confidence: number - } - export function detect(buffer: Buffer): IDetectedMap; - - export const Constants: { - MINIMUM_THRESHOLD: number, - } -} \ No newline at end of file diff --git a/extensions/git/src/uri.ts b/extensions/git/src/uri.ts index f23f8fbd8705..a66877b07b8d 100644 --- a/extensions/git/src/uri.ts +++ b/extensions/git/src/uri.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Uri } from 'vscode'; +import * as qs from 'querystring'; export interface GitUriParams { path: string; @@ -11,8 +12,26 @@ export interface GitUriParams { submoduleOf?: string; } +export function isGitUri(uri: Uri): boolean { + return /^git(fs)?$/.test(uri.scheme); +} + export function fromGitUri(uri: Uri): GitUriParams { - return JSON.parse(uri.query); + const result = qs.parse(uri.query) as any; + + if (!result) { + throw new Error('Invalid git URI: empty query'); + } + + if (typeof result.path !== 'string') { + throw new Error('Invalid git URI: missing path'); + } + + if (typeof result.ref !== 'string') { + throw new Error('Invalid git URI: missing ref'); + } + + return result; } export interface GitUriOptions { @@ -42,8 +61,8 @@ export function toGitUri(uri: Uri, ref: string, options: GitUriOptions = {}): Ur } return uri.with({ - scheme: 'git', + scheme: 'gitfs', path, - query: JSON.stringify(params) + query: qs.stringify(params as any) }); -} \ No newline at end of file +} diff --git a/extensions/git/src/util.ts b/extensions/git/src/util.ts index 8f9a0e0841de..742bcbfa0530 100644 --- a/extensions/git/src/util.ts +++ b/extensions/git/src/util.ts @@ -6,7 +6,7 @@ import { Event } from 'vscode'; import { dirname, sep } from 'path'; import { Readable } from 'stream'; -import * as fs from 'fs'; +import { promises as fs, createReadStream } from 'fs'; import * as byline from 'byline'; export function log(...args: any[]): void { @@ -140,25 +140,14 @@ export function groupBy(arr: T[], fn: (el: T) => string): { [key: string]: T[ }, Object.create(null)); } -export function denodeify(fn: Function): (a: A, b: B, c: C) => Promise; -export function denodeify(fn: Function): (a: A, b: B) => Promise; -export function denodeify(fn: Function): (a: A) => Promise; -export function denodeify(fn: Function): (...args: any[]) => Promise; -export function denodeify(fn: Function): (...args: any[]) => Promise { - return (...args) => new Promise((c, e) => fn(...args, (err: any, r: any) => err ? e(err) : c(r))); -} - -export function nfcall(fn: Function, ...args: any[]): Promise { - return new Promise((c, e) => fn(...args, (err: any, r: any) => err ? e(err) : c(r))); -} export async function mkdirp(path: string, mode?: number): Promise { const mkdir = async () => { try { - await nfcall(fs.mkdir, path, mode); + await fs.mkdir(path, mode); } catch (err) { if (err.code === 'EEXIST') { - const stat = await nfcall(fs.stat, path); + const stat = await fs.stat(path); if (stat.isDirectory()) { return; @@ -232,7 +221,7 @@ export function find(array: T[], fn: (t: T) => boolean): T | undefined { export async function grep(filename: string, pattern: RegExp): Promise { return new Promise((c, e) => { - const fileStream = fs.createReadStream(filename, { encoding: 'utf8' }); + const fileStream = createReadStream(filename, { encoding: 'utf8' }); const stream = byline(fileStream); stream.on('data', (line: string) => { if (pattern.test(line)) { diff --git a/extensions/git/yarn.lock b/extensions/git/yarn.lock index 1f2ea8aed42b..58a82bbe41c8 100644 --- a/extensions/git/yarn.lock +++ b/extensions/git/yarn.lock @@ -26,10 +26,10 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.51.tgz#b31d716fb8d58eeb95c068a039b9b6292817d5fb" integrity sha512-El3+WJk2D/ppWNd2X05aiP5l2k4EwF7KwheknQZls+I26eSICoWRhRIJ56jGgw2dqNGQ5LtNajmBU2ajS28EvQ== -"@types/node@^10.14.8": - version "10.14.8" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.14.8.tgz#fe444203ecef1162348cd6deb76c62477b2cc6e9" - integrity sha512-I4+DbJEhLEg4/vIy/2gkWDvXBOOtPKV9EnLhYjMoqxcRW+TTZtUftkHktz/a8suoD5mUL7m6ReLrkPvSsCQQmw== +"@types/node@^12.11.7": + version "12.11.7" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.11.7.tgz#57682a9771a3f7b09c2497f28129a0462966524a" + integrity sha512-JNbGaHFCLwgHn/iCckiGSOZ1XYHsKFwREtzPwSGCVld1SGhOlmZw2D4ZI94HQCrBHbADzW9m4LER/8olJTRGHA== "@types/which@^1.0.28": version "1.0.28" @@ -176,10 +176,10 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= -jschardet@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/jschardet/-/jschardet-1.6.0.tgz#c7d1a71edcff2839db2f9ec30fc5d5ebd3c1a678" - integrity sha512-xYuhvQ7I9PDJIGBWev9xm0+SMSed3ZDBAmvVjbFR1ZRLAF+vlXcQu6cRI9uAlj81rzikElRVteehwV7DuX2ZmQ== +jschardet@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/jschardet/-/jschardet-2.1.1.tgz#af6f8fd0b3b0f5d46a8fd9614a4fce490575c184" + integrity sha512-pA5qG9Zwm8CBpGlK/lo2GE9jPxwqRgMV7Lzc/1iaPccw6v4Rhj8Zg2BTyrdmHmxlJojnbLupLeRnaPLsq03x6Q== json3@3.3.2: version "3.3.2" diff --git a/extensions/image-preview/media/main.css b/extensions/image-preview/media/main.css index 67c4a53049e7..f363cb0a54b7 100644 --- a/extensions/image-preview/media/main.css +++ b/extensions/image-preview/media/main.css @@ -21,6 +21,7 @@ body img { .container { padding: 5px 0 0 10px; box-sizing: border-box; + -webkit-user-select: none; user-select: none; } diff --git a/extensions/image-preview/media/main.js b/extensions/image-preview/media/main.js index 8843c26bdf81..eb42624fd5f5 100644 --- a/extensions/image-preview/media/main.js +++ b/extensions/image-preview/media/main.js @@ -70,7 +70,8 @@ let ctrlPressed = false; let altPressed = false; let hasLoadedImage = false; - let consumeClick = false; + let consumeClick = true; + let isActive = false; // Elements const container = document.body; @@ -117,10 +118,16 @@ }); } - function changeActive(value) { + function setActive(value) { + isActive = value; if (value) { - container.classList.add('zoom-in'); - consumeClick = true; + if (isMac ? altPressed : ctrlPressed) { + container.classList.remove('zoom-in'); + container.classList.add('zoom-out'); + } else { + container.classList.remove('zoom-out'); + container.classList.add('zoom-in'); + } } else { ctrlPressed = false; altPressed = false; @@ -202,7 +209,10 @@ return; } - consumeClick = false; + ctrlPressed = e.ctrlKey; + altPressed = e.altKey; + + consumeClick = !isActive; }); container.addEventListener('click', (/** @type {MouseEvent} */ e) => { @@ -214,14 +224,6 @@ return; } - ctrlPressed = e.ctrlKey; - altPressed = e.altKey; - - if (isMac ? altPressed : ctrlPressed) { - container.classList.remove('zoom-in'); - container.classList.add('zoom-out'); - } - if (consumeClick) { consumeClick = false; return; @@ -239,6 +241,11 @@ }); container.addEventListener('wheel', (/** @type {WheelEvent} */ e) => { + // Prevent pinch to zoom + if (e.ctrlKey) { + e.preventDefault(); + } + if (!image || !hasLoadedImage) { return; } @@ -254,9 +261,9 @@ let delta = e.deltaY > 0 ? 1 : -1; updateScale(scale * (1 - delta * SCALE_PINCH_FACTOR)); - }); + }, { passive: false }); - window.addEventListener('scroll', () => { + window.addEventListener('scroll', e => { if (!image || !hasLoadedImage || !image.parentElement || scale === 'fit') { return; } @@ -265,7 +272,7 @@ if (entry) { vscode.setState({ scale: entry.scale, offsetX: window.scrollX, offsetY: window.scrollY }); } - }); + }, { passive: true }); container.classList.add('image'); @@ -296,7 +303,7 @@ document.body.classList.remove('loading'); }); - image.src = decodeURI(settings.src); + image.src = settings.src; window.addEventListener('message', e => { switch (e.data.type) { @@ -305,7 +312,7 @@ break; case 'setActive': - changeActive(e.data.value); + setActive(e.data.value); break; case 'zoomIn': diff --git a/extensions/image-preview/package.json b/extensions/image-preview/package.json index c6149de85dec..8be8486a54b5 100644 --- a/extensions/image-preview/package.json +++ b/extensions/image-preview/package.json @@ -29,8 +29,7 @@ "priority": "builtin", "selector": [ { - "filenamePattern": "*.{jpg,jpe,jpeg,png,bmp,gif,ico,tga,webp}", - "mime": "image/*" + "filenamePattern": "*.{jpg,jpe,jpeg,png,bmp,gif,ico,webp}" } ] } diff --git a/extensions/image-preview/src/binarySizeStatusBarEntry.ts b/extensions/image-preview/src/binarySizeStatusBarEntry.ts new file mode 100644 index 000000000000..c339a84de5d2 --- /dev/null +++ b/extensions/image-preview/src/binarySizeStatusBarEntry.ts @@ -0,0 +1,57 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import * as nls from 'vscode-nls'; +import { PreviewStatusBarEntry } from './ownedStatusBarEntry'; + +const localize = nls.loadMessageBundle(); + +class BinarySize { + static readonly KB = 1024; + static readonly MB = BinarySize.KB * BinarySize.KB; + static readonly GB = BinarySize.MB * BinarySize.KB; + static readonly TB = BinarySize.GB * BinarySize.KB; + + static formatSize(size: number): string { + if (size < BinarySize.KB) { + return localize('sizeB', "{0}B", size); + } + + if (size < BinarySize.MB) { + return localize('sizeKB', "{0}KB", (size / BinarySize.KB).toFixed(2)); + } + + if (size < BinarySize.GB) { + return localize('sizeMB', "{0}MB", (size / BinarySize.MB).toFixed(2)); + } + + if (size < BinarySize.TB) { + return localize('sizeGB', "{0}GB", (size / BinarySize.GB).toFixed(2)); + } + + return localize('sizeTB', "{0}TB", (size / BinarySize.TB).toFixed(2)); + } +} + +export class BinarySizeStatusBarEntry extends PreviewStatusBarEntry { + + constructor() { + super({ + id: 'imagePreview.binarySize', + name: localize('sizeStatusBar.name', "Image Binary Size"), + alignment: vscode.StatusBarAlignment.Right, + priority: 100, + }); + } + + public show(owner: string, size: number | undefined) { + if (typeof size === 'number') { + super.showItem(owner, BinarySize.formatSize(size)); + } else { + this.hide(owner); + } + } +} diff --git a/extensions/image-preview/src/extension.ts b/extensions/image-preview/src/extension.ts index 2126e9c1cbf1..1804cbd56137 100644 --- a/extensions/image-preview/src/extension.ts +++ b/extensions/image-preview/src/extension.ts @@ -6,6 +6,7 @@ import * as vscode from 'vscode'; import { PreviewManager } from './preview'; import { SizeStatusBarEntry } from './sizeStatusBarEntry'; +import { BinarySizeStatusBarEntry } from './binarySizeStatusBarEntry'; import { ZoomStatusBarEntry } from './zoomStatusBarEntry'; export function activate(context: vscode.ExtensionContext) { @@ -14,18 +15,15 @@ export function activate(context: vscode.ExtensionContext) { const sizeStatusBarEntry = new SizeStatusBarEntry(); context.subscriptions.push(sizeStatusBarEntry); + const binarySizeStatusBarEntry = new BinarySizeStatusBarEntry(); + context.subscriptions.push(binarySizeStatusBarEntry); + const zoomStatusBarEntry = new ZoomStatusBarEntry(); context.subscriptions.push(zoomStatusBarEntry); - const previewManager = new PreviewManager(extensionRoot, sizeStatusBarEntry, zoomStatusBarEntry); + const previewManager = new PreviewManager(extensionRoot, sizeStatusBarEntry, binarySizeStatusBarEntry, zoomStatusBarEntry); - context.subscriptions.push(vscode.window.registerWebviewEditorProvider( - PreviewManager.viewType, - { - async resolveWebviewEditor(resource: vscode.Uri, editor: vscode.WebviewEditor): Promise { - previewManager.resolve(resource, editor); - } - })); + context.subscriptions.push(vscode.window.registerWebviewEditorProvider(PreviewManager.viewType, previewManager)); context.subscriptions.push(vscode.commands.registerCommand('imagePreview.zoomIn', () => { previewManager.activePreview?.zoomIn(); diff --git a/extensions/image-preview/src/ownedStatusBarEntry.ts b/extensions/image-preview/src/ownedStatusBarEntry.ts new file mode 100644 index 000000000000..46852aa4e47f --- /dev/null +++ b/extensions/image-preview/src/ownedStatusBarEntry.ts @@ -0,0 +1,31 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import { Disposable } from './dispose'; + +export abstract class PreviewStatusBarEntry extends Disposable { + private _showOwner: string | undefined; + + protected readonly entry: vscode.StatusBarItem; + + constructor(options: vscode.window.StatusBarItemOptions) { + super(); + this.entry = this._register(vscode.window.createStatusBarItem(options)); + } + + protected showItem(owner: string, text: string) { + this._showOwner = owner; + this.entry.text = text; + this.entry.show(); + } + + public hide(owner: string) { + if (owner === this._showOwner) { + this.entry.hide(); + this._showOwner = undefined; + } + } +} diff --git a/extensions/image-preview/src/preview.ts b/extensions/image-preview/src/preview.ts index 2809c8ac7442..a2a36edbb6df 100644 --- a/extensions/image-preview/src/preview.ts +++ b/extensions/image-preview/src/preview.ts @@ -8,11 +8,12 @@ import * as nls from 'vscode-nls'; import { Disposable } from './dispose'; import { SizeStatusBarEntry } from './sizeStatusBarEntry'; import { Scale, ZoomStatusBarEntry } from './zoomStatusBarEntry'; +import { BinarySizeStatusBarEntry } from './binarySizeStatusBarEntry'; const localize = nls.loadMessageBundle(); -export class PreviewManager { +export class PreviewManager implements vscode.WebviewEditorProvider { public static readonly viewType = 'imagePreview.previewEditor'; @@ -22,14 +23,15 @@ export class PreviewManager { constructor( private readonly extensionRoot: vscode.Uri, private readonly sizeStatusBarEntry: SizeStatusBarEntry, + private readonly binarySizeStatusBarEntry: BinarySizeStatusBarEntry, private readonly zoomStatusBarEntry: ZoomStatusBarEntry, ) { } - public resolve( - resource: vscode.Uri, - webviewEditor: vscode.WebviewEditor, - ) { - const preview = new Preview(this.extensionRoot, resource, webviewEditor, this.sizeStatusBarEntry, this.zoomStatusBarEntry); + public async resolveWebviewEditor( + input: { readonly resource: vscode.Uri, }, + webviewEditor: vscode.WebviewPanel, + ): Promise { + const preview = new Preview(this.extensionRoot, input.resource, webviewEditor, this.sizeStatusBarEntry, this.binarySizeStatusBarEntry, this.zoomStatusBarEntry); this._previews.add(preview); this.setActivePreview(preview); @@ -42,6 +44,8 @@ export class PreviewManager { this.setActivePreview(undefined); } }); + + return {}; } public get activePreview() { return this._activePreview; } @@ -68,13 +72,15 @@ class Preview extends Disposable { private _previewState = PreviewState.Visible; private _imageSize: string | undefined; + private _imageBinarySize: number | undefined; private _imageZoom: Scale | undefined; constructor( private readonly extensionRoot: vscode.Uri, private readonly resource: vscode.Uri, - private readonly webviewEditor: vscode.WebviewEditor, + private readonly webviewEditor: vscode.WebviewPanel, private readonly sizeStatusBarEntry: SizeStatusBarEntry, + private readonly binarySizeStatusBarEntry: BinarySizeStatusBarEntry, private readonly zoomStatusBarEntry: ZoomStatusBarEntry, ) { super(); @@ -115,11 +121,13 @@ class Preview extends Disposable { this._register(webviewEditor.onDidChangeViewState(() => { this.update(); + this.webviewEditor.webview.postMessage({ type: 'setActive', value: this.webviewEditor.active }); })); this._register(webviewEditor.onDidDispose(() => { if (this._previewState === PreviewState.Active) { this.sizeStatusBarEntry.hide(this.id); + this.binarySizeStatusBarEntry.hide(this.id); this.zoomStatusBarEntry.hide(this.id); } this._previewState = PreviewState.Disposed; @@ -137,8 +145,14 @@ class Preview extends Disposable { } })); + vscode.workspace.fs.stat(resource).then(({ size }) => { + this._imageBinarySize = size; + this.update(); + }); + this.render(); this.update(); + this.webviewEditor.webview.postMessage({ type: 'setActive', value: this.webviewEditor.active }); } public zoomIn() { @@ -167,15 +181,16 @@ class Preview extends Disposable { if (this.webviewEditor.active) { this._previewState = PreviewState.Active; this.sizeStatusBarEntry.show(this.id, this._imageSize || ''); + this.binarySizeStatusBarEntry.show(this.id, this._imageBinarySize); this.zoomStatusBarEntry.show(this.id, this._imageZoom || 'fit'); } else { if (this._previewState === PreviewState.Active) { this.sizeStatusBarEntry.hide(this.id); + this.binarySizeStatusBarEntry.hide(this.id); this.zoomStatusBarEntry.hide(this.id); } this._previewState = PreviewState.Visible; } - this.webviewEditor.webview.postMessage({ type: 'setActive', value: this.webviewEditor.active }); } private getWebiewContents(): string { @@ -191,8 +206,11 @@ class Preview extends Disposable { - - + + + + Image Preview @@ -208,18 +226,21 @@ class Preview extends Disposable { `; } - private getResourcePath(webviewEditor: vscode.WebviewEditor, resource: vscode.Uri, version: string) { + private getResourcePath(webviewEditor: vscode.WebviewPanel, resource: vscode.Uri, version: string) { switch (resource.scheme) { case 'data': - return encodeURI(resource.toString(true)); + return resource.toString(true); case 'git': // Show blank image - return encodeURI('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAEElEQVR42gEFAPr/AP///wAI/AL+Sr4t6gAAAABJRU5ErkJggg=='); - + return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAEElEQVR42gEFAPr/AP///wAI/AL+Sr4t6gAAAABJRU5ErkJggg=='; default: - return encodeURI(webviewEditor.webview.asWebviewUri(resource).toString(true) + `?version=${version}`); + // Avoid adding cache busting if there is already a query string + if (resource.query) { + return webviewEditor.webview.asWebviewUri(resource).toString(true); + } + return webviewEditor.webview.asWebviewUri(resource).with({ query: `version=${version}` }).toString(true); } } diff --git a/extensions/image-preview/src/sizeStatusBarEntry.ts b/extensions/image-preview/src/sizeStatusBarEntry.ts index cc166dec6c3a..a2e7e50a8962 100644 --- a/extensions/image-preview/src/sizeStatusBarEntry.ts +++ b/extensions/image-preview/src/sizeStatusBarEntry.ts @@ -4,36 +4,23 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import { Disposable } from './dispose'; import * as nls from 'vscode-nls'; +import { PreviewStatusBarEntry } from './ownedStatusBarEntry'; const localize = nls.loadMessageBundle(); -export class SizeStatusBarEntry extends Disposable { - private readonly _entry: vscode.StatusBarItem; - - private _showingOwner: string | undefined; +export class SizeStatusBarEntry extends PreviewStatusBarEntry { constructor() { - super(); - this._entry = this._register(vscode.window.createStatusBarItem({ + super({ id: 'imagePreview.size', name: localize('sizeStatusBar.name', "Image Size"), alignment: vscode.StatusBarAlignment.Right, priority: 101 /* to the left of editor status (100) */, - })); + }); } public show(owner: string, text: string) { - this._showingOwner = owner; - this._entry.text = text; - this._entry.show(); - } - - public hide(owner: string) { - if (owner === this._showingOwner) { - this._entry.hide(); - this._showingOwner = undefined; - } + this.showItem(owner, text); } } diff --git a/extensions/image-preview/src/zoomStatusBarEntry.ts b/extensions/image-preview/src/zoomStatusBarEntry.ts index 42976b8dc59f..dfe166ae3bcf 100644 --- a/extensions/image-preview/src/zoomStatusBarEntry.ts +++ b/extensions/image-preview/src/zoomStatusBarEntry.ts @@ -5,7 +5,7 @@ import * as vscode from 'vscode'; import * as nls from 'vscode-nls'; -import { Disposable } from './dispose'; +import { PreviewStatusBarEntry as OwnedStatusBarEntry } from './ownedStatusBarEntry'; const localize = nls.loadMessageBundle(); @@ -13,22 +13,18 @@ const selectZoomLevelCommandId = '_imagePreview.selectZoomLevel'; export type Scale = number | 'fit'; -export class ZoomStatusBarEntry extends Disposable { - private readonly _entry: vscode.StatusBarItem; +export class ZoomStatusBarEntry extends OwnedStatusBarEntry { private readonly _onDidChangeScale = this._register(new vscode.EventEmitter<{ scale: Scale }>()); public readonly onDidChangeScale = this._onDidChangeScale.event; - private _showOwner: string | undefined; - constructor() { - super(); - this._entry = this._register(vscode.window.createStatusBarItem({ + super({ id: 'imagePreview.zoom', name: localize('zoomStatusBar.name', "Image Zoom"), alignment: vscode.StatusBarAlignment.Right, priority: 102 /* to the left of editor size entry (101) */, - })); + }); this._register(vscode.commands.registerCommand(selectZoomLevelCommandId, async () => { type MyPickItem = vscode.QuickPickItem & { scale: Scale }; @@ -47,20 +43,11 @@ export class ZoomStatusBarEntry extends Disposable { } })); - this._entry.command = selectZoomLevelCommandId; + this.entry.command = selectZoomLevelCommandId; } public show(owner: string, scale: Scale) { - this._showOwner = owner; - this._entry.text = this.zoomLabel(scale); - this._entry.show(); - } - - public hide(owner: string) { - if (owner === this._showOwner) { - this._entry.hide(); - this._showOwner = undefined; - } + this.showItem(owner, this.zoomLabel(scale)); } private zoomLabel(scale: Scale): string { diff --git a/extensions/import/package.json b/extensions/import/package.json index f3b0fb56af43..1ba9d0571751 100644 --- a/extensions/import/package.json +++ b/extensions/import/package.json @@ -71,7 +71,7 @@ "vscode-nls": "^3.2.1" }, "devDependencies": { - "@types/node": "10" + "@types/node": "^12.11.7" }, "__metadata": { "id": "23", diff --git a/extensions/import/yarn.lock b/extensions/import/yarn.lock index 8866bfc1d3fb..b17a263ef588 100644 --- a/extensions/import/yarn.lock +++ b/extensions/import/yarn.lock @@ -2,10 +2,10 @@ # yarn lockfile v1 -"@types/node@10": - version "10.17.4" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.4.tgz#8993a4fe3c4022fda66bf4ea660d615fc5659c6f" - integrity sha512-F2pgg+LcIr/elguz+x+fdBX5KeZXGUOp7TV8M0TVIrDezYLFRNt8oMTyps0VQ1kj5WGGoR18RdxnRDHXrIFHMQ== +"@types/node@^12.11.7": + version "12.12.7" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.7.tgz#01e4ea724d9e3bd50d90c11fd5980ba317d8fa11" + integrity sha512-E6Zn0rffhgd130zbCbAr/JdXfXkoOUFAKNs/rF8qnafSJ8KYaA/j3oz7dcwal+lYjLA7xvdd5J4wdYpCTlP8+w== agent-base@4: version "4.2.1" diff --git a/extensions/integration-tests/.vscode/launch.json b/extensions/integration-tests/.vscode/launch.json index 73c3753c75c2..f858d0059111 100644 --- a/extensions/integration-tests/.vscode/launch.json +++ b/extensions/integration-tests/.vscode/launch.json @@ -14,4 +14,4 @@ "preLaunchTask": "npm" } ] -} \ No newline at end of file +} diff --git a/extensions/json-language-features/client/src/jsonMain.ts b/extensions/json-language-features/client/src/jsonMain.ts index eac6eaefe564..c51dbb7c0cb2 100644 --- a/extensions/json-language-features/client/src/jsonMain.ts +++ b/extensions/json-language-features/client/src/jsonMain.ts @@ -46,6 +46,7 @@ interface Settings { json?: { schemas?: JSONSchemaSettings[]; format?: { enable: boolean; }; + resultLimit?: number; }; http?: { proxy?: string; @@ -142,12 +143,6 @@ export function activate(context: ExtensionContext) { let disposable = client.start(); toDispose.push(disposable); client.onReady().then(() => { - disposable = client.onTelemetry(e => { - if (telemetryReporter) { - telemetryReporter.sendTelemetryEvent(e.key, e.data); - } - }); - const schemaDocuments: { [uri: string]: boolean } = {}; // handle content request @@ -161,11 +156,23 @@ export function activate(context: ExtensionContext) { return Promise.reject(error); }); } else { + if (telemetryReporter && uri.authority === 'schema.management.azure.com') { + /* __GDPR__ + "json.schema" : { + "schemaURL" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + } + */ + telemetryReporter.sendTelemetryEvent('json.schema', { schemaURL: uriPath }); + } const headers = { 'Accept-Encoding': 'gzip, deflate' }; return xhr({ url: uriPath, followRedirects: 5, headers }).then(response => { return response.responseText; }, (error: XHRResponse) => { - return Promise.reject(new ResponseError(error.status, error.responseText || getErrorStatusDescription(error.status) || error.toString())); + let extraInfo = error.responseText || error.toString(); + if (extraInfo.length > 256) { + extraInfo = `${extraInfo.substr(0, 256)}...`; + } + return Promise.reject(new ResponseError(error.status, getErrorStatusDescription(error.status) + '\n' + extraInfo)); }); } }); @@ -320,6 +327,7 @@ function getSettings(): Settings { }, json: { schemas: [], + resultLimit: 5000 } }; let schemaSettingsById: { [schemaId: string]: JSONSchemaSettings } = Object.create(null); diff --git a/extensions/json-language-features/package.json b/extensions/json-language-features/package.json index cc61c9163c54..833059f3c839 100644 --- a/extensions/json-language-features/package.json +++ b/extensions/json-language-features/package.json @@ -102,12 +102,12 @@ } }, "dependencies": { - "request-light": "^0.2.4", + "request-light": "^0.2.5", "vscode-extension-telemetry": "0.1.1", - "vscode-languageclient": "^6.0.0-next.1", + "vscode-languageclient": "^6.0.0-next.3", "vscode-nls": "^4.1.1" }, "devDependencies": { - "@types/node": "^10.14.8" + "@types/node": "^12.11.7" } } diff --git a/extensions/json-language-features/server/package.json b/extensions/json-language-features/server/package.json index f3ddc4293bb4..6dc132cc742a 100644 --- a/extensions/json-language-features/server/package.json +++ b/extensions/json-language-features/server/package.json @@ -12,16 +12,15 @@ }, "main": "./out/jsonServerMain", "dependencies": { - "jsonc-parser": "^2.1.1", - "request-light": "^0.2.4", - "vscode-json-languageservice": "^3.3.5", - "vscode-languageserver": "^6.0.0-next.1", - "vscode-nls": "^4.1.1", - "vscode-uri": "^2.0.3" + "jsonc-parser": "^2.2.0", + "request-light": "^0.2.5", + "vscode-json-languageservice": "^3.4.7", + "vscode-languageserver": "^6.0.0-next.3", + "vscode-uri": "^2.1.1" }, "devDependencies": { "@types/mocha": "2.2.33", - "@types/node": "^10.14.8" + "@types/node": "^12.11.7" }, "scripts": { "prepublishOnly": "npm run clean && npm run compile", diff --git a/extensions/json-language-features/server/src/jsonServerMain.ts b/extensions/json-language-features/server/src/jsonServerMain.ts index 52427693a41f..183ab87c8a4b 100644 --- a/extensions/json-language-features/server/src/jsonServerMain.ts +++ b/extensions/json-language-features/server/src/jsonServerMain.ts @@ -5,16 +5,18 @@ import { createConnection, IConnection, - TextDocuments, TextDocument, InitializeParams, InitializeResult, NotificationType, RequestType, - DocumentRangeFormattingRequest, Disposable, ServerCapabilities, Diagnostic + TextDocuments, InitializeParams, InitializeResult, NotificationType, RequestType, + DocumentRangeFormattingRequest, Disposable, ServerCapabilities, TextDocumentSyncKind } from 'vscode-languageserver'; import { xhr, XHRResponse, configure as configureHttpRequests, getErrorStatusDescription } from 'request-light'; import * as fs from 'fs'; import { URI } from 'vscode-uri'; import * as URL from 'url'; +import { posix } from 'path'; +import { setTimeout, clearTimeout } from 'timers'; import { formatError, runSafe, runSafeAsync } from './utils/runner'; -import { JSONDocument, JSONSchema, getLanguageService, DocumentLanguageSettings, SchemaConfiguration, ClientCapabilities } from 'vscode-json-languageservice'; +import { TextDocument, JSONDocument, JSONSchema, getLanguageService, DocumentLanguageSettings, SchemaConfiguration, ClientCapabilities, SchemaRequestService, Diagnostic } from 'vscode-json-languageservice'; import { getLanguageModelCache } from './languageModelCache'; interface ISchemaAssociations { @@ -56,40 +58,40 @@ const workspaceContext = { return URL.resolve(resource, relativePath); } }; -function getSchemaRequestService(handledSchemas: { [schema: string]: boolean }) { +const fileRequestService: SchemaRequestService = (uri: string) => { + const fsPath = URI.parse(uri).fsPath; + return new Promise((c, e) => { + fs.readFile(fsPath, 'UTF-8', (err, result) => { + err ? e(err.message || err.toString()) : c(result.toString()); + }); + }); +}; + +const httpRequestService: SchemaRequestService = (uri: string) => { + const headers = { 'Accept-Encoding': 'gzip, deflate' }; + return xhr({ url: uri, followRedirects: 5, headers }).then(response => { + return response.responseText; + }, (error: XHRResponse) => { + return Promise.reject(error.responseText || getErrorStatusDescription(error.status) || error.toString()); + }); +}; + +function getSchemaRequestService(handledSchemas: string[] = ['https', 'http', 'file']) { + const builtInHandlers: { [protocol: string]: SchemaRequestService } = {}; + for (let protocol of handledSchemas) { + if (protocol === 'file') { + builtInHandlers[protocol] = fileRequestService; + } else if (protocol === 'http' || protocol === 'https') { + builtInHandlers[protocol] = httpRequestService; + } + } return (uri: string): Thenable => { const protocol = uri.substr(0, uri.indexOf(':')); - if (!handledSchemas || handledSchemas[protocol]) { - if (protocol === 'file') { - const fsPath = URI.parse(uri).fsPath; - return new Promise((c, e) => { - fs.readFile(fsPath, 'UTF-8', (err, result) => { - err ? e(err.message || err.toString()) : c(result.toString()); - }); - }); - } else if (protocol === 'http' || protocol === 'https') { - if (uri.indexOf('//schema.management.azure.com/') !== -1) { - /* __GDPR__ - "json.schema" : { - "schemaURL" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } - } - */ - connection.telemetry.logEvent({ - key: 'json.schema', - value: { - schemaURL: uri - } - }); - } - const headers = { 'Accept-Encoding': 'gzip, deflate' }; - return xhr({ url: uri, followRedirects: 5, headers }).then(response => { - return response.responseText; - }, (error: XHRResponse) => { - return Promise.reject(error.responseText || getErrorStatusDescription(error.status) || error.toString()); - }); - } + const builtInHandler = builtInHandlers[protocol]; + if (builtInHandler) { + return builtInHandler(uri); } return connection.sendRequest(VSCodeContentRequest.type, uri).then(responseText => { return responseText; @@ -107,7 +109,7 @@ let languageService = getLanguageService({ }); // Create a text document manager. -const documents: TextDocuments = new TextDocuments(); +const documents = new TextDocuments(TextDocument); // Make the text document manager listen on the connection // for open, change and close text document events @@ -115,9 +117,12 @@ documents.listen(connection); let clientSnippetSupport = false; let dynamicFormatterRegistration = false; -let foldingRangeLimit = Number.MAX_VALUE; let hierarchicalDocumentSymbolSupport = false; +let foldingRangeLimitDefault = Number.MAX_VALUE; +let foldingRangeLimit = Number.MAX_VALUE; +let resultLimit = Number.MAX_VALUE; + // After the server has started the client sends an initialize request. The server receives // in the passed params the rootPath of the workspace plus the client capabilities. connection.onInitialize((params: InitializeParams): InitializeResult => { @@ -145,11 +150,10 @@ connection.onInitialize((params: InitializeParams): InitializeResult => { clientSnippetSupport = getClientCapability('textDocument.completion.completionItem.snippetSupport', false); dynamicFormatterRegistration = getClientCapability('textDocument.rangeFormatting.dynamicRegistration', false) && (typeof params.initializationOptions.provideFormatter !== 'boolean'); - foldingRangeLimit = getClientCapability('textDocument.foldingRange.rangeLimit', Number.MAX_VALUE); + foldingRangeLimitDefault = getClientCapability('textDocument.foldingRange.rangeLimit', Number.MAX_VALUE); hierarchicalDocumentSymbolSupport = getClientCapability('textDocument.documentSymbol.hierarchicalDocumentSymbolSupport', false); const capabilities: ServerCapabilities = { - // Tell the client that the server works in FULL text document sync mode - textDocumentSync: documents.syncKind, + textDocumentSync: TextDocumentSyncKind.Incremental, completionProvider: clientSnippetSupport ? { resolveProvider: true, triggerCharacters: ['"', ':'] } : undefined, hoverProvider: true, documentSymbolProvider: true, @@ -169,6 +173,7 @@ interface Settings { json: { schemas: JSONSchemaSettings[]; format: { enable: boolean; }; + resultLimit?: number; }; http: { proxy: string; @@ -182,6 +187,39 @@ interface JSONSchemaSettings { schema?: JSONSchema; } +namespace LimitExceededWarnings { + const pendingWarnings: { [uri: string]: { features: { [name: string]: string }; timeout?: NodeJS.Timeout; } } = {}; + + export function cancel(uri: string) { + const warning = pendingWarnings[uri]; + if (warning && warning.timeout) { + clearTimeout(warning.timeout); + delete pendingWarnings[uri]; + } + } + + export function onResultLimitExceeded(uri: string, resultLimit: number, name: string) { + return () => { + let warning = pendingWarnings[uri]; + if (warning) { + if (!warning.timeout) { + // already shown + return; + } + warning.features[name] = name; + warning.timeout.refresh(); + } else { + warning = { features: { [name]: name } }; + warning.timeout = setTimeout(() => { + connection.window.showInformationMessage(`${posix.basename(uri)}: For performance reasons, ${Object.keys(warning.features).join(' and ')} have been limited to ${resultLimit} items.`); + warning.timeout = undefined; + }, 2000); + pendingWarnings[uri] = warning; + } + }; + } +} + let jsonConfigurationSettings: JSONSchemaSettings[] | undefined = undefined; let schemaAssociations: ISchemaAssociations | undefined = undefined; let formatterRegistration: Thenable | null = null; @@ -194,6 +232,9 @@ connection.onDidChangeConfiguration((change) => { jsonConfigurationSettings = settings.json && settings.json.schemas; updateConfiguration(); + foldingRangeLimit = Math.trunc(Math.max(settings.json && settings.json.resultLimit || foldingRangeLimitDefault, 0)); + resultLimit = Math.trunc(Math.max(settings.json && settings.json.resultLimit || Number.MAX_VALUE, 0)); + // dynamically enable & disable the formatter if (dynamicFormatterRegistration) { const enableFormatter = settings && settings.json && settings.json.format && settings.json.format.enable; @@ -270,11 +311,13 @@ function updateConfiguration() { // The content of a text document has changed. This event is emitted // when the text document first opened or when its content has changed. documents.onDidChangeContent((change) => { + LimitExceededWarnings.cancel(change.document.uri); triggerValidation(change.document); }); // a document has closed: clear all diagnostics documents.onDidClose(event => { + LimitExceededWarnings.cancel(event.document.uri); cleanPendingValidation(event.document); connection.sendDiagnostics({ uri: event.document.uri, diagnostics: [] }); }); @@ -383,10 +426,11 @@ connection.onDocumentSymbol((documentSymbolParams, token) => { const document = documents.get(documentSymbolParams.textDocument.uri); if (document) { const jsonDocument = getJSONDocument(document); + const onResultLimitExceeded = LimitExceededWarnings.onResultLimitExceeded(document.uri, resultLimit, 'document symbols'); if (hierarchicalDocumentSymbolSupport) { - return languageService.findDocumentSymbols2(document, jsonDocument); + return languageService.findDocumentSymbols2(document, jsonDocument, { resultLimit, onResultLimitExceeded }); } else { - return languageService.findDocumentSymbols(document, jsonDocument); + return languageService.findDocumentSymbols(document, jsonDocument, { resultLimit, onResultLimitExceeded }); } } return []; @@ -407,8 +451,9 @@ connection.onDocumentColor((params, token) => { return runSafeAsync(async () => { const document = documents.get(params.textDocument.uri); if (document) { + const onResultLimitExceeded = LimitExceededWarnings.onResultLimitExceeded(document.uri, resultLimit, 'document colors'); const jsonDocument = getJSONDocument(document); - return languageService.findDocumentColors(document, jsonDocument); + return languageService.findDocumentColors(document, jsonDocument, { resultLimit, onResultLimitExceeded }); } return []; }, [], `Error while computing document colors for ${params.textDocument.uri}`, token); @@ -429,7 +474,8 @@ connection.onFoldingRanges((params, token) => { return runSafe(() => { const document = documents.get(params.textDocument.uri); if (document) { - return languageService.getFoldingRanges(document, { rangeLimit: foldingRangeLimit }); + const onRangeLimitExceeded = LimitExceededWarnings.onResultLimitExceeded(document.uri, foldingRangeLimit, 'folding ranges'); + return languageService.getFoldingRanges(document, { rangeLimit: foldingRangeLimit, onRangeLimitExceeded }); } return null; }, null, `Error while computing folding ranges for ${params.textDocument.uri}`, token); diff --git a/extensions/json-language-features/server/yarn.lock b/extensions/json-language-features/server/yarn.lock index 7cb33a9e778c..f0fde79ab0cb 100644 --- a/extensions/json-language-features/server/yarn.lock +++ b/extensions/json-language-features/server/yarn.lock @@ -7,10 +7,10 @@ resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-2.2.33.tgz#d79a0061ec270379f4d9e225f4096fb436669def" integrity sha1-15oAYewnA3n02eIl9AlvtDZmne8= -"@types/node@^10.14.8": - version "10.14.8" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.14.8.tgz#fe444203ecef1162348cd6deb76c62477b2cc6e9" - integrity sha512-I4+DbJEhLEg4/vIy/2gkWDvXBOOtPKV9EnLhYjMoqxcRW+TTZtUftkHktz/a8suoD5mUL7m6ReLrkPvSsCQQmw== +"@types/node@^12.11.7": + version "12.11.7" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.11.7.tgz#57682a9771a3f7b09c2497f28129a0462966524a" + integrity sha512-JNbGaHFCLwgHn/iCckiGSOZ1XYHsKFwREtzPwSGCVld1SGhOlmZw2D4ZI94HQCrBHbADzW9m4LER/8olJTRGHA== agent-base@4: version "4.1.2" @@ -53,7 +53,7 @@ http-proxy-agent@^2.1.0: agent-base "4" debug "3.1.0" -https-proxy-agent@^2.2.1: +https-proxy-agent@^2.2.3: version "2.2.4" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz#4ee7a737abd92678a293d9b34a1af4d0d08c787b" integrity sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg== @@ -61,77 +61,77 @@ https-proxy-agent@^2.2.1: agent-base "^4.3.0" debug "^3.1.0" -jsonc-parser@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.1.1.tgz#83dc3d7a6e7186346b889b1280eefa04446c6d3e" - integrity sha512-VC0CjnWJylKB1iov4u76/W/5Ef0ydDkjtYWxoZ9t3HdWlSnZQwZL5MgFikaB/EtQ4RmMEw3tmQzuYnZA2/Ja1g== +jsonc-parser@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.2.0.tgz#f206f87f9d49d644b7502052c04e82dd6392e9ef" + integrity sha512-4fLQxW1j/5fWj6p78vAlAafoCKtuBm6ghv+Ij5W2DrDx0qE+ZdEl2c6Ko1mgJNF5ftX1iEWQQ4Ap7+3GlhjkOA== ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= -request-light@^0.2.4: - version "0.2.4" - resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.2.4.tgz#3cea29c126682e6bcadf7915353322eeba01a755" - integrity sha512-pM9Fq5jRnSb+82V7M97rp8FE9/YNeP2L9eckB4Szd7lyeclSIx02aIpPO/6e4m6Dy31+FBN/zkFMTd2HkNO3ow== +request-light@^0.2.5: + version "0.2.5" + resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.2.5.tgz#38a3da7b2e56f7af8cbba57e8a94930ee2380746" + integrity sha512-eBEh+GzJAftUnex6tcL6eV2JCifY0+sZMIUpUPOVXbs2nV5hla4ZMmO3icYKGuGVuQ2zHE9evh4OrRcH4iyYYw== dependencies: http-proxy-agent "^2.1.0" - https-proxy-agent "^2.2.1" - vscode-nls "^4.0.0" + https-proxy-agent "^2.2.3" + vscode-nls "^4.1.1" -vscode-json-languageservice@^3.3.5: - version "3.3.5" - resolved "https://registry.yarnpkg.com/vscode-json-languageservice/-/vscode-json-languageservice-3.3.5.tgz#e222e8391beeb23cfa40cf17fd57d1594d295fc7" - integrity sha512-Le6SG5aRdrRc5jVeVMRkYbGH9rrVaZHCW0Oa8zCFQ0T8viUud9qdZ29lSv5NPNLwTB8mn4pYucFyyEPM2YWvLA== +vscode-json-languageservice@^3.4.7: + version "3.4.7" + resolved "https://registry.yarnpkg.com/vscode-json-languageservice/-/vscode-json-languageservice-3.4.7.tgz#8d85f3c1d46a1e58e9867d747552fb8c83d934fd" + integrity sha512-y3MN2+/yph3yoIHGmHu4ScYpm285L58XVvfGkd49xTQzLja4apxSbwzsYcP9QsqS0W7KuvoyiPhqksiudoMwjg== dependencies: - jsonc-parser "^2.1.1" - vscode-languageserver-types "^3.15.0-next.5" + jsonc-parser "^2.2.0" + vscode-languageserver-textdocument "^1.0.0-next.4" + vscode-languageserver-types "^3.15.0-next.6" vscode-nls "^4.1.1" - vscode-uri "^2.0.3" + vscode-uri "^2.1.0" vscode-jsonrpc@^5.0.0-next.2: version "5.0.0-next.2" resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-5.0.0-next.2.tgz#a44bc03f67069e53f8d8beb88b96c0cacbfefbca" integrity sha512-Q3/jabZUNviCG9hhF6hHWjhrABevPF9mv0aiE2j8BYCAP2k+aHTpjMyk+04MzaAqWYwXdQuZkLSbcYCCqbzJLg== -vscode-languageserver-protocol@^3.15.0-next.9: - version "3.15.0-next.9" - resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.15.0-next.9.tgz#e768256bd5b580b25bfbc8099bc03bc4c42ebf30" - integrity sha512-b9PAxouMmtsLEe8ZjbIMPb7wRWPhckGfgjwZLmp/dWnaAuRPYtY3lGO0/rNbLc3jKIqCVlnEyYVFKalzDAzj0g== +vscode-languageserver-protocol@^3.15.0-next.10: + version "3.15.0-next.10" + resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.15.0-next.10.tgz#f1382f0c270ae5d0c2c7e552483285fb75810914" + integrity sha512-TmbBhKrBoYNX+/pQGwoXmy2qlOfjGBUhwUGIzQoWpj8qtDzYuLof8bi19rGLZ9sVuSHh3anvIyVpGJEqT0QODQ== dependencies: vscode-jsonrpc "^5.0.0-next.2" - vscode-languageserver-types "^3.15.0-next.5" - -vscode-languageserver-types@^3.15.0-next.5: - version "3.15.0-next.5" - resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.15.0-next.5.tgz#863d711bf47b338ff5e63ae19fb20d4fcd4d713b" - integrity sha512-7hrELhTeWieUgex3+6692KjCkcmO/+V/bFItM5MHGcBotzwmjEuXjapLLYTYhIspuJ1ibRSik5MhX5YwLpsPiw== - -vscode-languageserver@^6.0.0-next.1: - version "6.0.0-next.1" - resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-6.0.0-next.1.tgz#4d71886d4a17d22eafc61b3a5fbf84e8e27c191f" - integrity sha512-LSF6bXoFeXfMPRNyqzI3yFX/kD2DzXBemqvyj1kDWNVraiWttm4xKF4YXsvJ7Z3s9sVt/Dpu3CFU3w61PGNZMg== + vscode-languageserver-types "^3.15.0-next.6" + +vscode-languageserver-textdocument@^1.0.0-next.4: + version "1.0.0-next.4" + resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.0-next.4.tgz#8f7afdfe3e81411f57baaa29bb3214d1907160cd" + integrity sha512-LJ5WfoBO54nqinjlLJKnjoo2Im4bIvPJ8bFT7R0C84ZI36iK8M29ddslfe5jUeWNSTtCda7YuKdKsDIq38HpgA== + +vscode-languageserver-types@^3.15.0-next.6: + version "3.15.0-next.6" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.15.0-next.6.tgz#7a990d00c39ad4e744335afb4cc422a3e687ff25" + integrity sha512-+4jfvmZ26oFMSX6EgPRB75PWHoT8pzyWuSSWk0erC4hTzmJq2gWxVLh20bZutZjMmiivawvPshtM3XZhX2SttA== + +vscode-languageserver@^6.0.0-next.3: + version "6.0.0-next.3" + resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-6.0.0-next.3.tgz#41e2fda6417939792f6a19fc19ecbb2f080e2072" + integrity sha512-Q6T+KwYuoXV9KRHD6x7RfTU13pV0xAX2BtcuvSC/LBCiVAnEIOe7jKZjzya+B9gDvSk4hpfvhPefy5IdQK1mpQ== dependencies: - vscode-languageserver-protocol "^3.15.0-next.9" - vscode-textbuffer "^1.0.0" - -vscode-nls@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.0.0.tgz#4001c8a6caba5cedb23a9c5ce1090395c0e44002" - integrity sha512-qCfdzcH+0LgQnBpZA53bA32kzp9rpq/f66Som577ObeuDlFIrtbEJ+A/+CCxjIh4G8dpJYNCKIsxpRAHIfsbNw== + vscode-languageserver-protocol "^3.15.0-next.10" vscode-nls@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.1.1.tgz#f9916b64e4947b20322defb1e676a495861f133c" integrity sha512-4R+2UoUUU/LdnMnFjePxfLqNhBS8lrAFyX7pjb2ud/lqDkrUavFUTcG7wR0HBZFakae0Q6KLBFjMS6W93F403A== -vscode-textbuffer@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/vscode-textbuffer/-/vscode-textbuffer-1.0.0.tgz#1faee638c8e0e4131c8d5c353993a1874acda086" - integrity sha512-zPaHo4urgpwsm+PrJWfNakolRpryNja18SUip/qIIsfhuEqEIPEXMxHOlFPjvDC4JgTaimkncNW7UMXRJTY6ow== +vscode-uri@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-2.1.0.tgz#475a4269e63edbc13914b40c84bc1416e3398156" + integrity sha512-3voe44nOhb6OdKlpZShVsmVvY2vFQHMe6REP3Ky9RVJuPyM/XidsjH6HncCIDdSmbcF5YQHrTC/Q+Q2loJGkOw== -vscode-uri@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-2.0.3.tgz#25e5f37f552fbee3cec7e5f80cef8469cefc6543" - integrity sha512-4D3DI3F4uRy09WNtDGD93H9q034OHImxiIcSq664Hq1Y1AScehlP3qqZyTkX/RWxeu0MRMHGkrxYqm2qlDF/aw== +vscode-uri@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-2.1.1.tgz#5aa1803391b6ebdd17d047f51365cf62c38f6e90" + integrity sha512-eY9jmGoEnVf8VE8xr5znSah7Qt1P/xsCdErz+g8HYZtJ7bZqKH5E3d+6oVNm1AC/c6IHUDokbmVXKOi4qPAC9A== diff --git a/extensions/json-language-features/yarn.lock b/extensions/json-language-features/yarn.lock index ae47010432e9..27e4f7cf883d 100644 --- a/extensions/json-language-features/yarn.lock +++ b/extensions/json-language-features/yarn.lock @@ -2,10 +2,10 @@ # yarn lockfile v1 -"@types/node@^10.14.8": - version "10.14.8" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.14.8.tgz#fe444203ecef1162348cd6deb76c62477b2cc6e9" - integrity sha512-I4+DbJEhLEg4/vIy/2gkWDvXBOOtPKV9EnLhYjMoqxcRW+TTZtUftkHktz/a8suoD5mUL7m6ReLrkPvSsCQQmw== +"@types/node@^12.11.7": + version "12.11.7" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.11.7.tgz#57682a9771a3f7b09c2497f28129a0462966524a" + integrity sha512-JNbGaHFCLwgHn/iCckiGSOZ1XYHsKFwREtzPwSGCVld1SGhOlmZw2D4ZI94HQCrBHbADzW9m4LER/8olJTRGHA== agent-base@4: version "4.2.1" @@ -76,7 +76,7 @@ http-proxy-agent@^2.1.0: agent-base "4" debug "3.1.0" -https-proxy-agent@^2.2.1: +https-proxy-agent@^2.2.3: version "2.2.4" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz#4ee7a737abd92678a293d9b34a1af4d0d08c787b" integrity sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg== @@ -94,14 +94,14 @@ ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== -request-light@^0.2.4: - version "0.2.4" - resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.2.4.tgz#3cea29c126682e6bcadf7915353322eeba01a755" - integrity sha512-pM9Fq5jRnSb+82V7M97rp8FE9/YNeP2L9eckB4Szd7lyeclSIx02aIpPO/6e4m6Dy31+FBN/zkFMTd2HkNO3ow== +request-light@^0.2.5: + version "0.2.5" + resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.2.5.tgz#38a3da7b2e56f7af8cbba57e8a94930ee2380746" + integrity sha512-eBEh+GzJAftUnex6tcL6eV2JCifY0+sZMIUpUPOVXbs2nV5hla4ZMmO3icYKGuGVuQ2zHE9evh4OrRcH4iyYYw== dependencies: http-proxy-agent "^2.1.0" - https-proxy-agent "^2.2.1" - vscode-nls "^4.0.0" + https-proxy-agent "^2.2.3" + vscode-nls "^4.1.1" semver@^5.3.0: version "5.5.0" @@ -125,31 +125,26 @@ vscode-jsonrpc@^5.0.0-next.2: resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-5.0.0-next.2.tgz#a44bc03f67069e53f8d8beb88b96c0cacbfefbca" integrity sha512-Q3/jabZUNviCG9hhF6hHWjhrABevPF9mv0aiE2j8BYCAP2k+aHTpjMyk+04MzaAqWYwXdQuZkLSbcYCCqbzJLg== -vscode-languageclient@^6.0.0-next.1: - version "6.0.0-next.1" - resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-6.0.0-next.1.tgz#deca1743afd20da092e04e40ef73cedbbd978455" - integrity sha512-eJ9VjLFNINArgRzLbQ11YlWry7dM93GEODkQBXTRfrSypksiO9qSGr4SHhWgxxP26p4FRSpzc/17+N+Egnnchg== +vscode-languageclient@^6.0.0-next.3: + version "6.0.0-next.3" + resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-6.0.0-next.3.tgz#41b701d963fc7affc01e9279532a747fcd4f3810" + integrity sha512-SuSaG9xjqkROm4Ie0jQig0CFDuU/WxHERegl3kRsFHDbhMSK4dH45ZeBY5zMWUgZ+LrIrEbwf8qWNlrTRBlUgg== dependencies: semver "^6.3.0" - vscode-languageserver-protocol "^3.15.0-next.9" + vscode-languageserver-protocol "^3.15.0-next.10" -vscode-languageserver-protocol@^3.15.0-next.9: - version "3.15.0-next.9" - resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.15.0-next.9.tgz#e768256bd5b580b25bfbc8099bc03bc4c42ebf30" - integrity sha512-b9PAxouMmtsLEe8ZjbIMPb7wRWPhckGfgjwZLmp/dWnaAuRPYtY3lGO0/rNbLc3jKIqCVlnEyYVFKalzDAzj0g== +vscode-languageserver-protocol@^3.15.0-next.10: + version "3.15.0-next.10" + resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.15.0-next.10.tgz#f1382f0c270ae5d0c2c7e552483285fb75810914" + integrity sha512-TmbBhKrBoYNX+/pQGwoXmy2qlOfjGBUhwUGIzQoWpj8qtDzYuLof8bi19rGLZ9sVuSHh3anvIyVpGJEqT0QODQ== dependencies: vscode-jsonrpc "^5.0.0-next.2" - vscode-languageserver-types "^3.15.0-next.5" + vscode-languageserver-types "^3.15.0-next.6" -vscode-languageserver-types@^3.15.0-next.5: - version "3.15.0-next.5" - resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.15.0-next.5.tgz#863d711bf47b338ff5e63ae19fb20d4fcd4d713b" - integrity sha512-7hrELhTeWieUgex3+6692KjCkcmO/+V/bFItM5MHGcBotzwmjEuXjapLLYTYhIspuJ1ibRSik5MhX5YwLpsPiw== - -vscode-nls@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.0.0.tgz#4001c8a6caba5cedb23a9c5ce1090395c0e44002" - integrity sha512-qCfdzcH+0LgQnBpZA53bA32kzp9rpq/f66Som577ObeuDlFIrtbEJ+A/+CCxjIh4G8dpJYNCKIsxpRAHIfsbNw== +vscode-languageserver-types@^3.15.0-next.6: + version "3.15.0-next.6" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.15.0-next.6.tgz#7a990d00c39ad4e744335afb4cc422a3e687ff25" + integrity sha512-+4jfvmZ26oFMSX6EgPRB75PWHoT8pzyWuSSWk0erC4hTzmJq2gWxVLh20bZutZjMmiivawvPshtM3XZhX2SttA== vscode-nls@^4.1.1: version "4.1.1" diff --git a/extensions/json/syntaxes/JSON.tmLanguage.json b/extensions/json/syntaxes/JSON.tmLanguage.json index d296aac33ebc..910045be39ee 100644 --- a/extensions/json/syntaxes/JSON.tmLanguage.json +++ b/extensions/json/syntaxes/JSON.tmLanguage.json @@ -5,7 +5,7 @@ "Once accepted there, we are happy to receive an update request." ], "version": "https://github.com/Microsoft/vscode-JSON.tmLanguage/commit/9bd83f1c252b375e957203f21793316203f61f70", - "name": "JSON (JavaScript Next)", + "name": "JSON (Javascript Next)", "scopeName": "source.json", "patterns": [ { @@ -210,4 +210,4 @@ ] } } -} +} \ No newline at end of file diff --git a/extensions/liveshare/package.json b/extensions/liveshare/package.json index 1f2b11c96dbe..f0daa36908da 100644 --- a/extensions/liveshare/package.json +++ b/extensions/liveshare/package.json @@ -21,7 +21,7 @@ "compile": "gulp compile-extension:liveshare" }, "devDependencies": { - "@types/node": "12.0.9", + "@types/node": "^12.11.7", "ts-loader": "^5.3.3", "tslint": "^5.12.1", "typescript": "^3.3.1" diff --git a/extensions/liveshare/yarn.lock b/extensions/liveshare/yarn.lock index d01bdeabdc4d..aa202a3d63a0 100644 --- a/extensions/liveshare/yarn.lock +++ b/extensions/liveshare/yarn.lock @@ -18,10 +18,10 @@ esutils "^2.0.2" js-tokens "^4.0.0" -"@types/node@12.0.9": - version "12.0.9" - resolved "https://registry.yarnpkg.com/@types/node/-/node-12.0.9.tgz#18e0fc7bc6acc71f43a1a6ec9096c30d3954dd5c" - integrity sha512-xxrghIb6jMoEkNtdzGMUezwCEGuBd4QSA/Fko1XaUYpn6P/LwVw7UGpf4NzwGZXRC96fDgBJcBX7bXU0T52nWA== +"@types/node@^12.11.7": + version "12.12.7" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.7.tgz#01e4ea724d9e3bd50d90c11fd5980ba317d8fa11" + integrity sha512-E6Zn0rffhgd130zbCbAr/JdXfXkoOUFAKNs/rF8qnafSJ8KYaA/j3oz7dcwal+lYjLA7xvdd5J4wdYpCTlP8+w== ansi-styles@^3.2.1: version "3.2.1" diff --git a/extensions/markdown-basics/syntaxes/markdown.tmLanguage.json b/extensions/markdown-basics/syntaxes/markdown.tmLanguage.json index 269140f643bb..477fff231314 100644 --- a/extensions/markdown-basics/syntaxes/markdown.tmLanguage.json +++ b/extensions/markdown-basics/syntaxes/markdown.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/microsoft/vscode-markdown-tm-grammar/commit/46724e2885f9557400ed91727d75c3574ceded3a", + "version": "https://github.com/microsoft/vscode-markdown-tm-grammar/commit/e3091a421bdcad527018c897652ded47585cbd12", "name": "Markdown", "scopeName": "text.html.markdown", "patterns": [ @@ -63,7 +63,7 @@ "while": "(^|\\G)\\s*(>) ?" }, "fenced_code_block_css": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(css|css.erb)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(css|css.erb)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -96,7 +96,7 @@ ] }, "fenced_code_block_basic": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(html|htm|shtml|xhtml|inc|tmpl|tpl)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(html|htm|shtml|xhtml|inc|tmpl|tpl)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -129,7 +129,7 @@ ] }, "fenced_code_block_ini": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(ini|conf)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(ini|conf)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -162,7 +162,7 @@ ] }, "fenced_code_block_java": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(java|bsh)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(java|bsh)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -195,7 +195,7 @@ ] }, "fenced_code_block_lua": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(lua)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(lua)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -228,7 +228,7 @@ ] }, "fenced_code_block_makefile": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(Makefile|makefile|GNUmakefile|OCamlMakefile)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(Makefile|makefile|GNUmakefile|OCamlMakefile)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -261,7 +261,7 @@ ] }, "fenced_code_block_perl": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(perl|pl|pm|pod|t|PL|psgi|vcl)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(perl|pl|pm|pod|t|PL|psgi|vcl)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -294,7 +294,7 @@ ] }, "fenced_code_block_r": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(R|r|s|S|Rprofile|\\{\\.r.+?\\})(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(R|r|s|S|Rprofile|\\{\\.r.+?\\})((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -327,7 +327,7 @@ ] }, "fenced_code_block_ruby": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(ruby|rb|rbx|rjs|Rakefile|rake|cgi|fcgi|gemspec|irbrc|Capfile|ru|prawn|Cheffile|Gemfile|Guardfile|Hobofile|Vagrantfile|Appraisals|Rantfile|Berksfile|Berksfile.lock|Thorfile|Puppetfile)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(ruby|rb|rbx|rjs|Rakefile|rake|cgi|fcgi|gemspec|irbrc|Capfile|ru|prawn|Cheffile|Gemfile|Guardfile|Hobofile|Vagrantfile|Appraisals|Rantfile|Berksfile|Berksfile.lock|Thorfile|Puppetfile)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -360,7 +360,7 @@ ] }, "fenced_code_block_php": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(php|php3|php4|php5|phpt|phtml|aw|ctp)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(php|php3|php4|php5|phpt|phtml|aw|ctp)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -396,7 +396,7 @@ ] }, "fenced_code_block_sql": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(sql|ddl|dml)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(sql|ddl|dml)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -429,7 +429,7 @@ ] }, "fenced_code_block_vs_net": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(vb)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(vb)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -462,7 +462,7 @@ ] }, "fenced_code_block_xml": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(xml|xsd|tld|jsp|pt|cpt|dtml|rss|opml)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(xml|xsd|tld|jsp|pt|cpt|dtml|rss|opml)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -495,7 +495,7 @@ ] }, "fenced_code_block_xsl": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(xsl|xslt)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(xsl|xslt)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -528,7 +528,7 @@ ] }, "fenced_code_block_yaml": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(yaml|yml)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(yaml|yml)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -561,7 +561,7 @@ ] }, "fenced_code_block_dosbatch": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(bat|batch)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(bat|batch)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -594,7 +594,7 @@ ] }, "fenced_code_block_clojure": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(clj|cljs|clojure)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(clj|cljs|clojure)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -627,7 +627,7 @@ ] }, "fenced_code_block_coffee": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(coffee|Cakefile|coffee.erb)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(coffee|Cakefile|coffee.erb)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -660,7 +660,7 @@ ] }, "fenced_code_block_c": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(c|h)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(c|h)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -693,7 +693,7 @@ ] }, "fenced_code_block_cpp": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(cpp|c\\+\\+|cxx)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(cpp|c\\+\\+|cxx)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -726,7 +726,7 @@ ] }, "fenced_code_block_diff": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(patch|diff|rej)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(patch|diff|rej)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -759,7 +759,7 @@ ] }, "fenced_code_block_dockerfile": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(dockerfile|Dockerfile)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(dockerfile|Dockerfile)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -792,7 +792,7 @@ ] }, "fenced_code_block_git_commit": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(COMMIT_EDITMSG|MERGE_MSG)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(COMMIT_EDITMSG|MERGE_MSG)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -825,7 +825,7 @@ ] }, "fenced_code_block_git_rebase": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(git-rebase-todo)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(git-rebase-todo)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -858,7 +858,7 @@ ] }, "fenced_code_block_go": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(go|golang)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(go|golang)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -891,7 +891,7 @@ ] }, "fenced_code_block_groovy": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(groovy|gvy)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(groovy|gvy)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -924,7 +924,7 @@ ] }, "fenced_code_block_pug": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(jade|pug)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(jade|pug)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -957,7 +957,7 @@ ] }, "fenced_code_block_js": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(js|jsx|javascript|es6|mjs|\\{\\.js.+?\\})(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(js|jsx|javascript|es6|mjs|cjs|\\{\\.js.+?\\})((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -990,7 +990,7 @@ ] }, "fenced_code_block_js_regexp": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(regexp)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(regexp)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1023,7 +1023,7 @@ ] }, "fenced_code_block_json": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(json|json5|sublime-settings|sublime-menu|sublime-keymap|sublime-mousemap|sublime-theme|sublime-build|sublime-project|sublime-completions)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(json|json5|sublime-settings|sublime-menu|sublime-keymap|sublime-mousemap|sublime-theme|sublime-build|sublime-project|sublime-completions)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1056,7 +1056,7 @@ ] }, "fenced_code_block_jsonc": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(jsonc)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(jsonc)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1089,7 +1089,7 @@ ] }, "fenced_code_block_less": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(less)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(less)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1122,7 +1122,7 @@ ] }, "fenced_code_block_objc": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(objectivec|objective-c|mm|objc|obj-c|m|h)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(objectivec|objective-c|mm|objc|obj-c|m|h)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1155,7 +1155,7 @@ ] }, "fenced_code_block_swift": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(swift)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(swift)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1188,7 +1188,7 @@ ] }, "fenced_code_block_scss": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(scss)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(scss)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1221,7 +1221,7 @@ ] }, "fenced_code_block_perl6": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(perl6|p6|pl6|pm6|nqp)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(perl6|p6|pl6|pm6|nqp)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1254,7 +1254,7 @@ ] }, "fenced_code_block_powershell": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(powershell|ps1|psm1|psd1)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(powershell|ps1|psm1|psd1)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1287,7 +1287,7 @@ ] }, "fenced_code_block_python": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(python|py|py3|rpy|pyw|cpy|SConstruct|Sconstruct|sconstruct|SConscript|gyp|gypi|\\{\\.python.+?\\})(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(python|py|py3|rpy|pyw|cpy|SConstruct|Sconstruct|sconstruct|SConscript|gyp|gypi|\\{\\.python.+?\\})((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1320,7 +1320,7 @@ ] }, "fenced_code_block_regexp_python": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(re)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(re)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1353,7 +1353,7 @@ ] }, "fenced_code_block_rust": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(rust|rs|\\{\\.rust.+?\\})(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(rust|rs|\\{\\.rust.+?\\})((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1386,7 +1386,7 @@ ] }, "fenced_code_block_scala": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(scala|sbt)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(scala|sbt)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1419,7 +1419,7 @@ ] }, "fenced_code_block_shell": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(shell|sh|bash|zsh|bashrc|bash_profile|bash_login|profile|bash_logout|.textmate_init|\\{\\.bash.+?\\})(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(shell|sh|bash|zsh|bashrc|bash_profile|bash_login|profile|bash_logout|.textmate_init|\\{\\.bash.+?\\})((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1452,7 +1452,7 @@ ] }, "fenced_code_block_ts": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(typescript|ts)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(typescript|ts)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1485,7 +1485,7 @@ ] }, "fenced_code_block_tsx": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(tsx)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(tsx)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1518,7 +1518,7 @@ ] }, "fenced_code_block_csharp": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(cs|csharp|c#)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(cs|csharp|c#)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1551,7 +1551,7 @@ ] }, "fenced_code_block_fsharp": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(fs|fsharp|f#)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(fs|fsharp|f#)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1584,7 +1584,7 @@ ] }, "fenced_code_block_dart": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(dart)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(dart)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1617,7 +1617,7 @@ ] }, "fenced_code_block_handlebars": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(handlebars|hbs)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(handlebars|hbs)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1650,7 +1650,7 @@ ] }, "fenced_code_block_markdown": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(markdown|md)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(markdown|md)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1683,7 +1683,7 @@ ] }, "fenced_code_block_log": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(log)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(log)((\\s+|:|\\{)[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -2623,4 +2623,4 @@ "name": "markup.inline.raw.string.markdown" } } -} \ No newline at end of file +} diff --git a/extensions/markdown-language-features/media/index.js b/extensions/markdown-language-features/media/index.js index a274cb72d8f4..1aa7530d3412 100644 --- a/extensions/markdown-language-features/media/index.js +++ b/extensions/markdown-language-features/media/index.js @@ -1,2 +1,2 @@ -!function(e){var t={};function n(o){if(t[o])return t[o].exports;var i=t[o]={i:o,l:!1,exports:{}};return e[o].call(i.exports,i,i.exports,n),i.l=!0,i.exports}n.m=e,n.c=t,n.d=function(e,t,o){n.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:o})},n.r=function(e){Object.defineProperty(e,"__esModule",{value:!0})},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=11)}([function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});let o=void 0;function i(e){const t=document.getElementById("vscode-markdown-preview-data");if(t){const n=t.getAttribute(e);if(n)return JSON.parse(n)}throw new Error(`Could not load data for ${e}`)}t.getData=i,t.getSettings=function(){if(o)return o;if(o=i("data-settings"))return o;throw new Error("Could not load settings")}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const o=n(0);function i(e){return t=0,n=o.getSettings().lineCount-1,i=e,Math.min(n,Math.max(t,i));var t,n,i}const r=(()=>{let e;return()=>{if(!e){e=[{element:document.body,line:0}];for(const t of document.getElementsByClassName("code-line")){const n=+t.getAttribute("data-line");isNaN(n)||e.push({element:t,line:n})}}return e}})();function s(e){const t=Math.floor(e),n=r();let o=n[0]||null;for(const e of n){if(e.line===t)return{previous:e,next:void 0};if(e.line>t)return{previous:o,next:e};o=e}return{previous:o}}function c(e){const t=r(),n=e-window.scrollY;let o=-1,i=t.length-1;for(;o+1=n?i=e:o=e}const s=t[i],c=s.element.getBoundingClientRect();if(i>=1&&c.top>n){return{previous:t[o],next:s}}return{previous:s}}t.getElementsForSourceLine=s,t.getLineElementsAtPageOffset=c,t.scrollToRevealSourceLine=function(e){if(!o.getSettings().scrollPreviewWithEditor)return;if(e<=0)return void window.scroll(window.scrollX,0);const{previous:t,next:n}=s(e);if(!t)return;let i=0;const r=t.element.getBoundingClientRect(),c=r.top;if(n&&n.line!==t.line)i=c+(e-t.line)/(n.line-t.line)*(n.element.getBoundingClientRect().top-c);else{const t=e-Math.floor(e);i=c+r.height*t}window.scroll(window.scrollX,Math.max(1,window.scrollY+i))},t.getEditorLineNumberForPageOffset=function(e){const{previous:t,next:n}=c(e);if(t){const o=t.element.getBoundingClientRect(),r=e-window.scrollY-o.top;if(n){const e=r/(n.element.getBoundingClientRect().top-o.top);return i(t.line+e*(n.line-t.line))}{const e=r/o.height;return i(t.line+e)}}return null},t.getLineElementForFragment=function(e){return r().find(t=>t.element.id===e)}},,,,,function(e,t){var n;n=function(){return this}();try{n=n||Function("return this")()||(0,eval)("this")}catch(e){"object"==typeof window&&(n=window)}e.exports=n},function(e,t,n){(function(t){var n="Expected a function",o=NaN,i="[object Symbol]",r=/^\s+|\s+$/g,s=/^[-+]0x[0-9a-f]+$/i,c=/^0b[01]+$/i,a=/^0o[0-7]+$/i,u=parseInt,l="object"==typeof t&&t&&t.Object===Object&&t,d="object"==typeof self&&self&&self.Object===Object&&self,f=l||d||Function("return this")(),g=Object.prototype.toString,p=Math.max,m=Math.min,v=function(){return f.Date.now()};function h(e,t,o){var i,r,s,c,a,u,l=0,d=!1,f=!1,g=!0;if("function"!=typeof e)throw new TypeError(n);function h(t){var n=i,o=r;return i=r=void 0,l=t,c=e.apply(o,n)}function y(e){var n=e-u;return void 0===u||n>=t||n<0||f&&e-l>=s}function E(){var e=v();if(y(e))return L(e);a=setTimeout(E,function(e){var n=t-(e-u);return f?m(n,s-(e-l)):n}(e))}function L(e){return a=void 0,g&&i?h(e):(i=r=void 0,c)}function M(){var e=v(),n=y(e);if(i=arguments,r=this,u=e,n){if(void 0===a)return function(e){return l=e,a=setTimeout(E,t),d?h(e):c}(u);if(f)return a=setTimeout(E,t),h(u)}return void 0===a&&(a=setTimeout(E,t)),c}return t=b(t)||0,w(o)&&(d=!!o.leading,s=(f="maxWait"in o)?p(b(o.maxWait)||0,t):s,g="trailing"in o?!!o.trailing:g),M.cancel=function(){void 0!==a&&clearTimeout(a),l=0,i=u=r=a=void 0},M.flush=function(){return void 0===a?c:L(v())},M}function w(e){var t=typeof e;return!!e&&("object"==t||"function"==t)}function b(e){if("number"==typeof e)return e;if(function(e){return"symbol"==typeof e||function(e){return!!e&&"object"==typeof e}(e)&&g.call(e)==i}(e))return o;if(w(e)){var t="function"==typeof e.valueOf?e.valueOf():e;e=w(t)?t+"":t}if("string"!=typeof e)return 0===e?e:+e;e=e.replace(r,"");var n=c.test(e);return n||a.test(e)?u(e.slice(2),n?2:8):s.test(e)?o:+e}e.exports=function(e,t,o){var i=!0,r=!0;if("function"!=typeof e)throw new TypeError(n);return w(o)&&(i="leading"in o?!!o.leading:i,r="trailing"in o?!!o.trailing:r),h(e,t,{leading:i,maxWait:t,trailing:r})}}).call(this,n(6))},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const o=n(0);t.createPosterForVsCode=(e=>new class{postMessage(t,n){e.postMessage({type:t,source:o.getSettings().source,body:n})}})},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.onceDocumentLoaded=function(e){"loading"===document.readyState||"uninitialized"===document.readyState?document.addEventListener("DOMContentLoaded",e):e()}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const o=n(1);t.ActiveLineMarker=class{onDidChangeTextEditorSelection(e){const{previous:t}=o.getElementsForSourceLine(e);this._update(t&&t.element)}_update(e){this._unmarkActiveElement(this._current),this._markActiveElement(e),this._current=e}_unmarkActiveElement(e){e&&(e.className=e.className.replace(/\bcode-active-line\b/g,""))}_markActiveElement(e){e&&(e.className+=" code-active-line")}}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const o=n(10),i=n(9),r=n(8),s=n(1),c=n(0),a=n(7);let u=!0;const l=new o.ActiveLineMarker,d=c.getSettings(),f=acquireVsCodeApi();let g=c.getData("data-state");f.setState(g);const p=r.createPosterForVsCode(f);window.cspAlerter.setPoster(p),window.styleLoadingMonitor.setPoster(p),window.onload=(()=>{v()}),i.onceDocumentLoaded(()=>{d.scrollPreviewWithEditor&&setTimeout(()=>{if(g.fragment){const e=s.getLineElementForFragment(g.fragment);e&&(u=!0,s.scrollToRevealSourceLine(e.line))}else{const e=+d.line;isNaN(e)||(u=!0,s.scrollToRevealSourceLine(e))}},0)});const m=(()=>{const e=a(e=>{u=!0,s.scrollToRevealSourceLine(e)},50);return(t,n)=>{isNaN(t)||(n.line=t,e(t))}})();let v=a(()=>{const e=[];let t=document.getElementsByTagName("img");if(t){let n;for(n=0;n{u=!0,v()},!0),window.addEventListener("message",e=>{if(e.data.source===d.source)switch(e.data.type){case"onDidChangeTextEditorSelection":l.onDidChangeTextEditorSelection(e.data.line);break;case"updateView":m(e.data.line,d)}},!1),document.addEventListener("dblclick",e=>{if(!d.doubleClickToSwitchToEditor)return;for(let t=e.target;t;t=t.parentNode)if("A"===t.tagName)return;const t=e.pageY,n=s.getEditorLineNumberForPageOffset(t);"number"!=typeof n||isNaN(n)||p.postMessage("didClick",{line:Math.floor(n)})});const h=["http:","https:","mailto:","vscode:","vscode-insiders"];document.addEventListener("click",e=>{if(!e)return;let t=e.target;for(;t;){if(t.tagName&&"A"===t.tagName&&t.href){if(t.getAttribute("href").startsWith("#"))return;if(h.some(e=>t.href.startsWith(e)))return;const n=t.getAttribute("data-href")||t.getAttribute("href");return/^[a-z\-]+:/i.test(n)?void 0:(p.postMessage("openLink",{href:n}),e.preventDefault(),void e.stopPropagation())}t=t.parentNode}},!0),window.addEventListener("scroll",a(()=>{if(u)u=!1;else{const e=s.getEditorLineNumberForPageOffset(window.scrollY);"number"!=typeof e||isNaN(e)||(p.postMessage("revealLine",{line:e}),g.line=e,f.setState(g))}},50))}]); -//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vd2VicGFjay9ib290c3RyYXAiLCJ3ZWJwYWNrOi8vLy4vcHJldmlldy1zcmMvc2V0dGluZ3MudHMiLCJ3ZWJwYWNrOi8vLy4vcHJldmlldy1zcmMvc2Nyb2xsLXN5bmMudHMiLCJ3ZWJwYWNrOi8vLyh3ZWJwYWNrKS9idWlsZGluL2dsb2JhbC5qcyIsIndlYnBhY2s6Ly8vLi9ub2RlX21vZHVsZXMvbG9kYXNoLnRocm90dGxlL2luZGV4LmpzIiwid2VicGFjazovLy8uL3ByZXZpZXctc3JjL21lc3NhZ2luZy50cyIsIndlYnBhY2s6Ly8vLi9wcmV2aWV3LXNyYy9ldmVudHMudHMiLCJ3ZWJwYWNrOi8vLy4vcHJldmlldy1zcmMvYWN0aXZlTGluZU1hcmtlci50cyIsIndlYnBhY2s6Ly8vLi9wcmV2aWV3LXNyYy9pbmRleC50cyJdLCJuYW1lcyI6WyJpbnN0YWxsZWRNb2R1bGVzIiwiX193ZWJwYWNrX3JlcXVpcmVfXyIsIm1vZHVsZUlkIiwiZXhwb3J0cyIsIm1vZHVsZSIsImkiLCJsIiwibW9kdWxlcyIsImNhbGwiLCJtIiwiYyIsImQiLCJuYW1lIiwiZ2V0dGVyIiwibyIsIk9iamVjdCIsImRlZmluZVByb3BlcnR5IiwiY29uZmlndXJhYmxlIiwiZW51bWVyYWJsZSIsImdldCIsInIiLCJ2YWx1ZSIsIm4iLCJfX2VzTW9kdWxlIiwib2JqZWN0IiwicHJvcGVydHkiLCJwcm90b3R5cGUiLCJoYXNPd25Qcm9wZXJ0eSIsInAiLCJzIiwiY2FjaGVkU2V0dGluZ3MiLCJ1bmRlZmluZWQiLCJnZXREYXRhIiwia2V5IiwiZWxlbWVudCIsImRvY3VtZW50IiwiZ2V0RWxlbWVudEJ5SWQiLCJkYXRhIiwiZ2V0QXR0cmlidXRlIiwiSlNPTiIsInBhcnNlIiwiRXJyb3IiLCJnZXRTZXR0aW5ncyIsInNldHRpbmdzXzEiLCJjbGFtcExpbmUiLCJsaW5lIiwibWluIiwibWF4IiwibGluZUNvdW50IiwiTWF0aCIsImdldENvZGVMaW5lRWxlbWVudHMiLCJlbGVtZW50cyIsImJvZHkiLCJnZXRFbGVtZW50c0J5Q2xhc3NOYW1lIiwiaXNOYU4iLCJwdXNoIiwiZ2V0RWxlbWVudHNGb3JTb3VyY2VMaW5lIiwidGFyZ2V0TGluZSIsImxpbmVOdW1iZXIiLCJmbG9vciIsImxpbmVzIiwicHJldmlvdXMiLCJlbnRyeSIsIm5leHQiLCJnZXRMaW5lRWxlbWVudHNBdFBhZ2VPZmZzZXQiLCJvZmZzZXQiLCJwb3NpdGlvbiIsIndpbmRvdyIsInNjcm9sbFkiLCJsbyIsImhpIiwibGVuZ3RoIiwibWlkIiwiYm91bmRzIiwiZ2V0Qm91bmRpbmdDbGllbnRSZWN0IiwidG9wIiwiaGVpZ2h0IiwiaGlFbGVtZW50IiwiaGlCb3VuZHMiLCJzY3JvbGxUb1JldmVhbFNvdXJjZUxpbmUiLCJzY3JvbGxQcmV2aWV3V2l0aEVkaXRvciIsInNjcm9sbCIsInNjcm9sbFgiLCJzY3JvbGxUbyIsInJlY3QiLCJwcmV2aW91c1RvcCIsInByb2dyZXNzSW5FbGVtZW50IiwiZ2V0RWRpdG9yTGluZU51bWJlckZvclBhZ2VPZmZzZXQiLCJwcmV2aW91c0JvdW5kcyIsIm9mZnNldEZyb21QcmV2aW91cyIsInByb2dyZXNzQmV0d2VlbkVsZW1lbnRzIiwicHJvZ3Jlc3NXaXRoaW5FbGVtZW50IiwiZ2V0TGluZUVsZW1lbnRGb3JGcmFnbWVudCIsImZyYWdtZW50IiwiZmluZCIsImlkIiwiZyIsInRoaXMiLCJGdW5jdGlvbiIsImV2YWwiLCJlIiwiZ2xvYmFsIiwiRlVOQ19FUlJPUl9URVhUIiwiTkFOIiwic3ltYm9sVGFnIiwicmVUcmltIiwicmVJc0JhZEhleCIsInJlSXNCaW5hcnkiLCJyZUlzT2N0YWwiLCJmcmVlUGFyc2VJbnQiLCJwYXJzZUludCIsImZyZWVHbG9iYWwiLCJmcmVlU2VsZiIsInNlbGYiLCJyb290Iiwib2JqZWN0VG9TdHJpbmciLCJ0b1N0cmluZyIsIm5hdGl2ZU1heCIsIm5hdGl2ZU1pbiIsIm5vdyIsIkRhdGUiLCJkZWJvdW5jZSIsImZ1bmMiLCJ3YWl0Iiwib3B0aW9ucyIsImxhc3RBcmdzIiwibGFzdFRoaXMiLCJtYXhXYWl0IiwicmVzdWx0IiwidGltZXJJZCIsImxhc3RDYWxsVGltZSIsImxhc3RJbnZva2VUaW1lIiwibGVhZGluZyIsIm1heGluZyIsInRyYWlsaW5nIiwiVHlwZUVycm9yIiwiaW52b2tlRnVuYyIsInRpbWUiLCJhcmdzIiwidGhpc0FyZyIsImFwcGx5Iiwic2hvdWxkSW52b2tlIiwidGltZVNpbmNlTGFzdENhbGwiLCJ0aW1lckV4cGlyZWQiLCJ0cmFpbGluZ0VkZ2UiLCJzZXRUaW1lb3V0IiwicmVtYWluaW5nV2FpdCIsImRlYm91bmNlZCIsImlzSW52b2tpbmciLCJhcmd1bWVudHMiLCJsZWFkaW5nRWRnZSIsInRvTnVtYmVyIiwiaXNPYmplY3QiLCJjYW5jZWwiLCJjbGVhclRpbWVvdXQiLCJmbHVzaCIsInR5cGUiLCJpc09iamVjdExpa2UiLCJpc1N5bWJvbCIsIm90aGVyIiwidmFsdWVPZiIsInJlcGxhY2UiLCJpc0JpbmFyeSIsInRlc3QiLCJzbGljZSIsImNyZWF0ZVBvc3RlckZvclZzQ29kZSIsInZzY29kZSIsIltvYmplY3QgT2JqZWN0XSIsInBvc3RNZXNzYWdlIiwic291cmNlIiwib25jZURvY3VtZW50TG9hZGVkIiwiZiIsInJlYWR5U3RhdGUiLCJhZGRFdmVudExpc3RlbmVyIiwic2Nyb2xsX3N5bmNfMSIsIkFjdGl2ZUxpbmVNYXJrZXIiLCJfdXBkYXRlIiwiYmVmb3JlIiwiX3VubWFya0FjdGl2ZUVsZW1lbnQiLCJfY3VycmVudCIsIl9tYXJrQWN0aXZlRWxlbWVudCIsImNsYXNzTmFtZSIsImFjdGl2ZUxpbmVNYXJrZXJfMSIsImV2ZW50c18xIiwibWVzc2FnaW5nXzEiLCJ0aHJvdHRsZSIsInNjcm9sbERpc2FibGVkIiwibWFya2VyIiwic2V0dGluZ3MiLCJhY3F1aXJlVnNDb2RlQXBpIiwic3RhdGUiLCJzZXRTdGF0ZSIsIm1lc3NhZ2luZyIsImNzcEFsZXJ0ZXIiLCJzZXRQb3N0ZXIiLCJzdHlsZUxvYWRpbmdNb25pdG9yIiwib25sb2FkIiwidXBkYXRlSW1hZ2VTaXplcyIsImluaXRpYWxMaW5lIiwib25VcGRhdGVWaWV3IiwiZG9TY3JvbGwiLCJpbWFnZUluZm8iLCJpbWFnZXMiLCJnZXRFbGVtZW50c0J5VGFnTmFtZSIsImltZyIsImNsYXNzTGlzdCIsImNvbnRhaW5zIiwicmVtb3ZlIiwid2lkdGgiLCJldmVudCIsIm9uRGlkQ2hhbmdlVGV4dEVkaXRvclNlbGVjdGlvbiIsImRvdWJsZUNsaWNrVG9Td2l0Y2hUb0VkaXRvciIsIm5vZGUiLCJ0YXJnZXQiLCJwYXJlbnROb2RlIiwidGFnTmFtZSIsInBhZ2VZIiwicGFzc1Rocm91Z2hMaW5rU2NoZW1lcyIsImhyZWYiLCJzdGFydHNXaXRoIiwic29tZSIsInNjaGVtZSIsImhyZWZUZXh0IiwicHJldmVudERlZmF1bHQiLCJzdG9wUHJvcGFnYXRpb24iXSwibWFwcGluZ3MiOiJhQUNBLElBQUFBLEtBR0EsU0FBQUMsRUFBQUMsR0FHQSxHQUFBRixFQUFBRSxHQUNBLE9BQUFGLEVBQUFFLEdBQUFDLFFBR0EsSUFBQUMsRUFBQUosRUFBQUUsSUFDQUcsRUFBQUgsRUFDQUksR0FBQSxFQUNBSCxZQVVBLE9BTkFJLEVBQUFMLEdBQUFNLEtBQUFKLEVBQUFELFFBQUFDLElBQUFELFFBQUFGLEdBR0FHLEVBQUFFLEdBQUEsRUFHQUYsRUFBQUQsUUFLQUYsRUFBQVEsRUFBQUYsRUFHQU4sRUFBQVMsRUFBQVYsRUFHQUMsRUFBQVUsRUFBQSxTQUFBUixFQUFBUyxFQUFBQyxHQUNBWixFQUFBYSxFQUFBWCxFQUFBUyxJQUNBRyxPQUFBQyxlQUFBYixFQUFBUyxHQUNBSyxjQUFBLEVBQ0FDLFlBQUEsRUFDQUMsSUFBQU4sS0FNQVosRUFBQW1CLEVBQUEsU0FBQWpCLEdBQ0FZLE9BQUFDLGVBQUFiLEVBQUEsY0FBaURrQixPQUFBLEtBSWpEcEIsRUFBQXFCLEVBQUEsU0FBQWxCLEdBQ0EsSUFBQVMsRUFBQVQsS0FBQW1CLFdBQ0EsV0FBMkIsT0FBQW5CLEVBQUEsU0FDM0IsV0FBaUMsT0FBQUEsR0FFakMsT0FEQUgsRUFBQVUsRUFBQUUsRUFBQSxJQUFBQSxHQUNBQSxHQUlBWixFQUFBYSxFQUFBLFNBQUFVLEVBQUFDLEdBQXNELE9BQUFWLE9BQUFXLFVBQUFDLGVBQUFuQixLQUFBZ0IsRUFBQUMsSUFHdER4QixFQUFBMkIsRUFBQSxHQUlBM0IsSUFBQTRCLEVBQUEsbUNDOURBZCxPQUFBQyxlQUFBYixFQUFBLGNBQThDa0IsT0FBQSxJQUM5QyxJQUFBUyxPQUFBQyxFQUNBLFNBQUFDLEVBQUFDLEdBQ0EsTUFBQUMsRUFBQUMsU0FBQUMsZUFBQSxnQ0FDQSxHQUFBRixFQUFBLENBQ0EsTUFBQUcsRUFBQUgsRUFBQUksYUFBQUwsR0FDQSxHQUFBSSxFQUNBLE9BQUFFLEtBQUFDLE1BQUFILEdBR0EsVUFBQUksaUNBQStDUixLQUUvQzlCLEVBQUE2QixVQVdBN0IsRUFBQXVDLFlBVkEsV0FDQSxHQUFBWixFQUNBLE9BQUFBLEVBR0EsR0FEQUEsRUFBQUUsRUFBQSxpQkFFQSxPQUFBRixFQUVBLFVBQUFXLE1BQUEsMERDckJBMUIsT0FBQUMsZUFBQWIsRUFBQSxjQUE4Q2tCLE9BQUEsSUFDOUMsTUFBQXNCLEVBQUExQyxFQUFBLEdBSUEsU0FBQTJDLEVBQUFDLEdBQ0EsT0FKQUMsRUFJQSxFQUpBQyxFQUlBSixFQUFBRCxjQUFBTSxVQUFBLEVBSkEzQixFQUlBd0IsRUFIQUksS0FBQUgsSUFBQUMsRUFBQUUsS0FBQUYsSUFBQUQsRUFBQXpCLElBREEsSUFBQXlCLEVBQUFDLEVBQUExQixFQU1BLE1BQUE2QixFQUFBLE1BQ0EsSUFBQUMsRUFDQSxXQUNBLElBQUFBLEVBQUEsQ0FDQUEsSUFBeUJqQixRQUFBQyxTQUFBaUIsS0FBQVAsS0FBQSxJQUN6QixVQUFBWCxLQUFBQyxTQUFBa0IsdUJBQUEsY0FDQSxNQUFBUixHQUFBWCxFQUFBSSxhQUFBLGFBQ0FnQixNQUFBVCxJQUNBTSxFQUFBSSxNQUFtQ3JCLFVBQUFXLFVBSW5DLE9BQUFNLElBWkEsR0FxQkEsU0FBQUssRUFBQUMsR0FDQSxNQUFBQyxFQUFBVCxLQUFBVSxNQUFBRixHQUNBRyxFQUFBVixJQUNBLElBQUFXLEVBQUFELEVBQUEsU0FDQSxVQUFBRSxLQUFBRixFQUFBLENBQ0EsR0FBQUUsRUFBQWpCLE9BQUFhLEVBQ0EsT0FBb0JHLFNBQUFDLEVBQUFDLFVBQUFoQyxHQUVwQixHQUFBK0IsRUFBQWpCLEtBQUFhLEVBQ0EsT0FBb0JHLFdBQUFFLEtBQUFELEdBRXBCRCxFQUFBQyxFQUVBLE9BQVlELFlBTVosU0FBQUcsRUFBQUMsR0FDQSxNQUFBTCxFQUFBVixJQUNBZ0IsRUFBQUQsRUFBQUUsT0FBQUMsUUFDQSxJQUFBQyxHQUFBLEVBQ0FDLEVBQUFWLEVBQUFXLE9BQUEsRUFDQSxLQUFBRixFQUFBLEVBQUFDLEdBQUEsQ0FDQSxNQUFBRSxFQUFBdkIsS0FBQVUsT0FBQVUsRUFBQUMsR0FBQSxHQUNBRyxFQUFBYixFQUFBWSxHQUFBdEMsUUFBQXdDLHdCQUNBRCxFQUFBRSxJQUFBRixFQUFBRyxRQUFBVixFQUNBSSxFQUFBRSxFQUdBSCxFQUFBRyxFQUdBLE1BQUFLLEVBQUFqQixFQUFBVSxHQUNBUSxFQUFBRCxFQUFBM0MsUUFBQXdDLHdCQUNBLEdBQUFKLEdBQUEsR0FBQVEsRUFBQUgsSUFBQVQsRUFBQSxDQUVBLE9BQWdCTCxTQURoQkQsRUFBQVMsR0FDZ0JOLEtBQUFjLEdBRWhCLE9BQVloQixTQUFBZ0IsR0F6QloxRSxFQUFBcUQsMkJBMkJBckQsRUFBQTZELDhCQStCQTdELEVBQUE0RSx5QkEzQkEsU0FBQWxDLEdBQ0EsSUFBQUYsRUFBQUQsY0FBQXNDLHdCQUNBLE9BRUEsR0FBQW5DLEdBQUEsRUFFQSxZQURBc0IsT0FBQWMsT0FBQWQsT0FBQWUsUUFBQSxHQUdBLE1BQUFyQixTQUFXQSxFQUFBRSxRQUFpQlAsRUFBQVgsR0FDNUIsSUFBQWdCLEVBQ0EsT0FFQSxJQUFBc0IsRUFBQSxFQUNBLE1BQUFDLEVBQUF2QixFQUFBM0IsUUFBQXdDLHdCQUNBVyxFQUFBRCxFQUFBVCxJQUNBLEdBQUFaLEtBQUFsQixPQUFBZ0IsRUFBQWhCLEtBSUFzQyxFQUFBRSxHQUZBeEMsRUFBQWdCLEVBQUFoQixPQUFBa0IsRUFBQWxCLEtBQUFnQixFQUFBaEIsT0FDQWtCLEVBQUE3QixRQUFBd0Msd0JBQUFDLElBQUFVLE9BR0EsQ0FDQSxNQUFBQyxFQUFBekMsRUFBQUksS0FBQVUsTUFBQWQsR0FDQXNDLEVBQUFFLEVBQUFELEVBQUFSLE9BQUFVLEVBRUFuQixPQUFBYyxPQUFBZCxPQUFBZSxRQUFBakMsS0FBQUYsSUFBQSxFQUFBb0IsT0FBQUMsUUFBQWUsS0FxQkFoRixFQUFBb0YsaUNBbEJBLFNBQUF0QixHQUNBLE1BQUFKLFNBQVdBLEVBQUFFLFFBQWlCQyxFQUFBQyxHQUM1QixHQUFBSixFQUFBLENBQ0EsTUFBQTJCLEVBQUEzQixFQUFBM0IsUUFBQXdDLHdCQUNBZSxFQUFBeEIsRUFBQUUsT0FBQUMsUUFBQW9CLEVBQUFiLElBQ0EsR0FBQVosRUFBQSxDQUNBLE1BQUEyQixFQUFBRCxHQUFBMUIsRUFBQTdCLFFBQUF3Qyx3QkFBQUMsSUFBQWEsRUFBQWIsS0FFQSxPQUFBL0IsRUFEQWlCLEVBQUFoQixLQUFBNkMsR0FBQTNCLEVBQUFsQixLQUFBZ0IsRUFBQWhCLE9BR0EsQ0FDQSxNQUFBOEMsRUFBQUYsRUFBQUQsRUFBQSxPQUVBLE9BQUE1QyxFQURBaUIsRUFBQWhCLEtBQUE4QyxJQUlBLGFBV0F4RixFQUFBeUYsMEJBTEEsU0FBQUMsR0FDQSxPQUFBM0MsSUFBQTRDLEtBQUE1RCxHQUNBQSxVQUFBNkQsS0FBQUYsdUJDcElBLElBQUFHLEVBR0FBLEVBQUEsV0FDQSxPQUFBQyxLQURBLEdBSUEsSUFFQUQsS0FBQUUsU0FBQSxjQUFBQSxLQUFBLEVBQUFDLE1BQUEsUUFDQyxNQUFBQyxHQUVELGlCQUFBakMsU0FBQTZCLEVBQUE3QixRQU9BL0QsRUFBQUQsUUFBQTZGLG9CQ25CQSxTQUFBSyxHQVVBLElBQUFDLEVBQUEsc0JBR0FDLEVBQUEsSUFHQUMsRUFBQSxrQkFHQUMsRUFBQSxhQUdBQyxFQUFBLHFCQUdBQyxFQUFBLGFBR0FDLEVBQUEsY0FHQUMsRUFBQUMsU0FHQUMsRUFBQSxpQkFBQVYsUUFBQXRGLGlCQUFBc0YsRUFHQVcsRUFBQSxpQkFBQUMsaUJBQUFsRyxpQkFBQWtHLEtBR0FDLEVBQUFILEdBQUFDLEdBQUFkLFNBQUEsY0FBQUEsR0FVQWlCLEVBUEFwRyxPQUFBVyxVQU9BMEYsU0FHQUMsRUFBQXBFLEtBQUFGLElBQ0F1RSxFQUFBckUsS0FBQUgsSUFrQkF5RSxFQUFBLFdBQ0EsT0FBQUwsRUFBQU0sS0FBQUQsT0F5REEsU0FBQUUsRUFBQUMsRUFBQUMsRUFBQUMsR0FDQSxJQUFBQyxFQUNBQyxFQUNBQyxFQUNBQyxFQUNBQyxFQUNBQyxFQUNBQyxFQUFBLEVBQ0FDLEdBQUEsRUFDQUMsR0FBQSxFQUNBQyxHQUFBLEVBRUEsc0JBQUFaLEVBQ0EsVUFBQWEsVUFBQWpDLEdBVUEsU0FBQWtDLEVBQUFDLEdBQ0EsSUFBQUMsRUFBQWIsRUFDQWMsRUFBQWIsRUFLQSxPQUhBRCxFQUFBQyxPQUFBL0YsRUFDQW9HLEVBQUFNLEVBQ0FULEVBQUFOLEVBQUFrQixNQUFBRCxFQUFBRCxHQXFCQSxTQUFBRyxFQUFBSixHQUNBLElBQUFLLEVBQUFMLEVBQUFQLEVBTUEsWUFBQW5HLElBQUFtRyxHQUFBWSxHQUFBbkIsR0FDQW1CLEVBQUEsR0FBQVQsR0FOQUksRUFBQU4sR0FNQUosRUFHQSxTQUFBZ0IsSUFDQSxJQUFBTixFQUFBbEIsSUFDQSxHQUFBc0IsRUFBQUosR0FDQSxPQUFBTyxFQUFBUCxHQUdBUixFQUFBZ0IsV0FBQUYsRUF6QkEsU0FBQU4sR0FDQSxJQUVBVCxFQUFBTCxHQUZBYyxFQUFBUCxHQUlBLE9BQUFHLEVBQUFmLEVBQUFVLEVBQUFELEdBSEFVLEVBQUFOLElBR0FILEVBb0JBa0IsQ0FBQVQsSUFHQSxTQUFBTyxFQUFBUCxHQUtBLE9BSkFSLE9BQUFsRyxFQUlBdUcsR0FBQVQsRUFDQVcsRUFBQUMsSUFFQVosRUFBQUMsT0FBQS9GLEVBQ0FpRyxHQWVBLFNBQUFtQixJQUNBLElBQUFWLEVBQUFsQixJQUNBNkIsRUFBQVAsRUFBQUosR0FNQSxHQUpBWixFQUFBd0IsVUFDQXZCLEVBQUE3QixLQUNBaUMsRUFBQU8sRUFFQVcsRUFBQSxDQUNBLFFBQUFySCxJQUFBa0csRUFDQSxPQXZFQSxTQUFBUSxHQU1BLE9BSkFOLEVBQUFNLEVBRUFSLEVBQUFnQixXQUFBRixFQUFBcEIsR0FFQVMsRUFBQUksRUFBQUMsR0FBQVQsRUFpRUFzQixDQUFBcEIsR0FFQSxHQUFBRyxFQUdBLE9BREFKLEVBQUFnQixXQUFBRixFQUFBcEIsR0FDQWEsRUFBQU4sR0FNQSxZQUhBbkcsSUFBQWtHLElBQ0FBLEVBQUFnQixXQUFBRixFQUFBcEIsSUFFQUssRUFJQSxPQXhHQUwsRUFBQTRCLEVBQUE1QixJQUFBLEVBQ0E2QixFQUFBNUIsS0FDQVEsSUFBQVIsRUFBQVEsUUFFQUwsR0FEQU0sRUFBQSxZQUFBVCxHQUNBUCxFQUFBa0MsRUFBQTNCLEVBQUFHLFVBQUEsRUFBQUosR0FBQUksRUFDQU8sRUFBQSxhQUFBVixNQUFBVSxZQWlHQWEsRUFBQU0sT0FuQ0EsZ0JBQ0ExSCxJQUFBa0csR0FDQXlCLGFBQUF6QixHQUVBRSxFQUFBLEVBQ0FOLEVBQUFLLEVBQUFKLEVBQUFHLE9BQUFsRyxHQStCQW9ILEVBQUFRLE1BNUJBLFdBQ0EsWUFBQTVILElBQUFrRyxFQUFBRCxFQUFBZ0IsRUFBQXpCLE1BNEJBNEIsRUEwRkEsU0FBQUssRUFBQW5JLEdBQ0EsSUFBQXVJLFNBQUF2SSxFQUNBLFFBQUFBLElBQUEsVUFBQXVJLEdBQUEsWUFBQUEsR0E0RUEsU0FBQUwsRUFBQWxJLEdBQ0Esb0JBQUFBLEVBQ0EsT0FBQUEsRUFFQSxHQWhDQSxTQUFBQSxHQUNBLHVCQUFBQSxHQXRCQSxTQUFBQSxHQUNBLFFBQUFBLEdBQUEsaUJBQUFBLEVBc0JBd0ksQ0FBQXhJLElBQUE4RixFQUFBM0csS0FBQWEsSUFBQW1GLEVBOEJBc0QsQ0FBQXpJLEdBQ0EsT0FBQWtGLEVBRUEsR0FBQWlELEVBQUFuSSxHQUFBLENBQ0EsSUFBQTBJLEVBQUEsbUJBQUExSSxFQUFBMkksUUFBQTNJLEVBQUEySSxVQUFBM0ksRUFDQUEsRUFBQW1JLEVBQUFPLEtBQUEsR0FBQUEsRUFFQSxvQkFBQTFJLEVBQ0EsV0FBQUEsT0FFQUEsSUFBQTRJLFFBQUF4RCxFQUFBLElBQ0EsSUFBQXlELEVBQUF2RCxFQUFBd0QsS0FBQTlJLEdBQ0EsT0FBQTZJLEdBQUF0RCxFQUFBdUQsS0FBQTlJLEdBQ0F3RixFQUFBeEYsRUFBQStJLE1BQUEsR0FBQUYsRUFBQSxLQUNBeEQsRUFBQXlELEtBQUE5SSxHQUFBa0YsR0FBQWxGLEVBR0FqQixFQUFBRCxRQTlJQSxTQUFBdUgsRUFBQUMsRUFBQUMsR0FDQSxJQUFBUSxHQUFBLEVBQ0FFLEdBQUEsRUFFQSxzQkFBQVosRUFDQSxVQUFBYSxVQUFBakMsR0FNQSxPQUpBa0QsRUFBQTVCLEtBQ0FRLEVBQUEsWUFBQVIsTUFBQVEsVUFDQUUsRUFBQSxhQUFBVixNQUFBVSxZQUVBYixFQUFBQyxFQUFBQyxHQUNBUyxVQUNBTCxRQUFBSixFQUNBVyw4RENqVEF2SCxPQUFBQyxlQUFBYixFQUFBLGNBQThDa0IsT0FBQSxJQUM5QyxNQUFBc0IsRUFBQTFDLEVBQUEsR0FDQUUsRUFBQWtLLHNCQUFBLENBQUFDLEdBQ0EsVUFDQUMsWUFBQVgsRUFBQXhHLEdBQ0FrSCxFQUFBRSxhQUNBWixPQUNBYSxPQUFBOUgsRUFBQUQsY0FBQStILE9BQ0FySCwwQ0NSQXJDLE9BQUFDLGVBQUFiLEVBQUEsY0FBOENrQixPQUFBLElBUzlDbEIsRUFBQXVLLG1CQVJBLFNBQUFDLEdBQ0EsWUFBQXhJLFNBQUF5SSxZQUFBLGtCQUFBekksU0FBQXlJLFdBQ0F6SSxTQUFBMEksaUJBQUEsbUJBQUFGLEdBR0FBLG1DQ1ZBNUosT0FBQUMsZUFBQWIsRUFBQSxjQUE4Q2tCLE9BQUEsSUFLOUMsTUFBQXlKLEVBQUE3SyxFQUFBLEdBd0JBRSxFQUFBNEssdUJBdEJBUiwrQkFBQTFILEdBQ0EsTUFBQWdCLFNBQWVBLEdBQVdpSCxFQUFBdEgseUJBQUFYLEdBQzFCb0QsS0FBQStFLFFBQUFuSCxLQUFBM0IsU0FFQXFJLFFBQUFVLEdBQ0FoRixLQUFBaUYscUJBQUFqRixLQUFBa0YsVUFDQWxGLEtBQUFtRixtQkFBQUgsR0FDQWhGLEtBQUFrRixTQUFBRixFQUVBVixxQkFBQXJJLEdBQ0FBLElBR0FBLEVBQUFtSixVQUFBbkosRUFBQW1KLFVBQUFwQixRQUFBLDZCQUVBTSxtQkFBQXJJLEdBQ0FBLElBR0FBLEVBQUFtSixXQUFBLHFEQ3RCQXRLLE9BQUFDLGVBQUFiLEVBQUEsY0FBOENrQixPQUFBLElBQzlDLE1BQUFpSyxFQUFBckwsRUFBQSxJQUNBc0wsRUFBQXRMLEVBQUEsR0FDQXVMLEVBQUF2TCxFQUFBLEdBQ0E2SyxFQUFBN0ssRUFBQSxHQUNBMEMsRUFBQTFDLEVBQUEsR0FDQXdMLEVBQUF4TCxFQUFBLEdBQ0EsSUFBQXlMLEdBQUEsRUFDQSxNQUFBQyxFQUFBLElBQUFMLEVBQUFQLGlCQUNBYSxFQUFBakosRUFBQUQsY0FDQTRILEVBQUF1QixtQkFFQSxJQUFBQyxFQUFBbkosRUFBQVgsUUFBQSxjQUNBc0ksRUFBQXlCLFNBQUFELEdBQ0EsTUFBQUUsRUFBQVIsRUFBQW5CLHNCQUFBQyxHQUNBbkcsT0FBQThILFdBQUFDLFVBQUFGLEdBQ0E3SCxPQUFBZ0ksb0JBQUFELFVBQUFGLEdBQ0E3SCxPQUFBaUksT0FBQSxNQUNBQyxNQUVBZCxFQUFBYixtQkFBQSxLQUNBa0IsRUFBQTVHLHlCQUNBaUUsV0FBQSxLQUVBLEdBQUE2QyxFQUFBakcsU0FBQSxDQUNBLE1BQUEzRCxFQUFBNEksRUFBQWxGLDBCQUFBa0csRUFBQWpHLFVBQ0EzRCxJQUNBd0osR0FBQSxFQUNBWixFQUFBL0YseUJBQUE3QyxFQUFBVyxXQUdBLENBQ0EsTUFBQXlKLEdBQUFWLEVBQUEvSSxLQUNBUyxNQUFBZ0osS0FDQVosR0FBQSxFQUNBWixFQUFBL0YseUJBQUF1SCxNQUdTLEtBR1QsTUFBQUMsRUFBQSxNQUNBLE1BQUFDLEVBQUFmLEVBQUE1SSxJQUNBNkksR0FBQSxFQUNBWixFQUFBL0YseUJBQUFsQyxJQUNLLElBQ0wsT0FBQUEsRUFBQStJLEtBQ0F0SSxNQUFBVCxLQUNBK0ksRUFBQS9JLE9BQ0EySixFQUFBM0osTUFSQSxHQVlBLElBQUF3SixFQUFBWixFQUFBLEtBQ0EsTUFBQWdCLEtBQ0EsSUFBQUMsRUFBQXZLLFNBQUF3SyxxQkFBQSxPQUNBLEdBQUFELEVBQUEsQ0FDQSxJQUFBck0sRUFDQSxJQUFBQSxFQUFBLEVBQW1CQSxFQUFBcU0sRUFBQW5JLE9BQW1CbEUsSUFBQSxDQUN0QyxNQUFBdU0sRUFBQUYsRUFBQXJNLEdBQ0F1TSxFQUFBQyxVQUFBQyxTQUFBLFlBQ0FGLEVBQUFDLFVBQUFFLE9BQUEsV0FFQU4sRUFBQWxKLE1BQ0F3QyxHQUFBNkcsRUFBQTdHLEdBQ0FuQixPQUFBZ0ksRUFBQWhJLE9BQ0FvSSxNQUFBSixFQUFBSSxRQUdBaEIsRUFBQXhCLFlBQUEsa0JBQUFpQyxLQUVDLElBQ0R0SSxPQUFBMEcsaUJBQUEsY0FDQWEsR0FBQSxFQUNBVyxNQUNDLEdBQ0RsSSxPQUFBMEcsaUJBQUEsVUFBQW9DLElBQ0EsR0FBQUEsRUFBQTVLLEtBQUFvSSxTQUFBbUIsRUFBQW5CLE9BR0EsT0FBQXdDLEVBQUE1SyxLQUFBdUgsTUFDQSxxQ0FDQStCLEVBQUF1QiwrQkFBQUQsRUFBQTVLLEtBQUFRLE1BQ0EsTUFDQSxpQkFDQTBKLEVBQUFVLEVBQUE1SyxLQUFBUSxLQUFBK0ksTUFHQyxHQUNEekosU0FBQTBJLGlCQUFBLFdBQUFvQyxJQUNBLElBQUFyQixFQUFBdUIsNEJBQ0EsT0FHQSxRQUFBQyxFQUFBSCxFQUFBSSxPQUFpQ0QsRUFBTUEsSUFBQUUsV0FDdkMsU0FBQUYsRUFBQUcsUUFDQSxPQUdBLE1BQUF0SixFQUFBZ0osRUFBQU8sTUFDQTNLLEVBQUFpSSxFQUFBdkYsaUNBQUF0QixHQUNBLGlCQUFBcEIsR0FBQVMsTUFBQVQsSUFDQW1KLEVBQUF4QixZQUFBLFlBQTJDM0gsS0FBQUksS0FBQVUsTUFBQWQsT0FHM0MsTUFBQTRLLEdBQUEsd0RBQ0F0TCxTQUFBMEksaUJBQUEsUUFBQW9DLElBQ0EsSUFBQUEsRUFDQSxPQUVBLElBQUFHLEVBQUFILEVBQUFJLE9BQ0EsS0FBQUQsR0FBQSxDQUNBLEdBQUFBLEVBQUFHLFNBQUEsTUFBQUgsRUFBQUcsU0FBQUgsRUFBQU0sS0FBQSxDQUNBLEdBQUFOLEVBQUE5SyxhQUFBLFFBQUFxTCxXQUFBLEtBQ0EsT0FHQSxHQUFBRixFQUFBRyxLQUFBQyxHQUFBVCxFQUFBTSxLQUFBQyxXQUFBRSxJQUNBLE9BRUEsTUFBQUMsRUFBQVYsRUFBQTlLLGFBQUEsY0FBQThLLEVBQUE5SyxhQUFBLFFBRUEsb0JBQUE2SCxLQUFBMkQsUUFNQSxHQUxBOUIsRUFBQXhCLFlBQUEsWUFBbURrRCxLQUFBSSxJQUNuRGIsRUFBQWMsc0JBQ0FkLEVBQUFlLG1CQUtBWixJQUFBRSxjQUVDLEdBQ0RuSixPQUFBMEcsaUJBQUEsU0FBQVksRUFBQSxLQUNBLEdBQUFDLEVBQ0FBLEdBQUEsTUFFQSxDQUNBLE1BQUE3SSxFQUFBaUksRUFBQXZGLGlDQUFBcEIsT0FBQUMsU0FDQSxpQkFBQXZCLEdBQUFTLE1BQUFULEtBQ0FtSixFQUFBeEIsWUFBQSxjQUFpRDNILFNBQ2pEaUosRUFBQWpKLE9BQ0F5SCxFQUFBeUIsU0FBQUQsTUFHQyIsImZpbGUiOiJpbmRleC5qcyIsInNvdXJjZXNDb250ZW50IjpbIiBcdC8vIFRoZSBtb2R1bGUgY2FjaGVcbiBcdHZhciBpbnN0YWxsZWRNb2R1bGVzID0ge307XG5cbiBcdC8vIFRoZSByZXF1aXJlIGZ1bmN0aW9uXG4gXHRmdW5jdGlvbiBfX3dlYnBhY2tfcmVxdWlyZV9fKG1vZHVsZUlkKSB7XG5cbiBcdFx0Ly8gQ2hlY2sgaWYgbW9kdWxlIGlzIGluIGNhY2hlXG4gXHRcdGlmKGluc3RhbGxlZE1vZHVsZXNbbW9kdWxlSWRdKSB7XG4gXHRcdFx0cmV0dXJuIGluc3RhbGxlZE1vZHVsZXNbbW9kdWxlSWRdLmV4cG9ydHM7XG4gXHRcdH1cbiBcdFx0Ly8gQ3JlYXRlIGEgbmV3IG1vZHVsZSAoYW5kIHB1dCBpdCBpbnRvIHRoZSBjYWNoZSlcbiBcdFx0dmFyIG1vZHVsZSA9IGluc3RhbGxlZE1vZHVsZXNbbW9kdWxlSWRdID0ge1xuIFx0XHRcdGk6IG1vZHVsZUlkLFxuIFx0XHRcdGw6IGZhbHNlLFxuIFx0XHRcdGV4cG9ydHM6IHt9XG4gXHRcdH07XG5cbiBcdFx0Ly8gRXhlY3V0ZSB0aGUgbW9kdWxlIGZ1bmN0aW9uXG4gXHRcdG1vZHVsZXNbbW9kdWxlSWRdLmNhbGwobW9kdWxlLmV4cG9ydHMsIG1vZHVsZSwgbW9kdWxlLmV4cG9ydHMsIF9fd2VicGFja19yZXF1aXJlX18pO1xuXG4gXHRcdC8vIEZsYWcgdGhlIG1vZHVsZSBhcyBsb2FkZWRcbiBcdFx0bW9kdWxlLmwgPSB0cnVlO1xuXG4gXHRcdC8vIFJldHVybiB0aGUgZXhwb3J0cyBvZiB0aGUgbW9kdWxlXG4gXHRcdHJldHVybiBtb2R1bGUuZXhwb3J0cztcbiBcdH1cblxuXG4gXHQvLyBleHBvc2UgdGhlIG1vZHVsZXMgb2JqZWN0IChfX3dlYnBhY2tfbW9kdWxlc19fKVxuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5tID0gbW9kdWxlcztcblxuIFx0Ly8gZXhwb3NlIHRoZSBtb2R1bGUgY2FjaGVcbiBcdF9fd2VicGFja19yZXF1aXJlX18uYyA9IGluc3RhbGxlZE1vZHVsZXM7XG5cbiBcdC8vIGRlZmluZSBnZXR0ZXIgZnVuY3Rpb24gZm9yIGhhcm1vbnkgZXhwb3J0c1xuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5kID0gZnVuY3Rpb24oZXhwb3J0cywgbmFtZSwgZ2V0dGVyKSB7XG4gXHRcdGlmKCFfX3dlYnBhY2tfcmVxdWlyZV9fLm8oZXhwb3J0cywgbmFtZSkpIHtcbiBcdFx0XHRPYmplY3QuZGVmaW5lUHJvcGVydHkoZXhwb3J0cywgbmFtZSwge1xuIFx0XHRcdFx0Y29uZmlndXJhYmxlOiBmYWxzZSxcbiBcdFx0XHRcdGVudW1lcmFibGU6IHRydWUsXG4gXHRcdFx0XHRnZXQ6IGdldHRlclxuIFx0XHRcdH0pO1xuIFx0XHR9XG4gXHR9O1xuXG4gXHQvLyBkZWZpbmUgX19lc01vZHVsZSBvbiBleHBvcnRzXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLnIgPSBmdW5jdGlvbihleHBvcnRzKSB7XG4gXHRcdE9iamVjdC5kZWZpbmVQcm9wZXJ0eShleHBvcnRzLCAnX19lc01vZHVsZScsIHsgdmFsdWU6IHRydWUgfSk7XG4gXHR9O1xuXG4gXHQvLyBnZXREZWZhdWx0RXhwb3J0IGZ1bmN0aW9uIGZvciBjb21wYXRpYmlsaXR5IHdpdGggbm9uLWhhcm1vbnkgbW9kdWxlc1xuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5uID0gZnVuY3Rpb24obW9kdWxlKSB7XG4gXHRcdHZhciBnZXR0ZXIgPSBtb2R1bGUgJiYgbW9kdWxlLl9fZXNNb2R1bGUgP1xuIFx0XHRcdGZ1bmN0aW9uIGdldERlZmF1bHQoKSB7IHJldHVybiBtb2R1bGVbJ2RlZmF1bHQnXTsgfSA6XG4gXHRcdFx0ZnVuY3Rpb24gZ2V0TW9kdWxlRXhwb3J0cygpIHsgcmV0dXJuIG1vZHVsZTsgfTtcbiBcdFx0X193ZWJwYWNrX3JlcXVpcmVfXy5kKGdldHRlciwgJ2EnLCBnZXR0ZXIpO1xuIFx0XHRyZXR1cm4gZ2V0dGVyO1xuIFx0fTtcblxuIFx0Ly8gT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLm8gPSBmdW5jdGlvbihvYmplY3QsIHByb3BlcnR5KSB7IHJldHVybiBPYmplY3QucHJvdG90eXBlLmhhc093blByb3BlcnR5LmNhbGwob2JqZWN0LCBwcm9wZXJ0eSk7IH07XG5cbiBcdC8vIF9fd2VicGFja19wdWJsaWNfcGF0aF9fXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLnAgPSBcIlwiO1xuXG5cbiBcdC8vIExvYWQgZW50cnkgbW9kdWxlIGFuZCByZXR1cm4gZXhwb3J0c1xuIFx0cmV0dXJuIF9fd2VicGFja19yZXF1aXJlX18oX193ZWJwYWNrX3JlcXVpcmVfXy5zID0gMTEpO1xuIiwiXCJ1c2Ugc3RyaWN0XCI7XG4vKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuICogIENvcHlyaWdodCAoYykgTWljcm9zb2Z0IENvcnBvcmF0aW9uLiBBbGwgcmlnaHRzIHJlc2VydmVkLlxuICogIExpY2Vuc2VkIHVuZGVyIHRoZSBNSVQgTGljZW5zZS4gU2VlIExpY2Vuc2UudHh0IGluIHRoZSBwcm9qZWN0IHJvb3QgZm9yIGxpY2Vuc2UgaW5mb3JtYXRpb24uXG4gKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cbk9iamVjdC5kZWZpbmVQcm9wZXJ0eShleHBvcnRzLCBcIl9fZXNNb2R1bGVcIiwgeyB2YWx1ZTogdHJ1ZSB9KTtcbmxldCBjYWNoZWRTZXR0aW5ncyA9IHVuZGVmaW5lZDtcbmZ1bmN0aW9uIGdldERhdGEoa2V5KSB7XG4gICAgY29uc3QgZWxlbWVudCA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCd2c2NvZGUtbWFya2Rvd24tcHJldmlldy1kYXRhJyk7XG4gICAgaWYgKGVsZW1lbnQpIHtcbiAgICAgICAgY29uc3QgZGF0YSA9IGVsZW1lbnQuZ2V0QXR0cmlidXRlKGtleSk7XG4gICAgICAgIGlmIChkYXRhKSB7XG4gICAgICAgICAgICByZXR1cm4gSlNPTi5wYXJzZShkYXRhKTtcbiAgICAgICAgfVxuICAgIH1cbiAgICB0aHJvdyBuZXcgRXJyb3IoYENvdWxkIG5vdCBsb2FkIGRhdGEgZm9yICR7a2V5fWApO1xufVxuZXhwb3J0cy5nZXREYXRhID0gZ2V0RGF0YTtcbmZ1bmN0aW9uIGdldFNldHRpbmdzKCkge1xuICAgIGlmIChjYWNoZWRTZXR0aW5ncykge1xuICAgICAgICByZXR1cm4gY2FjaGVkU2V0dGluZ3M7XG4gICAgfVxuICAgIGNhY2hlZFNldHRpbmdzID0gZ2V0RGF0YSgnZGF0YS1zZXR0aW5ncycpO1xuICAgIGlmIChjYWNoZWRTZXR0aW5ncykge1xuICAgICAgICByZXR1cm4gY2FjaGVkU2V0dGluZ3M7XG4gICAgfVxuICAgIHRocm93IG5ldyBFcnJvcignQ291bGQgbm90IGxvYWQgc2V0dGluZ3MnKTtcbn1cbmV4cG9ydHMuZ2V0U2V0dGluZ3MgPSBnZXRTZXR0aW5ncztcbiIsIlwidXNlIHN0cmljdFwiO1xuLyotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cbiAqICBDb3B5cmlnaHQgKGMpIE1pY3Jvc29mdCBDb3Jwb3JhdGlvbi4gQWxsIHJpZ2h0cyByZXNlcnZlZC5cbiAqICBMaWNlbnNlZCB1bmRlciB0aGUgTUlUIExpY2Vuc2UuIFNlZSBMaWNlbnNlLnR4dCBpbiB0aGUgcHJvamVjdCByb290IGZvciBsaWNlbnNlIGluZm9ybWF0aW9uLlxuICotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSovXG5PYmplY3QuZGVmaW5lUHJvcGVydHkoZXhwb3J0cywgXCJfX2VzTW9kdWxlXCIsIHsgdmFsdWU6IHRydWUgfSk7XG5jb25zdCBzZXR0aW5nc18xID0gcmVxdWlyZShcIi4vc2V0dGluZ3NcIik7XG5mdW5jdGlvbiBjbGFtcChtaW4sIG1heCwgdmFsdWUpIHtcbiAgICByZXR1cm4gTWF0aC5taW4obWF4LCBNYXRoLm1heChtaW4sIHZhbHVlKSk7XG59XG5mdW5jdGlvbiBjbGFtcExpbmUobGluZSkge1xuICAgIHJldHVybiBjbGFtcCgwLCBzZXR0aW5nc18xLmdldFNldHRpbmdzKCkubGluZUNvdW50IC0gMSwgbGluZSk7XG59XG5jb25zdCBnZXRDb2RlTGluZUVsZW1lbnRzID0gKCgpID0+IHtcbiAgICBsZXQgZWxlbWVudHM7XG4gICAgcmV0dXJuICgpID0+IHtcbiAgICAgICAgaWYgKCFlbGVtZW50cykge1xuICAgICAgICAgICAgZWxlbWVudHMgPSBbeyBlbGVtZW50OiBkb2N1bWVudC5ib2R5LCBsaW5lOiAwIH1dO1xuICAgICAgICAgICAgZm9yIChjb25zdCBlbGVtZW50IG9mIGRvY3VtZW50LmdldEVsZW1lbnRzQnlDbGFzc05hbWUoJ2NvZGUtbGluZScpKSB7XG4gICAgICAgICAgICAgICAgY29uc3QgbGluZSA9ICtlbGVtZW50LmdldEF0dHJpYnV0ZSgnZGF0YS1saW5lJyk7XG4gICAgICAgICAgICAgICAgaWYgKCFpc05hTihsaW5lKSkge1xuICAgICAgICAgICAgICAgICAgICBlbGVtZW50cy5wdXNoKHsgZWxlbWVudDogZWxlbWVudCwgbGluZSB9KTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIGVsZW1lbnRzO1xuICAgIH07XG59KSgpO1xuLyoqXG4gKiBGaW5kIHRoZSBodG1sIGVsZW1lbnRzIHRoYXQgbWFwIHRvIGEgc3BlY2lmaWMgdGFyZ2V0IGxpbmUgaW4gdGhlIGVkaXRvci5cbiAqXG4gKiBJZiBhbiBleGFjdCBtYXRjaCwgcmV0dXJucyBhIHNpbmdsZSBlbGVtZW50LiBJZiB0aGUgbGluZSBpcyBiZXR3ZWVuIGVsZW1lbnRzLFxuICogcmV0dXJucyB0aGUgZWxlbWVudCBwcmlvciB0byBhbmQgdGhlIGVsZW1lbnQgYWZ0ZXIgdGhlIGdpdmVuIGxpbmUuXG4gKi9cbmZ1bmN0aW9uIGdldEVsZW1lbnRzRm9yU291cmNlTGluZSh0YXJnZXRMaW5lKSB7XG4gICAgY29uc3QgbGluZU51bWJlciA9IE1hdGguZmxvb3IodGFyZ2V0TGluZSk7XG4gICAgY29uc3QgbGluZXMgPSBnZXRDb2RlTGluZUVsZW1lbnRzKCk7XG4gICAgbGV0IHByZXZpb3VzID0gbGluZXNbMF0gfHwgbnVsbDtcbiAgICBmb3IgKGNvbnN0IGVudHJ5IG9mIGxpbmVzKSB7XG4gICAgICAgIGlmIChlbnRyeS5saW5lID09PSBsaW5lTnVtYmVyKSB7XG4gICAgICAgICAgICByZXR1cm4geyBwcmV2aW91czogZW50cnksIG5leHQ6IHVuZGVmaW5lZCB9O1xuICAgICAgICB9XG4gICAgICAgIGVsc2UgaWYgKGVudHJ5LmxpbmUgPiBsaW5lTnVtYmVyKSB7XG4gICAgICAgICAgICByZXR1cm4geyBwcmV2aW91cywgbmV4dDogZW50cnkgfTtcbiAgICAgICAgfVxuICAgICAgICBwcmV2aW91cyA9IGVudHJ5O1xuICAgIH1cbiAgICByZXR1cm4geyBwcmV2aW91cyB9O1xufVxuZXhwb3J0cy5nZXRFbGVtZW50c0ZvclNvdXJjZUxpbmUgPSBnZXRFbGVtZW50c0ZvclNvdXJjZUxpbmU7XG4vKipcbiAqIEZpbmQgdGhlIGh0bWwgZWxlbWVudHMgdGhhdCBhcmUgYXQgYSBzcGVjaWZpYyBwaXhlbCBvZmZzZXQgb24gdGhlIHBhZ2UuXG4gKi9cbmZ1bmN0aW9uIGdldExpbmVFbGVtZW50c0F0UGFnZU9mZnNldChvZmZzZXQpIHtcbiAgICBjb25zdCBsaW5lcyA9IGdldENvZGVMaW5lRWxlbWVudHMoKTtcbiAgICBjb25zdCBwb3NpdGlvbiA9IG9mZnNldCAtIHdpbmRvdy5zY3JvbGxZO1xuICAgIGxldCBsbyA9IC0xO1xuICAgIGxldCBoaSA9IGxpbmVzLmxlbmd0aCAtIDE7XG4gICAgd2hpbGUgKGxvICsgMSA8IGhpKSB7XG4gICAgICAgIGNvbnN0IG1pZCA9IE1hdGguZmxvb3IoKGxvICsgaGkpIC8gMik7XG4gICAgICAgIGNvbnN0IGJvdW5kcyA9IGxpbmVzW21pZF0uZWxlbWVudC5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKTtcbiAgICAgICAgaWYgKGJvdW5kcy50b3AgKyBib3VuZHMuaGVpZ2h0ID49IHBvc2l0aW9uKSB7XG4gICAgICAgICAgICBoaSA9IG1pZDtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIGxvID0gbWlkO1xuICAgICAgICB9XG4gICAgfVxuICAgIGNvbnN0IGhpRWxlbWVudCA9IGxpbmVzW2hpXTtcbiAgICBjb25zdCBoaUJvdW5kcyA9IGhpRWxlbWVudC5lbGVtZW50LmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpO1xuICAgIGlmIChoaSA+PSAxICYmIGhpQm91bmRzLnRvcCA+IHBvc2l0aW9uKSB7XG4gICAgICAgIGNvbnN0IGxvRWxlbWVudCA9IGxpbmVzW2xvXTtcbiAgICAgICAgcmV0dXJuIHsgcHJldmlvdXM6IGxvRWxlbWVudCwgbmV4dDogaGlFbGVtZW50IH07XG4gICAgfVxuICAgIHJldHVybiB7IHByZXZpb3VzOiBoaUVsZW1lbnQgfTtcbn1cbmV4cG9ydHMuZ2V0TGluZUVsZW1lbnRzQXRQYWdlT2Zmc2V0ID0gZ2V0TGluZUVsZW1lbnRzQXRQYWdlT2Zmc2V0O1xuLyoqXG4gKiBBdHRlbXB0IHRvIHJldmVhbCB0aGUgZWxlbWVudCBmb3IgYSBzb3VyY2UgbGluZSBpbiB0aGUgZWRpdG9yLlxuICovXG5mdW5jdGlvbiBzY3JvbGxUb1JldmVhbFNvdXJjZUxpbmUobGluZSkge1xuICAgIGlmICghc2V0dGluZ3NfMS5nZXRTZXR0aW5ncygpLnNjcm9sbFByZXZpZXdXaXRoRWRpdG9yKSB7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG4gICAgaWYgKGxpbmUgPD0gMCkge1xuICAgICAgICB3aW5kb3cuc2Nyb2xsKHdpbmRvdy5zY3JvbGxYLCAwKTtcbiAgICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBjb25zdCB7IHByZXZpb3VzLCBuZXh0IH0gPSBnZXRFbGVtZW50c0ZvclNvdXJjZUxpbmUobGluZSk7XG4gICAgaWYgKCFwcmV2aW91cykge1xuICAgICAgICByZXR1cm47XG4gICAgfVxuICAgIGxldCBzY3JvbGxUbyA9IDA7XG4gICAgY29uc3QgcmVjdCA9IHByZXZpb3VzLmVsZW1lbnQuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCk7XG4gICAgY29uc3QgcHJldmlvdXNUb3AgPSByZWN0LnRvcDtcbiAgICBpZiAobmV4dCAmJiBuZXh0LmxpbmUgIT09IHByZXZpb3VzLmxpbmUpIHtcbiAgICAgICAgLy8gQmV0d2VlbiB0d28gZWxlbWVudHMuIEdvIHRvIHBlcmNlbnRhZ2Ugb2Zmc2V0IGJldHdlZW4gdGhlbS5cbiAgICAgICAgY29uc3QgYmV0d2VlblByb2dyZXNzID0gKGxpbmUgLSBwcmV2aW91cy5saW5lKSAvIChuZXh0LmxpbmUgLSBwcmV2aW91cy5saW5lKTtcbiAgICAgICAgY29uc3QgZWxlbWVudE9mZnNldCA9IG5leHQuZWxlbWVudC5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKS50b3AgLSBwcmV2aW91c1RvcDtcbiAgICAgICAgc2Nyb2xsVG8gPSBwcmV2aW91c1RvcCArIGJldHdlZW5Qcm9ncmVzcyAqIGVsZW1lbnRPZmZzZXQ7XG4gICAgfVxuICAgIGVsc2Uge1xuICAgICAgICBjb25zdCBwcm9ncmVzc0luRWxlbWVudCA9IGxpbmUgLSBNYXRoLmZsb29yKGxpbmUpO1xuICAgICAgICBzY3JvbGxUbyA9IHByZXZpb3VzVG9wICsgKHJlY3QuaGVpZ2h0ICogcHJvZ3Jlc3NJbkVsZW1lbnQpO1xuICAgIH1cbiAgICB3aW5kb3cuc2Nyb2xsKHdpbmRvdy5zY3JvbGxYLCBNYXRoLm1heCgxLCB3aW5kb3cuc2Nyb2xsWSArIHNjcm9sbFRvKSk7XG59XG5leHBvcnRzLnNjcm9sbFRvUmV2ZWFsU291cmNlTGluZSA9IHNjcm9sbFRvUmV2ZWFsU291cmNlTGluZTtcbmZ1bmN0aW9uIGdldEVkaXRvckxpbmVOdW1iZXJGb3JQYWdlT2Zmc2V0KG9mZnNldCkge1xuICAgIGNvbnN0IHsgcHJldmlvdXMsIG5leHQgfSA9IGdldExpbmVFbGVtZW50c0F0UGFnZU9mZnNldChvZmZzZXQpO1xuICAgIGlmIChwcmV2aW91cykge1xuICAgICAgICBjb25zdCBwcmV2aW91c0JvdW5kcyA9IHByZXZpb3VzLmVsZW1lbnQuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCk7XG4gICAgICAgIGNvbnN0IG9mZnNldEZyb21QcmV2aW91cyA9IChvZmZzZXQgLSB3aW5kb3cuc2Nyb2xsWSAtIHByZXZpb3VzQm91bmRzLnRvcCk7XG4gICAgICAgIGlmIChuZXh0KSB7XG4gICAgICAgICAgICBjb25zdCBwcm9ncmVzc0JldHdlZW5FbGVtZW50cyA9IG9mZnNldEZyb21QcmV2aW91cyAvIChuZXh0LmVsZW1lbnQuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCkudG9wIC0gcHJldmlvdXNCb3VuZHMudG9wKTtcbiAgICAgICAgICAgIGNvbnN0IGxpbmUgPSBwcmV2aW91cy5saW5lICsgcHJvZ3Jlc3NCZXR3ZWVuRWxlbWVudHMgKiAobmV4dC5saW5lIC0gcHJldmlvdXMubGluZSk7XG4gICAgICAgICAgICByZXR1cm4gY2xhbXBMaW5lKGxpbmUpO1xuICAgICAgICB9XG4gICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgY29uc3QgcHJvZ3Jlc3NXaXRoaW5FbGVtZW50ID0gb2Zmc2V0RnJvbVByZXZpb3VzIC8gKHByZXZpb3VzQm91bmRzLmhlaWdodCk7XG4gICAgICAgICAgICBjb25zdCBsaW5lID0gcHJldmlvdXMubGluZSArIHByb2dyZXNzV2l0aGluRWxlbWVudDtcbiAgICAgICAgICAgIHJldHVybiBjbGFtcExpbmUobGluZSk7XG4gICAgICAgIH1cbiAgICB9XG4gICAgcmV0dXJuIG51bGw7XG59XG5leHBvcnRzLmdldEVkaXRvckxpbmVOdW1iZXJGb3JQYWdlT2Zmc2V0ID0gZ2V0RWRpdG9yTGluZU51bWJlckZvclBhZ2VPZmZzZXQ7XG4vKipcbiAqIFRyeSB0byBmaW5kIHRoZSBodG1sIGVsZW1lbnQgYnkgdXNpbmcgYSBmcmFnbWVudCBpZFxuICovXG5mdW5jdGlvbiBnZXRMaW5lRWxlbWVudEZvckZyYWdtZW50KGZyYWdtZW50KSB7XG4gICAgcmV0dXJuIGdldENvZGVMaW5lRWxlbWVudHMoKS5maW5kKChlbGVtZW50KSA9PiB7XG4gICAgICAgIHJldHVybiBlbGVtZW50LmVsZW1lbnQuaWQgPT09IGZyYWdtZW50O1xuICAgIH0pO1xufVxuZXhwb3J0cy5nZXRMaW5lRWxlbWVudEZvckZyYWdtZW50ID0gZ2V0TGluZUVsZW1lbnRGb3JGcmFnbWVudDtcbiIsInZhciBnO1xyXG5cclxuLy8gVGhpcyB3b3JrcyBpbiBub24tc3RyaWN0IG1vZGVcclxuZyA9IChmdW5jdGlvbigpIHtcclxuXHRyZXR1cm4gdGhpcztcclxufSkoKTtcclxuXHJcbnRyeSB7XHJcblx0Ly8gVGhpcyB3b3JrcyBpZiBldmFsIGlzIGFsbG93ZWQgKHNlZSBDU1ApXHJcblx0ZyA9IGcgfHwgRnVuY3Rpb24oXCJyZXR1cm4gdGhpc1wiKSgpIHx8ICgxLCBldmFsKShcInRoaXNcIik7XHJcbn0gY2F0Y2ggKGUpIHtcclxuXHQvLyBUaGlzIHdvcmtzIGlmIHRoZSB3aW5kb3cgcmVmZXJlbmNlIGlzIGF2YWlsYWJsZVxyXG5cdGlmICh0eXBlb2Ygd2luZG93ID09PSBcIm9iamVjdFwiKSBnID0gd2luZG93O1xyXG59XHJcblxyXG4vLyBnIGNhbiBzdGlsbCBiZSB1bmRlZmluZWQsIGJ1dCBub3RoaW5nIHRvIGRvIGFib3V0IGl0Li4uXHJcbi8vIFdlIHJldHVybiB1bmRlZmluZWQsIGluc3RlYWQgb2Ygbm90aGluZyBoZXJlLCBzbyBpdCdzXHJcbi8vIGVhc2llciB0byBoYW5kbGUgdGhpcyBjYXNlLiBpZighZ2xvYmFsKSB7IC4uLn1cclxuXHJcbm1vZHVsZS5leHBvcnRzID0gZztcclxuIiwiLyoqXG4gKiBsb2Rhc2ggKEN1c3RvbSBCdWlsZCkgPGh0dHBzOi8vbG9kYXNoLmNvbS8+XG4gKiBCdWlsZDogYGxvZGFzaCBtb2R1bGFyaXplIGV4cG9ydHM9XCJucG1cIiAtbyAuL2BcbiAqIENvcHlyaWdodCBqUXVlcnkgRm91bmRhdGlvbiBhbmQgb3RoZXIgY29udHJpYnV0b3JzIDxodHRwczovL2pxdWVyeS5vcmcvPlxuICogUmVsZWFzZWQgdW5kZXIgTUlUIGxpY2Vuc2UgPGh0dHBzOi8vbG9kYXNoLmNvbS9saWNlbnNlPlxuICogQmFzZWQgb24gVW5kZXJzY29yZS5qcyAxLjguMyA8aHR0cDovL3VuZGVyc2NvcmVqcy5vcmcvTElDRU5TRT5cbiAqIENvcHlyaWdodCBKZXJlbXkgQXNoa2VuYXMsIERvY3VtZW50Q2xvdWQgYW5kIEludmVzdGlnYXRpdmUgUmVwb3J0ZXJzICYgRWRpdG9yc1xuICovXG5cbi8qKiBVc2VkIGFzIHRoZSBgVHlwZUVycm9yYCBtZXNzYWdlIGZvciBcIkZ1bmN0aW9uc1wiIG1ldGhvZHMuICovXG52YXIgRlVOQ19FUlJPUl9URVhUID0gJ0V4cGVjdGVkIGEgZnVuY3Rpb24nO1xuXG4vKiogVXNlZCBhcyByZWZlcmVuY2VzIGZvciB2YXJpb3VzIGBOdW1iZXJgIGNvbnN0YW50cy4gKi9cbnZhciBOQU4gPSAwIC8gMDtcblxuLyoqIGBPYmplY3QjdG9TdHJpbmdgIHJlc3VsdCByZWZlcmVuY2VzLiAqL1xudmFyIHN5bWJvbFRhZyA9ICdbb2JqZWN0IFN5bWJvbF0nO1xuXG4vKiogVXNlZCB0byBtYXRjaCBsZWFkaW5nIGFuZCB0cmFpbGluZyB3aGl0ZXNwYWNlLiAqL1xudmFyIHJlVHJpbSA9IC9eXFxzK3xcXHMrJC9nO1xuXG4vKiogVXNlZCB0byBkZXRlY3QgYmFkIHNpZ25lZCBoZXhhZGVjaW1hbCBzdHJpbmcgdmFsdWVzLiAqL1xudmFyIHJlSXNCYWRIZXggPSAvXlstK10weFswLTlhLWZdKyQvaTtcblxuLyoqIFVzZWQgdG8gZGV0ZWN0IGJpbmFyeSBzdHJpbmcgdmFsdWVzLiAqL1xudmFyIHJlSXNCaW5hcnkgPSAvXjBiWzAxXSskL2k7XG5cbi8qKiBVc2VkIHRvIGRldGVjdCBvY3RhbCBzdHJpbmcgdmFsdWVzLiAqL1xudmFyIHJlSXNPY3RhbCA9IC9eMG9bMC03XSskL2k7XG5cbi8qKiBCdWlsdC1pbiBtZXRob2QgcmVmZXJlbmNlcyB3aXRob3V0IGEgZGVwZW5kZW5jeSBvbiBgcm9vdGAuICovXG52YXIgZnJlZVBhcnNlSW50ID0gcGFyc2VJbnQ7XG5cbi8qKiBEZXRlY3QgZnJlZSB2YXJpYWJsZSBgZ2xvYmFsYCBmcm9tIE5vZGUuanMuICovXG52YXIgZnJlZUdsb2JhbCA9IHR5cGVvZiBnbG9iYWwgPT0gJ29iamVjdCcgJiYgZ2xvYmFsICYmIGdsb2JhbC5PYmplY3QgPT09IE9iamVjdCAmJiBnbG9iYWw7XG5cbi8qKiBEZXRlY3QgZnJlZSB2YXJpYWJsZSBgc2VsZmAuICovXG52YXIgZnJlZVNlbGYgPSB0eXBlb2Ygc2VsZiA9PSAnb2JqZWN0JyAmJiBzZWxmICYmIHNlbGYuT2JqZWN0ID09PSBPYmplY3QgJiYgc2VsZjtcblxuLyoqIFVzZWQgYXMgYSByZWZlcmVuY2UgdG8gdGhlIGdsb2JhbCBvYmplY3QuICovXG52YXIgcm9vdCA9IGZyZWVHbG9iYWwgfHwgZnJlZVNlbGYgfHwgRnVuY3Rpb24oJ3JldHVybiB0aGlzJykoKTtcblxuLyoqIFVzZWQgZm9yIGJ1aWx0LWluIG1ldGhvZCByZWZlcmVuY2VzLiAqL1xudmFyIG9iamVjdFByb3RvID0gT2JqZWN0LnByb3RvdHlwZTtcblxuLyoqXG4gKiBVc2VkIHRvIHJlc29sdmUgdGhlXG4gKiBbYHRvU3RyaW5nVGFnYF0oaHR0cDovL2VjbWEtaW50ZXJuYXRpb25hbC5vcmcvZWNtYS0yNjIvNy4wLyNzZWMtb2JqZWN0LnByb3RvdHlwZS50b3N0cmluZylcbiAqIG9mIHZhbHVlcy5cbiAqL1xudmFyIG9iamVjdFRvU3RyaW5nID0gb2JqZWN0UHJvdG8udG9TdHJpbmc7XG5cbi8qIEJ1aWx0LWluIG1ldGhvZCByZWZlcmVuY2VzIGZvciB0aG9zZSB3aXRoIHRoZSBzYW1lIG5hbWUgYXMgb3RoZXIgYGxvZGFzaGAgbWV0aG9kcy4gKi9cbnZhciBuYXRpdmVNYXggPSBNYXRoLm1heCxcbiAgICBuYXRpdmVNaW4gPSBNYXRoLm1pbjtcblxuLyoqXG4gKiBHZXRzIHRoZSB0aW1lc3RhbXAgb2YgdGhlIG51bWJlciBvZiBtaWxsaXNlY29uZHMgdGhhdCBoYXZlIGVsYXBzZWQgc2luY2VcbiAqIHRoZSBVbml4IGVwb2NoICgxIEphbnVhcnkgMTk3MCAwMDowMDowMCBVVEMpLlxuICpcbiAqIEBzdGF0aWNcbiAqIEBtZW1iZXJPZiBfXG4gKiBAc2luY2UgMi40LjBcbiAqIEBjYXRlZ29yeSBEYXRlXG4gKiBAcmV0dXJucyB7bnVtYmVyfSBSZXR1cm5zIHRoZSB0aW1lc3RhbXAuXG4gKiBAZXhhbXBsZVxuICpcbiAqIF8uZGVmZXIoZnVuY3Rpb24oc3RhbXApIHtcbiAqICAgY29uc29sZS5sb2coXy5ub3coKSAtIHN0YW1wKTtcbiAqIH0sIF8ubm93KCkpO1xuICogLy8gPT4gTG9ncyB0aGUgbnVtYmVyIG9mIG1pbGxpc2Vjb25kcyBpdCB0b29rIGZvciB0aGUgZGVmZXJyZWQgaW52b2NhdGlvbi5cbiAqL1xudmFyIG5vdyA9IGZ1bmN0aW9uKCkge1xuICByZXR1cm4gcm9vdC5EYXRlLm5vdygpO1xufTtcblxuLyoqXG4gKiBDcmVhdGVzIGEgZGVib3VuY2VkIGZ1bmN0aW9uIHRoYXQgZGVsYXlzIGludm9raW5nIGBmdW5jYCB1bnRpbCBhZnRlciBgd2FpdGBcbiAqIG1pbGxpc2Vjb25kcyBoYXZlIGVsYXBzZWQgc2luY2UgdGhlIGxhc3QgdGltZSB0aGUgZGVib3VuY2VkIGZ1bmN0aW9uIHdhc1xuICogaW52b2tlZC4gVGhlIGRlYm91bmNlZCBmdW5jdGlvbiBjb21lcyB3aXRoIGEgYGNhbmNlbGAgbWV0aG9kIHRvIGNhbmNlbFxuICogZGVsYXllZCBgZnVuY2AgaW52b2NhdGlvbnMgYW5kIGEgYGZsdXNoYCBtZXRob2QgdG8gaW1tZWRpYXRlbHkgaW52b2tlIHRoZW0uXG4gKiBQcm92aWRlIGBvcHRpb25zYCB0byBpbmRpY2F0ZSB3aGV0aGVyIGBmdW5jYCBzaG91bGQgYmUgaW52b2tlZCBvbiB0aGVcbiAqIGxlYWRpbmcgYW5kL29yIHRyYWlsaW5nIGVkZ2Ugb2YgdGhlIGB3YWl0YCB0aW1lb3V0LiBUaGUgYGZ1bmNgIGlzIGludm9rZWRcbiAqIHdpdGggdGhlIGxhc3QgYXJndW1lbnRzIHByb3ZpZGVkIHRvIHRoZSBkZWJvdW5jZWQgZnVuY3Rpb24uIFN1YnNlcXVlbnRcbiAqIGNhbGxzIHRvIHRoZSBkZWJvdW5jZWQgZnVuY3Rpb24gcmV0dXJuIHRoZSByZXN1bHQgb2YgdGhlIGxhc3QgYGZ1bmNgXG4gKiBpbnZvY2F0aW9uLlxuICpcbiAqICoqTm90ZToqKiBJZiBgbGVhZGluZ2AgYW5kIGB0cmFpbGluZ2Agb3B0aW9ucyBhcmUgYHRydWVgLCBgZnVuY2AgaXNcbiAqIGludm9rZWQgb24gdGhlIHRyYWlsaW5nIGVkZ2Ugb2YgdGhlIHRpbWVvdXQgb25seSBpZiB0aGUgZGVib3VuY2VkIGZ1bmN0aW9uXG4gKiBpcyBpbnZva2VkIG1vcmUgdGhhbiBvbmNlIGR1cmluZyB0aGUgYHdhaXRgIHRpbWVvdXQuXG4gKlxuICogSWYgYHdhaXRgIGlzIGAwYCBhbmQgYGxlYWRpbmdgIGlzIGBmYWxzZWAsIGBmdW5jYCBpbnZvY2F0aW9uIGlzIGRlZmVycmVkXG4gKiB1bnRpbCB0byB0aGUgbmV4dCB0aWNrLCBzaW1pbGFyIHRvIGBzZXRUaW1lb3V0YCB3aXRoIGEgdGltZW91dCBvZiBgMGAuXG4gKlxuICogU2VlIFtEYXZpZCBDb3JiYWNobydzIGFydGljbGVdKGh0dHBzOi8vY3NzLXRyaWNrcy5jb20vZGVib3VuY2luZy10aHJvdHRsaW5nLWV4cGxhaW5lZC1leGFtcGxlcy8pXG4gKiBmb3IgZGV0YWlscyBvdmVyIHRoZSBkaWZmZXJlbmNlcyBiZXR3ZWVuIGBfLmRlYm91bmNlYCBhbmQgYF8udGhyb3R0bGVgLlxuICpcbiAqIEBzdGF0aWNcbiAqIEBtZW1iZXJPZiBfXG4gKiBAc2luY2UgMC4xLjBcbiAqIEBjYXRlZ29yeSBGdW5jdGlvblxuICogQHBhcmFtIHtGdW5jdGlvbn0gZnVuYyBUaGUgZnVuY3Rpb24gdG8gZGVib3VuY2UuXG4gKiBAcGFyYW0ge251bWJlcn0gW3dhaXQ9MF0gVGhlIG51bWJlciBvZiBtaWxsaXNlY29uZHMgdG8gZGVsYXkuXG4gKiBAcGFyYW0ge09iamVjdH0gW29wdGlvbnM9e31dIFRoZSBvcHRpb25zIG9iamVjdC5cbiAqIEBwYXJhbSB7Ym9vbGVhbn0gW29wdGlvbnMubGVhZGluZz1mYWxzZV1cbiAqICBTcGVjaWZ5IGludm9raW5nIG9uIHRoZSBsZWFkaW5nIGVkZ2Ugb2YgdGhlIHRpbWVvdXQuXG4gKiBAcGFyYW0ge251bWJlcn0gW29wdGlvbnMubWF4V2FpdF1cbiAqICBUaGUgbWF4aW11bSB0aW1lIGBmdW5jYCBpcyBhbGxvd2VkIHRvIGJlIGRlbGF5ZWQgYmVmb3JlIGl0J3MgaW52b2tlZC5cbiAqIEBwYXJhbSB7Ym9vbGVhbn0gW29wdGlvbnMudHJhaWxpbmc9dHJ1ZV1cbiAqICBTcGVjaWZ5IGludm9raW5nIG9uIHRoZSB0cmFpbGluZyBlZGdlIG9mIHRoZSB0aW1lb3V0LlxuICogQHJldHVybnMge0Z1bmN0aW9ufSBSZXR1cm5zIHRoZSBuZXcgZGVib3VuY2VkIGZ1bmN0aW9uLlxuICogQGV4YW1wbGVcbiAqXG4gKiAvLyBBdm9pZCBjb3N0bHkgY2FsY3VsYXRpb25zIHdoaWxlIHRoZSB3aW5kb3cgc2l6ZSBpcyBpbiBmbHV4LlxuICogalF1ZXJ5KHdpbmRvdykub24oJ3Jlc2l6ZScsIF8uZGVib3VuY2UoY2FsY3VsYXRlTGF5b3V0LCAxNTApKTtcbiAqXG4gKiAvLyBJbnZva2UgYHNlbmRNYWlsYCB3aGVuIGNsaWNrZWQsIGRlYm91bmNpbmcgc3Vic2VxdWVudCBjYWxscy5cbiAqIGpRdWVyeShlbGVtZW50KS5vbignY2xpY2snLCBfLmRlYm91bmNlKHNlbmRNYWlsLCAzMDAsIHtcbiAqICAgJ2xlYWRpbmcnOiB0cnVlLFxuICogICAndHJhaWxpbmcnOiBmYWxzZVxuICogfSkpO1xuICpcbiAqIC8vIEVuc3VyZSBgYmF0Y2hMb2dgIGlzIGludm9rZWQgb25jZSBhZnRlciAxIHNlY29uZCBvZiBkZWJvdW5jZWQgY2FsbHMuXG4gKiB2YXIgZGVib3VuY2VkID0gXy5kZWJvdW5jZShiYXRjaExvZywgMjUwLCB7ICdtYXhXYWl0JzogMTAwMCB9KTtcbiAqIHZhciBzb3VyY2UgPSBuZXcgRXZlbnRTb3VyY2UoJy9zdHJlYW0nKTtcbiAqIGpRdWVyeShzb3VyY2UpLm9uKCdtZXNzYWdlJywgZGVib3VuY2VkKTtcbiAqXG4gKiAvLyBDYW5jZWwgdGhlIHRyYWlsaW5nIGRlYm91bmNlZCBpbnZvY2F0aW9uLlxuICogalF1ZXJ5KHdpbmRvdykub24oJ3BvcHN0YXRlJywgZGVib3VuY2VkLmNhbmNlbCk7XG4gKi9cbmZ1bmN0aW9uIGRlYm91bmNlKGZ1bmMsIHdhaXQsIG9wdGlvbnMpIHtcbiAgdmFyIGxhc3RBcmdzLFxuICAgICAgbGFzdFRoaXMsXG4gICAgICBtYXhXYWl0LFxuICAgICAgcmVzdWx0LFxuICAgICAgdGltZXJJZCxcbiAgICAgIGxhc3RDYWxsVGltZSxcbiAgICAgIGxhc3RJbnZva2VUaW1lID0gMCxcbiAgICAgIGxlYWRpbmcgPSBmYWxzZSxcbiAgICAgIG1heGluZyA9IGZhbHNlLFxuICAgICAgdHJhaWxpbmcgPSB0cnVlO1xuXG4gIGlmICh0eXBlb2YgZnVuYyAhPSAnZnVuY3Rpb24nKSB7XG4gICAgdGhyb3cgbmV3IFR5cGVFcnJvcihGVU5DX0VSUk9SX1RFWFQpO1xuICB9XG4gIHdhaXQgPSB0b051bWJlcih3YWl0KSB8fCAwO1xuICBpZiAoaXNPYmplY3Qob3B0aW9ucykpIHtcbiAgICBsZWFkaW5nID0gISFvcHRpb25zLmxlYWRpbmc7XG4gICAgbWF4aW5nID0gJ21heFdhaXQnIGluIG9wdGlvbnM7XG4gICAgbWF4V2FpdCA9IG1heGluZyA/IG5hdGl2ZU1heCh0b051bWJlcihvcHRpb25zLm1heFdhaXQpIHx8IDAsIHdhaXQpIDogbWF4V2FpdDtcbiAgICB0cmFpbGluZyA9ICd0cmFpbGluZycgaW4gb3B0aW9ucyA/ICEhb3B0aW9ucy50cmFpbGluZyA6IHRyYWlsaW5nO1xuICB9XG5cbiAgZnVuY3Rpb24gaW52b2tlRnVuYyh0aW1lKSB7XG4gICAgdmFyIGFyZ3MgPSBsYXN0QXJncyxcbiAgICAgICAgdGhpc0FyZyA9IGxhc3RUaGlzO1xuXG4gICAgbGFzdEFyZ3MgPSBsYXN0VGhpcyA9IHVuZGVmaW5lZDtcbiAgICBsYXN0SW52b2tlVGltZSA9IHRpbWU7XG4gICAgcmVzdWx0ID0gZnVuYy5hcHBseSh0aGlzQXJnLCBhcmdzKTtcbiAgICByZXR1cm4gcmVzdWx0O1xuICB9XG5cbiAgZnVuY3Rpb24gbGVhZGluZ0VkZ2UodGltZSkge1xuICAgIC8vIFJlc2V0IGFueSBgbWF4V2FpdGAgdGltZXIuXG4gICAgbGFzdEludm9rZVRpbWUgPSB0aW1lO1xuICAgIC8vIFN0YXJ0IHRoZSB0aW1lciBmb3IgdGhlIHRyYWlsaW5nIGVkZ2UuXG4gICAgdGltZXJJZCA9IHNldFRpbWVvdXQodGltZXJFeHBpcmVkLCB3YWl0KTtcbiAgICAvLyBJbnZva2UgdGhlIGxlYWRpbmcgZWRnZS5cbiAgICByZXR1cm4gbGVhZGluZyA/IGludm9rZUZ1bmModGltZSkgOiByZXN1bHQ7XG4gIH1cblxuICBmdW5jdGlvbiByZW1haW5pbmdXYWl0KHRpbWUpIHtcbiAgICB2YXIgdGltZVNpbmNlTGFzdENhbGwgPSB0aW1lIC0gbGFzdENhbGxUaW1lLFxuICAgICAgICB0aW1lU2luY2VMYXN0SW52b2tlID0gdGltZSAtIGxhc3RJbnZva2VUaW1lLFxuICAgICAgICByZXN1bHQgPSB3YWl0IC0gdGltZVNpbmNlTGFzdENhbGw7XG5cbiAgICByZXR1cm4gbWF4aW5nID8gbmF0aXZlTWluKHJlc3VsdCwgbWF4V2FpdCAtIHRpbWVTaW5jZUxhc3RJbnZva2UpIDogcmVzdWx0O1xuICB9XG5cbiAgZnVuY3Rpb24gc2hvdWxkSW52b2tlKHRpbWUpIHtcbiAgICB2YXIgdGltZVNpbmNlTGFzdENhbGwgPSB0aW1lIC0gbGFzdENhbGxUaW1lLFxuICAgICAgICB0aW1lU2luY2VMYXN0SW52b2tlID0gdGltZSAtIGxhc3RJbnZva2VUaW1lO1xuXG4gICAgLy8gRWl0aGVyIHRoaXMgaXMgdGhlIGZpcnN0IGNhbGwsIGFjdGl2aXR5IGhhcyBzdG9wcGVkIGFuZCB3ZSdyZSBhdCB0aGVcbiAgICAvLyB0cmFpbGluZyBlZGdlLCB0aGUgc3lzdGVtIHRpbWUgaGFzIGdvbmUgYmFja3dhcmRzIGFuZCB3ZSdyZSB0cmVhdGluZ1xuICAgIC8vIGl0IGFzIHRoZSB0cmFpbGluZyBlZGdlLCBvciB3ZSd2ZSBoaXQgdGhlIGBtYXhXYWl0YCBsaW1pdC5cbiAgICByZXR1cm4gKGxhc3RDYWxsVGltZSA9PT0gdW5kZWZpbmVkIHx8ICh0aW1lU2luY2VMYXN0Q2FsbCA+PSB3YWl0KSB8fFxuICAgICAgKHRpbWVTaW5jZUxhc3RDYWxsIDwgMCkgfHwgKG1heGluZyAmJiB0aW1lU2luY2VMYXN0SW52b2tlID49IG1heFdhaXQpKTtcbiAgfVxuXG4gIGZ1bmN0aW9uIHRpbWVyRXhwaXJlZCgpIHtcbiAgICB2YXIgdGltZSA9IG5vdygpO1xuICAgIGlmIChzaG91bGRJbnZva2UodGltZSkpIHtcbiAgICAgIHJldHVybiB0cmFpbGluZ0VkZ2UodGltZSk7XG4gICAgfVxuICAgIC8vIFJlc3RhcnQgdGhlIHRpbWVyLlxuICAgIHRpbWVySWQgPSBzZXRUaW1lb3V0KHRpbWVyRXhwaXJlZCwgcmVtYWluaW5nV2FpdCh0aW1lKSk7XG4gIH1cblxuICBmdW5jdGlvbiB0cmFpbGluZ0VkZ2UodGltZSkge1xuICAgIHRpbWVySWQgPSB1bmRlZmluZWQ7XG5cbiAgICAvLyBPbmx5IGludm9rZSBpZiB3ZSBoYXZlIGBsYXN0QXJnc2Agd2hpY2ggbWVhbnMgYGZ1bmNgIGhhcyBiZWVuXG4gICAgLy8gZGVib3VuY2VkIGF0IGxlYXN0IG9uY2UuXG4gICAgaWYgKHRyYWlsaW5nICYmIGxhc3RBcmdzKSB7XG4gICAgICByZXR1cm4gaW52b2tlRnVuYyh0aW1lKTtcbiAgICB9XG4gICAgbGFzdEFyZ3MgPSBsYXN0VGhpcyA9IHVuZGVmaW5lZDtcbiAgICByZXR1cm4gcmVzdWx0O1xuICB9XG5cbiAgZnVuY3Rpb24gY2FuY2VsKCkge1xuICAgIGlmICh0aW1lcklkICE9PSB1bmRlZmluZWQpIHtcbiAgICAgIGNsZWFyVGltZW91dCh0aW1lcklkKTtcbiAgICB9XG4gICAgbGFzdEludm9rZVRpbWUgPSAwO1xuICAgIGxhc3RBcmdzID0gbGFzdENhbGxUaW1lID0gbGFzdFRoaXMgPSB0aW1lcklkID0gdW5kZWZpbmVkO1xuICB9XG5cbiAgZnVuY3Rpb24gZmx1c2goKSB7XG4gICAgcmV0dXJuIHRpbWVySWQgPT09IHVuZGVmaW5lZCA/IHJlc3VsdCA6IHRyYWlsaW5nRWRnZShub3coKSk7XG4gIH1cblxuICBmdW5jdGlvbiBkZWJvdW5jZWQoKSB7XG4gICAgdmFyIHRpbWUgPSBub3coKSxcbiAgICAgICAgaXNJbnZva2luZyA9IHNob3VsZEludm9rZSh0aW1lKTtcblxuICAgIGxhc3RBcmdzID0gYXJndW1lbnRzO1xuICAgIGxhc3RUaGlzID0gdGhpcztcbiAgICBsYXN0Q2FsbFRpbWUgPSB0aW1lO1xuXG4gICAgaWYgKGlzSW52b2tpbmcpIHtcbiAgICAgIGlmICh0aW1lcklkID09PSB1bmRlZmluZWQpIHtcbiAgICAgICAgcmV0dXJuIGxlYWRpbmdFZGdlKGxhc3RDYWxsVGltZSk7XG4gICAgICB9XG4gICAgICBpZiAobWF4aW5nKSB7XG4gICAgICAgIC8vIEhhbmRsZSBpbnZvY2F0aW9ucyBpbiBhIHRpZ2h0IGxvb3AuXG4gICAgICAgIHRpbWVySWQgPSBzZXRUaW1lb3V0KHRpbWVyRXhwaXJlZCwgd2FpdCk7XG4gICAgICAgIHJldHVybiBpbnZva2VGdW5jKGxhc3RDYWxsVGltZSk7XG4gICAgICB9XG4gICAgfVxuICAgIGlmICh0aW1lcklkID09PSB1bmRlZmluZWQpIHtcbiAgICAgIHRpbWVySWQgPSBzZXRUaW1lb3V0KHRpbWVyRXhwaXJlZCwgd2FpdCk7XG4gICAgfVxuICAgIHJldHVybiByZXN1bHQ7XG4gIH1cbiAgZGVib3VuY2VkLmNhbmNlbCA9IGNhbmNlbDtcbiAgZGVib3VuY2VkLmZsdXNoID0gZmx1c2g7XG4gIHJldHVybiBkZWJvdW5jZWQ7XG59XG5cbi8qKlxuICogQ3JlYXRlcyBhIHRocm90dGxlZCBmdW5jdGlvbiB0aGF0IG9ubHkgaW52b2tlcyBgZnVuY2AgYXQgbW9zdCBvbmNlIHBlclxuICogZXZlcnkgYHdhaXRgIG1pbGxpc2Vjb25kcy4gVGhlIHRocm90dGxlZCBmdW5jdGlvbiBjb21lcyB3aXRoIGEgYGNhbmNlbGBcbiAqIG1ldGhvZCB0byBjYW5jZWwgZGVsYXllZCBgZnVuY2AgaW52b2NhdGlvbnMgYW5kIGEgYGZsdXNoYCBtZXRob2QgdG9cbiAqIGltbWVkaWF0ZWx5IGludm9rZSB0aGVtLiBQcm92aWRlIGBvcHRpb25zYCB0byBpbmRpY2F0ZSB3aGV0aGVyIGBmdW5jYFxuICogc2hvdWxkIGJlIGludm9rZWQgb24gdGhlIGxlYWRpbmcgYW5kL29yIHRyYWlsaW5nIGVkZ2Ugb2YgdGhlIGB3YWl0YFxuICogdGltZW91dC4gVGhlIGBmdW5jYCBpcyBpbnZva2VkIHdpdGggdGhlIGxhc3QgYXJndW1lbnRzIHByb3ZpZGVkIHRvIHRoZVxuICogdGhyb3R0bGVkIGZ1bmN0aW9uLiBTdWJzZXF1ZW50IGNhbGxzIHRvIHRoZSB0aHJvdHRsZWQgZnVuY3Rpb24gcmV0dXJuIHRoZVxuICogcmVzdWx0IG9mIHRoZSBsYXN0IGBmdW5jYCBpbnZvY2F0aW9uLlxuICpcbiAqICoqTm90ZToqKiBJZiBgbGVhZGluZ2AgYW5kIGB0cmFpbGluZ2Agb3B0aW9ucyBhcmUgYHRydWVgLCBgZnVuY2AgaXNcbiAqIGludm9rZWQgb24gdGhlIHRyYWlsaW5nIGVkZ2Ugb2YgdGhlIHRpbWVvdXQgb25seSBpZiB0aGUgdGhyb3R0bGVkIGZ1bmN0aW9uXG4gKiBpcyBpbnZva2VkIG1vcmUgdGhhbiBvbmNlIGR1cmluZyB0aGUgYHdhaXRgIHRpbWVvdXQuXG4gKlxuICogSWYgYHdhaXRgIGlzIGAwYCBhbmQgYGxlYWRpbmdgIGlzIGBmYWxzZWAsIGBmdW5jYCBpbnZvY2F0aW9uIGlzIGRlZmVycmVkXG4gKiB1bnRpbCB0byB0aGUgbmV4dCB0aWNrLCBzaW1pbGFyIHRvIGBzZXRUaW1lb3V0YCB3aXRoIGEgdGltZW91dCBvZiBgMGAuXG4gKlxuICogU2VlIFtEYXZpZCBDb3JiYWNobydzIGFydGljbGVdKGh0dHBzOi8vY3NzLXRyaWNrcy5jb20vZGVib3VuY2luZy10aHJvdHRsaW5nLWV4cGxhaW5lZC1leGFtcGxlcy8pXG4gKiBmb3IgZGV0YWlscyBvdmVyIHRoZSBkaWZmZXJlbmNlcyBiZXR3ZWVuIGBfLnRocm90dGxlYCBhbmQgYF8uZGVib3VuY2VgLlxuICpcbiAqIEBzdGF0aWNcbiAqIEBtZW1iZXJPZiBfXG4gKiBAc2luY2UgMC4xLjBcbiAqIEBjYXRlZ29yeSBGdW5jdGlvblxuICogQHBhcmFtIHtGdW5jdGlvbn0gZnVuYyBUaGUgZnVuY3Rpb24gdG8gdGhyb3R0bGUuXG4gKiBAcGFyYW0ge251bWJlcn0gW3dhaXQ9MF0gVGhlIG51bWJlciBvZiBtaWxsaXNlY29uZHMgdG8gdGhyb3R0bGUgaW52b2NhdGlvbnMgdG8uXG4gKiBAcGFyYW0ge09iamVjdH0gW29wdGlvbnM9e31dIFRoZSBvcHRpb25zIG9iamVjdC5cbiAqIEBwYXJhbSB7Ym9vbGVhbn0gW29wdGlvbnMubGVhZGluZz10cnVlXVxuICogIFNwZWNpZnkgaW52b2tpbmcgb24gdGhlIGxlYWRpbmcgZWRnZSBvZiB0aGUgdGltZW91dC5cbiAqIEBwYXJhbSB7Ym9vbGVhbn0gW29wdGlvbnMudHJhaWxpbmc9dHJ1ZV1cbiAqICBTcGVjaWZ5IGludm9raW5nIG9uIHRoZSB0cmFpbGluZyBlZGdlIG9mIHRoZSB0aW1lb3V0LlxuICogQHJldHVybnMge0Z1bmN0aW9ufSBSZXR1cm5zIHRoZSBuZXcgdGhyb3R0bGVkIGZ1bmN0aW9uLlxuICogQGV4YW1wbGVcbiAqXG4gKiAvLyBBdm9pZCBleGNlc3NpdmVseSB1cGRhdGluZyB0aGUgcG9zaXRpb24gd2hpbGUgc2Nyb2xsaW5nLlxuICogalF1ZXJ5KHdpbmRvdykub24oJ3Njcm9sbCcsIF8udGhyb3R0bGUodXBkYXRlUG9zaXRpb24sIDEwMCkpO1xuICpcbiAqIC8vIEludm9rZSBgcmVuZXdUb2tlbmAgd2hlbiB0aGUgY2xpY2sgZXZlbnQgaXMgZmlyZWQsIGJ1dCBub3QgbW9yZSB0aGFuIG9uY2UgZXZlcnkgNSBtaW51dGVzLlxuICogdmFyIHRocm90dGxlZCA9IF8udGhyb3R0bGUocmVuZXdUb2tlbiwgMzAwMDAwLCB7ICd0cmFpbGluZyc6IGZhbHNlIH0pO1xuICogalF1ZXJ5KGVsZW1lbnQpLm9uKCdjbGljaycsIHRocm90dGxlZCk7XG4gKlxuICogLy8gQ2FuY2VsIHRoZSB0cmFpbGluZyB0aHJvdHRsZWQgaW52b2NhdGlvbi5cbiAqIGpRdWVyeSh3aW5kb3cpLm9uKCdwb3BzdGF0ZScsIHRocm90dGxlZC5jYW5jZWwpO1xuICovXG5mdW5jdGlvbiB0aHJvdHRsZShmdW5jLCB3YWl0LCBvcHRpb25zKSB7XG4gIHZhciBsZWFkaW5nID0gdHJ1ZSxcbiAgICAgIHRyYWlsaW5nID0gdHJ1ZTtcblxuICBpZiAodHlwZW9mIGZ1bmMgIT0gJ2Z1bmN0aW9uJykge1xuICAgIHRocm93IG5ldyBUeXBlRXJyb3IoRlVOQ19FUlJPUl9URVhUKTtcbiAgfVxuICBpZiAoaXNPYmplY3Qob3B0aW9ucykpIHtcbiAgICBsZWFkaW5nID0gJ2xlYWRpbmcnIGluIG9wdGlvbnMgPyAhIW9wdGlvbnMubGVhZGluZyA6IGxlYWRpbmc7XG4gICAgdHJhaWxpbmcgPSAndHJhaWxpbmcnIGluIG9wdGlvbnMgPyAhIW9wdGlvbnMudHJhaWxpbmcgOiB0cmFpbGluZztcbiAgfVxuICByZXR1cm4gZGVib3VuY2UoZnVuYywgd2FpdCwge1xuICAgICdsZWFkaW5nJzogbGVhZGluZyxcbiAgICAnbWF4V2FpdCc6IHdhaXQsXG4gICAgJ3RyYWlsaW5nJzogdHJhaWxpbmdcbiAgfSk7XG59XG5cbi8qKlxuICogQ2hlY2tzIGlmIGB2YWx1ZWAgaXMgdGhlXG4gKiBbbGFuZ3VhZ2UgdHlwZV0oaHR0cDovL3d3dy5lY21hLWludGVybmF0aW9uYWwub3JnL2VjbWEtMjYyLzcuMC8jc2VjLWVjbWFzY3JpcHQtbGFuZ3VhZ2UtdHlwZXMpXG4gKiBvZiBgT2JqZWN0YC4gKGUuZy4gYXJyYXlzLCBmdW5jdGlvbnMsIG9iamVjdHMsIHJlZ2V4ZXMsIGBuZXcgTnVtYmVyKDApYCwgYW5kIGBuZXcgU3RyaW5nKCcnKWApXG4gKlxuICogQHN0YXRpY1xuICogQG1lbWJlck9mIF9cbiAqIEBzaW5jZSAwLjEuMFxuICogQGNhdGVnb3J5IExhbmdcbiAqIEBwYXJhbSB7Kn0gdmFsdWUgVGhlIHZhbHVlIHRvIGNoZWNrLlxuICogQHJldHVybnMge2Jvb2xlYW59IFJldHVybnMgYHRydWVgIGlmIGB2YWx1ZWAgaXMgYW4gb2JqZWN0LCBlbHNlIGBmYWxzZWAuXG4gKiBAZXhhbXBsZVxuICpcbiAqIF8uaXNPYmplY3Qoe30pO1xuICogLy8gPT4gdHJ1ZVxuICpcbiAqIF8uaXNPYmplY3QoWzEsIDIsIDNdKTtcbiAqIC8vID0+IHRydWVcbiAqXG4gKiBfLmlzT2JqZWN0KF8ubm9vcCk7XG4gKiAvLyA9PiB0cnVlXG4gKlxuICogXy5pc09iamVjdChudWxsKTtcbiAqIC8vID0+IGZhbHNlXG4gKi9cbmZ1bmN0aW9uIGlzT2JqZWN0KHZhbHVlKSB7XG4gIHZhciB0eXBlID0gdHlwZW9mIHZhbHVlO1xuICByZXR1cm4gISF2YWx1ZSAmJiAodHlwZSA9PSAnb2JqZWN0JyB8fCB0eXBlID09ICdmdW5jdGlvbicpO1xufVxuXG4vKipcbiAqIENoZWNrcyBpZiBgdmFsdWVgIGlzIG9iamVjdC1saWtlLiBBIHZhbHVlIGlzIG9iamVjdC1saWtlIGlmIGl0J3Mgbm90IGBudWxsYFxuICogYW5kIGhhcyBhIGB0eXBlb2ZgIHJlc3VsdCBvZiBcIm9iamVjdFwiLlxuICpcbiAqIEBzdGF0aWNcbiAqIEBtZW1iZXJPZiBfXG4gKiBAc2luY2UgNC4wLjBcbiAqIEBjYXRlZ29yeSBMYW5nXG4gKiBAcGFyYW0geyp9IHZhbHVlIFRoZSB2YWx1ZSB0byBjaGVjay5cbiAqIEByZXR1cm5zIHtib29sZWFufSBSZXR1cm5zIGB0cnVlYCBpZiBgdmFsdWVgIGlzIG9iamVjdC1saWtlLCBlbHNlIGBmYWxzZWAuXG4gKiBAZXhhbXBsZVxuICpcbiAqIF8uaXNPYmplY3RMaWtlKHt9KTtcbiAqIC8vID0+IHRydWVcbiAqXG4gKiBfLmlzT2JqZWN0TGlrZShbMSwgMiwgM10pO1xuICogLy8gPT4gdHJ1ZVxuICpcbiAqIF8uaXNPYmplY3RMaWtlKF8ubm9vcCk7XG4gKiAvLyA9PiBmYWxzZVxuICpcbiAqIF8uaXNPYmplY3RMaWtlKG51bGwpO1xuICogLy8gPT4gZmFsc2VcbiAqL1xuZnVuY3Rpb24gaXNPYmplY3RMaWtlKHZhbHVlKSB7XG4gIHJldHVybiAhIXZhbHVlICYmIHR5cGVvZiB2YWx1ZSA9PSAnb2JqZWN0Jztcbn1cblxuLyoqXG4gKiBDaGVja3MgaWYgYHZhbHVlYCBpcyBjbGFzc2lmaWVkIGFzIGEgYFN5bWJvbGAgcHJpbWl0aXZlIG9yIG9iamVjdC5cbiAqXG4gKiBAc3RhdGljXG4gKiBAbWVtYmVyT2YgX1xuICogQHNpbmNlIDQuMC4wXG4gKiBAY2F0ZWdvcnkgTGFuZ1xuICogQHBhcmFtIHsqfSB2YWx1ZSBUaGUgdmFsdWUgdG8gY2hlY2suXG4gKiBAcmV0dXJucyB7Ym9vbGVhbn0gUmV0dXJucyBgdHJ1ZWAgaWYgYHZhbHVlYCBpcyBhIHN5bWJvbCwgZWxzZSBgZmFsc2VgLlxuICogQGV4YW1wbGVcbiAqXG4gKiBfLmlzU3ltYm9sKFN5bWJvbC5pdGVyYXRvcik7XG4gKiAvLyA9PiB0cnVlXG4gKlxuICogXy5pc1N5bWJvbCgnYWJjJyk7XG4gKiAvLyA9PiBmYWxzZVxuICovXG5mdW5jdGlvbiBpc1N5bWJvbCh2YWx1ZSkge1xuICByZXR1cm4gdHlwZW9mIHZhbHVlID09ICdzeW1ib2wnIHx8XG4gICAgKGlzT2JqZWN0TGlrZSh2YWx1ZSkgJiYgb2JqZWN0VG9TdHJpbmcuY2FsbCh2YWx1ZSkgPT0gc3ltYm9sVGFnKTtcbn1cblxuLyoqXG4gKiBDb252ZXJ0cyBgdmFsdWVgIHRvIGEgbnVtYmVyLlxuICpcbiAqIEBzdGF0aWNcbiAqIEBtZW1iZXJPZiBfXG4gKiBAc2luY2UgNC4wLjBcbiAqIEBjYXRlZ29yeSBMYW5nXG4gKiBAcGFyYW0geyp9IHZhbHVlIFRoZSB2YWx1ZSB0byBwcm9jZXNzLlxuICogQHJldHVybnMge251bWJlcn0gUmV0dXJucyB0aGUgbnVtYmVyLlxuICogQGV4YW1wbGVcbiAqXG4gKiBfLnRvTnVtYmVyKDMuMik7XG4gKiAvLyA9PiAzLjJcbiAqXG4gKiBfLnRvTnVtYmVyKE51bWJlci5NSU5fVkFMVUUpO1xuICogLy8gPT4gNWUtMzI0XG4gKlxuICogXy50b051bWJlcihJbmZpbml0eSk7XG4gKiAvLyA9PiBJbmZpbml0eVxuICpcbiAqIF8udG9OdW1iZXIoJzMuMicpO1xuICogLy8gPT4gMy4yXG4gKi9cbmZ1bmN0aW9uIHRvTnVtYmVyKHZhbHVlKSB7XG4gIGlmICh0eXBlb2YgdmFsdWUgPT0gJ251bWJlcicpIHtcbiAgICByZXR1cm4gdmFsdWU7XG4gIH1cbiAgaWYgKGlzU3ltYm9sKHZhbHVlKSkge1xuICAgIHJldHVybiBOQU47XG4gIH1cbiAgaWYgKGlzT2JqZWN0KHZhbHVlKSkge1xuICAgIHZhciBvdGhlciA9IHR5cGVvZiB2YWx1ZS52YWx1ZU9mID09ICdmdW5jdGlvbicgPyB2YWx1ZS52YWx1ZU9mKCkgOiB2YWx1ZTtcbiAgICB2YWx1ZSA9IGlzT2JqZWN0KG90aGVyKSA/IChvdGhlciArICcnKSA6IG90aGVyO1xuICB9XG4gIGlmICh0eXBlb2YgdmFsdWUgIT0gJ3N0cmluZycpIHtcbiAgICByZXR1cm4gdmFsdWUgPT09IDAgPyB2YWx1ZSA6ICt2YWx1ZTtcbiAgfVxuICB2YWx1ZSA9IHZhbHVlLnJlcGxhY2UocmVUcmltLCAnJyk7XG4gIHZhciBpc0JpbmFyeSA9IHJlSXNCaW5hcnkudGVzdCh2YWx1ZSk7XG4gIHJldHVybiAoaXNCaW5hcnkgfHwgcmVJc09jdGFsLnRlc3QodmFsdWUpKVxuICAgID8gZnJlZVBhcnNlSW50KHZhbHVlLnNsaWNlKDIpLCBpc0JpbmFyeSA/IDIgOiA4KVxuICAgIDogKHJlSXNCYWRIZXgudGVzdCh2YWx1ZSkgPyBOQU4gOiArdmFsdWUpO1xufVxuXG5tb2R1bGUuZXhwb3J0cyA9IHRocm90dGxlO1xuIiwiXCJ1c2Ugc3RyaWN0XCI7XG4vKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuICogIENvcHlyaWdodCAoYykgTWljcm9zb2Z0IENvcnBvcmF0aW9uLiBBbGwgcmlnaHRzIHJlc2VydmVkLlxuICogIExpY2Vuc2VkIHVuZGVyIHRoZSBNSVQgTGljZW5zZS4gU2VlIExpY2Vuc2UudHh0IGluIHRoZSBwcm9qZWN0IHJvb3QgZm9yIGxpY2Vuc2UgaW5mb3JtYXRpb24uXG4gKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cbk9iamVjdC5kZWZpbmVQcm9wZXJ0eShleHBvcnRzLCBcIl9fZXNNb2R1bGVcIiwgeyB2YWx1ZTogdHJ1ZSB9KTtcbmNvbnN0IHNldHRpbmdzXzEgPSByZXF1aXJlKFwiLi9zZXR0aW5nc1wiKTtcbmV4cG9ydHMuY3JlYXRlUG9zdGVyRm9yVnNDb2RlID0gKHZzY29kZSkgPT4ge1xuICAgIHJldHVybiBuZXcgY2xhc3Mge1xuICAgICAgICBwb3N0TWVzc2FnZSh0eXBlLCBib2R5KSB7XG4gICAgICAgICAgICB2c2NvZGUucG9zdE1lc3NhZ2Uoe1xuICAgICAgICAgICAgICAgIHR5cGUsXG4gICAgICAgICAgICAgICAgc291cmNlOiBzZXR0aW5nc18xLmdldFNldHRpbmdzKCkuc291cmNlLFxuICAgICAgICAgICAgICAgIGJvZHlcbiAgICAgICAgICAgIH0pO1xuICAgICAgICB9XG4gICAgfTtcbn07XG4iLCJcInVzZSBzdHJpY3RcIjtcbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gKiAgQ29weXJpZ2h0IChjKSBNaWNyb3NvZnQgQ29ycG9yYXRpb24uIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiAgTGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBTZWUgTGljZW5zZS50eHQgaW4gdGhlIHByb2plY3Qgcm9vdCBmb3IgbGljZW5zZSBpbmZvcm1hdGlvbi5cbiAqLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuT2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsIFwiX19lc01vZHVsZVwiLCB7IHZhbHVlOiB0cnVlIH0pO1xuZnVuY3Rpb24gb25jZURvY3VtZW50TG9hZGVkKGYpIHtcbiAgICBpZiAoZG9jdW1lbnQucmVhZHlTdGF0ZSA9PT0gJ2xvYWRpbmcnIHx8IGRvY3VtZW50LnJlYWR5U3RhdGUgPT09ICd1bmluaXRpYWxpemVkJykge1xuICAgICAgICBkb2N1bWVudC5hZGRFdmVudExpc3RlbmVyKCdET01Db250ZW50TG9hZGVkJywgZik7XG4gICAgfVxuICAgIGVsc2Uge1xuICAgICAgICBmKCk7XG4gICAgfVxufVxuZXhwb3J0cy5vbmNlRG9jdW1lbnRMb2FkZWQgPSBvbmNlRG9jdW1lbnRMb2FkZWQ7XG4iLCJcInVzZSBzdHJpY3RcIjtcbk9iamVjdC5kZWZpbmVQcm9wZXJ0eShleHBvcnRzLCBcIl9fZXNNb2R1bGVcIiwgeyB2YWx1ZTogdHJ1ZSB9KTtcbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gKiAgQ29weXJpZ2h0IChjKSBNaWNyb3NvZnQgQ29ycG9yYXRpb24uIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiAgTGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBTZWUgTGljZW5zZS50eHQgaW4gdGhlIHByb2plY3Qgcm9vdCBmb3IgbGljZW5zZSBpbmZvcm1hdGlvbi5cbiAqLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuY29uc3Qgc2Nyb2xsX3N5bmNfMSA9IHJlcXVpcmUoXCIuL3Njcm9sbC1zeW5jXCIpO1xuY2xhc3MgQWN0aXZlTGluZU1hcmtlciB7XG4gICAgb25EaWRDaGFuZ2VUZXh0RWRpdG9yU2VsZWN0aW9uKGxpbmUpIHtcbiAgICAgICAgY29uc3QgeyBwcmV2aW91cyB9ID0gc2Nyb2xsX3N5bmNfMS5nZXRFbGVtZW50c0ZvclNvdXJjZUxpbmUobGluZSk7XG4gICAgICAgIHRoaXMuX3VwZGF0ZShwcmV2aW91cyAmJiBwcmV2aW91cy5lbGVtZW50KTtcbiAgICB9XG4gICAgX3VwZGF0ZShiZWZvcmUpIHtcbiAgICAgICAgdGhpcy5fdW5tYXJrQWN0aXZlRWxlbWVudCh0aGlzLl9jdXJyZW50KTtcbiAgICAgICAgdGhpcy5fbWFya0FjdGl2ZUVsZW1lbnQoYmVmb3JlKTtcbiAgICAgICAgdGhpcy5fY3VycmVudCA9IGJlZm9yZTtcbiAgICB9XG4gICAgX3VubWFya0FjdGl2ZUVsZW1lbnQoZWxlbWVudCkge1xuICAgICAgICBpZiAoIWVsZW1lbnQpIHtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuICAgICAgICBlbGVtZW50LmNsYXNzTmFtZSA9IGVsZW1lbnQuY2xhc3NOYW1lLnJlcGxhY2UoL1xcYmNvZGUtYWN0aXZlLWxpbmVcXGIvZywgJycpO1xuICAgIH1cbiAgICBfbWFya0FjdGl2ZUVsZW1lbnQoZWxlbWVudCkge1xuICAgICAgICBpZiAoIWVsZW1lbnQpIHtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuICAgICAgICBlbGVtZW50LmNsYXNzTmFtZSArPSAnIGNvZGUtYWN0aXZlLWxpbmUnO1xuICAgIH1cbn1cbmV4cG9ydHMuQWN0aXZlTGluZU1hcmtlciA9IEFjdGl2ZUxpbmVNYXJrZXI7XG4iLCJcInVzZSBzdHJpY3RcIjtcbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gKiAgQ29weXJpZ2h0IChjKSBNaWNyb3NvZnQgQ29ycG9yYXRpb24uIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiAgTGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBTZWUgTGljZW5zZS50eHQgaW4gdGhlIHByb2plY3Qgcm9vdCBmb3IgbGljZW5zZSBpbmZvcm1hdGlvbi5cbiAqLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuT2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsIFwiX19lc01vZHVsZVwiLCB7IHZhbHVlOiB0cnVlIH0pO1xuY29uc3QgYWN0aXZlTGluZU1hcmtlcl8xID0gcmVxdWlyZShcIi4vYWN0aXZlTGluZU1hcmtlclwiKTtcbmNvbnN0IGV2ZW50c18xID0gcmVxdWlyZShcIi4vZXZlbnRzXCIpO1xuY29uc3QgbWVzc2FnaW5nXzEgPSByZXF1aXJlKFwiLi9tZXNzYWdpbmdcIik7XG5jb25zdCBzY3JvbGxfc3luY18xID0gcmVxdWlyZShcIi4vc2Nyb2xsLXN5bmNcIik7XG5jb25zdCBzZXR0aW5nc18xID0gcmVxdWlyZShcIi4vc2V0dGluZ3NcIik7XG5jb25zdCB0aHJvdHRsZSA9IHJlcXVpcmUoXCJsb2Rhc2gudGhyb3R0bGVcIik7XG5sZXQgc2Nyb2xsRGlzYWJsZWQgPSB0cnVlO1xuY29uc3QgbWFya2VyID0gbmV3IGFjdGl2ZUxpbmVNYXJrZXJfMS5BY3RpdmVMaW5lTWFya2VyKCk7XG5jb25zdCBzZXR0aW5ncyA9IHNldHRpbmdzXzEuZ2V0U2V0dGluZ3MoKTtcbmNvbnN0IHZzY29kZSA9IGFjcXVpcmVWc0NvZGVBcGkoKTtcbi8vIFNldCBWUyBDb2RlIHN0YXRlXG5sZXQgc3RhdGUgPSBzZXR0aW5nc18xLmdldERhdGEoJ2RhdGEtc3RhdGUnKTtcbnZzY29kZS5zZXRTdGF0ZShzdGF0ZSk7XG5jb25zdCBtZXNzYWdpbmcgPSBtZXNzYWdpbmdfMS5jcmVhdGVQb3N0ZXJGb3JWc0NvZGUodnNjb2RlKTtcbndpbmRvdy5jc3BBbGVydGVyLnNldFBvc3RlcihtZXNzYWdpbmcpO1xud2luZG93LnN0eWxlTG9hZGluZ01vbml0b3Iuc2V0UG9zdGVyKG1lc3NhZ2luZyk7XG53aW5kb3cub25sb2FkID0gKCkgPT4ge1xuICAgIHVwZGF0ZUltYWdlU2l6ZXMoKTtcbn07XG5ldmVudHNfMS5vbmNlRG9jdW1lbnRMb2FkZWQoKCkgPT4ge1xuICAgIGlmIChzZXR0aW5ncy5zY3JvbGxQcmV2aWV3V2l0aEVkaXRvcikge1xuICAgICAgICBzZXRUaW1lb3V0KCgpID0+IHtcbiAgICAgICAgICAgIC8vIFRyeSB0byBzY3JvbGwgdG8gZnJhZ21lbnQgaWYgYXZhaWxhYmxlXG4gICAgICAgICAgICBpZiAoc3RhdGUuZnJhZ21lbnQpIHtcbiAgICAgICAgICAgICAgICBjb25zdCBlbGVtZW50ID0gc2Nyb2xsX3N5bmNfMS5nZXRMaW5lRWxlbWVudEZvckZyYWdtZW50KHN0YXRlLmZyYWdtZW50KTtcbiAgICAgICAgICAgICAgICBpZiAoZWxlbWVudCkge1xuICAgICAgICAgICAgICAgICAgICBzY3JvbGxEaXNhYmxlZCA9IHRydWU7XG4gICAgICAgICAgICAgICAgICAgIHNjcm9sbF9zeW5jXzEuc2Nyb2xsVG9SZXZlYWxTb3VyY2VMaW5lKGVsZW1lbnQubGluZSk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICAgICAgY29uc3QgaW5pdGlhbExpbmUgPSArc2V0dGluZ3MubGluZTtcbiAgICAgICAgICAgICAgICBpZiAoIWlzTmFOKGluaXRpYWxMaW5lKSkge1xuICAgICAgICAgICAgICAgICAgICBzY3JvbGxEaXNhYmxlZCA9IHRydWU7XG4gICAgICAgICAgICAgICAgICAgIHNjcm9sbF9zeW5jXzEuc2Nyb2xsVG9SZXZlYWxTb3VyY2VMaW5lKGluaXRpYWxMaW5lKTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgICAgIH0sIDApO1xuICAgIH1cbn0pO1xuY29uc3Qgb25VcGRhdGVWaWV3ID0gKCgpID0+IHtcbiAgICBjb25zdCBkb1Njcm9sbCA9IHRocm90dGxlKChsaW5lKSA9PiB7XG4gICAgICAgIHNjcm9sbERpc2FibGVkID0gdHJ1ZTtcbiAgICAgICAgc2Nyb2xsX3N5bmNfMS5zY3JvbGxUb1JldmVhbFNvdXJjZUxpbmUobGluZSk7XG4gICAgfSwgNTApO1xuICAgIHJldHVybiAobGluZSwgc2V0dGluZ3MpID0+IHtcbiAgICAgICAgaWYgKCFpc05hTihsaW5lKSkge1xuICAgICAgICAgICAgc2V0dGluZ3MubGluZSA9IGxpbmU7XG4gICAgICAgICAgICBkb1Njcm9sbChsaW5lKTtcbiAgICAgICAgfVxuICAgIH07XG59KSgpO1xubGV0IHVwZGF0ZUltYWdlU2l6ZXMgPSB0aHJvdHRsZSgoKSA9PiB7XG4gICAgY29uc3QgaW1hZ2VJbmZvID0gW107XG4gICAgbGV0IGltYWdlcyA9IGRvY3VtZW50LmdldEVsZW1lbnRzQnlUYWdOYW1lKCdpbWcnKTtcbiAgICBpZiAoaW1hZ2VzKSB7XG4gICAgICAgIGxldCBpO1xuICAgICAgICBmb3IgKGkgPSAwOyBpIDwgaW1hZ2VzLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgICAgICBjb25zdCBpbWcgPSBpbWFnZXNbaV07XG4gICAgICAgICAgICBpZiAoaW1nLmNsYXNzTGlzdC5jb250YWlucygnbG9hZGluZycpKSB7XG4gICAgICAgICAgICAgICAgaW1nLmNsYXNzTGlzdC5yZW1vdmUoJ2xvYWRpbmcnKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGltYWdlSW5mby5wdXNoKHtcbiAgICAgICAgICAgICAgICBpZDogaW1nLmlkLFxuICAgICAgICAgICAgICAgIGhlaWdodDogaW1nLmhlaWdodCxcbiAgICAgICAgICAgICAgICB3aWR0aDogaW1nLndpZHRoXG4gICAgICAgICAgICB9KTtcbiAgICAgICAgfVxuICAgICAgICBtZXNzYWdpbmcucG9zdE1lc3NhZ2UoJ2NhY2hlSW1hZ2VTaXplcycsIGltYWdlSW5mbyk7XG4gICAgfVxufSwgNTApO1xud2luZG93LmFkZEV2ZW50TGlzdGVuZXIoJ3Jlc2l6ZScsICgpID0+IHtcbiAgICBzY3JvbGxEaXNhYmxlZCA9IHRydWU7XG4gICAgdXBkYXRlSW1hZ2VTaXplcygpO1xufSwgdHJ1ZSk7XG53aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcignbWVzc2FnZScsIGV2ZW50ID0+IHtcbiAgICBpZiAoZXZlbnQuZGF0YS5zb3VyY2UgIT09IHNldHRpbmdzLnNvdXJjZSkge1xuICAgICAgICByZXR1cm47XG4gICAgfVxuICAgIHN3aXRjaCAoZXZlbnQuZGF0YS50eXBlKSB7XG4gICAgICAgIGNhc2UgJ29uRGlkQ2hhbmdlVGV4dEVkaXRvclNlbGVjdGlvbic6XG4gICAgICAgICAgICBtYXJrZXIub25EaWRDaGFuZ2VUZXh0RWRpdG9yU2VsZWN0aW9uKGV2ZW50LmRhdGEubGluZSk7XG4gICAgICAgICAgICBicmVhaztcbiAgICAgICAgY2FzZSAndXBkYXRlVmlldyc6XG4gICAgICAgICAgICBvblVwZGF0ZVZpZXcoZXZlbnQuZGF0YS5saW5lLCBzZXR0aW5ncyk7XG4gICAgICAgICAgICBicmVhaztcbiAgICB9XG59LCBmYWxzZSk7XG5kb2N1bWVudC5hZGRFdmVudExpc3RlbmVyKCdkYmxjbGljaycsIGV2ZW50ID0+IHtcbiAgICBpZiAoIXNldHRpbmdzLmRvdWJsZUNsaWNrVG9Td2l0Y2hUb0VkaXRvcikge1xuICAgICAgICByZXR1cm47XG4gICAgfVxuICAgIC8vIElnbm9yZSBjbGlja3Mgb24gbGlua3NcbiAgICBmb3IgKGxldCBub2RlID0gZXZlbnQudGFyZ2V0OyBub2RlOyBub2RlID0gbm9kZS5wYXJlbnROb2RlKSB7XG4gICAgICAgIGlmIChub2RlLnRhZ05hbWUgPT09ICdBJykge1xuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG4gICAgfVxuICAgIGNvbnN0IG9mZnNldCA9IGV2ZW50LnBhZ2VZO1xuICAgIGNvbnN0IGxpbmUgPSBzY3JvbGxfc3luY18xLmdldEVkaXRvckxpbmVOdW1iZXJGb3JQYWdlT2Zmc2V0KG9mZnNldCk7XG4gICAgaWYgKHR5cGVvZiBsaW5lID09PSAnbnVtYmVyJyAmJiAhaXNOYU4obGluZSkpIHtcbiAgICAgICAgbWVzc2FnaW5nLnBvc3RNZXNzYWdlKCdkaWRDbGljaycsIHsgbGluZTogTWF0aC5mbG9vcihsaW5lKSB9KTtcbiAgICB9XG59KTtcbmNvbnN0IHBhc3NUaHJvdWdoTGlua1NjaGVtZXMgPSBbJ2h0dHA6JywgJ2h0dHBzOicsICdtYWlsdG86JywgJ3ZzY29kZTonLCAndnNjb2RlLWluc2lkZXJzJ107XG5kb2N1bWVudC5hZGRFdmVudExpc3RlbmVyKCdjbGljaycsIGV2ZW50ID0+IHtcbiAgICBpZiAoIWV2ZW50KSB7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG4gICAgbGV0IG5vZGUgPSBldmVudC50YXJnZXQ7XG4gICAgd2hpbGUgKG5vZGUpIHtcbiAgICAgICAgaWYgKG5vZGUudGFnTmFtZSAmJiBub2RlLnRhZ05hbWUgPT09ICdBJyAmJiBub2RlLmhyZWYpIHtcbiAgICAgICAgICAgIGlmIChub2RlLmdldEF0dHJpYnV0ZSgnaHJlZicpLnN0YXJ0c1dpdGgoJyMnKSkge1xuICAgICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIC8vIFBhc3MgdGhyb3VnaCBrbm93biBzY2hlbWVzXG4gICAgICAgICAgICBpZiAocGFzc1Rocm91Z2hMaW5rU2NoZW1lcy5zb21lKHNjaGVtZSA9PiBub2RlLmhyZWYuc3RhcnRzV2l0aChzY2hlbWUpKSkge1xuICAgICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGNvbnN0IGhyZWZUZXh0ID0gbm9kZS5nZXRBdHRyaWJ1dGUoJ2RhdGEtaHJlZicpIHx8IG5vZGUuZ2V0QXR0cmlidXRlKCdocmVmJyk7XG4gICAgICAgICAgICAvLyBJZiBvcmlnaW5hbCBsaW5rIGRvZXNuJ3QgbG9vayBsaWtlIGEgdXJsLCBkZWxlZ2F0ZSBiYWNrIHRvIFZTIENvZGUgdG8gcmVzb2x2ZVxuICAgICAgICAgICAgaWYgKCEvXlthLXpcXC1dKzovaS50ZXN0KGhyZWZUZXh0KSkge1xuICAgICAgICAgICAgICAgIG1lc3NhZ2luZy5wb3N0TWVzc2FnZSgnb3BlbkxpbmsnLCB7IGhyZWY6IGhyZWZUZXh0IH0pO1xuICAgICAgICAgICAgICAgIGV2ZW50LnByZXZlbnREZWZhdWx0KCk7XG4gICAgICAgICAgICAgICAgZXZlbnQuc3RvcFByb3BhZ2F0aW9uKCk7XG4gICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG4gICAgICAgIG5vZGUgPSBub2RlLnBhcmVudE5vZGU7XG4gICAgfVxufSwgdHJ1ZSk7XG53aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcignc2Nyb2xsJywgdGhyb3R0bGUoKCkgPT4ge1xuICAgIGlmIChzY3JvbGxEaXNhYmxlZCkge1xuICAgICAgICBzY3JvbGxEaXNhYmxlZCA9IGZhbHNlO1xuICAgIH1cbiAgICBlbHNlIHtcbiAgICAgICAgY29uc3QgbGluZSA9IHNjcm9sbF9zeW5jXzEuZ2V0RWRpdG9yTGluZU51bWJlckZvclBhZ2VPZmZzZXQod2luZG93LnNjcm9sbFkpO1xuICAgICAgICBpZiAodHlwZW9mIGxpbmUgPT09ICdudW1iZXInICYmICFpc05hTihsaW5lKSkge1xuICAgICAgICAgICAgbWVzc2FnaW5nLnBvc3RNZXNzYWdlKCdyZXZlYWxMaW5lJywgeyBsaW5lIH0pO1xuICAgICAgICAgICAgc3RhdGUubGluZSA9IGxpbmU7XG4gICAgICAgICAgICB2c2NvZGUuc2V0U3RhdGUoc3RhdGUpO1xuICAgICAgICB9XG4gICAgfVxufSwgNTApKTtcbiJdLCJzb3VyY2VSb290IjoiIn0= +!function(e){var t={};function n(o){if(t[o])return t[o].exports;var r=t[o]={i:o,l:!1,exports:{}};return e[o].call(r.exports,r,r.exports,n),r.l=!0,r.exports}n.m=e,n.c=t,n.d=function(e,t,o){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:o})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var o=Object.create(null);if(n.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)n.d(o,r,function(t){return e[t]}.bind(null,r));return o},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=2)}([function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});let o=void 0;function r(e){const t=document.getElementById("vscode-markdown-preview-data");if(t){const n=t.getAttribute(e);if(n)return JSON.parse(n)}throw new Error(`Could not load data for ${e}`)}t.getData=r,t.getSettings=function(){if(o)return o;if(o=r("data-settings"))return o;throw new Error("Could not load settings")}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const o=n(0),r="code-line";function i(e){return t=0,n=o.getSettings().lineCount-1,r=e,Math.min(n,Math.max(t,r));var t,n,r}const s=(()=>{let e;return()=>{if(!e){e=[{element:document.body,line:0}];for(const t of document.getElementsByClassName(r)){const n=+t.getAttribute("data-line");isNaN(n)||("CODE"===t.tagName&&t.parentElement&&"PRE"===t.parentElement.tagName?e.push({element:t.parentElement,line:n}):e.push({element:t,line:n}))}}return e}})();function a(e){const t=Math.floor(e),n=s();let o=n[0]||null;for(const e of n){if(e.line===t)return{previous:e,next:void 0};if(e.line>t)return{previous:o,next:e};o=e}return{previous:o}}function c(e){const t=s(),n=e-window.scrollY;let o=-1,r=t.length-1;for(;o+1=n?r=e:o=e}const i=t[r],a=u(i);if(r>=1&&a.top>n){return{previous:t[o],next:i}}return r>1&&rn?{previous:i,next:t[r+1]}:{previous:i}}function u({element:e}){const t=e.getBoundingClientRect(),n=e.querySelector(`.${r}`);if(n){const e=n.getBoundingClientRect(),o=Math.max(1,e.top-t.top);return{top:t.top,height:o}}return t}t.getElementsForSourceLine=a,t.getLineElementsAtPageOffset=c,t.scrollToRevealSourceLine=function(e){if(!o.getSettings().scrollPreviewWithEditor)return;if(e<=0)return void window.scroll(window.scrollX,0);const{previous:t,next:n}=a(e);if(!t)return;let r=0;const i=u(t),s=i.top;if(n&&n.line!==t.line){r=s+(e-t.line)/(n.line-t.line)*(n.element.getBoundingClientRect().top-s)}else{const t=e-Math.floor(e);r=s+i.height*t}window.scroll(window.scrollX,Math.max(1,window.scrollY+r))},t.getEditorLineNumberForPageOffset=function(e){const{previous:t,next:n}=c(e);if(t){const o=u(t),r=e-window.scrollY-o.top;if(n){const e=r/(u(n).top-o.top);return i(t.line+e*(n.line-t.line))}{const e=r/o.height;return i(t.line+e)}}return null},t.getLineElementForFragment=function(e){return s().find(t=>t.element.id===e)}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const o=n(3),r=n(4),i=n(5),s=n(1),a=n(0),c=n(6);let u=!0;const l=new o.ActiveLineMarker,f=a.getSettings(),d=acquireVsCodeApi();let g=a.getData("data-state");d.setState(g);const p=i.createPosterForVsCode(d);window.cspAlerter.setPoster(p),window.styleLoadingMonitor.setPoster(p),window.onload=()=>{v()},r.onceDocumentLoaded(()=>{f.scrollPreviewWithEditor&&setTimeout(()=>{if(g.fragment){const e=s.getLineElementForFragment(g.fragment);e&&(u=!0,s.scrollToRevealSourceLine(e.line))}else{const e=+f.line;isNaN(e)||(u=!0,s.scrollToRevealSourceLine(e))}},0)});const m=(()=>{const e=c(e=>{u=!0,s.scrollToRevealSourceLine(e)},50);return(t,n)=>{isNaN(t)||(n.line=t,e(t))}})();let v=c(()=>{const e=[];let t=document.getElementsByTagName("img");if(t){let n;for(n=0;n{u=!0,v()},!0),window.addEventListener("message",e=>{if(e.data.source===f.source)switch(e.data.type){case"onDidChangeTextEditorSelection":l.onDidChangeTextEditorSelection(e.data.line);break;case"updateView":m(e.data.line,f)}},!1),document.addEventListener("dblclick",e=>{if(!f.doubleClickToSwitchToEditor)return;for(let t=e.target;t;t=t.parentNode)if("A"===t.tagName)return;const t=e.pageY,n=s.getEditorLineNumberForPageOffset(t);"number"!=typeof n||isNaN(n)||p.postMessage("didClick",{line:Math.floor(n)})});const h=["http:","https:","mailto:","vscode:","vscode-insiders:"];document.addEventListener("click",e=>{if(!e)return;let t=e.target;for(;t;){if(t.tagName&&"A"===t.tagName&&t.href){if(t.getAttribute("href").startsWith("#"))return;if(h.some(e=>t.href.startsWith(e)))return;const n=t.getAttribute("data-href")||t.getAttribute("href");return/^[a-z\-]+:/i.test(n)?void 0:(p.postMessage("openLink",{href:n}),e.preventDefault(),void e.stopPropagation())}t=t.parentNode}},!0),window.addEventListener("scroll",c(()=>{if(u)u=!1;else{const e=s.getEditorLineNumberForPageOffset(window.scrollY);"number"!=typeof e||isNaN(e)||(p.postMessage("revealLine",{line:e}),g.line=e,d.setState(g))}},50))},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const o=n(1);t.ActiveLineMarker=class{onDidChangeTextEditorSelection(e){const{previous:t}=o.getElementsForSourceLine(e);this._update(t&&t.element)}_update(e){this._unmarkActiveElement(this._current),this._markActiveElement(e),this._current=e}_unmarkActiveElement(e){e&&(e.className=e.className.replace(/\bcode-active-line\b/g,""))}_markActiveElement(e){e&&(e.className+=" code-active-line")}}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.onceDocumentLoaded=function(e){"loading"===document.readyState||"uninitialized"===document.readyState?document.addEventListener("DOMContentLoaded",e):e()}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const o=n(0);t.createPosterForVsCode=e=>new class{postMessage(t,n){e.postMessage({type:t,source:o.getSettings().source,body:n})}}},function(e,t,n){(function(t){var n="Expected a function",o=NaN,r="[object Symbol]",i=/^\s+|\s+$/g,s=/^[-+]0x[0-9a-f]+$/i,a=/^0b[01]+$/i,c=/^0o[0-7]+$/i,u=parseInt,l="object"==typeof t&&t&&t.Object===Object&&t,f="object"==typeof self&&self&&self.Object===Object&&self,d=l||f||Function("return this")(),g=Object.prototype.toString,p=Math.max,m=Math.min,v=function(){return d.Date.now()};function h(e,t,o){var r,i,s,a,c,u,l=0,f=!1,d=!1,g=!0;if("function"!=typeof e)throw new TypeError(n);function h(t){var n=r,o=i;return r=i=void 0,l=t,a=e.apply(o,n)}function y(e){var n=e-u;return void 0===u||n>=t||n<0||d&&e-l>=s}function E(){var e=v();if(y(e))return M(e);c=setTimeout(E,function(e){var n=t-(e-u);return d?m(n,s-(e-l)):n}(e))}function M(e){return c=void 0,g&&r?h(e):(r=i=void 0,a)}function S(){var e=v(),n=y(e);if(r=arguments,i=this,u=e,n){if(void 0===c)return function(e){return l=e,c=setTimeout(E,t),f?h(e):a}(u);if(d)return c=setTimeout(E,t),h(u)}return void 0===c&&(c=setTimeout(E,t)),a}return t=b(t)||0,w(o)&&(f=!!o.leading,s=(d="maxWait"in o)?p(b(o.maxWait)||0,t):s,g="trailing"in o?!!o.trailing:g),S.cancel=function(){void 0!==c&&clearTimeout(c),l=0,r=u=i=c=void 0},S.flush=function(){return void 0===c?a:M(v())},S}function w(e){var t=typeof e;return!!e&&("object"==t||"function"==t)}function b(e){if("number"==typeof e)return e;if(function(e){return"symbol"==typeof e||function(e){return!!e&&"object"==typeof e}(e)&&g.call(e)==r}(e))return o;if(w(e)){var t="function"==typeof e.valueOf?e.valueOf():e;e=w(t)?t+"":t}if("string"!=typeof e)return 0===e?e:+e;e=e.replace(i,"");var n=a.test(e);return n||c.test(e)?u(e.slice(2),n?2:8):s.test(e)?o:+e}e.exports=function(e,t,o){var r=!0,i=!0;if("function"!=typeof e)throw new TypeError(n);return w(o)&&(r="leading"in o?!!o.leading:r,i="trailing"in o?!!o.trailing:i),h(e,t,{leading:r,maxWait:t,trailing:i})}}).call(this,n(7))},function(e,t){var n;n=function(){return this}();try{n=n||new Function("return this")()}catch(e){"object"==typeof window&&(n=window)}e.exports=n}]); +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vd2VicGFjay9ib290c3RyYXAiLCJ3ZWJwYWNrOi8vLy4vcHJldmlldy1zcmMvc2V0dGluZ3MudHMiLCJ3ZWJwYWNrOi8vLy4vcHJldmlldy1zcmMvc2Nyb2xsLXN5bmMudHMiLCJ3ZWJwYWNrOi8vLy4vcHJldmlldy1zcmMvaW5kZXgudHMiLCJ3ZWJwYWNrOi8vLy4vcHJldmlldy1zcmMvYWN0aXZlTGluZU1hcmtlci50cyIsIndlYnBhY2s6Ly8vLi9wcmV2aWV3LXNyYy9ldmVudHMudHMiLCJ3ZWJwYWNrOi8vLy4vcHJldmlldy1zcmMvbWVzc2FnaW5nLnRzIiwid2VicGFjazovLy8uL25vZGVfbW9kdWxlcy9sb2Rhc2gudGhyb3R0bGUvaW5kZXguanMiLCJ3ZWJwYWNrOi8vLyh3ZWJwYWNrKS9idWlsZGluL2dsb2JhbC5qcyJdLCJuYW1lcyI6WyJpbnN0YWxsZWRNb2R1bGVzIiwiX193ZWJwYWNrX3JlcXVpcmVfXyIsIm1vZHVsZUlkIiwiZXhwb3J0cyIsIm1vZHVsZSIsImkiLCJsIiwibW9kdWxlcyIsImNhbGwiLCJtIiwiYyIsImQiLCJuYW1lIiwiZ2V0dGVyIiwibyIsIk9iamVjdCIsImRlZmluZVByb3BlcnR5IiwiZW51bWVyYWJsZSIsImdldCIsInIiLCJTeW1ib2wiLCJ0b1N0cmluZ1RhZyIsInZhbHVlIiwidCIsIm1vZGUiLCJfX2VzTW9kdWxlIiwibnMiLCJjcmVhdGUiLCJrZXkiLCJiaW5kIiwibiIsIm9iamVjdCIsInByb3BlcnR5IiwicHJvdG90eXBlIiwiaGFzT3duUHJvcGVydHkiLCJwIiwicyIsImNhY2hlZFNldHRpbmdzIiwidW5kZWZpbmVkIiwiZ2V0RGF0YSIsImVsZW1lbnQiLCJkb2N1bWVudCIsImdldEVsZW1lbnRCeUlkIiwiZGF0YSIsImdldEF0dHJpYnV0ZSIsIkpTT04iLCJwYXJzZSIsIkVycm9yIiwiZ2V0U2V0dGluZ3MiLCJzZXR0aW5nc18xIiwiY29kZUxpbmVDbGFzcyIsImNsYW1wTGluZSIsImxpbmUiLCJtaW4iLCJtYXgiLCJsaW5lQ291bnQiLCJNYXRoIiwiZ2V0Q29kZUxpbmVFbGVtZW50cyIsImVsZW1lbnRzIiwiYm9keSIsImdldEVsZW1lbnRzQnlDbGFzc05hbWUiLCJpc05hTiIsInRhZ05hbWUiLCJwYXJlbnRFbGVtZW50IiwicHVzaCIsImdldEVsZW1lbnRzRm9yU291cmNlTGluZSIsInRhcmdldExpbmUiLCJsaW5lTnVtYmVyIiwiZmxvb3IiLCJsaW5lcyIsInByZXZpb3VzIiwiZW50cnkiLCJuZXh0IiwiZ2V0TGluZUVsZW1lbnRzQXRQYWdlT2Zmc2V0Iiwib2Zmc2V0IiwicG9zaXRpb24iLCJ3aW5kb3ciLCJzY3JvbGxZIiwibG8iLCJoaSIsImxlbmd0aCIsIm1pZCIsImJvdW5kcyIsImdldEVsZW1lbnRCb3VuZHMiLCJ0b3AiLCJoZWlnaHQiLCJoaUVsZW1lbnQiLCJoaUJvdW5kcyIsIm15Qm91bmRzIiwiZ2V0Qm91bmRpbmdDbGllbnRSZWN0IiwiY29kZUxpbmVDaGlsZCIsInF1ZXJ5U2VsZWN0b3IiLCJjaGlsZEJvdW5kcyIsInNjcm9sbFRvUmV2ZWFsU291cmNlTGluZSIsInNjcm9sbFByZXZpZXdXaXRoRWRpdG9yIiwic2Nyb2xsIiwic2Nyb2xsWCIsInNjcm9sbFRvIiwicmVjdCIsInByZXZpb3VzVG9wIiwicHJvZ3Jlc3NJbkVsZW1lbnQiLCJnZXRFZGl0b3JMaW5lTnVtYmVyRm9yUGFnZU9mZnNldCIsInByZXZpb3VzQm91bmRzIiwib2Zmc2V0RnJvbVByZXZpb3VzIiwicHJvZ3Jlc3NCZXR3ZWVuRWxlbWVudHMiLCJwcm9ncmVzc1dpdGhpbkVsZW1lbnQiLCJnZXRMaW5lRWxlbWVudEZvckZyYWdtZW50IiwiZnJhZ21lbnQiLCJmaW5kIiwiaWQiLCJhY3RpdmVMaW5lTWFya2VyXzEiLCJldmVudHNfMSIsIm1lc3NhZ2luZ18xIiwic2Nyb2xsX3N5bmNfMSIsInRocm90dGxlIiwic2Nyb2xsRGlzYWJsZWQiLCJtYXJrZXIiLCJBY3RpdmVMaW5lTWFya2VyIiwic2V0dGluZ3MiLCJ2c2NvZGUiLCJhY3F1aXJlVnNDb2RlQXBpIiwic3RhdGUiLCJzZXRTdGF0ZSIsIm1lc3NhZ2luZyIsImNyZWF0ZVBvc3RlckZvclZzQ29kZSIsImNzcEFsZXJ0ZXIiLCJzZXRQb3N0ZXIiLCJzdHlsZUxvYWRpbmdNb25pdG9yIiwib25sb2FkIiwidXBkYXRlSW1hZ2VTaXplcyIsIm9uY2VEb2N1bWVudExvYWRlZCIsInNldFRpbWVvdXQiLCJpbml0aWFsTGluZSIsIm9uVXBkYXRlVmlldyIsImRvU2Nyb2xsIiwiaW1hZ2VJbmZvIiwiaW1hZ2VzIiwiZ2V0RWxlbWVudHNCeVRhZ05hbWUiLCJpbWciLCJjbGFzc0xpc3QiLCJjb250YWlucyIsInJlbW92ZSIsIndpZHRoIiwicG9zdE1lc3NhZ2UiLCJhZGRFdmVudExpc3RlbmVyIiwiZXZlbnQiLCJzb3VyY2UiLCJ0eXBlIiwib25EaWRDaGFuZ2VUZXh0RWRpdG9yU2VsZWN0aW9uIiwiZG91YmxlQ2xpY2tUb1N3aXRjaFRvRWRpdG9yIiwibm9kZSIsInRhcmdldCIsInBhcmVudE5vZGUiLCJwYWdlWSIsInBhc3NUaHJvdWdoTGlua1NjaGVtZXMiLCJocmVmIiwic3RhcnRzV2l0aCIsInNvbWUiLCJzY2hlbWUiLCJocmVmVGV4dCIsInRlc3QiLCJwcmV2ZW50RGVmYXVsdCIsInN0b3BQcm9wYWdhdGlvbiIsInRoaXMiLCJfdXBkYXRlIiwiYmVmb3JlIiwiX3VubWFya0FjdGl2ZUVsZW1lbnQiLCJfY3VycmVudCIsIl9tYXJrQWN0aXZlRWxlbWVudCIsImNsYXNzTmFtZSIsInJlcGxhY2UiLCJmIiwicmVhZHlTdGF0ZSIsIkZVTkNfRVJST1JfVEVYVCIsIk5BTiIsInN5bWJvbFRhZyIsInJlVHJpbSIsInJlSXNCYWRIZXgiLCJyZUlzQmluYXJ5IiwicmVJc09jdGFsIiwiZnJlZVBhcnNlSW50IiwicGFyc2VJbnQiLCJmcmVlR2xvYmFsIiwiZ2xvYmFsIiwiZnJlZVNlbGYiLCJzZWxmIiwicm9vdCIsIkZ1bmN0aW9uIiwib2JqZWN0VG9TdHJpbmciLCJ0b1N0cmluZyIsIm5hdGl2ZU1heCIsIm5hdGl2ZU1pbiIsIm5vdyIsIkRhdGUiLCJkZWJvdW5jZSIsImZ1bmMiLCJ3YWl0Iiwib3B0aW9ucyIsImxhc3RBcmdzIiwibGFzdFRoaXMiLCJtYXhXYWl0IiwicmVzdWx0IiwidGltZXJJZCIsImxhc3RDYWxsVGltZSIsImxhc3RJbnZva2VUaW1lIiwibGVhZGluZyIsIm1heGluZyIsInRyYWlsaW5nIiwiVHlwZUVycm9yIiwiaW52b2tlRnVuYyIsInRpbWUiLCJhcmdzIiwidGhpc0FyZyIsImFwcGx5Iiwic2hvdWxkSW52b2tlIiwidGltZVNpbmNlTGFzdENhbGwiLCJ0aW1lckV4cGlyZWQiLCJ0cmFpbGluZ0VkZ2UiLCJyZW1haW5pbmdXYWl0IiwiZGVib3VuY2VkIiwiaXNJbnZva2luZyIsImFyZ3VtZW50cyIsImxlYWRpbmdFZGdlIiwidG9OdW1iZXIiLCJpc09iamVjdCIsImNhbmNlbCIsImNsZWFyVGltZW91dCIsImZsdXNoIiwiaXNPYmplY3RMaWtlIiwiaXNTeW1ib2wiLCJvdGhlciIsInZhbHVlT2YiLCJpc0JpbmFyeSIsInNsaWNlIiwiZyIsImUiXSwibWFwcGluZ3MiOiJhQUNFLElBQUlBLEVBQW1CLEdBR3ZCLFNBQVNDLEVBQW9CQyxHQUc1QixHQUFHRixFQUFpQkUsR0FDbkIsT0FBT0YsRUFBaUJFLEdBQVVDLFFBR25DLElBQUlDLEVBQVNKLEVBQWlCRSxHQUFZLENBQ3pDRyxFQUFHSCxFQUNISSxHQUFHLEVBQ0hILFFBQVMsSUFVVixPQU5BSSxFQUFRTCxHQUFVTSxLQUFLSixFQUFPRCxRQUFTQyxFQUFRQSxFQUFPRCxRQUFTRixHQUcvREcsRUFBT0UsR0FBSSxFQUdKRixFQUFPRCxRQUtmRixFQUFvQlEsRUFBSUYsRUFHeEJOLEVBQW9CUyxFQUFJVixFQUd4QkMsRUFBb0JVLEVBQUksU0FBU1IsRUFBU1MsRUFBTUMsR0FDM0NaLEVBQW9CYSxFQUFFWCxFQUFTUyxJQUNsQ0csT0FBT0MsZUFBZWIsRUFBU1MsRUFBTSxDQUFFSyxZQUFZLEVBQU1DLElBQUtMLEtBS2hFWixFQUFvQmtCLEVBQUksU0FBU2hCLEdBQ1gsb0JBQVhpQixRQUEwQkEsT0FBT0MsYUFDMUNOLE9BQU9DLGVBQWViLEVBQVNpQixPQUFPQyxZQUFhLENBQUVDLE1BQU8sV0FFN0RQLE9BQU9DLGVBQWViLEVBQVMsYUFBYyxDQUFFbUIsT0FBTyxLQVF2RHJCLEVBQW9Cc0IsRUFBSSxTQUFTRCxFQUFPRSxHQUV2QyxHQURVLEVBQVBBLElBQVVGLEVBQVFyQixFQUFvQnFCLElBQy9CLEVBQVBFLEVBQVUsT0FBT0YsRUFDcEIsR0FBVyxFQUFQRSxHQUE4QixpQkFBVkYsR0FBc0JBLEdBQVNBLEVBQU1HLFdBQVksT0FBT0gsRUFDaEYsSUFBSUksRUFBS1gsT0FBT1ksT0FBTyxNQUd2QixHQUZBMUIsRUFBb0JrQixFQUFFTyxHQUN0QlgsT0FBT0MsZUFBZVUsRUFBSSxVQUFXLENBQUVULFlBQVksRUFBTUssTUFBT0EsSUFDdEQsRUFBUEUsR0FBNEIsaUJBQVRGLEVBQW1CLElBQUksSUFBSU0sS0FBT04sRUFBT3JCLEVBQW9CVSxFQUFFZSxFQUFJRSxFQUFLLFNBQVNBLEdBQU8sT0FBT04sRUFBTU0sSUFBUUMsS0FBSyxLQUFNRCxJQUM5SSxPQUFPRixHQUlSekIsRUFBb0I2QixFQUFJLFNBQVMxQixHQUNoQyxJQUFJUyxFQUFTVCxHQUFVQSxFQUFPcUIsV0FDN0IsV0FBd0IsT0FBT3JCLEVBQWdCLFNBQy9DLFdBQThCLE9BQU9BLEdBRXRDLE9BREFILEVBQW9CVSxFQUFFRSxFQUFRLElBQUtBLEdBQzVCQSxHQUlSWixFQUFvQmEsRUFBSSxTQUFTaUIsRUFBUUMsR0FBWSxPQUFPakIsT0FBT2tCLFVBQVVDLGVBQWUxQixLQUFLdUIsRUFBUUMsSUFHekcvQixFQUFvQmtDLEVBQUksR0FJakJsQyxFQUFvQkEsRUFBb0JtQyxFQUFJLEcsK0JDN0VyRHJCLE9BQU9DLGVBQWViLEVBQVMsYUFBYyxDQUFFbUIsT0FBTyxJQUN0RCxJQUFJZSxPQUFpQkMsRUFDckIsU0FBU0MsRUFBUVgsR0FDYixNQUFNWSxFQUFVQyxTQUFTQyxlQUFlLGdDQUN4QyxHQUFJRixFQUFTLENBQ1QsTUFBTUcsRUFBT0gsRUFBUUksYUFBYWhCLEdBQ2xDLEdBQUllLEVBQ0EsT0FBT0UsS0FBS0MsTUFBTUgsR0FHMUIsTUFBTSxJQUFJSSxNQUFNLDJCQUEyQm5CLEtBRS9DekIsRUFBUW9DLFFBQVVBLEVBV2xCcEMsRUFBUTZDLFlBVlIsV0FDSSxHQUFJWCxFQUNBLE9BQU9BLEVBR1gsR0FEQUEsRUFBaUJFLEVBQVEsaUJBRXJCLE9BQU9GLEVBRVgsTUFBTSxJQUFJVSxNQUFNLDZCLDZCQ3JCcEJoQyxPQUFPQyxlQUFlYixFQUFTLGFBQWMsQ0FBRW1CLE9BQU8sSUFDdEQsTUFBTTJCLEVBQWEsRUFBUSxHQUNyQkMsRUFBZ0IsWUFJdEIsU0FBU0MsRUFBVUMsR0FDZixPQUpXQyxFQUlFLEVBSkdDLEVBSUFMLEVBQVdELGNBQWNPLFVBQVksRUFKaENqQyxFQUltQzhCLEVBSGpESSxLQUFLSCxJQUFJQyxFQUFLRSxLQUFLRixJQUFJRCxFQUFLL0IsSUFEdkMsSUFBZStCLEVBQUtDLEVBQUtoQyxFQU16QixNQUFNbUMsRUFBc0IsTUFDeEIsSUFBSUMsRUFDSixNQUFPLEtBQ0gsSUFBS0EsRUFBVSxDQUNYQSxFQUFXLENBQUMsQ0FBRWxCLFFBQVNDLFNBQVNrQixLQUFNUCxLQUFNLElBQzVDLElBQUssTUFBTVosS0FBV0MsU0FBU21CLHVCQUF1QlYsR0FBZ0IsQ0FDbEUsTUFBTUUsR0FBUVosRUFBUUksYUFBYSxhQUMvQmlCLE1BQU1ULEtBR2MsU0FBcEJaLEVBQVFzQixTQUFzQnRCLEVBQVF1QixlQUFtRCxRQUFsQ3ZCLEVBQVF1QixjQUFjRCxRQUc3RUosRUFBU00sS0FBSyxDQUFFeEIsUUFBU0EsRUFBUXVCLGNBQWVYLFNBR2hETSxFQUFTTSxLQUFLLENBQUV4QixRQUFTQSxFQUFTWSxXQUk5QyxPQUFPTSxJQXBCYSxHQTZCNUIsU0FBU08sRUFBeUJDLEdBQzlCLE1BQU1DLEVBQWFYLEtBQUtZLE1BQU1GLEdBQ3hCRyxFQUFRWixJQUNkLElBQUlhLEVBQVdELEVBQU0sSUFBTSxLQUMzQixJQUFLLE1BQU1FLEtBQVNGLEVBQU8sQ0FDdkIsR0FBSUUsRUFBTW5CLE9BQVNlLEVBQ2YsTUFBTyxDQUFFRyxTQUFVQyxFQUFPQyxVQUFNbEMsR0FFL0IsR0FBSWlDLEVBQU1uQixLQUFPZSxFQUNsQixNQUFPLENBQUVHLFdBQVVFLEtBQU1ELEdBRTdCRCxFQUFXQyxFQUVmLE1BQU8sQ0FBRUQsWUFNYixTQUFTRyxFQUE0QkMsR0FDakMsTUFBTUwsRUFBUVosSUFDUmtCLEVBQVdELEVBQVNFLE9BQU9DLFFBQ2pDLElBQUlDLEdBQU0sRUFDTkMsRUFBS1YsRUFBTVcsT0FBUyxFQUN4QixLQUFPRixFQUFLLEVBQUlDLEdBQUksQ0FDaEIsTUFBTUUsRUFBTXpCLEtBQUtZLE9BQU9VLEVBQUtDLEdBQU0sR0FDN0JHLEVBQVNDLEVBQWlCZCxFQUFNWSxJQUNsQ0MsRUFBT0UsSUFBTUYsRUFBT0csUUFBVVYsRUFDOUJJLEVBQUtFLEVBR0xILEVBQUtHLEVBR2IsTUFBTUssRUFBWWpCLEVBQU1VLEdBQ2xCUSxFQUFXSixFQUFpQkcsR0FDbEMsR0FBSVAsR0FBTSxHQUFLUSxFQUFTSCxJQUFNVCxFQUFVLENBRXBDLE1BQU8sQ0FBRUwsU0FEU0QsRUFBTVMsR0FDTU4sS0FBTWMsR0FFeEMsT0FBSVAsRUFBSyxHQUFLQSxFQUFLVixFQUFNVyxRQUFVTyxFQUFTSCxJQUFNRyxFQUFTRixPQUFTVixFQUN6RCxDQUFFTCxTQUFVZ0IsRUFBV2QsS0FBTUgsRUFBTVUsRUFBSyxJQUU1QyxDQUFFVCxTQUFVZ0IsR0FHdkIsU0FBU0gsR0FBaUIsUUFBRTNDLElBQ3hCLE1BQU1nRCxFQUFXaEQsRUFBUWlELHdCQUduQkMsRUFBZ0JsRCxFQUFRbUQsY0FBYyxJQUFJekMsS0FDaEQsR0FBSXdDLEVBQWUsQ0FDZixNQUFNRSxFQUFjRixFQUFjRCx3QkFDNUJKLEVBQVM3QixLQUFLRixJQUFJLEVBQUlzQyxFQUFZUixJQUFNSSxFQUFTSixLQUN2RCxNQUFPLENBQ0hBLElBQUtJLEVBQVNKLElBQ2RDLE9BQVFBLEdBR2hCLE9BQU9HLEVBNUNYckYsRUFBUThELHlCQUEyQkEsRUE4Qm5DOUQsRUFBUXNFLDRCQUE4QkEsRUE4Q3RDdEUsRUFBUTBGLHlCQTNCUixTQUFrQ3pDLEdBQzlCLElBQUtILEVBQVdELGNBQWM4Qyx3QkFDMUIsT0FFSixHQUFJMUMsR0FBUSxFQUVSLFlBREF3QixPQUFPbUIsT0FBT25CLE9BQU9vQixRQUFTLEdBR2xDLE1BQU0sU0FBRTFCLEVBQVEsS0FBRUUsR0FBU1AsRUFBeUJiLEdBQ3BELElBQUtrQixFQUNELE9BRUosSUFBSTJCLEVBQVcsRUFDZixNQUFNQyxFQUFPZixFQUFpQmIsR0FDeEI2QixFQUFjRCxFQUFLZCxJQUN6QixHQUFJWixHQUFRQSxFQUFLcEIsT0FBU2tCLEVBQVNsQixLQUFNLENBSXJDNkMsRUFBV0UsR0FGYy9DLEVBQU9rQixFQUFTbEIsT0FBU29CLEVBQUtwQixLQUFPa0IsRUFBU2xCLE9BQ2pEb0IsRUFBS2hDLFFBQVFpRCx3QkFBd0JMLElBQU1lLE9BR2hFLENBQ0QsTUFBTUMsRUFBb0JoRCxFQUFPSSxLQUFLWSxNQUFNaEIsR0FDNUM2QyxFQUFXRSxFQUFlRCxFQUFLYixPQUFTZSxFQUU1Q3hCLE9BQU9tQixPQUFPbkIsT0FBT29CLFFBQVN4QyxLQUFLRixJQUFJLEVBQUdzQixPQUFPQyxRQUFVb0IsS0FxQi9EOUYsRUFBUWtHLGlDQWxCUixTQUEwQzNCLEdBQ3RDLE1BQU0sU0FBRUosRUFBUSxLQUFFRSxHQUFTQyxFQUE0QkMsR0FDdkQsR0FBSUosRUFBVSxDQUNWLE1BQU1nQyxFQUFpQm5CLEVBQWlCYixHQUNsQ2lDLEVBQXNCN0IsRUFBU0UsT0FBT0MsUUFBVXlCLEVBQWVsQixJQUNyRSxHQUFJWixFQUFNLENBQ04sTUFBTWdDLEVBQTBCRCxHQUFzQnBCLEVBQWlCWCxHQUFNWSxJQUFNa0IsRUFBZWxCLEtBRWxHLE9BQU9qQyxFQURNbUIsRUFBU2xCLEtBQU9vRCxHQUEyQmhDLEVBQUtwQixLQUFPa0IsRUFBU2xCLE9BRzVFLENBQ0QsTUFBTXFELEVBQXdCRixFQUFzQkQsRUFBcUIsT0FFekUsT0FBT25ELEVBRE1tQixFQUFTbEIsS0FBT3FELElBSXJDLE9BQU8sTUFXWHRHLEVBQVF1RywwQkFMUixTQUFtQ0MsR0FDL0IsT0FBT2xELElBQXNCbUQsS0FBTXBFLEdBQ3hCQSxFQUFRQSxRQUFRcUUsS0FBT0YsSyw2QkMxSnRDNUYsT0FBT0MsZUFBZWIsRUFBUyxhQUFjLENBQUVtQixPQUFPLElBQ3RELE1BQU13RixFQUFxQixFQUFRLEdBQzdCQyxFQUFXLEVBQVEsR0FDbkJDLEVBQWMsRUFBUSxHQUN0QkMsRUFBZ0IsRUFBUSxHQUN4QmhFLEVBQWEsRUFBUSxHQUNyQmlFLEVBQVcsRUFBUSxHQUN6QixJQUFJQyxHQUFpQixFQUNyQixNQUFNQyxFQUFTLElBQUlOLEVBQW1CTyxpQkFDaENDLEVBQVdyRSxFQUFXRCxjQUN0QnVFLEVBQVNDLG1CQUVmLElBQUlDLEVBQVF4RSxFQUFXVixRQUFRLGNBQy9CZ0YsRUFBT0csU0FBU0QsR0FDaEIsTUFBTUUsRUFBWVgsRUFBWVksc0JBQXNCTCxHQUNwRDNDLE9BQU9pRCxXQUFXQyxVQUFVSCxHQUM1Qi9DLE9BQU9tRCxvQkFBb0JELFVBQVVILEdBQ3JDL0MsT0FBT29ELE9BQVMsS0FDWkMsS0FFSmxCLEVBQVNtQixtQkFBbUIsS0FDcEJaLEVBQVN4Qix5QkFDVHFDLFdBQVcsS0FFUCxHQUFJVixFQUFNZCxTQUFVLENBQ2hCLE1BQU1uRSxFQUFVeUUsRUFBY1AsMEJBQTBCZSxFQUFNZCxVQUMxRG5FLElBQ0EyRSxHQUFpQixFQUNqQkYsRUFBY3BCLHlCQUF5QnJELEVBQVFZLFdBR2xELENBQ0QsTUFBTWdGLEdBQWVkLEVBQVNsRSxLQUN6QlMsTUFBTXVFLEtBQ1BqQixHQUFpQixFQUNqQkYsRUFBY3BCLHlCQUF5QnVDLE1BR2hELEtBR1gsTUFBTUMsRUFBZSxNQUNqQixNQUFNQyxFQUFXcEIsRUFBVTlELElBQ3ZCK0QsR0FBaUIsRUFDakJGLEVBQWNwQix5QkFBeUJ6QyxJQUN4QyxJQUNILE1BQU8sQ0FBQ0EsRUFBTWtFLEtBQ0x6RCxNQUFNVCxLQUNQa0UsRUFBU2xFLEtBQU9BLEVBQ2hCa0YsRUFBU2xGLE1BUkEsR0FZckIsSUFBSTZFLEVBQW1CZixFQUFTLEtBQzVCLE1BQU1xQixFQUFZLEdBQ2xCLElBQUlDLEVBQVMvRixTQUFTZ0cscUJBQXFCLE9BQzNDLEdBQUlELEVBQVEsQ0FDUixJQUFJbkksRUFDSixJQUFLQSxFQUFJLEVBQUdBLEVBQUltSSxFQUFPeEQsT0FBUTNFLElBQUssQ0FDaEMsTUFBTXFJLEVBQU1GLEVBQU9uSSxHQUNmcUksRUFBSUMsVUFBVUMsU0FBUyxZQUN2QkYsRUFBSUMsVUFBVUUsT0FBTyxXQUV6Qk4sRUFBVXZFLEtBQUssQ0FDWDZDLEdBQUk2QixFQUFJN0IsR0FDUnhCLE9BQVFxRCxFQUFJckQsT0FDWnlELE1BQU9KLEVBQUlJLFFBR25CbkIsRUFBVW9CLFlBQVksa0JBQW1CUixLQUU5QyxJQUNIM0QsT0FBT29FLGlCQUFpQixTQUFVLEtBQzlCN0IsR0FBaUIsRUFDakJjLE1BQ0QsR0FDSHJELE9BQU9vRSxpQkFBaUIsVUFBV0MsSUFDL0IsR0FBSUEsRUFBTXRHLEtBQUt1RyxTQUFXNUIsRUFBUzRCLE9BR25DLE9BQVFELEVBQU10RyxLQUFLd0csTUFDZixJQUFLLGlDQUNEL0IsRUFBT2dDLCtCQUErQkgsRUFBTXRHLEtBQUtTLE1BQ2pELE1BQ0osSUFBSyxhQUNEaUYsRUFBYVksRUFBTXRHLEtBQUtTLEtBQU1rRSxNQUd2QyxHQUNIN0UsU0FBU3VHLGlCQUFpQixXQUFZQyxJQUNsQyxJQUFLM0IsRUFBUytCLDRCQUNWLE9BR0osSUFBSyxJQUFJQyxFQUFPTCxFQUFNTSxPQUFRRCxFQUFNQSxFQUFPQSxFQUFLRSxXQUM1QyxHQUFxQixNQUFqQkYsRUFBS3hGLFFBQ0wsT0FHUixNQUFNWSxFQUFTdUUsRUFBTVEsTUFDZnJHLEVBQU82RCxFQUFjWixpQ0FBaUMzQixHQUN4QyxpQkFBVHRCLEdBQXNCUyxNQUFNVCxJQUNuQ3VFLEVBQVVvQixZQUFZLFdBQVksQ0FBRTNGLEtBQU1JLEtBQUtZLE1BQU1oQixPQUc3RCxNQUFNc0csRUFBeUIsQ0FBQyxRQUFTLFNBQVUsVUFBVyxVQUFXLG9CQUN6RWpILFNBQVN1RyxpQkFBaUIsUUFBU0MsSUFDL0IsSUFBS0EsRUFDRCxPQUVKLElBQUlLLEVBQU9MLEVBQU1NLE9BQ2pCLEtBQU9ELEdBQU0sQ0FDVCxHQUFJQSxFQUFLeEYsU0FBNEIsTUFBakJ3RixFQUFLeEYsU0FBbUJ3RixFQUFLSyxLQUFNLENBQ25ELEdBQUlMLEVBQUsxRyxhQUFhLFFBQVFnSCxXQUFXLEtBQ3JDLE9BR0osR0FBSUYsRUFBdUJHLEtBQUtDLEdBQVVSLEVBQUtLLEtBQUtDLFdBQVdFLElBQzNELE9BRUosTUFBTUMsRUFBV1QsRUFBSzFHLGFBQWEsY0FBZ0IwRyxFQUFLMUcsYUFBYSxRQUVyRSxNQUFLLGNBQWNvSCxLQUFLRCxRQU14QixHQUxJcEMsRUFBVW9CLFlBQVksV0FBWSxDQUFFWSxLQUFNSSxJQUMxQ2QsRUFBTWdCLHNCQUNOaEIsRUFBTWlCLG1CQUtkWixFQUFPQSxFQUFLRSxjQUVqQixHQUNINUUsT0FBT29FLGlCQUFpQixTQUFVOUIsRUFBUyxLQUN2QyxHQUFJQyxFQUNBQSxHQUFpQixNQUVoQixDQUNELE1BQU0vRCxFQUFPNkQsRUFBY1osaUNBQWlDekIsT0FBT0MsU0FDL0MsaUJBQVR6QixHQUFzQlMsTUFBTVQsS0FDbkN1RSxFQUFVb0IsWUFBWSxhQUFjLENBQUUzRixTQUN0Q3FFLEVBQU1yRSxLQUFPQSxFQUNibUUsRUFBT0csU0FBU0QsTUFHekIsTSw2QkNySkgxRyxPQUFPQyxlQUFlYixFQUFTLGFBQWMsQ0FBRW1CLE9BQU8sSUFLdEQsTUFBTTJGLEVBQWdCLEVBQVEsR0F3QjlCOUcsRUFBUWtILGlCQXZCUixNQUNJLCtCQUErQmpFLEdBQzNCLE1BQU0sU0FBRWtCLEdBQWEyQyxFQUFjaEQseUJBQXlCYixHQUM1RCtHLEtBQUtDLFFBQVE5RixHQUFZQSxFQUFTOUIsU0FFdEMsUUFBUTZILEdBQ0pGLEtBQUtHLHFCQUFxQkgsS0FBS0ksVUFDL0JKLEtBQUtLLG1CQUFtQkgsR0FDeEJGLEtBQUtJLFNBQVdGLEVBRXBCLHFCQUFxQjdILEdBQ1pBLElBR0xBLEVBQVFpSSxVQUFZakksRUFBUWlJLFVBQVVDLFFBQVEsd0JBQXlCLEtBRTNFLG1CQUFtQmxJLEdBQ1ZBLElBR0xBLEVBQVFpSSxXQUFhLHdCLDZCQ3RCN0IxSixPQUFPQyxlQUFlYixFQUFTLGFBQWMsQ0FBRW1CLE9BQU8sSUFTdERuQixFQUFRK0gsbUJBUlIsU0FBNEJ5QyxHQUNJLFlBQXhCbEksU0FBU21JLFlBQW9ELGtCQUF4Qm5JLFNBQVNtSSxXQUM5Q25JLFNBQVN1RyxpQkFBaUIsbUJBQW9CMkIsR0FHOUNBLE0sNkJDTlI1SixPQUFPQyxlQUFlYixFQUFTLGFBQWMsQ0FBRW1CLE9BQU8sSUFDdEQsTUFBTTJCLEVBQWEsRUFBUSxHQUMzQjlDLEVBQVF5SCxzQkFBeUJMLEdBQ3RCLElBQUksTUFDUCxZQUFZNEIsRUFBTXhGLEdBQ2Q0RCxFQUFPd0IsWUFBWSxDQUNmSSxPQUNBRCxPQUFRakcsRUFBV0QsY0FBY2tHLE9BQ2pDdkYsWSxpQkNiaEIsWUFVQSxJQUFJa0gsRUFBa0Isc0JBR2xCQyxFQUFNLElBR05DLEVBQVksa0JBR1pDLEVBQVMsYUFHVEMsRUFBYSxxQkFHYkMsRUFBYSxhQUdiQyxFQUFZLGNBR1pDLEVBQWVDLFNBR2ZDLEVBQThCLGlCQUFWQyxHQUFzQkEsR0FBVUEsRUFBT3hLLFNBQVdBLFFBQVV3SyxFQUdoRkMsRUFBMEIsaUJBQVJDLE1BQW9CQSxNQUFRQSxLQUFLMUssU0FBV0EsUUFBVTBLLEtBR3hFQyxFQUFPSixHQUFjRSxHQUFZRyxTQUFTLGNBQVRBLEdBVWpDQyxFQVBjN0ssT0FBT2tCLFVBT1E0SixTQUc3QkMsRUFBWXRJLEtBQUtGLElBQ2pCeUksRUFBWXZJLEtBQUtILElBa0JqQjJJLEVBQU0sV0FDUixPQUFPTixFQUFLTyxLQUFLRCxPQXlEbkIsU0FBU0UsRUFBU0MsRUFBTUMsRUFBTUMsR0FDNUIsSUFBSUMsRUFDQUMsRUFDQUMsRUFDQUMsRUFDQUMsRUFDQUMsRUFDQUMsRUFBaUIsRUFDakJDLEdBQVUsRUFDVkMsR0FBUyxFQUNUQyxHQUFXLEVBRWYsR0FBbUIsbUJBQVJaLEVBQ1QsTUFBTSxJQUFJYSxVQUFVbkMsR0FVdEIsU0FBU29DLEVBQVdDLEdBQ2xCLElBQUlDLEVBQU9iLEVBQ1BjLEVBQVViLEVBS2QsT0FIQUQsRUFBV0MsT0FBV2pLLEVBQ3RCc0ssRUFBaUJNLEVBQ2pCVCxFQUFTTixFQUFLa0IsTUFBTUQsRUFBU0QsR0FxQi9CLFNBQVNHLEVBQWFKLEdBQ3BCLElBQUlLLEVBQW9CTCxFQUFPUCxFQU0vQixZQUF5QnJLLElBQWpCcUssR0FBK0JZLEdBQXFCbkIsR0FDekRtQixFQUFvQixHQUFPVCxHQU5KSSxFQUFPTixHQU04QkosRUFHakUsU0FBU2dCLElBQ1AsSUFBSU4sRUFBT2xCLElBQ1gsR0FBSXNCLEVBQWFKLEdBQ2YsT0FBT08sRUFBYVAsR0FHdEJSLEVBQVV2RSxXQUFXcUYsRUF6QnZCLFNBQXVCTixHQUNyQixJQUVJVCxFQUFTTCxHQUZXYyxFQUFPUCxHQUkvQixPQUFPRyxFQUFTZixFQUFVVSxFQUFRRCxHQUhSVSxFQUFPTixJQUdrQ0gsRUFvQmhDaUIsQ0FBY1IsSUFHbkQsU0FBU08sRUFBYVAsR0FLcEIsT0FKQVIsT0FBVXBLLEVBSU55SyxHQUFZVCxFQUNQVyxFQUFXQyxJQUVwQlosRUFBV0MsT0FBV2pLLEVBQ2ZtSyxHQWVULFNBQVNrQixJQUNQLElBQUlULEVBQU9sQixJQUNQNEIsRUFBYU4sRUFBYUosR0FNOUIsR0FKQVosRUFBV3VCLFVBQ1h0QixFQUFXcEMsS0FDWHdDLEVBQWVPLEVBRVhVLEVBQVksQ0FDZCxRQUFnQnRMLElBQVpvSyxFQUNGLE9BdkVOLFNBQXFCUSxHQU1uQixPQUpBTixFQUFpQk0sRUFFakJSLEVBQVV2RSxXQUFXcUYsRUFBY3BCLEdBRTVCUyxFQUFVSSxFQUFXQyxHQUFRVCxFQWlFekJxQixDQUFZbkIsR0FFckIsR0FBSUcsRUFHRixPQURBSixFQUFVdkUsV0FBV3FGLEVBQWNwQixHQUM1QmEsRUFBV04sR0FNdEIsWUFIZ0JySyxJQUFab0ssSUFDRkEsRUFBVXZFLFdBQVdxRixFQUFjcEIsSUFFOUJLLEVBSVQsT0F4R0FMLEVBQU8yQixFQUFTM0IsSUFBUyxFQUNyQjRCLEVBQVMzQixLQUNYUSxJQUFZUixFQUFRUSxRQUVwQkwsR0FEQU0sRUFBUyxZQUFhVCxHQUNIUCxFQUFVaUMsRUFBUzFCLEVBQVFHLFVBQVksRUFBR0osR0FBUUksRUFDckVPLEVBQVcsYUFBY1YsSUFBWUEsRUFBUVUsU0FBV0EsR0FpRzFEWSxFQUFVTSxPQW5DVixnQkFDa0IzTCxJQUFab0ssR0FDRndCLGFBQWF4QixHQUVmRSxFQUFpQixFQUNqQk4sRUFBV0ssRUFBZUosRUFBV0csT0FBVXBLLEdBK0JqRHFMLEVBQVVRLE1BNUJWLFdBQ0UsWUFBbUI3TCxJQUFab0ssRUFBd0JELEVBQVNnQixFQUFhekIsTUE0QmhEMkIsRUEwRlQsU0FBU0ssRUFBUzFNLEdBQ2hCLElBQUk2SCxTQUFjN0gsRUFDbEIsUUFBU0EsSUFBa0IsVUFBUjZILEdBQTRCLFlBQVJBLEdBNEV6QyxTQUFTNEUsRUFBU3pNLEdBQ2hCLEdBQW9CLGlCQUFUQSxFQUNULE9BQU9BLEVBRVQsR0FoQ0YsU0FBa0JBLEdBQ2hCLE1BQXVCLGlCQUFUQSxHQXRCaEIsU0FBc0JBLEdBQ3BCLFFBQVNBLEdBQXlCLGlCQUFUQSxFQXNCdEI4TSxDQUFhOU0sSUFBVXNLLEVBQWVwTCxLQUFLYyxJQUFVeUosRUE4QnBEc0QsQ0FBUy9NLEdBQ1gsT0FBT3dKLEVBRVQsR0FBSWtELEVBQVMxTSxHQUFRLENBQ25CLElBQUlnTixFQUFnQyxtQkFBakJoTixFQUFNaU4sUUFBd0JqTixFQUFNaU4sVUFBWWpOLEVBQ25FQSxFQUFRME0sRUFBU00sR0FBVUEsRUFBUSxHQUFNQSxFQUUzQyxHQUFvQixpQkFBVGhOLEVBQ1QsT0FBaUIsSUFBVkEsRUFBY0EsR0FBU0EsRUFFaENBLEVBQVFBLEVBQU1vSixRQUFRTSxFQUFRLElBQzlCLElBQUl3RCxFQUFXdEQsRUFBV2xCLEtBQUsxSSxHQUMvQixPQUFRa04sR0FBWXJELEVBQVVuQixLQUFLMUksR0FDL0I4SixFQUFhOUosRUFBTW1OLE1BQU0sR0FBSUQsRUFBVyxFQUFJLEdBQzNDdkQsRUFBV2pCLEtBQUsxSSxHQUFTd0osR0FBT3hKLEVBR3ZDbEIsRUFBT0QsUUE5SVAsU0FBa0JnTSxFQUFNQyxFQUFNQyxHQUM1QixJQUFJUSxHQUFVLEVBQ1ZFLEdBQVcsRUFFZixHQUFtQixtQkFBUlosRUFDVCxNQUFNLElBQUlhLFVBQVVuQyxHQU10QixPQUpJbUQsRUFBUzNCLEtBQ1hRLEVBQVUsWUFBYVIsSUFBWUEsRUFBUVEsUUFBVUEsRUFDckRFLEVBQVcsYUFBY1YsSUFBWUEsRUFBUVUsU0FBV0EsR0FFbkRiLEVBQVNDLEVBQU1DLEVBQU0sQ0FDMUIsUUFBV1MsRUFDWCxRQUFXVCxFQUNYLFNBQVlXLE8sK0JDdFRoQixJQUFJMkIsRUFHSkEsRUFBSSxXQUNILE9BQU92RSxLQURKLEdBSUosSUFFQ3VFLEVBQUlBLEdBQUssSUFBSS9DLFNBQVMsY0FBYixHQUNSLE1BQU9nRCxHQUVjLGlCQUFYL0osU0FBcUI4SixFQUFJOUosUUFPckN4RSxFQUFPRCxRQUFVdU8iLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VzQ29udGVudCI6WyIgXHQvLyBUaGUgbW9kdWxlIGNhY2hlXG4gXHR2YXIgaW5zdGFsbGVkTW9kdWxlcyA9IHt9O1xuXG4gXHQvLyBUaGUgcmVxdWlyZSBmdW5jdGlvblxuIFx0ZnVuY3Rpb24gX193ZWJwYWNrX3JlcXVpcmVfXyhtb2R1bGVJZCkge1xuXG4gXHRcdC8vIENoZWNrIGlmIG1vZHVsZSBpcyBpbiBjYWNoZVxuIFx0XHRpZihpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXSkge1xuIFx0XHRcdHJldHVybiBpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXS5leHBvcnRzO1xuIFx0XHR9XG4gXHRcdC8vIENyZWF0ZSBhIG5ldyBtb2R1bGUgKGFuZCBwdXQgaXQgaW50byB0aGUgY2FjaGUpXG4gXHRcdHZhciBtb2R1bGUgPSBpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXSA9IHtcbiBcdFx0XHRpOiBtb2R1bGVJZCxcbiBcdFx0XHRsOiBmYWxzZSxcbiBcdFx0XHRleHBvcnRzOiB7fVxuIFx0XHR9O1xuXG4gXHRcdC8vIEV4ZWN1dGUgdGhlIG1vZHVsZSBmdW5jdGlvblxuIFx0XHRtb2R1bGVzW21vZHVsZUlkXS5jYWxsKG1vZHVsZS5leHBvcnRzLCBtb2R1bGUsIG1vZHVsZS5leHBvcnRzLCBfX3dlYnBhY2tfcmVxdWlyZV9fKTtcblxuIFx0XHQvLyBGbGFnIHRoZSBtb2R1bGUgYXMgbG9hZGVkXG4gXHRcdG1vZHVsZS5sID0gdHJ1ZTtcblxuIFx0XHQvLyBSZXR1cm4gdGhlIGV4cG9ydHMgb2YgdGhlIG1vZHVsZVxuIFx0XHRyZXR1cm4gbW9kdWxlLmV4cG9ydHM7XG4gXHR9XG5cblxuIFx0Ly8gZXhwb3NlIHRoZSBtb2R1bGVzIG9iamVjdCAoX193ZWJwYWNrX21vZHVsZXNfXylcbiBcdF9fd2VicGFja19yZXF1aXJlX18ubSA9IG1vZHVsZXM7XG5cbiBcdC8vIGV4cG9zZSB0aGUgbW9kdWxlIGNhY2hlXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLmMgPSBpbnN0YWxsZWRNb2R1bGVzO1xuXG4gXHQvLyBkZWZpbmUgZ2V0dGVyIGZ1bmN0aW9uIGZvciBoYXJtb255IGV4cG9ydHNcbiBcdF9fd2VicGFja19yZXF1aXJlX18uZCA9IGZ1bmN0aW9uKGV4cG9ydHMsIG5hbWUsIGdldHRlcikge1xuIFx0XHRpZighX193ZWJwYWNrX3JlcXVpcmVfXy5vKGV4cG9ydHMsIG5hbWUpKSB7XG4gXHRcdFx0T2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsIG5hbWUsIHsgZW51bWVyYWJsZTogdHJ1ZSwgZ2V0OiBnZXR0ZXIgfSk7XG4gXHRcdH1cbiBcdH07XG5cbiBcdC8vIGRlZmluZSBfX2VzTW9kdWxlIG9uIGV4cG9ydHNcbiBcdF9fd2VicGFja19yZXF1aXJlX18uciA9IGZ1bmN0aW9uKGV4cG9ydHMpIHtcbiBcdFx0aWYodHlwZW9mIFN5bWJvbCAhPT0gJ3VuZGVmaW5lZCcgJiYgU3ltYm9sLnRvU3RyaW5nVGFnKSB7XG4gXHRcdFx0T2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsIFN5bWJvbC50b1N0cmluZ1RhZywgeyB2YWx1ZTogJ01vZHVsZScgfSk7XG4gXHRcdH1cbiBcdFx0T2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsICdfX2VzTW9kdWxlJywgeyB2YWx1ZTogdHJ1ZSB9KTtcbiBcdH07XG5cbiBcdC8vIGNyZWF0ZSBhIGZha2UgbmFtZXNwYWNlIG9iamVjdFxuIFx0Ly8gbW9kZSAmIDE6IHZhbHVlIGlzIGEgbW9kdWxlIGlkLCByZXF1aXJlIGl0XG4gXHQvLyBtb2RlICYgMjogbWVyZ2UgYWxsIHByb3BlcnRpZXMgb2YgdmFsdWUgaW50byB0aGUgbnNcbiBcdC8vIG1vZGUgJiA0OiByZXR1cm4gdmFsdWUgd2hlbiBhbHJlYWR5IG5zIG9iamVjdFxuIFx0Ly8gbW9kZSAmIDh8MTogYmVoYXZlIGxpa2UgcmVxdWlyZVxuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy50ID0gZnVuY3Rpb24odmFsdWUsIG1vZGUpIHtcbiBcdFx0aWYobW9kZSAmIDEpIHZhbHVlID0gX193ZWJwYWNrX3JlcXVpcmVfXyh2YWx1ZSk7XG4gXHRcdGlmKG1vZGUgJiA4KSByZXR1cm4gdmFsdWU7XG4gXHRcdGlmKChtb2RlICYgNCkgJiYgdHlwZW9mIHZhbHVlID09PSAnb2JqZWN0JyAmJiB2YWx1ZSAmJiB2YWx1ZS5fX2VzTW9kdWxlKSByZXR1cm4gdmFsdWU7XG4gXHRcdHZhciBucyA9IE9iamVjdC5jcmVhdGUobnVsbCk7XG4gXHRcdF9fd2VicGFja19yZXF1aXJlX18ucihucyk7XG4gXHRcdE9iamVjdC5kZWZpbmVQcm9wZXJ0eShucywgJ2RlZmF1bHQnLCB7IGVudW1lcmFibGU6IHRydWUsIHZhbHVlOiB2YWx1ZSB9KTtcbiBcdFx0aWYobW9kZSAmIDIgJiYgdHlwZW9mIHZhbHVlICE9ICdzdHJpbmcnKSBmb3IodmFyIGtleSBpbiB2YWx1ZSkgX193ZWJwYWNrX3JlcXVpcmVfXy5kKG5zLCBrZXksIGZ1bmN0aW9uKGtleSkgeyByZXR1cm4gdmFsdWVba2V5XTsgfS5iaW5kKG51bGwsIGtleSkpO1xuIFx0XHRyZXR1cm4gbnM7XG4gXHR9O1xuXG4gXHQvLyBnZXREZWZhdWx0RXhwb3J0IGZ1bmN0aW9uIGZvciBjb21wYXRpYmlsaXR5IHdpdGggbm9uLWhhcm1vbnkgbW9kdWxlc1xuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5uID0gZnVuY3Rpb24obW9kdWxlKSB7XG4gXHRcdHZhciBnZXR0ZXIgPSBtb2R1bGUgJiYgbW9kdWxlLl9fZXNNb2R1bGUgP1xuIFx0XHRcdGZ1bmN0aW9uIGdldERlZmF1bHQoKSB7IHJldHVybiBtb2R1bGVbJ2RlZmF1bHQnXTsgfSA6XG4gXHRcdFx0ZnVuY3Rpb24gZ2V0TW9kdWxlRXhwb3J0cygpIHsgcmV0dXJuIG1vZHVsZTsgfTtcbiBcdFx0X193ZWJwYWNrX3JlcXVpcmVfXy5kKGdldHRlciwgJ2EnLCBnZXR0ZXIpO1xuIFx0XHRyZXR1cm4gZ2V0dGVyO1xuIFx0fTtcblxuIFx0Ly8gT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLm8gPSBmdW5jdGlvbihvYmplY3QsIHByb3BlcnR5KSB7IHJldHVybiBPYmplY3QucHJvdG90eXBlLmhhc093blByb3BlcnR5LmNhbGwob2JqZWN0LCBwcm9wZXJ0eSk7IH07XG5cbiBcdC8vIF9fd2VicGFja19wdWJsaWNfcGF0aF9fXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLnAgPSBcIlwiO1xuXG5cbiBcdC8vIExvYWQgZW50cnkgbW9kdWxlIGFuZCByZXR1cm4gZXhwb3J0c1xuIFx0cmV0dXJuIF9fd2VicGFja19yZXF1aXJlX18oX193ZWJwYWNrX3JlcXVpcmVfXy5zID0gMik7XG4iLCJcInVzZSBzdHJpY3RcIjtcbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gKiAgQ29weXJpZ2h0IChjKSBNaWNyb3NvZnQgQ29ycG9yYXRpb24uIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiAgTGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBTZWUgTGljZW5zZS50eHQgaW4gdGhlIHByb2plY3Qgcm9vdCBmb3IgbGljZW5zZSBpbmZvcm1hdGlvbi5cbiAqLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuT2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsIFwiX19lc01vZHVsZVwiLCB7IHZhbHVlOiB0cnVlIH0pO1xubGV0IGNhY2hlZFNldHRpbmdzID0gdW5kZWZpbmVkO1xuZnVuY3Rpb24gZ2V0RGF0YShrZXkpIHtcbiAgICBjb25zdCBlbGVtZW50ID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ3ZzY29kZS1tYXJrZG93bi1wcmV2aWV3LWRhdGEnKTtcbiAgICBpZiAoZWxlbWVudCkge1xuICAgICAgICBjb25zdCBkYXRhID0gZWxlbWVudC5nZXRBdHRyaWJ1dGUoa2V5KTtcbiAgICAgICAgaWYgKGRhdGEpIHtcbiAgICAgICAgICAgIHJldHVybiBKU09OLnBhcnNlKGRhdGEpO1xuICAgICAgICB9XG4gICAgfVxuICAgIHRocm93IG5ldyBFcnJvcihgQ291bGQgbm90IGxvYWQgZGF0YSBmb3IgJHtrZXl9YCk7XG59XG5leHBvcnRzLmdldERhdGEgPSBnZXREYXRhO1xuZnVuY3Rpb24gZ2V0U2V0dGluZ3MoKSB7XG4gICAgaWYgKGNhY2hlZFNldHRpbmdzKSB7XG4gICAgICAgIHJldHVybiBjYWNoZWRTZXR0aW5ncztcbiAgICB9XG4gICAgY2FjaGVkU2V0dGluZ3MgPSBnZXREYXRhKCdkYXRhLXNldHRpbmdzJyk7XG4gICAgaWYgKGNhY2hlZFNldHRpbmdzKSB7XG4gICAgICAgIHJldHVybiBjYWNoZWRTZXR0aW5ncztcbiAgICB9XG4gICAgdGhyb3cgbmV3IEVycm9yKCdDb3VsZCBub3QgbG9hZCBzZXR0aW5ncycpO1xufVxuZXhwb3J0cy5nZXRTZXR0aW5ncyA9IGdldFNldHRpbmdzO1xuIiwiXCJ1c2Ugc3RyaWN0XCI7XG4vKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuICogIENvcHlyaWdodCAoYykgTWljcm9zb2Z0IENvcnBvcmF0aW9uLiBBbGwgcmlnaHRzIHJlc2VydmVkLlxuICogIExpY2Vuc2VkIHVuZGVyIHRoZSBNSVQgTGljZW5zZS4gU2VlIExpY2Vuc2UudHh0IGluIHRoZSBwcm9qZWN0IHJvb3QgZm9yIGxpY2Vuc2UgaW5mb3JtYXRpb24uXG4gKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cbk9iamVjdC5kZWZpbmVQcm9wZXJ0eShleHBvcnRzLCBcIl9fZXNNb2R1bGVcIiwgeyB2YWx1ZTogdHJ1ZSB9KTtcbmNvbnN0IHNldHRpbmdzXzEgPSByZXF1aXJlKFwiLi9zZXR0aW5nc1wiKTtcbmNvbnN0IGNvZGVMaW5lQ2xhc3MgPSAnY29kZS1saW5lJztcbmZ1bmN0aW9uIGNsYW1wKG1pbiwgbWF4LCB2YWx1ZSkge1xuICAgIHJldHVybiBNYXRoLm1pbihtYXgsIE1hdGgubWF4KG1pbiwgdmFsdWUpKTtcbn1cbmZ1bmN0aW9uIGNsYW1wTGluZShsaW5lKSB7XG4gICAgcmV0dXJuIGNsYW1wKDAsIHNldHRpbmdzXzEuZ2V0U2V0dGluZ3MoKS5saW5lQ291bnQgLSAxLCBsaW5lKTtcbn1cbmNvbnN0IGdldENvZGVMaW5lRWxlbWVudHMgPSAoKCkgPT4ge1xuICAgIGxldCBlbGVtZW50cztcbiAgICByZXR1cm4gKCkgPT4ge1xuICAgICAgICBpZiAoIWVsZW1lbnRzKSB7XG4gICAgICAgICAgICBlbGVtZW50cyA9IFt7IGVsZW1lbnQ6IGRvY3VtZW50LmJvZHksIGxpbmU6IDAgfV07XG4gICAgICAgICAgICBmb3IgKGNvbnN0IGVsZW1lbnQgb2YgZG9jdW1lbnQuZ2V0RWxlbWVudHNCeUNsYXNzTmFtZShjb2RlTGluZUNsYXNzKSkge1xuICAgICAgICAgICAgICAgIGNvbnN0IGxpbmUgPSArZWxlbWVudC5nZXRBdHRyaWJ1dGUoJ2RhdGEtbGluZScpO1xuICAgICAgICAgICAgICAgIGlmIChpc05hTihsaW5lKSkge1xuICAgICAgICAgICAgICAgICAgICBjb250aW51ZTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgaWYgKGVsZW1lbnQudGFnTmFtZSA9PT0gJ0NPREUnICYmIGVsZW1lbnQucGFyZW50RWxlbWVudCAmJiBlbGVtZW50LnBhcmVudEVsZW1lbnQudGFnTmFtZSA9PT0gJ1BSRScpIHtcbiAgICAgICAgICAgICAgICAgICAgLy8gRmVuY2hlZCBjb2RlIGJsb2NrcyBhcmUgYSBzcGVjaWFsIGNhc2Ugc2luY2UgdGhlIGBjb2RlLWxpbmVgIGNhbiBvbmx5IGJlIG1hcmtlZCBvblxuICAgICAgICAgICAgICAgICAgICAvLyB0aGUgYDxjb2RlPmAgZWxlbWVudCBhbmQgbm90IHRoZSBwYXJlbnQgYDxwcmU+YCBlbGVtZW50LlxuICAgICAgICAgICAgICAgICAgICBlbGVtZW50cy5wdXNoKHsgZWxlbWVudDogZWxlbWVudC5wYXJlbnRFbGVtZW50LCBsaW5lIH0pO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgICAgICAgICAgZWxlbWVudHMucHVzaCh7IGVsZW1lbnQ6IGVsZW1lbnQsIGxpbmUgfSk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIHJldHVybiBlbGVtZW50cztcbiAgICB9O1xufSkoKTtcbi8qKlxuICogRmluZCB0aGUgaHRtbCBlbGVtZW50cyB0aGF0IG1hcCB0byBhIHNwZWNpZmljIHRhcmdldCBsaW5lIGluIHRoZSBlZGl0b3IuXG4gKlxuICogSWYgYW4gZXhhY3QgbWF0Y2gsIHJldHVybnMgYSBzaW5nbGUgZWxlbWVudC4gSWYgdGhlIGxpbmUgaXMgYmV0d2VlbiBlbGVtZW50cyxcbiAqIHJldHVybnMgdGhlIGVsZW1lbnQgcHJpb3IgdG8gYW5kIHRoZSBlbGVtZW50IGFmdGVyIHRoZSBnaXZlbiBsaW5lLlxuICovXG5mdW5jdGlvbiBnZXRFbGVtZW50c0ZvclNvdXJjZUxpbmUodGFyZ2V0TGluZSkge1xuICAgIGNvbnN0IGxpbmVOdW1iZXIgPSBNYXRoLmZsb29yKHRhcmdldExpbmUpO1xuICAgIGNvbnN0IGxpbmVzID0gZ2V0Q29kZUxpbmVFbGVtZW50cygpO1xuICAgIGxldCBwcmV2aW91cyA9IGxpbmVzWzBdIHx8IG51bGw7XG4gICAgZm9yIChjb25zdCBlbnRyeSBvZiBsaW5lcykge1xuICAgICAgICBpZiAoZW50cnkubGluZSA9PT0gbGluZU51bWJlcikge1xuICAgICAgICAgICAgcmV0dXJuIHsgcHJldmlvdXM6IGVudHJ5LCBuZXh0OiB1bmRlZmluZWQgfTtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIGlmIChlbnRyeS5saW5lID4gbGluZU51bWJlcikge1xuICAgICAgICAgICAgcmV0dXJuIHsgcHJldmlvdXMsIG5leHQ6IGVudHJ5IH07XG4gICAgICAgIH1cbiAgICAgICAgcHJldmlvdXMgPSBlbnRyeTtcbiAgICB9XG4gICAgcmV0dXJuIHsgcHJldmlvdXMgfTtcbn1cbmV4cG9ydHMuZ2V0RWxlbWVudHNGb3JTb3VyY2VMaW5lID0gZ2V0RWxlbWVudHNGb3JTb3VyY2VMaW5lO1xuLyoqXG4gKiBGaW5kIHRoZSBodG1sIGVsZW1lbnRzIHRoYXQgYXJlIGF0IGEgc3BlY2lmaWMgcGl4ZWwgb2Zmc2V0IG9uIHRoZSBwYWdlLlxuICovXG5mdW5jdGlvbiBnZXRMaW5lRWxlbWVudHNBdFBhZ2VPZmZzZXQob2Zmc2V0KSB7XG4gICAgY29uc3QgbGluZXMgPSBnZXRDb2RlTGluZUVsZW1lbnRzKCk7XG4gICAgY29uc3QgcG9zaXRpb24gPSBvZmZzZXQgLSB3aW5kb3cuc2Nyb2xsWTtcbiAgICBsZXQgbG8gPSAtMTtcbiAgICBsZXQgaGkgPSBsaW5lcy5sZW5ndGggLSAxO1xuICAgIHdoaWxlIChsbyArIDEgPCBoaSkge1xuICAgICAgICBjb25zdCBtaWQgPSBNYXRoLmZsb29yKChsbyArIGhpKSAvIDIpO1xuICAgICAgICBjb25zdCBib3VuZHMgPSBnZXRFbGVtZW50Qm91bmRzKGxpbmVzW21pZF0pO1xuICAgICAgICBpZiAoYm91bmRzLnRvcCArIGJvdW5kcy5oZWlnaHQgPj0gcG9zaXRpb24pIHtcbiAgICAgICAgICAgIGhpID0gbWlkO1xuICAgICAgICB9XG4gICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgbG8gPSBtaWQ7XG4gICAgICAgIH1cbiAgICB9XG4gICAgY29uc3QgaGlFbGVtZW50ID0gbGluZXNbaGldO1xuICAgIGNvbnN0IGhpQm91bmRzID0gZ2V0RWxlbWVudEJvdW5kcyhoaUVsZW1lbnQpO1xuICAgIGlmIChoaSA+PSAxICYmIGhpQm91bmRzLnRvcCA+IHBvc2l0aW9uKSB7XG4gICAgICAgIGNvbnN0IGxvRWxlbWVudCA9IGxpbmVzW2xvXTtcbiAgICAgICAgcmV0dXJuIHsgcHJldmlvdXM6IGxvRWxlbWVudCwgbmV4dDogaGlFbGVtZW50IH07XG4gICAgfVxuICAgIGlmIChoaSA+IDEgJiYgaGkgPCBsaW5lcy5sZW5ndGggJiYgaGlCb3VuZHMudG9wICsgaGlCb3VuZHMuaGVpZ2h0ID4gcG9zaXRpb24pIHtcbiAgICAgICAgcmV0dXJuIHsgcHJldmlvdXM6IGhpRWxlbWVudCwgbmV4dDogbGluZXNbaGkgKyAxXSB9O1xuICAgIH1cbiAgICByZXR1cm4geyBwcmV2aW91czogaGlFbGVtZW50IH07XG59XG5leHBvcnRzLmdldExpbmVFbGVtZW50c0F0UGFnZU9mZnNldCA9IGdldExpbmVFbGVtZW50c0F0UGFnZU9mZnNldDtcbmZ1bmN0aW9uIGdldEVsZW1lbnRCb3VuZHMoeyBlbGVtZW50IH0pIHtcbiAgICBjb25zdCBteUJvdW5kcyA9IGVsZW1lbnQuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCk7XG4gICAgLy8gU29tZSBjb2RlIGxpbmUgZWxlbWVudHMgbWF5IGNvbnRhaW4gb3RoZXIgY29kZSBsaW5lIGVsZW1lbnRzLlxuICAgIC8vIEluIHRob3NlIGNhc2VzLCBvbmx5IHRha2UgdGhlIGhlaWdodCB1cCB0byB0aGF0IGNoaWxkLlxuICAgIGNvbnN0IGNvZGVMaW5lQ2hpbGQgPSBlbGVtZW50LnF1ZXJ5U2VsZWN0b3IoYC4ke2NvZGVMaW5lQ2xhc3N9YCk7XG4gICAgaWYgKGNvZGVMaW5lQ2hpbGQpIHtcbiAgICAgICAgY29uc3QgY2hpbGRCb3VuZHMgPSBjb2RlTGluZUNoaWxkLmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpO1xuICAgICAgICBjb25zdCBoZWlnaHQgPSBNYXRoLm1heCgxLCAoY2hpbGRCb3VuZHMudG9wIC0gbXlCb3VuZHMudG9wKSk7XG4gICAgICAgIHJldHVybiB7XG4gICAgICAgICAgICB0b3A6IG15Qm91bmRzLnRvcCxcbiAgICAgICAgICAgIGhlaWdodDogaGVpZ2h0XG4gICAgICAgIH07XG4gICAgfVxuICAgIHJldHVybiBteUJvdW5kcztcbn1cbi8qKlxuICogQXR0ZW1wdCB0byByZXZlYWwgdGhlIGVsZW1lbnQgZm9yIGEgc291cmNlIGxpbmUgaW4gdGhlIGVkaXRvci5cbiAqL1xuZnVuY3Rpb24gc2Nyb2xsVG9SZXZlYWxTb3VyY2VMaW5lKGxpbmUpIHtcbiAgICBpZiAoIXNldHRpbmdzXzEuZ2V0U2V0dGluZ3MoKS5zY3JvbGxQcmV2aWV3V2l0aEVkaXRvcikge1xuICAgICAgICByZXR1cm47XG4gICAgfVxuICAgIGlmIChsaW5lIDw9IDApIHtcbiAgICAgICAgd2luZG93LnNjcm9sbCh3aW5kb3cuc2Nyb2xsWCwgMCk7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG4gICAgY29uc3QgeyBwcmV2aW91cywgbmV4dCB9ID0gZ2V0RWxlbWVudHNGb3JTb3VyY2VMaW5lKGxpbmUpO1xuICAgIGlmICghcHJldmlvdXMpIHtcbiAgICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBsZXQgc2Nyb2xsVG8gPSAwO1xuICAgIGNvbnN0IHJlY3QgPSBnZXRFbGVtZW50Qm91bmRzKHByZXZpb3VzKTtcbiAgICBjb25zdCBwcmV2aW91c1RvcCA9IHJlY3QudG9wO1xuICAgIGlmIChuZXh0ICYmIG5leHQubGluZSAhPT0gcHJldmlvdXMubGluZSkge1xuICAgICAgICAvLyBCZXR3ZWVuIHR3byBlbGVtZW50cy4gR28gdG8gcGVyY2VudGFnZSBvZmZzZXQgYmV0d2VlbiB0aGVtLlxuICAgICAgICBjb25zdCBiZXR3ZWVuUHJvZ3Jlc3MgPSAobGluZSAtIHByZXZpb3VzLmxpbmUpIC8gKG5leHQubGluZSAtIHByZXZpb3VzLmxpbmUpO1xuICAgICAgICBjb25zdCBlbGVtZW50T2Zmc2V0ID0gbmV4dC5lbGVtZW50LmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpLnRvcCAtIHByZXZpb3VzVG9wO1xuICAgICAgICBzY3JvbGxUbyA9IHByZXZpb3VzVG9wICsgYmV0d2VlblByb2dyZXNzICogZWxlbWVudE9mZnNldDtcbiAgICB9XG4gICAgZWxzZSB7XG4gICAgICAgIGNvbnN0IHByb2dyZXNzSW5FbGVtZW50ID0gbGluZSAtIE1hdGguZmxvb3IobGluZSk7XG4gICAgICAgIHNjcm9sbFRvID0gcHJldmlvdXNUb3AgKyAocmVjdC5oZWlnaHQgKiBwcm9ncmVzc0luRWxlbWVudCk7XG4gICAgfVxuICAgIHdpbmRvdy5zY3JvbGwod2luZG93LnNjcm9sbFgsIE1hdGgubWF4KDEsIHdpbmRvdy5zY3JvbGxZICsgc2Nyb2xsVG8pKTtcbn1cbmV4cG9ydHMuc2Nyb2xsVG9SZXZlYWxTb3VyY2VMaW5lID0gc2Nyb2xsVG9SZXZlYWxTb3VyY2VMaW5lO1xuZnVuY3Rpb24gZ2V0RWRpdG9yTGluZU51bWJlckZvclBhZ2VPZmZzZXQob2Zmc2V0KSB7XG4gICAgY29uc3QgeyBwcmV2aW91cywgbmV4dCB9ID0gZ2V0TGluZUVsZW1lbnRzQXRQYWdlT2Zmc2V0KG9mZnNldCk7XG4gICAgaWYgKHByZXZpb3VzKSB7XG4gICAgICAgIGNvbnN0IHByZXZpb3VzQm91bmRzID0gZ2V0RWxlbWVudEJvdW5kcyhwcmV2aW91cyk7XG4gICAgICAgIGNvbnN0IG9mZnNldEZyb21QcmV2aW91cyA9IChvZmZzZXQgLSB3aW5kb3cuc2Nyb2xsWSAtIHByZXZpb3VzQm91bmRzLnRvcCk7XG4gICAgICAgIGlmIChuZXh0KSB7XG4gICAgICAgICAgICBjb25zdCBwcm9ncmVzc0JldHdlZW5FbGVtZW50cyA9IG9mZnNldEZyb21QcmV2aW91cyAvIChnZXRFbGVtZW50Qm91bmRzKG5leHQpLnRvcCAtIHByZXZpb3VzQm91bmRzLnRvcCk7XG4gICAgICAgICAgICBjb25zdCBsaW5lID0gcHJldmlvdXMubGluZSArIHByb2dyZXNzQmV0d2VlbkVsZW1lbnRzICogKG5leHQubGluZSAtIHByZXZpb3VzLmxpbmUpO1xuICAgICAgICAgICAgcmV0dXJuIGNsYW1wTGluZShsaW5lKTtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIGNvbnN0IHByb2dyZXNzV2l0aGluRWxlbWVudCA9IG9mZnNldEZyb21QcmV2aW91cyAvIChwcmV2aW91c0JvdW5kcy5oZWlnaHQpO1xuICAgICAgICAgICAgY29uc3QgbGluZSA9IHByZXZpb3VzLmxpbmUgKyBwcm9ncmVzc1dpdGhpbkVsZW1lbnQ7XG4gICAgICAgICAgICByZXR1cm4gY2xhbXBMaW5lKGxpbmUpO1xuICAgICAgICB9XG4gICAgfVxuICAgIHJldHVybiBudWxsO1xufVxuZXhwb3J0cy5nZXRFZGl0b3JMaW5lTnVtYmVyRm9yUGFnZU9mZnNldCA9IGdldEVkaXRvckxpbmVOdW1iZXJGb3JQYWdlT2Zmc2V0O1xuLyoqXG4gKiBUcnkgdG8gZmluZCB0aGUgaHRtbCBlbGVtZW50IGJ5IHVzaW5nIGEgZnJhZ21lbnQgaWRcbiAqL1xuZnVuY3Rpb24gZ2V0TGluZUVsZW1lbnRGb3JGcmFnbWVudChmcmFnbWVudCkge1xuICAgIHJldHVybiBnZXRDb2RlTGluZUVsZW1lbnRzKCkuZmluZCgoZWxlbWVudCkgPT4ge1xuICAgICAgICByZXR1cm4gZWxlbWVudC5lbGVtZW50LmlkID09PSBmcmFnbWVudDtcbiAgICB9KTtcbn1cbmV4cG9ydHMuZ2V0TGluZUVsZW1lbnRGb3JGcmFnbWVudCA9IGdldExpbmVFbGVtZW50Rm9yRnJhZ21lbnQ7XG4iLCJcInVzZSBzdHJpY3RcIjtcbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gKiAgQ29weXJpZ2h0IChjKSBNaWNyb3NvZnQgQ29ycG9yYXRpb24uIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiAgTGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBTZWUgTGljZW5zZS50eHQgaW4gdGhlIHByb2plY3Qgcm9vdCBmb3IgbGljZW5zZSBpbmZvcm1hdGlvbi5cbiAqLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuT2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsIFwiX19lc01vZHVsZVwiLCB7IHZhbHVlOiB0cnVlIH0pO1xuY29uc3QgYWN0aXZlTGluZU1hcmtlcl8xID0gcmVxdWlyZShcIi4vYWN0aXZlTGluZU1hcmtlclwiKTtcbmNvbnN0IGV2ZW50c18xID0gcmVxdWlyZShcIi4vZXZlbnRzXCIpO1xuY29uc3QgbWVzc2FnaW5nXzEgPSByZXF1aXJlKFwiLi9tZXNzYWdpbmdcIik7XG5jb25zdCBzY3JvbGxfc3luY18xID0gcmVxdWlyZShcIi4vc2Nyb2xsLXN5bmNcIik7XG5jb25zdCBzZXR0aW5nc18xID0gcmVxdWlyZShcIi4vc2V0dGluZ3NcIik7XG5jb25zdCB0aHJvdHRsZSA9IHJlcXVpcmUoXCJsb2Rhc2gudGhyb3R0bGVcIik7XG5sZXQgc2Nyb2xsRGlzYWJsZWQgPSB0cnVlO1xuY29uc3QgbWFya2VyID0gbmV3IGFjdGl2ZUxpbmVNYXJrZXJfMS5BY3RpdmVMaW5lTWFya2VyKCk7XG5jb25zdCBzZXR0aW5ncyA9IHNldHRpbmdzXzEuZ2V0U2V0dGluZ3MoKTtcbmNvbnN0IHZzY29kZSA9IGFjcXVpcmVWc0NvZGVBcGkoKTtcbi8vIFNldCBWUyBDb2RlIHN0YXRlXG5sZXQgc3RhdGUgPSBzZXR0aW5nc18xLmdldERhdGEoJ2RhdGEtc3RhdGUnKTtcbnZzY29kZS5zZXRTdGF0ZShzdGF0ZSk7XG5jb25zdCBtZXNzYWdpbmcgPSBtZXNzYWdpbmdfMS5jcmVhdGVQb3N0ZXJGb3JWc0NvZGUodnNjb2RlKTtcbndpbmRvdy5jc3BBbGVydGVyLnNldFBvc3RlcihtZXNzYWdpbmcpO1xud2luZG93LnN0eWxlTG9hZGluZ01vbml0b3Iuc2V0UG9zdGVyKG1lc3NhZ2luZyk7XG53aW5kb3cub25sb2FkID0gKCkgPT4ge1xuICAgIHVwZGF0ZUltYWdlU2l6ZXMoKTtcbn07XG5ldmVudHNfMS5vbmNlRG9jdW1lbnRMb2FkZWQoKCkgPT4ge1xuICAgIGlmIChzZXR0aW5ncy5zY3JvbGxQcmV2aWV3V2l0aEVkaXRvcikge1xuICAgICAgICBzZXRUaW1lb3V0KCgpID0+IHtcbiAgICAgICAgICAgIC8vIFRyeSB0byBzY3JvbGwgdG8gZnJhZ21lbnQgaWYgYXZhaWxhYmxlXG4gICAgICAgICAgICBpZiAoc3RhdGUuZnJhZ21lbnQpIHtcbiAgICAgICAgICAgICAgICBjb25zdCBlbGVtZW50ID0gc2Nyb2xsX3N5bmNfMS5nZXRMaW5lRWxlbWVudEZvckZyYWdtZW50KHN0YXRlLmZyYWdtZW50KTtcbiAgICAgICAgICAgICAgICBpZiAoZWxlbWVudCkge1xuICAgICAgICAgICAgICAgICAgICBzY3JvbGxEaXNhYmxlZCA9IHRydWU7XG4gICAgICAgICAgICAgICAgICAgIHNjcm9sbF9zeW5jXzEuc2Nyb2xsVG9SZXZlYWxTb3VyY2VMaW5lKGVsZW1lbnQubGluZSk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICAgICAgY29uc3QgaW5pdGlhbExpbmUgPSArc2V0dGluZ3MubGluZTtcbiAgICAgICAgICAgICAgICBpZiAoIWlzTmFOKGluaXRpYWxMaW5lKSkge1xuICAgICAgICAgICAgICAgICAgICBzY3JvbGxEaXNhYmxlZCA9IHRydWU7XG4gICAgICAgICAgICAgICAgICAgIHNjcm9sbF9zeW5jXzEuc2Nyb2xsVG9SZXZlYWxTb3VyY2VMaW5lKGluaXRpYWxMaW5lKTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgICAgIH0sIDApO1xuICAgIH1cbn0pO1xuY29uc3Qgb25VcGRhdGVWaWV3ID0gKCgpID0+IHtcbiAgICBjb25zdCBkb1Njcm9sbCA9IHRocm90dGxlKChsaW5lKSA9PiB7XG4gICAgICAgIHNjcm9sbERpc2FibGVkID0gdHJ1ZTtcbiAgICAgICAgc2Nyb2xsX3N5bmNfMS5zY3JvbGxUb1JldmVhbFNvdXJjZUxpbmUobGluZSk7XG4gICAgfSwgNTApO1xuICAgIHJldHVybiAobGluZSwgc2V0dGluZ3MpID0+IHtcbiAgICAgICAgaWYgKCFpc05hTihsaW5lKSkge1xuICAgICAgICAgICAgc2V0dGluZ3MubGluZSA9IGxpbmU7XG4gICAgICAgICAgICBkb1Njcm9sbChsaW5lKTtcbiAgICAgICAgfVxuICAgIH07XG59KSgpO1xubGV0IHVwZGF0ZUltYWdlU2l6ZXMgPSB0aHJvdHRsZSgoKSA9PiB7XG4gICAgY29uc3QgaW1hZ2VJbmZvID0gW107XG4gICAgbGV0IGltYWdlcyA9IGRvY3VtZW50LmdldEVsZW1lbnRzQnlUYWdOYW1lKCdpbWcnKTtcbiAgICBpZiAoaW1hZ2VzKSB7XG4gICAgICAgIGxldCBpO1xuICAgICAgICBmb3IgKGkgPSAwOyBpIDwgaW1hZ2VzLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgICAgICBjb25zdCBpbWcgPSBpbWFnZXNbaV07XG4gICAgICAgICAgICBpZiAoaW1nLmNsYXNzTGlzdC5jb250YWlucygnbG9hZGluZycpKSB7XG4gICAgICAgICAgICAgICAgaW1nLmNsYXNzTGlzdC5yZW1vdmUoJ2xvYWRpbmcnKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGltYWdlSW5mby5wdXNoKHtcbiAgICAgICAgICAgICAgICBpZDogaW1nLmlkLFxuICAgICAgICAgICAgICAgIGhlaWdodDogaW1nLmhlaWdodCxcbiAgICAgICAgICAgICAgICB3aWR0aDogaW1nLndpZHRoXG4gICAgICAgICAgICB9KTtcbiAgICAgICAgfVxuICAgICAgICBtZXNzYWdpbmcucG9zdE1lc3NhZ2UoJ2NhY2hlSW1hZ2VTaXplcycsIGltYWdlSW5mbyk7XG4gICAgfVxufSwgNTApO1xud2luZG93LmFkZEV2ZW50TGlzdGVuZXIoJ3Jlc2l6ZScsICgpID0+IHtcbiAgICBzY3JvbGxEaXNhYmxlZCA9IHRydWU7XG4gICAgdXBkYXRlSW1hZ2VTaXplcygpO1xufSwgdHJ1ZSk7XG53aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcignbWVzc2FnZScsIGV2ZW50ID0+IHtcbiAgICBpZiAoZXZlbnQuZGF0YS5zb3VyY2UgIT09IHNldHRpbmdzLnNvdXJjZSkge1xuICAgICAgICByZXR1cm47XG4gICAgfVxuICAgIHN3aXRjaCAoZXZlbnQuZGF0YS50eXBlKSB7XG4gICAgICAgIGNhc2UgJ29uRGlkQ2hhbmdlVGV4dEVkaXRvclNlbGVjdGlvbic6XG4gICAgICAgICAgICBtYXJrZXIub25EaWRDaGFuZ2VUZXh0RWRpdG9yU2VsZWN0aW9uKGV2ZW50LmRhdGEubGluZSk7XG4gICAgICAgICAgICBicmVhaztcbiAgICAgICAgY2FzZSAndXBkYXRlVmlldyc6XG4gICAgICAgICAgICBvblVwZGF0ZVZpZXcoZXZlbnQuZGF0YS5saW5lLCBzZXR0aW5ncyk7XG4gICAgICAgICAgICBicmVhaztcbiAgICB9XG59LCBmYWxzZSk7XG5kb2N1bWVudC5hZGRFdmVudExpc3RlbmVyKCdkYmxjbGljaycsIGV2ZW50ID0+IHtcbiAgICBpZiAoIXNldHRpbmdzLmRvdWJsZUNsaWNrVG9Td2l0Y2hUb0VkaXRvcikge1xuICAgICAgICByZXR1cm47XG4gICAgfVxuICAgIC8vIElnbm9yZSBjbGlja3Mgb24gbGlua3NcbiAgICBmb3IgKGxldCBub2RlID0gZXZlbnQudGFyZ2V0OyBub2RlOyBub2RlID0gbm9kZS5wYXJlbnROb2RlKSB7XG4gICAgICAgIGlmIChub2RlLnRhZ05hbWUgPT09ICdBJykge1xuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG4gICAgfVxuICAgIGNvbnN0IG9mZnNldCA9IGV2ZW50LnBhZ2VZO1xuICAgIGNvbnN0IGxpbmUgPSBzY3JvbGxfc3luY18xLmdldEVkaXRvckxpbmVOdW1iZXJGb3JQYWdlT2Zmc2V0KG9mZnNldCk7XG4gICAgaWYgKHR5cGVvZiBsaW5lID09PSAnbnVtYmVyJyAmJiAhaXNOYU4obGluZSkpIHtcbiAgICAgICAgbWVzc2FnaW5nLnBvc3RNZXNzYWdlKCdkaWRDbGljaycsIHsgbGluZTogTWF0aC5mbG9vcihsaW5lKSB9KTtcbiAgICB9XG59KTtcbmNvbnN0IHBhc3NUaHJvdWdoTGlua1NjaGVtZXMgPSBbJ2h0dHA6JywgJ2h0dHBzOicsICdtYWlsdG86JywgJ3ZzY29kZTonLCAndnNjb2RlLWluc2lkZXJzOiddO1xuZG9jdW1lbnQuYWRkRXZlbnRMaXN0ZW5lcignY2xpY2snLCBldmVudCA9PiB7XG4gICAgaWYgKCFldmVudCkge1xuICAgICAgICByZXR1cm47XG4gICAgfVxuICAgIGxldCBub2RlID0gZXZlbnQudGFyZ2V0O1xuICAgIHdoaWxlIChub2RlKSB7XG4gICAgICAgIGlmIChub2RlLnRhZ05hbWUgJiYgbm9kZS50YWdOYW1lID09PSAnQScgJiYgbm9kZS5ocmVmKSB7XG4gICAgICAgICAgICBpZiAobm9kZS5nZXRBdHRyaWJ1dGUoJ2hyZWYnKS5zdGFydHNXaXRoKCcjJykpIHtcbiAgICAgICAgICAgICAgICByZXR1cm47XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICAvLyBQYXNzIHRocm91Z2gga25vd24gc2NoZW1lc1xuICAgICAgICAgICAgaWYgKHBhc3NUaHJvdWdoTGlua1NjaGVtZXMuc29tZShzY2hlbWUgPT4gbm9kZS5ocmVmLnN0YXJ0c1dpdGgoc2NoZW1lKSkpIHtcbiAgICAgICAgICAgICAgICByZXR1cm47XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBjb25zdCBocmVmVGV4dCA9IG5vZGUuZ2V0QXR0cmlidXRlKCdkYXRhLWhyZWYnKSB8fCBub2RlLmdldEF0dHJpYnV0ZSgnaHJlZicpO1xuICAgICAgICAgICAgLy8gSWYgb3JpZ2luYWwgbGluayBkb2Vzbid0IGxvb2sgbGlrZSBhIHVybCwgZGVsZWdhdGUgYmFjayB0byBWUyBDb2RlIHRvIHJlc29sdmVcbiAgICAgICAgICAgIGlmICghL15bYS16XFwtXSs6L2kudGVzdChocmVmVGV4dCkpIHtcbiAgICAgICAgICAgICAgICBtZXNzYWdpbmcucG9zdE1lc3NhZ2UoJ29wZW5MaW5rJywgeyBocmVmOiBocmVmVGV4dCB9KTtcbiAgICAgICAgICAgICAgICBldmVudC5wcmV2ZW50RGVmYXVsdCgpO1xuICAgICAgICAgICAgICAgIGV2ZW50LnN0b3BQcm9wYWdhdGlvbigpO1xuICAgICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuICAgICAgICBub2RlID0gbm9kZS5wYXJlbnROb2RlO1xuICAgIH1cbn0sIHRydWUpO1xud2luZG93LmFkZEV2ZW50TGlzdGVuZXIoJ3Njcm9sbCcsIHRocm90dGxlKCgpID0+IHtcbiAgICBpZiAoc2Nyb2xsRGlzYWJsZWQpIHtcbiAgICAgICAgc2Nyb2xsRGlzYWJsZWQgPSBmYWxzZTtcbiAgICB9XG4gICAgZWxzZSB7XG4gICAgICAgIGNvbnN0IGxpbmUgPSBzY3JvbGxfc3luY18xLmdldEVkaXRvckxpbmVOdW1iZXJGb3JQYWdlT2Zmc2V0KHdpbmRvdy5zY3JvbGxZKTtcbiAgICAgICAgaWYgKHR5cGVvZiBsaW5lID09PSAnbnVtYmVyJyAmJiAhaXNOYU4obGluZSkpIHtcbiAgICAgICAgICAgIG1lc3NhZ2luZy5wb3N0TWVzc2FnZSgncmV2ZWFsTGluZScsIHsgbGluZSB9KTtcbiAgICAgICAgICAgIHN0YXRlLmxpbmUgPSBsaW5lO1xuICAgICAgICAgICAgdnNjb2RlLnNldFN0YXRlKHN0YXRlKTtcbiAgICAgICAgfVxuICAgIH1cbn0sIDUwKSk7XG4iLCJcInVzZSBzdHJpY3RcIjtcbk9iamVjdC5kZWZpbmVQcm9wZXJ0eShleHBvcnRzLCBcIl9fZXNNb2R1bGVcIiwgeyB2YWx1ZTogdHJ1ZSB9KTtcbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gKiAgQ29weXJpZ2h0IChjKSBNaWNyb3NvZnQgQ29ycG9yYXRpb24uIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiAgTGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBTZWUgTGljZW5zZS50eHQgaW4gdGhlIHByb2plY3Qgcm9vdCBmb3IgbGljZW5zZSBpbmZvcm1hdGlvbi5cbiAqLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuY29uc3Qgc2Nyb2xsX3N5bmNfMSA9IHJlcXVpcmUoXCIuL3Njcm9sbC1zeW5jXCIpO1xuY2xhc3MgQWN0aXZlTGluZU1hcmtlciB7XG4gICAgb25EaWRDaGFuZ2VUZXh0RWRpdG9yU2VsZWN0aW9uKGxpbmUpIHtcbiAgICAgICAgY29uc3QgeyBwcmV2aW91cyB9ID0gc2Nyb2xsX3N5bmNfMS5nZXRFbGVtZW50c0ZvclNvdXJjZUxpbmUobGluZSk7XG4gICAgICAgIHRoaXMuX3VwZGF0ZShwcmV2aW91cyAmJiBwcmV2aW91cy5lbGVtZW50KTtcbiAgICB9XG4gICAgX3VwZGF0ZShiZWZvcmUpIHtcbiAgICAgICAgdGhpcy5fdW5tYXJrQWN0aXZlRWxlbWVudCh0aGlzLl9jdXJyZW50KTtcbiAgICAgICAgdGhpcy5fbWFya0FjdGl2ZUVsZW1lbnQoYmVmb3JlKTtcbiAgICAgICAgdGhpcy5fY3VycmVudCA9IGJlZm9yZTtcbiAgICB9XG4gICAgX3VubWFya0FjdGl2ZUVsZW1lbnQoZWxlbWVudCkge1xuICAgICAgICBpZiAoIWVsZW1lbnQpIHtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuICAgICAgICBlbGVtZW50LmNsYXNzTmFtZSA9IGVsZW1lbnQuY2xhc3NOYW1lLnJlcGxhY2UoL1xcYmNvZGUtYWN0aXZlLWxpbmVcXGIvZywgJycpO1xuICAgIH1cbiAgICBfbWFya0FjdGl2ZUVsZW1lbnQoZWxlbWVudCkge1xuICAgICAgICBpZiAoIWVsZW1lbnQpIHtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuICAgICAgICBlbGVtZW50LmNsYXNzTmFtZSArPSAnIGNvZGUtYWN0aXZlLWxpbmUnO1xuICAgIH1cbn1cbmV4cG9ydHMuQWN0aXZlTGluZU1hcmtlciA9IEFjdGl2ZUxpbmVNYXJrZXI7XG4iLCJcInVzZSBzdHJpY3RcIjtcbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gKiAgQ29weXJpZ2h0IChjKSBNaWNyb3NvZnQgQ29ycG9yYXRpb24uIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiAgTGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBTZWUgTGljZW5zZS50eHQgaW4gdGhlIHByb2plY3Qgcm9vdCBmb3IgbGljZW5zZSBpbmZvcm1hdGlvbi5cbiAqLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuT2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsIFwiX19lc01vZHVsZVwiLCB7IHZhbHVlOiB0cnVlIH0pO1xuZnVuY3Rpb24gb25jZURvY3VtZW50TG9hZGVkKGYpIHtcbiAgICBpZiAoZG9jdW1lbnQucmVhZHlTdGF0ZSA9PT0gJ2xvYWRpbmcnIHx8IGRvY3VtZW50LnJlYWR5U3RhdGUgPT09ICd1bmluaXRpYWxpemVkJykge1xuICAgICAgICBkb2N1bWVudC5hZGRFdmVudExpc3RlbmVyKCdET01Db250ZW50TG9hZGVkJywgZik7XG4gICAgfVxuICAgIGVsc2Uge1xuICAgICAgICBmKCk7XG4gICAgfVxufVxuZXhwb3J0cy5vbmNlRG9jdW1lbnRMb2FkZWQgPSBvbmNlRG9jdW1lbnRMb2FkZWQ7XG4iLCJcInVzZSBzdHJpY3RcIjtcbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gKiAgQ29weXJpZ2h0IChjKSBNaWNyb3NvZnQgQ29ycG9yYXRpb24uIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiAgTGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBTZWUgTGljZW5zZS50eHQgaW4gdGhlIHByb2plY3Qgcm9vdCBmb3IgbGljZW5zZSBpbmZvcm1hdGlvbi5cbiAqLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuT2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsIFwiX19lc01vZHVsZVwiLCB7IHZhbHVlOiB0cnVlIH0pO1xuY29uc3Qgc2V0dGluZ3NfMSA9IHJlcXVpcmUoXCIuL3NldHRpbmdzXCIpO1xuZXhwb3J0cy5jcmVhdGVQb3N0ZXJGb3JWc0NvZGUgPSAodnNjb2RlKSA9PiB7XG4gICAgcmV0dXJuIG5ldyBjbGFzcyB7XG4gICAgICAgIHBvc3RNZXNzYWdlKHR5cGUsIGJvZHkpIHtcbiAgICAgICAgICAgIHZzY29kZS5wb3N0TWVzc2FnZSh7XG4gICAgICAgICAgICAgICAgdHlwZSxcbiAgICAgICAgICAgICAgICBzb3VyY2U6IHNldHRpbmdzXzEuZ2V0U2V0dGluZ3MoKS5zb3VyY2UsXG4gICAgICAgICAgICAgICAgYm9keVxuICAgICAgICAgICAgfSk7XG4gICAgICAgIH1cbiAgICB9O1xufTtcbiIsIi8qKlxuICogbG9kYXNoIChDdXN0b20gQnVpbGQpIDxodHRwczovL2xvZGFzaC5jb20vPlxuICogQnVpbGQ6IGBsb2Rhc2ggbW9kdWxhcml6ZSBleHBvcnRzPVwibnBtXCIgLW8gLi9gXG4gKiBDb3B5cmlnaHQgalF1ZXJ5IEZvdW5kYXRpb24gYW5kIG90aGVyIGNvbnRyaWJ1dG9ycyA8aHR0cHM6Ly9qcXVlcnkub3JnLz5cbiAqIFJlbGVhc2VkIHVuZGVyIE1JVCBsaWNlbnNlIDxodHRwczovL2xvZGFzaC5jb20vbGljZW5zZT5cbiAqIEJhc2VkIG9uIFVuZGVyc2NvcmUuanMgMS44LjMgPGh0dHA6Ly91bmRlcnNjb3JlanMub3JnL0xJQ0VOU0U+XG4gKiBDb3B5cmlnaHQgSmVyZW15IEFzaGtlbmFzLCBEb2N1bWVudENsb3VkIGFuZCBJbnZlc3RpZ2F0aXZlIFJlcG9ydGVycyAmIEVkaXRvcnNcbiAqL1xuXG4vKiogVXNlZCBhcyB0aGUgYFR5cGVFcnJvcmAgbWVzc2FnZSBmb3IgXCJGdW5jdGlvbnNcIiBtZXRob2RzLiAqL1xudmFyIEZVTkNfRVJST1JfVEVYVCA9ICdFeHBlY3RlZCBhIGZ1bmN0aW9uJztcblxuLyoqIFVzZWQgYXMgcmVmZXJlbmNlcyBmb3IgdmFyaW91cyBgTnVtYmVyYCBjb25zdGFudHMuICovXG52YXIgTkFOID0gMCAvIDA7XG5cbi8qKiBgT2JqZWN0I3RvU3RyaW5nYCByZXN1bHQgcmVmZXJlbmNlcy4gKi9cbnZhciBzeW1ib2xUYWcgPSAnW29iamVjdCBTeW1ib2xdJztcblxuLyoqIFVzZWQgdG8gbWF0Y2ggbGVhZGluZyBhbmQgdHJhaWxpbmcgd2hpdGVzcGFjZS4gKi9cbnZhciByZVRyaW0gPSAvXlxccyt8XFxzKyQvZztcblxuLyoqIFVzZWQgdG8gZGV0ZWN0IGJhZCBzaWduZWQgaGV4YWRlY2ltYWwgc3RyaW5nIHZhbHVlcy4gKi9cbnZhciByZUlzQmFkSGV4ID0gL15bLStdMHhbMC05YS1mXSskL2k7XG5cbi8qKiBVc2VkIHRvIGRldGVjdCBiaW5hcnkgc3RyaW5nIHZhbHVlcy4gKi9cbnZhciByZUlzQmluYXJ5ID0gL14wYlswMV0rJC9pO1xuXG4vKiogVXNlZCB0byBkZXRlY3Qgb2N0YWwgc3RyaW5nIHZhbHVlcy4gKi9cbnZhciByZUlzT2N0YWwgPSAvXjBvWzAtN10rJC9pO1xuXG4vKiogQnVpbHQtaW4gbWV0aG9kIHJlZmVyZW5jZXMgd2l0aG91dCBhIGRlcGVuZGVuY3kgb24gYHJvb3RgLiAqL1xudmFyIGZyZWVQYXJzZUludCA9IHBhcnNlSW50O1xuXG4vKiogRGV0ZWN0IGZyZWUgdmFyaWFibGUgYGdsb2JhbGAgZnJvbSBOb2RlLmpzLiAqL1xudmFyIGZyZWVHbG9iYWwgPSB0eXBlb2YgZ2xvYmFsID09ICdvYmplY3QnICYmIGdsb2JhbCAmJiBnbG9iYWwuT2JqZWN0ID09PSBPYmplY3QgJiYgZ2xvYmFsO1xuXG4vKiogRGV0ZWN0IGZyZWUgdmFyaWFibGUgYHNlbGZgLiAqL1xudmFyIGZyZWVTZWxmID0gdHlwZW9mIHNlbGYgPT0gJ29iamVjdCcgJiYgc2VsZiAmJiBzZWxmLk9iamVjdCA9PT0gT2JqZWN0ICYmIHNlbGY7XG5cbi8qKiBVc2VkIGFzIGEgcmVmZXJlbmNlIHRvIHRoZSBnbG9iYWwgb2JqZWN0LiAqL1xudmFyIHJvb3QgPSBmcmVlR2xvYmFsIHx8IGZyZWVTZWxmIHx8IEZ1bmN0aW9uKCdyZXR1cm4gdGhpcycpKCk7XG5cbi8qKiBVc2VkIGZvciBidWlsdC1pbiBtZXRob2QgcmVmZXJlbmNlcy4gKi9cbnZhciBvYmplY3RQcm90byA9IE9iamVjdC5wcm90b3R5cGU7XG5cbi8qKlxuICogVXNlZCB0byByZXNvbHZlIHRoZVxuICogW2B0b1N0cmluZ1RhZ2BdKGh0dHA6Ly9lY21hLWludGVybmF0aW9uYWwub3JnL2VjbWEtMjYyLzcuMC8jc2VjLW9iamVjdC5wcm90b3R5cGUudG9zdHJpbmcpXG4gKiBvZiB2YWx1ZXMuXG4gKi9cbnZhciBvYmplY3RUb1N0cmluZyA9IG9iamVjdFByb3RvLnRvU3RyaW5nO1xuXG4vKiBCdWlsdC1pbiBtZXRob2QgcmVmZXJlbmNlcyBmb3IgdGhvc2Ugd2l0aCB0aGUgc2FtZSBuYW1lIGFzIG90aGVyIGBsb2Rhc2hgIG1ldGhvZHMuICovXG52YXIgbmF0aXZlTWF4ID0gTWF0aC5tYXgsXG4gICAgbmF0aXZlTWluID0gTWF0aC5taW47XG5cbi8qKlxuICogR2V0cyB0aGUgdGltZXN0YW1wIG9mIHRoZSBudW1iZXIgb2YgbWlsbGlzZWNvbmRzIHRoYXQgaGF2ZSBlbGFwc2VkIHNpbmNlXG4gKiB0aGUgVW5peCBlcG9jaCAoMSBKYW51YXJ5IDE5NzAgMDA6MDA6MDAgVVRDKS5cbiAqXG4gKiBAc3RhdGljXG4gKiBAbWVtYmVyT2YgX1xuICogQHNpbmNlIDIuNC4wXG4gKiBAY2F0ZWdvcnkgRGF0ZVxuICogQHJldHVybnMge251bWJlcn0gUmV0dXJucyB0aGUgdGltZXN0YW1wLlxuICogQGV4YW1wbGVcbiAqXG4gKiBfLmRlZmVyKGZ1bmN0aW9uKHN0YW1wKSB7XG4gKiAgIGNvbnNvbGUubG9nKF8ubm93KCkgLSBzdGFtcCk7XG4gKiB9LCBfLm5vdygpKTtcbiAqIC8vID0+IExvZ3MgdGhlIG51bWJlciBvZiBtaWxsaXNlY29uZHMgaXQgdG9vayBmb3IgdGhlIGRlZmVycmVkIGludm9jYXRpb24uXG4gKi9cbnZhciBub3cgPSBmdW5jdGlvbigpIHtcbiAgcmV0dXJuIHJvb3QuRGF0ZS5ub3coKTtcbn07XG5cbi8qKlxuICogQ3JlYXRlcyBhIGRlYm91bmNlZCBmdW5jdGlvbiB0aGF0IGRlbGF5cyBpbnZva2luZyBgZnVuY2AgdW50aWwgYWZ0ZXIgYHdhaXRgXG4gKiBtaWxsaXNlY29uZHMgaGF2ZSBlbGFwc2VkIHNpbmNlIHRoZSBsYXN0IHRpbWUgdGhlIGRlYm91bmNlZCBmdW5jdGlvbiB3YXNcbiAqIGludm9rZWQuIFRoZSBkZWJvdW5jZWQgZnVuY3Rpb24gY29tZXMgd2l0aCBhIGBjYW5jZWxgIG1ldGhvZCB0byBjYW5jZWxcbiAqIGRlbGF5ZWQgYGZ1bmNgIGludm9jYXRpb25zIGFuZCBhIGBmbHVzaGAgbWV0aG9kIHRvIGltbWVkaWF0ZWx5IGludm9rZSB0aGVtLlxuICogUHJvdmlkZSBgb3B0aW9uc2AgdG8gaW5kaWNhdGUgd2hldGhlciBgZnVuY2Agc2hvdWxkIGJlIGludm9rZWQgb24gdGhlXG4gKiBsZWFkaW5nIGFuZC9vciB0cmFpbGluZyBlZGdlIG9mIHRoZSBgd2FpdGAgdGltZW91dC4gVGhlIGBmdW5jYCBpcyBpbnZva2VkXG4gKiB3aXRoIHRoZSBsYXN0IGFyZ3VtZW50cyBwcm92aWRlZCB0byB0aGUgZGVib3VuY2VkIGZ1bmN0aW9uLiBTdWJzZXF1ZW50XG4gKiBjYWxscyB0byB0aGUgZGVib3VuY2VkIGZ1bmN0aW9uIHJldHVybiB0aGUgcmVzdWx0IG9mIHRoZSBsYXN0IGBmdW5jYFxuICogaW52b2NhdGlvbi5cbiAqXG4gKiAqKk5vdGU6KiogSWYgYGxlYWRpbmdgIGFuZCBgdHJhaWxpbmdgIG9wdGlvbnMgYXJlIGB0cnVlYCwgYGZ1bmNgIGlzXG4gKiBpbnZva2VkIG9uIHRoZSB0cmFpbGluZyBlZGdlIG9mIHRoZSB0aW1lb3V0IG9ubHkgaWYgdGhlIGRlYm91bmNlZCBmdW5jdGlvblxuICogaXMgaW52b2tlZCBtb3JlIHRoYW4gb25jZSBkdXJpbmcgdGhlIGB3YWl0YCB0aW1lb3V0LlxuICpcbiAqIElmIGB3YWl0YCBpcyBgMGAgYW5kIGBsZWFkaW5nYCBpcyBgZmFsc2VgLCBgZnVuY2AgaW52b2NhdGlvbiBpcyBkZWZlcnJlZFxuICogdW50aWwgdG8gdGhlIG5leHQgdGljaywgc2ltaWxhciB0byBgc2V0VGltZW91dGAgd2l0aCBhIHRpbWVvdXQgb2YgYDBgLlxuICpcbiAqIFNlZSBbRGF2aWQgQ29yYmFjaG8ncyBhcnRpY2xlXShodHRwczovL2Nzcy10cmlja3MuY29tL2RlYm91bmNpbmctdGhyb3R0bGluZy1leHBsYWluZWQtZXhhbXBsZXMvKVxuICogZm9yIGRldGFpbHMgb3ZlciB0aGUgZGlmZmVyZW5jZXMgYmV0d2VlbiBgXy5kZWJvdW5jZWAgYW5kIGBfLnRocm90dGxlYC5cbiAqXG4gKiBAc3RhdGljXG4gKiBAbWVtYmVyT2YgX1xuICogQHNpbmNlIDAuMS4wXG4gKiBAY2F0ZWdvcnkgRnVuY3Rpb25cbiAqIEBwYXJhbSB7RnVuY3Rpb259IGZ1bmMgVGhlIGZ1bmN0aW9uIHRvIGRlYm91bmNlLlxuICogQHBhcmFtIHtudW1iZXJ9IFt3YWl0PTBdIFRoZSBudW1iZXIgb2YgbWlsbGlzZWNvbmRzIHRvIGRlbGF5LlxuICogQHBhcmFtIHtPYmplY3R9IFtvcHRpb25zPXt9XSBUaGUgb3B0aW9ucyBvYmplY3QuXG4gKiBAcGFyYW0ge2Jvb2xlYW59IFtvcHRpb25zLmxlYWRpbmc9ZmFsc2VdXG4gKiAgU3BlY2lmeSBpbnZva2luZyBvbiB0aGUgbGVhZGluZyBlZGdlIG9mIHRoZSB0aW1lb3V0LlxuICogQHBhcmFtIHtudW1iZXJ9IFtvcHRpb25zLm1heFdhaXRdXG4gKiAgVGhlIG1heGltdW0gdGltZSBgZnVuY2AgaXMgYWxsb3dlZCB0byBiZSBkZWxheWVkIGJlZm9yZSBpdCdzIGludm9rZWQuXG4gKiBAcGFyYW0ge2Jvb2xlYW59IFtvcHRpb25zLnRyYWlsaW5nPXRydWVdXG4gKiAgU3BlY2lmeSBpbnZva2luZyBvbiB0aGUgdHJhaWxpbmcgZWRnZSBvZiB0aGUgdGltZW91dC5cbiAqIEByZXR1cm5zIHtGdW5jdGlvbn0gUmV0dXJucyB0aGUgbmV3IGRlYm91bmNlZCBmdW5jdGlvbi5cbiAqIEBleGFtcGxlXG4gKlxuICogLy8gQXZvaWQgY29zdGx5IGNhbGN1bGF0aW9ucyB3aGlsZSB0aGUgd2luZG93IHNpemUgaXMgaW4gZmx1eC5cbiAqIGpRdWVyeSh3aW5kb3cpLm9uKCdyZXNpemUnLCBfLmRlYm91bmNlKGNhbGN1bGF0ZUxheW91dCwgMTUwKSk7XG4gKlxuICogLy8gSW52b2tlIGBzZW5kTWFpbGAgd2hlbiBjbGlja2VkLCBkZWJvdW5jaW5nIHN1YnNlcXVlbnQgY2FsbHMuXG4gKiBqUXVlcnkoZWxlbWVudCkub24oJ2NsaWNrJywgXy5kZWJvdW5jZShzZW5kTWFpbCwgMzAwLCB7XG4gKiAgICdsZWFkaW5nJzogdHJ1ZSxcbiAqICAgJ3RyYWlsaW5nJzogZmFsc2VcbiAqIH0pKTtcbiAqXG4gKiAvLyBFbnN1cmUgYGJhdGNoTG9nYCBpcyBpbnZva2VkIG9uY2UgYWZ0ZXIgMSBzZWNvbmQgb2YgZGVib3VuY2VkIGNhbGxzLlxuICogdmFyIGRlYm91bmNlZCA9IF8uZGVib3VuY2UoYmF0Y2hMb2csIDI1MCwgeyAnbWF4V2FpdCc6IDEwMDAgfSk7XG4gKiB2YXIgc291cmNlID0gbmV3IEV2ZW50U291cmNlKCcvc3RyZWFtJyk7XG4gKiBqUXVlcnkoc291cmNlKS5vbignbWVzc2FnZScsIGRlYm91bmNlZCk7XG4gKlxuICogLy8gQ2FuY2VsIHRoZSB0cmFpbGluZyBkZWJvdW5jZWQgaW52b2NhdGlvbi5cbiAqIGpRdWVyeSh3aW5kb3cpLm9uKCdwb3BzdGF0ZScsIGRlYm91bmNlZC5jYW5jZWwpO1xuICovXG5mdW5jdGlvbiBkZWJvdW5jZShmdW5jLCB3YWl0LCBvcHRpb25zKSB7XG4gIHZhciBsYXN0QXJncyxcbiAgICAgIGxhc3RUaGlzLFxuICAgICAgbWF4V2FpdCxcbiAgICAgIHJlc3VsdCxcbiAgICAgIHRpbWVySWQsXG4gICAgICBsYXN0Q2FsbFRpbWUsXG4gICAgICBsYXN0SW52b2tlVGltZSA9IDAsXG4gICAgICBsZWFkaW5nID0gZmFsc2UsXG4gICAgICBtYXhpbmcgPSBmYWxzZSxcbiAgICAgIHRyYWlsaW5nID0gdHJ1ZTtcblxuICBpZiAodHlwZW9mIGZ1bmMgIT0gJ2Z1bmN0aW9uJykge1xuICAgIHRocm93IG5ldyBUeXBlRXJyb3IoRlVOQ19FUlJPUl9URVhUKTtcbiAgfVxuICB3YWl0ID0gdG9OdW1iZXIod2FpdCkgfHwgMDtcbiAgaWYgKGlzT2JqZWN0KG9wdGlvbnMpKSB7XG4gICAgbGVhZGluZyA9ICEhb3B0aW9ucy5sZWFkaW5nO1xuICAgIG1heGluZyA9ICdtYXhXYWl0JyBpbiBvcHRpb25zO1xuICAgIG1heFdhaXQgPSBtYXhpbmcgPyBuYXRpdmVNYXgodG9OdW1iZXIob3B0aW9ucy5tYXhXYWl0KSB8fCAwLCB3YWl0KSA6IG1heFdhaXQ7XG4gICAgdHJhaWxpbmcgPSAndHJhaWxpbmcnIGluIG9wdGlvbnMgPyAhIW9wdGlvbnMudHJhaWxpbmcgOiB0cmFpbGluZztcbiAgfVxuXG4gIGZ1bmN0aW9uIGludm9rZUZ1bmModGltZSkge1xuICAgIHZhciBhcmdzID0gbGFzdEFyZ3MsXG4gICAgICAgIHRoaXNBcmcgPSBsYXN0VGhpcztcblxuICAgIGxhc3RBcmdzID0gbGFzdFRoaXMgPSB1bmRlZmluZWQ7XG4gICAgbGFzdEludm9rZVRpbWUgPSB0aW1lO1xuICAgIHJlc3VsdCA9IGZ1bmMuYXBwbHkodGhpc0FyZywgYXJncyk7XG4gICAgcmV0dXJuIHJlc3VsdDtcbiAgfVxuXG4gIGZ1bmN0aW9uIGxlYWRpbmdFZGdlKHRpbWUpIHtcbiAgICAvLyBSZXNldCBhbnkgYG1heFdhaXRgIHRpbWVyLlxuICAgIGxhc3RJbnZva2VUaW1lID0gdGltZTtcbiAgICAvLyBTdGFydCB0aGUgdGltZXIgZm9yIHRoZSB0cmFpbGluZyBlZGdlLlxuICAgIHRpbWVySWQgPSBzZXRUaW1lb3V0KHRpbWVyRXhwaXJlZCwgd2FpdCk7XG4gICAgLy8gSW52b2tlIHRoZSBsZWFkaW5nIGVkZ2UuXG4gICAgcmV0dXJuIGxlYWRpbmcgPyBpbnZva2VGdW5jKHRpbWUpIDogcmVzdWx0O1xuICB9XG5cbiAgZnVuY3Rpb24gcmVtYWluaW5nV2FpdCh0aW1lKSB7XG4gICAgdmFyIHRpbWVTaW5jZUxhc3RDYWxsID0gdGltZSAtIGxhc3RDYWxsVGltZSxcbiAgICAgICAgdGltZVNpbmNlTGFzdEludm9rZSA9IHRpbWUgLSBsYXN0SW52b2tlVGltZSxcbiAgICAgICAgcmVzdWx0ID0gd2FpdCAtIHRpbWVTaW5jZUxhc3RDYWxsO1xuXG4gICAgcmV0dXJuIG1heGluZyA/IG5hdGl2ZU1pbihyZXN1bHQsIG1heFdhaXQgLSB0aW1lU2luY2VMYXN0SW52b2tlKSA6IHJlc3VsdDtcbiAgfVxuXG4gIGZ1bmN0aW9uIHNob3VsZEludm9rZSh0aW1lKSB7XG4gICAgdmFyIHRpbWVTaW5jZUxhc3RDYWxsID0gdGltZSAtIGxhc3RDYWxsVGltZSxcbiAgICAgICAgdGltZVNpbmNlTGFzdEludm9rZSA9IHRpbWUgLSBsYXN0SW52b2tlVGltZTtcblxuICAgIC8vIEVpdGhlciB0aGlzIGlzIHRoZSBmaXJzdCBjYWxsLCBhY3Rpdml0eSBoYXMgc3RvcHBlZCBhbmQgd2UncmUgYXQgdGhlXG4gICAgLy8gdHJhaWxpbmcgZWRnZSwgdGhlIHN5c3RlbSB0aW1lIGhhcyBnb25lIGJhY2t3YXJkcyBhbmQgd2UncmUgdHJlYXRpbmdcbiAgICAvLyBpdCBhcyB0aGUgdHJhaWxpbmcgZWRnZSwgb3Igd2UndmUgaGl0IHRoZSBgbWF4V2FpdGAgbGltaXQuXG4gICAgcmV0dXJuIChsYXN0Q2FsbFRpbWUgPT09IHVuZGVmaW5lZCB8fCAodGltZVNpbmNlTGFzdENhbGwgPj0gd2FpdCkgfHxcbiAgICAgICh0aW1lU2luY2VMYXN0Q2FsbCA8IDApIHx8IChtYXhpbmcgJiYgdGltZVNpbmNlTGFzdEludm9rZSA+PSBtYXhXYWl0KSk7XG4gIH1cblxuICBmdW5jdGlvbiB0aW1lckV4cGlyZWQoKSB7XG4gICAgdmFyIHRpbWUgPSBub3coKTtcbiAgICBpZiAoc2hvdWxkSW52b2tlKHRpbWUpKSB7XG4gICAgICByZXR1cm4gdHJhaWxpbmdFZGdlKHRpbWUpO1xuICAgIH1cbiAgICAvLyBSZXN0YXJ0IHRoZSB0aW1lci5cbiAgICB0aW1lcklkID0gc2V0VGltZW91dCh0aW1lckV4cGlyZWQsIHJlbWFpbmluZ1dhaXQodGltZSkpO1xuICB9XG5cbiAgZnVuY3Rpb24gdHJhaWxpbmdFZGdlKHRpbWUpIHtcbiAgICB0aW1lcklkID0gdW5kZWZpbmVkO1xuXG4gICAgLy8gT25seSBpbnZva2UgaWYgd2UgaGF2ZSBgbGFzdEFyZ3NgIHdoaWNoIG1lYW5zIGBmdW5jYCBoYXMgYmVlblxuICAgIC8vIGRlYm91bmNlZCBhdCBsZWFzdCBvbmNlLlxuICAgIGlmICh0cmFpbGluZyAmJiBsYXN0QXJncykge1xuICAgICAgcmV0dXJuIGludm9rZUZ1bmModGltZSk7XG4gICAgfVxuICAgIGxhc3RBcmdzID0gbGFzdFRoaXMgPSB1bmRlZmluZWQ7XG4gICAgcmV0dXJuIHJlc3VsdDtcbiAgfVxuXG4gIGZ1bmN0aW9uIGNhbmNlbCgpIHtcbiAgICBpZiAodGltZXJJZCAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICBjbGVhclRpbWVvdXQodGltZXJJZCk7XG4gICAgfVxuICAgIGxhc3RJbnZva2VUaW1lID0gMDtcbiAgICBsYXN0QXJncyA9IGxhc3RDYWxsVGltZSA9IGxhc3RUaGlzID0gdGltZXJJZCA9IHVuZGVmaW5lZDtcbiAgfVxuXG4gIGZ1bmN0aW9uIGZsdXNoKCkge1xuICAgIHJldHVybiB0aW1lcklkID09PSB1bmRlZmluZWQgPyByZXN1bHQgOiB0cmFpbGluZ0VkZ2Uobm93KCkpO1xuICB9XG5cbiAgZnVuY3Rpb24gZGVib3VuY2VkKCkge1xuICAgIHZhciB0aW1lID0gbm93KCksXG4gICAgICAgIGlzSW52b2tpbmcgPSBzaG91bGRJbnZva2UodGltZSk7XG5cbiAgICBsYXN0QXJncyA9IGFyZ3VtZW50cztcbiAgICBsYXN0VGhpcyA9IHRoaXM7XG4gICAgbGFzdENhbGxUaW1lID0gdGltZTtcblxuICAgIGlmIChpc0ludm9raW5nKSB7XG4gICAgICBpZiAodGltZXJJZCA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICAgIHJldHVybiBsZWFkaW5nRWRnZShsYXN0Q2FsbFRpbWUpO1xuICAgICAgfVxuICAgICAgaWYgKG1heGluZykge1xuICAgICAgICAvLyBIYW5kbGUgaW52b2NhdGlvbnMgaW4gYSB0aWdodCBsb29wLlxuICAgICAgICB0aW1lcklkID0gc2V0VGltZW91dCh0aW1lckV4cGlyZWQsIHdhaXQpO1xuICAgICAgICByZXR1cm4gaW52b2tlRnVuYyhsYXN0Q2FsbFRpbWUpO1xuICAgICAgfVxuICAgIH1cbiAgICBpZiAodGltZXJJZCA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICB0aW1lcklkID0gc2V0VGltZW91dCh0aW1lckV4cGlyZWQsIHdhaXQpO1xuICAgIH1cbiAgICByZXR1cm4gcmVzdWx0O1xuICB9XG4gIGRlYm91bmNlZC5jYW5jZWwgPSBjYW5jZWw7XG4gIGRlYm91bmNlZC5mbHVzaCA9IGZsdXNoO1xuICByZXR1cm4gZGVib3VuY2VkO1xufVxuXG4vKipcbiAqIENyZWF0ZXMgYSB0aHJvdHRsZWQgZnVuY3Rpb24gdGhhdCBvbmx5IGludm9rZXMgYGZ1bmNgIGF0IG1vc3Qgb25jZSBwZXJcbiAqIGV2ZXJ5IGB3YWl0YCBtaWxsaXNlY29uZHMuIFRoZSB0aHJvdHRsZWQgZnVuY3Rpb24gY29tZXMgd2l0aCBhIGBjYW5jZWxgXG4gKiBtZXRob2QgdG8gY2FuY2VsIGRlbGF5ZWQgYGZ1bmNgIGludm9jYXRpb25zIGFuZCBhIGBmbHVzaGAgbWV0aG9kIHRvXG4gKiBpbW1lZGlhdGVseSBpbnZva2UgdGhlbS4gUHJvdmlkZSBgb3B0aW9uc2AgdG8gaW5kaWNhdGUgd2hldGhlciBgZnVuY2BcbiAqIHNob3VsZCBiZSBpbnZva2VkIG9uIHRoZSBsZWFkaW5nIGFuZC9vciB0cmFpbGluZyBlZGdlIG9mIHRoZSBgd2FpdGBcbiAqIHRpbWVvdXQuIFRoZSBgZnVuY2AgaXMgaW52b2tlZCB3aXRoIHRoZSBsYXN0IGFyZ3VtZW50cyBwcm92aWRlZCB0byB0aGVcbiAqIHRocm90dGxlZCBmdW5jdGlvbi4gU3Vic2VxdWVudCBjYWxscyB0byB0aGUgdGhyb3R0bGVkIGZ1bmN0aW9uIHJldHVybiB0aGVcbiAqIHJlc3VsdCBvZiB0aGUgbGFzdCBgZnVuY2AgaW52b2NhdGlvbi5cbiAqXG4gKiAqKk5vdGU6KiogSWYgYGxlYWRpbmdgIGFuZCBgdHJhaWxpbmdgIG9wdGlvbnMgYXJlIGB0cnVlYCwgYGZ1bmNgIGlzXG4gKiBpbnZva2VkIG9uIHRoZSB0cmFpbGluZyBlZGdlIG9mIHRoZSB0aW1lb3V0IG9ubHkgaWYgdGhlIHRocm90dGxlZCBmdW5jdGlvblxuICogaXMgaW52b2tlZCBtb3JlIHRoYW4gb25jZSBkdXJpbmcgdGhlIGB3YWl0YCB0aW1lb3V0LlxuICpcbiAqIElmIGB3YWl0YCBpcyBgMGAgYW5kIGBsZWFkaW5nYCBpcyBgZmFsc2VgLCBgZnVuY2AgaW52b2NhdGlvbiBpcyBkZWZlcnJlZFxuICogdW50aWwgdG8gdGhlIG5leHQgdGljaywgc2ltaWxhciB0byBgc2V0VGltZW91dGAgd2l0aCBhIHRpbWVvdXQgb2YgYDBgLlxuICpcbiAqIFNlZSBbRGF2aWQgQ29yYmFjaG8ncyBhcnRpY2xlXShodHRwczovL2Nzcy10cmlja3MuY29tL2RlYm91bmNpbmctdGhyb3R0bGluZy1leHBsYWluZWQtZXhhbXBsZXMvKVxuICogZm9yIGRldGFpbHMgb3ZlciB0aGUgZGlmZmVyZW5jZXMgYmV0d2VlbiBgXy50aHJvdHRsZWAgYW5kIGBfLmRlYm91bmNlYC5cbiAqXG4gKiBAc3RhdGljXG4gKiBAbWVtYmVyT2YgX1xuICogQHNpbmNlIDAuMS4wXG4gKiBAY2F0ZWdvcnkgRnVuY3Rpb25cbiAqIEBwYXJhbSB7RnVuY3Rpb259IGZ1bmMgVGhlIGZ1bmN0aW9uIHRvIHRocm90dGxlLlxuICogQHBhcmFtIHtudW1iZXJ9IFt3YWl0PTBdIFRoZSBudW1iZXIgb2YgbWlsbGlzZWNvbmRzIHRvIHRocm90dGxlIGludm9jYXRpb25zIHRvLlxuICogQHBhcmFtIHtPYmplY3R9IFtvcHRpb25zPXt9XSBUaGUgb3B0aW9ucyBvYmplY3QuXG4gKiBAcGFyYW0ge2Jvb2xlYW59IFtvcHRpb25zLmxlYWRpbmc9dHJ1ZV1cbiAqICBTcGVjaWZ5IGludm9raW5nIG9uIHRoZSBsZWFkaW5nIGVkZ2Ugb2YgdGhlIHRpbWVvdXQuXG4gKiBAcGFyYW0ge2Jvb2xlYW59IFtvcHRpb25zLnRyYWlsaW5nPXRydWVdXG4gKiAgU3BlY2lmeSBpbnZva2luZyBvbiB0aGUgdHJhaWxpbmcgZWRnZSBvZiB0aGUgdGltZW91dC5cbiAqIEByZXR1cm5zIHtGdW5jdGlvbn0gUmV0dXJucyB0aGUgbmV3IHRocm90dGxlZCBmdW5jdGlvbi5cbiAqIEBleGFtcGxlXG4gKlxuICogLy8gQXZvaWQgZXhjZXNzaXZlbHkgdXBkYXRpbmcgdGhlIHBvc2l0aW9uIHdoaWxlIHNjcm9sbGluZy5cbiAqIGpRdWVyeSh3aW5kb3cpLm9uKCdzY3JvbGwnLCBfLnRocm90dGxlKHVwZGF0ZVBvc2l0aW9uLCAxMDApKTtcbiAqXG4gKiAvLyBJbnZva2UgYHJlbmV3VG9rZW5gIHdoZW4gdGhlIGNsaWNrIGV2ZW50IGlzIGZpcmVkLCBidXQgbm90IG1vcmUgdGhhbiBvbmNlIGV2ZXJ5IDUgbWludXRlcy5cbiAqIHZhciB0aHJvdHRsZWQgPSBfLnRocm90dGxlKHJlbmV3VG9rZW4sIDMwMDAwMCwgeyAndHJhaWxpbmcnOiBmYWxzZSB9KTtcbiAqIGpRdWVyeShlbGVtZW50KS5vbignY2xpY2snLCB0aHJvdHRsZWQpO1xuICpcbiAqIC8vIENhbmNlbCB0aGUgdHJhaWxpbmcgdGhyb3R0bGVkIGludm9jYXRpb24uXG4gKiBqUXVlcnkod2luZG93KS5vbigncG9wc3RhdGUnLCB0aHJvdHRsZWQuY2FuY2VsKTtcbiAqL1xuZnVuY3Rpb24gdGhyb3R0bGUoZnVuYywgd2FpdCwgb3B0aW9ucykge1xuICB2YXIgbGVhZGluZyA9IHRydWUsXG4gICAgICB0cmFpbGluZyA9IHRydWU7XG5cbiAgaWYgKHR5cGVvZiBmdW5jICE9ICdmdW5jdGlvbicpIHtcbiAgICB0aHJvdyBuZXcgVHlwZUVycm9yKEZVTkNfRVJST1JfVEVYVCk7XG4gIH1cbiAgaWYgKGlzT2JqZWN0KG9wdGlvbnMpKSB7XG4gICAgbGVhZGluZyA9ICdsZWFkaW5nJyBpbiBvcHRpb25zID8gISFvcHRpb25zLmxlYWRpbmcgOiBsZWFkaW5nO1xuICAgIHRyYWlsaW5nID0gJ3RyYWlsaW5nJyBpbiBvcHRpb25zID8gISFvcHRpb25zLnRyYWlsaW5nIDogdHJhaWxpbmc7XG4gIH1cbiAgcmV0dXJuIGRlYm91bmNlKGZ1bmMsIHdhaXQsIHtcbiAgICAnbGVhZGluZyc6IGxlYWRpbmcsXG4gICAgJ21heFdhaXQnOiB3YWl0LFxuICAgICd0cmFpbGluZyc6IHRyYWlsaW5nXG4gIH0pO1xufVxuXG4vKipcbiAqIENoZWNrcyBpZiBgdmFsdWVgIGlzIHRoZVxuICogW2xhbmd1YWdlIHR5cGVdKGh0dHA6Ly93d3cuZWNtYS1pbnRlcm5hdGlvbmFsLm9yZy9lY21hLTI2Mi83LjAvI3NlYy1lY21hc2NyaXB0LWxhbmd1YWdlLXR5cGVzKVxuICogb2YgYE9iamVjdGAuIChlLmcuIGFycmF5cywgZnVuY3Rpb25zLCBvYmplY3RzLCByZWdleGVzLCBgbmV3IE51bWJlcigwKWAsIGFuZCBgbmV3IFN0cmluZygnJylgKVxuICpcbiAqIEBzdGF0aWNcbiAqIEBtZW1iZXJPZiBfXG4gKiBAc2luY2UgMC4xLjBcbiAqIEBjYXRlZ29yeSBMYW5nXG4gKiBAcGFyYW0geyp9IHZhbHVlIFRoZSB2YWx1ZSB0byBjaGVjay5cbiAqIEByZXR1cm5zIHtib29sZWFufSBSZXR1cm5zIGB0cnVlYCBpZiBgdmFsdWVgIGlzIGFuIG9iamVjdCwgZWxzZSBgZmFsc2VgLlxuICogQGV4YW1wbGVcbiAqXG4gKiBfLmlzT2JqZWN0KHt9KTtcbiAqIC8vID0+IHRydWVcbiAqXG4gKiBfLmlzT2JqZWN0KFsxLCAyLCAzXSk7XG4gKiAvLyA9PiB0cnVlXG4gKlxuICogXy5pc09iamVjdChfLm5vb3ApO1xuICogLy8gPT4gdHJ1ZVxuICpcbiAqIF8uaXNPYmplY3QobnVsbCk7XG4gKiAvLyA9PiBmYWxzZVxuICovXG5mdW5jdGlvbiBpc09iamVjdCh2YWx1ZSkge1xuICB2YXIgdHlwZSA9IHR5cGVvZiB2YWx1ZTtcbiAgcmV0dXJuICEhdmFsdWUgJiYgKHR5cGUgPT0gJ29iamVjdCcgfHwgdHlwZSA9PSAnZnVuY3Rpb24nKTtcbn1cblxuLyoqXG4gKiBDaGVja3MgaWYgYHZhbHVlYCBpcyBvYmplY3QtbGlrZS4gQSB2YWx1ZSBpcyBvYmplY3QtbGlrZSBpZiBpdCdzIG5vdCBgbnVsbGBcbiAqIGFuZCBoYXMgYSBgdHlwZW9mYCByZXN1bHQgb2YgXCJvYmplY3RcIi5cbiAqXG4gKiBAc3RhdGljXG4gKiBAbWVtYmVyT2YgX1xuICogQHNpbmNlIDQuMC4wXG4gKiBAY2F0ZWdvcnkgTGFuZ1xuICogQHBhcmFtIHsqfSB2YWx1ZSBUaGUgdmFsdWUgdG8gY2hlY2suXG4gKiBAcmV0dXJucyB7Ym9vbGVhbn0gUmV0dXJucyBgdHJ1ZWAgaWYgYHZhbHVlYCBpcyBvYmplY3QtbGlrZSwgZWxzZSBgZmFsc2VgLlxuICogQGV4YW1wbGVcbiAqXG4gKiBfLmlzT2JqZWN0TGlrZSh7fSk7XG4gKiAvLyA9PiB0cnVlXG4gKlxuICogXy5pc09iamVjdExpa2UoWzEsIDIsIDNdKTtcbiAqIC8vID0+IHRydWVcbiAqXG4gKiBfLmlzT2JqZWN0TGlrZShfLm5vb3ApO1xuICogLy8gPT4gZmFsc2VcbiAqXG4gKiBfLmlzT2JqZWN0TGlrZShudWxsKTtcbiAqIC8vID0+IGZhbHNlXG4gKi9cbmZ1bmN0aW9uIGlzT2JqZWN0TGlrZSh2YWx1ZSkge1xuICByZXR1cm4gISF2YWx1ZSAmJiB0eXBlb2YgdmFsdWUgPT0gJ29iamVjdCc7XG59XG5cbi8qKlxuICogQ2hlY2tzIGlmIGB2YWx1ZWAgaXMgY2xhc3NpZmllZCBhcyBhIGBTeW1ib2xgIHByaW1pdGl2ZSBvciBvYmplY3QuXG4gKlxuICogQHN0YXRpY1xuICogQG1lbWJlck9mIF9cbiAqIEBzaW5jZSA0LjAuMFxuICogQGNhdGVnb3J5IExhbmdcbiAqIEBwYXJhbSB7Kn0gdmFsdWUgVGhlIHZhbHVlIHRvIGNoZWNrLlxuICogQHJldHVybnMge2Jvb2xlYW59IFJldHVybnMgYHRydWVgIGlmIGB2YWx1ZWAgaXMgYSBzeW1ib2wsIGVsc2UgYGZhbHNlYC5cbiAqIEBleGFtcGxlXG4gKlxuICogXy5pc1N5bWJvbChTeW1ib2wuaXRlcmF0b3IpO1xuICogLy8gPT4gdHJ1ZVxuICpcbiAqIF8uaXNTeW1ib2woJ2FiYycpO1xuICogLy8gPT4gZmFsc2VcbiAqL1xuZnVuY3Rpb24gaXNTeW1ib2wodmFsdWUpIHtcbiAgcmV0dXJuIHR5cGVvZiB2YWx1ZSA9PSAnc3ltYm9sJyB8fFxuICAgIChpc09iamVjdExpa2UodmFsdWUpICYmIG9iamVjdFRvU3RyaW5nLmNhbGwodmFsdWUpID09IHN5bWJvbFRhZyk7XG59XG5cbi8qKlxuICogQ29udmVydHMgYHZhbHVlYCB0byBhIG51bWJlci5cbiAqXG4gKiBAc3RhdGljXG4gKiBAbWVtYmVyT2YgX1xuICogQHNpbmNlIDQuMC4wXG4gKiBAY2F0ZWdvcnkgTGFuZ1xuICogQHBhcmFtIHsqfSB2YWx1ZSBUaGUgdmFsdWUgdG8gcHJvY2Vzcy5cbiAqIEByZXR1cm5zIHtudW1iZXJ9IFJldHVybnMgdGhlIG51bWJlci5cbiAqIEBleGFtcGxlXG4gKlxuICogXy50b051bWJlcigzLjIpO1xuICogLy8gPT4gMy4yXG4gKlxuICogXy50b051bWJlcihOdW1iZXIuTUlOX1ZBTFVFKTtcbiAqIC8vID0+IDVlLTMyNFxuICpcbiAqIF8udG9OdW1iZXIoSW5maW5pdHkpO1xuICogLy8gPT4gSW5maW5pdHlcbiAqXG4gKiBfLnRvTnVtYmVyKCczLjInKTtcbiAqIC8vID0+IDMuMlxuICovXG5mdW5jdGlvbiB0b051bWJlcih2YWx1ZSkge1xuICBpZiAodHlwZW9mIHZhbHVlID09ICdudW1iZXInKSB7XG4gICAgcmV0dXJuIHZhbHVlO1xuICB9XG4gIGlmIChpc1N5bWJvbCh2YWx1ZSkpIHtcbiAgICByZXR1cm4gTkFOO1xuICB9XG4gIGlmIChpc09iamVjdCh2YWx1ZSkpIHtcbiAgICB2YXIgb3RoZXIgPSB0eXBlb2YgdmFsdWUudmFsdWVPZiA9PSAnZnVuY3Rpb24nID8gdmFsdWUudmFsdWVPZigpIDogdmFsdWU7XG4gICAgdmFsdWUgPSBpc09iamVjdChvdGhlcikgPyAob3RoZXIgKyAnJykgOiBvdGhlcjtcbiAgfVxuICBpZiAodHlwZW9mIHZhbHVlICE9ICdzdHJpbmcnKSB7XG4gICAgcmV0dXJuIHZhbHVlID09PSAwID8gdmFsdWUgOiArdmFsdWU7XG4gIH1cbiAgdmFsdWUgPSB2YWx1ZS5yZXBsYWNlKHJlVHJpbSwgJycpO1xuICB2YXIgaXNCaW5hcnkgPSByZUlzQmluYXJ5LnRlc3QodmFsdWUpO1xuICByZXR1cm4gKGlzQmluYXJ5IHx8IHJlSXNPY3RhbC50ZXN0KHZhbHVlKSlcbiAgICA/IGZyZWVQYXJzZUludCh2YWx1ZS5zbGljZSgyKSwgaXNCaW5hcnkgPyAyIDogOClcbiAgICA6IChyZUlzQmFkSGV4LnRlc3QodmFsdWUpID8gTkFOIDogK3ZhbHVlKTtcbn1cblxubW9kdWxlLmV4cG9ydHMgPSB0aHJvdHRsZTtcbiIsInZhciBnO1xuXG4vLyBUaGlzIHdvcmtzIGluIG5vbi1zdHJpY3QgbW9kZVxuZyA9IChmdW5jdGlvbigpIHtcblx0cmV0dXJuIHRoaXM7XG59KSgpO1xuXG50cnkge1xuXHQvLyBUaGlzIHdvcmtzIGlmIGV2YWwgaXMgYWxsb3dlZCAoc2VlIENTUClcblx0ZyA9IGcgfHwgbmV3IEZ1bmN0aW9uKFwicmV0dXJuIHRoaXNcIikoKTtcbn0gY2F0Y2ggKGUpIHtcblx0Ly8gVGhpcyB3b3JrcyBpZiB0aGUgd2luZG93IHJlZmVyZW5jZSBpcyBhdmFpbGFibGVcblx0aWYgKHR5cGVvZiB3aW5kb3cgPT09IFwib2JqZWN0XCIpIGcgPSB3aW5kb3c7XG59XG5cbi8vIGcgY2FuIHN0aWxsIGJlIHVuZGVmaW5lZCwgYnV0IG5vdGhpbmcgdG8gZG8gYWJvdXQgaXQuLi5cbi8vIFdlIHJldHVybiB1bmRlZmluZWQsIGluc3RlYWQgb2Ygbm90aGluZyBoZXJlLCBzbyBpdCdzXG4vLyBlYXNpZXIgdG8gaGFuZGxlIHRoaXMgY2FzZS4gaWYoIWdsb2JhbCkgeyAuLi59XG5cbm1vZHVsZS5leHBvcnRzID0gZztcbiJdLCJzb3VyY2VSb290IjoiIn0= \ No newline at end of file diff --git a/extensions/markdown-language-features/media/markdown.css b/extensions/markdown-language-features/media/markdown.css index ebf3b5c35fb9..d4efc854d404 100644 --- a/extensions/markdown-language-features/media/markdown.css +++ b/extensions/markdown-language-features/media/markdown.css @@ -129,15 +129,6 @@ h1, h2, h3 { font-weight: normal; } -h1 code, -h2 code, -h3 code, -h4 code, -h5 code, -h6 code { - line-height: auto; -} - table { border-collapse: collapse; } diff --git a/extensions/markdown-language-features/media/pre.js b/extensions/markdown-language-features/media/pre.js index 2d0f917feb85..bddc3b86ac57 100644 --- a/extensions/markdown-language-features/media/pre.js +++ b/extensions/markdown-language-features/media/pre.js @@ -1,2 +1,2 @@ -!function(e){var t={};function s(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,s),o.l=!0,o.exports}s.m=e,s.c=t,s.d=function(e,t,n){s.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:n})},s.r=function(e){Object.defineProperty(e,"__esModule",{value:!0})},s.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return s.d(t,"a",t),t},s.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},s.p="",s(s.s=5)}([function(e,t,s){"use strict";Object.defineProperty(t,"__esModule",{value:!0});let n=void 0;function o(e){const t=document.getElementById("vscode-markdown-preview-data");if(t){const s=t.getAttribute(e);if(s)return JSON.parse(s)}throw new Error(`Could not load data for ${e}`)}t.getData=o,t.getSettings=function(){if(n)return n;if(n=o("data-settings"))return n;throw new Error("Could not load settings")}},,function(e,t,s){"use strict";Object.defineProperty(t,"__esModule",{value:!0});t.StyleLoadingMonitor=class{constructor(){this.unloadedStyles=[],this.finishedLoading=!1;const e=e=>{const t=e.target.dataset.source;this.unloadedStyles.push(t)};window.addEventListener("DOMContentLoaded",()=>{for(const t of document.getElementsByClassName("code-user-style"))t.dataset.source&&(t.onerror=e)}),window.addEventListener("load",()=>{this.unloadedStyles.length&&(this.finishedLoading=!0,this.poster&&this.poster.postMessage("previewStyleLoadError",{unloadedStyles:this.unloadedStyles}))})}setPoster(e){this.poster=e,this.finishedLoading&&e.postMessage("previewStyleLoadError",{unloadedStyles:this.unloadedStyles})}}},function(e,t,s){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.getStrings=function(){const e=document.getElementById("vscode-markdown-preview-data");if(e){const t=e.getAttribute("data-strings");if(t)return JSON.parse(t)}throw new Error("Could not load strings")}},function(e,t,s){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const n=s(0),o=s(3);t.CspAlerter=class{constructor(){this.didShow=!1,this.didHaveCspWarning=!1,document.addEventListener("securitypolicyviolation",()=>{this.onCspWarning()}),window.addEventListener("message",e=>{e&&e.data&&"vscode-did-block-svg"===e.data.name&&this.onCspWarning()})}setPoster(e){this.messaging=e,this.didHaveCspWarning&&this.showCspWarning()}onCspWarning(){this.didHaveCspWarning=!0,this.showCspWarning()}showCspWarning(){const e=o.getStrings(),t=n.getSettings();if(this.didShow||t.disableSecurityWarnings||!this.messaging)return;this.didShow=!0;const s=document.createElement("a");s.innerText=e.cspAlertMessageText,s.setAttribute("id","code-csp-warning"),s.setAttribute("title",e.cspAlertMessageTitle),s.setAttribute("role","button"),s.setAttribute("aria-label",e.cspAlertMessageLabel),s.onclick=(()=>{this.messaging.postMessage("showPreviewSecuritySelector",{source:t.source})}),document.body.appendChild(s)}}},function(e,t,s){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const n=s(4),o=s(2);window.cspAlerter=new n.CspAlerter,window.styleLoadingMonitor=new o.StyleLoadingMonitor}]); -//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vd2VicGFjay9ib290c3RyYXAiLCJ3ZWJwYWNrOi8vLy4vcHJldmlldy1zcmMvc2V0dGluZ3MudHMiLCJ3ZWJwYWNrOi8vLy4vcHJldmlldy1zcmMvbG9hZGluZy50cyIsIndlYnBhY2s6Ly8vLi9wcmV2aWV3LXNyYy9zdHJpbmdzLnRzIiwid2VicGFjazovLy8uL3ByZXZpZXctc3JjL2NzcC50cyIsIndlYnBhY2s6Ly8vLi9wcmV2aWV3LXNyYy9wcmUudHMiXSwibmFtZXMiOlsiaW5zdGFsbGVkTW9kdWxlcyIsIl9fd2VicGFja19yZXF1aXJlX18iLCJtb2R1bGVJZCIsImV4cG9ydHMiLCJtb2R1bGUiLCJpIiwibCIsIm1vZHVsZXMiLCJjYWxsIiwibSIsImMiLCJkIiwibmFtZSIsImdldHRlciIsIm8iLCJPYmplY3QiLCJkZWZpbmVQcm9wZXJ0eSIsImNvbmZpZ3VyYWJsZSIsImVudW1lcmFibGUiLCJnZXQiLCJyIiwidmFsdWUiLCJuIiwiX19lc01vZHVsZSIsIm9iamVjdCIsInByb3BlcnR5IiwicHJvdG90eXBlIiwiaGFzT3duUHJvcGVydHkiLCJwIiwicyIsImNhY2hlZFNldHRpbmdzIiwidW5kZWZpbmVkIiwiZ2V0RGF0YSIsImtleSIsImVsZW1lbnQiLCJkb2N1bWVudCIsImdldEVsZW1lbnRCeUlkIiwiZGF0YSIsImdldEF0dHJpYnV0ZSIsIkpTT04iLCJwYXJzZSIsIkVycm9yIiwiZ2V0U2V0dGluZ3MiLCJTdHlsZUxvYWRpbmdNb25pdG9yIiwiW29iamVjdCBPYmplY3RdIiwidGhpcyIsInVubG9hZGVkU3R5bGVzIiwiZmluaXNoZWRMb2FkaW5nIiwib25TdHlsZUxvYWRFcnJvciIsImV2ZW50Iiwic291cmNlIiwidGFyZ2V0IiwiZGF0YXNldCIsInB1c2giLCJ3aW5kb3ciLCJhZGRFdmVudExpc3RlbmVyIiwibGluayIsImdldEVsZW1lbnRzQnlDbGFzc05hbWUiLCJvbmVycm9yIiwibGVuZ3RoIiwicG9zdGVyIiwicG9zdE1lc3NhZ2UiLCJnZXRTdHJpbmdzIiwic3RvcmUiLCJzZXR0aW5nc18xIiwic3RyaW5nc18xIiwiQ3NwQWxlcnRlciIsImRpZFNob3ciLCJkaWRIYXZlQ3NwV2FybmluZyIsIm9uQ3NwV2FybmluZyIsIm1lc3NhZ2luZyIsInNob3dDc3BXYXJuaW5nIiwic3RyaW5ncyIsInNldHRpbmdzIiwiZGlzYWJsZVNlY3VyaXR5V2FybmluZ3MiLCJub3RpZmljYXRpb24iLCJjcmVhdGVFbGVtZW50IiwiaW5uZXJUZXh0IiwiY3NwQWxlcnRNZXNzYWdlVGV4dCIsInNldEF0dHJpYnV0ZSIsImNzcEFsZXJ0TWVzc2FnZVRpdGxlIiwiY3NwQWxlcnRNZXNzYWdlTGFiZWwiLCJvbmNsaWNrIiwiYm9keSIsImFwcGVuZENoaWxkIiwiY3NwXzEiLCJsb2FkaW5nXzEiLCJjc3BBbGVydGVyIiwic3R5bGVMb2FkaW5nTW9uaXRvciJdLCJtYXBwaW5ncyI6ImFBQ0EsSUFBQUEsS0FHQSxTQUFBQyxFQUFBQyxHQUdBLEdBQUFGLEVBQUFFLEdBQ0EsT0FBQUYsRUFBQUUsR0FBQUMsUUFHQSxJQUFBQyxFQUFBSixFQUFBRSxJQUNBRyxFQUFBSCxFQUNBSSxHQUFBLEVBQ0FILFlBVUEsT0FOQUksRUFBQUwsR0FBQU0sS0FBQUosRUFBQUQsUUFBQUMsSUFBQUQsUUFBQUYsR0FHQUcsRUFBQUUsR0FBQSxFQUdBRixFQUFBRCxRQUtBRixFQUFBUSxFQUFBRixFQUdBTixFQUFBUyxFQUFBVixFQUdBQyxFQUFBVSxFQUFBLFNBQUFSLEVBQUFTLEVBQUFDLEdBQ0FaLEVBQUFhLEVBQUFYLEVBQUFTLElBQ0FHLE9BQUFDLGVBQUFiLEVBQUFTLEdBQ0FLLGNBQUEsRUFDQUMsWUFBQSxFQUNBQyxJQUFBTixLQU1BWixFQUFBbUIsRUFBQSxTQUFBakIsR0FDQVksT0FBQUMsZUFBQWIsRUFBQSxjQUFpRGtCLE9BQUEsS0FJakRwQixFQUFBcUIsRUFBQSxTQUFBbEIsR0FDQSxJQUFBUyxFQUFBVCxLQUFBbUIsV0FDQSxXQUEyQixPQUFBbkIsRUFBQSxTQUMzQixXQUFpQyxPQUFBQSxHQUVqQyxPQURBSCxFQUFBVSxFQUFBRSxFQUFBLElBQUFBLEdBQ0FBLEdBSUFaLEVBQUFhLEVBQUEsU0FBQVUsRUFBQUMsR0FBc0QsT0FBQVYsT0FBQVcsVUFBQUMsZUFBQW5CLEtBQUFnQixFQUFBQyxJQUd0RHhCLEVBQUEyQixFQUFBLEdBSUEzQixJQUFBNEIsRUFBQSxrQ0M5REFkLE9BQUFDLGVBQUFiLEVBQUEsY0FBOENrQixPQUFBLElBQzlDLElBQUFTLE9BQUFDLEVBQ0EsU0FBQUMsRUFBQUMsR0FDQSxNQUFBQyxFQUFBQyxTQUFBQyxlQUFBLGdDQUNBLEdBQUFGLEVBQUEsQ0FDQSxNQUFBRyxFQUFBSCxFQUFBSSxhQUFBTCxHQUNBLEdBQUFJLEVBQ0EsT0FBQUUsS0FBQUMsTUFBQUgsR0FHQSxVQUFBSSxpQ0FBK0NSLEtBRS9DOUIsRUFBQTZCLFVBV0E3QixFQUFBdUMsWUFWQSxXQUNBLEdBQUFaLEVBQ0EsT0FBQUEsRUFHQSxHQURBQSxFQUFBRSxFQUFBLGlCQUVBLE9BQUFGLEVBRUEsVUFBQVcsTUFBQSwyREN6QkExQixPQUFBQyxlQUFBYixFQUFBLGNBQThDa0IsT0FBQSxJQWlDOUNsQixFQUFBd0MsMEJBL0JBQyxjQUNBQyxLQUFBQyxrQkFDQUQsS0FBQUUsaUJBQUEsRUFDQSxNQUFBQyxFQUFBQyxJQUNBLE1BQUFDLEVBQUFELEVBQUFFLE9BQUFDLFFBQUFGLE9BQ0FMLEtBQUFDLGVBQUFPLEtBQUFILElBRUFJLE9BQUFDLGlCQUFBLHdCQUNBLFVBQUFDLEtBQUFyQixTQUFBc0IsdUJBQUEsbUJBQ0FELEVBQUFKLFFBQUFGLFNBQ0FNLEVBQUFFLFFBQUFWLEtBSUFNLE9BQUFDLGlCQUFBLFlBQ0FWLEtBQUFDLGVBQUFhLFNBR0FkLEtBQUFFLGlCQUFBLEVBQ0FGLEtBQUFlLFFBQ0FmLEtBQUFlLE9BQUFDLFlBQUEseUJBQWtFZixlQUFBRCxLQUFBQyxvQkFJbEVGLFVBQUFnQixHQUNBZixLQUFBZSxTQUNBZixLQUFBRSxpQkFDQWEsRUFBQUMsWUFBQSx5QkFBeURmLGVBQUFELEtBQUFDLGlEQ3pCekQvQixPQUFBQyxlQUFBYixFQUFBLGNBQThDa0IsT0FBQSxJQVc5Q2xCLEVBQUEyRCxXQVZBLFdBQ0EsTUFBQUMsRUFBQTVCLFNBQUFDLGVBQUEsZ0NBQ0EsR0FBQTJCLEVBQUEsQ0FDQSxNQUFBMUIsRUFBQTBCLEVBQUF6QixhQUFBLGdCQUNBLEdBQUFELEVBQ0EsT0FBQUUsS0FBQUMsTUFBQUgsR0FHQSxVQUFBSSxNQUFBLHlEQ1RBMUIsT0FBQUMsZUFBQWIsRUFBQSxjQUE4Q2tCLE9BQUEsSUFDOUMsTUFBQTJDLEVBQUEvRCxFQUFBLEdBQ0FnRSxFQUFBaEUsRUFBQSxHQThDQUUsRUFBQStELGlCQXpDQXRCLGNBQ0FDLEtBQUFzQixTQUFBLEVBQ0F0QixLQUFBdUIsbUJBQUEsRUFDQWpDLFNBQUFvQixpQkFBQSwrQkFDQVYsS0FBQXdCLGlCQUVBZixPQUFBQyxpQkFBQSxVQUFBTixJQUNBQSxLQUFBWixNQUFBLHlCQUFBWSxFQUFBWixLQUFBekIsTUFDQWlDLEtBQUF3QixpQkFJQXpCLFVBQUFnQixHQUNBZixLQUFBeUIsVUFBQVYsRUFDQWYsS0FBQXVCLG1CQUNBdkIsS0FBQTBCLGlCQUdBM0IsZUFDQUMsS0FBQXVCLG1CQUFBLEVBQ0F2QixLQUFBMEIsaUJBRUEzQixpQkFDQSxNQUFBNEIsRUFBQVAsRUFBQUgsYUFDQVcsRUFBQVQsRUFBQXRCLGNBQ0EsR0FBQUcsS0FBQXNCLFNBQUFNLEVBQUFDLDBCQUFBN0IsS0FBQXlCLFVBQ0EsT0FFQXpCLEtBQUFzQixTQUFBLEVBQ0EsTUFBQVEsRUFBQXhDLFNBQUF5QyxjQUFBLEtBQ0FELEVBQUFFLFVBQUFMLEVBQUFNLG9CQUNBSCxFQUFBSSxhQUFBLHlCQUNBSixFQUFBSSxhQUFBLFFBQUFQLEVBQUFRLHNCQUNBTCxFQUFBSSxhQUFBLGlCQUNBSixFQUFBSSxhQUFBLGFBQUFQLEVBQUFTLHNCQUNBTixFQUFBTyxRQUFBLE1BQ0FyQyxLQUFBeUIsVUFBQVQsWUFBQSwrQkFBdUVYLE9BQUF1QixFQUFBdkIsV0FFdkVmLFNBQUFnRCxLQUFBQyxZQUFBVCxtQ0M3Q0E1RCxPQUFBQyxlQUFBYixFQUFBLGNBQThDa0IsT0FBQSxJQUM5QyxNQUFBZ0UsRUFBQXBGLEVBQUEsR0FDQXFGLEVBQUFyRixFQUFBLEdBQ0FxRCxPQUFBaUMsV0FBQSxJQUFBRixFQUFBbkIsV0FDQVosT0FBQWtDLG9CQUFBLElBQUFGLEVBQUEzQyIsImZpbGUiOiJwcmUuanMiLCJzb3VyY2VzQ29udGVudCI6WyIgXHQvLyBUaGUgbW9kdWxlIGNhY2hlXG4gXHR2YXIgaW5zdGFsbGVkTW9kdWxlcyA9IHt9O1xuXG4gXHQvLyBUaGUgcmVxdWlyZSBmdW5jdGlvblxuIFx0ZnVuY3Rpb24gX193ZWJwYWNrX3JlcXVpcmVfXyhtb2R1bGVJZCkge1xuXG4gXHRcdC8vIENoZWNrIGlmIG1vZHVsZSBpcyBpbiBjYWNoZVxuIFx0XHRpZihpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXSkge1xuIFx0XHRcdHJldHVybiBpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXS5leHBvcnRzO1xuIFx0XHR9XG4gXHRcdC8vIENyZWF0ZSBhIG5ldyBtb2R1bGUgKGFuZCBwdXQgaXQgaW50byB0aGUgY2FjaGUpXG4gXHRcdHZhciBtb2R1bGUgPSBpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXSA9IHtcbiBcdFx0XHRpOiBtb2R1bGVJZCxcbiBcdFx0XHRsOiBmYWxzZSxcbiBcdFx0XHRleHBvcnRzOiB7fVxuIFx0XHR9O1xuXG4gXHRcdC8vIEV4ZWN1dGUgdGhlIG1vZHVsZSBmdW5jdGlvblxuIFx0XHRtb2R1bGVzW21vZHVsZUlkXS5jYWxsKG1vZHVsZS5leHBvcnRzLCBtb2R1bGUsIG1vZHVsZS5leHBvcnRzLCBfX3dlYnBhY2tfcmVxdWlyZV9fKTtcblxuIFx0XHQvLyBGbGFnIHRoZSBtb2R1bGUgYXMgbG9hZGVkXG4gXHRcdG1vZHVsZS5sID0gdHJ1ZTtcblxuIFx0XHQvLyBSZXR1cm4gdGhlIGV4cG9ydHMgb2YgdGhlIG1vZHVsZVxuIFx0XHRyZXR1cm4gbW9kdWxlLmV4cG9ydHM7XG4gXHR9XG5cblxuIFx0Ly8gZXhwb3NlIHRoZSBtb2R1bGVzIG9iamVjdCAoX193ZWJwYWNrX21vZHVsZXNfXylcbiBcdF9fd2VicGFja19yZXF1aXJlX18ubSA9IG1vZHVsZXM7XG5cbiBcdC8vIGV4cG9zZSB0aGUgbW9kdWxlIGNhY2hlXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLmMgPSBpbnN0YWxsZWRNb2R1bGVzO1xuXG4gXHQvLyBkZWZpbmUgZ2V0dGVyIGZ1bmN0aW9uIGZvciBoYXJtb255IGV4cG9ydHNcbiBcdF9fd2VicGFja19yZXF1aXJlX18uZCA9IGZ1bmN0aW9uKGV4cG9ydHMsIG5hbWUsIGdldHRlcikge1xuIFx0XHRpZighX193ZWJwYWNrX3JlcXVpcmVfXy5vKGV4cG9ydHMsIG5hbWUpKSB7XG4gXHRcdFx0T2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsIG5hbWUsIHtcbiBcdFx0XHRcdGNvbmZpZ3VyYWJsZTogZmFsc2UsXG4gXHRcdFx0XHRlbnVtZXJhYmxlOiB0cnVlLFxuIFx0XHRcdFx0Z2V0OiBnZXR0ZXJcbiBcdFx0XHR9KTtcbiBcdFx0fVxuIFx0fTtcblxuIFx0Ly8gZGVmaW5lIF9fZXNNb2R1bGUgb24gZXhwb3J0c1xuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5yID0gZnVuY3Rpb24oZXhwb3J0cykge1xuIFx0XHRPYmplY3QuZGVmaW5lUHJvcGVydHkoZXhwb3J0cywgJ19fZXNNb2R1bGUnLCB7IHZhbHVlOiB0cnVlIH0pO1xuIFx0fTtcblxuIFx0Ly8gZ2V0RGVmYXVsdEV4cG9ydCBmdW5jdGlvbiBmb3IgY29tcGF0aWJpbGl0eSB3aXRoIG5vbi1oYXJtb255IG1vZHVsZXNcbiBcdF9fd2VicGFja19yZXF1aXJlX18ubiA9IGZ1bmN0aW9uKG1vZHVsZSkge1xuIFx0XHR2YXIgZ2V0dGVyID0gbW9kdWxlICYmIG1vZHVsZS5fX2VzTW9kdWxlID9cbiBcdFx0XHRmdW5jdGlvbiBnZXREZWZhdWx0KCkgeyByZXR1cm4gbW9kdWxlWydkZWZhdWx0J107IH0gOlxuIFx0XHRcdGZ1bmN0aW9uIGdldE1vZHVsZUV4cG9ydHMoKSB7IHJldHVybiBtb2R1bGU7IH07XG4gXHRcdF9fd2VicGFja19yZXF1aXJlX18uZChnZXR0ZXIsICdhJywgZ2V0dGVyKTtcbiBcdFx0cmV0dXJuIGdldHRlcjtcbiBcdH07XG5cbiBcdC8vIE9iamVjdC5wcm90b3R5cGUuaGFzT3duUHJvcGVydHkuY2FsbFxuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5vID0gZnVuY3Rpb24ob2JqZWN0LCBwcm9wZXJ0eSkgeyByZXR1cm4gT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsKG9iamVjdCwgcHJvcGVydHkpOyB9O1xuXG4gXHQvLyBfX3dlYnBhY2tfcHVibGljX3BhdGhfX1xuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5wID0gXCJcIjtcblxuXG4gXHQvLyBMb2FkIGVudHJ5IG1vZHVsZSBhbmQgcmV0dXJuIGV4cG9ydHNcbiBcdHJldHVybiBfX3dlYnBhY2tfcmVxdWlyZV9fKF9fd2VicGFja19yZXF1aXJlX18ucyA9IDUpO1xuIiwiXCJ1c2Ugc3RyaWN0XCI7XG4vKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuICogIENvcHlyaWdodCAoYykgTWljcm9zb2Z0IENvcnBvcmF0aW9uLiBBbGwgcmlnaHRzIHJlc2VydmVkLlxuICogIExpY2Vuc2VkIHVuZGVyIHRoZSBNSVQgTGljZW5zZS4gU2VlIExpY2Vuc2UudHh0IGluIHRoZSBwcm9qZWN0IHJvb3QgZm9yIGxpY2Vuc2UgaW5mb3JtYXRpb24uXG4gKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cbk9iamVjdC5kZWZpbmVQcm9wZXJ0eShleHBvcnRzLCBcIl9fZXNNb2R1bGVcIiwgeyB2YWx1ZTogdHJ1ZSB9KTtcbmxldCBjYWNoZWRTZXR0aW5ncyA9IHVuZGVmaW5lZDtcbmZ1bmN0aW9uIGdldERhdGEoa2V5KSB7XG4gICAgY29uc3QgZWxlbWVudCA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCd2c2NvZGUtbWFya2Rvd24tcHJldmlldy1kYXRhJyk7XG4gICAgaWYgKGVsZW1lbnQpIHtcbiAgICAgICAgY29uc3QgZGF0YSA9IGVsZW1lbnQuZ2V0QXR0cmlidXRlKGtleSk7XG4gICAgICAgIGlmIChkYXRhKSB7XG4gICAgICAgICAgICByZXR1cm4gSlNPTi5wYXJzZShkYXRhKTtcbiAgICAgICAgfVxuICAgIH1cbiAgICB0aHJvdyBuZXcgRXJyb3IoYENvdWxkIG5vdCBsb2FkIGRhdGEgZm9yICR7a2V5fWApO1xufVxuZXhwb3J0cy5nZXREYXRhID0gZ2V0RGF0YTtcbmZ1bmN0aW9uIGdldFNldHRpbmdzKCkge1xuICAgIGlmIChjYWNoZWRTZXR0aW5ncykge1xuICAgICAgICByZXR1cm4gY2FjaGVkU2V0dGluZ3M7XG4gICAgfVxuICAgIGNhY2hlZFNldHRpbmdzID0gZ2V0RGF0YSgnZGF0YS1zZXR0aW5ncycpO1xuICAgIGlmIChjYWNoZWRTZXR0aW5ncykge1xuICAgICAgICByZXR1cm4gY2FjaGVkU2V0dGluZ3M7XG4gICAgfVxuICAgIHRocm93IG5ldyBFcnJvcignQ291bGQgbm90IGxvYWQgc2V0dGluZ3MnKTtcbn1cbmV4cG9ydHMuZ2V0U2V0dGluZ3MgPSBnZXRTZXR0aW5ncztcbiIsIlwidXNlIHN0cmljdFwiO1xuT2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsIFwiX19lc01vZHVsZVwiLCB7IHZhbHVlOiB0cnVlIH0pO1xuY2xhc3MgU3R5bGVMb2FkaW5nTW9uaXRvciB7XG4gICAgY29uc3RydWN0b3IoKSB7XG4gICAgICAgIHRoaXMudW5sb2FkZWRTdHlsZXMgPSBbXTtcbiAgICAgICAgdGhpcy5maW5pc2hlZExvYWRpbmcgPSBmYWxzZTtcbiAgICAgICAgY29uc3Qgb25TdHlsZUxvYWRFcnJvciA9IChldmVudCkgPT4ge1xuICAgICAgICAgICAgY29uc3Qgc291cmNlID0gZXZlbnQudGFyZ2V0LmRhdGFzZXQuc291cmNlO1xuICAgICAgICAgICAgdGhpcy51bmxvYWRlZFN0eWxlcy5wdXNoKHNvdXJjZSk7XG4gICAgICAgIH07XG4gICAgICAgIHdpbmRvdy5hZGRFdmVudExpc3RlbmVyKCdET01Db250ZW50TG9hZGVkJywgKCkgPT4ge1xuICAgICAgICAgICAgZm9yIChjb25zdCBsaW5rIG9mIGRvY3VtZW50LmdldEVsZW1lbnRzQnlDbGFzc05hbWUoJ2NvZGUtdXNlci1zdHlsZScpKSB7XG4gICAgICAgICAgICAgICAgaWYgKGxpbmsuZGF0YXNldC5zb3VyY2UpIHtcbiAgICAgICAgICAgICAgICAgICAgbGluay5vbmVycm9yID0gb25TdHlsZUxvYWRFcnJvcjtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgICAgIH0pO1xuICAgICAgICB3aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcignbG9hZCcsICgpID0+IHtcbiAgICAgICAgICAgIGlmICghdGhpcy51bmxvYWRlZFN0eWxlcy5sZW5ndGgpIHtcbiAgICAgICAgICAgICAgICByZXR1cm47XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICB0aGlzLmZpbmlzaGVkTG9hZGluZyA9IHRydWU7XG4gICAgICAgICAgICBpZiAodGhpcy5wb3N0ZXIpIHtcbiAgICAgICAgICAgICAgICB0aGlzLnBvc3Rlci5wb3N0TWVzc2FnZSgncHJldmlld1N0eWxlTG9hZEVycm9yJywgeyB1bmxvYWRlZFN0eWxlczogdGhpcy51bmxvYWRlZFN0eWxlcyB9KTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfSk7XG4gICAgfVxuICAgIHNldFBvc3Rlcihwb3N0ZXIpIHtcbiAgICAgICAgdGhpcy5wb3N0ZXIgPSBwb3N0ZXI7XG4gICAgICAgIGlmICh0aGlzLmZpbmlzaGVkTG9hZGluZykge1xuICAgICAgICAgICAgcG9zdGVyLnBvc3RNZXNzYWdlKCdwcmV2aWV3U3R5bGVMb2FkRXJyb3InLCB7IHVubG9hZGVkU3R5bGVzOiB0aGlzLnVubG9hZGVkU3R5bGVzIH0pO1xuICAgICAgICB9XG4gICAgfVxufVxuZXhwb3J0cy5TdHlsZUxvYWRpbmdNb25pdG9yID0gU3R5bGVMb2FkaW5nTW9uaXRvcjtcbiIsIlwidXNlIHN0cmljdFwiO1xuLyotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cbiAqICBDb3B5cmlnaHQgKGMpIE1pY3Jvc29mdCBDb3Jwb3JhdGlvbi4gQWxsIHJpZ2h0cyByZXNlcnZlZC5cbiAqICBMaWNlbnNlZCB1bmRlciB0aGUgTUlUIExpY2Vuc2UuIFNlZSBMaWNlbnNlLnR4dCBpbiB0aGUgcHJvamVjdCByb290IGZvciBsaWNlbnNlIGluZm9ybWF0aW9uLlxuICotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSovXG5PYmplY3QuZGVmaW5lUHJvcGVydHkoZXhwb3J0cywgXCJfX2VzTW9kdWxlXCIsIHsgdmFsdWU6IHRydWUgfSk7XG5mdW5jdGlvbiBnZXRTdHJpbmdzKCkge1xuICAgIGNvbnN0IHN0b3JlID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ3ZzY29kZS1tYXJrZG93bi1wcmV2aWV3LWRhdGEnKTtcbiAgICBpZiAoc3RvcmUpIHtcbiAgICAgICAgY29uc3QgZGF0YSA9IHN0b3JlLmdldEF0dHJpYnV0ZSgnZGF0YS1zdHJpbmdzJyk7XG4gICAgICAgIGlmIChkYXRhKSB7XG4gICAgICAgICAgICByZXR1cm4gSlNPTi5wYXJzZShkYXRhKTtcbiAgICAgICAgfVxuICAgIH1cbiAgICB0aHJvdyBuZXcgRXJyb3IoJ0NvdWxkIG5vdCBsb2FkIHN0cmluZ3MnKTtcbn1cbmV4cG9ydHMuZ2V0U3RyaW5ncyA9IGdldFN0cmluZ3M7XG4iLCJcInVzZSBzdHJpY3RcIjtcbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gKiAgQ29weXJpZ2h0IChjKSBNaWNyb3NvZnQgQ29ycG9yYXRpb24uIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiAgTGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBTZWUgTGljZW5zZS50eHQgaW4gdGhlIHByb2plY3Qgcm9vdCBmb3IgbGljZW5zZSBpbmZvcm1hdGlvbi5cbiAqLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuT2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsIFwiX19lc01vZHVsZVwiLCB7IHZhbHVlOiB0cnVlIH0pO1xuY29uc3Qgc2V0dGluZ3NfMSA9IHJlcXVpcmUoXCIuL3NldHRpbmdzXCIpO1xuY29uc3Qgc3RyaW5nc18xID0gcmVxdWlyZShcIi4vc3RyaW5nc1wiKTtcbi8qKlxuICogU2hvd3MgYW4gYWxlcnQgd2hlbiB0aGVyZSBpcyBhIGNvbnRlbnQgc2VjdXJpdHkgcG9saWN5IHZpb2xhdGlvbi5cbiAqL1xuY2xhc3MgQ3NwQWxlcnRlciB7XG4gICAgY29uc3RydWN0b3IoKSB7XG4gICAgICAgIHRoaXMuZGlkU2hvdyA9IGZhbHNlO1xuICAgICAgICB0aGlzLmRpZEhhdmVDc3BXYXJuaW5nID0gZmFsc2U7XG4gICAgICAgIGRvY3VtZW50LmFkZEV2ZW50TGlzdGVuZXIoJ3NlY3VyaXR5cG9saWN5dmlvbGF0aW9uJywgKCkgPT4ge1xuICAgICAgICAgICAgdGhpcy5vbkNzcFdhcm5pbmcoKTtcbiAgICAgICAgfSk7XG4gICAgICAgIHdpbmRvdy5hZGRFdmVudExpc3RlbmVyKCdtZXNzYWdlJywgKGV2ZW50KSA9PiB7XG4gICAgICAgICAgICBpZiAoZXZlbnQgJiYgZXZlbnQuZGF0YSAmJiBldmVudC5kYXRhLm5hbWUgPT09ICd2c2NvZGUtZGlkLWJsb2NrLXN2ZycpIHtcbiAgICAgICAgICAgICAgICB0aGlzLm9uQ3NwV2FybmluZygpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9KTtcbiAgICB9XG4gICAgc2V0UG9zdGVyKHBvc3Rlcikge1xuICAgICAgICB0aGlzLm1lc3NhZ2luZyA9IHBvc3RlcjtcbiAgICAgICAgaWYgKHRoaXMuZGlkSGF2ZUNzcFdhcm5pbmcpIHtcbiAgICAgICAgICAgIHRoaXMuc2hvd0NzcFdhcm5pbmcoKTtcbiAgICAgICAgfVxuICAgIH1cbiAgICBvbkNzcFdhcm5pbmcoKSB7XG4gICAgICAgIHRoaXMuZGlkSGF2ZUNzcFdhcm5pbmcgPSB0cnVlO1xuICAgICAgICB0aGlzLnNob3dDc3BXYXJuaW5nKCk7XG4gICAgfVxuICAgIHNob3dDc3BXYXJuaW5nKCkge1xuICAgICAgICBjb25zdCBzdHJpbmdzID0gc3RyaW5nc18xLmdldFN0cmluZ3MoKTtcbiAgICAgICAgY29uc3Qgc2V0dGluZ3MgPSBzZXR0aW5nc18xLmdldFNldHRpbmdzKCk7XG4gICAgICAgIGlmICh0aGlzLmRpZFNob3cgfHwgc2V0dGluZ3MuZGlzYWJsZVNlY3VyaXR5V2FybmluZ3MgfHwgIXRoaXMubWVzc2FnaW5nKSB7XG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cbiAgICAgICAgdGhpcy5kaWRTaG93ID0gdHJ1ZTtcbiAgICAgICAgY29uc3Qgbm90aWZpY2F0aW9uID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnYScpO1xuICAgICAgICBub3RpZmljYXRpb24uaW5uZXJUZXh0ID0gc3RyaW5ncy5jc3BBbGVydE1lc3NhZ2VUZXh0O1xuICAgICAgICBub3RpZmljYXRpb24uc2V0QXR0cmlidXRlKCdpZCcsICdjb2RlLWNzcC13YXJuaW5nJyk7XG4gICAgICAgIG5vdGlmaWNhdGlvbi5zZXRBdHRyaWJ1dGUoJ3RpdGxlJywgc3RyaW5ncy5jc3BBbGVydE1lc3NhZ2VUaXRsZSk7XG4gICAgICAgIG5vdGlmaWNhdGlvbi5zZXRBdHRyaWJ1dGUoJ3JvbGUnLCAnYnV0dG9uJyk7XG4gICAgICAgIG5vdGlmaWNhdGlvbi5zZXRBdHRyaWJ1dGUoJ2FyaWEtbGFiZWwnLCBzdHJpbmdzLmNzcEFsZXJ0TWVzc2FnZUxhYmVsKTtcbiAgICAgICAgbm90aWZpY2F0aW9uLm9uY2xpY2sgPSAoKSA9PiB7XG4gICAgICAgICAgICB0aGlzLm1lc3NhZ2luZy5wb3N0TWVzc2FnZSgnc2hvd1ByZXZpZXdTZWN1cml0eVNlbGVjdG9yJywgeyBzb3VyY2U6IHNldHRpbmdzLnNvdXJjZSB9KTtcbiAgICAgICAgfTtcbiAgICAgICAgZG9jdW1lbnQuYm9keS5hcHBlbmRDaGlsZChub3RpZmljYXRpb24pO1xuICAgIH1cbn1cbmV4cG9ydHMuQ3NwQWxlcnRlciA9IENzcEFsZXJ0ZXI7XG4iLCJcInVzZSBzdHJpY3RcIjtcbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gKiAgQ29weXJpZ2h0IChjKSBNaWNyb3NvZnQgQ29ycG9yYXRpb24uIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiAgTGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBTZWUgTGljZW5zZS50eHQgaW4gdGhlIHByb2plY3Qgcm9vdCBmb3IgbGljZW5zZSBpbmZvcm1hdGlvbi5cbiAqLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuT2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsIFwiX19lc01vZHVsZVwiLCB7IHZhbHVlOiB0cnVlIH0pO1xuY29uc3QgY3NwXzEgPSByZXF1aXJlKFwiLi9jc3BcIik7XG5jb25zdCBsb2FkaW5nXzEgPSByZXF1aXJlKFwiLi9sb2FkaW5nXCIpO1xud2luZG93LmNzcEFsZXJ0ZXIgPSBuZXcgY3NwXzEuQ3NwQWxlcnRlcigpO1xud2luZG93LnN0eWxlTG9hZGluZ01vbml0b3IgPSBuZXcgbG9hZGluZ18xLlN0eWxlTG9hZGluZ01vbml0b3IoKTtcbiJdLCJzb3VyY2VSb290IjoiIn0= +!function(e){var t={};function n(s){if(t[s])return t[s].exports;var o=t[s]={i:s,l:!1,exports:{}};return e[s].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,s){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:s})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var s=Object.create(null);if(n.r(s),Object.defineProperty(s,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(s,o,function(t){return e[t]}.bind(null,o));return s},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=8)}([function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});let s=void 0;function o(e){const t=document.getElementById("vscode-markdown-preview-data");if(t){const n=t.getAttribute(e);if(n)return JSON.parse(n)}throw new Error(`Could not load data for ${e}`)}t.getData=o,t.getSettings=function(){if(s)return s;if(s=o("data-settings"))return s;throw new Error("Could not load settings")}},,,,,,,,function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const s=n(9),o=n(11);window.cspAlerter=new s.CspAlerter,window.styleLoadingMonitor=new o.StyleLoadingMonitor},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const s=n(0),o=n(10);t.CspAlerter=class{constructor(){this.didShow=!1,this.didHaveCspWarning=!1,document.addEventListener("securitypolicyviolation",()=>{this.onCspWarning()}),window.addEventListener("message",e=>{e&&e.data&&"vscode-did-block-svg"===e.data.name&&this.onCspWarning()})}setPoster(e){this.messaging=e,this.didHaveCspWarning&&this.showCspWarning()}onCspWarning(){this.didHaveCspWarning=!0,this.showCspWarning()}showCspWarning(){const e=o.getStrings(),t=s.getSettings();if(this.didShow||t.disableSecurityWarnings||!this.messaging)return;this.didShow=!0;const n=document.createElement("a");n.innerText=e.cspAlertMessageText,n.setAttribute("id","code-csp-warning"),n.setAttribute("title",e.cspAlertMessageTitle),n.setAttribute("role","button"),n.setAttribute("aria-label",e.cspAlertMessageLabel),n.onclick=()=>{this.messaging.postMessage("showPreviewSecuritySelector",{source:t.source})},document.body.appendChild(n)}}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.getStrings=function(){const e=document.getElementById("vscode-markdown-preview-data");if(e){const t=e.getAttribute("data-strings");if(t)return JSON.parse(t)}throw new Error("Could not load strings")}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});t.StyleLoadingMonitor=class{constructor(){this.unloadedStyles=[],this.finishedLoading=!1;const e=e=>{const t=e.target.dataset.source;this.unloadedStyles.push(t)};window.addEventListener("DOMContentLoaded",()=>{for(const t of document.getElementsByClassName("code-user-style"))t.dataset.source&&(t.onerror=e)}),window.addEventListener("load",()=>{this.unloadedStyles.length&&(this.finishedLoading=!0,this.poster&&this.poster.postMessage("previewStyleLoadError",{unloadedStyles:this.unloadedStyles}))})}setPoster(e){this.poster=e,this.finishedLoading&&e.postMessage("previewStyleLoadError",{unloadedStyles:this.unloadedStyles})}}}]); +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vd2VicGFjay9ib290c3RyYXAiLCJ3ZWJwYWNrOi8vLy4vcHJldmlldy1zcmMvc2V0dGluZ3MudHMiLCJ3ZWJwYWNrOi8vLy4vcHJldmlldy1zcmMvcHJlLnRzIiwid2VicGFjazovLy8uL3ByZXZpZXctc3JjL2NzcC50cyIsIndlYnBhY2s6Ly8vLi9wcmV2aWV3LXNyYy9zdHJpbmdzLnRzIiwid2VicGFjazovLy8uL3ByZXZpZXctc3JjL2xvYWRpbmcudHMiXSwibmFtZXMiOlsiaW5zdGFsbGVkTW9kdWxlcyIsIl9fd2VicGFja19yZXF1aXJlX18iLCJtb2R1bGVJZCIsImV4cG9ydHMiLCJtb2R1bGUiLCJpIiwibCIsIm1vZHVsZXMiLCJjYWxsIiwibSIsImMiLCJkIiwibmFtZSIsImdldHRlciIsIm8iLCJPYmplY3QiLCJkZWZpbmVQcm9wZXJ0eSIsImVudW1lcmFibGUiLCJnZXQiLCJyIiwiU3ltYm9sIiwidG9TdHJpbmdUYWciLCJ2YWx1ZSIsInQiLCJtb2RlIiwiX19lc01vZHVsZSIsIm5zIiwiY3JlYXRlIiwia2V5IiwiYmluZCIsIm4iLCJvYmplY3QiLCJwcm9wZXJ0eSIsInByb3RvdHlwZSIsImhhc093blByb3BlcnR5IiwicCIsInMiLCJjYWNoZWRTZXR0aW5ncyIsInVuZGVmaW5lZCIsImdldERhdGEiLCJlbGVtZW50IiwiZG9jdW1lbnQiLCJnZXRFbGVtZW50QnlJZCIsImRhdGEiLCJnZXRBdHRyaWJ1dGUiLCJKU09OIiwicGFyc2UiLCJFcnJvciIsImdldFNldHRpbmdzIiwiY3NwXzEiLCJsb2FkaW5nXzEiLCJ3aW5kb3ciLCJjc3BBbGVydGVyIiwiQ3NwQWxlcnRlciIsInN0eWxlTG9hZGluZ01vbml0b3IiLCJTdHlsZUxvYWRpbmdNb25pdG9yIiwic2V0dGluZ3NfMSIsInN0cmluZ3NfMSIsInRoaXMiLCJkaWRTaG93IiwiZGlkSGF2ZUNzcFdhcm5pbmciLCJhZGRFdmVudExpc3RlbmVyIiwib25Dc3BXYXJuaW5nIiwiZXZlbnQiLCJwb3N0ZXIiLCJtZXNzYWdpbmciLCJzaG93Q3NwV2FybmluZyIsInN0cmluZ3MiLCJnZXRTdHJpbmdzIiwic2V0dGluZ3MiLCJkaXNhYmxlU2VjdXJpdHlXYXJuaW5ncyIsIm5vdGlmaWNhdGlvbiIsImNyZWF0ZUVsZW1lbnQiLCJpbm5lclRleHQiLCJjc3BBbGVydE1lc3NhZ2VUZXh0Iiwic2V0QXR0cmlidXRlIiwiY3NwQWxlcnRNZXNzYWdlVGl0bGUiLCJjc3BBbGVydE1lc3NhZ2VMYWJlbCIsIm9uY2xpY2siLCJwb3N0TWVzc2FnZSIsInNvdXJjZSIsImJvZHkiLCJhcHBlbmRDaGlsZCIsInN0b3JlIiwidW5sb2FkZWRTdHlsZXMiLCJmaW5pc2hlZExvYWRpbmciLCJvblN0eWxlTG9hZEVycm9yIiwidGFyZ2V0IiwiZGF0YXNldCIsInB1c2giLCJsaW5rIiwiZ2V0RWxlbWVudHNCeUNsYXNzTmFtZSIsIm9uZXJyb3IiLCJsZW5ndGgiXSwibWFwcGluZ3MiOiJhQUNFLElBQUlBLEVBQW1CLEdBR3ZCLFNBQVNDLEVBQW9CQyxHQUc1QixHQUFHRixFQUFpQkUsR0FDbkIsT0FBT0YsRUFBaUJFLEdBQVVDLFFBR25DLElBQUlDLEVBQVNKLEVBQWlCRSxHQUFZLENBQ3pDRyxFQUFHSCxFQUNISSxHQUFHLEVBQ0hILFFBQVMsSUFVVixPQU5BSSxFQUFRTCxHQUFVTSxLQUFLSixFQUFPRCxRQUFTQyxFQUFRQSxFQUFPRCxRQUFTRixHQUcvREcsRUFBT0UsR0FBSSxFQUdKRixFQUFPRCxRQUtmRixFQUFvQlEsRUFBSUYsRUFHeEJOLEVBQW9CUyxFQUFJVixFQUd4QkMsRUFBb0JVLEVBQUksU0FBU1IsRUFBU1MsRUFBTUMsR0FDM0NaLEVBQW9CYSxFQUFFWCxFQUFTUyxJQUNsQ0csT0FBT0MsZUFBZWIsRUFBU1MsRUFBTSxDQUFFSyxZQUFZLEVBQU1DLElBQUtMLEtBS2hFWixFQUFvQmtCLEVBQUksU0FBU2hCLEdBQ1gsb0JBQVhpQixRQUEwQkEsT0FBT0MsYUFDMUNOLE9BQU9DLGVBQWViLEVBQVNpQixPQUFPQyxZQUFhLENBQUVDLE1BQU8sV0FFN0RQLE9BQU9DLGVBQWViLEVBQVMsYUFBYyxDQUFFbUIsT0FBTyxLQVF2RHJCLEVBQW9Cc0IsRUFBSSxTQUFTRCxFQUFPRSxHQUV2QyxHQURVLEVBQVBBLElBQVVGLEVBQVFyQixFQUFvQnFCLElBQy9CLEVBQVBFLEVBQVUsT0FBT0YsRUFDcEIsR0FBVyxFQUFQRSxHQUE4QixpQkFBVkYsR0FBc0JBLEdBQVNBLEVBQU1HLFdBQVksT0FBT0gsRUFDaEYsSUFBSUksRUFBS1gsT0FBT1ksT0FBTyxNQUd2QixHQUZBMUIsRUFBb0JrQixFQUFFTyxHQUN0QlgsT0FBT0MsZUFBZVUsRUFBSSxVQUFXLENBQUVULFlBQVksRUFBTUssTUFBT0EsSUFDdEQsRUFBUEUsR0FBNEIsaUJBQVRGLEVBQW1CLElBQUksSUFBSU0sS0FBT04sRUFBT3JCLEVBQW9CVSxFQUFFZSxFQUFJRSxFQUFLLFNBQVNBLEdBQU8sT0FBT04sRUFBTU0sSUFBUUMsS0FBSyxLQUFNRCxJQUM5SSxPQUFPRixHQUlSekIsRUFBb0I2QixFQUFJLFNBQVMxQixHQUNoQyxJQUFJUyxFQUFTVCxHQUFVQSxFQUFPcUIsV0FDN0IsV0FBd0IsT0FBT3JCLEVBQWdCLFNBQy9DLFdBQThCLE9BQU9BLEdBRXRDLE9BREFILEVBQW9CVSxFQUFFRSxFQUFRLElBQUtBLEdBQzVCQSxHQUlSWixFQUFvQmEsRUFBSSxTQUFTaUIsRUFBUUMsR0FBWSxPQUFPakIsT0FBT2tCLFVBQVVDLGVBQWUxQixLQUFLdUIsRUFBUUMsSUFHekcvQixFQUFvQmtDLEVBQUksR0FJakJsQyxFQUFvQkEsRUFBb0JtQyxFQUFJLEcsK0JDN0VyRHJCLE9BQU9DLGVBQWViLEVBQVMsYUFBYyxDQUFFbUIsT0FBTyxJQUN0RCxJQUFJZSxPQUFpQkMsRUFDckIsU0FBU0MsRUFBUVgsR0FDYixNQUFNWSxFQUFVQyxTQUFTQyxlQUFlLGdDQUN4QyxHQUFJRixFQUFTLENBQ1QsTUFBTUcsRUFBT0gsRUFBUUksYUFBYWhCLEdBQ2xDLEdBQUllLEVBQ0EsT0FBT0UsS0FBS0MsTUFBTUgsR0FHMUIsTUFBTSxJQUFJSSxNQUFNLDJCQUEyQm5CLEtBRS9DekIsRUFBUW9DLFFBQVVBLEVBV2xCcEMsRUFBUTZDLFlBVlIsV0FDSSxHQUFJWCxFQUNBLE9BQU9BLEVBR1gsR0FEQUEsRUFBaUJFLEVBQVEsaUJBRXJCLE9BQU9GLEVBRVgsTUFBTSxJQUFJVSxNQUFNLDZCLG9DQ3JCcEJoQyxPQUFPQyxlQUFlYixFQUFTLGFBQWMsQ0FBRW1CLE9BQU8sSUFDdEQsTUFBTTJCLEVBQVEsRUFBUSxHQUNoQkMsRUFBWSxFQUFRLElBQzFCQyxPQUFPQyxXQUFhLElBQUlILEVBQU1JLFdBQzlCRixPQUFPRyxvQkFBc0IsSUFBSUosRUFBVUsscUIsNkJDSjNDeEMsT0FBT0MsZUFBZWIsRUFBUyxhQUFjLENBQUVtQixPQUFPLElBQ3RELE1BQU1rQyxFQUFhLEVBQVEsR0FDckJDLEVBQVksRUFBUSxJQThDMUJ0RCxFQUFRa0QsV0ExQ1IsTUFDSSxjQUNJSyxLQUFLQyxTQUFVLEVBQ2ZELEtBQUtFLG1CQUFvQixFQUN6Qm5CLFNBQVNvQixpQkFBaUIsMEJBQTJCLEtBQ2pESCxLQUFLSSxpQkFFVFgsT0FBT1UsaUJBQWlCLFVBQVlFLElBQzVCQSxHQUFTQSxFQUFNcEIsTUFBNEIseUJBQXBCb0IsRUFBTXBCLEtBQUsvQixNQUNsQzhDLEtBQUtJLGlCQUlqQixVQUFVRSxHQUNOTixLQUFLTyxVQUFZRCxFQUNiTixLQUFLRSxtQkFDTEYsS0FBS1EsaUJBR2IsZUFDSVIsS0FBS0UsbUJBQW9CLEVBQ3pCRixLQUFLUSxpQkFFVCxpQkFDSSxNQUFNQyxFQUFVVixFQUFVVyxhQUNwQkMsRUFBV2IsRUFBV1IsY0FDNUIsR0FBSVUsS0FBS0MsU0FBV1UsRUFBU0MsMEJBQTRCWixLQUFLTyxVQUMxRCxPQUVKUCxLQUFLQyxTQUFVLEVBQ2YsTUFBTVksRUFBZTlCLFNBQVMrQixjQUFjLEtBQzVDRCxFQUFhRSxVQUFZTixFQUFRTyxvQkFDakNILEVBQWFJLGFBQWEsS0FBTSxvQkFDaENKLEVBQWFJLGFBQWEsUUFBU1IsRUFBUVMsc0JBQzNDTCxFQUFhSSxhQUFhLE9BQVEsVUFDbENKLEVBQWFJLGFBQWEsYUFBY1IsRUFBUVUsc0JBQ2hETixFQUFhTyxRQUFVLEtBQ25CcEIsS0FBS08sVUFBVWMsWUFBWSw4QkFBK0IsQ0FBRUMsT0FBUVgsRUFBU1csVUFFakZ2QyxTQUFTd0MsS0FBS0MsWUFBWVgsTSw2QkM3Q2xDeEQsT0FBT0MsZUFBZWIsRUFBUyxhQUFjLENBQUVtQixPQUFPLElBV3REbkIsRUFBUWlFLFdBVlIsV0FDSSxNQUFNZSxFQUFRMUMsU0FBU0MsZUFBZSxnQ0FDdEMsR0FBSXlDLEVBQU8sQ0FDUCxNQUFNeEMsRUFBT3dDLEVBQU12QyxhQUFhLGdCQUNoQyxHQUFJRCxFQUNBLE9BQU9FLEtBQUtDLE1BQU1ILEdBRzFCLE1BQU0sSUFBSUksTUFBTSw0Qiw2QkNicEJoQyxPQUFPQyxlQUFlYixFQUFTLGFBQWMsQ0FBRW1CLE9BQU8sSUFpQ3REbkIsRUFBUW9ELG9CQWhDUixNQUNJLGNBQ0lHLEtBQUswQixlQUFpQixHQUN0QjFCLEtBQUsyQixpQkFBa0IsRUFDdkIsTUFBTUMsRUFBb0J2QixJQUN0QixNQUFNaUIsRUFBU2pCLEVBQU13QixPQUFPQyxRQUFRUixPQUNwQ3RCLEtBQUswQixlQUFlSyxLQUFLVCxJQUU3QjdCLE9BQU9VLGlCQUFpQixtQkFBb0IsS0FDeEMsSUFBSyxNQUFNNkIsS0FBUWpELFNBQVNrRCx1QkFBdUIsbUJBQzNDRCxFQUFLRixRQUFRUixTQUNiVSxFQUFLRSxRQUFVTixLQUkzQm5DLE9BQU9VLGlCQUFpQixPQUFRLEtBQ3ZCSCxLQUFLMEIsZUFBZVMsU0FHekJuQyxLQUFLMkIsaUJBQWtCLEVBQ25CM0IsS0FBS00sUUFDTE4sS0FBS00sT0FBT2UsWUFBWSx3QkFBeUIsQ0FBRUssZUFBZ0IxQixLQUFLMEIsb0JBSXBGLFVBQVVwQixHQUNOTixLQUFLTSxPQUFTQSxFQUNWTixLQUFLMkIsaUJBQ0xyQixFQUFPZSxZQUFZLHdCQUF5QixDQUFFSyxlQUFnQjFCLEtBQUswQiIsImZpbGUiOiJwcmUuanMiLCJzb3VyY2VzQ29udGVudCI6WyIgXHQvLyBUaGUgbW9kdWxlIGNhY2hlXG4gXHR2YXIgaW5zdGFsbGVkTW9kdWxlcyA9IHt9O1xuXG4gXHQvLyBUaGUgcmVxdWlyZSBmdW5jdGlvblxuIFx0ZnVuY3Rpb24gX193ZWJwYWNrX3JlcXVpcmVfXyhtb2R1bGVJZCkge1xuXG4gXHRcdC8vIENoZWNrIGlmIG1vZHVsZSBpcyBpbiBjYWNoZVxuIFx0XHRpZihpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXSkge1xuIFx0XHRcdHJldHVybiBpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXS5leHBvcnRzO1xuIFx0XHR9XG4gXHRcdC8vIENyZWF0ZSBhIG5ldyBtb2R1bGUgKGFuZCBwdXQgaXQgaW50byB0aGUgY2FjaGUpXG4gXHRcdHZhciBtb2R1bGUgPSBpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXSA9IHtcbiBcdFx0XHRpOiBtb2R1bGVJZCxcbiBcdFx0XHRsOiBmYWxzZSxcbiBcdFx0XHRleHBvcnRzOiB7fVxuIFx0XHR9O1xuXG4gXHRcdC8vIEV4ZWN1dGUgdGhlIG1vZHVsZSBmdW5jdGlvblxuIFx0XHRtb2R1bGVzW21vZHVsZUlkXS5jYWxsKG1vZHVsZS5leHBvcnRzLCBtb2R1bGUsIG1vZHVsZS5leHBvcnRzLCBfX3dlYnBhY2tfcmVxdWlyZV9fKTtcblxuIFx0XHQvLyBGbGFnIHRoZSBtb2R1bGUgYXMgbG9hZGVkXG4gXHRcdG1vZHVsZS5sID0gdHJ1ZTtcblxuIFx0XHQvLyBSZXR1cm4gdGhlIGV4cG9ydHMgb2YgdGhlIG1vZHVsZVxuIFx0XHRyZXR1cm4gbW9kdWxlLmV4cG9ydHM7XG4gXHR9XG5cblxuIFx0Ly8gZXhwb3NlIHRoZSBtb2R1bGVzIG9iamVjdCAoX193ZWJwYWNrX21vZHVsZXNfXylcbiBcdF9fd2VicGFja19yZXF1aXJlX18ubSA9IG1vZHVsZXM7XG5cbiBcdC8vIGV4cG9zZSB0aGUgbW9kdWxlIGNhY2hlXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLmMgPSBpbnN0YWxsZWRNb2R1bGVzO1xuXG4gXHQvLyBkZWZpbmUgZ2V0dGVyIGZ1bmN0aW9uIGZvciBoYXJtb255IGV4cG9ydHNcbiBcdF9fd2VicGFja19yZXF1aXJlX18uZCA9IGZ1bmN0aW9uKGV4cG9ydHMsIG5hbWUsIGdldHRlcikge1xuIFx0XHRpZighX193ZWJwYWNrX3JlcXVpcmVfXy5vKGV4cG9ydHMsIG5hbWUpKSB7XG4gXHRcdFx0T2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsIG5hbWUsIHsgZW51bWVyYWJsZTogdHJ1ZSwgZ2V0OiBnZXR0ZXIgfSk7XG4gXHRcdH1cbiBcdH07XG5cbiBcdC8vIGRlZmluZSBfX2VzTW9kdWxlIG9uIGV4cG9ydHNcbiBcdF9fd2VicGFja19yZXF1aXJlX18uciA9IGZ1bmN0aW9uKGV4cG9ydHMpIHtcbiBcdFx0aWYodHlwZW9mIFN5bWJvbCAhPT0gJ3VuZGVmaW5lZCcgJiYgU3ltYm9sLnRvU3RyaW5nVGFnKSB7XG4gXHRcdFx0T2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsIFN5bWJvbC50b1N0cmluZ1RhZywgeyB2YWx1ZTogJ01vZHVsZScgfSk7XG4gXHRcdH1cbiBcdFx0T2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsICdfX2VzTW9kdWxlJywgeyB2YWx1ZTogdHJ1ZSB9KTtcbiBcdH07XG5cbiBcdC8vIGNyZWF0ZSBhIGZha2UgbmFtZXNwYWNlIG9iamVjdFxuIFx0Ly8gbW9kZSAmIDE6IHZhbHVlIGlzIGEgbW9kdWxlIGlkLCByZXF1aXJlIGl0XG4gXHQvLyBtb2RlICYgMjogbWVyZ2UgYWxsIHByb3BlcnRpZXMgb2YgdmFsdWUgaW50byB0aGUgbnNcbiBcdC8vIG1vZGUgJiA0OiByZXR1cm4gdmFsdWUgd2hlbiBhbHJlYWR5IG5zIG9iamVjdFxuIFx0Ly8gbW9kZSAmIDh8MTogYmVoYXZlIGxpa2UgcmVxdWlyZVxuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy50ID0gZnVuY3Rpb24odmFsdWUsIG1vZGUpIHtcbiBcdFx0aWYobW9kZSAmIDEpIHZhbHVlID0gX193ZWJwYWNrX3JlcXVpcmVfXyh2YWx1ZSk7XG4gXHRcdGlmKG1vZGUgJiA4KSByZXR1cm4gdmFsdWU7XG4gXHRcdGlmKChtb2RlICYgNCkgJiYgdHlwZW9mIHZhbHVlID09PSAnb2JqZWN0JyAmJiB2YWx1ZSAmJiB2YWx1ZS5fX2VzTW9kdWxlKSByZXR1cm4gdmFsdWU7XG4gXHRcdHZhciBucyA9IE9iamVjdC5jcmVhdGUobnVsbCk7XG4gXHRcdF9fd2VicGFja19yZXF1aXJlX18ucihucyk7XG4gXHRcdE9iamVjdC5kZWZpbmVQcm9wZXJ0eShucywgJ2RlZmF1bHQnLCB7IGVudW1lcmFibGU6IHRydWUsIHZhbHVlOiB2YWx1ZSB9KTtcbiBcdFx0aWYobW9kZSAmIDIgJiYgdHlwZW9mIHZhbHVlICE9ICdzdHJpbmcnKSBmb3IodmFyIGtleSBpbiB2YWx1ZSkgX193ZWJwYWNrX3JlcXVpcmVfXy5kKG5zLCBrZXksIGZ1bmN0aW9uKGtleSkgeyByZXR1cm4gdmFsdWVba2V5XTsgfS5iaW5kKG51bGwsIGtleSkpO1xuIFx0XHRyZXR1cm4gbnM7XG4gXHR9O1xuXG4gXHQvLyBnZXREZWZhdWx0RXhwb3J0IGZ1bmN0aW9uIGZvciBjb21wYXRpYmlsaXR5IHdpdGggbm9uLWhhcm1vbnkgbW9kdWxlc1xuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5uID0gZnVuY3Rpb24obW9kdWxlKSB7XG4gXHRcdHZhciBnZXR0ZXIgPSBtb2R1bGUgJiYgbW9kdWxlLl9fZXNNb2R1bGUgP1xuIFx0XHRcdGZ1bmN0aW9uIGdldERlZmF1bHQoKSB7IHJldHVybiBtb2R1bGVbJ2RlZmF1bHQnXTsgfSA6XG4gXHRcdFx0ZnVuY3Rpb24gZ2V0TW9kdWxlRXhwb3J0cygpIHsgcmV0dXJuIG1vZHVsZTsgfTtcbiBcdFx0X193ZWJwYWNrX3JlcXVpcmVfXy5kKGdldHRlciwgJ2EnLCBnZXR0ZXIpO1xuIFx0XHRyZXR1cm4gZ2V0dGVyO1xuIFx0fTtcblxuIFx0Ly8gT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLm8gPSBmdW5jdGlvbihvYmplY3QsIHByb3BlcnR5KSB7IHJldHVybiBPYmplY3QucHJvdG90eXBlLmhhc093blByb3BlcnR5LmNhbGwob2JqZWN0LCBwcm9wZXJ0eSk7IH07XG5cbiBcdC8vIF9fd2VicGFja19wdWJsaWNfcGF0aF9fXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLnAgPSBcIlwiO1xuXG5cbiBcdC8vIExvYWQgZW50cnkgbW9kdWxlIGFuZCByZXR1cm4gZXhwb3J0c1xuIFx0cmV0dXJuIF9fd2VicGFja19yZXF1aXJlX18oX193ZWJwYWNrX3JlcXVpcmVfXy5zID0gOCk7XG4iLCJcInVzZSBzdHJpY3RcIjtcbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gKiAgQ29weXJpZ2h0IChjKSBNaWNyb3NvZnQgQ29ycG9yYXRpb24uIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiAgTGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBTZWUgTGljZW5zZS50eHQgaW4gdGhlIHByb2plY3Qgcm9vdCBmb3IgbGljZW5zZSBpbmZvcm1hdGlvbi5cbiAqLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuT2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsIFwiX19lc01vZHVsZVwiLCB7IHZhbHVlOiB0cnVlIH0pO1xubGV0IGNhY2hlZFNldHRpbmdzID0gdW5kZWZpbmVkO1xuZnVuY3Rpb24gZ2V0RGF0YShrZXkpIHtcbiAgICBjb25zdCBlbGVtZW50ID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ3ZzY29kZS1tYXJrZG93bi1wcmV2aWV3LWRhdGEnKTtcbiAgICBpZiAoZWxlbWVudCkge1xuICAgICAgICBjb25zdCBkYXRhID0gZWxlbWVudC5nZXRBdHRyaWJ1dGUoa2V5KTtcbiAgICAgICAgaWYgKGRhdGEpIHtcbiAgICAgICAgICAgIHJldHVybiBKU09OLnBhcnNlKGRhdGEpO1xuICAgICAgICB9XG4gICAgfVxuICAgIHRocm93IG5ldyBFcnJvcihgQ291bGQgbm90IGxvYWQgZGF0YSBmb3IgJHtrZXl9YCk7XG59XG5leHBvcnRzLmdldERhdGEgPSBnZXREYXRhO1xuZnVuY3Rpb24gZ2V0U2V0dGluZ3MoKSB7XG4gICAgaWYgKGNhY2hlZFNldHRpbmdzKSB7XG4gICAgICAgIHJldHVybiBjYWNoZWRTZXR0aW5ncztcbiAgICB9XG4gICAgY2FjaGVkU2V0dGluZ3MgPSBnZXREYXRhKCdkYXRhLXNldHRpbmdzJyk7XG4gICAgaWYgKGNhY2hlZFNldHRpbmdzKSB7XG4gICAgICAgIHJldHVybiBjYWNoZWRTZXR0aW5ncztcbiAgICB9XG4gICAgdGhyb3cgbmV3IEVycm9yKCdDb3VsZCBub3QgbG9hZCBzZXR0aW5ncycpO1xufVxuZXhwb3J0cy5nZXRTZXR0aW5ncyA9IGdldFNldHRpbmdzO1xuIiwiXCJ1c2Ugc3RyaWN0XCI7XG4vKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuICogIENvcHlyaWdodCAoYykgTWljcm9zb2Z0IENvcnBvcmF0aW9uLiBBbGwgcmlnaHRzIHJlc2VydmVkLlxuICogIExpY2Vuc2VkIHVuZGVyIHRoZSBNSVQgTGljZW5zZS4gU2VlIExpY2Vuc2UudHh0IGluIHRoZSBwcm9qZWN0IHJvb3QgZm9yIGxpY2Vuc2UgaW5mb3JtYXRpb24uXG4gKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cbk9iamVjdC5kZWZpbmVQcm9wZXJ0eShleHBvcnRzLCBcIl9fZXNNb2R1bGVcIiwgeyB2YWx1ZTogdHJ1ZSB9KTtcbmNvbnN0IGNzcF8xID0gcmVxdWlyZShcIi4vY3NwXCIpO1xuY29uc3QgbG9hZGluZ18xID0gcmVxdWlyZShcIi4vbG9hZGluZ1wiKTtcbndpbmRvdy5jc3BBbGVydGVyID0gbmV3IGNzcF8xLkNzcEFsZXJ0ZXIoKTtcbndpbmRvdy5zdHlsZUxvYWRpbmdNb25pdG9yID0gbmV3IGxvYWRpbmdfMS5TdHlsZUxvYWRpbmdNb25pdG9yKCk7XG4iLCJcInVzZSBzdHJpY3RcIjtcbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gKiAgQ29weXJpZ2h0IChjKSBNaWNyb3NvZnQgQ29ycG9yYXRpb24uIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiAgTGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBTZWUgTGljZW5zZS50eHQgaW4gdGhlIHByb2plY3Qgcm9vdCBmb3IgbGljZW5zZSBpbmZvcm1hdGlvbi5cbiAqLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuT2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsIFwiX19lc01vZHVsZVwiLCB7IHZhbHVlOiB0cnVlIH0pO1xuY29uc3Qgc2V0dGluZ3NfMSA9IHJlcXVpcmUoXCIuL3NldHRpbmdzXCIpO1xuY29uc3Qgc3RyaW5nc18xID0gcmVxdWlyZShcIi4vc3RyaW5nc1wiKTtcbi8qKlxuICogU2hvd3MgYW4gYWxlcnQgd2hlbiB0aGVyZSBpcyBhIGNvbnRlbnQgc2VjdXJpdHkgcG9saWN5IHZpb2xhdGlvbi5cbiAqL1xuY2xhc3MgQ3NwQWxlcnRlciB7XG4gICAgY29uc3RydWN0b3IoKSB7XG4gICAgICAgIHRoaXMuZGlkU2hvdyA9IGZhbHNlO1xuICAgICAgICB0aGlzLmRpZEhhdmVDc3BXYXJuaW5nID0gZmFsc2U7XG4gICAgICAgIGRvY3VtZW50LmFkZEV2ZW50TGlzdGVuZXIoJ3NlY3VyaXR5cG9saWN5dmlvbGF0aW9uJywgKCkgPT4ge1xuICAgICAgICAgICAgdGhpcy5vbkNzcFdhcm5pbmcoKTtcbiAgICAgICAgfSk7XG4gICAgICAgIHdpbmRvdy5hZGRFdmVudExpc3RlbmVyKCdtZXNzYWdlJywgKGV2ZW50KSA9PiB7XG4gICAgICAgICAgICBpZiAoZXZlbnQgJiYgZXZlbnQuZGF0YSAmJiBldmVudC5kYXRhLm5hbWUgPT09ICd2c2NvZGUtZGlkLWJsb2NrLXN2ZycpIHtcbiAgICAgICAgICAgICAgICB0aGlzLm9uQ3NwV2FybmluZygpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9KTtcbiAgICB9XG4gICAgc2V0UG9zdGVyKHBvc3Rlcikge1xuICAgICAgICB0aGlzLm1lc3NhZ2luZyA9IHBvc3RlcjtcbiAgICAgICAgaWYgKHRoaXMuZGlkSGF2ZUNzcFdhcm5pbmcpIHtcbiAgICAgICAgICAgIHRoaXMuc2hvd0NzcFdhcm5pbmcoKTtcbiAgICAgICAgfVxuICAgIH1cbiAgICBvbkNzcFdhcm5pbmcoKSB7XG4gICAgICAgIHRoaXMuZGlkSGF2ZUNzcFdhcm5pbmcgPSB0cnVlO1xuICAgICAgICB0aGlzLnNob3dDc3BXYXJuaW5nKCk7XG4gICAgfVxuICAgIHNob3dDc3BXYXJuaW5nKCkge1xuICAgICAgICBjb25zdCBzdHJpbmdzID0gc3RyaW5nc18xLmdldFN0cmluZ3MoKTtcbiAgICAgICAgY29uc3Qgc2V0dGluZ3MgPSBzZXR0aW5nc18xLmdldFNldHRpbmdzKCk7XG4gICAgICAgIGlmICh0aGlzLmRpZFNob3cgfHwgc2V0dGluZ3MuZGlzYWJsZVNlY3VyaXR5V2FybmluZ3MgfHwgIXRoaXMubWVzc2FnaW5nKSB7XG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cbiAgICAgICAgdGhpcy5kaWRTaG93ID0gdHJ1ZTtcbiAgICAgICAgY29uc3Qgbm90aWZpY2F0aW9uID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnYScpO1xuICAgICAgICBub3RpZmljYXRpb24uaW5uZXJUZXh0ID0gc3RyaW5ncy5jc3BBbGVydE1lc3NhZ2VUZXh0O1xuICAgICAgICBub3RpZmljYXRpb24uc2V0QXR0cmlidXRlKCdpZCcsICdjb2RlLWNzcC13YXJuaW5nJyk7XG4gICAgICAgIG5vdGlmaWNhdGlvbi5zZXRBdHRyaWJ1dGUoJ3RpdGxlJywgc3RyaW5ncy5jc3BBbGVydE1lc3NhZ2VUaXRsZSk7XG4gICAgICAgIG5vdGlmaWNhdGlvbi5zZXRBdHRyaWJ1dGUoJ3JvbGUnLCAnYnV0dG9uJyk7XG4gICAgICAgIG5vdGlmaWNhdGlvbi5zZXRBdHRyaWJ1dGUoJ2FyaWEtbGFiZWwnLCBzdHJpbmdzLmNzcEFsZXJ0TWVzc2FnZUxhYmVsKTtcbiAgICAgICAgbm90aWZpY2F0aW9uLm9uY2xpY2sgPSAoKSA9PiB7XG4gICAgICAgICAgICB0aGlzLm1lc3NhZ2luZy5wb3N0TWVzc2FnZSgnc2hvd1ByZXZpZXdTZWN1cml0eVNlbGVjdG9yJywgeyBzb3VyY2U6IHNldHRpbmdzLnNvdXJjZSB9KTtcbiAgICAgICAgfTtcbiAgICAgICAgZG9jdW1lbnQuYm9keS5hcHBlbmRDaGlsZChub3RpZmljYXRpb24pO1xuICAgIH1cbn1cbmV4cG9ydHMuQ3NwQWxlcnRlciA9IENzcEFsZXJ0ZXI7XG4iLCJcInVzZSBzdHJpY3RcIjtcbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gKiAgQ29weXJpZ2h0IChjKSBNaWNyb3NvZnQgQ29ycG9yYXRpb24uIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiAgTGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBTZWUgTGljZW5zZS50eHQgaW4gdGhlIHByb2plY3Qgcm9vdCBmb3IgbGljZW5zZSBpbmZvcm1hdGlvbi5cbiAqLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuT2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsIFwiX19lc01vZHVsZVwiLCB7IHZhbHVlOiB0cnVlIH0pO1xuZnVuY3Rpb24gZ2V0U3RyaW5ncygpIHtcbiAgICBjb25zdCBzdG9yZSA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCd2c2NvZGUtbWFya2Rvd24tcHJldmlldy1kYXRhJyk7XG4gICAgaWYgKHN0b3JlKSB7XG4gICAgICAgIGNvbnN0IGRhdGEgPSBzdG9yZS5nZXRBdHRyaWJ1dGUoJ2RhdGEtc3RyaW5ncycpO1xuICAgICAgICBpZiAoZGF0YSkge1xuICAgICAgICAgICAgcmV0dXJuIEpTT04ucGFyc2UoZGF0YSk7XG4gICAgICAgIH1cbiAgICB9XG4gICAgdGhyb3cgbmV3IEVycm9yKCdDb3VsZCBub3QgbG9hZCBzdHJpbmdzJyk7XG59XG5leHBvcnRzLmdldFN0cmluZ3MgPSBnZXRTdHJpbmdzO1xuIiwiXCJ1c2Ugc3RyaWN0XCI7XG5PYmplY3QuZGVmaW5lUHJvcGVydHkoZXhwb3J0cywgXCJfX2VzTW9kdWxlXCIsIHsgdmFsdWU6IHRydWUgfSk7XG5jbGFzcyBTdHlsZUxvYWRpbmdNb25pdG9yIHtcbiAgICBjb25zdHJ1Y3RvcigpIHtcbiAgICAgICAgdGhpcy51bmxvYWRlZFN0eWxlcyA9IFtdO1xuICAgICAgICB0aGlzLmZpbmlzaGVkTG9hZGluZyA9IGZhbHNlO1xuICAgICAgICBjb25zdCBvblN0eWxlTG9hZEVycm9yID0gKGV2ZW50KSA9PiB7XG4gICAgICAgICAgICBjb25zdCBzb3VyY2UgPSBldmVudC50YXJnZXQuZGF0YXNldC5zb3VyY2U7XG4gICAgICAgICAgICB0aGlzLnVubG9hZGVkU3R5bGVzLnB1c2goc291cmNlKTtcbiAgICAgICAgfTtcbiAgICAgICAgd2luZG93LmFkZEV2ZW50TGlzdGVuZXIoJ0RPTUNvbnRlbnRMb2FkZWQnLCAoKSA9PiB7XG4gICAgICAgICAgICBmb3IgKGNvbnN0IGxpbmsgb2YgZG9jdW1lbnQuZ2V0RWxlbWVudHNCeUNsYXNzTmFtZSgnY29kZS11c2VyLXN0eWxlJykpIHtcbiAgICAgICAgICAgICAgICBpZiAobGluay5kYXRhc2V0LnNvdXJjZSkge1xuICAgICAgICAgICAgICAgICAgICBsaW5rLm9uZXJyb3IgPSBvblN0eWxlTG9hZEVycm9yO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgfSk7XG4gICAgICAgIHdpbmRvdy5hZGRFdmVudExpc3RlbmVyKCdsb2FkJywgKCkgPT4ge1xuICAgICAgICAgICAgaWYgKCF0aGlzLnVubG9hZGVkU3R5bGVzLmxlbmd0aCkge1xuICAgICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIHRoaXMuZmluaXNoZWRMb2FkaW5nID0gdHJ1ZTtcbiAgICAgICAgICAgIGlmICh0aGlzLnBvc3Rlcikge1xuICAgICAgICAgICAgICAgIHRoaXMucG9zdGVyLnBvc3RNZXNzYWdlKCdwcmV2aWV3U3R5bGVMb2FkRXJyb3InLCB7IHVubG9hZGVkU3R5bGVzOiB0aGlzLnVubG9hZGVkU3R5bGVzIH0pO1xuICAgICAgICAgICAgfVxuICAgICAgICB9KTtcbiAgICB9XG4gICAgc2V0UG9zdGVyKHBvc3Rlcikge1xuICAgICAgICB0aGlzLnBvc3RlciA9IHBvc3RlcjtcbiAgICAgICAgaWYgKHRoaXMuZmluaXNoZWRMb2FkaW5nKSB7XG4gICAgICAgICAgICBwb3N0ZXIucG9zdE1lc3NhZ2UoJ3ByZXZpZXdTdHlsZUxvYWRFcnJvcicsIHsgdW5sb2FkZWRTdHlsZXM6IHRoaXMudW5sb2FkZWRTdHlsZXMgfSk7XG4gICAgICAgIH1cbiAgICB9XG59XG5leHBvcnRzLlN0eWxlTG9hZGluZ01vbml0b3IgPSBTdHlsZUxvYWRpbmdNb25pdG9yO1xuIl0sInNvdXJjZVJvb3QiOiIifQ== \ No newline at end of file diff --git a/extensions/markdown-language-features/package.json b/extensions/markdown-language-features/package.json index 29cef108b98d..bcc21a57aa72 100644 --- a/extensions/markdown-language-features/package.json +++ b/extensions/markdown-language-features/package.json @@ -25,7 +25,9 @@ "onCommand:markdown.showSource", "onCommand:markdown.showPreviewSecuritySelector", "onCommand:markdown.api.render", - "onWebviewPanel:markdown.preview" + "onCommand:notebook.showPreview", + "onWebviewPanel:markdown.preview", + "onWebviewEditor:vscode.markdown.preview.editor" ], "contributes": { "commands": [ @@ -307,6 +309,18 @@ ], "markdown.previewScripts": [ "./media/index.js" + ], + "webviewEditors": [ + { + "viewType": "vscode.markdown.preview.editor", + "displayName": "(Experimental) VS Code Markdown Preview", + "priority": "option", + "selector": [ + { + "filenamePattern": "*.md" + } + ] + } ] }, "scripts": { @@ -327,14 +341,14 @@ "@types/highlight.js": "9.12.3", "@types/lodash.throttle": "^4.1.3", "@types/markdown-it": "0.0.2", - "@types/node": "^10.14.8", + "@types/node": "^12.11.7", "lodash.throttle": "^4.1.1", "mocha-junit-reporter": "^1.17.0", "mocha-multi-reporters": "^1.1.7", - "ts-loader": "^4.0.1", - "typescript": "^3.3.1", + "ts-loader": "^6.2.1", + "typescript": "^3.7.2", "vscode": "^1.1.10", - "webpack": "^4.1.0", - "webpack-cli": "^2.0.10" + "webpack": "^4.41.2", + "webpack-cli": "^3.3.0" } } diff --git a/extensions/markdown-language-features/preview-src/index.ts b/extensions/markdown-language-features/preview-src/index.ts index ea34ba3b320a..ba524893fae6 100644 --- a/extensions/markdown-language-features/preview-src/index.ts +++ b/extensions/markdown-language-features/preview-src/index.ts @@ -129,7 +129,7 @@ document.addEventListener('dblclick', event => { } }); -const passThroughLinkSchemes = ['http:', 'https:', 'mailto:', 'vscode:', 'vscode-insiders']; +const passThroughLinkSchemes = ['http:', 'https:', 'mailto:', 'vscode:', 'vscode-insiders:']; document.addEventListener('click', event => { if (!event) { diff --git a/extensions/markdown-language-features/preview-src/scroll-sync.ts b/extensions/markdown-language-features/preview-src/scroll-sync.ts index 91fe31c2296e..f7a97f6a42d3 100644 --- a/extensions/markdown-language-features/preview-src/scroll-sync.ts +++ b/extensions/markdown-language-features/preview-src/scroll-sync.ts @@ -5,6 +5,7 @@ import { getSettings } from './settings'; +const codeLineClass = 'code-line'; function clamp(min: number, max: number, value: number) { return Math.min(max, Math.max(min, value)); @@ -25,9 +26,17 @@ const getCodeLineElements = (() => { return () => { if (!elements) { elements = [{ element: document.body, line: 0 }]; - for (const element of document.getElementsByClassName('code-line')) { + for (const element of document.getElementsByClassName(codeLineClass)) { const line = +element.getAttribute('data-line')!; - if (!isNaN(line)) { + if (isNaN(line)) { + continue; + } + + if (element.tagName === 'CODE' && element.parentElement && element.parentElement.tagName === 'PRE') { + // Fenched code blocks are a special case since the `code-line` can only be marked on + // the `` element and not the parent `
` element.
+					elements.push({ element: element.parentElement as HTMLElement, line });
+				} else {
 					elements.push({ element: element as HTMLElement, line });
 				}
 			}
@@ -67,7 +76,7 @@ export function getLineElementsAtPageOffset(offset: number): { previous: CodeLin
 	let hi = lines.length - 1;
 	while (lo + 1 < hi) {
 		const mid = Math.floor((lo + hi) / 2);
-		const bounds = lines[mid].element.getBoundingClientRect();
+		const bounds = getElementBounds(lines[mid]);
 		if (bounds.top + bounds.height >= position) {
 			hi = mid;
 		}
@@ -76,14 +85,35 @@ export function getLineElementsAtPageOffset(offset: number): { previous: CodeLin
 		}
 	}
 	const hiElement = lines[hi];
-	const hiBounds = hiElement.element.getBoundingClientRect();
+	const hiBounds = getElementBounds(hiElement);
 	if (hi >= 1 && hiBounds.top > position) {
 		const loElement = lines[lo];
 		return { previous: loElement, next: hiElement };
 	}
+	if (hi > 1 && hi < lines.length && hiBounds.top + hiBounds.height > position) {
+		return { previous: hiElement, next: lines[hi + 1] };
+	}
 	return { previous: hiElement };
 }
 
+function getElementBounds({ element }: CodeLineElement): { top: number, height: number } {
+	const myBounds = element.getBoundingClientRect();
+
+	// Some code line elements may contain other code line elements.
+	// In those cases, only take the height up to that child.
+	const codeLineChild = element.querySelector(`.${codeLineClass}`);
+	if (codeLineChild) {
+		const childBounds = codeLineChild.getBoundingClientRect();
+		const height = Math.max(1, (childBounds.top - myBounds.top));
+		return {
+			top: myBounds.top,
+			height: height
+		};
+	}
+
+	return myBounds;
+}
+
 /**
  * Attempt to reveal the element for a source line in the editor.
  */
@@ -102,7 +132,7 @@ export function scrollToRevealSourceLine(line: number) {
 		return;
 	}
 	let scrollTo = 0;
-	const rect = previous.element.getBoundingClientRect();
+	const rect = getElementBounds(previous);
 	const previousTop = rect.top;
 	if (next && next.line !== previous.line) {
 		// Between two elements. Go to percentage offset between them.
@@ -119,14 +149,13 @@ export function scrollToRevealSourceLine(line: number) {
 export function getEditorLineNumberForPageOffset(offset: number) {
 	const { previous, next } = getLineElementsAtPageOffset(offset);
 	if (previous) {
-		const previousBounds = previous.element.getBoundingClientRect();
+		const previousBounds = getElementBounds(previous);
 		const offsetFromPrevious = (offset - window.scrollY - previousBounds.top);
 		if (next) {
-			const progressBetweenElements = offsetFromPrevious / (next.element.getBoundingClientRect().top - previousBounds.top);
+			const progressBetweenElements = offsetFromPrevious / (getElementBounds(next).top - previousBounds.top);
 			const line = previous.line + progressBetweenElements * (next.line - previous.line);
 			return clampLine(line);
-		}
-		else {
+		} else {
 			const progressWithinElement = offsetFromPrevious / (previousBounds.height);
 			const line = previous.line + progressWithinElement;
 			return clampLine(line);
diff --git a/extensions/markdown-language-features/src/commands/showPreview.ts b/extensions/markdown-language-features/src/commands/showPreview.ts
index 659696e129d0..b58571dcb9de 100644
--- a/extensions/markdown-language-features/src/commands/showPreview.ts
+++ b/extensions/markdown-language-features/src/commands/showPreview.ts
@@ -6,9 +6,8 @@
 import * as vscode from 'vscode';
 
 import { Command } from '../commandManager';
-import { MarkdownPreviewManager } from '../features/previewManager';
+import { MarkdownPreviewManager, DynamicPreviewSettings } from '../features/previewManager';
 import { TelemetryReporter } from '../telemetryReporter';
-import { PreviewSettings } from '../features/preview';
 
 interface ShowPreviewSettings {
 	readonly sideBySide?: boolean;
@@ -39,7 +38,7 @@ async function showPreview(
 	}
 
 	const resourceColumn = (vscode.window.activeTextEditor && vscode.window.activeTextEditor.viewColumn) || vscode.ViewColumn.One;
-	webviewManager.preview(resource, {
+	webviewManager.openDynamicPreview(resource, {
 		resourceColumn: resourceColumn,
 		previewColumn: previewSettings.sideBySide ? resourceColumn + 1 : resourceColumn,
 		locked: !!previewSettings.locked
@@ -59,7 +58,7 @@ export class ShowPreviewCommand implements Command {
 		private readonly telemetryReporter: TelemetryReporter
 	) { }
 
-	public execute(mainUri?: vscode.Uri, allUris?: vscode.Uri[], previewSettings?: PreviewSettings) {
+	public execute(mainUri?: vscode.Uri, allUris?: vscode.Uri[], previewSettings?: DynamicPreviewSettings) {
 		for (const uri of Array.isArray(allUris) ? allUris : [mainUri]) {
 			showPreview(this.webviewManager, this.telemetryReporter, uri, {
 				sideBySide: false,
@@ -77,7 +76,7 @@ export class ShowPreviewToSideCommand implements Command {
 		private readonly telemetryReporter: TelemetryReporter
 	) { }
 
-	public execute(uri?: vscode.Uri, previewSettings?: PreviewSettings) {
+	public execute(uri?: vscode.Uri, previewSettings?: DynamicPreviewSettings) {
 		showPreview(this.webviewManager, this.telemetryReporter, uri, {
 			sideBySide: true,
 			locked: previewSettings && previewSettings.locked
diff --git a/extensions/markdown-language-features/src/extension.ts b/extensions/markdown-language-features/src/extension.ts
index 567d5291a70e..3e030be58fd8 100644
--- a/extensions/markdown-language-features/src/extension.ts
+++ b/extensions/markdown-language-features/src/extension.ts
@@ -54,9 +54,11 @@ function registerMarkdownLanguageFeatures(
 		{ language: 'markdown', scheme: 'untitled' }
 	];
 
+	const charPattern = '(\\p{Alphabetic}|\\p{Number}|\\p{Nonspacing_Mark})';
+
 	return vscode.Disposable.from(
 		vscode.languages.setLanguageConfiguration('markdown', {
-			wordPattern: new RegExp('(\\p{Alphabetic}|\\p{Number}|\\p{Nonspacing_Mark})+', 'ug'),
+			wordPattern: new RegExp(`${charPattern}((${charPattern}|[_])?${charPattern})*`, 'ug'),
 		}),
 		vscode.languages.registerDocumentSymbolProvider(selector, symbolProvider),
 		vscode.languages.registerDocumentLinkProvider(selector, new LinkProvider()),
diff --git a/extensions/markdown-language-features/src/features/documentLinkProvider.ts b/extensions/markdown-language-features/src/features/documentLinkProvider.ts
index 4554ad03f9bc..e601c59b84a5 100644
--- a/extensions/markdown-language-features/src/features/documentLinkProvider.ts
+++ b/extensions/markdown-language-features/src/features/documentLinkProvider.ts
@@ -7,7 +7,7 @@ import * as path from 'path';
 import * as vscode from 'vscode';
 import * as nls from 'vscode-nls';
 import { OpenDocumentLinkCommand } from '../commands/openDocumentLink';
-import { getUriForLinkWithKnownExternalScheme } from '../util/links';
+import { getUriForLinkWithKnownExternalScheme, isOfScheme, Schemes } from '../util/links';
 
 const localize = nls.loadMessageBundle();
 
@@ -18,6 +18,10 @@ function parseLink(
 ): { uri: vscode.Uri, tooltip?: string } {
 	const externalSchemeUri = getUriForLinkWithKnownExternalScheme(link);
 	if (externalSchemeUri) {
+		// Normalize VS Code links to target currently running version
+		if (isOfScheme(Schemes.vscode, link) || isOfScheme(Schemes['vscode-insiders'], link)) {
+			return { uri: vscode.Uri.parse(link).with({ scheme: vscode.env.uriScheme }) };
+		}
 		return { uri: externalSchemeUri };
 	}
 
diff --git a/extensions/markdown-language-features/src/features/preview.ts b/extensions/markdown-language-features/src/features/preview.ts
index 2624bcdadb60..aaf8083c3f6b 100644
--- a/extensions/markdown-language-features/src/features/preview.ts
+++ b/extensions/markdown-language-features/src/features/preview.ts
@@ -11,7 +11,7 @@ import { MarkdownContentProvider } from './previewContentProvider';
 import { Disposable } from '../util/dispose';
 
 import * as nls from 'vscode-nls';
-import { getVisibleLine, MarkdownFileTopmostLineMonitor } from '../util/topmostLineMonitor';
+import { getVisibleLine, TopmostLineMonitor } from '../util/topmostLineMonitor';
 import { MarkdownPreviewConfigurationManager } from './previewConfig';
 import { MarkdownContributionProvider, MarkdownContributions } from '../markdownExtensions';
 import { isMarkdownFile } from '../util/file';
@@ -72,11 +72,22 @@ export class PreviewDocumentVersion {
 	}
 }
 
-export class MarkdownPreview extends Disposable {
+interface DynamicPreviewInput {
+	readonly resource: vscode.Uri;
+	readonly resourceColumn: vscode.ViewColumn;
+	readonly locked: boolean;
+	readonly line?: number;
+}
+
+export class DynamicMarkdownPreview extends Disposable {
 
 	public static readonly viewType = 'markdown.preview';
 
+	private readonly delay = 300;
+
 	private _resource: vscode.Uri;
+	private readonly _resourceColumn: vscode.ViewColumn;
+
 	private _locked: boolean;
 
 	private readonly editor: vscode.WebviewPanel;
@@ -84,92 +95,65 @@ export class MarkdownPreview extends Disposable {
 	private line: number | undefined = undefined;
 	private firstUpdate = true;
 	private currentVersion?: PreviewDocumentVersion;
-	private forceUpdate = false;
 	private isScrolling = false;
 	private _disposed: boolean = false;
 	private imageInfo: { id: string, width: number, height: number; }[] = [];
 	private scrollToFragment: string | undefined;
 
-	public static async revive(
+	public static revive(
+		input: DynamicPreviewInput,
 		webview: vscode.WebviewPanel,
-		state: any,
 		contentProvider: MarkdownContentProvider,
 		previewConfigurations: MarkdownPreviewConfigurationManager,
 		logger: Logger,
-		topmostLineMonitor: MarkdownFileTopmostLineMonitor,
+		topmostLineMonitor: TopmostLineMonitor,
 		contributionProvider: MarkdownContributionProvider,
-	): Promise {
-		const resource = vscode.Uri.parse(state.resource);
-		const locked = state.locked;
-		const line = state.line;
-		const resourceColumn = state.resourceColumn;
-
-		const preview = new MarkdownPreview(
-			webview,
-			resource,
-			locked,
-			resourceColumn,
-			contentProvider,
-			previewConfigurations,
-			logger,
-			topmostLineMonitor,
-			contributionProvider);
-
-		preview.editor.webview.options = MarkdownPreview.getWebviewOptions(resource, contributionProvider.contributions);
-
-		if (!isNaN(line)) {
-			preview.line = line;
-		}
-		await preview.doUpdate();
-		return preview;
+	): DynamicMarkdownPreview {
+		webview.webview.options = DynamicMarkdownPreview.getWebviewOptions(input.resource, contributionProvider.contributions);
+		webview.title = DynamicMarkdownPreview.getPreviewTitle(input.resource, input.locked);
+
+		return new DynamicMarkdownPreview(webview, input,
+			contentProvider, previewConfigurations, logger, topmostLineMonitor, contributionProvider);
 	}
 
 	public static create(
-		resource: vscode.Uri,
+		input: DynamicPreviewInput,
 		previewColumn: vscode.ViewColumn,
-		resourceColumn: vscode.ViewColumn,
-		locked: boolean,
 		contentProvider: MarkdownContentProvider,
 		previewConfigurations: MarkdownPreviewConfigurationManager,
 		logger: Logger,
-		topmostLineMonitor: MarkdownFileTopmostLineMonitor,
+		topmostLineMonitor: TopmostLineMonitor,
 		contributionProvider: MarkdownContributionProvider
-	): MarkdownPreview {
+	): DynamicMarkdownPreview {
 		const webview = vscode.window.createWebviewPanel(
-			MarkdownPreview.viewType,
-			MarkdownPreview.getPreviewTitle(resource, locked),
+			DynamicMarkdownPreview.viewType,
+			DynamicMarkdownPreview.getPreviewTitle(input.resource, input.locked),
 			previewColumn, {
 			enableFindWidget: true,
-			...MarkdownPreview.getWebviewOptions(resource, contributionProvider.contributions)
+			...DynamicMarkdownPreview.getWebviewOptions(input.resource, contributionProvider.contributions)
 		});
 
-		return new MarkdownPreview(
-			webview,
-			resource,
-			locked,
-			resourceColumn,
-			contentProvider,
-			previewConfigurations,
-			logger,
-			topmostLineMonitor,
-			contributionProvider);
+		return new DynamicMarkdownPreview(webview, input,
+			contentProvider, previewConfigurations, logger, topmostLineMonitor, contributionProvider);
 	}
 
 	private constructor(
 		webview: vscode.WebviewPanel,
-		resource: vscode.Uri,
-		locked: boolean,
-		private readonly _resourceColumn: vscode.ViewColumn,
+		input: DynamicPreviewInput,
 		private readonly _contentProvider: MarkdownContentProvider,
 		private readonly _previewConfigurations: MarkdownPreviewConfigurationManager,
 		private readonly _logger: Logger,
-		topmostLineMonitor: MarkdownFileTopmostLineMonitor,
+		topmostLineMonitor: TopmostLineMonitor,
 		private readonly _contributionProvider: MarkdownContributionProvider,
 	) {
 		super();
-		this._resource = resource;
-		this._locked = locked;
+		this._resource = input.resource;
+		this._resourceColumn = input.resourceColumn;
+		this._locked = input.locked;
 		this.editor = webview;
+		if (!isNaN(input.line!)) {
+			this.line = input.line;
+		}
 
 		this._register(this.editor.onDidDispose(() => {
 			this.dispose();
@@ -190,7 +174,7 @@ export class MarkdownPreview extends Disposable {
 
 			switch (e.type) {
 				case 'cacheImageSizes':
-					this.onCacheImageSizes(e.body);
+					this.imageInfo = e.body;
 					break;
 
 				case 'revealLine':
@@ -221,7 +205,7 @@ export class MarkdownPreview extends Disposable {
 			}
 		}));
 
-		this._register(topmostLineMonitor.onDidChangeTopmostLine(event => {
+		this._register(topmostLineMonitor.onDidChanged(event => {
 			if (this.isPreviewOf(event.resource)) {
 				this.updateForView(event.resource, event.line);
 			}
@@ -239,9 +223,11 @@ export class MarkdownPreview extends Disposable {
 
 		this._register(vscode.window.onDidChangeActiveTextEditor(editor => {
 			if (editor && isMarkdownFile(editor.document) && !this._locked) {
-				this.update(editor.document.uri);
+				this.update(editor.document.uri, false);
 			}
 		}));
+
+		this.doUpdate();
 	}
 
 	private readonly _onDisposeEmitter = this._register(new vscode.EventEmitter());
@@ -278,7 +264,6 @@ export class MarkdownPreview extends Disposable {
 		this._onDisposeEmitter.fire();
 		this._onDisposeEmitter.dispose();
 
-		this._onDidChangeViewStateEmitter.dispose();
 		this.editor.dispose();
 		super.dispose();
 	}
@@ -297,7 +282,6 @@ export class MarkdownPreview extends Disposable {
 			}
 		}
 
-
 		// If we have changed resources, cancel any pending updates
 		const isResourceChange = resource.fsPath !== this._resource.fsPath;
 		if (isResourceChange) {
@@ -310,9 +294,9 @@ export class MarkdownPreview extends Disposable {
 		// Schedule update if none is pending
 		if (!this.throttleTimer) {
 			if (isResourceChange || this.firstUpdate) {
-				this.doUpdate();
+				this.doUpdate(isRefresh);
 			} else {
-				this.throttleTimer = setTimeout(() => this.doUpdate(), 300);
+				this.throttleTimer = setTimeout(() => this.doUpdate(isRefresh), this.delay);
 			}
 		}
 
@@ -320,7 +304,6 @@ export class MarkdownPreview extends Disposable {
 	}
 
 	public refresh() {
-		this.forceUpdate = true;
 		this.update(this._resource, true);
 	}
 
@@ -350,7 +333,7 @@ export class MarkdownPreview extends Disposable {
 		}
 	}
 
-	public matches(otherPreview: MarkdownPreview): boolean {
+	public matches(otherPreview: DynamicMarkdownPreview): boolean {
 		return this.matchesResource(otherPreview._resource, otherPreview.position, otherPreview._locked);
 	}
 
@@ -360,7 +343,7 @@ export class MarkdownPreview extends Disposable {
 
 	public toggleLock() {
 		this._locked = !this._locked;
-		this.editor.title = MarkdownPreview.getPreviewTitle(this._resource, this._locked);
+		this.editor.title = DynamicMarkdownPreview.getPreviewTitle(this._resource, this._locked);
 	}
 
 	private get iconPath() {
@@ -408,7 +391,7 @@ export class MarkdownPreview extends Disposable {
 		}
 	}
 
-	private async doUpdate(): Promise {
+	private async doUpdate(forceUpdate?: boolean): Promise {
 		if (this._disposed) {
 			return;
 		}
@@ -431,13 +414,12 @@ export class MarkdownPreview extends Disposable {
 		}
 
 		const pendingVersion = new PreviewDocumentVersion(markdownResource, document.version);
-		if (!this.forceUpdate && this.currentVersion && this.currentVersion.equals(pendingVersion)) {
+		if (!forceUpdate && this.currentVersion?.equals(pendingVersion)) {
 			if (this.line) {
 				this.updateForView(markdownResource, this.line);
 			}
 			return;
 		}
-		this.forceUpdate = false;
 
 		this.currentVersion = pendingVersion;
 		if (this._resource === markdownResource) {
@@ -463,7 +445,7 @@ export class MarkdownPreview extends Disposable {
 	): vscode.WebviewOptions {
 		return {
 			enableScripts: true,
-			localResourceRoots: MarkdownPreview.getLocalResourceRoots(resource, contributions)
+			localResourceRoots: DynamicMarkdownPreview.getLocalResourceRoots(resource, contributions)
 		};
 	}
 
@@ -508,6 +490,9 @@ export class MarkdownPreview extends Disposable {
 	}
 
 	private async onDidClickPreview(line: number): Promise {
+		// fix #82457, find currently opened but unfocused source tab
+		await vscode.commands.executeCommand('markdown.showSource');
+
 		for (const visibleEditor of vscode.window.visibleTextEditors) {
 			if (this.isPreviewOf(visibleEditor.document.uri)) {
 				const editor = await vscode.window.showTextDocument(visibleEditor.document, visibleEditor.viewColumn);
@@ -529,9 +514,9 @@ export class MarkdownPreview extends Disposable {
 	}
 
 	private setContent(html: string): void {
-		this.editor.title = MarkdownPreview.getPreviewTitle(this._resource, this._locked);
+		this.editor.title = DynamicMarkdownPreview.getPreviewTitle(this._resource, this._locked);
 		this.editor.iconPath = this.iconPath;
-		this.editor.webview.options = MarkdownPreview.getWebviewOptions(this._resource, this._contributionProvider.contributions);
+		this.editor.webview.options = DynamicMarkdownPreview.getWebviewOptions(this._resource, this._contributionProvider.contributions);
 		this.editor.webview.html = html;
 	}
 
@@ -559,14 +544,4 @@ export class MarkdownPreview extends Disposable {
 
 		vscode.commands.executeCommand('_markdown.openDocumentLink', { path: hrefPath, fragment, fromResource: this.resource });
 	}
-
-	private async onCacheImageSizes(imageInfo: { id: string, width: number, height: number; }[]) {
-		this.imageInfo = imageInfo;
-	}
-}
-
-export interface PreviewSettings {
-	readonly resourceColumn: vscode.ViewColumn;
-	readonly previewColumn: vscode.ViewColumn;
-	readonly locked: boolean;
 }
diff --git a/extensions/markdown-language-features/src/features/previewManager.ts b/extensions/markdown-language-features/src/features/previewManager.ts
index 787d65510fe0..21388327ce88 100644
--- a/extensions/markdown-language-features/src/features/previewManager.ts
+++ b/extensions/markdown-language-features/src/features/previewManager.ts
@@ -7,19 +7,61 @@ import * as vscode from 'vscode';
 import { Logger } from '../logger';
 import { MarkdownContributionProvider } from '../markdownExtensions';
 import { disposeAll, Disposable } from '../util/dispose';
-import { MarkdownFileTopmostLineMonitor } from '../util/topmostLineMonitor';
-import { MarkdownPreview, PreviewSettings } from './preview';
+import { TopmostLineMonitor } from '../util/topmostLineMonitor';
+import { DynamicMarkdownPreview } from './preview';
 import { MarkdownPreviewConfigurationManager } from './previewConfig';
 import { MarkdownContentProvider } from './previewContentProvider';
 
+export interface DynamicPreviewSettings {
+	readonly resourceColumn: vscode.ViewColumn;
+	readonly previewColumn: vscode.ViewColumn;
+	readonly locked: boolean;
+}
+
+class PreviewStore extends Disposable {
+
+	private readonly _previews = new Set();
+
+	public dispose(): void {
+		super.dispose();
+		for (const preview of this._previews) {
+			preview.dispose();
+		}
+		this._previews.clear();
+	}
+
+	[Symbol.iterator](): Iterator {
+		return this._previews[Symbol.iterator]();
+	}
+
+	public get(resource: vscode.Uri, previewSettings: DynamicPreviewSettings): DynamicMarkdownPreview | undefined {
+		for (const preview of this._previews) {
+			if (preview.matchesResource(resource, previewSettings.previewColumn, previewSettings.locked)) {
+				return preview;
+			}
+		}
+		return undefined;
+	}
 
-export class MarkdownPreviewManager extends Disposable implements vscode.WebviewPanelSerializer {
+	public add(preview: DynamicMarkdownPreview) {
+		this._previews.add(preview);
+	}
+
+	public delete(preview: DynamicMarkdownPreview) {
+		this._previews.delete(preview);
+	}
+}
+
+export class MarkdownPreviewManager extends Disposable implements vscode.WebviewPanelSerializer, vscode.WebviewEditorProvider {
 	private static readonly markdownPreviewActiveContextKey = 'markdownPreviewFocus';
 
-	private readonly _topmostLineMonitor = new MarkdownFileTopmostLineMonitor();
+	private readonly _topmostLineMonitor = new TopmostLineMonitor();
 	private readonly _previewConfigurations = new MarkdownPreviewConfigurationManager();
-	private readonly _previews: MarkdownPreview[] = [];
-	private _activePreview: MarkdownPreview | undefined = undefined;
+
+	private readonly _dynamicPreviews = this._register(new PreviewStore());
+	private readonly _staticPreviews = this._register(new PreviewStore());
+
+	private _activePreview: DynamicMarkdownPreview | undefined = undefined;
 
 	public constructor(
 		private readonly _contentProvider: MarkdownContentProvider,
@@ -27,46 +69,48 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview
 		private readonly _contributions: MarkdownContributionProvider
 	) {
 		super();
-		this._register(vscode.window.registerWebviewPanelSerializer(MarkdownPreview.viewType, this));
-	}
-
-	public dispose(): void {
-		super.dispose();
-		disposeAll(this._previews);
+		this._register(vscode.window.registerWebviewPanelSerializer(DynamicMarkdownPreview.viewType, this));
+		this._register(vscode.window.registerWebviewEditorProvider('vscode.markdown.preview.editor', this));
 	}
 
 	public refresh() {
-		for (const preview of this._previews) {
+		for (const preview of this._dynamicPreviews) {
+			preview.refresh();
+		}
+		for (const preview of this._staticPreviews) {
 			preview.refresh();
 		}
 	}
 
 	public updateConfiguration() {
-		for (const preview of this._previews) {
+		for (const preview of this._dynamicPreviews) {
+			preview.updateConfiguration();
+		}
+		for (const preview of this._staticPreviews) {
 			preview.updateConfiguration();
 		}
 	}
 
-	public preview(
+	public openDynamicPreview(
 		resource: vscode.Uri,
-		previewSettings: PreviewSettings
+		settings: DynamicPreviewSettings
 	): void {
-		let preview = this.getExistingPreview(resource, previewSettings);
+		let preview = this._dynamicPreviews.get(resource, settings);
 		if (preview) {
-			preview.reveal(previewSettings.previewColumn);
+			preview.reveal(settings.previewColumn);
 		} else {
-			preview = this.createNewPreview(resource, previewSettings);
+			preview = this.createNewDynamicPreview(resource, settings);
 		}
 
 		preview.update(resource);
 	}
 
 	public get activePreviewResource() {
-		return this._activePreview && this._activePreview.resource;
+		return this._activePreview?.resource;
 	}
 
 	public get activePreviewResourceColumn() {
-		return this._activePreview && this._activePreview.resourceColumn;
+		return this._activePreview?.resourceColumn;
 	}
 
 	public toggleLock() {
@@ -75,7 +119,7 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview
 			preview.toggleLock();
 
 			// Close any previews that are now redundant, such as having two dynamic previews in the same editor group
-			for (const otherPreview of this._previews) {
+			for (const otherPreview of this._dynamicPreviews) {
 				if (otherPreview !== preview && preview.matches(otherPreview)) {
 					otherPreview.dispose();
 				}
@@ -87,35 +131,50 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview
 		webview: vscode.WebviewPanel,
 		state: any
 	): Promise {
-		const preview = await MarkdownPreview.revive(
+		const resource = vscode.Uri.parse(state.resource);
+		const locked = state.locked;
+		const line = state.line;
+		const resourceColumn = state.resourceColumn;
+
+		const preview = await DynamicMarkdownPreview.revive(
+			{ resource, locked, line, resourceColumn },
 			webview,
-			state,
 			this._contentProvider,
 			this._previewConfigurations,
 			this._logger,
 			this._topmostLineMonitor,
 			this._contributions);
 
-		this.registerPreview(preview);
+		this.registerDynamicPreview(preview);
 	}
 
-	private getExistingPreview(
-		resource: vscode.Uri,
-		previewSettings: PreviewSettings
-	): MarkdownPreview | undefined {
-		return this._previews.find(preview =>
-			preview.matchesResource(resource, previewSettings.previewColumn, previewSettings.locked));
+	public async resolveWebviewEditor(
+		input: { readonly resource: vscode.Uri; },
+		webview: vscode.WebviewPanel
+	): Promise {
+		const preview = DynamicMarkdownPreview.revive(
+			{ resource: input.resource, locked: false, resourceColumn: vscode.ViewColumn.One },
+			webview,
+			this._contentProvider,
+			this._previewConfigurations,
+			this._logger,
+			this._topmostLineMonitor,
+			this._contributions);
+		this.registerStaticPreview(preview);
+		return {};
 	}
 
-	private createNewPreview(
+	private createNewDynamicPreview(
 		resource: vscode.Uri,
-		previewSettings: PreviewSettings
-	): MarkdownPreview {
-		const preview = MarkdownPreview.create(
-			resource,
+		previewSettings: DynamicPreviewSettings
+	): DynamicMarkdownPreview {
+		const preview = DynamicMarkdownPreview.create(
+			{
+				resource,
+				resourceColumn: previewSettings.resourceColumn,
+				locked: previewSettings.locked,
+			},
 			previewSettings.previewColumn,
-			previewSettings.resourceColumn,
-			previewSettings.locked,
 			this._contentProvider,
 			this._previewConfigurations,
 			this._logger,
@@ -124,34 +183,48 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview
 
 		this.setPreviewActiveContext(true);
 		this._activePreview = preview;
-		return this.registerPreview(preview);
+		return this.registerDynamicPreview(preview);
 	}
 
-	private registerPreview(
-		preview: MarkdownPreview
-	): MarkdownPreview {
-		this._previews.push(preview);
+	private registerDynamicPreview(preview: DynamicMarkdownPreview): DynamicMarkdownPreview {
+		this._dynamicPreviews.add(preview);
 
 		preview.onDispose(() => {
-			const existing = this._previews.indexOf(preview);
-			if (existing === -1) {
-				return;
-			}
+			this._dynamicPreviews.delete(preview);
+		});
 
-			this._previews.splice(existing, 1);
-			if (this._activePreview === preview) {
-				this.setPreviewActiveContext(false);
-				this._activePreview = undefined;
-			}
+		this.trackActive(preview);
+
+		preview.onDidChangeViewState(() => {
+			// Remove other dynamic previews in our column
+			disposeAll(Array.from(this._dynamicPreviews).filter(otherPreview => preview !== otherPreview && preview.matches(otherPreview)));
 		});
+		return preview;
+	}
+
+	private registerStaticPreview(preview: DynamicMarkdownPreview): DynamicMarkdownPreview {
+		this._staticPreviews.add(preview);
+
+		preview.onDispose(() => {
+			this._staticPreviews.delete(preview);
+		});
+
+		this.trackActive(preview);
+		return preview;
+	}
 
+	private trackActive(preview: DynamicMarkdownPreview): void {
 		preview.onDidChangeViewState(({ webviewPanel }) => {
-			disposeAll(this._previews.filter(otherPreview => preview !== otherPreview && preview!.matches(otherPreview)));
 			this.setPreviewActiveContext(webviewPanel.active);
 			this._activePreview = webviewPanel.active ? preview : undefined;
 		});
 
-		return preview;
+		preview.onDispose(() => {
+			if (this._activePreview === preview) {
+				this.setPreviewActiveContext(false);
+				this._activePreview = undefined;
+			}
+		});
 	}
 
 	private setPreviewActiveContext(value: boolean) {
diff --git a/extensions/markdown-language-features/src/logger.ts b/extensions/markdown-language-features/src/logger.ts
index 98da9ea4feac..c1bb92023fb9 100644
--- a/extensions/markdown-language-features/src/logger.ts
+++ b/extensions/markdown-language-features/src/logger.ts
@@ -41,13 +41,21 @@ export class Logger {
 
 	public log(message: string, data?: any): void {
 		if (this.trace === Trace.Verbose) {
-			this.appendLine(`[Log - ${(new Date().toLocaleTimeString())}] ${message}`);
+			this.appendLine(`[Log - ${this.now()}] ${message}`);
 			if (data) {
 				this.appendLine(Logger.data2String(data));
 			}
 		}
 	}
 
+
+	private now(): string {
+		const now = new Date();
+		return padLeft(now.getUTCHours() + '', 2, '0')
+			+ ':' + padLeft(now.getMinutes() + '', 2, '0')
+			+ ':' + padLeft(now.getUTCSeconds() + '', 2, '0') + '.' + now.getMilliseconds();
+	}
+
 	public updateConfiguration() {
 		this.trace = this.readTrace();
 	}
@@ -73,3 +81,7 @@ export class Logger {
 		return JSON.stringify(data, undefined, 2);
 	}
 }
+
+function padLeft(s: string, n: number, pad = ' ') {
+	return pad.repeat(Math.max(0, n - s.length)) + s;
+}
diff --git a/extensions/markdown-language-features/src/markdownEngine.ts b/extensions/markdown-language-features/src/markdownEngine.ts
index 74d7634d8a06..079bb4873c19 100644
--- a/extensions/markdown-language-features/src/markdownEngine.ts
+++ b/extensions/markdown-language-features/src/markdownEngine.ts
@@ -234,6 +234,11 @@ export class MarkdownEngine {
 		const normalizeLink = md.normalizeLink;
 		md.normalizeLink = (link: string) => {
 			try {
+				// Normalize VS Code schemes to target the current version
+				if (isOfScheme(Schemes.vscode, link) || isOfScheme(Schemes['vscode-insiders'], link)) {
+					return normalizeLink(vscode.Uri.parse(link).with({ scheme: vscode.env.uriScheme }).toString());
+				}
+
 				// If original link doesn't look like a url with a scheme, assume it must be a link to a file in workspace
 				if (!/^[a-z\-]+:/i.test(link)) {
 					// Use a fake scheme for parsing
@@ -268,7 +273,11 @@ export class MarkdownEngine {
 		const validateLink = md.validateLink;
 		md.validateLink = (link: string) => {
 			// support file:// links
-			return validateLink(link) || isOfScheme(Schemes.file, link) || /^data:image\/.*?;/.test(link);
+			return validateLink(link)
+				|| isOfScheme(Schemes.file, link)
+				|| isOfScheme(Schemes.vscode, link)
+				|| isOfScheme(Schemes['vscode-insiders'], link)
+				|| /^data:image\/.*?;/.test(link);
 		};
 	}
 
diff --git a/extensions/markdown-language-features/src/test/index.ts b/extensions/markdown-language-features/src/test/index.ts
index a6ac226afd59..c8a387eeec4f 100644
--- a/extensions/markdown-language-features/src/test/index.ts
+++ b/extensions/markdown-language-features/src/test/index.ts
@@ -10,7 +10,7 @@ const suite = 'Integration Markdown Tests';
 
 const options: any = {
 	ui: 'tdd',
-	useColors: true,
+	useColors: (!process.env.BUILD_ARTIFACTSTAGINGDIRECTORY && process.platform !== 'win32'),
 	timeout: 60000
 };
 
diff --git a/extensions/markdown-language-features/src/util/links.ts b/extensions/markdown-language-features/src/util/links.ts
index b4b0e3533114..fd0a7e173ce0 100644
--- a/extensions/markdown-language-features/src/util/links.ts
+++ b/extensions/markdown-language-features/src/util/links.ts
@@ -13,7 +13,7 @@ export const Schemes = {
 	data: 'data:',
 	vscode: 'vscode:',
 	'vscode-insiders': 'vscode-insiders:',
-	'vscode-resource': 'vscode-resource',
+	'vscode-resource': 'vscode-resource:',
 };
 
 const knownSchemes = [
diff --git a/extensions/markdown-language-features/src/util/topmostLineMonitor.ts b/extensions/markdown-language-features/src/util/topmostLineMonitor.ts
index 389149031e12..acf08006cf68 100644
--- a/extensions/markdown-language-features/src/util/topmostLineMonitor.ts
+++ b/extensions/markdown-language-features/src/util/topmostLineMonitor.ts
@@ -4,33 +4,28 @@
  *--------------------------------------------------------------------------------------------*/
 
 import * as vscode from 'vscode';
-import { disposeAll } from '../util/dispose';
+import { Disposable } from '../util/dispose';
 import { isMarkdownFile } from './file';
 
-export class MarkdownFileTopmostLineMonitor {
-	private readonly disposables: vscode.Disposable[] = [];
+export class TopmostLineMonitor extends Disposable {
 
 	private readonly pendingUpdates = new Map();
-
 	private readonly throttle = 50;
 
 	constructor() {
-		vscode.window.onDidChangeTextEditorVisibleRanges(event => {
+		super();
+		this._register(vscode.window.onDidChangeTextEditorVisibleRanges(event => {
 			if (isMarkdownFile(event.textEditor.document)) {
 				const line = getVisibleLine(event.textEditor);
 				if (typeof line === 'number') {
 					this.updateLine(event.textEditor.document.uri, line);
 				}
 			}
-		}, null, this.disposables);
-	}
-
-	dispose() {
-		disposeAll(this.disposables);
+		}));
 	}
 
-	private readonly _onDidChangeTopmostLineEmitter = new vscode.EventEmitter<{ resource: vscode.Uri, line: number }>();
-	public readonly onDidChangeTopmostLine = this._onDidChangeTopmostLineEmitter.event;
+	private readonly _onChanged = this._register(new vscode.EventEmitter<{ readonly resource: vscode.Uri, readonly line: number }>());
+	public readonly onDidChanged = this._onChanged.event;
 
 	private updateLine(
 		resource: vscode.Uri,
@@ -41,7 +36,7 @@ export class MarkdownFileTopmostLineMonitor {
 			// schedule update
 			setTimeout(() => {
 				if (this.pendingUpdates.has(key)) {
-					this._onDidChangeTopmostLineEmitter.fire({
+					this._onChanged.fire({
 						resource,
 						line: this.pendingUpdates.get(key) as number
 					});
diff --git a/extensions/markdown-language-features/yarn.lock b/extensions/markdown-language-features/yarn.lock
index ccaebc2cbe6f..956c45715238 100644
--- a/extensions/markdown-language-features/yarn.lock
+++ b/extensions/markdown-language-features/yarn.lock
@@ -2,11 +2,6 @@
 # yarn lockfile v1
 
 
-"@sindresorhus/is@^0.7.0":
-  version "0.7.0"
-  resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd"
-  integrity sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow==
-
 "@types/highlight.js@9.12.3":
   version "9.12.3"
   resolved "https://registry.yarnpkg.com/@types/highlight.js/-/highlight.js-9.12.3.tgz#b672cfaac25cbbc634a0fd92c515f66faa18dbca"
@@ -29,33 +24,192 @@
   resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-0.0.2.tgz#5d9ad19e6e6508cdd2f2596df86fd0aade598660"
   integrity sha1-XZrRnm5lCM3S8llt+G/Qqt5ZhmA=
 
-"@types/node@^10.14.8":
-  version "10.14.8"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-10.14.8.tgz#fe444203ecef1162348cd6deb76c62477b2cc6e9"
-  integrity sha512-I4+DbJEhLEg4/vIy/2gkWDvXBOOtPKV9EnLhYjMoqxcRW+TTZtUftkHktz/a8suoD5mUL7m6ReLrkPvSsCQQmw==
+"@types/node@^12.11.7":
+  version "12.11.7"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-12.11.7.tgz#57682a9771a3f7b09c2497f28129a0462966524a"
+  integrity sha512-JNbGaHFCLwgHn/iCckiGSOZ1XYHsKFwREtzPwSGCVld1SGhOlmZw2D4ZI94HQCrBHbADzW9m4LER/8olJTRGHA==
+
+"@webassemblyjs/ast@1.8.5":
+  version "1.8.5"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.8.5.tgz#51b1c5fe6576a34953bf4b253df9f0d490d9e359"
+  integrity sha512-aJMfngIZ65+t71C3y2nBBg5FFG0Okt9m0XEgWZ7Ywgn1oMAT8cNwx00Uv1cQyHtidq0Xn94R4TAywO+LCQ+ZAQ==
+  dependencies:
+    "@webassemblyjs/helper-module-context" "1.8.5"
+    "@webassemblyjs/helper-wasm-bytecode" "1.8.5"
+    "@webassemblyjs/wast-parser" "1.8.5"
+
+"@webassemblyjs/floating-point-hex-parser@1.8.5":
+  version "1.8.5"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.8.5.tgz#1ba926a2923613edce496fd5b02e8ce8a5f49721"
+  integrity sha512-9p+79WHru1oqBh9ewP9zW95E3XAo+90oth7S5Re3eQnECGq59ly1Ri5tsIipKGpiStHsUYmY3zMLqtk3gTcOtQ==
+
+"@webassemblyjs/helper-api-error@1.8.5":
+  version "1.8.5"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.8.5.tgz#c49dad22f645227c5edb610bdb9697f1aab721f7"
+  integrity sha512-Za/tnzsvnqdaSPOUXHyKJ2XI7PDX64kWtURyGiJJZKVEdFOsdKUCPTNEVFZq3zJ2R0G5wc2PZ5gvdTRFgm81zA==
+
+"@webassemblyjs/helper-buffer@1.8.5":
+  version "1.8.5"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.8.5.tgz#fea93e429863dd5e4338555f42292385a653f204"
+  integrity sha512-Ri2R8nOS0U6G49Q86goFIPNgjyl6+oE1abW1pS84BuhP1Qcr5JqMwRFT3Ah3ADDDYGEgGs1iyb1DGX+kAi/c/Q==
+
+"@webassemblyjs/helper-code-frame@1.8.5":
+  version "1.8.5"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.8.5.tgz#9a740ff48e3faa3022b1dff54423df9aa293c25e"
+  integrity sha512-VQAadSubZIhNpH46IR3yWO4kZZjMxN1opDrzePLdVKAZ+DFjkGD/rf4v1jap744uPVU6yjL/smZbRIIJTOUnKQ==
+  dependencies:
+    "@webassemblyjs/wast-printer" "1.8.5"
+
+"@webassemblyjs/helper-fsm@1.8.5":
+  version "1.8.5"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-fsm/-/helper-fsm-1.8.5.tgz#ba0b7d3b3f7e4733da6059c9332275d860702452"
+  integrity sha512-kRuX/saORcg8se/ft6Q2UbRpZwP4y7YrWsLXPbbmtepKr22i8Z4O3V5QE9DbZK908dh5Xya4Un57SDIKwB9eow==
+
+"@webassemblyjs/helper-module-context@1.8.5":
+  version "1.8.5"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-module-context/-/helper-module-context-1.8.5.tgz#def4b9927b0101dc8cbbd8d1edb5b7b9c82eb245"
+  integrity sha512-/O1B236mN7UNEU4t9X7Pj38i4VoU8CcMHyy3l2cV/kIF4U5KoHXDVqcDuOs1ltkac90IM4vZdHc52t1x8Yfs3g==
+  dependencies:
+    "@webassemblyjs/ast" "1.8.5"
+    mamacro "^0.0.3"
+
+"@webassemblyjs/helper-wasm-bytecode@1.8.5":
+  version "1.8.5"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.8.5.tgz#537a750eddf5c1e932f3744206551c91c1b93e61"
+  integrity sha512-Cu4YMYG3Ddl72CbmpjU/wbP6SACcOPVbHN1dI4VJNJVgFwaKf1ppeFJrwydOG3NDHxVGuCfPlLZNyEdIYlQ6QQ==
+
+"@webassemblyjs/helper-wasm-section@1.8.5":
+  version "1.8.5"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.8.5.tgz#74ca6a6bcbe19e50a3b6b462847e69503e6bfcbf"
+  integrity sha512-VV083zwR+VTrIWWtgIUpqfvVdK4ff38loRmrdDBgBT8ADXYsEZ5mPQ4Nde90N3UYatHdYoDIFb7oHzMncI02tA==
+  dependencies:
+    "@webassemblyjs/ast" "1.8.5"
+    "@webassemblyjs/helper-buffer" "1.8.5"
+    "@webassemblyjs/helper-wasm-bytecode" "1.8.5"
+    "@webassemblyjs/wasm-gen" "1.8.5"
+
+"@webassemblyjs/ieee754@1.8.5":
+  version "1.8.5"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.8.5.tgz#712329dbef240f36bf57bd2f7b8fb9bf4154421e"
+  integrity sha512-aaCvQYrvKbY/n6wKHb/ylAJr27GglahUO89CcGXMItrOBqRarUMxWLJgxm9PJNuKULwN5n1csT9bYoMeZOGF3g==
+  dependencies:
+    "@xtuc/ieee754" "^1.2.0"
+
+"@webassemblyjs/leb128@1.8.5":
+  version "1.8.5"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.8.5.tgz#044edeb34ea679f3e04cd4fd9824d5e35767ae10"
+  integrity sha512-plYUuUwleLIziknvlP8VpTgO4kqNaH57Y3JnNa6DLpu/sGcP6hbVdfdX5aHAV716pQBKrfuU26BJK29qY37J7A==
+  dependencies:
+    "@xtuc/long" "4.2.2"
+
+"@webassemblyjs/utf8@1.8.5":
+  version "1.8.5"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.8.5.tgz#a8bf3b5d8ffe986c7c1e373ccbdc2a0915f0cedc"
+  integrity sha512-U7zgftmQriw37tfD934UNInokz6yTmn29inT2cAetAsaU9YeVCveWEwhKL1Mg4yS7q//NGdzy79nlXh3bT8Kjw==
+
+"@webassemblyjs/wasm-edit@1.8.5":
+  version "1.8.5"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.8.5.tgz#962da12aa5acc1c131c81c4232991c82ce56e01a"
+  integrity sha512-A41EMy8MWw5yvqj7MQzkDjU29K7UJq1VrX2vWLzfpRHt3ISftOXqrtojn7nlPsZ9Ijhp5NwuODuycSvfAO/26Q==
+  dependencies:
+    "@webassemblyjs/ast" "1.8.5"
+    "@webassemblyjs/helper-buffer" "1.8.5"
+    "@webassemblyjs/helper-wasm-bytecode" "1.8.5"
+    "@webassemblyjs/helper-wasm-section" "1.8.5"
+    "@webassemblyjs/wasm-gen" "1.8.5"
+    "@webassemblyjs/wasm-opt" "1.8.5"
+    "@webassemblyjs/wasm-parser" "1.8.5"
+    "@webassemblyjs/wast-printer" "1.8.5"
+
+"@webassemblyjs/wasm-gen@1.8.5":
+  version "1.8.5"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.8.5.tgz#54840766c2c1002eb64ed1abe720aded714f98bc"
+  integrity sha512-BCZBT0LURC0CXDzj5FXSc2FPTsxwp3nWcqXQdOZE4U7h7i8FqtFK5Egia6f9raQLpEKT1VL7zr4r3+QX6zArWg==
+  dependencies:
+    "@webassemblyjs/ast" "1.8.5"
+    "@webassemblyjs/helper-wasm-bytecode" "1.8.5"
+    "@webassemblyjs/ieee754" "1.8.5"
+    "@webassemblyjs/leb128" "1.8.5"
+    "@webassemblyjs/utf8" "1.8.5"
+
+"@webassemblyjs/wasm-opt@1.8.5":
+  version "1.8.5"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.8.5.tgz#b24d9f6ba50394af1349f510afa8ffcb8a63d264"
+  integrity sha512-HKo2mO/Uh9A6ojzu7cjslGaHaUU14LdLbGEKqTR7PBKwT6LdPtLLh9fPY33rmr5wcOMrsWDbbdCHq4hQUdd37Q==
+  dependencies:
+    "@webassemblyjs/ast" "1.8.5"
+    "@webassemblyjs/helper-buffer" "1.8.5"
+    "@webassemblyjs/wasm-gen" "1.8.5"
+    "@webassemblyjs/wasm-parser" "1.8.5"
+
+"@webassemblyjs/wasm-parser@1.8.5":
+  version "1.8.5"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.8.5.tgz#21576f0ec88b91427357b8536383668ef7c66b8d"
+  integrity sha512-pi0SYE9T6tfcMkthwcgCpL0cM9nRYr6/6fjgDtL6q/ZqKHdMWvxitRi5JcZ7RI4SNJJYnYNaWy5UUrHQy998lw==
+  dependencies:
+    "@webassemblyjs/ast" "1.8.5"
+    "@webassemblyjs/helper-api-error" "1.8.5"
+    "@webassemblyjs/helper-wasm-bytecode" "1.8.5"
+    "@webassemblyjs/ieee754" "1.8.5"
+    "@webassemblyjs/leb128" "1.8.5"
+    "@webassemblyjs/utf8" "1.8.5"
+
+"@webassemblyjs/wast-parser@1.8.5":
+  version "1.8.5"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-parser/-/wast-parser-1.8.5.tgz#e10eecd542d0e7bd394f6827c49f3df6d4eefb8c"
+  integrity sha512-daXC1FyKWHF1i11obK086QRlsMsY4+tIOKgBqI1lxAnkp9xe9YMcgOxm9kLe+ttjs5aWV2KKE1TWJCN57/Btsg==
+  dependencies:
+    "@webassemblyjs/ast" "1.8.5"
+    "@webassemblyjs/floating-point-hex-parser" "1.8.5"
+    "@webassemblyjs/helper-api-error" "1.8.5"
+    "@webassemblyjs/helper-code-frame" "1.8.5"
+    "@webassemblyjs/helper-fsm" "1.8.5"
+    "@xtuc/long" "4.2.2"
+
+"@webassemblyjs/wast-printer@1.8.5":
+  version "1.8.5"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.8.5.tgz#114bbc481fd10ca0e23b3560fa812748b0bae5bc"
+  integrity sha512-w0U0pD4EhlnvRyeJzBqaVSJAo9w/ce7/WPogeXLzGkO6hzhr4GnQIZ4W4uUt5b9ooAaXPtnXlj0gzsXEOUNYMg==
+  dependencies:
+    "@webassemblyjs/ast" "1.8.5"
+    "@webassemblyjs/wast-parser" "1.8.5"
+    "@xtuc/long" "4.2.2"
+
+"@xtuc/ieee754@^1.2.0":
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790"
+  integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==
+
+"@xtuc/long@4.2.2":
+  version "4.2.2"
+  resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d"
+  integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==
 
 abbrev@1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
   integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==
 
-acorn-dynamic-import@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/acorn-dynamic-import/-/acorn-dynamic-import-3.0.0.tgz#901ceee4c7faaef7e07ad2a47e890675da50a278"
-  integrity sha512-zVWV8Z8lislJoOKKqdNMOB+s6+XV5WERty8MnKBeFgwA+19XJjJHs2RP5dzM57FftIs+jQnRToLiWazKr6sSWg==
-  dependencies:
-    acorn "^5.0.0"
+acorn@^6.2.1:
+  version "6.3.0"
+  resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.3.0.tgz#0087509119ffa4fc0a0041d1e93a417e68cb856e"
+  integrity sha512-/czfa8BwS88b9gWQVhc8eknunSA2DoJpJyTQkhheIf5E48u1N0R4q/YxxsAeqRrmK9TQ/uYfgLDfZo91UlANIA==
 
-acorn@^5.0.0:
-  version "5.5.0"
-  resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.5.0.tgz#1abb587fbf051f94e3de20e6b26ef910b1828298"
-  integrity sha512-arn53F07VXmls4o4pUhSzBa4fvaagPRe7AVZ8l7NHxFWUie2DsuFSBMMNAkgzRlOhEhzAnxeKyaWVzOH4xqp/g==
+ajv-errors@^1.0.0:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d"
+  integrity sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==
 
 ajv-keywords@^3.1.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.1.0.tgz#ac2b27939c543e95d2c06e7f7f5c27be4aa543be"
   integrity sha1-rCsnk5xUPpXSwG5/f1wnvkqlQ74=
 
+ajv-keywords@^3.4.1:
+  version "3.4.1"
+  resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.4.1.tgz#ef916e271c64ac12171fd8384eaae6b2345854da"
+  integrity sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==
+
 ajv@^4.9.1:
   version "4.11.8"
   resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536"
@@ -83,6 +237,16 @@ ajv@^6.1.0:
     fast-json-stable-stringify "^2.0.0"
     json-schema-traverse "^0.3.0"
 
+ajv@^6.10.2:
+  version "6.10.2"
+  resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.2.tgz#d3cea04d6b017b2894ad69040fec8b623eb4bd52"
+  integrity sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==
+  dependencies:
+    fast-deep-equal "^2.0.1"
+    fast-json-stable-stringify "^2.0.0"
+    json-schema-traverse "^0.4.1"
+    uri-js "^4.2.2"
+
 ansi-cyan@^0.1.1:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/ansi-cyan/-/ansi-cyan-0.1.1.tgz#538ae528af8982f28ae30d86f2f17456d2609873"
@@ -90,16 +254,6 @@ ansi-cyan@^0.1.1:
   dependencies:
     ansi-wrap "0.1.0"
 
-ansi-escapes@^1.0.0, ansi-escapes@^1.1.0:
-  version "1.4.0"
-  resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e"
-  integrity sha1-06ioOzGapneTZisT52HHkRQiMG4=
-
-ansi-escapes@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.0.0.tgz#ec3e8b4e9f8064fc02c3ac9b65f1c275bda8ef92"
-  integrity sha512-O/klc27mWNUigtv0F8NJWbLF00OcegQalkqKURWdosW08YZKi4m6CnSUSvIZG1otNJbTWhN01Hhz389DW7mvDQ==
-
 ansi-gray@^0.1.1:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/ansi-gray/-/ansi-gray-0.1.1.tgz#2962cf54ec9792c48510a3deb524436861ef7251"
@@ -124,33 +278,28 @@ ansi-regex@^3.0.0:
   resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998"
   integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=
 
+ansi-regex@^4.1.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997"
+  integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==
+
 ansi-styles@^2.2.1:
   version "2.2.1"
   resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
   integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=
 
-ansi-styles@^3.2.1:
+ansi-styles@^3.2.0, ansi-styles@^3.2.1:
   version "3.2.1"
   resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
   integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==
   dependencies:
     color-convert "^1.9.0"
 
-ansi-styles@~1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-1.0.0.tgz#cb102df1c56f5123eab8b67cd7b98027a0279178"
-  integrity sha1-yxAt8cVvUSPquLZ817mAJ6AnkXg=
-
 ansi-wrap@0.1.0:
   version "0.1.0"
   resolved "https://registry.yarnpkg.com/ansi-wrap/-/ansi-wrap-0.1.0.tgz#a82250ddb0015e9a27ca82e82ea603bbfa45efaf"
   integrity sha1-qCJQ3bABXponyoLoLqYDu/pF768=
 
-any-observable@^0.2.0:
-  version "0.2.0"
-  resolved "https://registry.yarnpkg.com/any-observable/-/any-observable-0.2.0.tgz#c67870058003579009083f54ac0abafb5c33d242"
-  integrity sha1-xnhwBYADV5AJCD9UrAq6+1wz0kI=
-
 anymatch@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb"
@@ -188,11 +337,6 @@ argparse@^1.0.7:
   dependencies:
     sprintf-js "~1.0.2"
 
-argv@0.0.2:
-  version "0.0.2"
-  resolved "https://registry.yarnpkg.com/argv/-/argv-0.0.2.tgz#ecbd16f8949b157183711b1bda334f37840185ab"
-  integrity sha1-7L0W+JSbFXGDcRsb2jNPN4QBhas=
-
 arr-diff@^1.0.1:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-1.1.0.tgz#687c32758163588fef7de7b36fabe495eb1a399a"
@@ -301,33 +445,11 @@ assign-symbols@^1.0.0:
   resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367"
   integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=
 
-ast-types@0.10.1:
-  version "0.10.1"
-  resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.10.1.tgz#f52fca9715579a14f841d67d7f8d25432ab6a3dd"
-  integrity sha512-UY7+9DPzlJ9VM8eY0b2TUZcZvF+1pO0hzMtAyjBYKhOmnvRlqYNYnWdtsMj0V16CGaMlpL0G1jnLbLo4AyotuQ==
-
-ast-types@0.11.2:
-  version "0.11.2"
-  resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.11.2.tgz#cc4e1d15a36b39979a1986fe1e91321cbfae7783"
-  integrity sha512-aL+pcOQ+6dpWd0xrUe+Obo2CgdkFvsntkXEmzZKqEN4cR0PStF+1MBuc4V+YZsv4Q36luvyjG7F4lc+wH2bmag==
-
 async-each@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d"
   integrity sha1-GdOGodntxufByF04iu28xW0zYC0=
 
-async@^1.5.0:
-  version "1.5.2"
-  resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
-  integrity sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=
-
-async@^2.0.0:
-  version "2.6.0"
-  resolved "https://registry.yarnpkg.com/async/-/async-2.6.0.tgz#61a29abb6fcc026fea77e56d1c6ec53a795951f4"
-  integrity sha512-xAfGg1/NTLBBKlHFmnd7PlmUW9KhVQIUuSrYem9xzFUZy13ScvtyGGejaae9iAVRiRq9+Cx7DPFaAAhCpyxyPw==
-  dependencies:
-    lodash "^4.14.0"
-
 asynckit@^0.4.0:
   version "0.4.0"
   resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
@@ -353,663 +475,6 @@ aws4@^1.2.1, aws4@^1.6.0:
   resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e"
   integrity sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=
 
-babel-code-frame@^6.26.0:
-  version "6.26.0"
-  resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b"
-  integrity sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=
-  dependencies:
-    chalk "^1.1.3"
-    esutils "^2.0.2"
-    js-tokens "^3.0.2"
-
-babel-core@^6.26.0:
-  version "6.26.0"
-  resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.26.0.tgz#af32f78b31a6fcef119c87b0fd8d9753f03a0bb8"
-  integrity sha1-rzL3izGm/O8RnIew/Y2XU/A6C7g=
-  dependencies:
-    babel-code-frame "^6.26.0"
-    babel-generator "^6.26.0"
-    babel-helpers "^6.24.1"
-    babel-messages "^6.23.0"
-    babel-register "^6.26.0"
-    babel-runtime "^6.26.0"
-    babel-template "^6.26.0"
-    babel-traverse "^6.26.0"
-    babel-types "^6.26.0"
-    babylon "^6.18.0"
-    convert-source-map "^1.5.0"
-    debug "^2.6.8"
-    json5 "^0.5.1"
-    lodash "^4.17.4"
-    minimatch "^3.0.4"
-    path-is-absolute "^1.0.1"
-    private "^0.1.7"
-    slash "^1.0.0"
-    source-map "^0.5.6"
-
-babel-generator@^6.26.0:
-  version "6.26.1"
-  resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.1.tgz#1844408d3b8f0d35a404ea7ac180f087a601bd90"
-  integrity sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==
-  dependencies:
-    babel-messages "^6.23.0"
-    babel-runtime "^6.26.0"
-    babel-types "^6.26.0"
-    detect-indent "^4.0.0"
-    jsesc "^1.3.0"
-    lodash "^4.17.4"
-    source-map "^0.5.7"
-    trim-right "^1.0.1"
-
-babel-helper-bindify-decorators@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-helper-bindify-decorators/-/babel-helper-bindify-decorators-6.24.1.tgz#14c19e5f142d7b47f19a52431e52b1ccbc40a330"
-  integrity sha1-FMGeXxQte0fxmlJDHlKxzLxAozA=
-  dependencies:
-    babel-runtime "^6.22.0"
-    babel-traverse "^6.24.1"
-    babel-types "^6.24.1"
-
-babel-helper-builder-binary-assignment-operator-visitor@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz#cce4517ada356f4220bcae8a02c2b346f9a56664"
-  integrity sha1-zORReto1b0IgvK6KAsKzRvmlZmQ=
-  dependencies:
-    babel-helper-explode-assignable-expression "^6.24.1"
-    babel-runtime "^6.22.0"
-    babel-types "^6.24.1"
-
-babel-helper-call-delegate@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz#ece6aacddc76e41c3461f88bfc575bd0daa2df8d"
-  integrity sha1-7Oaqzdx25Bw0YfiL/Fdb0Nqi340=
-  dependencies:
-    babel-helper-hoist-variables "^6.24.1"
-    babel-runtime "^6.22.0"
-    babel-traverse "^6.24.1"
-    babel-types "^6.24.1"
-
-babel-helper-define-map@^6.24.1:
-  version "6.26.0"
-  resolved "https://registry.yarnpkg.com/babel-helper-define-map/-/babel-helper-define-map-6.26.0.tgz#a5f56dab41a25f97ecb498c7ebaca9819f95be5f"
-  integrity sha1-pfVtq0GiX5fstJjH66ypgZ+Vvl8=
-  dependencies:
-    babel-helper-function-name "^6.24.1"
-    babel-runtime "^6.26.0"
-    babel-types "^6.26.0"
-    lodash "^4.17.4"
-
-babel-helper-explode-assignable-expression@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.24.1.tgz#f25b82cf7dc10433c55f70592d5746400ac22caa"
-  integrity sha1-8luCz33BBDPFX3BZLVdGQArCLKo=
-  dependencies:
-    babel-runtime "^6.22.0"
-    babel-traverse "^6.24.1"
-    babel-types "^6.24.1"
-
-babel-helper-explode-class@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-helper-explode-class/-/babel-helper-explode-class-6.24.1.tgz#7dc2a3910dee007056e1e31d640ced3d54eaa9eb"
-  integrity sha1-fcKjkQ3uAHBW4eMdZAztPVTqqes=
-  dependencies:
-    babel-helper-bindify-decorators "^6.24.1"
-    babel-runtime "^6.22.0"
-    babel-traverse "^6.24.1"
-    babel-types "^6.24.1"
-
-babel-helper-function-name@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz#d3475b8c03ed98242a25b48351ab18399d3580a9"
-  integrity sha1-00dbjAPtmCQqJbSDUasYOZ01gKk=
-  dependencies:
-    babel-helper-get-function-arity "^6.24.1"
-    babel-runtime "^6.22.0"
-    babel-template "^6.24.1"
-    babel-traverse "^6.24.1"
-    babel-types "^6.24.1"
-
-babel-helper-get-function-arity@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz#8f7782aa93407c41d3aa50908f89b031b1b6853d"
-  integrity sha1-j3eCqpNAfEHTqlCQj4mwMbG2hT0=
-  dependencies:
-    babel-runtime "^6.22.0"
-    babel-types "^6.24.1"
-
-babel-helper-hoist-variables@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz#1ecb27689c9d25513eadbc9914a73f5408be7a76"
-  integrity sha1-HssnaJydJVE+rbyZFKc/VAi+enY=
-  dependencies:
-    babel-runtime "^6.22.0"
-    babel-types "^6.24.1"
-
-babel-helper-optimise-call-expression@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz#f7a13427ba9f73f8f4fa993c54a97882d1244257"
-  integrity sha1-96E0J7qfc/j0+pk8VKl4gtEkQlc=
-  dependencies:
-    babel-runtime "^6.22.0"
-    babel-types "^6.24.1"
-
-babel-helper-regex@^6.24.1:
-  version "6.26.0"
-  resolved "https://registry.yarnpkg.com/babel-helper-regex/-/babel-helper-regex-6.26.0.tgz#325c59f902f82f24b74faceed0363954f6495e72"
-  integrity sha1-MlxZ+QL4LyS3T6zu0DY5VPZJXnI=
-  dependencies:
-    babel-runtime "^6.26.0"
-    babel-types "^6.26.0"
-    lodash "^4.17.4"
-
-babel-helper-remap-async-to-generator@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.24.1.tgz#5ec581827ad723fecdd381f1c928390676e4551b"
-  integrity sha1-XsWBgnrXI/7N04HxySg5BnbkVRs=
-  dependencies:
-    babel-helper-function-name "^6.24.1"
-    babel-runtime "^6.22.0"
-    babel-template "^6.24.1"
-    babel-traverse "^6.24.1"
-    babel-types "^6.24.1"
-
-babel-helper-replace-supers@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz#bf6dbfe43938d17369a213ca8a8bf74b6a90ab1a"
-  integrity sha1-v22/5Dk40XNpohPKiov3S2qQqxo=
-  dependencies:
-    babel-helper-optimise-call-expression "^6.24.1"
-    babel-messages "^6.23.0"
-    babel-runtime "^6.22.0"
-    babel-template "^6.24.1"
-    babel-traverse "^6.24.1"
-    babel-types "^6.24.1"
-
-babel-helpers@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-helpers/-/babel-helpers-6.24.1.tgz#3471de9caec388e5c850e597e58a26ddf37602b2"
-  integrity sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=
-  dependencies:
-    babel-runtime "^6.22.0"
-    babel-template "^6.24.1"
-
-babel-messages@^6.23.0:
-  version "6.23.0"
-  resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e"
-  integrity sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=
-  dependencies:
-    babel-runtime "^6.22.0"
-
-babel-plugin-check-es2015-constants@^6.22.0:
-  version "6.22.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz#35157b101426fd2ffd3da3f75c7d1e91835bbf8a"
-  integrity sha1-NRV7EBQm/S/9PaP3XH0ekYNbv4o=
-  dependencies:
-    babel-runtime "^6.22.0"
-
-babel-plugin-syntax-async-functions@^6.8.0:
-  version "6.13.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz#cad9cad1191b5ad634bf30ae0872391e0647be95"
-  integrity sha1-ytnK0RkbWtY0vzCuCHI5HgZHvpU=
-
-babel-plugin-syntax-async-generators@^6.5.0:
-  version "6.13.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-generators/-/babel-plugin-syntax-async-generators-6.13.0.tgz#6bc963ebb16eccbae6b92b596eb7f35c342a8b9a"
-  integrity sha1-a8lj67FuzLrmuStZbrfzXDQqi5o=
-
-babel-plugin-syntax-class-constructor-call@^6.18.0:
-  version "6.18.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-syntax-class-constructor-call/-/babel-plugin-syntax-class-constructor-call-6.18.0.tgz#9cb9d39fe43c8600bec8146456ddcbd4e1a76416"
-  integrity sha1-nLnTn+Q8hgC+yBRkVt3L1OGnZBY=
-
-babel-plugin-syntax-class-properties@^6.8.0:
-  version "6.13.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz#d7eb23b79a317f8543962c505b827c7d6cac27de"
-  integrity sha1-1+sjt5oxf4VDlixQW4J8fWysJ94=
-
-babel-plugin-syntax-decorators@^6.13.0:
-  version "6.13.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-syntax-decorators/-/babel-plugin-syntax-decorators-6.13.0.tgz#312563b4dbde3cc806cee3e416cceeaddd11ac0b"
-  integrity sha1-MSVjtNvePMgGzuPkFszurd0RrAs=
-
-babel-plugin-syntax-dynamic-import@^6.18.0:
-  version "6.18.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-syntax-dynamic-import/-/babel-plugin-syntax-dynamic-import-6.18.0.tgz#8d6a26229c83745a9982a441051572caa179b1da"
-  integrity sha1-jWomIpyDdFqZgqRBBRVyyqF5sdo=
-
-babel-plugin-syntax-exponentiation-operator@^6.8.0:
-  version "6.13.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz#9ee7e8337290da95288201a6a57f4170317830de"
-  integrity sha1-nufoM3KQ2pUoggGmpX9BcDF4MN4=
-
-babel-plugin-syntax-export-extensions@^6.8.0:
-  version "6.13.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-syntax-export-extensions/-/babel-plugin-syntax-export-extensions-6.13.0.tgz#70a1484f0f9089a4e84ad44bac353c95b9b12721"
-  integrity sha1-cKFITw+QiaToStRLrDU8lbmxJyE=
-
-babel-plugin-syntax-flow@^6.18.0:
-  version "6.18.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz#4c3ab20a2af26aa20cd25995c398c4eb70310c8d"
-  integrity sha1-TDqyCiryaqIM0lmVw5jE63AxDI0=
-
-babel-plugin-syntax-object-rest-spread@^6.8.0:
-  version "6.13.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5"
-  integrity sha1-/WU28rzhODb/o6VFjEkDpZe7O/U=
-
-babel-plugin-syntax-trailing-function-commas@^6.22.0:
-  version "6.22.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz#ba0360937f8d06e40180a43fe0d5616fff532cf3"
-  integrity sha1-ugNgk3+NBuQBgKQ/4NVhb/9TLPM=
-
-babel-plugin-transform-async-generator-functions@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-async-generator-functions/-/babel-plugin-transform-async-generator-functions-6.24.1.tgz#f058900145fd3e9907a6ddf28da59f215258a5db"
-  integrity sha1-8FiQAUX9PpkHpt3yjaWfIVJYpds=
-  dependencies:
-    babel-helper-remap-async-to-generator "^6.24.1"
-    babel-plugin-syntax-async-generators "^6.5.0"
-    babel-runtime "^6.22.0"
-
-babel-plugin-transform-async-to-generator@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz#6536e378aff6cb1d5517ac0e40eb3e9fc8d08761"
-  integrity sha1-ZTbjeK/2yx1VF6wOQOs+n8jQh2E=
-  dependencies:
-    babel-helper-remap-async-to-generator "^6.24.1"
-    babel-plugin-syntax-async-functions "^6.8.0"
-    babel-runtime "^6.22.0"
-
-babel-plugin-transform-class-constructor-call@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-class-constructor-call/-/babel-plugin-transform-class-constructor-call-6.24.1.tgz#80dc285505ac067dcb8d6c65e2f6f11ab7765ef9"
-  integrity sha1-gNwoVQWsBn3LjWxl4vbxGrd2Xvk=
-  dependencies:
-    babel-plugin-syntax-class-constructor-call "^6.18.0"
-    babel-runtime "^6.22.0"
-    babel-template "^6.24.1"
-
-babel-plugin-transform-class-properties@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-class-properties/-/babel-plugin-transform-class-properties-6.24.1.tgz#6a79763ea61d33d36f37b611aa9def81a81b46ac"
-  integrity sha1-anl2PqYdM9NvN7YRqp3vgagbRqw=
-  dependencies:
-    babel-helper-function-name "^6.24.1"
-    babel-plugin-syntax-class-properties "^6.8.0"
-    babel-runtime "^6.22.0"
-    babel-template "^6.24.1"
-
-babel-plugin-transform-decorators@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-decorators/-/babel-plugin-transform-decorators-6.24.1.tgz#788013d8f8c6b5222bdf7b344390dfd77569e24d"
-  integrity sha1-eIAT2PjGtSIr33s0Q5Df13Vp4k0=
-  dependencies:
-    babel-helper-explode-class "^6.24.1"
-    babel-plugin-syntax-decorators "^6.13.0"
-    babel-runtime "^6.22.0"
-    babel-template "^6.24.1"
-    babel-types "^6.24.1"
-
-babel-plugin-transform-es2015-arrow-functions@^6.22.0:
-  version "6.22.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz#452692cb711d5f79dc7f85e440ce41b9f244d221"
-  integrity sha1-RSaSy3EdX3ncf4XkQM5BufJE0iE=
-  dependencies:
-    babel-runtime "^6.22.0"
-
-babel-plugin-transform-es2015-block-scoped-functions@^6.22.0:
-  version "6.22.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz#bbc51b49f964d70cb8d8e0b94e820246ce3a6141"
-  integrity sha1-u8UbSflk1wy42OC5ToICRs46YUE=
-  dependencies:
-    babel-runtime "^6.22.0"
-
-babel-plugin-transform-es2015-block-scoping@^6.24.1:
-  version "6.26.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz#d70f5299c1308d05c12f463813b0a09e73b1895f"
-  integrity sha1-1w9SmcEwjQXBL0Y4E7CgnnOxiV8=
-  dependencies:
-    babel-runtime "^6.26.0"
-    babel-template "^6.26.0"
-    babel-traverse "^6.26.0"
-    babel-types "^6.26.0"
-    lodash "^4.17.4"
-
-babel-plugin-transform-es2015-classes@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz#5a4c58a50c9c9461e564b4b2a3bfabc97a2584db"
-  integrity sha1-WkxYpQyclGHlZLSyo7+ryXolhNs=
-  dependencies:
-    babel-helper-define-map "^6.24.1"
-    babel-helper-function-name "^6.24.1"
-    babel-helper-optimise-call-expression "^6.24.1"
-    babel-helper-replace-supers "^6.24.1"
-    babel-messages "^6.23.0"
-    babel-runtime "^6.22.0"
-    babel-template "^6.24.1"
-    babel-traverse "^6.24.1"
-    babel-types "^6.24.1"
-
-babel-plugin-transform-es2015-computed-properties@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz#6fe2a8d16895d5634f4cd999b6d3480a308159b3"
-  integrity sha1-b+Ko0WiV1WNPTNmZttNICjCBWbM=
-  dependencies:
-    babel-runtime "^6.22.0"
-    babel-template "^6.24.1"
-
-babel-plugin-transform-es2015-destructuring@^6.22.0:
-  version "6.23.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz#997bb1f1ab967f682d2b0876fe358d60e765c56d"
-  integrity sha1-mXux8auWf2gtKwh2/jWNYOdlxW0=
-  dependencies:
-    babel-runtime "^6.22.0"
-
-babel-plugin-transform-es2015-duplicate-keys@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz#73eb3d310ca969e3ef9ec91c53741a6f1576423e"
-  integrity sha1-c+s9MQypaePvnskcU3QabxV2Qj4=
-  dependencies:
-    babel-runtime "^6.22.0"
-    babel-types "^6.24.1"
-
-babel-plugin-transform-es2015-for-of@^6.22.0:
-  version "6.23.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz#f47c95b2b613df1d3ecc2fdb7573623c75248691"
-  integrity sha1-9HyVsrYT3x0+zC/bdXNiPHUkhpE=
-  dependencies:
-    babel-runtime "^6.22.0"
-
-babel-plugin-transform-es2015-function-name@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz#834c89853bc36b1af0f3a4c5dbaa94fd8eacaa8b"
-  integrity sha1-g0yJhTvDaxrw86TF26qU/Y6sqos=
-  dependencies:
-    babel-helper-function-name "^6.24.1"
-    babel-runtime "^6.22.0"
-    babel-types "^6.24.1"
-
-babel-plugin-transform-es2015-literals@^6.22.0:
-  version "6.22.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz#4f54a02d6cd66cf915280019a31d31925377ca2e"
-  integrity sha1-T1SgLWzWbPkVKAAZox0xklN3yi4=
-  dependencies:
-    babel-runtime "^6.22.0"
-
-babel-plugin-transform-es2015-modules-amd@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz#3b3e54017239842d6d19c3011c4bd2f00a00d154"
-  integrity sha1-Oz5UAXI5hC1tGcMBHEvS8AoA0VQ=
-  dependencies:
-    babel-plugin-transform-es2015-modules-commonjs "^6.24.1"
-    babel-runtime "^6.22.0"
-    babel-template "^6.24.1"
-
-babel-plugin-transform-es2015-modules-commonjs@^6.24.1:
-  version "6.26.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.0.tgz#0d8394029b7dc6abe1a97ef181e00758dd2e5d8a"
-  integrity sha1-DYOUApt9xqvhqX7xgeAHWN0uXYo=
-  dependencies:
-    babel-plugin-transform-strict-mode "^6.24.1"
-    babel-runtime "^6.26.0"
-    babel-template "^6.26.0"
-    babel-types "^6.26.0"
-
-babel-plugin-transform-es2015-modules-systemjs@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz#ff89a142b9119a906195f5f106ecf305d9407d23"
-  integrity sha1-/4mhQrkRmpBhlfXxBuzzBdlAfSM=
-  dependencies:
-    babel-helper-hoist-variables "^6.24.1"
-    babel-runtime "^6.22.0"
-    babel-template "^6.24.1"
-
-babel-plugin-transform-es2015-modules-umd@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz#ac997e6285cd18ed6176adb607d602344ad38468"
-  integrity sha1-rJl+YoXNGO1hdq22B9YCNErThGg=
-  dependencies:
-    babel-plugin-transform-es2015-modules-amd "^6.24.1"
-    babel-runtime "^6.22.0"
-    babel-template "^6.24.1"
-
-babel-plugin-transform-es2015-object-super@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz#24cef69ae21cb83a7f8603dad021f572eb278f8d"
-  integrity sha1-JM72muIcuDp/hgPa0CH1cusnj40=
-  dependencies:
-    babel-helper-replace-supers "^6.24.1"
-    babel-runtime "^6.22.0"
-
-babel-plugin-transform-es2015-parameters@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz#57ac351ab49caf14a97cd13b09f66fdf0a625f2b"
-  integrity sha1-V6w1GrScrxSpfNE7CfZv3wpiXys=
-  dependencies:
-    babel-helper-call-delegate "^6.24.1"
-    babel-helper-get-function-arity "^6.24.1"
-    babel-runtime "^6.22.0"
-    babel-template "^6.24.1"
-    babel-traverse "^6.24.1"
-    babel-types "^6.24.1"
-
-babel-plugin-transform-es2015-shorthand-properties@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz#24f875d6721c87661bbd99a4622e51f14de38aa0"
-  integrity sha1-JPh11nIch2YbvZmkYi5R8U3jiqA=
-  dependencies:
-    babel-runtime "^6.22.0"
-    babel-types "^6.24.1"
-
-babel-plugin-transform-es2015-spread@^6.22.0:
-  version "6.22.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz#d6d68a99f89aedc4536c81a542e8dd9f1746f8d1"
-  integrity sha1-1taKmfia7cRTbIGlQujdnxdG+NE=
-  dependencies:
-    babel-runtime "^6.22.0"
-
-babel-plugin-transform-es2015-sticky-regex@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz#00c1cdb1aca71112cdf0cf6126c2ed6b457ccdbc"
-  integrity sha1-AMHNsaynERLN8M9hJsLta0V8zbw=
-  dependencies:
-    babel-helper-regex "^6.24.1"
-    babel-runtime "^6.22.0"
-    babel-types "^6.24.1"
-
-babel-plugin-transform-es2015-template-literals@^6.22.0:
-  version "6.22.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz#a84b3450f7e9f8f1f6839d6d687da84bb1236d8d"
-  integrity sha1-qEs0UPfp+PH2g51taH2oS7EjbY0=
-  dependencies:
-    babel-runtime "^6.22.0"
-
-babel-plugin-transform-es2015-typeof-symbol@^6.22.0:
-  version "6.23.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz#dec09f1cddff94b52ac73d505c84df59dcceb372"
-  integrity sha1-3sCfHN3/lLUqxz1QXITfWdzOs3I=
-  dependencies:
-    babel-runtime "^6.22.0"
-
-babel-plugin-transform-es2015-unicode-regex@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz#d38b12f42ea7323f729387f18a7c5ae1faeb35e9"
-  integrity sha1-04sS9C6nMj9yk4fxinxa4frrNek=
-  dependencies:
-    babel-helper-regex "^6.24.1"
-    babel-runtime "^6.22.0"
-    regexpu-core "^2.0.0"
-
-babel-plugin-transform-exponentiation-operator@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz#2ab0c9c7f3098fa48907772bb813fe41e8de3a0e"
-  integrity sha1-KrDJx/MJj6SJB3cruBP+QejeOg4=
-  dependencies:
-    babel-helper-builder-binary-assignment-operator-visitor "^6.24.1"
-    babel-plugin-syntax-exponentiation-operator "^6.8.0"
-    babel-runtime "^6.22.0"
-
-babel-plugin-transform-export-extensions@^6.22.0:
-  version "6.22.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-export-extensions/-/babel-plugin-transform-export-extensions-6.22.0.tgz#53738b47e75e8218589eea946cbbd39109bbe653"
-  integrity sha1-U3OLR+deghhYnuqUbLvTkQm75lM=
-  dependencies:
-    babel-plugin-syntax-export-extensions "^6.8.0"
-    babel-runtime "^6.22.0"
-
-babel-plugin-transform-flow-strip-types@^6.8.0:
-  version "6.22.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-flow-strip-types/-/babel-plugin-transform-flow-strip-types-6.22.0.tgz#84cb672935d43714fdc32bce84568d87441cf7cf"
-  integrity sha1-hMtnKTXUNxT9wyvOhFaNh0Qc988=
-  dependencies:
-    babel-plugin-syntax-flow "^6.18.0"
-    babel-runtime "^6.22.0"
-
-babel-plugin-transform-object-rest-spread@^6.22.0:
-  version "6.26.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.26.0.tgz#0f36692d50fef6b7e2d4b3ac1478137a963b7b06"
-  integrity sha1-DzZpLVD+9rfi1LOsFHgTepY7ewY=
-  dependencies:
-    babel-plugin-syntax-object-rest-spread "^6.8.0"
-    babel-runtime "^6.26.0"
-
-babel-plugin-transform-regenerator@^6.24.1:
-  version "6.26.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz#e0703696fbde27f0a3efcacf8b4dca2f7b3a8f2f"
-  integrity sha1-4HA2lvveJ/Cj78rPi03KL3s6jy8=
-  dependencies:
-    regenerator-transform "^0.10.0"
-
-babel-plugin-transform-strict-mode@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz#d5faf7aa578a65bbe591cf5edae04a0c67020758"
-  integrity sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g=
-  dependencies:
-    babel-runtime "^6.22.0"
-    babel-types "^6.24.1"
-
-babel-preset-es2015@^6.9.0:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-preset-es2015/-/babel-preset-es2015-6.24.1.tgz#d44050d6bc2c9feea702aaf38d727a0210538939"
-  integrity sha1-1EBQ1rwsn+6nAqrzjXJ6AhBTiTk=
-  dependencies:
-    babel-plugin-check-es2015-constants "^6.22.0"
-    babel-plugin-transform-es2015-arrow-functions "^6.22.0"
-    babel-plugin-transform-es2015-block-scoped-functions "^6.22.0"
-    babel-plugin-transform-es2015-block-scoping "^6.24.1"
-    babel-plugin-transform-es2015-classes "^6.24.1"
-    babel-plugin-transform-es2015-computed-properties "^6.24.1"
-    babel-plugin-transform-es2015-destructuring "^6.22.0"
-    babel-plugin-transform-es2015-duplicate-keys "^6.24.1"
-    babel-plugin-transform-es2015-for-of "^6.22.0"
-    babel-plugin-transform-es2015-function-name "^6.24.1"
-    babel-plugin-transform-es2015-literals "^6.22.0"
-    babel-plugin-transform-es2015-modules-amd "^6.24.1"
-    babel-plugin-transform-es2015-modules-commonjs "^6.24.1"
-    babel-plugin-transform-es2015-modules-systemjs "^6.24.1"
-    babel-plugin-transform-es2015-modules-umd "^6.24.1"
-    babel-plugin-transform-es2015-object-super "^6.24.1"
-    babel-plugin-transform-es2015-parameters "^6.24.1"
-    babel-plugin-transform-es2015-shorthand-properties "^6.24.1"
-    babel-plugin-transform-es2015-spread "^6.22.0"
-    babel-plugin-transform-es2015-sticky-regex "^6.24.1"
-    babel-plugin-transform-es2015-template-literals "^6.22.0"
-    babel-plugin-transform-es2015-typeof-symbol "^6.22.0"
-    babel-plugin-transform-es2015-unicode-regex "^6.24.1"
-    babel-plugin-transform-regenerator "^6.24.1"
-
-babel-preset-stage-1@^6.5.0:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-preset-stage-1/-/babel-preset-stage-1-6.24.1.tgz#7692cd7dcd6849907e6ae4a0a85589cfb9e2bfb0"
-  integrity sha1-dpLNfc1oSZB+auSgqFWJz7niv7A=
-  dependencies:
-    babel-plugin-transform-class-constructor-call "^6.24.1"
-    babel-plugin-transform-export-extensions "^6.22.0"
-    babel-preset-stage-2 "^6.24.1"
-
-babel-preset-stage-2@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-preset-stage-2/-/babel-preset-stage-2-6.24.1.tgz#d9e2960fb3d71187f0e64eec62bc07767219bdc1"
-  integrity sha1-2eKWD7PXEYfw5k7sYrwHdnIZvcE=
-  dependencies:
-    babel-plugin-syntax-dynamic-import "^6.18.0"
-    babel-plugin-transform-class-properties "^6.24.1"
-    babel-plugin-transform-decorators "^6.24.1"
-    babel-preset-stage-3 "^6.24.1"
-
-babel-preset-stage-3@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-preset-stage-3/-/babel-preset-stage-3-6.24.1.tgz#836ada0a9e7a7fa37cb138fb9326f87934a48395"
-  integrity sha1-g2raCp56f6N8sTj7kyb4eTSkg5U=
-  dependencies:
-    babel-plugin-syntax-trailing-function-commas "^6.22.0"
-    babel-plugin-transform-async-generator-functions "^6.24.1"
-    babel-plugin-transform-async-to-generator "^6.24.1"
-    babel-plugin-transform-exponentiation-operator "^6.24.1"
-    babel-plugin-transform-object-rest-spread "^6.22.0"
-
-babel-register@^6.26.0, babel-register@^6.9.0:
-  version "6.26.0"
-  resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.26.0.tgz#6ed021173e2fcb486d7acb45c6009a856f647071"
-  integrity sha1-btAhFz4vy0htestFxgCahW9kcHE=
-  dependencies:
-    babel-core "^6.26.0"
-    babel-runtime "^6.26.0"
-    core-js "^2.5.0"
-    home-or-tmp "^2.0.0"
-    lodash "^4.17.4"
-    mkdirp "^0.5.1"
-    source-map-support "^0.4.15"
-
-babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0:
-  version "6.26.0"
-  resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe"
-  integrity sha1-llxwWGaOgrVde/4E/yM3vItWR/4=
-  dependencies:
-    core-js "^2.4.0"
-    regenerator-runtime "^0.11.0"
-
-babel-template@^6.24.1, babel-template@^6.26.0:
-  version "6.26.0"
-  resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.26.0.tgz#de03e2d16396b069f46dd9fff8521fb1a0e35e02"
-  integrity sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=
-  dependencies:
-    babel-runtime "^6.26.0"
-    babel-traverse "^6.26.0"
-    babel-types "^6.26.0"
-    babylon "^6.18.0"
-    lodash "^4.17.4"
-
-babel-traverse@^6.24.1, babel-traverse@^6.26.0:
-  version "6.26.0"
-  resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.26.0.tgz#46a9cbd7edcc62c8e5c064e2d2d8d0f4035766ee"
-  integrity sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=
-  dependencies:
-    babel-code-frame "^6.26.0"
-    babel-messages "^6.23.0"
-    babel-runtime "^6.26.0"
-    babel-types "^6.26.0"
-    babylon "^6.18.0"
-    debug "^2.6.8"
-    globals "^9.18.0"
-    invariant "^2.2.2"
-    lodash "^4.17.4"
-
-babel-types@^6.19.0, babel-types@^6.24.1, babel-types@^6.26.0:
-  version "6.26.0"
-  resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497"
-  integrity sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=
-  dependencies:
-    babel-runtime "^6.26.0"
-    esutils "^2.0.2"
-    lodash "^4.17.4"
-    to-fast-properties "^1.0.3"
-
-babylon@^6.17.3, babylon@^6.18.0:
-  version "6.18.0"
-  resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3"
-  integrity sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==
-
 balanced-match@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
@@ -1050,16 +515,16 @@ big.js@^3.1.3:
   resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.2.0.tgz#a5fc298b81b9e0dca2e458824784b65c52ba588e"
   integrity sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==
 
+big.js@^5.2.2:
+  version "5.2.2"
+  resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"
+  integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==
+
 binary-extensions@^1.0.0:
   version "1.11.0"
   resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.11.0.tgz#46aa1751fb6a2f93ee5e689bb1087d4b14c6c205"
   integrity sha1-RqoXUftqL5PuXmibsQh9SxTGwgU=
 
-binaryextensions@2:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/binaryextensions/-/binaryextensions-2.1.1.tgz#3209a51ca4a4ad541a3b8d3d6a6d5b83a2485935"
-  integrity sha512-XBaoWE9RW8pPdPQNibZsW2zh8TW6gcarXp1FZPwT8Uop8ScSNldJEWf2k9l3HeTqdrEwsOsFcq74RiJECW34yA==
-
 block-stream@*:
   version "0.0.9"
   resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a"
@@ -1067,10 +532,10 @@ block-stream@*:
   dependencies:
     inherits "~2.0.0"
 
-bluebird@^3.5.1:
-  version "3.5.1"
-  resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9"
-  integrity sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==
+bluebird@^3.5.5:
+  version "3.7.1"
+  resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.1.tgz#df70e302b471d7473489acf26a93d63b53f874de"
+  integrity sha512-DdmyoGCleJnkbp3nkbxTLJ18rjDsE4yCggEwKNXkeV123sPNfOCYeDoeuOY+F2FrSjO1YXcTU+dsy96KMy+gcg==
 
 bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0:
   version "4.11.8"
@@ -1133,6 +598,13 @@ braces@^2.3.0, braces@^2.3.1:
     split-string "^3.0.2"
     to-regex "^3.0.1"
 
+braces@^3.0.1:
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
+  integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
+  dependencies:
+    fill-range "^7.0.1"
+
 brorand@^1.0.1:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
@@ -1206,6 +678,11 @@ buffer-crc32@~0.2.3:
   resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
   integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=
 
+buffer-from@^1.0.0:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
+  integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==
+
 buffer-xor@^1.0.3:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9"
@@ -1220,33 +697,30 @@ buffer@^4.3.0:
     ieee754 "^1.1.4"
     isarray "^1.0.0"
 
-builtin-modules@^1.0.0:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f"
-  integrity sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=
-
 builtin-status-codes@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8"
   integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=
 
-cacache@^10.0.1:
-  version "10.0.4"
-  resolved "https://registry.yarnpkg.com/cacache/-/cacache-10.0.4.tgz#6452367999eff9d4188aefd9a14e9d7c6a263460"
-  integrity sha512-Dph0MzuH+rTQzGPNT9fAnrPmMmjKfST6trxJeK7NQuHRaVw24VzPRWTmg9MpcwOVQZO0E1FBICUlFeNaKPIfHA==
-  dependencies:
-    bluebird "^3.5.1"
-    chownr "^1.0.1"
-    glob "^7.1.2"
-    graceful-fs "^4.1.11"
-    lru-cache "^4.1.1"
-    mississippi "^2.0.0"
+cacache@^12.0.2:
+  version "12.0.3"
+  resolved "https://registry.yarnpkg.com/cacache/-/cacache-12.0.3.tgz#be99abba4e1bf5df461cd5a2c1071fc432573390"
+  integrity sha512-kqdmfXEGFepesTuROHMs3MpFLWrPkSSpRqOw80RCflZXy/khxaArvFrQ7uJxSUduzAufc6G0g1VUCOZXxWavPw==
+  dependencies:
+    bluebird "^3.5.5"
+    chownr "^1.1.1"
+    figgy-pudding "^3.5.1"
+    glob "^7.1.4"
+    graceful-fs "^4.1.15"
+    infer-owner "^1.0.3"
+    lru-cache "^5.1.1"
+    mississippi "^3.0.0"
     mkdirp "^0.5.1"
     move-concurrently "^1.0.1"
     promise-inflight "^1.0.1"
-    rimraf "^2.6.2"
-    ssri "^5.2.4"
-    unique-filename "^1.1.0"
+    rimraf "^2.6.3"
+    ssri "^6.0.1"
+    unique-filename "^1.1.1"
     y18n "^4.0.0"
 
 cache-base@^1.0.1:
@@ -1264,23 +738,10 @@ cache-base@^1.0.1:
     union-value "^1.0.0"
     unset-value "^1.0.0"
 
-cacheable-request@^2.1.1:
-  version "2.1.4"
-  resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-2.1.4.tgz#0d808801b6342ad33c91df9d0b44dc09b91e5c3d"
-  integrity sha1-DYCIAbY0KtM8kd+dC0TcCbkeXD0=
-  dependencies:
-    clone-response "1.0.2"
-    get-stream "3.0.0"
-    http-cache-semantics "3.8.1"
-    keyv "3.0.0"
-    lowercase-keys "1.0.0"
-    normalize-url "2.0.1"
-    responselike "1.0.2"
-
-camelcase@^4.1.0:
-  version "4.1.0"
-  resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd"
-  integrity sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=
+camelcase@^5.0.0:
+  version "5.3.1"
+  resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
+  integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
 
 caseless@~0.11.0:
   version "0.11.0"
@@ -1292,7 +753,16 @@ caseless@~0.12.0:
   resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
   integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=
 
-chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3:
+chalk@2.4.2:
+  version "2.4.2"
+  resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
+  integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
+  dependencies:
+    ansi-styles "^3.2.1"
+    escape-string-regexp "^1.0.5"
+    supports-color "^5.3.0"
+
+chalk@^1.0.0, chalk@^1.1.1:
   version "1.1.3"
   resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
   integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=
@@ -1303,7 +773,7 @@ chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3:
     strip-ansi "^3.0.0"
     supports-color "^2.0.0"
 
-chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.3.1:
+chalk@^2.3.0:
   version "2.3.2"
   resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.2.tgz#250dc96b07491bfd601e648d66ddf5f60c7a5c65"
   integrity sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==
@@ -1312,20 +782,6 @@ chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.3.1:
     escape-string-regexp "^1.0.5"
     supports-color "^5.3.0"
 
-chalk@~0.4.0:
-  version "0.4.0"
-  resolved "https://registry.yarnpkg.com/chalk/-/chalk-0.4.0.tgz#5199a3ddcd0c1efe23bc08c1b027b06176e0c64f"
-  integrity sha1-UZmj3c0MHv4jvAjBsCewYXbgxk8=
-  dependencies:
-    ansi-styles "~1.0.0"
-    has-color "~0.1.0"
-    strip-ansi "~0.1.0"
-
-chardet@^0.4.0:
-  version "0.4.2"
-  resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.4.2.tgz#b5473b33dc97c424e5d98dc87d55d4d8a29c8bf2"
-  integrity sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=
-
 charenc@~0.0.1:
   version "0.0.2"
   resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667"
@@ -1350,15 +806,17 @@ chokidar@^2.0.2:
   optionalDependencies:
     fsevents "^1.0.0"
 
-chownr@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.0.1.tgz#e2a75042a9551908bebd25b8523d5f9769d79181"
-  integrity sha1-4qdQQqlVGQi+vSW4Uj1fl2nXkYE=
+chownr@^1.1.1:
+  version "1.1.3"
+  resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.3.tgz#42d837d5239688d55f303003a508230fa6727142"
+  integrity sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw==
 
-chrome-trace-event@^0.1.1:
-  version "0.1.2"
-  resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-0.1.2.tgz#90f36885d5345a50621332f0717b595883d5d982"
-  integrity sha1-kPNohdU0WlBiEzLwcXtZWIPV2YI=
+chrome-trace-event@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz#234090ee97c7d4ad1a2c4beae27505deffc608a4"
+  integrity sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==
+  dependencies:
+    tslib "^1.9.0"
 
 cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3:
   version "1.0.4"
@@ -1378,66 +836,20 @@ class-utils@^0.3.5:
     isobject "^3.0.0"
     static-extend "^0.1.1"
 
-cli-cursor@^1.0.1, cli-cursor@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-1.0.2.tgz#64da3f7d56a54412e59794bd62dc35295e8f2987"
-  integrity sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=
-  dependencies:
-    restore-cursor "^1.0.1"
-
-cli-cursor@^2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5"
-  integrity sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=
-  dependencies:
-    restore-cursor "^2.0.0"
-
-cli-spinners@^0.1.2:
-  version "0.1.2"
-  resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-0.1.2.tgz#bb764d88e185fb9e1e6a2a1f19772318f605e31c"
-  integrity sha1-u3ZNiOGF+54eaiofGXcjGPYF4xw=
-
-cli-table@^0.3.1:
-  version "0.3.1"
-  resolved "https://registry.yarnpkg.com/cli-table/-/cli-table-0.3.1.tgz#f53b05266a8b1a0b934b3d0821e6e2dc5914ae23"
-  integrity sha1-9TsFJmqLGguTSz0IIebi3FkUriM=
-  dependencies:
-    colors "1.0.3"
-
-cli-truncate@^0.2.1:
-  version "0.2.1"
-  resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-0.2.1.tgz#9f15cfbb0705005369216c626ac7d05ab90dd574"
-  integrity sha1-nxXPuwcFAFNpIWxiasfQWrkN1XQ=
-  dependencies:
-    slice-ansi "0.0.4"
-    string-width "^1.0.1"
-
-cli-width@^2.0.0:
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639"
-  integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=
-
-cliui@^3.2.0:
-  version "3.2.0"
-  resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d"
-  integrity sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=
+cliui@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5"
+  integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==
   dependencies:
-    string-width "^1.0.1"
-    strip-ansi "^3.0.1"
-    wrap-ansi "^2.0.0"
+    string-width "^3.1.0"
+    strip-ansi "^5.2.0"
+    wrap-ansi "^5.1.0"
 
 clone-buffer@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/clone-buffer/-/clone-buffer-1.0.0.tgz#e3e25b207ac4e701af721e2cb5a16792cac3dc58"
   integrity sha1-4+JbIHrE5wGvch4staFnksrD3Fg=
 
-clone-response@1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b"
-  integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=
-  dependencies:
-    mimic-response "^1.0.0"
-
 clone-stats@^0.0.1:
   version "0.0.1"
   resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-0.0.1.tgz#b88f94a82cf38b8791d58046ea4029ad88ca99d1"
@@ -1482,15 +894,6 @@ code-point-at@^1.0.0:
   resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
   integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=
 
-codecov@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/codecov/-/codecov-3.0.0.tgz#c273b8c4f12945723e8dc9d25803d89343e5f28e"
-  integrity sha1-wnO4xPEpRXI+jcnSWAPYk0Pl8o4=
-  dependencies:
-    argv "0.0.2"
-    request "2.81.0"
-    urlgrey "0.4.4"
-
 collection-visit@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0"
@@ -1516,16 +919,6 @@ color-support@^1.1.3:
   resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2"
   integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==
 
-colors@1.0.3:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b"
-  integrity sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=
-
-colors@^1.1.2:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63"
-  integrity sha1-FopHAXVran9RoSzgyXv6KMCE7WM=
-
 combined-stream@^1.0.5, combined-stream@~1.0.5:
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009"
@@ -1538,7 +931,12 @@ commander@2.11.0:
   resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563"
   integrity sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==
 
-commander@^2.9.0, commander@~2.14.1:
+commander@^2.20.0:
+  version "2.20.3"
+  resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
+  integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
+
+commander@^2.9.0:
   version "2.14.1"
   resolved "https://registry.yarnpkg.com/commander/-/commander-2.14.1.tgz#2235123e37af8ca3c65df45b026dbd357b01b9aa"
   integrity sha512-+YR16o3rK53SmWHU3rEM3tPAh2rwb1yPcQX5irVn7mb0gXbwuCCrnkbV5+PBfETdfg1vui07nM6PCG1zndcjQw==
@@ -1558,7 +956,7 @@ concat-map@0.0.1:
   resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
   integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
 
-concat-stream@^1.4.7, concat-stream@^1.5.0:
+concat-stream@^1.5.0:
   version "1.6.1"
   resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.1.tgz#261b8f518301f1d834e36342b9fea095d2620a26"
   integrity sha512-gslSSJx03QKa59cIKqeJO9HQ/WZMotvYJCuaUULrLpjj8oG40kV2Z+gz82pVxlTkOADi4PJxQPPfhl1ELYrrXw==
@@ -1584,7 +982,7 @@ constants-browserify@^1.0.0:
   resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75"
   integrity sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=
 
-convert-source-map@^1.1.1, convert-source-map@^1.5.0:
+convert-source-map@^1.1.1:
   version "1.5.1"
   resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.1.tgz#b8278097b9bc229365de5c62cf5fcaed8b5599e5"
   integrity sha1-uCeAl7m8IpNl3lxiz1/K7YtVmeU=
@@ -1606,11 +1004,6 @@ copy-descriptor@^0.1.0:
   resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
   integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=
 
-core-js@^2.4.0, core-js@^2.4.1, core-js@^2.5.0:
-  version "2.5.3"
-  resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.3.tgz#8acc38345824f16d8365b7c9b4259168e8ed603e"
-  integrity sha1-isw4NFgk8W2DZbfJtCWRaOjtYD4=
-
 core-util-is@1.0.2, core-util-is@~1.0.0:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
@@ -1646,16 +1039,7 @@ create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4:
     safe-buffer "^5.0.1"
     sha.js "^2.4.8"
 
-cross-spawn@^5.0.1:
-  version "5.1.0"
-  resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449"
-  integrity sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=
-  dependencies:
-    lru-cache "^4.0.1"
-    shebang-command "^1.2.0"
-    which "^1.2.9"
-
-cross-spawn@^6.0.4:
+cross-spawn@6.0.5, cross-spawn@^6.0.0:
   version "6.0.5"
   resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
   integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==
@@ -1707,11 +1091,6 @@ cyclist@~0.2.2:
   resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-0.2.2.tgz#1b33792e11e914a2fd6d6ed6447464444e5fa640"
   integrity sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=
 
-dargs@^5.1.0:
-  version "5.1.0"
-  resolved "https://registry.yarnpkg.com/dargs/-/dargs-5.1.0.tgz#ec7ea50c78564cd36c9d5ec18f66329fade27829"
-  integrity sha1-7H6lDHhWTNNsnV7Bj2Yyn63ieCk=
-
 dashdash@^1.12.0:
   version "1.14.1"
   resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
@@ -1719,11 +1098,6 @@ dashdash@^1.12.0:
   dependencies:
     assert-plus "^1.0.0"
 
-date-fns@^1.27.2:
-  version "1.29.0"
-  resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.29.0.tgz#12e609cdcb935127311d04d33334e2960a2a54e6"
-  integrity sha512-lbTXWZ6M20cWH8N9S6afb0SBm6tMk+uUg6z3MqHPKE9atmsY3kJkTm8vKe93izJ2B2+q5MV990sM2CHgtAZaOw==
-
 date-now@^0.1.4:
   version "0.1.4"
   resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b"
@@ -1741,14 +1115,14 @@ debug@3.1.0, debug@^3.1.0:
   dependencies:
     ms "2.0.0"
 
-debug@^2.0.0, debug@^2.1.0, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8:
+debug@^2.2.0, debug@^2.3.3:
   version "2.6.9"
   resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
   integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
   dependencies:
     ms "2.0.0"
 
-decamelize@^1.1.1:
+decamelize@^1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
   integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
@@ -1758,13 +1132,6 @@ decode-uri-component@^0.2.0:
   resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545"
   integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=
 
-decompress-response@^3.2.0, decompress-response@^3.3.0:
-  version "3.3.0"
-  resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3"
-  integrity sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=
-  dependencies:
-    mimic-response "^1.0.0"
-
 deep-assign@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/deep-assign/-/deep-assign-1.0.0.tgz#b092743be8427dc621ea0067cdec7e70dd19f37b"
@@ -1772,7 +1139,7 @@ deep-assign@^1.0.0:
   dependencies:
     is-obj "^1.0.0"
 
-deep-extend@^0.4.0, deep-extend@~0.4.0:
+deep-extend@~0.4.0:
   version "0.4.2"
   resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.2.tgz#48b699c27e334bf89f10892be432f6e4c7d34a7f"
   integrity sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8=
@@ -1817,17 +1184,10 @@ des.js@^1.0.0:
     inherits "^2.0.1"
     minimalistic-assert "^1.0.0"
 
-detect-conflict@^1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/detect-conflict/-/detect-conflict-1.0.1.tgz#088657a66a961c05019db7c4230883b1c6b4176e"
-  integrity sha1-CIZXpmqWHAUBnbfEIwiDsca0F24=
-
-detect-indent@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208"
-  integrity sha1-920GQ1LN9Docts5hnE7jqUdd4gg=
-  dependencies:
-    repeating "^2.0.0"
+detect-file@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7"
+  integrity sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=
 
 detect-libc@^1.0.2:
   version "1.0.3"
@@ -1851,16 +1211,6 @@ diff@3.3.1:
   resolved "https://registry.yarnpkg.com/diff/-/diff-3.3.1.tgz#aa8567a6eed03c531fc89d3f711cd0e5259dec75"
   integrity sha512-MKPHZDMB0o6yHyDryUOScqZibp914ksXwAMYMTHj6KO8UeKsRYNJD3oNCKjTqZon+V488P7N/HzXF8t7ZR95ww==
 
-diff@^2.1.2:
-  version "2.2.3"
-  resolved "https://registry.yarnpkg.com/diff/-/diff-2.2.3.tgz#60eafd0d28ee906e4e8ff0a52c1229521033bf99"
-  integrity sha1-YOr9DSjukG5Oj/ClLBIpUhAzv5k=
-
-diff@^3.3.0, diff@^3.3.1:
-  version "3.5.0"
-  resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
-  integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==
-
 diffie-hellman@^5.0.0:
   version "5.0.2"
   resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.2.tgz#b5835739270cfe26acf632099fded2a07f209e5e"
@@ -1870,11 +1220,6 @@ diffie-hellman@^5.0.0:
     miller-rabin "^4.0.0"
     randombytes "^2.0.0"
 
-dom-walk@^0.1.0:
-  version "0.1.1"
-  resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.1.tgz#672226dc74c8f799ad35307df936aba11acd6018"
-  integrity sha1-ZyIm3HTI95mtNTB9+TaroRrNYBg=
-
 domain-browser@^1.1.1:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda"
@@ -1887,11 +1232,6 @@ duplexer2@0.0.2:
   dependencies:
     readable-stream "~1.1.9"
 
-duplexer3@^0.1.4:
-  version "0.1.4"
-  resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2"
-  integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=
-
 duplexer@~0.1.1:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1"
@@ -1924,21 +1264,6 @@ ecc-jsbn@~0.1.1:
   dependencies:
     jsbn "~0.1.0"
 
-editions@^1.3.3:
-  version "1.3.4"
-  resolved "https://registry.yarnpkg.com/editions/-/editions-1.3.4.tgz#3662cb592347c3168eb8e498a0ff73271d67f50b"
-  integrity sha512-gzao+mxnYDzIysXKMQi/+M1mjy/rjestjg6OPoYTtI+3Izp23oiGZitsl9lPDPiTGXbcSIk1iJWhliSaglxnUg==
-
-ejs@^2.3.1:
-  version "2.5.7"
-  resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.5.7.tgz#cc872c168880ae3c7189762fd5ffc00896c9518a"
-  integrity sha1-zIcsFoiArjxxiXYv1f/ACJbJUYo=
-
-elegant-spinner@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/elegant-spinner/-/elegant-spinner-1.0.1.tgz#db043521c95d7e303fd8f345bedc3349cfb0729e"
-  integrity sha1-2wQ1IcldfjA/2PNFvtwzSc+wcp4=
-
 elliptic@^6.0.0:
   version "6.4.0"
   resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.4.0.tgz#cac9af8762c85836187003c8dfe193e5e2eae5df"
@@ -1952,6 +1277,11 @@ elliptic@^6.0.0:
     minimalistic-assert "^1.0.0"
     minimalistic-crypto-utils "^1.0.0"
 
+emoji-regex@^7.0.1:
+  version "7.0.3"
+  resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156"
+  integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==
+
 emojis-list@^2.0.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389"
@@ -1964,6 +1294,15 @@ end-of-stream@^1.0.0, end-of-stream@^1.1.0:
   dependencies:
     once "^1.4.0"
 
+enhanced-resolve@4.1.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz#41c7e0bfdfe74ac1ffe1e57ad6a5c6c9f3742a7f"
+  integrity sha512-F/7vkyTtyc/llOIn8oWclcB25KdRaiPBpZYDgJHgh/UHtpgT2p2eldQgtQnLtUvfMKPKxbRaQM/hHkvLHt1Vng==
+  dependencies:
+    graceful-fs "^4.1.2"
+    memory-fs "^0.4.0"
+    tapable "^1.0.0"
+
 enhanced-resolve@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.0.0.tgz#e34a6eaa790f62fccd71d93959f56b2b432db10a"
@@ -1973,6 +1312,15 @@ enhanced-resolve@^4.0.0:
     memory-fs "^0.4.0"
     tapable "^1.0.0"
 
+enhanced-resolve@^4.1.0:
+  version "4.1.1"
+  resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.1.1.tgz#2937e2b8066cd0fe7ce0990a98f0d71a35189f66"
+  integrity sha512-98p2zE+rL7/g/DzMHMTF4zZlCgeVdJ7yr6xzEpJRYwFYrGi9ANdn5DnJURg6RpBkyk60XYDnWIv51VfIhfNGuA==
+  dependencies:
+    graceful-fs "^4.1.2"
+    memory-fs "^0.5.0"
+    tapable "^1.0.0"
+
 entities@~2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.0.tgz#68d6084cab1b079767540d80e56a39b423e4abf4"
@@ -1985,39 +1333,19 @@ errno@^0.1.3, errno@~0.1.7:
   dependencies:
     prr "~1.0.1"
 
-error-ex@^1.2.0:
-  version "1.3.1"
-  resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.1.tgz#f855a86ce61adc4e8621c3cda21e7a7612c3a8dc"
-  integrity sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=
-  dependencies:
-    is-arrayish "^0.2.1"
-
-error@^7.0.2:
-  version "7.0.2"
-  resolved "https://registry.yarnpkg.com/error/-/error-7.0.2.tgz#a5f75fff4d9926126ddac0ea5dc38e689153cb02"
-  integrity sha1-pfdf/02ZJhJt2sDqXcOOaJFTywI=
-  dependencies:
-    string-template "~0.2.1"
-    xtend "~4.0.0"
-
 escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
   integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
 
-eslint-scope@^3.7.1:
-  version "3.7.1"
-  resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-3.7.1.tgz#3d63c3edfda02e06e01a452ad88caacc7cdcb6e8"
-  integrity sha1-PWPD7f2gLgbgGkUq2IyqzHzctug=
+eslint-scope@^4.0.3:
+  version "4.0.3"
+  resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848"
+  integrity sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==
   dependencies:
     esrecurse "^4.1.0"
     estraverse "^4.1.1"
 
-esprima@~4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804"
-  integrity sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==
-
 esrecurse@^4.1.0:
   version "4.2.1"
   resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.1.tgz#007a3b9fdbc2b3bb87e4879ea19c92fdbd3942cf"
@@ -2030,11 +1358,6 @@ estraverse@^4.1.0, estraverse@^4.1.1:
   resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13"
   integrity sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=
 
-esutils@^2.0.2:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b"
-  integrity sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=
-
 event-stream@^3.3.1, event-stream@~3.3.4:
   version "3.3.4"
   resolved "https://registry.yarnpkg.com/event-stream/-/event-stream-3.3.4.tgz#4ab4c9a0f5a54db9338b4c34d86bfce8f4b35571"
@@ -2048,10 +1371,10 @@ event-stream@^3.3.1, event-stream@~3.3.4:
     stream-combiner "~0.0.4"
     through "~2.3.1"
 
-events@^1.0.0:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924"
-  integrity sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=
+events@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/events/-/events-3.0.0.tgz#9a0a0dfaf62893d92b875b8f2698ca4114973e88"
+  integrity sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA==
 
 evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3:
   version "1.0.3"
@@ -2061,24 +1384,19 @@ evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3:
     md5.js "^1.3.4"
     safe-buffer "^5.1.1"
 
-execa@^0.7.0:
-  version "0.7.0"
-  resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777"
-  integrity sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=
+execa@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8"
+  integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==
   dependencies:
-    cross-spawn "^5.0.1"
-    get-stream "^3.0.0"
+    cross-spawn "^6.0.0"
+    get-stream "^4.0.0"
     is-stream "^1.1.0"
     npm-run-path "^2.0.0"
     p-finally "^1.0.0"
     signal-exit "^3.0.0"
     strip-eof "^1.0.0"
 
-exit-hook@^1.0.0:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-1.1.1.tgz#f05ca233b48c05d54fff07765df8507e95c02ff8"
-  integrity sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=
-
 expand-brackets@^0.1.4:
   version "0.1.5"
   resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b"
@@ -2140,24 +1458,6 @@ extend@^3.0.0, extend@~3.0.0, extend@~3.0.1:
   resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444"
   integrity sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=
 
-external-editor@^1.1.0:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-1.1.1.tgz#12d7b0db850f7ff7e7081baf4005700060c4600b"
-  integrity sha1-Etew24UPf/fnCBuvQAVwAGDEYAs=
-  dependencies:
-    extend "^3.0.0"
-    spawn-sync "^1.0.15"
-    tmp "^0.0.29"
-
-external-editor@^2.0.4, external-editor@^2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-2.1.0.tgz#3d026a21b7f95b5726387d4200ac160d372c3b48"
-  integrity sha512-E44iT5QVOUJBKij4IIV3uvxuNlbKS38Tw1HiupxEIHPv9qtC2PrDYohbXV5U+1jnfIXttny8gUhj+oZvflFlzA==
-  dependencies:
-    chardet "^0.4.0"
-    iconv-lite "^0.4.17"
-    tmp "^0.0.33"
-
 extglob@^0.3.1:
   version "0.3.2"
   resolved "https://registry.yarnpkg.com/extglob/-/extglob-0.3.2.tgz#2e18ff3d2f49ab2765cec9023f011daa8d8349a1"
@@ -2203,6 +1503,11 @@ fast-deep-equal@^1.0.0:
   resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff"
   integrity sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8=
 
+fast-deep-equal@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49"
+  integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=
+
 fast-json-stable-stringify@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2"
@@ -2215,20 +1520,10 @@ fd-slicer@~1.0.1:
   dependencies:
     pend "~1.2.0"
 
-figures@^1.3.5, figures@^1.7.0:
-  version "1.7.0"
-  resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e"
-  integrity sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=
-  dependencies:
-    escape-string-regexp "^1.0.5"
-    object-assign "^4.1.0"
-
-figures@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962"
-  integrity sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=
-  dependencies:
-    escape-string-regexp "^1.0.5"
+figgy-pudding@^3.5.1:
+  version "3.5.1"
+  resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.1.tgz#862470112901c727a0e495a80744bd5baa1d6790"
+  integrity sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==
 
 filename-regex@^2.0.0:
   version "2.0.1"
@@ -2256,39 +1551,44 @@ fill-range@^4.0.0:
     repeat-string "^1.6.1"
     to-regex-range "^2.1.0"
 
-find-cache-dir@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-1.0.0.tgz#9288e3e9e3cc3748717d39eade17cf71fc30ee6f"
-  integrity sha1-kojj6ePMN0hxfTnq3hfPcfww7m8=
+fill-range@^7.0.1:
+  version "7.0.1"
+  resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
+  integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
   dependencies:
-    commondir "^1.0.1"
-    make-dir "^1.0.0"
-    pkg-dir "^2.0.0"
+    to-regex-range "^5.0.1"
 
-find-up@^2.0.0, find-up@^2.1.0:
+find-cache-dir@^2.1.0:
   version "2.1.0"
-  resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7"
-  integrity sha1-RdG35QbHF93UgndaK3eSCjwMV6c=
+  resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7"
+  integrity sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==
+  dependencies:
+    commondir "^1.0.1"
+    make-dir "^2.0.0"
+    pkg-dir "^3.0.0"
+
+find-up@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73"
+  integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==
+  dependencies:
+    locate-path "^3.0.0"
+
+findup-sync@3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-3.0.0.tgz#17b108f9ee512dfb7a5c7f3c8b27ea9e1a9c08d1"
+  integrity sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==
   dependencies:
-    locate-path "^2.0.0"
+    detect-file "^1.0.0"
+    is-glob "^4.0.0"
+    micromatch "^3.0.4"
+    resolve-dir "^1.0.1"
 
 first-chunk-stream@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/first-chunk-stream/-/first-chunk-stream-1.0.0.tgz#59bfb50cd905f60d7c394cd3d9acaab4e6ad934e"
   integrity sha1-Wb+1DNkF9g18OUzT2ayqtOatk04=
 
-first-chunk-stream@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/first-chunk-stream/-/first-chunk-stream-2.0.0.tgz#1bdecdb8e083c0664b91945581577a43a9f31d70"
-  integrity sha1-G97NuOCDwGZLkZRVgVd6Q6nzHXA=
-  dependencies:
-    readable-stream "^2.0.2"
-
-flow-parser@^0.*:
-  version "0.66.0"
-  resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.66.0.tgz#be583fefb01192aa5164415d31a6241b35718983"
-  integrity sha1-vlg/77ARkqpRZEFdMaYkGzVxiYM=
-
 flush-write-stream@^1.0.0:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.0.2.tgz#c81b90d8746766f1a609a46809946c45dd8ae417"
@@ -2339,7 +1639,7 @@ fragment-cache@^0.2.1:
   dependencies:
     map-cache "^0.2.2"
 
-from2@^2.1.0, from2@^2.1.1:
+from2@^2.1.0:
   version "2.3.0"
   resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af"
   integrity sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=
@@ -2420,15 +1720,17 @@ generate-object-property@^1.1.0:
   dependencies:
     is-property "^1.0.0"
 
-get-caller-file@^1.0.1:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5"
-  integrity sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=
+get-caller-file@^2.0.1:
+  version "2.0.5"
+  resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
+  integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
 
-get-stream@3.0.0, get-stream@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14"
-  integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=
+get-stream@^4.0.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5"
+  integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==
+  dependencies:
+    pump "^3.0.0"
 
 get-value@^2.0.3, get-value@^2.0.6:
   version "2.0.6"
@@ -2442,29 +1744,6 @@ getpass@^0.1.1:
   dependencies:
     assert-plus "^1.0.0"
 
-gh-got@^6.0.0:
-  version "6.0.0"
-  resolved "https://registry.yarnpkg.com/gh-got/-/gh-got-6.0.0.tgz#d74353004c6ec466647520a10bd46f7299d268d0"
-  integrity sha512-F/mS+fsWQMo1zfgG9MD8KWvTWPPzzhuVwY++fhQ5Ggd+0P+CAMHtzMZhNxG+TqGfHDChJKsbh6otfMGqO2AKBw==
-  dependencies:
-    got "^7.0.0"
-    is-plain-obj "^1.1.0"
-
-github-username@^4.0.0:
-  version "4.1.0"
-  resolved "https://registry.yarnpkg.com/github-username/-/github-username-4.1.0.tgz#cbe280041883206da4212ae9e4b5f169c30bf417"
-  integrity sha1-y+KABBiDIG2kISrp5LXxacML9Bc=
-  dependencies:
-    gh-got "^6.0.0"
-
-glob-all@^3.1.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/glob-all/-/glob-all-3.1.0.tgz#8913ddfb5ee1ac7812656241b03d5217c64b02ab"
-  integrity sha1-iRPd+17hrHgSZWJBsD1SF8ZLAqs=
-  dependencies:
-    glob "^7.0.5"
-    yargs "~1.2.6"
-
 glob-base@^0.3.0:
   version "0.3.0"
   resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4"
@@ -2502,7 +1781,7 @@ glob-stream@^5.3.2:
     to-absolute-glob "^0.1.1"
     unique-stream "^2.0.2"
 
-glob@7.1.2, glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.2:
+glob@7.1.2, glob@^7.0.5, glob@^7.1.2:
   version "7.1.2"
   resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15"
   integrity sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==
@@ -2525,17 +1804,25 @@ glob@^5.0.3:
     once "^1.3.0"
     path-is-absolute "^1.0.0"
 
-glob@^6.0.1:
-  version "6.0.4"
-  resolved "https://registry.yarnpkg.com/glob/-/glob-6.0.4.tgz#0f08860f6a155127b2fadd4f9ce24b1aab6e4d22"
-  integrity sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=
+glob@^7.1.3, glob@^7.1.4:
+  version "7.1.5"
+  resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.5.tgz#6714c69bee20f3c3e64c4dd905553e532b40cdc0"
+  integrity sha512-J9dlskqUXK1OeTOYBEn5s8aMukWMwWfs+rPTn/jn50Ux4MNXVhubL1wu/j2t+H4NVI+cXEcCaYellqaPVGXNqQ==
   dependencies:
+    fs.realpath "^1.0.0"
     inflight "^1.0.4"
     inherits "2"
-    minimatch "2 || 3"
+    minimatch "^3.0.4"
     once "^1.3.0"
     path-is-absolute "^1.0.0"
 
+global-modules@2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780"
+  integrity sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==
+  dependencies:
+    global-prefix "^3.0.0"
+
 global-modules@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea"
@@ -2556,41 +1843,14 @@ global-prefix@^1.0.1:
     is-windows "^1.0.1"
     which "^1.2.14"
 
-global@^4.3.2:
-  version "4.3.2"
-  resolved "https://registry.yarnpkg.com/global/-/global-4.3.2.tgz#e76989268a6c74c38908b1305b10fc0e394e9d0f"
-  integrity sha1-52mJJopsdMOJCLEwWxD8DjlOnQ8=
-  dependencies:
-    min-document "^2.19.0"
-    process "~0.5.1"
-
-globals@^9.18.0:
-  version "9.18.0"
-  resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a"
-  integrity sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==
-
-globby@^4.0.0:
-  version "4.1.0"
-  resolved "https://registry.yarnpkg.com/globby/-/globby-4.1.0.tgz#080f54549ec1b82a6c60e631fc82e1211dbe95f8"
-  integrity sha1-CA9UVJ7BuCpsYOYx/ILhIR2+lfg=
-  dependencies:
-    array-union "^1.0.1"
-    arrify "^1.0.0"
-    glob "^6.0.1"
-    object-assign "^4.0.1"
-    pify "^2.0.0"
-    pinkie-promise "^2.0.0"
-
-globby@^6.1.0:
-  version "6.1.0"
-  resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c"
-  integrity sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=
+global-prefix@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-3.0.0.tgz#fc85f73064df69f50421f47f883fe5b913ba9b97"
+  integrity sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==
   dependencies:
-    array-union "^1.0.1"
-    glob "^7.0.3"
-    object-assign "^4.0.1"
-    pify "^2.0.0"
-    pinkie-promise "^2.0.0"
+    ini "^1.3.5"
+    kind-of "^6.0.2"
+    which "^1.3.1"
 
 glogg@^1.0.0:
   version "1.0.1"
@@ -2599,60 +1859,15 @@ glogg@^1.0.0:
   dependencies:
     sparkles "^1.0.0"
 
-got@^7.0.0:
-  version "7.1.0"
-  resolved "https://registry.yarnpkg.com/got/-/got-7.1.0.tgz#05450fd84094e6bbea56f451a43a9c289166385a"
-  integrity sha512-Y5WMo7xKKq1muPsxD+KmrR8DH5auG7fBdDVueZwETwV6VytKyU9OX/ddpq2/1hp1vIPvVb4T81dKQz3BivkNLw==
-  dependencies:
-    decompress-response "^3.2.0"
-    duplexer3 "^0.1.4"
-    get-stream "^3.0.0"
-    is-plain-obj "^1.1.0"
-    is-retry-allowed "^1.0.0"
-    is-stream "^1.0.0"
-    isurl "^1.0.0-alpha5"
-    lowercase-keys "^1.0.0"
-    p-cancelable "^0.3.0"
-    p-timeout "^1.1.1"
-    safe-buffer "^5.0.1"
-    timed-out "^4.0.0"
-    url-parse-lax "^1.0.0"
-    url-to-options "^1.0.1"
-
-got@^8.2.0:
-  version "8.2.0"
-  resolved "https://registry.yarnpkg.com/got/-/got-8.2.0.tgz#0d11a071d05046348a2f5c0a5fa047fb687fdfc6"
-  integrity sha512-giadqJpXIwjY+ZsuWys8p2yjZGhOHiU4hiJHjS/oeCxw1u8vANQz3zPlrxW2Zw/siCXsSMI3hvzWGcnFyujyAg==
-  dependencies:
-    "@sindresorhus/is" "^0.7.0"
-    cacheable-request "^2.1.1"
-    decompress-response "^3.3.0"
-    duplexer3 "^0.1.4"
-    get-stream "^3.0.0"
-    into-stream "^3.1.0"
-    is-retry-allowed "^1.1.0"
-    isurl "^1.0.0-alpha5"
-    lowercase-keys "^1.0.0"
-    mimic-response "^1.0.0"
-    p-cancelable "^0.3.0"
-    p-timeout "^2.0.1"
-    pify "^3.0.0"
-    safe-buffer "^5.1.1"
-    timed-out "^4.0.1"
-    url-parse-lax "^3.0.0"
-    url-to-options "^1.0.1"
-
-graceful-fs@^4.0.0, graceful-fs@^4.1.11, graceful-fs@^4.1.2:
+graceful-fs@^4.0.0, graceful-fs@^4.1.2:
   version "4.1.11"
   resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"
   integrity sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=
 
-grouped-queue@^0.3.0, grouped-queue@^0.3.3:
-  version "0.3.3"
-  resolved "https://registry.yarnpkg.com/grouped-queue/-/grouped-queue-0.3.3.tgz#c167d2a5319c5a0e0964ef6a25b7c2df8996c85c"
-  integrity sha1-wWfSpTGcWg4JZO9qJbfC34mWyFw=
-  dependencies:
-    lodash "^4.17.2"
+graceful-fs@^4.1.15:
+  version "4.2.3"
+  resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423"
+  integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==
 
 growl@1.10.3:
   version "1.10.3"
@@ -2815,11 +2030,6 @@ has-ansi@^2.0.0:
   dependencies:
     ansi-regex "^2.0.0"
 
-has-color@~0.1.0:
-  version "0.1.7"
-  resolved "https://registry.yarnpkg.com/has-color/-/has-color-0.1.7.tgz#67144a5260c34fc3cca677d041daf52fe7b78b2f"
-  integrity sha1-ZxRKUmDDT8PMpnfQQdr1L+e3iy8=
-
 has-flag@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51"
@@ -2837,18 +2047,6 @@ has-gulplog@^0.1.0:
   dependencies:
     sparkles "^1.0.0"
 
-has-symbol-support-x@^1.4.1:
-  version "1.4.2"
-  resolved "https://registry.yarnpkg.com/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz#1409f98bc00247da45da67cee0a36f282ff26455"
-  integrity sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw==
-
-has-to-string-tag-x@^1.2.0:
-  version "1.4.1"
-  resolved "https://registry.yarnpkg.com/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz#a045ab383d7b4b2012a00148ab0aa5f290044d4d"
-  integrity sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw==
-  dependencies:
-    has-symbol-support-x "^1.4.1"
-
 has-unicode@^2.0.0:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
@@ -2957,14 +2155,6 @@ hoek@4.x.x:
   resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.0.tgz#72d9d0754f7fe25ca2d01ad8f8f9a9449a89526d"
   integrity sha512-v0XCLxICi9nPfYrS9RL8HbYnXi9obYAeLbSP00BmnZwCK9+Ih9WOjoZ8YoHCoav2csqn4FOz4Orldsy2dmDwmQ==
 
-home-or-tmp@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8"
-  integrity sha1-42w/LSyufXRqhX440Y1fMqeILbg=
-  dependencies:
-    os-homedir "^1.0.0"
-    os-tmpdir "^1.0.1"
-
 homedir-polyfill@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz#4c2bbc8a758998feebf5ed68580f76d46768b4bc"
@@ -2972,16 +2162,6 @@ homedir-polyfill@^1.0.1:
   dependencies:
     parse-passwd "^1.0.0"
 
-hosted-git-info@^2.1.4:
-  version "2.5.0"
-  resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.5.0.tgz#6d60e34b3abbc8313062c3b798ef8d901a07af3c"
-  integrity sha512-pNgbURSuab90KbTqvRPsseaTxOJCZBD0a7t+haSN33piP9cCM4l0CqdzAif2hUqm716UovKB2ROmiabGAKVXyg==
-
-http-cache-semantics@3.8.1:
-  version "3.8.1"
-  resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz#39b0e16add9b605bf0a9ef3d9daaf4843b4cacd2"
-  integrity sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==
-
 http-signature@~1.1.0:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf"
@@ -3005,11 +2185,6 @@ https-browserify@^1.0.0:
   resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
   integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=
 
-iconv-lite@^0.4.17:
-  version "0.4.19"
-  resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b"
-  integrity sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==
-
 ieee754@^1.1.4:
   version "1.1.8"
   resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4"
@@ -3020,27 +2195,23 @@ iferr@^0.1.5:
   resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501"
   integrity sha1-xg7taebY/bazEEofy8ocGS3FtQE=
 
+import-local@2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/import-local/-/import-local-2.0.0.tgz#55070be38a5993cf18ef6db7e961f5bee5c5a09d"
+  integrity sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==
+  dependencies:
+    pkg-dir "^3.0.0"
+    resolve-cwd "^2.0.0"
+
 imurmurhash@^0.1.4:
   version "0.1.4"
   resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
   integrity sha1-khi5srkoojixPcT7a21XbyMUU+o=
 
-indent-string@^2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80"
-  integrity sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=
-  dependencies:
-    repeating "^2.0.0"
-
-indent-string@^3.0.0:
-  version "3.2.0"
-  resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-3.2.0.tgz#4a5fd6d27cc332f37e5419a504dbb837105c9289"
-  integrity sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=
-
-indexof@0.0.1:
-  version "0.0.1"
-  resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d"
-  integrity sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=
+infer-owner@^1.0.3:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467"
+  integrity sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==
 
 inflight@^1.0.4:
   version "1.0.6"
@@ -3050,7 +2221,7 @@ inflight@^1.0.4:
     once "^1.3.0"
     wrappy "1"
 
-inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3:
+inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3:
   version "2.0.3"
   resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
   integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
@@ -3060,94 +2231,20 @@ inherits@2.0.1:
   resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1"
   integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=
 
-ini@^1.3.4, ini@~1.3.0:
+ini@^1.3.4, ini@^1.3.5, ini@~1.3.0:
   version "1.3.5"
   resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
   integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==
 
-inquirer@^1.0.2:
-  version "1.2.3"
-  resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-1.2.3.tgz#4dec6f32f37ef7bb0b2ed3f1d1a5c3f545074918"
-  integrity sha1-TexvMvN+97sLLtPx0aXD9UUHSRg=
-  dependencies:
-    ansi-escapes "^1.1.0"
-    chalk "^1.0.0"
-    cli-cursor "^1.0.1"
-    cli-width "^2.0.0"
-    external-editor "^1.1.0"
-    figures "^1.3.5"
-    lodash "^4.3.0"
-    mute-stream "0.0.6"
-    pinkie-promise "^2.0.0"
-    run-async "^2.2.0"
-    rx "^4.1.0"
-    string-width "^1.0.1"
-    strip-ansi "^3.0.0"
-    through "^2.3.6"
-
-inquirer@^3.3.0:
-  version "3.3.0"
-  resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.3.0.tgz#9dd2f2ad765dcab1ff0443b491442a20ba227dc9"
-  integrity sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ==
-  dependencies:
-    ansi-escapes "^3.0.0"
-    chalk "^2.0.0"
-    cli-cursor "^2.1.0"
-    cli-width "^2.0.0"
-    external-editor "^2.0.4"
-    figures "^2.0.0"
-    lodash "^4.3.0"
-    mute-stream "0.0.7"
-    run-async "^2.2.0"
-    rx-lite "^4.0.8"
-    rx-lite-aggregates "^4.0.8"
-    string-width "^2.1.0"
-    strip-ansi "^4.0.0"
-    through "^2.3.6"
-
-inquirer@^5.1.0:
-  version "5.1.0"
-  resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-5.1.0.tgz#19da508931892328abbbdd4c477f1efc65abfd67"
-  integrity sha512-kn7N70US1MSZHZHSGJLiZ7iCwwncc7b0gc68YtlX29OjI3Mp0tSVV+snVXpZ1G+ONS3Ac9zd1m6hve2ibLDYfA==
-  dependencies:
-    ansi-escapes "^3.0.0"
-    chalk "^2.0.0"
-    cli-cursor "^2.1.0"
-    cli-width "^2.0.0"
-    external-editor "^2.1.0"
-    figures "^2.0.0"
-    lodash "^4.3.0"
-    mute-stream "0.0.7"
-    run-async "^2.2.0"
-    rxjs "^5.5.2"
-    string-width "^2.1.0"
-    strip-ansi "^4.0.0"
-    through "^2.3.6"
-
-interpret@^1.0.0, interpret@^1.0.4:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614"
-  integrity sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=
-
-into-stream@^3.1.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/into-stream/-/into-stream-3.1.0.tgz#96fb0a936c12babd6ff1752a17d05616abd094c6"
-  integrity sha1-lvsKk2wSur1v8XUqF9BWFqvQlMY=
-  dependencies:
-    from2 "^2.1.1"
-    p-is-promise "^1.1.0"
-
-invariant@^2.2.2:
-  version "2.2.3"
-  resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.3.tgz#1a827dfde7dcbd7c323f0ca826be8fa7c5e9d688"
-  integrity sha512-7Z5PPegwDTyjbaeCnV0efcyS6vdKAU51kpEmS7QFib3P4822l8ICYyMn7qvJnc+WzLoDsuI9gPMKbJ8pCu8XtA==
-  dependencies:
-    loose-envify "^1.0.0"
+interpret@1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.2.0.tgz#d5061a6224be58e8083985f5014d844359576296"
+  integrity sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==
 
-invert-kv@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6"
-  integrity sha1-EEqOSqym09jNFXqO+L+rLXo//bY=
+invert-kv@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-2.0.0.tgz#7393f5afa59ec9ff5f67a27620d11c226e3eec02"
+  integrity sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==
 
 is-accessor-descriptor@^0.1.6:
   version "0.1.6"
@@ -3163,11 +2260,6 @@ is-accessor-descriptor@^1.0.0:
   dependencies:
     kind-of "^6.0.0"
 
-is-arrayish@^0.2.1:
-  version "0.2.1"
-  resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
-  integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=
-
 is-binary-path@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898"
@@ -3180,13 +2272,6 @@ is-buffer@^1.1.5, is-buffer@~1.1.1:
   resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
   integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
 
-is-builtin-module@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-1.0.0.tgz#540572d34f7ac3119f8f76c30cbc1b1e037affbe"
-  integrity sha1-VAVy0096wxGfj3bDDLwbHgN6/74=
-  dependencies:
-    builtin-modules "^1.0.0"
-
 is-data-descriptor@^0.1.4:
   version "0.1.4"
   resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56"
@@ -3253,13 +2338,6 @@ is-extglob@^2.1.0, is-extglob@^2.1.1:
   resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
   integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=
 
-is-finite@^1.0.0:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa"
-  integrity sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=
-  dependencies:
-    number-is-nan "^1.0.0"
-
 is-fullwidth-code-point@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb"
@@ -3322,23 +2400,16 @@ is-number@^4.0.0:
   resolved "https://registry.yarnpkg.com/is-number/-/is-number-4.0.0.tgz#0026e37f5454d73e356dfe6564699867c6a7f0ff"
   integrity sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==
 
+is-number@^7.0.0:
+  version "7.0.0"
+  resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
+  integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
+
 is-obj@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f"
   integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8=
 
-is-object@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.1.tgz#8952688c5ec2ffd6b03ecc85e769e02903083470"
-  integrity sha1-iVJojF7C/9awPsyF52ngKQMINHA=
-
-is-observable@^0.2.0:
-  version "0.2.0"
-  resolved "https://registry.yarnpkg.com/is-observable/-/is-observable-0.2.0.tgz#b361311d83c6e5d726cabf5e250b0237106f5ae2"
-  integrity sha1-s2ExHYPG5dcmyr9eJQsCNxBvWuI=
-  dependencies:
-    symbol-observable "^0.2.2"
-
 is-odd@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/is-odd/-/is-odd-2.0.0.tgz#7646624671fd7ea558ccd9a2795182f2958f1b24"
@@ -3346,11 +2417,6 @@ is-odd@^2.0.0:
   dependencies:
     is-number "^4.0.0"
 
-is-plain-obj@^1.0.0, is-plain-obj@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e"
-  integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4=
-
 is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4:
   version "2.0.4"
   resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677"
@@ -3368,29 +2434,12 @@ is-primitive@^2.0.0:
   resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575"
   integrity sha1-IHurkWOEmcB7Kt8kCkGochADRXU=
 
-is-promise@^2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa"
-  integrity sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=
-
 is-property@^1.0.0:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84"
   integrity sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=
 
-is-retry-allowed@^1.0.0, is-retry-allowed@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz#11a060568b67339444033d0125a61a20d564fb34"
-  integrity sha1-EaBgVotnM5REAz0BJaYaINVk+zQ=
-
-is-scoped@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/is-scoped/-/is-scoped-1.0.0.tgz#449ca98299e713038256289ecb2b540dc437cb30"
-  integrity sha1-RJypgpnnEwOCViieyytUDcQ3yzA=
-  dependencies:
-    scoped-regex "^1.0.0"
-
-is-stream@^1.0.0, is-stream@^1.0.1, is-stream@^1.1.0:
+is-stream@^1.0.1, is-stream@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
   integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ=
@@ -3415,6 +2464,11 @@ is-windows@^1.0.1, is-windows@^1.0.2:
   resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
   integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==
 
+is-wsl@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d"
+  integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=
+
 is@^3.1.0:
   version "3.2.1"
   resolved "https://registry.yarnpkg.com/is/-/is-3.2.1.tgz#d0ac2ad55eb7b0bec926a5266f6c662aaa83dca5"
@@ -3452,74 +2506,26 @@ isstream@~0.1.2:
   resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
   integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=
 
-istextorbinary@^2.1.0:
-  version "2.2.1"
-  resolved "https://registry.yarnpkg.com/istextorbinary/-/istextorbinary-2.2.1.tgz#a5231a08ef6dd22b268d0895084cf8d58b5bec53"
-  integrity sha512-TS+hoFl8Z5FAFMK38nhBkdLt44CclNRgDHWeMgsV8ko3nDlr/9UI2Sf839sW7enijf8oKsZYXRvM8g0it9Zmcw==
-  dependencies:
-    binaryextensions "2"
-    editions "^1.3.3"
-    textextensions "2"
-
-isurl@^1.0.0-alpha5:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/isurl/-/isurl-1.0.0.tgz#b27f4f49f3cdaa3ea44a0a5b7f3462e6edc39d67"
-  integrity sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w==
-  dependencies:
-    has-to-string-tag-x "^1.2.0"
-    is-object "^1.0.1"
-
-js-tokens@^3.0.0, js-tokens@^3.0.2:
-  version "3.0.2"
-  resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
-  integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls=
-
 jsbn@~0.1.0:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
   integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM=
 
-jscodeshift@^0.4.0, jscodeshift@^0.4.1:
-  version "0.4.1"
-  resolved "https://registry.yarnpkg.com/jscodeshift/-/jscodeshift-0.4.1.tgz#da91a1c2eccfa03a3387a21d39948e251ced444a"
-  integrity sha512-iOX6If+hsw0q99V3n31t4f5VlD1TQZddH08xbT65ZqA7T4Vkx68emrDZMUOLVvCEAJ6NpAk7DECe3fjC/t52AQ==
-  dependencies:
-    async "^1.5.0"
-    babel-plugin-transform-flow-strip-types "^6.8.0"
-    babel-preset-es2015 "^6.9.0"
-    babel-preset-stage-1 "^6.5.0"
-    babel-register "^6.9.0"
-    babylon "^6.17.3"
-    colors "^1.1.2"
-    flow-parser "^0.*"
-    lodash "^4.13.1"
-    micromatch "^2.3.7"
-    node-dir "0.1.8"
-    nomnom "^1.8.1"
-    recast "^0.12.5"
-    temp "^0.8.1"
-    write-file-atomic "^1.2.0"
-
-jsesc@^1.3.0:
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b"
-  integrity sha1-RsP+yMGJKxKwgz25vHYiF226s0s=
-
-jsesc@~0.5.0:
-  version "0.5.0"
-  resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d"
-  integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=
-
-json-buffer@3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898"
-  integrity sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=
+json-parse-better-errors@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9"
+  integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==
 
 json-schema-traverse@^0.3.0:
   version "0.3.1"
   resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340"
   integrity sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=
 
+json-schema-traverse@^0.4.1:
+  version "0.4.1"
+  resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
+  integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
+
 json-schema@0.2.3:
   version "0.2.3"
   resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13"
@@ -3537,11 +2543,18 @@ json-stringify-safe@~5.0.1:
   resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
   integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=
 
-json5@^0.5.0, json5@^0.5.1:
+json5@^0.5.0:
   version "0.5.1"
   resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821"
   integrity sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=
 
+json5@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe"
+  integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==
+  dependencies:
+    minimist "^1.2.0"
+
 jsonify@~0.0.0:
   version "0.0.0"
   resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"
@@ -3562,13 +2575,6 @@ jsprim@^1.2.2:
     json-schema "0.2.3"
     verror "1.10.0"
 
-keyv@3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.0.0.tgz#44923ba39e68b12a7cec7df6c3268c031f2ef373"
-  integrity sha512-eguHnq22OE3uVoSYG0LVWNP+4ppamWr9+zWBe1bsNcovIMy6huUJFPgy4mGwCd/rnl3vOLGW1MTlu4c57CT1xA==
-  dependencies:
-    json-buffer "3.0.0"
-
 kind-of@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-1.1.0.tgz#140a3d2d41a36d2efcfa9377b62c24f8495a5c44"
@@ -3612,12 +2618,12 @@ lazystream@^1.0.0:
   dependencies:
     readable-stream "^2.0.5"
 
-lcid@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835"
-  integrity sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=
+lcid@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/lcid/-/lcid-2.0.0.tgz#6ef5d2df60e52f82eb228a4c373e8d1f397253cf"
+  integrity sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==
   dependencies:
-    invert-kv "^1.0.0"
+    invert-kv "^2.0.0"
 
 linkify-it@^2.0.0:
   version "2.0.3"
@@ -3626,74 +2632,21 @@ linkify-it@^2.0.0:
   dependencies:
     uc.micro "^1.0.1"
 
-listr-silent-renderer@^1.1.1:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/listr-silent-renderer/-/listr-silent-renderer-1.1.1.tgz#924b5a3757153770bf1a8e3fbf74b8bbf3f9242e"
-  integrity sha1-kktaN1cVN3C/Go4/v3S4u/P5JC4=
-
-listr-update-renderer@^0.4.0:
-  version "0.4.0"
-  resolved "https://registry.yarnpkg.com/listr-update-renderer/-/listr-update-renderer-0.4.0.tgz#344d980da2ca2e8b145ba305908f32ae3f4cc8a7"
-  integrity sha1-NE2YDaLKLosUW6MFkI8yrj9MyKc=
-  dependencies:
-    chalk "^1.1.3"
-    cli-truncate "^0.2.1"
-    elegant-spinner "^1.0.1"
-    figures "^1.7.0"
-    indent-string "^3.0.0"
-    log-symbols "^1.0.2"
-    log-update "^1.0.2"
-    strip-ansi "^3.0.1"
-
-listr-verbose-renderer@^0.4.0:
-  version "0.4.1"
-  resolved "https://registry.yarnpkg.com/listr-verbose-renderer/-/listr-verbose-renderer-0.4.1.tgz#8206f4cf6d52ddc5827e5fd14989e0e965933a35"
-  integrity sha1-ggb0z21S3cWCfl/RSYng6WWTOjU=
-  dependencies:
-    chalk "^1.1.3"
-    cli-cursor "^1.0.2"
-    date-fns "^1.27.2"
-    figures "^1.7.0"
-
-listr@^0.13.0:
-  version "0.13.0"
-  resolved "https://registry.yarnpkg.com/listr/-/listr-0.13.0.tgz#20bb0ba30bae660ee84cc0503df4be3d5623887d"
-  integrity sha1-ILsLowuuZg7oTMBQPfS+PVYjiH0=
-  dependencies:
-    chalk "^1.1.3"
-    cli-truncate "^0.2.1"
-    figures "^1.7.0"
-    indent-string "^2.1.0"
-    is-observable "^0.2.0"
-    is-promise "^2.1.0"
-    is-stream "^1.1.0"
-    listr-silent-renderer "^1.1.1"
-    listr-update-renderer "^0.4.0"
-    listr-verbose-renderer "^0.4.0"
-    log-symbols "^1.0.2"
-    log-update "^1.0.2"
-    ora "^0.2.3"
-    p-map "^1.1.1"
-    rxjs "^5.4.2"
-    stream-to-observable "^0.2.0"
-    strip-ansi "^3.0.1"
+loader-runner@^2.4.0:
+  version "2.4.0"
+  resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357"
+  integrity sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==
 
-load-json-file@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8"
-  integrity sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=
+loader-utils@1.2.3, loader-utils@^1.2.3:
+  version "1.2.3"
+  resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7"
+  integrity sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==
   dependencies:
-    graceful-fs "^4.1.2"
-    parse-json "^2.2.0"
-    pify "^2.0.0"
-    strip-bom "^3.0.0"
-
-loader-runner@^2.3.0:
-  version "2.3.0"
-  resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.3.0.tgz#f482aea82d543e07921700d5a46ef26fdac6b8a2"
-  integrity sha1-9IKuqC1UPgeSFwDVpG7yb9rGuKI=
+    big.js "^5.2.2"
+    emojis-list "^2.0.0"
+    json5 "^1.0.1"
 
-loader-utils@^1.0.2, loader-utils@^1.1.0:
+loader-utils@^1.0.2:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.1.0.tgz#c98aef488bcceda2ffb5e2de646d6a754429f5cd"
   integrity sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=
@@ -3702,12 +2655,12 @@ loader-utils@^1.0.2, loader-utils@^1.1.0:
     emojis-list "^2.0.0"
     json5 "^0.5.0"
 
-locate-path@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e"
-  integrity sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=
+locate-path@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e"
+  integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==
   dependencies:
-    p-locate "^2.0.0"
+    p-locate "^3.0.0"
     path-exists "^3.0.0"
 
 lodash._basecopy@^3.0.0:
@@ -3819,64 +2772,37 @@ lodash.throttle@^4.1.1:
   resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4"
   integrity sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ=
 
-lodash@^4.11.1, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.3.0:
-  version "4.17.5"
-  resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.5.tgz#99a92d65c0272debe8c96b6057bc8fbfa3bed511"
-  integrity sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw==
-
 lodash@^4.16.4:
   version "4.17.10"
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7"
   integrity sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==
 
-log-symbols@2.2.0, log-symbols@^2.1.0:
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a"
-  integrity sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==
-  dependencies:
-    chalk "^2.0.1"
-
-log-symbols@^1.0.1, log-symbols@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-1.0.2.tgz#376ff7b58ea3086a0f09facc74617eca501e1a18"
-  integrity sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg=
-  dependencies:
-    chalk "^1.0.0"
-
-log-update@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/log-update/-/log-update-1.0.2.tgz#19929f64c4093d2d2e7075a1dad8af59c296b8d1"
-  integrity sha1-GZKfZMQJPS0ucHWh2tivWcKWuNE=
+lru-cache@^5.1.1:
+  version "5.1.1"
+  resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920"
+  integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==
   dependencies:
-    ansi-escapes "^1.0.0"
-    cli-cursor "^1.0.2"
+    yallist "^3.0.2"
 
-loose-envify@^1.0.0:
-  version "1.3.1"
-  resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848"
-  integrity sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=
+make-dir@^2.0.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5"
+  integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==
   dependencies:
-    js-tokens "^3.0.0"
-
-lowercase-keys@1.0.0, lowercase-keys@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306"
-  integrity sha1-TjNms55/VFfjXxMkvfb4jQv8cwY=
+    pify "^4.0.1"
+    semver "^5.6.0"
 
-lru-cache@^4.0.1, lru-cache@^4.1.1:
-  version "4.1.1"
-  resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.1.tgz#622e32e82488b49279114a4f9ecf45e7cd6bba55"
-  integrity sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==
-  dependencies:
-    pseudomap "^1.0.2"
-    yallist "^2.1.2"
+mamacro@^0.0.3:
+  version "0.0.3"
+  resolved "https://registry.yarnpkg.com/mamacro/-/mamacro-0.0.3.tgz#ad2c9576197c9f1abf308d0787865bd975a3f3e4"
+  integrity sha512-qMEwh+UujcQ+kbz3T6V+wAmO2U8veoq2w+3wY8MquqwVA3jChfwY+Tk52GZKDfACEPjuZ7r2oJLejwpt8jtwTA==
 
-make-dir@^1.0.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.2.0.tgz#6d6a49eead4aae296c53bbf3a1a008bd6c89469b"
-  integrity sha512-aNUAa4UMg/UougV25bbrU4ZaaKNjJ/3/xnvg/twpmKROPdKZPZ9wGgI0opdZzO8q/zUFawoUuixuOv33eZ61Iw==
+map-age-cleaner@^0.1.1:
+  version "0.1.3"
+  resolved "https://registry.yarnpkg.com/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz#7d583a7306434c055fe474b0f45078e6e1b4b92a"
+  integrity sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==
   dependencies:
-    pify "^3.0.0"
+    p-defer "^1.0.0"
 
 map-cache@^0.2.2:
   version "0.2.2"
@@ -3933,39 +2859,16 @@ mdurl@^1.0.1:
   resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e"
   integrity sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=
 
-mem-fs-editor@^3.0.0:
-  version "3.0.2"
-  resolved "https://registry.yarnpkg.com/mem-fs-editor/-/mem-fs-editor-3.0.2.tgz#dd0a6eaf2bb8a6b37740067aa549eb530105af9f"
-  integrity sha1-3Qpuryu4prN3QAZ6pUnrUwEFr58=
-  dependencies:
-    commondir "^1.0.1"
-    deep-extend "^0.4.0"
-    ejs "^2.3.1"
-    glob "^7.0.3"
-    globby "^6.1.0"
-    mkdirp "^0.5.0"
-    multimatch "^2.0.0"
-    rimraf "^2.2.8"
-    through2 "^2.0.0"
-    vinyl "^2.0.1"
-
-mem-fs@^1.1.0:
-  version "1.1.3"
-  resolved "https://registry.yarnpkg.com/mem-fs/-/mem-fs-1.1.3.tgz#b8ae8d2e3fcb6f5d3f9165c12d4551a065d989cc"
-  integrity sha1-uK6NLj/Lb10/kWXBLUVRoGXZicw=
-  dependencies:
-    through2 "^2.0.0"
-    vinyl "^1.1.0"
-    vinyl-file "^2.0.0"
-
-mem@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/mem/-/mem-1.1.0.tgz#5edd52b485ca1d900fe64895505399a0dfa45f76"
-  integrity sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=
+mem@^4.0.0:
+  version "4.3.0"
+  resolved "https://registry.yarnpkg.com/mem/-/mem-4.3.0.tgz#461af497bc4ae09608cdb2e60eefb69bff744178"
+  integrity sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==
   dependencies:
-    mimic-fn "^1.0.0"
+    map-age-cleaner "^0.1.1"
+    mimic-fn "^2.0.0"
+    p-is-promise "^2.0.0"
 
-memory-fs@^0.4.0, memory-fs@~0.4.1:
+memory-fs@^0.4.0, memory-fs@^0.4.1:
   version "0.4.1"
   resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552"
   integrity sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=
@@ -3973,6 +2876,14 @@ memory-fs@^0.4.0, memory-fs@~0.4.1:
     errno "^0.1.3"
     readable-stream "^2.0.1"
 
+memory-fs@^0.5.0:
+  version "0.5.0"
+  resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.5.0.tgz#324c01288b88652966d161db77838720845a8e3c"
+  integrity sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==
+  dependencies:
+    errno "^0.1.3"
+    readable-stream "^2.0.1"
+
 merge-stream@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-1.0.1.tgz#4041202d508a342ba00174008df0c251b8c135e1"
@@ -3999,7 +2910,26 @@ micromatch@^2.3.7:
     parse-glob "^3.0.4"
     regex-cache "^0.4.2"
 
-micromatch@^3.1.4, micromatch@^3.1.8:
+micromatch@^3.0.4, micromatch@^3.1.10:
+  version "3.1.10"
+  resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23"
+  integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==
+  dependencies:
+    arr-diff "^4.0.0"
+    array-unique "^0.3.2"
+    braces "^2.3.1"
+    define-property "^2.0.2"
+    extend-shallow "^3.0.2"
+    extglob "^2.0.4"
+    fragment-cache "^0.2.1"
+    kind-of "^6.0.2"
+    nanomatch "^1.2.9"
+    object.pick "^1.3.0"
+    regex-not "^1.0.0"
+    snapdragon "^0.8.1"
+    to-regex "^3.0.2"
+
+micromatch@^3.1.4:
   version "3.1.9"
   resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.9.tgz#15dc93175ae39e52e93087847096effc73efcf89"
   integrity sha512-SlIz6sv5UPaAVVFRKodKjCg48EbNoIhgetzfK/Cy0v5U52Z6zB136M8tp0UC9jM53LYbmIRihJszvvqpKkfm9g==
@@ -4018,6 +2948,14 @@ micromatch@^3.1.4, micromatch@^3.1.8:
     snapdragon "^0.8.1"
     to-regex "^3.0.1"
 
+micromatch@^4.0.0:
+  version "4.0.2"
+  resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259"
+  integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==
+  dependencies:
+    braces "^3.0.1"
+    picomatch "^2.0.5"
+
 miller-rabin@^4.0.0:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d"
@@ -4038,22 +2976,10 @@ mime-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.7:
   dependencies:
     mime-db "~1.30.0"
 
-mimic-fn@^1.0.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022"
-  integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==
-
-mimic-response@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.0.tgz#df3d3652a73fded6b9b0b24146e6fd052353458e"
-  integrity sha1-3z02Uqc/3ta5sLJBRub9BSNTRY4=
-
-min-document@^2.19.0:
-  version "2.19.0"
-  resolved "https://registry.yarnpkg.com/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685"
-  integrity sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=
-  dependencies:
-    dom-walk "^0.1.0"
+mimic-fn@^2.0.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
+  integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
 
 minimalistic-assert@^1.0.0:
   version "1.0.0"
@@ -4077,20 +3003,15 @@ minimist@0.0.8:
   resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
   integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=
 
-minimist@^0.1.0:
-  version "0.1.0"
-  resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.1.0.tgz#99df657a52574c21c9057497df742790b2b4c0de"
-  integrity sha1-md9lelJXTCHJBXSX33QnkLK0wN4=
-
 minimist@^1.1.0, minimist@^1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
   integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=
 
-mississippi@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-2.0.0.tgz#3442a508fafc28500486feea99409676e4ee5a6f"
-  integrity sha512-zHo8v+otD1J10j/tC+VNoGK9keCuByhKovAvdn74dmxJl9+mWHnx6EMsDN4lgRoMI/eYo2nchAxniIbUPb5onw==
+mississippi@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-3.0.0.tgz#ea0a3291f97e0b5e8776b363d5f0a12d94c67022"
+  integrity sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==
   dependencies:
     concat-stream "^1.5.0"
     duplexify "^3.4.2"
@@ -4098,7 +3019,7 @@ mississippi@^2.0.0:
     flush-write-stream "^1.0.0"
     from2 "^2.1.0"
     parallel-transform "^1.1.0"
-    pump "^2.0.1"
+    pump "^3.0.0"
     pumpify "^1.3.3"
     stream-each "^1.1.0"
     through2 "^2.0.0"
@@ -4111,7 +3032,7 @@ mixin-deep@^1.2.0:
     for-in "^1.0.2"
     is-extendable "^1.0.1"
 
-mkdirp@0.5.1, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1:
+mkdirp@0.5.1, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.1:
   version "0.5.1"
   resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
   integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=
@@ -4187,16 +3108,6 @@ multipipe@^0.1.2:
   dependencies:
     duplexer2 "0.0.2"
 
-mute-stream@0.0.6:
-  version "0.0.6"
-  resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.6.tgz#48962b19e169fd1dfc240b3f1e7317627bbc47db"
-  integrity sha1-SJYrGeFp/R38JAs/HnMXYnu8R9s=
-
-mute-stream@0.0.7:
-  version "0.0.7"
-  resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab"
-  integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=
-
 nan@^2.3.0:
   version "2.9.2"
   resolved "https://registry.yarnpkg.com/nan/-/nan-2.9.2.tgz#f564d75f5f8f36a6d9456cca7a6c4fe488ab7866"
@@ -4225,20 +3136,20 @@ neo-async@^2.5.0:
   resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.5.0.tgz#76b1c823130cca26acfbaccc8fbaf0a2fa33b18f"
   integrity sha512-nJmSswG4As/MkRq7QZFuH/sf/yuv8ODdMZrY4Bedjp77a5MK4A6s7YbBB64c9u79EBUOfXUXBvArmvzTD0X+6g==
 
+neo-async@^2.6.1:
+  version "2.6.1"
+  resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c"
+  integrity sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==
+
 nice-try@^1.0.4:
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.4.tgz#d93962f6c52f2c1558c0fbda6d512819f1efe1c4"
   integrity sha512-2NpiFHqC87y/zFke0fC0spBXL3bBsoh/p5H1EFhshxjCR5+0g2d6BiXbUFz9v1sAcxsk2htp2eQnNIci2dIYcA==
 
-node-dir@0.1.8:
-  version "0.1.8"
-  resolved "https://registry.yarnpkg.com/node-dir/-/node-dir-0.1.8.tgz#55fb8deb699070707fb67f91a460f0448294c77d"
-  integrity sha1-VfuN62mQcHB/tn+RpGDwRIKUx30=
-
-node-libs-browser@^2.0.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.1.0.tgz#5f94263d404f6e44767d726901fff05478d600df"
-  integrity sha512-5AzFzdoIMb89hBGMZglEegffzgRg+ZFoUmisQ8HI4j1KDdpx13J0taNp2y9xPbur6W61gepGDDotGBVQ7mfUCg==
+node-libs-browser@^2.2.1:
+  version "2.2.1"
+  resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.2.1.tgz#b64f513d18338625f90346d27b0d235e631f6425"
+  integrity sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==
   dependencies:
     assert "^1.1.1"
     browserify-zlib "^0.2.0"
@@ -4247,10 +3158,10 @@ node-libs-browser@^2.0.0:
     constants-browserify "^1.0.0"
     crypto-browserify "^3.11.0"
     domain-browser "^1.1.1"
-    events "^1.0.0"
+    events "^3.0.0"
     https-browserify "^1.0.0"
     os-browserify "^0.3.0"
-    path-browserify "0.0.0"
+    path-browserify "0.0.1"
     process "^0.11.10"
     punycode "^1.2.4"
     querystring-es3 "^0.2.0"
@@ -4261,8 +3172,8 @@ node-libs-browser@^2.0.0:
     timers-browserify "^2.0.4"
     tty-browserify "0.0.0"
     url "^0.11.0"
-    util "^0.10.3"
-    vm-browserify "0.0.4"
+    util "^0.11.0"
+    vm-browserify "^1.0.1"
 
 node-pre-gyp@^0.6.39:
   version "0.6.39"
@@ -4288,14 +3199,6 @@ node.extend@~1.1.2:
   dependencies:
     is "^3.1.0"
 
-nomnom@^1.8.1:
-  version "1.8.1"
-  resolved "https://registry.yarnpkg.com/nomnom/-/nomnom-1.8.1.tgz#2151f722472ba79e50a76fc125bb8c8f2e4dc2a7"
-  integrity sha1-IVH3Ikcrp55Qp2/BJbuMjy5Nwqc=
-  dependencies:
-    chalk "~0.4.0"
-    underscore "~1.6.0"
-
 nopt@^4.0.1:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d"
@@ -4304,16 +3207,6 @@ nopt@^4.0.1:
     abbrev "1"
     osenv "^0.1.4"
 
-normalize-package-data@^2.3.2:
-  version "2.4.0"
-  resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.4.0.tgz#12f95a307d58352075a04907b84ac8be98ac012f"
-  integrity sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==
-  dependencies:
-    hosted-git-info "^2.1.4"
-    is-builtin-module "^1.0.0"
-    semver "2 || 3 || 4 || 5"
-    validate-npm-package-license "^3.0.1"
-
 normalize-path@^2.0.1, normalize-path@^2.1.1:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9"
@@ -4321,15 +3214,6 @@ normalize-path@^2.0.1, normalize-path@^2.1.1:
   dependencies:
     remove-trailing-separator "^1.0.1"
 
-normalize-url@2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-2.0.1.tgz#835a9da1551fa26f70e92329069a23aa6574d7e6"
-  integrity sha512-D6MUW4K/VzoJ4rJ01JFKxDrtY1v9wrgzCX5f2qj/lzH1m/lW6MhUZFKerVsnyjOhOsYzI9Kqqak+10l4LvLpMw==
-  dependencies:
-    prepend-http "^2.0.0"
-    query-string "^5.0.1"
-    sort-keys "^2.0.0"
-
 npm-run-path@^2.0.0:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f"
@@ -4362,7 +3246,7 @@ object-assign@^3.0.0:
   resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-3.0.0.tgz#9bedd5ca0897949bca47e7ff408062d549f587f2"
   integrity sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=
 
-object-assign@^4.0.0, object-assign@^4.0.1, object-assign@^4.1.0:
+object-assign@^4.0.0, object-assign@^4.1.0:
   version "4.1.1"
   resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
   integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
@@ -4405,28 +3289,6 @@ once@^1.3.0, once@^1.3.1, once@^1.3.3, once@^1.4.0:
   dependencies:
     wrappy "1"
 
-onetime@^1.0.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/onetime/-/onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789"
-  integrity sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=
-
-onetime@^2.0.0:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4"
-  integrity sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=
-  dependencies:
-    mimic-fn "^1.0.0"
-
-ora@^0.2.3:
-  version "0.2.3"
-  resolved "https://registry.yarnpkg.com/ora/-/ora-0.2.3.tgz#37527d220adcd53c39b73571d754156d5db657a4"
-  integrity sha1-N1J9Igrc1Tw5tzVx11QVbV22V6Q=
-  dependencies:
-    chalk "^1.1.1"
-    cli-cursor "^1.0.2"
-    cli-spinners "^0.1.2"
-    object-assign "^4.0.1"
-
 ordered-read-streams@^0.3.0:
   version "0.3.0"
   resolved "https://registry.yarnpkg.com/ordered-read-streams/-/ordered-read-streams-0.3.0.tgz#7137e69b3298bb342247a1bbee3881c80e2fd78b"
@@ -4445,21 +3307,16 @@ os-homedir@^1.0.0:
   resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3"
   integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M=
 
-os-locale@^2.0.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-2.1.0.tgz#42bc2900a6b5b8bd17376c8e882b65afccf24bf2"
-  integrity sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==
+os-locale@^3.1.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-3.1.0.tgz#a802a6ee17f24c10483ab9935719cef4ed16bf1a"
+  integrity sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==
   dependencies:
-    execa "^0.7.0"
-    lcid "^1.0.0"
-    mem "^1.1.0"
-
-os-shim@^0.1.2:
-  version "0.1.3"
-  resolved "https://registry.yarnpkg.com/os-shim/-/os-shim-0.1.3.tgz#6b62c3791cf7909ea35ed46e17658bb417cb3917"
-  integrity sha1-a2LDeRz3kJ6jXtRuF2WLtBfLORc=
+    execa "^1.0.0"
+    lcid "^2.0.0"
+    mem "^4.0.0"
 
-os-tmpdir@^1.0.0, os-tmpdir@^1.0.1, os-tmpdir@~1.0.1, os-tmpdir@~1.0.2:
+os-tmpdir@^1.0.0:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
   integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=
@@ -4472,75 +3329,39 @@ osenv@^0.1.4:
     os-homedir "^1.0.0"
     os-tmpdir "^1.0.0"
 
-p-cancelable@^0.3.0:
-  version "0.3.0"
-  resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-0.3.0.tgz#b9e123800bcebb7ac13a479be195b507b98d30fa"
-  integrity sha512-RVbZPLso8+jFeq1MfNvgXtCRED2raz/dKpacfTNxsx6pLEpEomM7gah6VeHSYV3+vo0OAi4MkArtQcWWXuQoyw==
-
-p-each-series@^1.0.0:
+p-defer@^1.0.0:
   version "1.0.0"
-  resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-1.0.0.tgz#930f3d12dd1f50e7434457a22cd6f04ac6ad7f71"
-  integrity sha1-kw89Et0fUOdDRFeiLNbwSsatf3E=
-  dependencies:
-    p-reduce "^1.0.0"
+  resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c"
+  integrity sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=
 
 p-finally@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
   integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=
 
-p-is-promise@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-1.1.0.tgz#9c9456989e9f6588017b0434d56097675c3da05e"
-  integrity sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4=
-
-p-lazy@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/p-lazy/-/p-lazy-1.0.0.tgz#ec53c802f2ee3ac28f166cc82d0b2b02de27a835"
-  integrity sha1-7FPIAvLuOsKPFmzILQsrAt4nqDU=
-
-p-limit@^1.1.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.2.0.tgz#0e92b6bedcb59f022c13d0f1949dc82d15909f1c"
-  integrity sha512-Y/OtIaXtUPr4/YpMv1pCL5L5ed0rumAaAeBSj12F+bSlMdys7i8oQF/GUJmfpTS/QoaRrS/k6pma29haJpsMng==
-  dependencies:
-    p-try "^1.0.0"
-
-p-locate@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43"
-  integrity sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=
-  dependencies:
-    p-limit "^1.1.0"
-
-p-map@^1.1.1:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.2.0.tgz#e4e94f311eabbc8633a1e79908165fca26241b6b"
-  integrity sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==
-
-p-reduce@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/p-reduce/-/p-reduce-1.0.0.tgz#18c2b0dd936a4690a529f8231f58a0fdb6a47dfa"
-  integrity sha1-GMKw3ZNqRpClKfgjH1ig/bakffo=
+p-is-promise@^2.0.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-2.1.0.tgz#918cebaea248a62cf7ffab8e3bca8c5f882fc42e"
+  integrity sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==
 
-p-timeout@^1.1.1:
-  version "1.2.1"
-  resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-1.2.1.tgz#5eb3b353b7fce99f101a1038880bb054ebbea386"
-  integrity sha1-XrOzU7f86Z8QGhA4iAuwVOu+o4Y=
+p-limit@^2.0.0:
+  version "2.2.1"
+  resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.1.tgz#aa07a788cc3151c939b5131f63570f0dd2009537"
+  integrity sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==
   dependencies:
-    p-finally "^1.0.0"
+    p-try "^2.0.0"
 
-p-timeout@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-2.0.1.tgz#d8dd1979595d2dc0139e1fe46b8b646cb3cdf038"
-  integrity sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA==
+p-locate@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4"
+  integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==
   dependencies:
-    p-finally "^1.0.0"
+    p-limit "^2.0.0"
 
-p-try@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3"
-  integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=
+p-try@^2.0.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
+  integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
 
 pako@~1.0.5:
   version "1.0.6"
@@ -4577,13 +3398,6 @@ parse-glob@^3.0.4:
     is-extglob "^1.0.0"
     is-glob "^2.0.0"
 
-parse-json@^2.2.0:
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9"
-  integrity sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=
-  dependencies:
-    error-ex "^1.2.0"
-
 parse-passwd@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6"
@@ -4594,10 +3408,10 @@ pascalcase@^0.1.1:
   resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14"
   integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=
 
-path-browserify@0.0.0:
-  version "0.0.0"
-  resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.0.tgz#a0b870729aae214005b7d5032ec2cbbb0fb4451a"
-  integrity sha1-oLhwcpquIUAFt9UDLsLLuw+0RRo=
+path-browserify@0.0.1:
+  version "0.0.1"
+  resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a"
+  integrity sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==
 
 path-dirname@^1.0.0:
   version "1.0.2"
@@ -4609,7 +3423,7 @@ path-exists@^3.0.0:
   resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515"
   integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=
 
-path-is-absolute@^1.0.0, path-is-absolute@^1.0.1:
+path-is-absolute@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
   integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
@@ -4619,18 +3433,6 @@ path-key@^2.0.0, path-key@^2.0.1:
   resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40"
   integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=
 
-path-parse@^1.0.5:
-  version "1.0.5"
-  resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1"
-  integrity sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=
-
-path-type@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73"
-  integrity sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=
-  dependencies:
-    pify "^2.0.0"
-
 pause-stream@0.0.11:
   version "0.0.11"
   resolved "https://registry.yarnpkg.com/pause-stream/-/pause-stream-0.0.11.tgz#fe5a34b0cbce12b5aa6a2b403ee2e73b602f1445"
@@ -4664,15 +3466,15 @@ performance-now@^2.1.0:
   resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
   integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=
 
-pify@^2.0.0, pify@^2.3.0:
-  version "2.3.0"
-  resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
-  integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw=
+picomatch@^2.0.5:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.1.0.tgz#0fd042f568d08b1ad9ff2d3ec0f0bfb3cb80e177"
+  integrity sha512-uhnEDzAbrcJ8R3g2fANnSuXZMBtkpSjxTTgn2LeSiQlfmq72enQJWdQllXW24MBLYnA1SBD2vfvx2o0Zw3Ielw==
 
-pify@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176"
-  integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=
+pify@^4.0.1:
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231"
+  integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==
 
 pinkie-promise@^2.0.0:
   version "2.0.1"
@@ -4686,12 +3488,12 @@ pinkie@^2.0.0:
   resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
   integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA=
 
-pkg-dir@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b"
-  integrity sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=
+pkg-dir@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3"
+  integrity sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==
   dependencies:
-    find-up "^2.1.0"
+    find-up "^3.0.0"
 
 plugin-error@^0.1.2:
   version "0.1.2"
@@ -4709,36 +3511,11 @@ posix-character-classes@^0.1.0:
   resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab"
   integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=
 
-prepend-http@^1.0.1:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc"
-  integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=
-
-prepend-http@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897"
-  integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=
-
 preserve@^0.2.0:
   version "0.2.0"
   resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b"
   integrity sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=
 
-prettier@^1.5.3:
-  version "1.11.1"
-  resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.11.1.tgz#61e43fc4cd44e68f2b0dfc2c38cd4bb0fccdcc75"
-  integrity sha512-T/KD65Ot0PB97xTrG8afQ46x3oiVhnfGjGESSI9NWYcG92+OUPZKkwHqGWXH2t9jK1crnQjubECW0FuOth+hxw==
-
-pretty-bytes@^4.0.2:
-  version "4.0.2"
-  resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-4.0.2.tgz#b2bf82e7350d65c6c33aa95aaa5a4f6327f61cd9"
-  integrity sha1-sr+C5zUNZcbDOqlaqlpPYyf2HNk=
-
-private@^0.1.6, private@^0.1.7, private@~0.1.5:
-  version "0.1.8"
-  resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff"
-  integrity sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==
-
 process-nextick-args@^1.0.6, process-nextick-args@~1.0.6:
   version "1.0.7"
   resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3"
@@ -4754,11 +3531,6 @@ process@^0.11.10:
   resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
   integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI=
 
-process@~0.5.1:
-  version "0.5.2"
-  resolved "https://registry.yarnpkg.com/process/-/process-0.5.2.tgz#1638d8a8e34c2f440a91db95ab9aeb677fc185cf"
-  integrity sha1-FjjYqONML0QKkduVq5rrZ3/Bhc8=
-
 promise-inflight@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3"
@@ -4769,11 +3541,6 @@ prr@~1.0.1:
   resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476"
   integrity sha1-0/wRS6BplaRexok/SEzrHXj19HY=
 
-pseudomap@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
-  integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM=
-
 public-encrypt@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.0.tgz#39f699f3a46560dd5ebacbca693caf7c65c18cc6"
@@ -4785,7 +3552,7 @@ public-encrypt@^4.0.0:
     parse-asn1 "^5.0.0"
     randombytes "^2.0.1"
 
-pump@^2.0.0, pump@^2.0.1:
+pump@^2.0.0:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909"
   integrity sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==
@@ -4793,6 +3560,14 @@ pump@^2.0.0, pump@^2.0.1:
     end-of-stream "^1.1.0"
     once "^1.3.1"
 
+pump@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64"
+  integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==
+  dependencies:
+    end-of-stream "^1.1.0"
+    once "^1.3.1"
+
 pumpify@^1.3.3:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.4.0.tgz#80b7c5df7e24153d03f0e7ac8a05a5d068bd07fb"
@@ -4812,6 +3587,11 @@ punycode@^1.2.4, punycode@^1.4.1:
   resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
   integrity sha1-wNWmOycYgArY4esPpSachN1BhF4=
 
+punycode@^2.1.0:
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
+  integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
+
 qs@~6.3.0:
   version "6.3.2"
   resolved "https://registry.yarnpkg.com/qs/-/qs-6.3.2.tgz#e75bd5f6e268122a2a0e0bda630b2550c166502c"
@@ -4827,15 +3607,6 @@ qs@~6.5.1:
   resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8"
   integrity sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==
 
-query-string@^5.0.1:
-  version "5.1.0"
-  resolved "https://registry.yarnpkg.com/query-string/-/query-string-5.1.0.tgz#9583b15fd1307f899e973ed418886426a9976469"
-  integrity sha512-F3DkxxlY0AqD/rwe4YAwjRE2HjOkKW7TxsuteyrS/Jbwrxw887PqYBL4sWUJ9D/V1hmFns0SCD6FDyvlwo9RCQ==
-  dependencies:
-    decode-uri-component "^0.2.0"
-    object-assign "^4.1.0"
-    strict-uri-encode "^1.0.0"
-
 querystring-es3@^0.2.0:
   version "0.2.1"
   resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73"
@@ -4898,31 +3669,6 @@ rc@^1.1.7:
     minimist "^1.2.0"
     strip-json-comments "~2.0.1"
 
-read-chunk@^2.0.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/read-chunk/-/read-chunk-2.1.0.tgz#6a04c0928005ed9d42e1a6ac5600e19cbc7ff655"
-  integrity sha1-agTAkoAF7Z1C4aasVgDhnLx/9lU=
-  dependencies:
-    pify "^3.0.0"
-    safe-buffer "^5.1.1"
-
-read-pkg-up@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be"
-  integrity sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=
-  dependencies:
-    find-up "^2.0.0"
-    read-pkg "^2.0.0"
-
-read-pkg@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8"
-  integrity sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=
-  dependencies:
-    load-json-file "^2.0.0"
-    normalize-package-data "^2.3.2"
-    path-type "^2.0.0"
-
 "readable-stream@1 || 2", readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.2.2, readable-stream@^2.3.3:
   version "2.3.5"
   resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.5.tgz#b4f85003a938cbb6ecbce2a124fb1012bd1a838d"
@@ -4979,53 +3725,6 @@ readdirp@^2.0.0:
     readable-stream "^2.0.2"
     set-immediate-shim "^1.0.1"
 
-recast@^0.12.5:
-  version "0.12.9"
-  resolved "https://registry.yarnpkg.com/recast/-/recast-0.12.9.tgz#e8e52bdb9691af462ccbd7c15d5a5113647a15f1"
-  integrity sha512-y7ANxCWmMW8xLOaiopiRDlyjQ9ajKRENBH+2wjntIbk3A6ZR1+BLQttkmSHMY7Arl+AAZFwJ10grg2T6f1WI8A==
-  dependencies:
-    ast-types "0.10.1"
-    core-js "^2.4.1"
-    esprima "~4.0.0"
-    private "~0.1.5"
-    source-map "~0.6.1"
-
-recast@^0.14.4:
-  version "0.14.4"
-  resolved "https://registry.yarnpkg.com/recast/-/recast-0.14.4.tgz#383dd606eac924c1157b0293b53191c34bd3c641"
-  integrity sha512-b6fRXujYf8mTIyljymL3rglje1LfuGKdD44CuKs6o1B18MmZ+mEEpD5gsaxGVABZHyPvYwPLcyBTA/SvxtiyFg==
-  dependencies:
-    ast-types "0.11.2"
-    esprima "~4.0.0"
-    private "~0.1.5"
-    source-map "~0.6.1"
-
-rechoir@^0.6.2:
-  version "0.6.2"
-  resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384"
-  integrity sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=
-  dependencies:
-    resolve "^1.1.6"
-
-regenerate@^1.2.1:
-  version "1.3.3"
-  resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.3.3.tgz#0c336d3980553d755c39b586ae3b20aa49c82b7f"
-  integrity sha512-jVpo1GadrDAK59t/0jRx5VxYWQEDkkEKi6+HjE3joFVLfDOh9Xrdh0dF1eSq+BI/SwvTQ44gSscJ8N5zYL61sg==
-
-regenerator-runtime@^0.11.0:
-  version "0.11.1"
-  resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
-  integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==
-
-regenerator-transform@^0.10.0:
-  version "0.10.1"
-  resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.10.1.tgz#1e4996837231da8b7f3cf4114d71b5691a0680dd"
-  integrity sha512-PJepbvDbuK1xgIgnau7Y90cwaAmO/LCLMI2mPvaXq2heGMR3aWW5/BQvYrhJ8jgmQjXewXvBjzfqKcVOmhjZ6Q==
-  dependencies:
-    babel-runtime "^6.18.0"
-    babel-types "^6.19.0"
-    private "^0.1.6"
-
 regex-cache@^0.4.2:
   version "0.4.4"
   resolved "https://registry.yarnpkg.com/regex-cache/-/regex-cache-0.4.4.tgz#75bdc58a2a1496cec48a12835bc54c8d562336dd"
@@ -5041,27 +3740,6 @@ regex-not@^1.0.0, regex-not@^1.0.2:
     extend-shallow "^3.0.2"
     safe-regex "^1.1.0"
 
-regexpu-core@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-2.0.0.tgz#49d038837b8dcf8bfa5b9a42139938e6ea2ae240"
-  integrity sha1-SdA4g3uNz4v6W5pCE5k45uoq4kA=
-  dependencies:
-    regenerate "^1.2.1"
-    regjsgen "^0.2.0"
-    regjsparser "^0.1.4"
-
-regjsgen@^0.2.0:
-  version "0.2.0"
-  resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.2.0.tgz#6c016adeac554f75823fe37ac05b92d5a4edb1f7"
-  integrity sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=
-
-regjsparser@^0.1.4:
-  version "0.1.5"
-  resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.1.5.tgz#7ee8f84dc6fa792d3fd0ae228d24bd949ead205c"
-  integrity sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=
-  dependencies:
-    jsesc "~0.5.0"
-
 remove-trailing-separator@^1.0.1:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef"
@@ -5077,13 +3755,6 @@ repeat-string@^1.5.2, repeat-string@^1.6.1:
   resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637"
   integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc=
 
-repeating@^2.0.0:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda"
-  integrity sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=
-  dependencies:
-    is-finite "^1.0.0"
-
 replace-ext@0.0.1:
   version "0.0.1"
   resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-0.0.1.tgz#29bbd92078a739f0bcce2b4ee41e837953522924"
@@ -5181,10 +3852,10 @@ require-directory@^2.1.1:
   resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
   integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I=
 
-require-main-filename@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1"
-  integrity sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=
+require-main-filename@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b"
+  integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==
 
 requires-port@~1.0.0:
   version "1.0.0"
@@ -5198,7 +3869,7 @@ resolve-cwd@^2.0.0:
   dependencies:
     resolve-from "^3.0.0"
 
-resolve-dir@^1.0.0:
+resolve-dir@^1.0.0, resolve-dir@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-1.0.1.tgz#79a40644c362be82f26effe739c9bb5382046f43"
   integrity sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=
@@ -5216,52 +3887,24 @@ resolve-url@^0.2.1:
   resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
   integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=
 
-resolve@^1.1.6:
-  version "1.5.0"
-  resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.5.0.tgz#1f09acce796c9a762579f31b2c1cc4c3cddf9f36"
-  integrity sha512-hgoSGrc3pjzAPHNBg+KnFcK2HwlHTs/YrAGUr6qgTVUZmXv1UEXXl0bZNBKMA9fud6lRYFdPGz0xXxycPzmmiw==
-  dependencies:
-    path-parse "^1.0.5"
-
-responselike@1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7"
-  integrity sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=
-  dependencies:
-    lowercase-keys "^1.0.0"
-
-restore-cursor@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-1.0.1.tgz#34661f46886327fed2991479152252df92daa541"
-  integrity sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=
-  dependencies:
-    exit-hook "^1.0.0"
-    onetime "^1.0.0"
-
-restore-cursor@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf"
-  integrity sha1-n37ih/gv0ybU/RYpI9YhKe7g368=
-  dependencies:
-    onetime "^2.0.0"
-    signal-exit "^3.0.2"
-
 ret@~0.1.10:
   version "0.1.15"
   resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc"
   integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==
 
-rimraf@2, rimraf@^2.2.0, rimraf@^2.2.8, rimraf@^2.5.1, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2:
+rimraf@2, rimraf@^2.5.1, rimraf@^2.5.4, rimraf@^2.6.1:
   version "2.6.2"
   resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36"
   integrity sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==
   dependencies:
     glob "^7.0.5"
 
-rimraf@~2.2.6:
-  version "2.2.8"
-  resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.2.8.tgz#e439be2aaee327321952730f99a8929e4fc50582"
-  integrity sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=
+rimraf@^2.6.3:
+  version "2.7.1"
+  resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
+  integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==
+  dependencies:
+    glob "^7.1.3"
 
 ripemd160@^2.0.0, ripemd160@^2.0.1:
   version "2.0.1"
@@ -5271,13 +3914,6 @@ ripemd160@^2.0.0, ripemd160@^2.0.1:
     hash-base "^2.0.0"
     inherits "^2.0.1"
 
-run-async@^2.0.0, run-async@^2.2.0:
-  version "2.3.0"
-  resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0"
-  integrity sha1-A3GrSuC91yDUFm19/aZP96RFpsA=
-  dependencies:
-    is-promise "^2.1.0"
-
 run-queue@^1.0.0, run-queue@^1.0.3:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/run-queue/-/run-queue-1.0.3.tgz#e848396f057d223f24386924618e25694161ec47"
@@ -5285,30 +3921,6 @@ run-queue@^1.0.0, run-queue@^1.0.3:
   dependencies:
     aproba "^1.1.1"
 
-rx-lite-aggregates@^4.0.8:
-  version "4.0.8"
-  resolved "https://registry.yarnpkg.com/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz#753b87a89a11c95467c4ac1626c4efc4e05c67be"
-  integrity sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74=
-  dependencies:
-    rx-lite "*"
-
-rx-lite@*, rx-lite@^4.0.8:
-  version "4.0.8"
-  resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-4.0.8.tgz#0b1e11af8bc44836f04a6407e92da42467b79444"
-  integrity sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=
-
-rx@^4.1.0:
-  version "4.1.0"
-  resolved "https://registry.yarnpkg.com/rx/-/rx-4.1.0.tgz#a5f13ff79ef3b740fe30aa803fb09f98805d4782"
-  integrity sha1-pfE/957zt0D+MKqAP7CfmIBdR4I=
-
-rxjs@^5.4.2, rxjs@^5.5.2:
-  version "5.5.6"
-  resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.5.6.tgz#e31fb96d6fd2ff1fd84bcea8ae9c02d007179c02"
-  integrity sha512-v4Q5HDC0FHAQ7zcBX7T2IL6O5ltl1a2GX4ENjPXg6SjDY69Cmx9v4113C99a4wGF16ClPv5Z8mghuYorVkg/kg==
-  dependencies:
-    symbol-observable "1.0.1"
-
 safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
   version "5.1.1"
   resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853"
@@ -5321,28 +3933,34 @@ safe-regex@^1.1.0:
   dependencies:
     ret "~0.1.10"
 
-schema-utils@^0.4.2:
-  version "0.4.5"
-  resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.4.5.tgz#21836f0608aac17b78f9e3e24daff14a5ca13a3e"
-  integrity sha512-yYrjb9TX2k/J1Y5UNy3KYdZq10xhYcF8nMpAW6o3hy6Q8WSIEf9lJHG/ePnOBfziPM3fvQwfOwa13U/Fh8qTfA==
+schema-utils@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770"
+  integrity sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==
   dependencies:
     ajv "^6.1.0"
+    ajv-errors "^1.0.0"
     ajv-keywords "^3.1.0"
 
-scoped-regex@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/scoped-regex/-/scoped-regex-1.0.0.tgz#a346bb1acd4207ae70bd7c0c7ca9e566b6baddb8"
-  integrity sha1-o0a7Gs1CB65wvXwMfKnlZra63bg=
-
-"semver@2 || 3 || 4 || 5", semver@^5.0.1, semver@^5.3.0, semver@^5.4.1, semver@^5.5.0:
+semver@^5.3.0, semver@^5.4.1, semver@^5.5.0:
   version "5.5.0"
   resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab"
   integrity sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==
 
-serialize-javascript@^1.4.0:
-  version "1.4.0"
-  resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.4.0.tgz#7c958514db6ac2443a8abc062dc9f7886a7f6005"
-  integrity sha1-fJWFFNtqwkQ6irwGLcn3iGp/YAU=
+semver@^5.6.0:
+  version "5.7.1"
+  resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
+  integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
+
+semver@^6.0.0:
+  version "6.3.0"
+  resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
+  integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
+
+serialize-javascript@^1.7.0:
+  version "1.9.1"
+  resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.9.1.tgz#cfc200aef77b600c47da9bb8149c943e798c2fdb"
+  integrity sha512-0Vb/54WJ6k5v8sSWN09S0ora+Hnr+cX40r9F170nT+mSkaxltoE/7R3OrIdBSUv1OoiobH1QoWQbCnAO+e8J1A==
 
 set-blocking@^2.0.0, set-blocking@~2.0.0:
   version "2.0.0"
@@ -5406,35 +4024,11 @@ shebang-regex@^1.0.0:
   resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3"
   integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=
 
-shelljs@^0.7.0:
-  version "0.7.8"
-  resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.7.8.tgz#decbcf874b0d1e5fb72e14b164a9683048e9acb3"
-  integrity sha1-3svPh0sNHl+3LhSxZKloMEjprLM=
-  dependencies:
-    glob "^7.0.0"
-    interpret "^1.0.0"
-    rechoir "^0.6.2"
-
-signal-exit@^3.0.0, signal-exit@^3.0.2:
+signal-exit@^3.0.0:
   version "3.0.2"
   resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
   integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=
 
-slash@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55"
-  integrity sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=
-
-slice-ansi@0.0.4:
-  version "0.0.4"
-  resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35"
-  integrity sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=
-
-slide@^1.1.5:
-  version "1.1.6"
-  resolved "https://registry.yarnpkg.com/slide/-/slide-1.1.6.tgz#56eb027d65b4d2dce6cb2e2d32c4d4afc9e1d707"
-  integrity sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=
-
 snapdragon-node@^2.0.1:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b"
@@ -5479,13 +4073,6 @@ sntp@2.x.x:
   dependencies:
     hoek "4.x.x"
 
-sort-keys@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-2.0.0.tgz#658535584861ec97d730d6cf41822e1f56684128"
-  integrity sha1-ZYU1WEhh7JfXMNbPQYIuH1ZoQSg=
-  dependencies:
-    is-plain-obj "^1.0.0"
-
 source-list-map@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.0.tgz#aaa47403f7b245a92fbc97ea08f250d6087ed085"
@@ -5502,13 +4089,6 @@ source-map-resolve@^0.5.0:
     source-map-url "^0.4.0"
     urix "^0.1.0"
 
-source-map-support@^0.4.15:
-  version "0.4.18"
-  resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f"
-  integrity sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==
-  dependencies:
-    source-map "^0.5.6"
-
 source-map-support@^0.5.0:
   version "0.5.3"
   resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.3.tgz#2b3d5fff298cfa4d1afd7d4352d569e9a0158e76"
@@ -5516,12 +4096,20 @@ source-map-support@^0.5.0:
   dependencies:
     source-map "^0.6.0"
 
+source-map-support@~0.5.12:
+  version "0.5.16"
+  resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.16.tgz#0ae069e7fe3ba7538c64c98515e35339eac5a042"
+  integrity sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==
+  dependencies:
+    buffer-from "^1.0.0"
+    source-map "^0.6.0"
+
 source-map-url@^0.4.0:
   version "0.4.0"
   resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3"
   integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=
 
-source-map@^0.5.6, source-map@^0.5.7:
+source-map@^0.5.6:
   version "0.5.7"
   resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
   integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=
@@ -5536,40 +4124,6 @@ sparkles@^1.0.0:
   resolved "https://registry.yarnpkg.com/sparkles/-/sparkles-1.0.0.tgz#1acbbfb592436d10bbe8f785b7cc6f82815012c3"
   integrity sha1-Gsu/tZJDbRC76PeFt8xvgoFQEsM=
 
-spawn-sync@^1.0.15:
-  version "1.0.15"
-  resolved "https://registry.yarnpkg.com/spawn-sync/-/spawn-sync-1.0.15.tgz#b00799557eb7fb0c8376c29d44e8a1ea67e57476"
-  integrity sha1-sAeZVX63+wyDdsKdROih6mfldHY=
-  dependencies:
-    concat-stream "^1.4.7"
-    os-shim "^0.1.2"
-
-spdx-correct@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.0.0.tgz#05a5b4d7153a195bc92c3c425b69f3b2a9524c82"
-  integrity sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==
-  dependencies:
-    spdx-expression-parse "^3.0.0"
-    spdx-license-ids "^3.0.0"
-
-spdx-exceptions@^2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz#2c7ae61056c714a5b9b9b2b2af7d311ef5c78fe9"
-  integrity sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==
-
-spdx-expression-parse@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz#99e119b7a5da00e05491c9fa338b7904823b41d0"
-  integrity sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==
-  dependencies:
-    spdx-exceptions "^2.1.0"
-    spdx-license-ids "^3.0.0"
-
-spdx-license-ids@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz#7a7cd28470cc6d3a1cfe6d66886f6bc430d3ac87"
-  integrity sha512-2+EPwgbnmOIl8HjGBXXMd9NAu02vLjOO1nWw4kmeRDFyHn+M/ETfHxQUK0oXg8ctgVnl9t3rosNVsZ1jG61nDA==
-
 split-string@^3.0.1, split-string@^3.0.2:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2"
@@ -5604,12 +4158,12 @@ sshpk@^1.7.0:
     jsbn "~0.1.0"
     tweetnacl "~0.14.0"
 
-ssri@^5.2.4:
-  version "5.2.4"
-  resolved "https://registry.yarnpkg.com/ssri/-/ssri-5.2.4.tgz#9985e14041e65fc397af96542be35724ac11da52"
-  integrity sha512-UnEAgMZa15973iH7cUi0AHjJn1ACDIkaMyZILoqwN6yzt+4P81I8tBc5Hl+qwi5auMplZtPQsHrPBR5vJLcQtQ==
+ssri@^6.0.1:
+  version "6.0.1"
+  resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.1.tgz#2a3c41b28dd45b62b63676ecb74001265ae9edd8"
+  integrity sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==
   dependencies:
-    safe-buffer "^5.1.1"
+    figgy-pudding "^3.5.1"
 
 stat-mode@^0.2.0:
   version "0.2.2"
@@ -5663,13 +4217,6 @@ stream-shift@^1.0.0:
   resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952"
   integrity sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=
 
-stream-to-observable@^0.2.0:
-  version "0.2.0"
-  resolved "https://registry.yarnpkg.com/stream-to-observable/-/stream-to-observable-0.2.0.tgz#59d6ea393d87c2c0ddac10aa0d561bc6ba6f0e10"
-  integrity sha1-WdbqOT2HwsDdrBCqDVYbxrpvDhA=
-  dependencies:
-    any-observable "^0.2.0"
-
 streamfilter@^1.0.5:
   version "1.0.7"
   resolved "https://registry.yarnpkg.com/streamfilter/-/streamfilter-1.0.7.tgz#ae3e64522aa5a35c061fd17f67620c7653c643c9"
@@ -5682,16 +4229,6 @@ streamifier@~0.1.1:
   resolved "https://registry.yarnpkg.com/streamifier/-/streamifier-0.1.1.tgz#97e98d8fa4d105d62a2691d1dc07e820db8dfc4f"
   integrity sha1-l+mNj6TRBdYqJpHR3AfoINuN/E8=
 
-strict-uri-encode@^1.0.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713"
-  integrity sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=
-
-string-template@~0.2.1:
-  version "0.2.1"
-  resolved "https://registry.yarnpkg.com/string-template/-/string-template-0.2.1.tgz#42932e598a352d01fc22ec3367d9d84eec6c9add"
-  integrity sha1-QpMuWYo1LQH8IuwzZ9nYTuxsmt0=
-
 string-width@^1.0.1, string-width@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3"
@@ -5701,13 +4238,14 @@ string-width@^1.0.1, string-width@^1.0.2:
     is-fullwidth-code-point "^1.0.0"
     strip-ansi "^3.0.0"
 
-string-width@^2.0.0, string-width@^2.1.0:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
-  integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==
+string-width@^3.0.0, string-width@^3.1.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961"
+  integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==
   dependencies:
+    emoji-regex "^7.0.1"
     is-fullwidth-code-point "^2.0.0"
-    strip-ansi "^4.0.0"
+    strip-ansi "^5.1.0"
 
 string_decoder@^1.0.0, string_decoder@~1.0.3:
   version "1.0.3"
@@ -5740,10 +4278,12 @@ strip-ansi@^4.0.0:
   dependencies:
     ansi-regex "^3.0.0"
 
-strip-ansi@~0.1.0:
-  version "0.1.1"
-  resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-0.1.1.tgz#39e8a98d044d150660abe4a6808acf70bb7bc991"
-  integrity sha1-OeipjQRNFQZgq+SmgIrPcLt7yZE=
+strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0:
+  version "5.2.0"
+  resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae"
+  integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==
+  dependencies:
+    ansi-regex "^4.1.0"
 
 strip-bom-stream@^1.0.0:
   version "1.0.0"
@@ -5753,14 +4293,6 @@ strip-bom-stream@^1.0.0:
     first-chunk-stream "^1.0.0"
     strip-bom "^2.0.0"
 
-strip-bom-stream@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/strip-bom-stream/-/strip-bom-stream-2.0.0.tgz#f87db5ef2613f6968aa545abfe1ec728b6a829ca"
-  integrity sha1-+H217yYT9paKpUWr/h7HKLaoKco=
-  dependencies:
-    first-chunk-stream "^2.0.0"
-    strip-bom "^2.0.0"
-
 strip-bom@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e"
@@ -5768,11 +4300,6 @@ strip-bom@^2.0.0:
   dependencies:
     is-utf8 "^0.2.0"
 
-strip-bom@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
-  integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=
-
 strip-eof@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"
@@ -5790,33 +4317,35 @@ supports-color@4.4.0:
   dependencies:
     has-flag "^2.0.0"
 
+supports-color@6.1.0:
+  version "6.1.0"
+  resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3"
+  integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==
+  dependencies:
+    has-flag "^3.0.0"
+
 supports-color@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
   integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=
 
-supports-color@^5.2.0, supports-color@^5.3.0:
+supports-color@^5.3.0:
   version "5.3.0"
   resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.3.0.tgz#5b24ac15db80fa927cf5227a4a33fd3c4c7676c0"
   integrity sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==
   dependencies:
     has-flag "^3.0.0"
 
-symbol-observable@1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.1.tgz#8340fc4702c3122df5d22288f88283f513d3fdd4"
-  integrity sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ=
-
-symbol-observable@^0.2.2:
-  version "0.2.4"
-  resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-0.2.4.tgz#95a83db26186d6af7e7a18dbd9760a2f86d08f40"
-  integrity sha1-lag9smGG1q9+ehjb2XYKL4bQj0A=
-
 tapable@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.0.0.tgz#cbb639d9002eed9c6b5975eb20598d7936f1f9f2"
   integrity sha512-dQRhbNQkRnaqauC7WqSJ21EEksgT0fYZX2lqXzGkpo8JNig9zGZTYoMGvyI2nWmXlE2VSVXVDu7wLVGu/mQEsg==
 
+tapable@^1.1.3:
+  version "1.1.3"
+  resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2"
+  integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==
+
 tar-pack@^3.4.0:
   version "3.4.1"
   resolved "https://registry.yarnpkg.com/tar-pack/-/tar-pack-3.4.1.tgz#e1dbc03a9b9d3ba07e896ad027317eb679a10a1f"
@@ -5840,23 +4369,29 @@ tar@^2.2.1:
     fstream "^1.0.2"
     inherits "2"
 
-temp@^0.8.1:
-  version "0.8.3"
-  resolved "https://registry.yarnpkg.com/temp/-/temp-0.8.3.tgz#e0c6bc4d26b903124410e4fed81103014dfc1f59"
-  integrity sha1-4Ma8TSa5AxJEEOT+2BEDAU38H1k=
-  dependencies:
-    os-tmpdir "^1.0.0"
-    rimraf "~2.2.6"
-
-text-table@^0.2.0:
-  version "0.2.0"
-  resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
-  integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=
+terser-webpack-plugin@^1.4.1:
+  version "1.4.1"
+  resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.1.tgz#61b18e40eaee5be97e771cdbb10ed1280888c2b4"
+  integrity sha512-ZXmmfiwtCLfz8WKZyYUuuHf3dMYEjg8NrjHMb0JqHVHVOSkzp3cW2/XG1fP3tRhqEqSzMwzzRQGtAPbs4Cncxg==
+  dependencies:
+    cacache "^12.0.2"
+    find-cache-dir "^2.1.0"
+    is-wsl "^1.1.0"
+    schema-utils "^1.0.0"
+    serialize-javascript "^1.7.0"
+    source-map "^0.6.1"
+    terser "^4.1.2"
+    webpack-sources "^1.4.0"
+    worker-farm "^1.7.0"
 
-textextensions@2:
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/textextensions/-/textextensions-2.2.0.tgz#38ac676151285b658654581987a0ce1a4490d286"
-  integrity sha512-j5EMxnryTvKxwH2Cq+Pb43tsf6sdEgw6Pdwxk83mPaq0ToeFJt6WE4J3s5BqY7vmjlLgkgXvhtXUxo80FyBhCA==
+terser@^4.1.2:
+  version "4.3.9"
+  resolved "https://registry.yarnpkg.com/terser/-/terser-4.3.9.tgz#e4be37f80553d02645668727777687dad26bbca8"
+  integrity sha512-NFGMpHjlzmyOtPL+fDw3G7+6Ueh/sz4mkaUYa4lJCxOPTNzd0Uj0aZJOmsDYoSQyfuVoWDMSWTPU3huyOm2zdA==
+  dependencies:
+    commander "^2.20.0"
+    source-map "~0.6.1"
+    source-map-support "~0.5.12"
 
 through2-filter@^2.0.0:
   version "2.0.0"
@@ -5882,7 +4417,7 @@ through2@^2.0.0, through2@^2.0.1, through2@^2.0.3, through2@~2.0.0, through2@~2.
     readable-stream "^2.1.5"
     xtend "~4.0.1"
 
-through@2, through@^2.3.6, through@~2.3, through@~2.3.1:
+through@2, through@~2.3, through@~2.3.1:
   version "2.3.8"
   resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
   integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=
@@ -5892,11 +4427,6 @@ time-stamp@^1.0.0:
   resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-1.1.0.tgz#764a5a11af50561921b133f3b44e618687e0f5c3"
   integrity sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=
 
-timed-out@^4.0.0, timed-out@^4.0.1:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f"
-  integrity sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=
-
 timers-browserify@^2.0.4:
   version "2.0.6"
   resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.6.tgz#241e76927d9ca05f4d959819022f5b3664b64bae"
@@ -5904,20 +4434,6 @@ timers-browserify@^2.0.4:
   dependencies:
     setimmediate "^1.0.4"
 
-tmp@^0.0.29:
-  version "0.0.29"
-  resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.29.tgz#f25125ff0dd9da3ccb0c2dd371ee1288bb9128c0"
-  integrity sha1-8lEl/w3Z2jzLDC3Tce4SiLuRKMA=
-  dependencies:
-    os-tmpdir "~1.0.1"
-
-tmp@^0.0.33:
-  version "0.0.33"
-  resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
-  integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==
-  dependencies:
-    os-tmpdir "~1.0.2"
-
 to-absolute-glob@^0.1.1:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/to-absolute-glob/-/to-absolute-glob-0.1.1.tgz#1cdfa472a9ef50c239ee66999b662ca0eb39937f"
@@ -5930,11 +4446,6 @@ to-arraybuffer@^1.0.0:
   resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43"
   integrity sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=
 
-to-fast-properties@^1.0.3:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47"
-  integrity sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=
-
 to-object-path@^0.3.0:
   version "0.3.0"
   resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af"
@@ -5950,7 +4461,14 @@ to-regex-range@^2.1.0:
     is-number "^3.0.0"
     repeat-string "^1.6.1"
 
-to-regex@^3.0.1:
+to-regex-range@^5.0.1:
+  version "5.0.1"
+  resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
+  integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
+  dependencies:
+    is-number "^7.0.0"
+
+to-regex@^3.0.1, to-regex@^3.0.2:
   version "3.0.2"
   resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce"
   integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==
@@ -5967,21 +4485,21 @@ tough-cookie@~2.3.0, tough-cookie@~2.3.3:
   dependencies:
     punycode "^1.4.1"
 
-trim-right@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003"
-  integrity sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=
-
-ts-loader@^4.0.1:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-4.0.1.tgz#3d920b059966efec9637133ab0ca9b04d625d59a"
-  integrity sha512-dzgQnkAGY4sLqVw6t4LJH8FGR8j0zsPmC1Ff2ChzKhYO+hgWJkSEyrHTlSSZqn5oWr0Po7q/IRoeq8O4SNKQ9A==
+ts-loader@^6.2.1:
+  version "6.2.1"
+  resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-6.2.1.tgz#67939d5772e8a8c6bdaf6277ca023a4812da02ef"
+  integrity sha512-Dd9FekWuABGgjE1g0TlQJ+4dFUfYGbYcs52/HQObE0ZmUNjQlmLAS7xXsSzy23AMaMwipsx5sNHvoEpT2CZq1g==
   dependencies:
     chalk "^2.3.0"
     enhanced-resolve "^4.0.0"
     loader-utils "^1.0.2"
-    micromatch "^3.1.4"
-    semver "^5.0.1"
+    micromatch "^4.0.0"
+    semver "^6.0.0"
+
+tslib@^1.9.0:
+  version "1.10.0"
+  resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a"
+  integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==
 
 tty-browserify@0.0.0:
   version "0.0.0"
@@ -6010,10 +4528,10 @@ typedarray@^0.0.6:
   resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
   integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
 
-typescript@^3.3.1:
-  version "3.3.1"
-  resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.3.1.tgz#6de14e1db4b8a006ac535e482c8ba018c55f750b"
-  integrity sha512-cTmIDFW7O0IHbn1DPYjkiebHxwtCMU+eTy30ZtJNBPF9j2O1ITu5XH2YnBeVRKWHqF+3JQwWJv0Q0aUgX8W7IA==
+typescript@^3.7.2:
+  version "3.7.2"
+  resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.2.tgz#27e489b95fa5909445e9fef5ee48d81697ad18fb"
+  integrity sha512-ml7V7JfiN2Xwvcer+XAf2csGO1bPBdRbFCkYBczNZggrBZ9c7G3riSUeJmqEU5uOtXNPMhE3n+R4FA/3YOAWOQ==
 
 uc.micro@^1.0.1:
   version "1.0.3"
@@ -6025,38 +4543,11 @@ uc.micro@^1.0.5:
   resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.5.tgz#0c65f15f815aa08b560a61ce8b4db7ffc3f45376"
   integrity sha512-JoLI4g5zv5qNyT09f4YAvEZIIV1oOjqnewYg5D38dkQljIzpPT296dbIGvKro3digYI1bkb7W6EP1y4uDlmzLg==
 
-uglify-es@^3.3.4:
-  version "3.3.10"
-  resolved "https://registry.yarnpkg.com/uglify-es/-/uglify-es-3.3.10.tgz#8b0b7992cebe20edc26de1bf325cef797b8f3fa5"
-  integrity sha512-rPzPisCzW68Okj1zNrfa2dR9uEm43SevDmpR6FChoZABFk9dANGnzzBMgHYUXI3609//63fnVkyQ1SQmAMyjww==
-  dependencies:
-    commander "~2.14.1"
-    source-map "~0.6.1"
-
-uglifyjs-webpack-plugin@^1.1.1, uglifyjs-webpack-plugin@^1.2.2:
-  version "1.2.2"
-  resolved "https://registry.yarnpkg.com/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-1.2.2.tgz#e7516d4367afdb715c3847841eb46f94c45ca2b9"
-  integrity sha512-CG/NvzXfemUAm5Y4Guh5eEaJYHtkG7kKNpXEJHp9QpxsFVB5/qKvYWoMaq4sa99ccZ0hM3MK8vQV9XPZB4357A==
-  dependencies:
-    cacache "^10.0.1"
-    find-cache-dir "^1.0.0"
-    schema-utils "^0.4.2"
-    serialize-javascript "^1.4.0"
-    source-map "^0.6.1"
-    uglify-es "^3.3.4"
-    webpack-sources "^1.1.0"
-    worker-farm "^1.5.2"
-
 uid-number@^0.0.6:
   version "0.0.6"
   resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81"
   integrity sha1-DqEOgDXo61uOREnwbaHHMGY7qoE=
 
-underscore@~1.6.0:
-  version "1.6.0"
-  resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.6.0.tgz#8b38b10cacdef63337b8b24e4ff86d45aea529a8"
-  integrity sha1-izixDKze9jM3uLJOT/htRa6lKag=
-
 union-value@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.0.tgz#5c71c34cb5bad5dcebe3ea0cd08207ba5aa1aea4"
@@ -6067,10 +4558,10 @@ union-value@^1.0.0:
     is-extendable "^0.1.1"
     set-value "^0.4.3"
 
-unique-filename@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.0.tgz#d05f2fe4032560871f30e93cbe735eea201514f3"
-  integrity sha1-0F8v5AMlYIcfMOk8vnNe6iAVFPM=
+unique-filename@^1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230"
+  integrity sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==
   dependencies:
     unique-slug "^2.0.0"
 
@@ -6097,42 +4588,23 @@ unset-value@^1.0.0:
     has-value "^0.3.1"
     isobject "^3.0.0"
 
-untildify@^2.0.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/untildify/-/untildify-2.1.0.tgz#17eb2807987f76952e9c0485fc311d06a826a2e0"
-  integrity sha1-F+soB5h/dpUunASF/DEdBqgmouA=
-  dependencies:
-    os-homedir "^1.0.0"
-
-untildify@^3.0.2:
-  version "3.0.2"
-  resolved "https://registry.yarnpkg.com/untildify/-/untildify-3.0.2.tgz#7f1f302055b3fea0f3e81dc78eb36766cb65e3f1"
-  integrity sha1-fx8wIFWz/qDz6B3HjrNnZstl4/E=
-
 upath@^1.0.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/upath/-/upath-1.1.0.tgz#35256597e46a581db4793d0ce47fa9aebfc9fabd"
   integrity sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw==
 
+uri-js@^4.2.2:
+  version "4.2.2"
+  resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0"
+  integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==
+  dependencies:
+    punycode "^2.1.0"
+
 urix@^0.1.0:
   version "0.1.0"
   resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72"
   integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=
 
-url-parse-lax@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-1.0.0.tgz#7af8f303645e9bd79a272e7a14ac68bc0609da73"
-  integrity sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=
-  dependencies:
-    prepend-http "^1.0.1"
-
-url-parse-lax@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c"
-  integrity sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=
-  dependencies:
-    prepend-http "^2.0.0"
-
 url-parse@^1.1.9:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.2.0.tgz#3a19e8aaa6d023ddd27dcc44cb4fc8f7fec23986"
@@ -6141,11 +4613,6 @@ url-parse@^1.1.9:
     querystringify "~1.0.0"
     requires-port "~1.0.0"
 
-url-to-options@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/url-to-options/-/url-to-options-1.0.1.tgz#1505a03a289a48cbd7a434efbaeec5055f5633a9"
-  integrity sha1-FQWgOiiaSMvXpDTvuu7FBV9WM6k=
-
 url@^0.11.0:
   version "0.11.0"
   resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1"
@@ -6154,11 +4621,6 @@ url@^0.11.0:
     punycode "1.3.2"
     querystring "0.2.0"
 
-urlgrey@0.4.4:
-  version "0.4.4"
-  resolved "https://registry.yarnpkg.com/urlgrey/-/urlgrey-0.4.4.tgz#892fe95960805e85519f1cd4389f2cb4cbb7652f"
-  integrity sha1-iS/pWWCAXoVRnxzUOJ8stMu3ZS8=
-
 use@^2.0.0:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/use/-/use-2.0.2.tgz#ae28a0d72f93bf22422a18a2e379993112dec8e8"
@@ -6173,36 +4635,35 @@ util-deprecate@~1.0.1:
   resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
   integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
 
-util@0.10.3, util@^0.10.3:
+util@0.10.3:
   version "0.10.3"
   resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9"
   integrity sha1-evsa/lCAUkZInj23/g7TeTNqwPk=
   dependencies:
     inherits "2.0.1"
 
+util@^0.11.0:
+  version "0.11.1"
+  resolved "https://registry.yarnpkg.com/util/-/util-0.11.1.tgz#3236733720ec64bb27f6e26f421aaa2e1b588d61"
+  integrity sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==
+  dependencies:
+    inherits "2.0.3"
+
 uuid@^3.0.0, uuid@^3.1.0:
   version "3.2.1"
   resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.2.1.tgz#12c528bb9d58d0b9265d9a2f6f0fe8be17ff1f14"
   integrity sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==
 
-v8-compile-cache@^1.1.2:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-1.1.2.tgz#8d32e4f16974654657e676e0e467a348e89b0dc4"
-  integrity sha512-ejdrifsIydN1XDH7EuR2hn8ZrkRKUYF7tUcBjBy/lhrCvs2K+zRlbW9UHc0IQ9RsYFZJFqJrieoIHfkCa0DBRA==
+v8-compile-cache@2.0.3:
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz#00f7494d2ae2b688cfe2899df6ed2c54bef91dbe"
+  integrity sha512-CNmdbwQMBjwr9Gsmohvm0pbL954tJrNzf6gWL3K+QMQf00PF7ERGrEiLgjuU3mKreLC2MeGhUsNV9ybTbLgd3w==
 
 vali-date@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/vali-date/-/vali-date-1.0.0.tgz#1b904a59609fb328ef078138420934f6b86709a6"
   integrity sha1-G5BKWWCfsyjvB4E4Qgk09rhnCaY=
 
-validate-npm-package-license@^3.0.1:
-  version "3.0.3"
-  resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz#81643bcbef1bdfecd4623793dc4648948ba98338"
-  integrity sha512-63ZOUnL4SIXj4L0NixR3L1lcjO38crAbgrTpl28t8jjrfuiOBL5Iygm+60qPs/KsZGzPNg6Smnc/oY16QTjF0g==
-  dependencies:
-    spdx-correct "^3.0.0"
-    spdx-expression-parse "^3.0.0"
-
 verror@1.10.0:
   version "1.10.0"
   resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400"
@@ -6212,18 +4673,6 @@ verror@1.10.0:
     core-util-is "1.0.2"
     extsprintf "^1.2.0"
 
-vinyl-file@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/vinyl-file/-/vinyl-file-2.0.0.tgz#a7ebf5ffbefda1b7d18d140fcb07b223efb6751a"
-  integrity sha1-p+v1/779obfRjRQPyweyI++2dRo=
-  dependencies:
-    graceful-fs "^4.1.2"
-    pify "^2.3.0"
-    pinkie-promise "^2.0.0"
-    strip-bom "^2.0.0"
-    strip-bom-stream "^2.0.0"
-    vinyl "^1.1.0"
-
 vinyl-fs@^2.0.0, vinyl-fs@^2.4.3:
   version "2.4.4"
   resolved "https://registry.yarnpkg.com/vinyl-fs/-/vinyl-fs-2.4.4.tgz#be6ff3270cb55dfd7d3063640de81f25d7532239"
@@ -6272,7 +4721,7 @@ vinyl@^0.5.0:
     clone-stats "^0.0.1"
     replace-ext "0.0.1"
 
-vinyl@^1.0.0, vinyl@^1.1.0:
+vinyl@^1.0.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-1.2.0.tgz#5c88036cf565e5df05558bfc911f8656df218884"
   integrity sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=
@@ -6281,7 +4730,7 @@ vinyl@^1.0.0, vinyl@^1.1.0:
     clone-stats "^0.0.1"
     replace-ext "0.0.1"
 
-vinyl@^2.0.1, vinyl@^2.0.2:
+vinyl@^2.0.2:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-2.1.0.tgz#021f9c2cf951d6b939943c89eb5ee5add4fd924c"
   integrity sha1-Ah+cLPlR1rk5lDyJ617lrdT9kkw=
@@ -6306,12 +4755,10 @@ vinyl@~2.0.1:
     remove-trailing-separator "^1.0.1"
     replace-ext "^1.0.0"
 
-vm-browserify@0.0.4:
-  version "0.0.4"
-  resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-0.0.4.tgz#5d7ea45bbef9e4a6ff65f95438e0a87c357d5a73"
-  integrity sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=
-  dependencies:
-    indexof "0.0.1"
+vm-browserify@^1.0.1:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.0.tgz#bd76d6a23323e2ca8ffa12028dc04559c75f9019"
+  integrity sha512-iq+S7vZJE60yejDYM0ek6zg308+UZsdtPExWP9VZoCFCz1zkJoXFnAX7aZfd/ZwrkidzdUZL0C/ryW+JwAiIGw==
 
 vscode-extension-telemetry@0.1.1:
   version "0.1.1"
@@ -6345,89 +4792,68 @@ vscode@^1.1.10:
     url-parse "^1.1.9"
     vinyl-source-stream "^1.1.0"
 
-watchpack@^1.5.0:
-  version "1.5.0"
-  resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.5.0.tgz#231e783af830a22f8966f65c4c4bacc814072eed"
-  integrity sha512-RSlipNQB1u48cq0wH/BNfCu1tD/cJ8ydFIkNYhp9o+3d+8unClkIovpW5qpFPgmL9OE48wfAnlZydXByWP82AA==
+watchpack@^1.6.0:
+  version "1.6.0"
+  resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.6.0.tgz#4bc12c2ebe8aa277a71f1d3f14d685c7b446cd00"
+  integrity sha512-i6dHe3EyLjMmDlU1/bGQpEw25XSjkJULPuAVKCbNRefQVq48yXKUpwg538F7AZTf9kyr57zj++pQFltUa5H7yA==
   dependencies:
     chokidar "^2.0.2"
     graceful-fs "^4.1.2"
     neo-async "^2.5.0"
 
-webpack-addons@^1.1.5:
-  version "1.1.5"
-  resolved "https://registry.yarnpkg.com/webpack-addons/-/webpack-addons-1.1.5.tgz#2b178dfe873fb6e75e40a819fa5c26e4a9bc837a"
-  integrity sha512-MGO0nVniCLFAQz1qv22zM02QPjcpAoJdy7ED0i3Zy7SY1IecgXCm460ib7H/Wq7e9oL5VL6S2BxaObxwIcag0g==
-  dependencies:
-    jscodeshift "^0.4.0"
-
-webpack-cli@^2.0.10:
-  version "2.0.10"
-  resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-2.0.10.tgz#09b888fbaa0b4288ba4b94c4462b6f559dfcf51e"
-  integrity sha512-PQWEOoXkhjBV4svPuESghZRc80VvDoSSRPaLiInWifDlRJgoPWpiLCFXyMLQTTaug7ApLrSEW7BcuwwY6DEv5w==
-  dependencies:
-    chalk "^2.3.1"
-    codecov "^3.0.0"
-    cross-spawn "^6.0.4"
-    diff "^3.3.0"
-    enhanced-resolve "^4.0.0"
-    glob-all "^3.1.0"
-    global "^4.3.2"
-    global-modules "^1.0.0"
-    got "^8.2.0"
-    inquirer "^5.1.0"
-    interpret "^1.0.4"
-    jscodeshift "^0.4.1"
-    listr "^0.13.0"
-    loader-utils "^1.1.0"
-    lodash "^4.17.5"
-    log-symbols "2.2.0"
-    mkdirp "^0.5.1"
-    p-each-series "^1.0.0"
-    p-lazy "^1.0.0"
-    prettier "^1.5.3"
-    recast "^0.14.4"
-    resolve-cwd "^2.0.0"
-    supports-color "^5.2.0"
-    uglifyjs-webpack-plugin "^1.2.2"
-    v8-compile-cache "^1.1.2"
-    webpack-addons "^1.1.5"
-    yargs "9.0.1"
-    yeoman-environment "^2.0.0"
-    yeoman-generator "github:ev1stensberg/generator#Feature-getArgument"
-
-webpack-sources@^1.0.1, webpack-sources@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.1.0.tgz#a101ebae59d6507354d71d8013950a3a8b7a5a54"
-  integrity sha512-aqYp18kPphgoO5c/+NaUvEeACtZjMESmDChuD3NBciVpah3XpMEU9VAAtIaB1BsfJWWTSdv8Vv1m3T0aRk2dUw==
+webpack-cli@^3.3.0:
+  version "3.3.10"
+  resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-3.3.10.tgz#17b279267e9b4fb549023fae170da8e6e766da13"
+  integrity sha512-u1dgND9+MXaEt74sJR4PR7qkPxXUSQ0RXYq8x1L6Jg1MYVEmGPrH6Ah6C4arD4r0J1P5HKjRqpab36k0eIzPqg==
+  dependencies:
+    chalk "2.4.2"
+    cross-spawn "6.0.5"
+    enhanced-resolve "4.1.0"
+    findup-sync "3.0.0"
+    global-modules "2.0.0"
+    import-local "2.0.0"
+    interpret "1.2.0"
+    loader-utils "1.2.3"
+    supports-color "6.1.0"
+    v8-compile-cache "2.0.3"
+    yargs "13.2.4"
+
+webpack-sources@^1.4.0, webpack-sources@^1.4.1:
+  version "1.4.3"
+  resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933"
+  integrity sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==
   dependencies:
     source-list-map "^2.0.0"
     source-map "~0.6.1"
 
-webpack@^4.1.0:
-  version "4.1.0"
-  resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.1.0.tgz#91b6862e56eb3b18b79bb10b51866987ff10d2d6"
-  integrity sha512-ZFYcAZ44kOT+xsS5MS2H1fQr0PJkwQdYem/d17wacDkkupzsAkBJ3hDShWHdPVvWluFs6pfhHWw/dVso1m0rsA==
-  dependencies:
-    acorn "^5.0.0"
-    acorn-dynamic-import "^3.0.0"
-    ajv "^6.1.0"
-    ajv-keywords "^3.1.0"
-    chrome-trace-event "^0.1.1"
-    enhanced-resolve "^4.0.0"
-    eslint-scope "^3.7.1"
-    loader-runner "^2.3.0"
-    loader-utils "^1.1.0"
-    memory-fs "~0.4.1"
-    micromatch "^3.1.8"
-    mkdirp "~0.5.0"
-    neo-async "^2.5.0"
-    node-libs-browser "^2.0.0"
-    schema-utils "^0.4.2"
-    tapable "^1.0.0"
-    uglifyjs-webpack-plugin "^1.1.1"
-    watchpack "^1.5.0"
-    webpack-sources "^1.0.1"
+webpack@^4.41.2:
+  version "4.41.2"
+  resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.41.2.tgz#c34ec76daa3a8468c9b61a50336d8e3303dce74e"
+  integrity sha512-Zhw69edTGfbz9/8JJoyRQ/pq8FYUoY0diOXqW0T6yhgdhCv6wr0hra5DwwWexNRns2Z2+gsnrNcbe9hbGBgk/A==
+  dependencies:
+    "@webassemblyjs/ast" "1.8.5"
+    "@webassemblyjs/helper-module-context" "1.8.5"
+    "@webassemblyjs/wasm-edit" "1.8.5"
+    "@webassemblyjs/wasm-parser" "1.8.5"
+    acorn "^6.2.1"
+    ajv "^6.10.2"
+    ajv-keywords "^3.4.1"
+    chrome-trace-event "^1.0.2"
+    enhanced-resolve "^4.1.0"
+    eslint-scope "^4.0.3"
+    json-parse-better-errors "^1.0.2"
+    loader-runner "^2.4.0"
+    loader-utils "^1.2.3"
+    memory-fs "^0.4.1"
+    micromatch "^3.1.10"
+    mkdirp "^0.5.1"
+    neo-async "^2.6.1"
+    node-libs-browser "^2.2.1"
+    schema-utils "^1.0.0"
+    tapable "^1.1.3"
+    terser-webpack-plugin "^1.4.1"
+    watchpack "^1.6.0"
+    webpack-sources "^1.4.1"
 
 which-module@^2.0.0:
   version "2.0.0"
@@ -6441,6 +4867,13 @@ which@^1.2.14, which@^1.2.9:
   dependencies:
     isexe "^2.0.0"
 
+which@^1.3.1:
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
+  integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==
+  dependencies:
+    isexe "^2.0.0"
+
 wide-align@^1.1.0:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.2.tgz#571e0f1b0604636ebc0dfc21b0339bbe31341710"
@@ -6448,36 +4881,27 @@ wide-align@^1.1.0:
   dependencies:
     string-width "^1.0.2"
 
-worker-farm@^1.5.2:
-  version "1.5.4"
-  resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.5.4.tgz#4debbe46b40edefcc717ebde74a90b1ae1e909a1"
-  integrity sha512-ITyClEvcfv0ozqJl1vmWFWhvI+OIrkbInYqkEPE50wFPXj8J9Gd3FYf8+CkZJXJJsQBYe+2DvmoK9Zhx5w8W+w==
+worker-farm@^1.7.0:
+  version "1.7.0"
+  resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.7.0.tgz#26a94c5391bbca926152002f69b84a4bf772e5a8"
+  integrity sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==
   dependencies:
     errno "~0.1.7"
-    xtend "~4.0.1"
 
-wrap-ansi@^2.0.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85"
-  integrity sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=
+wrap-ansi@^5.1.0:
+  version "5.1.0"
+  resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09"
+  integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==
   dependencies:
-    string-width "^1.0.1"
-    strip-ansi "^3.0.1"
+    ansi-styles "^3.2.0"
+    string-width "^3.0.0"
+    strip-ansi "^5.0.0"
 
 wrappy@1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
   integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
 
-write-file-atomic@^1.2.0:
-  version "1.3.4"
-  resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-1.3.4.tgz#f807a4f0b1d9e913ae7a48112e6cc3af1991b45f"
-  integrity sha1-+Aek8LHZ6ROuekgRLmzDrxmRtF8=
-  dependencies:
-    graceful-fs "^4.1.11"
-    imurmurhash "^0.1.4"
-    slide "^1.1.5"
-
 xml@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/xml/-/xml-1.0.1.tgz#78ba72020029c5bc87b8a81a3cfcd74b4a2fc1e5"
@@ -6488,53 +4912,40 @@ xml@^1.0.0:
   resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
   integrity sha1-pcbVMr5lbiPbgg77lDofBJmNY68=
 
-y18n@^3.2.1:
-  version "3.2.1"
-  resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41"
-  integrity sha1-bRX7qITAhnnA136I53WegR4H+kE=
-
 y18n@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b"
   integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==
 
-yallist@^2.1.2:
-  version "2.1.2"
-  resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52"
-  integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=
-
-yargs-parser@^7.0.0:
-  version "7.0.0"
-  resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-7.0.0.tgz#8d0ac42f16ea55debd332caf4c4038b3e3f5dfd9"
-  integrity sha1-jQrELxbqVd69MyyvTEA4s+P139k=
-  dependencies:
-    camelcase "^4.1.0"
-
-yargs@9.0.1:
-  version "9.0.1"
-  resolved "https://registry.yarnpkg.com/yargs/-/yargs-9.0.1.tgz#52acc23feecac34042078ee78c0c007f5085db4c"
-  integrity sha1-UqzCP+7Kw0BCB47njAwAf1CF20w=
-  dependencies:
-    camelcase "^4.1.0"
-    cliui "^3.2.0"
-    decamelize "^1.1.1"
-    get-caller-file "^1.0.1"
-    os-locale "^2.0.0"
-    read-pkg-up "^2.0.0"
+yallist@^3.0.2:
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"
+  integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==
+
+yargs-parser@^13.1.0:
+  version "13.1.1"
+  resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.1.tgz#d26058532aa06d365fe091f6a1fc06b2f7e5eca0"
+  integrity sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==
+  dependencies:
+    camelcase "^5.0.0"
+    decamelize "^1.2.0"
+
+yargs@13.2.4:
+  version "13.2.4"
+  resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.2.4.tgz#0b562b794016eb9651b98bd37acf364aa5d6dc83"
+  integrity sha512-HG/DWAJa1PAnHT9JAhNa8AbAv3FPaiLzioSjCcmuXXhP8MlpHO5vwls4g4j6n30Z74GVQj8Xa62dWVx1QCGklg==
+  dependencies:
+    cliui "^5.0.0"
+    find-up "^3.0.0"
+    get-caller-file "^2.0.1"
+    os-locale "^3.1.0"
     require-directory "^2.1.1"
-    require-main-filename "^1.0.1"
+    require-main-filename "^2.0.0"
     set-blocking "^2.0.0"
-    string-width "^2.0.0"
+    string-width "^3.0.0"
     which-module "^2.0.0"
-    y18n "^3.2.1"
-    yargs-parser "^7.0.0"
-
-yargs@~1.2.6:
-  version "1.2.6"
-  resolved "https://registry.yarnpkg.com/yargs/-/yargs-1.2.6.tgz#9c7b4a82fd5d595b2bf17ab6dcc43135432fe34b"
-  integrity sha1-nHtKgv1dWVsr8Xq23MQxNUMv40s=
-  dependencies:
-    minimist "^0.1.0"
+    y18n "^4.0.0"
+    yargs-parser "^13.1.0"
 
 yauzl@^2.2.1:
   version "2.9.1"
@@ -6551,73 +4962,6 @@ yazl@^2.2.1:
   dependencies:
     buffer-crc32 "~0.2.3"
 
-yeoman-environment@^1.1.0:
-  version "1.6.6"
-  resolved "https://registry.yarnpkg.com/yeoman-environment/-/yeoman-environment-1.6.6.tgz#cd85fa67d156060e440d7807d7ef7cf0d2d1d671"
-  integrity sha1-zYX6Z9FWBg5EDXgH1+988NLR1nE=
-  dependencies:
-    chalk "^1.0.0"
-    debug "^2.0.0"
-    diff "^2.1.2"
-    escape-string-regexp "^1.0.2"
-    globby "^4.0.0"
-    grouped-queue "^0.3.0"
-    inquirer "^1.0.2"
-    lodash "^4.11.1"
-    log-symbols "^1.0.1"
-    mem-fs "^1.1.0"
-    text-table "^0.2.0"
-    untildify "^2.0.0"
-
-yeoman-environment@^2.0.0:
-  version "2.0.5"
-  resolved "https://registry.yarnpkg.com/yeoman-environment/-/yeoman-environment-2.0.5.tgz#84f22bafa84088971fe99ea85f654a3a3dd2b693"
-  integrity sha512-6/W7/B54OPHJXob0n0+pmkwFsirC8cokuQkPSmT/D0lCcSxkKtg/BA6ZnjUBIwjuGqmw3DTrT4en++htaUju5g==
-  dependencies:
-    chalk "^2.1.0"
-    debug "^3.1.0"
-    diff "^3.3.1"
-    escape-string-regexp "^1.0.2"
-    globby "^6.1.0"
-    grouped-queue "^0.3.3"
-    inquirer "^3.3.0"
-    is-scoped "^1.0.0"
-    lodash "^4.17.4"
-    log-symbols "^2.1.0"
-    mem-fs "^1.1.0"
-    text-table "^0.2.0"
-    untildify "^3.0.2"
-
-"yeoman-generator@github:ev1stensberg/generator#Feature-getArgument":
-  version "1.1.1"
-  resolved "https://codeload.github.com/ev1stensberg/generator/tar.gz/9e24fa31c85302ca1145ae34fc68b4f133251ca0"
-  dependencies:
-    async "^2.0.0"
-    chalk "^1.0.0"
-    cli-table "^0.3.1"
-    cross-spawn "^5.0.1"
-    dargs "^5.1.0"
-    dateformat "^2.0.0"
-    debug "^2.1.0"
-    detect-conflict "^1.0.0"
-    error "^7.0.2"
-    find-up "^2.1.0"
-    github-username "^4.0.0"
-    istextorbinary "^2.1.0"
-    lodash "^4.11.1"
-    mem-fs-editor "^3.0.0"
-    minimist "^1.2.0"
-    mkdirp "^0.5.0"
-    pretty-bytes "^4.0.2"
-    read-chunk "^2.0.0"
-    read-pkg-up "^2.0.0"
-    rimraf "^2.2.0"
-    run-async "^2.0.0"
-    shelljs "^0.7.0"
-    text-table "^0.2.0"
-    through2 "^2.0.0"
-    yeoman-environment "^1.1.0"
-
 zone.js@0.7.6:
   version "0.7.6"
   resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.7.6.tgz#fbbc39d3e0261d0986f1ba06306eb3aeb0d22009"
diff --git a/extensions/merge-conflict/package.json b/extensions/merge-conflict/package.json
index 2828669b04d3..815a412492a7 100644
--- a/extensions/merge-conflict/package.json
+++ b/extensions/merge-conflict/package.json
@@ -138,6 +138,6 @@
     "vscode-nls": "^4.0.0"
   },
   "devDependencies": {
-    "@types/node": "^10.14.8"
+    "@types/node": "^12.11.7"
   }
 }
diff --git a/extensions/merge-conflict/src/documentMergeConflict.ts b/extensions/merge-conflict/src/documentMergeConflict.ts
index 006260d86ba3..eca2e3d1b5f5 100644
--- a/extensions/merge-conflict/src/documentMergeConflict.ts
+++ b/extensions/merge-conflict/src/documentMergeConflict.ts
@@ -35,7 +35,7 @@ export class DocumentMergeConflict implements interfaces.IDocumentMergeConflict
 	public applyEdit(type: interfaces.CommitType, document: vscode.TextDocument, edit: { replace(range: vscode.Range, newText: string): void; }): void {
 
 		// Each conflict is a set of ranges as follows, note placements or newlines
-		// which may not in in spans
+		// which may not in spans
 		// [ Conflict Range             -- (Entire content below)
 		//   [ Current Header ]\n       -- >>>>> Header
 		//   [ Current Content ]        -- (content)
@@ -75,4 +75,4 @@ export class DocumentMergeConflict implements interfaces.IDocumentMergeConflict
 	private isNewlineOnly(text: string) {
 		return text === '\n' || text === '\r\n';
 	}
-}
\ No newline at end of file
+}
diff --git a/extensions/merge-conflict/yarn.lock b/extensions/merge-conflict/yarn.lock
index e6247e292557..7af62a72ce7d 100644
--- a/extensions/merge-conflict/yarn.lock
+++ b/extensions/merge-conflict/yarn.lock
@@ -2,10 +2,10 @@
 # yarn lockfile v1
 
 
-"@types/node@^10.14.8":
-  version "10.14.8"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-10.14.8.tgz#fe444203ecef1162348cd6deb76c62477b2cc6e9"
-  integrity sha512-I4+DbJEhLEg4/vIy/2gkWDvXBOOtPKV9EnLhYjMoqxcRW+TTZtUftkHktz/a8suoD5mUL7m6ReLrkPvSsCQQmw==
+"@types/node@^12.11.7":
+  version "12.11.7"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-12.11.7.tgz#57682a9771a3f7b09c2497f28129a0462966524a"
+  integrity sha512-JNbGaHFCLwgHn/iCckiGSOZ1XYHsKFwREtzPwSGCVld1SGhOlmZw2D4ZI94HQCrBHbADzW9m4LER/8olJTRGHA==
 
 vscode-nls@^4.0.0:
   version "4.0.0"
diff --git a/extensions/notebook/package.json b/extensions/notebook/package.json
index 5a8120142dc8..707a1bb2156f 100644
--- a/extensions/notebook/package.json
+++ b/extensions/notebook/package.json
@@ -457,7 +457,7 @@
     "@types/glob": "^7.1.1",
     "@types/js-yaml": "^3.12.1",
     "@types/mocha": "^5.2.5",
-    "@types/node": "^11.9.3",
+    "@types/node": "^12.11.7",
     "@types/request": "^2.48.1",
     "@types/rimraf": "^2.0.2",
     "@types/temp-write": "^3.3.0",
diff --git a/extensions/notebook/yarn.lock b/extensions/notebook/yarn.lock
index b02097a258fc..ae3493c2ed0d 100644
--- a/extensions/notebook/yarn.lock
+++ b/extensions/notebook/yarn.lock
@@ -161,10 +161,10 @@
   resolved "https://registry.yarnpkg.com/@types/node/-/node-11.13.4.tgz#f83ec3c3e05b174b7241fadeb6688267fe5b22ca"
   integrity sha512-+rabAZZ3Yn7tF/XPGHupKIL5EcAbrLxnTr/hgQICxbeuAfWtT0UZSfULE+ndusckBItcv4o6ZeOJplQikVcLvQ==
 
-"@types/node@^11.9.3":
-  version "11.9.3"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-11.9.3.tgz#14adbb5ab8cd563f549fbae8dbe92e0b7d6e76cc"
-  integrity sha512-DMiqG51GwES/c4ScBY0u5bDlH44+oY8AeYHjY1SGCWidD7h08o1dfHue/TGK7REmif2KiJzaUskO+Q0eaeZ2fQ==
+"@types/node@^12.11.7":
+  version "12.12.7"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.7.tgz#01e4ea724d9e3bd50d90c11fd5980ba317d8fa11"
+  integrity sha512-E6Zn0rffhgd130zbCbAr/JdXfXkoOUFAKNs/rF8qnafSJ8KYaA/j3oz7dcwal+lYjLA7xvdd5J4wdYpCTlP8+w==
 
 "@types/request@^2.48.1":
   version "2.48.1"
diff --git a/extensions/package.json b/extensions/package.json
index 688a5c05063c..d1030bbe6dd7 100644
--- a/extensions/package.json
+++ b/extensions/package.json
@@ -3,7 +3,7 @@
   "version": "0.0.1",
   "description": "Dependencies shared by all extensions",
   "dependencies": {
-    "typescript": "3.7.2"
+    "typescript": "3.7.3-insiders.20191123"
   },
   "scripts": {
     "postinstall": "node ./postinstall"
diff --git a/extensions/python/cgmanifest.json b/extensions/python/cgmanifest.json
index 139ae46369b0..3ee82895a82a 100644
--- a/extensions/python/cgmanifest.json
+++ b/extensions/python/cgmanifest.json
@@ -6,7 +6,7 @@
 				"git": {
 					"name": "MagicStack/MagicPython",
 					"repositoryUrl": "https://github.com/MagicStack/MagicPython",
-					"commitHash": "38422d302fe0b3e7716d26ce8cd7d0b9685f3a38"
+					"commitHash": "c0f8d514bbe6e9d3899f2b002bcd6971aef5e34b"
 				}
 			},
 			"license": "MIT",
diff --git a/extensions/python/syntaxes/MagicPython.tmLanguage.json b/extensions/python/syntaxes/MagicPython.tmLanguage.json
index d89e7ed110de..f59618f75ff7 100644
--- a/extensions/python/syntaxes/MagicPython.tmLanguage.json
+++ b/extensions/python/syntaxes/MagicPython.tmLanguage.json
@@ -4,7 +4,7 @@
 		"If you want to provide a fix or improvement, please create a pull request against the original repository.",
 		"Once accepted there, we are happy to receive an update request."
 	],
-	"version": "https://github.com/MagicStack/MagicPython/commit/38422d302fe0b3e7716d26ce8cd7d0b9685f3a38",
+	"version": "https://github.com/MagicStack/MagicPython/commit/c0f8d514bbe6e9d3899f2b002bcd6971aef5e34b",
 	"name": "MagicPython",
 	"scopeName": "source.python",
 	"patterns": [
@@ -374,6 +374,7 @@
 			]
 		},
 		"member-access": {
+			"name": "meta.member.access.python",
 			"begin": "(\\.)\\s*(?!\\.)",
 			"end": "(?x)\n  # stop when you've just read non-whitespace followed by non-word\n  # i.e. when finished reading an identifier or function call\n  (?<=\\S)(?=\\W) |\n  # stop when seeing the start of something that's not a word,\n  # i.e. when seeing a non-identifier\n  (^|(?<=\\s))(?=[^\\\\\\w\\s]) |\n  $\n",
 			"beginCaptures": {
@@ -1067,6 +1068,7 @@
 			}
 		},
 		"member-access-class": {
+			"name": "meta.member.access.python",
 			"begin": "(\\.)\\s*(?!\\.)",
 			"end": "(?<=\\S)(?=\\W)|$",
 			"beginCaptures": {
diff --git a/extensions/python/test/colorize-results/test-freeze-56377_py.json b/extensions/python/test/colorize-results/test-freeze-56377_py.json
index 364269c85559..63889e26feeb 100644
--- a/extensions/python/test/colorize-results/test-freeze-56377_py.json
+++ b/extensions/python/test/colorize-results/test-freeze-56377_py.json
@@ -287,7 +287,7 @@
 	},
 	{
 		"c": ".",
-		"t": "source.python punctuation.separator.period.python",
+		"t": "source.python meta.member.access.python punctuation.separator.period.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -298,7 +298,7 @@
 	},
 	{
 		"c": "request",
-		"t": "source.python",
+		"t": "source.python meta.member.access.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -309,7 +309,7 @@
 	},
 	{
 		"c": ".",
-		"t": "source.python punctuation.separator.period.python",
+		"t": "source.python meta.member.access.python punctuation.separator.period.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -320,7 +320,7 @@
 	},
 	{
 		"c": "META",
-		"t": "source.python constant.other.caps.python",
+		"t": "source.python meta.member.access.python constant.other.caps.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -331,7 +331,7 @@
 	},
 	{
 		"c": ".",
-		"t": "source.python punctuation.separator.period.python",
+		"t": "source.python meta.member.access.python punctuation.separator.period.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -342,7 +342,7 @@
 	},
 	{
 		"c": "items",
-		"t": "source.python meta.function-call.python meta.function-call.generic.python",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.generic.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -353,7 +353,7 @@
 	},
 	{
 		"c": "(",
-		"t": "source.python meta.function-call.python punctuation.definition.arguments.begin.python",
+		"t": "source.python meta.member.access.python meta.function-call.python punctuation.definition.arguments.begin.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -364,7 +364,7 @@
 	},
 	{
 		"c": ")",
-		"t": "source.python meta.function-call.python punctuation.definition.arguments.end.python",
+		"t": "source.python meta.member.access.python meta.function-call.python punctuation.definition.arguments.end.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -408,7 +408,7 @@
 	},
 	{
 		"c": ".",
-		"t": "source.python punctuation.separator.period.python",
+		"t": "source.python meta.member.access.python punctuation.separator.period.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -419,7 +419,7 @@
 	},
 	{
 		"c": "startswith",
-		"t": "source.python meta.function-call.python meta.function-call.generic.python",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.generic.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -430,7 +430,7 @@
 	},
 	{
 		"c": "(",
-		"t": "source.python meta.function-call.python punctuation.definition.arguments.begin.python",
+		"t": "source.python meta.member.access.python meta.function-call.python punctuation.definition.arguments.begin.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -441,7 +441,7 @@
 	},
 	{
 		"c": "'",
-		"t": "source.python meta.function-call.python meta.function-call.arguments.python string.quoted.single.python punctuation.definition.string.begin.python",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.quoted.single.python punctuation.definition.string.begin.python",
 		"r": {
 			"dark_plus": "string: #CE9178",
 			"light_plus": "string: #A31515",
@@ -452,7 +452,7 @@
 	},
 	{
 		"c": "HTTP_",
-		"t": "source.python meta.function-call.python meta.function-call.arguments.python string.quoted.single.python",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.quoted.single.python",
 		"r": {
 			"dark_plus": "string: #CE9178",
 			"light_plus": "string: #A31515",
@@ -463,7 +463,7 @@
 	},
 	{
 		"c": "'",
-		"t": "source.python meta.function-call.python meta.function-call.arguments.python string.quoted.single.python punctuation.definition.string.end.python",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.quoted.single.python punctuation.definition.string.end.python",
 		"r": {
 			"dark_plus": "string: #CE9178",
 			"light_plus": "string: #A31515",
@@ -474,7 +474,7 @@
 	},
 	{
 		"c": ")",
-		"t": "source.python meta.function-call.python punctuation.definition.arguments.end.python",
+		"t": "source.python meta.member.access.python meta.function-call.python punctuation.definition.arguments.end.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
diff --git a/extensions/python/test/colorize-results/test_py.json b/extensions/python/test/colorize-results/test_py.json
index 8586d7c9b9ed..2c3126586250 100644
--- a/extensions/python/test/colorize-results/test_py.json
+++ b/extensions/python/test/colorize-results/test_py.json
@@ -419,7 +419,7 @@
 	},
 	{
 		"c": ".",
-		"t": "source.python punctuation.separator.period.python",
+		"t": "source.python meta.member.access.python punctuation.separator.period.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -430,7 +430,7 @@
 	},
 	{
 		"c": "size",
-		"t": "source.python",
+		"t": "source.python meta.member.access.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -2223,7 +2223,7 @@
 	},
 	{
 		"c": ".",
-		"t": "source.python punctuation.separator.period.python",
+		"t": "source.python meta.member.access.python punctuation.separator.period.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -2234,7 +2234,7 @@
 	},
 	{
 		"c": "next",
-		"t": "source.python meta.function-call.python meta.function-call.generic.python",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.generic.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -2245,7 +2245,7 @@
 	},
 	{
 		"c": "(",
-		"t": "source.python meta.function-call.python punctuation.definition.arguments.begin.python",
+		"t": "source.python meta.member.access.python meta.function-call.python punctuation.definition.arguments.begin.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -2256,7 +2256,7 @@
 	},
 	{
 		"c": ")",
-		"t": "source.python meta.function-call.python punctuation.definition.arguments.end.python",
+		"t": "source.python meta.member.access.python meta.function-call.python punctuation.definition.arguments.end.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -4533,7 +4533,18 @@
 	},
 	{
 		"c": ".",
-		"t": "source.python punctuation.separator.period.python",
+		"t": "source.python meta.member.access.python punctuation.separator.period.python",
+		"r": {
+			"dark_plus": "default: #D4D4D4",
+			"light_plus": "default: #000000",
+			"dark_vs": "default: #D4D4D4",
+			"light_vs": "default: #000000",
+			"hc_black": "default: #FFFFFF"
+		}
+	},
+	{
+		"c": "fn",
+		"t": "source.python meta.member.access.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -4543,7 +4554,7 @@
 		}
 	},
 	{
-		"c": "fn ",
+		"c": " ",
 		"t": "source.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
@@ -4599,7 +4610,18 @@
 	},
 	{
 		"c": ".",
-		"t": "source.python punctuation.separator.period.python",
+		"t": "source.python meta.member.access.python punctuation.separator.period.python",
+		"r": {
+			"dark_plus": "default: #D4D4D4",
+			"light_plus": "default: #000000",
+			"dark_vs": "default: #D4D4D4",
+			"light_vs": "default: #000000",
+			"hc_black": "default: #FFFFFF"
+		}
+	},
+	{
+		"c": "memo",
+		"t": "source.python meta.member.access.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -4609,7 +4631,7 @@
 		}
 	},
 	{
-		"c": "memo ",
+		"c": " ",
 		"t": "source.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
@@ -4885,7 +4907,7 @@
 	},
 	{
 		"c": ".",
-		"t": "source.python punctuation.separator.period.python",
+		"t": "source.python meta.member.access.python punctuation.separator.period.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -4896,7 +4918,7 @@
 	},
 	{
 		"c": "memo",
-		"t": "source.python",
+		"t": "source.python meta.member.access.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -4940,7 +4962,7 @@
 	},
 	{
 		"c": ".",
-		"t": "source.python punctuation.separator.period.python",
+		"t": "source.python meta.member.access.python punctuation.separator.period.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -4951,7 +4973,7 @@
 	},
 	{
 		"c": "memo",
-		"t": "source.python meta.item-access.python",
+		"t": "source.python meta.member.access.python meta.item-access.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -4962,7 +4984,7 @@
 	},
 	{
 		"c": "[",
-		"t": "source.python meta.item-access.python punctuation.definition.arguments.begin.python",
+		"t": "source.python meta.member.access.python meta.item-access.python punctuation.definition.arguments.begin.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -4973,7 +4995,7 @@
 	},
 	{
 		"c": "args",
-		"t": "source.python meta.item-access.python meta.item-access.arguments.python",
+		"t": "source.python meta.member.access.python meta.item-access.python meta.item-access.arguments.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -4984,7 +5006,7 @@
 	},
 	{
 		"c": "]",
-		"t": "source.python meta.item-access.python punctuation.definition.arguments.end.python",
+		"t": "source.python meta.member.access.python meta.item-access.python punctuation.definition.arguments.end.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -5039,7 +5061,7 @@
 	},
 	{
 		"c": ".",
-		"t": "source.python punctuation.separator.period.python",
+		"t": "source.python meta.member.access.python punctuation.separator.period.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -5050,7 +5072,7 @@
 	},
 	{
 		"c": "fn",
-		"t": "source.python meta.function-call.python meta.function-call.generic.python",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.generic.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -5061,7 +5083,7 @@
 	},
 	{
 		"c": "(",
-		"t": "source.python meta.function-call.python punctuation.definition.arguments.begin.python",
+		"t": "source.python meta.member.access.python meta.function-call.python punctuation.definition.arguments.begin.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -5072,7 +5094,7 @@
 	},
 	{
 		"c": "*",
-		"t": "source.python meta.function-call.python meta.function-call.arguments.python keyword.operator.unpacking.arguments.python",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python keyword.operator.unpacking.arguments.python",
 		"r": {
 			"dark_plus": "keyword.operator: #D4D4D4",
 			"light_plus": "keyword.operator: #000000",
@@ -5083,7 +5105,7 @@
 	},
 	{
 		"c": "args",
-		"t": "source.python meta.function-call.python meta.function-call.arguments.python",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -5094,7 +5116,7 @@
 	},
 	{
 		"c": ")",
-		"t": "source.python meta.function-call.python punctuation.definition.arguments.end.python",
+		"t": "source.python meta.member.access.python meta.function-call.python punctuation.definition.arguments.end.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -5149,7 +5171,7 @@
 	},
 	{
 		"c": ".",
-		"t": "source.python punctuation.separator.period.python",
+		"t": "source.python meta.member.access.python punctuation.separator.period.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -5160,7 +5182,7 @@
 	},
 	{
 		"c": "memo",
-		"t": "source.python meta.item-access.python",
+		"t": "source.python meta.member.access.python meta.item-access.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -5171,7 +5193,7 @@
 	},
 	{
 		"c": "[",
-		"t": "source.python meta.item-access.python punctuation.definition.arguments.begin.python",
+		"t": "source.python meta.member.access.python meta.item-access.python punctuation.definition.arguments.begin.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -5182,7 +5204,7 @@
 	},
 	{
 		"c": "args",
-		"t": "source.python meta.item-access.python meta.item-access.arguments.python",
+		"t": "source.python meta.member.access.python meta.item-access.python meta.item-access.arguments.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -5193,7 +5215,7 @@
 	},
 	{
 		"c": "]",
-		"t": "source.python meta.item-access.python punctuation.definition.arguments.end.python",
+		"t": "source.python meta.member.access.python meta.item-access.python punctuation.definition.arguments.end.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -5237,7 +5259,7 @@
 	},
 	{
 		"c": ".",
-		"t": "source.python punctuation.separator.period.python",
+		"t": "source.python meta.member.access.python punctuation.separator.period.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -5248,7 +5270,7 @@
 	},
 	{
 		"c": "search",
-		"t": "source.python meta.function-call.python meta.function-call.generic.python",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.generic.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -5259,7 +5281,7 @@
 	},
 	{
 		"c": "(",
-		"t": "source.python meta.function-call.python punctuation.definition.arguments.begin.python",
+		"t": "source.python meta.member.access.python meta.function-call.python punctuation.definition.arguments.begin.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -5270,7 +5292,7 @@
 	},
 	{
 		"c": "r",
-		"t": "source.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python storage.type.string.python",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python storage.type.string.python",
 		"r": {
 			"dark_plus": "storage.type: #569CD6",
 			"light_plus": "storage.type: #0000FF",
@@ -5281,7 +5303,7 @@
 	},
 	{
 		"c": "\"",
-		"t": "source.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python punctuation.definition.string.begin.python",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python punctuation.definition.string.begin.python",
 		"r": {
 			"dark_plus": "string.regexp: #D16969",
 			"light_plus": "string.regexp: #811F3F",
@@ -5292,7 +5314,7 @@
 	},
 	{
 		"c": "(",
-		"t": "source.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python support.other.parenthesis.regexp punctuation.parenthesis.begin.regexp",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python support.other.parenthesis.regexp punctuation.parenthesis.begin.regexp",
 		"r": {
 			"dark_plus": "support.other.parenthesis.regexp: #CE9178",
 			"light_plus": "support.other.parenthesis.regexp: #D16969",
@@ -5303,7 +5325,7 @@
 	},
 	{
 		"c": "[",
-		"t": "source.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python meta.character.set.regexp punctuation.character.set.begin.regexp constant.other.set.regexp",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python meta.character.set.regexp punctuation.character.set.begin.regexp constant.other.set.regexp",
 		"r": {
 			"dark_plus": "punctuation.character.set.begin.regexp: #CE9178",
 			"light_plus": "punctuation.character.set.begin.regexp: #D16969",
@@ -5314,7 +5336,7 @@
 	},
 	{
 		"c": "0-9-",
-		"t": "source.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python meta.character.set.regexp constant.character.set.regexp",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python meta.character.set.regexp constant.character.set.regexp",
 		"r": {
 			"dark_plus": "constant.character.set.regexp: #D16969",
 			"light_plus": "constant.character.set.regexp: #811F3F",
@@ -5325,7 +5347,7 @@
 	},
 	{
 		"c": "]",
-		"t": "source.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python meta.character.set.regexp punctuation.character.set.end.regexp constant.other.set.regexp",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python meta.character.set.regexp punctuation.character.set.end.regexp constant.other.set.regexp",
 		"r": {
 			"dark_plus": "punctuation.character.set.end.regexp: #CE9178",
 			"light_plus": "punctuation.character.set.end.regexp: #D16969",
@@ -5336,7 +5358,7 @@
 	},
 	{
 		"c": "*",
-		"t": "source.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python keyword.operator.quantifier.regexp",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python keyword.operator.quantifier.regexp",
 		"r": {
 			"dark_plus": "keyword.operator.quantifier.regexp: #D7BA7D",
 			"light_plus": "keyword.operator.quantifier.regexp: #000000",
@@ -5347,7 +5369,7 @@
 	},
 	{
 		"c": ")",
-		"t": "source.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python support.other.parenthesis.regexp punctuation.parenthesis.end.regexp",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python support.other.parenthesis.regexp punctuation.parenthesis.end.regexp",
 		"r": {
 			"dark_plus": "support.other.parenthesis.regexp: #CE9178",
 			"light_plus": "support.other.parenthesis.regexp: #D16969",
@@ -5358,7 +5380,7 @@
 	},
 	{
 		"c": "\\s",
-		"t": "source.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python support.other.escape.special.regexp",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python support.other.escape.special.regexp",
 		"r": {
 			"dark_plus": "string.regexp: #D16969",
 			"light_plus": "string.regexp: #811F3F",
@@ -5369,7 +5391,7 @@
 	},
 	{
 		"c": "*",
-		"t": "source.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python keyword.operator.quantifier.regexp",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python keyword.operator.quantifier.regexp",
 		"r": {
 			"dark_plus": "keyword.operator.quantifier.regexp: #D7BA7D",
 			"light_plus": "keyword.operator.quantifier.regexp: #000000",
@@ -5380,7 +5402,7 @@
 	},
 	{
 		"c": "(",
-		"t": "source.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python support.other.parenthesis.regexp punctuation.parenthesis.begin.regexp",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python support.other.parenthesis.regexp punctuation.parenthesis.begin.regexp",
 		"r": {
 			"dark_plus": "support.other.parenthesis.regexp: #CE9178",
 			"light_plus": "support.other.parenthesis.regexp: #D16969",
@@ -5391,7 +5413,7 @@
 	},
 	{
 		"c": "[",
-		"t": "source.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python meta.character.set.regexp punctuation.character.set.begin.regexp constant.other.set.regexp",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python meta.character.set.regexp punctuation.character.set.begin.regexp constant.other.set.regexp",
 		"r": {
 			"dark_plus": "punctuation.character.set.begin.regexp: #CE9178",
 			"light_plus": "punctuation.character.set.begin.regexp: #D16969",
@@ -5402,7 +5424,7 @@
 	},
 	{
 		"c": "A-Za-z",
-		"t": "source.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python meta.character.set.regexp constant.character.set.regexp",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python meta.character.set.regexp constant.character.set.regexp",
 		"r": {
 			"dark_plus": "constant.character.set.regexp: #D16969",
 			"light_plus": "constant.character.set.regexp: #811F3F",
@@ -5413,7 +5435,7 @@
 	},
 	{
 		"c": "]",
-		"t": "source.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python meta.character.set.regexp punctuation.character.set.end.regexp constant.other.set.regexp",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python meta.character.set.regexp punctuation.character.set.end.regexp constant.other.set.regexp",
 		"r": {
 			"dark_plus": "punctuation.character.set.end.regexp: #CE9178",
 			"light_plus": "punctuation.character.set.end.regexp: #D16969",
@@ -5424,7 +5446,7 @@
 	},
 	{
 		"c": "+",
-		"t": "source.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python keyword.operator.quantifier.regexp",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python keyword.operator.quantifier.regexp",
 		"r": {
 			"dark_plus": "keyword.operator.quantifier.regexp: #D7BA7D",
 			"light_plus": "keyword.operator.quantifier.regexp: #000000",
@@ -5435,7 +5457,7 @@
 	},
 	{
 		"c": ")",
-		"t": "source.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python support.other.parenthesis.regexp punctuation.parenthesis.end.regexp",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python support.other.parenthesis.regexp punctuation.parenthesis.end.regexp",
 		"r": {
 			"dark_plus": "support.other.parenthesis.regexp: #CE9178",
 			"light_plus": "support.other.parenthesis.regexp: #D16969",
@@ -5446,7 +5468,7 @@
 	},
 	{
 		"c": ",",
-		"t": "source.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python",
 		"r": {
 			"dark_plus": "string.regexp: #D16969",
 			"light_plus": "string.regexp: #811F3F",
@@ -5457,7 +5479,7 @@
 	},
 	{
 		"c": "\\s",
-		"t": "source.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python support.other.escape.special.regexp",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python support.other.escape.special.regexp",
 		"r": {
 			"dark_plus": "string.regexp: #D16969",
 			"light_plus": "string.regexp: #811F3F",
@@ -5468,7 +5490,7 @@
 	},
 	{
 		"c": "+",
-		"t": "source.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python keyword.operator.quantifier.regexp",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python keyword.operator.quantifier.regexp",
 		"r": {
 			"dark_plus": "keyword.operator.quantifier.regexp: #D7BA7D",
 			"light_plus": "keyword.operator.quantifier.regexp: #000000",
@@ -5479,7 +5501,7 @@
 	},
 	{
 		"c": "(",
-		"t": "source.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python support.other.parenthesis.regexp punctuation.parenthesis.begin.regexp",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python support.other.parenthesis.regexp punctuation.parenthesis.begin.regexp",
 		"r": {
 			"dark_plus": "support.other.parenthesis.regexp: #CE9178",
 			"light_plus": "support.other.parenthesis.regexp: #D16969",
@@ -5490,7 +5512,7 @@
 	},
 	{
 		"c": ".",
-		"t": "source.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python support.other.match.any.regexp",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python support.other.match.any.regexp",
 		"r": {
 			"dark_plus": "string.regexp: #D16969",
 			"light_plus": "string.regexp: #811F3F",
@@ -5501,7 +5523,7 @@
 	},
 	{
 		"c": "*",
-		"t": "source.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python keyword.operator.quantifier.regexp",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python keyword.operator.quantifier.regexp",
 		"r": {
 			"dark_plus": "keyword.operator.quantifier.regexp: #D7BA7D",
 			"light_plus": "keyword.operator.quantifier.regexp: #000000",
@@ -5512,7 +5534,7 @@
 	},
 	{
 		"c": ")",
-		"t": "source.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python support.other.parenthesis.regexp punctuation.parenthesis.end.regexp",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python support.other.parenthesis.regexp punctuation.parenthesis.end.regexp",
 		"r": {
 			"dark_plus": "support.other.parenthesis.regexp: #CE9178",
 			"light_plus": "support.other.parenthesis.regexp: #D16969",
@@ -5523,7 +5545,7 @@
 	},
 	{
 		"c": "\"",
-		"t": "source.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python punctuation.definition.string.end.python",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python punctuation.definition.string.end.python",
 		"r": {
 			"dark_plus": "string.regexp: #D16969",
 			"light_plus": "string.regexp: #811F3F",
@@ -5534,7 +5556,7 @@
 	},
 	{
 		"c": ",",
-		"t": "source.python meta.function-call.python meta.function-call.arguments.python punctuation.separator.arguments.python",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python punctuation.separator.arguments.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -5545,7 +5567,7 @@
 	},
 	{
 		"c": " i",
-		"t": "source.python meta.function-call.python meta.function-call.arguments.python",
+		"t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
@@ -5556,7 +5578,7 @@
 	},
 	{
 		"c": ")",
-		"t": "source.python meta.function-call.python punctuation.definition.arguments.end.python",
+		"t": "source.python meta.member.access.python meta.function-call.python punctuation.definition.arguments.end.python",
 		"r": {
 			"dark_plus": "default: #D4D4D4",
 			"light_plus": "default: #000000",
diff --git a/extensions/resource-deployment/package.json b/extensions/resource-deployment/package.json
index 19c5c915d312..18fb2bdcc17d 100644
--- a/extensions/resource-deployment/package.json
+++ b/extensions/resource-deployment/package.json
@@ -356,7 +356,7 @@
 	"dependencies": {
 		"linux-release-info": "^2.0.0",
 		"promisify-child-process": "^3.1.1",
-		"sudo-prompt": "^9.0.0",
+    "sudo-prompt": "9.1.1",
 		"vscode-nls": "^4.0.0",
 		"yamljs": "^0.3.0"
 	},
diff --git a/extensions/resource-deployment/src/services/platformService.ts b/extensions/resource-deployment/src/services/platformService.ts
index 8d42dfb955a8..4522050a7e58 100644
--- a/extensions/resource-deployment/src/services/platformService.ts
+++ b/extensions/resource-deployment/src/services/platformService.ts
@@ -182,7 +182,7 @@ export class PlatformService implements IPlatformService {
 		}
 	}
 
-	private sudoExec(command: string, options: sudo.SudoOptions): Promise {
+	private sudoExec(command: string, options: { [key: string]: any }): Promise {
 		return new Promise((resolve, reject) => {
 			sudo.exec(command, options, (error, stdout, stderr) => {
 				if (error) {
diff --git a/extensions/resource-deployment/src/typings/ref.d.ts b/extensions/resource-deployment/src/typings/ref.d.ts
index 31c2c56a194f..cfdf5dd135fa 100644
--- a/extensions/resource-deployment/src/typings/ref.d.ts
+++ b/extensions/resource-deployment/src/typings/ref.d.ts
@@ -6,5 +6,4 @@
 /// 
 /// 
 /// 
-/// 
 /// 
diff --git a/extensions/resource-deployment/yarn.lock b/extensions/resource-deployment/yarn.lock
index b00b46b39469..73d20a174191 100644
--- a/extensions/resource-deployment/yarn.lock
+++ b/extensions/resource-deployment/yarn.lock
@@ -643,10 +643,10 @@ strip-ansi@^4.0.0:
   dependencies:
     ansi-regex "^3.0.0"
 
-sudo-prompt@^9.0.0:
-  version "9.0.0"
-  resolved "https://registry.yarnpkg.com/sudo-prompt/-/sudo-prompt-9.0.0.tgz#eebedeee9fcd6f661324e6bb46335e3288e8dc8a"
-  integrity sha512-kUn5fiOk0nhY2oKD9onIkcNCE4Zt85WTsvOfSmqCplmlEvXCcPOmp1npH5YWuf8Bmyy9wLWkIxx+D+8cThBORQ==
+sudo-prompt@9.1.1:
+  version "9.1.1"
+  resolved "https://registry.yarnpkg.com/sudo-prompt/-/sudo-prompt-9.1.1.tgz#73853d729770392caec029e2470db9c221754db0"
+  integrity sha512-es33J1g2HjMpyAhz8lOR+ICmXXAqTuKbuXuUWLhOLew20oN9oUCgCJx615U/v7aioZg7IX5lIh9x34vwneu4pA==
 
 supports-color@4.4.0:
   version "4.4.0"
diff --git a/extensions/schema-compare/package.json b/extensions/schema-compare/package.json
index 5e234a9375bd..360c16ab6b53 100644
--- a/extensions/schema-compare/package.json
+++ b/extensions/schema-compare/package.json
@@ -64,7 +64,7 @@
   },
   "devDependencies": {
     "@types/mocha": "^5.2.5",
-    "@types/node": "^10.14.8",
+    "@types/node": "^12.11.7",
     "mocha": "^5.2.0",
     "mocha-junit-reporter": "^1.17.0",
     "mocha-multi-reporters": "^1.1.7",
diff --git a/extensions/schema-compare/yarn.lock b/extensions/schema-compare/yarn.lock
index 7ee2b32bd681..f6241630306d 100644
--- a/extensions/schema-compare/yarn.lock
+++ b/extensions/schema-compare/yarn.lock
@@ -7,10 +7,10 @@
   resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-5.2.6.tgz#b8622d50557dd155e9f2f634b7d68fd38de5e94b"
   integrity sha512-1axi39YdtBI7z957vdqXI4Ac25e7YihYQtJa+Clnxg1zTJEaIRbndt71O3sP4GAMgiAm0pY26/b9BrY4MR/PMw==
 
-"@types/node@^10.14.8":
-  version "10.14.17"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-10.14.17.tgz#b96d4dd3e427382482848948041d3754d40fd5ce"
-  integrity sha512-p/sGgiPaathCfOtqu2fx5Mu1bcjuP8ALFg4xpGgNkcin7LwRyzUKniEHBKdcE1RPsenq5JVPIpMTJSygLboygQ==
+"@types/node@^12.11.7":
+  version "12.12.7"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.7.tgz#01e4ea724d9e3bd50d90c11fd5980ba317d8fa11"
+  integrity sha512-E6Zn0rffhgd130zbCbAr/JdXfXkoOUFAKNs/rF8qnafSJ8KYaA/j3oz7dcwal+lYjLA7xvdd5J4wdYpCTlP8+w==
 
 ajv@^6.5.5:
   version "6.10.0"
diff --git a/extensions/search-result/README.md b/extensions/search-result/README.md
new file mode 100644
index 000000000000..a28a54db1b84
--- /dev/null
+++ b/extensions/search-result/README.md
@@ -0,0 +1,3 @@
+# Language Features for Search Result files
+
+**Notice:** This extension is bundled with Visual Studio Code. It can be disabled but not uninstalled.
diff --git a/src/typings/yazl.d.ts b/extensions/search-result/extension.webpack.config.js
similarity index 60%
rename from src/typings/yazl.d.ts
rename to extensions/search-result/extension.webpack.config.js
index 172f8d66ad25..f35561d9f2dd 100644
--- a/src/typings/yazl.d.ts
+++ b/extensions/search-result/extension.webpack.config.js
@@ -3,13 +3,18 @@
  *  Licensed under the Source EULA. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
 
-declare module 'yazl' {
-	import * as stream from 'stream';
-
-	class ZipFile {
-		outputStream: stream.Stream;
-		addBuffer(buffer: Buffer, path: string): void;
-		addFile(localPath: string, path: string): void;
-		end(): void;
+//@ts-check
+
+'use strict';
+
+const withDefaults = require('../shared.webpack.config');
+
+module.exports = withDefaults({
+	context: __dirname,
+	resolve: {
+		mainFields: ['module', 'main']
+	},
+	entry: {
+		extension: './src/extension.ts',
 	}
-}
\ No newline at end of file
+});
diff --git a/extensions/search-result/package.json b/extensions/search-result/package.json
new file mode 100644
index 000000000000..bc7c6cf951c6
--- /dev/null
+++ b/extensions/search-result/package.json
@@ -0,0 +1,64 @@
+{
+  "name": "search-result",
+  "displayName": "%displayName%",
+  "description": "%description%",
+  "version": "1.0.0",
+  "publisher": "vscode",
+  "license": "MIT",
+  "engines": {
+    "vscode": "^1.39.0"
+  },
+  "categories": [
+    "Programming Languages"
+  ],
+  "main": "./out/extension.js",
+  "activationEvents": [
+    "*"
+  ],
+  "scripts": {
+    "vscode:prepublish": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:search-result ./tsconfig.json"
+  },
+  "contributes": {
+    "commands": [
+      {
+        "command": "searchResult.rerunSearch",
+        "title": "%searchResult.rerunSearch.title%",
+        "category": "Search Result",
+        "icon": {
+          "light": "./src/media/refresh-light.svg",
+          "dark": "./src/media/refresh-dark.svg"
+        }
+      }
+    ],
+    "menus": {
+      "editor/title": [
+        {
+          "command": "searchResult.rerunSearch",
+          "when": "editorLangId == search-result",
+          "group": "navigation"
+        }
+      ]
+    },
+    "languages": [
+      {
+        "id": "search-result",
+        "extensions": [
+          ".code-search"
+        ],
+        "aliases": [
+          "Search Result"
+        ]
+      }
+    ],
+    "grammars": [
+      {
+        "language": "search-result",
+        "scopeName": "text.searchResult",
+        "path": "./syntaxes/searchResult.tmLanguage.json"
+      }
+    ]
+  },
+  "devDependencies": {
+    "vscode": "^1.1.36"
+  }
+}
diff --git a/extensions/search-result/package.nls.json b/extensions/search-result/package.nls.json
new file mode 100644
index 000000000000..694f6b61d802
--- /dev/null
+++ b/extensions/search-result/package.nls.json
@@ -0,0 +1,5 @@
+{
+	"displayName": "Search Result",
+	"description": "Provides syntax highlighting and language features for tabbed search results.",
+	"searchResult.rerunSearch.title": "Search Again"
+}
diff --git a/extensions/search-result/src/extension.ts b/extensions/search-result/src/extension.ts
new file mode 100644
index 000000000000..56cbecd087cb
--- /dev/null
+++ b/extensions/search-result/src/extension.ts
@@ -0,0 +1,186 @@
+/*---------------------------------------------------------------------------------------------
+ *  Copyright (c) Microsoft Corporation. All rights reserved.
+ *  Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import * as vscode from 'vscode';
+import * as pathUtils from 'path';
+
+const FILE_LINE_REGEX = /^(\S.*):$/;
+const RESULT_LINE_REGEX = /^(\s+)(\d+):(\s+)(.*)$/;
+const SEARCH_RESULT_SELECTOR = { language: 'search-result' };
+
+let cachedLastParse: { version: number, parse: ParsedSearchResults } | undefined;
+
+export function activate() {
+
+	vscode.commands.registerCommand('searchResult.rerunSearch', () => vscode.commands.executeCommand('search.action.rerunEditorSearch'));
+
+	vscode.languages.registerDocumentSymbolProvider(SEARCH_RESULT_SELECTOR, {
+		provideDocumentSymbols(document: vscode.TextDocument, token: vscode.CancellationToken): vscode.DocumentSymbol[] {
+			const results = parseSearchResults(document, token)
+				.filter(isFileLine)
+				.map(line => new vscode.DocumentSymbol(
+					line.path,
+					'',
+					vscode.SymbolKind.File,
+					line.allLocations.map(({ originSelectionRange }) => originSelectionRange!).reduce((p, c) => p.union(c), line.location.originSelectionRange!),
+					line.location.originSelectionRange!,
+				));
+
+			return results;
+		}
+	});
+
+	vscode.languages.registerCompletionItemProvider(SEARCH_RESULT_SELECTOR, {
+		provideCompletionItems(document: vscode.TextDocument, position: vscode.Position): vscode.CompletionItem[] {
+
+			const line = document.lineAt(position.line);
+			if (position.line > 3) { return []; }
+			if (position.character === 0 || (position.character === 1 && line.text === '#')) {
+				const header = Array.from({ length: 4 }).map((_, i) => document.lineAt(i).text);
+
+				return ['# Query:', '# Flags:', '# Including:', '# Excluding:']
+					.filter(suggestion => header.every(line => line.indexOf(suggestion) === -1))
+					.map(flag => ({ label: flag, insertText: (flag.slice(position.character)) + ' ' }));
+			}
+
+			if (line.text.indexOf('# Flags:') === -1) { return []; }
+
+			return ['RegExp', 'CaseSensitive', 'IgnoreExcludeSettings', 'WordMatch']
+				.filter(flag => line.text.indexOf(flag) === -1)
+				.map(flag => ({ label: flag, insertText: flag + ' ' }));
+		}
+	}, '#');
+
+	vscode.languages.registerDefinitionProvider(SEARCH_RESULT_SELECTOR, {
+		provideDefinition(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): vscode.DefinitionLink[] {
+			const lineResult = parseSearchResults(document, token)[position.line];
+			if (!lineResult) { return []; }
+			if (lineResult.type === 'file') {
+				// TODO: The multi-match peek UX isnt very smooth.
+				// return lineResult.allLocations.length > 1 ? lineResult.allLocations : [lineResult.location];
+				return [];
+			}
+
+			return [lineResult.location];
+		}
+	});
+
+	vscode.languages.registerDocumentLinkProvider(SEARCH_RESULT_SELECTOR, {
+		async provideDocumentLinks(document: vscode.TextDocument, token: vscode.CancellationToken): Promise {
+			return parseSearchResults(document, token)
+				.filter(({ type }) => type === 'file')
+				.map(({ location }) => ({ range: location.originSelectionRange!, target: location.targetUri }));
+		}
+	});
+
+	vscode.window.onDidChangeActiveTextEditor(e => {
+		if (e?.document.languageId === 'search-result') {
+			// Clear the parse whenever we open a new editor.
+			// Conservative because things like the URI might remain constant even if the contents change, and re-parsing even large files is relatively fast.
+			cachedLastParse = undefined;
+		}
+	});
+}
+
+
+function relativePathToUri(path: string, resultsUri: vscode.Uri): vscode.Uri | undefined {
+	if (pathUtils.isAbsolute(path)) { return vscode.Uri.file(path); }
+	if (path.indexOf('~/') === 0) {
+		return vscode.Uri.file(pathUtils.join(process.env.HOME!, path.slice(2)));
+	}
+
+
+	if (vscode.workspace.workspaceFolders) {
+		const multiRootFormattedPath = /^(.*) • (.*)$/.exec(path);
+		if (multiRootFormattedPath) {
+			const [, workspaceName, workspacePath] = multiRootFormattedPath;
+			const folder = vscode.workspace.workspaceFolders.filter(wf => wf.name === workspaceName)[0];
+			if (folder) {
+				return vscode.Uri.file(pathUtils.join(folder.uri.fsPath, workspacePath));
+			}
+		}
+
+		else if (vscode.workspace.workspaceFolders.length === 1) {
+			return vscode.Uri.file(pathUtils.join(vscode.workspace.workspaceFolders[0].uri.fsPath, path));
+		} else if (resultsUri.scheme !== 'untitled') {
+			// We're in a multi-root workspace, but the path is not multi-root formatted
+			// Possibly a saved search from a single root session. Try checking if the search result document's URI is in a current workspace folder.
+			const prefixMatch = vscode.workspace.workspaceFolders.filter(wf => resultsUri.toString().startsWith(wf.uri.toString()))[0];
+			if (prefixMatch) { return vscode.Uri.file(pathUtils.join(prefixMatch.uri.fsPath, path)); }
+		}
+	}
+
+	console.error(`Unable to resolve path ${path}`);
+	return undefined;
+}
+
+type ParsedSearchFileLine = { type: 'file', location: vscode.LocationLink, allLocations: vscode.LocationLink[], path: string };
+type ParsedSearchResultLine = { type: 'result', location: vscode.LocationLink };
+type ParsedSearchResults = Array;
+const isFileLine = (line: ParsedSearchResultLine | ParsedSearchFileLine): line is ParsedSearchFileLine => line.type === 'file';
+
+
+function parseSearchResults(document: vscode.TextDocument, token: vscode.CancellationToken): ParsedSearchResults {
+
+	if (cachedLastParse && cachedLastParse.version === document.version) {
+		return cachedLastParse.parse;
+	}
+
+	const lines = document.getText().split(/\r?\n/);
+	const links: ParsedSearchResults = [];
+
+	let currentTarget: vscode.Uri | undefined = undefined;
+	let currentTargetLocations: vscode.LocationLink[] | undefined = undefined;
+
+	for (let i = 0; i < lines.length; i++) {
+		if (token.isCancellationRequested) { return []; }
+		const line = lines[i];
+
+		const fileLine = FILE_LINE_REGEX.exec(line);
+		if (fileLine) {
+			const [, path] = fileLine;
+
+			currentTarget = relativePathToUri(path, document.uri);
+			if (!currentTarget) { continue; }
+			currentTargetLocations = [];
+
+			const location: vscode.LocationLink = {
+				targetRange: new vscode.Range(0, 0, 0, 1),
+				targetUri: currentTarget,
+				originSelectionRange: new vscode.Range(i, 0, i, line.length),
+			};
+
+
+			links[i] = { type: 'file', location, allLocations: currentTargetLocations, path };
+		}
+
+		if (!currentTarget) { continue; }
+
+		const resultLine = RESULT_LINE_REGEX.exec(line);
+		if (resultLine) {
+			const [, indentation, _lineNumber, resultIndentation] = resultLine;
+			const lineNumber = +_lineNumber - 1;
+			const resultStart = (indentation + _lineNumber + ':' + resultIndentation).length;
+
+			const location: vscode.LocationLink = {
+				targetRange: new vscode.Range(Math.max(lineNumber - 3, 0), 0, lineNumber + 3, line.length),
+				targetSelectionRange: new vscode.Range(lineNumber, 0, lineNumber, line.length),
+				targetUri: currentTarget,
+				originSelectionRange: new vscode.Range(i, resultStart, i, line.length),
+			};
+
+			currentTargetLocations?.push(location);
+
+			links[i] = { type: 'result', location };
+		}
+	}
+
+	cachedLastParse = {
+		version: document.version,
+		parse: links
+	};
+
+	return links;
+}
diff --git a/extensions/search-result/src/media/refresh-dark.svg b/extensions/search-result/src/media/refresh-dark.svg
new file mode 100644
index 000000000000..e1f05aadeebd
--- /dev/null
+++ b/extensions/search-result/src/media/refresh-dark.svg
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/extensions/search-result/src/media/refresh-light.svg b/extensions/search-result/src/media/refresh-light.svg
new file mode 100644
index 000000000000..9b1d91084091
--- /dev/null
+++ b/extensions/search-result/src/media/refresh-light.svg
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/src/typings/graceful-fs.d.ts b/extensions/search-result/src/typings/refs.d.ts
similarity index 74%
rename from src/typings/graceful-fs.d.ts
rename to extensions/search-result/src/typings/refs.d.ts
index 8ef505afa4d2..c82a621bfa3b 100644
--- a/src/typings/graceful-fs.d.ts
+++ b/extensions/search-result/src/typings/refs.d.ts
@@ -3,6 +3,5 @@
  *  Licensed under the Source EULA. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
 
-declare module 'graceful-fs' {
-	export function gracefulify(fsModule: any): void;
-}
\ No newline at end of file
+/// 
+/// 
diff --git a/extensions/search-result/syntaxes/searchResult.tmLanguage.json b/extensions/search-result/syntaxes/searchResult.tmLanguage.json
new file mode 100644
index 000000000000..4de2a40ba40e
--- /dev/null
+++ b/extensions/search-result/syntaxes/searchResult.tmLanguage.json
@@ -0,0 +1,18 @@
+{
+	"name": "Search Results",
+	"scopeName": "text.searchResult",
+	"patterns": [
+		{
+			"match": "^# (Query|Flags|Including|Excluding): .*$",
+			"name": "comment"
+		},
+		{
+			"match": "^\\S.*:$",
+			"name": "string path.searchResult"
+		},
+		{
+			"match": "^  \\d+",
+			"name": "constant.numeric lineNumber.searchResult"
+		}
+	]
+}
diff --git a/extensions/search-result/tsconfig.json b/extensions/search-result/tsconfig.json
new file mode 100644
index 000000000000..16ed233f6e8f
--- /dev/null
+++ b/extensions/search-result/tsconfig.json
@@ -0,0 +1,9 @@
+{
+	"extends": "../shared.tsconfig.json",
+	"compilerOptions": {
+		"outDir": "./out",
+	},
+	"include": [
+		"src/**/*"
+	]
+}
diff --git a/extensions/search-result/yarn.lock b/extensions/search-result/yarn.lock
new file mode 100644
index 000000000000..4ff83ab98bbf
--- /dev/null
+++ b/extensions/search-result/yarn.lock
@@ -0,0 +1,602 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+agent-base@4, agent-base@^4.3.0:
+  version "4.3.0"
+  resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee"
+  integrity sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==
+  dependencies:
+    es6-promisify "^5.0.0"
+
+ajv@^6.5.5:
+  version "6.10.2"
+  resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.2.tgz#d3cea04d6b017b2894ad69040fec8b623eb4bd52"
+  integrity sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==
+  dependencies:
+    fast-deep-equal "^2.0.1"
+    fast-json-stable-stringify "^2.0.0"
+    json-schema-traverse "^0.4.1"
+    uri-js "^4.2.2"
+
+asn1@~0.2.3:
+  version "0.2.4"
+  resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136"
+  integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==
+  dependencies:
+    safer-buffer "~2.1.0"
+
+assert-plus@1.0.0, assert-plus@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525"
+  integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=
+
+asynckit@^0.4.0:
+  version "0.4.0"
+  resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
+  integrity sha1-x57Zf380y48robyXkLzDZkdLS3k=
+
+aws-sign2@~0.7.0:
+  version "0.7.0"
+  resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8"
+  integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=
+
+aws4@^1.8.0:
+  version "1.8.0"
+  resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f"
+  integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==
+
+balanced-match@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
+  integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
+
+bcrypt-pbkdf@^1.0.0:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e"
+  integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=
+  dependencies:
+    tweetnacl "^0.14.3"
+
+brace-expansion@^1.1.7:
+  version "1.1.11"
+  resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
+  integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
+  dependencies:
+    balanced-match "^1.0.0"
+    concat-map "0.0.1"
+
+browser-stdout@1.3.1:
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60"
+  integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==
+
+buffer-from@^1.0.0:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
+  integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==
+
+caseless@~0.12.0:
+  version "0.12.0"
+  resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
+  integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=
+
+combined-stream@^1.0.6, combined-stream@~1.0.6:
+  version "1.0.8"
+  resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
+  integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
+  dependencies:
+    delayed-stream "~1.0.0"
+
+commander@2.15.1:
+  version "2.15.1"
+  resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f"
+  integrity sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==
+
+concat-map@0.0.1:
+  version "0.0.1"
+  resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
+  integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
+
+core-util-is@1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
+  integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
+
+dashdash@^1.12.0:
+  version "1.14.1"
+  resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
+  integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=
+  dependencies:
+    assert-plus "^1.0.0"
+
+debug@3.1.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
+  integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
+  dependencies:
+    ms "2.0.0"
+
+debug@^3.1.0:
+  version "3.2.6"
+  resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
+  integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
+  dependencies:
+    ms "^2.1.1"
+
+delayed-stream@~1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
+  integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk=
+
+diff@3.5.0:
+  version "3.5.0"
+  resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
+  integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==
+
+ecc-jsbn@~0.1.1:
+  version "0.1.2"
+  resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9"
+  integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=
+  dependencies:
+    jsbn "~0.1.0"
+    safer-buffer "^2.1.0"
+
+es6-promise@^4.0.3:
+  version "4.2.8"
+  resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a"
+  integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==
+
+es6-promisify@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203"
+  integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=
+  dependencies:
+    es6-promise "^4.0.3"
+
+escape-string-regexp@1.0.5:
+  version "1.0.5"
+  resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
+  integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
+
+extend@~3.0.2:
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
+  integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
+
+extsprintf@1.3.0:
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
+  integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=
+
+extsprintf@^1.2.0:
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f"
+  integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8=
+
+fast-deep-equal@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49"
+  integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=
+
+fast-json-stable-stringify@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2"
+  integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I=
+
+forever-agent@~0.6.1:
+  version "0.6.1"
+  resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
+  integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=
+
+form-data@~2.3.2:
+  version "2.3.3"
+  resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6"
+  integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==
+  dependencies:
+    asynckit "^0.4.0"
+    combined-stream "^1.0.6"
+    mime-types "^2.1.12"
+
+fs.realpath@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
+  integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
+
+getpass@^0.1.1:
+  version "0.1.7"
+  resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa"
+  integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=
+  dependencies:
+    assert-plus "^1.0.0"
+
+glob@7.1.2:
+  version "7.1.2"
+  resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15"
+  integrity sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==
+  dependencies:
+    fs.realpath "^1.0.0"
+    inflight "^1.0.4"
+    inherits "2"
+    minimatch "^3.0.4"
+    once "^1.3.0"
+    path-is-absolute "^1.0.0"
+
+glob@^7.1.2:
+  version "7.1.6"
+  resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
+  integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
+  dependencies:
+    fs.realpath "^1.0.0"
+    inflight "^1.0.4"
+    inherits "2"
+    minimatch "^3.0.4"
+    once "^1.3.0"
+    path-is-absolute "^1.0.0"
+
+growl@1.10.5:
+  version "1.10.5"
+  resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e"
+  integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==
+
+har-schema@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
+  integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=
+
+har-validator@~5.1.0:
+  version "5.1.3"
+  resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080"
+  integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==
+  dependencies:
+    ajv "^6.5.5"
+    har-schema "^2.0.0"
+
+has-flag@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
+  integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
+
+he@1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd"
+  integrity sha1-k0EP0hsAlzUVH4howvJx80J+I/0=
+
+http-proxy-agent@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405"
+  integrity sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==
+  dependencies:
+    agent-base "4"
+    debug "3.1.0"
+
+http-signature@~1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1"
+  integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=
+  dependencies:
+    assert-plus "^1.0.0"
+    jsprim "^1.2.2"
+    sshpk "^1.7.0"
+
+https-proxy-agent@^2.2.1:
+  version "2.2.4"
+  resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz#4ee7a737abd92678a293d9b34a1af4d0d08c787b"
+  integrity sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==
+  dependencies:
+    agent-base "^4.3.0"
+    debug "^3.1.0"
+
+inflight@^1.0.4:
+  version "1.0.6"
+  resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
+  integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=
+  dependencies:
+    once "^1.3.0"
+    wrappy "1"
+
+inherits@2:
+  version "2.0.4"
+  resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
+  integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
+
+is-typedarray@~1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
+  integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=
+
+isstream@~0.1.2:
+  version "0.1.2"
+  resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
+  integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=
+
+jsbn@~0.1.0:
+  version "0.1.1"
+  resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
+  integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM=
+
+json-schema-traverse@^0.4.1:
+  version "0.4.1"
+  resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
+  integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
+
+json-schema@0.2.3:
+  version "0.2.3"
+  resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13"
+  integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=
+
+json-stringify-safe@~5.0.1:
+  version "5.0.1"
+  resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
+  integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=
+
+jsprim@^1.2.2:
+  version "1.4.1"
+  resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
+  integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=
+  dependencies:
+    assert-plus "1.0.0"
+    extsprintf "1.3.0"
+    json-schema "0.2.3"
+    verror "1.10.0"
+
+mime-db@1.40.0:
+  version "1.40.0"
+  resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.40.0.tgz#a65057e998db090f732a68f6c276d387d4126c32"
+  integrity sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==
+
+mime-types@^2.1.12, mime-types@~2.1.19:
+  version "2.1.24"
+  resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.24.tgz#b6f8d0b3e951efb77dedeca194cff6d16f676f81"
+  integrity sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==
+  dependencies:
+    mime-db "1.40.0"
+
+minimatch@3.0.4, minimatch@^3.0.4:
+  version "3.0.4"
+  resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
+  integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
+  dependencies:
+    brace-expansion "^1.1.7"
+
+minimist@0.0.8:
+  version "0.0.8"
+  resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
+  integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=
+
+mkdirp@0.5.1:
+  version "0.5.1"
+  resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
+  integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=
+  dependencies:
+    minimist "0.0.8"
+
+mocha@^5.2.0:
+  version "5.2.0"
+  resolved "https://registry.yarnpkg.com/mocha/-/mocha-5.2.0.tgz#6d8ae508f59167f940f2b5b3c4a612ae50c90ae6"
+  integrity sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==
+  dependencies:
+    browser-stdout "1.3.1"
+    commander "2.15.1"
+    debug "3.1.0"
+    diff "3.5.0"
+    escape-string-regexp "1.0.5"
+    glob "7.1.2"
+    growl "1.10.5"
+    he "1.1.1"
+    minimatch "3.0.4"
+    mkdirp "0.5.1"
+    supports-color "5.4.0"
+
+ms@2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
+  integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
+
+ms@^2.1.1:
+  version "2.1.2"
+  resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
+  integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
+
+oauth-sign@~0.9.0:
+  version "0.9.0"
+  resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455"
+  integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==
+
+once@^1.3.0:
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
+  integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
+  dependencies:
+    wrappy "1"
+
+path-is-absolute@^1.0.0:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
+  integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
+
+performance-now@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
+  integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=
+
+psl@^1.1.24:
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/psl/-/psl-1.4.0.tgz#5dd26156cdb69fa1fdb8ab1991667d3f80ced7c2"
+  integrity sha512-HZzqCGPecFLyoRj5HLfuDSKYTJkAfB5thKBIkRHtGjWwY7p1dAyveIbXIq4tO0KYfDF2tHqPUgY9SDnGm00uFw==
+
+punycode@^1.4.1:
+  version "1.4.1"
+  resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
+  integrity sha1-wNWmOycYgArY4esPpSachN1BhF4=
+
+punycode@^2.1.0:
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
+  integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
+
+qs@~6.5.2:
+  version "6.5.2"
+  resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
+  integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==
+
+querystringify@^2.1.1:
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.1.tgz#60e5a5fd64a7f8bfa4d2ab2ed6fdf4c85bad154e"
+  integrity sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==
+
+request@^2.88.0:
+  version "2.88.0"
+  resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef"
+  integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==
+  dependencies:
+    aws-sign2 "~0.7.0"
+    aws4 "^1.8.0"
+    caseless "~0.12.0"
+    combined-stream "~1.0.6"
+    extend "~3.0.2"
+    forever-agent "~0.6.1"
+    form-data "~2.3.2"
+    har-validator "~5.1.0"
+    http-signature "~1.2.0"
+    is-typedarray "~1.0.0"
+    isstream "~0.1.2"
+    json-stringify-safe "~5.0.1"
+    mime-types "~2.1.19"
+    oauth-sign "~0.9.0"
+    performance-now "^2.1.0"
+    qs "~6.5.2"
+    safe-buffer "^5.1.2"
+    tough-cookie "~2.4.3"
+    tunnel-agent "^0.6.0"
+    uuid "^3.3.2"
+
+requires-port@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
+  integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=
+
+safe-buffer@^5.0.1, safe-buffer@^5.1.2:
+  version "5.2.0"
+  resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519"
+  integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==
+
+safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0:
+  version "2.1.2"
+  resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
+  integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
+
+semver@^5.4.1:
+  version "5.7.1"
+  resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
+  integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
+
+source-map-support@^0.5.0:
+  version "0.5.16"
+  resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.16.tgz#0ae069e7fe3ba7538c64c98515e35339eac5a042"
+  integrity sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==
+  dependencies:
+    buffer-from "^1.0.0"
+    source-map "^0.6.0"
+
+source-map@^0.6.0:
+  version "0.6.1"
+  resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
+  integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
+
+sshpk@^1.7.0:
+  version "1.16.1"
+  resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877"
+  integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==
+  dependencies:
+    asn1 "~0.2.3"
+    assert-plus "^1.0.0"
+    bcrypt-pbkdf "^1.0.0"
+    dashdash "^1.12.0"
+    ecc-jsbn "~0.1.1"
+    getpass "^0.1.1"
+    jsbn "~0.1.0"
+    safer-buffer "^2.0.2"
+    tweetnacl "~0.14.0"
+
+supports-color@5.4.0:
+  version "5.4.0"
+  resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.4.0.tgz#1c6b337402c2137605efe19f10fec390f6faab54"
+  integrity sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==
+  dependencies:
+    has-flag "^3.0.0"
+
+tough-cookie@~2.4.3:
+  version "2.4.3"
+  resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781"
+  integrity sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==
+  dependencies:
+    psl "^1.1.24"
+    punycode "^1.4.1"
+
+tunnel-agent@^0.6.0:
+  version "0.6.0"
+  resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd"
+  integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=
+  dependencies:
+    safe-buffer "^5.0.1"
+
+tweetnacl@^0.14.3, tweetnacl@~0.14.0:
+  version "0.14.5"
+  resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
+  integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=
+
+uri-js@^4.2.2:
+  version "4.2.2"
+  resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0"
+  integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==
+  dependencies:
+    punycode "^2.1.0"
+
+url-parse@^1.4.4:
+  version "1.4.7"
+  resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278"
+  integrity sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==
+  dependencies:
+    querystringify "^2.1.1"
+    requires-port "^1.0.0"
+
+uuid@^3.3.2:
+  version "3.3.3"
+  resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.3.tgz#4568f0216e78760ee1dbf3a4d2cf53e224112866"
+  integrity sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==
+
+verror@1.10.0:
+  version "1.10.0"
+  resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400"
+  integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=
+  dependencies:
+    assert-plus "^1.0.0"
+    core-util-is "1.0.2"
+    extsprintf "^1.2.0"
+
+vscode-test@^0.4.1:
+  version "0.4.3"
+  resolved "https://registry.yarnpkg.com/vscode-test/-/vscode-test-0.4.3.tgz#461ebf25fc4bc93d77d982aed556658a2e2b90b8"
+  integrity sha512-EkMGqBSefZH2MgW65nY05rdRSko15uvzq4VAPM5jVmwYuFQKE7eikKXNJDRxL+OITXHB6pI+a3XqqD32Y3KC5w==
+  dependencies:
+    http-proxy-agent "^2.1.0"
+    https-proxy-agent "^2.2.1"
+
+vscode@^1.1.36:
+  version "1.1.36"
+  resolved "https://registry.yarnpkg.com/vscode/-/vscode-1.1.36.tgz#5e1a0d1bf4977d0c7bc5159a9a13d5b104d4b1b6"
+  integrity sha512-cGFh9jmGLcTapCpPCKvn8aG/j9zVQ+0x5hzYJq5h5YyUXVGa1iamOaB2M2PZXoumQPES4qeAP1FwkI0b6tL4bQ==
+  dependencies:
+    glob "^7.1.2"
+    mocha "^5.2.0"
+    request "^2.88.0"
+    semver "^5.4.1"
+    source-map-support "^0.5.0"
+    url-parse "^1.4.4"
+    vscode-test "^0.4.1"
+
+wrappy@1:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
+  integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
diff --git a/extensions/theme-abyss/themes/abyss-color-theme.json b/extensions/theme-abyss/themes/abyss-color-theme.json
index f0a326bb34d9..1df39d31c90b 100644
--- a/extensions/theme-abyss/themes/abyss-color-theme.json
+++ b/extensions/theme-abyss/themes/abyss-color-theme.json
@@ -252,6 +252,9 @@
 	],
 	"colors": {
 
+		"editor.background": "#000c18",
+		"editor.foreground": "#6688cc",
+
 		// Base
 		// "foreground": "",
 		"focusBorder": "#596F99",
@@ -295,8 +298,6 @@
 		"scrollbarSlider.hoverBackground": "#3B3F5188",
 
 		// Editor
-		"editor.background": "#000c18",
-		// "editor.foreground": "#6688cc",
 		"editorWidget.background": "#262641",
 		"editorCursor.foreground": "#ddbb88",
 		"editorWhitespace.foreground": "#103050",
diff --git a/extensions/theme-defaults/themes/dark_plus.json b/extensions/theme-defaults/themes/dark_plus.json
index f8a0ff5b7c51..03e62612d62f 100644
--- a/extensions/theme-defaults/themes/dark_plus.json
+++ b/extensions/theme-defaults/themes/dark_plus.json
@@ -9,7 +9,8 @@
 				"entity.name.function",
 				"support.function",
 				"support.constant.handlebars",
-				"source.powershell variable.other.member"
+				"source.powershell variable.other.member",
+				"entity.name.operator.custom-literal" // See https://en.cppreference.com/w/cpp/language/user_literal
 			],
 			"settings": {
 				"foreground": "#DCDCAA"
diff --git a/extensions/theme-defaults/themes/dark_vs.json b/extensions/theme-defaults/themes/dark_vs.json
index fe0f8fc889c7..6e5f4c25f80e 100644
--- a/extensions/theme-defaults/themes/dark_vs.json
+++ b/extensions/theme-defaults/themes/dark_vs.json
@@ -171,7 +171,8 @@
 		},
 		{
 			"scope": [
-				"meta.preprocessor"
+				"meta.preprocessor",
+				"entity.name.function.preprocessor"
 			],
 			"settings": {
 				"foreground": "#569cd6"
@@ -226,6 +227,7 @@
 			"scope": [
 				"string",
 				"entity.name.operator.custom-literal.string",
+				"meta.embedded.assembly"
 			],
 			"settings": {
 				"foreground": "#ce9178"
@@ -306,6 +308,7 @@
 				"keyword.operator.expression",
 				"keyword.operator.cast",
 				"keyword.operator.sizeof",
+				"keyword.operator.alignof",
 				"keyword.operator.typeid",
 				"keyword.operator.alignas",
 				"keyword.operator.instanceof",
diff --git a/extensions/theme-defaults/themes/light_plus.json b/extensions/theme-defaults/themes/light_plus.json
index c7599d60d57a..faa2b836c2d2 100644
--- a/extensions/theme-defaults/themes/light_plus.json
+++ b/extensions/theme-defaults/themes/light_plus.json
@@ -9,7 +9,8 @@
 				"entity.name.function",
 				"support.function",
 				"support.constant.handlebars",
-				"source.powershell variable.other.member"
+				"source.powershell variable.other.member",
+				"entity.name.operator.custom-literal" // See https://en.cppreference.com/w/cpp/language/user_literal
 			],
 			"settings": {
 				"foreground": "#795E26"
diff --git a/extensions/theme-defaults/themes/light_vs.json b/extensions/theme-defaults/themes/light_vs.json
index 74bd28d2455d..5453787c4edc 100644
--- a/extensions/theme-defaults/themes/light_vs.json
+++ b/extensions/theme-defaults/themes/light_vs.json
@@ -169,7 +169,8 @@
 		},
 		{
 			"scope": [
-				"meta.preprocessor"
+				"meta.preprocessor",
+				"entity.name.function.preprocessor"
 			],
 			"settings": {
 				"foreground": "#0000ff"
@@ -218,6 +219,7 @@
 			"scope": [
 				"string",
 				"entity.name.operator.custom-literal.string",
+				"meta.embedded.assembly"
 			],
 			"settings": {
 				"foreground": "#a31515"
@@ -330,6 +332,7 @@
 				"keyword.operator.expression",
 				"keyword.operator.cast",
 				"keyword.operator.sizeof",
+				"keyword.operator.alignof",
 				"keyword.operator.typeid",
 				"keyword.operator.alignas",
 				"keyword.operator.instanceof",
diff --git a/extensions/theme-monokai/themes/monokai-color-theme.json b/extensions/theme-monokai/themes/monokai-color-theme.json
index 6bc2e84ce98c..c695640f2997 100644
--- a/extensions/theme-monokai/themes/monokai-color-theme.json
+++ b/extensions/theme-monokai/themes/monokai-color-theme.json
@@ -382,7 +382,66 @@
 			"name": "Markup Setext Header",
 			"scope": "markup.heading.setext",
 			"settings": {
-				"fontStyle": "",
+				"foreground": "#A6E22E",
+				"fontStyle": "bold"
+			}
+		},
+		{
+			"name": "Markup Headings",
+			"scope": "markup.heading.markdown",
+			"settings": {
+				"fontStyle": "bold"
+			}
+		},
+		{
+			"name": "Markdown Quote",
+			"scope": "markup.quote.markdown",
+			"settings": {
+				"fontStyle": "italic",
+				"foreground": "#75715E"
+			}
+		},
+		{
+			"name": "Markdown Bold",
+			"scope": "markup.bold.markdown",
+			"settings": {
+				"fontStyle": "bold"
+			}
+		},
+		{
+			"name": "Markdown Link Title/Description",
+			"scope": "string.other.link.title.markdown,string.other.link.description.markdown",
+			"settings": {
+				"foreground": "#AE81FF"
+			}
+		},
+		{
+			"name": "Markdown Underline Link/Image",
+			"scope": "markup.underline.link.markdown,markup.underline.link.image.markdown",
+			"settings": {
+				"foreground": "#E6DB74"
+			}
+		},
+		{
+			"name": "Markdown Emphasis",
+			"scope": "markup.italic.markdown",
+			"settings": {
+				"fontStyle": "italic"
+			}
+		},
+		{
+			"name": "Markdown Punctuation Definition Link",
+			"scope": "markup.list.unnumbered.markdown, markup.list.numbered.markdown",
+			"settings": {
+				"foreground": "#f8f8f2"
+			}
+		},
+		{
+			"name": "Markdown List Punctuation",
+			"scope": [
+				"punctuation.definition.list.begin.markdown"
+			],
+			"settings": {
 				"foreground": "#A6E22E"
 			}
 		},
diff --git a/extensions/vscode-colorize-tests/package.json b/extensions/vscode-colorize-tests/package.json
index 702e36e66cd2..c9834b05549c 100644
--- a/extensions/vscode-colorize-tests/package.json
+++ b/extensions/vscode-colorize-tests/package.json
@@ -5,14 +5,22 @@
 	"publisher": "vscode",
 	"license": "MIT",
 	"private": true,
+	"activationEvents": [
+		"onLanguage:json"
+	],
+	"main": "./out/colorizerTestMain",
+	"enableProposedApi": true,
 	"engines": {
 		"vscode": "*"
 	},
 	"scripts": {
 		"vscode:prepublish": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:vscode-colorize-tests ./tsconfig.json"
 	},
+	"dependencies": {
+		"jsonc-parser": "2.2.0"
+	},
 	"devDependencies": {
-		"@types/node": "^10.14.8",
+		"@types/node": "^12.11.7",
 		"mocha-junit-reporter": "^1.17.0",
 		"mocha-multi-reporters": "^1.1.7",
 		"vscode": "1.1.5"
diff --git a/extensions/vscode-colorize-tests/src/colorizerTestMain.ts b/extensions/vscode-colorize-tests/src/colorizerTestMain.ts
new file mode 100644
index 000000000000..ebd24ee35021
--- /dev/null
+++ b/extensions/vscode-colorize-tests/src/colorizerTestMain.ts
@@ -0,0 +1,47 @@
+/*---------------------------------------------------------------------------------------------
+ *  Copyright (c) Microsoft Corporation. All rights reserved.
+ *  Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import * as vscode from 'vscode';
+import * as jsoncParser from 'jsonc-parser';
+
+export function activate(context: vscode.ExtensionContext): any {
+
+	const tokenModifiers = ['static', 'abstract', 'deprecated'];
+	const tokenTypes = ['strings', 'types', 'structs', 'classes', 'functions', 'variables'];
+	const legend = new vscode.SemanticTokensLegend(tokenTypes, tokenModifiers);
+
+	const semanticHighlightProvider: vscode.SemanticTokensProvider = {
+		provideSemanticTokens(document: vscode.TextDocument): vscode.ProviderResult {
+			const builder = new vscode.SemanticTokensBuilder();
+
+			const visitor: jsoncParser.JSONVisitor = {
+				onObjectProperty: (property: string, _offset: number, length: number, startLine: number, startCharacter: number) => {
+					const [type, ...modifiers] = property.split('.');
+					let tokenType = legend.tokenTypes.indexOf(type);
+					if (tokenType === -1) {
+						tokenType = 0;
+					}
+
+					let tokenModifiers = 0;
+					for (let i = 0; i < modifiers.length; i++) {
+						const index = legend.tokenModifiers.indexOf(modifiers[i]);
+						if (index !== -1) {
+							tokenModifiers = tokenModifiers | 1 << index;
+						}
+					}
+
+					builder.push(startLine, startCharacter, length, tokenType, tokenModifiers);
+				}
+			};
+			jsoncParser.visit(document.getText(), visitor);
+
+			return new vscode.SemanticTokens(builder.build());
+		}
+	};
+
+
+	context.subscriptions.push(vscode.languages.registerSemanticTokensProvider({ pattern: '**/color-test.json' }, semanticHighlightProvider, legend));
+
+}
diff --git a/extensions/vscode-colorize-tests/src/index.ts b/extensions/vscode-colorize-tests/src/index.ts
index c62250651429..94656bf0bad2 100644
--- a/extensions/vscode-colorize-tests/src/index.ts
+++ b/extensions/vscode-colorize-tests/src/index.ts
@@ -10,7 +10,7 @@ const suite = 'Integration Colorize Tests';
 
 const options: any = {
 	ui: 'tdd',
-	useColors: true,
+	useColors: (!process.env.BUILD_ARTIFACTSTAGINGDIRECTORY && process.platform !== 'win32'),
 	timeout: 60000
 };
 
diff --git a/extensions/vscode-colorize-tests/src/typings/ref.d.ts b/extensions/vscode-colorize-tests/src/typings/ref.d.ts
index 3a5401b477d1..b6afd2b31377 100644
--- a/extensions/vscode-colorize-tests/src/typings/ref.d.ts
+++ b/extensions/vscode-colorize-tests/src/typings/ref.d.ts
@@ -3,5 +3,7 @@
  *  Licensed under the Source EULA. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
 
+/// 
+/// 
 /// 
-/// 
+
diff --git a/extensions/vscode-colorize-tests/yarn.lock b/extensions/vscode-colorize-tests/yarn.lock
index 368b81f2f961..c6b3fdb43135 100644
--- a/extensions/vscode-colorize-tests/yarn.lock
+++ b/extensions/vscode-colorize-tests/yarn.lock
@@ -2,10 +2,10 @@
 # yarn lockfile v1
 
 
-"@types/node@^10.14.8":
-  version "10.14.8"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-10.14.8.tgz#fe444203ecef1162348cd6deb76c62477b2cc6e9"
-  integrity sha512-I4+DbJEhLEg4/vIy/2gkWDvXBOOtPKV9EnLhYjMoqxcRW+TTZtUftkHktz/a8suoD5mUL7m6ReLrkPvSsCQQmw==
+"@types/node@^12.11.7":
+  version "12.11.7"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-12.11.7.tgz#57682a9771a3f7b09c2497f28129a0462966524a"
+  integrity sha512-JNbGaHFCLwgHn/iCckiGSOZ1XYHsKFwREtzPwSGCVld1SGhOlmZw2D4ZI94HQCrBHbADzW9m4LER/8olJTRGHA==
 
 ajv@^5.1.0:
   version "5.3.0"
@@ -1042,6 +1042,11 @@ json3@3.3.2:
   resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.2.tgz#3c0434743df93e2f5c42aee7b19bcb483575f4e1"
   integrity sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=
 
+jsonc-parser@2.2.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.2.0.tgz#f206f87f9d49d644b7502052c04e82dd6392e9ef"
+  integrity sha512-4fLQxW1j/5fWj6p78vAlAafoCKtuBm6ghv+Ij5W2DrDx0qE+ZdEl2c6Ko1mgJNF5ftX1iEWQQ4Ap7+3GlhjkOA==
+
 jsonify@~0.0.0:
   version "0.0.0"
   resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"
diff --git a/extensions/vscode-test-resolver/package.json b/extensions/vscode-test-resolver/package.json
index 0d93c4ea6d0e..90e3e0e87eed 100644
--- a/extensions/vscode-test-resolver/package.json
+++ b/extensions/vscode-test-resolver/package.json
@@ -8,7 +8,7 @@
 	"engines": {
 		"vscode": "^1.25.0"
 	},
-	"extensionKind": "ui",
+	"extensionKind": [ "ui" ],
 	"scripts": {
 		"compile": "node ./node_modules/vscode/bin/compile -watch -p ./",
 		"vscode:prepublish": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:vscode-test-resolver ./tsconfig.json"
@@ -21,7 +21,7 @@
 	],
 	"main": "./out/extension",
 	"devDependencies": {
-		"@types/node": "^10.14.8",
+		"@types/node": "^12.11.7",
 		"vscode": "1.1.5"
 	},
 	"contributes": {
@@ -90,4 +90,4 @@
 	}
 
 
-}
\ No newline at end of file
+}
diff --git a/extensions/vscode-test-resolver/src/extension.ts b/extensions/vscode-test-resolver/src/extension.ts
index 0910e5e1f7c7..90ac1ff669af 100644
--- a/extensions/vscode-test-resolver/src/extension.ts
+++ b/extensions/vscode-test-resolver/src/extension.ts
@@ -102,8 +102,8 @@ export function activate(context: vscode.ExtensionContext) {
 
 				extHostProcess = cp.spawn(path.join(serverLocation, serverCommand), commandArgs, { env, cwd: serverLocation });
 			}
-			extHostProcess.stdout.on('data', (data: Buffer) => processOutput(data.toString()));
-			extHostProcess.stderr.on('data', (data: Buffer) => processOutput(data.toString()));
+			extHostProcess.stdout!.on('data', (data: Buffer) => processOutput(data.toString()));
+			extHostProcess.stderr!.on('data', (data: Buffer) => processOutput(data.toString()));
 			extHostProcess.on('error', (error: Error) => {
 				processError(`server failed with error:\n${error.message}`);
 				extHostProcess = undefined;
@@ -210,7 +210,7 @@ export function activate(context: vscode.ExtensionContext) {
 		resolve(_authority: string): Thenable {
 			return vscode.window.withProgress({
 				location: vscode.ProgressLocation.Notification,
-				title: 'Open TestResolver Remote ([details](command:remote-testresolver.showLog))',
+				title: 'Open TestResolver Remote ([details](command:vscode-testresolver.showLog))',
 				cancellable: false
 			}, (progress) => doResolve(_authority, progress));
 		}
diff --git a/extensions/vscode-test-resolver/yarn.lock b/extensions/vscode-test-resolver/yarn.lock
index 925cef2c1428..4bc1451096e9 100644
--- a/extensions/vscode-test-resolver/yarn.lock
+++ b/extensions/vscode-test-resolver/yarn.lock
@@ -2,10 +2,10 @@
 # yarn lockfile v1
 
 
-"@types/node@^10.14.8":
-  version "10.14.8"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-10.14.8.tgz#fe444203ecef1162348cd6deb76c62477b2cc6e9"
-  integrity sha512-I4+DbJEhLEg4/vIy/2gkWDvXBOOtPKV9EnLhYjMoqxcRW+TTZtUftkHktz/a8suoD5mUL7m6ReLrkPvSsCQQmw==
+"@types/node@^12.11.7":
+  version "12.11.7"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-12.11.7.tgz#57682a9771a3f7b09c2497f28129a0462966524a"
+  integrity sha512-JNbGaHFCLwgHn/iCckiGSOZ1XYHsKFwREtzPwSGCVld1SGhOlmZw2D4ZI94HQCrBHbADzW9m4LER/8olJTRGHA==
 
 ajv@^6.5.5:
   version "6.10.0"
diff --git a/extensions/yaml/package.json b/extensions/yaml/package.json
index 5b12e0fbc5bf..c7dec1b1b83e 100644
--- a/extensions/yaml/package.json
+++ b/extensions/yaml/package.json
@@ -40,8 +40,8 @@
 			"[yaml]": {
 				"editor.insertSpaces": true,
 				"editor.tabSize": 2,
-				"editor.autoIndent": false
+				"editor.autoIndent": "advanced"
 			}
 		}
 	}
-}
\ No newline at end of file
+}
diff --git a/extensions/yarn.lock b/extensions/yarn.lock
index 6cae6ac12deb..f2e7ae95079f 100644
--- a/extensions/yarn.lock
+++ b/extensions/yarn.lock
@@ -2,7 +2,7 @@
 # yarn lockfile v1
 
 
-typescript@3.7.2:
-  version "3.7.2"
-  resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.2.tgz#27e489b95fa5909445e9fef5ee48d81697ad18fb"
-  integrity sha512-ml7V7JfiN2Xwvcer+XAf2csGO1bPBdRbFCkYBczNZggrBZ9c7G3riSUeJmqEU5uOtXNPMhE3n+R4FA/3YOAWOQ==
+typescript@3.7.3-insiders.20191123:
+  version "3.7.3-insiders.20191123"
+  resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.3-insiders.20191123.tgz#f3bef33a2a3f6e02f11bcc0c20b6f0de526f17fd"
+  integrity sha512-b+tLx4D0a6SeuaCa7iehdgkRKHsS67FkioQWw+0REjVNOYZ+AqJ0NjlnomK1hEUvSzSNrH9Du+m+Yiv7JlVpSg==
diff --git a/package.json b/package.json
index 23a44dce630c..387e3d5b76fd 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
 {
   "name": "azuredatastudio",
   "version": "1.14.0",
-  "distro": "75ae90e1c1dc7eec7de3d6a49f488eb5adbe3182",
+  "distro": "58e879c1d775f402e72ab7bcd800fb84b5334a51",
   "author": {
     "name": "Microsoft Corporation"
   },
@@ -28,6 +28,7 @@
     "strict-null-check": "node node_modules/typescript/bin/tsc -p src/tsconfig.strictNullChecks.json",
     "strict-null-check-watch": "node node_modules/typescript/bin/tsc -p src/tsconfig.strictNullChecks.json --watch",
     "strict-initialization-watch": "tsc --watch -p src/tsconfig.json --noEmit --strictPropertyInitialization",
+    "strict-function-types-watch": "tsc --watch -p src/tsconfig.json --noEmit --strictFunctionTypes",
     "update-distro": "node build/npm/update-distro.js",
     "web": "node scripts/code-web.js"
   },
@@ -45,49 +46,54 @@
     "ansi_up": "^3.0.0",
     "applicationinsights": "1.0.8",
     "chart.js": "^2.6.0",
-    "chokidar": "3.2.2",
+    "chokidar": "3.2.3",
     "graceful-fs": "4.1.11",
     "html-query-plan": "git://github.com/anthonydresser/html-query-plan.git#2.6",
     "http-proxy-agent": "^2.1.0",
     "https-proxy-agent": "^2.2.3",
     "iconv-lite": "0.5.0",
     "jquery": "3.4.0",
-    "jschardet": "1.6.0",
+    "jschardet": "2.1.1",
     "keytar": "^4.11.0",
-    "native-is-elevated": "0.3.0",
-    "native-keymap": "2.0.0",
-    "native-watchdog": "1.2.0",
+    "native-is-elevated": "0.4.1",
+    "native-keymap": "2.1.0",
+    "native-watchdog": "1.3.0",
     "ng2-charts": "^1.6.0",
-    "node-pty": "0.9.0",
-    "nsfw": "1.2.5",
-    "onigasm-umd": "^2.2.2",
+    "node-pty": "^0.10.0-beta2",
+    "onigasm-umd": "2.2.5",
     "plotly.js-dist": "^1.48.3",
     "reflect-metadata": "^0.1.8",
     "rxjs": "5.4.0",
     "sanitize-html": "^1.19.1",
     "semver-umd": "^5.5.3",
     "slickgrid": "github:anthonydresser/SlickGrid#2.3.32",
-    "spdlog": "^0.9.0",
-    "sudo-prompt": "9.0.0",
+    "spdlog": "^0.11.1",
+    "sudo-prompt": "9.1.1",
     "underscore": "^1.8.2",
     "v8-inspect-profiler": "^0.0.20",
-    "vscode-minimist": "^1.2.1",
-    "vscode-proxy-agent": "^0.5.1",
+    "vscode-minimist": "^1.2.2",
+    "vscode-nsfw": "1.2.8",
+    "vscode-proxy-agent": "^0.5.2",
     "vscode-ripgrep": "^1.5.7",
-    "vscode-sqlite3": "4.0.8",
-    "vscode-textmate": "^4.2.2",
-    "xterm": "^4.2.0-beta20",
-    "xterm-addon-search": "0.3.0-beta5",
+    "vscode-sqlite3": "4.0.9",
+    "vscode-textmate": "4.4.0",
+    "xterm": "4.3.0-beta.28",
+    "xterm-addon-search": "0.4.0-beta4",
     "xterm-addon-web-links": "0.2.1",
+    "xterm-addon-webgl": "0.4.0-beta.11",
     "yauzl": "^2.9.2",
     "yazl": "^2.4.3",
     "zone.js": "^0.8.4"
   },
   "devDependencies": {
     "7zip": "0.0.6",
+    "@types/applicationinsights": "0.20.0",
     "@types/chart.js": "^2.7.31",
+    "@types/chokidar": "2.1.3",
     "@types/cookie": "^0.3.3",
-    "@types/htmlparser2": "^3.7.31",
+    "@types/graceful-fs": "4.1.2",
+    "@types/http-proxy-agent": "^2.0.1",
+    "@types/iconv-lite": "0.0.1",
     "@types/keytar": "^4.4.0",
     "@types/mocha": "2.2.39",
     "@types/node": "^10.12.12",
@@ -96,7 +102,11 @@
     "@types/semver": "^5.5.0",
     "@types/sinon": "^1.16.36",
     "@types/webpack": "^4.4.10",
+    "@types/windows-foreground-love": "^0.3.0",
+    "@types/windows-process-tree": "^0.2.0",
     "@types/winreg": "^1.2.30",
+    "@types/yauzl": "^2.9.1",
+    "@types/yazl": "^2.4.2",
     "ansi-colors": "^3.2.3",
     "asar": "^0.14.0",
     "chromium-pickle-js": "^0.2.0",
@@ -104,7 +114,6 @@
     "coveralls": "^2.11.11",
     "cson-parser": "^1.3.3",
     "debounce": "^1.0.0",
-    "documentdb": "^1.5.1",
     "event-stream": "3.3.4",
     "express": "^4.13.1",
     "fancy-log": "^1.3.3",
@@ -160,7 +169,7 @@
     "tslint": "^5.16.0",
     "tslint-microsoft-contrib": "^6.0.0",
     "typemoq": "^0.3.2",
-    "typescript": "3.7.0-dev.20191017",
+    "typescript": "3.7.2",
     "typescript-formatter": "7.1.0",
     "vinyl": "^2.0.0",
     "vinyl-fs": "^3.0.0",
@@ -179,10 +188,10 @@
     "url": "https://github.com/Microsoft/azuredatastudio/issues"
   },
   "optionalDependencies": {
-    "vscode-windows-ca-certs": "0.1.0",
+    "vscode-windows-ca-certs": "0.2.0",
     "vscode-windows-registry": "1.0.2",
     "windows-foreground-love": "0.2.0",
     "windows-mutex": "0.3.0",
     "windows-process-tree": "0.2.4"
   }
-}
\ No newline at end of file
+}
diff --git a/remote/package.json b/remote/package.json
index 6a14e5399dc7..216a7c370154 100644
--- a/remote/package.json
+++ b/remote/package.json
@@ -3,31 +3,32 @@
   "version": "0.0.0",
   "dependencies": {
     "applicationinsights": "1.0.8",
-    "chokidar": "3.2.2",
+    "chokidar": "3.2.3",
     "cookie": "^0.4.0",
     "graceful-fs": "4.1.11",
     "http-proxy-agent": "^2.1.0",
     "https-proxy-agent": "^2.2.3",
     "iconv-lite": "0.5.0",
-    "jschardet": "1.6.0",
-    "native-watchdog": "1.2.0",
-    "node-pty": "0.9.0",
-    "nsfw": "1.2.5",
-    "onigasm-umd": "^2.2.2",
+    "jschardet": "2.1.1",
+    "native-watchdog": "1.3.0",
+    "node-pty": "^0.10.0-beta2",
+    "onigasm-umd": "2.2.5",
     "semver-umd": "^5.5.3",
-    "spdlog": "^0.9.0",
-    "vscode-minimist": "^1.2.1",
-    "vscode-proxy-agent": "^0.5.1",
+    "spdlog": "^0.11.1",
+    "vscode-minimist": "^1.2.2",
+    "vscode-nsfw": "1.2.8",
+    "vscode-proxy-agent": "^0.5.2",
     "vscode-ripgrep": "^1.5.7",
-    "vscode-textmate": "^4.2.2",
-    "xterm": "^4.2.0-beta20",
-    "xterm-addon-search": "0.3.0-beta5",
+    "vscode-textmate": "4.4.0",
+    "xterm": "4.3.0-beta.28",
+    "xterm-addon-search": "0.4.0-beta4",
     "xterm-addon-web-links": "0.2.1",
+    "xterm-addon-webgl": "0.4.0-beta.11",
     "yauzl": "^2.9.2",
     "yazl": "^2.4.3"
   },
   "optionalDependencies": {
-    "vscode-windows-ca-certs": "0.1.0",
+    "vscode-windows-ca-certs": "0.2.0",
     "vscode-windows-registry": "1.0.2"
   }
 }
diff --git a/remote/web/package.json b/remote/web/package.json
index cf40cc22b1b9..2a0519d282d6 100644
--- a/remote/web/package.json
+++ b/remote/web/package.json
@@ -2,11 +2,12 @@
 	"name": "vscode-web",
 	"version": "0.0.0",
 	"dependencies": {
-		"onigasm-umd": "^2.2.2",
+		"onigasm-umd": "2.2.5",
 		"semver-umd": "^5.5.3",
-		"vscode-textmate": "^4.2.2",
-		"xterm": "^4.2.0-beta20",
-		"xterm-addon-search": "0.3.0-beta5",
-		"xterm-addon-web-links": "0.2.1"
+		"vscode-textmate": "4.4.0",
+		"xterm": "4.3.0-beta.28",
+		"xterm-addon-search": "0.4.0-beta4",
+		"xterm-addon-web-links": "0.2.1",
+		"xterm-addon-webgl": "0.4.0-beta.11"
 	}
 }
diff --git a/remote/web/yarn.lock b/remote/web/yarn.lock
index f3ca259409be..1e2d3f0de214 100644
--- a/remote/web/yarn.lock
+++ b/remote/web/yarn.lock
@@ -7,10 +7,10 @@ nan@^2.14.0:
   resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c"
   integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==
 
-onigasm-umd@^2.2.2:
-  version "2.2.2"
-  resolved "https://registry.yarnpkg.com/onigasm-umd/-/onigasm-umd-2.2.2.tgz#b989d762df61f899a3052ac794a50bd93fe20257"
-  integrity sha512-v2eMOJu7iE444L2iJN+U6s6s5S0y7oj/N0DAkrd6wokRtTVoq/v/yaDI1lIqFrTeJbNtqNzYvguDF5yNzW3Rvw==
+onigasm-umd@2.2.5:
+  version "2.2.5"
+  resolved "https://registry.yarnpkg.com/onigasm-umd/-/onigasm-umd-2.2.5.tgz#f104247334a543accd3f8d641a4d99b3d908d6a1"
+  integrity sha512-R3qD7hq6i2bBklF+QyjqZl/G4fe7GwtukI28YLH2vuiatqx52tb9vpg2sxwemKc3nF76SgkeyOKJLchBmTm0Aw==
 
 oniguruma@^7.2.0:
   version "7.2.0"
@@ -24,24 +24,29 @@ semver-umd@^5.5.3:
   resolved "https://registry.yarnpkg.com/semver-umd/-/semver-umd-5.5.3.tgz#b64d7a2d4f5a717b369d56e31940a38e47e34d1e"
   integrity sha512-HOnQrn2iKnVe/xlqCTzMXQdvSz3rPbD0DmQXYuQ+oK1dpptGFfPghonQrx5JHl2O7EJwDqtQnjhE7ME23q6ngw==
 
-vscode-textmate@^4.2.2:
-  version "4.2.2"
-  resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-4.2.2.tgz#0b4dabc69a6fba79a065cb6b615f66eac07c8f4c"
-  integrity sha512-1U4ih0E/KP1zNK/EbpUqyYtI7PY+Ccd2nDGTtiMR/UalLFnmaYkwoWhN1oI7B91ptBN8NdVwWuvyUnvJAulCUw==
+vscode-textmate@4.4.0:
+  version "4.4.0"
+  resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-4.4.0.tgz#14032afeb50152e8f53258c95643e555f2948305"
+  integrity sha512-dFpm2eK0HwEjeFSD1DDh3j0q47bDSVuZt20RiJWxGqjtm73Wu2jip3C2KaZI3dQx/fSeeXCr/uEN4LNaNj7Ytw==
   dependencies:
     oniguruma "^7.2.0"
 
-xterm-addon-search@0.3.0-beta5:
-  version "0.3.0-beta5"
-  resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.3.0-beta5.tgz#fd53d33a77a0235018479c712be8c12f7c0d083a"
-  integrity sha512-3GkGc4hST35/4hzgnQPLLvQ29WH7MkZ0mUrBE/Vm1IQum7TnMvWPTkGemwM+wAl4tdBmynNccHJlFeQzaQtVUg==
+xterm-addon-search@0.4.0-beta4:
+  version "0.4.0-beta4"
+  resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.4.0-beta4.tgz#7762ea342c6b4f5e824d83466bd93793c9d7d779"
+  integrity sha512-TIbEBVhydGIxcyu/CfKJbD+BKHisMGbkAfaWlCPaWis2Xmw8yE7CKrCPn+lhZYl1MdjDVEmb8lQI6WetbC2OZA==
 
 xterm-addon-web-links@0.2.1:
   version "0.2.1"
   resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.2.1.tgz#6d1f2ce613e09870badf17615e7a1170a31542b2"
   integrity sha512-2KnHtiq0IG7hfwv3jw2/jQeH1RBk2d5CH4zvgwQe00rLofSJqSfgnJ7gwowxxpGHrpbPr6Lv4AmH/joaNw2+HQ==
 
-xterm@^4.2.0-beta20:
-  version "4.2.0-beta20"
-  resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.2.0-beta20.tgz#74a759414511b4577159293397914ac33a2375d8"
-  integrity sha512-lH52ksaNQqWmLVV4OdLKWvhGkSRUXgJNvb2YYmVkBAm1PdVVS36ir8Qr4zYygnc2tBw689Wj65t4cNckmfpU1Q==
+xterm-addon-webgl@0.4.0-beta.11:
+  version "0.4.0-beta.11"
+  resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.4.0-beta.11.tgz#0e4a7242e2353cf74aba55e5a5bdc0b4ec87ad10"
+  integrity sha512-AteDxm1RFy1WnjY9r5iJSETozLebvUkR+jextdZk/ASsK21vsYK0DuVWwRI8afgiN2hUVhxcxuHEJUOV+CJDQA==
+
+xterm@4.3.0-beta.28:
+  version "4.3.0-beta.28"
+  resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.3.0-beta.28.tgz#80f7c4ba8f6ee3c953e6f33f8ce5aef08d5a8354"
+  integrity sha512-WWZ4XCvce5h+klL6ObwtMauJff/n2KGGOwJJkDbJhrAjVy2a77GKgAedJTDDFGgKJ6ix1d7puHtVSSKflIVaDQ==
diff --git a/remote/yarn.lock b/remote/yarn.lock
index 5671b877e731..6b661b17e472 100644
--- a/remote/yarn.lock
+++ b/remote/yarn.lock
@@ -64,10 +64,10 @@ buffer-crc32@~0.2.3:
   resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
   integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=
 
-chokidar@3.2.2:
-  version "3.2.2"
-  resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.2.2.tgz#a433973350021e09f2b853a2287781022c0dc935"
-  integrity sha512-bw3pm7kZ2Wa6+jQWYP/c7bAZy3i4GwiIiMO2EeRjrE48l8vBqC/WvFhSF0xyM8fQiPEGvwMY/5bqDG7sSEOuhg==
+chokidar@3.2.3:
+  version "3.2.3"
+  resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.2.3.tgz#b9270a565d14f02f6bfdd537a6a2bbf5549b8c8c"
+  integrity sha512-GtrxGuRf6bzHQmXWRepvsGnXpkQkVU+D2/9a7dAe4a7v1NhrfZOZ2oKf76M3nOs46fFYL8D+Q8JYA4GYeJ8Cjw==
   dependencies:
     anymatch "~3.1.1"
     braces "~3.0.2"
@@ -155,15 +155,15 @@ glob-parent@~5.1.0:
   dependencies:
     is-glob "^4.0.1"
 
-graceful-fs@4.1.11, graceful-fs@^4.1.2:
+graceful-fs@4.1.11:
   version "4.1.11"
   resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"
   integrity sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=
 
-graceful-fs@^4.1.6:
-  version "4.2.0"
-  resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.0.tgz#8d8fdc73977cb04104721cb53666c1ca64cd328b"
-  integrity sha512-jpSvDPV4Cq/bgtpndIWbI5hmYxhQGHPC4d4cqBPb4DLniCfhJokdXhwhaDuLBGLQdvvRum/UiX6ECVIPvDXqdg==
+graceful-fs@^4.1.2, graceful-fs@^4.1.6:
+  version "4.2.3"
+  resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423"
+  integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==
 
 http-proxy-agent@^2.1.0:
   version "2.1.0"
@@ -217,10 +217,10 @@ is-number@^7.0.0:
   resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
   integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
 
-jschardet@1.6.0:
-  version "1.6.0"
-  resolved "https://registry.yarnpkg.com/jschardet/-/jschardet-1.6.0.tgz#c7d1a71edcff2839db2f9ec30fc5d5ebd3c1a678"
-  integrity sha512-xYuhvQ7I9PDJIGBWev9xm0+SMSed3ZDBAmvVjbFR1ZRLAF+vlXcQu6cRI9uAlj81rzikElRVteehwV7DuX2ZmQ==
+jschardet@2.1.1:
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/jschardet/-/jschardet-2.1.1.tgz#af6f8fd0b3b0f5d46a8fd9614a4fce490575c184"
+  integrity sha512-pA5qG9Zwm8CBpGlK/lo2GE9jPxwqRgMV7Lzc/1iaPccw6v4Rhj8Zg2BTyrdmHmxlJojnbLupLeRnaPLsq03x6Q==
 
 jsonfile@^4.0.0:
   version "4.0.0"
@@ -256,25 +256,25 @@ ms@2.0.0:
   resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
   integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
 
-nan@^2.0.0, nan@^2.14.0:
+nan@^2.10.0, nan@^2.14.0:
   version "2.14.0"
   resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c"
   integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==
 
-native-watchdog@1.2.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/native-watchdog/-/native-watchdog-1.2.0.tgz#9c710093ac6e9e60b19517cb1ef4ac9d7c997395"
-  integrity sha512-jOOoA3PLSxt1adaeuEW7ymV9cApZiDxn4y4iFs7b4sP73EG+5Lsz+OgUNFcGMyrtznTw6ZvlLcilIN4jeAIgaQ==
+native-watchdog@1.3.0:
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/native-watchdog/-/native-watchdog-1.3.0.tgz#88cee94c9dc766b85c8506eda14c8bd8c9618e27"
+  integrity sha512-WOjGRNGkYZ5MXsntcvCYrKtSYMaewlbCFplbcUVo9bE80LPVt8TAVFHYWB8+a6fWCGYheq21+Wtt6CJrUaCJhw==
 
 node-addon-api@1.6.2:
   version "1.6.2"
   resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-1.6.2.tgz#d8aad9781a5cfc4132cc2fecdbdd982534265217"
   integrity sha512-479Bjw9nTE5DdBSZZWprFryHGjUaQC31y1wHo19We/k0BZlrmhqQitWoUL0cD8+scljCbIUL+E58oRDEakdGGA==
 
-node-pty@0.9.0:
-  version "0.9.0"
-  resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-0.9.0.tgz#8f9bcc0d1c5b970a3184ffd533d862c7eb6590a6"
-  integrity sha512-MBnCQl83FTYOu7B4xWw10AW77AAh7ThCE1VXEv+JeWj8mSpGo+0bwgsV+b23ljBFwEM9OmsOv3kM27iUPPm84g==
+node-pty@^0.10.0-beta2:
+  version "0.10.0-beta2"
+  resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-0.10.0-beta2.tgz#6fd0d2fbbe881869e4e19795a05c557ac958da81"
+  integrity sha512-IU2lzlPUZ+gKG7pHJjzBHpnuwPTxWGgT3iyQicZfdL7dwLvP5cm00QxavAXCInBmRkOMhvM4aBSKvfzqQnCDBA==
   dependencies:
     nan "^2.14.0"
 
@@ -283,20 +283,10 @@ normalize-path@^3.0.0, normalize-path@~3.0.0:
   resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
   integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
 
-nsfw@1.2.5:
-  version "1.2.5"
-  resolved "https://registry.yarnpkg.com/nsfw/-/nsfw-1.2.5.tgz#febe581af616f7b042f89df133abe62416c4c803"
-  integrity sha512-m3mwZUKXiCR69PDMLfAmKmiNzy0Oe9LhFE0DYZC5cc1htNj5Hyb1sAgglXhuaDkibFy22AVvPC5cCFB3A6mYIw==
-  dependencies:
-    fs-extra "^7.0.0"
-    lodash.isinteger "^4.0.4"
-    lodash.isundefined "^3.0.1"
-    nan "^2.0.0"
-
-onigasm-umd@^2.2.2:
-  version "2.2.2"
-  resolved "https://registry.yarnpkg.com/onigasm-umd/-/onigasm-umd-2.2.2.tgz#b989d762df61f899a3052ac794a50bd93fe20257"
-  integrity sha512-v2eMOJu7iE444L2iJN+U6s6s5S0y7oj/N0DAkrd6wokRtTVoq/v/yaDI1lIqFrTeJbNtqNzYvguDF5yNzW3Rvw==
+onigasm-umd@2.2.5:
+  version "2.2.5"
+  resolved "https://registry.yarnpkg.com/onigasm-umd/-/onigasm-umd-2.2.5.tgz#f104247334a543accd3f8d641a4d99b3d908d6a1"
+  integrity sha512-R3qD7hq6i2bBklF+QyjqZl/G4fe7GwtukI28YLH2vuiatqx52tb9vpg2sxwemKc3nF76SgkeyOKJLchBmTm0Aw==
 
 oniguruma@^7.2.0:
   version "7.2.0"
@@ -358,10 +348,10 @@ socks@~2.3.2:
     ip "^1.1.5"
     smart-buffer "4.0.2"
 
-spdlog@^0.9.0:
-  version "0.9.0"
-  resolved "https://registry.yarnpkg.com/spdlog/-/spdlog-0.9.0.tgz#c85dd9d0b9cd385f6f3f5b92dc9d2e1691092b5c"
-  integrity sha512-AeLWPCYjGi4w5DfpXFKb9pCdgMe4gFBMroGfgwXiNfzwmcNYGoFQkIuD1MChZBR1Iwrx0nGhsTSHFslt/qfTAQ==
+spdlog@^0.11.1:
+  version "0.11.1"
+  resolved "https://registry.yarnpkg.com/spdlog/-/spdlog-0.11.1.tgz#29721b31018a5fe6a3ce2531f9d8d43e0bd6b825"
+  integrity sha512-M+sg9/Tnr0lrfnW2/hqgpoc4Z8Jzq7W8NUn35iiSslj+1uj1pgutI60MCpulDP2QyFzOpC8VsJmYD6Fub7wHoA==
   dependencies:
     bindings "^1.5.0"
     mkdirp "^0.5.1"
@@ -379,15 +369,25 @@ universalify@^0.1.0:
   resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
   integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
 
-vscode-minimist@^1.2.1:
-  version "1.2.1"
-  resolved "https://registry.yarnpkg.com/vscode-minimist/-/vscode-minimist-1.2.1.tgz#e63d3f4a9bf3680dcb8f9304eed612323fd6926a"
-  integrity sha512-cmB72+qDoiCFJ1UKnGUBdGYfXzdpJ3bQM/D/+XhkVk5v7uZgLbYiCz5JcwVyk7NC7hSi5VGtQ4wihzmi12NeXw==
+vscode-minimist@^1.2.2:
+  version "1.2.2"
+  resolved "https://registry.yarnpkg.com/vscode-minimist/-/vscode-minimist-1.2.2.tgz#65403f44f0c6010d259b2271d36eb5c6f4ad8aab"
+  integrity sha512-DXMNG2QgrXn1jOP12LzjVfvxVkzxv/0Qa27JrMBj/XP2esj+fJ/wP2T4YUH5derj73Lc96dC8F25WyfDUbTpxQ==
 
-vscode-proxy-agent@^0.5.1:
-  version "0.5.1"
-  resolved "https://registry.yarnpkg.com/vscode-proxy-agent/-/vscode-proxy-agent-0.5.1.tgz#7fd15e157c02176a0dca9f87840ad0991a62ca57"
-  integrity sha512-Nnkc7gBk9iAbbZURYZm3p/wvDvRVwDvdzEvDqF1Jh40p6przwQU/JTlV1XLrmd4cCwh6TOAWD81Z3cq+K7Xdmw==
+vscode-nsfw@1.2.8:
+  version "1.2.8"
+  resolved "https://registry.yarnpkg.com/vscode-nsfw/-/vscode-nsfw-1.2.8.tgz#1bf452e72ff1304934de63692870d039a2d972af"
+  integrity sha512-yRLFDk2nwV0Fp+NWJkIbeXkHYIWoTuWC2siz6JfHc21FkRUjDmuI/1rVL6B+MXW15AsSbXnH5dw4Fo9kUyYclw==
+  dependencies:
+    fs-extra "^7.0.0"
+    lodash.isinteger "^4.0.4"
+    lodash.isundefined "^3.0.1"
+    nan "^2.10.0"
+
+vscode-proxy-agent@^0.5.2:
+  version "0.5.2"
+  resolved "https://registry.yarnpkg.com/vscode-proxy-agent/-/vscode-proxy-agent-0.5.2.tgz#0c90d24d353957b841d741da7b2701e3f0a044c4"
+  integrity sha512-1cCNPxrWIrmUwS+1XGaXxkh3G1y7z2fpXl1sT74OZvELaryQWYb3NMxMLJJ4Q/CpPLEyuhp/bAN7nzHxxFcQ5Q==
   dependencies:
     debug "^3.1.0"
     http-proxy-agent "^2.1.0"
@@ -399,17 +399,17 @@ vscode-ripgrep@^1.5.7:
   resolved "https://registry.yarnpkg.com/vscode-ripgrep/-/vscode-ripgrep-1.5.7.tgz#acb6b548af488a4bca5d0f1bb5faf761343289ce"
   integrity sha512-/Vsz/+k8kTvui0q3O74pif9FK0nKopgFTiGNVvxicZANxtSA8J8gUE9GQ/4dpi7D/2yI/YVORszwVskFbz46hQ==
 
-vscode-textmate@^4.2.2:
-  version "4.2.2"
-  resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-4.2.2.tgz#0b4dabc69a6fba79a065cb6b615f66eac07c8f4c"
-  integrity sha512-1U4ih0E/KP1zNK/EbpUqyYtI7PY+Ccd2nDGTtiMR/UalLFnmaYkwoWhN1oI7B91ptBN8NdVwWuvyUnvJAulCUw==
+vscode-textmate@4.4.0:
+  version "4.4.0"
+  resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-4.4.0.tgz#14032afeb50152e8f53258c95643e555f2948305"
+  integrity sha512-dFpm2eK0HwEjeFSD1DDh3j0q47bDSVuZt20RiJWxGqjtm73Wu2jip3C2KaZI3dQx/fSeeXCr/uEN4LNaNj7Ytw==
   dependencies:
     oniguruma "^7.2.0"
 
-vscode-windows-ca-certs@0.1.0:
-  version "0.1.0"
-  resolved "https://registry.yarnpkg.com/vscode-windows-ca-certs/-/vscode-windows-ca-certs-0.1.0.tgz#d58eeb40b536130918cfde2b01e6dc7e5c1bd757"
-  integrity sha512-ZfZbfJIE09Q0dwGqmqTj7kuAq4g6lul9WPJvo0DkKjln8/FL+dY3wUKIKbYwWQp4x56SBTLBq3tJkD72xQ9Gqw==
+vscode-windows-ca-certs@0.2.0:
+  version "0.2.0"
+  resolved "https://registry.yarnpkg.com/vscode-windows-ca-certs/-/vscode-windows-ca-certs-0.2.0.tgz#086f0f4de57e2760a35ac6920831bff246237115"
+  integrity sha512-YBrJRT0zos+Yb1Qdn73GD8QZr7pa2IE96b5Y1hmmp6XeR8aYB7Iiq5gDAF/+/AxL+caSR9KPZQ6jiYWh5biD7w==
   dependencies:
     node-addon-api "1.6.2"
 
@@ -418,20 +418,25 @@ vscode-windows-registry@1.0.2:
   resolved "https://registry.yarnpkg.com/vscode-windows-registry/-/vscode-windows-registry-1.0.2.tgz#b863e704a6a69c50b3098a55fbddbe595b0c124a"
   integrity sha512-/CLLvuOSM2Vme2z6aNyB+4Omd7hDxpf4Thrt8ImxnXeQtxzel2bClJpFQvQqK/s4oaXlkBKS7LqVLeZM+uSVIA==
 
-xterm-addon-search@0.3.0-beta5:
-  version "0.3.0-beta5"
-  resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.3.0-beta5.tgz#fd53d33a77a0235018479c712be8c12f7c0d083a"
-  integrity sha512-3GkGc4hST35/4hzgnQPLLvQ29WH7MkZ0mUrBE/Vm1IQum7TnMvWPTkGemwM+wAl4tdBmynNccHJlFeQzaQtVUg==
+xterm-addon-search@0.4.0-beta4:
+  version "0.4.0-beta4"
+  resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.4.0-beta4.tgz#7762ea342c6b4f5e824d83466bd93793c9d7d779"
+  integrity sha512-TIbEBVhydGIxcyu/CfKJbD+BKHisMGbkAfaWlCPaWis2Xmw8yE7CKrCPn+lhZYl1MdjDVEmb8lQI6WetbC2OZA==
 
 xterm-addon-web-links@0.2.1:
   version "0.2.1"
   resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.2.1.tgz#6d1f2ce613e09870badf17615e7a1170a31542b2"
   integrity sha512-2KnHtiq0IG7hfwv3jw2/jQeH1RBk2d5CH4zvgwQe00rLofSJqSfgnJ7gwowxxpGHrpbPr6Lv4AmH/joaNw2+HQ==
 
-xterm@^4.2.0-beta20:
-  version "4.2.0-beta20"
-  resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.2.0-beta20.tgz#74a759414511b4577159293397914ac33a2375d8"
-  integrity sha512-lH52ksaNQqWmLVV4OdLKWvhGkSRUXgJNvb2YYmVkBAm1PdVVS36ir8Qr4zYygnc2tBw689Wj65t4cNckmfpU1Q==
+xterm-addon-webgl@0.4.0-beta.11:
+  version "0.4.0-beta.11"
+  resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.4.0-beta.11.tgz#0e4a7242e2353cf74aba55e5a5bdc0b4ec87ad10"
+  integrity sha512-AteDxm1RFy1WnjY9r5iJSETozLebvUkR+jextdZk/ASsK21vsYK0DuVWwRI8afgiN2hUVhxcxuHEJUOV+CJDQA==
+
+xterm@4.3.0-beta.28:
+  version "4.3.0-beta.28"
+  resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.3.0-beta.28.tgz#80f7c4ba8f6ee3c953e6f33f8ce5aef08d5a8354"
+  integrity sha512-WWZ4XCvce5h+klL6ObwtMauJff/n2KGGOwJJkDbJhrAjVy2a77GKgAedJTDDFGgKJ6ix1d7puHtVSSKflIVaDQ==
 
 yauzl@^2.9.2:
   version "2.10.0"
diff --git a/scripts/code-web.js b/scripts/code-web.js
index 33e586d958fe..a7370520c914 100755
--- a/scripts/code-web.js
+++ b/scripts/code-web.js
@@ -16,15 +16,43 @@ const opn = require('opn');
 const minimist = require('vscode-minimist');
 
 const APP_ROOT = path.dirname(__dirname);
+const EXTENSIONS_ROOT = path.join(APP_ROOT, 'extensions');
 const WEB_MAIN = path.join(APP_ROOT, 'src', 'vs', 'code', 'browser', 'workbench', 'workbench-dev.html');
-const PORT = 8080;
 
 const args = minimist(process.argv, {
+	boolean: [
+		'no-launch',
+		'help'
+	],
 	string: [
-		'no-launch'
-	]
+		'scheme',
+		'host',
+		'port',
+		'local_port'
+	],
 });
 
+if (args.help) {
+	console.log(
+		'yarn web [options]\n' +
+		' --no-launch   Do not open VSCode web in the browser\n' +
+		' --scheme      Protocol (https or http)\n' +
+		' --host        Remote host\n' +
+		' --port        Remote/Local port\n' +
+		' --local_port  Local port override\n' +
+		' --help\n' +
+		'[Example]\n' +
+		' yarn web --scheme https --host example.com --port 8080 --local_port 30000'
+	);
+	process.exit(0);
+}
+
+const PORT = args.port || process.env.PORT || 8080;
+const LOCAL_PORT = args.local_port || process.env.LOCAL_PORT || PORT;
+const SCHEME = args.scheme || process.env.VSCODE_SCHEME || 'http';
+const HOST = args.host || 'localhost';
+const AUTHORITY = process.env.VSCODE_AUTHORITY || `${HOST}:${PORT}`;
+
 const server = http.createServer((req, res) => {
 	const parsedUrl = url.parse(req.url, true);
 	const pathname = parsedUrl.pathname;
@@ -55,8 +83,11 @@ const server = http.createServer((req, res) => {
 	}
 });
 
-server.listen(PORT, () => {
-	console.log(`Web UI available at http://localhost:${PORT}`);
+server.listen(LOCAL_PORT, () => {
+	if (LOCAL_PORT !== PORT) {
+		console.log(`Operating location at http://0.0.0.0:${LOCAL_PORT}`);
+	}
+	console.log(`Web UI available at   ${SCHEME}://${AUTHORITY}`);
 });
 
 server.on('error', err => {
@@ -87,7 +118,7 @@ function handleStaticExtension(req, res, parsedUrl) {
 	// Strip `/static-extension/` from the path
 	const relativeFilePath = path.normalize(decodeURIComponent(parsedUrl.pathname.substr('/static-extension/'.length)));
 
-	const filePath = path.join(APP_ROOT, 'extensions', relativeFilePath);
+	const filePath = path.join(EXTENSIONS_ROOT, relativeFilePath);
 
 	return serveFile(req, res, filePath);
 }
@@ -97,12 +128,12 @@ function handleStaticExtension(req, res, parsedUrl) {
  * @param {import('http').ServerResponse} res
  */
 async function handleRoot(req, res) {
-	const extensionFolders = await util.promisify(fs.readdir)(path.join(APP_ROOT, 'extensions'));
+	const extensionFolders = await util.promisify(fs.readdir)(EXTENSIONS_ROOT);
 	const mapExtensionFolderToExtensionPackageJSON = new Map();
 
 	await Promise.all(extensionFolders.map(async extensionFolder => {
 		try {
-			const packageJSON = JSON.parse((await util.promisify(fs.readFile)(path.join(APP_ROOT, 'extensions', extensionFolder, 'package.json'))).toString());
+			const packageJSON = JSON.parse((await util.promisify(fs.readFile)(path.join(EXTENSIONS_ROOT, extensionFolder, 'package.json'))).toString());
 			if (packageJSON.main && packageJSON.name !== 'vscode-api-tests') {
 				return; // unsupported
 			}
@@ -111,7 +142,7 @@ async function handleRoot(req, res) {
 				return; // seems to fail to JSON.parse()?!
 			}
 
-			packageJSON.extensionKind = 'web'; // enable for Web
+			packageJSON.extensionKind = ['web']; // enable for Web
 
 			mapExtensionFolderToExtensionPackageJSON.set(extensionFolder, packageJSON);
 		} catch (error) {
@@ -125,14 +156,14 @@ async function handleRoot(req, res) {
 	mapExtensionFolderToExtensionPackageJSON.forEach((packageJSON, extensionFolder) => {
 		staticExtensions.push({
 			packageJSON,
-			extensionLocation: { scheme: 'http', authority: `localhost:${PORT}`, path: `/static-extension/${extensionFolder}` }
+			extensionLocation: { scheme: SCHEME, authority: AUTHORITY, path: `/static-extension/${extensionFolder}` }
 		});
 	});
 
 	const data = (await util.promisify(fs.readFile)(WEB_MAIN)).toString()
 		.replace('{{WORKBENCH_WEB_CONFIGURATION}}', escapeAttribute(JSON.stringify({
 			staticExtensions,
-			folderUri: { scheme: 'memfs', path: `/` }
+			folderUri: { scheme: 'memfs', path: `/sample-folder` }
 		})))
 		.replace('{{WEBVIEW_ENDPOINT}}', '')
 		.replace('{{REMOTE_USER_DATA_URI}}', '');
@@ -196,6 +227,14 @@ function getMediaMime(forPath) {
  */
 async function serveFile(req, res, filePath, responseHeaders = Object.create(null)) {
 	try {
+
+		// Sanity checks
+		filePath = path.normalize(filePath); // ensure no "." and ".."
+		if (filePath.indexOf(`${APP_ROOT}${path.sep}`) !== 0) {
+			// invalid location outside of APP_ROOT
+			return serveError(req, res, 400, `Bad request.`);
+		}
+
 		const stat = await util.promisify(fs.stat)(filePath);
 
 		// Check if file modified since
@@ -221,5 +260,5 @@ async function serveFile(req, res, filePath, responseHeaders = Object.create(nul
 }
 
 if (args.launch !== false) {
-	opn(`http://localhost:${PORT}`);
+	opn(`${SCHEME}://${HOST}:${PORT}`);
 }
diff --git a/scripts/code.bat b/scripts/code.bat
index 0eb0eb0b342b..770d37b7aece 100644
--- a/scripts/code.bat
+++ b/scripts/code.bat
@@ -13,9 +13,8 @@ set NAMESHORT=%NAMESHORT: "=%
 set NAMESHORT=%NAMESHORT:"=%.exe
 set CODE=".build\electron\%NAMESHORT%"
 
-:: Download Electron if needed
-node build\lib\electron.js
-if %errorlevel% neq 0 node .\node_modules\gulp\bin\gulp.js electron
+:: Get electron
+call yarn electron
 
 :: Manage built-in extensions
 if "%1"=="--builtin" goto builtin
diff --git a/scripts/test-integration.bat b/scripts/test-integration.bat
index a9732aec3594..57cc0e0f9ce9 100755
--- a/scripts/test-integration.bat
+++ b/scripts/test-integration.bat
@@ -8,7 +8,9 @@ set VSCODEUSERDATADIR=%TMP%\vscodeuserfolder-%RANDOM%-%TIME:~6,5%
 :: Figure out which Electron to use for running tests
 if "%INTEGRATION_TEST_ELECTRON_PATH%"=="" (
 	:: Run out of sources: no need to compile as code.sh takes care of it
+	chcp 65001
 	set INTEGRATION_TEST_ELECTRON_PATH=.\scripts\code.bat
+	set VSCODE_BUILD_BUILTIN_EXTENSIONS_SILENCE_PLEASE=1
 
 	echo "Running integration tests out of sources."
 ) else (
@@ -35,7 +37,6 @@ call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\vscode-api-tests\tes
 if %errorlevel% neq 0 exit /b %errorlevel%
 
 call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\vscode-api-tests\testworkspace.code-workspace --enable-proposed-api=vscode.vscode-api-tests --extensionDevelopmentPath=%~dp0\..\extensions\vscode-api-tests --extensionTestsPath=%~dp0\..\extensions\vscode-api-tests\out\workspace-tests --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --user-data-dir=%VSCODEUSERDATADIR%
-
 if %errorlevel% neq 0 exit /b %errorlevel%
 
 call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\vscode-colorize-tests\test --extensionDevelopmentPath=%~dp0\..\extensions\vscode-colorize-tests --extensionTestsPath=%~dp0\..\extensions\vscode-colorize-tests\out --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --user-data-dir=%VSCODEUSERDATADIR%
diff --git a/src/bootstrap-fork.js b/src/bootstrap-fork.js
index 41c57a40a0e1..531e1be44803 100644
--- a/src/bootstrap-fork.js
+++ b/src/bootstrap-fork.js
@@ -20,11 +20,6 @@ if (!!process.send && process.env.PIPE_LOGGING === 'true') {
 	pipeLoggingToParent();
 }
 
-// Disable IO if configured
-if (!process.env['VSCODE_ALLOW_IO']) {
-	disableSTDIO();
-}
-
 // Handle Exceptions
 if (!process.env['VSCODE_HANDLES_UNCAUGHT_ERRORS']) {
 	handleExceptions();
@@ -141,20 +136,6 @@ function pipeLoggingToParent() {
 	console.error = function () { safeSend({ type: '__$console', severity: 'error', arguments: safeToArray(arguments) }); };
 }
 
-function disableSTDIO() {
-
-	// const stdout, stderr and stdin be no-op streams. This prevents an issue where we would get an EBADF
-	// error when we are inside a forked process and this process tries to access those channels.
-	const stream = require('stream');
-	const writable = new stream.Writable({
-		write: function () { /* No OP */ }
-	});
-
-	process['__defineGetter__']('stdout', function () { return writable; });
-	process['__defineGetter__']('stderr', function () { return writable; });
-	process['__defineGetter__']('stdin', function () { return writable; });
-}
-
 function handleExceptions() {
 
 	// Handle uncaught exceptions
@@ -198,4 +179,4 @@ function configureCrashReporter() {
 	}
 }
 
-//#endregion
\ No newline at end of file
+//#endregion
diff --git a/src/bootstrap-window.js b/src/bootstrap-window.js
index f7b16145ea33..7b2c31b81a5d 100644
--- a/src/bootstrap-window.js
+++ b/src/bootstrap-window.js
@@ -161,7 +161,7 @@ exports.load = function (modulePaths, resultCallback, options) {
 		} catch (error) {
 			onUnexpectedError(error, enableDeveloperTools);
 		}
-	});
+	}, onUnexpectedError);
 };
 
 /**
diff --git a/src/bootstrap.js b/src/bootstrap.js
index 8a6a02ab5ac1..58e1408811c5 100644
--- a/src/bootstrap.js
+++ b/src/bootstrap.js
@@ -39,10 +39,12 @@ exports.injectNodeModuleLookupPath = function (injectPath) {
 	// @ts-ignore
 	Module._resolveLookupPaths = function (moduleName, parent) {
 		const paths = originalResolveLookupPaths(moduleName, parent);
-		for (let i = 0, len = paths.length; i < len; i++) {
-			if (paths[i] === nodeModulesPath) {
-				paths.splice(i, 0, injectPath);
-				break;
+		if (Array.isArray(paths)) {
+			for (let i = 0, len = paths.length; i < len; i++) {
+				if (paths[i] === nodeModulesPath) {
+					paths.splice(i, 0, injectPath);
+					break;
+				}
 			}
 		}
 
@@ -74,10 +76,12 @@ exports.enableASARSupport = function (nodeModulesPath) {
 	// @ts-ignore
 	Module._resolveLookupPaths = function (request, parent) {
 		const paths = originalResolveLookupPaths(request, parent);
-		for (let i = 0, len = paths.length; i < len; i++) {
-			if (paths[i] === NODE_MODULES_PATH) {
-				paths.splice(i, 0, NODE_MODULES_ASAR_PATH);
-				break;
+		if (Array.isArray(paths)) {
+			for (let i = 0, len = paths.length; i < len; i++) {
+				if (paths[i] === NODE_MODULES_PATH) {
+					paths.splice(i, 0, NODE_MODULES_ASAR_PATH);
+					break;
+				}
 			}
 		}
 
@@ -149,34 +153,14 @@ exports.writeFile = function (file, content) {
 	});
 };
 
-/**
- * @param {string} dir
- * @returns {Promise}
- */
-function mkdir(dir) {
-	const fs = require('fs');
-
-	return new Promise((c, e) => fs.mkdir(dir, err => (err && err.code !== 'EEXIST') ? e(err) : c(dir)));
-}
-
 /**
  * @param {string} dir
  * @returns {Promise}
  */
 exports.mkdirp = function mkdirp(dir) {
-	const path = require('path');
-
-	return mkdir(dir).then(null, err => {
-		if (err && err.code === 'ENOENT') {
-			const parent = path.dirname(dir);
-
-			if (parent !== dir) { // if not arrived at root
-				return mkdirp(parent).then(() => mkdir(dir));
-			}
-		}
+	const fs = require('fs');
 
-		throw err;
-	});
+	return new Promise((c, e) => fs.mkdir(dir, { recursive: true }, err => (err && err.code !== 'EEXIST') ? e(err) : c(dir)));
 };
 //#endregion
 
@@ -279,7 +263,12 @@ exports.configurePortable = function () {
 	}
 
 	if (isTempPortable) {
-		process.env[process.platform === 'win32' ? 'TEMP' : 'TMPDIR'] = portableTempPath;
+		if (process.platform === 'win32') {
+			process.env['TMP'] = portableTempPath;
+			process.env['TEMP'] = portableTempPath;
+		} else {
+			process.env['TMPDIR'] = portableTempPath;
+		}
 	}
 
 	return {
diff --git a/src/main.js b/src/main.js
index d08f5de5cb9f..d7dafd836c25 100644
--- a/src/main.js
+++ b/src/main.js
@@ -155,10 +155,8 @@ function configureCommandlineSwitchesSync(cliArgs) {
 			if (argvKey === 'disable-hardware-acceleration') {
 				app.disableHardwareAcceleration(); // needs to be called explicitly
 			} else {
-				app.commandLine.appendArgument(argvKey);
+				app.commandLine.appendSwitch(argvKey);
 			}
-		} else {
-			app.commandLine.appendSwitch(argvKey, argvValue);
 		}
 	});
 
@@ -221,24 +219,19 @@ function createDefaultArgvConfigSync(argvConfigPath) {
 
 		// Default argv content
 		const defaultArgvConfigContent = [
-			'// This configuration file allows to pass permanent command line arguments to VSCode.',
+			'// This configuration file allows you to pass permanent command line arguments to VS Code.',
 			'// Only a subset of arguments is currently supported to reduce the likelyhood of breaking',
 			'// the installation.',
 			'//',
 			'// PLEASE DO NOT CHANGE WITHOUT UNDERSTANDING THE IMPACT',
 			'//',
-			'// If the command line argument does not have any values, simply assign',
-			'// it in the JSON below with a value of \'true\'. Otherwise, put the value',
-			'// directly.',
-			'//',
-			'// If you see rendering issues in VSCode and have a better experience',
-			'// with software rendering, you can configure this by adding:',
-			'//',
-			'// \'disable-hardware-acceleration\': true',
-			'//',
-			'// NOTE: Changing this file requires a restart of VSCode.',
+			'// NOTE: Changing this file requires a restart of VS Code.',
 			'{',
-			'	// Enabled by default by VSCode to resolve color issues in the renderer',
+			'	// Use software rendering instead of hardware accelerated rendering.',
+			'	// This can help in cases where you see rendering issues in VS Code.',
+			'	// "disable-hardware-acceleration": true,',
+			'',
+			'	// Enabled by default by VS Code to resolve color issues in the renderer',
 			'	// See https://github.com/Microsoft/vscode/issues/51791 for details',
 			'	"disable-color-correct-rendering": true'
 		];
@@ -247,7 +240,7 @@ function createDefaultArgvConfigSync(argvConfigPath) {
 			defaultArgvConfigContent[defaultArgvConfigContent.length - 1] = `${defaultArgvConfigContent[defaultArgvConfigContent.length - 1]},`; // append trailing ","
 
 			defaultArgvConfigContent.push('');
-			defaultArgvConfigContent.push('	// Display language of VSCode');
+			defaultArgvConfigContent.push('	// Display language of VS Code');
 			defaultArgvConfigContent.push(`	"locale": "${legacyLocale}"`);
 		}
 
diff --git a/src/sql/base/browser/ui/selectBox/media/selectBox.css b/src/sql/base/browser/ui/selectBox/media/selectBox.css
index 3c2a8bfbc11a..0996d599cbf5 100644
--- a/src/sql/base/browser/ui/selectBox/media/selectBox.css
+++ b/src/sql/base/browser/ui/selectBox/media/selectBox.css
@@ -13,3 +13,8 @@
 	flex-direction: row;
 	margin-right: 5px;
 }
+
+.monaco-select-box.monaco-select-box-dropdown-padding {
+	padding: 2px 8px;
+	padding: 0 22px 0 6px !important; /* I don't like this but for now its fine */
+}
diff --git a/src/sql/media/actionBarLabel.css b/src/sql/media/actionBarLabel.css
index caf4409f50b8..2e886846d6da 100644
--- a/src/sql/media/actionBarLabel.css
+++ b/src/sql/media/actionBarLabel.css
@@ -26,9 +26,3 @@
 	-webkit-mask: url('icons/search_inverse.svg') no-repeat 50% 50%;
 	-webkit-mask-size: 25px 25px;
 }
-
-/* Activity Bar - Data Explorer */
-.monaco-workbench > .activitybar .monaco-action-bar .action-label.dataExplorer {
-	-webkit-mask: url('icons/server_page_inverse.svg') no-repeat 50% 50%;
-	-webkit-mask-size: 25px 25px;
-}
diff --git a/src/sql/platform/tasks/browser/tasksRegistry.ts b/src/sql/platform/tasks/browser/tasksRegistry.ts
index 92aed2d119c2..c36b997dac43 100644
--- a/src/sql/platform/tasks/browser/tasksRegistry.ts
+++ b/src/sql/platform/tasks/browser/tasksRegistry.ts
@@ -14,6 +14,7 @@ import { createCSSRule, asCSSUrl } from 'vs/base/browser/dom';
 import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
 import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
 import { IdGenerator } from 'vs/base/common/idGenerator';
+import { ThemeIcon } from 'vs/platform/theme/common/themeService';
 
 const ids = new IdGenerator('task-icon-');
 
@@ -56,10 +57,12 @@ export const TaskRegistry: ITaskRegistry = new class implements ITaskRegistry {
 		let iconClass: string | undefined;
 		if (this.taskIdToIconClassNameMap.has(item.id)) {
 			iconClass = this.taskIdToIconClassNameMap.get(item.id);
-		} else if (item.iconLocation) {
+		} else if (ThemeIcon.isThemeIcon(item.icon)) {
+			// TODO
+		} else if (item.icon?.dark) { // at the very least we need a dark icon
 			iconClass = ids.nextId();
-			createCSSRule(`.codicon.${iconClass}`, `background-image: ${asCSSUrl(item.iconLocation.light || item.iconLocation.dark)}`);
-			createCSSRule(`.vs-dark .codicon.${iconClass}, .hc-black .codicon.${iconClass}`, `background-image: ${asCSSUrl(item.iconLocation.dark)}`);
+			createCSSRule(`.codicon.${iconClass}`, `background-image: ${asCSSUrl(item.icon.light || item.icon.dark)}`);
+			createCSSRule(`.vs-dark .codicon.${iconClass}, .hc-black .codicon.${iconClass}`, `background-image: ${asCSSUrl(item.icon.dark)}`);
 			this.taskIdToIconClassNameMap.set(item.id, iconClass);
 		}
 		return iconClass;
@@ -102,7 +105,7 @@ export abstract class Task {
 
 	private toCommandAction(): ICommandAction {
 		return {
-			iconLocation: this.iconPath,
+			icon: this.iconPath,
 			id: this.id,
 			title: this.title
 		};
diff --git a/src/sql/platform/theme/common/styler.ts b/src/sql/platform/theme/common/styler.ts
index a14374b96c04..b86a5137e814 100644
--- a/src/sql/platform/theme/common/styler.ts
+++ b/src/sql/platform/theme/common/styler.ts
@@ -7,9 +7,10 @@ import * as sqlcolors from './colors';
 
 import { IThemeService } from 'vs/platform/theme/common/themeService';
 import * as cr from 'vs/platform/theme/common/colorRegistry';
-import { IThemable, attachStyler } from 'vs/platform/theme/common/styler';
+import { attachStyler } from 'vs/platform/theme/common/styler';
 import { IDisposable } from 'vs/base/common/lifecycle';
 import { SIDE_BAR_BACKGROUND, SIDE_BAR_SECTION_HEADER_FOREGROUND, SIDE_BAR_SECTION_HEADER_BACKGROUND, SIDE_BAR_DRAG_AND_DROP_BACKGROUND, PANEL_INACTIVE_TITLE_FOREGROUND, PANEL_ACTIVE_TITLE_BORDER, PANEL_ACTIVE_TITLE_FOREGROUND } from 'vs/workbench/common/theme';
+import { IThemable } from 'vs/base/common/styler';
 
 export function attachModalDialogStyler(widget: IThemable, themeService: IThemeService, style?:
 	{
diff --git a/src/sql/workbench/api/browser/mainThreadNotebook.ts b/src/sql/workbench/api/browser/mainThreadNotebook.ts
index ead712bf1cdc..520aad68911d 100644
--- a/src/sql/workbench/api/browser/mainThreadNotebook.ts
+++ b/src/sql/workbench/api/browser/mainThreadNotebook.ts
@@ -91,7 +91,7 @@ class NotebookProviderWrapper extends Disposable implements INotebookProvider {
 
 	constructor(
 		private _proxy: Proxies,
-		public readonly providerId,
+		public readonly providerId: string,
 		public readonly providerHandle: number,
 		@IInstantiationService private readonly instantiationService: IInstantiationService
 	) {
@@ -127,7 +127,7 @@ class NotebookManagerWrapper implements INotebookManager {
 	private managerDetails: INotebookManagerDetails;
 
 	constructor(private _proxy: Proxies,
-		public readonly providerId,
+		public readonly providerId: string,
 		private notebookUri: URI,
 		@IInstantiationService private readonly instantiationService: IInstantiationService
 	) { }
diff --git a/src/sql/workbench/api/browser/mainThreadNotebookDocumentsAndEditors.ts b/src/sql/workbench/api/browser/mainThreadNotebookDocumentsAndEditors.ts
index 44c37fec449f..f6dd0ca4e460 100644
--- a/src/sql/workbench/api/browser/mainThreadNotebookDocumentsAndEditors.ts
+++ b/src/sql/workbench/api/browser/mainThreadNotebookDocumentsAndEditors.ts
@@ -27,7 +27,6 @@ import { disposed } from 'vs/base/common/errors';
 import { ICellModel, NotebookContentChange, INotebookModel } from 'sql/workbench/contrib/notebook/browser/models/modelInterfaces';
 import { NotebookChangeType, CellTypes } from 'sql/workbench/contrib/notebook/common/models/contracts';
 import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
-import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
 import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
 import { viewColumnToEditorGroup } from 'vs/workbench/api/common/shared/editor';
 import { localize } from 'vs/nls';
@@ -35,6 +34,9 @@ import { IFileService } from 'vs/platform/files/common/files';
 import { UntitledNotebookInput } from 'sql/workbench/contrib/notebook/common/models/untitledNotebookInput';
 import { FileNotebookInput } from 'sql/workbench/contrib/notebook/common/models/fileNotebookInput';
 import { find } from 'vs/base/common/arrays';
+import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService';
+import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput';
+import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput';
 
 class MainThreadNotebookEditor extends Disposable {
 	private _contentChangedEmitter = new Emitter();
@@ -325,7 +327,7 @@ export class MainThreadNotebookDocumentsAndEditors extends Disposable implements
 	private _modelToDisposeMap = new Map();
 	constructor(
 		extHostContext: IExtHostContext,
-		@IUntitledEditorService private _untitledEditorService: IUntitledEditorService,
+		@IUntitledTextEditorService private _untitledEditorService: IUntitledTextEditorService,
 		@IInstantiationService private _instantiationService: IInstantiationService,
 		@IEditorService private _editorService: IEditorService,
 		@IEditorGroupsService private _editorGroupService: IEditorGroupsService,
@@ -458,9 +460,9 @@ export class MainThreadNotebookDocumentsAndEditors extends Disposable implements
 			this._editorService.createInput({ resource: uri, mode: 'notebook' });
 		let input: NotebookInput;
 		if (isUntitled) {
-			input = this._instantiationService.createInstance(UntitledNotebookInput, path.basename(uri.fsPath), uri, fileInput);
+			input = this._instantiationService.createInstance(UntitledNotebookInput, path.basename(uri.fsPath), uri, fileInput as UntitledTextEditorInput);
 		} else {
-			input = this._instantiationService.createInstance(FileNotebookInput, path.basename(uri.fsPath), uri, fileInput);
+			input = this._instantiationService.createInstance(FileNotebookInput, path.basename(uri.fsPath), uri, fileInput as FileEditorInput);
 		}
 		input.defaultKernel = options.defaultKernel;
 		input.connectionProfile = new ConnectionProfile(this._capabilitiesService, options.connectionProfile);
diff --git a/src/sql/workbench/browser/modal/modal.ts b/src/sql/workbench/browser/modal/modal.ts
index ccfb3c2ae192..8c56eaa8020b 100644
--- a/src/sql/workbench/browser/modal/modal.ts
+++ b/src/sql/workbench/browser/modal/modal.ts
@@ -3,7 +3,7 @@
  *  Licensed under the Source EULA. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
 import 'vs/css!./media/modal';
-import { IThemable, attachButtonStyler } from 'vs/platform/theme/common/styler';
+import { attachButtonStyler } from 'vs/platform/theme/common/styler';
 import { Color } from 'vs/base/common/color';
 import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
 import { mixin } from 'vs/base/common/objects';
@@ -26,6 +26,7 @@ import { ITextResourcePropertiesService } from 'vs/editor/common/services/resour
 import { URI } from 'vs/base/common/uri';
 import { Schemas } from 'vs/base/common/network';
 import { find, firstIndex } from 'vs/base/common/arrays';
+import { IThemable } from 'vs/base/common/styler';
 import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry';
 
 export const MODAL_SHOWING_KEY = 'modalShowing';
diff --git a/src/sql/workbench/browser/modal/optionsDialog.ts b/src/sql/workbench/browser/modal/optionsDialog.ts
index f6faee774c1c..78744439d07d 100644
--- a/src/sql/workbench/browser/modal/optionsDialog.ts
+++ b/src/sql/workbench/browser/modal/optionsDialog.ts
@@ -24,7 +24,6 @@ import * as styler from 'vs/platform/theme/common/styler';
 import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox';
 import { Widget } from 'vs/base/browser/ui/widget';
 import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
-import { IViewletPanelOptions, ViewletPanel } from 'vs/workbench/browser/parts/views/panelViewlet';
 import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
 import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
 import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@@ -34,13 +33,14 @@ import { ILogService } from 'vs/platform/log/common/log';
 import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
 import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration';
 import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry';
+import { ViewletPane, IViewletPaneOptions } from 'vs/workbench/browser/parts/views/paneViewlet';
 
-export class CategoryView extends ViewletPanel {
+export class CategoryView extends ViewletPane {
 
 	constructor(
 		private contentElement: HTMLElement,
 		private size: number,
-		options: IViewletPanelOptions,
+		options: IViewletPaneOptions,
 		@IKeybindingService keybindingService: IKeybindingService,
 		@IContextMenuService contextMenuService: IContextMenuService,
 		@IConfigurationService configurationService: IConfigurationService,
diff --git a/src/sql/workbench/browser/modelComponents/diffeditor.component.ts b/src/sql/workbench/browser/modelComponents/diffeditor.component.ts
index 3f52af539ab6..465e52ab4d22 100644
--- a/src/sql/workbench/browser/modelComponents/diffeditor.component.ts
+++ b/src/sql/workbench/browser/modelComponents/diffeditor.component.ts
@@ -90,7 +90,7 @@ export default class DiffEditorComponent extends ComponentBase implements ICompo
 
 		let editorinput1 = this._instantiationService.createInstance(ResourceEditorInput, 'source', undefined, uri1, undefined);
 		let editorinput2 = this._instantiationService.createInstance(ResourceEditorInput, 'target', undefined, uri2, undefined);
-		this._editorInput = this._instantiationService.createInstance(DiffEditorInput, 'DiffEditor', undefined, editorinput1, editorinput2, true);
+		this._editorInput = new DiffEditorInput('DiffEditor', undefined, editorinput1, editorinput2, true);
 		this._editor.setInput(this._editorInput, undefined, cancellationTokenSource.token);
 
 
diff --git a/src/sql/workbench/browser/modelComponents/editor.component.ts b/src/sql/workbench/browser/modelComponents/editor.component.ts
index 64a789f37eef..c483f19f3ea6 100644
--- a/src/sql/workbench/browser/modelComponents/editor.component.ts
+++ b/src/sql/workbench/browser/modelComponents/editor.component.ts
@@ -11,7 +11,6 @@ import * as azdata from 'azdata';
 import * as DOM from 'vs/base/browser/dom';
 import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
 import { ITextModel } from 'vs/editor/common/model';
-import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
 import { URI } from 'vs/base/common/uri';
 import { Schemas } from 'vs/base/common/network';
 import { IModeService } from 'vs/editor/common/services/modeService';
@@ -24,6 +23,7 @@ import { ServiceCollection } from 'vs/platform/instantiation/common/serviceColle
 import { SimpleEditorProgressService } from 'vs/editor/standalone/browser/simpleServices';
 import { IProgressService } from 'vs/platform/progress/common/progress';
 import { ILogService } from 'vs/platform/log/common/log';
+import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput';
 
 @Component({
 	template: '',
@@ -33,7 +33,7 @@ export default class EditorComponent extends ComponentBase implements IComponent
 	@Input() descriptor: IComponentDescriptor;
 	@Input() modelStore: IModelStore;
 	private _editor: QueryTextEditor;
-	private _editorInput: UntitledEditorInput;
+	private _editorInput: UntitledTextEditorInput;
 	private _editorModel: ITextModel;
 	private _renderedContent: string;
 	private _languageMode: string;
@@ -66,7 +66,7 @@ export default class EditorComponent extends ComponentBase implements IComponent
 		this._editor.create(this._el.nativeElement);
 		this._editor.setVisible(true);
 		let uri = this.createUri();
-		this._editorInput = instantiationService.createInstance(UntitledEditorInput, uri, false, 'plaintext', '', '');
+		this._editorInput = instantiationService.createInstance(UntitledTextEditorInput, uri, false, 'plaintext', '', '');
 		await this._editor.setInput(this._editorInput, undefined);
 		const model = await this._editorInput.resolve();
 		this._editorModel = model.textEditorModel;
diff --git a/src/sql/workbench/browser/modelComponents/modelViewInput.ts b/src/sql/workbench/browser/modelComponents/modelViewInput.ts
index 002249b48c87..f4fb921f5cef 100644
--- a/src/sql/workbench/browser/modelComponents/modelViewInput.ts
+++ b/src/sql/workbench/browser/modelComponents/modelViewInput.ts
@@ -6,7 +6,7 @@
 import * as azdata from 'azdata';
 
 import { IEditorModel } from 'vs/platform/editor/common/editor';
-import { EditorInput, EditorModel, ConfirmResult } from 'vs/workbench/common/editor';
+import { EditorInput, EditorModel } from 'vs/workbench/common/editor';
 import * as DOM from 'vs/base/browser/dom';
 import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
 
@@ -136,17 +136,6 @@ export class ModelViewInput extends EditorInput {
 		return this._model.isDirty;
 	}
 
-	/**
-	 * Subclasses should bring up a proper dialog for the user if the editor is dirty and return the result.
-	 */
-	confirmSave(): Promise {
-		// TODO #2530 support save on close / confirm save. This is significantly more work
-		// as we need to either integrate with textFileService (seems like this isn't viable)
-		// or register our own complimentary service that handles the lifecycle operations such
-		// as close all, auto save etc.
-		return Promise.resolve(ConfirmResult.DONT_SAVE);
-	}
-
 	/**
 	 * Saves the editor if it is dirty. Subclasses return a promise with a boolean indicating the success of the operation.
 	 */
diff --git a/src/sql/workbench/browser/modelComponents/queryTextEditor.ts b/src/sql/workbench/browser/modelComponents/queryTextEditor.ts
index dc1a3cd85a6f..d1924f4c76af 100644
--- a/src/sql/workbench/browser/modelComponents/queryTextEditor.ts
+++ b/src/sql/workbench/browser/modelComponents/queryTextEditor.ts
@@ -6,7 +6,6 @@
 import { IEditorOptions, EditorOption } from 'vs/editor/common/config/editorOptions';
 import * as nls from 'vs/nls';
 import * as DOM from 'vs/base/browser/dom';
-import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
 import { ResourceEditorModel } from 'vs/workbench/common/editor/resourceEditorModel';
 import * as editorCommon from 'vs/editor/common/editorCommon';
 
@@ -16,7 +15,6 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
 import { IThemeService } from 'vs/platform/theme/common/themeService';
 import { IStorageService } from 'vs/platform/storage/common/storage';
 import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration';
-import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
 import { EditorOptions } from 'vs/workbench/common/editor';
 import { StandaloneCodeEditor } from 'vs/editor/standalone/browser/standaloneCodeEditor';
 import { CancellationToken } from 'vs/base/common/cancellation';
@@ -24,7 +22,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic
 import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
 import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
 import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
-import { IHostService } from 'vs/workbench/services/host/browser/host';
+import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput';
 
 /**
  * Extension of TextResourceEditor that is always readonly rather than only with non UntitledInputs
@@ -47,16 +45,14 @@ export class QueryTextEditor extends BaseTextEditor {
 		@IStorageService storageService: IStorageService,
 		@ITextResourceConfigurationService configurationService: ITextResourceConfigurationService,
 		@IThemeService themeService: IThemeService,
-		@ITextFileService textFileService: ITextFileService,
 		@IEditorGroupsService editorGroupService: IEditorGroupsService,
 		@IEditorService protected editorService: IEditorService,
-		@IHostService hostService: IHostService,
-		@IConfigurationService private workspaceConfigurationService: IConfigurationService,
+		@IConfigurationService private workspaceConfigurationService: IConfigurationService
 
 	) {
 		super(
 			QueryTextEditor.ID, telemetryService, instantiationService, storageService,
-			configurationService, themeService, textFileService, editorService, editorGroupService, hostService);
+			configurationService, themeService, editorService, editorGroupService);
 	}
 
 	public createEditorControl(parent: HTMLElement, configuration: IEditorOptions): editorCommon.IEditor {
@@ -90,7 +86,7 @@ export class QueryTextEditor extends BaseTextEditor {
 		return options;
 	}
 
-	setInput(input: UntitledEditorInput, options: EditorOptions): Promise {
+	setInput(input: UntitledTextEditorInput, options: EditorOptions): Promise {
 		return super.setInput(input, options, CancellationToken.None)
 			.then(() => this.input.resolve()
 				.then(editorModel => editorModel.load())
diff --git a/src/sql/workbench/browser/modelComponents/tree.component.ts b/src/sql/workbench/browser/modelComponents/tree.component.ts
index 305cc5613e47..1aa3acae86ea 100644
--- a/src/sql/workbench/browser/modelComponents/tree.component.ts
+++ b/src/sql/workbench/browser/modelComponents/tree.component.ts
@@ -16,7 +16,6 @@ import { IComponent, IComponentDescriptor, IModelStore } from 'sql/workbench/bro
 import { Tree } from 'vs/base/parts/tree/browser/treeImpl';
 import { TreeComponentRenderer } from 'sql/workbench/browser/modelComponents/treeComponentRenderer';
 import { TreeComponentDataSource } from 'sql/workbench/browser/modelComponents/treeDataSource';
-import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
 import { attachListStyler } from 'vs/platform/theme/common/styler';
 import { DefaultFilter, DefaultAccessibilityProvider, DefaultController } from 'vs/base/parts/tree/browser/treeDefaults';
 import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@@ -26,6 +25,7 @@ import * as DOM from 'vs/base/browser/dom';
 import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
 import { KeyCode } from 'vs/base/common/keyCodes';
 import { values } from 'vs/base/common/collections';
+import { IThemeService } from 'vs/platform/theme/common/themeService';
 
 class Root implements ITreeComponentItem {
 	label = {
@@ -54,7 +54,7 @@ export default class TreeComponent extends ComponentBase implements IComponent,
 	@ViewChild('input', { read: ElementRef }) private _inputContainer: ElementRef;
 	constructor(
 		@Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef,
-		@Inject(IWorkbenchThemeService) private themeService: IWorkbenchThemeService,
+		@Inject(IThemeService) private themeService: IThemeService,
 		@Inject(IInstantiationService) private _instantiationService: IInstantiationService,
 		@Inject(forwardRef(() => ElementRef)) el: ElementRef
 	) {
@@ -96,7 +96,7 @@ export default class TreeComponent extends ComponentBase implements IComponent,
 	private createTreeControl(): void {
 		if (!this._tree && this._dataProvider) {
 			const dataSource = this._instantiationService.createInstance(TreeComponentDataSource, this._dataProvider);
-			const renderer = this._instantiationService.createInstance(TreeComponentRenderer, this._dataProvider, this.themeService, { withCheckbox: this.withCheckbox });
+			const renderer = new TreeComponentRenderer(this._dataProvider, this.themeService, { withCheckbox: this.withCheckbox });
 			this._treeRenderer = renderer;
 			const controller = new DefaultController();
 			const filter = new DefaultFilter();
diff --git a/src/sql/workbench/browser/modelComponents/treeComponentRenderer.ts b/src/sql/workbench/browser/modelComponents/treeComponentRenderer.ts
index d0d8378f69b2..b9ba8d3bb2f8 100644
--- a/src/sql/workbench/browser/modelComponents/treeComponentRenderer.ts
+++ b/src/sql/workbench/browser/modelComponents/treeComponentRenderer.ts
@@ -5,11 +5,10 @@
 
 import * as dom from 'vs/base/browser/dom';
 import { ITree, IRenderer } from 'vs/base/parts/tree/browser/tree';
-import { LIGHT } from 'vs/platform/theme/common/themeService';
+import { LIGHT, IThemeService } from 'vs/platform/theme/common/themeService';
 import { Disposable } from 'vs/base/common/lifecycle';
 import { Event, Emitter } from 'vs/base/common/event';
 import { ITreeComponentItem } from 'sql/workbench/common/views';
-import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
 import { TreeViewDataProvider } from './treeViewDataProvider';
 import { URI } from 'vs/base/common/uri';
 
@@ -95,7 +94,7 @@ export class TreeComponentRenderer extends Disposable implements IRenderer {
 
 	constructor(
 		private _dataProvider: TreeViewDataProvider,
-		private themeService: IWorkbenchThemeService,
+		private themeService: IThemeService,
 		public options?: { withCheckbox: boolean }
 	) {
 		super();
diff --git a/src/sql/workbench/browser/modelComponents/webview.component.ts b/src/sql/workbench/browser/modelComponents/webview.component.ts
index 984cf54b0035..8e78fdf2feb6 100644
--- a/src/sql/workbench/browser/modelComponents/webview.component.ts
+++ b/src/sql/workbench/browser/modelComponents/webview.component.ts
@@ -85,7 +85,7 @@ export default class WebViewComponent extends ComponentBase implements IComponen
 		});
 
 		this._ready.then(() => {
-			this._register(this._webview.onDidClickLink(link => this.onDidClickLink(link)));
+			this._register(this._webview.onDidClickLink(link => this.onDidClickLink(URI.parse(link))));
 
 			this._register(this._webview.onMessage(e => {
 				this.fireEvent({
diff --git a/src/sql/workbench/browser/parts/views/customView.ts b/src/sql/workbench/browser/parts/views/customView.ts
index d576d4599c26..44b09f2f3c0b 100644
--- a/src/sql/workbench/browser/parts/views/customView.ts
+++ b/src/sql/workbench/browser/parts/views/customView.ts
@@ -28,7 +28,6 @@ import { dirname, basename } from 'vs/base/common/resources';
 import { LIGHT, FileThemeIcon, FolderThemeIcon, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
 import { FileKind } from 'vs/platform/files/common/files';
 import { WorkbenchAsyncDataTree, TreeResourceNavigator2 } from 'vs/platform/list/browser/listService';
-import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet';
 import { localize } from 'vs/nls';
 import { timeout } from 'vs/base/common/async';
 import { editorFindMatchHighlight, editorFindMatchHighlightBorder, textLinkForeground, textCodeBlockBackground, focusBorder } from 'vs/platform/theme/common/colorRegistry';
@@ -50,8 +49,9 @@ import { IOEShimService } from 'sql/workbench/contrib/objectExplorer/browser/obj
 import { NodeContextKey } from 'sql/workbench/contrib/dataExplorer/browser/nodeContext';
 import { UserCancelledConnectionError } from 'sql/base/common/errors';
 import { firstIndex } from 'vs/base/common/arrays';
+import { ViewletPane, IViewletPaneOptions } from 'vs/workbench/browser/parts/views/paneViewlet';
 
-export class CustomTreeViewPanel extends ViewletPanel {
+export class CustomTreeViewPanel extends ViewletPane {
 
 	private treeView: ITreeView;
 
@@ -63,7 +63,7 @@ export class CustomTreeViewPanel extends ViewletPanel {
 		@IConfigurationService configurationService: IConfigurationService,
 		@IContextKeyService contextKeyService: IContextKeyService,
 	) {
-		super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: options.title }, keybindingService, contextMenuService, configurationService, contextKeyService);
+		super({ ...(options as IViewletPaneOptions), ariaHeaderLabel: options.title }, keybindingService, contextMenuService, configurationService, contextKeyService);
 		const { treeView } = (Registry.as(Extensions.ViewsRegistry).getView(options.id));
 		this.treeView = treeView as ITreeView;
 		this._register(this.treeView.onDidChangeActions(() => this.updateActions(), this));
diff --git a/src/sql/workbench/common/editorReplacerContribution.ts b/src/sql/workbench/common/editorReplacerContribution.ts
index 612c4c666632..319825e7b132 100644
--- a/src/sql/workbench/common/editorReplacerContribution.ts
+++ b/src/sql/workbench/common/editorReplacerContribution.ts
@@ -9,7 +9,6 @@ import { IEditorService, IOpenEditorOverride } from 'vs/workbench/services/edito
 import { IEditorInput } from 'vs/workbench/common/editor';
 import { IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/editor';
 import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService';
-import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
 import { IModeService } from 'vs/editor/common/services/modeService';
 import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput';
 import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@@ -17,6 +16,7 @@ import { Registry } from 'vs/platform/registry/common/platform';
 import * as path from 'vs/base/common/path';
 
 import { ILanguageAssociationRegistry, Extensions as LanguageAssociationExtensions } from 'sql/workbench/common/languageAssociation';
+import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput';
 
 const languageAssociationRegistry = Registry.as(LanguageAssociationExtensions.LanguageAssociations);
 
@@ -39,14 +39,14 @@ export class EditorReplacementContribution implements IWorkbenchContribution {
 		// 	return undefined;
 		// }
 
-		if (!(editor instanceof FileEditorInput) && !(editor instanceof UntitledEditorInput)) {
+		if (!(editor instanceof FileEditorInput) && !(editor instanceof UntitledTextEditorInput)) {
 			return undefined;
 		}
 
 		let language: string;
 		if (editor instanceof FileEditorInput) {
 			language = editor.getPreferredMode();
-		} else if (editor instanceof UntitledEditorInput) {
+		} else if (editor instanceof UntitledTextEditorInput) {
 			language = editor.getMode();
 		} else {
 			return undefined;
diff --git a/src/sql/workbench/common/languageAssociation.ts b/src/sql/workbench/common/languageAssociation.ts
index 38fa1409425c..99a382c38864 100644
--- a/src/sql/workbench/common/languageAssociation.ts
+++ b/src/sql/workbench/common/languageAssociation.ts
@@ -7,7 +7,7 @@ import { Registry } from 'vs/platform/registry/common/platform';
 import { IEditorInput, EditorInput } from 'vs/workbench/common/editor';
 import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
 import { find } from 'vs/base/common/arrays';
-import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
+import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput';
 import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput';
 import { getCodeEditor } from 'vs/editor/browser/editorBrowser';
 
@@ -38,7 +38,7 @@ export const Extensions = {
 Registry.add(Extensions.LanguageAssociations, languageAssociationRegistery);
 
 export function doHandleUpgrade(accessor: ServicesAccessor, editor: EditorInput): EditorInput {
-	if (editor instanceof UntitledEditorInput || editor instanceof FileEditorInput) {
+	if (editor instanceof UntitledTextEditorInput || editor instanceof FileEditorInput) {
 		const instantiationService = accessor.get(IInstantiationService);
 		const activeWidget = getCodeEditor(editor);
 		const textModel = activeWidget.getModel();
diff --git a/src/sql/workbench/contrib/accounts/browser/accountDialog.ts b/src/sql/workbench/contrib/accounts/browser/accountDialog.ts
index 6f89f75a41a0..ef991f0b48da 100644
--- a/src/sql/workbench/contrib/accounts/browser/accountDialog.ts
+++ b/src/sql/workbench/contrib/accounts/browser/accountDialog.ts
@@ -17,7 +17,6 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
 import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
 import { SplitView, Sizing } from 'vs/base/browser/ui/splitview/splitview';
 import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
-import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet';
 import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
 import { values } from 'vs/base/common/map';
 
@@ -36,13 +35,14 @@ import { ILogService } from 'vs/platform/log/common/log';
 import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
 import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration';
 import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry';
+import { ViewletPane, IViewletPaneOptions } from 'vs/workbench/browser/parts/views/paneViewlet';
 
-class AccountPanel extends ViewletPanel {
+class AccountPanel extends ViewletPane {
 	public index: number;
 	private accountList: List;
 
 	constructor(
-		private options: IViewletPanelOptions,
+		private options: IViewletPaneOptions,
 		@IKeybindingService keybindingService: IKeybindingService,
 		@IContextMenuService contextMenuService: IContextMenuService,
 		@IConfigurationService configurationService: IConfigurationService,
@@ -174,7 +174,7 @@ export class AccountDialog extends Modal {
 
 	protected renderBody(container: HTMLElement) {
 		this._container = container;
-		this._splitViewContainer = DOM.$('div.account-view.monaco-panel-view');
+		this._splitViewContainer = DOM.$('div.account-view.monaco-pane-view');
 		DOM.append(container, this._splitViewContainer);
 		this._splitView = new SplitView(this._splitViewContainer);
 
diff --git a/src/sql/workbench/contrib/charts/browser/actions.ts b/src/sql/workbench/contrib/charts/browser/actions.ts
index 4b5f8cd8b85d..b2fda3a0c0b4 100644
--- a/src/sql/workbench/contrib/charts/browser/actions.ts
+++ b/src/sql/workbench/contrib/charts/browser/actions.ts
@@ -11,7 +11,6 @@ import { localize } from 'vs/nls';
 import { Action } from 'vs/base/common/actions';
 import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
 import { URI } from 'vs/base/common/uri';
-import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
 import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
 import { IInsightsConfig } from 'sql/platform/dashboard/browser/insightRegistry';
 import { IInsightOptions } from 'sql/workbench/contrib/charts/common/interfaces';
@@ -21,6 +20,7 @@ import { IFileDialogService, FileFilter } from 'vs/platform/dialogs/common/dialo
 import { VSBuffer } from 'vs/base/common/buffer';
 import { IOpenerService } from 'vs/platform/opener/common/opener';
 import { assign } from 'vs/base/common/objects';
+import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService';
 
 export interface IChartActionContext {
 	options: IInsightOptions;
@@ -35,7 +35,7 @@ export class CreateInsightAction extends Action {
 	constructor(
 		@IEditorService private editorService: IEditorService,
 		@INotificationService private notificationService: INotificationService,
-		@IUntitledEditorService private untitledEditorService: IUntitledEditorService
+		@IUntitledTextEditorService private untitledEditorService: IUntitledTextEditorService
 	) {
 		super(CreateInsightAction.ID, CreateInsightAction.LABEL, CreateInsightAction.ICON);
 	}
diff --git a/src/sql/workbench/contrib/commandLine/test/electron-browser/commandLine.test.ts b/src/sql/workbench/contrib/commandLine/test/electron-browser/commandLine.test.ts
index ec07fbefabf5..35da2347fb5f 100644
--- a/src/sql/workbench/contrib/commandLine/test/electron-browser/commandLine.test.ts
+++ b/src/sql/workbench/contrib/commandLine/test/electron-browser/commandLine.test.ts
@@ -29,12 +29,13 @@ import { UntitledQueryEditorInput } from 'sql/workbench/contrib/query/common/unt
 import { TestQueryModelService } from 'sql/platform/query/test/common/testQueryModelService';
 import { Event } from 'vs/base/common/event';
 import { IQueryModelService } from 'sql/platform/query/common/queryModel';
-import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
 import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
 import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
 import { INotificationService } from 'vs/platform/notification/common/notification';
 import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService';
 import { isUndefinedOrNull } from 'vs/base/common/types';
+import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput';
+import { SimpleUriLabelService } from 'vs/editor/standalone/browser/simpleServices';
 
 class TestParsedArgs implements ParsedArgs {
 	[arg: string]: any;
@@ -391,7 +392,7 @@ suite('commandLineService tests', () => {
 		querymodelService.setup(c => c.onRunQueryComplete).returns(() => Event.None);
 		const instantiationService = new TestInstantiationService();
 		let uri = URI.file(args._[0]);
-		const untitledEditorInput = new UntitledEditorInput(uri, false, '', '', '', instantiationService, undefined, undefined);
+		const untitledEditorInput = new UntitledTextEditorInput(uri, false, '', '', '', instantiationService, undefined, new SimpleUriLabelService(), undefined, undefined, undefined);
 		const queryInput = new UntitledQueryEditorInput(undefined, untitledEditorInput, undefined, connectionManagementService.object, querymodelService.object, configurationService.object, undefined);
 		queryInput.state.connected = true;
 		const editorService: TypeMoq.Mock = TypeMoq.Mock.ofType(TestEditorService, TypeMoq.MockBehavior.Strict);
diff --git a/src/sql/workbench/contrib/connection/browser/connection.contribution.ts b/src/sql/workbench/contrib/connection/browser/connection.contribution.ts
index e019ad61003b..3c383623d460 100644
--- a/src/sql/workbench/contrib/connection/browser/connection.contribution.ts
+++ b/src/sql/workbench/contrib/connection/browser/connection.contribution.ts
@@ -31,7 +31,7 @@ const actionRegistry = Registry.as(Extensions.Workbenc
 
 // Connection Actions
 actionRegistry.registerWorkbenchAction(
-	new SyncActionDescriptor(
+	SyncActionDescriptor.create(
 		ClearRecentConnectionsAction,
 		ClearRecentConnectionsAction.ID,
 		ClearRecentConnectionsAction.LABEL
@@ -40,7 +40,7 @@ actionRegistry.registerWorkbenchAction(
 );
 
 actionRegistry.registerWorkbenchAction(
-	new SyncActionDescriptor(
+	SyncActionDescriptor.create(
 		AddServerGroupAction,
 		AddServerGroupAction.ID,
 		AddServerGroupAction.LABEL
@@ -49,7 +49,7 @@ actionRegistry.registerWorkbenchAction(
 );
 
 actionRegistry.registerWorkbenchAction(
-	new SyncActionDescriptor(
+	SyncActionDescriptor.create(
 		AddServerAction,
 		AddServerAction.ID,
 		AddServerAction.LABEL
@@ -102,7 +102,7 @@ CommandsRegistry.registerCommand('azdata.connect',
 	});
 
 actionRegistry.registerWorkbenchAction(
-	new SyncActionDescriptor(
+	SyncActionDescriptor.create(
 		GetCurrentConnectionStringAction,
 		GetCurrentConnectionStringAction.ID,
 		GetCurrentConnectionStringAction.LABEL
diff --git a/src/sql/workbench/contrib/connection/common/connectionProviderExtension.ts b/src/sql/workbench/contrib/connection/common/connectionProviderExtension.ts
index 4f167049172f..16456103592e 100644
--- a/src/sql/workbench/contrib/connection/common/connectionProviderExtension.ts
+++ b/src/sql/workbench/contrib/connection/common/connectionProviderExtension.ts
@@ -130,7 +130,7 @@ const ConnectionProviderContrib: IJSONSchema = {
 						type: 'string'
 					},
 					defaultValue: {
-						type: 'any'
+						type: ['string', 'number', 'boolean', 'object', 'integer', 'null', 'array']
 					},
 					defaultValueOsOverrides: {
 						type: 'array',
@@ -142,22 +142,22 @@ const ConnectionProviderContrib: IJSONSchema = {
 									enum: ['Windows', 'Macintosh', 'Linux']
 								},
 								defaultValueOverride: {
-									type: 'any'
+									type: ['string', 'number', 'boolean', 'object', 'integer', 'null', 'array']
 								}
 							}
 						}
 					},
 					objectType: {
-						type: 'any'
+						type: ['string', 'number', 'boolean', 'object', 'integer', 'null', 'array']
 					},
 					categoryValues: {
-						type: 'any'
+						type: ['string', 'number', 'boolean', 'object', 'integer', 'null', 'array']
 					},
 					isRequired: {
 						type: 'boolean'
 					},
 					isArray: {
-						type: 'bolean'
+						type: 'boolean'
 					}
 				}
 			}
diff --git a/src/sql/workbench/contrib/dashboard/browser/contents/dashboardWidgetWrapper.component.ts b/src/sql/workbench/contrib/dashboard/browser/contents/dashboardWidgetWrapper.component.ts
index bcbe27c9f690..a2a2426e7409 100644
--- a/src/sql/workbench/contrib/dashboard/browser/contents/dashboardWidgetWrapper.component.ts
+++ b/src/sql/workbench/contrib/dashboard/browser/contents/dashboardWidgetWrapper.component.ts
@@ -29,7 +29,7 @@ import { CommonServiceInterface } from 'sql/workbench/services/bootstrap/browser
 import { IColorTheme, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
 import * as colors from 'vs/platform/theme/common/colorRegistry';
 import * as themeColors from 'vs/workbench/common/theme';
-import { Action } from 'vs/base/common/actions';
+import { Action, IAction } from 'vs/base/common/actions';
 import { Registry } from 'vs/platform/registry/common/platform';
 import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
 import { memoize } from 'vs/base/common/decorators';
@@ -117,7 +117,7 @@ export class DashboardWidgetWrapper extends AngularDisposable implements OnInit
 				this._collapseAction = this.instantiationService.createInstance(CollapseWidgetAction, this._bootstrap.getUnderlyingUri(), this.guid, this.collapsed);
 				this._actionbar.push(this._collapseAction, { icon: true, label: false });
 			}
-			this._actionbar.push(this.instantiationService.createInstance(ToggleMoreWidgetAction, this._actions, this._component.actionsContext), { icon: true, label: false });
+			this._actionbar.push(this.instantiationService.createInstance(ToggleMoreWidgetAction, this._actions as Array, this._component.actionsContext), { icon: true, label: false });
 		}
 		this.layout();
 	}
diff --git a/src/sql/workbench/contrib/dashboard/browser/core/actions.ts b/src/sql/workbench/contrib/dashboard/browser/core/actions.ts
index 4df21f05a0f7..52775eb8d429 100644
--- a/src/sql/workbench/contrib/dashboard/browser/core/actions.ts
+++ b/src/sql/workbench/contrib/dashboard/browser/core/actions.ts
@@ -13,6 +13,7 @@ import { INewDashboardTabDialogService } from 'sql/workbench/services/dashboard/
 import { IDashboardTab } from 'sql/workbench/contrib/dashboard/browser/dashboardRegistry';
 import { subscriptionToDisposable } from 'sql/base/browser/lifecycle';
 import { find, firstIndex } from 'vs/base/common/arrays';
+import { CellContext } from 'sql/workbench/contrib/notebook/browser/cellViews/codeActions';
 
 export class EditDashboardAction extends Action {
 
@@ -81,9 +82,9 @@ export class ToggleMoreWidgetAction extends Action {
 	private static readonly ICON = 'toggle-more';
 
 	constructor(
-		private _actions: Array,
-		private _context: any,
-		@IContextMenuService private _contextMenuService: IContextMenuService
+		private readonly _actions: Array,
+		private readonly _context: CellContext,
+		@IContextMenuService private readonly _contextMenuService: IContextMenuService
 	) {
 		super(ToggleMoreWidgetAction.ID, ToggleMoreWidgetAction.LABEL, ToggleMoreWidgetAction.ICON);
 	}
@@ -104,9 +105,9 @@ export class DeleteWidgetAction extends Action {
 	private static readonly ICON = 'close';
 
 	constructor(
-		private _widgetId,
-		private _uri,
-		@IAngularEventingService private angularEventService: IAngularEventingService
+		private _widgetId: string,
+		private _uri: string,
+		@IAngularEventingService private readonly angularEventService: IAngularEventingService
 	) {
 		super(DeleteWidgetAction.ID, DeleteWidgetAction.LABEL, DeleteWidgetAction.ICON);
 	}
diff --git a/src/sql/workbench/contrib/dashboard/browser/widgets/explorer/explorerTree.ts b/src/sql/workbench/contrib/dashboard/browser/widgets/explorer/explorerTree.ts
index 5f35e6b0884b..4236e8dbb617 100644
--- a/src/sql/workbench/contrib/dashboard/browser/widgets/explorer/explorerTree.ts
+++ b/src/sql/workbench/contrib/dashboard/browser/widgets/explorer/explorerTree.ts
@@ -37,7 +37,7 @@ export class ExplorerController extends TreeDefaults.DefaultController {
 
 	constructor(
 		// URI for the dashboard for managing, should look into some other way of doing this
-		private _uri,
+		private _uri: string,
 		private _connectionService: SingleConnectionManagementService,
 		private _router: Router,
 		private readonly bootStrapService: CommonServiceInterface,
diff --git a/src/sql/workbench/contrib/dataExplorer/browser/connectionViewletPanel.ts b/src/sql/workbench/contrib/dataExplorer/browser/connectionViewletPanel.ts
index 7a601de1b350..a85167f3c32a 100644
--- a/src/sql/workbench/contrib/dataExplorer/browser/connectionViewletPanel.ts
+++ b/src/sql/workbench/contrib/dataExplorer/browser/connectionViewletPanel.ts
@@ -10,7 +10,6 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView
 import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
 import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
 import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
-import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet';
 import { IAction } from 'vs/base/common/actions';
 import { ServerTreeView } from 'sql/workbench/contrib/objectExplorer/browser/serverTreeView';
 import {
@@ -20,8 +19,9 @@ import {
 import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/browser/objectExplorerService';
 import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
 import { ITree } from 'vs/base/parts/tree/browser/tree';
+import { ViewletPane, IViewletPaneOptions } from 'vs/workbench/browser/parts/views/paneViewlet';
 
-export class ConnectionViewletPanel extends ViewletPanel {
+export class ConnectionViewletPanel extends ViewletPane {
 
 	public static readonly ID = 'dataExplorer.servers';
 
@@ -40,7 +40,7 @@ export class ConnectionViewletPanel extends ViewletPanel {
 		@IObjectExplorerService private readonly objectExplorerService: IObjectExplorerService,
 		@IContextKeyService contextKeyService: IContextKeyService
 	) {
-		super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: options.title }, keybindingService, contextMenuService, configurationService, contextKeyService);
+		super({ ...(options as IViewletPaneOptions), ariaHeaderLabel: options.title }, keybindingService, contextMenuService, configurationService, contextKeyService);
 		this._addServerAction = this.instantiationService.createInstance(AddServerAction,
 			AddServerAction.ID,
 			AddServerAction.LABEL);
diff --git a/src/sql/workbench/contrib/dataExplorer/browser/dataExplorer.contribution.ts b/src/sql/workbench/contrib/dataExplorer/browser/dataExplorer.contribution.ts
index 7f548f13a1eb..a235876a61fc 100644
--- a/src/sql/workbench/contrib/dataExplorer/browser/dataExplorer.contribution.ts
+++ b/src/sql/workbench/contrib/dataExplorer/browser/dataExplorer.contribution.ts
@@ -18,7 +18,7 @@ import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
 import { DataExplorerContainerExtensionHandler } from 'sql/workbench/contrib/dataExplorer/browser/dataExplorerExtensionPoint';
 
 // Data Explorer Viewlet
-const viewletDescriptor = new ViewletDescriptor(
+const viewletDescriptor = ViewletDescriptor.create(
 	DataExplorerViewlet,
 	VIEWLET_ID,
 	localize('workbench.dataExplorer', "Connections"),
@@ -32,7 +32,7 @@ const workbenchRegistry = Registry.as(Workbench
 workbenchRegistry.registerWorkbenchContribution(DataExplorerViewletViewsContribution, LifecyclePhase.Starting);
 const registry = Registry.as(ActionExtensions.WorkbenchActions);
 registry.registerWorkbenchAction(
-	new SyncActionDescriptor(
+	SyncActionDescriptor.create(
 		OpenDataExplorerViewletAction,
 		OpenDataExplorerViewletAction.ID,
 		OpenDataExplorerViewletAction.LABEL,
diff --git a/src/sql/workbench/contrib/dataExplorer/browser/dataExplorerViewlet.ts b/src/sql/workbench/contrib/dataExplorer/browser/dataExplorerViewlet.ts
index 935db7056078..03acfb655944 100644
--- a/src/sql/workbench/contrib/dataExplorer/browser/dataExplorerViewlet.ts
+++ b/src/sql/workbench/contrib/dataExplorer/browser/dataExplorerViewlet.ts
@@ -16,7 +16,6 @@ import { IStorageService } from 'vs/platform/storage/common/storage';
 import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
 import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
 import { IAddedViewDescriptorRef } from 'vs/workbench/browser/parts/views/views';
-import { ViewletPanel } from 'vs/workbench/browser/parts/views/panelViewlet';
 import { ConnectionViewletPanel } from 'sql/workbench/contrib/dataExplorer/browser/connectionViewletPanel';
 import { Extensions as ViewContainerExtensions, IViewDescriptor, IViewsRegistry, IViewContainersRegistry } from 'vs/workbench/common/views';
 import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
@@ -27,6 +26,7 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
 import { ShowViewletAction } from 'vs/workbench/browser/viewlet';
 import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
 import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
+import { ViewletPane } from 'vs/workbench/browser/parts/views/paneViewlet';
 
 export const VIEWLET_ID = 'workbench.view.connections';
 
@@ -134,13 +134,13 @@ export class DataExplorerViewlet extends ViewContainerViewlet {
 		return actions;
 	}
 
-	protected onDidAddViews(added: IAddedViewDescriptorRef[]): ViewletPanel[] {
+	protected onDidAddViews(added: IAddedViewDescriptorRef[]): ViewletPane[] {
 		const addedViews = super.onDidAddViews(added);
 		return addedViews;
 	}
 
-	protected createView(viewDescriptor: IViewDescriptor, options: IViewletViewOptions): ViewletPanel {
-		let viewletPanel = this.instantiationService.createInstance(viewDescriptor.ctorDescriptor.ctor, options) as ViewletPanel;
+	protected createView(viewDescriptor: IViewDescriptor, options: IViewletViewOptions): ViewletPane {
+		let viewletPanel = this.instantiationService.createInstance(viewDescriptor.ctorDescriptor.ctor, options) as ViewletPane;
 		this._register(viewletPanel);
 		return viewletPanel;
 	}
diff --git a/src/sql/workbench/contrib/dataExplorer/browser/media/dataExplorer.contribution.css b/src/sql/workbench/contrib/dataExplorer/browser/media/dataExplorer.contribution.css
index 0e353e15e735..111e49c4bdad 100644
--- a/src/sql/workbench/contrib/dataExplorer/browser/media/dataExplorer.contribution.css
+++ b/src/sql/workbench/contrib/dataExplorer/browser/media/dataExplorer.contribution.css
@@ -5,6 +5,11 @@
 
 /* Activity Bar */
 .monaco-workbench .activitybar .monaco-action-bar .action-label.dataExplorer {
-	-webkit-mask: url('server_page_inverse.svg') no-repeat 50% 50%;
-	-webkit-mask-size: 25px 25px;
-}
\ No newline at end of file
+	-webkit-mask: url('server_page_inverse.svg') 50% 50% / 24px no-repeat;
+	background-color: rgba(255, 255, 255, 0.4);
+}
+
+/* Activity Bar */
+.monaco-workbench .activitybar .monaco-action-bar .checked .action-label.dataExplorer {
+	background-color: rgb(255, 255, 255); /* this is a patch, will need to find a better long term fix*/
+}
diff --git a/src/sql/workbench/contrib/dataExplorer/test/browser/dataExplorerViewlet.test.ts b/src/sql/workbench/contrib/dataExplorer/test/browser/dataExplorerViewlet.test.ts
index 3651632f083e..641efbdf359a 100644
--- a/src/sql/workbench/contrib/dataExplorer/test/browser/dataExplorerViewlet.test.ts
+++ b/src/sql/workbench/contrib/dataExplorer/test/browser/dataExplorerViewlet.test.ts
@@ -22,7 +22,7 @@ suite('Data Explorer Viewlet', () => {
 	}
 
 	test('ViewletDescriptor API', function () {
-		let d = new ViewletDescriptor(DataExplorerTestViewlet, 'id', 'name', 'class', 1);
+		let d = ViewletDescriptor.create(DataExplorerTestViewlet, 'id', 'name', 'class', 1);
 		assert.strictEqual(d.id, 'id');
 		assert.strictEqual(d.name, 'name');
 		assert.strictEqual(d.cssClass, 'class');
@@ -30,11 +30,11 @@ suite('Data Explorer Viewlet', () => {
 	});
 
 	test('Editor Aware ViewletDescriptor API', function () {
-		let d = new ViewletDescriptor(DataExplorerTestViewlet, 'id', 'name', 'class', 5);
+		let d = ViewletDescriptor.create(DataExplorerTestViewlet, 'id', 'name', 'class', 5);
 		assert.strictEqual(d.id, 'id');
 		assert.strictEqual(d.name, 'name');
 
-		d = new ViewletDescriptor(DataExplorerTestViewlet, 'id', 'name', 'class', 5);
+		d = ViewletDescriptor.create(DataExplorerTestViewlet, 'id', 'name', 'class', 5);
 		assert.strictEqual(d.id, 'id');
 		assert.strictEqual(d.name, 'name');
 	});
@@ -45,11 +45,11 @@ suite('Data Explorer Viewlet', () => {
 		assert(Types.isFunction(Platform.Registry.as(Extensions.Viewlets).getViewlets));
 
 		let oldCount = Platform.Registry.as(Extensions.Viewlets).getViewlets().length;
-		let d = new ViewletDescriptor(DataExplorerTestViewlet, 'dataExplorer-test-id', 'name');
+		let d = ViewletDescriptor.create(DataExplorerTestViewlet, 'dataExplorer-test-id', 'name');
 		Platform.Registry.as(Extensions.Viewlets).registerViewlet(d);
 		let retrieved = Platform.Registry.as(Extensions.Viewlets).getViewlet('dataExplorer-test-id');
 		assert(d === retrieved);
 		let newCount = Platform.Registry.as(Extensions.Viewlets).getViewlets().length;
 		assert.equal(oldCount + 1, newCount);
 	});
-});
\ No newline at end of file
+});
diff --git a/src/sql/workbench/contrib/editData/browser/editDataEditor.ts b/src/sql/workbench/contrib/editData/browser/editDataEditor.ts
index 06035328d915..05eb44a1e8f0 100644
--- a/src/sql/workbench/contrib/editData/browser/editDataEditor.ts
+++ b/src/sql/workbench/contrib/editData/browser/editDataEditor.ts
@@ -29,7 +29,7 @@ import {
 import { TextResourceEditor } from 'vs/workbench/browser/parts/editor/textResourceEditor';
 import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
 import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
-import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
+import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput';
 import { IFlexibleSash, HorizontalFlexibleSash } from 'sql/workbench/contrib/query/browser/flexibleSash';
 import { EditDataResultsEditor } from 'sql/workbench/contrib/editData/browser/editDataResultsEditor';
 import { EditDataResultsInput } from 'sql/workbench/contrib/editData/browser/editDataResultsInput';
@@ -491,7 +491,7 @@ export class EditDataEditor extends BaseEditor {
 	/**
 	 * Sets input for the SQL editor after it has been created.
 	 */
-	private _onSqlEditorCreated(sqlEditor: TextResourceEditor, sqlInput: UntitledEditorInput, options: EditorOptions): Thenable {
+	private _onSqlEditorCreated(sqlEditor: TextResourceEditor, sqlInput: UntitledTextEditorInput, options: EditorOptions): Thenable {
 		this._sqlEditor = sqlEditor;
 		return this._sqlEditor.setInput(sqlInput, options, CancellationToken.None);
 	}
@@ -522,7 +522,7 @@ export class EditDataEditor extends BaseEditor {
 			createEditors = () => {
 				return Promise.all([
 					this._createEditor(newInput.results, this._resultsEditorContainer),
-					this._createEditor(newInput.sql, this._sqlEditorContainer)
+					this._createEditor(newInput.sql, this._sqlEditorContainer)
 				]);
 			};
 			onEditorsCreated = (result: IEditor[]) => {
@@ -535,7 +535,7 @@ export class EditDataEditor extends BaseEditor {
 			// If only the sql editor exists, create a promise and wait for the sql editor to be created
 		} else {
 			createEditors = () => {
-				return this._createEditor(newInput.sql, this._sqlEditorContainer);
+				return this._createEditor(newInput.sql, this._sqlEditorContainer);
 			};
 			onEditorsCreated = (result: TextResourceEditor) => {
 				return Promise.all([
diff --git a/src/sql/workbench/contrib/editData/browser/editDataInput.ts b/src/sql/workbench/contrib/editData/browser/editDataInput.ts
index 61dbf730595c..dd1a5754e70f 100644
--- a/src/sql/workbench/contrib/editData/browser/editDataInput.ts
+++ b/src/sql/workbench/contrib/editData/browser/editDataInput.ts
@@ -3,7 +3,7 @@
  *  Licensed under the Source EULA. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
 
-import { EditorInput, EditorModel, ConfirmResult, EncodingMode } from 'vs/workbench/common/editor';
+import { EditorInput, EditorModel, EncodingMode } from 'vs/workbench/common/editor';
 import { IConnectionManagementService, IConnectableInput, INewConnectionParams } from 'sql/platform/connection/common/connectionManagement';
 import { IQueryModelService } from 'sql/platform/query/common/queryModel';
 import { Event, Emitter } from 'vs/base/common/event';
@@ -12,9 +12,9 @@ import { URI } from 'vs/base/common/uri';
 import * as nls from 'vs/nls';
 import { INotificationService } from 'vs/platform/notification/common/notification';
 import Severity from 'vs/base/common/severity';
-import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
 import { EditDataResultsInput } from 'sql/workbench/contrib/editData/browser/editDataResultsInput';
 import { IEditorViewState } from 'vs/editor/common/editorCommon';
+import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput';
 
 /**
  * Input for the EditDataEditor.
@@ -38,9 +38,9 @@ export class EditDataInput extends EditorInput implements IConnectableInput {
 
 	constructor(
 		private _uri: URI,
-		private _schemaName,
-		private _tableName,
-		private _sql: UntitledEditorInput,
+		private _schemaName: string,
+		private _tableName: string,
+		private _sql: UntitledTextEditorInput,
 		private _queryString: string,
 		private _results: EditDataResultsInput,
 		@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
@@ -92,7 +92,7 @@ export class EditDataInput extends EditorInput implements IConnectableInput {
 	public get tableName(): string { return this._tableName; }
 	public get schemaName(): string { return this._schemaName; }
 	public get uri(): string { return this._uri.toString(); }
-	public get sql(): UntitledEditorInput { return this._sql; }
+	public get sql(): UntitledTextEditorInput { return this._sql; }
 	public get results(): EditDataResultsInput { return this._results; }
 	public getResultsInputResource(): string { return this._results.uri; }
 	public get updateTaskbarEvent(): Event { return this._updateTaskbar.event; }
@@ -108,7 +108,6 @@ export class EditDataInput extends EditorInput implements IConnectableInput {
 	public showResultsEditor(): void { this._showResultsEditor.fire(undefined); }
 	public isDirty(): boolean { return false; }
 	public save(): Promise { return Promise.resolve(false); }
-	public confirmSave(): Promise { return Promise.resolve(ConfirmResult.DONT_SAVE); }
 	public getTypeId(): string { return EditDataInput.ID; }
 	public setBootstrappedTrue(): void { this._hasBootstrapped = true; }
 	public getResource(): URI { return this._uri; }
diff --git a/src/sql/workbench/contrib/notebook/browser/cellViews/code.component.ts b/src/sql/workbench/contrib/notebook/browser/cellViews/code.component.ts
index abf9734df19e..cbb7c08af1b2 100644
--- a/src/sql/workbench/contrib/notebook/browser/cellViews/code.component.ts
+++ b/src/sql/workbench/contrib/notebook/browser/cellViews/code.component.ts
@@ -21,7 +21,6 @@ import { SimpleEditorProgressService } from 'vs/editor/standalone/browser/simple
 import { IProgressService } from 'vs/platform/progress/common/progress';
 import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
 import { ITextModel } from 'vs/editor/common/model';
-import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
 import * as DOM from 'vs/base/browser/dom';
 import { IModeService } from 'vs/editor/common/services/modeService';
 import { IModelService } from 'vs/editor/common/services/modelService';
@@ -30,11 +29,12 @@ import { Event, Emitter } from 'vs/base/common/event';
 import { CellTypes } from 'sql/workbench/contrib/notebook/common/models/contracts';
 import { OVERRIDE_EDITOR_THEMING_SETTING } from 'sql/workbench/services/notebook/browser/notebookService';
 import * as notebookUtils from 'sql/workbench/contrib/notebook/browser/models/notebookUtils';
-import { UntitledEditorModel } from 'vs/workbench/common/editor/untitledEditorModel';
 import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
 import { ILogService } from 'vs/platform/log/common/log';
 import { CollapseComponent } from 'sql/workbench/contrib/notebook/browser/cellViews/collapse.component';
 import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
+import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput';
+import { UntitledTextEditorModel } from 'vs/workbench/common/editor/untitledTextEditorModel';
 
 export const CODE_SELECTOR: string = 'code-component';
 const MARKDOWN_CLASS = 'markdown';
@@ -93,7 +93,7 @@ export class CodeComponent extends AngularDisposable implements OnInit, OnChange
 	private readonly _maximumHeight = 4000;
 	private _cellModel: ICellModel;
 	private _editor: QueryTextEditor;
-	private _editorInput: UntitledEditorInput;
+	private _editorInput: UntitledTextEditorInput;
 	private _editorModel: ITextModel;
 	private _model: NotebookModel;
 	private _activeCellId: string;
@@ -197,11 +197,11 @@ export class CodeComponent extends AngularDisposable implements OnInit, OnChange
 		let uri = this.cellModel.cellUri;
 		let cellModelSource: string;
 		cellModelSource = Array.isArray(this.cellModel.source) ? this.cellModel.source.join('') : this.cellModel.source;
-		this._editorInput = instantiationService.createInstance(UntitledEditorInput, uri, false, this.cellModel.language, cellModelSource, '');
+		this._editorInput = instantiationService.createInstance(UntitledTextEditorInput, uri, false, this.cellModel.language, cellModelSource, '');
 		await this._editor.setInput(this._editorInput, undefined);
 		this.setFocusAndScroll();
 
-		let untitledEditorModel: UntitledEditorModel = await this._editorInput.resolve();
+		let untitledEditorModel: UntitledTextEditorModel = await this._editorInput.resolve();
 		this._editorModel = untitledEditorModel.textEditorModel;
 
 		let isActive = this.cellModel.id === this._activeCellId;
diff --git a/src/sql/workbench/contrib/notebook/browser/models/notebookInput.ts b/src/sql/workbench/contrib/notebook/browser/models/notebookInput.ts
index faa49b348403..d9567a1a6320 100644
--- a/src/sql/workbench/contrib/notebook/browser/models/notebookInput.ts
+++ b/src/sql/workbench/contrib/notebook/browser/models/notebookInput.ts
@@ -3,8 +3,7 @@
  *  Licensed under the Source EULA. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
 
-import { IEditorModel } from 'vs/platform/editor/common/editor';
-import { EditorInput, EditorModel, ConfirmResult } from 'vs/workbench/common/editor';
+import { EditorInput, EditorModel, ISaveOptions } from 'vs/workbench/common/editor';
 import { Emitter, Event } from 'vs/base/common/event';
 import { URI } from 'vs/base/common/uri';
 import * as resources from 'vs/base/common/resources';
@@ -16,12 +15,10 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
 import { ITextModelService } from 'vs/editor/common/services/resolverService';
 import { INotebookModel, IContentManager, NotebookContentChange } from 'sql/workbench/contrib/notebook/browser/models/modelInterfaces';
 import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel';
-import { UntitledEditorModel } from 'vs/workbench/common/editor/untitledEditorModel';
 import { Schemas } from 'vs/base/common/network';
-import { ITextFileService, ISaveOptions, StateChange } from 'vs/workbench/services/textfile/common/textfiles';
+import { ITextFileService, StateChange } from 'vs/workbench/services/textfile/common/textfiles';
 import { LocalContentManager } from 'sql/workbench/services/notebook/common/localContentManager';
 import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
-import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
 import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
 import { IDisposable } from 'vs/base/common/lifecycle';
 import { NotebookChangeType } from 'sql/workbench/contrib/notebook/common/models/contracts';
@@ -29,6 +26,8 @@ import { Deferred } from 'sql/base/common/promise';
 import { NotebookTextFileModel } from 'sql/workbench/contrib/notebook/browser/models/notebookTextFileModel';
 import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration';
 import { ResourceEditorModel } from 'vs/workbench/common/editor/resourceEditorModel';
+import { UntitledTextEditorModel } from 'vs/workbench/common/editor/untitledTextEditorModel';
+import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput';
 import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput';
 import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput';
 import { BinaryEditorModel } from 'vs/workbench/common/editor/binaryEditorModel';
@@ -42,7 +41,7 @@ export class NotebookEditorModel extends EditorModel {
 	private readonly _onDidChangeDirty: Emitter = this._register(new Emitter());
 	private _lastEditFullReplacement: boolean;
 	constructor(public readonly notebookUri: URI,
-		private textEditorModel: TextFileEditorModel | UntitledEditorModel | ResourceEditorModel,
+		private textEditorModel: TextFileEditorModel | UntitledTextEditorModel | ResourceEditorModel,
 		@INotebookService private notebookService: INotebookService,
 		@ITextFileService private textFileService: ITextFileService,
 		@ITextResourcePropertiesService private textResourcePropertiesService: ITextResourcePropertiesService
@@ -67,7 +66,7 @@ export class NotebookEditorModel extends EditorModel {
 				}, err => undefined);
 			}
 		}));
-		if (this.textEditorModel instanceof UntitledEditorModel) {
+		if (this.textEditorModel instanceof UntitledTextEditorModel) {
 			this._register(this.textEditorModel.onDidChangeDirty(e => {
 				let dirty = this.textEditorModel instanceof ResourceEditorModel ? false : this.textEditorModel.isDirty();
 				this.setDirty(dirty);
@@ -198,7 +197,7 @@ export class NotebookEditorModel extends EditorModel {
 	}
 }
 
-type TextInput = ResourceEditorInput | UntitledEditorInput | FileEditorInput;
+type TextInput = ResourceEditorInput | UntitledTextEditorInput | FileEditorInput;
 
 export abstract class NotebookInput extends EditorInput {
 	private _providerId: string;
@@ -211,7 +210,7 @@ export abstract class NotebookInput extends EditorInput {
 	private _parentContainer: HTMLElement;
 	private readonly _layoutChanged: Emitter = this._register(new Emitter());
 	private _model: NotebookEditorModel;
-	private _untitledEditorModel: UntitledEditorModel;
+	private _untitledEditorModel: UntitledTextEditorModel;
 	private _contentManager: IContentManager;
 	private _providersLoaded: Promise;
 	private _dirtyListener: IDisposable;
@@ -241,10 +240,6 @@ export abstract class NotebookInput extends EditorInput {
 		return this._textInput;
 	}
 
-	public confirmSave(): Promise {
-		return this._textInput.confirmSave();
-	}
-
 	public revert(): Promise {
 		return this._textInput.revert();
 	}
@@ -329,11 +324,11 @@ export abstract class NotebookInput extends EditorInput {
 		return this.resource;
 	}
 
-	public get untitledEditorModel(): UntitledEditorModel {
+	public get untitledEditorModel(): UntitledTextEditorModel {
 		return this._untitledEditorModel;
 	}
 
-	public set untitledEditorModel(value: UntitledEditorModel) {
+	public set untitledEditorModel(value: UntitledTextEditorModel) {
 		this._untitledEditorModel = value;
 	}
 
@@ -347,7 +342,7 @@ export abstract class NotebookInput extends EditorInput {
 		if (this._model) {
 			return Promise.resolve(this._model);
 		} else {
-			let textOrUntitledEditorModel: UntitledEditorModel | IEditorModel;
+			let textOrUntitledEditorModel: TextFileEditorModel | UntitledTextEditorModel | ResourceEditorModel;
 			if (this.resource.scheme === Schemas.untitled) {
 				if (this._untitledEditorModel) {
 					this._untitledEditorModel.textEditorModel.onBeforeAttached();
@@ -357,12 +352,12 @@ export abstract class NotebookInput extends EditorInput {
 					if (!(resolvedInput instanceof BinaryEditorModel)) {
 						resolvedInput.textEditorModel.onBeforeAttached();
 					}
-					textOrUntitledEditorModel = resolvedInput;
+					textOrUntitledEditorModel = resolvedInput as TextFileEditorModel | UntitledTextEditorModel | ResourceEditorModel;
 				}
 			} else {
 				const textEditorModelReference = await this.textModelService.createModelReference(this.resource);
 				textEditorModelReference.object.textEditorModel.onBeforeAttached();
-				textOrUntitledEditorModel = await textEditorModelReference.object.load();
+				textOrUntitledEditorModel = await textEditorModelReference.object.load() as TextFileEditorModel | ResourceEditorModel;
 			}
 			this._model = this._register(this.instantiationService.createInstance(NotebookEditorModel, this.resource, textOrUntitledEditorModel));
 			this.hookDirtyListener(this._model.onDidChangeDirty, () => this._onDidChangeDirty.fire());
diff --git a/src/sql/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/sql/workbench/contrib/notebook/browser/notebook.contribution.ts
index 063c57c059e8..e64aa2b64296 100644
--- a/src/sql/workbench/contrib/notebook/browser/notebook.contribution.ts
+++ b/src/sql/workbench/contrib/notebook/browser/notebook.contribution.ts
@@ -7,7 +7,6 @@ import { EditorDescriptor, IEditorRegistry, Extensions as EditorExtensions } fro
 import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
 import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
 import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput';
-import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
 import { localize } from 'vs/nls';
 import { IEditorInputFactoryRegistry, Extensions as EditorInputFactoryExtensions } from 'vs/workbench/common/editor';
 
@@ -46,6 +45,7 @@ import { IHostService } from 'vs/workbench/services/host/browser/host';
 import { MarkdownOutputComponent } from 'sql/workbench/contrib/notebook/browser/outputs/markdownOutput.component';
 import { registerCellComponent } from 'sql/platform/notebooks/common/outputRegistry';
 import { TextCellComponent } from 'sql/workbench/contrib/notebook/browser/cellViews/textCell.component';
+import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput';
 
 Registry.as(EditorInputFactoryExtensions.EditorInputFactories)
 	.registerEditorInputFactory(FileNotebookInput.ID, FileNoteBookEditorInputFactory);
@@ -58,7 +58,7 @@ Registry.as(LanguageAssociationExtensions.Language
 		const instantiationService = accessor.get(IInstantiationService);
 		if (editor instanceof FileEditorInput) {
 			return instantiationService.createInstance(FileNotebookInput, editor.getName(), editor.getResource(), editor);
-		} else if (editor instanceof UntitledEditorInput) {
+		} else if (editor instanceof UntitledTextEditorInput) {
 			return instantiationService.createInstance(UntitledNotebookInput, editor.getName(), editor.getResource(), editor);
 		} else {
 			return undefined;
@@ -73,7 +73,7 @@ Registry.as(EditorExtensions.Editors)
 const actionRegistry = Registry.as(WorkbenchActionsExtensions.WorkbenchActions);
 
 actionRegistry.registerWorkbenchAction(
-	new SyncActionDescriptor(
+	SyncActionDescriptor.create(
 		NewNotebookAction,
 		NewNotebookAction.ID,
 		NewNotebookAction.LABEL,
diff --git a/src/sql/workbench/contrib/notebook/browser/outputs/gridOutput.component.ts b/src/sql/workbench/contrib/notebook/browser/outputs/gridOutput.component.ts
index a26545cdab3a..9cbec76c5ab5 100644
--- a/src/sql/workbench/contrib/notebook/browser/outputs/gridOutput.component.ts
+++ b/src/sql/workbench/contrib/notebook/browser/outputs/gridOutput.component.ts
@@ -10,7 +10,6 @@ import { IGridDataProvider, getResultsString } from 'sql/platform/query/common/g
 import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
 import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
 import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
-import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
 import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
 import { SaveFormat } from 'sql/workbench/contrib/grid/common/interfaces';
 import { IDataResource } from 'sql/workbench/services/notebook/browser/sql/sqlSessionManager';
@@ -35,6 +34,7 @@ import { ResultSerializer, SaveResultsResponse } from 'sql/workbench/contrib/que
 import { ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
 import { values } from 'vs/base/common/collections';
 import { assign } from 'vs/base/common/objects';
+import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService';
 
 @Component({
 	selector: GridOutputComponent.SELECTOR,
@@ -138,7 +138,7 @@ class DataResourceTable extends GridTableBase {
 		@IContextMenuService contextMenuService: IContextMenuService,
 		@IInstantiationService instantiationService: IInstantiationService,
 		@IEditorService editorService: IEditorService,
-		@IUntitledEditorService untitledEditorService: IUntitledEditorService,
+		@IUntitledTextEditorService untitledEditorService: IUntitledTextEditorService,
 		@IConfigurationService configurationService: IConfigurationService,
 		@ISerializationService private _serializationService: ISerializationService
 	) {
diff --git a/src/sql/workbench/contrib/notebook/common/models/nodebookInputFactory.ts b/src/sql/workbench/contrib/notebook/common/models/nodebookInputFactory.ts
index f27c67d5426a..a22f81ac36fc 100644
--- a/src/sql/workbench/contrib/notebook/common/models/nodebookInputFactory.ts
+++ b/src/sql/workbench/contrib/notebook/common/models/nodebookInputFactory.ts
@@ -6,11 +6,11 @@
 import { IEditorInputFactory, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions } from 'vs/workbench/common/editor';
 import { Registry } from 'vs/platform/registry/common/platform';
 import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
-import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
 import { FILE_EDITOR_INPUT_ID } from 'vs/workbench/contrib/files/common/files';
 import { FileNotebookInput } from 'sql/workbench/contrib/notebook/common/models/fileNotebookInput';
 import { UntitledNotebookInput } from 'sql/workbench/contrib/notebook/common/models/untitledNotebookInput';
 import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput';
+import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput';
 
 const editorInputFactoryRegistry = Registry.as(EditorInputExtensions.EditorInputFactories);
 
@@ -32,7 +32,7 @@ export class FileNoteBookEditorInputFactory implements IEditorInputFactory {
 
 export class UntitledNoteBookEditorInputFactory implements IEditorInputFactory {
 	serialize(editorInput: UntitledNotebookInput): string {
-		const factory = editorInputFactoryRegistry.getEditorInputFactory(UntitledEditorInput.ID);
+		const factory = editorInputFactoryRegistry.getEditorInputFactory(UntitledTextEditorInput.ID);
 		if (factory) {
 			return factory.serialize(editorInput.textInput); // serialize based on the underlying input
 		}
@@ -40,8 +40,8 @@ export class UntitledNoteBookEditorInputFactory implements IEditorInputFactory {
 	}
 
 	deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): UntitledNotebookInput | undefined {
-		const factory = editorInputFactoryRegistry.getEditorInputFactory(UntitledEditorInput.ID);
-		const untitledEditorInput = factory.deserialize(instantiationService, serializedEditorInput) as UntitledEditorInput;
+		const factory = editorInputFactoryRegistry.getEditorInputFactory(UntitledTextEditorInput.ID);
+		const untitledEditorInput = factory.deserialize(instantiationService, serializedEditorInput) as UntitledTextEditorInput;
 		return instantiationService.createInstance(UntitledNotebookInput, untitledEditorInput.getName(), untitledEditorInput.getResource(), untitledEditorInput);
 	}
 }
diff --git a/src/sql/workbench/contrib/notebook/common/models/untitledNotebookInput.ts b/src/sql/workbench/contrib/notebook/common/models/untitledNotebookInput.ts
index adf0f50cbbc5..9f2b54c85881 100644
--- a/src/sql/workbench/contrib/notebook/common/models/untitledNotebookInput.ts
+++ b/src/sql/workbench/contrib/notebook/common/models/untitledNotebookInput.ts
@@ -7,9 +7,9 @@ import { URI } from 'vs/base/common/uri';
 import { ITextModelService } from 'vs/editor/common/services/resolverService';
 import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
 import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
-import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
 import { NotebookInput } from 'sql/workbench/contrib/notebook/browser/models/notebookInput';
 import { INotebookService } from 'sql/workbench/services/notebook/browser/notebookService';
+import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput';
 
 export class UntitledNotebookInput extends NotebookInput {
 	public static ID: string = 'workbench.editorinputs.untitledNotebookInput';
@@ -17,7 +17,7 @@ export class UntitledNotebookInput extends NotebookInput {
 	constructor(
 		title: string,
 		resource: URI,
-		textInput: UntitledEditorInput,
+		textInput: UntitledTextEditorInput,
 		@ITextModelService textModelService: ITextModelService,
 		@IInstantiationService instantiationService: IInstantiationService,
 		@INotebookService notebookService: INotebookService,
@@ -26,14 +26,19 @@ export class UntitledNotebookInput extends NotebookInput {
 		super(title, resource, textInput, textModelService, instantiationService, notebookService, extensionService);
 	}
 
-	public get textInput(): UntitledEditorInput {
-		return super.textInput as UntitledEditorInput;
+	public get textInput(): UntitledTextEditorInput {
+		return super.textInput as UntitledTextEditorInput;
 	}
 
 	public setMode(mode: string): void {
 		this.textInput.setMode(mode);
 	}
 
+	isUntitled(): boolean {
+		// Subclasses need to explicitly opt-in to being untitled.
+		return true;
+	}
+
 	public getTypeId(): string {
 		return UntitledNotebookInput.ID;
 	}
diff --git a/src/sql/workbench/contrib/notebook/test/browser/notebookEditorModel.test.ts b/src/sql/workbench/contrib/notebook/test/browser/notebookEditorModel.test.ts
index caaa043c3774..598b52fe076e 100644
--- a/src/sql/workbench/contrib/notebook/test/browser/notebookEditorModel.test.ts
+++ b/src/sql/workbench/contrib/notebook/test/browser/notebookEditorModel.test.ts
@@ -867,7 +867,7 @@ suite('Notebook Editor Model', function (): void {
 
 	async function createTextEditorModel(self: Mocha.ITestCallbackContext): Promise {
 		let textFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(self, defaultUri.toString()), 'utf8', undefined);
-		(accessor.textFileService.models).add(textFileEditorModel.getResource(), textFileEditorModel);
+		(accessor.textFileService.models).add(textFileEditorModel.resource, textFileEditorModel);
 		await textFileEditorModel.load();
 		return new NotebookEditorModel(defaultUri, textFileEditorModel, mockNotebookService.object, accessor.textFileService, testResourcePropertiesService);
 	}
diff --git a/src/sql/workbench/contrib/notebook/test/electron-browser/contentManagers.test.ts b/src/sql/workbench/contrib/notebook/test/electron-browser/contentManagers.test.ts
index e0011e8d85d1..52d210566a2d 100644
--- a/src/sql/workbench/contrib/notebook/test/electron-browser/contentManagers.test.ts
+++ b/src/sql/workbench/contrib/notebook/test/electron-browser/contentManagers.test.ts
@@ -53,11 +53,11 @@ suite('Local Content Manager', function (): void {
 		const fileService = new class extends TestFileService {
 			async readFile(resource: URI, options?: IReadFileOptions | undefined): Promise {
 				const content = await pfs.readFile(resource.fsPath);
-				return { name: ',', size: 0, etag: '', mtime: 0, value: VSBuffer.fromString(content.toString()), resource };
+				return { name: ',', size: 0, etag: '', mtime: 0, value: VSBuffer.fromString(content.toString()), resource, ctime: 0 };
 			}
 			async writeFile(resource: URI, bufferOrReadable: VSBuffer | VSBufferReadable, options?: IWriteFileOptions): Promise {
 				await pfs.writeFile(resource.fsPath, bufferOrReadable.toString());
-				return { resource: resource, mtime: 0, etag: '', size: 0, name: '', isDirectory: false };
+				return { resource: resource, mtime: 0, etag: '', size: 0, name: '', isDirectory: false, ctime: 0, isFile: true, isSymbolicLink: false };
 			}
 		};
 		instantiationService.set(IFileService, fileService);
diff --git a/src/sql/workbench/contrib/profiler/browser/profilerEditor.ts b/src/sql/workbench/contrib/profiler/browser/profilerEditor.ts
index 9cad6218ba01..129e563a4d48 100644
--- a/src/sql/workbench/contrib/profiler/browser/profilerEditor.ts
+++ b/src/sql/workbench/contrib/profiler/browser/profilerEditor.ts
@@ -20,7 +20,6 @@ import { ProfilerResourceEditor } from 'sql/workbench/contrib/profiler/browser/p
 import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
 import { ITextModel } from 'vs/editor/common/model';
 import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
-import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
 import { URI } from 'vs/base/common/uri';
 import { Schemas } from 'vs/base/common/network';
 import * as nls from 'vs/nls';
@@ -52,6 +51,7 @@ import { CellSelectionModel } from 'sql/base/browser/ui/table/plugins/cellSelect
 import { handleCopyRequest } from 'sql/workbench/contrib/profiler/browser/profilerCopyHandler';
 import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration';
 import { find } from 'vs/base/common/arrays';
+import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput';
 
 class BasicView implements IView {
 	public get element(): HTMLElement {
@@ -120,7 +120,7 @@ export class ProfilerEditor extends BaseEditor {
 
 	private _editor: ProfilerResourceEditor;
 	private _editorModel: ITextModel;
-	private _editorInput: UntitledEditorInput;
+	private _editorInput: UntitledTextEditorInput;
 	private _splitView: SplitView;
 	private _container: HTMLElement;
 	private _body: HTMLElement;
@@ -432,7 +432,7 @@ export class ProfilerEditor extends BaseEditor {
 		editorContainer.className = 'profiler-editor';
 		this._editor.create(editorContainer);
 		this._editor.setVisible(true);
-		this._editorInput = this._instantiationService.createInstance(UntitledEditorInput, URI.from({ scheme: Schemas.untitled }), false, 'sql', '', '');
+		this._editorInput = this._instantiationService.createInstance(UntitledTextEditorInput, URI.from({ scheme: Schemas.untitled }), false, 'sql', '', '');
 		this._editor.setInput(this._editorInput, undefined);
 		this._editorInput.resolve().then(model => this._editorModel = model.textEditorModel);
 		return editorContainer;
diff --git a/src/sql/workbench/contrib/profiler/browser/profilerInput.ts b/src/sql/workbench/contrib/profiler/browser/profilerInput.ts
index 7305dcfb0c61..acce74e513fa 100644
--- a/src/sql/workbench/contrib/profiler/browser/profilerInput.ts
+++ b/src/sql/workbench/contrib/profiler/browser/profilerInput.ts
@@ -11,15 +11,13 @@ import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
 import * as azdata from 'azdata';
 import * as nls from 'vs/nls';
 
-import { EditorInput, ConfirmResult } from 'vs/workbench/common/editor';
+import { EditorInput } from 'vs/workbench/common/editor';
 import { IEditorModel } from 'vs/platform/editor/common/editor';
 import { INotificationService } from 'vs/platform/notification/common/notification';
 import { Event, Emitter } from 'vs/base/common/event';
 import { generateUuid } from 'vs/base/common/uuid';
-import { IDialogService, IShowResult } from 'vs/platform/dialogs/common/dialogs';
 import * as types from 'vs/base/common/types';
 import { URI } from 'vs/base/common/uri';
-import Severity from 'vs/base/common/severity';
 import { FilterData } from 'sql/workbench/services/profiler/browser/profilerFilter';
 import { uriPrefixes } from 'sql/platform/connection/common/utils';
 import { find } from 'vs/base/common/arrays';
@@ -46,8 +44,7 @@ export class ProfilerInput extends EditorInput implements IProfilerSession {
 	constructor(
 		public connection: IConnectionProfile,
 		@IProfilerService private _profilerService: IProfilerService,
-		@INotificationService private _notificationService: INotificationService,
-		@IDialogService private _dialogService: IDialogService
+		@INotificationService private _notificationService: INotificationService
 	) {
 		super();
 		this._state = new ProfilerState();
@@ -282,29 +279,6 @@ export class ProfilerInput extends EditorInput implements IProfilerSession {
 		this.data.clearFilter();
 	}
 
-	confirmSave(): Promise {
-		if (this.state.isRunning || this.state.isPaused) {
-			return this._dialogService.show(Severity.Warning,
-				nls.localize('confirmStopProfilerSession', "Would you like to stop the running XEvent session?"),
-				[
-					nls.localize('profilerClosingActions.yes', "Yes"),
-					nls.localize('profilerClosingActions.no', "No"),
-					nls.localize('profilerClosingActions.cancel', "Cancel")
-				]).then((selection: IShowResult) => {
-					if (selection.choice === 0) {
-						this._profilerService.stopSession(this.id);
-						return ConfirmResult.DONT_SAVE;
-					} else if (selection.choice === 1) {
-						return ConfirmResult.DONT_SAVE;
-					} else {
-						return ConfirmResult.CANCEL;
-					}
-				});
-		} else {
-			return Promise.resolve(ConfirmResult.DONT_SAVE);
-		}
-	}
-
 	isDirty(): boolean {
 		return this.state.isRunning || this.state.isPaused;
 	}
diff --git a/src/sql/workbench/contrib/profiler/browser/profilerResourceEditor.ts b/src/sql/workbench/contrib/profiler/browser/profilerResourceEditor.ts
index a5d4ee024d18..694fab568e0a 100644
--- a/src/sql/workbench/contrib/profiler/browser/profilerResourceEditor.ts
+++ b/src/sql/workbench/contrib/profiler/browser/profilerResourceEditor.ts
@@ -6,7 +6,6 @@
 import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
 import * as nls from 'vs/nls';
 import * as DOM from 'vs/base/browser/dom';
-import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
 import { ResourceEditorModel } from 'vs/workbench/common/editor/resourceEditorModel';
 import * as editorCommon from 'vs/editor/common/editorCommon';
 
@@ -16,13 +15,12 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
 import { IThemeService } from 'vs/platform/theme/common/themeService';
 import { IStorageService } from 'vs/platform/storage/common/storage';
 import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration';
-import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
 import { EditorOptions } from 'vs/workbench/common/editor';
 import { StandaloneCodeEditor } from 'vs/editor/standalone/browser/standaloneCodeEditor';
 import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
 import { CancellationToken } from 'vs/base/common/cancellation';
 import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
-import { IHostService } from 'vs/workbench/services/host/browser/host';
+import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput';
 
 class ProfilerResourceCodeEditor extends StandaloneCodeEditor {
 
@@ -47,13 +45,11 @@ export class ProfilerResourceEditor extends BaseTextEditor {
 		@IStorageService storageService: IStorageService,
 		@ITextResourceConfigurationService configurationService: ITextResourceConfigurationService,
 		@IThemeService themeService: IThemeService,
-		@ITextFileService textFileService: ITextFileService,
 		@IEditorService protected editorService: IEditorService,
-		@IEditorGroupsService editorGroupService: IEditorGroupsService,
-		@IHostService hostService: IHostService
+		@IEditorGroupsService editorGroupService: IEditorGroupsService
 
 	) {
-		super(ProfilerResourceEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, textFileService, editorService, editorGroupService, hostService);
+		super(ProfilerResourceEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, editorService, editorGroupService);
 	}
 
 	public createEditorControl(parent: HTMLElement, configuration: IEditorOptions): editorCommon.IEditor {
@@ -79,7 +75,7 @@ export class ProfilerResourceEditor extends BaseTextEditor {
 		return options;
 	}
 
-	setInput(input: UntitledEditorInput, options: EditorOptions): Promise {
+	setInput(input: UntitledTextEditorInput, options: EditorOptions): Promise {
 		return super.setInput(input, options, CancellationToken.None)
 			.then(() => this.input.resolve()
 				.then(editorModel => editorModel.load())
diff --git a/src/sql/workbench/contrib/query/browser/gridPanel.ts b/src/sql/workbench/contrib/query/browser/gridPanel.ts
index 851255005426..f6357105ea3d 100644
--- a/src/sql/workbench/contrib/query/browser/gridPanel.ts
+++ b/src/sql/workbench/contrib/query/browser/gridPanel.ts
@@ -37,7 +37,6 @@ import { generateUuid } from 'vs/base/common/uuid';
 import { Separator, ActionBar, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
 import { isInDOM, Dimension } from 'vs/base/browser/dom';
 import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
-import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
 import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
 import { IAction } from 'vs/base/common/actions';
 import { ScrollbarVisibility } from 'vs/base/common/scrollable';
@@ -47,6 +46,7 @@ import { IGridDataProvider } from 'sql/platform/query/common/gridDataProvider';
 import { formatDocumentWithSelectedProvider, FormattingMode } from 'vs/editor/contrib/format/format';
 import { CancellationToken } from 'vs/base/common/cancellation';
 import { GridPanelState, GridTableState } from 'sql/workbench/contrib/query/common/gridPanelState';
+import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService';
 
 const ROW_HEIGHT = 29;
 const HEADER_HEIGHT = 26;
@@ -356,7 +356,7 @@ export abstract class GridTableBase extends Disposable implements IView {
 		protected contextMenuService: IContextMenuService,
 		protected instantiationService: IInstantiationService,
 		protected editorService: IEditorService,
-		protected untitledEditorService: IUntitledEditorService,
+		protected untitledEditorService: IUntitledTextEditorService,
 		protected configurationService: IConfigurationService
 	) {
 		super();
@@ -751,7 +751,7 @@ class GridTable extends GridTableBase {
 		@IInstantiationService instantiationService: IInstantiationService,
 		@IContextKeyService private contextKeyService: IContextKeyService,
 		@IEditorService editorService: IEditorService,
-		@IUntitledEditorService untitledEditorService: IUntitledEditorService,
+		@IUntitledTextEditorService untitledEditorService: IUntitledTextEditorService,
 		@IConfigurationService configurationService: IConfigurationService
 	) {
 		super(state, resultSet, contextMenuService, instantiationService, editorService, untitledEditorService, configurationService);
diff --git a/src/sql/workbench/contrib/query/browser/messagePanel.ts b/src/sql/workbench/contrib/query/browser/messagePanel.ts
index 59f96dc1551e..78c922709500 100644
--- a/src/sql/workbench/contrib/query/browser/messagePanel.ts
+++ b/src/sql/workbench/contrib/query/browser/messagePanel.ts
@@ -24,7 +24,6 @@ import { isArray } from 'vs/base/common/types';
 import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
 import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
 import { ScrollbarVisibility } from 'vs/base/common/scrollable';
-import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
 import { $, Dimension, createStyleSheet } from 'vs/base/browser/dom';
 import { QueryEditor } from 'sql/workbench/contrib/query/browser/queryEditor';
 import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
@@ -76,7 +75,6 @@ export class MessagePanel extends Disposable {
 	constructor(
 		@IInstantiationService instantiationService: IInstantiationService,
 		@IThemeService private readonly themeService: IThemeService,
-		@IClipboardService private readonly clipboardService: IClipboardService,
 		@IContextMenuService private readonly contextMenuService: IContextMenuService
 	) {
 		super();
@@ -107,7 +105,7 @@ export class MessagePanel extends Disposable {
 					selection: document.getSelection(),
 					tree: this.tree,
 				};
-				let copyMessageAction = instantiationService.createInstance(CopyMessagesAction, this.clipboardService);
+				let copyMessageAction = instantiationService.createInstance(CopyMessagesAction);
 				copyMessageAction.run(context);
 				event.preventDefault();
 				event.stopPropagation();
@@ -134,8 +132,8 @@ export class MessagePanel extends Disposable {
 				},
 				getActions: () => {
 					return [
-						instantiationService.createInstance(CopyMessagesAction, this.clipboardService),
-						instantiationService.createInstance(CopyAllMessagesAction, this.tree, this.clipboardService)
+						instantiationService.createInstance(CopyMessagesAction),
+						instantiationService.createInstance(CopyAllMessagesAction, this.tree)
 					];
 				},
 				getActionsContext: () => {
diff --git a/src/sql/workbench/contrib/query/browser/query.contribution.ts b/src/sql/workbench/contrib/query/browser/query.contribution.ts
index f1cff45c6ef9..76d6dbf2efca 100644
--- a/src/sql/workbench/contrib/query/browser/query.contribution.ts
+++ b/src/sql/workbench/contrib/query/browser/query.contribution.ts
@@ -8,7 +8,7 @@ import { Registry } from 'vs/platform/registry/common/platform';
 import { EditorDescriptor, IEditorRegistry, Extensions as EditorExtensions } from 'vs/workbench/browser/editor';
 import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
 import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions';
-import { IConfigurationRegistry, Extensions as ConfigExtensions } from 'vs/platform/configuration/common/configurationRegistry';
+import { IConfigurationRegistry, Extensions as ConfigExtensions, IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry';
 import { SyncActionDescriptor, MenuId, MenuRegistry } from 'vs/platform/actions/common/actions';
 import { KeyMod, KeyCode, KeyChord } from 'vs/base/common/keyCodes';
 import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
@@ -39,13 +39,13 @@ import { UntitledQueryEditorInput } from 'sql/workbench/contrib/query/common/unt
 import { ILanguageAssociationRegistry, Extensions as LanguageAssociationExtensions } from 'sql/workbench/common/languageAssociation';
 import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput';
 import { QueryEditorInput } from 'sql/workbench/contrib/query/common/queryEditorInput';
-import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
 import { NewQueryTask, OE_NEW_QUERY_ACTION_ID, DE_NEW_QUERY_COMMAND_ID } from 'sql/workbench/contrib/query/browser/queryActions';
 import { TreeNodeContextKey } from 'sql/workbench/contrib/objectExplorer/common/treeNodeContextKey';
 import { MssqlNodeContext } from 'sql/workbench/contrib/dataExplorer/browser/mssqlNodeContext';
 import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands';
 import { ManageActionContext } from 'sql/workbench/browser/actions';
 import { ItemContextKey } from 'sql/workbench/contrib/dashboard/browser/widgets/explorer/explorerTreeContext';
+import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput';
 
 export const QueryEditorVisibleCondition = ContextKeyExpr.has(queryContext.queryEditorVisibleId);
 export const ResultsGridFocusCondition = ContextKeyExpr.and(ContextKeyExpr.has(queryContext.resultsVisibleId), ContextKeyExpr.has(queryContext.resultsGridFocussedId));
@@ -63,7 +63,7 @@ Registry.as(LanguageAssociationExtensions.Language
 		const queryResultsInput = instantiationService.createInstance(QueryResultsInput, editor.getResource().toString(true));
 		if (editor instanceof FileEditorInput) {
 			return instantiationService.createInstance(FileQueryEditorInput, '', editor, queryResultsInput);
-		} else if (editor instanceof UntitledEditorInput) {
+		} else if (editor instanceof UntitledTextEditorInput) {
 			return instantiationService.createInstance(UntitledQueryEditorInput, '', editor, queryResultsInput);
 		} else {
 			return undefined;
@@ -118,7 +118,7 @@ MenuRegistry.appendMenuItem(MenuId.ExplorerWidgetContext, {
 
 // Query Actions
 actionRegistry.registerWorkbenchAction(
-	new SyncActionDescriptor(
+	SyncActionDescriptor.create(
 		RunQueryKeyboardAction,
 		RunQueryKeyboardAction.ID,
 		RunQueryKeyboardAction.LABEL,
@@ -135,7 +135,7 @@ MenuRegistry.appendMenuItem(MenuId.TouchBarContext, {
 });
 
 actionRegistry.registerWorkbenchAction(
-	new SyncActionDescriptor(
+	SyncActionDescriptor.create(
 		RunCurrentQueryKeyboardAction,
 		RunCurrentQueryKeyboardAction.ID,
 		RunCurrentQueryKeyboardAction.LABEL,
@@ -145,7 +145,7 @@ actionRegistry.registerWorkbenchAction(
 );
 
 actionRegistry.registerWorkbenchAction(
-	new SyncActionDescriptor(
+	SyncActionDescriptor.create(
 		RunCurrentQueryWithActualPlanKeyboardAction,
 		RunCurrentQueryWithActualPlanKeyboardAction.ID,
 		RunCurrentQueryWithActualPlanKeyboardAction.LABEL,
@@ -155,7 +155,7 @@ actionRegistry.registerWorkbenchAction(
 );
 
 actionRegistry.registerWorkbenchAction(
-	new SyncActionDescriptor(
+	SyncActionDescriptor.create(
 		CancelQueryKeyboardAction,
 		CancelQueryKeyboardAction.ID,
 		CancelQueryKeyboardAction.LABEL,
@@ -165,7 +165,7 @@ actionRegistry.registerWorkbenchAction(
 );
 
 actionRegistry.registerWorkbenchAction(
-	new SyncActionDescriptor(
+	SyncActionDescriptor.create(
 		RefreshIntellisenseKeyboardAction,
 		RefreshIntellisenseKeyboardAction.ID,
 		RefreshIntellisenseKeyboardAction.LABEL
@@ -174,7 +174,7 @@ actionRegistry.registerWorkbenchAction(
 );
 
 actionRegistry.registerWorkbenchAction(
-	new SyncActionDescriptor(
+	SyncActionDescriptor.create(
 		FocusOnCurrentQueryKeyboardAction,
 		FocusOnCurrentQueryKeyboardAction.ID,
 		FocusOnCurrentQueryKeyboardAction.LABEL,
@@ -184,7 +184,7 @@ actionRegistry.registerWorkbenchAction(
 );
 
 actionRegistry.registerWorkbenchAction(
-	new SyncActionDescriptor(
+	SyncActionDescriptor.create(
 		ParseSyntaxAction,
 		ParseSyntaxAction.ID,
 		ParseSyntaxAction.LABEL
@@ -195,7 +195,7 @@ actionRegistry.registerWorkbenchAction(
 // Grid actions
 
 actionRegistry.registerWorkbenchAction(
-	new SyncActionDescriptor(
+	SyncActionDescriptor.create(
 		ToggleQueryResultsKeyboardAction,
 		ToggleQueryResultsKeyboardAction.ID,
 		ToggleQueryResultsKeyboardAction.LABEL,
@@ -310,7 +310,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
 });
 
 // Intellisense and other configuration options
-const registryProperties = {
+const registryProperties: { [path: string]: IConfigurationPropertySchema; } = {
 	'sql.saveAsCsv.includeHeaders': {
 		'type': 'boolean',
 		'description': localize('sql.saveAsCsv.includeHeaders', "[Optional] When true, column headers are included when saving results as CSV"),
@@ -322,7 +322,7 @@ const registryProperties = {
 		'default': ','
 	},
 	'sql.saveAsCsv.lineSeperator': {
-		'type': '',
+		'type': 'string',
 		'description': localize('sql.saveAsCsv.lineSeperator', "[Optional] Character(s) used for seperating rows when saving results as CSV"),
 		'default': null
 	},
diff --git a/src/sql/workbench/contrib/query/common/fileQueryEditorInput.ts b/src/sql/workbench/contrib/query/common/fileQueryEditorInput.ts
index 67457a6d0594..fa13c88cfe70 100644
--- a/src/sql/workbench/contrib/query/common/fileQueryEditorInput.ts
+++ b/src/sql/workbench/contrib/query/common/fileQueryEditorInput.ts
@@ -14,6 +14,7 @@ import { EncodingMode } from 'vs/workbench/common/editor';
 import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel';
 import { BinaryEditorModel } from 'vs/workbench/common/editor/binaryEditorModel';
 import { IFileService } from 'vs/platform/files/common/files';
+import { ITextFileSaveOptions } from 'vs/workbench/services/textfile/common/textfiles';
 
 type PublicPart = { [K in keyof T]: T[K] };
 
@@ -81,6 +82,14 @@ export class FileQueryEditorInput extends QueryEditorInput implements PublicPart
 		this.text.setForceOpenAsBinary();
 	}
 
+	save(groupId: number, options?: ITextFileSaveOptions): Promise {
+		return this.text.save(groupId, options);
+	}
+
+	saveAs(group: number, options?: ITextFileSaveOptions): Promise {
+		return this.text.saveAs(group, options);
+	}
+
 	public isResolved(): boolean {
 		return this.text.isResolved();
 	}
diff --git a/src/sql/workbench/contrib/query/common/queryEditorInput.ts b/src/sql/workbench/contrib/query/common/queryEditorInput.ts
index 44b4c2f8e2bb..ff1876180bb5 100644
--- a/src/sql/workbench/contrib/query/common/queryEditorInput.ts
+++ b/src/sql/workbench/contrib/query/common/queryEditorInput.ts
@@ -7,7 +7,7 @@ import { localize } from 'vs/nls';
 import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
 import { Emitter } from 'vs/base/common/event';
 import { URI } from 'vs/base/common/uri';
-import { EditorInput, ConfirmResult } from 'vs/workbench/common/editor';
+import { EditorInput } from 'vs/workbench/common/editor';
 import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
 import { IFileService } from 'vs/platform/files/common/files';
 
@@ -186,9 +186,7 @@ export abstract class QueryEditorInput extends EditorInput implements IConnectab
 	}
 
 	// Forwarding resource functions to the inline sql file editor
-	public save(): Promise { return this._text.save(); }
 	public isDirty(): boolean { return this._text.isDirty(); }
-	public confirmSave(): Promise { return this._text.confirmSave(); }
 	public getResource(): URI { return this._text.getResource(); }
 
 	public matchInputInstanceType(inputType: any): boolean {
@@ -295,15 +293,6 @@ export abstract class QueryEditorInput extends EditorInput implements IConnectab
 		this.state.executing = false;
 	}
 
-	public close(): void {
-		this.queryModelService.disposeQuery(this.uri);
-		this.connectionManagementService.disconnectEditor(this, true);
-
-		this._text.close();
-		this._results.close();
-		super.close();
-	}
-
 	/**
 	 * Get the color that should be displayed
 	 */
@@ -311,6 +300,13 @@ export abstract class QueryEditorInput extends EditorInput implements IConnectab
 		return this.connectionManagementService.getTabColorForUri(this.uri);
 	}
 
+	public dispose() {
+		this.queryModelService.disposeQuery(this.uri);
+		this.connectionManagementService.disconnectEditor(this, true);
+
+		super.dispose();
+	}
+
 	public get isSharedSession(): boolean {
 		return !!(this.uri && startsWith(this.uri, 'vsls:'));
 	}
diff --git a/src/sql/workbench/contrib/query/common/queryInputFactory.ts b/src/sql/workbench/contrib/query/common/queryInputFactory.ts
index e8790d78c6ba..48fcff22d406 100644
--- a/src/sql/workbench/contrib/query/common/queryInputFactory.ts
+++ b/src/sql/workbench/contrib/query/common/queryInputFactory.ts
@@ -6,12 +6,12 @@
 import { IEditorInputFactory, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions } from 'vs/workbench/common/editor';
 import { Registry } from 'vs/platform/registry/common/platform';
 import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
-import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
 import { QueryResultsInput } from 'sql/workbench/contrib/query/common/queryResultsInput';
 import { FILE_EDITOR_INPUT_ID } from 'vs/workbench/contrib/files/common/files';
 import { UntitledQueryEditorInput } from 'sql/workbench/contrib/query/common/untitledQueryEditorInput';
 import { FileQueryEditorInput } from 'sql/workbench/contrib/query/common/fileQueryEditorInput';
 import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput';
+import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput';
 
 const editorInputFactoryRegistry = Registry.as(EditorInputExtensions.EditorInputFactories);
 
@@ -34,7 +34,7 @@ export class FileQueryEditorInputFactory implements IEditorInputFactory {
 
 export class UntitledQueryEditorInputFactory implements IEditorInputFactory {
 	serialize(editorInput: UntitledQueryEditorInput): string {
-		const factory = editorInputFactoryRegistry.getEditorInputFactory(UntitledEditorInput.ID);
+		const factory = editorInputFactoryRegistry.getEditorInputFactory(UntitledTextEditorInput.ID);
 		if (factory) {
 			return factory.serialize(editorInput.text); // serialize based on the underlying input
 		}
@@ -42,8 +42,8 @@ export class UntitledQueryEditorInputFactory implements IEditorInputFactory {
 	}
 
 	deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): UntitledQueryEditorInput | undefined {
-		const factory = editorInputFactoryRegistry.getEditorInputFactory(UntitledEditorInput.ID);
-		const untitledEditorInput = factory.deserialize(instantiationService, serializedEditorInput) as UntitledEditorInput;
+		const factory = editorInputFactoryRegistry.getEditorInputFactory(UntitledTextEditorInput.ID);
+		const untitledEditorInput = factory.deserialize(instantiationService, serializedEditorInput) as UntitledTextEditorInput;
 		const queryResultsInput = instantiationService.createInstance(QueryResultsInput, untitledEditorInput.getResource().toString());
 		return instantiationService.createInstance(UntitledQueryEditorInput, '', untitledEditorInput, queryResultsInput);
 	}
diff --git a/src/sql/workbench/contrib/query/common/queryResultsInput.ts b/src/sql/workbench/contrib/query/common/queryResultsInput.ts
index 48f5fb3a093e..5fc16c4c4578 100644
--- a/src/sql/workbench/contrib/query/common/queryResultsInput.ts
+++ b/src/sql/workbench/contrib/query/common/queryResultsInput.ts
@@ -52,12 +52,6 @@ export class QueryResultsInput extends EditorInput {
 		super();
 	}
 
-	close() {
-		this.state!.dispose();
-		this._state = undefined;
-		super.close();
-	}
-
 	getTypeId(): string {
 		return QueryResultsInput.ID;
 	}
diff --git a/src/sql/workbench/contrib/query/common/untitledQueryEditorInput.ts b/src/sql/workbench/contrib/query/common/untitledQueryEditorInput.ts
index 361683453e07..7ff7ab8fd5ff 100644
--- a/src/sql/workbench/contrib/query/common/untitledQueryEditorInput.ts
+++ b/src/sql/workbench/contrib/query/common/untitledQueryEditorInput.ts
@@ -9,15 +9,16 @@ import { IConnectionManagementService } from 'sql/platform/connection/common/con
 import { IQueryModelService } from 'sql/platform/query/common/queryModel';
 
 import { IEncodingSupport, EncodingMode } from 'vs/workbench/common/editor';
-import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
 import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
-import { UntitledEditorModel } from 'vs/workbench/common/editor/untitledEditorModel';
 import { IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService';
 import { IFileService } from 'vs/platform/files/common/files';
+import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput';
+import { UntitledTextEditorModel } from 'vs/workbench/common/editor/untitledTextEditorModel';
+import { ITextFileSaveOptions } from 'vs/workbench/services/textfile/common/textfiles';
 
 type PublicPart = { [K in keyof T]: T[K] };
 
-export class UntitledQueryEditorInput extends QueryEditorInput implements IEncodingSupport, PublicPart {
+export class UntitledQueryEditorInput extends QueryEditorInput implements IEncodingSupport, PublicPart {
 
 	public static readonly ID = 'workbench.editorInput.untitledQueryInput';
 
@@ -26,7 +27,7 @@ export class UntitledQueryEditorInput extends QueryEditorInput implements IEncod
 
 	constructor(
 		description: string,
-		text: UntitledEditorInput,
+		text: UntitledTextEditorInput,
 		results: QueryResultsInput,
 		@IConnectionManagementService connectionManagementService: IConnectionManagementService,
 		@IQueryModelService queryModelService: IQueryModelService,
@@ -36,12 +37,12 @@ export class UntitledQueryEditorInput extends QueryEditorInput implements IEncod
 		super(description, text, results, connectionManagementService, queryModelService, configurationService, fileService);
 	}
 
-	public resolve(): Promise {
+	public resolve(): Promise {
 		return this.text.resolve();
 	}
 
-	public get text(): UntitledEditorInput {
-		return this._text as UntitledEditorInput;
+	public get text(): UntitledTextEditorInput {
+		return this._text as UntitledTextEditorInput;
 	}
 
 	public get hasAssociatedFilePath(): boolean {
@@ -72,6 +73,19 @@ export class UntitledQueryEditorInput extends QueryEditorInput implements IEncod
 		this.text.setEncoding(encoding, mode);
 	}
 
+	save(groupId: number, options?: ITextFileSaveOptions): Promise {
+		return this.text.save(groupId, options);
+	}
+
+	saveAs(group: number, options?: ITextFileSaveOptions): Promise {
+		return this.text.saveAs(group, options);
+	}
+
+	isUntitled(): boolean {
+		// Subclasses need to explicitly opt-in to being untitled.
+		return true;
+	}
+
 	hasBackup(): boolean {
 		if (this.text) {
 			return this.text.hasBackup();
diff --git a/src/sql/workbench/contrib/query/test/browser/queryActions.test.ts b/src/sql/workbench/contrib/query/test/browser/queryActions.test.ts
index 75ac627e215f..f4a1ba74a3d7 100644
--- a/src/sql/workbench/contrib/query/test/browser/queryActions.test.ts
+++ b/src/sql/workbench/contrib/query/test/browser/queryActions.test.ts
@@ -28,11 +28,12 @@ import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKe
 import { UntitledQueryEditorInput } from 'sql/workbench/contrib/query/common/untitledQueryEditorInput';
 import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService';
 import { TestQueryModelService } from 'sql/platform/query/test/common/testQueryModelService';
-import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
 import { URI } from 'vs/base/common/uri';
 import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
 import { TestConnectionManagementService } from 'sql/platform/connection/test/common/testConnectionManagementService';
 import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
+import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput';
+import { SimpleUriLabelService } from 'vs/editor/standalone/browser/simpleServices';
 
 suite('SQL QueryAction Tests', () => {
 
@@ -69,7 +70,7 @@ suite('SQL QueryAction Tests', () => {
 		connectionManagementService = TypeMoq.Mock.ofType(TestConnectionManagementService);
 		connectionManagementService.setup(q => q.onDisconnect).returns(() => Event.None);
 		const instantiationService = new TestInstantiationService();
-		let fileInput = new UntitledEditorInput(URI.parse('file://testUri'), false, '', '', '', instantiationService, undefined, undefined);
+		let fileInput = new UntitledTextEditorInput(URI.parse('file://testUri'), false, '', '', '', instantiationService, undefined, new SimpleUriLabelService(), undefined, undefined, undefined);
 		// Setup a reusable mock QueryInput
 		testQueryInput = TypeMoq.Mock.ofType(UntitledQueryEditorInput, TypeMoq.MockBehavior.Strict, undefined, fileInput, undefined, connectionManagementService.object, queryModelService.object, configurationService.object);
 		testQueryInput.setup(x => x.uri).returns(() => testUri);
@@ -177,7 +178,7 @@ suite('SQL QueryAction Tests', () => {
 		queryModelService.setup(x => x.onRunQueryStart).returns(() => Event.None);
 		queryModelService.setup(x => x.onRunQueryComplete).returns(() => Event.None);
 		const instantiationService = new TestInstantiationService();
-		let fileInput = new UntitledEditorInput(URI.parse('file://testUri'), false, '', '', '', instantiationService, undefined, undefined);
+		let fileInput = new UntitledTextEditorInput(URI.parse('file://testUri'), false, '', '', '', instantiationService, undefined, new SimpleUriLabelService(), undefined, undefined, undefined);
 
 		// ... Mock "isSelectionEmpty" in QueryEditor
 		let queryInput = TypeMoq.Mock.ofType(UntitledQueryEditorInput, TypeMoq.MockBehavior.Strict, undefined, fileInput, undefined, connectionManagementService.object, queryModelService.object, configurationService.object);
@@ -227,7 +228,7 @@ suite('SQL QueryAction Tests', () => {
 
 		// ... Mock "getSelection" in QueryEditor
 		const instantiationService = new TestInstantiationService();
-		let fileInput = new UntitledEditorInput(URI.parse('file://testUri'), false, '', '', '', instantiationService, undefined, undefined);
+		let fileInput = new UntitledTextEditorInput(URI.parse('file://testUri'), false, '', '', '', instantiationService, undefined, new SimpleUriLabelService(), undefined, undefined, undefined);
 
 		let queryInput = TypeMoq.Mock.ofType(UntitledQueryEditorInput, TypeMoq.MockBehavior.Loose, undefined, fileInput, undefined, connectionManagementService.object, queryModelService.object, configurationService.object);
 		queryInput.setup(x => x.uri).returns(() => testUri);
diff --git a/src/sql/workbench/contrib/query/test/browser/queryEditor.test.ts b/src/sql/workbench/contrib/query/test/browser/queryEditor.test.ts
index ca88ff41a15b..2e9c9f6236de 100644
--- a/src/sql/workbench/contrib/query/test/browser/queryEditor.test.ts
+++ b/src/sql/workbench/contrib/query/test/browser/queryEditor.test.ts
@@ -7,7 +7,6 @@ import { InstantiationService } from 'vs/platform/instantiation/common/instantia
 import { IEditorDescriptor } from 'vs/workbench/browser/editor';
 import { URI } from 'vs/base/common/uri';
 import { Memento } from 'vs/workbench/common/memento';
-import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
 
 import { QueryResultsInput } from 'sql/workbench/contrib/query/common/queryResultsInput';
 import { INewConnectionParams, ConnectionType, RunQueryOnConnectionMode } from 'sql/platform/connection/common/connectionManagement';
@@ -21,9 +20,11 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
 import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
 import { TestStorageService } from 'vs/workbench/test/workbenchTestServices';
 import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
+import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput';
 import { UntitledQueryEditorInput } from 'sql/workbench/contrib/query/common/untitledQueryEditorInput';
 import { TestQueryModelService } from 'sql/platform/query/test/common/testQueryModelService';
 import { Event } from 'vs/base/common/event';
+import { SimpleUriLabelService } from 'vs/editor/standalone/browser/simpleServices';
 
 suite('SQL QueryEditor Tests', () => {
 	let instantiationService: TypeMoq.Mock;
@@ -284,7 +285,7 @@ suite('SQL QueryEditor Tests', () => {
 					return new RunQueryAction(undefined, undefined, undefined);
 				});
 
-			let fileInput = new UntitledEditorInput(URI.parse('file://testUri'), false, '', '', '', instantiationService.object, undefined, undefined);
+			let fileInput = new UntitledTextEditorInput(URI.parse('file://testUri'), false, '', '', '', instantiationService.object, undefined, new SimpleUriLabelService(), undefined, undefined, undefined);
 			queryModelService = TypeMoq.Mock.ofType(TestQueryModelService, TypeMoq.MockBehavior.Strict);
 			queryModelService.setup(x => x.disposeQuery(TypeMoq.It.isAny()));
 			queryModelService.setup(x => x.onRunQueryComplete).returns(() => Event.None);
@@ -321,7 +322,7 @@ suite('SQL QueryEditor Tests', () => {
 		test('Test that we attempt to dispose query when the queryInput is disposed', () => {
 			let queryResultsInput = new QueryResultsInput('testUri');
 			queryInput['_results'] = queryResultsInput;
-			queryInput.close();
+			queryInput.dispose();
 			queryModelService.verify(x => x.disposeQuery(TypeMoq.It.isAnyString()), TypeMoq.Times.once());
 		});
 	});
diff --git a/src/sql/workbench/contrib/queryHistory/browser/queryHistoryActions.ts b/src/sql/workbench/contrib/queryHistory/browser/queryHistoryActions.ts
index b54d7165bcb9..eedac0f22275 100644
--- a/src/sql/workbench/contrib/queryHistory/browser/queryHistoryActions.ts
+++ b/src/sql/workbench/contrib/queryHistory/browser/queryHistoryActions.ts
@@ -75,14 +75,14 @@ export class OpenQueryAction extends Action {
 	constructor(
 		id: string,
 		label: string,
-		@IInstantiationService private _instantiationService
+		@IInstantiationService private _instantiationService: IInstantiationService
 	) {
 		super(id, label);
 	}
 
 	public async run(element: QueryHistoryNode): Promise {
 		if (element instanceof QueryHistoryNode && element.info) {
-			return this._instantiationService.invokeFunction(openNewQuery, element.info.connectionProfile, element.info.queryText, RunQueryOnConnectionMode.none).then(() => true, () => false);
+			return this._instantiationService.invokeFunction(openNewQuery, element.info.connectionProfile, element.info.queryText, RunQueryOnConnectionMode.none).then();
 		}
 	}
 }
@@ -94,14 +94,14 @@ export class RunQueryAction extends Action {
 	constructor(
 		id: string,
 		label: string,
-		@IInstantiationService private _instantiationService
+		@IInstantiationService private _instantiationService: IInstantiationService
 	) {
 		super(id, label);
 	}
 
 	public async run(element: QueryHistoryNode): Promise {
 		if (element instanceof QueryHistoryNode && element.info) {
-			return this._instantiationService.invokeFunction(openNewQuery, element.info.connectionProfile, element.info.queryText, RunQueryOnConnectionMode.executeQuery).catch(() => true, () => false);
+			return this._instantiationService.invokeFunction(openNewQuery, element.info.connectionProfile, element.info.queryText, RunQueryOnConnectionMode.executeQuery).then();
 		}
 	}
 }
diff --git a/src/sql/workbench/contrib/queryHistory/electron-browser/queryHistory.ts b/src/sql/workbench/contrib/queryHistory/electron-browser/queryHistory.ts
index 02423b794e6a..b941509d928c 100644
--- a/src/sql/workbench/contrib/queryHistory/electron-browser/queryHistory.ts
+++ b/src/sql/workbench/contrib/queryHistory/electron-browser/queryHistory.ts
@@ -73,7 +73,7 @@ export class QueryHistoryWorkbenchContribution implements IWorkbenchContribution
 
 					const registry = Registry.as(ActionExtensions.WorkbenchActions);
 					registry.registerWorkbenchAction(
-						new SyncActionDescriptor(
+						SyncActionDescriptor.create(
 							ToggleQueryHistoryAction,
 							ToggleQueryHistoryAction.ID,
 							ToggleQueryHistoryAction.LABEL,
@@ -83,7 +83,7 @@ export class QueryHistoryWorkbenchContribution implements IWorkbenchContribution
 					);
 
 					// Register Output Panel
-					Registry.as(PanelExtensions.Panels).registerPanel(new PanelDescriptor(
+					Registry.as(PanelExtensions.Panels).registerPanel(PanelDescriptor.create(
 						QueryHistoryPanel,
 						QUERY_HISTORY_PANEL_ID,
 						localize('queryHistory', "Query History"),
diff --git a/src/sql/workbench/contrib/queryPlan/common/queryPlanInput.ts b/src/sql/workbench/contrib/queryPlan/common/queryPlanInput.ts
index 62a52db921e8..6334a29c52db 100644
--- a/src/sql/workbench/contrib/queryPlan/common/queryPlanInput.ts
+++ b/src/sql/workbench/contrib/queryPlan/common/queryPlanInput.ts
@@ -4,10 +4,10 @@
  *--------------------------------------------------------------------------------------------*/
 
 import { EditorInput, EditorModel } from 'vs/workbench/common/editor';
-import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
 import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
 import { IFileService } from 'vs/platform/files/common/files';
 import { URI } from 'vs/base/common/uri';
+import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput';
 
 export class QueryPlanInput extends EditorInput {
 
@@ -29,7 +29,7 @@ export class QueryPlanInput extends EditorInput {
 	}
 
 	public getTypeId(): string {
-		return UntitledEditorInput.ID;
+		return UntitledTextEditorInput.ID;
 	}
 
 	public getName(): string {
diff --git a/src/sql/workbench/contrib/tasks/browser/tasks.contribution.ts b/src/sql/workbench/contrib/tasks/browser/tasks.contribution.ts
index 300476a1665f..12b013deac5a 100644
--- a/src/sql/workbench/contrib/tasks/browser/tasks.contribution.ts
+++ b/src/sql/workbench/contrib/tasks/browser/tasks.contribution.ts
@@ -61,7 +61,7 @@ export class StatusUpdater extends lifecycle.Disposable implements ext.IWorkbenc
 
 const registry = Registry.as(ActionExtensions.WorkbenchActions);
 registry.registerWorkbenchAction(
-	new SyncActionDescriptor(
+	SyncActionDescriptor.create(
 		ToggleTasksAction,
 		ToggleTasksAction.ID,
 		ToggleTasksAction.LABEL,
@@ -71,7 +71,7 @@ registry.registerWorkbenchAction(
 );
 
 // Register Output Panel
-Registry.as(PanelExtensions.Panels).registerPanel(new PanelDescriptor(
+Registry.as(PanelExtensions.Panels).registerPanel(PanelDescriptor.create(
 	TasksPanel,
 	TASKS_PANEL_ID,
 	localize('tasks', "Tasks"),
diff --git a/src/sql/workbench/services/dialog/browser/dialogPane.ts b/src/sql/workbench/services/dialog/browser/dialogPane.ts
index dc3ce1cd33f0..879579ed0b1d 100644
--- a/src/sql/workbench/services/dialog/browser/dialogPane.ts
+++ b/src/sql/workbench/services/dialog/browser/dialogPane.ts
@@ -15,12 +15,12 @@ import { DialogModule } from 'sql/workbench/services/dialog/browser/dialog.modul
 import { DialogComponentParams, LayoutRequestParams } from 'sql/workbench/services/dialog/browser/dialogContainer.component';
 
 import * as DOM from 'vs/base/browser/dom';
-import { IThemable } from 'vs/platform/theme/common/styler';
 import { Disposable } from 'vs/base/common/lifecycle';
 import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
 import { Emitter } from 'vs/base/common/event';
 import { attachTabbedPanelStyler } from 'sql/platform/theme/common/styler';
 import { IThemeService } from 'vs/platform/theme/common/themeService';
+import { IThemable } from 'vs/base/common/styler';
 
 export class DialogPane extends Disposable implements IThemable {
 	private _tabbedPanel: TabbedPanel;
diff --git a/src/sql/workbench/services/insights/browser/insightsDialogView.ts b/src/sql/workbench/services/insights/browser/insightsDialogView.ts
index 97023ce9252f..67d1bf79fdd7 100644
--- a/src/sql/workbench/services/insights/browser/insightsDialogView.ts
+++ b/src/sql/workbench/services/insights/browser/insightsDialogView.ts
@@ -33,7 +33,6 @@ import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
 import { ICommandService } from 'vs/platform/commands/common/commands';
 import { MenuRegistry, ExecuteCommandAction } from 'vs/platform/actions/common/actions';
 import { SplitView, Orientation, Sizing } from 'vs/base/browser/ui/splitview/splitview';
-import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet';
 import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
 import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
 import { ILogService } from 'vs/platform/log/common/log';
@@ -42,13 +41,14 @@ import { IInsightsConfigDetails } from 'sql/platform/dashboard/browser/insightRe
 import { TaskRegistry } from 'sql/platform/tasks/browser/tasksRegistry';
 import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration';
 import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry';
+import { ViewletPane, IViewletPaneOptions } from 'vs/workbench/browser/parts/views/paneViewlet';
 import { onUnexpectedError } from 'vs/base/common/errors';
 
 const labelDisplay = nls.localize("insights.item", "Item");
 const valueDisplay = nls.localize("insights.value", "Value");
 const iconClass = 'codicon';
 
-class InsightTableView extends ViewletPanel {
+class InsightTableView extends ViewletPane {
 	private _table: Table;
 	public get table(): Table {
 		return this._table;
@@ -58,7 +58,7 @@ class InsightTableView extends ViewletPanel {
 		private columns: Slick.Column[],
 		private data: IDisposableDataProvider | Array,
 		private tableOptions: Slick.GridOptions,
-		options: IViewletPanelOptions,
+		options: IViewletPaneOptions,
 		@IKeybindingService keybindingService: IKeybindingService,
 		@IContextMenuService contextMenuService: IContextMenuService,
 		@IConfigurationService configurationService: IConfigurationService,
@@ -401,7 +401,7 @@ export class InsightsDialogView extends Modal {
 			let task = tasks.some(x => x === action);
 			let commandAction = MenuRegistry.getCommand(action);
 			if (task) {
-				returnActions.push(this._instantiationService.createInstance(ExecuteCommandAction, commandAction.id, commandAction.title));
+				returnActions.push(this._instantiationService.createInstance(ExecuteCommandAction, commandAction.id as string, commandAction.title as string));
 			}
 		}
 		return returnActions;
diff --git a/src/sql/workbench/services/insights/test/electron-browser/insightsUtils.test.ts b/src/sql/workbench/services/insights/test/electron-browser/insightsUtils.test.ts
index 86a324ebde61..6141c9d1ab77 100644
--- a/src/sql/workbench/services/insights/test/electron-browser/insightsUtils.test.ts
+++ b/src/sql/workbench/services/insights/test/electron-browser/insightsUtils.test.ts
@@ -25,6 +25,7 @@ import { getRandomTestPath } from 'vs/base/test/node/testUtils';
 import { IWorkbenchConstructionOptions } from 'vs/workbench/workbench.web.api';
 
 class TestEnvironmentService implements IWorkbenchEnvironmentService {
+	keybindingsSyncPreviewResource: URI;
 	argvResource: URI;
 	userDataSyncLogResource: URI;
 	settingsSyncPreviewResource: URI;
diff --git a/src/sql/workbench/services/queryEditor/browser/queryEditorService.ts b/src/sql/workbench/services/queryEditor/browser/queryEditorService.ts
index 7abb7c726b13..9384208d394d 100644
--- a/src/sql/workbench/services/queryEditor/browser/queryEditorService.ts
+++ b/src/sql/workbench/services/queryEditor/browser/queryEditorService.ts
@@ -10,7 +10,6 @@ import { IConnectableInput, IConnectionManagementService } from 'sql/platform/co
 import { IQueryEditorService } from 'sql/workbench/services/queryEditor/common/queryEditorService';
 import { UntitledQueryEditorInput } from 'sql/workbench/contrib/query/common/untitledQueryEditorInput';
 
-import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
 import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
 import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
 import { URI } from 'vs/base/common/uri';
@@ -21,6 +20,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
 import { replaceConnection } from 'sql/workbench/browser/taskUtilities';
 import { EditDataResultsInput } from 'sql/workbench/contrib/editData/browser/editDataResultsInput';
 import { ILogService } from 'vs/platform/log/common/log';
+import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService';
 
 /**
  * Service wrapper for opening and creating SQL documents as sql editor inputs
@@ -30,7 +30,7 @@ export class QueryEditorService implements IQueryEditorService {
 	public _serviceBrand: undefined;
 
 	constructor(
-		@IUntitledEditorService private _untitledEditorService: IUntitledEditorService,
+		@IUntitledTextEditorService private _untitledEditorService: IUntitledTextEditorService,
 		@IInstantiationService private _instantiationService: IInstantiationService,
 		@IEditorService private _editorService: IEditorService,
 		@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
diff --git a/src/sql/workbench/update/electron-browser/releaseNotes.contribution.ts b/src/sql/workbench/update/electron-browser/releaseNotes.contribution.ts
index 1cea49cb683a..1b804af8768d 100644
--- a/src/sql/workbench/update/electron-browser/releaseNotes.contribution.ts
+++ b/src/sql/workbench/update/electron-browser/releaseNotes.contribution.ts
@@ -10,4 +10,4 @@ import { Registry } from 'vs/platform/registry/common/platform';
 
 // add product update and release notes contributions
 Registry.as(ActionExtensions.WorkbenchActions)
-	.registerWorkbenchAction(new SyncActionDescriptor(ShowCurrentReleaseNotesAction, ShowCurrentReleaseNotesAction.ID, ShowCurrentReleaseNotesAction.LABEL), 'Show Getting Started');
+	.registerWorkbenchAction(SyncActionDescriptor.create(ShowCurrentReleaseNotesAction, ShowCurrentReleaseNotesAction.ID, ShowCurrentReleaseNotesAction.LABEL), 'Show Getting Started');
diff --git a/src/tsconfig.base.json b/src/tsconfig.base.json
index e67b902c3c54..6d87bff67dde 100644
--- a/src/tsconfig.base.json
+++ b/src/tsconfig.base.json
@@ -10,6 +10,7 @@
 		"alwaysStrict": true,
 		"strictBindCallApply": true,
 		"strictNullChecks": false,
+		"strictPropertyInitialization": false,
 		"forceConsistentCasingInFileNames": true,
 		"baseUrl": ".",
 		"paths": {
diff --git a/src/tsconfig.monaco.json b/src/tsconfig.monaco.json
index aa0af48b342b..a6430a44ccb8 100644
--- a/src/tsconfig.monaco.json
+++ b/src/tsconfig.monaco.json
@@ -14,7 +14,6 @@
 	},
 	"include": [
 		"typings/require.d.ts",
-		"typings/require-monaco.d.ts",
 		"typings/thenable.d.ts",
 		"typings/es6-promise.d.ts",
 		"typings/lib.es2018.promise.d.ts",
diff --git a/src/typings/applicationInsights.d.ts b/src/typings/applicationInsights.d.ts
deleted file mode 100644
index 5c6cb717a288..000000000000
--- a/src/typings/applicationInsights.d.ts
+++ /dev/null
@@ -1,218 +0,0 @@
-/**
- * The singleton meta class for the default client of the client. This class is used to setup/start and configure
- * the auto-collection behavior of the application insights module.
- */
-declare module ApplicationInsights {
-
-    /**
-    * The default client, initialized when setup was called. To initialize a different client
-    * with its own configuration, use `new TelemetryClient(instrumentationKey?)`.
-    */
-    var defaultClient: TelemetryClient;
-    /**
-     * Initializes the default client. Should be called after setting
-     * configuration options.
-     *
-     * @param instrumentationKey the instrumentation key to use. Optional, if
-     * this is not specified, the value will be read from the environment
-     * variable APPINSIGHTS_INSTRUMENTATIONKEY.
-     * @returns {Configuration} the configuration class to initialize
-     * and start the SDK.
-     */
-    function setup(instrumentationKey?: string): typeof Configuration;
-    /**
-     * Starts automatic collection of telemetry. Prior to calling start no
-     * telemetry will be *automatically* collected, though manual collection
-     * is enabled.
-     * @returns {ApplicationInsights} this class
-     */
-    function start(): typeof Configuration;
-    /**
-     * The active configuration for global SDK behaviors, such as autocollection.
-     */
-    class Configuration {
-        static start: typeof start;
-        /**
-         * Sets the state of console and logger tracking (enabled by default for third-party loggers only)
-         * @param value if true logger activity will be sent to Application Insights
-         * @param collectConsoleLog if true, logger autocollection will include console.log calls (default false)
-         * @returns {Configuration} this class
-         */
-        static setAutoCollectConsole(value: boolean, collectConsoleLog?: boolean): typeof Configuration;
-        /**
-         * Sets the state of exception tracking (enabled by default)
-         * @param value if true uncaught exceptions will be sent to Application Insights
-         * @returns {Configuration} this class
-         */
-        static setAutoCollectExceptions(value: boolean): typeof Configuration;
-        /**
-         * Sets the state of performance tracking (enabled by default)
-         * @param value if true performance counters will be collected every second and sent to Application Insights
-         * @returns {Configuration} this class
-         */
-        static setAutoCollectPerformance(value: boolean): typeof Configuration;
-        /**
-         * Sets the state of request tracking (enabled by default)
-         * @param value if true requests will be sent to Application Insights
-         * @returns {Configuration} this class
-         */
-        static setAutoCollectRequests(value: boolean): typeof Configuration;
-        /**
-         * Sets the state of dependency tracking (enabled by default)
-         * @param value if true dependencies will be sent to Application Insights
-         * @returns {Configuration} this class
-         */
-        static setAutoCollectDependencies(value: boolean): typeof Configuration;
-        /**
-         * Sets the state of automatic dependency correlation (enabled by default)
-         * @param value if true dependencies will be correlated with requests
-         * @returns {Configuration} this class
-         */
-        static setAutoDependencyCorrelation(value: boolean): typeof Configuration;
-        /**
-         * Enable or disable disk-backed retry caching to cache events when client is offline (enabled by default)
-         * Note that this method only applies to the default client. Disk-backed retry caching is disabled by default for additional clients.
-         * For enable for additional clients, use client.channel.setUseDiskRetryCaching(true).
-         * These cached events are stored in your system or user's temporary directory and access restricted to your user when possible.
-         * @param value if true events that occured while client is offline will be cached on disk
-         * @param resendInterval The wait interval for resending cached events.
-         * @param maxBytesOnDisk The maximum size (in bytes) that the created temporary directory for cache events can grow to, before caching is disabled.
-         * @returns {Configuration} this class
-         */
-        static setUseDiskRetryCaching(value: boolean, resendInterval?: number, maxBytesOnDisk?: number): typeof Configuration;
-        /**
-         * Enables debug and warning logging for AppInsights itself.
-         * @param enableDebugLogging if true, enables debug logging
-         * @param enableWarningLogging if true, enables warning logging
-         * @returns {Configuration} this class
-         */
-        static setInternalLogging(enableDebugLogging?: boolean, enableWarningLogging?: boolean): typeof Configuration;
-    }
-    /**
-     * Disposes the default client and all the auto collectors so they can be reinitialized with different configuration
-    */
-    function dispose(): void;
-
-    interface ITelemetryClient {
-        config: Config;
-        channel: Channel;
-        /**
-         * Log a user action or other occurrence.
-         * @param telemetry      Object encapsulating tracking options
-         */
-        trackEvent(telemetry: EventTelemetry): void;
-        /**
-         * Immediately send all queued telemetry.
-         * @param options Flush options, including indicator whether app is crashing and callback
-         */
-        flush(options?: FlushOptions): void;
-
-    }
-
-    class TelemetryClient implements ITelemetryClient {
-        config: Config;
-        channel: Channel;
-        /**
-         * Constructs a new client of the client
-         * @param iKey the instrumentation key to use (read from environment variable if not specified)
-         */
-        constructor(iKey?: string);
-        /**
-         * Log a user action or other occurrence.
-         * @param telemetry      Object encapsulating tracking options
-         */
-        trackEvent(telemetry: EventTelemetry): void;
-        /**
-         * Immediately send all queued telemetry.
-         * @param options Flush options, including indicator whether app is crashing and callback
-         */
-        flush(options?: FlushOptions): void;
-
-    }
-
-    class Config {
-        static ENV_azurePrefix: string;
-        static ENV_iKey: string;
-        static legacy_ENV_iKey: string;
-        static ENV_profileQueryEndpoint: string;
-        static ENV_http_proxy: string;
-        static ENV_https_proxy: string;
-        /** An identifier for your Application Insights resource */
-        instrumentationKey: string;
-        /** The id for cross-component correlation. READ ONLY. */
-        correlationId: string;
-        /** The ingestion endpoint to send telemetry payloads to */
-        endpointUrl: string;
-        /** The maximum number of telemetry items to include in a payload to the ingestion endpoint (Default 250) */
-        maxBatchSize: number;
-        /** The maximum amount of time to wait for a payload to reach maxBatchSize (Default 15000) */
-        maxBatchIntervalMs: number;
-        /** A flag indicating if telemetry transmission is disabled (Default false) */
-        disableAppInsights: boolean;
-        /** The percentage of telemetry items tracked that should be transmitted (Default 100) */
-        samplingPercentage: number;
-        /** The time to wait before retrying to retrieve the id for cross-component correlation (Default 30000) */
-        correlationIdRetryIntervalMs: number;
-        /** A list of domains to exclude from cross-component header injection */
-        correlationHeaderExcludedDomains: string[];
-        /** A proxy server for SDK HTTP traffic (Optional, Default pulled from `http_proxy` environment variable) */
-        proxyHttpUrl: string;
-        /** A proxy server for SDK HTTPS traffic (Optional, Default pulled from `https_proxy` environment variable) */
-        proxyHttpsUrl: string;
-    }
-
-    interface Channel {
-        /**
-    * Enable or disable disk-backed retry caching to cache events when client is offline (enabled by default)
-    * These cached events are stored in your system or user's temporary directory and access restricted to your user when possible.
-    * @param value if true events that occured while client is offline will be cached on disk
-    * @param resendInterval The wait interval for resending cached events.
-    * @param maxBytesOnDisk The maximum size (in bytes) that the created temporary directory for cache events can grow to, before caching is disabled.
-    * @returns {Configuration} this class
-    */
-        setUseDiskRetryCaching(value: boolean, resendInterval?: number, maxBytesOnDisk?: number): void;
-    }
-
-    /**
-     * Telemetry about the custom event of interest, such application workflow event, business logic event (purchase) and anything that
-     * you would like to track and aggregate by count. Event can contain measurements such as purchase amount associated with purchase event
-     */
-    interface EventTelemetry {
-        /**
-         * Name of the event
-         */
-        name: string;
-        /**
-         * Metrics associated with this event, displayed in Metrics Explorer on the portal.
-         */
-        measurements?: {
-            [key: string]: number;
-        };
-        /**
-         * Additional data used to filter events and metrics in the portal. Defaults to empty.
-         */
-        properties?: {
-            [key: string]: string;
-        };
-    }
-
-    /**
-     * Encapsulates options passed into client.flush() function
-     */
-    interface FlushOptions {
-        /**
-         * Flag indicating whether application is crashing. When this flag is set to true
-         * and storing data locally is enabled, Node.JS SDK will attempt to store data on disk
-         */
-        isAppCrashing?: boolean;
-        /**
-         * Callback that will be invoked with the response from server, in case of isAppCrashing set to true,
-         * with immediate notification that data was stored
-         */
-        callback?: (v: string) => void;
-    }
-}
-
-declare module 'applicationinsights' {
-    export = ApplicationInsights;
-}
diff --git a/src/typings/applicationinsights-web.d.ts b/src/typings/applicationinsights-web.d.ts
deleted file mode 100644
index a069fed7c607..000000000000
--- a/src/typings/applicationinsights-web.d.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- *  Copyright (c) Microsoft Corporation. All rights reserved.
- *  Licensed under the Source EULA. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-
-declare module '@microsoft/applicationinsights-web' {
-	export interface IConfig {
-		instrumentationKey?: string;
-		endpointUrl?: string;
-		emitLineDelimitedJson?: boolean;
-		accountId?: string;
-		sessionRenewalMs?: number;
-		sessionExpirationMs?: number;
-		maxBatchSizeInBytes?: number;
-		maxBatchInterval?: number;
-		enableDebug?: boolean;
-		disableExceptionTracking?: boolean;
-		disableTelemetry?: boolean;
-		verboseLogging?: boolean;
-		diagnosticLogInterval?: number;
-		samplingPercentage?: number;
-		autoTrackPageVisitTime?: boolean;
-		disableAjaxTracking?: boolean;
-		overridePageViewDuration?: boolean;
-		maxAjaxCallsPerView?: number;
-		disableDataLossAnalysis?: boolean;
-		disableCorrelationHeaders?: boolean;
-		correlationHeaderExcludedDomains?: string[];
-		disableFlushOnBeforeUnload?: boolean;
-		enableSessionStorageBuffer?: boolean;
-		isCookieUseDisabled?: boolean;
-		cookieDomain?: string;
-		isRetryDisabled?: boolean;
-		url?: string;
-		isStorageUseDisabled?: boolean;
-		isBeaconApiDisabled?: boolean;
-		sdkExtension?: string;
-		isBrowserLinkTrackingEnabled?: boolean;
-		appId?: string;
-		enableCorsCorrelation?: boolean;
-	}
-
-	export interface ISnippet {
-		config: IConfig;
-	}
-
-	export interface IEventTelemetry {
-		name: string;
-		properties?: { [key: string]: string };
-		measurements?: { [key: string]: number };
-	}
-
-	export class ApplicationInsights {
-		constructor(config: ISnippet);
-		loadAppInsights(): void;
-		trackEvent(data: IEventTelemetry): void;
-		flush(): void;
-	}
-}
diff --git a/src/typings/chokidar.d.ts b/src/typings/chokidar.d.ts
deleted file mode 100644
index 87d93fd5ab1a..000000000000
--- a/src/typings/chokidar.d.ts
+++ /dev/null
@@ -1,200 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- *  Copyright (c) Microsoft Corporation. All rights reserved.
- *  Licensed under the Source EULA. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-
-declare module 'chokidar' {
-
-	// TypeScript Version: 3.0
-
-	import * as fs from "fs";
-	import { EventEmitter } from "events";
-
-	/**
-	 * The object's keys are all the directories (using absolute paths unless the `cwd` option was
-	 * used), and the values are arrays of the names of the items contained in each directory.
-	 */
-	export interface WatchedPaths {
-		[directory: string]: string[];
-	}
-
-	export class FSWatcher extends EventEmitter implements fs.FSWatcher {
-
-		readonly options?: WatchOptions;
-
-		/**
-		 * Constructs a new FSWatcher instance with optional WatchOptions parameter.
-		 */
-		constructor(options?: WatchOptions);
-
-		/**
-		 * Add files, directories, or glob patterns for tracking. Takes an array of strings or just one
-		 * string.
-		 */
-		add(paths: string | string[]): void;
-
-		/**
-		 * Stop watching files, directories, or glob patterns. Takes an array of strings or just one
-		 * string.
-		 */
-		unwatch(paths: string | string[]): void;
-
-		/**
-		 * Returns an object representing all the paths on the file system being watched by this
-		 * `FSWatcher` instance. The object's keys are all the directories (using absolute paths unless
-		 * the `cwd` option was used), and the values are arrays of the names of the items contained in
-		 * each directory.
-		 */
-		getWatched(): WatchedPaths;
-
-		/**
-		 * Removes all listeners from watched files.
-		 */
-		close(): void;
-
-		on(event: 'add' | 'addDir' | 'change', listener: (path: string, stats?: fs.Stats) => void): this;
-
-		on(event: 'all', listener: (eventName: 'add' | 'addDir' | 'change' | 'unlink' | 'unlinkDir', path: string, stats?: fs.Stats) => void): this;
-
-		/**
-		 * Error occured
-		 */
-		on(event: 'error', listener: (error: Error) => void): this;
-
-		/**
-		 * Exposes the native Node `fs.FSWatcher events`
-		 */
-		on(event: 'raw', listener: (eventName: string, path: string, details: any) => void): this;
-
-		/**
-		 * Fires when the initial scan is complete
-		 */
-		on(event: 'ready', listener: () => void): this;
-
-		on(event: 'unlink' | 'unlinkDir', listener: (path: string) => void): this;
-
-		on(event: string, listener: (...args: any[]) => void): this;
-	}
-
-	export interface WatchOptions {
-		/**
-		 * Indicates whether the process should continue to run as long as files are being watched. If
-		 * set to `false` when using `fsevents` to watch, no more events will be emitted after `ready`,
-		 * even if the process continues to run.
-		 */
-		persistent?: boolean;
-
-		/**
-		 * ([anymatch](https://github.com/es128/anymatch)-compatible definition) Defines files/paths to
-		 * be ignored. The whole relative or absolute path is tested, not just filename. If a function
-		 * with two arguments is provided, it gets called twice per path - once with a single argument
-		 * (the path), second time with two arguments (the path and the
-		 * [`fs.Stats`](http://nodejs.org/api/fs.html#fs_class_fs_stats) object of that path).
-		 */
-		ignored?: any;
-
-		/**
-		 * If set to `false` then `add`/`addDir` events are also emitted for matching paths while
-		 * instantiating the watching as chokidar discovers these file paths (before the `ready` event).
-		 */
-		ignoreInitial?: boolean;
-
-		/**
-		 * When `false`, only the symlinks themselves will be watched for changes instead of following
-		 * the link references and bubbling events through the link's path.
-		 */
-		followSymlinks?: boolean;
-
-		/**
-		 * The base directory from which watch `paths` are to be derived. Paths emitted with events will
-		 * be relative to this.
-		 */
-		cwd?: string;
-
-		/**
-		 *  If set to true then the strings passed to .watch() and .add() are treated as literal path
-		 *  names, even if they look like globs. Default: false.
-		 */
-		disableGlobbing?: boolean;
-
-		/**
-		 * Whether to use fs.watchFile (backed by polling), or fs.watch. If polling leads to high CPU
-		 * utilization, consider setting this to `false`. It is typically necessary to **set this to
-		 * `true` to successfully watch files over a network**, and it may be necessary to successfully
-		 * watch files in other non-standard situations. Setting to `true` explicitly on OS X overrides
-		 * the `useFsEvents` default.
-		 */
-		usePolling?: boolean;
-
-		/**
-		 * Whether to use the `fsevents` watching interface if available. When set to `true` explicitly
-		 * and `fsevents` is available this supercedes the `usePolling` setting. When set to `false` on
-		 * OS X, `usePolling: true` becomes the default.
-		 */
-		useFsEvents?: boolean;
-
-		/**
-		 * If relying upon the [`fs.Stats`](http://nodejs.org/api/fs.html#fs_class_fs_stats) object that
-		 * may get passed with `add`, `addDir`, and `change` events, set this to `true` to ensure it is
-		 * provided even in cases where it wasn't already available from the underlying watch events.
-		 */
-		alwaysStat?: boolean;
-
-		/**
-		 * If set, limits how many levels of subdirectories will be traversed.
-		 */
-		depth?: number;
-
-		/**
-		 * Interval of file system polling.
-		 */
-		interval?: number;
-
-		/**
-		 * Interval of file system polling for binary files. ([see list of binary extensions](https://gi
-		 * thub.com/sindresorhus/binary-extensions/blob/master/binary-extensions.json))
-		 */
-		binaryInterval?: number;
-
-		/**
-		 *  Indicates whether to watch files that don't have read permissions if possible. If watching
-		 *  fails due to `EPERM` or `EACCES` with this set to `true`, the errors will be suppressed
-		 *  silently.
-		 */
-		ignorePermissionErrors?: boolean;
-
-		/**
-		 * `true` if `useFsEvents` and `usePolling` are `false`). Automatically filters out artifacts
-		 * that occur when using editors that use "atomic writes" instead of writing directly to the
-		 * source file. If a file is re-added within 100 ms of being deleted, Chokidar emits a `change`
-		 * event rather than `unlink` then `add`. If the default of 100 ms does not work well for you,
-		 * you can override it by setting `atomic` to a custom value, in milliseconds.
-		 */
-		atomic?: boolean | number;
-
-		/**
-		 * can be set to an object in order to adjust timing params:
-		 */
-		awaitWriteFinish?: AwaitWriteFinishOptions | boolean;
-	}
-
-	export interface AwaitWriteFinishOptions {
-		/**
-		 * Amount of time in milliseconds for a file size to remain constant before emitting its event.
-		 */
-		stabilityThreshold?: number;
-
-		/**
-		 * File size polling interval.
-		 */
-		pollInterval?: number;
-	}
-
-	/**
-	 * produces an instance of `FSWatcher`.
-	 */
-	export function watch(
-		paths: string | string[],
-		options?: WatchOptions
-	): FSWatcher;
-}
diff --git a/src/typings/electron.d.ts b/src/typings/electron.d.ts
index f1d41613b516..369817dcd12e 100644
--- a/src/typings/electron.d.ts
+++ b/src/typings/electron.d.ts
@@ -1,4 +1,4 @@
-// Type definitions for Electron 6.0.12
+// Type definitions for Electron 6.1.5
 // Project: http://electronjs.org/
 // Definitions by: The Electron Team 
 // Definitions: https://github.com/electron/electron-typescript-definitions
@@ -70,6 +70,7 @@ declare namespace Electron {
 
 	interface RendererInterface extends CommonInterface {
 		BrowserWindowProxy: typeof BrowserWindowProxy;
+		contextBridge: ContextBridge;
 		desktopCapturer: DesktopCapturer;
 		ipcRenderer: IpcRenderer;
 		remote: Remote;
@@ -83,6 +84,7 @@ declare namespace Electron {
 	const autoUpdater: AutoUpdater;
 	const clipboard: Clipboard;
 	const contentTracing: ContentTracing;
+	const contextBridge: ContextBridge;
 	const crashReporter: CrashReporter;
 	const desktopCapturer: DesktopCapturer;
 	const dialog: Dialog;
@@ -2511,6 +2513,13 @@ declare namespace Electron {
 		stopRecording(resultFilePath: string): Promise;
 	}
 
+	interface ContextBridge extends EventEmitter {
+
+		// Docs: http://electronjs.org/docs/api/context-bridge
+
+		exposeInMainWorld(apiKey: string, api: Record): void;
+	}
+
 	interface Cookie {
 
 		// Docs: http://electronjs.org/docs/api/structures/cookie
diff --git a/src/typings/http-proxy-agent.d.ts b/src/typings/http-proxy-agent.d.ts
deleted file mode 100644
index 062771cd75be..000000000000
--- a/src/typings/http-proxy-agent.d.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- *  Copyright (c) Microsoft Corporation. All rights reserved.
- *  Licensed under the Source EULA. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-
-declare module 'http-proxy-agent' {
-
-	interface IHttpProxyAgentOptions {
-		host: string;
-		port: number;
-		auth?: string;
-	}
-
-	class HttpProxyAgent {
-		constructor(proxy: string);
-		constructor(opts: IHttpProxyAgentOptions);
-	}
-
-	export = HttpProxyAgent;
-}
\ No newline at end of file
diff --git a/src/typings/iconv-lite.d.ts b/src/typings/iconv-lite.d.ts
deleted file mode 100644
index 15be5a0ca6d1..000000000000
--- a/src/typings/iconv-lite.d.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- *  Copyright (c) Microsoft Corporation. All rights reserved.
- *  Licensed under the Source EULA. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-
-/// 
-
-declare module 'iconv-lite' {
-	export function decode(buffer: Buffer, encoding: string): string;
-
-	export function encode(content: string | Buffer, encoding: string, options?: { addBOM?: boolean }): Buffer;
-
-	export function encodingExists(encoding: string): boolean;
-
-	export function decodeStream(encoding: string): NodeJS.ReadWriteStream;
-
-	export function encodeStream(encoding: string, options?: { addBOM?: boolean }): NodeJS.ReadWriteStream;
-}
\ No newline at end of file
diff --git a/src/typings/jschardet.d.ts b/src/typings/jschardet.d.ts
deleted file mode 100644
index f252a47fd094..000000000000
--- a/src/typings/jschardet.d.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-declare module 'jschardet' {
-	export interface IDetectedMap {
-		encoding: string,
-		confidence: number
-	}
-	export function detect(buffer: Buffer): IDetectedMap;
-
-	export const Constants: {
-		MINIMUM_THRESHOLD: number,
-	}
-}
\ No newline at end of file
diff --git a/src/typings/native-is-elevated.d.ts b/src/typings/native-is-elevated.d.ts
deleted file mode 100644
index 87c86d53f907..000000000000
--- a/src/typings/native-is-elevated.d.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- *  Copyright (c) Microsoft Corporation. All rights reserved.
- *  Licensed under the Source EULA. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-
-declare module 'native-is-elevated' {
-	function isElevated(): boolean;
-
-	export = isElevated;
-}
\ No newline at end of file
diff --git a/src/typings/native-keymap.d.ts b/src/typings/native-keymap.d.ts
deleted file mode 100644
index f51f58928776..000000000000
--- a/src/typings/native-keymap.d.ts
+++ /dev/null
@@ -1,93 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- *  Copyright (c) Microsoft Corporation. All rights reserved.
- *  Licensed under the Source EULA. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-
-declare module 'native-keymap' {
-
-	export interface IWindowsKeyMapping {
-		vkey: string;
-		value: string;
-		withShift: string;
-		withAltGr: string;
-		withShiftAltGr: string;
-	}
-	export interface IWindowsKeyboardMapping {
-		[code: string]: IWindowsKeyMapping;
-	}
-	export interface ILinuxKeyMapping {
-		value: string;
-		withShift: string;
-		withAltGr: string;
-		withShiftAltGr: string;
-	}
-	export interface ILinuxKeyboardMapping {
-		[code: string]: ILinuxKeyMapping;
-	}
-	export interface IMacKeyMapping {
-		value: string;
-		withShift: string;
-		withAltGr: string;
-		withShiftAltGr: string;
-		valueIsDeadKey: boolean;
-		withShiftIsDeadKey: boolean;
-		withAltGrIsDeadKey: boolean;
-		withShiftAltGrIsDeadKey: boolean;
-	}
-	export interface IMacKeyboardMapping {
-		[code: string]: IMacKeyMapping;
-	}
-
-	export type IKeyboardMapping = IWindowsKeyboardMapping | ILinuxKeyboardMapping | IMacKeyboardMapping;
-
-	export function getKeyMap(): IKeyboardMapping;
-
-	/* __GDPR__FRAGMENT__
-		"IKeyboardLayoutInfo" : {
-			"name" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
-			"id": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
-			"text": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
-		}
-	*/
-	export interface IWindowsKeyboardLayoutInfo {
-		name: string;
-		id: string;
-		text: string;
-	}
-
-	/* __GDPR__FRAGMENT__
-		"IKeyboardLayoutInfo" : {
-			"model" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
-			"layout": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
-			"variant": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
-			"options": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
-			"rules": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
-		}
-	*/
-	export interface ILinuxKeyboardLayoutInfo {
-		model: string;
-		layout: string;
-		variant: string;
-		options: string;
-		rules: string;
-	}
-
-	/* __GDPR__FRAGMENT__
-		"IKeyboardLayoutInfo" : {
-			"id" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
-			"lang": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
-		}
-	*/
-	export interface IMacKeyboardLayoutInfo {
-		id: string;
-		lang: string;
-	}
-
-	export type IKeyboardLayoutInfo = IWindowsKeyboardLayoutInfo | ILinuxKeyboardLayoutInfo | IMacKeyboardLayoutInfo;
-
-	export function getCurrentKeyboardLayout(): IKeyboardLayoutInfo;
-
-	export function onDidChangeKeyboardLayout(callback: () => void): void;
-
-	export function isISOKeyboard(): boolean;
-}
\ No newline at end of file
diff --git a/src/typings/native-watchdog.d.ts b/src/typings/native-watchdog.d.ts
deleted file mode 100644
index 0694dd2db0c1..000000000000
--- a/src/typings/native-watchdog.d.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- *  Copyright (c) Microsoft Corporation. All rights reserved.
- *  Licensed under the Source EULA. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-
-declare module 'native-watchdog' {
-
-	/**
-	 * Start monitoring for a certain pid to exist.
-	 * If the process indicated by pid ceases to execute,
-	 * the current process will exit in 6 seconds with exit code 87
-	 */
-	export function start(pid: number): void;
-
-	export function exit(exitCode: number): void;
-
-}
diff --git a/src/typings/node-pty.d.ts b/src/typings/node-pty.d.ts
deleted file mode 100644
index 100e9eef8710..000000000000
--- a/src/typings/node-pty.d.ts
+++ /dev/null
@@ -1,143 +0,0 @@
-/**
- * Copyright (c) 2017, Daniel Imms (Source EULA).
- * Copyright (c) 2018, Microsoft Corporation (Source EULA).
- */
-
-declare module 'node-pty' {
-	/**
-	 * Forks a process as a pseudoterminal.
-	 * @param file The file to launch.
-	 * @param args The file's arguments as argv (string[]) or in a pre-escaped CommandLine format
-	 * (string). Note that the CommandLine option is only available on Windows and is expected to be
-	 * escaped properly.
-	 * @param options The options of the terminal.
-	 * @see CommandLineToArgvW https://msdn.microsoft.com/en-us/library/windows/desktop/bb776391(v=vs.85).aspx
-	 * @see Parsing C++ Comamnd-Line Arguments https://msdn.microsoft.com/en-us/library/17w5ykft.aspx
-	 * @see GetCommandLine https://msdn.microsoft.com/en-us/library/windows/desktop/ms683156.aspx
-	 */
-	export function spawn(file: string, args: string[] | string, options: IPtyForkOptions | IWindowsPtyForkOptions): IPty;
-
-	export interface IPtyForkOptions {
-		name?: string;
-		cols?: number;
-		rows?: number;
-		cwd?: string;
-		env?: { [key: string]: string };
-		uid?: number;
-		gid?: number;
-		encoding?: string;
-	}
-
-	export interface IWindowsPtyForkOptions {
-		name?: string;
-		cols?: number;
-		rows?: number;
-		cwd?: string;
-		env?: { [key: string]: string };
-		encoding?: string;
-		/**
-		 * Whether to use the experimental ConPTY system on Windows. When this is not set, ConPTY will
-		 * be used when the Windows build number is >= 18309 (it's available in 17134 and 17692 but is
-		 * too unstable to enable by default).
-		 *
-		 * This setting does nothing on non-Windows.
-		 */
-		experimentalUseConpty?: boolean;
-		/**
-		 * Whether to use PSEUDOCONSOLE_INHERIT_CURSOR in conpty.
-		 * @see https://docs.microsoft.com/en-us/windows/console/createpseudoconsole
-		 */
-		conptyInheritCursor?: boolean;
-	}
-
-	/**
-	 * An interface representing a pseudoterminal, on Windows this is emulated via the winpty library.
-	 */
-	export interface IPty {
-		/**
-		 * The process ID of the outer process.
-		 */
-		readonly pid: number;
-
-		/**
-		 * The column size in characters.
-		 */
-		readonly cols: number;
-
-		/**
-		 * The row size in characters.
-		 */
-		readonly rows: number;
-
-		/**
-		 * The title of the active process.
-		 */
-		readonly process: string;
-
-		/**
-		 * Adds an event listener for when a data event fires. This happens when data is returned from
-		 * the pty.
-		 * @returns an `IDisposable` to stop listening.
-		 */
-		readonly onData: IEvent;
-
-		/**
-		 * Adds an event listener for when an exit event fires. This happens when the pty exits.
-		 * @returns an `IDisposable` to stop listening.
-		 */
-		readonly onExit: IEvent<{ exitCode: number, signal?: number }>;
-
-		/**
-		 * Adds a listener to the data event, fired when data is returned from the pty.
-		 * @param event The name of the event.
-		 * @param listener The callback function.
-		 * @deprecated Use IPty.onData
-		 */
-		on(event: 'data', listener: (data: string) => void): void;
-
-		/**
-		 * Adds a listener to the exit event, fired when the pty exits.
-		 * @param event The name of the event.
-		 * @param listener The callback function, exitCode is the exit code of the process and signal is
-		 * the signal that triggered the exit. signal is not supported on Windows.
-		 * @deprecated Use IPty.onExit
-		 */
-		on(event: 'exit', listener: (exitCode: number, signal?: number) => void): void;
-
-		/**
-		 * Resizes the dimensions of the pty.
-		 * @param columns THe number of columns to use.
-		 * @param rows The number of rows to use.
-		 */
-		resize(columns: number, rows: number): void;
-
-		/**
-		 * Writes data to the pty.
-		 * @param data The data to write.
-		 */
-		write(data: string): void;
-
-		/**
-		 * Kills the pty.
-		 * @param signal The signal to use, defaults to SIGHUP. This parameter is not supported on
-		 * Windows.
-		 * @throws Will throw when signal is used on Windows.
-		 */
-		kill(signal?: string): void;
-	}
-
-	/**
-	 * An object that can be disposed via a dispose function.
-	 */
-	export interface IDisposable {
-		dispose(): void;
-	}
-
-	/**
-	 * An event that can be listened to.
-	 * @returns an `IDisposable` to stop listening.
-	 */
-	export interface IEvent {
-		(listener: (e: T) => any): IDisposable;
-	}
-}
diff --git a/src/typings/nsfw.d.ts b/src/typings/nsfw.d.ts
deleted file mode 100644
index 37c4c2ec926c..000000000000
--- a/src/typings/nsfw.d.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- *  Copyright (c) Microsoft Corporation. All rights reserved.
- *  Licensed under the Source EULA. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-
-declare module 'nsfw' {
-	interface NsfwWatcher {
-		start(): any;
-		stop(): any;
-	}
-
-	interface NsfwWatchingPromise {
-		then(): void;
-	}
-
-	interface NsfwStartWatchingPromise {
-		then(fn: (watcher: NsfwWatcher) => void): NsfwWatchingPromise;
-	}
-
-	interface NsfwEvent {
-		action: number;
-		directory: string;
-		file?: string;
-		newFile?: string;
-		newDirectory?: string;
-		oldFile?: string;
-	}
-
-	interface NsfwFunction {
-		(dir: string, eventHandler: (events: NsfwEvent[]) => void, options?: any): NsfwStartWatchingPromise;
-		actions: {
-			CREATED: number;
-			DELETED: number;
-			MODIFIED: number;
-			RENAMED: number;
-		}
-	}
-
-	var nsfw: NsfwFunction;
-	export = nsfw;
-}
diff --git a/src/typings/onigasm-umd.d.ts b/src/typings/onigasm-umd.d.ts
deleted file mode 100644
index 24396aafa9f9..000000000000
--- a/src/typings/onigasm-umd.d.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- *  Copyright (c) Microsoft Corporation. All rights reserved.
- *  Licensed under the Source EULA. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-
-declare module "onigasm-umd" {
-
-	function loadWASM(data: string | ArrayBuffer): Promise;
-
-	class OnigString {
-		constructor(content: string);
-		readonly content: string;
-		readonly dispose?: () => void;
-	}
-
-	class OnigScanner {
-		constructor(patterns: string[]);
-		findNextMatchSync(string: string | OnigString, startPosition: number): IOnigMatch;
-	}
-
-	export interface IOnigCaptureIndex {
-		index: number
-		start: number
-		end: number
-		length: number
-	}
-
-	export interface IOnigMatch {
-		index: number
-		captureIndices: IOnigCaptureIndex[]
-		scanner: OnigScanner
-	}
-}
\ No newline at end of file
diff --git a/src/typings/require.d.ts b/src/typings/require.d.ts
index 5b98dc2f4bc7..b11fa0b66000 100644
--- a/src/typings/require.d.ts
+++ b/src/typings/require.d.ts
@@ -46,5 +46,7 @@ interface NodeRequire {
 	config(data: any): any;
 	onError: Function;
 	__$__nodeRequire(moduleName: string): T;
-	getStats(): ReadonlyArray
+	getStats(): ReadonlyArray;
 }
+
+declare var require: NodeRequire;
diff --git a/src/typings/spdlog.d.ts b/src/typings/spdlog.d.ts
deleted file mode 100644
index 32b3c756ac75..000000000000
--- a/src/typings/spdlog.d.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- *  Copyright (c) Microsoft Corporation. All rights reserved.
- *  Licensed under the Source EULA. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-
-declare module 'spdlog' {
-
-	export const version: string;
-	export function setAsyncMode(bufferSize: number, flushInterval: number): void;
-	export function createRotatingLogger(name: string, filename: string, filesize: number, filecount: number): RotatingLogger;
-	export function createRotatingLoggerAsync(name: string, filename: string, filesize: number, filecount: number): Promise;
-
-	export enum LogLevel {
-		CRITICAL,
-		ERROR,
-		WARN,
-		INFO,
-		DEBUG,
-		TRACE,
-		OFF
-	}
-
-	export class RotatingLogger {
-		constructor(name: string, filename: string, filesize: number, filecount: number);
-
-		trace(message: string): void;
-		debug(message: string): void;
-		info(message: string): void;
-		warn(message: string): void;
-		error(message: string): void;
-		critical(message: string): void;
-		setLevel(level: number): void;
-		clearFormatters(): void;
-		/**
-		 * A synchronous operation to flush the contents into file
-		*/
-		flush(): void;
-		drop(): void;
-	}
-}
\ No newline at end of file
diff --git a/src/typings/sudo-prompt.d.ts b/src/typings/sudo-prompt.d.ts
deleted file mode 100644
index 8bf5e71785ef..000000000000
--- a/src/typings/sudo-prompt.d.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- *  Copyright (c) Microsoft Corporation. All rights reserved.
- *  Licensed under the Source EULA. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-
-declare module 'sudo-prompt' {
-	type SudoOptions = {
-		name?: string;
-		icns?: string;
-		env?: NodeJS.ProcessEnv;
-	};
-	export function exec(cmd: string, options: SudoOptions, callback: (error: string, stdout: string, stderr: string) => void): void;
-}
diff --git a/src/typings/v8-inspect-profiler.d.ts b/src/typings/v8-inspect-profiler.d.ts
deleted file mode 100644
index 59d7f81cfa06..000000000000
--- a/src/typings/v8-inspect-profiler.d.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-declare module 'v8-inspect-profiler' {
-
-	export interface ProfileResult {
-		profile: Profile;
-	}
-
-	export interface Profile {
-		nodes: ProfileNode[];
-		samples?: number[];
-		timeDeltas?: number[];
-		startTime: number;
-		endTime: number;
-	}
-
-	export interface ProfileNode {
-		id: number;
-		hitCount?: number;
-		children?: number[];
-		callFrame: {
-			url: string;
-			scriptId: string;
-			functionName: string;
-			lineNumber: number;
-			columnNumber: number;
-		};
-		deoptReason?: string;
-		positionTicks?: { line: number; ticks: number }[];
-	}
-
-	export interface ProfilingSession {
-		stop(afterDelay?: number): PromiseLike;
-	}
-
-	export interface Target {
-		description: string;
-		devtoolsFrontendUrl: string;
-		id: string;
-		title: string;
-		type: string;
-		url: string;
-		webSocketDebuggerUrl: string;
-	}
-
-	export interface StartOptions {
-		port: number;
-		tries?: number;
-		retyWait?: number;
-		checkForPaused?: boolean;
-		target?: (targets: Target[]) => Target;
-	}
-
-	export function startProfiling(options: StartOptions): PromiseLike;
-	export function writeProfile(profile: ProfileResult, name?: string): PromiseLike;
-	export function rewriteAbsolutePaths(profile: ProfileResult, replaceWith?: string): ProfileResult;
-}
diff --git a/src/typings/vscode-minimist.d.ts b/src/typings/vscode-minimist.d.ts
deleted file mode 100644
index 17558a1a7383..000000000000
--- a/src/typings/vscode-minimist.d.ts
+++ /dev/null
@@ -1,92 +0,0 @@
-// Type definitions for minimist 1.2.0
-// Project: https://github.com/substack/minimist
-// Definitions by: Bart van der Schoor , Necroskillz , kamranayub 
-// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
-
-/**
- * Return an argument object populated with the array arguments from args
- *
- * @param args An optional argument array (typically `process.argv.slice(2)`)
- * @param opts An optional options object to customize the parsing
- */
-declare function minimist(args?: string[], opts?: minimist.Opts): minimist.ParsedArgs;
-
-/**
- * Return an argument object populated with the array arguments from args. Strongly-typed
- * to be the intersect of type T with minimist.ParsedArgs.
- *
- * @type T The type that will be intersected with minimist.ParsedArgs to represent the argument object
- * @param args An optional argument array (typically `process.argv.slice(2)`)
- * @param opts An optional options object to customize the parsing
- */
-declare function minimist(args?: string[], opts?: minimist.Opts): T & minimist.ParsedArgs;
-
-/**
- * Return an argument object populated with the array arguments from args. Strongly-typed
- * to be the the type T which should extend minimist.ParsedArgs
- *
- * @type T The type that extends minimist.ParsedArgs and represents the argument object
- * @param args An optional argument array (typically `process.argv.slice(2)`)
- * @param opts An optional options object to customize the parsing
- */
-declare function minimist(args?: string[], opts?: minimist.Opts): T;
-
-declare namespace minimist {
-	export interface Opts {
-		/**
-		 * A string or array of strings argument names to always treat as strings
-		 */
-		string?: string | string[];
-
-		/**
-		 * A boolean, string or array of strings to always treat as booleans. If true will treat
-		 * all double hyphenated arguments without equals signs as boolean (e.g. affects `--foo`, not `-f` or `--foo=bar`)
-		 */
-		boolean?: boolean | string | string[];
-
-		/**
-		 * An object mapping string names to strings or arrays of string argument names to use as aliases
-		 */
-		alias?: { [key: string]: string | string[] };
-
-		/**
-		 * An object mapping string argument names to default values
-		 */
-		default?: { [key: string]: any };
-
-		/**
-		 * When true, populate argv._ with everything after the first non-option
-		 */
-		stopEarly?: boolean;
-
-		/**
-		 * A function which is invoked with a command line parameter not defined in the opts
-		 * configuration object. If the function returns false, the unknown option is not added to argv
-		 */
-		unknown?: (arg: string) => boolean;
-
-		/**
-		 * When true, populate argv._ with everything before the -- and argv['--'] with everything after the --.
-		 * Note that with -- set, parsing for arguments still stops after the `--`.
-		 */
-		'--'?: boolean;
-	}
-
-	export interface ParsedArgs {
-		[arg: string]: any;
-
-		/**
-		 * If opts['--'] is true, populated with everything after the --
-		 */
-		'--'?: string[];
-
-		/**
-		 * Contains all the arguments that didn't have an option associated with them
-		 */
-		_: string[];
-	}
-}
-
-declare module "vscode-minimist" {
-	export = minimist;
-}
diff --git a/src/typings/vscode-proxy-agent.d.ts b/src/typings/vscode-proxy-agent.d.ts
deleted file mode 100644
index 959c106c984a..000000000000
--- a/src/typings/vscode-proxy-agent.d.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- *  Copyright (c) Microsoft Corporation. All rights reserved.
- *  Licensed under the Source EULA. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-
-declare module 'vscode-proxy-agent';
diff --git a/src/typings/vscode-ripgrep.d.ts b/src/typings/vscode-ripgrep.d.ts
deleted file mode 100644
index 4c5c89c3ca89..000000000000
--- a/src/typings/vscode-ripgrep.d.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-declare module 'vscode-ripgrep' {
-	export const rgPath: string;
-}
diff --git a/src/typings/vscode-sqlite3.d.ts b/src/typings/vscode-sqlite3.d.ts
deleted file mode 100644
index 6791f2e96717..000000000000
--- a/src/typings/vscode-sqlite3.d.ts
+++ /dev/null
@@ -1,115 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- *  Copyright (c) Microsoft Corporation. All rights reserved.
- *  Licensed under the Source EULA. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-
-// Type definitions for sqlite3 3.1
-// Project: http://github.com/mapbox/node-sqlite3
-// Definitions by: Nick Malaguti 
-//                 Sumant Manne 
-//                 Behind The Math 
-// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
-
-/// 
-
-declare module 'vscode-sqlite3' {
-	import events = require("events");
-
-	export const OPEN_READONLY: number;
-	export const OPEN_READWRITE: number;
-	export const OPEN_CREATE: number;
-	export const OPEN_SHAREDCACHE: number;
-	export const OPEN_PRIVATECACHE: number;
-	export const OPEN_URI: number;
-
-	export const cached: {
-		Database(filename: string, callback?: (this: Database, err: Error | null) => void): Database;
-		Database(filename: string, mode?: number, callback?: (this: Database, err: Error | null) => void): Database;
-	};
-
-	export interface RunResult extends Statement {
-		lastID: number;
-		changes: number;
-	}
-
-	export class Statement extends events.EventEmitter {
-		bind(callback?: (err: Error | null) => void): this;
-		bind(...params: any[]): this;
-
-		reset(callback?: (err: null) => void): this;
-
-		finalize(callback?: (err: Error) => void): Database;
-
-		run(callback?: (err: Error | null) => void): this;
-		run(params: any, callback?: (this: RunResult, err: Error | null) => void): this;
-		run(...params: any[]): this;
-
-		get(callback?: (err: Error | null, row?: any) => void): this;
-		get(params: any, callback?: (this: RunResult, err: Error | null, row?: any) => void): this;
-		get(...params: any[]): this;
-
-		all(callback?: (err: Error | null, rows: any[]) => void): this;
-		all(params: any, callback?: (this: RunResult, err: Error | null, rows: any[]) => void): this;
-		all(...params: any[]): this;
-
-		each(callback?: (err: Error | null, row: any) => void, complete?: (err: Error | null, count: number) => void): this;
-		each(params: any, callback?: (this: RunResult, err: Error | null, row: any) => void, complete?: (err: Error | null, count: number) => void): this;
-		each(...params: any[]): this;
-	}
-
-	export class Database extends events.EventEmitter {
-		constructor(filename: string, callback?: (err: Error | null) => void);
-		constructor(filename: string, mode?: number, callback?: (err: Error | null) => void);
-
-		close(callback?: (err: Error | null) => void): void;
-
-		run(sql: string, callback?: (this: RunResult, err: Error | null) => void): this;
-		run(sql: string, params: any, callback?: (this: RunResult, err: Error | null) => void): this;
-		run(sql: string, ...params: any[]): this;
-
-		get(sql: string, callback?: (this: Statement, err: Error | null, row: any) => void): this;
-		get(sql: string, params: any, callback?: (this: Statement, err: Error | null, row: any) => void): this;
-		get(sql: string, ...params: any[]): this;
-
-		all(sql: string, callback?: (this: Statement, err: Error | null, rows: any[]) => void): this;
-		all(sql: string, params: any, callback?: (this: Statement, err: Error | null, rows: any[]) => void): this;
-		all(sql: string, ...params: any[]): this;
-
-		each(sql: string, callback?: (this: Statement, err: Error | null, row: any) => void, complete?: (err: Error | null, count: number) => void): this;
-		each(sql: string, params: any, callback?: (this: Statement, err: Error | null, row: any) => void, complete?: (err: Error | null, count: number) => void): this;
-		each(sql: string, ...params: any[]): this;
-
-		exec(sql: string, callback?: (this: Statement, err: Error | null) => void): this;
-
-		prepare(sql: string, callback?: (this: Statement, err: Error | null) => void): Statement;
-		prepare(sql: string, params: any, callback?: (this: Statement, err: Error | null) => void): Statement;
-		prepare(sql: string, ...params: any[]): Statement;
-
-		serialize(callback?: () => void): void;
-		parallelize(callback?: () => void): void;
-
-		on(event: "trace", listener: (sql: string) => void): this;
-		on(event: "profile", listener: (sql: string, time: number) => void): this;
-		on(event: "error", listener: (err: Error) => void): this;
-		on(event: "open" | "close", listener: () => void): this;
-		on(event: string, listener: (...args: any[]) => void): this;
-
-		configure(option: "busyTimeout", value: number): void;
-	}
-
-	export function verbose(): sqlite3;
-
-	export interface sqlite3 {
-		OPEN_READONLY: number;
-		OPEN_READWRITE: number;
-		OPEN_CREATE: number;
-		OPEN_SHAREDCACHE: number;
-		OPEN_PRIVATECACHE: number;
-		OPEN_URI: number;
-		cached: typeof cached;
-		RunResult: RunResult;
-		Statement: typeof Statement;
-		Database: typeof Database;
-		verbose(): this;
-	}
-}
\ No newline at end of file
diff --git a/src/typings/vscode-textmate.d.ts b/src/typings/vscode-textmate.d.ts
deleted file mode 100644
index 28f28fbbf1e7..000000000000
--- a/src/typings/vscode-textmate.d.ts
+++ /dev/null
@@ -1,256 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- *  Copyright (c) Microsoft Corporation. All rights reserved.
- *  Licensed under the Source EULA. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-
-declare module "vscode-textmate" {
-	/**
-	 * A single theme setting.
-	 */
-	export interface IRawThemeSetting {
-		readonly name?: string;
-		readonly scope?: string | string[];
-		readonly settings: {
-			readonly fontStyle?: string;
-			readonly foreground?: string;
-			readonly background?: string;
-		};
-	}
-	/**
-	 * A TextMate theme.
-	 */
-	export interface IRawTheme {
-		readonly name?: string;
-		readonly settings: IRawThemeSetting[];
-	}
-	export interface Thenable extends PromiseLike {
-	}
-	/**
-	 * A registry helper that can locate grammar file paths given scope names.
-	 */
-	export interface RegistryOptions {
-		theme?: IRawTheme;
-		loadGrammar(scopeName: string): Thenable;
-		getInjections?(scopeName: string): string[];
-		getOnigLib?(): Thenable;
-	}
-	/**
-	 * A map from scope name to a language id. Please do not use language id 0.
-	 */
-	export interface IEmbeddedLanguagesMap {
-		[scopeName: string]: number;
-	}
-	/**
-	 * A map from selectors to token types.
-	 */
-	export interface ITokenTypeMap {
-		[selector: string]: StandardTokenType;
-	}
-	export const enum StandardTokenType {
-		Other = 0,
-		Comment = 1,
-		String = 2,
-		RegEx = 4,
-	}
-	export interface IGrammarConfiguration {
-		embeddedLanguages?: IEmbeddedLanguagesMap;
-		tokenTypes?: ITokenTypeMap;
-	}
-	/**
-	 * The registry that will hold all grammars.
-	 */
-	export class Registry {
-		private readonly _locator;
-		private readonly _syncRegistry;
-		constructor(locator?: RegistryOptions);
-		/**
-		 * Change the theme. Once called, no previous `ruleStack` should be used anymore.
-		 */
-		setTheme(theme: IRawTheme): void;
-		/**
-		 * Returns a lookup array for color ids.
-		 */
-		getColorMap(): string[];
-		/**
-		 * Load the grammar for `scopeName` and all referenced included grammars asynchronously.
-		 * Please do not use language id 0.
-		 */
-		loadGrammarWithEmbeddedLanguages(initialScopeName: string, initialLanguage: number, embeddedLanguages: IEmbeddedLanguagesMap): Thenable;
-		/**
-		 * Load the grammar for `scopeName` and all referenced included grammars asynchronously.
-		 * Please do not use language id 0.
-		 */
-		loadGrammarWithConfiguration(initialScopeName: string, initialLanguage: number, configuration: IGrammarConfiguration): Thenable;
-		/**
-		 * Load the grammar for `scopeName` and all referenced included grammars asynchronously.
-		 */
-		loadGrammar(initialScopeName: string): Thenable;
-		private _loadGrammar;
-		/**
-		 * Adds a rawGrammar.
-		 */
-		addGrammar(rawGrammar: IRawGrammar, injections?: string[], initialLanguage?: number, embeddedLanguages?: IEmbeddedLanguagesMap): Thenable;
-		/**
-		 * Get the grammar for `scopeName`. The grammar must first be created via `loadGrammar` or `addGrammar`.
-		 */
-		grammarForScopeName(scopeName: string, initialLanguage?: number, embeddedLanguages?: IEmbeddedLanguagesMap, tokenTypes?: ITokenTypeMap): Thenable;
-	}
-	/**
-	 * A grammar
-	 */
-	export interface IGrammar {
-		/**
-		 * Tokenize `lineText` using previous line state `prevState`.
-		 */
-		tokenizeLine(lineText: string, prevState: StackElement | null): ITokenizeLineResult;
-		/**
-		 * Tokenize `lineText` using previous line state `prevState`.
-		 * The result contains the tokens in binary format, resolved with the following information:
-		 *  - language
-		 *  - token type (regex, string, comment, other)
-		 *  - font style
-		 *  - foreground color
-		 *  - background color
-		 * e.g. for getting the languageId: `(metadata & MetadataConsts.LANGUAGEID_MASK) >>> MetadataConsts.LANGUAGEID_OFFSET`
-		 */
-		tokenizeLine2(lineText: string, prevState: StackElement | null): ITokenizeLineResult2;
-	}
-	export interface ITokenizeLineResult {
-		readonly tokens: IToken[];
-		/**
-		 * The `prevState` to be passed on to the next line tokenization.
-		 */
-		readonly ruleStack: StackElement;
-	}
-	/**
-	 * Helpers to manage the "collapsed" metadata of an entire StackElement stack.
-	 * The following assumptions have been made:
-	 *  - languageId < 256 => needs 8 bits
-	 *  - unique color count < 512 => needs 9 bits
-	 *
-	 * The binary format is:
-	 * - -------------------------------------------
-	 *     3322 2222 2222 1111 1111 1100 0000 0000
-	 *     1098 7654 3210 9876 5432 1098 7654 3210
-	 * - -------------------------------------------
-	 *     xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx
-	 *     bbbb bbbb bfff ffff ffFF FTTT LLLL LLLL
-	 * - -------------------------------------------
-	 *  - L = LanguageId (8 bits)
-	 *  - T = StandardTokenType (3 bits)
-	 *  - F = FontStyle (3 bits)
-	 *  - f = foreground color (9 bits)
-	 *  - b = background color (9 bits)
-	 */
-	export const enum MetadataConsts {
-		LANGUAGEID_MASK = 255,
-		TOKEN_TYPE_MASK = 1792,
-		FONT_STYLE_MASK = 14336,
-		FOREGROUND_MASK = 8372224,
-		BACKGROUND_MASK = 4286578688,
-		LANGUAGEID_OFFSET = 0,
-		TOKEN_TYPE_OFFSET = 8,
-		FONT_STYLE_OFFSET = 11,
-		FOREGROUND_OFFSET = 14,
-		BACKGROUND_OFFSET = 23,
-	}
-	export interface ITokenizeLineResult2 {
-		/**
-		 * The tokens in binary format. Each token occupies two array indices. For token i:
-		 *  - at offset 2*i => startIndex
-		 *  - at offset 2*i + 1 => metadata
-		 *
-		 */
-		readonly tokens: Uint32Array;
-		/**
-		 * The `prevState` to be passed on to the next line tokenization.
-		 */
-		readonly ruleStack: StackElement;
-	}
-	export interface IToken {
-		startIndex: number;
-		readonly endIndex: number;
-		readonly scopes: string[];
-	}
-	/**
-	 * **IMPORTANT** - Immutable!
-	 */
-	export interface StackElement {
-		_stackElementBrand: void;
-		readonly depth: number;
-		clone(): StackElement;
-		equals(other: StackElement): boolean;
-	}
-	export const INITIAL: StackElement;
-	export const parseRawGrammar: (content: string, filePath?: string) => IRawGrammar;
-	export interface ILocation {
-		readonly filename: string;
-		readonly line: number;
-		readonly char: number;
-	}
-	export interface ILocatable {
-		readonly $vscodeTextmateLocation?: ILocation;
-	}
-	export interface IRawGrammar extends ILocatable {
-		repository: IRawRepository;
-		readonly scopeName: string;
-		readonly patterns: IRawRule[];
-		readonly injections?: {
-			[expression: string]: IRawRule;
-		};
-		readonly injectionSelector?: string;
-		readonly fileTypes?: string[];
-		readonly name?: string;
-		readonly firstLineMatch?: string;
-	}
-	export interface IRawRepositoryMap {
-		[name: string]: IRawRule;
-		$self: IRawRule;
-		$base: IRawRule;
-	}
-	export type IRawRepository = IRawRepositoryMap & ILocatable;
-	export interface IRawRule extends ILocatable {
-		id?: number;
-		readonly include?: string;
-		readonly name?: string;
-		readonly contentName?: string;
-		readonly match?: string;
-		readonly captures?: IRawCaptures;
-		readonly begin?: string;
-		readonly beginCaptures?: IRawCaptures;
-		readonly end?: string;
-		readonly endCaptures?: IRawCaptures;
-		readonly while?: string;
-		readonly whileCaptures?: IRawCaptures;
-		readonly patterns?: IRawRule[];
-		readonly repository?: IRawRepository;
-		readonly applyEndPatternLast?: boolean;
-	}
-	export interface IRawCapturesMap {
-		[captureId: string]: IRawRule;
-	}
-	export type IRawCaptures = IRawCapturesMap & ILocatable;
-	export interface IOnigLib {
-		createOnigScanner(sources: string[]): OnigScanner;
-		createOnigString(sources: string): OnigString;
-	}
-	export interface IOnigCaptureIndex {
-		start: number;
-		end: number;
-		length: number;
-	}
-	export interface IOnigMatch {
-		index: number;
-		captureIndices: IOnigCaptureIndex[];
-		scanner: OnigScanner;
-	}
-	export interface OnigScanner {
-		findNextMatchSync(string: string | OnigString, startPosition: number): IOnigMatch;
-	}
-	export interface OnigString {
-		readonly content: string;
-		readonly dispose?: () => void;
-	}
-
-
-}
diff --git a/src/typings/vscode-windows-ca-certs.d.ts b/src/typings/vscode-windows-ca-certs.d.ts
deleted file mode 100644
index 37b2282827ae..000000000000
--- a/src/typings/vscode-windows-ca-certs.d.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- *  Copyright (c) Microsoft Corporation. All rights reserved.
- *  Licensed under the Source EULA. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-
-declare module 'vscode-windows-ca-certs';
diff --git a/src/typings/windows-process-tree.d.ts b/src/typings/windows-process-tree.d.ts
deleted file mode 100644
index 689ddb77d903..000000000000
--- a/src/typings/windows-process-tree.d.ts
+++ /dev/null
@@ -1,63 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- *  Copyright (c) Microsoft Corporation. All rights reserved.
- *  Licensed under the Source EULA. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-
-declare module 'windows-process-tree' {
-	export enum ProcessDataFlag {
-		None = 0,
-		Memory = 1,
-		CommandLine = 2
-	}
-
-	export interface IProcessInfo {
-		pid: number;
-		ppid: number;
-		name: string;
-
-		/**
-		 * The working set size of the process, in bytes.
-		 */
-		memory?: number;
-
-		/**
-		 * The string returned is at most 512 chars, strings exceeding this length are truncated.
-		 */
-		commandLine?: string;
-	}
-
-	export interface IProcessCpuInfo extends IProcessInfo {
-		cpu?: number;
-	}
-
-	export interface IProcessTreeNode {
-		pid: number;
-		name: string;
-		memory?: number;
-		commandLine?: string;
-		children: IProcessTreeNode[];
-	}
-
-	/**
-	 * Returns a tree of processes with the rootPid process as the root.
-	 * @param rootPid - The pid of the process that will be the root of the tree.
-	 * @param callback - The callback to use with the returned list of processes.
-	 * @param flags - The flags for what process data should be included.
-	 */
-	export function getProcessTree(rootPid: number, callback: (tree: IProcessTreeNode) => void, flags?: ProcessDataFlag): void;
-
-	/**
-	 * Returns a list of processes containing the rootPid process and all of its descendants.
-	 * @param rootPid - The pid of the process of interest.
-	 * @param callback - The callback to use with the returned set of processes.
-	 * @param flags - The flags for what process data should be included.
-	 */
-	export function getProcessList(rootPid: number, callback: (processList: IProcessInfo[]) => void, flags?: ProcessDataFlag): void;
-
-	/**
-	 * Returns the list of processes annotated with cpu usage information.
-	 * @param processList - The list of processes.
-	 * @param callback - The callback to use with the returned list of processes.
-	 */
-	export function getProcessCpuUsage(processList: IProcessInfo[], callback: (processListWithCpu: IProcessCpuInfo[]) => void): void;
-}
diff --git a/src/typings/xterm-addon-search.d.ts b/src/typings/xterm-addon-search.d.ts
deleted file mode 100644
index 2f3be6f82bd2..000000000000
--- a/src/typings/xterm-addon-search.d.ts
+++ /dev/null
@@ -1,69 +0,0 @@
-/**
- * Copyright (c) 2017 The xterm.js authors. All rights reserved.
- * @license MIT
- */
-
-// HACK: gulp-tsb doesn't play nice with importing from typings
-// import { Terminal, ITerminalAddon } from 'xterm';
-
-declare module 'xterm-addon-search' {
-	/**
-	 * Options for a search.
-	 */
-	export interface ISearchOptions {
-		/**
-		 * Whether the search term is a regex.
-		 */
-		regex?: boolean;
-
-		/**
-		 * Whether to search for a whole word, the result is only valid if it's
-		 * suppounded in "non-word" characters such as `_`, `(`, `)` or space.
-		 */
-		wholeWord?: boolean;
-
-		/**
-		 * Whether the search is case sensitive.
-		 */
-		caseSensitive?: boolean;
-
-		/**
-		 * Whether to do an indcremental search, this will expand the selection if it
-		 * still matches the term the user typed. Note that this only affects
-		 * `findNext`, not `findPrevious`.
-		 */
-		incremental?: boolean;
-	}
-
-	/**
-	 * An xterm.js addon that provides search functionality.
-	 */
-	export class SearchAddon {
-		/**
-		 * Activates the addon
-		 * @param terminal The terminal the addon is being loaded in.
-		 */
-		public activate(terminal: any): void;
-
-		/**
-		 * Disposes the addon.
-		 */
-		public dispose(): void;
-
-		/**
-		 * Search forwards for the next result that matches the search term and
-		 * options.
-		 * @param term The search term.
-		 * @param searchOptions The options for the search.
-		 */
-		public findNext(term: string, searchOptions?: ISearchOptions): boolean;
-
-		/**
-		 * Search backwards for the previous result that matches the search term and
-		 * options.
-		 * @param term The search term.
-		 * @param searchOptions The options for the search.
-		 */
-		public findPrevious(term: string, searchOptions?: ISearchOptions): boolean;
-	}
-}
diff --git a/src/typings/xterm-addon-web-links.d.ts b/src/typings/xterm-addon-web-links.d.ts
deleted file mode 100644
index da348f8c82eb..000000000000
--- a/src/typings/xterm-addon-web-links.d.ts
+++ /dev/null
@@ -1,71 +0,0 @@
-/**
- * Copyright (c) 2017 The xterm.js authors. All rights reserved.
- * @license MIT
- */
-
-// HACK: gulp-tsb doesn't play nice with importing from typings
-// import { Terminal, ITerminalAddon } from 'xterm';
-interface ILinkMatcherOptions {
-	/**
-	 * The index of the link from the regex.match(text) call. This defaults to 0
-	 * (for regular expressions without capture groups).
-	 */
-	matchIndex?: number;
-
-	/**
-	 * A callback that validates whether to create an individual link, pass
-	 * whether the link is valid to the callback.
-	 */
-	validationCallback?: (uri: string, callback: (isValid: boolean) => void) => void;
-
-	/**
-	 * A callback that fires when the mouse hovers over a link for a moment.
-	 */
-	tooltipCallback?: (event: MouseEvent, uri: string) => boolean | void;
-
-	/**
-	 * A callback that fires when the mouse leaves a link. Note that this can
-	 * happen even when tooltipCallback hasn't fired for the link yet.
-	 */
-	leaveCallback?: () => void;
-
-	/**
-	 * The priority of the link matcher, this defines the order in which the link
-	 * matcher is evaluated relative to others, from highest to lowest. The
-	 * default value is 0.
-	 */
-	priority?: number;
-
-	/**
-	 * A callback that fires when the mousedown and click events occur that
-	 * determines whether a link will be activated upon click. This enables
-	 * only activating a link when a certain modifier is held down, if not the
-	 * mouse event will continue propagation (eg. double click to select word).
-	 */
-	willLinkActivate?: (event: MouseEvent, uri: string) => boolean;
-}
-
-declare module 'xterm-addon-web-links' {
-	/**
-	 * An xterm.js addon that enables web links.
-	 */
-	export class WebLinksAddon {
-		/**
-		 * Creates a new web links addon.
-		 * @param handler The callback when the link is called.
-		 * @param options Options for the link matcher.
-		 */
-		constructor(handler?: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions);
-
-		/**
-		 * Activates the addon
-		 * @param terminal The terminal the addon is being loaded in.
-		 */
-		public activate(terminal: any): void;
-
-		/**
-		 * Disposes the addon.
-		 */
-		public dispose(): void;
-	}
-}
diff --git a/src/typings/xterm.d.ts b/src/typings/xterm.d.ts
deleted file mode 100644
index e163c256726a..000000000000
--- a/src/typings/xterm.d.ts
+++ /dev/null
@@ -1,1098 +0,0 @@
-/**
- * @license MIT
- *
- * This contains the type declarations for the xterm.js library. Note that
- * some interfaces differ between this file and the actual implementation in
- * src/, that's because this file declares the *public* API which is intended
- * to be stable and consumed by external programs.
- */
-
-/// 
-
-declare module 'xterm' {
-	/**
-	 * A string representing text font weight.
-	 */
-	export type FontWeight = 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900';
-
-	/**
-	 * A string representing log level.
-	 */
-	export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'off';
-
-	/**
-	 * A string representing a renderer type.
-	 */
-	export type RendererType = 'dom' | 'canvas';
-
-	/**
-	 * An object containing start up options for the terminal.
-	 */
-	export interface ITerminalOptions {
-		/**
-		 * Whether background should support non-opaque color. It must be set before
-		 * executing the `Terminal.open()` method and can't be changed later without
-		 * executing it again. Note that enabling this can negatively impact
-		 * performance.
-		 */
-		allowTransparency?: boolean;
-
-		/**
-		 * A data uri of the sound to use for the bell when `bellStyle = 'sound'`.
-		 */
-		bellSound?: string;
-
-		/**
-		 * The type of the bell notification the terminal will use.
-		 */
-		bellStyle?: 'none' /*| 'visual'*/ | 'sound' /*| 'both'*/;
-
-		/**
-		 * When enabled the cursor will be set to the beginning of the next line
-		 * with every new line. This equivalent to sending '\r\n' for each '\n'.
-		 * Normally the termios settings of the underlying PTY deals with the
-		 * translation of '\n' to '\r\n' and this setting should not be used. If you
-		 * deal with data from a non-PTY related source, this settings might be
-		 * useful.
-		 */
-		convertEol?: boolean;
-
-		/**
-		 * The number of columns in the terminal.
-		 */
-		cols?: number;
-
-		/**
-		 * Whether the cursor blinks.
-		 */
-		cursorBlink?: boolean;
-
-		/**
-		 * The style of the cursor.
-		 */
-		cursorStyle?: 'block' | 'underline' | 'bar';
-
-		/**
-		 * Whether input should be disabled.
-		 */
-		disableStdin?: boolean;
-
-		/**
-		 * Whether to draw bold text in bright colors. The default is true.
-		 */
-		drawBoldTextInBrightColors?: boolean;
-
-		/**
-		 * The modifier key hold to multiply scroll speed.
-		 */
-		fastScrollModifier?: 'alt' | 'ctrl' | 'shift' | undefined;
-
-		/**
-		 * The scroll speed multiplier used for fast scrolling.
-		 */
-		fastScrollSensitivity?: number;
-
-		/**
-		 * The font size used to render text.
-		 */
-		fontSize?: number;
-
-		/**
-		 * The font family used to render text.
-		 */
-		fontFamily?: string;
-
-		/**
-		 * The font weight used to render non-bold text.
-		 */
-		fontWeight?: FontWeight;
-
-		/**
-		 * The font weight used to render bold text.
-		 */
-		fontWeightBold?: FontWeight;
-
-		/**
-		 * The spacing in whole pixels between characters..
-		 */
-		letterSpacing?: number;
-
-		/**
-		 * The line height used to render text.
-		 */
-		lineHeight?: number;
-
-		/**
-		 * What log level to use, this will log for all levels below and including
-		 * what is set:
-		 *
-		 * 1. debug
-		 * 2. info (default)
-		 * 3. warn
-		 * 4. error
-		 * 5. off
-		 */
-		logLevel?: LogLevel;
-
-		/**
-		 * Whether to treat option as the meta key.
-		 */
-		macOptionIsMeta?: boolean;
-
-		/**
-		 * Whether holding a modifier key will force normal selection behavior,
-		 * regardless of whether the terminal is in mouse events mode. This will
-		 * also prevent mouse events from being emitted by the terminal. For
-		 * example, this allows you to use xterm.js' regular selection inside tmux
-		 * with mouse mode enabled.
-		 */
-		macOptionClickForcesSelection?: boolean;
-
-		/**
-		 * The type of renderer to use, this allows using the fallback DOM renderer
-		 * when canvas is too slow for the environment. The following features do
-		 * not work when the DOM renderer is used:
-		 *
-		 * - Letter spacing
-		 * - Cursor blink
-		 */
-		rendererType?: RendererType;
-
-		/**
-		 * Whether to select the word under the cursor on right click, this is
-		 * standard behavior in a lot of macOS applications.
-		 */
-		rightClickSelectsWord?: boolean;
-
-		/**
-		 * The number of rows in the terminal.
-		 */
-		rows?: number;
-
-		/**
-		 * Whether screen reader support is enabled. When on this will expose
-		 * supporting elements in the DOM to support NVDA on Windows and VoiceOver
-		 * on macOS.
-		 */
-		screenReaderMode?: boolean;
-
-		/**
-		 * The amount of scrollback in the terminal. Scrollback is the amount of
-		 * rows that are retained when lines are scrolled beyond the initial
-		 * viewport.
-		 */
-		scrollback?: number;
-
-		/**
-		 * The scrolling speed multiplier used for adjusting normal scrolling speed.
-		 */
-		scrollSensitivity?: number;
-
-		/**
-		 * The size of tab stops in the terminal.
-		 */
-		tabStopWidth?: number;
-
-		/**
-		 * The color theme of the terminal.
-		 */
-		theme?: ITheme;
-
-		/**
-		 * Whether "Windows mode" is enabled. Because Windows backends winpty and
-		 * conpty operate by doing line wrapping on their side, xterm.js does not
-		 * have access to wrapped lines. When Windows mode is enabled the following
-		 * changes will be in effect:
-		 *
-		 * - Reflow is disabled.
-		 * - Lines are assumed to be wrapped if the last character of the line is
-		 *   not whitespace.
-		 */
-		windowsMode?: boolean;
-
-		/**
-		 * A string containing all characters that are considered word separated by the
-		 * double click to select work logic.
-		*/
-		wordSeparator?: string;
-	}
-
-	/**
-	 * Contains colors to theme the terminal with.
-	 */
-	export interface ITheme {
-		/** The default foreground color */
-		foreground?: string;
-		/** The default background color */
-		background?: string;
-		/** The cursor color */
-		cursor?: string;
-		/** The accent color of the cursor (fg color for a block cursor) */
-		cursorAccent?: string;
-		/** The selection background color (can be transparent) */
-		selection?: string;
-		/** ANSI black (eg. `\x1b[30m`) */
-		black?: string;
-		/** ANSI red (eg. `\x1b[31m`) */
-		red?: string;
-		/** ANSI green (eg. `\x1b[32m`) */
-		green?: string;
-		/** ANSI yellow (eg. `\x1b[33m`) */
-		yellow?: string;
-		/** ANSI blue (eg. `\x1b[34m`) */
-		blue?: string;
-		/** ANSI magenta (eg. `\x1b[35m`) */
-		magenta?: string;
-		/** ANSI cyan (eg. `\x1b[36m`) */
-		cyan?: string;
-		/** ANSI white (eg. `\x1b[37m`) */
-		white?: string;
-		/** ANSI bright black (eg. `\x1b[1;30m`) */
-		brightBlack?: string;
-		/** ANSI bright red (eg. `\x1b[1;31m`) */
-		brightRed?: string;
-		/** ANSI bright green (eg. `\x1b[1;32m`) */
-		brightGreen?: string;
-		/** ANSI bright yellow (eg. `\x1b[1;33m`) */
-		brightYellow?: string;
-		/** ANSI bright blue (eg. `\x1b[1;34m`) */
-		brightBlue?: string;
-		/** ANSI bright magenta (eg. `\x1b[1;35m`) */
-		brightMagenta?: string;
-		/** ANSI bright cyan (eg. `\x1b[1;36m`) */
-		brightCyan?: string;
-		/** ANSI bright white (eg. `\x1b[1;37m`) */
-		brightWhite?: string;
-	}
-
-	/**
-	 * An object containing options for a link matcher.
-	 */
-	export interface ILinkMatcherOptions {
-		/**
-		 * The index of the link from the regex.match(text) call. This defaults to 0
-		 * (for regular expressions without capture groups).
-		 */
-		matchIndex?: number;
-
-		/**
-		 * A callback that validates whether to create an individual link, pass
-		 * whether the link is valid to the callback.
-		 */
-		validationCallback?: (uri: string, callback: (isValid: boolean) => void) => void;
-
-		/**
-		 * A callback that fires when the mouse hovers over a link for a moment.
-		 */
-		tooltipCallback?: (event: MouseEvent, uri: string, location: IViewportRange) => boolean | void;
-
-		/**
-		 * A callback that fires when the mouse leaves a link. Note that this can
-		 * happen even when tooltipCallback hasn't fired for the link yet.
-		 */
-		leaveCallback?: () => void;
-
-		/**
-		 * The priority of the link matcher, this defines the order in which the
-		 * link matcher is evaluated relative to others, from highest to lowest. The
-		 * default value is 0.
-		 */
-		priority?: number;
-
-		/**
-		 * A callback that fires when the mousedown and click events occur that
-		 * determines whether a link will be activated upon click. This enables
-		 * only activating a link when a certain modifier is held down, if not the
-		 * mouse event will continue propagation (eg. double click to select word).
-		 */
-		willLinkActivate?: (event: MouseEvent, uri: string) => boolean;
-	}
-
-	/**
-	 * An object that can be disposed via a dispose function.
-	 */
-	export interface IDisposable {
-		dispose(): void;
-	}
-
-	/**
-	 * An event that can be listened to.
-	 * @returns an `IDisposable` to stop listening.
-	 */
-	export interface IEvent {
-		(listener: (e: T) => any): IDisposable;
-	}
-
-	/**
-	 * Represents a specific line in the terminal that is tracked when scrollback
-	 * is trimmed and lines are added or removed.
-	 */
-	export interface IMarker extends IDisposable {
-		/**
-		 * A unique identifier for this marker.
-		 */
-		readonly id: number;
-
-		/**
-		 * Whether this marker is disposed.
-		 */
-		readonly isDisposed: boolean;
-
-		/**
-		 * The actual line index in the buffer at this point in time.
-		 */
-		readonly line: number;
-	}
-
-	/**
-	 * The set of localizable strings.
-	 */
-	export interface ILocalizableStrings {
-		/**
-		 * The aria label for the underlying input textarea for the terminal.
-		 */
-		promptLabel: string;
-
-		/**
-		 * Announcement for when line reading is suppressed due to too many lines
-		 * being printed to the terminal when `screenReaderMode` is enabled.
-		 */
-		tooMuchOutput: string;
-	}
-
-	/**
-	 * The class that represents an xterm.js terminal.
-	 */
-	export class Terminal implements IDisposable {
-		/**
-		 * The element containing the terminal.
-		 */
-		readonly element: HTMLElement | undefined;
-
-		/**
-		 * The textarea that accepts input for the terminal.
-		 */
-		readonly textarea: HTMLTextAreaElement | undefined;
-
-		/**
-		 * The number of rows in the terminal's viewport. Use
-		 * `ITerminalOptions.rows` to set this in the constructor and
-		 * `Terminal.resize` for when the terminal exists.
-		 */
-		readonly rows: number;
-
-		/**
-		 * The number of columns in the terminal's viewport. Use
-		 * `ITerminalOptions.cols` to set this in the constructor and
-		 * `Terminal.resize` for when the terminal exists.
-		 */
-		readonly cols: number;
-
-		/**
-		 * (EXPERIMENTAL) The terminal's current buffer, this might be either the
-		 * normal buffer or the alt buffer depending on what's running in the
-		 * terminal.
-		 */
-		readonly buffer: IBuffer;
-
-		/**
-		 * (EXPERIMENTAL) Get all markers registered against the buffer. If the alt
-		 * buffer is active this will always return [].
-		 */
-		readonly markers: ReadonlyArray;
-
-		/**
-		 * (EXPERIMENTAL) Get the parser interface to register
-		 * custom escape sequence handlers.
-		 */
-		readonly parser: IParser;
-
-		/**
-		 * Natural language strings that can be localized.
-		 */
-		static strings: ILocalizableStrings;
-
-		/**
-		 * Creates a new `Terminal` object.
-		 *
-		 * @param options An object containing a set of options.
-		 */
-		constructor(options?: ITerminalOptions);
-
-		/**
-		 * Adds an event listener for the cursor moves.
-		 * @returns an `IDisposable` to stop listening.
-		 */
-		onCursorMove: IEvent;
-
-		/**
-		 * Adds an event listener for when a data event fires. This happens for
-		 * example when the user types or pastes into the terminal. The event value
-		 * is whatever `string` results, in a typical setup, this should be passed
-		 * on to the backing pty.
-		 * @returns an `IDisposable` to stop listening.
-		 */
-		onData: IEvent;
-
-		/**
-		 * Adds an event listener for a key is pressed. The event value contains the
-		 * string that will be sent in the data event as well as the DOM event that
-		 * triggered it.
-		 * @returns an `IDisposable` to stop listening.
-		 */
-		onKey: IEvent<{ key: string, domEvent: KeyboardEvent }>;
-
-		/**
-		 * Adds an event listener for when a line feed is added.
-		 * @returns an `IDisposable` to stop listening.
-		 */
-		onLineFeed: IEvent;
-
-		/**
-		 * Adds an event listener for when a scroll occurs. The  event value is the
-		 * new position of the viewport.
-		 * @returns an `IDisposable` to stop listening.
-		 */
-		onScroll: IEvent;
-
-		/**
-		 * Adds an event listener for when a selection change occurs.
-		 * @returns an `IDisposable` to stop listening.
-		 */
-		onSelectionChange: IEvent;
-
-		/**
-		 * Adds an event listener for when rows are rendered. The event value
-		 * contains the start row and end rows of the rendered area (ranges from `0`
-		 * to `Terminal.rows - 1`).
-		 * @returns an `IDisposable` to stop listening.
-		 */
-		onRender: IEvent<{ start: number, end: number }>;
-
-		/**
-		 * Adds an event listener for when the terminal is resized. The event value
-		 * contains the new size.
-		 * @returns an `IDisposable` to stop listening.
-		 */
-		onResize: IEvent<{ cols: number, rows: number }>;
-
-		/**
-		 * Adds an event listener for when an OSC 0 or OSC 2 title change occurs.
-		 * The event value is the new title.
-		 * @returns an `IDisposable` to stop listening.
-		 */
-		onTitleChange: IEvent;
-
-		/**
-		 * Unfocus the terminal.
-		 */
-		blur(): void;
-
-		/**
-		 * Focus the terminal.
-		 */
-		focus(): void;
-
-		/**
-		 * Resizes the terminal. It's best practice to debounce calls to resize,
-		 * this will help ensure that the pty can respond to the resize event
-		 * before another one occurs.
-		 * @param x The number of columns to resize to.
-		 * @param y The number of rows to resize to.
-		 */
-		resize(columns: number, rows: number): void;
-
-		/**
-		 * Opens the terminal within an element.
-		 * @param parent The element to create the terminal within. This element
-		 * must be visible (have dimensions) when `open` is called as several DOM-
-		 * based measurements need to be performed when this function is called.
-		 */
-		open(parent: HTMLElement): void;
-
-		/**
-		 * Attaches a custom key event handler which is run before keys are
-		 * processed, giving consumers of xterm.js ultimate control as to what keys
-		 * should be processed by the terminal and what keys should not.
-		 * @param customKeyEventHandler The custom KeyboardEvent handler to attach.
-		 * This is a function that takes a KeyboardEvent, allowing consumers to stop
-		 * propagation and/or prevent the default action. The function returns
-		 * whether the event should be processed by xterm.js.
-		 */
-		attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void;
-
-		/**
-		 * (EXPERIMENTAL) Registers a link matcher, allowing custom link patterns to
-		 * be matched and handled.
-		 * @param regex The regular expression to search for, specifically this
-		 * searches the textContent of the rows. You will want to use \s to match a
-		 * space ' ' character for example.
-		 * @param handler The callback when the link is called.
-		 * @param options Options for the link matcher.
-		 * @return The ID of the new matcher, this can be used to deregister.
-		 */
-		registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions): number;
-
-		/**
-		 * (EXPERIMENTAL) Deregisters a link matcher if it has been registered.
-		 * @param matcherId The link matcher's ID (returned after register)
-		 */
-		deregisterLinkMatcher(matcherId: number): void;
-
-		/**
-		 * (EXPERIMENTAL) Registers a character joiner, allowing custom sequences of
-		 * characters to be rendered as a single unit. This is useful in particular
-		 * for rendering ligatures and graphemes, among other things.
-		 *
-		 * Each registered character joiner is called with a string of text
-		 * representing a portion of a line in the terminal that can be rendered as
-		 * a single unit. The joiner must return a sorted array, where each entry is
-		 * itself an array of length two, containing the start (inclusive) and end
-		 * (exclusive) index of a substring of the input that should be rendered as
-		 * a single unit. When multiple joiners are provided, the results of each
-		 * are collected. If there are any overlapping substrings between them, they
-		 * are combined into one larger unit that is drawn together.
-		 *
-		 * All character joiners that are registered get called every time a line is
-		 * rendered in the terminal, so it is essential for the handler function to
-		 * run as quickly as possible to avoid slowdowns when rendering. Similarly,
-		 * joiners should strive to return the smallest possible substrings to
-		 * render together, since they aren't drawn as optimally as individual
-		 * characters.
-		 *
-		 * NOTE: character joiners are only used by the canvas renderer.
-		 *
-		 * @param handler The function that determines character joins. It is called
-		 * with a string of text that is eligible for joining and returns an array
-		 * where each entry is an array containing the start (inclusive) and end
-		 * (exclusive) indexes of ranges that should be rendered as a single unit.
-		 * @return The ID of the new joiner, this can be used to deregister
-		 */
-		registerCharacterJoiner(handler: (text: string) => [number, number][]): number;
-
-		/**
-		 * (EXPERIMENTAL) Deregisters the character joiner if one was registered.
-		 * NOTE: character joiners are only used by the canvas renderer.
-		 * @param joinerId The character joiner's ID (returned after register)
-		 */
-		deregisterCharacterJoiner(joinerId: number): void;
-
-		/**
-		 * (EXPERIMENTAL) Adds a marker to the normal buffer and returns it. If the
-		 * alt buffer is active, undefined is returned.
-		 * @param cursorYOffset The y position offset of the marker from the cursor.
-		 */
-		addMarker(cursorYOffset: number): IMarker;
-
-		/**
-		 * Gets whether the terminal has an active selection.
-		 */
-		hasSelection(): boolean;
-
-		/**
-		 * Gets the terminal's current selection, this is useful for implementing
-		 * copy behavior outside of xterm.js.
-		 */
-		getSelection(): string;
-
-		/**
-		 * Gets the selection position or undefined if there is no selection.
-		 */
-		getSelectionPosition(): ISelectionPosition | undefined;
-
-		/**
-		 * Clears the current terminal selection.
-		 */
-		clearSelection(): void;
-
-		/**
-		 * Selects text within the terminal.
-		 * @param column The column the selection starts at..
-		 * @param row The row the selection starts at.
-		 * @param length The length of the selection.
-		 */
-		select(column: number, row: number, length: number): void;
-
-		/**
-		 * Selects all text within the terminal.
-		 */
-		selectAll(): void;
-
-		/**
-		 * Selects text in the buffer between 2 lines.
-		 * @param start The 0-based line index to select from (inclusive).
-		 * @param end The 0-based line index to select to (inclusive).
-		 */
-		selectLines(start: number, end: number): void;
-
-		/*
-		 * Disposes of the terminal, detaching it from the DOM and removing any
-		 * active listeners.
-		 */
-		dispose(): void;
-
-		/**
-		 * Scroll the display of the terminal
-		 * @param amount The number of lines to scroll down (negative scroll up).
-		 */
-		scrollLines(amount: number): void;
-
-		/**
-		 * Scroll the display of the terminal by a number of pages.
-		 * @param pageCount The number of pages to scroll (negative scrolls up).
-		 */
-		scrollPages(pageCount: number): void;
-
-		/**
-		 * Scrolls the display of the terminal to the top.
-		 */
-		scrollToTop(): void;
-
-		/**
-		 * Scrolls the display of the terminal to the bottom.
-		 */
-		scrollToBottom(): void;
-
-		/**
-		 * Scrolls to a line within the buffer.
-		 * @param line The 0-based line index to scroll to.
-		 */
-		scrollToLine(line: number): void;
-
-		/**
-		 * Clear the entire buffer, making the prompt line the new first line.
-		 */
-		clear(): void;
-
-		/**
-		 * Write data to the terminal.
-		 * @param data The data to write to the terminal. This can either be raw
-		 * bytes given as Uint8Array from the pty or a string. Raw bytes will always
-		 * be treated as UTF-8 encoded, string data as UTF-16.
-		 * @param callback Optional callback that fires when the data was processed
-		 * by the parser.
-		 */
-		write(data: string | Uint8Array, callback?: () => void): void;
-
-		/**
-		 * Writes data to the terminal, followed by a break line character (\n).
-		 * @param data The data to write to the terminal. This can either be raw
-		 * bytes given as Uint8Array from the pty or a string. Raw bytes will always
-		 * be treated as UTF-8 encoded, string data as UTF-16.
-		 * @param callback Optional callback that fires when the data was processed
-		 * by the parser.
-		 */
-		writeln(data: string | Uint8Array, callback?: () => void): void;
-
-		/**
-		 * Write UTF8 data to the terminal.
-		 * @param data The data to write to the terminal.
-		 * @param callback Optional callback when data was processed.
-		 * @deprecated use `write` instead
-		 */
-		writeUtf8(data: Uint8Array, callback?: () => void): void;
-
-		/**
-		 * Writes text to the terminal, performing the necessary transformations for pasted text.
-		 * @param data The text to write to the terminal.
-		 */
-		paste(data: string): void;
-
-		/**
-		 * Retrieves an option's value from the terminal.
-		 * @param key The option key.
-		 */
-		getOption(key: 'bellSound' | 'bellStyle' | 'cursorStyle' | 'fontFamily' | 'fontWeight' | 'fontWeightBold' | 'logLevel' | 'rendererType' | 'termName' | 'wordSeparator'): string;
-		/**
-		 * Retrieves an option's value from the terminal.
-		 * @param key The option key.
-		 */
-		getOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'disableStdin' | 'macOptionIsMeta' | 'rightClickSelectsWord' | 'popOnBell' | 'screenKeys' | 'useFlowControl' | 'visualBell' | 'windowsMode'): boolean;
-		/**
-		 * Retrieves an option's value from the terminal.
-		 * @param key The option key.
-		 */
-		getOption(key: 'colors'): string[];
-		/**
-		 * Retrieves an option's value from the terminal.
-		 * @param key The option key.
-		 */
-		getOption(key: 'cols' | 'fontSize' | 'letterSpacing' | 'lineHeight' | 'rows' | 'tabStopWidth' | 'scrollback'): number;
-		/**
-		 * Retrieves an option's value from the terminal.
-		 * @param key The option key.
-		 */
-		getOption(key: 'handler'): (data: string) => void;
-		/**
-		 * Retrieves an option's value from the terminal.
-		 * @param key The option key.
-		 */
-		getOption(key: string): any;
-
-		/**
-		 * Sets an option on the terminal.
-		 * @param key The option key.
-		 * @param value The option value.
-		 */
-		setOption(key: 'fontFamily' | 'termName' | 'bellSound' | 'wordSeparator', value: string): void;
-		/**
-		* Sets an option on the terminal.
-		* @param key The option key.
-		* @param value The option value.
-		*/
-		setOption(key: 'fontWeight' | 'fontWeightBold', value: null | 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900'): void;
-		/**
-		* Sets an option on the terminal.
-		* @param key The option key.
-		* @param value The option value.
-		*/
-		setOption(key: 'logLevel', value: LogLevel): void;
-		/**
-		 * Sets an option on the terminal.
-		 * @param key The option key.
-		 * @param value The option value.
-		 */
-		setOption(key: 'bellStyle', value: null | 'none' | 'visual' | 'sound' | 'both'): void;
-		/**
-		 * Sets an option on the terminal.
-		 * @param key The option key.
-		 * @param value The option value.
-		 */
-		setOption(key: 'cursorStyle', value: null | 'block' | 'underline' | 'bar'): void;
-		/**
-		 * Sets an option on the terminal.
-		 * @param key The option key.
-		 * @param value The option value.
-		 */
-		setOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'disableStdin' | 'macOptionIsMeta' | 'popOnBell' | 'rightClickSelectsWord' | 'screenKeys' | 'useFlowControl' | 'visualBell' | 'windowsMode', value: boolean): void;
-		/**
-		 * Sets an option on the terminal.
-		 * @param key The option key.
-		 * @param value The option value.
-		 */
-		setOption(key: 'colors', value: string[]): void;
-		/**
-		 * Sets an option on the terminal.
-		 * @param key The option key.
-		 * @param value The option value.
-		 */
-		setOption(key: 'fontSize' | 'letterSpacing' | 'lineHeight' | 'tabStopWidth' | 'scrollback', value: number): void;
-		/**
-		 * Sets an option on the terminal.
-		 * @param key The option key.
-		 * @param value The option value.
-		 */
-		setOption(key: 'handler', value: (data: string) => void): void;
-		/**
-		 * Sets an option on the terminal.
-		 * @param key The option key.
-		 * @param value The option value.
-		 */
-		setOption(key: 'theme', value: ITheme): void;
-		/**
-		 * Sets an option on the terminal.
-		 * @param key The option key.
-		 * @param value The option value.
-		 */
-		setOption(key: 'cols' | 'rows', value: number): void;
-		/**
-		 * Sets an option on the terminal.
-		 * @param key The option key.
-		 * @param value The option value.
-		 */
-		setOption(key: string, value: any): void;
-
-		/**
-		 * Tells the renderer to refresh terminal content between two rows
-		 * (inclusive) at the next opportunity.
-		 * @param start The row to start from (between 0 and this.rows - 1).
-		 * @param end The row to end at (between start and this.rows - 1).
-		 */
-		refresh(start: number, end: number): void;
-
-		/**
-		 * Perform a full reset (RIS, aka '\x1bc').
-		 */
-		reset(): void;
-
-		/**
-		 * Loads an addon into this instance of xterm.js.
-		 * @param addon The addon to load.
-		 */
-		loadAddon(addon: ITerminalAddon): void;
-	}
-
-	/**
-	 * An addon that can provide additional functionality to the terminal.
-	 */
-	export interface ITerminalAddon extends IDisposable {
-		/**
-		 * This is called when the addon is activated.
-		 */
-		activate(terminal: Terminal): void;
-	}
-
-	/**
-	 * An object representing a selection within the terminal.
-	 */
-	interface ISelectionPosition {
-		/**
-		 * The start column of the selection.
-		 */
-		startColumn: number;
-
-		/**
-		 * The start row of the selection.
-		 */
-		startRow: number;
-
-		/**
-		 * The end column of the selection.
-		 */
-		endColumn: number;
-
-		/**
-		 * The end row of the selection.
-		 */
-		endRow: number;
-	}
-
-	/**
-	 * An object representing a range within the viewport of the terminal.
-	 */
-	interface IViewportRange {
-		/**
-		 * The start cell of the range.
-		 */
-		start: IViewportCellPosition;
-
-		/**
-		 * The end cell of the range.
-		 */
-		end: IViewportCellPosition;
-	}
-
-	/**
-	 * An object representing a cell position within the viewport of the terminal.
-	 */
-	interface IViewportCellPosition {
-		/**
-		 * The column of the cell. Note that this is 1-based; the first column is column 1.
-		 */
-		col: number;
-
-		/**
-		 * The row of the cell. Note that this is 1-based; the first row is row 1.
-		 */
-		row: number;
-	}
-
-	/**
-	 * Represents a terminal buffer.
-	 */
-	interface IBuffer {
-		/**
-		 * The y position of the cursor. This ranges between `0` (when the
-		 * cursor is at baseY) and `Terminal.rows - 1` (when the cursor is on the
-		 * last row).
-		 */
-		readonly cursorY: number;
-
-		/**
-		 * The x position of the cursor. This ranges between `0` (left side) and
-		 * `Terminal.cols - 1` (right side).
-		 */
-		readonly cursorX: number;
-
-		/**
-		 * The line within the buffer where the top of the viewport is.
-		 */
-		readonly viewportY: number;
-
-		/**
-		 * The line within the buffer where the top of the bottom page is (when
-		 * fully scrolled down);
-		 */
-		readonly baseY: number;
-
-		/**
-		 * The amount of lines in the buffer.
-		 */
-		readonly length: number;
-
-		/**
-		 * Gets a line from the buffer, or undefined if the line index does not
-		 * exist.
-		 *
-		 * Note that the result of this function should be used immediately after
-		 * calling as when the terminal updates it could lead to unexpected
-		 * behavior.
-		 *
-		 * @param y The line index to get.
-		 */
-		getLine(y: number): IBufferLine | undefined;
-	}
-
-	/**
-	 * Represents a line in the terminal's buffer.
-	 */
-	interface IBufferLine {
-		/**
-		 * Whether the line is wrapped from the previous line.
-		 */
-		readonly isWrapped: boolean;
-
-		/**
-		 * Gets a cell from the line, or undefined if the line index does not exist.
-		 *
-		 * Note that the result of this function should be used immediately after
-		 * calling as when the terminal updates it could lead to unexpected
-		 * behavior.
-		 *
-		 * @param x The character index to get.
-		 */
-		getCell(x: number): IBufferCell | undefined;
-
-		/**
-		 * Gets the line as a string. Note that this is gets only the string for the
-		 * line, not taking isWrapped into account.
-		 *
-		 * @param trimRight Whether to trim any whitespace at the right of the line.
-		 * @param startColumn The column to start from (inclusive).
-		 * @param endColumn The column to end at (exclusive).
-		 */
-		translateToString(trimRight?: boolean, startColumn?: number, endColumn?: number): string;
-	}
-
-	/**
-	 * Represents a single cell in the terminal's buffer.
-	 */
-	interface IBufferCell {
-		/**
-		 * The character within the cell.
-		 */
-		readonly char: string;
-
-		/**
-		 * The width of the character. Some examples:
-		 *
-		 * - This is `1` for most cells.
-		 * - This is `2` for wide character like CJK glyphs.
-		 * - This is `0` for cells immediately following cells with a width of `2`.
-		 */
-		readonly width: number;
-	}
-
-	/**
-	 * (EXPERIMENTAL) Data type to register a CSI, DCS or ESC callback in the parser
-	 * in the form:
-	 *    ESC I..I F
-	 *    CSI Prefix P..P I..I F
-	 *    DCS Prefix P..P I..I F data_bytes ST
-	 *
-	 * with these rules/restrictions:
-	 * - prefix can only be used with CSI and DCS
-	 * - only one leading prefix byte is recognized by the parser
-	 *   before any other parameter bytes (P..P)
-	 * - intermediate bytes are recognized up to 2
-	 *
-	 * For custom sequences make sure to read ECMA-48 and the resources at
-	 * vt100.net to not clash with existing sequences or reserved address space.
-	 * General recommendations:
-	 * - use private address space (see ECMA-48)
-	 * - use max one intermediate byte (technically not limited by the spec,
-	 *   in practice there are no sequences with more than one intermediate byte,
-	 *   thus parsers might get confused with more intermediates)
-	 * - test against other common emulators to check whether they escape/ignore
-	 *   the sequence correctly
-	 *
-	 * Notes: OSC command registration is handled differently (see addOscHandler)
-	 *        APC, PM or SOS is currently not supported.
-	 */
-	export interface IFunctionIdentifier {
-		/**
-		 * Optional prefix byte, must be in range \x3c .. \x3f.
-		 * Usable in CSI and DCS.
-		 */
-		prefix?: string;
-		/**
-		 * Optional intermediate bytes, must be in range \x20 .. \x2f.
-		 * Usable in CSI, DCS and ESC.
-		 */
-		intermediates?: string;
-		/**
-		 * Final byte, must be in range \x40 .. \x7e for CSI and DCS,
-		 * \x30 .. \x7e for ESC.
-		 */
-		final: string;
-	}
-
-	/**
-	 * (EXPERIMENTAL) Parser interface.
-	 */
-	export interface IParser {
-		/**
-		 * Adds a handler for CSI escape sequences.
-		 * @param id Specifies the function identifier under which the callback
-		 * gets registered, e.g. {final: 'm'} for SGR.
-		 * @param callback The function to handle the sequence. The callback is
-		 * called with the numerical params. If the sequence has subparams the
-		 * array will contain subarrays with their numercial values.
-		 * Return true if the sequence was handled; false if we should try
-		 * a previous handler (set by addCsiHandler or setCsiHandler).
-		 * The most recently-added handler is tried first.
-		 * @return An IDisposable you can call to remove this handler.
-		 */
-		addCsiHandler(id: IFunctionIdentifier, callback: (params: (number | number[])[]) => boolean): IDisposable;
-
-		/**
-		 * Adds a handler for DCS escape sequences.
-		 * @param id Specifies the function identifier under which the callback
-		 * gets registered, e.g. {intermediates: '$' final: 'q'} for DECRQSS.
-		 * @param callback The function to handle the sequence. Note that the
-		 * function will only be called once if the sequence finished sucessfully.
-		 * There is currently no way to intercept smaller data chunks, data chunks
-		 * will be stored up until the sequence is finished. Since DCS sequences
-		 * are not limited by the amount of data this might impose a problem for
-		 * big payloads. Currently xterm.js limits DCS payload to 10 MB
-		 * which should give enough room for most use cases.
-		 * The function gets the payload and numerical parameters as arguments.
-		 * Return true if the sequence was handled; false if we should try
-		 * a previous handler (set by addDcsHandler or setDcsHandler).
-		 * The most recently-added handler is tried first.
-		 * @return An IDisposable you can call to remove this handler.
-		 */
-		addDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: (number | number[])[]) => boolean): IDisposable;
-
-		/**
-		 * Adds a handler for ESC escape sequences.
-		 * @param id Specifies the function identifier under which the callback
-		 * gets registered, e.g. {intermediates: '%' final: 'G'} for
-		 * default charset selection.
-		 * @param callback The function to handle the sequence.
-		 * Return true if the sequence was handled; false if we should try
-		 * a previous handler (set by addEscHandler or setEscHandler).
-		 * The most recently-added handler is tried first.
-		 * @return An IDisposable you can call to remove this handler.
-		 */
-		addEscHandler(id: IFunctionIdentifier, handler: () => boolean): IDisposable;
-
-		/**
-		 * Adds a handler for OSC escape sequences.
-		 * @param ident The number (first parameter) of the sequence.
-		 * @param callback The function to handle the sequence. Note that the
-		 * function will only be called once if the sequence finished sucessfully.
-		 * There is currently no way to intercept smaller data chunks, data chunks
-		 * will be stored up until the sequence is finished. Since OSC sequences
-		 * are not limited by the amount of data this might impose a problem for
-		 * big payloads. Currently xterm.js limits OSC payload to 10 MB
-		 * which should give enough room for most use cases.
-		 * The callback is called with OSC data string.
-		 * Return true if the sequence was handled; false if we should try
-		 * a previous handler (set by addOscHandler or setOscHandler).
-		 * The most recently-added handler is tried first.
-		 * @return An IDisposable you can call to remove this handler.
-		 */
-		addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable;
-	}
-}
diff --git a/src/typings/yauzl.d.ts b/src/typings/yauzl.d.ts
deleted file mode 100644
index 93163a2f213c..000000000000
--- a/src/typings/yauzl.d.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- *  Copyright (c) Microsoft Corporation. All rights reserved.
- *  Licensed under the Source EULA. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-
-declare module 'yauzl' {
-
-	import { EventEmitter } from 'events';
-	import { Readable } from 'stream';
-
-	export class Entry {
-		fileName: string;
-		extraFields: { id: number; data: Buffer; }[];
-		comment: string;
-		versionMadeBy: number;
-		versionNeededToExtract: number;
-		generalPurposeBitFlag: number;
-		compressionMethod: number;
-		lastModFileTime: number;
-		lastModFileDate: number;
-		crc32: number;
-		compressedSize: number;
-		uncompressedSize: number;
-		fileNameLength: number;
-		extraFieldLength: number;
-		fileCommentLength: number;
-		internalFileAttributes: number;
-		externalFileAttributes: number;
-		relativeOffsetOfLocalHeader: number;
-		getLastModDate(): Date;
-	}
-
-	export class ZipFile extends EventEmitter {
-		readEntry(): void;
-		openReadStream(entry: Entry, callback: (err?: Error, stream?: Readable) => void): void;
-		close(): void;
-		isOpen: boolean;
-		entryCount: number;
-		comment: string;
-	}
-
-	export interface IOptions {
-		autoClose?: boolean;
-		lazyEntries?: boolean;
-	}
-
-	export function open(path: string, callback: (err?: Error, zipfile?: ZipFile) => void): void;
-	export function open(path: string, options: IOptions | undefined, callback: (err?: Error, zipfile?: ZipFile) => void): void;
-}
\ No newline at end of file
diff --git a/src/vs/base/browser/browser.ts b/src/vs/base/browser/browser.ts
index d683c99d5550..e5600bb4f4f4 100644
--- a/src/vs/base/browser/browser.ts
+++ b/src/vs/base/browser/browser.ts
@@ -123,20 +123,3 @@ export const isWebkitWebView = (!isChrome && !isSafari && isWebKit);
 export const isIPad = (userAgent.indexOf('iPad') >= 0);
 export const isEdgeWebView = isEdge && (userAgent.indexOf('WebView/') >= 0);
 export const isStandalone = (window.matchMedia && window.matchMedia('(display-mode: standalone)').matches);
-
-export function hasClipboardSupport() {
-	if (isIE) {
-		return false;
-	}
-
-	if (isEdge) {
-		let index = userAgent.indexOf('Edge/');
-		let version = parseInt(userAgent.substring(index + 5, userAgent.indexOf('.', index)), 10);
-
-		if (!version || (version >= 12 && version <= 16)) {
-			return false;
-		}
-	}
-
-	return true;
-}
diff --git a/src/vs/base/browser/canIUse.ts b/src/vs/base/browser/canIUse.ts
new file mode 100644
index 000000000000..c44476cd5b3c
--- /dev/null
+++ b/src/vs/base/browser/canIUse.ts
@@ -0,0 +1,60 @@
+/*---------------------------------------------------------------------------------------------
+ *  Copyright (c) Microsoft Corporation. All rights reserved.
+ *  Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import * as browser from 'vs/base/browser/browser';
+import * as platform from 'vs/base/common/platform';
+
+export const enum KeyboardSupport {
+	Always,
+	FullScreen,
+	None
+}
+
+/**
+ * Browser feature we can support in current platform, browser and environment.
+ */
+export const BrowserFeatures = {
+	clipboard: {
+		writeText: (
+			platform.isNative
+			|| (document.queryCommandSupported && document.queryCommandSupported('copy'))
+			|| !!(navigator && navigator.clipboard && navigator.clipboard.writeText)
+		),
+		readText: (
+			platform.isNative
+			|| !!(navigator && navigator.clipboard && navigator.clipboard.readText)
+		),
+		richText: (() => {
+			if (browser.isIE) {
+				return false;
+			}
+
+			if (browser.isEdge) {
+				let index = navigator.userAgent.indexOf('Edge/');
+				let version = parseInt(navigator.userAgent.substring(index + 5, navigator.userAgent.indexOf('.', index)), 10);
+
+				if (!version || (version >= 12 && version <= 16)) {
+					return false;
+				}
+			}
+
+			return true;
+		})()
+	},
+	keyboard: (() => {
+		if (platform.isNative || browser.isStandalone) {
+			return KeyboardSupport.Always;
+		}
+
+		if ((navigator).keyboard || browser.isSafari) {
+			return KeyboardSupport.FullScreen;
+		}
+
+		return KeyboardSupport.None;
+	})(),
+
+	touch: 'ontouchstart' in window || navigator.maxTouchPoints > 0 || window.navigator.msMaxTouchPoints > 0,
+	pointerEvents: window.PointerEvent && ('ontouchstart' in window || window.navigator.maxTouchPoints > 0 || navigator.maxTouchPoints > 0 || window.navigator.msMaxTouchPoints > 0)
+};
diff --git a/src/vs/base/browser/contextmenu.ts b/src/vs/base/browser/contextmenu.ts
index 184b9c6f1f39..c52d1e8129d2 100644
--- a/src/vs/base/browser/contextmenu.ts
+++ b/src/vs/base/browser/contextmenu.ts
@@ -10,10 +10,10 @@ import { SubmenuAction } from 'vs/base/browser/ui/menu/menu';
 import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview';
 
 export interface IContextMenuEvent {
-	shiftKey?: boolean;
-	ctrlKey?: boolean;
-	altKey?: boolean;
-	metaKey?: boolean;
+	readonly shiftKey?: boolean;
+	readonly ctrlKey?: boolean;
+	readonly altKey?: boolean;
+	readonly metaKey?: boolean;
 }
 
 export class ContextSubMenu extends SubmenuAction {
diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts
index e322f00ad868..7a886f900abf 100644
--- a/src/vs/base/browser/dom.ts
+++ b/src/vs/base/browser/dom.ts
@@ -16,6 +16,7 @@ import * as platform from 'vs/base/common/platform';
 import { coalesce } from 'vs/base/common/arrays';
 import { URI } from 'vs/base/common/uri';
 import { Schemas, RemoteAuthorities } from 'vs/base/common/network';
+import { BrowserFeatures } from 'vs/base/browser/canIUse';
 
 export function clearNode(node: HTMLElement): void {
 	while (node.firstChild) {
@@ -266,6 +267,23 @@ export let addStandardDisposableListener: IAddStandardDisposableListenerSignatur
 	return addDisposableListener(node, type, wrapHandler, useCapture);
 };
 
+export let addStandardDisposableGenericMouseDownListner = function addStandardDisposableListener(node: HTMLElement, handler: (event: any) => void, useCapture?: boolean): IDisposable {
+	let wrapHandler = _wrapAsStandardMouseEvent(handler);
+
+	return addDisposableGenericMouseDownListner(node, wrapHandler, useCapture);
+};
+
+export function addDisposableGenericMouseDownListner(node: EventTarget, handler: (event: any) => void, useCapture?: boolean): IDisposable {
+	return addDisposableListener(node, platform.isIOS && BrowserFeatures.pointerEvents ? EventType.POINTER_DOWN : EventType.MOUSE_DOWN, handler, useCapture);
+}
+
+export function addDisposableGenericMouseMoveListner(node: EventTarget, handler: (event: any) => void, useCapture?: boolean): IDisposable {
+	return addDisposableListener(node, platform.isIOS && BrowserFeatures.pointerEvents ? EventType.POINTER_MOVE : EventType.MOUSE_MOVE, handler, useCapture);
+}
+
+export function addDisposableGenericMouseUpListner(node: EventTarget, handler: (event: any) => void, useCapture?: boolean): IDisposable {
+	return addDisposableListener(node, platform.isIOS && BrowserFeatures.pointerEvents ? EventType.POINTER_UP : EventType.MOUSE_UP, handler, useCapture);
+}
 export function addDisposableNonBubblingMouseOutListener(node: Element, handler: (event: MouseEvent) => void): IDisposable {
 	return addDisposableListener(node, 'mouseout', (e: MouseEvent) => {
 		// Mouse out bubbles, so this is an attempt to ignore faux mouse outs coming from children elements
@@ -281,6 +299,21 @@ export function addDisposableNonBubblingMouseOutListener(node: Element, handler:
 	});
 }
 
+export function addDisposableNonBubblingPointerOutListener(node: Element, handler: (event: MouseEvent) => void): IDisposable {
+	return addDisposableListener(node, 'pointerout', (e: MouseEvent) => {
+		// Mouse out bubbles, so this is an attempt to ignore faux mouse outs coming from children elements
+		let toElement: Node | null = (e.relatedTarget || e.target);
+		while (toElement && toElement !== node) {
+			toElement = toElement.parentNode;
+		}
+		if (toElement === node) {
+			return;
+		}
+
+		handler(e);
+	});
+}
+
 interface IRequestAnimationFrame {
 	(callback: (time: number) => void): number;
 }
@@ -428,7 +461,7 @@ export interface DOMEvent {
 }
 
 const MINIMUM_TIME_MS = 16;
-const DEFAULT_EVENT_MERGER: IEventMerger = function (lastEvent: DOMEvent, currentEvent: DOMEvent) {
+const DEFAULT_EVENT_MERGER: IEventMerger = function (lastEvent: DOMEvent | null, currentEvent: DOMEvent) {
 	return currentEvent;
 };
 
@@ -477,6 +510,20 @@ export function getClientArea(element: HTMLElement): Dimension {
 		return new Dimension(element.clientWidth, element.clientHeight);
 	}
 
+	// If visual view port exits and it's on mobile, it should be used instead of window innerWidth / innerHeight, or document.body.clientWidth / document.body.clientHeight
+	if (platform.isIOS && (window).visualViewport) {
+		const width = (window).visualViewport.width;
+		const height = (window).visualViewport.height - (
+			browser.isStandalone
+				// in PWA mode, the visual viewport always includes the safe-area-inset-bottom (which is for the home indicator)
+				// even when you are using the onscreen monitor, the visual viewport will include the area between system statusbar and the onscreen keyboard
+				// plus the area between onscreen keyboard and the bottom bezel, which is 20px on iOS.
+				? (20 + 4) // + 4px for body margin
+				: 0
+		);
+		return new Dimension(width, height);
+	}
+
 	// Try innerWidth / innerHeight
 	if (window.innerWidth && window.innerHeight) {
 		return new Dimension(window.innerWidth, window.innerHeight);
@@ -852,6 +899,9 @@ export const EventType = {
 	MOUSE_OUT: 'mouseout',
 	MOUSE_ENTER: 'mouseenter',
 	MOUSE_LEAVE: 'mouseleave',
+	POINTER_UP: 'pointerup',
+	POINTER_DOWN: 'pointerdown',
+	POINTER_MOVE: 'pointermove',
 	CONTEXT_MENU: 'contextmenu',
 	WHEEL: 'wheel',
 	// Keyboard
@@ -1029,6 +1079,11 @@ function _$(namespace: Namespace, description: string, attrs?
 
 	Object.keys(attrs).forEach(name => {
 		const value = attrs![name];
+
+		if (typeof value === 'undefined') {
+			return;
+		}
+
 		if (/^on\w+$/.test(name)) {
 			(result)[name] = value;
 		} else if (name === 'selected') {
@@ -1212,3 +1267,18 @@ export function asCSSUrl(uri: URI): string {
 	}
 	return `url('${asDomUri(uri).toString(true).replace(/'/g, '%27')}')`;
 }
+
+export function triggerDownload(uri: URI, name: string): void {
+	// In order to download from the browser, the only way seems
+	// to be creating a  element with download attribute that
+	// points to the file to download.
+	// See also https://developers.google.com/web/updates/2011/08/Downloading-resources-in-HTML5-a-download
+	const anchor = document.createElement('a');
+	document.body.appendChild(anchor);
+	anchor.download = name;
+	anchor.href = uri.toString(true);
+	anchor.click();
+
+	// Ensure to remove the element from DOM eventually
+	setTimeout(() => document.body.removeChild(anchor));
+}
diff --git a/src/vs/base/browser/fastDomNode.ts b/src/vs/base/browser/fastDomNode.ts
index 5e12e5b7c26a..202be357f35a 100644
--- a/src/vs/base/browser/fastDomNode.ts
+++ b/src/vs/base/browser/fastDomNode.ts
@@ -26,6 +26,7 @@ export class FastDomNode {
 	private _position: string;
 	private _visibility: string;
 	private _layerHint: boolean;
+	private _contain: 'none' | 'strict' | 'content' | 'size' | 'layout' | 'style' | 'paint';
 
 	constructor(domNode: T) {
 		this.domNode = domNode;
@@ -47,6 +48,7 @@ export class FastDomNode {
 		this._position = '';
 		this._visibility = '';
 		this._layerHint = false;
+		this._contain = 'none';
 	}
 
 	public setMaxWidth(maxWidth: number): void {
@@ -203,7 +205,15 @@ export class FastDomNode {
 			return;
 		}
 		this._layerHint = layerHint;
-		(this.domNode.style).willChange = this._layerHint ? 'transform' : 'auto';
+		this.domNode.style.transform = this._layerHint ? 'translate3d(0px, 0px, 0px)' : '';
+	}
+
+	public setContain(contain: 'none' | 'strict' | 'content' | 'size' | 'layout' | 'style' | 'paint'): void {
+		if (this._contain === contain) {
+			return;
+		}
+		this._contain = contain;
+		(this.domNode.style).contain = this._contain;
 	}
 
 	public setAttribute(name: string, value: string): void {
diff --git a/src/vs/base/browser/globalMouseMoveMonitor.ts b/src/vs/base/browser/globalMouseMoveMonitor.ts
index b29d574cfd40..98a5274b1942 100644
--- a/src/vs/base/browser/globalMouseMoveMonitor.ts
+++ b/src/vs/base/browser/globalMouseMoveMonitor.ts
@@ -4,9 +4,11 @@
  *--------------------------------------------------------------------------------------------*/
 
 import * as dom from 'vs/base/browser/dom';
+import * as platform from 'vs/base/common/platform';
 import { IframeUtils } from 'vs/base/browser/iframe';
 import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
 import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle';
+import { BrowserFeatures } from 'vs/base/browser/canIUse';
 
 export interface IStandardMouseMoveEventData {
 	leftButton: boolean;
@@ -15,7 +17,7 @@ export interface IStandardMouseMoveEventData {
 }
 
 export interface IEventMerger {
-	(lastEvent: R, currentEvent: MouseEvent): R;
+	(lastEvent: R | null, currentEvent: MouseEvent): R;
 }
 
 export interface IMouseMoveCallback {
@@ -26,7 +28,7 @@ export interface IOnStopCallback {
 	(): void;
 }
 
-export function standardMouseMoveMerger(lastEvent: IStandardMouseMoveEventData, currentEvent: MouseEvent): IStandardMouseMoveEventData {
+export function standardMouseMoveMerger(lastEvent: IStandardMouseMoveEventData | null, currentEvent: MouseEvent): IStandardMouseMoveEventData {
 	let ev = new StandardMouseEvent(currentEvent);
 	ev.preventDefault();
 	return {
@@ -38,10 +40,10 @@ export function standardMouseMoveMerger(lastEvent: IStandardMouseMoveEventData,
 
 export class GlobalMouseMoveMonitor implements IDisposable {
 
-	private readonly hooks = new DisposableStore();
-	private mouseMoveEventMerger: IEventMerger | null = null;
-	private mouseMoveCallback: IMouseMoveCallback | null = null;
-	private onStopCallback: IOnStopCallback | null = null;
+	protected readonly hooks = new DisposableStore();
+	protected mouseMoveEventMerger: IEventMerger | null = null;
+	protected mouseMoveCallback: IMouseMoveCallback | null = null;
+	protected onStopCallback: IOnStopCallback | null = null;
 
 	public dispose(): void {
 		this.stopMonitoring(false);
@@ -84,12 +86,14 @@ export class GlobalMouseMoveMonitor implements IDisposable {
 		this.onStopCallback = onStopCallback;
 
 		let windowChain = IframeUtils.getSameOriginWindowChain();
+		const mouseMove = platform.isIOS && BrowserFeatures.pointerEvents ? 'pointermove' : 'mousemove';
+		const mouseUp = platform.isIOS && BrowserFeatures.pointerEvents ? 'pointerup' : 'mouseup';
 		for (const element of windowChain) {
-			this.hooks.add(dom.addDisposableThrottledListener(element.window.document, 'mousemove',
+			this.hooks.add(dom.addDisposableThrottledListener(element.window.document, mouseMove,
 				(data: R) => this.mouseMoveCallback!(data),
-				(lastEvent: R, currentEvent) => this.mouseMoveEventMerger!(lastEvent, currentEvent as MouseEvent)
+				(lastEvent: R | null, currentEvent) => this.mouseMoveEventMerger!(lastEvent, currentEvent as MouseEvent)
 			));
-			this.hooks.add(dom.addDisposableListener(element.window.document, 'mouseup', (e: MouseEvent) => this.stopMonitoring(true)));
+			this.hooks.add(dom.addDisposableListener(element.window.document, mouseUp, (e: MouseEvent) => this.stopMonitoring(true)));
 		}
 
 		if (IframeUtils.hasDifferentOriginAncestor()) {
diff --git a/src/vs/base/browser/markdownRenderer.ts b/src/vs/base/browser/markdownRenderer.ts
index cc6912982270..b75bdaae7ab3 100644
--- a/src/vs/base/browser/markdownRenderer.ts
+++ b/src/vs/base/browser/markdownRenderer.ts
@@ -15,6 +15,7 @@ import { cloneAndChange } from 'vs/base/common/objects';
 import { escape } from 'vs/base/common/strings';
 import { URI } from 'vs/base/common/uri';
 import { Schemas } from 'vs/base/common/network';
+import { renderCodicons } from 'vs/base/browser/ui/codiconLabel/codiconLabel';
 
 export interface MarkdownRenderOptions extends FormattedTextRenderOptions {
 	codeBlockRenderer?: (modeId: string, value: string) => Promise;
@@ -50,28 +51,32 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
 	const _href = function (href: string, isDomUri: boolean): string {
 		const data = markdown.uris && markdown.uris[href];
 		if (!data) {
-			return href;
+			return href; // no uri exists
 		}
 		let uri = URI.revive(data);
+		if (URI.parse(href).toString() === uri.toString()) {
+			return href; // no tranformation performed
+		}
 		if (isDomUri) {
 			uri = DOM.asDomUri(uri);
 		}
 		if (uri.query) {
 			uri = uri.with({ query: _uriMassage(uri.query) });
 		}
-		if (data) {
-			href = uri.toString(true);
-		}
-		return href;
+		return uri.toString(true);
 	};
 
 	// signal to code-block render that the
 	// element has been created
 	let signalInnerHTML: () => void;
-	const withInnerHTML = new Promise(c => signalInnerHTML = c);
+	const withInnerHTML = new Promise(c => signalInnerHTML = c);
 
 	const renderer = new marked.Renderer();
 	renderer.image = (href: string, title: string, text: string) => {
+		if (href && href.indexOf('vscode-icon://codicon/') === 0) {
+			return renderCodicons(`$(${URI.parse(href).path.substr(1)})`);
+		}
+
 		let dimensions: string[] = [];
 		let attributes: string[] = [];
 		if (href) {
@@ -187,7 +192,7 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
 		renderer
 	};
 
-	const allowedSchemes = [Schemas.http, Schemas.https, Schemas.mailto, Schemas.data, Schemas.file, Schemas.vscodeRemote];
+	const allowedSchemes = [Schemas.http, Schemas.https, Schemas.mailto, Schemas.data, Schemas.file, Schemas.vscodeRemote, Schemas.vscodeRemoteResource];
 	if (markdown.isTrusted) {
 		allowedSchemes.push(Schemas.command);
 	}
@@ -199,7 +204,8 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
 			'a': ['href', 'name', 'target', 'data-href'],
 			'iframe': ['allowfullscreen', 'frameborder', 'src'],
 			'img': ['src', 'title', 'alt', 'width', 'height'],
-			'div': ['class', 'data-code']
+			'div': ['class', 'data-code'],
+			'span': ['class']
 		}
 	});
 
diff --git a/src/vs/base/browser/touch.ts b/src/vs/base/browser/touch.ts
index 4ab9f1ad0842..fee95cb4c769 100644
--- a/src/vs/base/browser/touch.ts
+++ b/src/vs/base/browser/touch.ts
@@ -33,6 +33,7 @@ export interface GestureEvent extends MouseEvent {
 	translationY: number;
 	pageX: number;
 	pageY: number;
+	tapCount: number;
 }
 
 interface Touch {
@@ -71,16 +72,24 @@ export class Gesture extends Disposable {
 
 	private dispatched = false;
 	private targets: HTMLElement[];
+	private ignoreTargets: HTMLElement[];
 	private handle: IDisposable | null;
 
 	private activeTouches: { [id: number]: TouchData; };
 
+	private _lastSetTapCountTime: number;
+
+	private static readonly CLEAR_TAP_COUNT_TIME = 400; // ms
+
+
 	private constructor() {
 		super();
 
 		this.activeTouches = {};
 		this.handle = null;
 		this.targets = [];
+		this.ignoreTargets = [];
+		this._lastSetTapCountTime = 0;
 		this._register(DomUtils.addDisposableListener(document, 'touchstart', (e: TouchEvent) => this.onTouchStart(e)));
 		this._register(DomUtils.addDisposableListener(document, 'touchend', (e: TouchEvent) => this.onTouchEnd(e)));
 		this._register(DomUtils.addDisposableListener(document, 'touchmove', (e: TouchEvent) => this.onTouchMove(e)));
@@ -103,6 +112,23 @@ export class Gesture extends Disposable {
 		};
 	}
 
+	public static ignoreTarget(element: HTMLElement): IDisposable {
+		if (!Gesture.isTouchDevice()) {
+			return Disposable.None;
+		}
+		if (!Gesture.INSTANCE) {
+			Gesture.INSTANCE = new Gesture();
+		}
+
+		Gesture.INSTANCE.ignoreTargets.push(element);
+
+		return {
+			dispose: () => {
+				Gesture.INSTANCE.ignoreTargets = Gesture.INSTANCE.ignoreTargets.filter(t => t !== element);
+			}
+		};
+	}
+
 	@memoize
 	private static isTouchDevice(): boolean {
 		return 'ontouchstart' in window as any || navigator.maxTouchPoints > 0 || window.navigator.msMaxTouchPoints > 0;
@@ -224,10 +250,33 @@ export class Gesture extends Disposable {
 		let event = (document.createEvent('CustomEvent'));
 		event.initEvent(type, false, true);
 		event.initialTarget = initialTarget;
+		event.tapCount = 0;
 		return event;
 	}
 
 	private dispatchEvent(event: GestureEvent): void {
+		if (event.type === EventType.Tap) {
+			const currentTime = (new Date()).getTime();
+			let setTapCount = 0;
+			if (currentTime - this._lastSetTapCountTime > Gesture.CLEAR_TAP_COUNT_TIME) {
+				setTapCount = 1;
+			} else {
+				setTapCount = 2;
+			}
+
+			this._lastSetTapCountTime = currentTime;
+			event.tapCount = setTapCount;
+		} else if (event.type === EventType.Change || event.type === EventType.Contextmenu) {
+			// tap is canceled by scrolling or context menu
+			this._lastSetTapCountTime = 0;
+		}
+
+		for (let i = 0; i < this.ignoreTargets.length; i++) {
+			if (event.initialTarget instanceof Node && this.ignoreTargets[i].contains(event.initialTarget)) {
+				return;
+			}
+		}
+
 		this.targets.forEach(target => {
 			if (event.initialTarget instanceof Node && target.contains(event.initialTarget)) {
 				target.dispatchEvent(event);
diff --git a/src/vs/base/browser/ui/actionbar/actionbar.css b/src/vs/base/browser/ui/actionbar/actionbar.css
index 1d162aa21f3c..d93c4f6f06d4 100644
--- a/src/vs/base/browser/ui/actionbar/actionbar.css
+++ b/src/vs/base/browser/ui/actionbar/actionbar.css
@@ -90,4 +90,5 @@
 	display: flex;
 	align-items: center;
 	justify-content: center;
+	margin-right: 10px;
 }
diff --git a/src/vs/base/browser/ui/actionbar/actionbar.ts b/src/vs/base/browser/ui/actionbar/actionbar.ts
index 7c29d8a942b5..8dc4c5ed2f26 100644
--- a/src/vs/base/browser/ui/actionbar/actionbar.ts
+++ b/src/vs/base/browser/ui/actionbar/actionbar.ts
@@ -16,6 +16,8 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
 import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
 import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview';
 import { Event, Emitter } from 'vs/base/common/event';
+import { DataTransfers } from 'vs/base/browser/dnd';
+import { isFirefox } from 'vs/base/browser/browser';
 
 export interface IActionViewItem extends IDisposable {
 	actionRunner: IActionRunner;
@@ -113,6 +115,11 @@ export class BaseActionViewItem extends Disposable implements IActionViewItem {
 		const enableDragging = this.options && this.options.draggable;
 		if (enableDragging) {
 			container.draggable = true;
+
+			if (isFirefox) {
+				// Firefox: requires to set a text data transfer to get going
+				this._register(DOM.addDisposableListener(container, DOM.EventType.DRAG_START, e => e.dataTransfer?.setData(DataTransfers.TEXT, this._action.label)));
+			}
 		}
 
 		this._register(DOM.addDisposableListener(element, EventType.Tap, e => this.onClick(e)));
diff --git a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.css b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.css
index e4b41ab482cc..4ccd6ad7b269 100644
--- a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.css
+++ b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.css
@@ -5,6 +5,8 @@
 
 .monaco-breadcrumbs {
 	user-select: none;
+	-webkit-user-select: none;
+	-ms-user-select: none;
 	display: flex;
 	flex-direction: row;
 	flex-wrap: nowrap;
@@ -23,6 +25,10 @@
 	outline: none;
 }
 
+.monaco-breadcrumbs .monaco-breadcrumb-item .codicon-chevron-right {
+	color: inherit;
+}
+
 .monaco-breadcrumbs .monaco-breadcrumb-item:first-of-type::before {
 	content: ' ';
 }
diff --git a/src/vs/base/browser/ui/button/button.css b/src/vs/base/browser/ui/button/button.css
index 09f83b2e700e..104257ed9c0d 100644
--- a/src/vs/base/browser/ui/button/button.css
+++ b/src/vs/base/browser/ui/button/button.css
@@ -4,7 +4,6 @@
  *--------------------------------------------------------------------------------------------*/
 
 .monaco-text-button {
-	-moz-box-sizing: border-box;
 	box-sizing: border-box;
 	display: inline-block;
 	width: 100%;
@@ -21,4 +20,4 @@
 .monaco-button.disabled {
 	opacity: 0.4;
 	cursor: default;
-}
\ No newline at end of file
+}
diff --git a/src/vs/base/browser/ui/centered/centeredViewLayout.ts b/src/vs/base/browser/ui/centered/centeredViewLayout.ts
index a6affaab3e94..64eabc1900bf 100644
--- a/src/vs/base/browser/ui/centered/centeredViewLayout.ts
+++ b/src/vs/base/browser/ui/centered/centeredViewLayout.ts
@@ -42,7 +42,7 @@ function toSplitViewView(view: IView, getHeight: () => number): ISplitViewView {
 		get maximumSize() { return view.maximumWidth; },
 		get minimumSize() { return view.minimumWidth; },
 		onDidChange: Event.map(view.onDidChange, e => e && e.width),
-		layout: size => view.layout(size, getHeight(), Orientation.HORIZONTAL)
+		layout: (size, offset) => view.layout(size, getHeight(), 0, offset)
 	};
 }
 
@@ -81,7 +81,7 @@ export class CenteredViewLayout implements IDisposable {
 				this.resizeMargins();
 			}
 		} else {
-			this.view.layout(width, height, Orientation.HORIZONTAL);
+			this.view.layout(width, height, 0, 0);
 		}
 		this.didLayout = true;
 	}
diff --git a/src/vs/base/browser/ui/checkbox/checkbox.css b/src/vs/base/browser/ui/checkbox/checkbox.css
index 9e592e1e2091..39b792326d66 100644
--- a/src/vs/base/browser/ui/checkbox/checkbox.css
+++ b/src/vs/base/browser/ui/checkbox/checkbox.css
@@ -13,19 +13,10 @@
 	height: 20px;
 	border: 1px solid transparent;
 	padding: 1px;
-
-	-webkit-box-sizing:	border-box;
-	-o-box-sizing:		border-box;
-	-moz-box-sizing:	border-box;
-	-ms-box-sizing:		border-box;
-	box-sizing:			border-box;
-
+	box-sizing:	border-box;
+	user-select: none;
 	-webkit-user-select: none;
-	-khtml-user-select: none;
-	-moz-user-select: none;
-	-o-user-select: none;
 	-ms-user-select: none;
-	user-select: none;
 }
 
 .monaco-custom-checkbox:hover,
diff --git a/src/vs/base/browser/ui/checkbox/checkbox.ts b/src/vs/base/browser/ui/checkbox/checkbox.ts
index e74d09c7e344..f91c4fe1cb22 100644
--- a/src/vs/base/browser/ui/checkbox/checkbox.ts
+++ b/src/vs/base/browser/ui/checkbox/checkbox.ts
@@ -113,6 +113,8 @@ export class Checkbox extends Widget {
 			ev.preventDefault();
 		});
 
+		this.ignoreGesture(this.domNode);
+
 		this.onkeydown(this.domNode, (keyboardEvent) => {
 			if (keyboardEvent.keyCode === KeyCode.Space || keyboardEvent.keyCode === KeyCode.Enter) {
 				this.checked = !this._checked;
diff --git a/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css b/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css
index 494225e6471a..0e31a773e9eb 100644
--- a/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css
+++ b/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css
@@ -5,7 +5,7 @@
 
 @font-face {
 	font-family: "codicon";
-	src: url("./codicon.ttf?3a05fcfc657285cdb4cd3eba790b7462") format("truetype");
+	src: url("./codicon.ttf?c4e66586cd3ad4acc55fc456c0760dec") format("truetype");
 }
 
 .codicon[class*='codicon-'] {
@@ -16,10 +16,9 @@
 	text-align: center;
 	-webkit-font-smoothing: antialiased;
 	-moz-osx-font-smoothing: grayscale;
+	user-select: none;
 	-webkit-user-select: none;
-	-moz-user-select: none;
 	-ms-user-select: none;
-	user-select: none;
 }
 
 
@@ -70,6 +69,10 @@
 .codicon-eye-watch:before { content: "\ea70" }
 .codicon-circle-filled:before { content: "\ea71" }
 .codicon-primitive-dot:before { content: "\ea71" }
+.codicon-close-dirty:before { content: "\ea71" }
+.codicon-debug-breakpoint:before { content: "\ea71" }
+.codicon-debug-breakpoint-disabled:before { content: "\ea71" }
+.codicon-debug-hint:before { content: "\ea71" }
 .codicon-primitive-square:before { content: "\ea72" }
 .codicon-edit:before { content: "\ea73" }
 .codicon-pencil:before { content: "\ea73" }
@@ -93,6 +96,7 @@
 .codicon-file:before { content: "\ea7b" }
 .codicon-file-text:before { content: "\ea7b" }
 .codicon-more:before { content: "\ea7c" }
+.codicon-ellipsis:before { content: "\ea7c" }
 .codicon-kebab-horizontal:before { content: "\ea7c" }
 .codicon-mail-reply:before { content: "\ea7d" }
 .codicon-reply:before { content: "\ea7d" }
@@ -139,7 +143,6 @@
 .codicon-symbol-parameter:before { content: "\ea92" }
 .codicon-symbol-type-parameter:before { content: "\ea92" }
 .codicon-symbol-key:before { content: "\ea93" }
-.codicon-symbol-string:before { content: "\ea93" }
 .codicon-symbol-text:before { content: "\ea93" }
 .codicon-symbol-reference:before { content: "\ea94" }
 .codicon-go-to-file:before { content: "\ea94" }
@@ -147,237 +150,259 @@
 .codicon-symbol-value:before { content: "\ea95" }
 .codicon-symbol-ruler:before { content: "\ea96" }
 .codicon-symbol-unit:before { content: "\ea96" }
-.codicon-activate-breakpoints:before { content: "\f101" }
-.codicon-archive:before { content: "\f102" }
-.codicon-arrow-both:before { content: "\f103" }
-.codicon-arrow-down:before { content: "\f104" }
-.codicon-arrow-left:before { content: "\f105" }
-.codicon-arrow-right:before { content: "\f106" }
-.codicon-arrow-small-down:before { content: "\f107" }
-.codicon-arrow-small-left:before { content: "\f108" }
-.codicon-arrow-small-right:before { content: "\f109" }
-.codicon-arrow-small-up:before { content: "\f10a" }
-.codicon-arrow-up:before { content: "\f10b" }
-.codicon-bell:before { content: "\f10c" }
-.codicon-bold:before { content: "\f10d" }
-.codicon-book:before { content: "\f10e" }
-.codicon-bookmark:before { content: "\f10f" }
-.codicon-breakpoint-conditional-unverified:before { content: "\f110" }
-.codicon-breakpoint-conditional:before { content: "\f111" }
-.codicon-breakpoint-data-unverified:before { content: "\f112" }
-.codicon-breakpoint-data:before { content: "\f113" }
-.codicon-breakpoint-log-unverified:before { content: "\f114" }
-.codicon-breakpoint-log:before { content: "\f115" }
-.codicon-briefcase:before { content: "\f116" }
-.codicon-broadcast:before { content: "\f117" }
-.codicon-browser:before { content: "\f118" }
-.codicon-bug:before { content: "\f119" }
-.codicon-calendar:before { content: "\f11a" }
-.codicon-case-sensitive:before { content: "\f11b" }
-.codicon-check:before { content: "\f11c" }
-.codicon-checklist:before { content: "\f11d" }
-.codicon-chevron-down:before { content: "\f11e" }
-.codicon-chevron-left:before { content: "\f11f" }
-.codicon-chevron-right:before { content: "\f120" }
-.codicon-chevron-up:before { content: "\f121" }
-.codicon-chrome-close:before { content: "\f122" }
-.codicon-chrome-maximize:before { content: "\f123" }
-.codicon-chrome-minimize:before { content: "\f124" }
-.codicon-chrome-restore:before { content: "\f125" }
-.codicon-circle-outline:before { content: "\f126" }
-.codicon-circle-slash:before { content: "\f127" }
-.codicon-circuit-board:before { content: "\f128" }
-.codicon-clear-all:before { content: "\f129" }
-.codicon-clippy:before { content: "\f12a" }
-.codicon-close-all:before { content: "\f12b" }
-.codicon-cloud-download:before { content: "\f12c" }
-.codicon-cloud-upload:before { content: "\f12d" }
-.codicon-code:before { content: "\f12e" }
-.codicon-collapse-all:before { content: "\f12f" }
-.codicon-color-mode:before { content: "\f130" }
-.codicon-comment-discussion:before { content: "\f131" }
-.codicon-compare-changes:before { content: "\f132" }
-.codicon-continue:before { content: "\f133" }
-.codicon-credit-card:before { content: "\f134" }
-.codicon-current-and-breakpoint:before { content: "\f135" }
-.codicon-current:before { content: "\f136" }
-.codicon-dash:before { content: "\f137" }
-.codicon-dashboard:before { content: "\f138" }
-.codicon-database:before { content: "\f139" }
-.codicon-debug-disconnect:before { content: "\f13a" }
-.codicon-debug-pause:before { content: "\f13b" }
-.codicon-debug-restart:before { content: "\f13c" }
-.codicon-debug-start:before { content: "\f13d" }
-.codicon-debug-step-into:before { content: "\f13e" }
-.codicon-debug-step-out:before { content: "\f13f" }
-.codicon-debug-step-over:before { content: "\f140" }
-.codicon-debug-stop:before { content: "\f141" }
-.codicon-debug:before { content: "\f142" }
-.codicon-device-camera-video:before { content: "\f143" }
-.codicon-device-camera:before { content: "\f144" }
-.codicon-device-mobile:before { content: "\f145" }
-.codicon-diff-added:before { content: "\f146" }
-.codicon-diff-ignored:before { content: "\f147" }
-.codicon-diff-modified:before { content: "\f148" }
-.codicon-diff-removed:before { content: "\f149" }
-.codicon-diff-renamed:before { content: "\f14a" }
-.codicon-diff:before { content: "\f14b" }
-.codicon-discard:before { content: "\f14c" }
-.codicon-editor-layout:before { content: "\f14d" }
-.codicon-ellipsis:before { content: "\f14e" }
-.codicon-empty-window:before { content: "\f14f" }
-.codicon-exclude:before { content: "\f150" }
-.codicon-extensions:before { content: "\f151" }
-.codicon-eye-closed:before { content: "\f152" }
-.codicon-file-binary:before { content: "\f153" }
-.codicon-file-code:before { content: "\f154" }
-.codicon-file-media:before { content: "\f155" }
-.codicon-file-pdf:before { content: "\f156" }
-.codicon-file-submodule:before { content: "\f157" }
-.codicon-file-symlink-directory:before { content: "\f158" }
-.codicon-file-symlink-file:before { content: "\f159" }
-.codicon-file-zip:before { content: "\f15a" }
-.codicon-files:before { content: "\f15b" }
-.codicon-filter:before { content: "\f15c" }
-.codicon-flame:before { content: "\f15d" }
-.codicon-fold-down:before { content: "\f15e" }
-.codicon-fold-up:before { content: "\f15f" }
-.codicon-fold:before { content: "\f160" }
-.codicon-folder-active:before { content: "\f161" }
-.codicon-folder-opened:before { content: "\f162" }
-.codicon-gear:before { content: "\f163" }
-.codicon-gift:before { content: "\f164" }
-.codicon-gist-secret:before { content: "\f165" }
-.codicon-gist:before { content: "\f166" }
-.codicon-git-commit:before { content: "\f167" }
-.codicon-git-compare:before { content: "\f168" }
-.codicon-git-merge:before { content: "\f169" }
-.codicon-github-action:before { content: "\f16a" }
-.codicon-github-alt:before { content: "\f16b" }
-.codicon-globe:before { content: "\f16c" }
-.codicon-grabber:before { content: "\f16d" }
-.codicon-graph:before { content: "\f16e" }
-.codicon-gripper:before { content: "\f16f" }
-.codicon-heart:before { content: "\f170" }
-.codicon-home:before { content: "\f171" }
-.codicon-horizontal-rule:before { content: "\f172" }
-.codicon-hubot:before { content: "\f173" }
-.codicon-inbox:before { content: "\f174" }
-.codicon-issue-closed:before { content: "\f175" }
-.codicon-issue-reopened:before { content: "\f176" }
-.codicon-issues:before { content: "\f177" }
-.codicon-italic:before { content: "\f178" }
-.codicon-jersey:before { content: "\f179" }
-.codicon-json:before { content: "\f17a" }
-.codicon-kebab-vertical:before { content: "\f17b" }
-.codicon-law:before { content: "\f17c" }
-.codicon-lightbulb-autofix:before { content: "\f17d" }
-.codicon-link-external:before { content: "\f17e" }
-.codicon-link:before { content: "\f17f" }
-.codicon-list-ordered:before { content: "\f180" }
-.codicon-list-unordered:before { content: "\f181" }
-.codicon-live-share:before { content: "\f182" }
-.codicon-loading:before { content: "\f183" }
-.codicon-location:before { content: "\f184" }
-.codicon-mail-read:before { content: "\f185" }
-.codicon-mail:before { content: "\f186" }
-.codicon-markdown:before { content: "\f187" }
-.codicon-megaphone:before { content: "\f188" }
-.codicon-mention:before { content: "\f189" }
-.codicon-milestone:before { content: "\f18a" }
-.codicon-mortar-board:before { content: "\f18b" }
-.codicon-move:before { content: "\f18c" }
-.codicon-multiple-windows:before { content: "\f18d" }
-.codicon-mute:before { content: "\f18e" }
-.codicon-no-newline:before { content: "\f18f" }
-.codicon-note:before { content: "\f190" }
-.codicon-octoface:before { content: "\f191" }
-.codicon-open-preview:before { content: "\f192" }
-.codicon-package:before { content: "\f193" }
-.codicon-paintcan:before { content: "\f194" }
-.codicon-pin:before { content: "\f195" }
-.codicon-play:before { content: "\f196" }
-.codicon-plug:before { content: "\f197" }
-.codicon-preserve-case:before { content: "\f198" }
-.codicon-preview:before { content: "\f199" }
-.codicon-project:before { content: "\f19a" }
-.codicon-pulse:before { content: "\f19b" }
-.codicon-question:before { content: "\f19c" }
-.codicon-quote:before { content: "\f19d" }
-.codicon-radio-tower:before { content: "\f19e" }
-.codicon-reactions:before { content: "\f19f" }
-.codicon-references:before { content: "\f1a0" }
-.codicon-refresh:before { content: "\f1a1" }
-.codicon-regex:before { content: "\f1a2" }
-.codicon-remote:before { content: "\f1a3" }
-.codicon-remove:before { content: "\f1a4" }
-.codicon-replace-all:before { content: "\f1a5" }
-.codicon-replace:before { content: "\f1a6" }
-.codicon-repo-clone:before { content: "\f1a7" }
-.codicon-repo-force-push:before { content: "\f1a8" }
-.codicon-repo-pull:before { content: "\f1a9" }
-.codicon-repo-push:before { content: "\f1aa" }
-.codicon-report:before { content: "\f1ab" }
-.codicon-request-changes:before { content: "\f1ac" }
-.codicon-rocket:before { content: "\f1ad" }
-.codicon-root-folder-opened:before { content: "\f1ae" }
-.codicon-root-folder:before { content: "\f1af" }
-.codicon-rss:before { content: "\f1b0" }
-.codicon-ruby:before { content: "\f1b1" }
-.codicon-save-all:before { content: "\f1b2" }
-.codicon-save-as:before { content: "\f1b3" }
-.codicon-save:before { content: "\f1b4" }
-.codicon-screen-full:before { content: "\f1b5" }
-.codicon-screen-normal:before { content: "\f1b6" }
-.codicon-search-stop:before { content: "\f1b7" }
-.codicon-selection:before { content: "\f1b8" }
-.codicon-server:before { content: "\f1b9" }
-.codicon-settings:before { content: "\f1ba" }
-.codicon-shield:before { content: "\f1bb" }
-.codicon-smiley:before { content: "\f1bc" }
-.codicon-sort-precedence:before { content: "\f1bd" }
-.codicon-split-horizontal:before { content: "\f1be" }
-.codicon-split-vertical:before { content: "\f1bf" }
-.codicon-squirrel:before { content: "\f1c0" }
-.codicon-star-full:before { content: "\f1c1" }
-.codicon-star-half:before { content: "\f1c2" }
-.codicon-symbol-class:before { content: "\f1c3" }
-.codicon-symbol-color:before { content: "\f1c4" }
-.codicon-symbol-constant:before { content: "\f1c5" }
-.codicon-symbol-enum-member:before { content: "\f1c6" }
-.codicon-symbol-field:before { content: "\f1c7" }
-.codicon-symbol-file:before { content: "\f1c8" }
-.codicon-symbol-interface:before { content: "\f1c9" }
-.codicon-symbol-keyword:before { content: "\f1ca" }
-.codicon-symbol-misc:before { content: "\f1cb" }
-.codicon-symbol-operator:before { content: "\f1cc" }
-.codicon-symbol-property:before { content: "\f1cd" }
-.codicon-symbol-snippet:before { content: "\f1ce" }
-.codicon-tasklist:before { content: "\f1cf" }
-.codicon-telescope:before { content: "\f1d0" }
-.codicon-text-size:before { content: "\f1d1" }
-.codicon-three-bars:before { content: "\f1d2" }
-.codicon-thumbsdown:before { content: "\f1d3" }
-.codicon-thumbsup:before { content: "\f1d4" }
-.codicon-tools:before { content: "\f1d5" }
-.codicon-triangle-down:before { content: "\f1d6" }
-.codicon-triangle-left:before { content: "\f1d7" }
-.codicon-triangle-right:before { content: "\f1d8" }
-.codicon-triangle-up:before { content: "\f1d9" }
-.codicon-twitter:before { content: "\f1da" }
-.codicon-unfold:before { content: "\f1db" }
-.codicon-unlock:before { content: "\f1dc" }
-.codicon-unmute:before { content: "\f1dd" }
-.codicon-unverified:before { content: "\f1de" }
-.codicon-verified:before { content: "\f1df" }
-.codicon-versions:before { content: "\f1e0" }
-.codicon-vm-active:before { content: "\f1e1" }
-.codicon-vm-outline:before { content: "\f1e2" }
-.codicon-vm-running:before { content: "\f1e3" }
-.codicon-watch:before { content: "\f1e4" }
-.codicon-whitespace:before { content: "\f1e5" }
-.codicon-whole-word:before { content: "\f1e6" }
-.codicon-window:before { content: "\f1e7" }
-.codicon-word-wrap:before { content: "\f1e8" }
-.codicon-zoom-in:before { content: "\f1e9" }
-.codicon-zoom-out:before { content: "\f1ea" }
+.codicon-activate-breakpoints:before { content: "\ea97" }
+.codicon-archive:before { content: "\ea98" }
+.codicon-arrow-both:before { content: "\ea99" }
+.codicon-arrow-down:before { content: "\ea9a" }
+.codicon-arrow-left:before { content: "\ea9b" }
+.codicon-arrow-right:before { content: "\ea9c" }
+.codicon-arrow-small-down:before { content: "\ea9d" }
+.codicon-arrow-small-left:before { content: "\ea9e" }
+.codicon-arrow-small-right:before { content: "\ea9f" }
+.codicon-arrow-small-up:before { content: "\eaa0" }
+.codicon-arrow-up:before { content: "\eaa1" }
+.codicon-bell:before { content: "\eaa2" }
+.codicon-bold:before { content: "\eaa3" }
+.codicon-book:before { content: "\eaa4" }
+.codicon-bookmark:before { content: "\eaa5" }
+.codicon-debug-breakpoint-conditional-unverified:before { content: "\eaa6" }
+.codicon-debug-breakpoint-conditional:before { content: "\eaa7" }
+.codicon-debug-breakpoint-conditional-disabled:before { content: "\eaa7" }
+.codicon-debug-breakpoint-data-unverified:before { content: "\eaa8" }
+.codicon-debug-breakpoint-data:before { content: "\eaa9" }
+.codicon-debug-breakpoint-data-disabled:before { content: "\eaa9" }
+.codicon-debug-breakpoint-log-unverified:before { content: "\eaaa" }
+.codicon-debug-breakpoint-log:before { content: "\eaab" }
+.codicon-debug-breakpoint-log-disabled:before { content: "\eaab" }
+.codicon-briefcase:before { content: "\eaac" }
+.codicon-broadcast:before { content: "\eaad" }
+.codicon-browser:before { content: "\eaae" }
+.codicon-bug:before { content: "\eaaf" }
+.codicon-calendar:before { content: "\eab0" }
+.codicon-case-sensitive:before { content: "\eab1" }
+.codicon-check:before { content: "\eab2" }
+.codicon-checklist:before { content: "\eab3" }
+.codicon-chevron-down:before { content: "\eab4" }
+.codicon-chevron-left:before { content: "\eab5" }
+.codicon-chevron-right:before { content: "\eab6" }
+.codicon-chevron-up:before { content: "\eab7" }
+.codicon-chrome-close:before { content: "\eab8" }
+.codicon-chrome-maximize:before { content: "\eab9" }
+.codicon-chrome-minimize:before { content: "\eaba" }
+.codicon-chrome-restore:before { content: "\eabb" }
+.codicon-circle-outline:before { content: "\eabc" }
+.codicon-debug-breakpoint-unverified:before { content: "\eabc" }
+.codicon-circle-slash:before { content: "\eabd" }
+.codicon-circuit-board:before { content: "\eabe" }
+.codicon-clear-all:before { content: "\eabf" }
+.codicon-clippy:before { content: "\eac0" }
+.codicon-close-all:before { content: "\eac1" }
+.codicon-cloud-download:before { content: "\eac2" }
+.codicon-cloud-upload:before { content: "\eac3" }
+.codicon-code:before { content: "\eac4" }
+.codicon-collapse-all:before { content: "\eac5" }
+.codicon-color-mode:before { content: "\eac6" }
+.codicon-comment-discussion:before { content: "\eac7" }
+.codicon-compare-changes:before { content: "\eac8" }
+.codicon-credit-card:before { content: "\eac9" }
+.codicon-dash:before { content: "\eacc" }
+.codicon-dashboard:before { content: "\eacd" }
+.codicon-database:before { content: "\eace" }
+.codicon-debug-continue:before { content: "\eacf" }
+.codicon-debug-disconnect:before { content: "\ead0" }
+.codicon-debug-pause:before { content: "\ead1" }
+.codicon-debug-restart:before { content: "\ead2" }
+.codicon-debug-start:before { content: "\ead3" }
+.codicon-debug-step-into:before { content: "\ead4" }
+.codicon-debug-step-out:before { content: "\ead5" }
+.codicon-debug-step-over:before { content: "\ead6" }
+.codicon-debug-stop:before { content: "\ead7" }
+.codicon-debug:before { content: "\ead8" }
+.codicon-device-camera-video:before { content: "\ead9" }
+.codicon-device-camera:before { content: "\eada" }
+.codicon-device-mobile:before { content: "\eadb" }
+.codicon-diff-added:before { content: "\eadc" }
+.codicon-diff-ignored:before { content: "\eadd" }
+.codicon-diff-modified:before { content: "\eade" }
+.codicon-diff-removed:before { content: "\eadf" }
+.codicon-diff-renamed:before { content: "\eae0" }
+.codicon-diff:before { content: "\eae1" }
+.codicon-discard:before { content: "\eae2" }
+.codicon-editor-layout:before { content: "\eae3" }
+.codicon-empty-window:before { content: "\eae4" }
+.codicon-exclude:before { content: "\eae5" }
+.codicon-extensions:before { content: "\eae6" }
+.codicon-eye-closed:before { content: "\eae7" }
+.codicon-file-binary:before { content: "\eae8" }
+.codicon-file-code:before { content: "\eae9" }
+.codicon-file-media:before { content: "\eaea" }
+.codicon-file-pdf:before { content: "\eaeb" }
+.codicon-file-submodule:before { content: "\eaec" }
+.codicon-file-symlink-directory:before { content: "\eaed" }
+.codicon-file-symlink-file:before { content: "\eaee" }
+.codicon-file-zip:before { content: "\eaef" }
+.codicon-files:before { content: "\eaf0" }
+.codicon-filter:before { content: "\eaf1" }
+.codicon-flame:before { content: "\eaf2" }
+.codicon-fold-down:before { content: "\eaf3" }
+.codicon-fold-up:before { content: "\eaf4" }
+.codicon-fold:before { content: "\eaf5" }
+.codicon-folder-active:before { content: "\eaf6" }
+.codicon-folder-opened:before { content: "\eaf7" }
+.codicon-gear:before { content: "\eaf8" }
+.codicon-gift:before { content: "\eaf9" }
+.codicon-gist-secret:before { content: "\eafa" }
+.codicon-gist:before { content: "\eafb" }
+.codicon-git-commit:before { content: "\eafc" }
+.codicon-git-compare:before { content: "\eafd" }
+.codicon-git-merge:before { content: "\eafe" }
+.codicon-github-action:before { content: "\eaff" }
+.codicon-github-alt:before { content: "\eb00" }
+.codicon-globe:before { content: "\eb01" }
+.codicon-grabber:before { content: "\eb02" }
+.codicon-graph:before { content: "\eb03" }
+.codicon-gripper:before { content: "\eb04" }
+.codicon-heart:before { content: "\eb05" }
+.codicon-home:before { content: "\eb06" }
+.codicon-horizontal-rule:before { content: "\eb07" }
+.codicon-hubot:before { content: "\eb08" }
+.codicon-inbox:before { content: "\eb09" }
+.codicon-issue-closed:before { content: "\eb0a" }
+.codicon-issue-reopened:before { content: "\eb0b" }
+.codicon-issues:before { content: "\eb0c" }
+.codicon-italic:before { content: "\eb0d" }
+.codicon-jersey:before { content: "\eb0e" }
+.codicon-json:before { content: "\eb0f" }
+.codicon-kebab-vertical:before { content: "\eb10" }
+.codicon-key:before { content: "\eb11" }
+.codicon-law:before { content: "\eb12" }
+.codicon-lightbulb-autofix:before { content: "\eb13" }
+.codicon-link-external:before { content: "\eb14" }
+.codicon-link:before { content: "\eb15" }
+.codicon-list-ordered:before { content: "\eb16" }
+.codicon-list-unordered:before { content: "\eb17" }
+.codicon-live-share:before { content: "\eb18" }
+.codicon-loading:before { content: "\eb19" }
+.codicon-location:before { content: "\eb1a" }
+.codicon-mail-read:before { content: "\eb1b" }
+.codicon-mail:before { content: "\eb1c" }
+.codicon-markdown:before { content: "\eb1d" }
+.codicon-megaphone:before { content: "\eb1e" }
+.codicon-mention:before { content: "\eb1f" }
+.codicon-milestone:before { content: "\eb20" }
+.codicon-mortar-board:before { content: "\eb21" }
+.codicon-move:before { content: "\eb22" }
+.codicon-multiple-windows:before { content: "\eb23" }
+.codicon-mute:before { content: "\eb24" }
+.codicon-no-newline:before { content: "\eb25" }
+.codicon-note:before { content: "\eb26" }
+.codicon-octoface:before { content: "\eb27" }
+.codicon-open-preview:before { content: "\eb28" }
+.codicon-package:before { content: "\eb29" }
+.codicon-paintcan:before { content: "\eb2a" }
+.codicon-pin:before { content: "\eb2b" }
+.codicon-play:before { content: "\eb2c" }
+.codicon-plug:before { content: "\eb2d" }
+.codicon-preserve-case:before { content: "\eb2e" }
+.codicon-preview:before { content: "\eb2f" }
+.codicon-project:before { content: "\eb30" }
+.codicon-pulse:before { content: "\eb31" }
+.codicon-question:before { content: "\eb32" }
+.codicon-quote:before { content: "\eb33" }
+.codicon-radio-tower:before { content: "\eb34" }
+.codicon-reactions:before { content: "\eb35" }
+.codicon-references:before { content: "\eb36" }
+.codicon-refresh:before { content: "\eb37" }
+.codicon-regex:before { content: "\eb38" }
+.codicon-remote-explorer:before { content: "\eb39" }
+.codicon-remote:before { content: "\eb3a" }
+.codicon-remove:before { content: "\eb3b" }
+.codicon-replace-all:before { content: "\eb3c" }
+.codicon-replace:before { content: "\eb3d" }
+.codicon-repo-clone:before { content: "\eb3e" }
+.codicon-repo-force-push:before { content: "\eb3f" }
+.codicon-repo-pull:before { content: "\eb40" }
+.codicon-repo-push:before { content: "\eb41" }
+.codicon-report:before { content: "\eb42" }
+.codicon-request-changes:before { content: "\eb43" }
+.codicon-rocket:before { content: "\eb44" }
+.codicon-root-folder-opened:before { content: "\eb45" }
+.codicon-root-folder:before { content: "\eb46" }
+.codicon-rss:before { content: "\eb47" }
+.codicon-ruby:before { content: "\eb48" }
+.codicon-save-all:before { content: "\eb49" }
+.codicon-save-as:before { content: "\eb4a" }
+.codicon-save:before { content: "\eb4b" }
+.codicon-screen-full:before { content: "\eb4c" }
+.codicon-screen-normal:before { content: "\eb4d" }
+.codicon-search-stop:before { content: "\eb4e" }
+.codicon-server:before { content: "\eb50" }
+.codicon-settings-gear:before { content: "\eb51" }
+.codicon-settings:before { content: "\eb52" }
+.codicon-shield:before { content: "\eb53" }
+.codicon-smiley:before { content: "\eb54" }
+.codicon-sort-precedence:before { content: "\eb55" }
+.codicon-split-horizontal:before { content: "\eb56" }
+.codicon-split-vertical:before { content: "\eb57" }
+.codicon-squirrel:before { content: "\eb58" }
+.codicon-star-full:before { content: "\eb59" }
+.codicon-star-half:before { content: "\eb5a" }
+.codicon-symbol-class:before { content: "\eb5b" }
+.codicon-symbol-color:before { content: "\eb5c" }
+.codicon-symbol-constant:before { content: "\eb5d" }
+.codicon-symbol-enum-member:before { content: "\eb5e" }
+.codicon-symbol-field:before { content: "\eb5f" }
+.codicon-symbol-file:before { content: "\eb60" }
+.codicon-symbol-interface:before { content: "\eb61" }
+.codicon-symbol-keyword:before { content: "\eb62" }
+.codicon-symbol-misc:before { content: "\eb63" }
+.codicon-symbol-operator:before { content: "\eb64" }
+.codicon-symbol-property:before { content: "\eb65" }
+.codicon-symbol-snippet:before { content: "\eb66" }
+.codicon-tasklist:before { content: "\eb67" }
+.codicon-telescope:before { content: "\eb68" }
+.codicon-text-size:before { content: "\eb69" }
+.codicon-three-bars:before { content: "\eb6a" }
+.codicon-thumbsdown:before { content: "\eb6b" }
+.codicon-thumbsup:before { content: "\eb6c" }
+.codicon-tools:before { content: "\eb6d" }
+.codicon-triangle-down:before { content: "\eb6e" }
+.codicon-triangle-left:before { content: "\eb6f" }
+.codicon-triangle-right:before { content: "\eb70" }
+.codicon-triangle-up:before { content: "\eb71" }
+.codicon-twitter:before { content: "\eb72" }
+.codicon-unfold:before { content: "\eb73" }
+.codicon-unlock:before { content: "\eb74" }
+.codicon-unmute:before { content: "\eb75" }
+.codicon-unverified:before { content: "\eb76" }
+.codicon-verified:before { content: "\eb77" }
+.codicon-versions:before { content: "\eb78" }
+.codicon-vm-active:before { content: "\eb79" }
+.codicon-vm-outline:before { content: "\eb7a" }
+.codicon-vm-running:before { content: "\eb7b" }
+.codicon-watch:before { content: "\eb7c" }
+.codicon-whitespace:before { content: "\eb7d" }
+.codicon-whole-word:before { content: "\eb7e" }
+.codicon-window:before { content: "\eb7f" }
+.codicon-word-wrap:before { content: "\eb80" }
+.codicon-zoom-in:before { content: "\eb81" }
+.codicon-zoom-out:before { content: "\eb82" }
+.codicon-list-filter:before { content: "\eb83" }
+.codicon-list-flat:before { content: "\eb84" }
+.codicon-list-selection:before { content: "\eb85" }
+.codicon-selection:before { content: "\eb85" }
+.codicon-list-tree:before { content: "\eb86" }
+.codicon-debug-breakpoint-function-unverified:before { content: "\eb87" }
+.codicon-debug-breakpoint-function:before { content: "\eb88" }
+.codicon-debug-breakpoint-function-disabled:before { content: "\eb88" }
+.codicon-debug-breakpoint-stackframe-active:before { content: "\eb89" }
+.codicon-debug-breakpoint-stackframe-dot:before { content: "\eb8a" }
+.codicon-debug-breakpoint-stackframe:before { content: "\eb8b" }
+.codicon-debug-breakpoint-stackframe-focused:before { content: "\eb8b" }
+.codicon-debug-breakpoint-unsupported:before { content: "\eb8c" }
+.codicon-symbol-string:before { content: "\eb8d" }
+.codicon-debug-reverse-continue:before { content: "\eb8e" }
+.codicon-debug-step-back:before { content: "\eb8f" }
+.codicon-debug-restart-frame:before { content: "\eb90" }
+.codicon-debug-alternate:before { content: "\eb91" }
+.codicon-debug-alt:before { content: "\f101" }
diff --git a/src/vs/base/browser/ui/codiconLabel/codicon/codicon.ttf b/src/vs/base/browser/ui/codiconLabel/codicon/codicon.ttf
index beeea24d9513097660ec7972200746989fd2bdb4..a51c284681e086082aecd8d267d334a25b9bce8f 100644
GIT binary patch
literal 47404
zcmeFadz@s~Ro}bMbDu}mspqMx?yi1TPuEP%OwUwzS66FhG(FNtZAn&JlHIapwQWf)
zd90D;2VjhO1PmDB7zdmi!gU-%2uVyJPA(x_LLA~a1PE}6LmWbY5CROO>a%Zo$Gg7f$v^l5_V*d3{Es~Uwx^%^
z^(U$yRI2qJrQFBf^3=QD?(V7Y=JOWYFTM4tx4d!ryYBjGrM~lk&y%;m?Tg;=8=rB<
zN`2RpN|k<6$+^EB;rS1Es4p;U%nJMDoHuLO?sr?yJ@G{8+HZ5M<{Llv`)~WaY$MCb
zcfWwQk#g-f<$HYl+f>E8m$eChN^$n)A;(VCWp!P-_v}4*pt9$mddFMUtnyENe)_fZ
z{OvbA#s1l?5#Ma-u*Om0@!GdLA5a1B*3>I_E=`sL*0pH?6?m-uLmm&`y7w7%WAx;2
zIOgh-NlVC)rVqL5KaoFagFn%M{||Hk+w&*w`u|S5Dr5h|&RsyTpx!5q#Z>N3;-kc(
zU0{hNX1(uzJMS}NwZqYSNADl~`1lp$5B!EBK4$df=zXJa8vW$>Vf&sfrw@nkyg?nQ
zpK*TPeVmo9`rp-0tADBfl==}TRX?D{_@Mu)E;}9dY4t1WKdEQbiu#y(oqE6eYwDbn
zs7F-MSy69cAD>YFr}{tC_c%56r`0>vyVZvJZ|X@WR==tKH$25}IgS%Kp86l^Yt-kQ
zfV20wq5ylZIp>(GNL7JzDXB7kW=GwjuBfYclwDO-HC0!+YN)1asTusr93Eg^EvQAc
zq?T1jt*SG4hpsxSdg?rFa#5|TORBFn)s`BlZFP^jSM90$)C1~4wU0M>NIk4xsa~ZH
z)T8Rv>NV;y^;-3~I#jP$PpGG8i>K8a)h+cV^{l$Bo>OmD&#Skpx1rTvpuSLjk$Q*v
zV)Z5JUFu8O=F8NVtFKUBslG~mwfb819`$wV>(zVJpHc5q-=O}i`bPCl>I3S}as7W@
zeY5%t>RZ$Y)n8QKs{V@lcJ)`)cc{Ov{)YOn`cCyX)px1Cr9Ptmw)#8j@2c-re@}g%
z`hNBI)jv=lRsRsY@Q>6#Rv%aYME#KZr|O5*kE(yBeoXzi`U&-ash?E;T>T66Q|goI
zU#Wksen$Np^|R{d)X%GbtA0WKJM}5`??E5Gr2d2YjQWr2m(_n(BlWB5zo=hRzpj2m
zeOCRp`W^MV>i5)tSHG`5?xKe)#%6yeNl~`wnDE|qi3wpKh@}sR@9HMyk&(xt443K
zLhn_hXRXkW)#$brL6xKDtkAdB=*?C@aBB3t75cjxy~PSWUya^s1skA7Z?l3mP$O);
z2zEh@K#d|;2sQdbE7%G(`XVb>4K;d)73_x^eX$iRi5k7riaN*gORQjB)aYGSurq4(
zrB+mtCCFR^+oMKbZUrl(MqgnCd!$CvMhKQkjlR+fHcE}Y$_my>joxiVeGkj8wt@vy
zqpz`oEmNbfwSrYsqxV?BzNyjIS;5k&(brqS=Bbf<2T@P5e6JPkpc=i;3Kmg~zQGE%
zL5-v>5W&-#a5%gY(-G~=-aGd#ntGq
zSP`!@`gSW=b~XB|RdKA)Czu6jegjQppDUwSizU7(T`fetE$mIvx0wBqaU+^r&XgLw}Q`Aqo1^b
z_f?~RZUsNAMxU^PM^>YMVFll;Mn7c*FRe!Z(hB}sjXr4w&#gxP$_hSQjegn+-dv6T
zwH5rj8vTqFi5Exz#tOb(jegb&USExV&I
zHTskl5Q7^1dn;fEHTp#>pa?bkB`e?wHTtv_kcArk2PM
zAP_bBPgcMpYV<2sKqYGQpRIsB)M#V{q@qS+D_|Bi`c*5S7d84XR=_c8^lMf?G-~u;
zt$=OR=+~`)a@6QItblja=r^qho*(^|6)=z*ebx$SNR58m3b;s(e#Z(3NsWHj3Rp>v
zK4%5gq(=YE3iwHle$NU>N{#-z6?0v`&uav9rAGh53OGxRK5qrYrAB{X1?(lR%?Kz=
zjU6lCF*SCrfXvj`vjRp_W8VsBO^pL9;5Ic5t$^UvSX%+hsc~clRHw$V74V%JCssgu
zYMfdD^Qm#c3g}Obi&nsaYMfaC5vp;?irD{g*$OC8jVo5bi)vi80&-O2niVjl8rQ9W
zCe=8%0?|lD_~MJZd(DJs`0!PaH<+F
zSOKxB@uC&5s~Rs^0mZ8EvK8>G8n0LZ*{X5J3K&<7SFM0{)%c7RaIYG#Spfm7an}l1
zSdGtG0Trw9IV<2}HSSpfDXZ~$D_~|dzF-CPti~6ufTPuT-HI@9<4ab+)@pp&3MgBR
z`&QJ)S#DSXd8_fJ6)?COZ&?A2tMR}JxLl35t$@(gcxVNzuEsl7K<#RLhZXR<8eg#j
zl2_xaR>1UXe9a2zUXAax0?t?C>sCPgYP@R&?61Z*tPlmL@m*Gk2h{j(D?|oreA5as
zf*Rjrg=j&I@3lhQpvHSvh#=JXWmbqK)c8ItL=|d$zZK#OHGaSfk%k(-+zK&=8b4@-
z=tGV7t*BpQ`3fsUBx?My6=D-Le#8nzsd@ciy9wTA%;=oN39UesPU_<
z5Z9>jYpf9AsPSV~h;`KXwN{9F)cA2L#6N0$XoW~fjbCSlm`IIZZ-wYcjo)B}I7yA4
zutLP7#!p%yc2eV~tPn-1@sSnvd6rLGaTLpEtT-;qH(GIgmba`(bTodG6{o=RSu0MF
zYV(s_{2iA<|Xj_ghiY&;G0xqF*)s
zMk~a@YWz)Bh=|qr16GKQ6->1eiIUCZ3JB(1So-fKdio^V|9iyaE6%p_r1LT7SKaIG
zpr829`0w;T=KqxcY5%i97%T-F!S&#E!Fz-634S?zIDB{b2_5Mb{Z{>P
z{h6p7ZAEX5J`lU{LVRC*5PuvRzn46jyeIkA7%7jm$UMA
z`F)k7a(CtZm5*0vtFNqnxR%sjS^JjS&(yEfpRa$Y{{8t%em#FYe`o%~`R5vIjSn_e
z^TFoVG=H!4j@GZuT$uU3+4b2E&;Ii4=jL|i-Z1ykxzDy&+DGk=wSR4XYyMsHKRW;U
zg|iE9UGx{X7C*T7`K5=K-naCT<-+pn@{Q&1UjCuw&#W|7-qBH=gU)+8-`)9%&SM2Y-8lP!v!6aUd#-oxv2)*Y?i1&J
zxtI0Ud++Ui|M|Pmf5!#)!mSG*yzsG$*~Q+)?TepTzi0iK^`E{pd+EKGe&zDU<#%8H
z$mLJ>clsafe|Dq2abx4lHa@!XsmXh}
z-1QT8efI7fcYpL|;pX+5Uv~5RZ~o3bjeEB5dB;89ey_T>eecKj+`VhUFYtJdzlOvN
z!+PfEt?qSaYi%XeYn|)P=Ej1nS2Cx*;B2sZ-Ra!c#mI?^S*_%FzFSRo8V7gBr6?*@
z(PzP5X|kApSD3(9zPmZ>>Om*(
zHabBknAh|D4vYKmpFh67L$e(xJnU``I>YJbk59Hg-tKYPZr2%fb(8DRyOaHtUbx-M
zi9zJJy2IIW&BK>h{c4cv!dA8Fr`>L0vcFdl^T>Ag%JNQgee(I4YEY5wU$mXMavHLG
zZL-~dC8!1koldr!9Cz2*q_wzJx+BNs^P3fRcXHfmyL3CcIm|Z)&8H^kKfNB|6L>t$
z{VYxHW|!N}CFeS9Ty-|KyWG{h+0nzz=2LUJr{`SX-}C)VC(C+SdO58O+FG{=(i~j663C98RF6wHLVljCaNj{mi@Ixk8A#LEw2|Qu4et
zon%=YhOXy3Zdiy)eh?LcFmhc#3=-G(yd;W)pcDpP-~`a!P9@FKh>l>}<5A+kjP}4I
za`_vLydLTWhv!Z==bQ&yoi2;5wewD=l@`Jv)Wx)1);cSf!bs~@H`rWsSU(tUYPlsym*=5q
zqJFNswmgg380NQTypG@U{X=iJ-QHh1XcPNH>C=HB_`AV=p`%V>gIUY*z^v0aOBN{~VU2RLs~ibbUvoD5)R%{2%GG-fD0&
ze8(?jnU15N+^B_Op#54azYdr0qLWqEDaqX}InD~-!-ys^J4=d1`D##**BnXqIni6O
z%Nh$Vc65`r?npat((;2&dgytFzCPe_+dJ_6gPENjogIJgIhdh41mO$^_!E4~ywkz<
zE0w?Og}c*{5&fAGW$3>@7zTWxOPdVKJ`9&K;qSP%Y2Xq360qKj7I5q7e0WQ(pOwhbp@L%GIltC1+;F
zzo^?fVvQD~>wB)}agRjT3yzpzzRoI%ohH8O7X6Wk$@<9Q8?T1VAiv|ad&~a(-E1S+
z!`h7y9y&9aVjk3UUhWi5b-3uz$YT49A7c=nxzVA)^x&n^1P%YuI_Ub&BZA=hQ`
ziv4i1a_@X
zI5o}Pov08+g|a-&Cgp_xYJ2jYmF-B@Bs8PFMzI&NKk@P6EQs6IovrJb`bKkzzi^sc
zYhAoPCtPe6i_KYinh%1j@l2z5Fj?N7tmi}8BfJ`UNnV(~kT#KfXz{Yf!d}4+J;?DU
z9#QU!*i-@bC^_$PKz##%0XDL$bqCmlbrKUR7uvOpt!+?=y>P+U7@ys;i^-$~@+|Xw
zmYK9h61r6&(r(W>p4@tii*|tr=c|v|&7&|su)7E`v4u$dY_>bu-hSIYpIp8)!T;IF|G-6<;W_VXR>rJBnT6Wzj^VVr#5*@$$6Q_3#!
z1YQfW-K>fM#IY66B!)e+Pi>v`+x`AfKAUWDQTh|!gN@q#yia>rd-ED{
z8DayDTUj@EW_=9uta;v?zThq2u{Pj2^0+<8?F{>(E}ek|t=)AOH_@g4EI3!4yo1la
z?l!OUPMau3tlmLfD8!-GVMsAZA#uGpid;Vm0W)#r`AH%0vvOfI)^SiGJS$WS(#+@%
z5IuC`Bnh=27mGl4zE`*qudT6R>C}0xOODW<*Leq!ZQ%4v&P<@&aC&kk)DbgjPA<#-
zkkhMF!q_J?@Mujr!Ss(kK|d^O&eoh_XL^bg`|u`Xy9LdP-IbtplZ8NG?!a=bMp(KX
z$l_j{_x-i12~5pm)!Q>B=0sOK_F{Vp9YQVu1$l{%5RD(|VfLPg$zNgS6hLr>E>-6)V^ke@DbU05r-O3QpQwbcug}?qaY&
zPIv&Fck%xr-RZ#7`>A)K7y{6N=k1w(;z!FGt6uSUJa4Jp_hyP-;&(i-y>7R?5NAI<
zcj-TJ?#2?bZwYuL(xHbN)}RvBFMIl;SMsU{N8YYq@#_BWH3#IqGwFj*bNzFi^O{5)>Ik
zRF8wK6eM*ZJ<+4*19n_@9#t;0G65jvre39I2h+F{YLsnfKu){7*GN3
zIu6USN(bjax>^%KoO3-s#ya5$O?N->QR0VTmIadv=oZY=9qOYa;AR%g%mnq@yqpbY
zT0ZSLX`AQZCFFa8g^hQ|41;7>K=XAI)f4ip37S2Uc;En&=*d$n%l0u%9Xxbj*70=0
z&+JdI0^jQNdOH|u!5YFHh#t1Egcq^Y_i#K63FuJ*tp-Xd@dPc;X*ak*Yj{{*S?5+C
zY5)R;=aRD~V6~|UwwiMZ4%SWcR-U=t>D0z{O#c?9;25ClyRy2n08E{3xNICKY%jp1Qju9suCK3jJ?QBmt+S)NuM4Jgr0
zaiRE_9|b`aT*rpSp?5!EJMhiZ{SL?(N@E}#gl$*h3FIE8*p9l?FWCNv{h(i7f)i-w
z-F(=jzZ~uE?(f5`y&?>kms$_>`-l1fn-sd2y;fLXsrcM0Yl{S%$-QdhxlZ3FP>#FS
zU6b1+9veBUg(%d;61*bZMG~XxzAnWc1W%qnTM`?`i7t2jTmx*1m$11oYlmJzlAvb@tj+n9XcevY5*?6h|dE+2pI
zaDs_VR4@R8(+@iJdVPmQxmKt1SwC&J!C_$X|_cL;R*AHf)E9WgB|H6CJ$;F0a7k42>;WlV}Ik)Bu(y;lsAP=*kNdUTBGh
zOgtpOS=_U9QJOLY(+svGfD4}>m)CqSqX95Cf0-ID>i@6F&*?V^DT}
zf}@JOG{^pnQE6xwK$!S3aSCQ5U4*kBJa%;>bj6VPUU-)87KNNhyeQJHYzufy6EXH!
zDB1GlUOY0@BV*C&2un%QPl?O+_SneA3(v8QZ|+!Cs(SFjVa_#o^5z;%FTJOUWyMvA
z4VN1s*e#dD{iy3Xp+(E~bh!(zdUGA{&vMQ&QoFf(yadw%YFy65?NPwTyi
z(}x)J%GlSiST49+Mj+>6b^00VS>{F0GDl(}(4ts%=rzs=pCm_rZPus-iC?PKN^yB+
z7Wg%jh4p%IC#?W({TRGhs3gTk93{nem_m60tfu<$gt2v2KSZ^1g8cl6G@aS4t(@qG
z?Z1a?Yx{3L>^60KcWZh12o1^`vOl}cGt@XXq&|z5Gi(TE8k!0Pwk_7QWL`Ziq+vC$
z6`VB5Dy5)4uP@lHc3-=yqqs_JiXD%1s<&;oob3NGbTpB=GjNMdIOxS`1WBN_=&5Zm9dL>9u^r#C8oieF^xQ
z{Z1c%=5Abfx5_J7P$GCU&*#I;FZkt(E0D?wFhqe<@=KvxsZb#e-56%I?Cf%!CF@sU
z7}H`Q4z@h`D#WBu9%(x$`o%&T=t9gMzy~4UFUJKP6cWB)3}P|~0hleWCG~OsiJWH&xT&^
z4i8#hSPk8oRQNt*$#$HOG!6d*k5Kt?c7%`j?l?q|a<%+Rl*qqklQ<-56jAK$N+o(G55HZB8u*BjHl^o|?J
z?#e$n*nLTR$aQ&KmvH$QFGvj&G0L^=bz0Laf;#S55_$hc=Yb-<=sJeP&(l*l$bc_2
zgkgXV`gyyZALCBpnB};ZNeA!PLk~H!wC3r(%KPj%!fUtJr@>yk{g@{T;Pu1;iY8pk
zGVp-)C$Gmo3rA?Yhlv-Ad&i{-F)^6&W~w2yOF|HGCLpur7s;;qM=eYsyOadTYOt!y
z
zOFpG3n=g6MHg??aykN1zwR7RHaHke0c1QB`!n)BrP!72-o8;%vH*aMu>FgCOU+(}fM?PtvyypcfWe
zND%>F!)sAF%nCskR~F!F!z`>6vKn1Jg=7y4aS=k)tpsGIE9IbE4sL_aN_m;Im*ZE;
zQ6;Jp93OhEEG~s9IRrnRpVPj3iS4UFmH!Hk#l|mhrcYd=hUXPh6AX8NZH1T+>TMI>
zVVnq&k?|jB?IOWGewnP_fYU6I;cJFkFaStVUYP4za6=ugJKI9l-Cg%eSyHJaS;;F2
z0m!u^coH|^1zfd#>wB_6(PxMh=r3S9m&~dgk>n~ypr)u^aKg@QvlqKV&wtS_CU4;j
zI8c^U$+BP@*%!NndeHU21(Ozh?Um}W;Y4jr_;%`ht*w&`YeyKHN#Eq`wV`b$?{Pd>
zz^m?{vr8yw{%`Zl%YEJJ@X#HKM$Gz7XO~WDKI0SCOMkHK{T$JK0sq#Kdh+3lZVKI+
zZvv!rGm{s?rg#kLVvCMsNSn}gmmDFd^PMudx>-+kv3WfuL!OiXfMFgSEYS#v?
z+VkSVQ51wQRB-G%(dD?DW?{Jy)`R_S4di5!eGGJx+`07=!fBT1p#(ug;%_;jjtxyX
zp9xDoJf_bLKTo<9zAMMuOlV3RP|uM1mK#fPSA0f+fjFm0PD<>r#7r%&zYBMNmAMLTK2U_sC_-MHyz?~Ov@<7v}bf63}7cd{LVzAl=M*6m>XF
zcN-Qe@8@|}68@e}gQye)^CS*Bx(dN27{U=hQ1WO<$!F)e*Yo|J-%i=Gv=F9=uFS9<
z8_F5lnhR-{j5D2q=RAup56KAM&$*gtSQ1<2lsPYwr*fha++$EpN@jyv0f~1vwwe@v
zoJZ?7@CJrXCm>@p!5L4g_hx$DOvCefYf)?OtOpN`RSUpeegz1}
zt7_)LZ}U&!+?M3!)IJ;$2UWTM7vy{_LXk7N)8NTmPEk$-(2v6qjRObKT?yYW8!Xvz
zH!g(U;@bK}FI-p%tFbr;$sOo4IwTcCOF9pxYoP<-=!AJV#|A>3(wGGUC2Y(g_t)y-
zl8ltGy_6n^zRNA5x5D*Il~C?1E9GPco6-Yj@)kU&zfCVlz|VccVhm!|
zy#({s5qRBS?~&uH`Zd!9#Mj%pIYAvSt~wAMqgA-auu%hv!K7ypDDa+BFsCO7m9=0q
z&XcE8%d2b&;P2%0pwg_nxskaIpvUX^Sxue|FX23HCc>?~W~qT!!!tvV4&NfrPY=7g`Uz4v&e>ya^vx
zG!;<7C~#9I_eHuLhC(_NT~HRAuLQ4a$}1XSo~GGOP7-0KlfRvndqNTJr0KyPcWRG7
z{3IUJ7BR~$w$(4vfVVsCPA}^m92J(k-Q@y)ZrR!g`@J`yKWAhd6QvRYa|Gi65Qcr=
z&o11k;Z>-4X*3()AHs9$FNHD9D3aj|WDo!mc^_P{wmrNdWjvu9NN-Q6-SOd-V4Z_B
z0WScM0CiadI6<+5hEQ^ay7nTc6f>eXpz}*T0u+>#a0YI*;L8{NIHiPbE+hL!shLhL
zd!!Gjr=iYGc;loI7t4;0!HNYxmY~XB*E3vKNNaOlf|YEoM04|Lzt;;c1Q+b$nLysT
z=6UkHHy(cvOAQhhge42JO>ogJuF2x!Ys~ZHd#_bbPL5{}XTWzy!_Z=C7VeyH2b2Rp
zF*oNgEbPzE`-_XB6J`dKBfQAM)UGvivNIBp=9_iuJgkj7wj?L%1$$LuN_^4sRrj3l
zcU*sJOiokSc_k!_Cn#NVL32*el%-PdB}QfDG@HHH^y^V*+=j&)Z#A*AQ$4`siUX6~dth&j0|
zV;`-Z7C$YF>J8)dx|}fDfK^%q$I-<(0JoQ5iS!tX!p)}g%fNeTz<|!M-Q54NU98flM1nvueA|+NRvFn)H9P88Mp4DJ6*BI-)CAtToH(kLs
zy@L+Y?B+x*Q>SaH@H82|%>g5u5L_NKmCqGX*K2LcquB47L6_1=bHlrfTiYN`T*Eq=BATH9
zB)yLni%G#d99PF~>Ptk=T?gathfcfgWR=Wi;boN;#*auRaFUX_d}|k^27Uo#d9REq
z7bUb1f{INl798%HgsPMLt>hYQnJ3vt^ZAQ1!Hs;v#bo61wop)NKZrSiW#kV%Ofl69
zLEVc2thbaIzdYc?$>Wk8qzn0Jfh#4R?BhetNg5RD@wKaY)*M5v4zrdh!dyktPsmGP9@u@48oHd3D^CVX%sSc-!
z@=2Z_dqwpC)!0zV!cExb5G^#9em*WrEG75FUUkAi3GRH8mI&nff|tPx>*>uU`%XqS
zNDEY@X)^I#HRpOJ4udr(Asd8}m@8h3DC8mB=!H>8E=dQMd@QP67sR38rQcBQ3P(ZJjMpFxyFb`MEg>E2iArZN?G5o?{BhBPGv-
z?NSY3a%zJug7+?3x2C&moe*84=v{216b_gT(PoS)|JNN>FF3=&+Hg&(q<5VS;L&yy
z`-v$%(rqqWD*L2vp9M-@qDWdQ96TydUAmA8RRYc$Zcv1OH7xD$|WLy*1tw4
z3#csx(XLY^bH$%7MH=ITMWvEUbPNrqj0b2Hn9b;*kTSAco{aYL6wD`7ra;t?Z~sU3W3Af>b!U4hv(JQe
z=(jt;K$xR76rj@>hMFN9*9v)|kk80dq2s4IaBIv{LtT4oj^`PmEXIcmN#$-;9XwDL
z>rzN!l2X7p^KVbS%pTg*+s8>x69q&P#69w8r<8Lhd_V^zfy0W3((#DeGm6ihR8nlG
zIPsjwpDgpx2BBh7px6W;#q
zU}vd-p5?yC$Pny;oKh}9zIb^FWP~y&P+}kj5$vh;=^G9;DJ{>-65XFHJCkLm58seY
zQz$nHs`n@t-MzafU_j_T%PsCt^3uZmOBKB!j>*}Qu|M^?lbJ``E)LCN&paPrukeO*
zGS|m&-CLorzzX8Lw3Jvu%3>l0TPBZ>A(8PJB=b=-S&ut2?DMl&O>QIARt$QOehWrA
zcMcpDIsWPyF$UmAVp^BmZ;}G^^@lLD+$z2Tg-q5x4CO0m87|#Bb4L0J)&s*s7fBLB
z5n*-!Z*mW;EqF8iLaNWDHiiKpj;6&?c9YgL`Z>QV}{7jZx%IFES1%AO=G@R6pLAg+V`NO$zI8STFG85LeE(t?oL%bAc-~B(#L5&;9Aeib
zPjJ@qGwfOP3C4k;1A;0p5yP7I!xdA5iRG?`64zf0N$!Q!vmtd8V#QsrPMIGDs36wG
zjraC?y=s;JDuJ!Vzr9LWWJ}Lq6MdfYGS7j?&r>;eP2CWUn&4F~&=wjRM9Vb}K#juP
zQh!R*<^()lvcut;6ky8$22M#-@G8I*oXp_Cu!4o-zbz$mzCW{*7u`C;yeRY#m5Kd%
zVF>gA(U`Pb&8^U4_#hNWYOb`qnxAt@nNz_S3-Qjr>R6Cd_9CVi2tz@4BN`)E5>CRz
zN+<-;0V{`_q|<8tmRCPKs0Ji}f@<9h+JRTEUe5f*ZP(pe_Hv3rVhvt$`^Wc1YB#GB
zw#Z~$qq5`WDxy2fxVGzyE6vOUT@GJrG4@OKvfrGmG03XIl>)k{aN%*8WVBzZ=VfZZvud^ImEFcJ?xL7^HPSA4*eou(%^Cz%
zt%>Q(XtXM&KxMpik#Hb6#^sU+nL!^U>u^FqNZp(nC^kevbL54!i*cROlDJy0^1LkP
z^Y{yj6RE?gmDyjr_oaby#b>neKv?T2)vwQA9RFg5uR2|%q4cQ$N;wn1edqSUMW-%V*{R^)MGR^
zo39#rm|%B-V90cH`hGAk{cCucA$cu=I*=I4gc6KPZp{aIv5EQj+=Nbd$-Cs6p#*f=
zs37;*+PowD!xfpM3zsI2p)dGS_cO=P>&-sn&E%hz&HjQbG$`dm!DQ
zmKpZVa8btVnQgjMkeGh}ZMVPfn!PQ!3{4~9M-P^)W~Ya15ts#(AlS!U=x_`lfb6Dt`>>pZ_
zyRky9)G*ZIhIWDa9-ywjy>33N3vVQv!vC(r2;SkOFmS=yeu!wxW%M~kZwwDhvAa$O
z$d+bh@Gm7`A^6O7C>Wqn9JKDoG(0w3&h1$SrAH~8gmzNC=~diLP$)+J7Wk?_yy|3B
z&U&jUozsUunK`~#+R5S<;Y%)2-Es$&M>ot!Mlqe3E4rnmKa3+`jSSiY$8SpVN5ZP(
zPpBDO>u!d`UJSa05d-p?6^!tMme#mGH%_Sn^a_p4%e-AAYZRty?*VuSy3O>3xF288
zS;4N@g{}vglI-Naz2j5^q7=c9@;()~9#^)KWUFFcAN!x@@Y}f=AwmD3!?A!2yWNye(FWENM+03@!ih}#@axbU$)+h{06MDlT@x~$J`q9$
zbCMg|gI9{Th5~xQp#$bCaLD;@QYBAWCFtx$hf6DE>7CPK$~`W?iQ$#yw&I9HMR_e~
ziMyFDwhuIE!6SVsxO8UyHq7%PRO$K|-s*|%k^J$h;X$RXCTC7UPb`VBg{DTHal#-|
zDes13nO@ywl?fcoRx&%gpGXoW6t912Jpyu*YW3ofy={bMWN@Zi>#-ad|TUeFRmLq|vZ`={G(ieK$G
zNE;KGIxN9r6S-3wc7fMMXD#oSs
zN-@jp`|D?Ttgpl^aw^ldy3M?klX+HbFO@(BATnsSxogC^&4JEd9R#HCs27nNM7?<6
zEI<74T`m8b%zl8*$#DIwDdeZ;mGiCU#IL;;zqV=S1qjW6PN0B{5C$o%h>Gw|=;RQ@
zCi-JwH}Mnrtt%Bu!)>g?{Rvai#AK?{EGkwDW#&$a8B{E&7QR)Od&;LXvIAsI(zwk~
z0oNnKngPibGv;F`L4*h$6H`1FHpza+Nii;?QIQwqu?~yVF^A7#6)X60GkcIN01$6(
zGLS}77($$PcJ!JLo3IFT>@F?bcVBv)nzfANRKG8CLO5;8H^tFSZM)Ru*A06kbx%aB
zfY(Ka6pGi%ClycJXOd`e?AOk_!G#Nfdp=r&?Cut&J~8d3c5%Dy-S8N($kUCPotb)_
zrxl-qL>aQo!jx7YANLTAkj$ghjEehf0uGF7!kfb3XgGB++t3U?ynWfdtjmEr=e6Ci
zHPeD14ytZ7XsxZS-B+t!=!6kv0@6nzGH0=Axul0hWF7Ses!Az0j%C#1&YeBJfrqF5?STPLji-iK)
zN$sV14yY)Yayk}+mC0V1QDl>TG!S9{AhJ0iJm7Q&T`5Nuch-}^pw&!9vA#xXy1YT3
z$1N#U2XWvlZB>mYF0Xyurq6PXl?neM?hKg6t(3S#1`u#{06uuzv$or@kv|blot$6x4zpgzi0#wPoL;5t9Ifb}<IH*+h%xZ4vDjK;Jf#jF>9wUxXkLnZSYtMgEV6pZHh9dzFcyI_z2_Dp|B+_
zahxFosg-5CJ&BU2oG>dLF_@%Xc)sE~^QqL@rt^MC{Tj47VScFsdcyTHoSNs1;EZi#
zo+;*Bl53sjsgJO!r&wcqaRlgtDVmYxwD5e7Ilp+c+edO$d;adO#X85=_8k6y9-nuo
z$>MJhx4IhKvbF*h)#--KdNY)i6k#VEGL#u~0L1YIRu}_kvcX|@I*eg+`r;`uUeLh0
zr30=9oEXX6Rau|xRKiqJzVQ3xfEngE2Pg=AACHga4ek?j1~?GRz>OdgP+$a86gLVN
zL7kN9uq$I2g`-|%V6P;x&GV`2k(i~7Hb@OEw|-4@LgE%-mYXZOLpBq7%W_G2lE1}+
zGZxqLJJAX~q;tOw>oGl~$w(_p(ps|1W8ge%!
z(lgN>=@WuH37R(No#Ao(L?l3#=sGc!iI8+>-Z>9}BU1&*OiN7r)EQjmQ#)r3VYium
z$;``SrX`RB_nKZI%P3Clh8epjI3*Sg1pp-nE%|GRFGiL`!ngprt`>{jTiT>ZP)*^&
z7JMwpX21*8Cgw&Ks6=qdG&%KRQC>pLGP46=?p>l3lIkHh4SeJA5Wp}pP$xSkDZq(j
z5;<(hkZlP+ygSi=qVF+oNFY+F?p!4A(4gL1;3R<2_Zndv8{eY1lAWH{6qXqD7nVGq
zvwe6{NT9>a><{#J(#H?EPm+s~G2B=snI8;~CME4dc=9fo9>1ms*UkoB@4V-qb=%&|
z+}(`M?vU|Gq2AKezr^$xgqhLB_$kA%b1(!Lpxk;KqLLJ5h>^iv+k%Bk}&07&=SD@f9CjkM#cRE5D3e4Q0dl0g5
zi}*nb7)P_xottlTXnWcy^qaXC^CccDC9p7?yZ%k{tytgSOZ2Nc-x3h&)D0H1Ht-er
zK^a?dj_f6QOTjua!_2hHLyMRgT{O|g={8o^6BV3y&`EmXoz8Tmg{>;`jLHNa^bIQ0
zMqSV(erV&!l4q0QON&uJHo8o{fyv1DO0-m?y27nB#T<}3oA`k@agMfDh8xA$3C1c2
zeZy#qAs)NrHgtEpDX^z`Z`h*vyFQ<^%wT|O$*X&HRB@I3_MF2&9VRFtuU(&SfkDD1
zXlxcJzRKWtFFF~Ic6w|iW!CNMh4V+RIlpk{^n4!Wd~)mY1a;9zvC`5@sW8BOOUa0I
z%DmoZzLYG9k_`398bP6;d$)6Mh8mJQcT4Gvm*?l{HffmW@o`PVf$;;9OE||Jp=tqK
zz1d_`M$^_yYdZ-^7wQ({El|lUuSMZZ7%@;cc9Qk7%*z*2{hMN~DZ>bLX{KDhx;o>}
zH?N8Xh_ZN4Bh53LQ!zh{A*C+*#0O2rMu@FFHnZRcQ$-;r7yAVnVPfsQBMO=)E*@&*~RmOBz8Tmu$hRn4noqq`Q|
zbbT@DSV}3of%OgWf73R9hB3%R!-7+kGU@O5esuQqyA1|ADMRvW7|U*l$+F=Ju1HsAeDhcPjnKdgJML9EbOW#K{g>cF1sEMP3@ITVnLe_
zj{{A~ZL+DEHgKgxKEn-%jA`eP2b?d@?^sq
zjOoxNlRLyxQ`l&3lkAhdmNKS~Ld{O;ZAyRcnc7kMbdIhGmK~fYVTt*d98AYdfB|qg
z8DC6ViTeV3P7_fWWN<^hVTYgPy`&r`m3lRW^^8m9VvNQn=IZ_21gpt-uiRuE?JS|vU+*@#4dJ`R)t#BVx(qrlshRF&Y}6o0iff
zC_iZ?^z{Z8VJM=l#%!N#L8MrcPWm`c`c*ft1(j{rwp6vktVe^`Du<}M@usg
zuL%()?l)1QT>74_3j0-^ESM7Hs9|y)X=AcnA;TtmZ+;c;P1x5(Ggipd-r9s?o+1tppP*mAW;L46&w39-ZZSD*F^Y;2lVVi?suZ^vwcy1q
z|E%XO-Can?vfOuevK1PFZZNj;aX8s8;x|CPl5OpfI>U4U7;In8
zX;aTRd@)}xqxz|_#u)Mv
zOfPu5&XZVGrf~?!0Fm90f0?^bzuqUa=y~lLm=hYyooCMpg#$7?fVnJ5O-d4iTq;Q&
z!TO$aH!p<>W6(aaAD^*OQ-x?k2dbAz-bnB5bb#Ny=uPq{2efUOKHSBI$~>gZ48uJ$
zjR!|h<8{e&n`RX*XOn!=WDuj|Bc*L}(>CyQ6H3)|Wagj3sRHoBs7EocOp3&8NOGx^
zc~IcZ{3Mt{kMj}YrQd&bxl9=n%|?Yo#9VhNbrCD%>$y763bXl$lcoyaT`?>yMC=r6
zJguI>g2FT%*V#(Y)+N*%UVv9=6x%>Ioh=p{PqnFiX|!ftH)EJgV>0$m>PuvvJgGt2
z8G=QgC)z=;wi!>5vos(!&}DO8
zAMUmnP#1}eiAF>x?qb-MG=O=yZtg}cMrl{2f{>za(GVwP{@pxhc$wh$o6z#yZDRS1
zadM`F1b!w@Un7-r=u}50#6lS`*Vqc%7wtUPn_Q7mUb!j}Iq#`&;guAVQW!gAScPae
zDj?FCy_bNAn)}_n$-Qu!P13^UAY-~xodvZls0T7v0se#p6BmZU>ELFdSAs05`NhFD
zHZo?bDXx2(T7gUS;k)FwmxfH7Ap@CgmOzTC1*
z%5gjJZlX_w&hiS<9x_qX>QL|QFA}-y6(LXSZ7W)&o5rMNK@Yxqe8cs
z@RyQ48H@@8gSLzTVQjmt3?T@HwLLXn0H
zxaw{XbIB5H4<$p(F)8#tDXV#7rIHoreW&N;UO~r)wOBK3HzBi>mS$$$yttC-dxU*~
zn$q#@SQpr^=lJu*tWsTy6B*+hRijq9n7cDGrCDOxZTzMb;G5*QFTXp>FNz(RY~v8?
zL8g0?Iv?u08Hu^Z$S_)&a+#cSGo#)#^rQrec&h1CMJCV32%8Dk-#ldIM)Ia^t#S_E
z>9y7v;0p=YVI15#PC8%;yfD**CfEa>T2w*+bo66Y#@}LPC3ngYxu)l@P|yUE$G{9N
z{M=-FmKFIz2@b&O%X6In90+F1#I!o!rq+RoSc-j&QJ{WZv>Q6&|Ni8uNcUw>-9=oy
zcVWr(&iWq1?qEFr$P+)3i><=eqttF6Py8~l!qq?aye$VO^TqjO!0ilw8;x#G@J-CU
zPNBo}6{EDGtTJEGwWV5IVdQ5$&!QWeHLRdEz7jKo!g20Ie&a&w$Uz!hGWU(BRGg>G
zi>uee^`^J(HT&mk7i#Bf#ii?)E(Oq5%?sz4t*b~j13q5=`1v>^c8wN
zaUhfK%D6Nc?a!NMQ}-Hu&fnwKxB1DCFzUc#IH}tiMz5#7sVbCz#lO0b;fg}$gXy>_
zgnn=VT6@BkqRgF66XdhrMyq;+Zfn5>`sV4h;s9
zlZNcb2ejj!{6I>MHOvonJ_A6R$qyKsPSqGw=@A3@GmLrWJzJczFA@jT(R}eN7F7}d
zU_daFl8h9`n9G@yiZtzq#m+Zpsu@t&uRv;59$_w+t7c?7CFRslWfjKb`TA})(Xft;
zb?i{{KtMh1XU}uAv)~NzV&pEUtpvnc&I3#_FN`qS8$yYg)Pb1?lz6BII>D^xk-
z(B^`<*v(oke$gSV)Uwlqbv2WD%`*)Q70j>IOn^dfq~y<^Wn$1+Q&)mdI0Q&*H|W>OA?(Yy%wW
zO7D{TEbiG0ggHN$Phw``NB1(lWmXJW!5euDWI6{*N+yqNpIUc{FDLK?htxk_!5eu@
zR!oQrRtR5W@2_wUO|ZaS^4nK3;!={W9l5`pKVcu&K#(hTg^0u$7xK?Uey7J0W^~|H*Cnq68AaUK7dgx1F`pgyfaf0pj
z<`o&CC_I?Zuo#O#rW2vhQCJY9XT6XKyO}Ny2lNAdBDi(YZU@2WK@_R!m98t0@@t9waHF{gMu@
zc=1E=U2&Be5af4}#DBc>co;$HQj_Bee=$8CCKo-XBfj)_>z&E*7+f(uU_qBB2W*`j
z(DM3kh9?mo-Hvhj!$$^8gQ?@F3btrEtcA+ibheebg~n{NXa1h621M4wZz`)#=nHWBSP>cKoWF{uY@C%^cj-66Jh>nP-
zVP&BJ*rE;E<(~;R@@!{LhTaCo-(zb)olj$HE==nFh*w3I8S`OEi8XntX}l^VJWQL^
zwh2*7y!yw*s!XIGc?6R%AQ0j@a+%###&$FDXbS(q&mn+2!M^*p(ri+2)`bNnasL3*
zGsERDs+q=Lb{Q2N^Q|7kqhX#?ItT@g3DmP}d!PNvIlG^!4hn{_xu{8WVbPLs7)Sdu
z{T8$^&2gJolCj@^@w|Ofr8@yy0uBbp-!ofJzQ#S89`8BEkKN?A&-s}
zoY<{-xo_ANY9$CVIx>`(cnbJ1AWRHbNC)#kkVp1AH(MYi*EL-Zk1v7e!)RK&-<0E0O
zusx>Uw=eHq_YKUI;uRUT20L_Hf-FcB>T=z+XDu2h4Yv4xn2Jt$fE>A(3o7_NGd&Gl?i=G9gf0B)G9mCspbL{1az
z@+^IC%4dMNjRyd68%IDaWJ3MvT9YN9DHzs0Y(5m!;-J_mW2UUB
ztQ)y3WeDr`n`iFanR(?4!^`MnLB3B__i2!M;E?gZFH_aoNTgW(%HA=8`5)3rr@>gv*x
zx4!;JrKK0OJZ+a}nM=7M9ggBby<(Cs1C!;#4H_nPxJ?Bi
z>cBXOjRrrXMv4J%$=%*svpFrwAqZ=v`7BXRWh(o^bHbT!Qcy|V0w@ErO3(&{WmFP+
z&Vun`cqGO;@pD9yL=d;=Yrv)ao*xJWujTmU`G|9*o{G5;WJP`3Ytsp#re(~WsjOho
zJA0G?M7XzBC@Ea_=a$lLy3tPur(crVmXPa9!x9tO|DzacoUJOL^Yzr
z^8Pkm0iK2;&Ha7()ekb-eZb3sNoaS>Gh7b&Y8yL>K1}!j5`_b<_U^2$R$BFXb*8o^6U1A;D)r)#siK@WwMCoxh71(Pvof)t
zP(Y`t22-1{M!?zTcTDz}Eu_73fhoeV(~`Gf25^#gOh_NomSGloM{W1a)k2v9W#*T=
z$RH%MMVJ-}i`*3Xts}3HO>|ZKsckpo^`!StLVOF^_bu0Ilk^&je@a)D>N=o};vD`rjZpCelNBc_!s5PzTv+x1Xg<
z56%WH&SbU3g-(&3Y{)JsEI-)?^{1kv5`Rb;rQL(flxX9#+hqRcu%MgM&M9lNlHup_
znJ*JNPi>5hRg@}6niQ6ur|YgEEck^wq1JPSy6d2Sxp
zAta?wlhd0`z1Puf8P_?J0awYD$?Q4U1b+9Hs%&ik4GdLu0{+k4JO)fED#UY
zVbXD#q@&F!NGWp5WFdpbOnzdjdx9nHEc14B`x&!uOF?yI@1jA5vYaf%%Sr->pEe+T
zevG}z!%|IVg3;UFBm4W0ct>Xa@e`MD%9l!fG*32fgYjyj?We8=Ecizh5;XgAsZNQ!9my-4&il)?Sw3)DhbX^^x5V|GTp2B<4f|(sFYSB!&Gd{pZ5mPv4cU773V>i&
zb6@3^m|MF%eO&n+>Kf=;Sm5&l0RFPus2Nfaw?<2Y(INDHF_1p
z_Rv`bsw_ZX5zkXUH(?KCYC0i?8Sg3+XfZWcshPu1&CXRHoUWo0)DE4nT+`1hA=8NbW-4lcf;z$u1M}mj(dqK7jOd%5yC(wfMX&RDQTA9nx!*X}m
zJoY-l5>qS}vo@TG+*j-O#GhJjayqZA6oJFK^3QJh7oTA6q})yNJZ4B5b+-@s{K7WI
z2;0MiS5>B45}lG#fARc>rlA*;kMsO$49
zd3$vpn{#|^Qs*nP{>mIdM6@!QjOOqX-O6hjbk(ZFe%WIljSz6!Oalzx84yXd+e?z(
zOsTZ~KPx*A@Hmd^0ORfTJKzo=0Z=R=DM17zN(YJ*K|21TFp+I4mO{mmW!jFFvUfLk
zx5VD=Y70Oxy*W=Sg4SRmdjA2gJbgL&np$r3Ec9J-5VM@<6zL1_|fgb;g;O}NP`>lG&#x#2n
zhqX1`Yt=){m7rtmfa`H?0=}|}eWBD2tIl!`v;We&pwB5c9w?y=;1pZOIYM7R!-*0~
z4y=xDq30^4;|OR~LMc{POHVm1Y}PBdgu)L|bvSp4ZQAqO#|~ZxMxrjy%4G^4dDJUt
zbzv`;FRii?M*M_*gSeIff~rrvV+~Z>bZww>0%@gMK3cA5180xIL-H2h&*y*ujNz0{
zY0QUJdhjuH&99~(mAk3ehl76&z2zX?GJ_n#G@WNnN<)uSb;YS>tyom(1Y;=z=AGm7
zh%>F`dk810^G5}A`N##<0i#N%-bU1FU{$odgdj@~K8W97z066k^jhaw<@lA>0!B!B
zusgAl=AI<>IgSIc#<}u*y?L>+#Q959W`EL=*AOAM!#HtR285Bly2ri3vwdxCOacW>kM|hUA*AIHe+f?_T@1H5H)DcmJn)|9O
z>He8wqE1}P2`O_G3XI|tCtr?K)fL?N^Eo5r7lRUOXQnbx?W!J@%O9Q7qCZfoYdX7C
z3xZOv^i*k0J+4-F?_Z+?_pJo9p?7+=%x(9<8Z14zpFd;we(`+W?YsO)URO))-uF05
zSR-?x^J6trYnSd_U0cR)0+UQIlHb)0g0C=&tCufcRyE=>mVP!EkXk2P@|1--><7+s
zBu5=(yml*WR2LvDd-RX`k0EbXh(@GVX|?KgDD5*4714wV+2&CsfL)Go|
zjC1Umb2t2RdHJM*Czk=29wQV7I=9-1#VN4Lc-B-ro?hv?g6{Ot_b*wmRSiOe4o8=(
zh^$ljg;#H^G%*FKl@^v1A+92FwbjG9g$q-2w$$NF3|;=K24LUXMtvPM7N7Dj)|OWG
zWn)B5l)yPg3B{{AlXAM(A}%99%NF2z&;#=2m_s6`U0GTtI9rZ7YE){6Y88T2P>ufuo!7WzM`+P2Eqws<2}4I9hc))FRnx&ID;3aP
z6&Awi5|EcsDnJdOV@S%Bz~-~0zKM&-9agZvNF<|2VPGrwGMP3Wxm1~5AWUWnAJbV}
zYG*mqprdAD#EEP<9rb-u78OIF8ZVZG5UpLIZy$JG?P-VzsOwRU1H-u33(S{5p{=v*(5NRa
z*viJ6jel0e<5v4;s0J*32_XtRT+}h!;5910IqJE72@as5jrw+$pFth4v|4_2|3-ch
zb-PzubDUFG*vhSyuRgSY*M2wJcHaLHv@f^2zT)<=1R*X?p^AJwMaqz^m8x
z&X_!(9|oc{X7_vZ_>gz_-U}zo?ecor1}a=zI`Bg1m9i{?v$xo>uu!SGymauj2X5Pf
zP8q%99$nFOL#`9Gq(tR)=KW{<;NU3$^d8b_pa7VcHlO4^SvVL7{p6CTtg*N|w>
zDeXry!l%o5K!zUT~R
zA5NucMY6(h%BrU~+JvF!$j3T9rgeKMb@z2|?azOGX-I8F^?!(P^u{x**7ysp7oCJ}n!2a4ub$hFp*g(<!Bt-
zj4~auk%c{M3Y7>Ejg+`3&x1NCp;3n2dsU^rP+NfWNOg_|zUZjhh*N6$BE{Iss%flS
zRkRfQNm}_#?L_;+h4#_I#h6Ujl6Tk-ERKBF*gd-0Qoi;C2RiM^cAnjlzJz2qm`UkX
z_5?c`QxIc4BFM38cR@{%FT&?)oC&T`64vp(#QKz6yD#BvtPsd&v3d@d-7C%o6ea2a
zh6=dzF9ff|^1tF1MgOnZ2F!^WjKWmnVe
zit>HBj@6|rMvj*WIakCwQN^5{GISpu_zd5FvU0LX%;n}?#}1uQk1Wm*Jk+k&+sCYR
z=*wyyEzyv9A|_TA&!I2F4PDKR7SL59FYvFxfoU677{&jm4XTrQp?5b{R$lPM>~+kr
zPxzKXN)54CU>dG*AseA6mXUNwE}L^f9nOTb~dl&%ZRW;PMf&P
zIqU%6D&P|E8wyyhBHviR6>Rd}RKOY~_ss>|oVh;p)&g$LtjfuLIPm&m@U$6az8e>*
z;&R=@i5CW^&#ymRAS7SHmDRz8{{B;_!y)r|aW1
zjb422+_1=B?}roY7eeNkXRiT%GX|L{v^9-{JGhuiG|Ez=V7qJ6*HrtT^z8g{*2Qk>#yGRu_DCk*8*54uQBM^F+csB!^{D
zmSkCu$X%>HkKxOG0%2K6?Piu!6kjG1NGOp!C$S`w
zN+w&fEjMLHZpjD82g(P@2g`@ZhsuY^hs#IEN6JUZ>*b^64e~MavGQ^9@$w1siSkME
z$?_@ksq$&^>GB!!M)^$nEctBt9Qj=NJo$Y20{KGuBKczZ68TbjlYE(cxqO9urF@lq
zwS0|yt$dw)y?ldwqkNNmvwVwut9+Y$yL^Xyr+k;ZS-xAoN4{6SPrhG%Kz>kuNPbv;
zM1EA>B0nZ?l^>U%ke`&FlAo5Jk)M^Hlb@GgkYAKvl3$izkzbWxlV6wLkl&QwlHZo!
zk>8cyli!y=kUx|^l0TL|kw29`lRuZgkiV3_lE0R}k-wF{lfRdLkbjhal7E(ek$;tc
zlYf{0kpGnblK+_9-3Kn`>D6ZjO_Me<8DTw
z7ov(bw^Gwi#G0Ses#P+9}pPE1_t
zWutoE^-VBv(;a%gZwBom6=tdL1!lHLCcc}D
zZOP0_>3TOFG^pgpUHY}!_q{0EA%nMTM#VyHezSr73ZL$n&68QAi89oehljrJMtPkk
zi7@U?$Ug*OotPl)4!oqFB}%YsMOKGvpI*bPP|AwNh$-?aL%8PKvm4J!|5YeRo*
zJL>~Cb$dE01H&lLzH}{z_VBCAW2nxHT)&$ITPF5~o*A5Yp>n5hyQweQS9|G>1@5~o
zcbElWJmbCh;-hb~(5c(*_~B?@&BZ(2zxTGxBz60n!`KC9>~u4?KX+e|L6{zYfk)il
zO<9m+Q543o)plNy4o;M>Y%sxN0lUh}D3GTAXaAVYG|Lv~tVq^KyDG
zcDq~Nz=X7NU*0q|345Mzngee*>_WiIVAg`(C}7?V=4=9lx44$yip_*Z6y;(Q@Cxvm
z`Zet_MQ%m&IsIX>`0fs0P=_3OQQ{@DW)h`4-EA*`&{YgnGOn4MeLowRrn#9ayA$Ch`Z|@}jb)D*BUD`Atn+8Tx!-V+bv^-CMIzMoDt!^v6J9lpmOBDvS`=2G#st
zc?1b6k34Ap2+9dfg$|_6kx~L^h^N^WRGQIHm?0aPd4-}3ju4yYGO%rqjB(bpuL=XL
zZ&Q9+&B4mZ4|}FL3cG2jgwoVTvD@qM0bHXoDF`5`G0jP9W12!wl*f?d&Nz&{TOdD#
zC8pOZJUC1%UeF6~mc3vY&U#6bO$Vf%Ct_24rA;PPkJ?_pdP8x3r+gy`gZ8HBxxFs<
zntHI6(x%xd`R?|DuRO1p`Fu*2hC}aWV>f5cj+5d+43#Tus-^5kzlS^uD(t)ItcAju
zzGwQ{L}GkK7wpMPrLi&jjSNh-<(fWY)J?|pb4>}n7mVtD*mt#38DO(9aXsyCXum>r
z<(SHk8xu343&X(Fl;-O@CW;{`In{}$NIW}%lE7*#IWOx3nV)2S>O~-Kj_gT!lBK2|
zm~A^cO$%7;X$E1Jl=8B25K<-tONVaX%xV{RqnLSXwr9h5cc|kd1&he9t=BNr|=cARI*NO+tM=?BB@wJ;gmuY7di-!v6mYBN5Homd)
zTt;JBF&(8iwN3xiHMWbB7Bh)n?J}lGGd4qpA?U;E0D0xO5}T2^*-=V?jAU-YPh%5T
z^R&He@nAzkp;0hn>RFh2k-x(Oqfq69!0-sbVHi^`%962Gvgu7Tpf$icMW#?PE^8hy
zP`XZC`zcdCT#XrGlOBr05G2ayZH~jOeOaYANy>57+o>lmJ*17xfuyY0R>JIMP=>sA
zu7JU>iR-r#CRTsk1uLRz4s)UNu&rmD%GgARV*4779GNtkIjC_jlqe?EzPD8$~>ZRZ)&=~icK!A#>fmE&|R*=<-*o1Q~s
z*B{O%I}_x@E&`;x(=HN-iwI7^^j;syD%nk_4AdzK(iol@q=$;M39fYBya{BIT+VA-@~fZHJ6TybY&(s8)^kO}-}T9AeyA@!#X
zWuRF>hw=0s5L>7^@0k{04p1cBnYK;|VLW|U5-9CVr*BQtnBkm0id5E5v)D`@wdKa1
z+w)C1*$Mik)E(7RH`!Do)nKu1kSZbrbQ4~H=-W)kU=(6NoB(E%UZNzYo`YFbNg0cz
zlEy0B@?JzrNQxHOOR3!5-o0l9@78WIi)!h%m-4CA3|j6FxG1sXq6DeAC(+fx9n0qx
zuza|&H7QtI6R4246^yeWpbMO>Np0JW6|8K#X@A_@9($=tpiTx5B%wV!-?X&z4F|dng0X7=w|2u

literal 52888
zcmeFad7NBTnKypUy|s4jdsTPW*40(j-Br~~RaaH7Rh?e4?>kvK`$85%7Dxg#VGT)$
zfFMC7AhHRlfGD6OuIPX$=qTVw&>073)OKLJ8l$$U%lm!qtqx)Iec#V#e!stdzvS5o0n@D`$jus)=dm`7dzjdjqhK-k3aD@bmrne9#YQWtoz~b99v`S
z`y(@nzosjG`h$n*8Oj#74vN1K^EfmxfzIL&_u%eVnL|0Evo-j~*!#+{@;i>(S&0e5
zQ)e$`)*b8iUB+se=Az#(-sd|m-Leklt9No-N568M;Y>GqR{S@92SfXqT_BDfSGW9M
zSO-T91f~^n_V4`7-#oIB4UDgq#ua|7KwG|s%P!&uwfcYPe|Yf!?LGh*@c+1<|G!bj
z|MW9@5BMWlMlvf6g1i$SRb`U!0M3$hN@EMDit`#_AC7Aj2`r2s89zRLLK=S`(sq39
z__6WZB~JU*KaT5Guu--H#{#>Jy~MuFu3%qbGW#6+IlGVjD=Tm>dxM=}Z?RRZi9N#>
zvzyon*2XGq2D5Mv+r;i?-(-)ohuFhh#}2W}*a6nX{(~)N=h?rpW9;|r-`QFAS9Xs5
zj$Orm%l?eoifDlrvRaQi8c_oiGczl*F*|gGlew6id6<{^n4blp2kKZo3$hRkvj~f_
z1{PzDEY1=v$(mUUOQGc%)(ZX6!LlsJI$53-SvTupy{wP*L#ve80Gq-F*$^9MBWx<0
z#-_8GY!;i%=CHYJ9-Ge=u!U?9Tf&yIW$Y60V=t%}9R(L|g$DWv
zyPe&^KFaQ7cd?JLyV*VL@Vzn
z&N$}+7rBOOxt<%ik=Jk&H**WOavQgE2X}H8cXJ>2^8l~qb-bPjp&&`~{{Q{YOboCb
zP}u;?gAu0^VWIJLDq)nd@%1WUys_~ODq-ZY@r^2B46^Y}Dq%FT@y#k>T(a>kDq)1O
z@vSOhtg`V-Rl=xcnkl
zDH|vLf*@I$#8o1VAfe9@k=Br^M4CgY5@{5vN`#xGDnTkUsY-;hr7A&EGpS0XX{0Ja
zdNZj?kmF3M5+pj4@GglU+nGeiAV_&8(J_c@0*Q`6ko-)dV-R!zljs-(ZNMZt20D{mJ;)^b4FrwIB>D{m-N+>R4FoO8
zq&AhHFPTKwfuK2=MAw0!LzzU^fuK#9MAw0!SD8fDfuLcTMAw0!YnfC~30jv)bPovn
zmq}eJK@&5H?gv39Gl}j8u>ox9eh~CDljwdBG&Ymyeh_pwljwdBv^bOKeh~CIlLl0R
zW@i%J4}y+o65S7iwr3LE4}#uj65S610$>u|4+0io65S61Dqs@b4+1`565S61Qee^y
zm4F$TL}h`19+*UBfq)~JL}h`1D40ZLfq*TTL}h`1GMGeVfq*xdL}h`1JeWjffq+4n
zL}h`1Mwmopfq+Yxv{)se4<=E$AYc_HQMn+t7F#M81pLCJHz}kV-nQ^1pLRO11bRtGKuO00w!eARVtB=kf?qj;6x@-{XjsBOrrXM
zfE}4c^#cJ#GU<>?z>`e6P9-2qCQ)5Mz?e*;x`KcJ0)aWfIjJ1boUQsy7Hol}S`@5HKr~sNNu;S0+)tLBO$0qI!ea1K3i%LBO_5x=kgZ
zTqaQ)K)}09qBekle3?XT009FtiP``H8fFr;0R&vkBx(Z)2$@OL1`x0^lTN4v)XXGm
z4+!{~Nz@(?kTjF1Js@CeCQ*ApK-Wy7_JDx1nMCaY0dX_wK9zvInRLHOK;cZHc7lM%
zne>24K;}%Mc7lM>nMCabk;hJYNG0HQCOxbY`IRJUKL}W!Nz{H2`v$hueh~0Ilc@b5
zAblo1t`aamlZY1}pnoP2FF>#ZnDjZ7U=c8h_yU4$z$D@e2v!1yzM&G=VoUr5aXq%gUl2E7OZ)|KBeuj}5U;`ZEtR+l+cPS0Gq%Kc
z5Vv4Ud*Z-a$d~gy
z{0X5&SS5T}cuVw(ed1Dat9XrgOngmqljc!vMEgmttaIzu>kjB{(><(vLHD-q_j;yp
z)X&u4pnuIUXgFYa)95v}8$V{8sEO1pt~p%u4>j+Z_L%N8ea`fAv)9~Xo^JlRWv=BL
zmN%{S)^_U(>tXB5)}Pz<*`BbyY45X-+KTQ~lfgxA~tA7y`qAs{&_hAFMrBH(2+h
z`pfFi2M2=ZLSGIq4L=b6Qdo{8BF7`IM|IKJ(PyH+Zs=;bzhNRaJ$6m(&Bharug3@D
z566Fw2Y2?(XS+xX0YHujh%L*L&T)>wAy)e!EZTyR7fgzHjws
z`>!ntrNPn-r9YR4%Wsw^2I{8_Pq|^r{Zqa_<#&TkgO?9QhGq^OA9{W0{BZm5^5Kt<
z)Qn6Yxpw5Wk%_4%roK6C?zF3>eR+D<^aInsHN!h&-;Dcbyf-s4vv1}DvxHeM%sM~Y
zIy*8uJ9~KcqqEUjvxx(DVbC1pa;k?MaZS(G!_oaCg^Rx3GoBzs!2N%4zuoJxp
zW98Wk@o}MX>gv3?c}6kB
zBfWDL&o3u6p@_a?&f3ndt@BcqCzpO?FnaJa_peFK+rr=O+%UII8wqI}`)4nj*B=w>
zyJoeRc<0KI1W(P|x@^sTj~$Foxov6JmU$@>1n+-W+>W|nWO7f`5pzWIQAf@pZkLai
z3+yHz*)}|?_xN%SU$p4R74!ONEFbl7N7NxkJyG%4
z`Pakv6P^x-Bcql1Vd1^X0%7+U6)2pGoC;U|13$XMr%sNIRbJy~WiuW`0}SD7~PMyje7Ixo4+gmt4oIagCl|FnJU#HWDtM{Xx>xJjZ
zl63JLPjWPke}{Lad3Rc_l;sNN^2@v{BUgmum3<`ah!<~w7klX*Blqwk2QN5(_CwbR
zd*zqX^2=#1mLL`Bo*&|#%jh00&%=#{W^*COyX2QMT&(O9j?0yHJQGh=9$RgjaG%ga
z_i%j-tpdY~!hI*xCsSL>Te$z^$@FHvN`82Y$_L>q{5)M7SGnMc`uK^HCo@}=OYXTZ
zbzd5HO_p1|mMfe>{gXmH6(>K;S8d_vCofj`d!Y`+95@XA=y^BlK&A2f=>f`3N*gcm
z(#T_Q$DzJlTtCT(GF*6vKGb0H1H6As+<|`Lfz2BMZ^V2qAIob!Igh9d3At#tB=Dl6
zh<>9lMt$POKa@)HOXWWp1V_DH*zKry2=b6_>?-+=>(}w8*VXZVnkaEz`jgyfjnv!i
z^%4G>1%5;MPvd_*4P~xAZ@chEjHoWMdg6(G-kD4!TX`~}*V(vEU;P)PT^sl1++JUZ
zqZavGQETN5t=Mtvim_<1P%PlyVu{mUv5*T1K5vk_!(5yiaC@5L+qxTrv9PPbUhAlJ
zxGVv#*P0XIGcK1U)76}Coe9g&8UyZt(>PWW3&?i|EEJ0em!r56z8=3>z?J0k&)8%x=Tm7kJ#+n+Z$7=Pg8|kWV
zOB~I&`O?;AqeUZVGc#M$?PH^}MyUM|EdMDSgeSyH9)!3fm-TwwI(>-aJ1Kf{*+RZE
zmQ*&06zAPP+PW>5$@g8p<=FAwk!x?eu4|}NzSbb@Ua~Mf(+ISm80p6!CSA;B}BL{ja&O3@xZ456S
zl*D%&TfK7RiHXPL$Gy$X9{I6wn9nSv3w&loPMO^DOFY%aKPJEIHKut=HlI@egx#mr
zKjC>}Qf6Lwxit8xva@EW6Rnfs&!Z2>wAzDW^f&dhC?s7so!!u
z%jJpXiy(e>c<<2A-XrwYJuxvM^i&UUMt-?mK2<8Id_Wsyl%-XqeN+#bP2wl#6L!k~
ze8(L;ddJsaT)+OsbwUpcc^rRL`BPEufvkcAjtZ4K)QkB5uDVd}?}UEzLf}CcBz3-+
z)c@tLfBox6KJpPh<^1`-UWT1&S=1J^ibLY?vr1j56ktjWV(MEK`047bfog?!~}{CpNdTI=f>oh
zCc>w~V997WJQ|kG=x1j~%cb_?;YcKWd**bhG{L9b?hS|Cx5?j9^qVTT3h;|uMZRTT
z;?)-|*m#~~Ljke@uUcc(kZACEAR0W7Gy)`=E{4&T;@Mb7A_)o9knA8igzI~G)~kpS
zz00kKIDudx)tiRwh-f_kUZnrIkU
zm>J&FS=TN)4LY}b+WJ*po8z60jh%7fxp1WW(ql8bwy&Dz(TieT!srR5_mx{$%;}5O
zxYC0QSFK(;y;iR^Ch9~n*xR&z=dR65D_=I=XK`pWM$3Ihqxsqzqbm?_8)~jK8+m)S
zB^1*++}$JTMdb!Q``W`Dg0?Yav9_(B=1MHe*2d$xcpPAxWVVVW^yrVI=c7Icx|qWU
z^bm*Y8pX@RSPYkd8k`E9i&UHd4DY=Kpm4S{rk3%5_z1>!5&pbIcw59(D-ZHmQt#92
zJZ>L}9S?Y{_Z3Ot7s}jMOct?$KBAY<#?e`$`OXs0hHzh)Lu~4;LIY`zd}o;pk992G
zu(+dhYkU)r@at}|r-iHIS7@5I?%CX|*&n|~$k_Rj8#s>$>l?SqCt`im`eJK@daq~3
z%5^Jec)az3P}i7U)WNfhvW+N2Tr}!*ik-spX(E-=)|qY99Ee{lPG2E(icaU6#p3$-
zmd03L44=llICJrgp@B8o?3#h08H;C*3-AWkDR>fdp_<)n}*|x#QT7-YJ3?Jq+
z_#EdcV<_f{=3|qiNQ6+%6RfEUVIQ_Gj4axcPSsU6#!y3?{ssa`m;m
zSGm1Z{TnNP@HaMj659*sh3Dylu=*Y=9YU^DmR}-CUE*Ep=2>NfV|53otMXU87y3;P
zIhZBCY}}U$^;X=uA3ZzRkr$s&r#Rj?&g0=GdyLiO-&;M*RI^~@`e;A&jH(9j(9)FFmvDU8fgqz!&f{s{pr%tpad`%&{
z(c!b${7wZ+{Xsk|e1xJPMxGRlE-oIf{Aczbb1(2e2^%Z7i@c>#{*$nfvYOcYzXtYy
z9@o}D?>M2^gYfXDFj5FK7Q=)!>7TYr;6_r9ey{xMs~(M~r=-)%)2kG8O$hrU`crxr
zeoF6DFS%De66yz2Ez8r+@)+LQScxykHK|NxN+z{DEx$nrX;qesE&K=6jyMou3~2ce
z+@6%*PRc(^e4X}whA~KDUB#L=;@v52)&nK`_}WR&yD^R7nMeb7UMN>w&MQ@30_E$`xbLR<9Od%@?aq~-p^H(6_kSzS5bh?;
zXXHK<4DK6o!&l{ZiWv66qWq5XMTY~*xext-GJqo>a5AWys^=6N#}Pd(?8b-yZa7%E
z9Z2L=epY=RuHDY3D7p>W_*XMFUlw5uoT&Ttyfv+9(3#Kpv_hG6^y1=>Yn;7(+q~A+
zc~d_?AQS28FX>s=wa#n9&#K%$EItVt=7sEPNBu)utEN?xhxN;W%z>$_8f|AHNk%Z*
zGv%5c3$vEm{#APpU4LNH^aLNg`qRtgqpc~y-n@R>w)OLSLc)8ksmkvz9G&Cw>6;E*
ze`wFD{#r|R;f`yjEc^7;gYvAwv7BI!rEK=hv=v(lII8?^@(4Ud53R~pJrGwtVqbj}
zuTg<*(E+>&#F#BW&{$!)e@J1yJ{kk?6Y
zu#?;#(9wkQa*mP!lw=iPwiUS^7Ab^TuE;0N(;vlszs&zc2c>A&TvoK=S%m%xzo0^Twl{MXH~1Utu%G&KsF+Obzy0K=9ctf{?%aT$gGjhAib{+
zlu_uMY3*!pGim+l?h*ZTmQtKru))xiNFgS9;*m$vr%v&IP1Ck=vWs_h@t-{MNal34UM
z*I(2pMU>>HoB{#@|J%NpgE8F=@5$fqghH8q_&Z%s;s0R;XL`t^0LxPo(v&p$0z8rq
zn7()yetd{O=M}>2VzZJnl=YK4`haXSi8kH}1DbH}iqW}Z|oCarK48sW{
z3$8>*&Zh-~azzJBL5HtM-bFG1C(6_F0HMNr;ZkM(XqX?}S6Cg1ka;)p7Jqg$Jbh33
zl4;(PF!pfvR~T=RxAu8)T^9~t4woh-!ddhwrH%A_8sB7H(=&1&A2~2YcrGBJAnz3?
z=E?jQ`D2l?yv=(!auS;I`Ecdc@Ja78VIfy}x90HW5k>E7AXkrzdxbxtHLb`1ScWnk
z9U&W8F0|^*cEeK(GB@3lUihRZ5ws84T^66ShI<+lb$Xlp7yNW2z3}s?e%?yA
zOf%4O>Z8HCB>#fG>YIiNgHt=oqYXNb0}PCoc+vqCOqR^{3)YL1
zbfoZJZJam0F*e4}Lr!DNoQ((MnP?PmtmV%I;=p2Ry
z<*B)~y!ns90vU0%stly(8Oe7A%&NH9s!xFT?C+I%#ov~rV(_Q=0C84vQFoI+ABa@N
zJl|1#wO)8nyP-4YQ=WzkjY1#-W)pB(NpJzZltc`E#q9z2jh7r>CE8JZii+neuJ`(M
zdS407KSjO3{MMr%YrMWcWcT(jxO-12Hne)zf&JT-bo)%LbC$HM&kbL@u9Jtl=Z$XK
zxMF&{yCylnKc2b8=I9=2*EJ1=dki%erwMLWje)z`Y@CN%Q^wfm<4x)LrD(Xd)~{>q
zU$Amji|~bp2432G_kzB?n`XC~{as79U3p;FXyps_LC!nZT|1mxzhod;<8Gh6V&kUK
zdHn74E#aXiUB^h5)3!_NwA9!P8jH(uS6i~VFV{No@!>hPC?r4jlPgv&o!aI!dnWtk
zF~yfy3u}8AyaYyV<$5a&lb8ek1)nzyZ%-M~hGIMm=aAQ}hrhW2p6Enp5l$aYht(5p
z+!5kMZ=on=mQ0=T#Ehv+kPj;wmky5HKQg$~s1XpMm{#g+2!|UwOSet#@F{{O6wUSC
z)|-olG(z{G!NEh_!X>F`9uHg>(^C4%8{T-y;dDAmaj%fluX0#@o(7NCX1CjX?go#~
z>Zt57I9wj9&1Utu90vYIG$II*D7BB|*XP9Jz-ms&eCi8~CkPU@@>l~<16N#M?$=mn&GePX7Jcx@n2<;(+pt
z#~bhHlAl_eTzAE~#Jc3Vx~Yk9Z(vDciOU_TUAMf(8_yQAuHJ9;AUv#$DSZ6^x>&K}
zI3$Y_914t%^5;gYW*kg8x|Zz5m&L0w{wPwXyh{?!@T&XJ?dgS5kO6%iFBP!g2cHto
zDKB0OCgbJ7*-LJXE(>lM8kxOm`I3&#&WvB!A}g9<4Ha+dvGA0
zST!>?xVb)<%Pv{={bftCxu(SM-ifx+fp{{3D^*$WI{M!8kP5)>kcM#!pMX`E;%WJH
zNW&oC_w}#8sK|_?Vh!G54WuB8lT$!}(qVqF9S0-v?BFA#j|`qY8SNSFZfNKp?uq`(
z*`bH|jq=#TLuY^5&=alxpg#MeGI!!#d`P~Q-|*1jA1jWF
zOI6GLF8KQre7;TKFnFl+H0ohF^e}{9h?#I<6{|jP)gl$I9xZ%o#rn_Yiv~W*_45a&
z%$h$kn0T6N`s9NRN@M7YzdG@$ftkx^&s$@+4qqOP#^e2?{c;dZtzJ^?4@wM_bd4T$
zAlXe8d9d=Nu&}Zgy8cq^JwVz*v70uD?_j1DhgKVbhiwDiL|#ko7<7F&Uy^V{(Jpxr
zm6!)wLg#@15^bD@N&@%>!7#D08O5J=@3%|bW%9hJ{oZoe?t5dkG<%p*JitG2cX%4p>7qYH<@8XSoZ_EC^t~Gns
zbjzRQElnfyN1FI2gvxm#?Dwadw_LuZrNtlM^Xw^Y4jpPad}9lLrv;^I*6#3l
z#6Drm0?i#QM>YALzJeCzY8Gr1`b3Xs`x*_3Zs}Uvg-=UM({NMM@SPgX;qXa~7|i&6;CvUzl
zv!%2pee#Nu$zV6S5a|hrD_<+~UlR5@ncj*B(N6rV=w*D7?k98&QOyx3P*r6fJ(b?6
z3(-^Y>hDDm?g#eG)j%evg$FJI1%d9-w0FU-O9yy;endDpS^d1xSot_VYb+Fu
zB@;aSqTX(_xOnMe$Wb1+bk_oJdbB$}l82E%ekWCTRsHR}>Tf4(Mm}K7u^_Z;4#NhO
zXCWl7gu>{3oc{yd@3&X+kD4z)Wk9-2`s}F;1o_DG0Z&IhHVR};PTrF6bY(uW2u`2I
z2jx}$zFFa^u~X$!rzk$B){FevR3`buE~=L{=Rp|gLy-V}7Wo4ouG<)$p+5KFvXI>~
zSr)|Y{8e*T0$HKfDJ^rO|8Ns&+e&PeO2*JZkB1$I<
zBNd}muJ*UeyJKS%>mz-5Oe_lDB!7`h(RYy2Awj2y2@635ETqQU^Jl_m?(a?Q*|Rhl
zOv$G*e5B~Y-(|C=PoGsx3(ubkmwQs7(2~7-Qu65xA1=lWcXVZ87eBj8I9EAumET`}SB;=qSy^>z
zMVQWBTpq~>@|Px~szs?}t|$o&QCOMzsQ5%kZVAcX4hHWJ3Ri^A{}{2v%4=&v@YmcQ
z=5?WvYzWH#77X$g1fUUJ46af89UiiSA40G2lb49pq5_W#UhU*=)~k!VhKf`cpdTrH^#=Iv{)&E8
zq|A5@NUP{J1;*-i&}OY7R8)z(6itOL07Q&`gmU#dx)`WfoK%i*FABBOrw$gz>H_WU
z<-r+qrp+1{+R&}{bzIwb=XOeoXmWY9#oW5<%6(Hhb~L(0p|1P#u4^qP()ZkxK5>FR
z!Z~d$lE3B4Gp7xgdJ;`mo5>VU>^lC*k&_FX6X)NsMH+I2?s9qdZMk(utAz~LrB(_OC)2-M4JG;
z-Ny4}pk5zjei(gFk3b>fzFuD*D&0oWU7ZS&$xf$TQtLi448Z`wD#C_BPEIs0%ziq2
z-PqE5?pZo^UHFEm-|G|vefwDFBG<=0=33M_)*hT0R1V`crWLU|x@>>bfF)F`ZSdAM
z)_I)f#!N%^p5XLEYC)(js&}^Z&zL>6tI=djns~Qxjw=PL&!~&7FxA9ARBH7hdO>h{
z{m~n!Dxc0Ynw_5dc&)cVTN|qchHEN8r$U~XvE%qhFeWz|-kE&f{U
zROF2+ZDQbARo+k}gVH@9Z50*CaZI*w4unV_@1`W561L};mU$O_%9RC_S%yq3BAk(5
z7S2sxMUE>3RQ8w4$YP*G2ELyzz%K8a^vx2!aKLj!bNU%?(EC)x==J0jWUlpDk?Yf0
zDEf3he*bmuY1Gh%8xg;=XoiU
zI-1}eOFQEO{?n<7lSiJ7C%`1$U9RbGE?dk7N9WS4@cgWFcPGI6ru~~Y^z@cjci%a0
z>cO_Lw!zM&9ku?a(!x{ngO3HGJ<;ar-HC2(mxfcB${52J$#6m#9gGzfOHnZ*JTave
zWM1gdA_XWygrM%l3t1pQ7v)}5_IH~%boZ5EJ}iHQ8gRN}=~hx)TbFi}J^;Kg9-ynJ
zZ3mQQ(&d{_d*wP*=L77l#2=3-xq;++pa?+{wd++7P?#%`dL;JX4JWm3#RUKh(bt)E
zH;@&S*L#qeB9atBHV7c`hc6NCeihj}^O214j@*x|9{JIwEv<{wt%bqCmp*iguwsvMlsfAwJhvd7{r-}9tQC*=EEPMA`j@6A1+
zoL~Bwg4HMMZ$_lNmS#}FPOsKnpMwejc1z|(U{H^*4EH^T`9(Feo`w@%*nKAar#(bj&Qa@hl8!AM^}S}Srcj(Z9hQ%R;rqiA8)qJF
zxk>(GcJ1o&$umw|TMHDT2fNeY$ii60-OWu2nrfhpdh6*32nxu^RRp>|S%N%V6Y*>8Bdns=7e6
zS48xb6tF%kq9-H2D2hnCBF|LEG#w!YDGJo@(Nbf}6fHv-2#nBEq673oT`k`P9JK&B
zM=-nV>8LC}R#*E=es%3Hzf)TWnfKU~e?l`)*4{i(4n^#3N^a6gxRb;7R(P1Nwzr*X
zZ#&g$FQ1;^-IMkEF7k$-hh6VPpQ5N~71u%H@;n^6uthlBtaw*2n0=GpWUJ_lkr~4O
z6&~#H`5&W3-Cq7xE?=4V%D>{vfdAxezPx;voAZw>X*+g%dihd+D(d9r+KwrWWOLlE
zyclC;#VB81FakB4?XSt7F8pz6>elqJ%wq12rhKs}xsX78pJIz@KT`XCgk}~h;|{N#
z#v45N&=DAaVzuK_QVR0C@)&-&L>Yl+P9u-Vl#`lk&y8O}1BhXm1PR?X~D2w91X3ETqa7m`H&o`6#lva5iQK;izsC*@u)Q(}&)cV~-t0|Qu;
zOi)+?h?`UsjFd=?FNQY?V2wmjit`FTh3_1RcKlC$FBVU4*f1uWPa^^Y>%04u*oj{%
z$NT!e*2n*}VFOIA-SXkSGE@R#Pw2(TJPEwTIHZQ+y9SKn`zNRy%4VcoIHv*`bkj$9
z-{^$se}mvl7tQa}CJ{72(32-sJgNs?O;)jLnN&ahOBO?-o?Ml{tB_GJHFShq_2hv{
zB(0EsN+Iy&P$3@tn80m9Z`5MedK|%ozSW?2nj*1|B_+gAxmIVj*M`$hkE_<<(P|y(
z-0Za8>NO*y+?9++4$K@YXhPwZ21~NvX|vQj%MD(S#nuwlAJ)$cTDhjs5w^QWuV{Cq
zGD)j{O5;FRYo(DEAo
zMSpr>D7d!6(v|4GvNIIarqb)yEpOa+%rG>&DZ+a%jm(_6;!?1ec=a9e0C=UND29krZqi8oD_{Q$VoSJScc{rlbnRxO%ae$M1jZ!1u1?(QWE^TkjhIA
zZM{zsmca)3$^yyA0i9L8O_7_)4@t|56MVmYclP>)N%U6)9KF^RBqyUGQfS6!6lwYP
zhq8Tt|5E((Q`(WKUiB&b5cRbvIR*@B)rHiEB*}5T!$mdqz`umrYoo3xlnuh3p+`bD
zN8}Tb1u)_vx4nEh97*!)!LsmNW$~rF!JFsXia!2a@?<1WP)qn|!8m5jd%y8%-em+3
znSAaC`UG539!OeUQR;Y7l313f8X(rRTzOhp%!CI3d;;DAKL*8tr;_5poCM*Z@+9br
z-Lhqr?>81oH6-ZpEQ%|rHfk7l^e9aGGS4e1nMs&mdO$LA
zo+6nORu-~~(Gfo6aMov$P(uhw=nl_{q{g}zT27pBEy#{#1GB>TUf{grgll1TED*kN
zY}ttu%f@aDKNY!gZ25^33&(B>y#4_$9;=VVx*8gUC~n8~X_SWB7rJo!MWy}X#DcLK
z0~eL>lv1ud^8?ttwxNqkLYqkss=gx1%bbsS%9o?n6d%Y_%!rI+C2pY8D+V+6A$TBO
zY}^gAvYPcwp38g;Hze|t6B0;XDaHW_D;NcIte&H&1jV2zPJz__7~L$+-Uvg(Y^iZu
zb>@J@Wwe{SQ|T_+2J0;zqh6=cih^b%-r9R%c@~*mr*VUz6*M}HQESl{bpc(CG2%39
z4JCut?2H&pIAe0?H71P?vJUeQp5@(ipU~s*m;(u?&8Zc%WH3s5@IiPyZl;8#hR-Q~VdfYYE*m(o*Dt2or?bZ02?^AJaAbbXs5+=&w
z3FI7v%l_7woAQg?uG&&L5I=SLG^(k{*N+PCLRZ$2Wk-q#Ar2*RoU8yflRS|Wtq8B-
z9Rs8ldt%^Cj$eGrNnTqUhuaM#?7U=+Wwi*P}`Xr=Y?7?
zKOcx_Cnj|9?&))8^fwtMCN!qNWkW6VLh_%z{?2rCYNB8+(0z3M49`5zK4){ilQ7e
zOG^=@B61p(3w>Vx+WeNAZ%)tKi;N+EX2{hzuRYS6_VdoP)mBV?!CzmS@e4M4XIGQS
zE8N!__4@P6Ru4Pl$v~)(+&3>x_wEaKr~Te&yUuCP7n@C9f4u=eq;Jm5Ln2X_udEqd
zy}XnA)4dUJMk~sX!H(0|>oT2QzsJ3?@iN4D`jsq>jmo=&feU7|ihvK80f0OhJ$d%I&!NN6two>M2bEgNap*(~W!9!T{@+vYVo
zhgw5{WZXG4x-9Qc2d&nQ+5IE>meSNnI?n@@-_$ysy7r+uzZcD{sc)lAr^71vGyLa1
zw8(2}F6QknJzwDUb*3#=ZOiboc0q?;5ba3^I5<6Il)tHS)F~{VOK%3F?8Hbitc!JF
zZVYvouCJMW^+RoXm-3GIGZE!g`MjN(I(f7IgI6+mfp4D!-x?4fUqJXP?nBqkJN_%n
zl*}`@*z(B_aRrGlN{)Y#scl21p(^~+p3pqLo0Y?wO72=NWIlbFl$cxuDx~8RjLXCO7i5q?H
z#und^quymn12^gGgAJkX%nX~uE;?$RJ?X}fvsQE%EEbJMD~N8B&tviEJT_mjp2*d6_gWne>%Fqx!R9!`}!U!v>4dQd?`m%tWnTqj7tHA8Ohoi6)a-@U%9XwPw51sD;+%qEKV7)EKm;&QOiX
z1?FhPpM&bW-V4tO}(v#2V4fL
z$pDZdpbzME7OSnM9=UWw)F$t;1GgT(dR0fqs;iIRdSIECRwpDI=V^>PAAFc;C97pN
zd|E5nT3UmKvRGjBL4=c!UZaJI20t+Xk}`i<5WuXnF
z7UyH!au>lIQGe3-mZ*`iZ~d9xV6=E+m<(hxcX_8cTS^wmf%H*3(dIqGy?R{<70{TYGC`UBF>)h=wCv4EDphHqakF
z6OZ-f10=Zb>_~U;*oLO)xSu7^;+-kn?sRfzCU+>l;@ZOvG
zqU&`%1sM7MXN4i;6&B|f%4%k?ANj-o*sYb<@aHD%t-3RT%Da$$KbXW+sMr(ytFWAu
zd<`V1h7st(o1&X_DH)cn@CS)s5
zBKS*SQVDIKCioMcuI3(51eyFcj4SR?C3+rH;&;_!-aV%LO{g2;pt8jTdiqm}Lf?K?
z$+3ti@eg!fM7Uu?2#Q1oUO}{5fEivAAu;qu#3T_!fRN6jk1HD=93SF?hLA6^bXBS;
zA~rXRk*3tDr4e7q&TSzd-_3XXLN-2C=e5hHahl?a#C1IYGes0AI~}Qr0+O#V^`6W@oEK-ewzn?Zf9&?dJL9GBnWN#+
zePahV&q|2tdE2hKX3vUXv0>ZcyKcMYk@PbqW47nmok#Y}Pa5*8ODEtfE-q}-H%!~K
zf9#5NgZ1Y8(yri?P5su`{3|ohygT7l>hoRXZ2vR7;fSkZvQQGeg&vD<2NHb{U4>CW
zN&iWqhToPhV?l#4P&v(yzcYqer(+Ys&*c;8Zh0%b_L!W?@4@l|+vGFyJRF?iUwDUb
zCi-$l=!KV({OuTFarj8#U{t)0+8maHz)aH=oQv?R>6F}e%Dm>}(c^dKau4Cd*LTl1
z`(36Qhu7yfeloWzTw~n2)#!4VOpX=gOMj8?f9N6k=tB=ZtaJLK$#B4F(#c2f&1+HYZJ7aa!M&%_k$5d!MK?o+f-p^&-tIMF1CWlpHxAD0K&(bIP}PGAO5mPhrlt
z^HXhgJXoLlH2-2<>Mw0|UwD{5Rt#hP(Vu%ga)3VW
zsLV%>1;CD~XM%Fjyh?^AWomU4v3vsrB)MVVST%1>Ya24ciiK5cem1(Q&;?KFs?l8=
zmM!T@x6hcpbaejQOj}!K?tK1>g_$>&W_G5x0;B^4HJW6wkTrd?#Hu!Q#
zO&0Xqc$SwO<)Mk4Y
z^WCUii9fZtCs(`jz>y;dR~Iy;mN}R1zw*+#o{#vy#}6&;DHT_h1J=&P+k1wu-897$
z7~VQ?mH(B`BF>
z4b-lwHYSHtEuF>JI_0Uns?G|)KN9`d0R#&~%ZF0K}rsfg3RB~8dHHthv
zrRb^BB+=zm~n>OG3?V
zUeg5hXExeWttq=fUn4g08edCjh3w&<3$F>jE^2Emfhp4)LVkwK4QkU8sJGhuW}9`|-Y~s4@{hj^nfN@Sp!Z;0
zcjPg{nCk)jI&_rb*+KJ>5bgAJcA#ty%PvC}#M_@J=->(Z6TWXA=2;Z4M>eRsw
zcbLt315OxgT8crOi25O)(54!@y~bkGalwWQ@&6Y5_ihxf>i4B;19i2$e3OT%MNZWTV4kHd`Ex$>tW1t07>k9RKg$smSq{g`DsLY~}#1
zx}k*^s*o%wz9pC9P9Z-OJX9a-^MAA}c~ojnRdzCK>Vy2LcpB4RoDocaiN|=Ol7>a8
zSB=%wE97J$^-7JeJ&yi*4qCYYtDzr~a~k>p#0gxofSfSQJvpV{LYOBZ8IkE5MV=6b
z?nS>~ahXIihoE?oenq(72eTO`#5*^Ylw6k5Ccn|PaN|O|F}U@`G5?rf$l+`3#jQc3
zeZi&$c4OtFr>(_u(yYZ~>|=A}S#$WY(%7Y*_GZgT6F)RTfyIe(drwb$88J2Uec9}N
zl-H`BS!3dUw|mhExdYklubvn+`#m&~f%M~zO4}^JpYYsJ-m&7rY872cJsdB{ebU1%
ziG~o?N7sWLS~XXZe1b{Fm~e~%pu=RYixN~b3m1fEt%^3RoVB@st?vqxVY$Jf*KKr!
zyq1PYQ--@6JImAOPA});Zm)$47bU0$vHDMWZRh6Ao!n_z(0xpAjs&^I8+2^U%n3Jb
z=t}EPo}TRKK+Lf^Hrw!i;)-QLt3D|613#!I#+#O^2|G<`2Ik`mKMglnydekG
z$=ncWPP?f{wMyy7kC_Y`4JPgj&&h0b1ihBOsQ_0lPxfrM7d14){7~zm+DK+dcjk&C
zybX+@q?wSNfvJ5kI7WG3LU^xC$r$4Hvb-Y0KO;Yjn8_bGR#BLce-(*{X#}ok$(LmK
zqXfsOePrjrR%nN%3M2+WP;FtJR7H`Nx==g?I1Aa+D#Don`J&B38G5YZ-wYL7eA5ca;m&&cg`Y`|McQQVXDSBdZ-GeS#JJqFx-mzGeYVOgR2bgE0F1nM1Gv5m4C?0;wRr!^+F(s$!DTL^u)?<52P}_ClFf(
zdcHsdf=1LJs$sH(tR@@WbD~zO6}e6`FxH*aV3w|i>+B|*&E&Egb&&I-Myt`lsUp}+
zf);B`;Bt+~a~jcTb$ea58l8aPi9vYCed?G~3&4A-%pJJKXx5p%4vkKyb+}Cs=eP-F
z>vR_F^oH~|MUBo-<8j-yTF!MfW(tZJ5Uk-Ez0n}@CIMa*tp?#6cymNkjownD(->;Z
zCT&fP9*z*KDN*fHWIIpVpK8u9gc%_YS_MQ|6$A{PTv&p(7p>+oMoHa&OTxkGk`Y)D
zL0vmS!9AcK@)JusXkkSqpxfym`3wQxe4ZlmD-v`@!5=yO#X(t`ZG%^>
z%6*})53{wu(C44q)Z13b6?z8{-&%6xgG*Ag-u>-?`Si|*V}5PrJvnXm2Aba&5O
zSB?aY`g5VW++eJ@qQx_3`=)<%+QUh2q!_TY4)iPeai0`#6?Mphh#~?-QO%`@q)?O(
zLlvS3f(Qdr3%YQuSG;l!@XrZ3*Tp|*vtD)SpCTDmwCkEYgI@VTY5c6c-fK2{>m%w$
z{$`DYGF@j3ASC^&qz)vcvyw7I=*o3Xm#1Ma%Irn0aU-nDW?sCjhofxDD+Vjr?I;G#2erK`X;QLt!9
z0II4&X!tb>!XvSjSs}Y}YEo$WcEh)ugyc`P+V~L3uh0VzggCE99;*$|0Nt$CU^@^|
z=4GlACWPa$WOC}t?DRf)H^04adUj4?7aJ-O~zsiTwh4^qjbeN{ihHZx0M6BeY
z`S3JPqc9VmGSa7N&XY^=>@$)llafVEl#8ox8#%yAST=*X@2fUqbug%JVRxzW8m4#5
z#5@g7X)Ppj_@S|mRZ~%`RUKG6;3!O!{ioxYOOGk_iEFSp$bRDAq@DXCjApYkgE@v(
zv0&@Q0py`~kd_2XSI|V{l@z(eZEKfS{$ru=c45<64Jy
zOcYyMsioBZHT;tzKB^ZY)|uOTx9jy7pS1plMpTvwP&a
z^@U)t*fW2ykn7>y^&wvBDXtHOG|^!0Ke?{0?_7KdUslc!Qw8h1H%(5yTv_NM~?Dw
zqcGx?#XkiXWO}1~bVK@+_olb-YlL$X{>nFN
zCnjozvVWrTouBbPo_XerKa`miz=?ij2-5edaUrKHl9u
zbqVx-VAt+I-IA#beyiQEQ4ku3>r18j;nBl49j?k>%x9+@LYI=^&En*6g%<_uuK19+
zjp=jAn5cjPdJ*tQs_!KxT(`5dGyP!tLHUlfy7$b^pZsKJ=F^|%iKu{^O4Fj`XL~4}Kg6*zUkP&?>T%`U93~^uB;c#zVjXL
z$&#
z6%Jx<9@1M%U3!`WM)|*PT73%BE=Xsi13v+IZbk2{$2u3xg`n<*1Ug>=wj|a>^g`fu
zCgZTau&63!*cX%0ES68avyCPs236bd%^+qSJJZTH@)9g^WlQE$pUQ0F@bL4uBOJ@m
zjKr%Jro$QS9SWwE@3AqnYjgVk#!sa;6%%9Bhp0~Q(|mwxFU+f>xqlasEO>tLzl%K-${8((+ouD}*lKm2~m?UdL-o7cT{aFaO_nu9P^GzSqBpbneyFo#%Y#
z8^7=Ok*7uql>5n**4iSuu=spHsU}1cz~2g)R5FUF5tOaD(2(^(^)JzxtR7!gG+19T
zTu~of)L7v$C`;smuDiWua0hej9BN)9u1&XX3`I*lwo-ScvvJXqjwVN?yVTYm4Q*_D
z#}f{>g+hPxghB{gvhRB;Iu{E}p)gr|I#?BQU6s{<*yG~P3Qy1IH5=FXJRaYgjn|Bd
z8MPr;v&FZl#HvCB7{OtcwJEW=HVA%4OUDHrXaDH7lMZ(i=klWQ8zkI3A*zAo2=M+!
zX^nu*R)86m!i1QI1Xy3VaRnrYk-?ydm-)pPR*A1b&jFZBxS^^-@Pv$d^QR)8@~!P|
zu^S@gl_lj~Cr7vd(E__aAwF%CBSUECs)Xf$c*|DYl1O+y8NJo6akX`?S!_1H
zCzF6H4g#^z`N9nP;6^;l_t1YS>%0;8rzupu_!25Cd=t-ADY@-&%k1GoifJA$bp)EsVx{!3mA{GWByw1iZ|6j}!02EdOCblK(H
z{Uy1zZY}YKV;rz!Z`1HZkNI~pnX(9s1TEMnsvX%9-6oqCDxx4`r;4lXcR8xd%*?8G
z8W21xnf6pN+SFVZN335Vq!g;S*LIiHs8T(CU{WCmqAcOHZPGcjcKTY-9hsl;HgL$5
zKNglO3&G4)oyHc1Z1tu|2Prx9bh_3iQ!sD
zwZE8oE11`I!xK2PjNM*rX2t$$$9Ftdt(JYlX{{xO?>MT(cfQZhNTvAx
zi#90zMx{k79!b!7KXC(ARCxKuKpu3@6n}bN#Q2~0#hN?@%%f!o^AvUO=D}QaabN)$EeJEA!SVB0m
zzf{~^HZoGizF4|H5jV6qS;fqDHne4n_?hi&u>Ahwd#O*79)FGO7U1zJIByWpc&z}B
z$+&Amr6g$zG))^w(UcU)U{E;GQ%af23imkBHz4Q3hd>QF;V34(N)bu2T7ybg9A75a8Fcbr!kwXD#cDMZbat21DCo3*
z+WfqnHEpnB7e;r5<)s<#dhag{Dw9ct9bIoM#YTpbQem6YpaxxuS-&AM|03<>KjeD7
zUe1c2R9Q3zr%^`xQ$6t)E-M}o!1sf>&<%gUCfr~6YbaA5F#STJJef{_$MB2CIFej<
zKomj~3e#sY8f!pekUvZ!+@&$d-p;#vfmf+?FHE6df2X6`S1bfQ5#NBx>(DVzkePke
zzfc)1nmaWXqYA**0fkC9!3Pab$xYO*TH9s>cMst@#b0A<#}_JhEQ04R#uV9`GEibADQyh6`YnvZYAbz1R6fts+Z;M%Y|8TnOl6%^bs5#2A6
z50QD+>J)}LUTYkyeNvp;_xkbr3Vq4F*E=8gd_`_htERuTI2QP`aYi`fy!uY3zC8M6
z@x9mYs2*u}C8jm}x8J?UJN>M|BY)JrnwUX~K6qGGg?X^R5dv-uU$M9P48VP0)wJJn9Xkn+n(mE2hv2`0k(#OW9<0oDwt$8R8(ANO6|ZyI^0@{$|U{e_OU_
zi(Jm+ip}iy+g5IBsBK-!`qu7AG%reZZ*5z0Y4xqQ3TAQUO`XeWc=IK}+|?Fc4z$k7
zwiR31j~{7_uK(q~v_vtP!!O2Tqzm;2@`~ZvEzHg*Eg}p>ibroD`onpD*)%-6aN{EU
zMoh(zlljg$L1*UX;?vW3#gA|zK7TXW`ux+Gcmgzl@#z^3RiWnwAz&1CbN%H!Yx}gM
z*Xn&>a6?3#r-?ydgN`fR%RH|BBlI4cc)_VyK>-Qk&swPWfZY{Ig%X@(A+G?=5$JtN
z4cF5tF?!+h5E+6@R-t3a>7;}=YqJM=wXMQ`9Ej)EEMD{Ei@v(6+HFu)LNlKkvCTbWV?hxM#nQj
zL~!>mg4Xcfasn^*>iI#=E*|(ipXaHgl(i<0m749|MQ*3xB&;{7ELuR{)kdvDZ!@ej
zX`Fg+>)*9z4M$Qf1ZXY3;M3Xoyyd(Y>o(3;)hd|G8l7HKS?jc!R4OLdn>FS#gGcW!
zGgteJZoS9o57ktOFRbC?@hRCs;bmybNI0kLAv@_2r1(M4py*#)O!1Lxo$R@TE$v!!
z^=i9w$Esb)O&jiF^R&iN-EDVnm)5KncCEW$=TTXuzp{SquIn#-{K`vrXa9Z;@85TT
zOPFjdila5cTX_L6aJ$gIG1}p;z~GM|umf?6H%um2MIssc2Q)UD(hABWC?HN+qaw7N
zRRuIf*h;K+&_VFL2!6;z#z7GnTK2O-tF|uM
z*xJfBdRK;2%-1^_#4k2>yl&IkOQ5Xn*XZR+(3)7Z7KOQOdTaYrG4}e3V~vrHPv8IP
zj!46?741XeXz2wzHkC!fLmw1H%JR+0A@*Ff;n=mu8u+F^DfgT)@qKd!W-E^rk{IfrlX+3A)8~__B*V&UcA#uOo{m_g{osQSdSq|H
zzAi=2u12hO5wLK}!CkhNGoW+CB2cmv{IXZnA>$BwI*fX2o{0!p1Ef{KHip`bbVa~{
z@>js4Ao;6>E)Ig6S8lG~e$p27noQoHEf}Oc27;cS++Kh4uf$0((A^RcC)s-OE5Ai6
z@W9pLSGu}R=6{ysy{qen8`cO1u@~$h9P}11XP&yn%R1}b0(SBK0|CJv2+aSkoc#db
zpZ{H;i~Zo_Z^ezvySkQl33o0(+4TbJ;xgq$jwRtZIY6-sy;34rYJpuPF_ck!HqSN3
z1r3F~06JoOmZv=Dc*e>D2YW2>eotv_4QHXNsi~}3qC);{T)4e-c=^h8gTzKgv}A*u
z*RCEZ>9p@y9AB|*&8iywtXi{e!;0m#ivYf(sY23wCZK;vEKJRIf`+Onb5`=CkijHm
zmfS}k`TLgK=ez%Y-+iKvX2<9I+LOulzUTAr;w!{AL;HI|7KYs7`H9F;0j?DE4am=H
zWwP+Q?}mBq->fKxDEIGYNDvg?rgddV5M&lq{ejS>6NA6v64J8C5h5W)*-`+-q}*Wg
zJ(-{h)q&w;Js7>lQHSw5ScJD?djw;Ea3gRAuA$iupE4J7NIJ6VF
zXbd(uZ5{xW^%|}HXtmK}bFzkDqeJ5}bh0X!O{>Ufmk4YDB)Y
z#0)}-Wbn~pfg&8h%YGo~sTSyB#JJc)HBhbaC8=OA6Kn?A?e?&uIsaJTOXt{0*a>)Y
z(w&#lJ;gE{FLt`C{IQ(%ClWWwi#J_gyVPRXx6e}HHnJ6LikSn!NT?zNdczoVR9NlS
zVq05DUF)({omCcxr>wHP%dDYlSR|3M}FoI1<$AVqcX5=p(g7CE!P8QEP>+_n6y9
zgu~L0**s%jf10`c{^Rikcvw69e)tVWr>EmHGtYzUo0e4v&Qvtvg+g9J<8>X6rZarTAJ7CaW$>@$GE~(
zaf}-!wJ2e4ICu#RSUtErFrUH=PVjb4cgWr0|1IZx!e)HW
zEdC&J^uo{A9)|Gx`LWtBZTlGLaLFe@HECuLV@^OmL1&}lKcsPyQwT}4(AywO>c}<3
zt!3~}5X=<**AQIxWXi>%50FEP=NS?WB+u_dTzLV8N;Vo=*4^$hHCJfVU;eUM%PE6<
zi89zz@-Wc{^Y%k7PgTqO&3Q|iII6Z&TMYyid5Z2i3%3#$RW9+VqFt+6)XOy{`R<+a
zV(oJMhP{cz-VOTYo~VB3X8nfVc)WLme)Becy|+u(RZ}qFE9ZZccM{nVX=cBXoLPj?
zf+z>*2KVFh=+`ySsT+{r-HzEFJPw%M0qhST*%MDTfSFH)OivDB%_l4HEHohodPkbt
z3rY)sg-~Vigb`!lJzN$g=>7rXjh=rN6+TcE2W|{aaWQvtsZ~Bat-kq5=qmh&y@Tg<
zh~0dw_KEqYdn~gS;kCVSQGXgwo5{}g*SPeQZC1PA%;vwvzGvcHm-!=y6PKWeh>Ln>
z$d#XuhY+uWG;XP$OA$0O(Bv0}DmTdC1EOJz{+qv;m|0QoG?VrsD|A;3NU-qq%-@sm
zl#Z8P(wYstvzoOp%?i!v#HW2IO~DW(3G?pyc=ROf*fVB~-2A9h8KkbNmO3v6p77Rs
zCN{8dCd8i>`sx!cI7>-hqpuPs>zvdn3W96x?_~{qpd@R6=pk7X#D$P{Ko5p%UipVH
z>ciQeFH7ios5Qb0@O}_i4C-R!g^8~R(&Tx;>i6v6b!J8Uksf<|iQ)ObqNHz$$>JHNozU
zvU|Vh5R=YVKyZ4~$T7S&UY7&BOdq9&A$x&u(1a+03J6@FW9cx
zZ`KJn^=kdcd9)F-!UjBD^K;MjG=gT6wL5w>(>;71xC7VpS1yr4T%HoU4_vj75jZTz
z8B_AG3KB`-R56n*oRA?;2|OAsEIc`E_4|hHU`CyVZWR|a--FBVwq8MeQ0-LC)a+F{
z)c{dE=wGI7V#^wZH;1EkU(4{^-25%j_$L#8gGyCP+bVi%W>naAr`Q=U+uz8RBf$mF
zHt%=T;BEu;iu2;ZzC0rwogPJ>WnFu<1QJ5-9zHpm%jNdDbzm~Z
z%ttbc@>C=qW+}D||0}N=Kz>(=e2L&8l5nK?Api*p(;PLJ=5NN`6|b9xAv%i*eC7>b
z!v6*c+Q^Pw1ue95-7!T#e&4AOFO?u1H$gHwi!z}*Gd8;x)f
zpejn%CO^pO;}B`h3E$nbiRruNo2KJF!OBU5DN7Ir?INu9Pv7lJ%s+}P0vCX&$%~*E)}AePg~(ixzp8(o_`>HeS!~~b+g6vW#GK>yJ=azlJRi6tYzYBFZPHarv6y7uReRv7p_@9e>2J#
zA1+^hqg1|_`eEKbE#X;KLcA2S!}%1bJzv-8~%SBogbujr}I_qki3}?
zYo&E!EmqPB`i#8_;yW-R`tut?dwd@?ERjzL)r@{H(}SaW)`vf+o|xC^2M8Cw4LHW~
zKj>W&m*0!Lb{z2(7r4PBKOgWX=H>u~4dpH>^6!$*3+!y@*w_b3$UT_`fry`sXPnO3^{c&i%;7(z$PJ^Ng`lYYa=j#-#
zfLBa&xp5rWviBiPdr)DsNKq%JLYZJXB-maX%v~g;;=F*PldBl4F#tcElsiL(H_O8n
z$fcol8sx#KVAK$c3k?FWjtH{)-tFt!VR;4G+geI0!tsO+ycs1eZS4WrW9{p<@2eL-
z!@P|vHbp|gP%yG-1@g(YR)s$S5NBzbsj1%I5nt+OyWKP?2Hc+M0q@ea+XiBtOHTR}
zI)}+=Fso&Hiw=Gbx&P#n&e*`VwM)IiS08Ta-?@H8nak;Lm#tX8v!S`=VJ0)WELx*R
zK%61yHps-kXd6l~UxrH?w4dW$7P4*}n28fWM0%hr7Xh!xyAX6i1(mx2eG$U5zRB9emj1->tSs)k
zF@c;z@5cGW7Gdd@yW$-9hByR&4(A`=E_LCv$A`-iv>KoOO_=y(M_MWtXchaP-E5
zzV?y}+k`Jw2A5xa|Bmyk&dpznN~srJx32x7y{l?q&ST7SXdNdsUj(q$-Y-hhEO3K?r6-T)~M5{}pC+483q36)mo{9Sw0fI@w)rg*)l5Ag8Z+QssnR>@OG7cXqg1jWd@=UZ
z=(;LZ>*gKH4gMf=IayTct7~7>9JbrT&5PRWd`j_3Wm&Ynqovv@GgmftbTn3Ie4$kx
zGEYtG>!tNTs>E7-vH2r;vKW7iO-KYUbfEk=p=U?)gv9qw)*2LW!f*NKW#uz?7RBtzDw&>V08Jt2{_kd%dCM4^7loq^A<4)Fv0
zLeBuzA*0wH0DA;Rf^Qp1y_AIs_gJH{LK&iEWDwCFF!CUjop~ZM*p96svgPqhXF|Uc
z+biEcg(yHcfVKQUI7|WOreYT&K}Jw{$}Ou0OqMopxoky+ONCXHO(mBWD{Wq+iU}&M
zlHK33uREZSMG#KVD!>+|KwgW^Ru?I^)CCuZw$&`DEjPhe)EZ(vDy25+EfYT(DA`o(
zvstWRx9+4uua+ZCwAfH=iyJj6nXRF>QeD})tU_T@>0y?-JT4t_YYe63^<|4awO*4-
zBM4fhOKUO|hb(r#LTfKomV-e{28s@)U0f%jn+(FuDy7+N-0VHvQxb6MR7@Ro2T>J1
zz_g_Sg}1h?RG|YcNoZYP^O{APQj@J%r$o9Ag2ozMS=iCBW^veNQ0t<>2De=sY-96x
zG?g~EwI!8;&spu)s9C#84TOWz=~insO6^9Kue^M5)QvsJl=>=fRh8Va-dgPg{7_@^
zM4T48Q(2-_s=&Uc1sWyb2(9yIs@s6JQma*!K6aR>f#DZL-#!=b)Ik3=aZcGHrenCt
zLHY|=!3@C~0}8^dsGMO{OxQUvAU?*-gD7039b}gGf6L69HnnUPV&Wggw{ESgyY+D0
z;lp+8%WRE!;z#0>r%tgAKLSWeeD_P@U)k5%Z@aDcw!M3A!?AJvv-ZP+V}AGm=kE#M
zBbg=T+G&8#BJQ^$=D`_TgY?%3%3J6&9U#^Yuv-QO*kEYidm86s;!%fbJ8H6(5?a<(l!`pHToYi;(R&?+>4;tlDZ90yOu8ED*g7nxoy-sTHz`C@cSZm#U6@N{?vo3UFqzr2ahcOM7O)jP1Kh-as_
zXb3q`M@U`~hyy}^g6Cy&!ahPw&>T+&j11v~Fk}ePe!$$phmcH^(Fk0KQC`8Pf8s4U
zs|O%qT;hH_s}BGuBo$9*Z==|2mn`7h88FvGs{IybGL`F`B@Mxy_g&o8b@6>Wg@E3|
zWI7`VNSc;+FL&w6iq-l+eET3YgpDl@T}}6%(e@=lFjPQ0n4t;2IVi4k@u{A@A}uE*
z-9h*t(iJ>Wr^661nV7{_9j!4}>@Zcj1Vv37HO_3%)09p}?q=-28E|Z$_Oq})z
zPT%W*XOfbYD5k|;aS`;1%;6)20N*U2PkF5*4qpej5&_UhC~bkBq=)q=X-S-~hkPzj
zl*sc67dKh_@L5Xa<4}_zpX~Sq1{(flqte0Wp!i%j_ofTj0!p0X=Ypx3P6o`ugvA0Z
zjaMR=jk{b6l_YM8y-f+Y6nnm222dMjenLONTmrBKBc>wklrcoZWbBppQ!Uq}qhi;<
z6+P|0e~LZ(n^UJ=6@FOJ|Jp@j`|3aK_8@cVMJQhD=#i1^dWQQq3VD}Bl=XotQ8Wjk
zQ3P8RF=TjH6|F(pHR8z*aUB2!?H%mtiuvEdN9FRNqV|2rS4WocZGz|c>6rMd_T#79
zQCQIX@!w9jT=~6Eia-7C|8U-9ocB3i=sb5jX)SP>Fg!|t!t&JXTyBBoo*39UjXL+$
z^y=X_L1iLf&7}K){`LeoYFxZV7e=wF0`VuL6O)!fcvJ91oNwB{Qnj`-r2J+f!1GPb
zwu>*`YH1x?+CVIQM7swzK4FokWo0;8h%7BlRtA%oOsy2JBU(aha6<#JKH|{)IXod_
z(3=yGC{%-l5JwOgD7?XWx`~!2M%jS^p_!VAnGpt}5-u@x94InWsfhx_VfacA0j5;<
zHsRY_7dKa%w9P#iHda?Pl`Qt}g{=*5A%IWGd%jp&1@<}Lrmh@wU{#zU@RukQR<5DqNvNRYQ+vwM+!;4mh
zYOBh{Zx8fhHwlf&{rtIH#0B`nzst)d!~!=BFve+|bKDKy89W?>M?^*tLgugm=7?`<
zn$$<}Bq8`Pw($Y%u?fRW0VTu|FJ(d_QM{9^GB
z07((~CYUz&?#z#Q()W2zG`f!@TMDk8R%itH=dMk3E#YyJD7c^WMlVP{JG(@+l~5mu
zU@ZLklHM}UuelOY%f+yG_wzcNm=Jl^U^&Hc(TAB6N;G_WQiRumG%O878=VOEd@jsO
z0{jlDITUs%`IIrx+5#XR>@S)}+h9uD2%z;8;PxtQt;TsEaYb>7S2}{vbFsppRB4MT1=(tnD|3AJVw`p!|5#zq*gu=XcktbEUx^Xlb(?)k
z4RYU=MmLxRHG1RMdEdm?hX8mGb=^F$nX57OktY#CIdzGE4tWx7t!7xNl)oRyt5on+
zkdh@lp0F4I>hR6@43;GZ3#Nf*_Czx2$o%*B?24o_>{%bhG#)ZoHIce_=Ee
zNk=c-8BI%dJZKL1IQ&)b;pj+>bV5bJLuvz^CGIxt+F@wiO*$$HPx;{Hoc`szP>G>#@#XjF+;1
zTiRkX*Eu|D59|Bu!U=m&
z?>2vR<5S_rvHpM}!dSx=44
zcK5xXmFN3^l-s)=Ne9NX5k}G-QCjf6PyL_%Aw&Sr;4o)V59tGrI&}X$3K~EMDqw){
zql9lJe04!@mn!s0{7vwDLC@wG40D)aToHPZ5Qc?!a=nt4aU6>FV_?ttYXW{xC=l>~
z;+5nctY*YqQBV$&@01^PgpuHP?g!n6lXUQTmuz^dMzILkK=>X>!>6mmlxPnA@5n8u
zg#K!jFh#JHIkt;t6V$x<2-Ft5v699GK=^-t1QzQH!5}bPWhG919>4Xg^HEq(Q08C8
zKAH)S-8}#Db03MVS^_{8M`kg$*PI7trO$&^(eODJSCMkWU9d_J%R+1sS5K}dOd=}s
z1!ESa+uj;jxN!DLD8~v2cM_s_rYK2AaLgGz;2F}Uqz^E^&`RVE=HK(j#~7yCjAU0y
zytlPDKDv=)ViCqjQtpWFT>(<4)>yEaLEnr41BJ{g)ik0fu)Jgjm|e&H-!)mqUzZ*?
zS(&R;JhOLi%EYU}9Q#8kN+*V{1ndolk@N$B0^5Xe>3~3!XvL_W?rFTD
z{)&b*%IzyxU9fZAnkpb9cMbJ#-_q0~JiNaC@`lUn*UDR(wroGSeM@sQfRYb%qrh+R
z`i9G{s$aY5DhQJ3y3mg1mWKMxTQ1p9euLwbWr(4gW0TyUVNG7|Yzg2_5Wl$+eo
zx}w*&cCNW{t6EkYwmIW<4RB)^8lz{xGQy6DzhRfLl6YBw3F@xF_6q;6HNpvvxGy5k
z-r$uh5{Fj9kv>#qHQh8(Oil#HRcDZTAv*?t^yT01ZLBRWj_s)2-4>j7TOdCVbq23d-#^A-UR**YeRuL1vQF*2DR8af#
zO6`4>QS)(~>E}8#Li{&41FL3MmQUS0ai{UwH3#DdS6(EXF^S);++}j_(UmMW4F{LI
zEB6Cd_Vu=o)}GI7x?`jGL4p;<#mmK0&(5*s;(Ko|xMuWq6|UI=jk(AwgFtgO%T%bw
zP$9L0=wWb&{oqqu{5O_w`_%4+M)tLV7nAirXy_B4TnnCd`9^k$c-@5;vO_<;`f7H^
z{HdF7e&bCl>HH=RBF%{l>Y|W_NM1yaZCy~+oRU%4`&1f}SI4A2|1B;D%ZirbR+7Gv
zfd0S<6X1FeP(m3eP-03MdF<#HIzE>MfW;x;hy+E6Jt)k!d7Q3d{gOu=;S!lP>NWh*LPh)T)O5-4`mrSi@>c?@$+O6>P5txIc~JCmIu@dn{eYBf?i<58yf&
z4Mr0tS&-G!BwP`Y+y($gu{JC5E6^|0DQ+ad!CkH*=Qg<_00b32b>4M&v=Ptz5AxT+
z#?QYF{|dSem3VdGI)oGaLQwWTYJF$Hm9XuO`L$|QWB;!6E=2sCny%xt*2HDK6U+iW)hrX1gpb
zE}x~$x$FUiUar*Xl<;KAh28oqdah|SvS4}nu4H7>
zk*U?50G>aJi`$kRo!n&!uGrZSnZH{G_Gtz3=9V5D-KBSG-(_E`T;A+y&jz)GvrYhiQb=RneHBy^YL
zDJxwyk0aNvn
zJZ3r(r>qB>x0~>V0viF&fp=bnMBXPWh`fTVV%jatxWxOK*^evA#Yd!VK6nxQz$a)M
z6c$jiR^dkaOgQ15U+H4|FSRk`awBm9{Y~a1+E-YQ4kzXxe5p-2{5t-V7rnc!_)wr+*Hti!FX
z^0o@y?s{uU+el;gf%Vl@AngfR15u9v@#C-W^a~DV2!v|uqxRaadaq0i7y0sr(lU?I
zVe)}tE$vLq5)ntuPvv$1>xsl7jBRoAh|qz&SGyB{OyL&DkIgpJc-u*fnlNmJFj-@A_U7EQ4
z_QcX>{$*+WjyvLc{h7|kUF4^;K?sG?y_#_^;dX$TlGiy^ouqZLj;8I+i<7ox##O0~
z71=Imu5c$)y)YC5yJWg)N7JfU+qMRVkIr+e0U0mNh4D}O_rXJqI94NsVk5M1g72`OM@Mh-kB$277>y3!8Mx@8z@6G_RQvX7u5s?!@3>O8
zFR8h*q<2r3LWx+NPGNGCT0J4BPH(oG^)bE0X4c6Bom%ZQH^pKDW4_x*qobcG8|Awj
zz1mM%gT%0C7@+C
z>NFaJj&$OS6jtQ@_T0T_`~SZaxNn#%RR83a8X~_32XEw`yO!bK6YcI<{SEj%vYQ+n
z!N=_-V5K1v;r}ft@C%v%KcP?`k*&ZYoQor1(i@*m&|3l|@Kl;&FO-ovXzFWyi5b)x
z29-LTt4Wg+AZSQBO0
ze&~cA18AxYpsB3@P&F0kGKts(A*8=Qs|N|CRkr9@PZQX|;JjNvk_kEj>0YhN*Efs8
zlk^YH4^)O2AV{pm_;7~pA9Y&gOWUIXizN_kPpmxGyblK5N1fJ(x0Al|?#IZ%MLHST
z_a```eG$hrL&y?H5#}Ut&4_Sw2s7I`%ic!uQn2I0!z|IQU;?GbG5@H5*-erYgk3s=
zIgq@BU>^gR6LT1h8baj}i6i@QjNRdR|78z=oOcN?iqC@?`^-G6tAml+2Pulqm!BJG
z&R0THgvE&%H6gmdkf@|i#p7Y58j=A8=|gD|@SQb#-lR^=g!NT3sE9
z@CLiBD|>CWi@z`-etd@2)pW0UcujZBH9p@pg(e2TMUdI~P4my}MO-i0Ej+87wGyDe
zOonNI-z69)AQHpG+KD1k=4O!aJP4;|ISas5*&9b;zu8{$hLhX8m$Z!&2{Efq
zl7(ds)B!5r#IXg38!U*WkUyDtvX>UECf_-a6yX4b8$b^R+?*nufL%#GR&D_UeZ@6O
z0t_Hijht4^O$0&;@(g|0=a@(n1gw)q;_-*oWpzHQ&!yAFVrsR+i)zmm<^dzh9)iBL
zEL4j87$trb8m-@MVOyz4!@RPt>&`{TS2V0-9a(q};6(ZlZ%qmEvWvehHdN{~CXJ^)
z5%e!txvgfK+E?c@L4F!7j<6HqbAws@N@DI3_6>2_CBOKRG#(^;&4(}=61D>Qe!S;l
zSn@ts=y!nHAya=6`E9T($wxw1+cWUS^n;s#d7Sdnp^*zv
zkKibQCz6DbN{PEEVI_iyJdl%s-&M@ExT|m%!Yyk95jpX$LUSq;09c~>3$5W$9-q+!
z|6nT&ciI!2x1fLoR+>^$^SCcn;4gZMH{`UdRf5(Sso(QFnCcEgF4ErAw*oRO!vuV!e{y%;5ijL2dV1b#|qvv%cJH
zF01u8{6+=mZ}nu*Hz;1+q6t$vg-mS
z7t*=RUhz4wl&Oom<&e+e<}y!xxed(L?Mf{yVYS?5C`R6kh^gW_-(}IKBRCg6wy%X>
z_HWSV$(xRtkP>Dc*+w!1j0MDoV=6A3xfeag2R__Pcxb|(Kx%Qkws2($GP*W`oGRghC%gDo{y9*{ZF0_T+<{;i;KP^Ssi#KFDSP_
zAgfjQmD(g7jQO@4ZF|10z>M$Bx0P5Q&bPIYqjmW<;YxSmikV!4HAd7ol-dH?NAqnN
z+SlgWab(XK^+gdQFp3Jv(MIlz!pB@5EeRxw_$8
zZgNq5{a}85U4MEU2)HyNG9&zb0<2IeAQz@^UCE-%hdw)h%k9`QQn&cb^+VWwy>>i8)rA%$#${b>_WDO?Pa}eAKTB8tdI4x0hVHeY={lB5q1$9Wn*ld
zO|UeZL=l?|%d#92dk-QodYBzyN7=>fV{DooW0$Z?;r6_oUBRwoSFx+vHSAh;9ZH+t
zz&_4yWH+&!*)8l7?33(M>{j+^WP9Dt?qHu`ce1){Qc~+>~rk%>;d*5dx$;E9${Ys(&$n47<-&O!JcGaVNbEA*)!|}`zrey`#PIpvnay-FYFuaIXF7M
z#hz#X%D&CM!(Lz~0UG!>_FeW8dzqbLudwg2?}HKGhwMk}RrVTto&7ueF?)lZW%vc6j7qN}J4N(ubmb
z>D+L>Igmayk#CNr26H-TgUra#aIRQtXUCJEQKaq8TR~f!7jD77V_djqYEmsNPE9KN
za6YMwybohv-j^O5z)N~mO)ulg%xK`;`9>jS21as_C&{ts)WpG5W@KZ1z=p)yH<+1e8f-Q~bf1>HjjHCwpli3tn>Er-fITc!mvZ;)$Z)!;0pBzg~
z3?wr~+9sM!O=NMt2UCjv;Z*;qhQE%DWOI774rbC5{N9~ip!;She8TUUwy-ociLD?c
zQ_=phbT(zmFN`M-kBpBTJ-0kE!IzEs&oilPE}cmk`bRSTW2xxi$kSwzLd;F@sO4MVk&6zDWc#PGG)JiNt4w&GOlEMU!^w%E
zR2J(evgxsu8t=K0iK&#XKa)Z?i1yBpre$1y2J4~`6^(m4Erb%mwzbl=EWN;@zzI2gsK
zO%3RIb7W`&&v(GU7jR?wfRH}Qq{eX)`R#CzaEJjpZKtApj(aDkyJ(;nJe0BI5nO>9
zV{l|LJCfC>#wT+}qK8H%FuD~fh=z+<4qwnkSf^jMdEUY=0DjuI2%Z*H;
zD@xrrD<7ZArPLFtL;QYgd4u-{?F4`{sY4_MYC
zl$;nj%FkIfnd~1;4yDwSNl1YH{
zWVM;pAa40YKZGQjIOMP*lNw4LR?^tdrTE)H>?nm3gOq{T#&*1o?Iy>LU>m@bQ<(LWqT
zSC~{vZA~^c2AP2ygcZ2$8Fe<5!(=g(Rc41rQey+EEF>jlwwk{~(kZjln~s>WIDdN3
z{iy-Ek;ZI#if#eTHJS8SadvVHKyI0i%|WHOFZ*Z}<SLhCRjITR#qZWz5O
z+Lz2^(VQCZ%aV*yOU2h+ydd?xav?c
zLruk@WUhZ$duVthm&!sCrO-qtiqg|kO43e4?dT!QC90$8^f>i-{*L`}i}L>gnAmku

diff --git a/src/vs/base/browser/ui/contextview/contextview.ts b/src/vs/base/browser/ui/contextview/contextview.ts
index c59a6dc632e8..2faf0490a34d 100644
--- a/src/vs/base/browser/ui/contextview/contextview.ts
+++ b/src/vs/base/browser/ui/contextview/contextview.ts
@@ -5,8 +5,10 @@
 
 import 'vs/css!./contextview';
 import * as DOM from 'vs/base/browser/dom';
+import * as platform from 'vs/base/common/platform';
 import { IDisposable, toDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle';
 import { Range } from 'vs/base/common/range';
+import { BrowserFeatures } from 'vs/base/browser/canIUse';
 
 export interface IAnchor {
 	x: number;
@@ -178,7 +180,7 @@ export class ContextView extends Disposable {
 			return;
 		}
 
-		if (this.delegate!.canRelayout === false) {
+		if (this.delegate!.canRelayout === false && !(platform.isIOS && BrowserFeatures.pointerEvents)) {
 			this.hide();
 			return;
 		}
diff --git a/src/vs/base/browser/ui/countBadge/countBadge.ts b/src/vs/base/browser/ui/countBadge/countBadge.ts
index da5da53f5bd8..b31da96c0655 100644
--- a/src/vs/base/browser/ui/countBadge/countBadge.ts
+++ b/src/vs/base/browser/ui/countBadge/countBadge.ts
@@ -8,6 +8,7 @@ import { $, append } from 'vs/base/browser/dom';
 import { format } from 'vs/base/common/strings';
 import { Color } from 'vs/base/common/color';
 import { mixin } from 'vs/base/common/objects';
+import { IThemable } from 'vs/base/common/styler';
 
 export interface ICountBadgeOptions extends ICountBadgetyles {
 	count?: number;
@@ -26,7 +27,7 @@ const defaultOpts = {
 	badgeForeground: Color.fromHex('#FFFFFF')
 };
 
-export class CountBadge {
+export class CountBadge implements IThemable {
 
 	private element: HTMLElement;
 	private count: number = 0;
diff --git a/src/vs/base/browser/ui/dialog/close-dark.svg b/src/vs/base/browser/ui/dialog/close-dark.svg
deleted file mode 100644
index 7305a8f099ab..000000000000
--- a/src/vs/base/browser/ui/dialog/close-dark.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/src/vs/base/browser/ui/dialog/close-light.svg b/src/vs/base/browser/ui/dialog/close-light.svg
deleted file mode 100644
index ecddcd665b58..000000000000
--- a/src/vs/base/browser/ui/dialog/close-light.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/src/vs/base/browser/ui/dialog/dialog.css b/src/vs/base/browser/ui/dialog/dialog.css
index b8e657ee1d9b..3620363cdd89 100644
--- a/src/vs/base/browser/ui/dialog/dialog.css
+++ b/src/vs/base/browser/ui/dialog/dialog.css
@@ -27,12 +27,12 @@
 	min-width: 500px;
 	max-width: 90%;
 	min-height: 75px;
-	padding: 5px;
+	padding: 10px;
 }
 
 /** Dialog: Title Actions Row */
 .monaco-workbench .dialog-box .dialog-toolbar-row {
-	padding-right: 1px;
+	padding-bottom: 4px;
 }
 
 .monaco-workbench .dialog-box .action-label {
@@ -46,73 +46,19 @@
 }
 
 
-.monaco-workbench .dialog-box .dialog-close-action {
-	background: url('close-light.svg') center center no-repeat;
-}
-
-.vs-dark .monaco-workbench .dialog-box .dialog-close-action,
-.hc-black .monaco-workbench .dialog-box .dialog-close-action {
-	background: url('close-dark.svg') center center no-repeat;
-}
-
 /** Dialog: Message Row */
 .monaco-workbench .dialog-box .dialog-message-row {
 	display: flex;
 	flex-grow: 1;
-	padding: 10px 15px 20px;
 	align-items: center;
+	padding: 0 10px;
 }
 
-.monaco-workbench .dialog-box .dialog-message-row .dialog-icon {
-	flex: 0 0 40px;
-	height: 40px;
+.monaco-workbench .dialog-box .dialog-message-row > .codicon {
+	flex: 0 0 48px;
+	height: 48px;
 	align-self: baseline;
-	background-position: center;
-	background-repeat: no-repeat;
-	background-size: 40px;
-}
-
-.vs .monaco-workbench .dialog-box .dialog-message-row .dialog-icon.icon-pending {
-	background-image: url('pending.svg');
-}
-
-.vs .monaco-workbench .dialog-box .dialog-message-row .dialog-icon.icon-info {
-	background-image: url('info-light.svg');
-}
-
-.vs .monaco-workbench .dialog-box .dialog-message-row .dialog-icon.icon-warning {
-	background-image: url('warning-light.svg');
-}
-
-.vs .monaco-workbench .dialog-box .dialog-message-row .dialog-icon.icon-error {
-	background-image: url('error-light.svg');
-}
-
-.vs-dark .monaco-workbench .dialog-box .dialog-message-row .dialog-icon.icon-pending {
-	background-image: url('pending-dark.svg');
-}
-
-.vs-dark .monaco-workbench .dialog-box .dialog-message-row .dialog-icon.icon-info,
-.hc-black .monaco-workbench .dialog-box .dialog-message-row .dialog-icon.icon-info {
-	background-image: url('info-dark.svg');
-}
-
-.vs-dark .monaco-workbench .dialog-box .dialog-message-row .dialog-icon.icon-warning,
-.hc-black .monaco-workbench .dialog-box .dialog-message-row .dialog-icon.icon-warning {
-	background-image: url('warning-dark.svg');
-}
-
-.vs-dark .monaco-workbench .dialog-box .dialog-message-row .dialog-icon.icon-error,
-.hc-black .monaco-workbench .dialog-box .dialog-message-row .dialog-icon.icon-error {
-	background-image: url('error-dark.svg');
-}
-
-.hc-black .monaco-workbench .dialog-box .dialog-message-row .dialog-icon.icon-pending {
-	background-image: url('pending-hc.svg');
-}
-
-.monaco-workbench .dialog-box .dialog-message-row .dialog-icon.icon-pending {
-	background-size: 30px;
+	font-size: 48px;
 }
 
 /** Dialog: Message Container */
@@ -121,8 +67,10 @@
 	flex-direction: column;
 	overflow: hidden;
 	text-overflow: ellipsis;
-	padding-left: 20px;
+	padding-left: 24px;
 	user-select: text;
+	-webkit-user-select: text;
+	-ms-user-select: text;
 	word-wrap: break-word; /* never overflow long words, but break to next line */
 	white-space: normal;
 }
@@ -134,7 +82,10 @@
 	flex: 1; /* let the message always grow */
 	white-space: normal;
 	word-wrap: break-word; /* never overflow long words, but break to next line */
-	padding-bottom: 10px;
+	min-height: 48px; /* matches icon height */
+	margin-bottom: 8px;
+	display: flex;
+	align-items: center;
 }
 
 /** Dialog: Details */
@@ -154,6 +105,13 @@
 	display: flex;
 }
 
+.monaco-workbench .dialog-box .dialog-message-row .dialog-message-container .dialog-checkbox-row .dialog-checkbox-message {
+	cursor: pointer;
+	user-select: none;
+	-webkit-user-select: none;
+	-ms-user-select: none;
+}
+
 /** Dialog: Buttons Row */
 .monaco-workbench .dialog-box > .dialog-buttons-row {
 	display: flex;
@@ -166,6 +124,7 @@
 .monaco-workbench .dialog-box > .dialog-buttons-row {
 	display: flex;
 	white-space: nowrap;
+	padding: 20px 10px 10px;
 }
 
 /** Dialog: Buttons */
@@ -175,7 +134,8 @@
 }
 
 .monaco-workbench .dialog-box > .dialog-buttons-row > .dialog-buttons > .monaco-button {
-	max-width: fit-content;
+	width: fit-content;
+	width: -moz-fit-content;
 	padding: 5px 10px;
 	margin: 4px 5px; /* allows button focus outline to be visible */
 	overflow: hidden;
diff --git a/src/vs/base/browser/ui/dialog/dialog.ts b/src/vs/base/browser/ui/dialog/dialog.ts
index a9d75da7aa14..55cdb2a3fa3a 100644
--- a/src/vs/base/browser/ui/dialog/dialog.ts
+++ b/src/vs/base/browser/ui/dialog/dialog.ts
@@ -6,7 +6,7 @@
 import 'vs/css!./dialog';
 import * as nls from 'vs/nls';
 import { Disposable } from 'vs/base/common/lifecycle';
-import { $, hide, show, EventHelper, clearNode, removeClasses, addClass, removeNode, isAncestor } from 'vs/base/browser/dom';
+import { $, hide, show, EventHelper, clearNode, removeClasses, addClass, addClasses, removeNode, isAncestor, addDisposableListener, EventType } from 'vs/base/browser/dom';
 import { domEvent } from 'vs/base/browser/event';
 import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
 import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
@@ -75,7 +75,8 @@ export class Dialog extends Disposable {
 
 		if (this.options.detail) {
 			const messageElement = messageContainer.appendChild($('.dialog-message'));
-			messageElement.innerText = this.message;
+			const messageTextElement = messageElement.appendChild($('.dialog-message-text'));
+			messageTextElement.innerText = this.message;
 		}
 
 		this.messageDetailElement = messageContainer.appendChild($('.dialog-message-detail'));
@@ -84,12 +85,13 @@ export class Dialog extends Disposable {
 		if (this.options.checkboxLabel) {
 			const checkboxRowElement = messageContainer.appendChild($('.dialog-checkbox-row'));
 
-			this.checkbox = this._register(new SimpleCheckbox(this.options.checkboxLabel, !!this.options.checkboxChecked));
+			const checkbox = this.checkbox = this._register(new SimpleCheckbox(this.options.checkboxLabel, !!this.options.checkboxChecked));
 
-			checkboxRowElement.appendChild(this.checkbox.domNode);
+			checkboxRowElement.appendChild(checkbox.domNode);
 
 			const checkboxMessageElement = checkboxRowElement.appendChild($('.dialog-checkbox-message'));
 			checkboxMessageElement.innerText = this.options.checkboxLabel;
+			this._register(addDisposableListener(checkboxMessageElement, EventType.CLICK, () => checkbox.checked = !checkbox.checked));
 		}
 
 		const toolbarRowElement = this.element.appendChild($('.dialog-toolbar-row'));
@@ -198,29 +200,30 @@ export class Dialog extends Disposable {
 				}
 			}));
 
-			removeClasses(this.iconElement, 'icon-error', 'icon-warning', 'icon-info');
+			addClass(this.iconElement, 'codicon');
+			removeClasses(this.iconElement, 'codicon-alert', 'codicon-warning', 'codicon-info');
 
 			switch (this.options.type) {
 				case 'error':
-					addClass(this.iconElement, 'icon-error');
+					addClass(this.iconElement, 'codicon-error');
 					break;
 				case 'warning':
-					addClass(this.iconElement, 'icon-warning');
+					addClass(this.iconElement, 'codicon-warning');
 					break;
 				case 'pending':
-					addClass(this.iconElement, 'icon-pending');
+					addClasses(this.iconElement, 'codicon-loading', 'codicon-animation-spin');
 					break;
 				case 'none':
 				case 'info':
 				case 'question':
 				default:
-					addClass(this.iconElement, 'icon-info');
+					addClass(this.iconElement, 'codicon-info');
 					break;
 			}
 
 			const actionBar = new ActionBar(this.toolbarContainer, {});
 
-			const action = new Action('dialog.close', nls.localize('dialogClose', "Close Dialog"), 'dialog-close-action', true, () => {
+			const action = new Action('dialog.close', nls.localize('dialogClose', "Close Dialog"), 'codicon codicon-close', true, () => {
 				resolve({ button: this.options.cancelId || 0, checkboxChecked: this.checkbox ? this.checkbox.checked : undefined });
 				return Promise.resolve();
 			});
diff --git a/src/vs/base/browser/ui/dialog/error-dark.svg b/src/vs/base/browser/ui/dialog/error-dark.svg
deleted file mode 100644
index efdc5f2ae2d2..000000000000
--- a/src/vs/base/browser/ui/dialog/error-dark.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/src/vs/base/browser/ui/dialog/error-light.svg b/src/vs/base/browser/ui/dialog/error-light.svg
deleted file mode 100644
index d646c72c740e..000000000000
--- a/src/vs/base/browser/ui/dialog/error-light.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/src/vs/base/browser/ui/dialog/info-dark.svg b/src/vs/base/browser/ui/dialog/info-dark.svg
deleted file mode 100644
index bb851afdfe58..000000000000
--- a/src/vs/base/browser/ui/dialog/info-dark.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/src/vs/base/browser/ui/dialog/info-light.svg b/src/vs/base/browser/ui/dialog/info-light.svg
deleted file mode 100644
index 6faf670cccc4..000000000000
--- a/src/vs/base/browser/ui/dialog/info-light.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/src/vs/base/browser/ui/dialog/pending-dark.svg b/src/vs/base/browser/ui/dialog/pending-dark.svg
deleted file mode 100644
index 5f3883811620..000000000000
--- a/src/vs/base/browser/ui/dialog/pending-dark.svg
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-	
-	
-		
-		
-		
-		
-		
-		
-		
-		
-	
-
diff --git a/src/vs/base/browser/ui/dialog/pending-hc.svg b/src/vs/base/browser/ui/dialog/pending-hc.svg
deleted file mode 100644
index c6d0ec7e29f1..000000000000
--- a/src/vs/base/browser/ui/dialog/pending-hc.svg
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-	
-	
-		
-		
-		
-		
-		
-		
-		
-		
-	
-
diff --git a/src/vs/base/browser/ui/dialog/pending.svg b/src/vs/base/browser/ui/dialog/pending.svg
deleted file mode 100644
index 47ce444bb240..000000000000
--- a/src/vs/base/browser/ui/dialog/pending.svg
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-	
-	
-		
-		
-		
-		
-		
-		
-		
-		
-	
-
diff --git a/src/vs/base/browser/ui/dialog/warning-dark.svg b/src/vs/base/browser/ui/dialog/warning-dark.svg
deleted file mode 100644
index a267963e5855..000000000000
--- a/src/vs/base/browser/ui/dialog/warning-dark.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/src/vs/base/browser/ui/dialog/warning-light.svg b/src/vs/base/browser/ui/dialog/warning-light.svg
deleted file mode 100644
index f2e2aa741e55..000000000000
--- a/src/vs/base/browser/ui/dialog/warning-light.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/src/vs/base/browser/ui/dropdown/dropdown.css b/src/vs/base/browser/ui/dropdown/dropdown.css
index 7ac5df590a3c..3ba96c0be788 100644
--- a/src/vs/base/browser/ui/dropdown/dropdown.css
+++ b/src/vs/base/browser/ui/dropdown/dropdown.css
@@ -5,12 +5,10 @@
 
 .monaco-dropdown {
 	height: 100%;
-	display: inline-block;
 	padding: 0;
 }
 
 .monaco-dropdown > .dropdown-label {
-	display: inline-block;
 	cursor: pointer;
 	height: 100%;
 }
diff --git a/src/vs/base/browser/ui/findinput/findInput.ts b/src/vs/base/browser/ui/findinput/findInput.ts
index ae43efd76a99..6cf8e814a633 100644
--- a/src/vs/base/browser/ui/findinput/findInput.ts
+++ b/src/vs/base/browser/ui/findinput/findInput.ts
@@ -396,30 +396,18 @@ export class FindInput extends Widget {
 	}
 
 	public validate(): void {
-		if (this.inputBox) {
-			this.inputBox.validate();
-		}
+		this.inputBox.validate();
 	}
 
 	public showMessage(message: InputBoxMessage): void {
-		if (this.inputBox) {
-			this.inputBox.showMessage(message);
-		}
+		this.inputBox.showMessage(message);
 	}
 
 	public clearMessage(): void {
-		if (this.inputBox) {
-			this.inputBox.hideMessage();
-		}
+		this.inputBox.hideMessage();
 	}
 
 	private clearValidation(): void {
-		if (this.inputBox) {
-			this.inputBox.hideMessage();
-		}
-	}
-
-	public dispose(): void {
-		super.dispose();
+		this.inputBox.hideMessage();
 	}
 }
diff --git a/src/vs/base/browser/ui/grid/grid.ts b/src/vs/base/browser/ui/grid/grid.ts
index 4478710b1f8a..4e09823bb006 100644
--- a/src/vs/base/browser/ui/grid/grid.ts
+++ b/src/vs/base/browser/ui/grid/grid.ts
@@ -605,7 +605,7 @@ export type GridNodeDescriptor = { size?: number, groups?: GridNodeDescriptor[]
 export type GridDescriptor = { orientation: Orientation, groups?: GridNodeDescriptor[] };
 
 export function sanitizeGridNodeDescriptor(nodeDescriptor: GridNodeDescriptor): void {
-	if (nodeDescriptor.groups && nodeDescriptor.groups.length === 0) {
+	if (nodeDescriptor.groups && nodeDescriptor.groups.length <= 1) {
 		nodeDescriptor.groups = undefined;
 	}
 
diff --git a/src/vs/base/browser/ui/grid/gridview.ts b/src/vs/base/browser/ui/grid/gridview.ts
index 078f6ad0eb14..9f46f9ba789c 100644
--- a/src/vs/base/browser/ui/grid/gridview.ts
+++ b/src/vs/base/browser/ui/grid/gridview.ts
@@ -30,7 +30,7 @@ export interface IView {
 	readonly onDidChange: Event;
 	readonly priority?: LayoutPriority;
 	readonly snap?: boolean;
-	layout(width: number, height: number, orientation: Orientation): void;
+	layout(width: number, height: number, top: number, left: number): void;
 	setVisible?(visible: boolean): void;
 }
 
@@ -69,10 +69,10 @@ export function orthogonal(orientation: Orientation): Orientation {
 }
 
 export interface Box {
-	top: number;
-	left: number;
-	width: number;
-	height: number;
+	readonly top: number;
+	readonly left: number;
+	readonly width: number;
+	readonly height: number;
 }
 
 export interface GridLeafNode {
@@ -117,11 +117,19 @@ export interface IGridViewOptions {
 	readonly layoutController?: ILayoutController;
 }
 
-class BranchNode implements ISplitView, IDisposable {
+interface ILayoutContext {
+	readonly orthogonalSize: number;
+	readonly absoluteOffset: number;
+	readonly absoluteOrthogonalOffset: number;
+	readonly absoluteSize: number;
+	readonly absoluteOrthogonalSize: number;
+}
+
+class BranchNode implements ISplitView, IDisposable {
 
 	readonly element: HTMLElement;
 	readonly children: Node[] = [];
-	private splitview: SplitView;
+	private splitview: SplitView;
 
 	private _size: number;
 	get size(): number { return this._size; }
@@ -129,6 +137,9 @@ class BranchNode implements ISplitView, IDisposable {
 	private _orthogonalSize: number;
 	get orthogonalSize(): number { return this._orthogonalSize; }
 
+	private absoluteOffset: number = 0;
+	private absoluteOrthogonalOffset: number = 0;
+
 	private _styles: IGridViewStyles;
 	get styles(): IGridViewStyles { return this._styles; }
 
@@ -140,6 +151,14 @@ class BranchNode implements ISplitView, IDisposable {
 		return this.orientation === Orientation.HORIZONTAL ? this.orthogonalSize : this.size;
 	}
 
+	get top(): number {
+		return this.orientation === Orientation.HORIZONTAL ? this.absoluteOffset : this.absoluteOrthogonalOffset;
+	}
+
+	get left(): number {
+		return this.orientation === Orientation.HORIZONTAL ? this.absoluteOrthogonalOffset : this.absoluteOffset;
+	}
+
 	get minimumSize(): number {
 		return this.children.length === 0 ? 0 : Math.max(...this.children.map(c => c.minimumOrthogonalSize));
 	}
@@ -221,7 +240,7 @@ class BranchNode implements ISplitView, IDisposable {
 		if (!childDescriptors) {
 			// Normal behavior, we have no children yet, just set up the splitview
 			this.splitview = new SplitView(this.element, { orientation, styles, proportionalLayout });
-			this.splitview.layout(size, orthogonalSize);
+			this.splitview.layout(size, { orthogonalSize, absoluteOffset: 0, absoluteOrthogonalOffset: 0, absoluteSize: size, absoluteOrthogonalSize: orthogonalSize });
 		} else {
 			// Reconstruction behavior, we want to reconstruct a splitview
 			const descriptor = {
@@ -268,20 +287,32 @@ class BranchNode implements ISplitView, IDisposable {
 		}
 	}
 
-	layout(size: number, orthogonalSize: number | undefined): void {
+	layout(size: number, offset: number, ctx: ILayoutContext | undefined): void {
 		if (!this.layoutController.isLayoutEnabled) {
 			return;
 		}
 
-		if (typeof orthogonalSize !== 'number') {
+		if (typeof ctx === 'undefined') {
 			throw new Error('Invalid state');
 		}
 
 		// branch nodes should flip the normal/orthogonal directions
-		this._size = orthogonalSize;
+		this._size = ctx.orthogonalSize;
 		this._orthogonalSize = size;
+		this.absoluteOffset = ctx.absoluteOffset + offset;
+		this.absoluteOrthogonalOffset = ctx.absoluteOrthogonalOffset;
+
+		this.splitview.layout(ctx.orthogonalSize, {
+			orthogonalSize: size,
+			absoluteOffset: this.absoluteOrthogonalOffset,
+			absoluteOrthogonalOffset: this.absoluteOffset,
+			absoluteSize: ctx.absoluteOrthogonalSize,
+			absoluteOrthogonalSize: ctx.absoluteSize
+		});
 
-		this.splitview.layout(orthogonalSize, size);
+		// Disable snapping on views which sit on the edges of the grid
+		this.splitview.startSnappingEnabled = this.absoluteOrthogonalOffset > 0;
+		this.splitview.endSnappingEnabled = this.absoluteOrthogonalOffset + ctx.orthogonalSize < ctx.absoluteOrthogonalSize;
 	}
 
 	setVisible(visible: boolean): void {
@@ -511,7 +542,7 @@ class BranchNode implements ISplitView, IDisposable {
 	}
 }
 
-class LeafNode implements ISplitView, IDisposable {
+class LeafNode implements ISplitView, IDisposable {
 
 	private _size: number = 0;
 	get size(): number { return this._size; }
@@ -519,6 +550,9 @@ class LeafNode implements ISplitView, IDisposable {
 	private _orthogonalSize: number;
 	get orthogonalSize(): number { return this._orthogonalSize; }
 
+	private absoluteOffset: number = 0;
+	private absoluteOrthogonalOffset: number = 0;
+
 	readonly onDidSashReset: Event = Event.None;
 
 	private _onDidLinkedWidthNodeChange = new Relay();
@@ -565,6 +599,14 @@ class LeafNode implements ISplitView, IDisposable {
 		return this.orientation === Orientation.HORIZONTAL ? this.size : this.orthogonalSize;
 	}
 
+	get top(): number {
+		return this.orientation === Orientation.HORIZONTAL ? this.absoluteOffset : this.absoluteOrthogonalOffset;
+	}
+
+	get left(): number {
+		return this.orientation === Orientation.HORIZONTAL ? this.absoluteOrthogonalOffset : this.absoluteOffset;
+	}
+
 	get element(): HTMLElement {
 		return this.view.element;
 	}
@@ -617,18 +659,20 @@ class LeafNode implements ISplitView, IDisposable {
 		// noop
 	}
 
-	layout(size: number, orthogonalSize: number | undefined): void {
+	layout(size: number, offset: number, ctx: ILayoutContext | undefined): void {
 		if (!this.layoutController.isLayoutEnabled) {
 			return;
 		}
 
-		if (typeof orthogonalSize !== 'number') {
+		if (typeof ctx === 'undefined') {
 			throw new Error('Invalid state');
 		}
 
 		this._size = size;
-		this._orthogonalSize = orthogonalSize;
-		this.view.layout(this.width, this.height, orthogonal(this.orientation));
+		this._orthogonalSize = ctx.orthogonalSize;
+		this.absoluteOffset = ctx.absoluteOffset + offset;
+		this.absoluteOrthogonalOffset = ctx.absoluteOrthogonalOffset;
+		this.view.layout(this.width, this.height, this.top, this.left);
 	}
 
 	setVisible(visible: boolean): void {
@@ -715,7 +759,7 @@ export class GridView implements IDisposable {
 
 		const { size, orthogonalSize } = this._root;
 		this.root = flipNode(this._root, orthogonalSize, size);
-		this.root.layout(size, orthogonalSize);
+		this.root.layout(size, 0, { orthogonalSize, absoluteOffset: 0, absoluteOrthogonalOffset: 0, absoluteSize: size, absoluteOrthogonalSize: orthogonalSize });
 	}
 
 	get width(): number { return this.root.width; }
@@ -771,7 +815,7 @@ export class GridView implements IDisposable {
 		this.firstLayoutController.isLayoutEnabled = true;
 
 		const [size, orthogonalSize] = this.root.orientation === Orientation.HORIZONTAL ? [height, width] : [width, height];
-		this.root.layout(size, orthogonalSize);
+		this.root.layout(size, 0, { orthogonalSize, absoluteOffset: 0, absoluteOrthogonalOffset: 0, absoluteSize: size, absoluteOrthogonalSize: orthogonalSize });
 	}
 
 	addView(view: IView, size: number | Sizing, location: number[]): void {
@@ -1032,7 +1076,7 @@ export class GridView implements IDisposable {
 	getView(location?: number[]): GridNode;
 	getView(location?: number[]): GridNode {
 		const node = location ? this.getNode(location)[1] : this._root;
-		return this._getViews(node, this.orientation, { top: 0, left: 0, width: this.width, height: this.height });
+		return this._getViews(node, this.orientation);
 	}
 
 	static deserialize(json: ISerializedGridView, deserializer: IViewDeserializer, options: IGridViewOptions = {}): GridView {
@@ -1076,24 +1120,20 @@ export class GridView implements IDisposable {
 		return result;
 	}
 
-	private _getViews(node: Node, orientation: Orientation, box: Box, cachedVisibleSize?: number): GridNode {
+	private _getViews(node: Node, orientation: Orientation, cachedVisibleSize?: number): GridNode {
+		const box = { top: node.top, left: node.left, width: node.width, height: node.height };
+
 		if (node instanceof LeafNode) {
 			return { view: node.view, box, cachedVisibleSize };
 		}
 
 		const children: GridNode[] = [];
-		let i = 0;
-		let offset = 0;
-
-		for (const child of node.children) {
-			const childOrientation = orthogonal(orientation);
-			const childBox: Box = orientation === Orientation.HORIZONTAL
-				? { top: box.top, left: box.left + offset, width: child.width, height: box.height }
-				: { top: box.top + offset, left: box.left, width: box.width, height: child.height };
-			const cachedVisibleSize = node.getChildCachedVisibleSize(i++);
-
-			children.push(this._getViews(child, childOrientation, childBox, cachedVisibleSize));
-			offset += orientation === Orientation.HORIZONTAL ? child.width : child.height;
+
+		for (let i = 0; i < node.children.length; i++) {
+			const child = node.children[i];
+			const cachedVisibleSize = node.getChildCachedVisibleSize(i);
+
+			children.push(this._getViews(child, orthogonal(orientation), cachedVisibleSize));
 		}
 
 		return { children, box };
diff --git a/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts b/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts
index b950a8cf7b55..c604bd4feb17 100644
--- a/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts
+++ b/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts
@@ -84,7 +84,11 @@ export class HighlightedLabel {
 		}
 
 		this.domNode.innerHTML = htmlContent;
-		this.domNode.title = this.title;
+		if (this.title) {
+			this.domNode.title = this.title;
+		} else {
+			this.domNode.removeAttribute('title');
+		}
 		this.didEverRender = true;
 	}
 
diff --git a/src/vs/base/browser/ui/iconLabel/iconLabel.ts b/src/vs/base/browser/ui/iconLabel/iconLabel.ts
index e873ac813d0b..04bcb1f41658 100644
--- a/src/vs/base/browser/ui/iconLabel/iconLabel.ts
+++ b/src/vs/base/browser/ui/iconLabel/iconLabel.ts
@@ -8,6 +8,7 @@ import * as dom from 'vs/base/browser/dom';
 import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel';
 import { IMatch } from 'vs/base/common/filters';
 import { Disposable } from 'vs/base/common/lifecycle';
+import { Range } from 'vs/base/common/range';
 
 export interface IIconLabelCreationOptions {
 	supportHighlights?: boolean;
@@ -24,6 +25,8 @@ export interface IIconLabelValueOptions {
 	matches?: IMatch[];
 	labelEscapeNewLines?: boolean;
 	descriptionMatches?: IMatch[];
+	readonly separator?: string;
+	readonly domId?: string;
 }
 
 class FastLabelNode {
@@ -86,9 +89,10 @@ class FastLabelNode {
 }
 
 export class IconLabel extends Disposable {
+
 	private domNode: FastLabelNode;
-	private labelDescriptionContainer: FastLabelNode;
-	private labelNode: FastLabelNode | HighlightedLabel;
+	private descriptionContainer: FastLabelNode;
+	private nameNode: Label | LabelWithHighlights;
 	private descriptionNode: FastLabelNode | HighlightedLabel | undefined;
 	private descriptionNodeFactory: () => FastLabelNode | HighlightedLabel;
 
@@ -97,18 +101,21 @@ export class IconLabel extends Disposable {
 
 		this.domNode = this._register(new FastLabelNode(dom.append(container, dom.$('.monaco-icon-label'))));
 
-		this.labelDescriptionContainer = this._register(new FastLabelNode(dom.append(this.domNode.element, dom.$('.monaco-icon-label-description-container'))));
+		const labelContainer = dom.append(this.domNode.element, dom.$('.monaco-icon-label-container'));
+
+		const nameContainer = dom.append(labelContainer, dom.$('span.monaco-icon-name-container'));
+		this.descriptionContainer = this._register(new FastLabelNode(dom.append(labelContainer, dom.$('span.monaco-icon-description-container'))));
 
 		if (options?.supportHighlights) {
-			this.labelNode = new HighlightedLabel(dom.append(this.labelDescriptionContainer.element, dom.$('a.label-name')), !!options.supportCodicons);
+			this.nameNode = new LabelWithHighlights(nameContainer, !!options.supportCodicons);
 		} else {
-			this.labelNode = this._register(new FastLabelNode(dom.append(this.labelDescriptionContainer.element, dom.$('a.label-name'))));
+			this.nameNode = new Label(nameContainer);
 		}
 
 		if (options?.supportDescriptionHighlights) {
-			this.descriptionNodeFactory = () => new HighlightedLabel(dom.append(this.labelDescriptionContainer.element, dom.$('span.label-description')), !!options.supportCodicons);
+			this.descriptionNodeFactory = () => new HighlightedLabel(dom.append(this.descriptionContainer.element, dom.$('span.label-description')), !!options.supportCodicons);
 		} else {
-			this.descriptionNodeFactory = () => this._register(new FastLabelNode(dom.append(this.labelDescriptionContainer.element, dom.$('span.label-description'))));
+			this.descriptionNodeFactory = () => this._register(new FastLabelNode(dom.append(this.descriptionContainer.element, dom.$('span.label-description'))));
 		}
 	}
 
@@ -116,7 +123,7 @@ export class IconLabel extends Disposable {
 		return this.domNode.element;
 	}
 
-	setLabel(label?: string, description?: string, options?: IIconLabelValueOptions): void {
+	setLabel(label: string | string[], description?: string, options?: IIconLabelValueOptions): void {
 		const classes = ['monaco-icon-label'];
 		if (options) {
 			if (options.extraClasses) {
@@ -131,11 +138,7 @@ export class IconLabel extends Disposable {
 		this.domNode.className = classes.join(' ');
 		this.domNode.title = options?.title || '';
 
-		if (this.labelNode instanceof HighlightedLabel) {
-			this.labelNode.set(label || '', options?.matches, options?.title, options?.labelEscapeNewLines);
-		} else {
-			this.labelNode.textContent = label || '';
-		}
+		this.nameNode.setLabel(label, options);
 
 		if (description || this.descriptionNode) {
 			if (!this.descriptionNode) {
@@ -157,3 +160,112 @@ export class IconLabel extends Disposable {
 		}
 	}
 }
+
+class Label {
+
+	private label: string | string[] | undefined = undefined;
+	private singleLabel: HTMLElement | undefined = undefined;
+
+	constructor(private container: HTMLElement) { }
+
+	setLabel(label: string | string[], options?: IIconLabelValueOptions): void {
+		if (this.label === label) {
+			return;
+		}
+
+		this.label = label;
+
+		if (typeof label === 'string') {
+			if (!this.singleLabel) {
+				this.container.innerHTML = '';
+				dom.removeClass(this.container, 'multiple');
+				this.singleLabel = dom.append(this.container, dom.$('a.label-name', { id: options?.domId }));
+			}
+
+			this.singleLabel.textContent = label;
+		} else {
+			this.container.innerHTML = '';
+			dom.addClass(this.container, 'multiple');
+			this.singleLabel = undefined;
+
+			for (let i = 0; i < label.length; i++) {
+				const l = label[i];
+				const id = options?.domId && `${options?.domId}_${i}`;
+
+				dom.append(this.container, dom.$('a.label-name', { id, 'data-icon-label-count': label.length, 'data-icon-label-index': i }, l));
+
+				if (i < label.length - 1) {
+					dom.append(this.container, dom.$('span.label-separator', undefined, options?.separator || '/'));
+				}
+			}
+		}
+	}
+}
+
+function splitMatches(labels: string[], separator: string, matches: IMatch[] | undefined): IMatch[][] | undefined {
+	if (!matches) {
+		return undefined;
+	}
+
+	let labelStart = 0;
+
+	return labels.map(label => {
+		const labelRange = { start: labelStart, end: labelStart + label.length };
+
+		const result = matches
+			.map(match => Range.intersect(labelRange, match))
+			.filter(range => !Range.isEmpty(range))
+			.map(({ start, end }) => ({ start: start - labelStart, end: end - labelStart }));
+
+		labelStart = labelRange.end + separator.length;
+		return result;
+	});
+}
+
+class LabelWithHighlights {
+
+	private label: string | string[] | undefined = undefined;
+	private singleLabel: HighlightedLabel | undefined = undefined;
+
+	constructor(private container: HTMLElement, private supportCodicons: boolean) { }
+
+	setLabel(label: string | string[], options?: IIconLabelValueOptions): void {
+		if (this.label === label) {
+			return;
+		}
+
+		this.label = label;
+
+		if (typeof label === 'string') {
+			if (!this.singleLabel) {
+				this.container.innerHTML = '';
+				dom.removeClass(this.container, 'multiple');
+				this.singleLabel = new HighlightedLabel(dom.append(this.container, dom.$('a.label-name', { id: options?.domId })), this.supportCodicons);
+			}
+
+			this.singleLabel.set(label, options?.matches, options?.title, options?.labelEscapeNewLines);
+		} else {
+
+			this.container.innerHTML = '';
+			dom.addClass(this.container, 'multiple');
+			this.singleLabel = undefined;
+
+			const separator = options?.separator || '/';
+			const matches = splitMatches(label, separator, options?.matches);
+
+			for (let i = 0; i < label.length; i++) {
+				const l = label[i];
+				const m = matches ? matches[i] : undefined;
+				const id = options?.domId && `${options?.domId}_${i}`;
+
+				const name = dom.$('a.label-name', { id, 'data-icon-label-count': label.length, 'data-icon-label-index': i });
+				const highlightedLabel = new HighlightedLabel(dom.append(this.container, name), this.supportCodicons);
+				highlightedLabel.set(l, m, options?.title, options?.labelEscapeNewLines);
+
+				if (i < label.length - 1) {
+					dom.append(name, dom.$('span.label-separator', undefined, separator));
+				}
+			}
+		}
+	}
+}
diff --git a/src/vs/base/browser/ui/iconLabel/iconlabel.css b/src/vs/base/browser/ui/iconLabel/iconlabel.css
index 5fa0615f0d19..39950a7cda97 100644
--- a/src/vs/base/browser/ui/iconLabel/iconlabel.css
+++ b/src/vs/base/browser/ui/iconLabel/iconlabel.css
@@ -25,30 +25,38 @@
 
 	/* fonts icons */
 	-webkit-font-smoothing: antialiased;
+	-moz-osx-font-smoothing: grayscale;
 	vertical-align: top;
 
 	flex-shrink: 0; /* fix for https://github.com/Microsoft/vscode/issues/13787 */
 }
 
-.monaco-icon-label > .monaco-icon-label-description-container {
-	overflow: hidden; /* this causes the label/description to shrink first if decorations are enabled */
+.monaco-icon-label > .monaco-icon-label-container {
+	min-width: 0;
+	overflow: hidden;
 	text-overflow: ellipsis;
+	flex: 1;
 }
 
-.monaco-icon-label > .monaco-icon-label-description-container > .label-name {
+.monaco-icon-label > .monaco-icon-label-container > .monaco-icon-name-container > .label-name {
 	color: inherit;
 	white-space: pre; /* enable to show labels that include multiple whitespaces */
 }
 
-.monaco-icon-label > .monaco-icon-label-description-container > .label-description {
+.monaco-icon-label > .monaco-icon-label-container > .monaco-icon-name-container > .label-name > .label-separator {
+	margin: 0 2px;
+	opacity: 0.5;
+}
+
+.monaco-icon-label > .monaco-icon-label-container > .monaco-icon-description-container > .label-description {
 	opacity: .7;
 	margin-left: 0.5em;
 	font-size: 0.9em;
 	white-space: pre; /* enable to show labels that include multiple whitespaces */
 }
 
-.monaco-icon-label.italic > .monaco-icon-label-description-container > .label-name,
-.monaco-icon-label.italic > .monaco-icon-label-description-container > .label-description {
+.monaco-icon-label.italic > .monaco-icon-label-container > .monaco-icon-name-container > .label-name,
+.monaco-icon-label.italic > .monaco-icon-description-container > .label-description {
 	font-style: italic;
 }
 
@@ -57,7 +65,6 @@
 	font-size: 90%;
 	font-weight: 600;
 	padding: 0 16px 0 5px;
-	margin-left: auto;
 	text-align: center;
 }
 
diff --git a/src/vs/base/browser/ui/inputbox/inputBox.css b/src/vs/base/browser/ui/inputbox/inputBox.css
index a56bf03fbe50..d7bcfe666945 100644
--- a/src/vs/base/browser/ui/inputbox/inputBox.css
+++ b/src/vs/base/browser/ui/inputbox/inputBox.css
@@ -7,12 +7,7 @@
 	position: relative;
 	display: block;
 	padding: 0;
-	-webkit-box-sizing:	border-box;
-	-o-box-sizing:		border-box;
-	-moz-box-sizing:	border-box;
-	-ms-box-sizing:		border-box;
-	box-sizing:			border-box;
-	line-height: auto !important;
+	box-sizing:	border-box;
 
 	/* Customizable */
 	font-size: inherit;
@@ -37,11 +32,7 @@
 
 .monaco-inputbox > .wrapper > .input {
 	display: inline-block;
-	-webkit-box-sizing:	border-box;
-	-o-box-sizing:		border-box;
-	-moz-box-sizing:	border-box;
-	-ms-box-sizing:		border-box;
-	box-sizing:			border-box;
+	box-sizing:	border-box;
 	width: 100%;
 	height: 100%;
 	line-height: inherit;
@@ -58,18 +49,17 @@
 
 .monaco-inputbox > .wrapper > textarea.input {
 	display: block;
-	-ms-overflow-style: none; /* IE 10+ */
-	overflow: -moz-scrollbars-none; /* Firefox */
-	scrollbar-width: none; /* Firefox ^64 */
+	-ms-overflow-style: none; /* IE 10+: hide scrollbars */
+	scrollbar-width: none; /* Firefox: hide scrollbars */
 	outline: none;
 }
 
-.monaco-inputbox > .wrapper > textarea.input.empty {
-	white-space: nowrap;
+.monaco-inputbox > .wrapper > textarea.input::-webkit-scrollbar {
+	display: none; /* Chrome + Safari: hide scrollbar */
 }
 
-.monaco-inputbox > .wrapper > textarea.input::-webkit-scrollbar {
-	display: none;
+.monaco-inputbox > .wrapper > textarea.input.empty {
+	white-space: nowrap;
 }
 
 .monaco-inputbox > .wrapper > .mirror {
@@ -78,11 +68,7 @@
 	width: 100%;
 	top: 0;
 	left: 0;
-	-webkit-box-sizing:	border-box;
-	-o-box-sizing:		border-box;
-	-moz-box-sizing:	border-box;
-	-ms-box-sizing:		border-box;
-	box-sizing:			border-box;
+	box-sizing: border-box;
 	white-space: pre-wrap;
 	visibility: hidden;
 	word-wrap: break-word;
@@ -99,11 +85,7 @@
 	overflow: hidden;
 	text-align: left;
 	width: 100%;
-	-webkit-box-sizing:	border-box;
-	-o-box-sizing:		border-box;
-	-moz-box-sizing:	border-box;
-	-ms-box-sizing:		border-box;
-	box-sizing:			border-box;
+	box-sizing:	border-box;
 	padding: 0.4em;
 	font-size: 12px;
 	line-height: 17px;
diff --git a/src/vs/base/browser/ui/inputbox/inputBox.ts b/src/vs/base/browser/ui/inputbox/inputBox.ts
index cba0a2751a57..d8e0e46acaee 100644
--- a/src/vs/base/browser/ui/inputbox/inputBox.ts
+++ b/src/vs/base/browser/ui/inputbox/inputBox.ts
@@ -183,7 +183,7 @@ export class InputBox extends Widget {
 			this.maxHeight = typeof this.options.flexibleMaxHeight === 'number' ? this.options.flexibleMaxHeight : Number.POSITIVE_INFINITY;
 
 			this.mirror = dom.append(wrapper, $('div.mirror'));
-			this.mirror.innerHTML = ' ';
+			this.mirror.innerHTML = ' ';
 
 			this.scrollableElement = new ScrollableElement(this.element, { vertical: ScrollbarVisibility.Auto });
 
@@ -242,6 +242,8 @@ export class InputBox extends Widget {
 			});
 		}
 
+		this.ignoreGesture(this.input);
+
 		setTimeout(() => this.updateMirror(), 0);
 
 		// Support actions
@@ -327,6 +329,7 @@ export class InputBox extends Widget {
 	}
 
 	public disable(): void {
+		this.blur();
 		this.input.disabled = true;
 		this._hideMessage();
 	}
@@ -561,7 +564,7 @@ export class InputBox extends Widget {
 		if (mirrorTextContent) {
 			this.mirror.textContent = value + suffix;
 		} else {
-			this.mirror.innerHTML = ' ';
+			this.mirror.innerHTML = ' ';
 		}
 
 		this.layout();
diff --git a/src/vs/base/browser/ui/list/list.css b/src/vs/base/browser/ui/list/list.css
index b2cbdd591b02..6e8e13f9e0bb 100644
--- a/src/vs/base/browser/ui/list/list.css
+++ b/src/vs/base/browser/ui/list/list.css
@@ -11,12 +11,9 @@
 }
 
 .monaco-list.mouse-support {
+	user-select: none;
 	-webkit-user-select: none;
-	-khtml-user-select: none;
-	-moz-user-select: -moz-none;
 	-ms-user-select: none;
-	-o-user-select: none;
-	user-select: none;
 }
 
 .monaco-list > .monaco-scrollable-element {
@@ -36,10 +33,7 @@
 
 .monaco-list-row {
 	position: absolute;
-	-moz-box-sizing:	border-box;
-	-o-box-sizing:		border-box;
-	-ms-box-sizing:		border-box;
-	box-sizing:			border-box;
+	box-sizing:	border-box;
 	overflow: hidden;
 	width: 100%;
 }
@@ -59,6 +53,10 @@
 	outline: 0 !important;
 }
 
+.monaco-list:focus .monaco-list-row.selected .codicon {
+	color: inherit;
+}
+
 /* Dnd */
 .monaco-drag-image {
 	display: inline-block;
@@ -115,54 +113,28 @@
 }
 
 .monaco-list-type-filter > .controls > * {
+	border: none;
 	box-sizing: border-box;
-	width: 16px;
-	height: 16px;
-	margin: 0 0 0 2px;
-	flex-shrink: 0;
-}
-
-.monaco-list-type-filter > .controls > .filter {
 	-webkit-appearance: none;
+	-moz-appearance: none;
+	background: none;
 	width: 16px;
 	height: 16px;
-	background: url("media/no-filter-light.svg");
-	background-position: 50% 50%;
-	cursor: pointer;
-}
-
-.monaco-list-type-filter > .controls > .filter:checked {
-	background-image: url("media/filter-light.svg");
-}
-
-.vs-dark .monaco-list-type-filter > .controls > .filter {
-	background-image: url("media/no-filter-dark.svg");
-}
-
-.vs-dark .monaco-list-type-filter > .controls > .filter:checked {
-	background-image: url("media/filter-dark.svg");
-}
-
-.hc-black .monaco-list-type-filter > .controls > .filter {
-	background-image: url("media/no-filter-hc.svg");
-}
-
-.hc-black .monaco-list-type-filter > .controls > .filter:checked {
-	background-image: url("media/filter-hc.svg");
-}
-
-.monaco-list-type-filter > .controls > .clear {
-	border: none;
-	background: url("media/close-light.svg");
+	flex-shrink: 0;
+	margin: 0;
+	padding: 0;
+	display: flex;
+	align-items: center;
+	justify-content: center;
 	cursor: pointer;
 }
 
-.vs-dark .monaco-list-type-filter > .controls > .clear {
-	background-image: url("media/close-dark.svg");
+.monaco-list-type-filter > .controls > .filter:checked::before {
+	content: "\eb83" !important; /* codicon-list-filter */
 }
 
-.hc-black .monaco-list-type-filter > .controls > .clear {
-	background-image: url("media/close-hc.svg");
+.monaco-list-type-filter > .controls > .filter {
+	margin-left: 4px;
 }
 
 .monaco-list-type-filter-message {
@@ -191,4 +163,4 @@
 
 .monaco-list-type-filter.dragging {
 	cursor: grabbing;
-}
\ No newline at end of file
+}
diff --git a/src/vs/base/browser/ui/list/list.ts b/src/vs/base/browser/ui/list/list.ts
index 9f38f5e1ee24..cb0918724b74 100644
--- a/src/vs/base/browser/ui/list/list.ts
+++ b/src/vs/base/browser/ui/list/list.ts
@@ -103,10 +103,11 @@ export const ListDragOverReactions = {
 
 export interface IListDragAndDrop {
 	getDragURI(element: T): string | null;
-	getDragLabel?(elements: T[]): string | undefined;
+	getDragLabel?(elements: T[], originalEvent: DragEvent): string | undefined;
 	onDragStart?(data: IDragAndDropData, originalEvent: DragEvent): void;
 	onDragOver(data: IDragAndDropData, targetElement: T | undefined, targetIndex: number | undefined, originalEvent: DragEvent): boolean | IListDragOverReaction;
 	drop(data: IDragAndDropData, targetElement: T | undefined, targetIndex: number | undefined, originalEvent: DragEvent): void;
+	onDragEnd?(originalEvent: DragEvent): void;
 }
 
 export class ListError extends Error {
diff --git a/src/vs/base/browser/ui/list/listView.ts b/src/vs/base/browser/ui/list/listView.ts
index 3ecb53271ab2..aae604189a2f 100644
--- a/src/vs/base/browser/ui/list/listView.ts
+++ b/src/vs/base/browser/ui/list/listView.ts
@@ -20,6 +20,7 @@ import { Range, IRange } from 'vs/base/common/range';
 import { equals, distinct } from 'vs/base/common/arrays';
 import { DataTransfers, StaticDND, IDragAndDropData } from 'vs/base/browser/dnd';
 import { disposableTimeout, Delayer } from 'vs/base/common/async';
+import { isFirefox } from 'vs/base/browser/browser';
 
 interface IItem {
 	readonly id: string;
@@ -73,9 +74,10 @@ const DefaultOptions = {
 	horizontalScrolling: false
 };
 
-export class ElementsDragAndDropData implements IDragAndDropData {
+export class ElementsDragAndDropData implements IDragAndDropData {
 
 	readonly elements: T[];
+	context: TContext | undefined;
 
 	constructor(elements: T[]) {
 		this.elements = elements;
@@ -83,7 +85,7 @@ export class ElementsDragAndDropData implements IDragAndDropData {
 
 	update(): void { }
 
-	getData(): any {
+	getData(): T[] {
 		return this.elements;
 	}
 }
@@ -98,7 +100,7 @@ export class ExternalElementsDragAndDropData implements IDragAndDropData {
 
 	update(): void { }
 
-	getData(): any {
+	getData(): T[] {
 		return this.elements;
 	}
 }
@@ -233,7 +235,7 @@ export class ListView implements ISpliceable, IDisposable {
 
 		this.rowsContainer = document.createElement('div');
 		this.rowsContainer.className = 'monaco-list-rows';
-		this.rowsContainer.style.willChange = 'transform';
+		this.rowsContainer.style.transform = 'translate3d(0px, 0px, 0px)';
 		this.disposables.add(Gesture.addTarget(this.rowsContainer));
 
 		this.scrollableElement = this.disposables.add(new ScrollableElement(this.rowsContainer, {
@@ -595,7 +597,7 @@ export class ListView implements ISpliceable, IDisposable {
 			return;
 		}
 
-		item.row.domNode.style.width = 'fit-content';
+		item.row.domNode.style.width = isFirefox ? '-moz-fit-content' : 'fit-content';
 		item.width = DOM.getContentWidth(item.row.domNode);
 		const style = window.getComputedStyle(item.row.domNode);
 
@@ -765,7 +767,7 @@ export class ListView implements ISpliceable, IDisposable {
 			let label: string | undefined;
 
 			if (this.dnd.getDragLabel) {
-				label = this.dnd.getDragLabel(elements);
+				label = this.dnd.getDragLabel(elements, event);
 			}
 
 			if (typeof label === 'undefined') {
@@ -845,10 +847,6 @@ export class ListView implements ISpliceable, IDisposable {
 		feedback = distinct(feedback).filter(i => i >= -1 && i < this.length).sort();
 		feedback = feedback[0] === -1 ? [-1] : feedback;
 
-		if (feedback.length === 0) {
-			throw new Error('Invalid empty feedback list');
-		}
-
 		if (equalsDragFeedback(this.currentDragFeedback, feedback)) {
 			return true;
 		}
@@ -858,7 +856,11 @@ export class ListView implements ISpliceable, IDisposable {
 
 		if (feedback[0] === -1) { // entire list feedback
 			DOM.addClass(this.domNode, 'drop-target');
-			this.currentDragFeedbackDisposable = toDisposable(() => DOM.removeClass(this.domNode, 'drop-target'));
+			DOM.addClass(this.rowsContainer, 'drop-target');
+			this.currentDragFeedbackDisposable = toDisposable(() => {
+				DOM.removeClass(this.domNode, 'drop-target');
+				DOM.removeClass(this.rowsContainer, 'drop-target');
+			});
 		} else {
 			for (const index of feedback) {
 				const item = this.items[index]!;
@@ -909,12 +911,16 @@ export class ListView implements ISpliceable, IDisposable {
 		this.dnd.drop(dragData, event.element, event.index, event.browserEvent);
 	}
 
-	private onDragEnd(): void {
+	private onDragEnd(event: DragEvent): void {
 		this.canDrop = false;
 		this.teardownDragAndDropScrollTopAnimation();
 		this.clearDragOverFeedback();
 		this.currentDragData = undefined;
 		StaticDND.CurrentDragAndDropData = undefined;
+
+		if (this.dnd.onDragEnd) {
+			this.dnd.onDragEnd(event);
+		}
 	}
 
 	private clearDragOverFeedback(): void {
diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts
index 2054db0e1e51..4f4aa38b9b5e 100644
--- a/src/vs/base/browser/ui/list/listWidget.ts
+++ b/src/vs/base/browser/ui/list/listWidget.ts
@@ -702,16 +702,27 @@ export interface IAccessibilityProvider {
 	 * https://www.w3.org/TR/wai-aria/#aria-level
 	 */
 	getAriaLevel?(element: T): number | undefined;
+
+	onDidChangeActiveDescendant?: Event;
+	getActiveDescendantId?(element: T): string | undefined;
 }
 
 export class DefaultStyleController implements IStyleController {
 
-	constructor(private styleElement: HTMLStyleElement, private selectorSuffix?: string) { }
+	constructor(private styleElement: HTMLStyleElement, private selectorSuffix: string) { }
 
 	style(styles: IListStyles): void {
-		const suffix = this.selectorSuffix ? `.${this.selectorSuffix}` : '';
+		const suffix = this.selectorSuffix && `.${this.selectorSuffix}`;
 		const content: string[] = [];
 
+		if (styles.listBackground) {
+			if (styles.listBackground.isOpaque()) {
+				content.push(`.monaco-list${suffix} .monaco-list-rows { background: ${styles.listBackground}; }`);
+			} else if (!platform.isMacintosh) { // subpixel AA doesn't exist in macOS
+				console.warn(`List with id '${this.selectorSuffix}' was styled with a non-opaque background color. This will break sub-pixel antialiasing.`);
+			}
+		}
+
 		if (styles.listFocusBackground) {
 			content.push(`.monaco-list${suffix}:focus .monaco-list-row.focused { background-color: ${styles.listFocusBackground}; }`);
 			content.push(`.monaco-list${suffix}:focus .monaco-list-row.focused:hover { background-color: ${styles.listFocusBackground}; }`); // overwrite :hover style in this case!
@@ -788,6 +799,7 @@ export class DefaultStyleController implements IStyleController {
 		if (styles.listDropBackground) {
 			content.push(`
 				.monaco-list${suffix}.drop-target,
+				.monaco-list${suffix} .monaco-list-rows.drop-target,
 				.monaco-list${suffix} .monaco-list-row.drop-target { background-color: ${styles.listDropBackground} !important; color: inherit !important; }
 			`);
 		}
@@ -815,7 +827,7 @@ export class DefaultStyleController implements IStyleController {
 	}
 }
 
-export interface IListOptions extends IListStyles {
+export interface IListOptions {
 	readonly identityProvider?: IIdentityProvider;
 	readonly dnd?: IListDragAndDrop;
 	readonly enableKeyboardNavigation?: boolean;
@@ -828,7 +840,7 @@ export interface IListOptions extends IListStyles {
 	readonly multipleSelectionSupport?: boolean;
 	readonly multipleSelectionController?: IMultipleSelectionController;
 	readonly openController?: IOpenController;
-	readonly styleController?: IStyleController;
+	readonly styleController?: (suffix: string) => IStyleController;
 	readonly accessibilityProvider?: IAccessibilityProvider;
 
 	// list view options
@@ -842,6 +854,7 @@ export interface IListOptions extends IListStyles {
 }
 
 export interface IListStyles {
+	listBackground?: Color;
 	listFocusBackground?: Color;
 	listFocusForeground?: Color;
 	listActiveSelectionBackground?: Color;
@@ -1062,9 +1075,9 @@ class ListViewDragAndDrop implements IListViewDragAndDrop {
 		return this.dnd.getDragURI(element);
 	}
 
-	getDragLabel?(elements: T[]): string | undefined {
+	getDragLabel?(elements: T[], originalEvent: DragEvent): string | undefined {
 		if (this.dnd.getDragLabel) {
-			return this.dnd.getDragLabel(elements);
+			return this.dnd.getDragLabel(elements, originalEvent);
 		}
 
 		return undefined;
@@ -1080,6 +1093,12 @@ class ListViewDragAndDrop implements IListViewDragAndDrop {
 		return this.dnd.onDragOver(data, targetElement, targetIndex, originalEvent);
 	}
 
+	onDragEnd(originalEvent: DragEvent): void {
+		if (this.dnd.onDragEnd) {
+			this.dnd.onDragEnd(originalEvent);
+		}
+	}
+
 	drop(data: IDragAndDropData, targetElement: T, targetIndex: number, originalEvent: DragEvent): void {
 		this.dnd.drop(data, targetElement, targetIndex, originalEvent);
 	}
@@ -1097,9 +1116,9 @@ export class List implements ISpliceable, IDisposable {
 	private eventBufferer = new EventBufferer();
 	private view: ListView;
 	private spliceable: ISpliceable;
-	private styleElement: HTMLStyleElement;
 	private styleController: IStyleController;
 	private typeLabelController?: TypeLabelController;
+	private accessibilityProvider?: IAccessibilityProvider;
 
 	protected readonly disposables = new DisposableStore();
 
@@ -1185,8 +1204,14 @@ export class List implements ISpliceable, IDisposable {
 
 		const baseRenderers: IListRenderer[] = [this.focus.renderer, this.selection.renderer];
 
-		if (_options.accessibilityProvider) {
-			baseRenderers.push(new AccessibiltyRenderer(_options.accessibilityProvider));
+		this.accessibilityProvider = _options.accessibilityProvider;
+
+		if (this.accessibilityProvider) {
+			baseRenderers.push(new AccessibiltyRenderer(this.accessibilityProvider));
+
+			if (this.accessibilityProvider.onDidChangeActiveDescendant) {
+				this.accessibilityProvider.onDidChangeActiveDescendant(this.onDidChangeActiveDescendant, this, this.disposables);
+			}
 		}
 
 		renderers = renderers.map(r => new PipelineRenderer(r.templateId, [...baseRenderers, r]));
@@ -1198,11 +1223,18 @@ export class List implements ISpliceable, IDisposable {
 
 		this.view = new ListView(container, virtualDelegate, renderers, viewOptions);
 
-		this.updateAriaRole();
-
-		this.styleElement = DOM.createStyleSheet(this.view.domNode);
+		if (typeof _options.ariaRole !== 'string') {
+			this.view.domNode.setAttribute('role', ListAriaRootRole.TREE);
+		} else {
+			this.view.domNode.setAttribute('role', _options.ariaRole);
+		}
 
-		this.styleController = _options.styleController || new DefaultStyleController(this.styleElement, this.view.domId);
+		if (_options.styleController) {
+			this.styleController = _options.styleController(this.view.domId);
+		} else {
+			const styleElement = DOM.createStyleSheet(this.view.domNode);
+			this.styleController = new DefaultStyleController(styleElement, this.view.domId);
+		}
 
 		this.spliceable = new CombinedSpliceable([
 			new TraitSpliceable(this.focus, this.view, _options.identityProvider),
@@ -1239,8 +1271,6 @@ export class List implements ISpliceable, IDisposable {
 		if (_options.ariaLabel) {
 			this.view.domNode.setAttribute('aria-label', localize('aria list', "{0}. Use the navigation keys to navigate.", _options.ariaLabel));
 		}
-
-		this.style(_options);
 	}
 
 	protected createMouseController(options: IListOptions): MouseController {
@@ -1603,23 +1633,23 @@ export class List implements ISpliceable, IDisposable {
 
 	private _onFocusChange(): void {
 		const focus = this.focus.get();
+		DOM.toggleClass(this.view.domNode, 'element-focused', focus.length > 0);
+		this.onDidChangeActiveDescendant();
+	}
 
-		if (focus.length > 0) {
-			this.view.domNode.setAttribute('aria-activedescendant', this.view.getElementDomId(focus[0]));
-		} else {
-			this.view.domNode.removeAttribute('aria-activedescendant');
-		}
+	private onDidChangeActiveDescendant(): void {
+		const focus = this.focus.get();
 
-		this.updateAriaRole();
+		if (focus.length > 0) {
+			let id: string | undefined;
 
-		DOM.toggleClass(this.view.domNode, 'element-focused', focus.length > 0);
-	}
+			if (this.accessibilityProvider?.getActiveDescendantId) {
+				id = this.accessibilityProvider.getActiveDescendantId(this.view.element(focus[0]));
+			}
 
-	private updateAriaRole(): void {
-		if (typeof this.options.ariaRole !== 'string') {
-			this.view.domNode.setAttribute('role', ListAriaRootRole.TREE);
+			this.view.domNode.setAttribute('aria-activedescendant', id || this.view.getElementDomId(focus[0]));
 		} else {
-			this.view.domNode.setAttribute('role', this.options.ariaRole);
+			this.view.domNode.removeAttribute('aria-activedescendant');
 		}
 	}
 
diff --git a/src/vs/base/browser/ui/list/media/close-dark.svg b/src/vs/base/browser/ui/list/media/close-dark.svg
deleted file mode 100644
index 7305a8f099ab..000000000000
--- a/src/vs/base/browser/ui/list/media/close-dark.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/src/vs/base/browser/ui/list/media/close-hc.svg b/src/vs/base/browser/ui/list/media/close-hc.svg
deleted file mode 100644
index 7305a8f099ab..000000000000
--- a/src/vs/base/browser/ui/list/media/close-hc.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/src/vs/base/browser/ui/list/media/close-light.svg b/src/vs/base/browser/ui/list/media/close-light.svg
deleted file mode 100644
index ecddcd665b58..000000000000
--- a/src/vs/base/browser/ui/list/media/close-light.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/src/vs/base/browser/ui/list/media/filter-dark.svg b/src/vs/base/browser/ui/list/media/filter-dark.svg
deleted file mode 100644
index 46c35f4374df..000000000000
--- a/src/vs/base/browser/ui/list/media/filter-dark.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/src/vs/base/browser/ui/list/media/filter-hc.svg b/src/vs/base/browser/ui/list/media/filter-hc.svg
deleted file mode 100644
index d7b6bdd39234..000000000000
--- a/src/vs/base/browser/ui/list/media/filter-hc.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/src/vs/base/browser/ui/list/media/filter-light.svg b/src/vs/base/browser/ui/list/media/filter-light.svg
deleted file mode 100644
index 2550d80cb700..000000000000
--- a/src/vs/base/browser/ui/list/media/filter-light.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/src/vs/base/browser/ui/list/media/no-filter-dark.svg b/src/vs/base/browser/ui/list/media/no-filter-dark.svg
deleted file mode 100644
index 6fc07d81a555..000000000000
--- a/src/vs/base/browser/ui/list/media/no-filter-dark.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/src/vs/base/browser/ui/list/media/no-filter-hc.svg b/src/vs/base/browser/ui/list/media/no-filter-hc.svg
deleted file mode 100644
index 6fc07d81a555..000000000000
--- a/src/vs/base/browser/ui/list/media/no-filter-hc.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/src/vs/base/browser/ui/list/media/no-filter-light.svg b/src/vs/base/browser/ui/list/media/no-filter-light.svg
deleted file mode 100644
index 3608b15d29eb..000000000000
--- a/src/vs/base/browser/ui/list/media/no-filter-light.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/src/vs/base/browser/ui/menu/menu.css b/src/vs/base/browser/ui/menu/menu.css
index b0e3961a14e7..8547e8b39a48 100644
--- a/src/vs/base/browser/ui/menu/menu.css
+++ b/src/vs/base/browser/ui/menu/menu.css
@@ -15,7 +15,6 @@
 .monaco-menu .monaco-action-bar.vertical .action-item {
 	padding: 0;
 	transform: none;
-	display: -ms-flexbox;
 	display: flex;
 }
 
@@ -24,9 +23,7 @@
 }
 
 .monaco-menu .monaco-action-bar.vertical .action-menu-item {
-	-ms-flex: 1 1 auto;
 	flex: 1 1 auto;
-	display: -ms-flexbox;
 	display: flex;
 	height: 2em;
 	align-items: center;
@@ -34,7 +31,6 @@
 }
 
 .monaco-menu .monaco-action-bar.vertical .action-label {
-	-ms-flex: 1 1 auto;
 	flex: 1 1 auto;
 	text-decoration: none;
 	padding: 0 1em;
@@ -46,7 +42,6 @@
 .monaco-menu .monaco-action-bar.vertical .keybinding,
 .monaco-menu .monaco-action-bar.vertical .submenu-indicator {
 	display: inline-block;
-	-ms-flex: 2 1 auto;
 	flex: 2 1 auto;
 	padding: 0 1em;
 	text-align: right;
@@ -56,8 +51,8 @@
 
 .monaco-menu .monaco-action-bar.vertical .submenu-indicator {
 	height: 100%;
-	-webkit-mask: url('submenu.svg') no-repeat 90% 50%/13px 13px;
 	mask: url('submenu.svg') no-repeat 90% 50%/13px 13px;
+	-webkit-mask: url('submenu.svg') no-repeat 90% 50%/13px 13px;
 }
 
 .monaco-menu .monaco-action-bar.vertical .action-item.disabled .keybinding,
@@ -67,11 +62,7 @@
 
 .monaco-menu .monaco-action-bar.vertical .action-label:not(.separator) {
 	display: inline-block;
-	-webkit-box-sizing:	border-box;
-	-o-box-sizing:		border-box;
-	-moz-box-sizing:	border-box;
-	-ms-box-sizing:		border-box;
-	box-sizing:			border-box;
+	box-sizing: border-box;
 	margin: 0;
 }
 
@@ -80,7 +71,6 @@
 	overflow: visible;
 }
 
-
 .monaco-menu .monaco-action-bar.vertical .action-item .monaco-submenu {
 	position: absolute;
 }
@@ -104,8 +94,8 @@
 .monaco-menu .monaco-action-bar.vertical .menu-item-check {
 	position: absolute;
 	visibility: hidden;
-	-webkit-mask: url('check.svg') no-repeat 50% 56%/15px 15px;
 	mask: url('check.svg') no-repeat 50% 56%/15px 15px;
+	-webkit-mask: url('check.svg') no-repeat 50% 56%/15px 15px;
 	width: 1em;
 	height: 100%;
 }
@@ -119,10 +109,6 @@
 .context-view.monaco-menu-container {
 	outline: 0;
 	border: none;
-	-webkit-animation: fadeIn 0.083s linear;
-	-o-animation: fadeIn 0.083s linear;
-	-moz-animation: fadeIn 0.083s linear;
-	-ms-animation: fadeIn 0.083s linear;
 	animation: fadeIn 0.083s linear;
 }
 
@@ -173,6 +159,10 @@
 	outline: 0;
 }
 
+.menubar.compact {
+	flex-shrink: 0;
+}
+
 .menubar.compact > .menubar-menu-button {
 	width: 100%;
 	height: 100%;
@@ -204,22 +194,24 @@
 }
 
 .menubar.compact .toolbar-toggle-more {
+	position: absolute;
+	left: 0px;
+	top: 0px;
 	background-position: center;
 	background-repeat: no-repeat;
 	background-size: 16px;
 	cursor: pointer;
 	width: 100%;
-	height: 100%;
 }
 
 .menubar .toolbar-toggle-more {
 	display: inline-block;
 	padding: 0;
-	-webkit-mask: url('ellipsis.svg') no-repeat 50% 55%/14px 14px;
 	mask: url('ellipsis.svg') no-repeat 50% 55%/14px 14px;
+	-webkit-mask: url('ellipsis.svg') no-repeat 50% 55%/14px 14px;
 }
 
 .menubar.compact .toolbar-toggle-more {
-	-webkit-mask: url('menu.svg') no-repeat 50% 55%/16px 16px;
 	mask: url('menu.svg') no-repeat 50% 55%/16px 16px;
+	-webkit-mask: url('menu.svg') no-repeat 50% 55%/16px 16px;
 }
diff --git a/src/vs/base/browser/ui/menu/menu.ts b/src/vs/base/browser/ui/menu/menu.ts
index 528be4b3f564..8d08e4ae84a3 100644
--- a/src/vs/base/browser/ui/menu/menu.ts
+++ b/src/vs/base/browser/ui/menu/menu.ts
@@ -6,7 +6,7 @@
 import 'vs/css!./menu';
 import * as nls from 'vs/nls';
 import * as strings from 'vs/base/common/strings';
-import { IActionRunner, IAction, Action } from 'vs/base/common/actions';
+import { IActionRunner, IAction, Action, IActionViewItem } from 'vs/base/common/actions';
 import { ActionBar, IActionViewItemProvider, ActionsOrientation, Separator, ActionViewItem, IActionViewItemOptions, BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar';
 import { ResolvedKeybinding, KeyCode } from 'vs/base/common/keyCodes';
 import { addClass, EventType, EventHelper, EventLike, removeTabIndexAndUpdateFocus, isAncestor, hasClass, addDisposableListener, removeClass, append, $, addClasses, removeClasses } from 'vs/base/browser/dom';
@@ -205,8 +205,8 @@ export class Menu extends ActionBar {
 		container.appendChild(this.scrollableElement.getDomNode());
 		this.scrollableElement.scanDomNode();
 
-		this.viewItems.filter(item => !(item instanceof MenuSeparatorActionViewItem)).forEach((item: BaseMenuActionViewItem, index: number, array: any[]) => {
-			item.updatePositionInSet(index + 1, array.length);
+		this.viewItems.filter(item => !(item instanceof MenuSeparatorActionViewItem)).forEach((item: IActionViewItem, index: number, array: any[]) => {
+			(item as BaseMenuActionViewItem).updatePositionInSet(index + 1, array.length);
 		});
 	}
 
@@ -215,7 +215,7 @@ export class Menu extends ActionBar {
 
 		const fgColor = style.foregroundColor ? `${style.foregroundColor}` : '';
 		const bgColor = style.backgroundColor ? `${style.backgroundColor}` : '';
-		const border = style.borderColor ? `2px solid ${style.borderColor}` : '';
+		const border = style.borderColor ? `1px solid ${style.borderColor}` : '';
 		const shadow = style.shadowColor ? `0 2px 4px ${style.shadowColor}` : '';
 
 		container.style.border = border;
@@ -661,7 +661,7 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem {
 		if (this.item) {
 			addClass(this.item, 'monaco-submenu-item');
 			this.item.setAttribute('aria-haspopup', 'true');
-
+			this.updateAriaExpanded('false');
 			this.submenuIndicator = append(this.item, $('span.submenu-indicator'));
 			this.submenuIndicator.setAttribute('aria-hidden', 'true');
 		}
@@ -726,7 +726,7 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem {
 		if (this.parentData.submenu && (force || (this.parentData.submenu !== this.mysubmenu))) {
 			this.parentData.submenu.dispose();
 			this.parentData.submenu = undefined;
-
+			this.updateAriaExpanded('false');
 			if (this.submenuContainer) {
 				this.submenuDisposables.clear();
 				this.submenuContainer = undefined;
@@ -740,6 +740,7 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem {
 		}
 
 		if (!this.parentData.submenu) {
+			this.updateAriaExpanded('true');
 			this.submenuContainer = append(this.element, $('div.monaco-submenu'));
 			addClasses(this.submenuContainer, 'menubar-menu-items-holder', 'context-view');
 
@@ -778,13 +779,7 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem {
 
 					this.parentData.parent.focus();
 
-					if (this.parentData.submenu) {
-						this.parentData.submenu.dispose();
-						this.parentData.submenu = undefined;
-					}
-
-					this.submenuDisposables.clear();
-					this.submenuContainer = undefined;
+					this.cleanupExistingSubmenu(true);
 				}
 			}));
 
@@ -799,13 +794,7 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem {
 			this.submenuDisposables.add(this.parentData.submenu.onDidCancel(() => {
 				this.parentData.parent.focus();
 
-				if (this.parentData.submenu) {
-					this.parentData.submenu.dispose();
-					this.parentData.submenu = undefined;
-				}
-
-				this.submenuDisposables.clear();
-				this.submenuContainer = undefined;
+				this.cleanupExistingSubmenu(true);
 			}));
 
 			this.parentData.submenu.focus(selectFirstItem);
@@ -816,6 +805,12 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem {
 		}
 	}
 
+	private updateAriaExpanded(value: string): void {
+		if (this.item) {
+			this.item?.setAttribute('aria-expanded', value);
+		}
+	}
+
 	protected applyStyle(): void {
 		super.applyStyle();
 
diff --git a/src/vs/base/browser/ui/menu/menubar.ts b/src/vs/base/browser/ui/menu/menubar.ts
index 0d6055691842..73e591815b00 100644
--- a/src/vs/base/browser/ui/menu/menubar.ts
+++ b/src/vs/base/browser/ui/menu/menubar.ts
@@ -309,7 +309,7 @@ export class MenuBar extends Disposable {
 
 	createOverflowMenu(): void {
 		const label = this.options.compactMode !== undefined ? nls.localize('mAppMenu', 'Application Menu') : nls.localize('mMore', "...");
-		const buttonElement = $('div.menubar-menu-button', { 'role': 'menuitem', 'tabindex': -1, 'aria-label': label, 'aria-haspopup': true });
+		const buttonElement = $('div.menubar-menu-button', { 'role': 'menuitem', 'tabindex': -1, 'aria-label': label, 'title': label, 'aria-haspopup': true });
 		const titleElement = $('div.menubar-menu-title.toolbar-toggle-more', { 'role': 'none', 'aria-hidden': true });
 
 		buttonElement.appendChild(titleElement);
diff --git a/src/vs/base/browser/ui/progressbar/progressbar.css b/src/vs/base/browser/ui/progressbar/progressbar.css
index 032d7b4dd9e3..2a950f9aafb3 100644
--- a/src/vs/base/browser/ui/progressbar/progressbar.css
+++ b/src/vs/base/browser/ui/progressbar/progressbar.css
@@ -35,19 +35,7 @@
 	animation-duration: 4s;
 	animation-iteration-count: infinite;
 	animation-timing-function: linear;
-	-ms-animation-name: progress;
-	-ms-animation-duration: 4s;
-	-ms-animation-iteration-count: infinite;
-	-ms-animation-timing-function: linear;
-	-webkit-animation-name: progress;
-	-webkit-animation-duration: 4s;
-	-webkit-animation-iteration-count: infinite;
-	-webkit-animation-timing-function: linear;
-	-moz-animation-name: progress;
-	-moz-animation-duration: 4s;
-	-moz-animation-iteration-count: infinite;
-	-moz-animation-timing-function: linear;
-	will-change: transform;
+	transform: translate3d(0px, 0px, 0px);
 }
 
 /**
@@ -58,6 +46,3 @@
  * 100%: 50 * 100 - 50 (do not overflow): 4950%
  */
 @keyframes progress { from { transform: translateX(0%) scaleX(1) } 50% { transform: translateX(2500%) scaleX(3) } to { transform: translateX(4950%) scaleX(1) } }
-@-ms-keyframes progress { from { transform: translateX(0%) scaleX(1) }	50% { transform: translateX(2500%) scaleX(3) } to { transform: translateX(4950%) scaleX(1) } }
-@-webkit-keyframes progress { from { transform: translateX(0%) scaleX(1) }	50% { transform: translateX(2500%) scaleX(3) } to { transform: translateX(4950%) scaleX(1) } }
-@-moz-keyframes progress { from { transform: translateX(0%) scaleX(1) }	50% { transform: translateX(2500%) scaleX(3) } to { transform: translateX(4950%) scaleX(1) } }
\ No newline at end of file
diff --git a/src/vs/base/browser/ui/scrollbar/abstractScrollbar.ts b/src/vs/base/browser/ui/scrollbar/abstractScrollbar.ts
index e50958988bcd..7d8b18d59c08 100644
--- a/src/vs/base/browser/ui/scrollbar/abstractScrollbar.ts
+++ b/src/vs/base/browser/ui/scrollbar/abstractScrollbar.ts
@@ -99,6 +99,7 @@ export abstract class AbstractScrollbar extends Widget {
 			this.slider.setHeight(height);
 		}
 		this.slider.setLayerHinting(true);
+		this.slider.setContain('strict');
 
 		this.domNode.domNode.appendChild(this.slider.domNode);
 
diff --git a/src/vs/base/browser/ui/scrollbar/scrollableElement.ts b/src/vs/base/browser/ui/scrollbar/scrollableElement.ts
index 7f3b977818ea..c59bc26f038a 100644
--- a/src/vs/base/browser/ui/scrollbar/scrollableElement.ts
+++ b/src/vs/base/browser/ui/scrollbar/scrollableElement.ts
@@ -64,7 +64,7 @@ export class MouseWheelClassifier {
 			return false;
 		}
 
-		// 0.5 * last + 0.25 * before last + 0.125 * before before last + ...
+		// 0.5 * last + 0.25 * 2nd last + 0.125 * 3rd last + ...
 		let remainingInfluence = 1;
 		let score = 0;
 		let iteration = 1;
diff --git a/src/vs/base/browser/ui/selectBox/selectBoxCustom.css b/src/vs/base/browser/ui/selectBox/selectBoxCustom.css
index 24d2ad857bca..40f7dce9bac9 100644
--- a/src/vs/base/browser/ui/selectBox/selectBoxCustom.css
+++ b/src/vs/base/browser/ui/selectBox/selectBoxCustom.css
@@ -16,11 +16,7 @@
 
 .monaco-select-box-dropdown-container {
 	display: none;
-	-webkit-box-sizing:	border-box;
-	-o-box-sizing:		border-box;
-	-moz-box-sizing:	border-box;
-	-ms-box-sizing:		border-box;
-	box-sizing:			border-box;
+	box-sizing:	border-box;
 }
 
 .monaco-select-box-dropdown-container > .select-box-details-pane > .select-box-description-markdown * {
@@ -55,11 +51,7 @@
 	padding-right: 1px;
 	width: 100%;
 	overflow: hidden;
-	-webkit-box-sizing:	border-box;
-	-o-box-sizing:		border-box;
-	-moz-box-sizing:	border-box;
-	-ms-box-sizing:		border-box;
-	box-sizing:			border-box;
+	box-sizing:	border-box;
 }
 
 .monaco-select-box-dropdown-container > .select-box-details-pane {
diff --git a/src/vs/base/browser/ui/selectBox/selectBoxNative.ts b/src/vs/base/browser/ui/selectBox/selectBoxNative.ts
index 09b3f3425443..9ca099622141 100644
--- a/src/vs/base/browser/ui/selectBox/selectBoxNative.ts
+++ b/src/vs/base/browser/ui/selectBox/selectBoxNative.ts
@@ -10,6 +10,7 @@ import * as dom from 'vs/base/browser/dom';
 import * as arrays from 'vs/base/common/arrays';
 import { ISelectBoxDelegate, ISelectOptionItem, ISelectBoxOptions, ISelectBoxStyles, ISelectData } from 'vs/base/browser/ui/selectBox/selectBox';
 import { isMacintosh } from 'vs/base/common/platform';
+import { Gesture, EventType } from 'vs/base/browser/touch';
 
 export class SelectBoxNative extends Disposable implements ISelectBoxDelegate {
 
@@ -44,6 +45,12 @@ export class SelectBoxNative extends Disposable implements ISelectBoxDelegate {
 	}
 
 	private registerListeners() {
+		this._register(Gesture.addTarget(this.selectElement));
+		[EventType.Tap].forEach(eventType => {
+			this._register(dom.addDisposableListener(this.selectElement, eventType, (e) => {
+				this.selectElement.focus();
+			}));
+		});
 
 		this._register(dom.addStandardDisposableListener(this.selectElement, 'change', (e) => {
 			this.selectElement.title = e.target.value;
diff --git a/src/vs/base/browser/ui/splitview/panelview.css b/src/vs/base/browser/ui/splitview/panelview.css
deleted file mode 100644
index 9ded1c239b3b..000000000000
--- a/src/vs/base/browser/ui/splitview/panelview.css
+++ /dev/null
@@ -1,100 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- *  Copyright (c) Microsoft Corporation. All rights reserved.
- *  Licensed under the Source EULA. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-
-.monaco-panel-view {
-	width: 100%;
-	height: 100%;
-}
-
-.monaco-panel-view .panel {
-	overflow: hidden;
-	width: 100%;
-	height: 100%;
-	display: flex;
-	flex-direction: column;
-}
-
-.monaco-panel-view .panel > .panel-header {
-	font-size: 11px;
-	font-weight: bold;
-	text-transform: uppercase;
-	overflow: hidden;
-	display: flex;
-	cursor: pointer;
-}
-
-.monaco-panel-view .panel > .panel-header > .twisties {
-	width: 20px;
-	display: flex;
-	align-items: center;
-	justify-content: center;
-	transform-origin: center;
-	color: inherit;
-	flex-shrink: 0;
-}
-
-.monaco-panel-view .panel > .panel-header.expanded > .twisties::before {
-	transform: rotate(90deg);
-}
-
-/* TODO: actions should be part of the panel, but they aren't yet */
-.monaco-panel-view .panel > .panel-header > .actions {
-	display: none;
-	flex: 1;
-}
-
-/* TODO: actions should be part of the panel, but they aren't yet */
-.monaco-panel-view .panel:hover > .panel-header.expanded > .actions,
-.monaco-panel-view .panel > .panel-header.actions-always-visible.expanded > .actions,
-.monaco-panel-view .panel > .panel-header.focused.expanded > .actions {
-	display: initial;
-}
-
-/* TODO: actions should be part of the panel, but they aren't yet */
-.monaco-panel-view .panel > .panel-header > .actions .action-label.icon,
-.monaco-panel-view .panel > .panel-header > .actions .action-label.codicon {
-	width: 28px;
-	height: 22px;
-	background-size: 16px;
-	background-position: center center;
-	background-repeat: no-repeat;
-	margin-right: 0;
-	display: flex;
-	align-items: center;
-	justify-content: center;
-	color: inherit;
-}
-
-/* Bold font style does not go well with CJK fonts */
-.monaco-panel-view:lang(zh-Hans) .panel > .panel-header,
-.monaco-panel-view:lang(zh-Hant) .panel > .panel-header,
-.monaco-panel-view:lang(ja) .panel > .panel-header,
-.monaco-panel-view:lang(ko) .panel > .panel-header {
-	font-weight: normal;
-}
-
-.monaco-panel-view .panel > .panel-header.hidden {
-	display: none;
-}
-
-.monaco-panel-view .panel > .panel-body {
-	overflow: hidden;
-	flex: 1;
-}
-
-/* Animation */
-
-.monaco-panel-view.animated .split-view-view {
-	transition-duration: 0.15s;
-	transition-timing-function: ease-out;
-}
-
-.monaco-panel-view.animated.vertical .split-view-view {
-	transition-property: height;
-}
-
-.monaco-panel-view.animated.horizontal .split-view-view {
-	transition-property: width;
-}
diff --git a/src/vs/base/browser/ui/splitview/paneview.css b/src/vs/base/browser/ui/splitview/paneview.css
new file mode 100644
index 000000000000..add6aa45e94a
--- /dev/null
+++ b/src/vs/base/browser/ui/splitview/paneview.css
@@ -0,0 +1,101 @@
+/*---------------------------------------------------------------------------------------------
+ *  Copyright (c) Microsoft Corporation. All rights reserved.
+ *  Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+.monaco-pane-view {
+	width: 100%;
+	height: 100%;
+}
+
+.monaco-pane-view .pane {
+	overflow: hidden;
+	width: 100%;
+	height: 100%;
+	display: flex;
+	flex-direction: column;
+}
+
+.monaco-pane-view .pane > .pane-header {
+	font-size: 11px;
+	font-weight: bold;
+	text-transform: uppercase;
+	overflow: hidden;
+	display: flex;
+	cursor: pointer;
+	align-items: center;
+}
+
+.monaco-pane-view .pane > .pane-header > .twisties {
+	width: 20px;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	transform-origin: center;
+	color: inherit;
+	flex-shrink: 0;
+}
+
+.monaco-pane-view .pane > .pane-header.expanded > .twisties::before {
+	transform: rotate(90deg);
+}
+
+/* TODO: actions should be part of the pane, but they aren't yet */
+.monaco-pane-view .pane > .pane-header > .actions {
+	display: none;
+	flex: 1;
+}
+
+/* TODO: actions should be part of the pane, but they aren't yet */
+.monaco-pane-view .pane:hover > .pane-header.expanded > .actions,
+.monaco-pane-view .pane > .pane-header.actions-always-visible.expanded > .actions,
+.monaco-pane-view .pane > .pane-header.focused.expanded > .actions {
+	display: initial;
+}
+
+/* TODO: actions should be part of the pane, but they aren't yet */
+.monaco-pane-view .pane > .pane-header > .actions .action-label.icon,
+.monaco-pane-view .pane > .pane-header > .actions .action-label.codicon {
+	width: 28px;
+	height: 22px;
+	background-size: 16px;
+	background-position: center center;
+	background-repeat: no-repeat;
+	margin-right: 0;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	color: inherit;
+}
+
+/* Bold font style does not go well with CJK fonts */
+.monaco-pane-view:lang(zh-Hans) .pane > .pane-header,
+.monaco-pane-view:lang(zh-Hant) .pane > .pane-header,
+.monaco-pane-view:lang(ja) .pane > .pane-header,
+.monaco-pane-view:lang(ko) .pane > .pane-header {
+	font-weight: normal;
+}
+
+.monaco-pane-view .pane > .pane-header.hidden {
+	display: none;
+}
+
+.monaco-pane-view .pane > .pane-body {
+	overflow: hidden;
+	flex: 1;
+}
+
+/* Animation */
+
+.monaco-pane-view.animated .split-view-view {
+	transition-duration: 0.15s;
+	transition-timing-function: ease-out;
+}
+
+.monaco-pane-view.animated.vertical .split-view-view {
+	transition-property: height;
+}
+
+.monaco-pane-view.animated.horizontal .split-view-view {
+	transition-property: width;
+}
diff --git a/src/vs/base/browser/ui/splitview/panelview.ts b/src/vs/base/browser/ui/splitview/paneview.ts
similarity index 70%
rename from src/vs/base/browser/ui/splitview/panelview.ts
rename to src/vs/base/browser/ui/splitview/paneview.ts
index 9e3bd31ca435..54b84489f207 100644
--- a/src/vs/base/browser/ui/splitview/panelview.ts
+++ b/src/vs/base/browser/ui/splitview/paneview.ts
@@ -3,25 +3,27 @@
  *  Licensed under the Source EULA. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
 
-import 'vs/css!./panelview';
+import 'vs/css!./paneview';
 import { IDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle';
 import { Event, Emitter } from 'vs/base/common/event';
 import { domEvent } from 'vs/base/browser/event';
 import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
 import { KeyCode } from 'vs/base/common/keyCodes';
-import { $, append, addClass, removeClass, toggleClass, trackFocus } from 'vs/base/browser/dom';
+import { $, append, addClass, removeClass, toggleClass, trackFocus, EventHelper } from 'vs/base/browser/dom';
 import { firstIndex } from 'vs/base/common/arrays';
 import { Color, RGBA } from 'vs/base/common/color';
 import { SplitView, IView } from './splitview';
+import { isFirefox } from 'vs/base/browser/browser';
+import { DataTransfers } from 'vs/base/browser/dnd';
 
-export interface IPanelOptions {
+export interface IPaneOptions {
 	ariaHeaderLabel?: string;
 	minimumBodySize?: number;
 	maximumBodySize?: number;
 	expanded?: boolean;
 }
 
-export interface IPanelStyles {
+export interface IPaneStyles {
 	dropBackground?: Color;
 	headerForeground?: Color;
 	headerBackground?: Color;
@@ -29,7 +31,7 @@ export interface IPanelStyles {
 }
 
 /**
- * A Panel is a structured SplitView view.
+ * A Pane is a structured SplitView view.
  *
  * WARNING: You must call `render()` after you contruct it.
  * It can't be done automatically at the end of the ctor
@@ -37,7 +39,7 @@ export interface IPanelStyles {
  * Subclasses wouldn't be able to set own properties
  * before the `render()` call, thus forbiding their use.
  */
-export abstract class Panel extends Disposable implements IView {
+export abstract class Pane extends Disposable implements IView {
 
 	private static readonly HEADER_SIZE = 22;
 
@@ -52,7 +54,7 @@ export abstract class Panel extends Disposable implements IView {
 	private _minimumBodySize: number;
 	private _maximumBodySize: number;
 	private ariaHeaderLabel: string;
-	private styles: IPanelStyles = {};
+	private styles: IPaneStyles = {};
 	private animationTimer: number | undefined = undefined;
 
 	private readonly _onDidChange = this._register(new Emitter());
@@ -93,7 +95,7 @@ export abstract class Panel extends Disposable implements IView {
 	}
 
 	private get headerSize(): number {
-		return this.headerVisible ? Panel.HEADER_SIZE : 0;
+		return this.headerVisible ? Pane.HEADER_SIZE : 0;
 	}
 
 	get minimumSize(): number {
@@ -114,14 +116,14 @@ export abstract class Panel extends Disposable implements IView {
 
 	width: number = 0;
 
-	constructor(options: IPanelOptions = {}) {
+	constructor(options: IPaneOptions = {}) {
 		super();
 		this._expanded = typeof options.expanded === 'undefined' ? true : !!options.expanded;
 		this.ariaHeaderLabel = options.ariaHeaderLabel || '';
 		this._minimumBodySize = typeof options.minimumBodySize === 'number' ? options.minimumBodySize : 120;
 		this._maximumBodySize = typeof options.maximumBodySize === 'number' ? options.maximumBodySize : Number.POSITIVE_INFINITY;
 
-		this.element = $('.panel');
+		this.element = $('.pane');
 	}
 
 	isExpanded(): boolean {
@@ -167,7 +169,7 @@ export abstract class Panel extends Disposable implements IView {
 	}
 
 	render(): void {
-		this.header = $('.panel-header');
+		this.header = $('.pane-header');
 		append(this.element, this.header);
 		this.header.setAttribute('tabindex', '0');
 		this.header.setAttribute('role', 'toolbar');
@@ -196,12 +198,12 @@ export abstract class Panel extends Disposable implements IView {
 		this._register(domEvent(this.header, 'click')
 			(() => this.setExpanded(!this.isExpanded()), null));
 
-		this.body = append(this.element, $('.panel-body'));
+		this.body = append(this.element, $('.pane-body'));
 		this.renderBody(this.body);
 	}
 
 	layout(height: number): void {
-		const headerSize = this.headerVisible ? Panel.HEADER_SIZE : 0;
+		const headerSize = this.headerVisible ? Pane.HEADER_SIZE : 0;
 
 		if (this.isExpanded()) {
 			this.layoutBody(height - headerSize, this.width);
@@ -209,7 +211,7 @@ export abstract class Panel extends Disposable implements IView {
 		}
 	}
 
-	style(styles: IPanelStyles): void {
+	style(styles: IPaneStyles): void {
 		this.styles = styles;
 
 		if (!this.header) {
@@ -240,31 +242,31 @@ export abstract class Panel extends Disposable implements IView {
 }
 
 interface IDndContext {
-	draggable: PanelDraggable | null;
+	draggable: PaneDraggable | null;
 }
 
-class PanelDraggable extends Disposable {
+class PaneDraggable extends Disposable {
 
 	private static readonly DefaultDragOverBackgroundColor = new Color(new RGBA(128, 128, 128, 0.5));
 
 	private dragOverCounter = 0; // see https://github.com/Microsoft/vscode/issues/14470
 
-	private _onDidDrop = this._register(new Emitter<{ from: Panel, to: Panel }>());
+	private _onDidDrop = this._register(new Emitter<{ from: Pane, to: Pane }>());
 	readonly onDidDrop = this._onDidDrop.event;
 
-	constructor(private panel: Panel, private dnd: IPanelDndController, private context: IDndContext) {
+	constructor(private pane: Pane, private dnd: IPaneDndController, private context: IDndContext) {
 		super();
 
-		panel.draggableElement.draggable = true;
-		this._register(domEvent(panel.draggableElement, 'dragstart')(this.onDragStart, this));
-		this._register(domEvent(panel.dropTargetElement, 'dragenter')(this.onDragEnter, this));
-		this._register(domEvent(panel.dropTargetElement, 'dragleave')(this.onDragLeave, this));
-		this._register(domEvent(panel.dropTargetElement, 'dragend')(this.onDragEnd, this));
-		this._register(domEvent(panel.dropTargetElement, 'drop')(this.onDrop, this));
+		pane.draggableElement.draggable = true;
+		this._register(domEvent(pane.draggableElement, 'dragstart')(this.onDragStart, this));
+		this._register(domEvent(pane.dropTargetElement, 'dragenter')(this.onDragEnter, this));
+		this._register(domEvent(pane.dropTargetElement, 'dragleave')(this.onDragLeave, this));
+		this._register(domEvent(pane.dropTargetElement, 'dragend')(this.onDragEnd, this));
+		this._register(domEvent(pane.dropTargetElement, 'drop')(this.onDrop, this));
 	}
 
 	private onDragStart(e: DragEvent): void {
-		if (!this.dnd.canDrag(this.panel) || !e.dataTransfer) {
+		if (!this.dnd.canDrag(this.pane) || !e.dataTransfer) {
 			e.preventDefault();
 			e.stopPropagation();
 			return;
@@ -272,7 +274,12 @@ class PanelDraggable extends Disposable {
 
 		e.dataTransfer.effectAllowed = 'move';
 
-		const dragImage = append(document.body, $('.monaco-drag-image', {}, this.panel.draggableElement.textContent || ''));
+		if (isFirefox) {
+			// Firefox: requires to set a text data transfer to get going
+			e.dataTransfer?.setData(DataTransfers.TEXT, this.pane.draggableElement.textContent || '');
+		}
+
+		const dragImage = append(document.body, $('.monaco-drag-image', {}, this.pane.draggableElement.textContent || ''));
 		e.dataTransfer.setDragImage(dragImage, -10, -10);
 		setTimeout(() => document.body.removeChild(dragImage), 0);
 
@@ -284,7 +291,7 @@ class PanelDraggable extends Disposable {
 			return;
 		}
 
-		if (!this.dnd.canDrop(this.context.draggable.panel, this.panel)) {
+		if (!this.dnd.canDrop(this.context.draggable.pane, this.pane)) {
 			return;
 		}
 
@@ -297,7 +304,7 @@ class PanelDraggable extends Disposable {
 			return;
 		}
 
-		if (!this.dnd.canDrop(this.context.draggable.panel, this.panel)) {
+		if (!this.dnd.canDrop(this.context.draggable.pane, this.pane)) {
 			return;
 		}
 
@@ -323,11 +330,13 @@ class PanelDraggable extends Disposable {
 			return;
 		}
 
+		EventHelper.stop(e);
+
 		this.dragOverCounter = 0;
 		this.render();
 
-		if (this.dnd.canDrop(this.context.draggable.panel, this.panel) && this.context.draggable !== this) {
-			this._onDidDrop.fire({ from: this.context.draggable.panel, to: this.panel });
+		if (this.dnd.canDrop(this.context.draggable.pane, this.pane) && this.context.draggable !== this) {
+			this._onDidDrop.fire({ from: this.context.draggable.pane, to: this.pane });
 		}
 
 		this.context.draggable = null;
@@ -337,106 +346,106 @@ class PanelDraggable extends Disposable {
 		let backgroundColor: string | null = null;
 
 		if (this.dragOverCounter > 0) {
-			backgroundColor = (this.panel.dropBackground || PanelDraggable.DefaultDragOverBackgroundColor).toString();
+			backgroundColor = (this.pane.dropBackground || PaneDraggable.DefaultDragOverBackgroundColor).toString();
 		}
 
-		this.panel.dropTargetElement.style.backgroundColor = backgroundColor || '';
+		this.pane.dropTargetElement.style.backgroundColor = backgroundColor || '';
 	}
 }
 
-export interface IPanelDndController {
-	canDrag(panel: Panel): boolean;
-	canDrop(panel: Panel, overPanel: Panel): boolean;
+export interface IPaneDndController {
+	canDrag(pane: Pane): boolean;
+	canDrop(pane: Pane, overPane: Pane): boolean;
 }
 
-export class DefaultPanelDndController implements IPanelDndController {
+export class DefaultPaneDndController implements IPaneDndController {
 
-	canDrag(panel: Panel): boolean {
+	canDrag(pane: Pane): boolean {
 		return true;
 	}
 
-	canDrop(panel: Panel, overPanel: Panel): boolean {
+	canDrop(pane: Pane, overPane: Pane): boolean {
 		return true;
 	}
 }
 
-export interface IPanelViewOptions {
-	dnd?: IPanelDndController;
+export interface IPaneViewOptions {
+	dnd?: IPaneDndController;
 }
 
-interface IPanelItem {
-	panel: Panel;
+interface IPaneItem {
+	pane: Pane;
 	disposable: IDisposable;
 }
 
-export class PanelView extends Disposable {
+export class PaneView extends Disposable {
 
-	private dnd: IPanelDndController | undefined;
+	private dnd: IPaneDndController | undefined;
 	private dndContext: IDndContext = { draggable: null };
 	private el: HTMLElement;
-	private panelItems: IPanelItem[] = [];
+	private paneItems: IPaneItem[] = [];
 	private width: number = 0;
 	private splitview: SplitView;
 	private animationTimer: number | undefined = undefined;
 
-	private _onDidDrop = this._register(new Emitter<{ from: Panel, to: Panel }>());
-	readonly onDidDrop: Event<{ from: Panel, to: Panel }> = this._onDidDrop.event;
+	private _onDidDrop = this._register(new Emitter<{ from: Pane, to: Pane }>());
+	readonly onDidDrop: Event<{ from: Pane, to: Pane }> = this._onDidDrop.event;
 
 	readonly onDidSashChange: Event;
 
-	constructor(container: HTMLElement, options: IPanelViewOptions = {}) {
+	constructor(container: HTMLElement, options: IPaneViewOptions = {}) {
 		super();
 
 		this.dnd = options.dnd;
-		this.el = append(container, $('.monaco-panel-view'));
+		this.el = append(container, $('.monaco-pane-view'));
 		this.splitview = this._register(new SplitView(this.el));
 		this.onDidSashChange = this.splitview.onDidSashChange;
 	}
 
-	addPanel(panel: Panel, size: number, index = this.splitview.length): void {
+	addPane(pane: Pane, size: number, index = this.splitview.length): void {
 		const disposables = new DisposableStore();
-		panel.onDidChangeExpansionState(this.setupAnimation, this, disposables);
+		pane.onDidChangeExpansionState(this.setupAnimation, this, disposables);
 
-		const panelItem = { panel, disposable: disposables };
-		this.panelItems.splice(index, 0, panelItem);
-		panel.width = this.width;
-		this.splitview.addView(panel, size, index);
+		const paneItem = { pane: pane, disposable: disposables };
+		this.paneItems.splice(index, 0, paneItem);
+		pane.width = this.width;
+		this.splitview.addView(pane, size, index);
 
 		if (this.dnd) {
-			const draggable = new PanelDraggable(panel, this.dnd, this.dndContext);
+			const draggable = new PaneDraggable(pane, this.dnd, this.dndContext);
 			disposables.add(draggable);
 			disposables.add(draggable.onDidDrop(this._onDidDrop.fire, this._onDidDrop));
 		}
 	}
 
-	removePanel(panel: Panel): void {
-		const index = firstIndex(this.panelItems, item => item.panel === panel);
+	removePane(pane: Pane): void {
+		const index = firstIndex(this.paneItems, item => item.pane === pane);
 
 		if (index === -1) {
 			return;
 		}
 
 		this.splitview.removeView(index);
-		const panelItem = this.panelItems.splice(index, 1)[0];
-		panelItem.disposable.dispose();
+		const paneItem = this.paneItems.splice(index, 1)[0];
+		paneItem.disposable.dispose();
 	}
 
-	movePanel(from: Panel, to: Panel): void {
-		const fromIndex = firstIndex(this.panelItems, item => item.panel === from);
-		const toIndex = firstIndex(this.panelItems, item => item.panel === to);
+	movePane(from: Pane, to: Pane): void {
+		const fromIndex = firstIndex(this.paneItems, item => item.pane === from);
+		const toIndex = firstIndex(this.paneItems, item => item.pane === to);
 
 		if (fromIndex === -1 || toIndex === -1) {
 			return;
 		}
 
-		const [panelItem] = this.panelItems.splice(fromIndex, 1);
-		this.panelItems.splice(toIndex, 0, panelItem);
+		const [paneItem] = this.paneItems.splice(fromIndex, 1);
+		this.paneItems.splice(toIndex, 0, paneItem);
 
 		this.splitview.moveView(fromIndex, toIndex);
 	}
 
-	resizePanel(panel: Panel, size: number): void {
-		const index = firstIndex(this.panelItems, item => item.panel === panel);
+	resizePane(pane: Pane, size: number): void {
+		const index = firstIndex(this.paneItems, item => item.pane === pane);
 
 		if (index === -1) {
 			return;
@@ -445,8 +454,8 @@ export class PanelView extends Disposable {
 		this.splitview.resizeView(index, size);
 	}
 
-	getPanelSize(panel: Panel): number {
-		const index = firstIndex(this.panelItems, item => item.panel === panel);
+	getPaneSize(pane: Pane): number {
+		const index = firstIndex(this.paneItems, item => item.pane === pane);
 
 		if (index === -1) {
 			return -1;
@@ -458,8 +467,8 @@ export class PanelView extends Disposable {
 	layout(height: number, width: number): void {
 		this.width = width;
 
-		for (const panelItem of this.panelItems) {
-			panelItem.panel.width = width;
+		for (const paneItem of this.paneItems) {
+			paneItem.pane.width = width;
 		}
 
 		this.splitview.layout(height);
@@ -481,6 +490,6 @@ export class PanelView extends Disposable {
 	dispose(): void {
 		super.dispose();
 
-		this.panelItems.forEach(i => i.disposable.dispose());
+		this.paneItems.forEach(i => i.disposable.dispose());
 	}
 }
diff --git a/src/vs/base/browser/ui/splitview/splitview.ts b/src/vs/base/browser/ui/splitview/splitview.ts
index 41200e6e692c..569314ea66ff 100644
--- a/src/vs/base/browser/ui/splitview/splitview.ts
+++ b/src/vs/base/browser/ui/splitview/splitview.ts
@@ -23,14 +23,14 @@ const defaultStyles: ISplitViewStyles = {
 	separatorBorder: Color.transparent
 };
 
-export interface ISplitViewOptions {
+export interface ISplitViewOptions {
 	readonly orientation?: Orientation; // default Orientation.VERTICAL
 	readonly styles?: ISplitViewStyles;
 	readonly orthogonalStartSash?: Sash;
 	readonly orthogonalEndSash?: Sash;
 	readonly inverseAltBehavior?: boolean;
 	readonly proportionalLayout?: boolean; // default true,
-	readonly descriptor?: ISplitViewDescriptor;
+	readonly descriptor?: ISplitViewDescriptor;
 }
 
 /**
@@ -42,14 +42,14 @@ export const enum LayoutPriority {
 	High
 }
 
-export interface IView {
+export interface IView {
 	readonly element: HTMLElement;
 	readonly minimumSize: number;
 	readonly maximumSize: number;
 	readonly onDidChange: Event;
 	readonly priority?: LayoutPriority;
 	readonly snap?: boolean;
-	layout(size: number, orthogonalSize: number | undefined): void;
+	layout(size: number, offset: number, context: TLayoutContext | undefined): void;
 	setVisible?(visible: boolean): void;
 }
 
@@ -62,7 +62,7 @@ interface ISashEvent {
 
 type ViewItemSize = number | { cachedVisibleSize: number };
 
-abstract class ViewItem {
+abstract class ViewItem {
 
 	private _size: number;
 	set size(size: number) {
@@ -109,9 +109,13 @@ abstract class ViewItem {
 	get priority(): LayoutPriority | undefined { return this.view.priority; }
 	get snap(): boolean { return !!this.view.snap; }
 
+	set enabled(enabled: boolean) {
+		this.container.style.pointerEvents = enabled ? null : 'none';
+	}
+
 	constructor(
 		protected container: HTMLElement,
-		private view: IView,
+		private view: IView,
 		size: ViewItemSize,
 		private disposable: IDisposable
 	) {
@@ -125,31 +129,31 @@ abstract class ViewItem {
 		}
 	}
 
-	layout(position: number, orthogonalSize: number | undefined): void {
-		this.layoutContainer(position);
-		this.view.layout(this.size, orthogonalSize);
+	layout(offset: number, layoutContext: TLayoutContext | undefined): void {
+		this.layoutContainer(offset);
+		this.view.layout(this.size, offset, layoutContext);
 	}
 
-	abstract layoutContainer(position: number): void;
+	abstract layoutContainer(offset: number): void;
 
-	dispose(): IView {
+	dispose(): IView {
 		this.disposable.dispose();
 		return this.view;
 	}
 }
 
-class VerticalViewItem extends ViewItem {
+class VerticalViewItem extends ViewItem {
 
-	layoutContainer(position: number): void {
-		this.container.style.top = `${position}px`;
+	layoutContainer(offset: number): void {
+		this.container.style.top = `${offset}px`;
 		this.container.style.height = `${this.size}px`;
 	}
 }
 
-class HorizontalViewItem extends ViewItem {
+class HorizontalViewItem extends ViewItem {
 
-	layoutContainer(position: number): void {
-		this.container.style.left = `${position}px`;
+	layoutContainer(offset: number): void {
+		this.container.style.left = `${offset}px`;
 		this.container.style.width = `${this.size}px`;
 	}
 }
@@ -194,26 +198,26 @@ export namespace Sizing {
 	export function Invisible(cachedVisibleSize: number): InvisibleSizing { return { type: 'invisible', cachedVisibleSize }; }
 }
 
-export interface ISplitViewDescriptor {
+export interface ISplitViewDescriptor {
 	size: number;
 	views: {
 		visible?: boolean;
 		size: number;
-		view: IView;
+		view: IView;
 	}[];
 }
 
-export class SplitView extends Disposable {
+export class SplitView extends Disposable {
 
 	readonly orientation: Orientation;
 	readonly el: HTMLElement;
 	private sashContainer: HTMLElement;
 	private viewContainer: HTMLElement;
 	private size = 0;
-	private orthogonalSize: number | undefined;
+	private layoutContext: TLayoutContext | undefined;
 	private contentSize = 0;
 	private proportions: undefined | number[] = undefined;
-	private viewItems: ViewItem[] = [];
+	private viewItems: ViewItem[] = [];
 	private sashItems: ISashItem[] = [];
 	private sashDragState: ISashDragState | undefined;
 	private state: State = State.Idle;
@@ -262,7 +266,29 @@ export class SplitView extends Disposable {
 		return this.sashItems.map(s => s.sash);
 	}
 
-	constructor(container: HTMLElement, options: ISplitViewOptions = {}) {
+	private _startSnappingEnabled = true;
+	get startSnappingEnabled(): boolean { return this._startSnappingEnabled; }
+	set startSnappingEnabled(startSnappingEnabled: boolean) {
+		if (this._startSnappingEnabled === startSnappingEnabled) {
+			return;
+		}
+
+		this._startSnappingEnabled = startSnappingEnabled;
+		this.updateSashEnablement();
+	}
+
+	private _endSnappingEnabled = true;
+	get endSnappingEnabled(): boolean { return this._endSnappingEnabled; }
+	set endSnappingEnabled(endSnappingEnabled: boolean) {
+		if (this._endSnappingEnabled === endSnappingEnabled) {
+			return;
+		}
+
+		this._endSnappingEnabled = endSnappingEnabled;
+		this.updateSashEnablement();
+	}
+
+	constructor(container: HTMLElement, options: ISplitViewOptions = {}) {
 		super();
 
 		this.orientation = types.isUndefined(options.orientation) ? Orientation.VERTICAL : options.orientation;
@@ -305,11 +331,11 @@ export class SplitView extends Disposable {
 		}
 	}
 
-	addView(view: IView, size: number | Sizing, index = this.viewItems.length): void {
+	addView(view: IView, size: number | Sizing, index = this.viewItems.length): void {
 		this.doAddView(view, size, index, false);
 	}
 
-	removeView(index: number, sizing?: Sizing): IView {
+	removeView(index: number, sizing?: Sizing): IView {
 		if (this.state !== State.Idle) {
 			throw new Error('Cant modify splitview');
 		}
@@ -401,10 +427,10 @@ export class SplitView extends Disposable {
 		return viewItem.cachedVisibleSize;
 	}
 
-	layout(size: number, orthogonalSize?: number): void {
+	layout(size: number, layoutContext?: TLayoutContext): void {
 		const previousSize = Math.max(this.size, this.contentSize);
 		this.size = size;
-		this.orthogonalSize = orthogonalSize;
+		this.layoutContext = layoutContext;
 
 		if (!this.proportions) {
 			const indexes = range(this.viewItems.length);
@@ -430,6 +456,10 @@ export class SplitView extends Disposable {
 	}
 
 	private onSashStart({ sash, start, alt }: ISashEvent): void {
+		for (const item of this.viewItems) {
+			item.enabled = false;
+		}
+
 		const index = firstIndex(this.sashItems, item => item.sash === sash);
 
 		// This way, we can press Alt while we resize a sash, macOS style!
@@ -535,9 +565,13 @@ export class SplitView extends Disposable {
 		this._onDidSashChange.fire(index);
 		this.sashDragState!.disposable.dispose();
 		this.saveProportions();
+
+		for (const item of this.viewItems) {
+			item.enabled = true;
+		}
 	}
 
-	private onViewChange(item: ViewItem, size: number | undefined): void {
+	private onViewChange(item: ViewItem, size: number | undefined): void {
 		const index = this.viewItems.indexOf(item);
 
 		if (index < 0 || index >= this.viewItems.length) {
@@ -584,7 +618,7 @@ export class SplitView extends Disposable {
 	}
 
 	distributeViewSizes(): void {
-		const flexibleViewItems: ViewItem[] = [];
+		const flexibleViewItems: ViewItem[] = [];
 		let flexibleSize = 0;
 
 		for (const item of this.viewItems) {
@@ -615,7 +649,7 @@ export class SplitView extends Disposable {
 		return this.viewItems[index].size;
 	}
 
-	private doAddView(view: IView, size: number | Sizing, index = this.viewItems.length, skipLayout?: boolean): void {
+	private doAddView(view: IView, size: number | Sizing, index = this.viewItems.length, skipLayout?: boolean): void {
 		if (this.state !== State.Idle) {
 			throw new Error('Cant modify splitview');
 		}
@@ -849,17 +883,19 @@ export class SplitView extends Disposable {
 		this.contentSize = this.viewItems.reduce((r, i) => r + i.size, 0);
 
 		// Layout views
-		let position = 0;
+		let offset = 0;
 
 		for (const viewItem of this.viewItems) {
-			viewItem.layout(position, this.orthogonalSize);
-			position += viewItem.size;
+			viewItem.layout(offset, this.layoutContext);
+			offset += viewItem.size;
 		}
 
 		// Layout sashes
 		this.sashItems.forEach(item => item.sash.layout());
+		this.updateSashEnablement();
+	}
 
-		// Update sashes enablement
+	private updateSashEnablement(): void {
 		let previous = false;
 		const collapsesDown = this.viewItems.map(i => previous = (i.size - i.minimumSize > 0) || previous);
 
@@ -873,7 +909,12 @@ export class SplitView extends Disposable {
 		previous = false;
 		const expandsUp = reverseViews.map(i => previous = (i.maximumSize - i.size > 0) || previous).reverse();
 
-		this.sashItems.forEach(({ sash }, index) => {
+		let position = 0;
+		for (let index = 0; index < this.sashItems.length; index++) {
+			const { sash } = this.sashItems[index];
+			const viewItem = this.viewItems[index];
+			position += viewItem.size;
+
 			const min = !(collapsesDown[index] && expandsUp[index + 1]);
 			const max = !(expandsDown[index] && collapsesUp[index + 1]);
 
@@ -886,9 +927,9 @@ export class SplitView extends Disposable {
 				const snappedBefore = typeof snapBeforeIndex === 'number' && !this.viewItems[snapBeforeIndex].visible;
 				const snappedAfter = typeof snapAfterIndex === 'number' && !this.viewItems[snapAfterIndex].visible;
 
-				if (snappedBefore && collapsesUp[index]) {
+				if (snappedBefore && collapsesUp[index] && (position > 0 || this.startSnappingEnabled)) {
 					sash.state = SashState.Minimum;
-				} else if (snappedAfter && collapsesDown[index]) {
+				} else if (snappedAfter && collapsesDown[index] && (position < this.contentSize || this.endSnappingEnabled)) {
 					sash.state = SashState.Maximum;
 				} else {
 					sash.state = SashState.Disabled;
@@ -900,8 +941,7 @@ export class SplitView extends Disposable {
 			} else {
 				sash.state = SashState.Enabled;
 			}
-			// }
-		});
+		}
 	}
 
 	private getSashPosition(sash: Sash): number {
diff --git a/src/vs/base/browser/ui/toolbar/toolbar.ts b/src/vs/base/browser/ui/toolbar/toolbar.ts
index 1ef430d158e5..21a3b47728ec 100644
--- a/src/vs/base/browser/ui/toolbar/toolbar.ts
+++ b/src/vs/base/browser/ui/toolbar/toolbar.ts
@@ -52,7 +52,7 @@ export class ToolBar extends Disposable {
 			orientation: options.orientation,
 			ariaLabel: options.ariaLabel,
 			actionRunner: options.actionRunner,
-			actionViewItemProvider: (action: Action) => {
+			actionViewItemProvider: (action: IAction) => {
 
 				// Return special action item for the toggle menu action
 				if (action.id === ToggleMenuAction.ID) {
diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts
index 0a9e363f687b..9a058a864337 100644
--- a/src/vs/base/browser/ui/tree/abstractTree.ts
+++ b/src/vs/base/browser/ui/tree/abstractTree.ts
@@ -7,7 +7,7 @@ import 'vs/css!./media/tree';
 import { IDisposable, dispose, Disposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
 import { IListOptions, List, IListStyles, MouseController, DefaultKeyboardNavigationDelegate } from 'vs/base/browser/ui/list/listWidget';
 import { IListVirtualDelegate, IListRenderer, IListMouseEvent, IListEvent, IListContextMenuEvent, IListDragAndDrop, IListDragOverReaction, IKeyboardNavigationLabelProvider, IIdentityProvider, IKeyboardNavigationDelegate } from 'vs/base/browser/ui/list/list';
-import { append, $, toggleClass, getDomNodePagePosition, removeClass, addClass, hasClass, hasParentWithClass, createStyleSheet, clearNode } from 'vs/base/browser/dom';
+import { append, $, toggleClass, getDomNodePagePosition, removeClass, addClass, hasClass, hasParentWithClass, createStyleSheet, clearNode, addClasses, removeClasses } from 'vs/base/browser/dom';
 import { Event, Relay, Emitter, EventBufferer } from 'vs/base/common/event';
 import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
 import { KeyCode } from 'vs/base/common/keyCodes';
@@ -27,10 +27,24 @@ import { clamp } from 'vs/base/common/numbers';
 import { ScrollEvent } from 'vs/base/common/scrollable';
 import { SetMap } from 'vs/base/common/collections';
 
+class TreeElementsDragAndDropData extends ElementsDragAndDropData {
+
+	set context(context: TContext | undefined) {
+		this.data.context = context;
+	}
+
+	get context(): TContext | undefined {
+		return this.data.context;
+	}
+
+	constructor(private data: ElementsDragAndDropData, TContext>) {
+		super(data.elements.map(node => node.element));
+	}
+}
+
 function asTreeDragAndDropData(data: IDragAndDropData): IDragAndDropData {
 	if (data instanceof ElementsDragAndDropData) {
-		const nodes = (data as ElementsDragAndDropData>).elements;
-		return new ElementsDragAndDropData(nodes.map(node => node.element));
+		return new TreeElementsDragAndDropData(data);
 	}
 
 	return data;
@@ -47,9 +61,9 @@ class TreeNodeListDragAndDrop implements IListDragAndDrop<
 		return this.dnd.getDragURI(node.element);
 	}
 
-	getDragLabel(nodes: ITreeNode[]): string | undefined {
+	getDragLabel(nodes: ITreeNode[], originalEvent: DragEvent): string | undefined {
 		if (this.dnd.getDragLabel) {
-			return this.dnd.getDragLabel(nodes.map(node => node.element));
+			return this.dnd.getDragLabel(nodes.map(node => node.element), originalEvent);
 		}
 
 		return undefined;
@@ -87,7 +101,7 @@ class TreeNodeListDragAndDrop implements IListDragAndDrop<
 			}, 500);
 		}
 
-		if (typeof result === 'boolean' || !result.accept || typeof result.bubble === 'undefined') {
+		if (typeof result === 'boolean' || !result.accept || typeof result.bubble === 'undefined' || result.feedback) {
 			if (!raw) {
 				const accept = typeof result === 'boolean' ? result : result.accept;
 				const effect = typeof result === 'boolean' ? undefined : result.effect;
@@ -121,6 +135,12 @@ class TreeNodeListDragAndDrop implements IListDragAndDrop<
 
 		this.dnd.drop(asTreeDragAndDropData(data), targetNode && targetNode.element, targetIndex, originalEvent);
 	}
+
+	onDragEnd(originalEvent: DragEvent): void {
+		if (this.dnd.onDragEnd) {
+			this.dnd.onDragEnd(originalEvent);
+		}
+	}
 }
 
 function asListOptions(modelProvider: () => ITreeModel, options?: IAbstractTreeOptions): IListOptions> | undefined {
@@ -141,12 +161,16 @@ function asListOptions(modelProvider: () => ITreeModel {
+				return options.accessibilityProvider!.getActiveDescendantId!(node.element);
+			})
 		},
 		keyboardNavigationLabelProvider: options.keyboardNavigationLabelProvider && {
 			...options.keyboardNavigationLabelProvider,
@@ -211,6 +235,8 @@ export enum RenderIndentGuides {
 interface ITreeRendererOptions {
 	readonly indent?: number;
 	readonly renderIndentGuides?: RenderIndentGuides;
+	// TODO@joao replace this with collapsible: boolean | 'ondemand'
+	readonly hideTwistiesOfChildlessElements?: boolean;
 }
 
 interface IRenderData {
@@ -244,6 +270,7 @@ class TreeRenderer implements IListRenderer
 	private renderedElements = new Map>();
 	private renderedNodes = new Map, IRenderData>();
 	private indent: number = TreeRenderer.DefaultIndent;
+	private hideTwistiesOfChildlessElements: boolean = false;
 
 	private shouldRenderIndentGuides: boolean = false;
 	private renderedIndentGuides = new SetMap, HTMLDivElement>();
@@ -290,6 +317,10 @@ class TreeRenderer implements IListRenderer
 				}
 			}
 		}
+
+		if (typeof options.hideTwistiesOfChildlessElements !== 'undefined') {
+			this.hideTwistiesOfChildlessElements = options.hideTwistiesOfChildlessElements;
+		}
 	}
 
 	renderTemplate(container: HTMLElement): ITreeListTemplateData {
@@ -309,7 +340,7 @@ class TreeRenderer implements IListRenderer
 		}
 
 		const indent = TreeRenderer.DefaultIndent + (node.depth - 1) * this.indent;
-		templateData.twistie.style.marginLeft = `${indent}px`;
+		templateData.twistie.style.paddingLeft = `${indent}px`;
 		templateData.indent.style.width = `${indent + this.indent - 16}px`;
 
 		this.renderTwistie(node, templateData);
@@ -365,10 +396,12 @@ class TreeRenderer implements IListRenderer
 			this.renderer.renderTwistie(node.element, templateData.twistie);
 		}
 
-		toggleClass(templateData.twistie, 'codicon', node.collapsible);
-		toggleClass(templateData.twistie, 'codicon-chevron-down', node.collapsible);
-		toggleClass(templateData.twistie, 'collapsible', node.collapsible);
-		toggleClass(templateData.twistie, 'collapsed', node.collapsible && node.collapsed);
+		if (node.collapsible && (!this.hideTwistiesOfChildlessElements || node.visibleChildrenCount > 0)) {
+			addClasses(templateData.twistie, 'codicon', 'codicon-chevron-down', 'collapsible');
+			toggleClass(templateData.twistie, 'collapsed', node.collapsed);
+		} else {
+			removeClasses(templateData.twistie, 'codicon', 'codicon-chevron-down', 'collapsible', 'collapsed');
+		}
 
 		if (node.collapsible) {
 			templateData.container.setAttribute('aria-expanded', String(!node.collapsed));
@@ -430,12 +463,16 @@ class TreeRenderer implements IListRenderer
 
 		nodes.forEach(node => {
 			const ref = model.getNodeLocation(node);
-			const parentRef = model.getParentNodeLocation(ref);
+			try {
+				const parentRef = model.getParentNodeLocation(ref);
 
-			if (node.collapsible && node.children.length > 0 && !node.collapsed) {
-				set.add(node);
-			} else if (parentRef) {
-				set.add(model.getNode(parentRef));
+				if (node.collapsible && node.children.length > 0 && !node.collapsed) {
+					set.add(node);
+				} else if (parentRef) {
+					set.add(model.getNode(parentRef));
+				}
+			} catch {
+				// noop
 			}
 		});
 
@@ -601,14 +638,14 @@ class TypeFilterController implements IDisposable {
 		const controls = append(this.domNode, $('.controls'));
 
 		this._filterOnType = !!tree.options.filterOnType;
-		this.filterOnTypeDomNode = append(controls, $('input.filter'));
+		this.filterOnTypeDomNode = append(controls, $('input.filter.codicon.codicon-list-selection'));
 		this.filterOnTypeDomNode.type = 'checkbox';
 		this.filterOnTypeDomNode.checked = this._filterOnType;
 		this.filterOnTypeDomNode.tabIndex = -1;
 		this.updateFilterOnTypeTitle();
 		domEvent(this.filterOnTypeDomNode, 'input')(this.onDidChangeFilterOnType, this, this.disposables);
 
-		this.clearDomNode = append(controls, $('button.clear'));
+		this.clearDomNode = append(controls, $('button.clear.codicon.codicon-close'));
 		this.clearDomNode.tabIndex = -1;
 		this.clearDomNode.title = localize('clear', "Clear");
 
@@ -657,7 +694,7 @@ class TypeFilterController implements IDisposable {
 
 		const onKeyDown = Event.chain(domEvent(this.view.getHTMLElement(), 'keydown'))
 			.filter(e => !isInputElement(e.target as HTMLElement) || e.target === this.filterOnTypeDomNode)
-			.filter(e => e.key !== 'Dead')
+			.filter(e => e.key !== 'Dead' && !/^Media/.test(e.key))
 			.map(e => new StandardKeyboardEvent(e))
 			.filter(this.keyboardNavigationEventFilter || (() => true))
 			.filter(() => this.automaticKeyboardNavigation || this.triggered)
@@ -918,7 +955,6 @@ export interface IAbstractTreeOptions extends IAbstractTr
 	readonly collapseByDefault?: boolean; // defaults to false
 	readonly filter?: ITreeFilter;
 	readonly dnd?: ITreeDragAndDrop;
-	readonly autoExpandSingleChildren?: boolean;
 	readonly keyboardNavigationEventFilter?: IKeyboardNavigationEventFilter;
 	readonly expandOnlyOnTwistieClick?: boolean | ((e: T) => boolean);
 	readonly additionalScrollHeight?: number;
@@ -1385,8 +1421,13 @@ export abstract class AbstractTree implements IDisposable
 		return this.view.renderHeight;
 	}
 
-	get firstVisibleElement(): T {
+	get firstVisibleElement(): T | undefined {
 		const index = this.view.firstVisibleIndex;
+
+		if (index < 0 || index >= this.view.length) {
+			return undefined;
+		}
+
 		const node = this.view.element(index);
 		return node.element;
 	}
diff --git a/src/vs/base/browser/ui/tree/asyncDataTree.ts b/src/vs/base/browser/ui/tree/asyncDataTree.ts
index 05ae77ac46ee..58a24e40dd55 100644
--- a/src/vs/base/browser/ui/tree/asyncDataTree.ts
+++ b/src/vs/base/browser/ui/tree/asyncDataTree.ts
@@ -6,7 +6,7 @@
 import { ComposedTreeDelegate, IAbstractTreeOptions, IAbstractTreeOptionsUpdate } from 'vs/base/browser/ui/tree/abstractTree';
 import { ObjectTree, IObjectTreeOptions, CompressibleObjectTree, ICompressibleTreeRenderer, ICompressibleKeyboardNavigationLabelProvider, ICompressibleObjectTreeOptions } from 'vs/base/browser/ui/tree/objectTree';
 import { IListVirtualDelegate, IIdentityProvider, IListDragAndDrop, IListDragOverReaction } from 'vs/base/browser/ui/list/list';
-import { ITreeElement, ITreeNode, ITreeRenderer, ITreeEvent, ITreeMouseEvent, ITreeContextMenuEvent, ITreeSorter, ICollapseStateChangeEvent, IAsyncDataSource, ITreeDragAndDrop, TreeError, WeakMapper } from 'vs/base/browser/ui/tree/tree';
+import { ITreeElement, ITreeNode, ITreeRenderer, ITreeEvent, ITreeMouseEvent, ITreeContextMenuEvent, ITreeSorter, ICollapseStateChangeEvent, IAsyncDataSource, ITreeDragAndDrop, TreeError, WeakMapper, ITreeFilter, TreeVisibility, TreeFilterResult } from 'vs/base/browser/ui/tree/tree';
 import { IDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle';
 import { Emitter, Event } from 'vs/base/common/event';
 import { timeout, CancelablePromise, createCancelablePromise } from 'vs/base/common/async';
@@ -19,6 +19,8 @@ import { toggleClass } from 'vs/base/browser/dom';
 import { values } from 'vs/base/common/map';
 import { ScrollEvent } from 'vs/base/common/scrollable';
 import { ICompressedTreeNode, ICompressedTreeElement } from 'vs/base/browser/ui/tree/compressedObjectTreeModel';
+import { IThemable } from 'vs/base/common/styler';
+import { isFilterResult, getVisibleState } from 'vs/base/browser/ui/tree/indexTreeModel';
 
 interface IAsyncDataTreeNode {
 	element: TInput | T;
@@ -149,20 +151,24 @@ function asTreeContextMenuEvent(e: ITreeContextMenuEvent extends ElementsDragAndDropData {
+
+	set context(context: TContext | undefined) {
+		this.data.context = context;
+	}
 
-export interface IChildrenResolutionEvent {
-	readonly element: T | null;
-	readonly reason: ChildrenResolutionReason;
+	get context(): TContext | undefined {
+		return this.data.context;
+	}
+
+	constructor(private data: ElementsDragAndDropData, TContext>) {
+		super(data.elements.map(node => node.element as T));
+	}
 }
 
 function asAsyncDataTreeDragAndDropData(data: IDragAndDropData): IDragAndDropData {
 	if (data instanceof ElementsDragAndDropData) {
-		const nodes = (data as ElementsDragAndDropData>).elements;
-		return new ElementsDragAndDropData(nodes.map(node => node.element));
+		return new AsyncDataTreeElementsDragAndDropData(data);
 	}
 
 	return data;
@@ -176,9 +182,9 @@ class AsyncDataTreeNodeListDragAndDrop implements IListDragAndDrop[]): string | undefined {
+	getDragLabel(nodes: IAsyncDataTreeNode[], originalEvent: DragEvent): string | undefined {
 		if (this.dnd.getDragLabel) {
-			return this.dnd.getDragLabel(nodes.map(node => node.element as T));
+			return this.dnd.getDragLabel(nodes.map(node => node.element as T), originalEvent);
 		}
 
 		return undefined;
@@ -197,6 +203,12 @@ class AsyncDataTreeNodeListDragAndDrop implements IListDragAndDrop | undefined, targetIndex: number | undefined, originalEvent: DragEvent): void {
 		this.dnd.drop(asAsyncDataTreeDragAndDropData(data), targetNode && targetNode.element as T, targetIndex, originalEvent);
 	}
+
+	onDragEnd(originalEvent: DragEvent): void {
+		if (this.dnd.onDragEnd) {
+			this.dnd.onDragEnd(originalEvent);
+		}
+	}
 }
 
 function asObjectTreeOptions(options?: IAsyncDataTreeOptions): IObjectTreeOptions, TFilterData> | undefined {
@@ -218,9 +230,16 @@ function asObjectTreeOptions(options?: IAsyncDataTreeOpt
 			}
 		},
 		accessibilityProvider: options.accessibilityProvider && {
+			...options.accessibilityProvider,
 			getAriaLabel(e) {
 				return options.accessibilityProvider!.getAriaLabel(e.element as T);
-			}
+			},
+			getAriaLevel: options.accessibilityProvider!.getAriaLevel && (node => {
+				return options.accessibilityProvider!.getAriaLevel!(node.element as T);
+			}),
+			getActiveDescendantId: options.accessibilityProvider.getActiveDescendantId && (node => {
+				return options.accessibilityProvider!.getActiveDescendantId!(node.element as T);
+			})
 		},
 		filter: options.filter && {
 			filter(e, parentVisibility) {
@@ -271,10 +290,10 @@ function dfs(node: IAsyncDataTreeNode, fn: (node: IAsyncDa
 	node.children.forEach(child => dfs(child, fn));
 }
 
-export class AsyncDataTree implements IDisposable {
+export class AsyncDataTree implements IDisposable, IThemable {
 
-	private readonly tree: ObjectTree, TFilterData>;
-	private readonly root: IAsyncDataTreeNode;
+	protected readonly tree: ObjectTree, TFilterData>;
+	protected readonly root: IAsyncDataTreeNode;
 	private readonly nodes = new Map>();
 	private readonly sorter?: ITreeSorter;
 	private readonly collapseByDefault?: { (e: T): boolean; };
@@ -282,7 +301,7 @@ export class AsyncDataTree implements IDisposable
 	private readonly subTreeRefreshPromises = new Map, Promise>();
 	private readonly refreshPromises = new Map, CancelablePromise>();
 
-	private readonly identityProvider?: IIdentityProvider;
+	protected readonly identityProvider?: IIdentityProvider;
 	private readonly autoExpandSingleChildren: boolean;
 
 	private readonly _onDidRender = new Emitter();
@@ -323,7 +342,7 @@ export class AsyncDataTree implements IDisposable
 	get onDidDispose(): Event { return this.tree.onDidDispose; }
 
 	constructor(
-		private user: string,
+		protected user: string,
 		container: HTMLElement,
 		delegate: IListVirtualDelegate,
 		renderers: ITreeRenderer[],
@@ -411,10 +430,6 @@ export class AsyncDataTree implements IDisposable
 		return this.tree.renderHeight;
 	}
 
-	get firstVisibleElement(): T {
-		return this.tree.firstVisibleElement!.element as T;
-	}
-
 	get lastVisibleElement(): T {
 		return this.tree.lastVisibleElement!.element as T;
 	}
@@ -445,7 +460,7 @@ export class AsyncDataTree implements IDisposable
 
 		const viewStateContext = viewState && { viewState, focus: [], selection: [] } as IAsyncDataTreeViewStateContext;
 
-		await this._updateChildren(input, true, viewStateContext);
+		await this._updateChildren(input, true, false, viewStateContext);
 
 		if (viewStateContext) {
 			this.tree.setFocus(viewStateContext.focus);
@@ -457,11 +472,11 @@ export class AsyncDataTree implements IDisposable
 		}
 	}
 
-	async updateChildren(element: TInput | T = this.root.element, recursive = true): Promise {
-		await this._updateChildren(element, recursive);
+	async updateChildren(element: TInput | T = this.root.element, recursive = true, rerender = false): Promise {
+		await this._updateChildren(element, recursive, rerender);
 	}
 
-	private async _updateChildren(element: TInput | T = this.root.element, recursive = true, viewStateContext?: IAsyncDataTreeViewStateContext): Promise {
+	private async _updateChildren(element: TInput | T = this.root.element, recursive = true, rerender = false, viewStateContext?: IAsyncDataTreeViewStateContext): Promise {
 		if (typeof this.root.element === 'undefined') {
 			throw new TreeError(this.user, 'Tree input not set');
 		}
@@ -471,7 +486,17 @@ export class AsyncDataTree implements IDisposable
 			await Event.toPromise(this._onDidRender.event);
 		}
 
-		await this.refreshAndRenderNode(this.getDataNode(element), recursive, ChildrenResolutionReason.Refresh, viewStateContext);
+		const node = this.getDataNode(element);
+		await this.refreshAndRenderNode(node, recursive, viewStateContext);
+
+		if (rerender) {
+			try {
+				this.tree.rerender(node);
+			} catch {
+				// missing nodes are fine, this could've resulted from
+				// parallel refresh calls, removing `node` altogether
+			}
+		}
 	}
 
 	resort(element: TInput | T = this.root.element, recursive = true): void {
@@ -653,18 +678,9 @@ export class AsyncDataTree implements IDisposable
 		return node;
 	}
 
-	private async refreshAndRenderNode(node: IAsyncDataTreeNode, recursive: boolean, reason: ChildrenResolutionReason, viewStateContext?: IAsyncDataTreeViewStateContext): Promise {
+	private async refreshAndRenderNode(node: IAsyncDataTreeNode, recursive: boolean, viewStateContext?: IAsyncDataTreeViewStateContext): Promise {
 		await this.refreshNode(node, recursive, viewStateContext);
 		this.render(node, viewStateContext);
-
-		if (node !== this.root && this.autoExpandSingleChildren && reason === ChildrenResolutionReason.Expand) {
-			const treeNode = this.tree.getNode(node);
-			const visibleChildren = treeNode.children.filter(node => node.visible);
-
-			if (visibleChildren.length === 1) {
-				await this.tree.expand(visibleChildren[0].element, false);
-			}
-		}
 	}
 
 	private async refreshNode(node: IAsyncDataTreeNode, recursive: boolean, viewStateContext?: IAsyncDataTreeViewStateContext): Promise {
@@ -752,12 +768,7 @@ export class AsyncDataTree implements IDisposable
 
 		result = createCancelablePromise(async () => {
 			const children = await this.dataSource.getChildren(node.element!);
-
-			if (this.sorter) {
-				children.sort(this.sorter.compare.bind(this.sorter));
-			}
-
-			return children;
+			return this.processChildren(children);
 		});
 
 		this.refreshPromises.set(node, result);
@@ -770,7 +781,7 @@ export class AsyncDataTree implements IDisposable
 			if (deep) {
 				this.collapse(node.element.element as T);
 			} else {
-				this.refreshAndRenderNode(node.element, false, ChildrenResolutionReason.Expand)
+				this.refreshAndRenderNode(node.element, false)
 					.catch(onUnexpectedError);
 			}
 		}
@@ -783,13 +794,14 @@ export class AsyncDataTree implements IDisposable
 		}
 
 		const nodesToForget = new Map>();
-		const childrenTreeNodesById = new Map | null, TFilterData>>();
+		const childrenTreeNodesById = new Map, collapsed: boolean }>();
 
 		for (const child of node.children) {
 			nodesToForget.set(child.element as T, child);
 
 			if (this.identityProvider) {
-				childrenTreeNodesById.set(child.id!, this.tree.getNode(child));
+				const collapsed = this.tree.isCollapsed(child);
+				childrenTreeNodesById.set(child.id!, { node: child, collapsed });
 			}
 		}
 
@@ -810,10 +822,10 @@ export class AsyncDataTree implements IDisposable
 			}
 
 			const id = this.identityProvider.getId(element).toString();
-			const childNode = childrenTreeNodesById.get(id);
+			const result = childrenTreeNodesById.get(id);
 
-			if (childNode) {
-				const asyncDataTreeNode = childNode.element!;
+			if (result) {
+				const asyncDataTreeNode = result.node;
 
 				nodesToForget.delete(asyncDataTreeNode.element as T);
 				this.nodes.delete(asyncDataTreeNode.element as T);
@@ -823,8 +835,10 @@ export class AsyncDataTree implements IDisposable
 				asyncDataTreeNode.hasChildren = hasChildren;
 
 				if (recursive) {
-					if (childNode.collapsed) {
-						dfs(asyncDataTreeNode, node => node.stale = true);
+					if (result.collapsed) {
+						asyncDataTreeNode.children.forEach(node => dfs(node, node => this.nodes.delete(node.element as T)));
+						asyncDataTreeNode.children.splice(0, asyncDataTreeNode.children.length);
+						asyncDataTreeNode.stale = true;
 					} else {
 						childrenToRefresh.push(asyncDataTreeNode);
 					}
@@ -866,10 +880,16 @@ export class AsyncDataTree implements IDisposable
 
 		node.children.splice(0, node.children.length, ...children);
 
+		// TODO@joao this doesn't take filter into account
+		if (node !== this.root && this.autoExpandSingleChildren && children.length === 1 && childrenToRefresh.length === 0) {
+			children[0].collapsedByDefault = false;
+			childrenToRefresh.push(children[0]);
+		}
+
 		return childrenToRefresh;
 	}
 
-	private render(node: IAsyncDataTreeNode, viewStateContext?: IAsyncDataTreeViewStateContext): void {
+	protected render(node: IAsyncDataTreeNode, viewStateContext?: IAsyncDataTreeViewStateContext): void {
 		const children = node.children.map(node => this.asTreeElement(node, viewStateContext));
 		this.tree.setChildren(node === this.root ? null : node, children);
 
@@ -881,6 +901,14 @@ export class AsyncDataTree implements IDisposable
 	}
 
 	protected asTreeElement(node: IAsyncDataTreeNode, viewStateContext?: IAsyncDataTreeViewStateContext): ITreeElement> {
+		if (node.stale) {
+			return {
+				element: node,
+				collapsible: node.hasChildren,
+				collapsed: true
+			};
+		}
+
 		let collapsed: boolean | undefined;
 
 		if (viewStateContext && viewStateContext.viewState.expanded && node.id && viewStateContext.viewState.expanded.indexOf(node.id) > -1) {
@@ -899,6 +927,14 @@ export class AsyncDataTree implements IDisposable
 		};
 	}
 
+	protected processChildren(children: T[]): T[] {
+		if (this.sorter) {
+			children.sort(this.sorter.compare.bind(this.sorter));
+		}
+
+		return children;
+	}
+
 	// view state
 
 	getViewState(): IAsyncDataTreeViewState {
@@ -994,6 +1030,12 @@ class CompressibleAsyncDataTreeRenderer i
 		}
 	}
 
+	disposeCompressedElements(node: ITreeNode>, TFilterData>, index: number, templateData: IDataTreeListTemplateData, height: number | undefined): void {
+		if (this.renderer.disposeCompressedElements) {
+			this.renderer.disposeCompressedElements(this.compressibleNodeMapperProvider().map(node) as ITreeNode, TFilterData>, index, templateData.templateData, height);
+		}
+	}
+
 	disposeTemplate(templateData: IDataTreeListTemplateData): void {
 		this.renderer.disposeTemplate(templateData.templateData);
 	}
@@ -1023,12 +1065,19 @@ function asCompressibleObjectTreeOptions(options?: IComp
 }
 
 export interface ICompressibleAsyncDataTreeOptions extends IAsyncDataTreeOptions {
+	readonly compressionEnabled?: boolean;
 	readonly keyboardNavigationLabelProvider?: ICompressibleKeyboardNavigationLabelProvider;
 }
 
+export interface ICompressibleAsyncDataTreeOptionsUpdate extends IAsyncDataTreeOptionsUpdate {
+	readonly compressionEnabled?: boolean;
+}
+
 export class CompressibleAsyncDataTree extends AsyncDataTree {
 
+	protected readonly tree!: CompressibleObjectTree, TFilterData>;
 	protected readonly compressibleNodeMapper: CompressibleAsyncDataTreeNodeMapper = new WeakMapper(node => new CompressibleAsyncDataTreeNodeWrapper(node));
+	private filter?: ITreeFilter;
 
 	constructor(
 		user: string,
@@ -1037,9 +1086,10 @@ export class CompressibleAsyncDataTree extends As
 		private compressionDelegate: ITreeCompressionDelegate,
 		renderers: ICompressibleTreeRenderer[],
 		dataSource: IAsyncDataSource,
-		options: IAsyncDataTreeOptions = {}
+		options: ICompressibleAsyncDataTreeOptions = {}
 	) {
 		super(user, container, virtualDelegate, renderers, dataSource, options);
+		this.filter = options.filter;
 	}
 
 	protected createTree(
@@ -1062,4 +1112,137 @@ export class CompressibleAsyncDataTree extends As
 			...super.asTreeElement(node, viewStateContext)
 		};
 	}
+
+	updateOptions(options: ICompressibleAsyncDataTreeOptionsUpdate = {}): void {
+		this.tree.updateOptions(options);
+	}
+
+	getViewState(): IAsyncDataTreeViewState {
+		if (!this.identityProvider) {
+			throw new TreeError(this.user, 'Can\'t get tree view state without an identity provider');
+		}
+
+		const getId = (element: T) => this.identityProvider!.getId(element).toString();
+		const focus = this.getFocus().map(getId);
+		const selection = this.getSelection().map(getId);
+
+		const expanded: string[] = [];
+		const root = this.tree.getCompressedTreeNode();
+		const queue = [root];
+
+		while (queue.length > 0) {
+			const node = queue.shift()!;
+
+			if (node !== root && node.collapsible && !node.collapsed) {
+				for (const asyncNode of node.element!.elements) {
+					expanded.push(getId(asyncNode.element as T));
+				}
+			}
+
+			queue.push(...node.children);
+		}
+
+		return { focus, selection, expanded, scrollTop: this.scrollTop };
+	}
+
+	protected render(node: IAsyncDataTreeNode, viewStateContext?: IAsyncDataTreeViewStateContext): void {
+		if (!this.identityProvider) {
+			return super.render(node, viewStateContext);
+		}
+
+		// Preserve traits across compressions. Hacky but does the trick.
+		// This is hard to fix properly since it requires rewriting the traits
+		// across trees and lists. Let's just keep it this way for now.
+		const getId = (element: T) => this.identityProvider!.getId(element).toString();
+		const getUncompressedIds = (nodes: IAsyncDataTreeNode[]): Set => {
+			const result = new Set();
+
+			for (const node of nodes) {
+				const compressedNode = this.tree.getCompressedTreeNode(node === this.root ? null : node);
+
+				if (!compressedNode.element) {
+					continue;
+				}
+
+				for (const node of compressedNode.element.elements) {
+					result.add(getId(node.element as T));
+				}
+			}
+
+			return result;
+		};
+
+		const oldSelection = getUncompressedIds(this.tree.getSelection() as IAsyncDataTreeNode[]);
+		const oldFocus = getUncompressedIds(this.tree.getFocus() as IAsyncDataTreeNode[]);
+
+		super.render(node, viewStateContext);
+
+		const selection = this.getSelection();
+		let didChangeSelection = false;
+
+		const focus = this.getFocus();
+		let didChangeFocus = false;
+
+		const visit = (node: ITreeNode> | null, TFilterData>) => {
+			const compressedNode = node.element;
+
+			if (compressedNode) {
+				for (let i = 0; i < compressedNode.elements.length; i++) {
+					const id = getId(compressedNode.elements[i].element as T);
+
+					if (oldSelection.has(id)) {
+						selection.push(compressedNode.elements[compressedNode.elements.length - 1].element as T);
+						didChangeSelection = true;
+					}
+
+					if (oldFocus.has(id)) {
+						focus.push(compressedNode.elements[compressedNode.elements.length - 1].element as T);
+						didChangeFocus = true;
+					}
+				}
+			}
+
+			node.children.forEach(visit);
+		};
+
+		visit(this.tree.getCompressedTreeNode(node === this.root ? null : node));
+
+		if (didChangeSelection) {
+			this.setSelection(selection);
+		}
+
+		if (didChangeFocus) {
+			this.setFocus(focus);
+		}
+	}
+
+	// For compressed async data trees, `TreeVisibility.Recurse` doesn't currently work
+	// and we have to filter everything beforehand
+	// Related to #85193 and #85835
+	protected processChildren(children: T[]): T[] {
+		if (this.filter) {
+			children = children.filter(e => {
+				const result = this.filter!.filter(e, TreeVisibility.Visible);
+				const visibility = getVisibility(result);
+
+				if (visibility === TreeVisibility.Recurse) {
+					throw new Error('Recursive tree visibility not supported in async data compressed trees');
+				}
+
+				return visibility === TreeVisibility.Visible;
+			});
+		}
+
+		return super.processChildren(children);
+	}
+}
+
+function getVisibility(filterResult: TreeFilterResult): TreeVisibility {
+	if (typeof filterResult === 'boolean') {
+		return filterResult ? TreeVisibility.Visible : TreeVisibility.Hidden;
+	} else if (isFilterResult(filterResult)) {
+		return getVisibleState(filterResult.visibility);
+	} else {
+		return getVisibleState(filterResult);
+	}
 }
diff --git a/src/vs/base/browser/ui/tree/compressedObjectTreeModel.ts b/src/vs/base/browser/ui/tree/compressedObjectTreeModel.ts
index 40c61feeadad..bc9347e5c253 100644
--- a/src/vs/base/browser/ui/tree/compressedObjectTreeModel.ts
+++ b/src/vs/base/browser/ui/tree/compressedObjectTreeModel.ts
@@ -11,7 +11,7 @@ import { IObjectTreeModelOptions, ObjectTreeModel, IObjectTreeModel } from 'vs/b
 
 // Exported only for test reasons, do not use directly
 export interface ICompressedTreeElement extends ITreeElement {
-	readonly children?: Iterator> | ICompressedTreeElement[];
+	readonly children?: ISequence>;
 	readonly incompressible?: boolean;
 }
 
@@ -100,16 +100,15 @@ export function decompress(element: ITreeElement>): IC
 
 function splice(treeElement: ICompressedTreeElement, element: T, children: Iterator>): ICompressedTreeElement {
 	if (treeElement.element === element) {
-		return { element, children };
+		return { ...treeElement, children };
 	}
 
-	return {
-		...treeElement,
-		children: Iterator.map(Iterator.from(treeElement.children), e => splice(e, element, children))
-	};
+	return { ...treeElement, children: Iterator.map(Iterator.from(treeElement.children), e => splice(e, element, children)) };
 }
 
-interface ICompressedObjectTreeModelOptions extends IObjectTreeModelOptions, TFilterData> { }
+interface ICompressedObjectTreeModelOptions extends IObjectTreeModelOptions, TFilterData> {
+	readonly compressionEnabled?: boolean;
+}
 
 // Exported only for test reasons, do not use directly
 export class CompressedObjectTreeModel, TFilterData extends NonNullable = void> implements ITreeModel | null, TFilterData, T | null> {
@@ -122,7 +121,7 @@ export class CompressedObjectTreeModel, TFilterData e
 
 	private model: ObjectTreeModel, TFilterData>;
 	private nodes = new Map>();
-	private enabled: boolean = true;
+	private enabled: boolean;
 
 	get size(): number { return this.nodes.size; }
 
@@ -132,13 +131,13 @@ export class CompressedObjectTreeModel, TFilterData e
 		options: ICompressedObjectTreeModelOptions = {}
 	) {
 		this.model = new ObjectTreeModel(user, list, options);
+		this.enabled = typeof options.compressionEnabled === 'undefined' ? true : options.compressionEnabled;
 	}
 
 	setChildren(
 		element: T | null,
 		children: ISequence> | undefined
 	): void {
-
 		if (element === null) {
 			const compressedChildren = Iterator.map(Iterator.from(children), this.enabled ? compress : noCompress);
 			this._setChildren(null, compressedChildren);
@@ -368,6 +367,7 @@ function mapOptions(compressedNodeUnwrapper: CompressedNodeUnwra
 }
 
 export interface ICompressibleObjectTreeModelOptions extends IObjectTreeModelOptions {
+	readonly compressionEnabled?: boolean;
 	readonly elementMapper?: ElementMapper;
 }
 
@@ -493,7 +493,7 @@ export class CompressibleObjectTreeModel, TFilterData
 		return this.model.resort(element, recursive);
 	}
 
-	getCompressedTreeNode(element: T): ITreeNode, TFilterData> {
-		return this.model.getNode(element) as ITreeNode, TFilterData>;
+	getCompressedTreeNode(location: T | null = null): ITreeNode | null, TFilterData> {
+		return this.model.getNode(location);
 	}
 }
diff --git a/src/vs/base/browser/ui/tree/dataTree.ts b/src/vs/base/browser/ui/tree/dataTree.ts
index 5b4aa9b168be..766628925b93 100644
--- a/src/vs/base/browser/ui/tree/dataTree.ts
+++ b/src/vs/base/browser/ui/tree/dataTree.ts
@@ -11,7 +11,7 @@ import { IListVirtualDelegate, IIdentityProvider } from 'vs/base/browser/ui/list
 import { Iterator } from 'vs/base/common/iterator';
 
 export interface IDataTreeOptions extends IAbstractTreeOptions {
-	sorter?: ITreeSorter;
+	readonly sorter?: ITreeSorter;
 }
 
 export interface IDataTreeViewState {
diff --git a/src/vs/base/browser/ui/tree/indexTreeModel.ts b/src/vs/base/browser/ui/tree/indexTreeModel.ts
index 2c4407fa2667..61da572b86cb 100644
--- a/src/vs/base/browser/ui/tree/indexTreeModel.ts
+++ b/src/vs/base/browser/ui/tree/indexTreeModel.ts
@@ -442,6 +442,7 @@ export class IndexTreeModel, TFilterData = voi
 
 			if (visibility === TreeVisibility.Hidden) {
 				node.visible = false;
+				node.renderNodeCount = 0;
 				return false;
 			}
 
diff --git a/src/vs/base/browser/ui/tree/media/panelviewlet.css b/src/vs/base/browser/ui/tree/media/paneviewlet.css
similarity index 80%
rename from src/vs/base/browser/ui/tree/media/panelviewlet.css
rename to src/vs/base/browser/ui/tree/media/paneviewlet.css
index 3715ee6a8041..740ab44eb2d3 100644
--- a/src/vs/base/browser/ui/tree/media/panelviewlet.css
+++ b/src/vs/base/browser/ui/tree/media/paneviewlet.css
@@ -3,11 +3,10 @@
  *  Licensed under the Source EULA. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
 
-.monaco-panel-view .panel > .panel-header h3.title {
+.monaco-pane-view .pane > .pane-header h3.title {
 	white-space: nowrap;
 	text-overflow: ellipsis;
 	overflow: hidden;
 	font-size: 11px;
-	-webkit-margin-before: 0;
-	-webkit-margin-after: 0;
+	margin: 0;
 }
diff --git a/src/vs/base/browser/ui/tree/media/tree.css b/src/vs/base/browser/ui/tree/media/tree.css
index d4dbaf2ea3ed..61cb32a82c57 100644
--- a/src/vs/base/browser/ui/tree/media/tree.css
+++ b/src/vs/base/browser/ui/tree/media/tree.css
@@ -14,7 +14,7 @@
 	height: 100%;
 	position: absolute;
 	top: 0;
-	left: 18px;
+	left: 16px;
 	pointer-events: none;
 }
 
@@ -41,7 +41,7 @@
 .monaco-tl-twistie {
 	font-size: 10px;
 	text-align: right;
-	margin-right: 6px;
+	padding-right: 6px;
 	flex-shrink: 0;
 	width: 16px;
 	display: flex !important;
diff --git a/src/vs/base/browser/ui/tree/objectTree.ts b/src/vs/base/browser/ui/tree/objectTree.ts
index b5ed7a3b632f..19930f9b0029 100644
--- a/src/vs/base/browser/ui/tree/objectTree.ts
+++ b/src/vs/base/browser/ui/tree/objectTree.ts
@@ -4,7 +4,7 @@
  *--------------------------------------------------------------------------------------------*/
 
 import { ISequence } from 'vs/base/common/iterator';
-import { AbstractTree, IAbstractTreeOptions } from 'vs/base/browser/ui/tree/abstractTree';
+import { AbstractTree, IAbstractTreeOptions, IAbstractTreeOptionsUpdate } from 'vs/base/browser/ui/tree/abstractTree';
 import { ISpliceable } from 'vs/base/common/sequence';
 import { ITreeNode, ITreeModel, ITreeElement, ITreeRenderer, ITreeSorter, ICollapseStateChangeEvent } from 'vs/base/browser/ui/tree/tree';
 import { ObjectTreeModel, IObjectTreeModel } from 'vs/base/browser/ui/tree/objectTreeModel';
@@ -56,7 +56,7 @@ export class ObjectTree, TFilterData = void> extends
 }
 
 interface ICompressedTreeNodeProvider {
-	getCompressedTreeNode(element: T): ITreeNode, TFilterData>;
+	getCompressedTreeNode(location: T | null): ITreeNode | null, TFilterData>;
 }
 
 export interface ICompressibleTreeRenderer extends ITreeRenderer {
@@ -69,7 +69,7 @@ interface CompressibleTemplateData {
 	readonly data: TTemplateData;
 }
 
-class CompressibleRenderer implements ITreeRenderer> {
+class CompressibleRenderer, TFilterData, TTemplateData> implements ITreeRenderer> {
 
 	readonly templateId: string;
 	readonly onDidChangeTwistieState: Event | undefined;
@@ -93,7 +93,7 @@ class CompressibleRenderer implements ITreeRender
 	}
 
 	renderElement(node: ITreeNode, index: number, templateData: CompressibleTemplateData, height: number | undefined): void {
-		const compressedTreeNode = this.compressedTreeNodeProvider.getCompressedTreeNode(node.element);
+		const compressedTreeNode = this.compressedTreeNodeProvider.getCompressedTreeNode(node.element) as ITreeNode, TFilterData>;
 
 		if (compressedTreeNode.element.elements.length === 1) {
 			templateData.compressedTreeNode = undefined;
@@ -132,6 +132,7 @@ export interface ICompressibleKeyboardNavigationLabelProvider extends IKeyboa
 }
 
 export interface ICompressibleObjectTreeOptions extends IObjectTreeOptions {
+	readonly compressionEnabled?: boolean;
 	readonly elementMapper?: ElementMapper;
 	readonly keyboardNavigationLabelProvider?: ICompressibleKeyboardNavigationLabelProvider;
 }
@@ -144,7 +145,7 @@ function asObjectTreeOptions(compressedTreeNodeProvider: () => I
 				let compressedTreeNode: ITreeNode, TFilterData>;
 
 				try {
-					compressedTreeNode = compressedTreeNodeProvider().getCompressedTreeNode(e);
+					compressedTreeNode = compressedTreeNodeProvider().getCompressedTreeNode(e) as ITreeNode, TFilterData>;
 				} catch {
 					return options.keyboardNavigationLabelProvider!.getKeyboardNavigationLabel(e);
 				}
@@ -159,6 +160,10 @@ function asObjectTreeOptions(compressedTreeNodeProvider: () => I
 	};
 }
 
+export interface ICompressibleObjectTreeOptionsUpdate extends IAbstractTreeOptionsUpdate {
+	readonly compressionEnabled?: boolean;
+}
+
 export class CompressibleObjectTree, TFilterData = void> extends ObjectTree implements ICompressedTreeNodeProvider {
 
 	protected model!: CompressibleObjectTreeModel;
@@ -171,7 +176,7 @@ export class CompressibleObjectTree, TFilterData = vo
 		options: ICompressibleObjectTreeOptions = {}
 	) {
 		const compressedTreeNodeProvider = () => this;
-		const compressibleRenderers = renderers.map(r => new CompressibleRenderer(compressedTreeNodeProvider, r));
+		const compressibleRenderers = renderers.map(r => new CompressibleRenderer(compressedTreeNodeProvider, r));
 		super(user, container, delegate, compressibleRenderers, asObjectTreeOptions(compressedTreeNodeProvider, options));
 	}
 
@@ -183,15 +188,15 @@ export class CompressibleObjectTree, TFilterData = vo
 		return new CompressibleObjectTreeModel(user, view, options);
 	}
 
-	isCompressionEnabled(): boolean {
-		return this.model.isCompressionEnabled();
-	}
+	updateOptions(optionsUpdate: ICompressibleObjectTreeOptionsUpdate = {}): void {
+		super.updateOptions(optionsUpdate);
 
-	setCompressionEnabled(enabled: boolean): void {
-		this.model.setCompressionEnabled(enabled);
+		if (typeof optionsUpdate.compressionEnabled !== 'undefined') {
+			this.model.setCompressionEnabled(optionsUpdate.compressionEnabled);
+		}
 	}
 
-	getCompressedTreeNode(element: T): ITreeNode, TFilterData> {
-		return this.model.getCompressedTreeNode(element)!;
+	getCompressedTreeNode(element: T | null = null): ITreeNode | null, TFilterData> {
+		return this.model.getCompressedTreeNode(element);
 	}
 }
diff --git a/src/vs/base/browser/ui/tree/objectTreeModel.ts b/src/vs/base/browser/ui/tree/objectTreeModel.ts
index 2906f9d7bdc9..268d6b4d5046 100644
--- a/src/vs/base/browser/ui/tree/objectTreeModel.ts
+++ b/src/vs/base/browser/ui/tree/objectTreeModel.ts
@@ -257,7 +257,12 @@ export class ObjectTreeModel, TFilterData extends Non
 			throw new TreeError(this.user, `Invalid getParentNodeLocation call`);
 		}
 
-		const node = this.nodes.get(element)!;
+		const node = this.nodes.get(element);
+
+		if (!node) {
+			throw new TreeError(this.user, `Tree element not found: ${element}`);
+		}
+
 		const location = this.model.getNodeLocation(node);
 		const parentLocation = this.model.getParentNodeLocation(location);
 		const parent = this.model.getNode(parentLocation);
diff --git a/src/vs/base/browser/ui/widget.ts b/src/vs/base/browser/ui/widget.ts
index 37be854e9948..4f8b9687b0f8 100644
--- a/src/vs/base/browser/ui/widget.ts
+++ b/src/vs/base/browser/ui/widget.ts
@@ -7,6 +7,7 @@ import * as dom from 'vs/base/browser/dom';
 import { IKeyboardEvent, StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
 import { IMouseEvent, StandardMouseEvent } from 'vs/base/browser/mouseEvent';
 import { Disposable } from 'vs/base/common/lifecycle';
+import { Gesture } from 'vs/base/browser/touch';
 
 export abstract class Widget extends Disposable {
 
@@ -49,4 +50,8 @@ export abstract class Widget extends Disposable {
 	protected onchange(domNode: HTMLElement, listener: (e: Event) => void): void {
 		this._register(dom.addDisposableListener(domNode, dom.EventType.CHANGE, listener));
 	}
+
+	protected ignoreGesture(domNode: HTMLElement): void {
+		Gesture.ignoreTarget(domNode);
+	}
 }
diff --git a/src/vs/base/common/async.ts b/src/vs/base/common/async.ts
index 131df405be4b..0d0c483568c8 100644
--- a/src/vs/base/common/async.ts
+++ b/src/vs/base/common/async.ts
@@ -764,5 +764,5 @@ export async function retry(task: ITask>, delay: number, retries:
 		}
 	}
 
-	return Promise.reject(lastError);
+	throw lastError;
 }
diff --git a/src/vs/base/common/buffer.ts b/src/vs/base/common/buffer.ts
index 9d70184b4e69..320613a3f620 100644
--- a/src/vs/base/common/buffer.ts
+++ b/src/vs/base/common/buffer.ts
@@ -3,8 +3,14 @@
  *  Licensed under the Source EULA. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
 
+import * as strings from 'vs/base/common/strings';
+import * as streams from 'vs/base/common/stream';
+
 declare var Buffer: any;
-export const hasBuffer = (typeof Buffer !== 'undefined');
+
+const hasBuffer = (typeof Buffer !== 'undefined');
+const hasTextEncoder = (typeof TextEncoder !== 'undefined');
+const hasTextDecoder = (typeof TextDecoder !== 'undefined');
 
 let textEncoder: TextEncoder | null;
 let textDecoder: TextDecoder | null;
@@ -31,11 +37,13 @@ export class VSBuffer {
 	static fromString(source: string): VSBuffer {
 		if (hasBuffer) {
 			return new VSBuffer(Buffer.from(source));
-		} else {
+		} else if (hasTextEncoder) {
 			if (!textEncoder) {
 				textEncoder = new TextEncoder();
 			}
 			return new VSBuffer(textEncoder.encode(source));
+		} else {
+			return new VSBuffer(strings.encodeUTF8(source));
 		}
 	}
 
@@ -69,11 +77,13 @@ export class VSBuffer {
 	toString(): string {
 		if (hasBuffer) {
 			return this.buffer.toString();
-		} else {
+		} else if (hasTextDecoder) {
 			if (!textDecoder) {
 				textDecoder = new TextDecoder();
 			}
 			return textDecoder.decode(this.buffer);
+		} else {
+			return strings.decodeUTF8(this.buffer);
 		}
 	}
 
@@ -132,335 +142,32 @@ function writeUInt8(destination: Uint8Array, value: number, offset: number): voi
 	destination[offset] = value;
 }
 
-export interface VSBufferReadable {
+export interface VSBufferReadable extends streams.Readable { }
 
-	/**
-	 * Read data from the underlying source. Will return
-	 * null to indicate that no more data can be read.
-	 */
-	read(): VSBuffer | null;
-}
+export interface VSBufferReadableStream extends streams.ReadableStream { }
 
-export interface ReadableStream {
-
-	/**
-	 * The 'data' event is emitted whenever the stream is
-	 * relinquishing ownership of a chunk of data to a consumer.
-	 */
-	on(event: 'data', callback: (chunk: T) => void): void;
-
-	/**
-	 * Emitted when any error occurs.
-	 */
-	on(event: 'error', callback: (err: any) => void): void;
-
-	/**
-	 * The 'end' event is emitted when there is no more data
-	 * to be consumed from the stream. The 'end' event will
-	 * not be emitted unless the data is completely consumed.
-	 */
-	on(event: 'end', callback: () => void): void;
-
-	/**
-	 * Stops emitting any events until resume() is called.
-	 */
-	pause?(): void;
-
-	/**
-	 * Starts emitting events again after pause() was called.
-	 */
-	resume?(): void;
-
-	/**
-	 * Destroys the stream and stops emitting any event.
-	 */
-	destroy?(): void;
-}
-
-/**
- * A readable stream that sends data via VSBuffer.
- */
-export interface VSBufferReadableStream extends ReadableStream {
-	pause(): void;
-	resume(): void;
-	destroy(): void;
-}
-
-export function isVSBufferReadableStream(obj: any): obj is VSBufferReadableStream {
-	const candidate: VSBufferReadableStream = obj;
-
-	return candidate && [candidate.on, candidate.pause, candidate.resume, candidate.destroy].every(fn => typeof fn === 'function');
-}
+export interface VSBufferWriteableStream extends streams.WriteableStream { }
 
-/**
- * Helper to fully read a VSBuffer readable into a single buffer.
- */
 export function readableToBuffer(readable: VSBufferReadable): VSBuffer {
-	const chunks: VSBuffer[] = [];
-
-	let chunk: VSBuffer | null;
-	while (chunk = readable.read()) {
-		chunks.push(chunk);
-	}
-
-	return VSBuffer.concat(chunks);
+	return streams.consumeReadable(readable, chunks => VSBuffer.concat(chunks));
 }
 
-/**
- * Helper to convert a buffer into a readable buffer.
- */
 export function bufferToReadable(buffer: VSBuffer): VSBufferReadable {
-	let done = false;
-
-	return {
-		read: () => {
-			if (done) {
-				return null;
-			}
-
-			done = true;
-
-			return buffer;
-		}
-	};
-}
-
-/**
- * Helper to fully read a VSBuffer stream into a single buffer.
- */
-export function streamToBuffer(stream: VSBufferReadableStream): Promise {
-	return new Promise((resolve, reject) => {
-		const chunks: VSBuffer[] = [];
-
-		stream.on('data', chunk => chunks.push(chunk));
-		stream.on('error', error => reject(error));
-		stream.on('end', () => resolve(VSBuffer.concat(chunks)));
-	});
-}
-
-/**
- * Helper to create a VSBufferStream from an existing VSBuffer.
- */
-export function bufferToStream(buffer: VSBuffer): VSBufferReadableStream {
-	const stream = writeableBufferStream();
-
-	stream.end(buffer);
-
-	return stream;
+	return streams.toReadable(buffer);
 }
 
-/**
- * Helper to create a VSBufferStream from a Uint8Array stream.
- */
-export function toVSBufferReadableStream(stream: ReadableStream): VSBufferReadableStream {
-	const vsbufferStream = writeableBufferStream();
-
-	stream.on('data', data => vsbufferStream.write(typeof data === 'string' ? VSBuffer.fromString(data) : VSBuffer.wrap(data)));
-	stream.on('end', () => vsbufferStream.end());
-	stream.on('error', error => vsbufferStream.error(error));
-
-	return vsbufferStream;
+export function streamToBuffer(stream: streams.ReadableStream): Promise {
+	return streams.consumeStream(stream, chunks => VSBuffer.concat(chunks));
 }
 
-/**
- * Helper to create a VSBufferStream that can be pushed
- * buffers to. Will only start to emit data when a listener
- * is added.
- */
-export function writeableBufferStream(): VSBufferWriteableStream {
-	return new VSBufferWriteableStreamImpl();
+export function bufferToStream(buffer: VSBuffer): streams.ReadableStream {
+	return streams.toStream(buffer, chunks => VSBuffer.concat(chunks));
 }
 
-export interface VSBufferWriteableStream extends VSBufferReadableStream {
-	write(chunk: VSBuffer): void;
-	error(error: Error): void;
-	end(result?: VSBuffer | Error): void;
+export function streamToBufferReadableStream(stream: streams.ReadableStreamEvents): streams.ReadableStream {
+	return streams.transform(stream, { data: data => typeof data === 'string' ? VSBuffer.fromString(data) : VSBuffer.wrap(data) }, chunks => VSBuffer.concat(chunks));
 }
 
-class VSBufferWriteableStreamImpl implements VSBufferWriteableStream {
-
-	private readonly state = {
-		flowing: false,
-		ended: false,
-		destroyed: false
-	};
-
-	private readonly buffer = {
-		data: [] as VSBuffer[],
-		error: [] as Error[]
-	};
-
-	private readonly listeners = {
-		data: [] as { (chunk: VSBuffer): void }[],
-		error: [] as { (error: Error): void }[],
-		end: [] as { (): void }[]
-	};
-
-	pause(): void {
-		if (this.state.destroyed) {
-			return;
-		}
-
-		this.state.flowing = false;
-	}
-
-	resume(): void {
-		if (this.state.destroyed) {
-			return;
-		}
-
-		if (!this.state.flowing) {
-			this.state.flowing = true;
-
-			// emit buffered events
-			this.flowData();
-			this.flowErrors();
-			this.flowEnd();
-		}
-	}
-
-	write(chunk: VSBuffer): void {
-		if (this.state.destroyed) {
-			return;
-		}
-
-		// flowing: directly send the data to listeners
-		if (this.state.flowing) {
-			this.listeners.data.forEach(listener => listener(chunk));
-		}
-
-		// not yet flowing: buffer data until flowing
-		else {
-			this.buffer.data.push(chunk);
-		}
-	}
-
-	error(error: Error): void {
-		if (this.state.destroyed) {
-			return;
-		}
-
-		// flowing: directly send the error to listeners
-		if (this.state.flowing) {
-			this.listeners.error.forEach(listener => listener(error));
-		}
-
-		// not yet flowing: buffer errors until flowing
-		else {
-			this.buffer.error.push(error);
-		}
-	}
-
-	end(result?: VSBuffer | Error): void {
-		if (this.state.destroyed) {
-			return;
-		}
-
-		// end with data or error if provided
-		if (result instanceof Error) {
-			this.error(result);
-		} else if (result) {
-			this.write(result);
-		}
-
-		// flowing: send end event to listeners
-		if (this.state.flowing) {
-			this.listeners.end.forEach(listener => listener());
-
-			this.destroy();
-		}
-
-		// not yet flowing: remember state
-		else {
-			this.state.ended = true;
-		}
-	}
-
-	on(event: 'data', callback: (chunk: VSBuffer) => void): void;
-	on(event: 'error', callback: (err: any) => void): void;
-	on(event: 'end', callback: () => void): void;
-	on(event: 'data' | 'error' | 'end', callback: (arg0?: any) => void): void {
-		if (this.state.destroyed) {
-			return;
-		}
-
-		switch (event) {
-			case 'data':
-				this.listeners.data.push(callback);
-
-				// switch into flowing mode as soon as the first 'data'
-				// listener is added and we are not yet in flowing mode
-				this.resume();
-
-				break;
-
-			case 'end':
-				this.listeners.end.push(callback);
-
-				// emit 'end' event directly if we are flowing
-				// and the end has already been reached
-				//
-				// finish() when it went through
-				if (this.state.flowing && this.flowEnd()) {
-					this.destroy();
-				}
-
-				break;
-
-			case 'error':
-				this.listeners.error.push(callback);
-
-				// emit buffered 'error' events unless done already
-				// now that we know that we have at least one listener
-				if (this.state.flowing) {
-					this.flowErrors();
-				}
-
-				break;
-		}
-	}
-
-	private flowData(): void {
-		if (this.buffer.data.length > 0) {
-			const fullDataBuffer = VSBuffer.concat(this.buffer.data);
-
-			this.listeners.data.forEach(listener => listener(fullDataBuffer));
-
-			this.buffer.data.length = 0;
-		}
-	}
-
-	private flowErrors(): void {
-		if (this.listeners.error.length > 0) {
-			for (const error of this.buffer.error) {
-				this.listeners.error.forEach(listener => listener(error));
-			}
-
-			this.buffer.error.length = 0;
-		}
-	}
-
-	private flowEnd(): boolean {
-		if (this.state.ended) {
-			this.listeners.end.forEach(listener => listener());
-
-			return this.listeners.end.length > 0;
-		}
-
-		return false;
-	}
-
-	destroy(): void {
-		if (!this.state.destroyed) {
-			this.state.destroyed = true;
-			this.state.ended = true;
-
-			this.buffer.data.length = 0;
-			this.buffer.error.length = 0;
-
-			this.listeners.data.length = 0;
-			this.listeners.error.length = 0;
-			this.listeners.end.length = 0;
-		}
-	}
+export function newWriteableBufferStream(): streams.WriteableStream {
+	return streams.newWriteableStream(chunks => VSBuffer.concat(chunks));
 }
diff --git a/src/vs/base/common/cache.ts b/src/vs/base/common/cache.ts
index bb43d5473075..655db1751018 100644
--- a/src/vs/base/common/cache.ts
+++ b/src/vs/base/common/cache.ts
@@ -22,7 +22,6 @@ export class Cache {
 
 		const cts = new CancellationTokenSource();
 		const promise = this.task(cts.token);
-		promise.finally(() => cts.dispose());
 
 		this.result = {
 			promise,
diff --git a/src/vs/base/common/color.ts b/src/vs/base/common/color.ts
index d5cf65fb2bd0..555c9bea075e 100644
--- a/src/vs/base/common/color.ts
+++ b/src/vs/base/common/color.ts
@@ -399,6 +399,23 @@ export class Color {
 		return new Color(new RGBA(r, g, b, a));
 	}
 
+	makeOpaque(opaqueBackground: Color): Color {
+		if (this.isOpaque() || opaqueBackground.rgba.a !== 1) {
+			// only allow to blend onto a non-opaque color onto a opaque color
+			return this;
+		}
+
+		const { r, g, b, a } = this.rgba;
+
+		// https://stackoverflow.com/questions/12228548/finding-equivalent-color-with-opacity
+		return new Color(new RGBA(
+			opaqueBackground.rgba.r - a * (opaqueBackground.rgba.r - r),
+			opaqueBackground.rgba.g - a * (opaqueBackground.rgba.g - g),
+			opaqueBackground.rgba.b - a * (opaqueBackground.rgba.b - b),
+			1
+		));
+	}
+
 	flatten(...backgrounds: Color[]): Color {
 		const background = backgrounds.reduceRight((accumulator, color) => {
 			return Color._flatten(color, accumulator);
diff --git a/src/vs/base/common/errors.ts b/src/vs/base/common/errors.ts
index 4a04854bf803..9f04cd4c90e4 100644
--- a/src/vs/base/common/errors.ts
+++ b/src/vs/base/common/errors.ts
@@ -195,7 +195,6 @@ export function getErrorMessage(err: any): string {
 	return String(err);
 }
 
-
 export class NotImplementedError extends Error {
 	constructor(message?: string) {
 		super('NotImplemented');
diff --git a/src/vs/base/common/event.ts b/src/vs/base/common/event.ts
index 0ae897bde66d..c83d0bd2cc98 100644
--- a/src/vs/base/common/event.ts
+++ b/src/vs/base/common/event.ts
@@ -7,6 +7,7 @@ import { onUnexpectedError } from 'vs/base/common/errors';
 import { once as onceFn } from 'vs/base/common/functional';
 import { Disposable, IDisposable, toDisposable, combinedDisposable, DisposableStore } from 'vs/base/common/lifecycle';
 import { LinkedList } from 'vs/base/common/linkedList';
+import { CancellationToken } from 'vs/base/common/cancellation';
 
 /**
  * To an event a function with one or zero parameters
@@ -653,27 +654,39 @@ export interface IWaitUntil {
 
 export class AsyncEmitter extends Emitter {
 
-	private _asyncDeliveryQueue?: [Listener, T, Promise[]][];
+	private _asyncDeliveryQueue?: LinkedList<[Listener, Omit]>;
 
-	async fireAsync(eventFn: (thenables: Promise[], listener: Function) => T): Promise {
+	async fireAsync(data: Omit, token: CancellationToken, promiseJoin?: (p: Promise, listener: Function) => Promise): Promise {
 		if (!this._listeners) {
 			return;
 		}
 
-		// put all [listener,event]-pairs into delivery queue
-		// then emit all event. an inner/nested event might be
-		// the driver of this
 		if (!this._asyncDeliveryQueue) {
-			this._asyncDeliveryQueue = [];
+			this._asyncDeliveryQueue = new LinkedList();
 		}
 
 		for (let iter = this._listeners.iterator(), e = iter.next(); !e.done; e = iter.next()) {
-			const thenables: Promise[] = [];
-			this._asyncDeliveryQueue.push([e.value, eventFn(thenables, typeof e.value === 'function' ? e.value : e.value[0]), thenables]);
+			this._asyncDeliveryQueue.push([e.value, data]);
 		}
 
-		while (this._asyncDeliveryQueue.length > 0) {
-			const [listener, event, thenables] = this._asyncDeliveryQueue.shift()!;
+		while (this._asyncDeliveryQueue.size > 0 && !token.isCancellationRequested) {
+
+			const [listener, data] = this._asyncDeliveryQueue.shift()!;
+			const thenables: Promise[] = [];
+
+			const event = {
+				...data,
+				waitUntil: (p: Promise): void => {
+					if (Object.isFrozen(thenables)) {
+						throw new Error('waitUntil can NOT be called asynchronous');
+					}
+					if (promiseJoin) {
+						p = promiseJoin(p, typeof listener === 'function' ? listener : listener[0]);
+					}
+					thenables.push(p);
+				}
+			};
+
 			try {
 				if (typeof listener === 'function') {
 					listener.call(undefined, event);
@@ -688,7 +701,7 @@ export class AsyncEmitter extends Emitter {
 			// freeze thenables-collection to enforce sync-calls to
 			// wait until and then wait for all thenables to resolve
 			Object.freeze(thenables);
-			await Promise.all(thenables);
+			await Promise.all(thenables).catch(e => onUnexpectedError(e));
 		}
 	}
 }
diff --git a/src/vs/base/common/filters.ts b/src/vs/base/common/filters.ts
index ff688e0986e6..d247714d266c 100644
--- a/src/vs/base/common/filters.ts
+++ b/src/vs/base/common/filters.ts
@@ -543,7 +543,7 @@ export function fuzzyScore(pattern: string, patternLow: string, patternStart: nu
 	const patternLen = pattern.length > _maxLen ? _maxLen : pattern.length;
 	const wordLen = word.length > _maxLen ? _maxLen : word.length;
 
-	if (patternStart >= patternLen || wordStart >= wordLen || patternLen > wordLen) {
+	if (patternStart >= patternLen || wordStart >= wordLen || (patternLen - patternStart) > (wordLen - wordStart)) {
 		return undefined;
 	}
 
diff --git a/src/vs/base/common/iterator.ts b/src/vs/base/common/iterator.ts
index f9bee211a77e..0fca7c7cabc4 100644
--- a/src/vs/base/common/iterator.ts
+++ b/src/vs/base/common/iterator.ts
@@ -172,13 +172,28 @@ export module Iterator {
 			}
 		};
 	}
+
+	export function chain(iterator: Iterator): ChainableIterator {
+		return new ChainableIterator(iterator);
+	}
+}
+
+export class ChainableIterator implements Iterator {
+
+	constructor(private it: Iterator) { }
+
+	next(): IteratorResult { return this.it.next(); }
+	map(fn: (t: T) => R): ChainableIterator { return new ChainableIterator(Iterator.map(this.it, fn)); }
+	filter(fn: (t: T) => boolean): ChainableIterator { return new ChainableIterator(Iterator.filter(this.it, fn)); }
 }
 
 export type ISequence = Iterator | T[];
 
-export function getSequenceIterator(arg: Iterator | T[]): Iterator {
+export function getSequenceIterator(arg: ISequence | undefined): Iterator {
 	if (Array.isArray(arg)) {
 		return Iterator.fromArray(arg);
+	} else if (!arg) {
+		return Iterator.empty();
 	} else {
 		return arg;
 	}
@@ -271,7 +286,7 @@ export interface INavigator extends INextIterator {
 
 export class MappedNavigator extends MappedIterator implements INavigator {
 
-	constructor(protected navigator: INavigator, fn: (item: T) => R) {
+	constructor(protected navigator: INavigator, fn: (item: T | null) => R) {
 		super(navigator, fn);
 	}
 
diff --git a/src/vs/base/common/json.ts b/src/vs/base/common/json.ts
index 9da1277bc8e6..a7a4cde64bee 100644
--- a/src/vs/base/common/json.ts
+++ b/src/vs/base/common/json.ts
@@ -137,6 +137,7 @@ export interface Location {
 export interface ParseOptions {
 	disallowComments?: boolean;
 	allowTrailingComma?: boolean;
+	allowEmptyContent?: boolean;
 }
 
 export namespace ParseOptions {
@@ -785,7 +786,7 @@ export function getLocation(text: string, position: number): Location {
 				if (position < offset) {
 					throw earlyReturnException;
 				}
-				setPreviousNode(value, offset, length, getLiteralNodeType(value));
+				setPreviousNode(value, offset, length, getNodeType(value));
 
 				if (position <= offset + length) {
 					throw earlyReturnException;
@@ -848,7 +849,7 @@ export function parse(text: string, errors: ParseError[] = [], options: ParseOpt
 	function onValue(value: any) {
 		if (Array.isArray(currentParent)) {
 			(currentParent).push(value);
-		} else if (currentProperty) {
+		} else if (currentProperty !== null) {
 			currentParent[currentProperty] = value;
 		}
 	}
@@ -927,7 +928,7 @@ export function parseTree(text: string, errors: ParseError[] = [], options: Pars
 			ensurePropertyComplete(offset + length);
 		},
 		onLiteralValue: (value: any, offset: number, length: number) => {
-			onValue({ type: getLiteralNodeType(value), offset, length, parent: currentParent, value });
+			onValue({ type: getNodeType(value), offset, length, parent: currentParent, value });
 			ensurePropertyComplete(offset + length);
 		},
 		onSeparator: (sep: string, offset: number, length: number) => {
@@ -1287,7 +1288,11 @@ export function visit(text: string, visitor: JSONVisitor, options: ParseOptions
 
 	scanNext();
 	if (_scanner.getToken() === SyntaxKind.EOF) {
-		return true;
+		if (options.allowEmptyContent) {
+			return true;
+		}
+		handleError(ParseErrorCode.ValueExpected, [], []);
+		return false;
 	}
 	if (!parseValue()) {
 		handleError(ParseErrorCode.ValueExpected, [], []);
@@ -1333,11 +1338,19 @@ export function stripComments(text: string, replaceCh?: string): string {
 	return parts.join('');
 }
 
-function getLiteralNodeType(value: any): NodeType {
+export function getNodeType(value: any): NodeType {
 	switch (typeof value) {
 		case 'boolean': return 'boolean';
 		case 'number': return 'number';
 		case 'string': return 'string';
+		case 'object': {
+			if (!value) {
+				return 'null';
+			} else if (Array.isArray(value)) {
+				return 'array';
+			}
+			return 'object';
+		}
 		default: return 'null';
 	}
 }
diff --git a/src/vs/base/common/jsonEdit.ts b/src/vs/base/common/jsonEdit.ts
index 1de95c55c087..a106d88dfc04 100644
--- a/src/vs/base/common/jsonEdit.ts
+++ b/src/vs/base/common/jsonEdit.ts
@@ -84,40 +84,36 @@ export function setProperty(text: string, originalPath: JSONPath, value: any, fo
 			return withFormatting(text, edit, formattingOptions);
 		}
 	} else if (parent.type === 'array' && typeof lastSegment === 'number' && Array.isArray(parent.children)) {
-		const insertIndex = lastSegment;
-		if (insertIndex === -1) {
+		if (value !== undefined) {
 			// Insert
 			const newProperty = `${JSON.stringify(value)}`;
 			let edit: Edit;
-			if (parent.children.length === 0) {
-				edit = { offset: parent.offset + 1, length: 0, content: newProperty };
+			if (parent.children.length === 0 || lastSegment === 0) {
+				edit = { offset: parent.offset + 1, length: 0, content: parent.children.length === 0 ? newProperty : newProperty + ',' };
 			} else {
-				const previous = parent.children[parent.children.length - 1];
+				const index = lastSegment === -1 || lastSegment > parent.children.length ? parent.children.length : lastSegment;
+				const previous = parent.children[index - 1];
 				edit = { offset: previous.offset + previous.length, length: 0, content: ',' + newProperty };
 			}
 			return withFormatting(text, edit, formattingOptions);
 		} else {
-			if (value === undefined && parent.children.length >= 0) {
-				//Removal
-				const removalIndex = lastSegment;
-				const toRemove = parent.children[removalIndex];
-				let edit: Edit;
-				if (parent.children.length === 1) {
-					// only item
-					edit = { offset: parent.offset + 1, length: parent.length - 2, content: '' };
-				} else if (parent.children.length - 1 === removalIndex) {
-					// last item
-					const previous = parent.children[removalIndex - 1];
-					const offset = previous.offset + previous.length;
-					const parentEndOffset = parent.offset + parent.length;
-					edit = { offset, length: parentEndOffset - 2 - offset, content: '' };
-				} else {
-					edit = { offset: toRemove.offset, length: parent.children[removalIndex + 1].offset - toRemove.offset, content: '' };
-				}
-				return withFormatting(text, edit, formattingOptions);
+			//Removal
+			const removalIndex = lastSegment;
+			const toRemove = parent.children[removalIndex];
+			let edit: Edit;
+			if (parent.children.length === 1) {
+				// only item
+				edit = { offset: parent.offset + 1, length: parent.length - 2, content: '' };
+			} else if (parent.children.length - 1 === removalIndex) {
+				// last item
+				const previous = parent.children[removalIndex - 1];
+				const offset = previous.offset + previous.length;
+				const parentEndOffset = parent.offset + parent.length;
+				edit = { offset, length: parentEndOffset - 2 - offset, content: '' };
 			} else {
-				throw new Error('Array modification not supported yet');
+				edit = { offset: toRemove.offset, length: parent.children[removalIndex + 1].offset - toRemove.offset, content: '' };
 			}
+			return withFormatting(text, edit, formattingOptions);
 		}
 	} else {
 		throw new Error(`Can not add ${typeof lastSegment !== 'number' ? 'index' : 'property'} to parent of type ${parent.type}`);
diff --git a/src/vs/base/common/jsonSchema.ts b/src/vs/base/common/jsonSchema.ts
index 005215ce1519..f91092e953b5 100644
--- a/src/vs/base/common/jsonSchema.ts
+++ b/src/vs/base/common/jsonSchema.ts
@@ -3,11 +3,13 @@
  *  Licensed under the Source EULA. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
 
+export type JSONSchemaType = 'string' | 'number' | 'integer' | 'boolean' | 'null' | 'array' | 'object';
+
 export interface IJSONSchema {
 	id?: string;
 	$id?: string;
 	$schema?: string;
-	type?: string | string[];
+	type?: JSONSchemaType | JSONSchemaType[];
 	title?: string;
 	default?: any;
 	definitions?: IJSONSchemaMap;
diff --git a/src/vs/base/common/lazy.ts b/src/vs/base/common/lazy.ts
index ad3d0a6c6c70..4ba28d52989e 100644
--- a/src/vs/base/common/lazy.ts
+++ b/src/vs/base/common/lazy.ts
@@ -54,6 +54,11 @@ export class Lazy {
 		return this._value!;
 	}
 
+	/**
+	 * Get the wrapped value without forcing evaluation.
+	 */
+	get rawValue(): T | undefined { return this._value; }
+
 	/**
 	 * Create a new lazy value that is the result of applying `f` to the wrapped value.
 	 *
diff --git a/src/vs/base/common/lifecycle.ts b/src/vs/base/common/lifecycle.ts
index e172e766e9e1..ec33e251e06d 100644
--- a/src/vs/base/common/lifecycle.ts
+++ b/src/vs/base/common/lifecycle.ts
@@ -206,43 +206,6 @@ export class MutableDisposable implements IDisposable {
 	}
 }
 
-/**
- * Wrapper class that stores a disposable that is not currently "owned" by anyone.
- *
- * Example use cases:
- *
- * - Express that a function/method will take ownership of a disposable parameter.
- * - Express that a function returns a disposable that the caller must explicitly take ownership of.
- */
-export class UnownedDisposable extends Disposable {
-	private _hasBeenAcquired = false;
-	private _value?: T;
-
-	public constructor(value: T) {
-		super();
-		this._value = value;
-	}
-
-	public acquire(): T {
-		if (this._hasBeenAcquired) {
-			throw new Error('This disposable has already been acquired');
-		}
-		this._hasBeenAcquired = true;
-		const value = this._value!;
-		this._value = undefined;
-		return value;
-	}
-
-	public dispose() {
-		super.dispose();
-		if (!this._hasBeenAcquired) {
-			this._hasBeenAcquired = true;
-			this._value!.dispose();
-			this._value = undefined;
-		}
-	}
-}
-
 export interface IReference extends IDisposable {
 	readonly object: T;
 }
diff --git a/src/vs/base/common/mime.ts b/src/vs/base/common/mime.ts
index db69800686ff..a86aee3a9717 100644
--- a/src/vs/base/common/mime.ts
+++ b/src/vs/base/common/mime.ts
@@ -262,7 +262,14 @@ export function suggestFilename(mode: string | undefined, prefix: string): strin
 		.filter(assoc => startsWith(assoc, '.'));
 
 	if (extensionsWithDotFirst.length > 0) {
-		return prefix + extensionsWithDotFirst[0];
+		const candidateExtension = extensionsWithDotFirst[0];
+		if (endsWith(prefix, candidateExtension)) {
+			// do not add the prefix if it already exists
+			// https://github.com/microsoft/vscode/issues/83603
+			return prefix;
+		}
+
+		return prefix + candidateExtension;
 	}
 
 	return extensions[0] || prefix;
diff --git a/src/vs/base/common/path.ts b/src/vs/base/common/path.ts
index ad915811d6e7..029422cedc82 100644
--- a/src/vs/base/common/path.ts
+++ b/src/vs/base/common/path.ts
@@ -69,11 +69,11 @@ function validateString(value: string, name: string) {
 	}
 }
 
-function isPathSeparator(code: number) {
+function isPathSeparator(code: number | undefined) {
 	return code === CHAR_FORWARD_SLASH || code === CHAR_BACKWARD_SLASH;
 }
 
-function isPosixPathSeparator(code: number) {
+function isPosixPathSeparator(code: number | undefined) {
 	return code === CHAR_FORWARD_SLASH;
 }
 
diff --git a/src/vs/base/common/platform.ts b/src/vs/base/common/platform.ts
index acb0b0d6e768..d94f1b6d4c43 100644
--- a/src/vs/base/common/platform.ts
+++ b/src/vs/base/common/platform.ts
@@ -10,6 +10,7 @@ let _isMacintosh = false;
 let _isLinux = false;
 let _isNative = false;
 let _isWeb = false;
+let _isIOS = false;
 let _locale: string | undefined = undefined;
 let _language: string = LANGUAGE_DEFAULT;
 let _translationsConfigFile: string | undefined = undefined;
@@ -41,6 +42,7 @@ declare const global: any;
 interface INavigator {
 	userAgent: string;
 	language: string;
+	maxTouchPoints?: number;
 }
 declare const navigator: INavigator;
 declare const self: any;
@@ -52,6 +54,7 @@ if (typeof navigator === 'object' && !isElectronRenderer) {
 	_userAgent = navigator.userAgent;
 	_isWindows = _userAgent.indexOf('Windows') >= 0;
 	_isMacintosh = _userAgent.indexOf('Macintosh') >= 0;
+	_isIOS = _userAgent.indexOf('Macintosh') >= 0 && !!navigator.maxTouchPoints && navigator.maxTouchPoints > 0;
 	_isLinux = _userAgent.indexOf('Linux') >= 0;
 	_isWeb = true;
 	_locale = navigator.language;
@@ -106,6 +109,7 @@ export const isMacintosh = _isMacintosh;
 export const isLinux = _isLinux;
 export const isNative = _isNative;
 export const isWeb = _isWeb;
+export const isIOS = _isIOS;
 export const platform = _platform;
 export const userAgent = _userAgent;
 
@@ -189,7 +193,7 @@ export const setImmediate: ISetImmediate = (function defineSetImmediate() {
 				id: myId,
 				callback: callback
 			});
-			globals.postMessage({ vscodeSetImmediateId: myId });
+			globals.postMessage({ vscodeSetImmediateId: myId }, '*');
 		};
 	}
 	if (typeof process !== 'undefined' && typeof process.nextTick === 'function') {
diff --git a/src/vs/base/common/stream.ts b/src/vs/base/common/stream.ts
new file mode 100644
index 000000000000..0b7884b56e4b
--- /dev/null
+++ b/src/vs/base/common/stream.ts
@@ -0,0 +1,487 @@
+/*---------------------------------------------------------------------------------------------
+ *  Copyright (c) Microsoft Corporation. All rights reserved.
+ *  Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+/**
+ * The payload that flows in readable stream events.
+ */
+export type ReadableStreamEventPayload = T | Error | 'end';
+
+export interface ReadableStreamEvents {
+
+	/**
+	 * The 'data' event is emitted whenever the stream is
+	 * relinquishing ownership of a chunk of data to a consumer.
+	 */
+	on(event: 'data', callback: (data: T) => void): void;
+
+	/**
+	 * Emitted when any error occurs.
+	 */
+	on(event: 'error', callback: (err: Error) => void): void;
+
+	/**
+	 * The 'end' event is emitted when there is no more data
+	 * to be consumed from the stream. The 'end' event will
+	 * not be emitted unless the data is completely consumed.
+	 */
+	on(event: 'end', callback: () => void): void;
+}
+
+/**
+ * A interface that emulates the API shape of a node.js readable
+ * stream for use in desktop and web environments.
+ */
+export interface ReadableStream extends ReadableStreamEvents {
+
+	/**
+	 * Stops emitting any events until resume() is called.
+	 */
+	pause(): void;
+
+	/**
+	 * Starts emitting events again after pause() was called.
+	 */
+	resume(): void;
+
+	/**
+	 * Destroys the stream and stops emitting any event.
+	 */
+	destroy(): void;
+}
+
+/**
+ * A interface that emulates the API shape of a node.js readable
+ * for use in desktop and web environments.
+ */
+export interface Readable {
+
+	/**
+	 * Read data from the underlying source. Will return
+	 * null to indicate that no more data can be read.
+	 */
+	read(): T | null;
+}
+
+/**
+ * A interface that emulates the API shape of a node.js writeable
+ * stream for use in desktop and web environments.
+ */
+export interface WriteableStream extends ReadableStream {
+
+	/**
+	 * Writing data to the stream will trigger the on('data')
+	 * event listener if the stream is flowing and buffer the
+	 * data otherwise until the stream is flowing.
+	 */
+	write(data: T): void;
+
+	/**
+	 * Signals an error to the consumer of the stream via the
+	 * on('error') handler if the stream is flowing.
+	 */
+	error(error: Error): void;
+
+	/**
+	 * Signals the end of the stream to the consumer. If the
+	 * result is not an error, will trigger the on('data') event
+	 * listener if the stream is flowing and buffer the data
+	 * otherwise until the stream is flowing.
+	 *
+	 * In case of an error, the on('error') event will be used
+	 * if the stream is flowing.
+	 */
+	end(result?: T | Error): void;
+}
+
+export function isReadableStream(obj: any): obj is ReadableStream {
+	const candidate: ReadableStream = obj;
+
+	return candidate && [candidate.on, candidate.pause, candidate.resume, candidate.destroy].every(fn => typeof fn === 'function');
+}
+
+export interface IReducer {
+	(data: T[]): T;
+}
+
+export interface IDataTransformer {
+	(data: Original): Transformed;
+}
+
+export interface IErrorTransformer {
+	(error: Error): Error;
+}
+
+export interface ITransformer {
+	data: IDataTransformer;
+	error?: IErrorTransformer;
+}
+
+export function newWriteableStream(reducer: IReducer): WriteableStream {
+	return new WriteableStreamImpl(reducer);
+}
+
+class WriteableStreamImpl implements WriteableStream {
+
+	private readonly state = {
+		flowing: false,
+		ended: false,
+		destroyed: false
+	};
+
+	private readonly buffer = {
+		data: [] as T[],
+		error: [] as Error[]
+	};
+
+	private readonly listeners = {
+		data: [] as { (data: T): void }[],
+		error: [] as { (error: Error): void }[],
+		end: [] as { (): void }[]
+	};
+
+	constructor(private reducer: IReducer) { }
+
+	pause(): void {
+		if (this.state.destroyed) {
+			return;
+		}
+
+		this.state.flowing = false;
+	}
+
+	resume(): void {
+		if (this.state.destroyed) {
+			return;
+		}
+
+		if (!this.state.flowing) {
+			this.state.flowing = true;
+
+			// emit buffered events
+			this.flowData();
+			this.flowErrors();
+			this.flowEnd();
+		}
+	}
+
+	write(data: T): void {
+		if (this.state.destroyed) {
+			return;
+		}
+
+		// flowing: directly send the data to listeners
+		if (this.state.flowing) {
+			this.listeners.data.forEach(listener => listener(data));
+		}
+
+		// not yet flowing: buffer data until flowing
+		else {
+			this.buffer.data.push(data);
+		}
+	}
+
+	error(error: Error): void {
+		if (this.state.destroyed) {
+			return;
+		}
+
+		// flowing: directly send the error to listeners
+		if (this.state.flowing) {
+			this.listeners.error.forEach(listener => listener(error));
+		}
+
+		// not yet flowing: buffer errors until flowing
+		else {
+			this.buffer.error.push(error);
+		}
+	}
+
+	end(result?: T | Error): void {
+		if (this.state.destroyed) {
+			return;
+		}
+
+		// end with data or error if provided
+		if (result instanceof Error) {
+			this.error(result);
+		} else if (result) {
+			this.write(result);
+		}
+
+		// flowing: send end event to listeners
+		if (this.state.flowing) {
+			this.listeners.end.forEach(listener => listener());
+
+			this.destroy();
+		}
+
+		// not yet flowing: remember state
+		else {
+			this.state.ended = true;
+		}
+	}
+
+	on(event: 'data', callback: (data: T) => void): void;
+	on(event: 'error', callback: (err: Error) => void): void;
+	on(event: 'end', callback: () => void): void;
+	on(event: 'data' | 'error' | 'end', callback: (arg0?: any) => void): void {
+		if (this.state.destroyed) {
+			return;
+		}
+
+		switch (event) {
+			case 'data':
+				this.listeners.data.push(callback);
+
+				// switch into flowing mode as soon as the first 'data'
+				// listener is added and we are not yet in flowing mode
+				this.resume();
+
+				break;
+
+			case 'end':
+				this.listeners.end.push(callback);
+
+				// emit 'end' event directly if we are flowing
+				// and the end has already been reached
+				//
+				// finish() when it went through
+				if (this.state.flowing && this.flowEnd()) {
+					this.destroy();
+				}
+
+				break;
+
+			case 'error':
+				this.listeners.error.push(callback);
+
+				// emit buffered 'error' events unless done already
+				// now that we know that we have at least one listener
+				if (this.state.flowing) {
+					this.flowErrors();
+				}
+
+				break;
+		}
+	}
+
+	private flowData(): void {
+		if (this.buffer.data.length > 0) {
+			const fullDataBuffer = this.reducer(this.buffer.data);
+
+			this.listeners.data.forEach(listener => listener(fullDataBuffer));
+
+			this.buffer.data.length = 0;
+		}
+	}
+
+	private flowErrors(): void {
+		if (this.listeners.error.length > 0) {
+			for (const error of this.buffer.error) {
+				this.listeners.error.forEach(listener => listener(error));
+			}
+
+			this.buffer.error.length = 0;
+		}
+	}
+
+	private flowEnd(): boolean {
+		if (this.state.ended) {
+			this.listeners.end.forEach(listener => listener());
+
+			return this.listeners.end.length > 0;
+		}
+
+		return false;
+	}
+
+	destroy(): void {
+		if (!this.state.destroyed) {
+			this.state.destroyed = true;
+			this.state.ended = true;
+
+			this.buffer.data.length = 0;
+			this.buffer.error.length = 0;
+
+			this.listeners.data.length = 0;
+			this.listeners.error.length = 0;
+			this.listeners.end.length = 0;
+		}
+	}
+}
+
+/**
+ * Helper to fully read a T readable into a T.
+ */
+export function consumeReadable(readable: Readable, reducer: IReducer): T {
+	const chunks: T[] = [];
+
+	let chunk: T | null;
+	while ((chunk = readable.read()) !== null) {
+		chunks.push(chunk);
+	}
+
+	return reducer(chunks);
+}
+
+/**
+ * Helper to read a T readable up to a maximum of chunks. If the limit is
+ * reached, will return a readable instead to ensure all data can still
+ * be read.
+ */
+export function consumeReadableWithLimit(readable: Readable, reducer: IReducer, maxChunks: number): T | Readable {
+	const chunks: T[] = [];
+
+	let chunk: T | null | undefined = undefined;
+	while ((chunk = readable.read()) !== null && chunks.length < maxChunks) {
+		chunks.push(chunk);
+	}
+
+	// If the last chunk is null, it means we reached the end of
+	// the readable and return all the data at once
+	if (chunk === null && chunks.length > 0) {
+		return reducer(chunks);
+	}
+
+	// Otherwise, we still have a chunk, it means we reached the maxChunks
+	// value and as such we return a new Readable that first returns
+	// the existing read chunks and then continues with reading from
+	// the underlying readable.
+	return {
+		read: () => {
+
+			// First consume chunks from our array
+			if (chunks.length > 0) {
+				return chunks.shift()!;
+			}
+
+			// Then ensure to return our last read chunk
+			if (typeof chunk !== 'undefined') {
+				const lastReadChunk = chunk;
+
+				// explicitly use undefined here to indicate that we consumed
+				// the chunk, which could have either been null or valued.
+				chunk = undefined;
+
+				return lastReadChunk;
+			}
+
+			// Finally delegate back to the Readable
+			return readable.read();
+		}
+	};
+}
+
+/**
+ * Helper to fully read a T stream into a T.
+ */
+export function consumeStream(stream: ReadableStream, reducer: IReducer): Promise {
+	return new Promise((resolve, reject) => {
+		const chunks: T[] = [];
+
+		stream.on('data', data => chunks.push(data));
+		stream.on('error', error => reject(error));
+		stream.on('end', () => resolve(reducer(chunks)));
+	});
+}
+
+/**
+ * Helper to read a T stream up to a maximum of chunks. If the limit is
+ * reached, will return a stream instead to ensure all data can still
+ * be read.
+ */
+export function consumeStreamWithLimit(stream: ReadableStream, reducer: IReducer, maxChunks: number): Promise> {
+	return new Promise((resolve, reject) => {
+		const chunks: T[] = [];
+
+		let wrapperStream: WriteableStream | undefined = undefined;
+
+		stream.on('data', data => {
+
+			// If we reach maxChunks, we start to return a stream
+			// and make sure that any data we have already read
+			// is in it as well
+			if (!wrapperStream && chunks.length === maxChunks) {
+				wrapperStream = newWriteableStream(reducer);
+				while (chunks.length) {
+					wrapperStream.write(chunks.shift()!);
+				}
+
+				wrapperStream.write(data);
+
+				return resolve(wrapperStream);
+			}
+
+			if (wrapperStream) {
+				wrapperStream.write(data);
+			} else {
+				chunks.push(data);
+			}
+		});
+
+		stream.on('error', error => {
+			if (wrapperStream) {
+				wrapperStream.error(error);
+			} else {
+				return reject(error);
+			}
+		});
+
+		stream.on('end', () => {
+			if (wrapperStream) {
+				while (chunks.length) {
+					wrapperStream.write(chunks.shift()!);
+				}
+
+				wrapperStream.end();
+			} else {
+				return resolve(reducer(chunks));
+			}
+		});
+	});
+}
+
+/**
+ * Helper to create a readable stream from an existing T.
+ */
+export function toStream(t: T, reducer: IReducer): ReadableStream {
+	const stream = newWriteableStream(reducer);
+
+	stream.end(t);
+
+	return stream;
+}
+
+/**
+ * Helper to convert a T into a Readable.
+ */
+export function toReadable(t: T): Readable {
+	let consumed = false;
+
+	return {
+		read: () => {
+			if (consumed) {
+				return null;
+			}
+
+			consumed = true;
+
+			return t;
+		}
+	};
+}
+
+/**
+ * Helper to transform a readable stream into another stream.
+ */
+export function transform(stream: ReadableStreamEvents, transformer: ITransformer, reducer: IReducer): ReadableStream {
+	const target = newWriteableStream(reducer);
+
+	stream.on('data', data => target.write(transformer.data(data)));
+	stream.on('end', () => target.end());
+	stream.on('error', error => target.error(transformer.error ? transformer.error(error) : error));
+
+	return target;
+}
diff --git a/src/vs/base/common/strings.ts b/src/vs/base/common/strings.ts
index dad6301dc081..d7de8ec9e49d 100644
--- a/src/vs/base/common/strings.ts
+++ b/src/vs/base/common/strings.ts
@@ -517,60 +517,80 @@ function getPrevCodePoint(str: string, offset: number): number {
 }
 
 export function nextCharLength(str: string, offset: number): number {
+	const graphemeBreakTree = GraphemeBreakTree.getInstance();
 	const initialOffset = offset;
 	const len = str.length;
 
-	let codePoint = getNextCodePoint(str, len, offset);
-	offset += (codePoint >= Constants.UNICODE_SUPPLEMENTARY_PLANE_BEGIN ? 2 : 1);
+	const initialCodePoint = getNextCodePoint(str, len, offset);
+	offset += (initialCodePoint >= Constants.UNICODE_SUPPLEMENTARY_PLANE_BEGIN ? 2 : 1);
 
+	let graphemeBreakType = graphemeBreakTree.getGraphemeBreakType(initialCodePoint);
 	while (offset < len) {
-		codePoint = getNextCodePoint(str, len, offset);
-		if (!isUnicodeMark(codePoint)) {
+		const nextCodePoint = getNextCodePoint(str, len, offset);
+		const nextGraphemeBreakType = graphemeBreakTree.getGraphemeBreakType(nextCodePoint);
+		if (breakBetweenGraphemeBreakType(graphemeBreakType, nextGraphemeBreakType)) {
 			break;
 		}
-		offset += (codePoint >= Constants.UNICODE_SUPPLEMENTARY_PLANE_BEGIN ? 2 : 1);
+		offset += (nextCodePoint >= Constants.UNICODE_SUPPLEMENTARY_PLANE_BEGIN ? 2 : 1);
+		graphemeBreakType = nextGraphemeBreakType;
 	}
 
 	return (offset - initialOffset);
 }
 
 export function prevCharLength(str: string, offset: number): number {
+	const graphemeBreakTree = GraphemeBreakTree.getInstance();
 	const initialOffset = offset;
 
-	let codePoint = getPrevCodePoint(str, offset);
-	offset -= (codePoint >= Constants.UNICODE_SUPPLEMENTARY_PLANE_BEGIN ? 2 : 1);
+	const initialCodePoint = getPrevCodePoint(str, offset);
+	offset -= (initialCodePoint >= Constants.UNICODE_SUPPLEMENTARY_PLANE_BEGIN ? 2 : 1);
 
-	while (offset > 0 && isUnicodeMark(codePoint)) {
-		codePoint = getPrevCodePoint(str, offset);
-		offset -= (codePoint >= Constants.UNICODE_SUPPLEMENTARY_PLANE_BEGIN ? 2 : 1);
+	let graphemeBreakType = graphemeBreakTree.getGraphemeBreakType(initialCodePoint);
+	while (offset > 0) {
+		const prevCodePoint = getPrevCodePoint(str, offset);
+		const prevGraphemeBreakType = graphemeBreakTree.getGraphemeBreakType(prevCodePoint);
+		if (breakBetweenGraphemeBreakType(prevGraphemeBreakType, graphemeBreakType)) {
+			break;
+		}
+		offset -= (prevCodePoint >= Constants.UNICODE_SUPPLEMENTARY_PLANE_BEGIN ? 2 : 1);
+		graphemeBreakType = prevGraphemeBreakType;
 	}
 
 	return (initialOffset - offset);
 }
 
 function _getCharContainingOffset(str: string, offset: number): [number, number] {
+	const graphemeBreakTree = GraphemeBreakTree.getInstance();
 	const len = str.length;
 	const initialOffset = offset;
 	const initialCodePoint = getNextCodePoint(str, len, offset);
+	const initialGraphemeBreakType = graphemeBreakTree.getGraphemeBreakType(initialCodePoint);
 	offset += (initialCodePoint >= Constants.UNICODE_SUPPLEMENTARY_PLANE_BEGIN ? 2 : 1);
 
 	// extend to the right
+	let graphemeBreakType = initialGraphemeBreakType;
 	while (offset < len) {
 		const nextCodePoint = getNextCodePoint(str, len, offset);
-		if (!isUnicodeMark(nextCodePoint)) {
+		const nextGraphemeBreakType = graphemeBreakTree.getGraphemeBreakType(nextCodePoint);
+		if (breakBetweenGraphemeBreakType(graphemeBreakType, nextGraphemeBreakType)) {
 			break;
 		}
 		offset += (nextCodePoint >= Constants.UNICODE_SUPPLEMENTARY_PLANE_BEGIN ? 2 : 1);
+		graphemeBreakType = nextGraphemeBreakType;
 	}
 	const endOffset = offset;
 
 	// extend to the left
 	offset = initialOffset;
-	let codePoint = initialCodePoint;
-
-	while (offset > 0 && isUnicodeMark(codePoint)) {
-		codePoint = getPrevCodePoint(str, offset);
-		offset -= (codePoint >= Constants.UNICODE_SUPPLEMENTARY_PLANE_BEGIN ? 2 : 1);
+	graphemeBreakType = initialGraphemeBreakType;
+	while (offset > 0) {
+		const prevCodePoint = getPrevCodePoint(str, offset);
+		const prevGraphemeBreakType = graphemeBreakTree.getGraphemeBreakType(prevCodePoint);
+		if (breakBetweenGraphemeBreakType(prevGraphemeBreakType, graphemeBreakType)) {
+			break;
+		}
+		offset -= (prevCodePoint >= Constants.UNICODE_SUPPLEMENTARY_PLANE_BEGIN ? 2 : 1);
+		graphemeBreakType = prevGraphemeBreakType;
 	}
 
 	return [offset, endOffset];
@@ -583,93 +603,117 @@ export function getCharContainingOffset(str: string, offset: number): [number, n
 	return _getCharContainingOffset(str, offset);
 }
 
-export function isUnicodeMark(codePoint: number): boolean {
-	return MarkClassifier.getInstance().isUnicodeMark(codePoint);
-}
-
-class MarkClassifier {
-
-	private static _INSTANCE: MarkClassifier | null = null;
+/**
+ * A manual encoding of `str` to UTF8.
+ * Use only in environments which do not offer native conversion methods!
+ */
+export function encodeUTF8(str: string): Uint8Array {
+	const strLen = str.length;
+
+	// See https://en.wikipedia.org/wiki/UTF-8
+
+	// first loop to establish needed buffer size
+	let neededSize = 0;
+	let strOffset = 0;
+	while (strOffset < strLen) {
+		const codePoint = getNextCodePoint(str, strLen, strOffset);
+		strOffset += (codePoint >= Constants.UNICODE_SUPPLEMENTARY_PLANE_BEGIN ? 2 : 1);
+
+		if (codePoint < 0x0080) {
+			neededSize += 1;
+		} else if (codePoint < 0x0800) {
+			neededSize += 2;
+		} else if (codePoint < 0x10000) {
+			neededSize += 3;
+		} else {
+			neededSize += 4;
+		}
+	}
 
-	public static getInstance(): MarkClassifier {
-		if (!MarkClassifier._INSTANCE) {
-			MarkClassifier._INSTANCE = new MarkClassifier();
+	// second loop to actually encode
+	const arr = new Uint8Array(neededSize);
+	strOffset = 0;
+	let arrOffset = 0;
+	while (strOffset < strLen) {
+		const codePoint = getNextCodePoint(str, strLen, strOffset);
+		strOffset += (codePoint >= Constants.UNICODE_SUPPLEMENTARY_PLANE_BEGIN ? 2 : 1);
+
+		if (codePoint < 0x0080) {
+			arr[arrOffset++] = codePoint;
+		} else if (codePoint < 0x0800) {
+			arr[arrOffset++] = 0b11000000 | ((codePoint & 0b00000000000000000000011111000000) >>> 6);
+			arr[arrOffset++] = 0b10000000 | ((codePoint & 0b00000000000000000000000000111111) >>> 0);
+		} else if (codePoint < 0x10000) {
+			arr[arrOffset++] = 0b11100000 | ((codePoint & 0b00000000000000001111000000000000) >>> 12);
+			arr[arrOffset++] = 0b10000000 | ((codePoint & 0b00000000000000000000111111000000) >>> 6);
+			arr[arrOffset++] = 0b10000000 | ((codePoint & 0b00000000000000000000000000111111) >>> 0);
+		} else {
+			arr[arrOffset++] = 0b11110000 | ((codePoint & 0b00000000000111000000000000000000) >>> 18);
+			arr[arrOffset++] = 0b10000000 | ((codePoint & 0b00000000000000111111000000000000) >>> 12);
+			arr[arrOffset++] = 0b10000000 | ((codePoint & 0b00000000000000000000111111000000) >>> 6);
+			arr[arrOffset++] = 0b10000000 | ((codePoint & 0b00000000000000000000000000111111) >>> 0);
 		}
-		return MarkClassifier._INSTANCE;
 	}
 
-	private arr: Uint8Array;
+	return arr;
+}
 
-	constructor() {
-		// generated using https://github.com/alexandrudima/unicode-utils/blob/master/generate-mark-test.js
-		const ranges = [
-			0x0300, 0x036F, 0x0483, 0x0489, 0x0591, 0x05BD, 0x05BF, 0x05BF, 0x05C1, 0x05C2, 0x05C4, 0x05C5,
-			0x05C7, 0x05C7, 0x0610, 0x061A, 0x064B, 0x065F, 0x0670, 0x0670, 0x06D6, 0x06DC, 0x06DF, 0x06E4,
-			0x06E7, 0x06E8, 0x06EA, 0x06ED, 0x0711, 0x0711, 0x0730, 0x074A, 0x07A6, 0x07B0, 0x07EB, 0x07F3,
-			0x07FD, 0x07FD, 0x0816, 0x0819, 0x081B, 0x0823, 0x0825, 0x0827, 0x0829, 0x082D, 0x0859, 0x085B,
-			0x08D3, 0x08E1, 0x08E3, 0x0903, 0x093A, 0x093C, 0x093E, 0x094F, 0x0951, 0x0957, 0x0962, 0x0963,
-			0x0981, 0x0983, 0x09BC, 0x09BC, 0x09BE, 0x09CD, 0x09D7, 0x09D7, 0x09E2, 0x09E3, 0x09FE, 0x0A03,
-			0x0A3C, 0x0A51, 0x0A70, 0x0A71, 0x0A75, 0x0A75, 0x0A81, 0x0A83, 0x0ABC, 0x0ABC, 0x0ABE, 0x0ACD,
-			0x0AE2, 0x0AE3, 0x0AFA, 0x0B03, 0x0B3C, 0x0B3C, 0x0B3E, 0x0B57, 0x0B62, 0x0B63, 0x0B82, 0x0B82,
-			0x0BBE, 0x0BCD, 0x0BD7, 0x0BD7, 0x0C00, 0x0C04, 0x0C3E, 0x0C56, 0x0C62, 0x0C63, 0x0C81, 0x0C83,
-			0x0CBC, 0x0CBC, 0x0CBE, 0x0CD6, 0x0CE2, 0x0CE3, 0x0D00, 0x0D03, 0x0D3B, 0x0D3C, 0x0D3E, 0x0D4D,
-			0x0D57, 0x0D57, 0x0D62, 0x0D63, 0x0D81, 0x0D83, 0x0DCA, 0x0DDF, 0x0DF2, 0x0DF3, 0x0E31, 0x0E31,
-			0x0E34, 0x0E3A, 0x0E47, 0x0E4E, 0x0EB1, 0x0EB1, 0x0EB4, 0x0EBC, 0x0EC8, 0x0ECD, 0x0F18, 0x0F19,
-			0x0F35, 0x0F35, 0x0F37, 0x0F37, 0x0F39, 0x0F39, 0x0F3E, 0x0F3F, 0x0F71, 0x0F84, 0x0F86, 0x0F87,
-			0x0F8D, 0x0FBC, 0x0FC6, 0x0FC6, 0x102B, 0x103E, 0x1056, 0x1059, 0x105E, 0x1060, 0x1062, 0x1064,
-			0x1067, 0x106D, 0x1071, 0x1074, 0x1082, 0x108D, 0x108F, 0x108F, 0x109A, 0x109D, 0x135D, 0x135F,
-			0x1712, 0x1714, 0x1732, 0x1734, 0x1752, 0x1753, 0x1772, 0x1773, 0x17B4, 0x17D3, 0x17DD, 0x17DD,
-			0x180B, 0x180D, 0x1885, 0x1886, 0x18A9, 0x18A9, 0x1920, 0x193B, 0x1A17, 0x1A1B, 0x1A55, 0x1A7F,
-			0x1AB0, 0x1B04, 0x1B34, 0x1B44, 0x1B6B, 0x1B73, 0x1B80, 0x1B82, 0x1BA1, 0x1BAD, 0x1BE6, 0x1BF3,
-			0x1C24, 0x1C37, 0x1CD0, 0x1CD2, 0x1CD4, 0x1CE8, 0x1CED, 0x1CED, 0x1CF4, 0x1CF4, 0x1CF7, 0x1CF9,
-			0x1DC0, 0x1DFF, 0x20D0, 0x20F0, 0x2CEF, 0x2CF1, 0x2D7F, 0x2D7F, 0x2DE0, 0x2DFF, 0x302A, 0x302F,
-			0x3099, 0x309A, 0xA66F, 0xA672, 0xA674, 0xA67D, 0xA69E, 0xA69F, 0xA6F0, 0xA6F1, 0xA802, 0xA802,
-			0xA806, 0xA806, 0xA80B, 0xA80B, 0xA823, 0xA827, 0xA82C, 0xA82C, 0xA880, 0xA881, 0xA8B4, 0xA8C5,
-			0xA8E0, 0xA8F1, 0xA8FF, 0xA8FF, 0xA926, 0xA92D, 0xA947, 0xA953, 0xA980, 0xA983, 0xA9B3, 0xA9C0,
-			0xA9E5, 0xA9E5, 0xAA29, 0xAA36, 0xAA43, 0xAA43, 0xAA4C, 0xAA4D, 0xAA7B, 0xAA7D, 0xAAB0, 0xAAB0,
-			0xAAB2, 0xAAB4, 0xAAB7, 0xAAB8, 0xAABE, 0xAABF, 0xAAC1, 0xAAC1, 0xAAEB, 0xAAEF, 0xAAF5, 0xAAF6,
-			0xABE3, 0xABEA, 0xABEC, 0xABED, 0xFB1E, 0xFB1E, 0xFE00, 0xFE0F, 0xFE20, 0xFE2F, 0x101FD, 0x101FD,
-			0x102E0, 0x102E0, 0x10376, 0x1037A, 0x10A01, 0x10A0F, 0x10A38, 0x10A3F, 0x10AE5, 0x10AE6, 0x10D24, 0x10D27,
-			0x10EAB, 0x10EAC, 0x10F46, 0x10F50, 0x11000, 0x11002, 0x11038, 0x11046, 0x1107F, 0x11082, 0x110B0, 0x110BA,
-			0x11100, 0x11102, 0x11127, 0x11134, 0x11145, 0x11146, 0x11173, 0x11173, 0x11180, 0x11182, 0x111B3, 0x111C0,
-			0x111C9, 0x111CC, 0x111CE, 0x111CF, 0x1122C, 0x11237, 0x1123E, 0x1123E, 0x112DF, 0x112EA, 0x11300, 0x11303,
-			0x1133B, 0x1133C, 0x1133E, 0x1134D, 0x11357, 0x11357, 0x11362, 0x11374, 0x11435, 0x11446, 0x1145E, 0x1145E,
-			0x114B0, 0x114C3, 0x115AF, 0x115C0, 0x115DC, 0x115DD, 0x11630, 0x11640, 0x116AB, 0x116B7, 0x1171D, 0x1172B,
-			0x1182C, 0x1183A, 0x11930, 0x1193E, 0x11940, 0x11940, 0x11942, 0x11943, 0x119D1, 0x119E0, 0x119E4, 0x119E4,
-			0x11A01, 0x11A0A, 0x11A33, 0x11A39, 0x11A3B, 0x11A3E, 0x11A47, 0x11A47, 0x11A51, 0x11A5B, 0x11A8A, 0x11A99,
-			0x11C2F, 0x11C3F, 0x11C92, 0x11CB6, 0x11D31, 0x11D45, 0x11D47, 0x11D47, 0x11D8A, 0x11D97, 0x11EF3, 0x11EF6,
-			0x16AF0, 0x16AF4, 0x16B30, 0x16B36, 0x16F4F, 0x16F4F, 0x16F51, 0x16F92, 0x16FE4, 0x16FF1, 0x1BC9D, 0x1BC9E,
-			0x1D165, 0x1D169, 0x1D16D, 0x1D172, 0x1D17B, 0x1D182, 0x1D185, 0x1D18B, 0x1D1AA, 0x1D1AD, 0x1D242, 0x1D244,
-			0x1DA00, 0x1DA36, 0x1DA3B, 0x1DA6C, 0x1DA75, 0x1DA75, 0x1DA84, 0x1DA84, 0x1DA9B, 0x1E02A, 0x1E130, 0x1E136,
-			0x1E2EC, 0x1E2EF, 0x1E8D0, 0x1E8D6, 0x1E944, 0x1E94A, 0xE0100, 0xE01EF
-		];
-
-		const maxCodePoint = ranges[ranges.length - 1];
-		const arrLen = Math.ceil(maxCodePoint / 8);
-		const arr = new Uint8Array(arrLen);
-
-		for (let i = 0, len = ranges.length / 2; i < len; i++) {
-			const from = ranges[2 * i];
-			const to = ranges[2 * i + 1];
-
-			for (let j = from; j <= to; j++) {
-				const div8 = j >>> 3;
-				const mod8 = j & 7;
-				arr[div8] = arr[div8] | (1 << mod8);
-			}
-		}
+/**
+ * A manual decoding of a UTF8 string.
+ * Use only in environments which do not offer native conversion methods!
+ */
+export function decodeUTF8(buffer: Uint8Array): string {
+	// https://en.wikipedia.org/wiki/UTF-8
 
-		this.arr = arr;
-	}
+	const len = buffer.byteLength;
+	const result: string[] = [];
+	let offset = 0;
+	while (offset < len) {
+		const v0 = buffer[offset];
+		let codePoint: number;
+		if (v0 >= 0b11110000 && offset + 3 < len) {
+			// 4 bytes
+			codePoint = (
+				(((buffer[offset++] & 0b00000111) << 18) >>> 0)
+				| (((buffer[offset++] & 0b00111111) << 12) >>> 0)
+				| (((buffer[offset++] & 0b00111111) << 6) >>> 0)
+				| (((buffer[offset++] & 0b00111111) << 0) >>> 0)
+			);
+		} else if (v0 >= 0b11100000 && offset + 2 < len) {
+			// 3 bytes
+			codePoint = (
+				(((buffer[offset++] & 0b00001111) << 12) >>> 0)
+				| (((buffer[offset++] & 0b00111111) << 6) >>> 0)
+				| (((buffer[offset++] & 0b00111111) << 0) >>> 0)
+			);
+		} else if (v0 >= 0b11000000 && offset + 1 < len) {
+			// 2 bytes
+			codePoint = (
+				(((buffer[offset++] & 0b00011111) << 6) >>> 0)
+				| (((buffer[offset++] & 0b00111111) << 0) >>> 0)
+			);
+		} else {
+			// 1 byte
+			codePoint = buffer[offset++];
+		}
 
-	public isUnicodeMark(codePoint: number): boolean {
-		const div8 = codePoint >>> 3;
-		const mod8 = codePoint & 7;
-		if (div8 >= this.arr.length) {
-			return false;
+		if ((codePoint >= 0 && codePoint <= 0xD7FF) || (codePoint >= 0xE000 && codePoint <= 0xFFFF)) {
+			// Basic Multilingual Plane
+			result.push(String.fromCharCode(codePoint));
+		} else if (codePoint >= 0x010000 && codePoint <= 0x10FFFF) {
+			// Supplementary Planes
+			const uPrime = codePoint - 0x10000;
+			const w1 = 0xD800 + ((uPrime & 0b11111111110000000000) >>> 10);
+			const w2 = 0xDC00 + ((uPrime & 0b00000000001111111111) >>> 0);
+			result.push(String.fromCharCode(w1));
+			result.push(String.fromCharCode(w2));
+		} else {
+			// illegal code point
+			result.push(String.fromCharCode(0xFFFD));
 		}
-		return (this.arr[div8] & (1 << mod8)) ? true : false;
 	}
+
+	return result.join('');
 }
 
 /**
@@ -925,3 +969,167 @@ export function singleLetterHash(n: number): string {
 
 	return String.fromCharCode(CharCode.A + n - LETTERS_CNT);
 }
+
+//#region Unicode Grapheme Break
+
+export function getGraphemeBreakType(codePoint: number): GraphemeBreakType {
+	const graphemeBreakTree = GraphemeBreakTree.getInstance();
+	return graphemeBreakTree.getGraphemeBreakType(codePoint);
+}
+
+export function breakBetweenGraphemeBreakType(breakTypeA: GraphemeBreakType, breakTypeB: GraphemeBreakType): boolean {
+	// http://www.unicode.org/reports/tr29/#Grapheme_Cluster_Boundary_Rules
+
+	// !!! Let's make the common case a bit faster
+	if (breakTypeA === GraphemeBreakType.Other) {
+		// see https://www.unicode.org/Public/13.0.0/ucd/auxiliary/GraphemeBreakTest-13.0.0d10.html#table
+		return (breakTypeB !== GraphemeBreakType.Extend && breakTypeB !== GraphemeBreakType.SpacingMark);
+	}
+
+	// Do not break between a CR and LF. Otherwise, break before and after controls.
+	// GB3                                        CR × LF
+	// GB4                       (Control | CR | LF) ÷
+	// GB5                                           ÷ (Control | CR | LF)
+	if (breakTypeA === GraphemeBreakType.CR) {
+		if (breakTypeB === GraphemeBreakType.LF) {
+			return false; // GB3
+		}
+	}
+	if (breakTypeA === GraphemeBreakType.Control || breakTypeA === GraphemeBreakType.CR || breakTypeA === GraphemeBreakType.LF) {
+		return true; // GB4
+	}
+	if (breakTypeB === GraphemeBreakType.Control || breakTypeB === GraphemeBreakType.CR || breakTypeB === GraphemeBreakType.LF) {
+		return true; // GB5
+	}
+
+	// Do not break Hangul syllable sequences.
+	// GB6                                         L × (L | V | LV | LVT)
+	// GB7                                  (LV | V) × (V | T)
+	// GB8                                 (LVT | T) × T
+	if (breakTypeA === GraphemeBreakType.L) {
+		if (breakTypeB === GraphemeBreakType.L || breakTypeB === GraphemeBreakType.V || breakTypeB === GraphemeBreakType.LV || breakTypeB === GraphemeBreakType.LVT) {
+			return false; // GB6
+		}
+	}
+	if (breakTypeA === GraphemeBreakType.LV || breakTypeA === GraphemeBreakType.V) {
+		if (breakTypeB === GraphemeBreakType.V || breakTypeB === GraphemeBreakType.T) {
+			return false; // GB7
+		}
+	}
+	if (breakTypeA === GraphemeBreakType.LVT || breakTypeA === GraphemeBreakType.T) {
+		if (breakTypeB === GraphemeBreakType.T) {
+			return false; // GB8
+		}
+	}
+
+	// Do not break before extending characters or ZWJ.
+	// GB9                                           × (Extend | ZWJ)
+	if (breakTypeB === GraphemeBreakType.Extend || breakTypeB === GraphemeBreakType.ZWJ) {
+		return false; // GB9
+	}
+
+	// The GB9a and GB9b rules only apply to extended grapheme clusters:
+	// Do not break before SpacingMarks, or after Prepend characters.
+	// GB9a                                          × SpacingMark
+	// GB9b                                  Prepend ×
+	if (breakTypeB === GraphemeBreakType.SpacingMark) {
+		return false; // GB9a
+	}
+	if (breakTypeA === GraphemeBreakType.Prepend) {
+		return false; // GB9b
+	}
+
+	// Do not break within emoji modifier sequences or emoji zwj sequences.
+	// GB11    \p{Extended_Pictographic} Extend* ZWJ × \p{Extended_Pictographic}
+	if (breakTypeA === GraphemeBreakType.ZWJ && breakTypeB === GraphemeBreakType.Extended_Pictographic) {
+		// Note: we are not implementing the rule entirely here to avoid introducing states
+		return false; // GB11
+	}
+
+	// GB12                          sot (RI RI)* RI × RI
+	// GB13                        [^RI] (RI RI)* RI × RI
+	if (breakTypeA === GraphemeBreakType.Regional_Indicator && breakTypeB === GraphemeBreakType.Regional_Indicator) {
+		// Note: we are not implementing the rule entirely here to avoid introducing states
+		return false; // GB12 & GB13
+	}
+
+	// GB999                                     Any ÷ Any
+	return true;
+}
+
+export const enum GraphemeBreakType {
+	Other = 0,
+	Prepend = 1,
+	CR = 2,
+	LF = 3,
+	Control = 4,
+	Extend = 5,
+	Regional_Indicator = 6,
+	SpacingMark = 7,
+	L = 8,
+	V = 9,
+	T = 10,
+	LV = 11,
+	LVT = 12,
+	ZWJ = 13,
+	Extended_Pictographic = 14
+}
+
+class GraphemeBreakTree {
+
+	private static _INSTANCE: GraphemeBreakTree | null = null;
+	public static getInstance(): GraphemeBreakTree {
+		if (!GraphemeBreakTree._INSTANCE) {
+			GraphemeBreakTree._INSTANCE = new GraphemeBreakTree();
+		}
+		return GraphemeBreakTree._INSTANCE;
+	}
+
+	private readonly _data: number[];
+
+	constructor() {
+		this._data = getGraphemeBreakRawData();
+	}
+
+	public getGraphemeBreakType(codePoint: number): GraphemeBreakType {
+		// !!! Let's make 7bit ASCII a bit faster: 0..31
+		if (codePoint < 32) {
+			if (codePoint === CharCode.LineFeed) {
+				return GraphemeBreakType.LF;
+			}
+			if (codePoint === CharCode.CarriageReturn) {
+				return GraphemeBreakType.CR;
+			}
+			return GraphemeBreakType.Control;
+		}
+		// !!! Let's make 7bit ASCII a bit faster: 32..126
+		if (codePoint < 127) {
+			return GraphemeBreakType.Other;
+		}
+
+		const data = this._data;
+		const nodeCount = data.length / 3;
+		let nodeIndex = 1;
+		while (nodeIndex <= nodeCount) {
+			if (codePoint < data[3 * nodeIndex]) {
+				// go left
+				nodeIndex = 2 * nodeIndex;
+			} else if (codePoint > data[3 * nodeIndex + 1]) {
+				// go right
+				nodeIndex = 2 * nodeIndex + 1;
+			} else {
+				// hit
+				return data[3 * nodeIndex + 2];
+			}
+		}
+
+		return GraphemeBreakType.Other;
+	}
+}
+
+function getGraphemeBreakRawData(): number[] {
+	// generated using https://github.com/alexandrudima/unicode-utils/blob/master/generate-grapheme-break.js
+	return JSON.parse('[0,0,0,51592,51592,11,44424,44424,11,72251,72254,5,7150,7150,7,48008,48008,11,55176,55176,11,128420,128420,14,3276,3277,5,9979,9980,14,46216,46216,11,49800,49800,11,53384,53384,11,70726,70726,5,122915,122916,5,129320,129327,14,2558,2558,5,5906,5908,5,9762,9763,14,43360,43388,8,45320,45320,11,47112,47112,11,48904,48904,11,50696,50696,11,52488,52488,11,54280,54280,11,70082,70083,1,71350,71350,7,73111,73111,5,127892,127893,14,128726,128727,14,129473,129474,14,2027,2035,5,2901,2902,5,3784,3789,5,6754,6754,5,8418,8420,5,9877,9877,14,11088,11088,14,44008,44008,5,44872,44872,11,45768,45768,11,46664,46664,11,47560,47560,11,48456,48456,11,49352,49352,11,50248,50248,11,51144,51144,11,52040,52040,11,52936,52936,11,53832,53832,11,54728,54728,11,69811,69814,5,70459,70460,5,71096,71099,7,71998,71998,5,72874,72880,5,119149,119149,7,127374,127374,14,128335,128335,14,128482,128482,14,128765,128767,14,129399,129400,14,129680,129685,14,1476,1477,5,2377,2380,7,2759,2760,5,3137,3140,7,3458,3459,7,4153,4154,5,6432,6434,5,6978,6978,5,7675,7679,5,9723,9726,14,9823,9823,14,9919,9923,14,10035,10036,14,42736,42737,5,43596,43596,5,44200,44200,11,44648,44648,11,45096,45096,11,45544,45544,11,45992,45992,11,46440,46440,11,46888,46888,11,47336,47336,11,47784,47784,11,48232,48232,11,48680,48680,11,49128,49128,11,49576,49576,11,50024,50024,11,50472,50472,11,50920,50920,11,51368,51368,11,51816,51816,11,52264,52264,11,52712,52712,11,53160,53160,11,53608,53608,11,54056,54056,11,54504,54504,11,54952,54952,11,68108,68111,5,69933,69940,5,70197,70197,7,70498,70499,7,70845,70845,5,71229,71229,5,71727,71735,5,72154,72155,5,72344,72345,5,73023,73029,5,94095,94098,5,121403,121452,5,126981,127182,14,127538,127546,14,127990,127990,14,128391,128391,14,128445,128449,14,128500,128505,14,128752,128752,14,129160,129167,14,129356,129356,14,129432,129442,14,129648,129651,14,129751,131069,14,173,173,4,1757,1757,1,2274,2274,1,2494,2494,5,2641,2641,5,2876,2876,5,3014,3016,7,3262,3262,7,3393,3396,5,3570,3571,7,3968,3972,5,4228,4228,7,6086,6086,5,6679,6680,5,6912,6915,5,7080,7081,5,7380,7392,5,8252,8252,14,9096,9096,14,9748,9749,14,9784,9786,14,9833,9850,14,9890,9894,14,9938,9938,14,9999,9999,14,10085,10087,14,12349,12349,14,43136,43137,7,43454,43456,7,43755,43755,7,44088,44088,11,44312,44312,11,44536,44536,11,44760,44760,11,44984,44984,11,45208,45208,11,45432,45432,11,45656,45656,11,45880,45880,11,46104,46104,11,46328,46328,11,46552,46552,11,46776,46776,11,47000,47000,11,47224,47224,11,47448,47448,11,47672,47672,11,47896,47896,11,48120,48120,11,48344,48344,11,48568,48568,11,48792,48792,11,49016,49016,11,49240,49240,11,49464,49464,11,49688,49688,11,49912,49912,11,50136,50136,11,50360,50360,11,50584,50584,11,50808,50808,11,51032,51032,11,51256,51256,11,51480,51480,11,51704,51704,11,51928,51928,11,52152,52152,11,52376,52376,11,52600,52600,11,52824,52824,11,53048,53048,11,53272,53272,11,53496,53496,11,53720,53720,11,53944,53944,11,54168,54168,11,54392,54392,11,54616,54616,11,54840,54840,11,55064,55064,11,65438,65439,5,69633,69633,5,69837,69837,1,70018,70018,7,70188,70190,7,70368,70370,7,70465,70468,7,70712,70719,5,70835,70840,5,70850,70851,5,71132,71133,5,71340,71340,7,71458,71461,5,71985,71989,7,72002,72002,7,72193,72202,5,72281,72283,5,72766,72766,7,72885,72886,5,73104,73105,5,92912,92916,5,113824,113827,4,119173,119179,5,121505,121519,5,125136,125142,5,127279,127279,14,127489,127490,14,127570,127743,14,127900,127901,14,128254,128254,14,128369,128370,14,128400,128400,14,128425,128432,14,128468,128475,14,128489,128494,14,128715,128720,14,128745,128745,14,128759,128760,14,129004,129023,14,129296,129304,14,129340,129342,14,129388,129392,14,129404,129407,14,129454,129455,14,129485,129487,14,129659,129663,14,129719,129727,14,917536,917631,5,13,13,2,1160,1161,5,1564,1564,4,1807,1807,1,2085,2087,5,2363,2363,7,2402,2403,5,2507,2508,7,2622,2624,7,2691,2691,7,2786,2787,5,2881,2884,5,3006,3006,5,3072,3072,5,3170,3171,5,3267,3268,7,3330,3331,7,3406,3406,1,3538,3540,5,3655,3662,5,3897,3897,5,4038,4038,5,4184,4185,5,4352,4447,8,6068,6069,5,6155,6157,5,6448,6449,7,6742,6742,5,6783,6783,5,6966,6970,5,7042,7042,7,7143,7143,7,7212,7219,5,7412,7412,5,8206,8207,4,8294,8303,4,8596,8601,14,9410,9410,14,9742,9742,14,9757,9757,14,9770,9770,14,9794,9794,14,9828,9828,14,9855,9855,14,9882,9882,14,9900,9903,14,9929,9933,14,9963,9967,14,9987,9988,14,10006,10006,14,10062,10062,14,10175,10175,14,11744,11775,5,42607,42607,5,43043,43044,7,43263,43263,5,43444,43445,7,43569,43570,5,43698,43700,5,43766,43766,5,44032,44032,11,44144,44144,11,44256,44256,11,44368,44368,11,44480,44480,11,44592,44592,11,44704,44704,11,44816,44816,11,44928,44928,11,45040,45040,11,45152,45152,11,45264,45264,11,45376,45376,11,45488,45488,11,45600,45600,11,45712,45712,11,45824,45824,11,45936,45936,11,46048,46048,11,46160,46160,11,46272,46272,11,46384,46384,11,46496,46496,11,46608,46608,11,46720,46720,11,46832,46832,11,46944,46944,11,47056,47056,11,47168,47168,11,47280,47280,11,47392,47392,11,47504,47504,11,47616,47616,11,47728,47728,11,47840,47840,11,47952,47952,11,48064,48064,11,48176,48176,11,48288,48288,11,48400,48400,11,48512,48512,11,48624,48624,11,48736,48736,11,48848,48848,11,48960,48960,11,49072,49072,11,49184,49184,11,49296,49296,11,49408,49408,11,49520,49520,11,49632,49632,11,49744,49744,11,49856,49856,11,49968,49968,11,50080,50080,11,50192,50192,11,50304,50304,11,50416,50416,11,50528,50528,11,50640,50640,11,50752,50752,11,50864,50864,11,50976,50976,11,51088,51088,11,51200,51200,11,51312,51312,11,51424,51424,11,51536,51536,11,51648,51648,11,51760,51760,11,51872,51872,11,51984,51984,11,52096,52096,11,52208,52208,11,52320,52320,11,52432,52432,11,52544,52544,11,52656,52656,11,52768,52768,11,52880,52880,11,52992,52992,11,53104,53104,11,53216,53216,11,53328,53328,11,53440,53440,11,53552,53552,11,53664,53664,11,53776,53776,11,53888,53888,11,54000,54000,11,54112,54112,11,54224,54224,11,54336,54336,11,54448,54448,11,54560,54560,11,54672,54672,11,54784,54784,11,54896,54896,11,55008,55008,11,55120,55120,11,64286,64286,5,66272,66272,5,68900,68903,5,69762,69762,7,69817,69818,5,69927,69931,5,70003,70003,5,70070,70078,5,70094,70094,7,70194,70195,7,70206,70206,5,70400,70401,5,70463,70463,7,70475,70477,7,70512,70516,5,70722,70724,5,70832,70832,5,70842,70842,5,70847,70848,5,71088,71089,7,71102,71102,7,71219,71226,5,71231,71232,5,71342,71343,7,71453,71455,5,71463,71467,5,71737,71738,5,71995,71996,5,72000,72000,7,72145,72147,7,72160,72160,5,72249,72249,7,72273,72278,5,72330,72342,5,72752,72758,5,72850,72871,5,72882,72883,5,73018,73018,5,73031,73031,5,73109,73109,5,73461,73462,7,94031,94031,5,94192,94193,7,119142,119142,7,119155,119162,4,119362,119364,5,121476,121476,5,122888,122904,5,123184,123190,5,126976,126979,14,127184,127231,14,127344,127345,14,127405,127461,14,127514,127514,14,127561,127567,14,127778,127779,14,127896,127896,14,127985,127986,14,127995,127999,5,128326,128328,14,128360,128366,14,128378,128378,14,128394,128397,14,128405,128406,14,128422,128423,14,128435,128443,14,128453,128464,14,128479,128480,14,128484,128487,14,128496,128498,14,128640,128709,14,128723,128724,14,128736,128741,14,128747,128748,14,128755,128755,14,128762,128762,14,128981,128991,14,129096,129103,14,129292,129292,14,129311,129311,14,129329,129330,14,129344,129349,14,129360,129374,14,129394,129394,14,129402,129402,14,129413,129425,14,129445,129450,14,129466,129471,14,129483,129483,14,129511,129535,14,129653,129655,14,129667,129670,14,129705,129711,14,129731,129743,14,917505,917505,4,917760,917999,5,10,10,3,127,159,4,768,879,5,1471,1471,5,1536,1541,1,1648,1648,5,1767,1768,5,1840,1866,5,2070,2073,5,2137,2139,5,2307,2307,7,2366,2368,7,2382,2383,7,2434,2435,7,2497,2500,5,2519,2519,5,2563,2563,7,2631,2632,5,2677,2677,5,2750,2752,7,2763,2764,7,2817,2817,5,2879,2879,5,2891,2892,7,2914,2915,5,3008,3008,5,3021,3021,5,3076,3076,5,3146,3149,5,3202,3203,7,3264,3265,7,3271,3272,7,3298,3299,5,3390,3390,5,3402,3404,7,3426,3427,5,3535,3535,5,3544,3550,7,3635,3635,7,3763,3763,7,3893,3893,5,3953,3966,5,3981,3991,5,4145,4145,7,4157,4158,5,4209,4212,5,4237,4237,5,4520,4607,10,5970,5971,5,6071,6077,5,6089,6099,5,6277,6278,5,6439,6440,5,6451,6456,7,6683,6683,5,6744,6750,5,6765,6770,7,6846,6846,5,6964,6964,5,6972,6972,5,7019,7027,5,7074,7077,5,7083,7085,5,7146,7148,7,7154,7155,7,7222,7223,5,7394,7400,5,7416,7417,5,8204,8204,5,8233,8233,4,8288,8292,4,8413,8416,5,8482,8482,14,8986,8987,14,9193,9203,14,9654,9654,14,9733,9733,14,9745,9745,14,9752,9752,14,9760,9760,14,9766,9766,14,9774,9775,14,9792,9792,14,9800,9811,14,9825,9826,14,9831,9831,14,9852,9853,14,9872,9873,14,9880,9880,14,9885,9887,14,9896,9897,14,9906,9916,14,9926,9927,14,9936,9936,14,9941,9960,14,9974,9974,14,9982,9985,14,9992,9997,14,10002,10002,14,10017,10017,14,10055,10055,14,10071,10071,14,10145,10145,14,11013,11015,14,11503,11505,5,12334,12335,5,12951,12951,14,42612,42621,5,43014,43014,5,43047,43047,7,43204,43205,5,43335,43345,5,43395,43395,7,43450,43451,7,43561,43566,5,43573,43574,5,43644,43644,5,43710,43711,5,43758,43759,7,44005,44005,5,44012,44012,7,44060,44060,11,44116,44116,11,44172,44172,11,44228,44228,11,44284,44284,11,44340,44340,11,44396,44396,11,44452,44452,11,44508,44508,11,44564,44564,11,44620,44620,11,44676,44676,11,44732,44732,11,44788,44788,11,44844,44844,11,44900,44900,11,44956,44956,11,45012,45012,11,45068,45068,11,45124,45124,11,45180,45180,11,45236,45236,11,45292,45292,11,45348,45348,11,45404,45404,11,45460,45460,11,45516,45516,11,45572,45572,11,45628,45628,11,45684,45684,11,45740,45740,11,45796,45796,11,45852,45852,11,45908,45908,11,45964,45964,11,46020,46020,11,46076,46076,11,46132,46132,11,46188,46188,11,46244,46244,11,46300,46300,11,46356,46356,11,46412,46412,11,46468,46468,11,46524,46524,11,46580,46580,11,46636,46636,11,46692,46692,11,46748,46748,11,46804,46804,11,46860,46860,11,46916,46916,11,46972,46972,11,47028,47028,11,47084,47084,11,47140,47140,11,47196,47196,11,47252,47252,11,47308,47308,11,47364,47364,11,47420,47420,11,47476,47476,11,47532,47532,11,47588,47588,11,47644,47644,11,47700,47700,11,47756,47756,11,47812,47812,11,47868,47868,11,47924,47924,11,47980,47980,11,48036,48036,11,48092,48092,11,48148,48148,11,48204,48204,11,48260,48260,11,48316,48316,11,48372,48372,11,48428,48428,11,48484,48484,11,48540,48540,11,48596,48596,11,48652,48652,11,48708,48708,11,48764,48764,11,48820,48820,11,48876,48876,11,48932,48932,11,48988,48988,11,49044,49044,11,49100,49100,11,49156,49156,11,49212,49212,11,49268,49268,11,49324,49324,11,49380,49380,11,49436,49436,11,49492,49492,11,49548,49548,11,49604,49604,11,49660,49660,11,49716,49716,11,49772,49772,11,49828,49828,11,49884,49884,11,49940,49940,11,49996,49996,11,50052,50052,11,50108,50108,11,50164,50164,11,50220,50220,11,50276,50276,11,50332,50332,11,50388,50388,11,50444,50444,11,50500,50500,11,50556,50556,11,50612,50612,11,50668,50668,11,50724,50724,11,50780,50780,11,50836,50836,11,50892,50892,11,50948,50948,11,51004,51004,11,51060,51060,11,51116,51116,11,51172,51172,11,51228,51228,11,51284,51284,11,51340,51340,11,51396,51396,11,51452,51452,11,51508,51508,11,51564,51564,11,51620,51620,11,51676,51676,11,51732,51732,11,51788,51788,11,51844,51844,11,51900,51900,11,51956,51956,11,52012,52012,11,52068,52068,11,52124,52124,11,52180,52180,11,52236,52236,11,52292,52292,11,52348,52348,11,52404,52404,11,52460,52460,11,52516,52516,11,52572,52572,11,52628,52628,11,52684,52684,11,52740,52740,11,52796,52796,11,52852,52852,11,52908,52908,11,52964,52964,11,53020,53020,11,53076,53076,11,53132,53132,11,53188,53188,11,53244,53244,11,53300,53300,11,53356,53356,11,53412,53412,11,53468,53468,11,53524,53524,11,53580,53580,11,53636,53636,11,53692,53692,11,53748,53748,11,53804,53804,11,53860,53860,11,53916,53916,11,53972,53972,11,54028,54028,11,54084,54084,11,54140,54140,11,54196,54196,11,54252,54252,11,54308,54308,11,54364,54364,11,54420,54420,11,54476,54476,11,54532,54532,11,54588,54588,11,54644,54644,11,54700,54700,11,54756,54756,11,54812,54812,11,54868,54868,11,54924,54924,11,54980,54980,11,55036,55036,11,55092,55092,11,55148,55148,11,55216,55238,9,65056,65071,5,65529,65531,4,68097,68099,5,68159,68159,5,69446,69456,5,69688,69702,5,69808,69810,7,69815,69816,7,69821,69821,1,69888,69890,5,69932,69932,7,69957,69958,7,70016,70017,5,70067,70069,7,70079,70080,7,70089,70092,5,70095,70095,5,70191,70193,5,70196,70196,5,70198,70199,5,70367,70367,5,70371,70378,5,70402,70403,7,70462,70462,5,70464,70464,5,70471,70472,7,70487,70487,5,70502,70508,5,70709,70711,7,70720,70721,7,70725,70725,7,70750,70750,5,70833,70834,7,70841,70841,7,70843,70844,7,70846,70846,7,70849,70849,7,71087,71087,5,71090,71093,5,71100,71101,5,71103,71104,5,71216,71218,7,71227,71228,7,71230,71230,7,71339,71339,5,71341,71341,5,71344,71349,5,71351,71351,5,71456,71457,7,71462,71462,7,71724,71726,7,71736,71736,7,71984,71984,5,71991,71992,7,71997,71997,7,71999,71999,1,72001,72001,1,72003,72003,5,72148,72151,5,72156,72159,7,72164,72164,7,72243,72248,5,72250,72250,1,72263,72263,5,72279,72280,7,72324,72329,1,72343,72343,7,72751,72751,7,72760,72765,5,72767,72767,5,72873,72873,7,72881,72881,7,72884,72884,7,73009,73014,5,73020,73021,5,73030,73030,1,73098,73102,7,73107,73108,7,73110,73110,7,73459,73460,5,78896,78904,4,92976,92982,5,94033,94087,7,94180,94180,5,113821,113822,5,119141,119141,5,119143,119145,5,119150,119154,5,119163,119170,5,119210,119213,5,121344,121398,5,121461,121461,5,121499,121503,5,122880,122886,5,122907,122913,5,122918,122922,5,123628,123631,5,125252,125258,5,126980,126980,14,127183,127183,14,127245,127247,14,127340,127343,14,127358,127359,14,127377,127386,14,127462,127487,6,127491,127503,14,127535,127535,14,127548,127551,14,127568,127569,14,127744,127777,14,127780,127891,14,127894,127895,14,127897,127899,14,127902,127984,14,127987,127989,14,127991,127994,14,128000,128253,14,128255,128317,14,128329,128334,14,128336,128359,14,128367,128368,14,128371,128377,14,128379,128390,14,128392,128393,14,128398,128399,14,128401,128404,14,128407,128419,14,128421,128421,14,128424,128424,14,128433,128434,14,128444,128444,14,128450,128452,14,128465,128467,14,128476,128478,14,128481,128481,14,128483,128483,14,128488,128488,14,128495,128495,14,128499,128499,14,128506,128591,14,128710,128714,14,128721,128722,14,128725,128725,14,128728,128735,14,128742,128744,14,128746,128746,14,128749,128751,14,128753,128754,14,128756,128758,14,128761,128761,14,128763,128764,14,128884,128895,14,128992,129003,14,129036,129039,14,129114,129119,14,129198,129279,14,129293,129295,14,129305,129310,14,129312,129319,14,129328,129328,14,129331,129338,14,129343,129343,14,129351,129355,14,129357,129359,14,129375,129387,14,129393,129393,14,129395,129398,14,129401,129401,14,129403,129403,14,129408,129412,14,129426,129431,14,129443,129444,14,129451,129453,14,129456,129465,14,129472,129472,14,129475,129482,14,129484,129484,14,129488,129510,14,129536,129647,14,129652,129652,14,129656,129658,14,129664,129666,14,129671,129679,14,129686,129704,14,129712,129718,14,129728,129730,14,129744,129750,14,917504,917504,4,917506,917535,4,917632,917759,4,918000,921599,4,0,9,4,11,12,4,14,31,4,169,169,14,174,174,14,1155,1159,5,1425,1469,5,1473,1474,5,1479,1479,5,1552,1562,5,1611,1631,5,1750,1756,5,1759,1764,5,1770,1773,5,1809,1809,5,1958,1968,5,2045,2045,5,2075,2083,5,2089,2093,5,2259,2273,5,2275,2306,5,2362,2362,5,2364,2364,5,2369,2376,5,2381,2381,5,2385,2391,5,2433,2433,5,2492,2492,5,2495,2496,7,2503,2504,7,2509,2509,5,2530,2531,5,2561,2562,5,2620,2620,5,2625,2626,5,2635,2637,5,2672,2673,5,2689,2690,5,2748,2748,5,2753,2757,5,2761,2761,7,2765,2765,5,2810,2815,5,2818,2819,7,2878,2878,5,2880,2880,7,2887,2888,7,2893,2893,5,2903,2903,5,2946,2946,5,3007,3007,7,3009,3010,7,3018,3020,7,3031,3031,5,3073,3075,7,3134,3136,5,3142,3144,5,3157,3158,5,3201,3201,5,3260,3260,5,3263,3263,5,3266,3266,5,3270,3270,5,3274,3275,7,3285,3286,5,3328,3329,5,3387,3388,5,3391,3392,7,3398,3400,7,3405,3405,5,3415,3415,5,3457,3457,5,3530,3530,5,3536,3537,7,3542,3542,5,3551,3551,5,3633,3633,5,3636,3642,5,3761,3761,5,3764,3772,5,3864,3865,5,3895,3895,5,3902,3903,7,3967,3967,7,3974,3975,5,3993,4028,5,4141,4144,5,4146,4151,5,4155,4156,7,4182,4183,7,4190,4192,5,4226,4226,5,4229,4230,5,4253,4253,5,4448,4519,9,4957,4959,5,5938,5940,5,6002,6003,5,6070,6070,7,6078,6085,7,6087,6088,7,6109,6109,5,6158,6158,4,6313,6313,5,6435,6438,7,6441,6443,7,6450,6450,5,6457,6459,5,6681,6682,7,6741,6741,7,6743,6743,7,6752,6752,5,6757,6764,5,6771,6780,5,6832,6845,5,6847,6848,5,6916,6916,7,6965,6965,5,6971,6971,7,6973,6977,7,6979,6980,7,7040,7041,5,7073,7073,7,7078,7079,7,7082,7082,7,7142,7142,5,7144,7145,5,7149,7149,5,7151,7153,5,7204,7211,7,7220,7221,7,7376,7378,5,7393,7393,7,7405,7405,5,7415,7415,7,7616,7673,5,8203,8203,4,8205,8205,13,8232,8232,4,8234,8238,4,8265,8265,14,8293,8293,4,8400,8412,5,8417,8417,5,8421,8432,5,8505,8505,14,8617,8618,14,9000,9000,14,9167,9167,14,9208,9210,14,9642,9643,14,9664,9664,14,9728,9732,14,9735,9741,14,9743,9744,14,9746,9746,14,9750,9751,14,9753,9756,14,9758,9759,14,9761,9761,14,9764,9765,14,9767,9769,14,9771,9773,14,9776,9783,14,9787,9791,14,9793,9793,14,9795,9799,14,9812,9822,14,9824,9824,14,9827,9827,14,9829,9830,14,9832,9832,14,9851,9851,14,9854,9854,14,9856,9861,14,9874,9876,14,9878,9879,14,9881,9881,14,9883,9884,14,9888,9889,14,9895,9895,14,9898,9899,14,9904,9905,14,9917,9918,14,9924,9925,14,9928,9928,14,9934,9935,14,9937,9937,14,9939,9940,14,9961,9962,14,9968,9973,14,9975,9978,14,9981,9981,14,9986,9986,14,9989,9989,14,9998,9998,14,10000,10001,14,10004,10004,14,10013,10013,14,10024,10024,14,10052,10052,14,10060,10060,14,10067,10069,14,10083,10084,14,10133,10135,14,10160,10160,14,10548,10549,14,11035,11036,14,11093,11093,14,11647,11647,5,12330,12333,5,12336,12336,14,12441,12442,5,12953,12953,14,42608,42610,5,42654,42655,5,43010,43010,5,43019,43019,5,43045,43046,5,43052,43052,5,43188,43203,7,43232,43249,5,43302,43309,5,43346,43347,7,43392,43394,5,43443,43443,5,43446,43449,5,43452,43453,5,43493,43493,5,43567,43568,7,43571,43572,7,43587,43587,5,43597,43597,7,43696,43696,5,43703,43704,5,43713,43713,5,43756,43757,5,43765,43765,7,44003,44004,7,44006,44007,7,44009,44010,7,44013,44013,5,44033,44059,12,44061,44087,12,44089,44115,12,44117,44143,12,44145,44171,12,44173,44199,12,44201,44227,12,44229,44255,12,44257,44283,12,44285,44311,12,44313,44339,12,44341,44367,12,44369,44395,12,44397,44423,12,44425,44451,12,44453,44479,12,44481,44507,12,44509,44535,12,44537,44563,12,44565,44591,12,44593,44619,12,44621,44647,12,44649,44675,12,44677,44703,12,44705,44731,12,44733,44759,12,44761,44787,12,44789,44815,12,44817,44843,12,44845,44871,12,44873,44899,12,44901,44927,12,44929,44955,12,44957,44983,12,44985,45011,12,45013,45039,12,45041,45067,12,45069,45095,12,45097,45123,12,45125,45151,12,45153,45179,12,45181,45207,12,45209,45235,12,45237,45263,12,45265,45291,12,45293,45319,12,45321,45347,12,45349,45375,12,45377,45403,12,45405,45431,12,45433,45459,12,45461,45487,12,45489,45515,12,45517,45543,12,45545,45571,12,45573,45599,12,45601,45627,12,45629,45655,12,45657,45683,12,45685,45711,12,45713,45739,12,45741,45767,12,45769,45795,12,45797,45823,12,45825,45851,12,45853,45879,12,45881,45907,12,45909,45935,12,45937,45963,12,45965,45991,12,45993,46019,12,46021,46047,12,46049,46075,12,46077,46103,12,46105,46131,12,46133,46159,12,46161,46187,12,46189,46215,12,46217,46243,12,46245,46271,12,46273,46299,12,46301,46327,12,46329,46355,12,46357,46383,12,46385,46411,12,46413,46439,12,46441,46467,12,46469,46495,12,46497,46523,12,46525,46551,12,46553,46579,12,46581,46607,12,46609,46635,12,46637,46663,12,46665,46691,12,46693,46719,12,46721,46747,12,46749,46775,12,46777,46803,12,46805,46831,12,46833,46859,12,46861,46887,12,46889,46915,12,46917,46943,12,46945,46971,12,46973,46999,12,47001,47027,12,47029,47055,12,47057,47083,12,47085,47111,12,47113,47139,12,47141,47167,12,47169,47195,12,47197,47223,12,47225,47251,12,47253,47279,12,47281,47307,12,47309,47335,12,47337,47363,12,47365,47391,12,47393,47419,12,47421,47447,12,47449,47475,12,47477,47503,12,47505,47531,12,47533,47559,12,47561,47587,12,47589,47615,12,47617,47643,12,47645,47671,12,47673,47699,12,47701,47727,12,47729,47755,12,47757,47783,12,47785,47811,12,47813,47839,12,47841,47867,12,47869,47895,12,47897,47923,12,47925,47951,12,47953,47979,12,47981,48007,12,48009,48035,12,48037,48063,12,48065,48091,12,48093,48119,12,48121,48147,12,48149,48175,12,48177,48203,12,48205,48231,12,48233,48259,12,48261,48287,12,48289,48315,12,48317,48343,12,48345,48371,12,48373,48399,12,48401,48427,12,48429,48455,12,48457,48483,12,48485,48511,12,48513,48539,12,48541,48567,12,48569,48595,12,48597,48623,12,48625,48651,12,48653,48679,12,48681,48707,12,48709,48735,12,48737,48763,12,48765,48791,12,48793,48819,12,48821,48847,12,48849,48875,12,48877,48903,12,48905,48931,12,48933,48959,12,48961,48987,12,48989,49015,12,49017,49043,12,49045,49071,12,49073,49099,12,49101,49127,12,49129,49155,12,49157,49183,12,49185,49211,12,49213,49239,12,49241,49267,12,49269,49295,12,49297,49323,12,49325,49351,12,49353,49379,12,49381,49407,12,49409,49435,12,49437,49463,12,49465,49491,12,49493,49519,12,49521,49547,12,49549,49575,12,49577,49603,12,49605,49631,12,49633,49659,12,49661,49687,12,49689,49715,12,49717,49743,12,49745,49771,12,49773,49799,12,49801,49827,12,49829,49855,12,49857,49883,12,49885,49911,12,49913,49939,12,49941,49967,12,49969,49995,12,49997,50023,12,50025,50051,12,50053,50079,12,50081,50107,12,50109,50135,12,50137,50163,12,50165,50191,12,50193,50219,12,50221,50247,12,50249,50275,12,50277,50303,12,50305,50331,12,50333,50359,12,50361,50387,12,50389,50415,12,50417,50443,12,50445,50471,12,50473,50499,12,50501,50527,12,50529,50555,12,50557,50583,12,50585,50611,12,50613,50639,12,50641,50667,12,50669,50695,12,50697,50723,12,50725,50751,12,50753,50779,12,50781,50807,12,50809,50835,12,50837,50863,12,50865,50891,12,50893,50919,12,50921,50947,12,50949,50975,12,50977,51003,12,51005,51031,12,51033,51059,12,51061,51087,12,51089,51115,12,51117,51143,12,51145,51171,12,51173,51199,12,51201,51227,12,51229,51255,12,51257,51283,12,51285,51311,12,51313,51339,12,51341,51367,12,51369,51395,12,51397,51423,12,51425,51451,12,51453,51479,12,51481,51507,12,51509,51535,12,51537,51563,12,51565,51591,12,51593,51619,12,51621,51647,12,51649,51675,12,51677,51703,12,51705,51731,12,51733,51759,12,51761,51787,12,51789,51815,12,51817,51843,12,51845,51871,12,51873,51899,12,51901,51927,12,51929,51955,12,51957,51983,12,51985,52011,12,52013,52039,12,52041,52067,12,52069,52095,12,52097,52123,12,52125,52151,12,52153,52179,12,52181,52207,12,52209,52235,12,52237,52263,12,52265,52291,12,52293,52319,12,52321,52347,12,52349,52375,12,52377,52403,12,52405,52431,12,52433,52459,12,52461,52487,12,52489,52515,12,52517,52543,12,52545,52571,12,52573,52599,12,52601,52627,12,52629,52655,12,52657,52683,12,52685,52711,12,52713,52739,12,52741,52767,12,52769,52795,12,52797,52823,12,52825,52851,12,52853,52879,12,52881,52907,12,52909,52935,12,52937,52963,12,52965,52991,12,52993,53019,12,53021,53047,12,53049,53075,12,53077,53103,12,53105,53131,12,53133,53159,12,53161,53187,12,53189,53215,12,53217,53243,12,53245,53271,12,53273,53299,12,53301,53327,12,53329,53355,12,53357,53383,12,53385,53411,12,53413,53439,12,53441,53467,12,53469,53495,12,53497,53523,12,53525,53551,12,53553,53579,12,53581,53607,12,53609,53635,12,53637,53663,12,53665,53691,12,53693,53719,12,53721,53747,12,53749,53775,12,53777,53803,12,53805,53831,12,53833,53859,12,53861,53887,12,53889,53915,12,53917,53943,12,53945,53971,12,53973,53999,12,54001,54027,12,54029,54055,12,54057,54083,12,54085,54111,12,54113,54139,12,54141,54167,12,54169,54195,12,54197,54223,12,54225,54251,12,54253,54279,12,54281,54307,12,54309,54335,12,54337,54363,12,54365,54391,12,54393,54419,12,54421,54447,12,54449,54475,12,54477,54503,12,54505,54531,12,54533,54559,12,54561,54587,12,54589,54615,12,54617,54643,12,54645,54671,12,54673,54699,12,54701,54727,12,54729,54755,12,54757,54783,12,54785,54811,12,54813,54839,12,54841,54867,12,54869,54895,12,54897,54923,12,54925,54951,12,54953,54979,12,54981,55007,12,55009,55035,12,55037,55063,12,55065,55091,12,55093,55119,12,55121,55147,12,55149,55175,12,55177,55203,12,55243,55291,10,65024,65039,5,65279,65279,4,65520,65528,4,66045,66045,5,66422,66426,5,68101,68102,5,68152,68154,5,68325,68326,5,69291,69292,5,69632,69632,7,69634,69634,7,69759,69761,5]');
+}
+
+//#endregion
diff --git a/src/typings/windows-foreground-love.d.ts b/src/vs/base/common/styler.ts
similarity index 67%
rename from src/typings/windows-foreground-love.d.ts
rename to src/vs/base/common/styler.ts
index 2b0925658466..d5e2b58df778 100644
--- a/src/typings/windows-foreground-love.d.ts
+++ b/src/vs/base/common/styler.ts
@@ -3,8 +3,10 @@
  *  Licensed under the Source EULA. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
 
-declare module 'windows-foreground-love' {
+import { Color } from 'vs/base/common/color';
 
-	export function allowSetForegroundWindow(pid?: number): boolean;
+export type styleFn = (colors: { [name: string]: Color | undefined }) => void;
 
-}
\ No newline at end of file
+export interface IThemable {
+	style: styleFn;
+}
diff --git a/src/vs/base/common/types.ts b/src/vs/base/common/types.ts
index 02e4baf10c94..24f4747bd4b8 100644
--- a/src/vs/base/common/types.ts
+++ b/src/vs/base/common/types.ts
@@ -93,6 +93,13 @@ export function isUndefinedOrNull(obj: any): obj is undefined | null {
 	return isUndefined(obj) || obj === null;
 }
 
+
+export function assertType(condition: any, type?: string): asserts condition {
+	if (!condition) {
+		throw new Error(type ? `Unexpected type, expected '${type}'` : 'Unexpected type');
+	}
+}
+
 /**
  * Asserts that the argument passed in is neither undefined nor null.
  */
diff --git a/src/vs/base/common/uri.ts b/src/vs/base/common/uri.ts
index 43019a9338b2..dc6ad3e4ab77 100644
--- a/src/vs/base/common/uri.ts
+++ b/src/vs/base/common/uri.ts
@@ -10,10 +10,10 @@ const _schemePattern = /^\w[\w\d+.-]*$/;
 const _singleSlashStart = /^\//;
 const _doubleSlashStart = /^\/\//;
 
-function _validateUri(ret: URI): void {
+function _validateUri(ret: URI, _strict?: boolean): void {
 
 	// scheme, must be set
-	if (!ret.scheme) {
+	if (!ret.scheme && _strict) {
 		throw new Error(`[UriError]: Scheme is missing: {scheme: "", authority: "${ret.authority}", path: "${ret.path}", query: "${ret.query}", fragment: "${ret.fragment}"}`);
 	}
 
@@ -41,11 +41,13 @@ function _validateUri(ret: URI): void {
 	}
 }
 
-// graceful behaviour when scheme is missing: fallback to using 'file'-scheme
-function _schemeFix(scheme: string): string {
-	if (!scheme) {
-		console.trace('BAD uri lacks scheme, falling back to file-scheme.');
-		scheme = 'file';
+// for a while we allowed uris *without* schemes and this is the migration
+// for them, e.g. an uri without scheme and without strict-mode warns and falls
+// back to the file-scheme. that should cause the least carnage and still be a
+// clear warning
+function _schemeFix(scheme: string, _strict: boolean): string {
+	if (!scheme && !_strict) {
+		return 'file';
 	}
 	return scheme;
 }
@@ -138,7 +140,7 @@ export class URI implements UriComponents {
 	/**
 	 * @internal
 	 */
-	protected constructor(scheme: string, authority?: string, path?: string, query?: string, fragment?: string);
+	protected constructor(scheme: string, authority?: string, path?: string, query?: string, fragment?: string, _strict?: boolean);
 
 	/**
 	 * @internal
@@ -148,7 +150,7 @@ export class URI implements UriComponents {
 	/**
 	 * @internal
 	 */
-	protected constructor(schemeOrData: string | UriComponents, authority?: string, path?: string, query?: string, fragment?: string) {
+	protected constructor(schemeOrData: string | UriComponents, authority?: string, path?: string, query?: string, fragment?: string, _strict: boolean = false) {
 
 		if (typeof schemeOrData === 'object') {
 			this.scheme = schemeOrData.scheme || _empty;
@@ -160,13 +162,13 @@ export class URI implements UriComponents {
 			// that creates uri components.
 			// _validateUri(this);
 		} else {
-			this.scheme = _schemeFix(schemeOrData);
+			this.scheme = _schemeFix(schemeOrData, _strict);
 			this.authority = authority || _empty;
 			this.path = _referenceResolution(this.scheme, path || _empty);
 			this.query = query || _empty;
 			this.fragment = fragment || _empty;
 
-			_validateUri(this);
+			_validateUri(this, _strict);
 		}
 	}
 
@@ -205,7 +207,7 @@ export class URI implements UriComponents {
 
 	// ---- modify to new -------------------------
 
-	with(change: { scheme?: string; authority?: string | null; path?: string | null; query?: string | null; fragment?: string | null; }): URI {
+	with(change: { scheme?: string; authority?: string | null; path?: string | null; query?: string | null; fragment?: string | null }): URI {
 
 		if (!change) {
 			return this;
@@ -258,17 +260,18 @@ export class URI implements UriComponents {
 	 *
 	 * @param value A string which represents an URI (see `URI#toString`).
 	 */
-	static parse(value: string): URI {
+	static parse(value: string, _strict: boolean = false): URI {
 		const match = _regexp.exec(value);
 		if (!match) {
 			return new _URI(_empty, _empty, _empty, _empty, _empty);
 		}
 		return new _URI(
 			match[2] || _empty,
-			decodeURIComponent(match[4] || _empty),
-			decodeURIComponent(match[5] || _empty),
-			decodeURIComponent(match[7] || _empty),
-			decodeURIComponent(match[9] || _empty)
+			percentDecode(match[4] || _empty),
+			percentDecode(match[5] || _empty),
+			percentDecode(match[7] || _empty),
+			percentDecode(match[9] || _empty),
+			_strict
 		);
 	}
 
@@ -320,7 +323,7 @@ export class URI implements UriComponents {
 		return new _URI('file', authority, path, _empty, _empty);
 	}
 
-	static from(components: { scheme: string; authority?: string; path?: string; query?: string; fragment?: string; }): URI {
+	static from(components: { scheme: string; authority?: string; path?: string; query?: string; fragment?: string }): URI {
 		return new _URI(
 			components.scheme,
 			components.authority,
@@ -445,7 +448,7 @@ class _URI extends URI {
 }
 
 // reserved characters: https://tools.ietf.org/html/rfc3986#section-2.2
-const encodeTable: { [ch: number]: string; } = {
+const encodeTable: { [ch: number]: string } = {
 	[CharCode.Colon]: '%3A', // gen-delims
 	[CharCode.Slash]: '%2F',
 	[CharCode.QuestionMark]: '%3F',
@@ -646,3 +649,26 @@ function _asFormatted(uri: URI, skipEncoding: boolean): string {
 	}
 	return res;
 }
+
+// --- decode
+
+function decodeURIComponentGraceful(str: string): string {
+	try {
+		return decodeURIComponent(str);
+	} catch {
+		if (str.length > 3) {
+			return str.substr(0, 3) + decodeURIComponentGraceful(str.substr(3));
+		} else {
+			return str;
+		}
+	}
+}
+
+const _rEncodedAsHex = /(%[0-9A-Za-z][0-9A-Za-z])+/g;
+
+function percentDecode(str: string): string {
+	if (!str.match(_rEncodedAsHex)) {
+		return str;
+	}
+	return str.replace(_rEncodedAsHex, (match) => decodeURIComponentGraceful(match));
+}
diff --git a/src/vs/base/node/decoder.ts b/src/vs/base/node/decoder.ts
index b087eee4c844..4c910522f7af 100644
--- a/src/vs/base/node/decoder.ts
+++ b/src/vs/base/node/decoder.ts
@@ -59,4 +59,4 @@ export class LineDecoder {
 	end(): string | null {
 		return this.remaining;
 	}
-}
\ No newline at end of file
+}
diff --git a/src/vs/base/node/encoding.ts b/src/vs/base/node/encoding.ts
index 68f08c84243e..7aa3d01a1f93 100644
--- a/src/vs/base/node/encoding.ts
+++ b/src/vs/base/node/encoding.ts
@@ -111,7 +111,7 @@ export function toDecodeStream(readable: Readable, options: IDecodeStreamOptions
 				});
 			}
 
-			_final(callback: (error: Error | null) => void) {
+			_final(callback: () => void) {
 
 				// normal finish
 				if (this.decodeStream) {
@@ -144,7 +144,7 @@ export function decode(buffer: Buffer, encoding: string): string {
 }
 
 export function encode(content: string | Buffer, encoding: string, options?: { addBOM?: boolean }): Buffer {
-	return iconv.encode(content, toNodeEncoding(encoding), options);
+	return iconv.encode(content as string /* TODO report into upstream typings */, toNodeEncoding(encoding), options);
 }
 
 export function encodingExists(encoding: string): boolean {
@@ -167,7 +167,7 @@ function toNodeEncoding(enc: string | null): string {
 	return enc;
 }
 
-export function detectEncodingByBOMFromBuffer(buffer: Buffer | VSBuffer | null, bytesRead: number): string | null {
+export function detectEncodingByBOMFromBuffer(buffer: Buffer | VSBuffer | null, bytesRead: number): typeof UTF8_with_bom | typeof UTF16le | typeof UTF16be | null {
 	if (!buffer || bytesRead < UTF16be_BOM.length) {
 		return null;
 	}
@@ -193,33 +193,31 @@ export function detectEncodingByBOMFromBuffer(buffer: Buffer | VSBuffer | null,
 
 	// UTF-8
 	if (b0 === UTF8_BOM[0] && b1 === UTF8_BOM[1] && b2 === UTF8_BOM[2]) {
-		return UTF8;
+		return UTF8_with_bom;
 	}
 
 	return null;
 }
 
-const MINIMUM_THRESHOLD = 0.2;
-const IGNORE_ENCODINGS = ['ascii', 'utf-8', 'utf-16', 'utf-32'];
-
 /**
  * Guesses the encoding from buffer.
  */
 async function guessEncodingByBuffer(buffer: Buffer): Promise {
 	const jschardet = await import('jschardet');
 
-	jschardet.Constants.MINIMUM_THRESHOLD = MINIMUM_THRESHOLD;
-
 	const guessed = jschardet.detect(buffer);
 	if (!guessed || !guessed.encoding) {
 		return null;
 	}
 
-	const enc = guessed.encoding.toLowerCase();
-
-	// Ignore encodings that cannot guess correctly
-	// (http://chardet.readthedocs.io/en/latest/supported-encodings.html)
-	if (0 <= IGNORE_ENCODINGS.indexOf(enc)) {
+	// Ignore 'ascii' as guessed encoding because that
+	// is almost never what we want, rather fallback
+	// to the configured encoding then. Otherwise,
+	// opening a ascii-only file with auto guessing
+	// enabled will put the file into 'ascii' mode
+	// and thus typing any special characters is
+	// not possible anymore.
+	if (guessed.encoding.toLowerCase() === 'ascii') {
 		return null;
 	}
 
diff --git a/src/vs/base/node/languagePacks.js b/src/vs/base/node/languagePacks.js
index c63e26f401d0..82fdd4359a2f 100644
--- a/src/vs/base/node/languagePacks.js
+++ b/src/vs/base/node/languagePacks.js
@@ -50,8 +50,8 @@ function factory(nodeRequire, path, fs, perf) {
 	 * @param {string} dir
 	 * @returns {Promise}
 	 */
-	function mkdir(dir) {
-		return new Promise((c, e) => fs.mkdir(dir, err => (err && err.code !== 'EEXIST') ? e(err) : c(dir)));
+	function mkdirp(dir) {
+		return new Promise((c, e) => fs.mkdir(dir, { recursive: true }, err => (err && err.code !== 'EEXIST') ? e(err) : c(dir)));
 	}
 
 	/**
@@ -91,24 +91,6 @@ function factory(nodeRequire, path, fs, perf) {
 		});
 	}
 
-	/**
-	 * @param {string} dir
-	 * @returns {Promise}
-	 */
-	function mkdirp(dir) {
-		return mkdir(dir).then(null, err => {
-			if (err && err.code === 'ENOENT') {
-				const parent = path.dirname(dir);
-
-				if (parent !== dir) { // if not arrived at root
-					return mkdirp(parent).then(() => mkdir(dir));
-				}
-			}
-
-			throw err;
-		});
-	}
-
 	function readFile(file) {
 		return new Promise(function (resolve, reject) {
 			fs.readFile(file, 'utf8', function (err, data) {
diff --git a/src/vs/base/node/pfs.ts b/src/vs/base/node/pfs.ts
index b7a9b153436e..3ec31d9a9e9c 100644
--- a/src/vs/base/node/pfs.ts
+++ b/src/vs/base/node/pfs.ts
@@ -3,7 +3,7 @@
  *  Licensed under the Source EULA. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
 
-import { join, dirname } from 'vs/base/common/path';
+import { join } from 'vs/base/common/path';
 import { Queue } from 'vs/base/common/async';
 import * as fs from 'fs';
 import * as os from 'os';
@@ -11,7 +11,6 @@ import * as platform from 'vs/base/common/platform';
 import { Event } from 'vs/base/common/event';
 import { endsWith } from 'vs/base/common/strings';
 import { promisify } from 'util';
-import { CancellationToken } from 'vs/base/common/cancellation';
 import { isRootOrDriveLetter } from 'vs/base/common/extpath';
 import { generateUuid } from 'vs/base/common/uuid';
 import { normalizeNFC } from 'vs/base/common/normalization';
@@ -398,7 +397,7 @@ function doWriteFileStreamAndFlush(path: string, reader: NodeJS.ReadableStream,
 // not in some cache.
 //
 // See https://github.com/nodejs/node/blob/v5.10.0/lib/fs.js#L1194
-function doWriteFileAndFlush(path: string, data: string | Buffer | Uint8Array, options: IEnsuredWriteFileOptions, callback: (error?: Error) => void): void {
+function doWriteFileAndFlush(path: string, data: string | Buffer | Uint8Array, options: IEnsuredWriteFileOptions, callback: (error: Error | null) => void): void {
 	if (options.encoding) {
 		data = encode(data instanceof Uint8Array ? Buffer.from(data) : data, options.encoding.charset, { addBOM: options.encoding.addBOM });
 	}
@@ -633,55 +632,8 @@ async function doCopyFile(source: string, target: string, mode: number): Promise
 	});
 }
 
-export async function mkdirp(path: string, mode?: number, token?: CancellationToken): Promise {
-	const mkdir = async () => {
-		try {
-			await promisify(fs.mkdir)(path, mode);
-		} catch (error) {
-
-			// ENOENT: a parent folder does not exist yet
-			if (error.code === 'ENOENT') {
-				return Promise.reject(error);
-			}
-
-			// Any other error: check if folder exists and
-			// return normally in that case if its a folder
-			try {
-				const fileStat = await stat(path);
-				if (!fileStat.isDirectory()) {
-					return Promise.reject(new Error(`'${path}' exists and is not a directory.`));
-				}
-			} catch (statError) {
-				throw error; // rethrow original error
-			}
-		}
-	};
-
-	// stop at root
-	if (path === dirname(path)) {
-		return Promise.resolve();
-	}
-
-	try {
-		await mkdir();
-	} catch (error) {
-
-		// Respect cancellation
-		if (token && token.isCancellationRequested) {
-			return Promise.resolve();
-		}
-
-		// ENOENT: a parent folder does not exist yet, continue
-		// to create the parent folder and then try again.
-		if (error.code === 'ENOENT') {
-			await mkdirp(dirname(path), mode);
-
-			return mkdir();
-		}
-
-		// Any other error
-		return Promise.reject(error);
-	}
+export async function mkdirp(path: string, mode?: number): Promise {
+	return promisify(fs.mkdir)(path, { mode, recursive: true });
 }
 
 // See https://github.com/Microsoft/vscode/issues/30180
diff --git a/src/vs/base/node/processes.ts b/src/vs/base/node/processes.ts
index 21f96596ec9c..3557b9f59425 100644
--- a/src/vs/base/node/processes.ts
+++ b/src/vs/base/node/processes.ts
@@ -365,11 +365,11 @@ export class LineProcess extends AbstractProcess {
 	protected handleSpawn(childProcess: cp.ChildProcess, cc: ValueCallback, pp: ProgressCallback, ee: ErrorCallback, sync: boolean): void {
 		const stdoutLineDecoder = new LineDecoder();
 		const stderrLineDecoder = new LineDecoder();
-		childProcess.stdout.on('data', (data: Buffer) => {
+		childProcess.stdout!.on('data', (data: Buffer) => {
 			const lines = stdoutLineDecoder.write(data);
 			lines.forEach(line => pp({ line: line, source: Source.stdout }));
 		});
-		childProcess.stderr.on('data', (data: Buffer) => {
+		childProcess.stderr!.on('data', (data: Buffer) => {
 			const lines = stderrLineDecoder.write(data);
 			lines.forEach(line => pp({ line: line, source: Source.stderr }));
 		});
@@ -454,6 +454,14 @@ export namespace win32 {
 		if (paths === undefined || paths.length === 0) {
 			return path.join(cwd, command);
 		}
+
+		async function fileExists(path: string): Promise {
+			if (await promisify(fs.exists)(path)) {
+				return !((await promisify(fs.stat)(path)).isDirectory);
+			}
+			return false;
+		}
+
 		// We have a simple file name. We get the path variable from the env
 		// and try to find the executable on the path.
 		for (let pathEntry of paths) {
@@ -464,15 +472,15 @@ export namespace win32 {
 			} else {
 				fullPath = path.join(cwd, pathEntry, command);
 			}
-			if (await promisify(fs.exists)(fullPath)) {
+			if (await fileExists(fullPath)) {
 				return fullPath;
 			}
 			let withExtension = fullPath + '.com';
-			if (await promisify(fs.exists)(withExtension)) {
+			if (await fileExists(withExtension)) {
 				return withExtension;
 			}
 			withExtension = fullPath + '.exe';
-			if (await promisify(fs.exists)(withExtension)) {
+			if (await fileExists(withExtension)) {
 				return withExtension;
 			}
 		}
diff --git a/src/vs/base/node/watcher.ts b/src/vs/base/node/watcher.ts
index 5248c2e798ed..9205aec656f8 100644
--- a/src/vs/base/node/watcher.ts
+++ b/src/vs/base/node/watcher.ts
@@ -10,7 +10,7 @@ import { normalizeNFC } from 'vs/base/common/normalization';
 import { toDisposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
 import { exists, readdir } from 'vs/base/node/pfs';
 
-export function watchFile(path: string, onChange: (type: 'changed' | 'deleted', path: string) => void, onError: (error: string) => void): IDisposable {
+export function watchFile(path: string, onChange: (type: 'added' | 'changed' | 'deleted', path: string) => void, onError: (error: string) => void): IDisposable {
 	return doWatchNonRecursive({ path, isDirectory: false }, onChange, onError);
 }
 
@@ -189,4 +189,4 @@ function doWatchNonRecursive(file: { path: string, isDirectory: boolean }, onCha
 
 		watcherDisposables = dispose(watcherDisposables);
 	});
-}
\ No newline at end of file
+}
diff --git a/src/vs/base/node/zip.ts b/src/vs/base/node/zip.ts
index 320e2b2670fe..30724dc30f2f 100644
--- a/src/vs/base/node/zip.ts
+++ b/src/vs/base/node/zip.ts
@@ -86,7 +86,7 @@ function extractEntry(stream: Readable, fileName: string, mode: number, targetPa
 		}
 	});
 
-	return Promise.resolve(mkdirp(targetDirName, undefined, token)).then(() => new Promise((c, e) => {
+	return Promise.resolve(mkdirp(targetDirName)).then(() => new Promise((c, e) => {
 		if (token.isCancellationRequested) {
 			return;
 		}
@@ -149,7 +149,7 @@ function extractZip(zipfile: ZipFile, targetPath: string, options: IOptions, tok
 			// directory file names end with '/'
 			if (/\/$/.test(fileName)) {
 				const targetFileName = path.join(targetPath, fileName);
-				last = createCancelablePromise(token => mkdirp(targetFileName, undefined, token).then(() => readNextEntry(token)).then(undefined, e));
+				last = createCancelablePromise(token => mkdirp(targetFileName).then(() => readNextEntry(token)).then(undefined, e));
 				return;
 			}
 
@@ -163,7 +163,7 @@ function extractZip(zipfile: ZipFile, targetPath: string, options: IOptions, tok
 
 function openZip(zipFile: string, lazy: boolean = false): Promise {
 	return new Promise((resolve, reject) => {
-		_openZip(zipFile, lazy ? { lazyEntries: true } : undefined, (error?: Error, zipfile?: ZipFile) => {
+		_openZip(zipFile, lazy ? { lazyEntries: true } : undefined!, (error?: Error, zipfile?: ZipFile) => {
 			if (error) {
 				reject(toExtractError(error));
 			} else {
diff --git a/src/vs/base/parts/ipc/common/ipc.net.ts b/src/vs/base/parts/ipc/common/ipc.net.ts
index 552815306bd5..effadd30fb12 100644
--- a/src/vs/base/parts/ipc/common/ipc.net.ts
+++ b/src/vs/base/parts/ipc/common/ipc.net.ts
@@ -137,7 +137,7 @@ export const enum ProtocolConstants {
 	/**
 	 * If there is a message that has been unacknowledged for 10 seconds, consider the connection closed...
 	 */
-	AcknowledgeTimeoutTime = 10000, // 10 seconds
+	AcknowledgeTimeoutTime = 20000, // 20 seconds
 	/**
 	 * Send at least a message every 5s for keep alive reasons.
 	 */
@@ -145,7 +145,7 @@ export const enum ProtocolConstants {
 	/**
 	 * If there is no message received for 10 seconds, consider the connection closed...
 	 */
-	KeepAliveTimeoutTime = 10000, // 10 seconds
+	KeepAliveTimeoutTime = 20000, // 20 seconds
 	/**
 	 * If there is no reconnection within this time-frame, consider the connection permanently closed...
 	 */
@@ -284,8 +284,8 @@ class ProtocolWriter {
 
 	public write(msg: ProtocolMessage) {
 		if (this._isDisposed) {
-			console.warn(`Cannot write message in a disposed ProtocolWriter`);
-			console.warn(msg);
+			// ignore: there could be left-over promises which complete and then
+			// decide to write a response, etc...
 			return;
 		}
 		msg.writtenTime = Date.now();
diff --git a/src/vs/base/parts/ipc/common/ipc.ts b/src/vs/base/parts/ipc/common/ipc.ts
index c53a5cd49416..51a8185c6590 100644
--- a/src/vs/base/parts/ipc/common/ipc.ts
+++ b/src/vs/base/parts/ipc/common/ipc.ts
@@ -553,7 +553,7 @@ export class ChannelClient implements IChannelClient, IDisposable {
 			}
 		});
 
-		const handler: IHandler = (res: IRawEventFireResponse) => emitter.fire(res.data);
+		const handler: IHandler = (res: IRawResponse) => emitter.fire((res as IRawEventFireResponse).data);
 		this.handlers.set(id, handler);
 
 		return emitter.event;
diff --git a/src/vs/base/parts/ipc/node/ipc.net.ts b/src/vs/base/parts/ipc/node/ipc.net.ts
index 5a79572f419e..1a96d81731c1 100644
--- a/src/vs/base/parts/ipc/node/ipc.net.ts
+++ b/src/vs/base/parts/ipc/node/ipc.net.ts
@@ -56,7 +56,7 @@ export class NodeSocket implements ISocket {
 		// anyways and nodejs is already doing that for us:
 		// > https://nodejs.org/api/stream.html#stream_writable_write_chunk_encoding_callback
 		// > However, the false return value is only advisory and the writable stream will unconditionally
-		// > accept and buffer chunk even if it has not not been allowed to drain.
+		// > accept and buffer chunk even if it has not been allowed to drain.
 		this.socket.write(buffer.buffer);
 	}
 
diff --git a/src/vs/base/parts/ipc/node/ipc.ts b/src/vs/base/parts/ipc/node/ipc.ts
index 5ee86d2a262f..69678de891a0 100644
--- a/src/vs/base/parts/ipc/node/ipc.ts
+++ b/src/vs/base/parts/ipc/node/ipc.ts
@@ -92,7 +92,7 @@ export function createChannelSender(channel: IChannel, options?: IChannelSend
 	const disableMarshalling = options && options.disableMarshalling;
 
 	return new Proxy({}, {
-		get(_target, propKey, _receiver) {
+		get(_target: T, propKey: PropertyKey) {
 			if (typeof propKey === 'string') {
 
 				// Event
diff --git a/src/vs/base/parts/quickopen/browser/quickOpenModel.ts b/src/vs/base/parts/quickopen/browser/quickOpenModel.ts
index 5bf24732ca1d..366c4915e2fa 100644
--- a/src/vs/base/parts/quickopen/browser/quickOpenModel.ts
+++ b/src/vs/base/parts/quickopen/browser/quickOpenModel.ts
@@ -19,13 +19,14 @@ import { OS } from 'vs/base/common/platform';
 import { ResolvedKeybinding } from 'vs/base/common/keyCodes';
 import { IItemAccessor } from 'vs/base/parts/quickopen/common/quickOpenScorer';
 import { coalesce } from 'vs/base/common/arrays';
+import { IMatch } from 'vs/base/common/filters';
 
 export interface IContext {
 	event: any;
 	quickNavigateConfiguration: IQuickNavigateConfiguration;
 }
 
-export interface IHighlight {
+export interface IHighlight extends IMatch {
 	start: number;
 	end: number;
 }
@@ -461,7 +462,7 @@ class Renderer implements IRenderer {
 			options.title = entry.getTooltip();
 			options.descriptionTitle = entry.getDescriptionTooltip() || entry.getDescription(); // tooltip over description because it could overflow
 			options.descriptionMatches = descriptionHighlights || [];
-			data.label.setLabel(types.withNullAsUndefined(entry.getLabel()), entry.getDescription(), options);
+			data.label.setLabel(entry.getLabel() || '', entry.getDescription(), options);
 
 			// Meta
 			data.detail.set(entry.getDetail(), detailHighlights);
diff --git a/src/vs/base/parts/quickopen/browser/quickOpenWidget.ts b/src/vs/base/parts/quickopen/browser/quickOpenWidget.ts
index b65e1a39f550..6c0906bf940e 100644
--- a/src/vs/base/parts/quickopen/browser/quickOpenWidget.ts
+++ b/src/vs/base/parts/quickopen/browser/quickOpenWidget.ts
@@ -23,6 +23,7 @@ import { ScrollbarVisibility } from 'vs/base/common/scrollable';
 import { Color } from 'vs/base/common/color';
 import { mixin } from 'vs/base/common/objects';
 import { StandardMouseEvent, IMouseEvent } from 'vs/base/browser/mouseEvent';
+import { IThemable } from 'vs/base/common/styler';
 
 export interface IQuickOpenCallbacks {
 	onOk: () => void;
@@ -92,7 +93,7 @@ const defaultStyles = {
 
 const DEFAULT_INPUT_ARIA_LABEL = nls.localize('quickOpenAriaLabel', "Quick picker. Type to narrow down results.");
 
-export class QuickOpenWidget extends Disposable implements IModelProvider {
+export class QuickOpenWidget extends Disposable implements IModelProvider, IThemable {
 
 	private static readonly MAX_WIDTH = 600;			// Max total width of quick open widget
 	private static readonly MAX_ITEMS_HEIGHT = 20 * 22;	// Max height of item list below input field
@@ -313,6 +314,16 @@ export class QuickOpenWidget extends Disposable implements IModelProvider {
 
 				this.navigateInTree(keyboardEvent.keyCode);
 			}
+
+			// Support to open item with Enter still even in quick nav mode
+			else if (keyboardEvent.keyCode === KeyCode.Enter) {
+				DOM.EventHelper.stop(e, true);
+
+				const focus = this.tree.getFocus();
+				if (focus) {
+					this.elementSelected(focus, e);
+				}
+			}
 		}));
 
 		this._register(DOM.addDisposableListener(this.treeContainer, DOM.EventType.KEY_UP, e => {
@@ -326,7 +337,7 @@ export class QuickOpenWidget extends Disposable implements IModelProvider {
 
 			// Select element when keys are pressed that signal it
 			const quickNavKeys = this.quickNavigateConfiguration.keybindings;
-			const wasTriggerKeyPressed = keyCode === KeyCode.Enter || quickNavKeys.some(k => {
+			const wasTriggerKeyPressed = quickNavKeys.some(k => {
 				const [firstPart, chordPart] = k.getParts();
 				if (chordPart) {
 					return false;
diff --git a/src/vs/base/parts/storage/test/node/storage.test.ts b/src/vs/base/parts/storage/test/node/storage.test.ts
index d656c148168d..b244d1b415d8 100644
--- a/src/vs/base/parts/storage/test/node/storage.test.ts
+++ b/src/vs/base/parts/storage/test/node/storage.test.ts
@@ -294,7 +294,7 @@ suite('SQLite Storage Library', () => {
 		return set;
 	}
 
-	async function testDBBasics(path: string, logError?: (error: Error) => void) {
+	async function testDBBasics(path: string, logError?: (error: Error | string) => void) {
 		let options!: ISQLiteStorageDatabaseOptions;
 		if (logError) {
 			options = {
diff --git a/src/vs/base/parts/tree/browser/tree.css b/src/vs/base/parts/tree/browser/tree.css
index 405161811b37..3f28e544eeb6 100644
--- a/src/vs/base/parts/tree/browser/tree.css
+++ b/src/vs/base/parts/tree/browser/tree.css
@@ -6,12 +6,9 @@
 	height: 100%;
 	width: 100%;
 	white-space: nowrap;
+	user-select: none;
 	-webkit-user-select: none;
-	-khtml-user-select: none;
-	-moz-user-select: -moz-none;
 	-ms-user-select: none;
-	-o-user-select: none;
-	user-select: none;
 	position: relative;
 }
 
@@ -32,10 +29,7 @@
 }
 
 .monaco-tree .monaco-tree-rows > .monaco-tree-row {
-	-moz-box-sizing:	border-box;
-	-o-box-sizing:		border-box;
-	-ms-box-sizing:		border-box;
-	box-sizing:			border-box;
+	box-sizing:	border-box;
 	cursor: pointer;
 	overflow: hidden;
 	width: 100%;
diff --git a/src/vs/base/parts/tree/browser/treeView.ts b/src/vs/base/parts/tree/browser/treeView.ts
index 77664bff7192..c1e1efa29402 100644
--- a/src/vs/base/parts/tree/browser/treeView.ts
+++ b/src/vs/base/parts/tree/browser/treeView.ts
@@ -265,7 +265,7 @@ export class ViewItem implements IViewItem {
 			}
 
 			if (this.context.horizontalScrolling) {
-				this.element.style.width = 'fit-content';
+				this.element.style.width = Browser.isFirefox ? '-moz-fit-content' : 'fit-content';
 			}
 
 			try {
@@ -289,7 +289,7 @@ export class ViewItem implements IViewItem {
 
 		const style = window.getComputedStyle(this.element);
 		const paddingLeft = parseFloat(style.paddingLeft!);
-		this.element.style.width = 'fit-content';
+		this.element.style.width = Browser.isFirefox ? '-moz-fit-content' : 'fit-content';
 		this.width = DOM.getContentWidth(this.element) + paddingLeft;
 		this.element.style.width = '';
 	}
diff --git a/src/vs/base/test/browser/ui/splitview/splitview.test.ts b/src/vs/base/test/browser/ui/splitview/splitview.test.ts
index bb68a9d97bc2..b78f502ea19d 100644
--- a/src/vs/base/test/browser/ui/splitview/splitview.test.ts
+++ b/src/vs/base/test/browser/ui/splitview/splitview.test.ts
@@ -8,7 +8,7 @@ import { Emitter } from 'vs/base/common/event';
 import { SplitView, IView, Sizing, LayoutPriority } from 'vs/base/browser/ui/splitview/splitview';
 import { Sash, SashState } from 'vs/base/browser/ui/sash/sash';
 
-class TestView implements IView {
+class TestView implements IView {
 
 	private readonly _onDidChange = new Emitter();
 	readonly onDidChange = this._onDidChange.event;
@@ -43,7 +43,7 @@ class TestView implements IView {
 		assert(_minimumSize <= _maximumSize, 'splitview view minimum size must be <= maximum size');
 	}
 
-	layout(size: number, orthogonalSize: number | undefined): void {
+	layout(size: number, _offset: number, orthogonalSize: number | undefined): void {
 		this._size = size;
 		this._orthogonalSize = orthogonalSize;
 		this._onDidLayout.fire({ size, orthogonalSize });
@@ -527,11 +527,11 @@ suite('Splitview', () => {
 		view1.dispose();
 	});
 
-	test('orthogonal size propagates to views', () => {
+	test('context propagates to views', () => {
 		const view1 = new TestView(20, Number.POSITIVE_INFINITY);
 		const view2 = new TestView(20, Number.POSITIVE_INFINITY);
 		const view3 = new TestView(20, Number.POSITIVE_INFINITY, LayoutPriority.Low);
-		const splitview = new SplitView(container, { proportionalLayout: false });
+		const splitview = new SplitView(container, { proportionalLayout: false });
 		splitview.layout(200);
 
 		splitview.addView(view1, Sizing.Distribute);
diff --git a/src/vs/base/test/browser/ui/tree/asyncDataTree.test.ts b/src/vs/base/test/browser/ui/tree/asyncDataTree.test.ts
index 85fc0d82ac05..4ed0de0f1adb 100644
--- a/src/vs/base/test/browser/ui/tree/asyncDataTree.test.ts
+++ b/src/vs/base/test/browser/ui/tree/asyncDataTree.test.ts
@@ -12,19 +12,28 @@ import { timeout } from 'vs/base/common/async';
 
 interface Element {
 	id: string;
+	suffix?: string;
 	children?: Element[];
 }
 
-function find(elements: Element[] | undefined, id: string): Element {
-	while (elements) {
-		for (const element of elements) {
-			if (element.id === id) {
-				return element;
-			}
+function find(element: Element, id: string): Element | undefined {
+	if (element.id === id) {
+		return element;
+	}
+
+	if (!element.children) {
+		return undefined;
+	}
+
+	for (const child of element.children) {
+		const result = find(child, id);
+
+		if (result) {
+			return result;
 		}
 	}
 
-	throw new Error('element not found');
+	return undefined;
 }
 
 class Renderer implements ITreeRenderer {
@@ -33,7 +42,7 @@ class Renderer implements ITreeRenderer {
 		return container;
 	}
 	renderElement(element: ITreeNode, index: number, templateData: HTMLElement): void {
-		templateData.textContent = element.element.id;
+		templateData.textContent = element.element.id + (element.element.suffix || '');
 	}
 	disposeTemplate(templateData: HTMLElement): void {
 		// noop
@@ -65,7 +74,13 @@ class Model {
 	constructor(readonly root: Element) { }
 
 	get(id: string): Element {
-		return find(this.root.children, id);
+		const result = find(this.root, id);
+
+		if (!result) {
+			throw new Error('element not found');
+		}
+
+		return result;
 	}
 }
 
@@ -389,4 +404,36 @@ suite('AsyncDataTree', function () {
 		assert(!hasClass(twistie, 'collapsible'));
 		assert(!hasClass(twistie, 'collapsed'));
 	});
+
+	test('issues #84569, #82629 - rerender', async () => {
+		const container = document.createElement('div');
+		const model = new Model({
+			id: 'root',
+			children: [{
+				id: 'a',
+				children: [{
+					id: 'b',
+					suffix: '1'
+				}]
+			}]
+		});
+
+		const tree = new AsyncDataTree('test', container, new VirtualDelegate(), [new Renderer()], new DataSource(), { identityProvider: new IdentityProvider() });
+		tree.layout(200);
+
+		await tree.setInput(model.root);
+		await tree.expand(model.get('a'));
+		assert.deepEqual(Array.from(container.querySelectorAll('.monaco-list-row')).map(e => e.textContent), ['a', 'b1']);
+
+		const a = model.get('a');
+		const b = model.get('b');
+		a.children?.splice(0, 1, { id: 'b', suffix: '2' });
+
+		await Promise.all([
+			tree.updateChildren(a, true, true),
+			tree.updateChildren(b, true, true)
+		]);
+
+		assert.deepEqual(Array.from(container.querySelectorAll('.monaco-list-row')).map(e => e.textContent), ['a', 'b2']);
+	});
 });
diff --git a/src/vs/base/test/browser/ui/tree/indexTreeModel.test.ts b/src/vs/base/test/browser/ui/tree/indexTreeModel.test.ts
index 009f9ca9d635..bbfc063f2083 100644
--- a/src/vs/base/test/browser/ui/tree/indexTreeModel.test.ts
+++ b/src/vs/base/test/browser/ui/tree/indexTreeModel.test.ts
@@ -724,4 +724,35 @@ suite('IndexTreeModel', function () {
 		model.refilter();
 		assert.deepEqual(toArray(list), ['platinum']);
 	});
+
+	test('explicit hidden nodes should have renderNodeCount == 0, issue #83211', function () {
+		const list: ITreeNode[] = [];
+		let query = new RegExp('');
+		const filter = new class implements ITreeFilter {
+			filter(element: string): boolean {
+				return query.test(element);
+			}
+		};
+
+		const model = new IndexTreeModel('test', toSpliceable(list), 'root', { filter });
+
+		model.splice([0], 0, [
+			{ element: 'a', children: [{ element: 'aa' }] },
+			{ element: 'b', children: [{ element: 'bb' }] }
+		]);
+
+		assert.deepEqual(toArray(list), ['a', 'aa', 'b', 'bb']);
+		assert.deepEqual(model.getListIndex([0]), 0);
+		assert.deepEqual(model.getListIndex([0, 0]), 1);
+		assert.deepEqual(model.getListIndex([1]), 2);
+		assert.deepEqual(model.getListIndex([1, 0]), 3);
+
+		query = /b/;
+		model.refilter();
+		assert.deepEqual(toArray(list), ['b', 'bb']);
+		assert.deepEqual(model.getListIndex([0]), -1);
+		assert.deepEqual(model.getListIndex([0, 0]), -1);
+		assert.deepEqual(model.getListIndex([1]), 0);
+		assert.deepEqual(model.getListIndex([1, 0]), 1);
+	});
 });
diff --git a/src/vs/base/test/browser/ui/tree/objectTree.test.ts b/src/vs/base/test/browser/ui/tree/objectTree.test.ts
index d74830705182..3a5635c27d27 100644
--- a/src/vs/base/test/browser/ui/tree/objectTree.test.ts
+++ b/src/vs/base/test/browser/ui/tree/objectTree.test.ts
@@ -348,8 +348,6 @@ suite('CompressibleObjectTree', function () {
 		const tree = new CompressibleObjectTree('test', container, new Delegate(), [new Renderer()]);
 		tree.layout(200);
 
-		assert.equal(tree.isCompressionEnabled(), true);
-
 		tree.setChildren(null, Iterator.fromArray([
 			{
 				element: 1, children: Iterator.fromArray([{
@@ -367,11 +365,11 @@ suite('CompressibleObjectTree', function () {
 		let rows = toArray(container.querySelectorAll('.monaco-tl-contents')).map(row => row.textContent);
 		assert.deepEqual(rows, ['1/11/111', '1111', '1112', '1113']);
 
-		tree.setCompressionEnabled(false);
+		tree.updateOptions({ compressionEnabled: false });
 		rows = toArray(container.querySelectorAll('.monaco-tl-contents')).map(row => row.textContent);
 		assert.deepEqual(rows, ['1', '11', '111', '1111', '1112', '1113']);
 
-		tree.setCompressionEnabled(true);
+		tree.updateOptions({ compressionEnabled: true });
 		rows = toArray(container.querySelectorAll('.monaco-tl-contents')).map(row => row.textContent);
 		assert.deepEqual(rows, ['1/11/111', '1111', '1112', '1113']);
 	});
diff --git a/src/vs/base/test/common/event.test.ts b/src/vs/base/test/common/event.test.ts
index 7ae0a979d15f..71f299f82431 100644
--- a/src/vs/base/test/common/event.test.ts
+++ b/src/vs/base/test/common/event.test.ts
@@ -3,10 +3,11 @@
  *  Licensed under the Source EULA. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
 import * as assert from 'assert';
-import { Event, Emitter, EventBufferer, EventMultiplexer, AsyncEmitter, IWaitUntil, PauseableEmitter } from 'vs/base/common/event';
+import { Event, Emitter, EventBufferer, EventMultiplexer, IWaitUntil, PauseableEmitter, AsyncEmitter } from 'vs/base/common/event';
 import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle';
 import * as Errors from 'vs/base/common/errors';
 import { timeout } from 'vs/base/common/async';
+import { CancellationToken } from 'vs/base/common/cancellation';
 
 namespace Samples {
 
@@ -174,7 +175,7 @@ suite('Event', function () {
 	test('Debounce Event', function (done: () => void) {
 		let doc = new Samples.Document3();
 
-		let onDocDidChange = Event.debounce(doc.onDidChange, (prev: string[], cur) => {
+		let onDocDidChange = Event.debounce(doc.onDidChange, (prev: string[] | undefined, cur) => {
 			if (!prev) {
 				prev = [cur];
 			} else if (prev.indexOf(cur) < 0) {
@@ -272,11 +273,7 @@ suite('AsyncEmitter', function () {
 			assert.equal(typeof e.waitUntil, 'function');
 		});
 
-		emitter.fireAsync(thenables => ({
-			foo: true,
-			bar: 1,
-			waitUntil(t: Promise) { thenables.push(t); }
-		}));
+		emitter.fireAsync({ foo: true, bar: 1, }, CancellationToken.None);
 		emitter.dispose();
 	});
 
@@ -303,12 +300,7 @@ suite('AsyncEmitter', function () {
 			}));
 		});
 
-		await emitter.fireAsync(thenables => ({
-			foo: true,
-			waitUntil(t) {
-				thenables.push(t);
-			}
-		}));
+		await emitter.fireAsync({ foo: true }, CancellationToken.None);
 		assert.equal(globalState, 2);
 	});
 
@@ -324,12 +316,7 @@ suite('AsyncEmitter', function () {
 		emitter.event(e => {
 			e.waitUntil(timeout(10).then(async _ => {
 				if (e.foo === 1) {
-					await emitter.fireAsync(thenables => ({
-						foo: 2,
-						waitUntil(t) {
-							thenables.push(t);
-						}
-					}));
+					await emitter.fireAsync({ foo: 2 }, CancellationToken.None);
 					assert.deepEqual(events, [1, 2]);
 					done = true;
 				}
@@ -342,14 +329,40 @@ suite('AsyncEmitter', function () {
 			e.waitUntil(timeout(7));
 		});
 
-		await emitter.fireAsync(thenables => ({
-			foo: 1,
-			waitUntil(t) {
-				thenables.push(t);
-			}
-		}));
+		await emitter.fireAsync({ foo: 1 }, CancellationToken.None);
 		assert.ok(done);
 	});
+
+	test('catch errors', async function () {
+		const origErrorHandler = Errors.errorHandler.getUnexpectedErrorHandler();
+		Errors.setUnexpectedErrorHandler(() => null);
+
+		interface E extends IWaitUntil {
+			foo: boolean;
+		}
+
+		let globalState = 0;
+		let emitter = new AsyncEmitter();
+
+		emitter.event(e => {
+			globalState += 1;
+			e.waitUntil(new Promise((_r, reject) => reject(new Error())));
+		});
+
+		emitter.event(e => {
+			globalState += 1;
+			e.waitUntil(timeout(10));
+		});
+
+		await emitter.fireAsync({ foo: true }, CancellationToken.None).then(() => {
+			assert.equal(globalState, 2);
+		}).catch(e => {
+			console.log(e);
+			assert.ok(false);
+		});
+
+		Errors.setUnexpectedErrorHandler(origErrorHandler);
+	});
 });
 
 suite('PausableEmitter', function () {
diff --git a/src/vs/base/test/common/filters.test.ts b/src/vs/base/test/common/filters.test.ts
index dbd841b1d721..aeb862398420 100644
--- a/src/vs/base/test/common/filters.test.ts
+++ b/src/vs/base/test/common/filters.test.ts
@@ -498,4 +498,14 @@ suite('Filters', () => {
 			fuzzyScore
 		);
 	});
+
+	test('"Go to Symbol" with the exact method name doesn\'t work as expected #84787', function () {
+		const match = fuzzyScore(':get', ':get', 1, 'get', 'get', 0, true);
+		assert.ok(Boolean(match));
+	});
+
+	test('Suggestion is not highlighted #85826', function () {
+		assertMatches('SemanticTokens', 'SemanticTokensEdits', '^S^e^m^a^n^t^i^c^T^o^k^e^n^sEdits', fuzzyScore);
+		assertMatches('SemanticTokens', 'SemanticTokensEdits', '^S^e^m^a^n^t^i^c^T^o^k^e^n^sEdits', fuzzyScoreGracefulAggressive);
+	});
 });
diff --git a/src/vs/base/test/common/jsonEdit.test.ts b/src/vs/base/test/common/jsonEdit.test.ts
index 7ace5de2fb7a..d61996802fde 100644
--- a/src/vs/base/test/common/jsonEdit.test.ts
+++ b/src/vs/base/test/common/jsonEdit.test.ts
@@ -118,13 +118,43 @@ suite('JSON - edits', () => {
 		assertEdit(content, edits, '{\n  "x": "y"\n}');
 	});
 
-	test('insert item to empty array', () => {
+	test('insert item at 0', () => {
+		let content = '[\n  2,\n  3\n]';
+		let edits = setProperty(content, [0], 1, formatterOptions);
+		assertEdit(content, edits, '[\n  1,\n  2,\n  3\n]');
+	});
+
+	test('insert item at 0 in empty array', () => {
+		let content = '[\n]';
+		let edits = setProperty(content, [0], 1, formatterOptions);
+		assertEdit(content, edits, '[\n  1\n]');
+	});
+
+	test('insert item at an index', () => {
+		let content = '[\n  1,\n  3\n]';
+		let edits = setProperty(content, [1], 2, formatterOptions);
+		assertEdit(content, edits, '[\n  1,\n  2,\n  3\n]');
+	});
+
+	test('insert item at an index im empty array', () => {
+		let content = '[\n]';
+		let edits = setProperty(content, [1], 1, formatterOptions);
+		assertEdit(content, edits, '[\n  1\n]');
+	});
+
+	test('insert item at end index', () => {
+		let content = '[\n  1,\n  2\n]';
+		let edits = setProperty(content, [2], 3, formatterOptions);
+		assertEdit(content, edits, '[\n  1,\n  2,\n  3\n]');
+	});
+
+	test('insert item at end to empty array', () => {
 		let content = '[\n]';
 		let edits = setProperty(content, [-1], 'bar', formatterOptions);
 		assertEdit(content, edits, '[\n  "bar"\n]');
 	});
 
-	test('insert item', () => {
+	test('insert item at end', () => {
 		let content = '[\n  1,\n  2\n]';
 		let edits = setProperty(content, [-1], 'bar', formatterOptions);
 		assertEdit(content, edits, '[\n  1,\n  2,\n  "bar"\n]');
@@ -160,4 +190,4 @@ suite('JSON - edits', () => {
 		assertEdit(content, edits, '// This is a comment\n[\n  1,\n  "foo"\n]');
 	});
 
-});
\ No newline at end of file
+});
diff --git a/src/vs/base/test/common/stream.test.ts b/src/vs/base/test/common/stream.test.ts
new file mode 100644
index 000000000000..b08b8a68011e
--- /dev/null
+++ b/src/vs/base/test/common/stream.test.ts
@@ -0,0 +1,176 @@
+/*---------------------------------------------------------------------------------------------
+ *  Copyright (c) Microsoft Corporation. All rights reserved.
+ *  Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import * as assert from 'assert';
+import { isReadableStream, newWriteableStream, Readable, consumeReadable, consumeReadableWithLimit, consumeStream, ReadableStream, toStream, toReadable, transform, consumeStreamWithLimit } from 'vs/base/common/stream';
+
+suite('Stream', () => {
+
+	test('isReadableStream', () => {
+		assert.ok(!isReadableStream(Object.create(null)));
+		assert.ok(isReadableStream(newWriteableStream(d => d)));
+	});
+
+	test('WriteableStream', () => {
+		const stream = newWriteableStream(strings => strings.join());
+
+		let error = false;
+		stream.on('error', e => {
+			error = true;
+		});
+
+		let end = false;
+		stream.on('end', () => {
+			end = true;
+		});
+
+		stream.write('Hello');
+
+		const chunks: string[] = [];
+		stream.on('data', data => {
+			chunks.push(data);
+		});
+
+		assert.equal(chunks[0], 'Hello');
+
+		stream.write('World');
+		assert.equal(chunks[1], 'World');
+
+		assert.equal(error, false);
+		assert.equal(end, false);
+
+		stream.pause();
+		stream.write('1');
+		stream.write('2');
+		stream.write('3');
+
+		assert.equal(chunks.length, 2);
+
+		stream.resume();
+
+		assert.equal(chunks.length, 3);
+		assert.equal(chunks[2], '1,2,3');
+
+		stream.error(new Error());
+		assert.equal(error, true);
+
+		stream.end('Final Bit');
+		assert.equal(chunks.length, 4);
+		assert.equal(chunks[3], 'Final Bit');
+
+		stream.destroy();
+
+		stream.write('Unexpected');
+		assert.equal(chunks.length, 4);
+	});
+
+	test('consumeReadable', () => {
+		const readable = arrayToReadable(['1', '2', '3', '4', '5']);
+		const consumed = consumeReadable(readable, strings => strings.join());
+		assert.equal(consumed, '1,2,3,4,5');
+	});
+
+	test('consumeReadableWithLimit', () => {
+		for (let i = 0; i < 5; i++) {
+			const readable = arrayToReadable(['1', '2', '3', '4', '5']);
+
+			const consumedOrReadable = consumeReadableWithLimit(readable, strings => strings.join(), i);
+			if (typeof consumedOrReadable === 'string') {
+				assert.fail('Unexpected result');
+			} else {
+				const consumed = consumeReadable(consumedOrReadable, strings => strings.join());
+				assert.equal(consumed, '1,2,3,4,5');
+			}
+		}
+
+		let readable = arrayToReadable(['1', '2', '3', '4', '5']);
+		let consumedOrReadable = consumeReadableWithLimit(readable, strings => strings.join(), 5);
+		assert.equal(consumedOrReadable, '1,2,3,4,5');
+
+		readable = arrayToReadable(['1', '2', '3', '4', '5']);
+		consumedOrReadable = consumeReadableWithLimit(readable, strings => strings.join(), 6);
+		assert.equal(consumedOrReadable, '1,2,3,4,5');
+	});
+
+	function arrayToReadable(array: T[]): Readable {
+		return {
+			read: () => array.shift() || null
+		};
+	}
+
+	function readableToStream(readable: Readable): ReadableStream {
+		const stream = newWriteableStream(strings => strings.join());
+
+		// Simulate async behavior
+		setTimeout(() => {
+			let chunk: string | null = null;
+			while ((chunk = readable.read()) !== null) {
+				stream.write(chunk);
+			}
+
+			stream.end();
+		}, 0);
+
+		return stream;
+	}
+
+	test('consumeStream', async () => {
+		const stream = readableToStream(arrayToReadable(['1', '2', '3', '4', '5']));
+		const consumed = await consumeStream(stream, strings => strings.join());
+		assert.equal(consumed, '1,2,3,4,5');
+	});
+
+	test('consumeStreamWithLimit', async () => {
+		for (let i = 0; i < 5; i++) {
+			const readable = readableToStream(arrayToReadable(['1', '2', '3', '4', '5']));
+
+			const consumedOrStream = await consumeStreamWithLimit(readable, strings => strings.join(), i);
+			if (typeof consumedOrStream === 'string') {
+				assert.fail('Unexpected result');
+			} else {
+				const consumed = await consumeStream(consumedOrStream, strings => strings.join());
+				assert.equal(consumed, '1,2,3,4,5');
+			}
+		}
+
+		let stream = readableToStream(arrayToReadable(['1', '2', '3', '4', '5']));
+		let consumedOrStream = await consumeStreamWithLimit(stream, strings => strings.join(), 5);
+		assert.equal(consumedOrStream, '1,2,3,4,5');
+
+		stream = readableToStream(arrayToReadable(['1', '2', '3', '4', '5']));
+		consumedOrStream = await consumeStreamWithLimit(stream, strings => strings.join(), 6);
+		assert.equal(consumedOrStream, '1,2,3,4,5');
+	});
+
+	test('toStream', async () => {
+		const stream = toStream('1,2,3,4,5', strings => strings.join());
+		const consumed = await consumeStream(stream, strings => strings.join());
+		assert.equal(consumed, '1,2,3,4,5');
+	});
+
+	test('toReadable', async () => {
+		const readable = toReadable('1,2,3,4,5');
+		const consumed = await consumeReadable(readable, strings => strings.join());
+		assert.equal(consumed, '1,2,3,4,5');
+	});
+
+	test('transform', async () => {
+		const source = newWriteableStream(strings => strings.join());
+
+		const result = transform(source, { data: string => string + string }, strings => strings.join());
+
+		// Simulate async behavior
+		setTimeout(() => {
+			source.write('1');
+			source.write('2');
+			source.write('3');
+			source.write('4');
+			source.end('5');
+		}, 0);
+
+		const consumed = await consumeStream(result, strings => strings.join());
+		assert.equal(consumed, '11,22,33,44,55');
+	});
+});
diff --git a/src/vs/base/test/common/strings.test.ts b/src/vs/base/test/common/strings.test.ts
index 1a33054e5fa7..640e4732b110 100644
--- a/src/vs/base/test/common/strings.test.ts
+++ b/src/vs/base/test/common/strings.test.ts
@@ -458,4 +458,42 @@ suite('Strings', () => {
 		assert.equal(strings.removeAccents('ñice'), 'nice');
 		assert.equal(strings.removeAccents('Å„ice'), 'nice');
 	});
+
+	test('encodeUTF8', function () {
+		function assertEncodeUTF8(str: string, expected: number[]): void {
+			const actual = strings.encodeUTF8(str);
+			const actualArr: number[] = [];
+			for (let offset = 0; offset < actual.byteLength; offset++) {
+				actualArr[offset] = actual[offset];
+			}
+			assert.deepEqual(actualArr, expected);
+		}
+
+		function assertDecodeUTF8(data: number[], expected: string): void {
+			const actual = strings.decodeUTF8(new Uint8Array(data));
+			assert.deepEqual(actual, expected);
+		}
+
+		function assertEncodeDecodeUTF8(str: string, buff: number[]): void {
+			assertEncodeUTF8(str, buff);
+			assertDecodeUTF8(buff, str);
+		}
+
+		assertEncodeDecodeUTF8('\u0000', [0]);
+		assertEncodeDecodeUTF8('!', [33]);
+		assertEncodeDecodeUTF8('\u007F', [127]);
+		assertEncodeDecodeUTF8('\u0080', [194, 128]);
+		assertEncodeDecodeUTF8('Æ', [198, 157]);
+		assertEncodeDecodeUTF8('\u07FF', [223, 191]);
+		assertEncodeDecodeUTF8('\u0800', [224, 160, 128]);
+		assertEncodeDecodeUTF8('ஂ', [224, 174, 130]);
+		assertEncodeDecodeUTF8('\uffff', [239, 191, 191]);
+		assertEncodeDecodeUTF8('\u10000', [225, 128, 128, 48]);
+		assertEncodeDecodeUTF8('ðŸ§', [240, 159, 167, 157]);
+
+	});
+
+	test('getGraphemeBreakType', () => {
+		assert.equal(strings.getGraphemeBreakType(0xBC1), strings.GraphemeBreakType.SpacingMark);
+	});
 });
diff --git a/src/vs/base/test/common/uri.test.ts b/src/vs/base/test/common/uri.test.ts
index a43e1cebfacc..a85930d3ef45 100644
--- a/src/vs/base/test/common/uri.test.ts
+++ b/src/vs/base/test/common/uri.test.ts
@@ -439,6 +439,10 @@ suite('URI', () => {
 		assert.equal(uri.path, uri2.path);
 	});
 
+	test('Unable to open \'%A0.txt\': URI malformed #76506', function () {
+		assert.equal(URI.parse('file://some/%.txt'), 'file://some/%25.txt');
+		assert.equal(URI.parse('file://some/%A0.txt'), 'file://some/%25A0.txt');
+	});
 
 	test('Links in markdown are broken if url contains encoded parameters #79474', function () {
 		this.skip();
diff --git a/src/vs/base/test/node/buffer.test.ts b/src/vs/base/test/node/buffer.test.ts
index 2cc4c5ee9301..f7d465406e26 100644
--- a/src/vs/base/test/node/buffer.test.ts
+++ b/src/vs/base/test/node/buffer.test.ts
@@ -4,7 +4,7 @@
  *--------------------------------------------------------------------------------------------*/
 
 import * as assert from 'assert';
-import { VSBuffer, bufferToReadable, readableToBuffer, bufferToStream, streamToBuffer, writeableBufferStream } from 'vs/base/common/buffer';
+import { VSBuffer, bufferToReadable, readableToBuffer, bufferToStream, streamToBuffer, newWriteableBufferStream } from 'vs/base/common/buffer';
 import { timeout } from 'vs/base/common/async';
 
 suite('Buffer', () => {
@@ -30,7 +30,7 @@ suite('Buffer', () => {
 	});
 
 	test('bufferWriteableStream - basics (no error)', async () => {
-		const stream = writeableBufferStream();
+		const stream = newWriteableBufferStream();
 
 		let chunks: VSBuffer[] = [];
 		stream.on('data', data => {
@@ -60,7 +60,7 @@ suite('Buffer', () => {
 	});
 
 	test('bufferWriteableStream - basics (error)', async () => {
-		const stream = writeableBufferStream();
+		const stream = newWriteableBufferStream();
 
 		let chunks: VSBuffer[] = [];
 		stream.on('data', data => {
@@ -89,7 +89,7 @@ suite('Buffer', () => {
 	});
 
 	test('bufferWriteableStream - buffers data when no listener', async () => {
-		const stream = writeableBufferStream();
+		const stream = newWriteableBufferStream();
 
 		await timeout(0);
 		stream.write(VSBuffer.fromString('Hello'));
@@ -118,7 +118,7 @@ suite('Buffer', () => {
 	});
 
 	test('bufferWriteableStream - buffers errors when no listener', async () => {
-		const stream = writeableBufferStream();
+		const stream = newWriteableBufferStream();
 
 		await timeout(0);
 		stream.write(VSBuffer.fromString('Hello'));
@@ -149,7 +149,7 @@ suite('Buffer', () => {
 	});
 
 	test('bufferWriteableStream - buffers end when no listener', async () => {
-		const stream = writeableBufferStream();
+		const stream = newWriteableBufferStream();
 
 		await timeout(0);
 		stream.write(VSBuffer.fromString('Hello'));
@@ -178,7 +178,7 @@ suite('Buffer', () => {
 	});
 
 	test('bufferWriteableStream - nothing happens after end()', async () => {
-		const stream = writeableBufferStream();
+		const stream = newWriteableBufferStream();
 
 		let chunks: VSBuffer[] = [];
 		stream.on('data', data => {
@@ -222,7 +222,7 @@ suite('Buffer', () => {
 	});
 
 	test('bufferWriteableStream - pause/resume (simple)', async () => {
-		const stream = writeableBufferStream();
+		const stream = newWriteableBufferStream();
 
 		let chunks: VSBuffer[] = [];
 		stream.on('data', data => {
@@ -259,7 +259,7 @@ suite('Buffer', () => {
 	});
 
 	test('bufferWriteableStream - pause/resume (pause after first write)', async () => {
-		const stream = writeableBufferStream();
+		const stream = newWriteableBufferStream();
 
 		let chunks: VSBuffer[] = [];
 		stream.on('data', data => {
@@ -299,7 +299,7 @@ suite('Buffer', () => {
 	});
 
 	test('bufferWriteableStream - pause/resume (error)', async () => {
-		const stream = writeableBufferStream();
+		const stream = newWriteableBufferStream();
 
 		let chunks: VSBuffer[] = [];
 		stream.on('data', data => {
@@ -336,7 +336,7 @@ suite('Buffer', () => {
 	});
 
 	test('bufferWriteableStream - destroy', async () => {
-		const stream = writeableBufferStream();
+		const stream = newWriteableBufferStream();
 
 		let chunks: VSBuffer[] = [];
 		stream.on('data', data => {
diff --git a/src/vs/base/test/node/encoding/encoding.test.ts b/src/vs/base/test/node/encoding/encoding.test.ts
index a6c0399e3fd8..9bceda47ec58 100644
--- a/src/vs/base/test/node/encoding/encoding.test.ts
+++ b/src/vs/base/test/node/encoding/encoding.test.ts
@@ -9,7 +9,7 @@ import * as encoding from 'vs/base/node/encoding';
 import { Readable } from 'stream';
 import { getPathFromAmdModule } from 'vs/base/common/amd';
 
-export async function detectEncodingByBOM(file: string): Promise {
+export async function detectEncodingByBOM(file: string): Promise {
 	try {
 		const { buffer, bytesRead } = await readExactlyByFile(file, 3);
 
@@ -86,7 +86,7 @@ suite('Encoding', () => {
 		const file = getPathFromAmdModule(require, './fixtures/some_utf8.css');
 
 		const detectedEncoding = await detectEncodingByBOM(file);
-		assert.equal(detectedEncoding, 'utf8');
+		assert.equal(detectedEncoding, 'utf8bom');
 	});
 
 	test('detectBOM UTF-16 LE', async () => {
@@ -189,6 +189,20 @@ suite('Encoding', () => {
 		assert.equal(mimes.seemsBinary, false);
 	});
 
+	test('autoGuessEncoding (UTF8)', async function () {
+		const file = getPathFromAmdModule(require, './fixtures/some_file.css');
+		const buffer = await readExactlyByFile(file, 512 * 8);
+		const mimes = await encoding.detectEncodingFromBuffer(buffer, true);
+		assert.equal(mimes.encoding, 'utf8');
+	});
+
+	test('autoGuessEncoding (ASCII)', async function () {
+		const file = getPathFromAmdModule(require, './fixtures/some_ansi.css');
+		const buffer = await readExactlyByFile(file, 512 * 8);
+		const mimes = await encoding.detectEncodingFromBuffer(buffer, true);
+		assert.equal(mimes.encoding, null);
+	});
+
 	test('autoGuessEncoding (ShiftJIS)', async function () {
 		const file = getPathFromAmdModule(require, './fixtures/some.shiftjis.txt');
 		const buffer = await readExactlyByFile(file, 512 * 8);
diff --git a/src/vs/base/test/node/encoding/fixtures/some_file.css b/src/vs/base/test/node/encoding/fixtures/some_file.css
new file mode 100644
index 000000000000..35b4fcf5abcc
--- /dev/null
+++ b/src/vs/base/test/node/encoding/fixtures/some_file.css
@@ -0,0 +1,42 @@
+/*---------------------------------------------------------------------------------------------
+ *  Copyright (c) Microsoft Corporation. All rights reserved.
+ *  Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+/*----------------------------------------------------------
+The base color for this template is #5c87b2. If you'd like
+to use a different color start by replacing all instances of
+#5c87b2 with your new color.
+
+öäüßßß
+----------------------------------------------------------*/
+body
+{
+    background-color: #5c87b2;
+    font-size: .75em;
+    font-family: Segoe UI, Verdana, Helvetica, Sans-Serif;
+    margin: 8px;
+    padding: 0;
+    color: #696969;
+}
+
+h1, h2, h3, h4, h5, h6
+{
+    color: #000;
+    font-size: 40px;
+    margin: 0px;
+}
+
+textarea
+{
+   font-family: Consolas
+}
+
+#results
+{
+    margin-top: 2em;
+    margin-left: 2em;
+    color: black;
+    font-size: medium;
+}
+
diff --git a/src/vs/base/test/node/encoding/fixtures/some_utf16be.css b/src/vs/base/test/node/encoding/fixtures/some_utf16be.css
index 30c3b9d38ba3e73ef884db9b587be76fe28d751f..894438140007df4e2ed5e3e3fbce4f54e5bf5f02 100644
GIT binary patch
delta 44
wcmZqRp1{3fJ)%xXjGLIa6&QFKxEQKI {
 	// 	let patterns = [
 	// 		'{**/*.cs,**/*.json,**/*.csproj,**/*.sln}',
 	// 		'{**/*.cs,**/*.csproj,**/*.sln}',
-	// 		'{**/*.ts,**/*.tsx,**/*.js,**/*.jsx,**/*.es6,**/*.mjs}',
+	// 		'{**/*.ts,**/*.tsx,**/*.js,**/*.jsx,**/*.es6,**/*.mjs,**/*.cjs}',
 	// 		'**/*.go',
 	// 		'{**/*.ps,**/*.ps1}',
 	// 		'{**/*.c,**/*.cpp,**/*.h}',
 	// 		'{**/*.fsx,**/*.fsi,**/*.fs,**/*.ml,**/*.mli}',
-	// 		'{**/*.js,**/*.jsx,**/*.es6,**/*.mjs}',
+	// 		'{**/*.js,**/*.jsx,**/*.es6,**/*.mjs,**/*.cjs}',
 	// 		'{**/*.ts,**/*.tsx}',
 	// 		'{**/*.php}',
 	// 		'{**/*.php}',
@@ -1015,4 +1015,4 @@ suite('Glob', () => {
 			assertNoGlobMatch(p, '/DNXConsoleApp/foo/Program.cs');
 		}
 	});
-});
\ No newline at end of file
+});
diff --git a/src/vs/base/test/node/pfs/pfs.test.ts b/src/vs/base/test/node/pfs/pfs.test.ts
index ab9bf356b76c..69613608415f 100644
--- a/src/vs/base/test/node/pfs/pfs.test.ts
+++ b/src/vs/base/test/node/pfs/pfs.test.ts
@@ -12,7 +12,6 @@ import * as uuid from 'vs/base/common/uuid';
 import * as pfs from 'vs/base/node/pfs';
 import { timeout } from 'vs/base/common/async';
 import { getPathFromAmdModule } from 'vs/base/common/amd';
-import { CancellationTokenSource } from 'vs/base/common/cancellation';
 import { isWindows, isLinux } from 'vs/base/common/platform';
 import { canNormalize } from 'vs/base/common/normalization';
 import { VSBuffer } from 'vs/base/common/buffer';
@@ -50,7 +49,13 @@ function toReadable(value: string, throwError?: boolean): Readable {
 	});
 }
 
-suite('PFS', () => {
+suite('PFS', function () {
+
+	// Given issues such as https://github.com/microsoft/vscode/issues/84066
+	// we see random test failures when accessing the native file system. To
+	// diagnose further, we retry node.js file access tests up to 3 times to
+	// rule out any random disk issue.
+	this.retries(3);
 
 	test('writeFile', async () => {
 		const id = uuid.generateUuid();
@@ -253,7 +258,7 @@ suite('PFS', () => {
 		}
 		catch (error) {
 			assert.fail(error);
-			return Promise.reject(error);
+			throw error;
 		}
 	});
 
@@ -306,23 +311,6 @@ suite('PFS', () => {
 		return pfs.rimraf(parentDir, pfs.RimRafMode.MOVE);
 	});
 
-	test('mkdirp cancellation', async () => {
-		const id = uuid.generateUuid();
-		const parentDir = path.join(os.tmpdir(), 'vsctests', id);
-		const newDir = path.join(parentDir, 'pfs', id);
-
-		const source = new CancellationTokenSource();
-
-		const mkdirpPromise = pfs.mkdirp(newDir, 493, source.token);
-		source.cancel();
-
-		await mkdirpPromise;
-
-		assert.ok(!fs.existsSync(newDir));
-
-		return pfs.rimraf(parentDir, pfs.RimRafMode.MOVE);
-	});
-
 	test('readDirsInDir', async () => {
 		const id = uuid.generateUuid();
 		const parentDir = path.join(os.tmpdir(), 'vsctests', id);
@@ -525,7 +513,7 @@ suite('PFS', () => {
 		}
 
 		if (!expectedError || (expectedError).code !== 'EISDIR') {
-			return Promise.reject(new Error('Expected EISDIR error for writing to folder but got: ' + (expectedError ? (expectedError).code : 'no error')));
+			throw new Error('Expected EISDIR error for writing to folder but got: ' + (expectedError ? (expectedError).code : 'no error'));
 		}
 
 		// verify that the stream is still consumable (for https://github.com/Microsoft/vscode/issues/42542)
@@ -551,7 +539,7 @@ suite('PFS', () => {
 		}
 
 		if (!expectedError || expectedError.message !== readError) {
-			return Promise.reject(new Error('Expected error for writing to folder'));
+			throw new Error('Expected error for writing to folder');
 		}
 
 		await pfs.rimraf(parentDir);
@@ -582,7 +570,7 @@ suite('PFS', () => {
 		}
 
 		if (!expectedError || !((expectedError).code !== 'EACCES' || (expectedError).code !== 'EPERM')) {
-			return Promise.reject(new Error('Expected EACCES/EPERM error for writing to folder but got: ' + (expectedError ? (expectedError).code : 'no error')));
+			throw new Error('Expected EACCES/EPERM error for writing to folder but got: ' + (expectedError ? (expectedError).code : 'no error'));
 		}
 
 		await pfs.rimraf(parentDir);
@@ -609,7 +597,7 @@ suite('PFS', () => {
 		}
 
 		if (!expectedError) {
-			return Promise.reject(new Error('Expected error for writing to folder'));
+			throw new Error('Expected error for writing to folder');
 		}
 
 		await pfs.rimraf(parentDir);
diff --git a/src/vs/base/node/test/fixtures/extract.zip b/src/vs/base/test/node/zip/fixtures/extract.zip
similarity index 100%
rename from src/vs/base/node/test/fixtures/extract.zip
rename to src/vs/base/test/node/zip/fixtures/extract.zip
diff --git a/src/vs/base/node/test/zip.test.ts b/src/vs/base/test/node/zip/zip.test.ts
similarity index 100%
rename from src/vs/base/node/test/zip.test.ts
rename to src/vs/base/test/node/zip/zip.test.ts
diff --git a/src/vs/code/browser/workbench/workbench-dev.html b/src/vs/code/browser/workbench/workbench-dev.html
index 7ae960dbc913..af6317d035f8 100644
--- a/src/vs/code/browser/workbench/workbench-dev.html
+++ b/src/vs/code/browser/workbench/workbench-dev.html
@@ -32,6 +32,7 @@
 				'xterm': `${window.location.origin}/static/remote/web/node_modules/xterm/lib/xterm.js`,
 				'xterm-addon-search': `${window.location.origin}/static/remote/web/node_modules/xterm-addon-search/lib/xterm-addon-search.js`,
 				'xterm-addon-web-links': `${window.location.origin}/static/remote/web/node_modules/xterm-addon-web-links/lib/xterm-addon-web-links.js`,
+				'xterm-addon-webgl': `${window.location.origin}/static/remote/web/node_modules/xterm-addon-webgl/lib/xterm-addon-webgl.js`,
 				'semver-umd': `${window.location.origin}/static/remote/web/node_modules/semver-umd/lib/semver-umd.js`,
 			}
 		};
diff --git a/src/vs/code/browser/workbench/workbench.html b/src/vs/code/browser/workbench/workbench.html
index dbcb8e490fef..8a6a9f54e673 100644
--- a/src/vs/code/browser/workbench/workbench.html
+++ b/src/vs/code/browser/workbench/workbench.html
@@ -36,6 +36,7 @@
 				'xterm': `${window.location.origin}/static/node_modules/xterm/lib/xterm.js`,
 				'xterm-addon-search': `${window.location.origin}/static/node_modules/xterm-addon-search/lib/xterm-addon-search.js`,
 				'xterm-addon-web-links': `${window.location.origin}/static/node_modules/xterm-addon-web-links/lib/xterm-addon-web-links.js`,
+				'xterm-addon-webgl': `${window.location.origin}/static/node_modules/xterm-addon-webgl/lib/xterm-addon-webgl.js`,
 				'semver-umd': `${window.location.origin}/static/node_modules/semver-umd/lib/semver-umd.js`,
 			}
 		};
diff --git a/src/vs/code/browser/workbench/workbench.ts b/src/vs/code/browser/workbench/workbench.ts
index ac95d50d3fe1..4864b9255e41 100644
--- a/src/vs/code/browser/workbench/workbench.ts
+++ b/src/vs/code/browser/workbench/workbench.ts
@@ -277,13 +277,22 @@ class WorkspaceProvider implements IWorkspaceProvider {
 
 (function () {
 
-	// Find config element in DOM
+	// Find config by checking for DOM
 	const configElement = document.getElementById('vscode-workbench-web-configuration');
 	const configElementAttribute = configElement ? configElement.getAttribute('data-settings') : undefined;
 	if (!configElement || !configElementAttribute) {
 		throw new Error('Missing web configuration element');
 	}
 
+	const config: IWorkbenchConstructionOptions & { folderUri?: UriComponents, workspaceUri?: UriComponents } = JSON.parse(configElementAttribute);
+
+	// Revive static extension locations
+	if (Array.isArray(config.staticExtensions)) {
+		config.staticExtensions.forEach(extension => {
+			extension.extensionLocation = URI.revive(extension.extensionLocation);
+		});
+	}
+
 	// Find workspace to open and payload
 	let foundWorkspace = false;
 	let workspace: IWorkspace;
@@ -319,27 +328,21 @@ class WorkspaceProvider implements IWorkspaceProvider {
 	});
 
 	// If no workspace is provided through the URL, check for config attribute from server
-	const options: IWorkbenchConstructionOptions & { folderUri?: UriComponents, workspaceUri?: UriComponents } = JSON.parse(configElementAttribute);
 	if (!foundWorkspace) {
-		if (options.folderUri) {
-			workspace = { folderUri: URI.revive(options.folderUri) };
-		} else if (options.workspaceUri) {
-			workspace = { workspaceUri: URI.revive(options.workspaceUri) };
+		if (config.folderUri) {
+			workspace = { folderUri: URI.revive(config.folderUri) };
+		} else if (config.workspaceUri) {
+			workspace = { workspaceUri: URI.revive(config.workspaceUri) };
 		} else {
 			workspace = undefined;
 		}
 	}
 
-	options.workspaceProvider = new WorkspaceProvider(workspace, payload);
-	options.urlCallbackProvider = new PollingURLCallbackProvider();
-	options.credentialsProvider = new LocalStorageCredentialsProvider();
-
-	if (Array.isArray(options.staticExtensions)) {
-		options.staticExtensions.forEach(extension => {
-			extension.extensionLocation = URI.revive(extension.extensionLocation);
-		});
-	}
-
 	// Finally create workbench
-	create(document.body, options);
+	create(document.body, {
+		...config,
+		workspaceProvider: new WorkspaceProvider(workspace, payload),
+		urlCallbackProvider: new PollingURLCallbackProvider(),
+		credentialsProvider: new LocalStorageCredentialsProvider()
+	});
 })();
diff --git a/src/vs/code/electron-browser/issue/issueReporterUtil.ts b/src/vs/code/common/issue/issueReporterUtil.ts
similarity index 100%
rename from src/vs/code/electron-browser/issue/issueReporterUtil.ts
rename to src/vs/code/common/issue/issueReporterUtil.ts
diff --git a/src/vs/code/electron-browser/issue/issueReporterMain.ts b/src/vs/code/electron-browser/issue/issueReporterMain.ts
index ffb9fdac5f8d..381e7ecf8ac5 100644
--- a/src/vs/code/electron-browser/issue/issueReporterMain.ts
+++ b/src/vs/code/electron-browser/issue/issueReporterMain.ts
@@ -35,7 +35,7 @@ import BaseHtml from 'vs/code/electron-browser/issue/issueReporterPage';
 import { LoggerChannelClient, FollowerLogService } from 'vs/platform/log/common/logIpc';
 import { ILogService, getLogLevel } from 'vs/platform/log/common/log';
 import { CodiconLabel } from 'vs/base/browser/ui/codiconLabel/codiconLabel';
-import { normalizeGitHubUrl } from 'vs/code/electron-browser/issue/issueReporterUtil';
+import { normalizeGitHubUrl } from 'vs/code/common/issue/issueReporterUtil';
 import { Button } from 'vs/base/browser/ui/button/button';
 import { SystemInfo, isRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnostics';
 import { SpdLogService } from 'vs/platform/log/node/spdlogService';
@@ -345,8 +345,8 @@ export class IssueReporter extends Disposable {
 
 		const showInfoElements = document.getElementsByClassName('showInfo');
 		for (let i = 0; i < showInfoElements.length; i++) {
-			const showInfo = showInfoElements.item(i);
-			showInfo!.addEventListener('click', (e: MouseEvent) => {
+			const showInfo = showInfoElements.item(i)!;
+			(showInfo as HTMLAnchorElement).addEventListener('click', (e: MouseEvent) => {
 				e.preventDefault();
 				const label = (e.target);
 				if (label) {
@@ -432,9 +432,9 @@ export class IssueReporter extends Disposable {
 			sendWorkbenchCommand('workbench.action.reloadWindowWithExtensionsDisabled');
 		});
 
-		this.addEventListener('disableExtensions', 'keydown', (e: KeyboardEvent) => {
+		this.addEventListener('disableExtensions', 'keydown', (e: Event) => {
 			e.stopPropagation();
-			if (e.keyCode === 13 || e.keyCode === 32) {
+			if ((e as KeyboardEvent).keyCode === 13 || (e as KeyboardEvent).keyCode === 32) {
 				sendWorkbenchCommand('workbench.extensions.action.disableAll');
 				sendWorkbenchCommand('workbench.action.reloadWindow');
 			}
@@ -1120,16 +1120,6 @@ export class IssueReporter extends Disposable {
 		if (element) {
 			return element;
 		} else {
-			const error = new Error(`${elementId} not found.`);
-			this.logService.error(error);
-			type IssueReporterGetElementErrorClassification = {
-				message: { classification: 'CallstackOrException', purpose: 'PerformanceAndHealth' };
-			};
-			type IssueReporterGetElementErrorEvent = {
-				message: string;
-			};
-			this.telemetryService.publicLog2('issueReporterGetElementError', { message: error.message });
-
 			return undefined;
 		}
 	}
diff --git a/src/vs/code/electron-browser/issue/test/testReporterModel.test.ts b/src/vs/code/electron-browser/issue/test/testReporterModel.test.ts
index 678b54455e8e..bf6bdc8add39 100644
--- a/src/vs/code/electron-browser/issue/test/testReporterModel.test.ts
+++ b/src/vs/code/electron-browser/issue/test/testReporterModel.test.ts
@@ -5,7 +5,7 @@
 
 import * as assert from 'assert';
 import { IssueReporterModel } from 'vs/code/electron-browser/issue/issueReporterModel';
-import { normalizeGitHubUrl } from 'vs/code/electron-browser/issue/issueReporterUtil';
+import { normalizeGitHubUrl } from 'vs/code/common/issue/issueReporterUtil';
 import { IssueType } from 'vs/platform/issue/node/issue';
 
 suite('IssueReporter', () => {
diff --git a/src/vs/code/electron-browser/processExplorer/media/processExplorer.css b/src/vs/code/electron-browser/processExplorer/media/processExplorer.css
index 3e3acaae00be..2d6476c18d65 100644
--- a/src/vs/code/electron-browser/processExplorer/media/processExplorer.css
+++ b/src/vs/code/electron-browser/processExplorer/media/processExplorer.css
@@ -29,8 +29,6 @@ body {
 	padding: 0;
 	height: 100%;
 	width: 100%;
-	-webkit-touch-callout: none;
-	-webkit-user-select: none;
 	user-select: none;
 	color: #cccccc;
 }
diff --git a/src/vs/code/electron-browser/proxy/auth.html b/src/vs/code/electron-browser/proxy/auth.html
index a6b932662ae9..d02876abb940 100644
--- a/src/vs/code/electron-browser/proxy/auth.html
+++ b/src/vs/code/electron-browser/proxy/auth.html
@@ -12,8 +12,6 @@
 			height: 100%;
 			width: 100%;
 			overflow: hidden;
-			-webkit-touch-callout: none;
-			-webkit-user-select: none;
 			user-select: none;
 		}
 
@@ -117,4 +115,4 @@ 

- \ No newline at end of file + diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index 6b2be413a439..ad4689db4449 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -50,8 +50,8 @@ import { IFileService } from 'vs/platform/files/common/files'; import { DiskFileSystemProvider } from 'vs/platform/files/electron-browser/diskFileSystemProvider'; import { Schemas } from 'vs/base/common/network'; import { IProductService } from 'vs/platform/product/common/productService'; -import { IUserDataSyncService, IUserDataSyncStoreService, ISettingsMergeService, registerConfiguration, IUserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSync'; -import { UserDataSyncService, UserDataAutoSync } from 'vs/platform/userDataSync/common/userDataSyncService'; +import { IUserDataSyncService, IUserDataSyncStoreService, ISettingsMergeService, registerConfiguration, IUserDataSyncLogService, IUserDataSyncUtilService } from 'vs/platform/userDataSync/common/userDataSync'; +import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService'; import { UserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; import { UserDataSyncChannel } from 'vs/platform/userDataSync/common/userDataSyncIpc'; import { SettingsMergeChannelClient } from 'vs/platform/userDataSync/common/settingsSyncIpc'; @@ -59,10 +59,12 @@ import { IElectronService } from 'vs/platform/electron/node/electron'; import { LoggerService } from 'vs/platform/log/node/loggerService'; import { UserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSyncLog'; import { IAuthTokenService } from 'vs/platform/auth/common/auth'; -import { AuthTokenService } from 'vs/platform/auth/common/authTokenService'; +import { AuthTokenService } from 'vs/platform/auth/electron-browser/authTokenService'; import { AuthTokenChannel } from 'vs/platform/auth/common/authTokenIpc'; import { ICredentialsService } from 'vs/platform/credentials/common/credentials'; import { KeytarCredentialsService } from 'vs/platform/credentials/node/credentialsService'; +import { UserDataSyncUtilServiceClient } from 'vs/platform/userDataSync/common/keybindingsSyncIpc'; +import { UserDataAutoSync } from 'vs/platform/userDataSync/electron-browser/userDataAutoSync'; export interface ISharedProcessConfiguration { readonly machineId: string; @@ -186,6 +188,7 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat services.set(IUserDataSyncLogService, new SyncDescriptor(UserDataSyncLogService)); const settingsMergeChannel = server.getChannel('settingsMerge', activeWindowRouter); services.set(ISettingsMergeService, new SettingsMergeChannelClient(settingsMergeChannel)); + services.set(IUserDataSyncUtilService, new UserDataSyncUtilServiceClient(server.getChannel('userDataSyncUtil', activeWindowRouter))); services.set(IUserDataSyncStoreService, new SyncDescriptor(UserDataSyncStoreService)); services.set(IUserDataSyncService, new SyncDescriptor(UserDataSyncService)); registerConfiguration(); diff --git a/src/vs/code/electron-browser/workbench/workbench.js b/src/vs/code/electron-browser/workbench/workbench.js index e275ba1b6d86..604183394d65 100644 --- a/src/vs/code/electron-browser/workbench/workbench.js +++ b/src/vs/code/electron-browser/workbench/workbench.js @@ -33,17 +33,17 @@ bootstrapWindow.load([ return require('vs/workbench/electron-browser/desktop.main').main(configuration); }); }, { - removeDeveloperKeybindingsAfterLoad: true, - canModifyDOM: function (windowConfig) { - showPartsSplash(windowConfig); - }, - beforeLoaderConfig: function (windowConfig, loaderConfig) { - loaderConfig.recordStats = true; - }, - beforeRequire: function () { - perf.mark('willLoadWorkbenchMain'); - } - }); + removeDeveloperKeybindingsAfterLoad: true, + canModifyDOM: function (windowConfig) { + showPartsSplash(windowConfig); + }, + beforeLoaderConfig: function (windowConfig, loaderConfig) { + loaderConfig.recordStats = true; + }, + beforeRequire: function () { + perf.mark('willLoadWorkbenchMain'); + } +}); /** * @param {{ @@ -84,7 +84,7 @@ function showPartsSplash(configuration) { style.className = 'initialShellColors'; document.head.appendChild(style); document.body.className = baseTheme; - style.innerHTML = `body { background-color: ${shellBackground}; color: ${shellForeground}; }`; + style.innerHTML = `body { background-color: ${shellBackground}; color: ${shellForeground}; margin: 0; padding: 0; }`; if (data && data.layoutInfo) { // restore parts if possible (we might not always store layout info) @@ -92,6 +92,18 @@ function showPartsSplash(configuration) { const splash = document.createElement('div'); splash.id = id; + if (layoutInfo.windowBorder) { + splash.style.position = 'relative'; + splash.style.height = 'calc(100vh - 2px)'; + splash.style.width = 'calc(100vw - 2px)'; + splash.style.border = '1px solid var(--window-border-color)'; + splash.style.setProperty('--window-border-color', colorInfo.windowBorder); + + if (layoutInfo.windowBorderRadius) { + splash.style.borderRadius = layoutInfo.windowBorderRadius; + } + } + // ensure there is enough space layoutInfo.sideBarWidth = Math.min(layoutInfo.sideBarWidth, window.innerWidth - (layoutInfo.activityBarWidth + layoutInfo.editorPartMinWidth)); diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 8958936e2dc0..c715f4d51736 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -79,6 +79,7 @@ import { ISharedProcessMainService, SharedProcessMainService } from 'vs/platform import { assign } from 'vs/base/common/objects'; import { IDialogMainService, DialogMainService } from 'vs/platform/dialogs/electron-main/dialogs'; import { withNullAsUndefined } from 'vs/base/common/types'; +import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv'; export class CodeApplication extends Disposable { @@ -574,14 +575,15 @@ export class CodeApplication extends Disposable { electronIpcServer.registerChannel('logger', loggerChannel); sharedProcessClient.then(client => client.registerChannel('logger', loggerChannel)); + const windowsMainService = this.windowsMainService = accessor.get(IWindowsMainService); + // ExtensionHost Debug broadcast service - electronIpcServer.registerChannel(ExtensionHostDebugBroadcastChannel.ChannelName, new ExtensionHostDebugBroadcastChannel()); + electronIpcServer.registerChannel(ExtensionHostDebugBroadcastChannel.ChannelName, new ElectronExtensionHostDebugBroadcastChannel(windowsMainService)); // Signal phase: ready (services set) this.lifecycleMainService.phase = LifecycleMainPhase.Ready; // Propagate to clients - const windowsMainService = this.windowsMainService = accessor.get(IWindowsMainService); this.dialogMainService = accessor.get(IDialogMainService); // Create a URL handler to open file URIs in the active window @@ -637,7 +639,7 @@ export class CodeApplication extends Disposable { // Watch Electron URLs and forward them to the UrlService const args = this.environmentService.args; const urls = args['open-url'] ? args._urls : []; - const urlListener = new ElectronURLListener(urls || [], urlService, windowsMainService); + const urlListener = new ElectronURLListener(urls || [], urlService, windowsMainService, this.environmentService); this._register(urlListener); // Open our first window @@ -723,3 +725,28 @@ export class CodeApplication extends Disposable { }); } } + +class ElectronExtensionHostDebugBroadcastChannel extends ExtensionHostDebugBroadcastChannel { + + constructor(private windowsMainService: IWindowsMainService) { + super(); + } + + call(ctx: TContext, command: string, arg?: any): Promise { + if (command === 'openExtensionDevelopmentHostWindow') { + const env = arg[1]; + const pargs = parseArgs(arg[0], OPTIONS); + const extDevPaths = pargs.extensionDevelopmentPath; + if (extDevPaths) { + this.windowsMainService.openExtensionDevelopmentHostWindow(extDevPaths, { + context: OpenContext.API, + cli: pargs, + userEnv: Object.keys(env).length > 0 ? env : undefined + }); + } + return Promise.resolve(); + } else { + return super.call(ctx, command, arg); + } + } +} diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index cc50acfc4c8c..79f9874681ce 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -8,8 +8,8 @@ import { app, dialog } from 'electron'; import { assign } from 'vs/base/common/objects'; import { isWindows, IProcessEnvironment, isMacintosh } from 'vs/base/common/platform'; import product from 'vs/platform/product/common/product'; -import { parseMainProcessArgv } from 'vs/platform/environment/node/argvHelper'; -import { addArg, createWaitMarkerFile } from 'vs/platform/environment/node/argv'; +import { parseMainProcessArgv, addArg } from 'vs/platform/environment/node/argvHelper'; +import { createWaitMarkerFile } from 'vs/platform/environment/node/waitMarkerFile'; import { mkdirp } from 'vs/base/node/pfs'; import { validatePaths } from 'vs/code/node/paths'; import { LifecycleMainService, ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; diff --git a/src/vs/code/electron-main/sharedProcess.ts b/src/vs/code/electron-main/sharedProcess.ts index 0f5f7c302841..6ebf353f02f0 100644 --- a/src/vs/code/electron-main/sharedProcess.ts +++ b/src/vs/code/electron-main/sharedProcess.ts @@ -6,13 +6,14 @@ import { assign } from 'vs/base/common/objects'; import { memoize } from 'vs/base/common/decorators'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { BrowserWindow, ipcMain } from 'electron'; +import { BrowserWindow, ipcMain, WebContents, Event as ElectronEvent } from 'electron'; import { ISharedProcess } from 'vs/platform/ipc/electron-main/sharedProcessMainService'; import { Barrier } from 'vs/base/common/async'; import { ILogService } from 'vs/platform/log/common/log'; import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { IThemeMainService } from 'vs/platform/theme/electron-main/themeMainService'; import { toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { Event } from 'vs/base/common/event'; export class SharedProcess implements ISharedProcess { @@ -53,7 +54,7 @@ export class SharedProcess implements ISharedProcess { this.window.loadURL(url); // Prevent the window from dying - const onClose = (e: Event) => { + const onClose = (e: ElectronEvent) => { this.logService.trace('SharedProcess#close prevented'); // We never allow to close the shared process unless we get explicitly disposed() @@ -97,7 +98,8 @@ export class SharedProcess implements ISharedProcess { }); return new Promise(c => { - ipcMain.once('handshake:hello', ({ sender }: { sender: any }) => { + const onHello = Event.once(Event.fromNodeEventEmitter(ipcMain, 'handshake:hello', ({ sender }: { sender: WebContents }) => sender)); + disposables.add(onHello(sender => { sender.send('handshake:hey there', { sharedIPCHandle: this.environmentService.sharedIPCHandle, args: this.environmentService.args, @@ -106,7 +108,7 @@ export class SharedProcess implements ISharedProcess { disposables.add(toDisposable(() => sender.send('handshake:goodbye'))); ipcMain.once('handshake:im ready', () => c(undefined)); - }); + })); }); } diff --git a/src/vs/code/electron-main/window.ts b/src/vs/code/electron-main/window.ts index 05b741cae2ea..e264267e500b 100644 --- a/src/vs/code/electron-main/window.ts +++ b/src/vs/code/electron-main/window.ts @@ -31,6 +31,7 @@ import { IFileService } from 'vs/platform/files/common/files'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IDialogMainService } from 'vs/platform/dialogs/electron-main/dialogs'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; +import { ThemeIcon } from 'vs/platform/theme/common/themeService'; const RUN_TEXTMATE_IN_WORKER = false; @@ -60,7 +61,7 @@ const enum WindowError { export class CodeWindow extends Disposable implements ICodeWindow { private static readonly MIN_WIDTH = 600; - private static readonly MIN_HEIGHT = 600; + private static readonly MIN_HEIGHT = 270; private static readonly MAX_URL_LENGTH = 2 * 1024 * 1024; // https://cs.chromium.org/chromium/src/url/url_constants.cc?l=32 @@ -438,15 +439,8 @@ export class CodeWindow extends Disposable implements ICodeWindow { // Inject headers when requests are incoming const urls = ['https://marketplace.visualstudio.com/*', 'https://*.vsassets.io/*']; - this._win.webContents.session.webRequest.onBeforeSendHeaders({ urls }, (details, cb) => { - this.marketplaceHeadersPromise.then(headers => { - const requestHeaders = objects.assign(details.requestHeaders, headers) as { [key: string]: string | undefined }; - if (!this.configurationService.getValue('extensions.disableExperimentalAzureSearch')) { - requestHeaders['Cookie'] = `${requestHeaders['Cookie'] ? requestHeaders['Cookie'] + ';' : ''}EnableExternalSearchForVSCode=true`; - } - cb({ cancel: false, requestHeaders }); - }); - }); + this._win.webContents.session.webRequest.onBeforeSendHeaders({ urls }, (details, cb) => + this.marketplaceHeadersPromise.then(headers => cb({ cancel: false, requestHeaders: objects.assign(details.requestHeaders, headers) as { [key: string]: string | undefined } }))); } private onWindowError(error: WindowError): void { @@ -635,6 +629,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { // Set window ID windowConfiguration.windowId = this._win.id; + windowConfiguration.sessionId = `window:${this._win.id}`; windowConfiguration.logLevel = this.logService.getLevel(); // Set zoomlevel @@ -657,7 +652,6 @@ export class CodeWindow extends Disposable implements ICodeWindow { // Title style related windowConfiguration.maximized = this._win.isMaximized(); - windowConfiguration.frameless = this.hasHiddenTitleBarStyle && !isMacintosh; // Dump Perf Counters windowConfiguration.perfEntries = perf.exportEntries(); @@ -1096,8 +1090,8 @@ export class CodeWindow extends Disposable implements ICodeWindow { private createTouchBarGroupSegments(items: ISerializableCommandAction[] = []): ITouchBarSegment[] { const segments: ITouchBarSegment[] = items.map(item => { let icon: NativeImage | undefined; - if (item.iconLocation && item.iconLocation.dark.scheme === 'file') { - icon = nativeImage.createFromPath(URI.revive(item.iconLocation.dark).fsPath); + if (item.icon && !ThemeIcon.isThemeIcon(item.icon) && item.icon?.dark?.scheme === 'file') { + icon = nativeImage.createFromPath(URI.revive(item.icon.dark).fsPath); if (icon.isEmpty()) { icon = undefined; } diff --git a/src/vs/code/node/cli.ts b/src/vs/code/node/cli.ts index 0ba64781a44d..f523559052dc 100644 --- a/src/vs/code/node/cli.ts +++ b/src/vs/code/node/cli.ts @@ -6,8 +6,9 @@ import * as os from 'os'; import * as fs from 'fs'; import { spawn, ChildProcess, SpawnOptions } from 'child_process'; -import { buildHelpMessage, buildVersionMessage, addArg, createWaitMarkerFile, OPTIONS } from 'vs/platform/environment/node/argv'; -import { parseCLIProcessArgv } from 'vs/platform/environment/node/argvHelper'; +import { buildHelpMessage, buildVersionMessage, OPTIONS } from 'vs/platform/environment/node/argv'; +import { parseCLIProcessArgv, addArg } from 'vs/platform/environment/node/argvHelper'; +import { createWaitMarkerFile } from 'vs/platform/environment/node/waitMarkerFile'; import { ParsedArgs } from 'vs/platform/environment/common/environment'; import product from 'vs/platform/product/common/product'; import * as paths from 'vs/base/common/path'; @@ -134,8 +135,8 @@ export async function main(argv: string[]): Promise { env['ELECTRON_ENABLE_LOGGING'] = '1'; processCallbacks.push(async child => { - child.stdout.on('data', (data: Buffer) => console.log(data.toString('utf8').trim())); - child.stderr.on('data', (data: Buffer) => console.log(data.toString('utf8').trim())); + child.stdout!.on('data', (data: Buffer) => console.log(data.toString('utf8').trim())); + child.stderr!.on('data', (data: Buffer) => console.log(data.toString('utf8').trim())); await new Promise(c => child.once('exit', () => c())); }); diff --git a/src/vs/code/node/paths.ts b/src/vs/code/node/paths.ts index d30866673322..b1084eb02d99 100644 --- a/src/vs/code/node/paths.ts +++ b/src/vs/code/node/paths.ts @@ -25,9 +25,6 @@ export function validatePaths(args: ParsedArgs): ParsedArgs { args._ = paths; } - // Update environment - args.diff = args.diff && args._.length === 2; - return args; } diff --git a/src/vs/code/test/electron-main/nativeHelpers.test.ts b/src/vs/code/test/electron-main/nativeHelpers.test.ts index 2d7a1412c55f..d35f0bb76b6c 100644 --- a/src/vs/code/test/electron-main/nativeHelpers.test.ts +++ b/src/vs/code/test/electron-main/nativeHelpers.test.ts @@ -28,7 +28,9 @@ suite('Windows Native Helpers', () => { }); test('vscode-windows-ca-certs', async () => { - const windowsCerts = await import('vscode-windows-ca-certs'); + const windowsCerts = await new Promise((resolve, reject) => { + require(['vscode-windows-ca-certs'], resolve, reject); + }); assert.ok(windowsCerts, 'Unable to load vscode-windows-ca-certs dependency.'); }); diff --git a/src/vs/code/test/node/argv.test.ts b/src/vs/code/test/node/argv.test.ts index 0dee81f772a4..18155d59be9c 100644 --- a/src/vs/code/test/node/argv.test.ts +++ b/src/vs/code/test/node/argv.test.ts @@ -3,7 +3,8 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { formatOptions, Option, addArg } from 'vs/platform/environment/node/argv'; +import { formatOptions, Option } from 'vs/platform/environment/node/argv'; +import { addArg } from 'vs/platform/environment/node/argvHelper'; suite('formatOptions', () => { diff --git a/src/vs/editor/browser/config/charWidthReader.ts b/src/vs/editor/browser/config/charWidthReader.ts index 49900650f906..b45846e0177a 100644 --- a/src/vs/editor/browser/config/charWidthReader.ts +++ b/src/vs/editor/browser/config/charWidthReader.ts @@ -124,7 +124,7 @@ class DomCharWidthReader { private static _render(testElement: HTMLElement, request: CharWidthRequest): void { if (request.chr === ' ') { - let htmlString = ' '; + let htmlString = ' '; // Repeat character 256 (2^8) times for (let i = 0; i < 8; i++) { htmlString += htmlString; diff --git a/src/vs/editor/browser/controller/coreCommands.ts b/src/vs/editor/browser/controller/coreCommands.ts index f9779cc32615..41d5b451ae91 100644 --- a/src/vs/editor/browser/controller/coreCommands.ts +++ b/src/vs/editor/browser/controller/coreCommands.ts @@ -1753,11 +1753,9 @@ registerCommand(new EditorOrNativeTextInputCommand({ kbExpr: null, primary: KeyMod.CtrlCmd | KeyCode.KEY_A }, - menubarOpts: { - // {{SQL CARBON EDIT}} - Put this in the edit menu since we disabled the selection menu - menuId: MenuId.MenubarEditMenu, - group: '4_find_global', - // {{SQL CARBON EDIT}} - End + menuOpts: { + menuId: MenuId.MenubarEditMenu, // {{SQL CARBON EDIT}} - Put this in the edit menu since we disabled the selection menu + group: '4_find_global', // {{SQL CARBON EDIT}} - Put this in the edit menu since we disabled the selection menu title: nls.localize({ key: 'miSelectAll', comment: ['&& denotes a mnemonic'] }, "&&Select All"), order: 1 } @@ -1773,7 +1771,7 @@ registerCommand(new EditorOrNativeTextInputCommand({ kbExpr: EditorContextKeys.textInputFocus, primary: KeyMod.CtrlCmd | KeyCode.KEY_Z }, - menubarOpts: { + menuOpts: { menuId: MenuId.MenubarEditMenu, group: '1_do', title: nls.localize({ key: 'miUndo', comment: ['&& denotes a mnemonic'] }, "&&Undo"), @@ -1794,7 +1792,7 @@ registerCommand(new EditorOrNativeTextInputCommand({ secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_Z], mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_Z } }, - menubarOpts: { + menuOpts: { menuId: MenuId.MenubarEditMenu, group: '1_do', title: nls.localize({ key: 'miRedo', comment: ['&& denotes a mnemonic'] }, "&&Redo"), diff --git a/src/vs/editor/browser/controller/mouseHandler.ts b/src/vs/editor/browser/controller/mouseHandler.ts index daf187e6648d..6781bbf8e4bb 100644 --- a/src/vs/editor/browser/controller/mouseHandler.ts +++ b/src/vs/editor/browser/controller/mouseHandler.ts @@ -17,18 +17,17 @@ import { IViewCursorRenderData } from 'vs/editor/browser/viewParts/viewCursors/v import { EditorZoom } from 'vs/editor/common/config/editorZoom'; import { Position } from 'vs/editor/common/core/position'; import { Selection } from 'vs/editor/common/core/selection'; -import { HorizontalRange } from 'vs/editor/common/view/renderingContext'; +import { HorizontalPosition } from 'vs/editor/common/view/renderingContext'; import { ViewContext } from 'vs/editor/common/view/viewContext'; import * as viewEvents from 'vs/editor/common/view/viewEvents'; import { ViewEventHandler } from 'vs/editor/common/viewModel/viewEventHandler'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; - /** * Merges mouse events when mouse move events are throttled */ -function createMouseMoveEventMerger(mouseTargetFactory: MouseTargetFactory | null) { - return function (lastEvent: EditorMouseEvent, currentEvent: EditorMouseEvent): EditorMouseEvent { +export function createMouseMoveEventMerger(mouseTargetFactory: MouseTargetFactory | null) { + return function (lastEvent: EditorMouseEvent | null, currentEvent: EditorMouseEvent): EditorMouseEvent { let targetIsWidget = false; if (mouseTargetFactory) { targetIsWidget = mouseTargetFactory.mouseTargetIsWidget(currentEvent); @@ -59,7 +58,7 @@ export interface IPointerHandlerHelper { */ getPositionFromDOMInfo(spanNode: HTMLElement, offset: number): Position | null; - visibleRangeForPosition2(lineNumber: number, column: number): HorizontalRange | null; + visibleRangeForPosition(lineNumber: number, column: number): HorizontalPosition | null; getLineWidth(lineNumber: number): number; } @@ -72,8 +71,7 @@ export class MouseHandler extends ViewEventHandler { protected viewHelper: IPointerHandlerHelper; protected mouseTargetFactory: MouseTargetFactory; private readonly _asyncFocus: RunOnceScheduler; - - private readonly _mouseDownOperation: MouseDownOperation; + protected readonly _mouseDownOperation: MouseDownOperation; private lastMouseLeaveTime: number; constructor(context: ViewContext, viewController: ViewController, viewHelper: IPointerHandlerHelper) { @@ -180,7 +178,7 @@ export class MouseHandler extends ViewEventHandler { }); } - private _onMouseMove(e: EditorMouseEvent): void { + public _onMouseMove(e: EditorMouseEvent): void { if (this._mouseDownOperation.isActive()) { // In selection/drag operation return; @@ -197,7 +195,7 @@ export class MouseHandler extends ViewEventHandler { }); } - private _onMouseLeave(e: EditorMouseEvent): void { + public _onMouseLeave(e: EditorMouseEvent): void { this.lastMouseLeaveTime = (new Date()).getTime(); this.viewController.emitMouseLeave({ event: e, diff --git a/src/vs/editor/browser/controller/mouseTarget.ts b/src/vs/editor/browser/controller/mouseTarget.ts index bbd0dcbbae05..51d3080b2b9e 100644 --- a/src/vs/editor/browser/controller/mouseTarget.ts +++ b/src/vs/editor/browser/controller/mouseTarget.ts @@ -13,7 +13,7 @@ import { IViewCursorRenderData } from 'vs/editor/browser/viewParts/viewCursors/v import { EditorLayoutInfo, EditorOption } from 'vs/editor/common/config/editorOptions'; import { Position } from 'vs/editor/common/core/position'; import { Range as EditorRange } from 'vs/editor/common/core/range'; -import { HorizontalRange } from 'vs/editor/common/view/renderingContext'; +import { HorizontalPosition } from 'vs/editor/common/view/renderingContext'; import { ViewContext } from 'vs/editor/common/view/viewContext'; import { IViewModel } from 'vs/editor/common/viewModel/viewModel'; import { CursorColumns } from 'vs/editor/common/controller/cursorCommon'; @@ -346,8 +346,8 @@ export class HitTestContext { return this._viewHelper.getLineWidth(lineNumber); } - public visibleRangeForPosition2(lineNumber: number, column: number): HorizontalRange | null { - return this._viewHelper.visibleRangeForPosition2(lineNumber, column); + public visibleRangeForPosition(lineNumber: number, column: number): HorizontalPosition | null { + return this._viewHelper.visibleRangeForPosition(lineNumber, column); } public getPositionFromDOMInfo(spanNode: HTMLElement, offset: number): Position | null { @@ -743,7 +743,7 @@ export class MouseTargetFactory { return request.fulfill(MouseTargetType.CONTENT_EMPTY, pos, undefined, detail); } - const visibleRange = ctx.visibleRangeForPosition2(lineNumber, column); + const visibleRange = ctx.visibleRangeForPosition(lineNumber, column); if (!visibleRange) { return request.fulfill(MouseTargetType.UNKNOWN, pos); @@ -761,14 +761,14 @@ export class MouseTargetFactory { const points: OffsetColumn[] = []; points.push({ offset: visibleRange.left, column: column }); if (column > 1) { - const visibleRange = ctx.visibleRangeForPosition2(lineNumber, column - 1); + const visibleRange = ctx.visibleRangeForPosition(lineNumber, column - 1); if (visibleRange) { points.push({ offset: visibleRange.left, column: column - 1 }); } } const lineMaxColumn = ctx.model.getLineMaxColumn(lineNumber); if (column < lineMaxColumn) { - const visibleRange = ctx.visibleRangeForPosition2(lineNumber, column + 1); + const visibleRange = ctx.visibleRangeForPosition(lineNumber, column + 1); if (visibleRange) { points.push({ offset: visibleRange.left, column: column + 1 }); } diff --git a/src/vs/editor/browser/controller/pointerHandler.ts b/src/vs/editor/browser/controller/pointerHandler.ts index 7b87db0424f5..eb9f1de2629e 100644 --- a/src/vs/editor/browser/controller/pointerHandler.ts +++ b/src/vs/editor/browser/controller/pointerHandler.ts @@ -4,20 +4,22 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from 'vs/base/browser/dom'; +import * as platform from 'vs/base/common/platform'; import { EventType, Gesture, GestureEvent } from 'vs/base/browser/touch'; import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; -import { IPointerHandlerHelper, MouseHandler } from 'vs/editor/browser/controller/mouseHandler'; +import { IPointerHandlerHelper, MouseHandler, createMouseMoveEventMerger } from 'vs/editor/browser/controller/mouseHandler'; import { IMouseTarget } from 'vs/editor/browser/editorBrowser'; -import { EditorMouseEvent } from 'vs/editor/browser/editorDom'; +import { EditorMouseEvent, EditorPointerEventFactory } from 'vs/editor/browser/editorDom'; import { ViewController } from 'vs/editor/browser/view/viewController'; import { ViewContext } from 'vs/editor/common/view/viewContext'; +import { BrowserFeatures } from 'vs/base/browser/canIUse'; interface IThrottledGestureEvent { translationX: number; translationY: number; } -function gestureChangeEventMerger(lastEvent: IThrottledGestureEvent, currentEvent: MSGestureEvent): IThrottledGestureEvent { +function gestureChangeEventMerger(lastEvent: IThrottledGestureEvent | null, currentEvent: MSGestureEvent): IThrottledGestureEvent { const r = { translationY: currentEvent.translationY, translationX: currentEvent.translationX @@ -52,7 +54,7 @@ class MsPointerHandler extends MouseHandler implements IDisposable { const penGesture = new MSGesture(); touchGesture.target = this.viewHelper.linesContentDomNode; penGesture.target = this.viewHelper.linesContentDomNode; - this.viewHelper.linesContentDomNode.addEventListener('MSPointerDown', (e: MSPointerEvent) => { + this.viewHelper.linesContentDomNode.addEventListener('MSPointerDown', (e: MSPointerEvent) => { // Circumvent IE11 breaking change in e.pointerType & TypeScript's stale definitions const pointerType = e.pointerType; if (pointerType === ((e).MSPOINTER_TYPE_MOUSE || 'mouse')) { @@ -66,7 +68,7 @@ class MsPointerHandler extends MouseHandler implements IDisposable { penGesture.addPointer(e.pointerId); } }); - this._register(dom.addDisposableThrottledListener(this.viewHelper.linesContentDomNode, 'MSGestureChange', (e) => this._onGestureChange(e), gestureChangeEventMerger)); + this._register(dom.addDisposableThrottledListener(this.viewHelper.linesContentDomNode, 'MSGestureChange', (e) => this._onGestureChange(e), gestureChangeEventMerger)); this._register(dom.addDisposableListener(this.viewHelper.linesContentDomNode, 'MSGestureTap', (e) => this._onCaptureGestureTap(e), true)); } }, 100); @@ -131,7 +133,7 @@ class StandardPointerHandler extends MouseHandler implements IDisposable { const penGesture = new MSGesture(); touchGesture.target = this.viewHelper.linesContentDomNode; penGesture.target = this.viewHelper.linesContentDomNode; - this.viewHelper.linesContentDomNode.addEventListener('pointerdown', (e: MSPointerEvent) => { + this.viewHelper.linesContentDomNode.addEventListener('pointerdown', (e: PointerEvent) => { const pointerType = e.pointerType; if (pointerType === 'mouse') { this._lastPointerType = 'mouse'; @@ -144,7 +146,7 @@ class StandardPointerHandler extends MouseHandler implements IDisposable { penGesture.addPointer(e.pointerId); } }); - this._register(dom.addDisposableThrottledListener(this.viewHelper.linesContentDomNode, 'MSGestureChange', (e) => this._onGestureChange(e), gestureChangeEventMerger)); + this._register(dom.addDisposableThrottledListener(this.viewHelper.linesContentDomNode, 'MSGestureChange', (e) => this._onGestureChange(e), gestureChangeEventMerger)); this._register(dom.addDisposableListener(this.viewHelper.linesContentDomNode, 'MSGestureTap', (e) => this._onCaptureGestureTap(e), true)); } }, 100); @@ -185,6 +187,87 @@ class StandardPointerHandler extends MouseHandler implements IDisposable { } } +/** + * Currently only tested on iOS 13/ iPadOS. + */ +export class PointerEventHandler extends MouseHandler { + private _lastPointerType: string; + constructor(context: ViewContext, viewController: ViewController, viewHelper: IPointerHandlerHelper) { + super(context, viewController, viewHelper); + + this._register(Gesture.addTarget(this.viewHelper.linesContentDomNode)); + this._register(dom.addDisposableListener(this.viewHelper.linesContentDomNode, EventType.Tap, (e) => this.onTap(e))); + this._register(dom.addDisposableListener(this.viewHelper.linesContentDomNode, EventType.Change, (e) => this.onChange(e))); + this._register(dom.addDisposableListener(this.viewHelper.linesContentDomNode, EventType.Contextmenu, (e: MouseEvent) => this._onContextMenu(new EditorMouseEvent(e, this.viewHelper.viewDomNode), false))); + + this._lastPointerType = 'mouse'; + + this._register(dom.addDisposableListener(this.viewHelper.linesContentDomNode, 'pointerdown', (e: any) => { + const pointerType = e.pointerType; + if (pointerType === 'mouse') { + this._lastPointerType = 'mouse'; + return; + } else if (pointerType === 'touch') { + this._lastPointerType = 'touch'; + } else { + this._lastPointerType = 'pen'; + } + })); + + // PonterEvents + const pointerEvents = new EditorPointerEventFactory(this.viewHelper.viewDomNode); + + this._register(pointerEvents.onPointerMoveThrottled(this.viewHelper.viewDomNode, + (e) => this._onMouseMove(e), + createMouseMoveEventMerger(this.mouseTargetFactory), MouseHandler.MOUSE_MOVE_MINIMUM_TIME)); + this._register(pointerEvents.onPointerUp(this.viewHelper.viewDomNode, (e) => this._onMouseUp(e))); + this._register(pointerEvents.onPointerLeave(this.viewHelper.viewDomNode, (e) => this._onMouseLeave(e))); + this._register(pointerEvents.onPointerDown(this.viewHelper.viewDomNode, (e) => this._onMouseDown(e))); + } + + private onTap(event: GestureEvent): void { + if (!event.initialTarget || !this.viewHelper.linesContentDomNode.contains(event.initialTarget)) { + return; + } + + event.preventDefault(); + this.viewHelper.focusTextArea(); + const target = this._createMouseTarget(new EditorMouseEvent(event, this.viewHelper.viewDomNode), false); + + if (target.position) { + // this.viewController.moveTo(target.position); + this.viewController.dispatchMouse({ + position: target.position, + mouseColumn: target.position.column, + startedOnLineNumbers: false, + mouseDownCount: event.tapCount, + inSelectionMode: false, + altKey: false, + ctrlKey: false, + metaKey: false, + shiftKey: false, + + leftButton: false, + middleButton: false, + }); + } + } + + private onChange(e: GestureEvent): void { + if (this._lastPointerType === 'touch') { + this._context.viewLayout.deltaScrollNow(-e.translationX, -e.translationY); + } + } + + public _onMouseDown(e: EditorMouseEvent): void { + if (e.target && this.viewHelper.linesContentDomNode.contains(e.target) && this._lastPointerType === 'touch') { + return; + } + + super._onMouseDown(e); + } +} + class TouchHandler extends MouseHandler { constructor(context: ViewContext, viewController: ViewController, viewHelper: IPointerHandlerHelper) { @@ -221,6 +304,8 @@ export class PointerHandler extends Disposable { super(); if (window.navigator.msPointerEnabled) { this.handler = this._register(new MsPointerHandler(context, viewController, viewHelper)); + } else if ((platform.isIOS && BrowserFeatures.pointerEvents)) { + this.handler = this._register(new PointerEventHandler(context, viewController, viewHelper)); } else if ((window).TouchEvent) { this.handler = this._register(new TouchHandler(context, viewController, viewHelper)); } else if (window.navigator.pointerEnabled || (window).PointerEvent) { diff --git a/src/vs/editor/browser/controller/textAreaHandler.ts b/src/vs/editor/browser/controller/textAreaHandler.ts index d0ee9589563a..c5f36bae597e 100644 --- a/src/vs/editor/browser/controller/textAreaHandler.ts +++ b/src/vs/editor/browser/controller/textAreaHandler.ts @@ -25,13 +25,13 @@ import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import { ScrollType } from 'vs/editor/common/editorCommon'; import { EndOfLinePreference } from 'vs/editor/common/model'; -import { HorizontalRange, RenderingContext, RestrictedRenderingContext } from 'vs/editor/common/view/renderingContext'; +import { RenderingContext, RestrictedRenderingContext, HorizontalPosition } from 'vs/editor/common/view/renderingContext'; import { ViewContext } from 'vs/editor/common/view/viewContext'; import * as viewEvents from 'vs/editor/common/view/viewEvents'; import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; export interface ITextAreaHandlerHelper { - visibleRangeForPositionRelativeToEditor(lineNumber: number, column: number): HorizontalRange | null; + visibleRangeForPositionRelativeToEditor(lineNumber: number, column: number): HorizontalPosition | null; } class VisibleTextAreaData { @@ -62,6 +62,7 @@ export class TextAreaHandler extends ViewPart { private _scrollTop: number; private _accessibilitySupport: AccessibilitySupport; + private _accessibilityPageSize: number; private _contentLeft: number; private _contentWidth: number; private _contentHeight: number; @@ -92,6 +93,7 @@ export class TextAreaHandler extends ViewPart { const layoutInfo = options.get(EditorOption.layoutInfo); this._accessibilitySupport = options.get(EditorOption.accessibilitySupport); + this._accessibilityPageSize = options.get(EditorOption.accessibilityPageSize); this._contentLeft = layoutInfo.contentLeft; this._contentWidth = layoutInfo.contentWidth; this._contentHeight = layoutInfo.contentHeight; @@ -189,7 +191,7 @@ export class TextAreaHandler extends ViewPart { return TextAreaState.EMPTY; } - return PagedScreenReaderStrategy.fromEditorSelection(currentState, simpleModel, this._selections[0], this._accessibilitySupport === AccessibilitySupport.Unknown); + return PagedScreenReaderStrategy.fromEditorSelection(currentState, simpleModel, this._selections[0], this._accessibilityPageSize, this._accessibilitySupport === AccessibilitySupport.Unknown); }, deduceModelPosition: (viewAnchorPosition: Position, deltaOffset: number, lineFeedCnt: number): Position => { @@ -341,6 +343,7 @@ export class TextAreaHandler extends ViewPart { const layoutInfo = options.get(EditorOption.layoutInfo); this._accessibilitySupport = options.get(EditorOption.accessibilitySupport); + this._accessibilityPageSize = options.get(EditorOption.accessibilityPageSize); this._contentLeft = layoutInfo.contentLeft; this._contentWidth = layoutInfo.contentWidth; this._contentHeight = layoutInfo.contentHeight; @@ -406,9 +409,13 @@ export class TextAreaHandler extends ViewPart { this._textAreaInput.focusTextArea(); } + public refreshFocusState() { + this._textAreaInput.refreshFocusState(); + } + // --- end view API - private _primaryCursorVisibleRange: HorizontalRange | null = null; + private _primaryCursorVisibleRange: HorizontalPosition | null = null; public prepareRender(ctx: RenderingContext): void { const primaryCursorPosition = new Position(this._selections[0].positionLineNumber, this._selections[0].positionColumn); @@ -427,8 +434,7 @@ export class TextAreaHandler extends ViewPart { this._visibleTextArea.top - this._scrollTop, this._contentLeft + this._visibleTextArea.left - this._scrollLeft, this._visibleTextArea.width, - this._lineHeight, - true + this._lineHeight ); return; } @@ -460,29 +466,22 @@ export class TextAreaHandler extends ViewPart { // We will also make the fontSize and lineHeight the correct dimensions to help with the placement of these pickers this._renderInsideEditor( top, left, - canUseZeroSizeTextarea ? 0 : 1, this._lineHeight, - true + canUseZeroSizeTextarea ? 0 : 1, this._lineHeight ); return; } this._renderInsideEditor( top, left, - canUseZeroSizeTextarea ? 0 : 1, canUseZeroSizeTextarea ? 0 : 1, - false + canUseZeroSizeTextarea ? 0 : 1, canUseZeroSizeTextarea ? 0 : 1 ); } - private _renderInsideEditor(top: number, left: number, width: number, height: number, useEditorFont: boolean): void { + private _renderInsideEditor(top: number, left: number, width: number, height: number): void { const ta = this.textArea; const tac = this.textAreaCover; - if (useEditorFont) { - Configuration.applyFontInfo(ta, this._fontInfo); - } else { - ta.setFontSize(1); - ta.setLineHeight(this._fontInfo.lineHeight); - } + Configuration.applyFontInfo(ta, this._fontInfo); ta.setTop(top); ta.setLeft(left); diff --git a/src/vs/editor/browser/controller/textAreaInput.ts b/src/vs/editor/browser/controller/textAreaInput.ts index b6098cec5f47..818d2b2add0b 100644 --- a/src/vs/editor/browser/controller/textAreaInput.ts +++ b/src/vs/editor/browser/controller/textAreaInput.ts @@ -16,6 +16,7 @@ import * as strings from 'vs/base/common/strings'; import { ITextAreaWrapper, ITypeData, TextAreaState } from 'vs/editor/browser/controller/textAreaState'; import { Position } from 'vs/editor/common/core/position'; import { Selection } from 'vs/editor/common/core/selection'; +import { BrowserFeatures } from 'vs/base/browser/canIUse'; export interface ICompositionData { data: string; @@ -162,7 +163,7 @@ export class TextAreaInput extends Disposable { private _isDoingComposition: boolean; private _nextCommand: ReadFromTextArea; - constructor(host: ITextAreaInputHost, textArea: FastDomNode) { + constructor(host: ITextAreaInputHost, private textArea: FastDomNode) { super(); this._host = host; this._textArea = this._register(new TextAreaWrapper(textArea)); @@ -273,7 +274,11 @@ export class TextAreaInput extends Disposable { this._register(dom.addDisposableListener(textArea.domNode, 'compositionend', (e: CompositionEvent) => { this._lastTextAreaEvent = TextAreaInputEventType.compositionend; - + // https://github.com/microsoft/monaco-editor/issues/1663 + // On iOS 13.2, Chinese system IME randomly trigger an additional compositionend event with empty data + if (!this._isDoingComposition) { + return; + } if (compositionDataInValid(e.locale)) { // https://github.com/Microsoft/monaco-editor/issues/339 const [newState, typeInput] = deduceInputFromTextAreaValue(/*couldBeEmojiInput*/false, /*couldBeTypingAtOffset0*/false); @@ -482,6 +487,14 @@ export class TextAreaInput extends Disposable { return this._hasFocus; } + public refreshFocusState(): void { + if (document.body.contains(this.textArea.domNode) && document.activeElement === this.textArea.domNode) { + this._setHasFocus(true); + } else { + this._setHasFocus(false); + } + } + private _setHasFocus(newHasFocus: boolean): void { if (this._hasFocus === newHasFocus) { // no change @@ -533,7 +546,7 @@ export class TextAreaInput extends Disposable { } private _ensureClipboardGetsEditorSelection(e: ClipboardEvent): void { - const dataToCopy = this._host.getDataToCopy(ClipboardEventUtils.canUseTextData(e) && browser.hasClipboardSupport()); + const dataToCopy = this._host.getDataToCopy(ClipboardEventUtils.canUseTextData(e) && BrowserFeatures.clipboard.richText); const storedMetadata: ClipboardStoredMetadata = { version: 1, isFromEmptySelection: dataToCopy.isFromEmptySelection, diff --git a/src/vs/editor/browser/controller/textAreaState.ts b/src/vs/editor/browser/controller/textAreaState.ts index 2f7730da8a00..76e07f80d878 100644 --- a/src/vs/editor/browser/controller/textAreaState.ts +++ b/src/vs/editor/browser/controller/textAreaState.ts @@ -226,26 +226,24 @@ export class TextAreaState { } export class PagedScreenReaderStrategy { - private static readonly _LINES_PER_PAGE = 10; - - private static _getPageOfLine(lineNumber: number): number { - return Math.floor((lineNumber - 1) / PagedScreenReaderStrategy._LINES_PER_PAGE); + private static _getPageOfLine(lineNumber: number, linesPerPage: number): number { + return Math.floor((lineNumber - 1) / linesPerPage); } - private static _getRangeForPage(page: number): Range { - const offset = page * PagedScreenReaderStrategy._LINES_PER_PAGE; + private static _getRangeForPage(page: number, linesPerPage: number): Range { + const offset = page * linesPerPage; const startLineNumber = offset + 1; - const endLineNumber = offset + PagedScreenReaderStrategy._LINES_PER_PAGE; + const endLineNumber = offset + linesPerPage; return new Range(startLineNumber, 1, endLineNumber + 1, 1); } - public static fromEditorSelection(previousState: TextAreaState, model: ISimpleModel, selection: Range, trimLongText: boolean): TextAreaState { + public static fromEditorSelection(previousState: TextAreaState, model: ISimpleModel, selection: Range, linesPerPage: number, trimLongText: boolean): TextAreaState { - const selectionStartPage = PagedScreenReaderStrategy._getPageOfLine(selection.startLineNumber); - const selectionStartPageRange = PagedScreenReaderStrategy._getRangeForPage(selectionStartPage); + const selectionStartPage = PagedScreenReaderStrategy._getPageOfLine(selection.startLineNumber, linesPerPage); + const selectionStartPageRange = PagedScreenReaderStrategy._getRangeForPage(selectionStartPage, linesPerPage); - const selectionEndPage = PagedScreenReaderStrategy._getPageOfLine(selection.endLineNumber); - const selectionEndPageRange = PagedScreenReaderStrategy._getRangeForPage(selectionEndPage); + const selectionEndPage = PagedScreenReaderStrategy._getPageOfLine(selection.endLineNumber, linesPerPage); + const selectionEndPageRange = PagedScreenReaderStrategy._getRangeForPage(selectionEndPage, linesPerPage); const pretextRange = selectionStartPageRange.intersectRanges(new Range(1, 1, selection.startLineNumber, selection.startColumn))!; let pretext = model.getValueInRange(pretextRange, EndOfLinePreference.LF); diff --git a/src/vs/editor/browser/editorBrowser.ts b/src/vs/editor/browser/editorBrowser.ts index 4d49958cfa87..02b5da393f27 100644 --- a/src/vs/editor/browser/editorBrowser.ts +++ b/src/vs/editor/browser/editorBrowser.ts @@ -6,7 +6,7 @@ import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { IMouseEvent, IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; import { IDisposable } from 'vs/base/common/lifecycle'; -import { OverviewRulerPosition, ConfigurationChangedEvent, EditorLayoutInfo, IComputedEditorOptions, EditorOption, FindComputedEditorOptionValueById, IEditorOptions } from 'vs/editor/common/config/editorOptions'; +import { OverviewRulerPosition, ConfigurationChangedEvent, EditorLayoutInfo, IComputedEditorOptions, EditorOption, FindComputedEditorOptionValueById, IEditorOptions, IDiffEditorOptions } from 'vs/editor/common/config/editorOptions'; import { ICursors } from 'vs/editor/common/controller/cursorCommon'; import { ICursorPositionChangedEvent, ICursorSelectionChangedEvent } from 'vs/editor/common/controller/cursorEvents'; import { IPosition, Position } from 'vs/editor/common/core/position'; @@ -16,7 +16,7 @@ import * as editorCommon from 'vs/editor/common/editorCommon'; import { IIdentifiedSingleEditOperation, IModelDecoration, IModelDeltaDecoration, ITextModel, ICursorStateComputer } from 'vs/editor/common/model'; import { IModelContentChangedEvent, IModelDecorationsChangedEvent, IModelLanguageChangedEvent, IModelLanguageConfigurationChangedEvent, IModelOptionsChangedEvent } from 'vs/editor/common/model/textModelEvents'; import { OverviewRulerZone } from 'vs/editor/common/view/overviewZoneManager'; -import { IEditorWhitespace } from 'vs/editor/common/viewLayout/whitespaceComputer'; +import { IEditorWhitespace } from 'vs/editor/common/viewLayout/linesLayout'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IDiffComputationResult } from 'vs/editor/common/services/editorWorkerService'; @@ -938,6 +938,11 @@ export interface IDiffEditor extends editorCommon.IEditor { * If the diff computation is not finished or the model is missing, will return null. */ getDiffLineInformationForModified(lineNumber: number): IDiffLineInformation | null; + + /** + * Update the editor's options after the editor has been created. + */ + updateOptions(newOptions: IDiffEditorOptions): void; } /** diff --git a/src/vs/editor/browser/editorDom.ts b/src/vs/editor/browser/editorDom.ts index acbb8a7612a7..97104f762d5b 100644 --- a/src/vs/editor/browser/editorDom.ts +++ b/src/vs/editor/browser/editorDom.ts @@ -84,7 +84,7 @@ export class EditorMouseEvent extends StandardMouseEvent { } export interface EditorMouseEventMerger { - (lastEvent: EditorMouseEvent, currentEvent: EditorMouseEvent): EditorMouseEvent; + (lastEvent: EditorMouseEvent | null, currentEvent: EditorMouseEvent): EditorMouseEvent; } export class EditorMouseEventFactory { @@ -124,17 +124,55 @@ export class EditorMouseEventFactory { } public onMouseMoveThrottled(target: HTMLElement, callback: (e: EditorMouseEvent) => void, merger: EditorMouseEventMerger, minimumTimeMs: number): IDisposable { - const myMerger: dom.IEventMerger = (lastEvent: EditorMouseEvent, currentEvent: MouseEvent): EditorMouseEvent => { + const myMerger: dom.IEventMerger = (lastEvent: EditorMouseEvent | null, currentEvent: MouseEvent): EditorMouseEvent => { return merger(lastEvent, this._create(currentEvent)); }; return dom.addDisposableThrottledListener(target, 'mousemove', callback, myMerger, minimumTimeMs); } } +export class EditorPointerEventFactory { + + private readonly _editorViewDomNode: HTMLElement; + + constructor(editorViewDomNode: HTMLElement) { + this._editorViewDomNode = editorViewDomNode; + } + + private _create(e: MouseEvent): EditorMouseEvent { + return new EditorMouseEvent(e, this._editorViewDomNode); + } + + public onPointerUp(target: HTMLElement, callback: (e: EditorMouseEvent) => void): IDisposable { + return dom.addDisposableListener(target, 'pointerup', (e: MouseEvent) => { + callback(this._create(e)); + }); + } + + public onPointerDown(target: HTMLElement, callback: (e: EditorMouseEvent) => void): IDisposable { + return dom.addDisposableListener(target, 'pointerdown', (e: MouseEvent) => { + callback(this._create(e)); + }); + } + + public onPointerLeave(target: HTMLElement, callback: (e: EditorMouseEvent) => void): IDisposable { + return dom.addDisposableNonBubblingPointerOutListener(target, (e: MouseEvent) => { + callback(this._create(e)); + }); + } + + public onPointerMoveThrottled(target: HTMLElement, callback: (e: EditorMouseEvent) => void, merger: EditorMouseEventMerger, minimumTimeMs: number): IDisposable { + const myMerger: dom.IEventMerger = (lastEvent: EditorMouseEvent | null, currentEvent: MouseEvent): EditorMouseEvent => { + return merger(lastEvent, this._create(currentEvent)); + }; + return dom.addDisposableThrottledListener(target, 'pointermove', callback, myMerger, minimumTimeMs); + } +} + export class GlobalEditorMouseMoveMonitor extends Disposable { private readonly _editorViewDomNode: HTMLElement; - private readonly _globalMouseMoveMonitor: GlobalMouseMoveMonitor; + protected readonly _globalMouseMoveMonitor: GlobalMouseMoveMonitor; private _keydownListener: IDisposable | null; constructor(editorViewDomNode: HTMLElement) { @@ -157,7 +195,7 @@ export class GlobalEditorMouseMoveMonitor extends Disposable { this._globalMouseMoveMonitor.stopMonitoring(true); }, true); - const myMerger: dom.IEventMerger = (lastEvent: EditorMouseEvent, currentEvent: MouseEvent): EditorMouseEvent => { + const myMerger: dom.IEventMerger = (lastEvent: EditorMouseEvent | null, currentEvent: MouseEvent): EditorMouseEvent => { return merger(lastEvent, new EditorMouseEvent(currentEvent, this._editorViewDomNode)); }; diff --git a/src/vs/editor/browser/editorExtensions.ts b/src/vs/editor/browser/editorExtensions.ts index 0c281a235c15..b795204dbdf9 100644 --- a/src/vs/editor/browser/editorExtensions.ts +++ b/src/vs/editor/browser/editorExtensions.ts @@ -16,7 +16,7 @@ import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; import { CommandsRegistry, ICommandHandlerDescription } from 'vs/platform/commands/common/commands'; import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IConstructorSignature1, ServicesAccessor as InstantiationServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IConstructorSignature1, ServicesAccessor as InstantiationServicesAccessor, BrandedService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindings, KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -42,7 +42,7 @@ export interface ICommandKeybindingsOptions extends IKeybindings { kbExpr?: ContextKeyExpr | null; weight: number; } -export interface ICommandMenubarOptions { +export interface ICommandMenuOptions { menuId: MenuId; group: string; order: number; @@ -54,36 +54,29 @@ export interface ICommandOptions { precondition: ContextKeyExpr | undefined; kbOpts?: ICommandKeybindingsOptions; description?: ICommandHandlerDescription; - menubarOpts?: ICommandMenubarOptions; + menuOpts?: ICommandMenuOptions | ICommandMenuOptions[]; } export abstract class Command { public readonly id: string; public readonly precondition: ContextKeyExpr | undefined; private readonly _kbOpts: ICommandKeybindingsOptions | undefined; - private readonly _menubarOpts: ICommandMenubarOptions | undefined; + private readonly _menuOpts: ICommandMenuOptions | ICommandMenuOptions[] | undefined; private readonly _description: ICommandHandlerDescription | undefined; constructor(opts: ICommandOptions) { this.id = opts.id; this.precondition = opts.precondition; this._kbOpts = opts.kbOpts; - this._menubarOpts = opts.menubarOpts; + this._menuOpts = opts.menuOpts; this._description = opts.description; } public register(): void { - if (this._menubarOpts) { - MenuRegistry.appendMenuItem(this._menubarOpts.menuId, { - group: this._menubarOpts.group, - command: { - id: this.id, - title: this._menubarOpts.title, - // precondition: this.precondition - }, - when: this._menubarOpts.when, - order: this._menubarOpts.order - }); + if (Array.isArray(this._menuOpts)) { + this._menuOpts.forEach(this._registerMenuItem, this); + } else if (this._menuOpts) { + this._registerMenuItem(this._menuOpts); } if (this._kbOpts) { @@ -119,6 +112,19 @@ export abstract class Command { } } + private _registerMenuItem(item: ICommandMenuOptions): void { + MenuRegistry.appendMenuItem(item.menuId, { + group: item.group, + command: { + id: this.id, + title: item.title, + // precondition: this.precondition + }, + when: item.when, + order: item.order + }); + } + public abstract runCommand(accessor: ServicesAccessor, args: any): void | Promise; } @@ -184,44 +190,59 @@ export abstract class EditorCommand extends Command { //#region EditorAction -export interface IEditorCommandMenuOptions { +export interface IEditorActionContextMenuOptions { group: string; order: number; when?: ContextKeyExpr; + menuId?: MenuId; } export interface IActionOptions extends ICommandOptions { label: string; alias: string; - menuOpts?: IEditorCommandMenuOptions; + contextMenuOpts?: IEditorActionContextMenuOptions | IEditorActionContextMenuOptions[]; } + export abstract class EditorAction extends EditorCommand { + private static convertOptions(opts: IActionOptions): ICommandOptions { + + let menuOpts: ICommandMenuOptions[]; + if (Array.isArray(opts.menuOpts)) { + menuOpts = opts.menuOpts; + } else if (opts.menuOpts) { + menuOpts = [opts.menuOpts]; + } else { + menuOpts = []; + } + + function withDefaults(item: Partial): ICommandMenuOptions { + if (!item.menuId) { + item.menuId = MenuId.EditorContext; + } + if (!item.title) { + item.title = opts.label; + } + item.when = ContextKeyExpr.and(opts.precondition, item.when); + return item; + } + + if (Array.isArray(opts.contextMenuOpts)) { + menuOpts.push(...opts.contextMenuOpts.map(withDefaults)); + } else if (opts.contextMenuOpts) { + menuOpts.push(withDefaults(opts.contextMenuOpts)); + } + + opts.menuOpts = menuOpts; + return opts; + } + public readonly label: string; public readonly alias: string; - private readonly menuOpts: IEditorCommandMenuOptions | undefined; constructor(opts: IActionOptions) { - super(opts); + super(EditorAction.convertOptions(opts)); this.label = opts.label; this.alias = opts.alias; - this.menuOpts = opts.menuOpts; - } - - public register(): void { - - if (this.menuOpts) { - MenuRegistry.appendMenuItem(MenuId.EditorContext, { - command: { - id: this.id, - title: this.label - }, - when: ContextKeyExpr.and(this.precondition, this.menuOpts.when), - group: this.menuOpts.group, - order: this.menuOpts.order - }); - } - - super.register(); } public runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void | Promise { @@ -303,11 +324,11 @@ export function registerInstantiatedEditorAction(editorAction: EditorAction): vo EditorContributionRegistry.INSTANCE.registerEditorAction(editorAction); } -export function registerEditorContribution(id: string, ctor: IEditorContributionCtor): void { +export function registerEditorContribution(id: string, ctor: { new(editor: ICodeEditor, ...services: Services): IEditorContribution }): void { EditorContributionRegistry.INSTANCE.registerEditorContribution(id, ctor); } -export function registerDiffEditorContribution(id: string, ctor: IDiffEditorContributionCtor): void { +export function registerDiffEditorContribution(id: string, ctor: { new(editor: IDiffEditor, ...services: Services): IEditorContribution }): void { EditorContributionRegistry.INSTANCE.registerDiffEditorContribution(id, ctor); } @@ -355,16 +376,16 @@ class EditorContributionRegistry { this.editorCommands = Object.create(null); } - public registerEditorContribution(id: string, ctor: IEditorContributionCtor): void { - this.editorContributions.push({ id, ctor }); + public registerEditorContribution(id: string, ctor: { new(editor: ICodeEditor, ...services: Services): IEditorContribution }): void { + this.editorContributions.push({ id, ctor: ctor as IEditorContributionCtor }); } public getEditorContributions(): IEditorContributionDescription[] { return this.editorContributions.slice(0); } - public registerDiffEditorContribution(id: string, ctor: IDiffEditorContributionCtor): void { - this.diffEditorContributions.push({ id, ctor }); + public registerDiffEditorContribution(id: string, ctor: { new(editor: IDiffEditor, ...services: Services): IEditorContribution }): void { + this.diffEditorContributions.push({ id, ctor: ctor as IDiffEditorContributionCtor }); } public getDiffEditorContributions(): IDiffEditorContributionDescription[] { diff --git a/src/vs/editor/browser/services/openerService.ts b/src/vs/editor/browser/services/openerService.ts index 3b49459671fc..772e75ae42ee 100644 --- a/src/vs/editor/browser/services/openerService.ts +++ b/src/vs/editor/browser/services/openerService.ts @@ -4,19 +4,89 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from 'vs/base/browser/dom'; -import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable } from 'vs/base/common/lifecycle'; import { LinkedList } from 'vs/base/common/linkedList'; import { parse } from 'vs/base/common/marshalling'; import { Schemas } from 'vs/base/common/network'; -import * as resources from 'vs/base/common/resources'; -import { equalsIgnoreCase } from 'vs/base/common/strings'; +import { normalizePath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; -import { IOpener, IOpenerService, IValidator, IExternalUriResolver, OpenOptions } from 'vs/platform/opener/common/opener'; +import { IOpener, IOpenerService, IValidator, IExternalUriResolver, OpenOptions, ResolveExternalUriOptions, IResolvedExternalUri, IExternalOpener, matchesScheme } from 'vs/platform/opener/common/opener'; import { EditorOpenContext } from 'vs/platform/editor/common/editor'; -export class OpenerService extends Disposable implements IOpenerService { + +class CommandOpener implements IOpener { + + constructor(@ICommandService private readonly _commandService: ICommandService) { } + + async open(target: URI | string) { + if (!matchesScheme(target, Schemas.command)) { + return false; + } + // run command or bail out if command isn't known + if (typeof target === 'string') { + target = URI.parse(target); + } + if (!CommandsRegistry.getCommand(target.path)) { + throw new Error(`command '${target.path}' NOT known`); + } + // execute as command + let args: any = []; + try { + args = parse(decodeURIComponent(target.query)); + } catch { + // ignore and retry + try { + args = parse(target.query); + } catch { + // ignore error + } + } + if (!Array.isArray(args)) { + args = [args]; + } + await this._commandService.executeCommand(target.path, ...args); + return true; + } +} + +class EditorOpener implements IOpener { + + constructor(@ICodeEditorService private readonly _editorService: ICodeEditorService) { } + + async open(target: URI | string, options: OpenOptions) { + if (typeof target === 'string') { + target = URI.parse(target); + } + let selection: { startLineNumber: number; startColumn: number; } | undefined = undefined; + const match = /^L?(\d+)(?:,(\d+))?/.exec(target.fragment); + if (match) { + // support file:///some/file.js#73,84 + // support file:///some/file.js#L73 + selection = { + startLineNumber: parseInt(match[1]), + startColumn: match[2] ? parseInt(match[2]) : 1 + }; + // remove fragment + target = target.with({ fragment: '' }); + } + + if (target.scheme === Schemas.file) { + target = normalizePath(target); // workaround for non-normalized paths (https://github.com/Microsoft/vscode/issues/12954) + } + + await this._editorService.openCodeEditor( + { resource: target, options: { selection, context: options?.fromUserGesture ? EditorOpenContext.USER : EditorOpenContext.API } }, + this._editorService.getFocusedCodeEditor(), + options?.openToSide + ); + + return true; + } +} + +export class OpenerService implements IOpenerService { _serviceBrand: undefined; @@ -24,58 +94,75 @@ export class OpenerService extends Disposable implements IOpenerService { private readonly _validators = new LinkedList(); private readonly _resolvers = new LinkedList(); + private _externalOpener: IExternalOpener; + constructor( - @ICodeEditorService private readonly _editorService: ICodeEditorService, - @ICommandService private readonly _commandService: ICommandService, + @ICodeEditorService editorService: ICodeEditorService, + @ICommandService commandService: ICommandService, ) { - super(); + // Default external opener is going through window.open() + this._externalOpener = { + openExternal: href => { + dom.windowOpenNoOpener(href); + return Promise.resolve(true); + } + }; + + // Default opener: maito, http(s), command, and catch-all-editors + this._openers.push({ + open: async (target: URI | string, options?: OpenOptions) => { + if (options?.openExternal || matchesScheme(target, Schemas.mailto) || matchesScheme(target, Schemas.http) || matchesScheme(target, Schemas.https)) { + // open externally + await this._doOpenExternal(target, options); + return true; + } + return false; + } + }); + this._openers.push(new CommandOpener(commandService)); + this._openers.push(new EditorOpener(editorService)); } registerOpener(opener: IOpener): IDisposable { - const remove = this._openers.push(opener); - + const remove = this._openers.unshift(opener); return { dispose: remove }; } registerValidator(validator: IValidator): IDisposable { const remove = this._validators.push(validator); - return { dispose: remove }; } registerExternalUriResolver(resolver: IExternalUriResolver): IDisposable { const remove = this._resolvers.push(resolver); - return { dispose: remove }; } - async open(resource: URI, options?: OpenOptions): Promise { + setExternalOpener(externalOpener: IExternalOpener): void { + this._externalOpener = externalOpener; + } - // no scheme ?!? - if (!resource.scheme) { - return Promise.resolve(false); - } + async open(target: URI | string, options?: OpenOptions): Promise { // check with contributed validators for (const validator of this._validators.toArray()) { - if (!(await validator.shouldOpen(resource))) { + if (!(await validator.shouldOpen(target))) { return false; } } // check with contributed openers for (const opener of this._openers.toArray()) { - const handled = await opener.open(resource, options); + const handled = await opener.open(target, options); if (handled) { return true; } } - // use default openers - return this._doOpen(resource, options); + return false; } - async resolveExternalUri(resource: URI, options?: { readonly allowTunneling?: boolean }): Promise<{ resolved: URI, dispose(): void }> { + async resolveExternalUri(resource: URI, options?: ResolveExternalUriOptions): Promise { for (const resolver of this._resolvers.toArray()) { const result = await resolver.resolveExternalUri(resource, options); if (result) { @@ -86,72 +173,19 @@ export class OpenerService extends Disposable implements IOpenerService { return { resolved: resource, dispose: () => { } }; } - private async _doOpen(resource: URI, options: OpenOptions | undefined): Promise { - const { scheme, path, query, fragment } = resource; - - if (equalsIgnoreCase(scheme, Schemas.mailto) || options?.openExternal) { - // open default mail application - return this._doOpenExternal(resource, options); - } - - if (equalsIgnoreCase(scheme, Schemas.http) || equalsIgnoreCase(scheme, Schemas.https)) { - // open link in default browser - return this._doOpenExternal(resource, options); - } - - if (equalsIgnoreCase(scheme, Schemas.command)) { - // run command or bail out if command isn't known - if (!CommandsRegistry.getCommand(path)) { - return Promise.reject(`command '${path}' NOT known`); - } - // execute as command - let args: any = []; - try { - args = parse(query); - if (!Array.isArray(args)) { - args = [args]; - } - } catch (e) { - // ignore error - } - - await this._commandService.executeCommand(path, ...args); - - return true; - } + private async _doOpenExternal(resource: URI | string, options: OpenOptions | undefined): Promise { - // finally open in editor - let selection: { startLineNumber: number; startColumn: number; } | undefined = undefined; - const match = /^L?(\d+)(?:,(\d+))?/.exec(fragment); - if (match) { - // support file:///some/file.js#73,84 - // support file:///some/file.js#L73 - selection = { - startLineNumber: parseInt(match[1]), - startColumn: match[2] ? parseInt(match[2]) : 1 - }; - // remove fragment - resource = resource.with({ fragment: '' }); - } + //todo@joh IExternalUriResolver should support `uri: URI | string` + const uri = typeof resource === 'string' ? URI.parse(resource) : resource; + const { resolved } = await this.resolveExternalUri(uri, options); - if (resource.scheme === Schemas.file) { - resource = resources.normalizePath(resource); // workaround for non-normalized paths (https://github.com/Microsoft/vscode/issues/12954) + if (typeof resource === 'string' && uri.toString() === resolved.toString()) { + // open the url-string AS IS + return this._externalOpener.openExternal(resource); + } else { + // open URI using the toString(noEncode)+encodeURI-trick + return this._externalOpener.openExternal(encodeURI(resolved.toString(true))); } - - await this._editorService.openCodeEditor( - { resource, options: { selection, context: options?.fromUserGesture ? EditorOpenContext.USER : EditorOpenContext.API } }, - this._editorService.getFocusedCodeEditor(), - options?.openToSide - ); - - return true; - } - - private async _doOpenExternal(resource: URI, options: OpenOptions | undefined): Promise { - const { resolved } = await this.resolveExternalUri(resource, options); - dom.windowOpenNoOpener(encodeURI(resolved.toString(true))); - - return true; } dispose() { diff --git a/src/vs/editor/browser/view/viewImpl.ts b/src/vs/editor/browser/view/viewImpl.ts index ae67d9315f5a..e5f64de83c7c 100644 --- a/src/vs/editor/browser/view/viewImpl.ts +++ b/src/vs/editor/browser/view/viewImpl.ts @@ -11,14 +11,13 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import { IPointerHandlerHelper } from 'vs/editor/browser/controller/mouseHandler'; import { PointerHandler } from 'vs/editor/browser/controller/pointerHandler'; import { ITextAreaHandlerHelper, TextAreaHandler } from 'vs/editor/browser/controller/textAreaHandler'; -import * as editorBrowser from 'vs/editor/browser/editorBrowser'; +import { IContentWidget, IContentWidgetPosition, IOverlayWidget, IOverlayWidgetPosition, IMouseTarget, IViewZoneChangeAccessor } from 'vs/editor/browser/editorBrowser'; import { ICommandDelegate, ViewController } from 'vs/editor/browser/view/viewController'; import { ViewOutgoingEvents } from 'vs/editor/browser/view/viewOutgoingEvents'; import { ContentViewOverlays, MarginViewOverlays } from 'vs/editor/browser/view/viewOverlays'; import { PartFingerprint, PartFingerprints, ViewPart } from 'vs/editor/browser/view/viewPart'; import { ViewContentWidgets } from 'vs/editor/browser/viewParts/contentWidgets/contentWidgets'; -import { CurrentLineHighlightOverlay } from 'vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight'; -import { CurrentLineMarginHighlightOverlay } from 'vs/editor/browser/viewParts/currentLineMarginHighlight/currentLineMarginHighlight'; +import { CurrentLineHighlightOverlay, CurrentLineMarginHighlightOverlay } from 'vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight'; import { DecorationsOverlay } from 'vs/editor/browser/viewParts/decorations/decorations'; import { EditorScrollbar } from 'vs/editor/browser/viewParts/editorScrollbar/editorScrollbar'; import { GlyphMarginOverlay } from 'vs/editor/browser/viewParts/glyphMargin/glyphMargin'; @@ -52,17 +51,15 @@ import { EditorOption } from 'vs/editor/common/config/editorOptions'; export interface IContentWidgetData { - widget: editorBrowser.IContentWidget; - position: editorBrowser.IContentWidgetPosition | null; + widget: IContentWidget; + position: IContentWidgetPosition | null; } export interface IOverlayWidgetData { - widget: editorBrowser.IOverlayWidget; - position: editorBrowser.IOverlayWidgetPosition | null; + widget: IOverlayWidget; + position: IOverlayWidgetPosition | null; } -const invalidFunc = () => { throw new Error(`Invalid change accessor`); }; - export class View extends ViewEventHandler { private readonly eventDispatcher: ViewEventDispatcher; @@ -261,7 +258,7 @@ export class View extends ViewEventHandler { return this.viewLines.getPositionFromDOMInfo(spanNode, offset); }, - visibleRangeForPosition2: (lineNumber: number, column: number) => { + visibleRangeForPosition: (lineNumber: number, column: number) => { this._flushAccumulatedAndRenderNow(); return this.viewLines.visibleRangeForPosition(new Position(lineNumber, column)); }, @@ -349,7 +346,7 @@ export class View extends ViewEventHandler { super.dispose(); } - private _renderOnce(callback: () => any): any { + private _renderOnce(callback: () => T): T { const r = safeInvokeNoArg(callback); this._scheduleRender(); return r; @@ -459,7 +456,7 @@ export class View extends ViewEventHandler { return visibleRange.left; } - public getTargetAtClientPoint(clientX: number, clientY: number): editorBrowser.IMouseTarget | null { + public getTargetAtClientPoint(clientX: number, clientY: number): IMouseTarget | null { return this.pointerHandler.getTargetAtClientPoint(clientX, clientY); } @@ -467,42 +464,15 @@ export class View extends ViewEventHandler { return new OverviewRuler(this._context, cssClassName); } - public change(callback: (changeAccessor: editorBrowser.IViewZoneChangeAccessor) => any): boolean { - let zonesHaveChanged = false; - - this._renderOnce(() => { - const changeAccessor: editorBrowser.IViewZoneChangeAccessor = { - addZone: (zone: editorBrowser.IViewZone): string => { - zonesHaveChanged = true; - return this.viewZones.addZone(zone); - }, - removeZone: (id: string): void => { - if (!id) { - return; - } - zonesHaveChanged = this.viewZones.removeZone(id) || zonesHaveChanged; - }, - layoutZone: (id: string): void => { - if (!id) { - return; - } - zonesHaveChanged = this.viewZones.layoutZone(id) || zonesHaveChanged; - } - }; - - safeInvoke1Arg(callback, changeAccessor); - - // Invalidate changeAccessor - changeAccessor.addZone = invalidFunc; - changeAccessor.removeZone = invalidFunc; - changeAccessor.layoutZone = invalidFunc; - + public change(callback: (changeAccessor: IViewZoneChangeAccessor) => any): boolean { + return this._renderOnce(() => { + const zonesHaveChanged = this.viewZones.changeViewZones(callback); if (zonesHaveChanged) { this._context.viewLayout.onHeightMaybeChanged(); this._context.privateViewEventBus.emit(new viewEvents.ViewZonesChangedEvent()); } + return zonesHaveChanged; }); - return zonesHaveChanged; } public render(now: boolean, everything: boolean): void { @@ -529,6 +499,10 @@ export class View extends ViewEventHandler { return this._textAreaHandler.isFocused(); } + public refreshFocusState() { + this._textAreaHandler.refreshFocusState(); + } + public addContentWidget(widgetData: IContentWidgetData): void { this.contentWidgets.addWidget(widgetData.widget); this.layoutContentWidget(widgetData); @@ -579,10 +553,3 @@ function safeInvokeNoArg(func: Function): any { } } -function safeInvoke1Arg(func: Function, arg1: any): any { - try { - return func(arg1); - } catch (e) { - onUnexpectedError(e); - } -} diff --git a/src/vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight.css b/src/vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight.css index 25966f4e5ad3..d427d435bfce 100644 --- a/src/vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight.css +++ b/src/vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight.css @@ -9,4 +9,16 @@ left: 0; top: 0; box-sizing: border-box; -} \ No newline at end of file +} + +.monaco-editor .margin-view-overlays .current-line { + display: block; + position: absolute; + left: 0; + top: 0; + box-sizing: border-box; +} + +.monaco-editor .margin-view-overlays .current-line.current-line-margin.current-line-margin-both { + border-right: 0; +} diff --git a/src/vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight.ts b/src/vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight.ts index 762d2cff11a0..7d613b77ae14 100644 --- a/src/vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight.ts +++ b/src/vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight.ts @@ -9,18 +9,23 @@ import { editorLineHighlight, editorLineHighlightBorder } from 'vs/editor/common import { RenderingContext } from 'vs/editor/common/view/renderingContext'; import { ViewContext } from 'vs/editor/common/view/viewContext'; import * as viewEvents from 'vs/editor/common/view/viewEvents'; +import * as arrays from 'vs/base/common/arrays'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { Selection } from 'vs/editor/common/core/selection'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; +let isRenderedUsingBorder = true; -export class CurrentLineHighlightOverlay extends DynamicViewOverlay { +export abstract class AbstractLineHighlightOverlay extends DynamicViewOverlay { private readonly _context: ViewContext; - private _lineHeight: number; - private _renderLineHighlight: 'none' | 'gutter' | 'line' | 'all'; - private _contentWidth: number; - private _selectionIsEmpty: boolean; - private _primaryCursorLineNumber: number; - private _scrollWidth: number; + protected _lineHeight: number; + protected _renderLineHighlight: 'none' | 'gutter' | 'line' | 'all'; + protected _contentLeft: number; + protected _contentWidth: number; + protected _selectionIsEmpty: boolean; + private _cursorLineNumbers: number[]; + private _selections: Selection[]; + private _renderData: string[] | null; constructor(context: ViewContext) { super(); @@ -28,15 +33,14 @@ export class CurrentLineHighlightOverlay extends DynamicViewOverlay { const options = this._context.configuration.options; const layoutInfo = options.get(EditorOption.layoutInfo); - this._lineHeight = options.get(EditorOption.lineHeight); this._renderLineHighlight = options.get(EditorOption.renderLineHighlight); + this._contentLeft = layoutInfo.contentLeft; this._contentWidth = layoutInfo.contentWidth; - this._selectionIsEmpty = true; - this._primaryCursorLineNumber = 1; - this._scrollWidth = 0; - + this._cursorLineNumbers = []; + this._selections = []; + this._renderData = null; this._context.addEventHandler(this); } @@ -46,34 +50,45 @@ export class CurrentLineHighlightOverlay extends DynamicViewOverlay { super.dispose(); } - // --- begin event handlers - - public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { - const options = this._context.configuration.options; - const layoutInfo = options.get(EditorOption.layoutInfo); - - this._lineHeight = options.get(EditorOption.lineHeight); - this._renderLineHighlight = options.get(EditorOption.renderLineHighlight); - this._contentWidth = layoutInfo.contentWidth; - return true; - } - public onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean { + private _readFromSelections(): boolean { let hasChanged = false; - const primaryCursorLineNumber = e.selections[0].positionLineNumber; - if (this._primaryCursorLineNumber !== primaryCursorLineNumber) { - this._primaryCursorLineNumber = primaryCursorLineNumber; + // Only render the first selection when using border + const renderSelections = isRenderedUsingBorder ? this._selections.slice(0, 1) : this._selections; + + const cursorsLineNumbers = renderSelections.map(s => s.positionLineNumber); + cursorsLineNumbers.sort(); + if (!arrays.equals(this._cursorLineNumbers, cursorsLineNumbers)) { + this._cursorLineNumbers = cursorsLineNumbers; hasChanged = true; } - const selectionIsEmpty = e.selections[0].isEmpty(); + const selectionIsEmpty = renderSelections.every(s => s.isEmpty()); if (this._selectionIsEmpty !== selectionIsEmpty) { this._selectionIsEmpty = selectionIsEmpty; - return true; + hasChanged = true; } return hasChanged; } + + // --- begin event handlers + public onThemeChanged(e: viewEvents.ViewThemeChangedEvent): boolean { + return this._readFromSelections(); + } + public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { + const options = this._context.configuration.options; + const layoutInfo = options.get(EditorOption.layoutInfo); + this._lineHeight = options.get(EditorOption.lineHeight); + this._renderLineHighlight = options.get(EditorOption.renderLineHighlight); + this._contentLeft = layoutInfo.contentLeft; + this._contentWidth = layoutInfo.contentWidth; + return true; + } + public onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean { + this._selections = e.selections; + return this._readFromSelections(); + } public onFlushed(e: viewEvents.ViewFlushedEvent): boolean { return true; } @@ -84,7 +99,7 @@ export class CurrentLineHighlightOverlay extends DynamicViewOverlay { return true; } public onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean { - return e.scrollWidthChanged; + return e.scrollWidthChanged || e.scrollTopChanged; } public onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean { return true; @@ -92,55 +107,99 @@ export class CurrentLineHighlightOverlay extends DynamicViewOverlay { // --- end event handlers public prepareRender(ctx: RenderingContext): void { - this._scrollWidth = ctx.scrollWidth; + if (!this._shouldRenderThis()) { + this._renderData = null; + return; + } + const renderedLine = this._renderOne(ctx); + const visibleStartLineNumber = ctx.visibleRange.startLineNumber; + const visibleEndLineNumber = ctx.visibleRange.endLineNumber; + const len = this._cursorLineNumbers.length; + let index = 0; + const renderData: string[] = []; + for (let lineNumber = visibleStartLineNumber; lineNumber <= visibleEndLineNumber; lineNumber++) { + const lineIndex = lineNumber - visibleStartLineNumber; + while (index < len && this._cursorLineNumbers[index] < lineNumber) { + index++; + } + if (index < len && this._cursorLineNumbers[index] === lineNumber) { + renderData[lineIndex] = renderedLine; + } else { + renderData[lineIndex] = ''; + } + } + this._renderData = renderData; } public render(startLineNumber: number, lineNumber: number): string { - if (lineNumber === this._primaryCursorLineNumber) { - if (this._shouldShowCurrentLine()) { - const paintedInMargin = this._willRenderMarginCurrentLine(); - const className = 'current-line' + (paintedInMargin ? ' current-line-both' : ''); - return ( - '
' - ); - } else { - return ''; - } + if (!this._renderData) { + return ''; } - return ''; + const lineIndex = lineNumber - startLineNumber; + if (lineIndex >= this._renderData.length) { + return ''; + } + return this._renderData[lineIndex]; } - private _shouldShowCurrentLine(): boolean { + protected abstract _shouldRenderThis(): boolean; + protected abstract _shouldRenderOther(): boolean; + protected abstract _renderOne(ctx: RenderingContext): string; +} + +export class CurrentLineHighlightOverlay extends AbstractLineHighlightOverlay { + + protected _renderOne(ctx: RenderingContext): string { + const className = 'current-line' + (this._shouldRenderOther() ? ' current-line-both' : ''); + return `
`; + } + protected _shouldRenderThis(): boolean { return ( (this._renderLineHighlight === 'line' || this._renderLineHighlight === 'all') && this._selectionIsEmpty ); } + protected _shouldRenderOther(): boolean { + return ( + (this._renderLineHighlight === 'gutter' || this._renderLineHighlight === 'all') + ); + } +} - private _willRenderMarginCurrentLine(): boolean { +export class CurrentLineMarginHighlightOverlay extends AbstractLineHighlightOverlay { + protected _renderOne(ctx: RenderingContext): string { + const className = 'current-line current-line-margin' + (this._shouldRenderOther() ? ' current-line-margin-both' : ''); + return `
`; + } + protected _shouldRenderThis(): boolean { return ( (this._renderLineHighlight === 'gutter' || this._renderLineHighlight === 'all') ); } + protected _shouldRenderOther(): boolean { + return ( + (this._renderLineHighlight === 'line' || this._renderLineHighlight === 'all') + && this._selectionIsEmpty + ); + } } registerThemingParticipant((theme, collector) => { + isRenderedUsingBorder = false; const lineHighlight = theme.getColor(editorLineHighlight); if (lineHighlight) { collector.addRule(`.monaco-editor .view-overlays .current-line { background-color: ${lineHighlight}; }`); + collector.addRule(`.monaco-editor .margin-view-overlays .current-line-margin { background-color: ${lineHighlight}; border: none; }`); } if (!lineHighlight || lineHighlight.isTransparent() || theme.defines(editorLineHighlightBorder)) { const lineHighlightBorder = theme.getColor(editorLineHighlightBorder); if (lineHighlightBorder) { + isRenderedUsingBorder = true; collector.addRule(`.monaco-editor .view-overlays .current-line { border: 2px solid ${lineHighlightBorder}; }`); + collector.addRule(`.monaco-editor .margin-view-overlays .current-line-margin { border: 2px solid ${lineHighlightBorder}; }`); if (theme.type === 'hc') { collector.addRule(`.monaco-editor .view-overlays .current-line { border-width: 1px; }`); + collector.addRule(`.monaco-editor .margin-view-overlays .current-line-margin { border-width: 1px; }`); } } } diff --git a/src/vs/editor/browser/viewParts/currentLineMarginHighlight/currentLineMarginHighlight.css b/src/vs/editor/browser/viewParts/currentLineMarginHighlight/currentLineMarginHighlight.css deleted file mode 100644 index 970c90a454a4..000000000000 --- a/src/vs/editor/browser/viewParts/currentLineMarginHighlight/currentLineMarginHighlight.css +++ /dev/null @@ -1,16 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -.monaco-editor .margin-view-overlays .current-line { - display: block; - position: absolute; - left: 0; - top: 0; - box-sizing: border-box; -} - -.monaco-editor .margin-view-overlays .current-line.current-line-margin.current-line-margin-both { - border-right: 0; -} \ No newline at end of file diff --git a/src/vs/editor/browser/viewParts/currentLineMarginHighlight/currentLineMarginHighlight.ts b/src/vs/editor/browser/viewParts/currentLineMarginHighlight/currentLineMarginHighlight.ts deleted file mode 100644 index cd0aa2c9fa6c..000000000000 --- a/src/vs/editor/browser/viewParts/currentLineMarginHighlight/currentLineMarginHighlight.ts +++ /dev/null @@ -1,139 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import 'vs/css!./currentLineMarginHighlight'; -import { DynamicViewOverlay } from 'vs/editor/browser/view/dynamicViewOverlay'; -import { editorLineHighlight, editorLineHighlightBorder } from 'vs/editor/common/view/editorColorRegistry'; -import { RenderingContext } from 'vs/editor/common/view/renderingContext'; -import { ViewContext } from 'vs/editor/common/view/viewContext'; -import * as viewEvents from 'vs/editor/common/view/viewEvents'; -import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; -import { EditorOption } from 'vs/editor/common/config/editorOptions'; - - -export class CurrentLineMarginHighlightOverlay extends DynamicViewOverlay { - private readonly _context: ViewContext; - private _lineHeight: number; - private _renderLineHighlight: 'none' | 'gutter' | 'line' | 'all'; - private _contentLeft: number; - private _selectionIsEmpty: boolean; - private _primaryCursorLineNumber: number; - - constructor(context: ViewContext) { - super(); - this._context = context; - - const options = this._context.configuration.options; - const layoutInfo = options.get(EditorOption.layoutInfo); - - this._lineHeight = options.get(EditorOption.lineHeight); - this._renderLineHighlight = options.get(EditorOption.renderLineHighlight); - this._contentLeft = layoutInfo.contentLeft; - - this._selectionIsEmpty = true; - this._primaryCursorLineNumber = 1; - - this._context.addEventHandler(this); - } - - public dispose(): void { - this._context.removeEventHandler(this); - super.dispose(); - } - - // --- begin event handlers - - public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { - const options = this._context.configuration.options; - const layoutInfo = options.get(EditorOption.layoutInfo); - - this._lineHeight = options.get(EditorOption.lineHeight); - this._renderLineHighlight = options.get(EditorOption.renderLineHighlight); - this._contentLeft = layoutInfo.contentLeft; - return true; - } - public onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean { - let hasChanged = false; - - const primaryCursorLineNumber = e.selections[0].positionLineNumber; - if (this._primaryCursorLineNumber !== primaryCursorLineNumber) { - this._primaryCursorLineNumber = primaryCursorLineNumber; - hasChanged = true; - } - - const selectionIsEmpty = e.selections[0].isEmpty(); - if (this._selectionIsEmpty !== selectionIsEmpty) { - this._selectionIsEmpty = selectionIsEmpty; - return true; - } - - return hasChanged; - } - public onFlushed(e: viewEvents.ViewFlushedEvent): boolean { - return true; - } - public onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): boolean { - return true; - } - public onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): boolean { - return true; - } - public onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean { - return true; - } - // --- end event handlers - - public prepareRender(ctx: RenderingContext): void { - } - - public render(startLineNumber: number, lineNumber: number): string { - if (lineNumber === this._primaryCursorLineNumber) { - let className = 'current-line'; - if (this._shouldShowCurrentLine()) { - const paintedInContent = this._willRenderContentCurrentLine(); - className = 'current-line current-line-margin' + (paintedInContent ? ' current-line-margin-both' : ''); - } - - return ( - '
' - ); - } - return ''; - } - - private _shouldShowCurrentLine(): boolean { - return ( - (this._renderLineHighlight === 'gutter' || this._renderLineHighlight === 'all') - ); - } - - private _willRenderContentCurrentLine(): boolean { - return ( - (this._renderLineHighlight === 'line' || this._renderLineHighlight === 'all') - && this._selectionIsEmpty - ); - } -} - -registerThemingParticipant((theme, collector) => { - const lineHighlight = theme.getColor(editorLineHighlight); - if (lineHighlight) { - collector.addRule(`.monaco-editor .margin-view-overlays .current-line-margin { background-color: ${lineHighlight}; border: none; }`); - } else { - const lineHighlightBorder = theme.getColor(editorLineHighlightBorder); - if (lineHighlightBorder) { - collector.addRule(`.monaco-editor .margin-view-overlays .current-line-margin { border: 2px solid ${lineHighlightBorder}; }`); - } - if (theme.type === 'hc') { - collector.addRule(`.monaco-editor .margin-view-overlays .current-line-margin { border-width: 1px; }`); - } - } -}); diff --git a/src/vs/editor/browser/viewParts/decorations/decorations.ts b/src/vs/editor/browser/viewParts/decorations/decorations.ts index 7baed2b6f009..dac0caeb534e 100644 --- a/src/vs/editor/browser/viewParts/decorations/decorations.ts +++ b/src/vs/editor/browser/viewParts/decorations/decorations.ts @@ -195,6 +195,9 @@ export class DecorationsOverlay extends DynamicViewOverlay { for (let j = 0, lenJ = linesVisibleRanges.length; j < lenJ; j++) { const lineVisibleRanges = linesVisibleRanges[j]; + if (lineVisibleRanges.outsideRenderedLine) { + continue; + } const lineIndex = lineVisibleRanges.lineNumber - visibleStartLineNumber; if (showIfCollapsed && lineVisibleRanges.ranges.length === 1) { diff --git a/src/vs/editor/browser/viewParts/glyphMargin/glyphMargin.ts b/src/vs/editor/browser/viewParts/glyphMargin/glyphMargin.ts index 4b25021a9e07..52ac1498bf82 100644 --- a/src/vs/editor/browser/viewParts/glyphMargin/glyphMargin.ts +++ b/src/vs/editor/browser/viewParts/glyphMargin/glyphMargin.ts @@ -177,7 +177,7 @@ export class GlyphMarginOverlay extends DedupOverlay { output[lineIndex] = ''; } else { output[lineIndex] = ( - '
stopRenderingLineAfter && endColumn > stopRenderingLineAfter) { + if (stopRenderingLineAfter !== -1 && startColumn > stopRenderingLineAfter + 1 && endColumn > stopRenderingLineAfter + 1) { // This range is obviously not visible - return null; + outsideRenderedLine = true; } - if (stopRenderingLineAfter !== -1 && startColumn > stopRenderingLineAfter) { - startColumn = stopRenderingLineAfter; + if (stopRenderingLineAfter !== -1 && startColumn > stopRenderingLineAfter + 1) { + startColumn = stopRenderingLineAfter + 1; } - if (stopRenderingLineAfter !== -1 && endColumn > stopRenderingLineAfter) { - endColumn = stopRenderingLineAfter; + if (stopRenderingLineAfter !== -1 && endColumn > stopRenderingLineAfter + 1) { + endColumn = stopRenderingLineAfter + 1; } - return this._renderedViewLine.getVisibleRangesForRange(startColumn, endColumn, context); + const horizontalRanges = this._renderedViewLine.getVisibleRangesForRange(startColumn, endColumn, context); + if (horizontalRanges && horizontalRanges.length > 0) { + return new VisibleRanges(outsideRenderedLine, horizontalRanges); + } + + return null; } public getColumnOfNodeOffset(lineNumber: number, spanNode: HTMLElement, offset: number): number { @@ -401,7 +407,7 @@ class FastRenderedViewLine implements IRenderedViewLine { */ class RenderedViewLine implements IRenderedViewLine { - public domNode: FastDomNode; + public domNode: FastDomNode | null; public readonly input: RenderLineInput; protected readonly _characterMapping: CharacterMapping; @@ -414,7 +420,7 @@ class RenderedViewLine implements IRenderedViewLine { */ private readonly _pixelOffsetCache: Int32Array | null; - constructor(domNode: FastDomNode, renderLineInput: RenderLineInput, characterMapping: CharacterMapping, containsRTL: boolean, containsForeignElements: ForeignElementType) { + constructor(domNode: FastDomNode | null, renderLineInput: RenderLineInput, characterMapping: CharacterMapping, containsRTL: boolean, containsForeignElements: ForeignElementType) { this.domNode = domNode; this.input = renderLineInput; this._characterMapping = characterMapping; @@ -433,16 +439,19 @@ class RenderedViewLine implements IRenderedViewLine { // --- Reading from the DOM methods - protected _getReadingTarget(): HTMLElement { - return this.domNode.domNode.firstChild; + protected _getReadingTarget(myDomNode: FastDomNode): HTMLElement { + return myDomNode.domNode.firstChild; } /** * Width of the line in pixels */ public getWidth(): number { + if (!this.domNode) { + return 0; + } if (this._cachedWidth === -1) { - this._cachedWidth = this._getReadingTarget().offsetWidth; + this._cachedWidth = this._getReadingTarget(this.domNode).offsetWidth; } return this._cachedWidth; } @@ -458,14 +467,17 @@ class RenderedViewLine implements IRenderedViewLine { * Visible ranges for a model range */ public getVisibleRangesForRange(startColumn: number, endColumn: number, context: DomReadingContext): HorizontalRange[] | null { + if (!this.domNode) { + return null; + } if (this._pixelOffsetCache !== null) { // the text is LTR - const startOffset = this._readPixelOffset(startColumn, context); + const startOffset = this._readPixelOffset(this.domNode, startColumn, context); if (startOffset === -1) { return null; } - const endOffset = this._readPixelOffset(endColumn, context); + const endOffset = this._readPixelOffset(this.domNode, endColumn, context); if (endOffset === -1) { return null; } @@ -473,23 +485,23 @@ class RenderedViewLine implements IRenderedViewLine { return [new HorizontalRange(startOffset, endOffset - startOffset)]; } - return this._readVisibleRangesForRange(startColumn, endColumn, context); + return this._readVisibleRangesForRange(this.domNode, startColumn, endColumn, context); } - protected _readVisibleRangesForRange(startColumn: number, endColumn: number, context: DomReadingContext): HorizontalRange[] | null { + protected _readVisibleRangesForRange(domNode: FastDomNode, startColumn: number, endColumn: number, context: DomReadingContext): HorizontalRange[] | null { if (startColumn === endColumn) { - const pixelOffset = this._readPixelOffset(startColumn, context); + const pixelOffset = this._readPixelOffset(domNode, startColumn, context); if (pixelOffset === -1) { return null; } else { return [new HorizontalRange(pixelOffset, 0)]; } } else { - return this._readRawVisibleRangesForRange(startColumn, endColumn, context); + return this._readRawVisibleRangesForRange(domNode, startColumn, endColumn, context); } } - protected _readPixelOffset(column: number, context: DomReadingContext): number { + protected _readPixelOffset(domNode: FastDomNode, column: number, context: DomReadingContext): number { if (this._characterMapping.length === 0) { // This line has no content if (this._containsForeignElements === ForeignElementType.None) { @@ -514,18 +526,18 @@ class RenderedViewLine implements IRenderedViewLine { return cachedPixelOffset; } - const result = this._actualReadPixelOffset(column, context); + const result = this._actualReadPixelOffset(domNode, column, context); this._pixelOffsetCache[column] = result; return result; } - return this._actualReadPixelOffset(column, context); + return this._actualReadPixelOffset(domNode, column, context); } - private _actualReadPixelOffset(column: number, context: DomReadingContext): number { + private _actualReadPixelOffset(domNode: FastDomNode, column: number, context: DomReadingContext): number { if (this._characterMapping.length === 0) { // This line has no content - const r = RangeUtil.readHorizontalRanges(this._getReadingTarget(), 0, 0, 0, 0, context.clientRectDeltaLeft, context.endNode); + const r = RangeUtil.readHorizontalRanges(this._getReadingTarget(domNode), 0, 0, 0, 0, context.clientRectDeltaLeft, context.endNode); if (!r || r.length === 0) { return -1; } @@ -541,14 +553,14 @@ class RenderedViewLine implements IRenderedViewLine { const partIndex = CharacterMapping.getPartIndex(partData); const charOffsetInPart = CharacterMapping.getCharIndex(partData); - const r = RangeUtil.readHorizontalRanges(this._getReadingTarget(), partIndex, charOffsetInPart, partIndex, charOffsetInPart, context.clientRectDeltaLeft, context.endNode); + const r = RangeUtil.readHorizontalRanges(this._getReadingTarget(domNode), partIndex, charOffsetInPart, partIndex, charOffsetInPart, context.clientRectDeltaLeft, context.endNode); if (!r || r.length === 0) { return -1; } return r[0].left; } - private _readRawVisibleRangesForRange(startColumn: number, endColumn: number, context: DomReadingContext): HorizontalRange[] | null { + private _readRawVisibleRangesForRange(domNode: FastDomNode, startColumn: number, endColumn: number, context: DomReadingContext): HorizontalRange[] | null { if (startColumn === 1 && endColumn === this._characterMapping.length) { // This branch helps IE with bidi text & gives a performance boost to other browsers when reading visible ranges for an entire line @@ -564,7 +576,7 @@ class RenderedViewLine implements IRenderedViewLine { const endPartIndex = CharacterMapping.getPartIndex(endPartData); const endCharOffsetInPart = CharacterMapping.getCharIndex(endPartData); - return RangeUtil.readHorizontalRanges(this._getReadingTarget(), startPartIndex, startCharOffsetInPart, endPartIndex, endCharOffsetInPart, context.clientRectDeltaLeft, context.endNode); + return RangeUtil.readHorizontalRanges(this._getReadingTarget(domNode), startPartIndex, startCharOffsetInPart, endPartIndex, endCharOffsetInPart, context.clientRectDeltaLeft, context.endNode); } /** @@ -585,8 +597,8 @@ class RenderedViewLine implements IRenderedViewLine { } class WebKitRenderedViewLine extends RenderedViewLine { - protected _readVisibleRangesForRange(startColumn: number, endColumn: number, context: DomReadingContext): HorizontalRange[] | null { - const output = super._readVisibleRangesForRange(startColumn, endColumn, context); + protected _readVisibleRangesForRange(domNode: FastDomNode, startColumn: number, endColumn: number, context: DomReadingContext): HorizontalRange[] | null { + const output = super._readVisibleRangesForRange(domNode, startColumn, endColumn, context); if (!output || output.length === 0 || startColumn === endColumn || (startColumn === 1 && endColumn === this._characterMapping.length)) { return output; @@ -597,7 +609,7 @@ class WebKitRenderedViewLine extends RenderedViewLine { if (!this.input.containsRTL) { // This is an attempt to patch things up // Find position of last column - const endPixelOffset = this._readPixelOffset(endColumn, context); + const endPixelOffset = this._readPixelOffset(domNode, endColumn, context); if (endPixelOffset !== -1) { const lastRange = output[output.length - 1]; if (lastRange.left < endPixelOffset) { @@ -618,10 +630,10 @@ const createRenderedLine: (domNode: FastDomNode | null, renderLineI return createNormalRenderedLine; })(); -function createWebKitRenderedLine(domNode: FastDomNode, renderLineInput: RenderLineInput, characterMapping: CharacterMapping, containsRTL: boolean, containsForeignElements: ForeignElementType): RenderedViewLine { +function createWebKitRenderedLine(domNode: FastDomNode | null, renderLineInput: RenderLineInput, characterMapping: CharacterMapping, containsRTL: boolean, containsForeignElements: ForeignElementType): RenderedViewLine { return new WebKitRenderedViewLine(domNode, renderLineInput, characterMapping, containsRTL, containsForeignElements); } -function createNormalRenderedLine(domNode: FastDomNode, renderLineInput: RenderLineInput, characterMapping: CharacterMapping, containsRTL: boolean, containsForeignElements: ForeignElementType): RenderedViewLine { +function createNormalRenderedLine(domNode: FastDomNode | null, renderLineInput: RenderLineInput, characterMapping: CharacterMapping, containsRTL: boolean, containsForeignElements: ForeignElementType): RenderedViewLine { return new RenderedViewLine(domNode, renderLineInput, characterMapping, containsRTL, containsForeignElements); } diff --git a/src/vs/editor/browser/viewParts/lines/viewLines.css b/src/vs/editor/browser/viewParts/lines/viewLines.css index b43f182662a3..46c57aa0fa2b 100644 --- a/src/vs/editor/browser/viewParts/lines/viewLines.css +++ b/src/vs/editor/browser/viewParts/lines/viewLines.css @@ -17,12 +17,9 @@ .monaco-editor.no-user-select .lines-content, .monaco-editor.no-user-select .view-line, .monaco-editor.no-user-select .view-lines { + user-select: none; -webkit-user-select: none; -ms-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -o-user-select: none; - user-select: none; } .monaco-editor .view-lines { @@ -45,4 +42,4 @@ float: none; min-height: inherit; margin-left: inherit; -}*/ \ No newline at end of file +}*/ diff --git a/src/vs/editor/browser/viewParts/lines/viewLines.ts b/src/vs/editor/browser/viewParts/lines/viewLines.ts index 5710667d8b7a..664e0d257537 100644 --- a/src/vs/editor/browser/viewParts/lines/viewLines.ts +++ b/src/vs/editor/browser/viewParts/lines/viewLines.ts @@ -12,9 +12,8 @@ import { PartFingerprint, PartFingerprints, ViewPart } from 'vs/editor/browser/v import { DomReadingContext, ViewLine, ViewLineOptions } from 'vs/editor/browser/viewParts/lines/viewLine'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; -import { Selection } from 'vs/editor/common/core/selection'; import { ScrollType } from 'vs/editor/common/editorCommon'; -import { HorizontalRange, IViewLines, LineVisibleRanges } from 'vs/editor/common/view/renderingContext'; +import { IViewLines, LineVisibleRanges, VisibleRanges, HorizontalPosition } from 'vs/editor/common/view/renderingContext'; import { ViewContext } from 'vs/editor/common/view/viewContext'; import * as viewEvents from 'vs/editor/common/view/viewEvents'; import { ViewportData } from 'vs/editor/common/viewLayout/viewLinesViewportData'; @@ -74,8 +73,8 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, private _typicalHalfwidthCharacterWidth: number; private _isViewportWrapping: boolean; private _revealHorizontalRightPadding: number; - private _selections: Selection[]; private _cursorSurroundingLines: number; + private _cursorSurroundingLinesStyle: 'default' | 'all'; private _canUseLayerHinting: boolean; private _viewLineOptions: ViewLineOptions; @@ -103,9 +102,9 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, this._isViewportWrapping = wrappingInfo.isViewportWrapping; this._revealHorizontalRightPadding = options.get(EditorOption.revealHorizontalRightPadding); this._cursorSurroundingLines = options.get(EditorOption.cursorSurroundingLines); + this._cursorSurroundingLinesStyle = options.get(EditorOption.cursorSurroundingLinesStyle); this._canUseLayerHinting = !options.get(EditorOption.disableLayerHinting); this._viewLineOptions = new ViewLineOptions(conf, this._context.theme.type); - this._selections = []; PartFingerprints.write(this.domNode, PartFingerprint.ViewLines); this.domNode.setClassName('view-lines'); @@ -156,6 +155,7 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, this._isViewportWrapping = wrappingInfo.isViewportWrapping; this._revealHorizontalRightPadding = options.get(EditorOption.revealHorizontalRightPadding); this._cursorSurroundingLines = options.get(EditorOption.cursorSurroundingLines); + this._cursorSurroundingLinesStyle = options.get(EditorOption.cursorSurroundingLinesStyle); this._canUseLayerHinting = !options.get(EditorOption.disableLayerHinting); Configuration.applyFontInfo(this.domNode, fontInfo); @@ -186,7 +186,6 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, return false; } public onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean { - this._selections = e.selections; const rendStartLineNumber = this._visibleLines.getStartLineNumber(); const rendEndLineNumber = this._visibleLines.getEndLineNumber(); let r = false; @@ -390,7 +389,7 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, const endColumn = lineNumber === range.endLineNumber ? range.endColumn : this._context.model.getLineMaxColumn(lineNumber); const visibleRangesForLine = this._visibleLines.getVisibleLine(lineNumber).getVisibleRangesForRange(startColumn, endColumn, domReadingContext); - if (!visibleRangesForLine || visibleRangesForLine.length === 0) { + if (!visibleRangesForLine) { continue; } @@ -399,11 +398,11 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, nextLineModelLineNumber = this._context.model.coordinatesConverter.convertViewPositionToModelPosition(new Position(lineNumber + 1, 1)).lineNumber; if (currentLineModelLineNumber !== nextLineModelLineNumber) { - visibleRangesForLine[visibleRangesForLine.length - 1].width += this._typicalHalfwidthCharacterWidth; + visibleRangesForLine.ranges[visibleRangesForLine.ranges.length - 1].width += this._typicalHalfwidthCharacterWidth; } } - visibleRanges[visibleRangesLen++] = new LineVisibleRanges(lineNumber, visibleRangesForLine); + visibleRanges[visibleRangesLen++] = new LineVisibleRanges(visibleRangesForLine.outsideRenderedLine, lineNumber, visibleRangesForLine.ranges); } if (visibleRangesLen === 0) { @@ -413,54 +412,26 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, return visibleRanges; } - private visibleRangesForRange2(_range: Range): HorizontalRange[] | null { - + private _visibleRangesForLineRange(lineNumber: number, startColumn: number, endColumn: number): VisibleRanges | null { if (this.shouldRender()) { // Cannot read from the DOM because it is dirty // i.e. the model & the dom are out of sync, so I'd be reading something stale return null; } - const range = Range.intersectRanges(_range, this._lastRenderedData.getCurrentVisibleRange()); - if (!range) { - return null; - } - - let result: HorizontalRange[] = []; - const domReadingContext = new DomReadingContext(this.domNode.domNode, this._textRangeRestingSpot); - - const rendStartLineNumber = this._visibleLines.getStartLineNumber(); - const rendEndLineNumber = this._visibleLines.getEndLineNumber(); - for (let lineNumber = range.startLineNumber; lineNumber <= range.endLineNumber; lineNumber++) { - - if (lineNumber < rendStartLineNumber || lineNumber > rendEndLineNumber) { - continue; - } - - const startColumn = lineNumber === range.startLineNumber ? range.startColumn : 1; - const endColumn = lineNumber === range.endLineNumber ? range.endColumn : this._context.model.getLineMaxColumn(lineNumber); - const visibleRangesForLine = this._visibleLines.getVisibleLine(lineNumber).getVisibleRangesForRange(startColumn, endColumn, domReadingContext); - - if (!visibleRangesForLine || visibleRangesForLine.length === 0) { - continue; - } - - result = result.concat(visibleRangesForLine); - } - - if (result.length === 0) { + if (lineNumber < this._visibleLines.getStartLineNumber() || lineNumber > this._visibleLines.getEndLineNumber()) { return null; } - return result; + return this._visibleLines.getVisibleLine(lineNumber).getVisibleRangesForRange(startColumn, endColumn, new DomReadingContext(this.domNode.domNode, this._textRangeRestingSpot)); } - public visibleRangeForPosition(position: Position): HorizontalRange | null { - const visibleRanges = this.visibleRangesForRange2(new Range(position.lineNumber, position.column, position.lineNumber, position.column)); + public visibleRangeForPosition(position: Position): HorizontalPosition | null { + const visibleRanges = this._visibleRangesForLineRange(position.lineNumber, position.column, position.column); if (!visibleRanges) { return null; } - return visibleRanges[0]; + return new HorizontalPosition(visibleRanges.outsideRenderedLine, visibleRanges.ranges[0].left); } // --- implementation @@ -573,6 +544,7 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, // (3) handle scrolling this._linesContent.setLayerHinting(this._canUseLayerHinting); + this._linesContent.setContain('strict'); const adjustedScrollTop = this._context.viewLayout.getCurrentScrollTop() - viewportData.bigNumbersDelta; this._linesContent.setTop(-adjustedScrollTop); this._linesContent.setLeft(-this._context.viewLayout.getCurrentScrollLeft()); @@ -599,10 +571,7 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, boxStartY = this._context.viewLayout.getVerticalOffsetForLineNumber(range.startLineNumber); boxEndY = this._context.viewLayout.getVerticalOffsetForLineNumber(range.endLineNumber) + this._lineHeight; - const shouldIgnoreScrollOff = source === 'mouse' && ( - this._selections.length > 1 // scroll off might trigger scrolling and mess up with multi cursor - || (this._selections.length > 0 && this._selections[0].isEmpty()) // we don't want to single click triggering selection - ); + const shouldIgnoreScrollOff = source === 'mouse' && this._cursorSurroundingLinesStyle === 'default'; if (!shouldIgnoreScrollOff) { const context = Math.min((viewportHeight / this._lineHeight) / 2, this._cursorSurroundingLines); @@ -617,7 +586,10 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, let newScrollTop: number; - if (verticalType === viewEvents.VerticalRevealType.Center || verticalType === viewEvents.VerticalRevealType.CenterIfOutsideViewport) { + if (boxEndY - boxStartY > viewportHeight) { + // the box is larger than the viewport ... scroll to its top + newScrollTop = boxStartY; + } else if (verticalType === viewEvents.VerticalRevealType.Center || verticalType === viewEvents.VerticalRevealType.CenterIfOutsideViewport) { if (verticalType === viewEvents.VerticalRevealType.CenterIfOutsideViewport && viewportStartY <= boxStartY && boxEndY <= viewportEndY) { // Box is already in the viewport... do nothing newScrollTop = viewportStartY; @@ -641,7 +613,7 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, const viewportStartX = viewport.left; const viewportEndX = viewportStartX + viewport.width; - const visibleRanges = this.visibleRangesForRange2(new Range(lineNumber, startColumn, lineNumber, endColumn)); + const visibleRanges = this._visibleRangesForLineRange(lineNumber, startColumn, endColumn); let boxStartX = Constants.MAX_SAFE_SMALL_INTEGER; let boxEndX = 0; @@ -653,7 +625,7 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, }; } - for (const visibleRange of visibleRanges) { + for (const visibleRange of visibleRanges.ranges) { if (visibleRange.left < boxStartX) { boxStartX = visibleRange.left; } diff --git a/src/vs/editor/browser/viewParts/margin/margin.ts b/src/vs/editor/browser/viewParts/margin/margin.ts index b55687e3b174..52e4677c3292 100644 --- a/src/vs/editor/browser/viewParts/margin/margin.ts +++ b/src/vs/editor/browser/viewParts/margin/margin.ts @@ -78,6 +78,7 @@ export class Margin extends ViewPart { public render(ctx: RestrictedRenderingContext): void { this._domNode.setLayerHinting(this._canUseLayerHinting); + this._domNode.setContain('strict'); const adjustedScrollTop = ctx.scrollTop - ctx.bigNumbersDelta; this._domNode.setTop(-adjustedScrollTop); diff --git a/src/vs/editor/browser/viewParts/minimap/minimap.ts b/src/vs/editor/browser/viewParts/minimap/minimap.ts index 974b15eb6df9..d7f1c47b0c46 100644 --- a/src/vs/editor/browser/viewParts/minimap/minimap.ts +++ b/src/vs/editor/browser/viewParts/minimap/minimap.ts @@ -13,7 +13,7 @@ import * as platform from 'vs/base/common/platform'; import * as strings from 'vs/base/common/strings'; import { ILine, RenderedLinesCollection } from 'vs/editor/browser/view/viewLayer'; import { PartFingerprint, PartFingerprints, ViewPart } from 'vs/editor/browser/view/viewPart'; -import { RenderMinimap, EditorOption } from 'vs/editor/common/config/editorOptions'; +import { RenderMinimap, EditorOption, MINIMAP_GUTTER_WIDTH } from 'vs/editor/common/config/editorOptions'; import { Range } from 'vs/editor/common/core/range'; import { RGBA8 } from 'vs/editor/common/core/rgba'; import { IConfiguration, ScrollType } from 'vs/editor/common/editorCommon'; @@ -32,6 +32,8 @@ import { Selection } from 'vs/editor/common/core/selection'; import { Color } from 'vs/base/common/color'; import { GestureEvent, EventType, Gesture } from 'vs/base/browser/touch'; import { MinimapCharRendererFactory } from 'vs/editor/browser/viewParts/minimap/minimapCharRendererFactory'; +import { MinimapPosition } from 'vs/editor/common/model'; +import { once } from 'vs/base/common/functional'; function getMinimapLineHeight(renderMinimap: RenderMinimap, scale: number): number { if (renderMinimap === RenderMinimap.Text) { @@ -54,6 +56,8 @@ function getMinimapCharWidth(renderMinimap: RenderMinimap, scale: number): numbe */ const MOUSE_DRAG_RESET_DISTANCE = 140; +const GUTTER_DECORATION_WIDTH = 2; + class MinimapOptions { public readonly renderMinimap: RenderMinimap; @@ -70,7 +74,7 @@ class MinimapOptions { public readonly fontScale: number; - public readonly charRenderer: MinimapCharRenderer; + public readonly charRenderer: () => MinimapCharRenderer; /** * container dom node left position (in CSS px) @@ -114,7 +118,7 @@ class MinimapOptions { const minimapOpts = options.get(EditorOption.minimap); this.showSlider = minimapOpts.showSlider; this.fontScale = Math.round(minimapOpts.scale * pixelRatio); - this.charRenderer = MinimapCharRendererFactory.create(this.fontScale, fontInfo.fontFamily); + this.charRenderer = once(() => MinimapCharRendererFactory.create(this.fontScale, fontInfo.fontFamily)); this.pixelRatio = pixelRatio; this.typicalHalfwidthCharacterWidth = fontInfo.typicalHalfwidthCharacterWidth; this.lineHeight = options.get(EditorOption.lineHeight); @@ -500,6 +504,7 @@ export class Minimap extends ViewPart { this._slider.setPosition('absolute'); this._slider.setClassName('minimap-slider'); this._slider.setLayerHinting(true); + this._slider.setContain('strict'); this._domNode.appendChild(this._slider); this._sliderHorizontal = createFastDomNode(document.createElement('div')); @@ -815,9 +820,20 @@ export class Minimap extends ViewPart { continue; } + const decorationColor = (decoration.options.minimap).getColor(this._context.theme); for (let line = decoration.range.startLineNumber; line <= decoration.range.endLineNumber; line++) { - const decorationColor = (decoration.options.minimap).getColor(this._context.theme); - this.renderDecorationOnLine(canvasContext, lineOffsetMap, decoration.range, decorationColor, layout, line, lineHeight, lineHeight, tabSize, characterWidth); + switch (decoration.options.minimap.position) { + + case MinimapPosition.Inline: + this.renderDecorationOnLine(canvasContext, lineOffsetMap, decoration.range, decorationColor, layout, line, lineHeight, lineHeight, tabSize, characterWidth); + continue; + + case MinimapPosition.Gutter: + const y = (line - layout.startLineNumber) * lineHeight; + const x = 2; + this.renderDecoration(canvasContext, decorationColor, x, y, GUTTER_DECORATION_WIDTH, lineHeight); + continue; + } } } } @@ -835,12 +851,17 @@ export class Minimap extends ViewPart { charWidth: number): void { const y = (lineNumber - layout.startLineNumber) * lineHeight; + // Skip rendering the line if it's vertically outside our viewport + if (y + height < 0 || y > this._options.canvasOuterHeight) { + return; + } + // Cache line offset data so that it is only read once per line let lineIndexToXOffset = lineOffsetMap.get(lineNumber); const isFirstDecorationForLine = !lineIndexToXOffset; if (!lineIndexToXOffset) { const lineData = this._context.model.getLineContent(lineNumber); - lineIndexToXOffset = [0]; + lineIndexToXOffset = [MINIMAP_GUTTER_WIDTH]; for (let i = 1; i < lineData.length + 1; i++) { const charCode = lineData.charCodeAt(i - 1); const dx = charCode === CharCode.Tab @@ -856,7 +877,7 @@ export class Minimap extends ViewPart { } const { startColumn, endColumn, startLineNumber, endLineNumber } = decorationRange; - const x = startLineNumber === lineNumber ? lineIndexToXOffset[startColumn - 1] : 0; + const x = startLineNumber === lineNumber ? lineIndexToXOffset[startColumn - 1] : MINIMAP_GUTTER_WIDTH; const endColumnForLine = endLineNumber > lineNumber ? lineIndexToXOffset.length - 1 : endColumn - 1; @@ -875,7 +896,7 @@ export class Minimap extends ViewPart { private renderLineHighlight(canvasContext: CanvasRenderingContext2D, decorationColor: Color | undefined, y: number, height: number): void { canvasContext.fillStyle = decorationColor && decorationColor.transparent(0.5).toString() || ''; - canvasContext.fillRect(0, y, canvasContext.canvas.width, height); + canvasContext.fillRect(MINIMAP_GUTTER_WIDTH, y, canvasContext.canvas.width, height); } private renderDecoration(canvasContext: CanvasRenderingContext2D, decorationColor: Color | undefined, x: number, y: number, width: number, height: number) { @@ -885,6 +906,7 @@ export class Minimap extends ViewPart { private renderLines(layout: MinimapLayout): RenderData { const renderMinimap = this._options.renderMinimap; + const charRenderer = this._options.charRenderer(); const startLineNumber = layout.startLineNumber; const endLineNumber = layout.endLineNumber; const minimapLineHeight = getMinimapLineHeight(renderMinimap, this._options.fontScale); @@ -926,7 +948,7 @@ export class Minimap extends ViewPart { useLighterFont, renderMinimap, this._tokensColorTracker, - this._options.charRenderer, + charRenderer, dy, tabSize, lineInfo.data[lineIndex]!, @@ -1062,7 +1084,7 @@ export class Minimap extends ViewPart { const charWidth = getMinimapCharWidth(renderMinimap, fontScale); const maxDx = target.width - charWidth; - let dx = 0; + let dx = MINIMAP_GUTTER_WIDTH; let charIndex = 0; let tabsCharDelta = 0; @@ -1094,7 +1116,7 @@ export class Minimap extends ViewPart { if (renderMinimap === RenderMinimap.Blocks) { minimapCharRenderer.blockRenderChar(target, dx, dy, tokenColor, backgroundColor, useLighterFont); } else { // RenderMinimap.Text - minimapCharRenderer.renderChar(target, dx, dy, charCode, tokenColor, backgroundColor, useLighterFont); + minimapCharRenderer.renderChar(target, dx, dy, charCode, tokenColor, backgroundColor, fontScale, useLighterFont); } dx += charWidth; diff --git a/src/vs/editor/browser/viewParts/minimap/minimapCharRenderer.ts b/src/vs/editor/browser/viewParts/minimap/minimapCharRenderer.ts index 03ec73e22ec5..f4f5144e27a2 100644 --- a/src/vs/editor/browser/viewParts/minimap/minimapCharRenderer.ts +++ b/src/vs/editor/browser/viewParts/minimap/minimapCharRenderer.ts @@ -32,6 +32,7 @@ export class MinimapCharRenderer { chCode: number, color: RGBA8, backgroundColor: RGBA8, + fontScale: number, useLighterFont: boolean ): void { const charWidth = Constants.BASE_CHAR_WIDTH * this.scale; @@ -42,7 +43,7 @@ export class MinimapCharRenderer { } const charData = useLighterFont ? this.charDataLight : this.charDataNormal; - const charIndex = getCharIndex(chCode); + const charIndex = getCharIndex(chCode, fontScale); const destWidth = target.width * Constants.RGBA_CHANNELS_CNT; diff --git a/src/vs/editor/browser/viewParts/minimap/minimapCharRendererFactory.ts b/src/vs/editor/browser/viewParts/minimap/minimapCharRendererFactory.ts index 5b8a80917d54..300d51789f4c 100644 --- a/src/vs/editor/browser/viewParts/minimap/minimapCharRendererFactory.ts +++ b/src/vs/editor/browser/viewParts/minimap/minimapCharRendererFactory.ts @@ -5,6 +5,7 @@ import { MinimapCharRenderer } from 'vs/editor/browser/viewParts/minimap/minimapCharRenderer'; import { allCharCodes } from 'vs/editor/browser/viewParts/minimap/minimapCharSheet'; +import { prebakedMiniMaps } from 'vs/editor/browser/viewParts/minimap/minimapPreBaked'; import { Constants } from './minimapCharSheet'; /** @@ -28,10 +29,16 @@ export class MinimapCharRendererFactory { return this.lastCreated; } - const factory = MinimapCharRendererFactory.createFromSampleData( - MinimapCharRendererFactory.createSampleData(fontFamily).data, - scale - ); + let factory: MinimapCharRenderer; + if (prebakedMiniMaps[scale]) { + factory = new MinimapCharRenderer(prebakedMiniMaps[scale](), scale); + } else { + factory = MinimapCharRendererFactory.createFromSampleData( + MinimapCharRendererFactory.createSampleData(fontFamily).data, + scale + ); + } + this.lastFontFamily = fontFamily; this.lastCreated = factory; return factory; diff --git a/src/vs/editor/browser/viewParts/minimap/minimapCharSheet.ts b/src/vs/editor/browser/viewParts/minimap/minimapCharSheet.ts index 5ca6c822549c..dde22130eab9 100644 --- a/src/vs/editor/browser/viewParts/minimap/minimapCharSheet.ts +++ b/src/vs/editor/browser/viewParts/minimap/minimapCharSheet.ts @@ -29,9 +29,13 @@ export const allCharCodes: ReadonlyArray = (() => { return v; })(); -export const getCharIndex = (chCode: number) => { +export const getCharIndex = (chCode: number, fontScale: number) => { chCode -= Constants.START_CH_CODE; if (chCode < 0 || chCode > Constants.CHAR_COUNT) { + if (fontScale <= 2) { + // for smaller scales, we can get away with using any ASCII character... + return (chCode + Constants.CHAR_COUNT) % Constants.CHAR_COUNT; + } return Constants.CHAR_COUNT - 1; // unknown symbol } diff --git a/src/vs/editor/browser/viewParts/minimap/minimapPreBaked.ts b/src/vs/editor/browser/viewParts/minimap/minimapPreBaked.ts new file mode 100644 index 000000000000..35f6be018b59 --- /dev/null +++ b/src/vs/editor/browser/viewParts/minimap/minimapPreBaked.ts @@ -0,0 +1,63 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { once } from 'vs/base/common/functional'; + +const charTable: { [hex: string]: number } = { + '0': 0, + '1': 1, + '2': 2, + '3': 3, + '4': 4, + '5': 5, + '6': 6, + '7': 7, + '8': 8, + '9': 9, + A: 10, + B: 11, + C: 12, + D: 13, + E: 14, + F: 15 +}; + +const decodeData = (str: string) => { + const output = new Uint8ClampedArray(str.length / 2); + for (let i = 0; i < str.length; i += 2) { + output[i >> 1] = (charTable[str[i]] << 4) | (charTable[str[i + 1]] & 0xF); + } + + return output; +}; + +/* +const encodeData = (data: Uint8ClampedArray, length: string) => { + const chars = '0123456789ABCDEF'; + let output = ''; + for (let i = 0; i < data.length; i++) { + output += chars[data[i] >> 4] + chars[data[i] & 0xf]; + } + return output; +}; +*/ + +/** + * Map of minimap scales to prebaked sample data at those scales. We don't + * sample much larger data, because then font family becomes visible, which + * is use-configurable. + */ +export const prebakedMiniMaps: { [scale: number]: () => Uint8ClampedArray } = { + 1: once(() => + decodeData( + '0000511D6300CF609C709645A78432005642574171487021003C451900274D35D762755E8B629C5BA856AF57BA649530C167D1512A272A3F6038604460398526BCA2A968DB6F8957C768BE5FBE2FB467CF5D8D5B795DC7625B5DFF50DE64C466DB2FC47CD860A65E9A2EB96CB54CE06DA763AB2EA26860524D3763536601005116008177A8705E53AB738E6A982F88BAA35B5F5B626D9C636B449B737E5B7B678598869A662F6B5B8542706C704C80736A607578685B70594A49715A4522E792' + ) + ), + 2: once(() => + decodeData( + '000000000000000055394F383D2800008B8B1F210002000081B1CBCBCC820000847AAF6B9AAF2119BE08B8881AD60000A44FD07DCCF107015338130C00000000385972265F390B406E2437634B4B48031B12B8A0847000001E15B29A402F0000000000004B33460B00007A752C2A0000000000004D3900000084394B82013400ABA5CFC7AD9C0302A45A3E5A98AB000089A43382D97900008BA54AA087A70A0248A6A7AE6DBE0000BF6F94987EA40A01A06DCFA7A7A9030496C32F77891D0000A99FB1A0AFA80603B29AB9CA75930D010C0948354D3900000C0948354F37460D0028BE673D8400000000AF9D7B6E00002B007AA8933400007AA642675C2700007984CFB9C3985B768772A8A6B7B20000CAAECAAFC4B700009F94A6009F840009D09F9BA4CA9C0000CC8FC76DC87F0000C991C472A2000000A894A48CA7B501079BA2C9C69BA20000B19A5D3FA89000005CA6009DA2960901B0A7F0669FB200009D009E00B7890000DAD0F5D092820000D294D4C48BD10000B5A7A4A3B1A50402CAB6CBA6A2000000B5A7A4A3B1A8044FCDADD19D9CB00000B7778F7B8AAE0803C9AB5D3F5D3F00009EA09EA0BAB006039EA0989A8C7900009B9EF4D6B7C00000A9A7816CACA80000ABAC84705D3F000096DA635CDC8C00006F486F266F263D4784006124097B00374F6D2D6D2D6D4A3A95872322000000030000000000008D8939130000000000002E22A5C9CBC70600AB25C0B5C9B400061A2DB04CA67001082AA6BEBEBFC606002321DACBC19E03087AA08B6768380000282FBAC0B8CA7A88AD25BBA5A29900004C396C5894A6000040485A6E356E9442A32CD17EADA70000B4237923628600003E2DE9C1D7B500002F25BBA5A2990000231DB6AFB4A804023025C0B5CAB588062B2CBDBEC0C706882435A75CA20000002326BD6A82A908048B4B9A5A668000002423A09CB4BB060025259C9D8A7900001C1FCAB2C7C700002A2A9387ABA200002626A4A47D6E9D14333163A0C87500004B6F9C2D643A257049364936493647358A34438355497F1A0000A24C1D590000D38DFFBDD4CD3126' + ) + ) +}; diff --git a/src/vs/editor/browser/viewParts/overviewRuler/decorationsOverviewRuler.ts b/src/vs/editor/browser/viewParts/overviewRuler/decorationsOverviewRuler.ts index f9f47e7c8d3a..148cd44427cf 100644 --- a/src/vs/editor/browser/viewParts/overviewRuler/decorationsOverviewRuler.ts +++ b/src/vs/editor/browser/viewParts/overviewRuler/decorationsOverviewRuler.ts @@ -215,6 +215,7 @@ export class DecorationsOverviewRuler extends ViewPart { this._domNode.setClassName('decorationsOverviewRuler'); this._domNode.setPosition('absolute'); this._domNode.setLayerHinting(true); + this._domNode.setContain('strict'); this._domNode.setAttribute('aria-hidden', 'true'); this._updateSettings(false); diff --git a/src/vs/editor/browser/viewParts/overviewRuler/overviewRuler.ts b/src/vs/editor/browser/viewParts/overviewRuler/overviewRuler.ts index 5d12d2ac3ace..b2bc2399b440 100644 --- a/src/vs/editor/browser/viewParts/overviewRuler/overviewRuler.ts +++ b/src/vs/editor/browser/viewParts/overviewRuler/overviewRuler.ts @@ -26,6 +26,7 @@ export class OverviewRuler extends ViewEventHandler implements IOverviewRuler { this._domNode.setClassName(cssClassName); this._domNode.setPosition('absolute'); this._domNode.setLayerHinting(true); + this._domNode.setContain('strict'); this._zoneManager = new OverviewZoneManager((lineNumber: number) => this._context.viewLayout.getVerticalOffsetForLineNumber(lineNumber)); this._zoneManager.setDOMWidth(0); diff --git a/src/vs/editor/browser/viewParts/selections/selections.ts b/src/vs/editor/browser/viewParts/selections/selections.ts index 91c8058cf8df..a5b67cbe658e 100644 --- a/src/vs/editor/browser/viewParts/selections/selections.ts +++ b/src/vs/editor/browser/viewParts/selections/selections.ts @@ -369,7 +369,7 @@ export class SelectionsOverlay extends DynamicViewOverlay { private _previousFrameVisibleRangesWithStyle: (LineVisibleRangesWithStyle[] | null)[] = []; public prepareRender(ctx: RenderingContext): void { - // Build HTML for inner corners separate from HTML for the the rest of selections, + // Build HTML for inner corners separate from HTML for the rest of selections, // as the inner corner HTML can interfere with that of other selections. // In final render, make sure to place the inner corner HTML before the rest of selection HTML. See issue #77777. const output: [string, string][] = []; diff --git a/src/vs/editor/browser/viewParts/viewCursors/viewCursor.ts b/src/vs/editor/browser/viewParts/viewCursors/viewCursor.ts index 3c01e05ca66a..fc9517797510 100644 --- a/src/vs/editor/browser/viewParts/viewCursors/viewCursor.ts +++ b/src/vs/editor/browser/viewParts/viewCursors/viewCursor.ts @@ -121,7 +121,7 @@ export class ViewCursor { if (this._cursorStyle === TextEditorCursorStyle.Line || this._cursorStyle === TextEditorCursorStyle.LineThin) { const visibleRange = ctx.visibleRangeForPosition(this._position); - if (!visibleRange) { + if (!visibleRange || visibleRange.outsideRenderedLine) { // Outside viewport return null; } @@ -151,13 +151,18 @@ export class ViewCursor { const lineContent = this._context.model.getLineContent(this._position.lineNumber); const nextCharLength = strings.nextCharLength(lineContent, this._position.column - 1); const visibleRangeForCharacter = ctx.linesVisibleRangesForRange(new Range(this._position.lineNumber, this._position.column, this._position.lineNumber, this._position.column + nextCharLength), false); + if (!visibleRangeForCharacter || visibleRangeForCharacter.length === 0) { + // Outside viewport + return null; + } - if (!visibleRangeForCharacter || visibleRangeForCharacter.length === 0 || visibleRangeForCharacter[0].ranges.length === 0) { + const firstVisibleRangeForCharacter = visibleRangeForCharacter[0]; + if (firstVisibleRangeForCharacter.outsideRenderedLine || firstVisibleRangeForCharacter.ranges.length === 0) { // Outside viewport return null; } - const range = visibleRangeForCharacter[0].ranges[0]; + const range = firstVisibleRangeForCharacter.ranges[0]; const width = range.width < 1 ? this._typicalHalfwidthCharacterWidth : range.width; let textContentClassName = ''; diff --git a/src/vs/editor/browser/viewParts/viewZones/viewZones.ts b/src/vs/editor/browser/viewParts/viewZones/viewZones.ts index feff29fc4869..76664fd367ba 100644 --- a/src/vs/editor/browser/viewParts/viewZones/viewZones.ts +++ b/src/vs/editor/browser/viewParts/viewZones/viewZones.ts @@ -5,7 +5,7 @@ import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode'; import { onUnexpectedError } from 'vs/base/common/errors'; -import { IViewZone } from 'vs/editor/browser/editorBrowser'; +import { IViewZone, IViewZoneChangeAccessor } from 'vs/editor/browser/editorBrowser'; import { ViewPart } from 'vs/editor/browser/view/viewPart'; import { Position } from 'vs/editor/common/core/position'; import { RenderingContext, RestrictedRenderingContext } from 'vs/editor/common/view/renderingContext'; @@ -13,7 +13,7 @@ import { ViewContext } from 'vs/editor/common/view/viewContext'; import * as viewEvents from 'vs/editor/common/view/viewEvents'; import { IViewWhitespaceViewportData } from 'vs/editor/common/viewModel/viewModel'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; - +import { IWhitespaceChangeAccessor, IEditorWhitespace } from 'vs/editor/common/viewLayout/linesLayout'; export interface IMyViewZone { whitespaceId: string; @@ -29,6 +29,8 @@ interface IComputedViewZoneProps { minWidthInPx: number; } +const invalidFunc = () => { throw new Error(`Invalid change accessor`); }; + export class ViewZones extends ViewPart { private _zones: { [id: string]: IMyViewZone; }; @@ -72,20 +74,29 @@ export class ViewZones extends ViewPart { // ---- begin view event handlers private _recomputeWhitespacesProps(): boolean { - let hadAChange = false; - - const keys = Object.keys(this._zones); - for (let i = 0, len = keys.length; i < len; i++) { - const id = keys[i]; - const zone = this._zones[id]; - const props = this._computeWhitespaceProps(zone.delegate); - if (this._context.viewLayout.changeWhitespace(id, props.afterViewLineNumber, props.heightInPx)) { - this._safeCallOnComputedHeight(zone.delegate, props.heightInPx); - hadAChange = true; - } + const whitespaces = this._context.viewLayout.getWhitespaces(); + const oldWhitespaces = new Map(); + for (const whitespace of whitespaces) { + oldWhitespaces.set(whitespace.id, whitespace); } + return this._context.viewLayout.changeWhitespace((whitespaceAccessor: IWhitespaceChangeAccessor) => { + let hadAChange = false; + + const keys = Object.keys(this._zones); + for (let i = 0, len = keys.length; i < len; i++) { + const id = keys[i]; + const zone = this._zones[id]; + const props = this._computeWhitespaceProps(zone.delegate); + const oldWhitespace = oldWhitespaces.get(id); + if (oldWhitespace && (oldWhitespace.afterLineNumber !== props.afterViewLineNumber || oldWhitespace.height !== props.heightInPx)) { + whitespaceAccessor.changeOneWhitespace(id, props.afterViewLineNumber, props.heightInPx); + this._safeCallOnComputedHeight(zone.delegate, props.heightInPx); + hadAChange = true; + } + } - return hadAChange; + return hadAChange; + }); } public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { @@ -138,7 +149,6 @@ export class ViewZones extends ViewPart { return 10000; } - private _computeWhitespaceProps(zone: IViewZone): IComputedViewZoneProps { if (zone.afterLineNumber === 0) { return { @@ -188,9 +198,44 @@ export class ViewZones extends ViewPart { }; } - public addZone(zone: IViewZone): string { + public changeViewZones(callback: (changeAccessor: IViewZoneChangeAccessor) => any): boolean { + + return this._context.viewLayout.changeWhitespace((whitespaceAccessor: IWhitespaceChangeAccessor) => { + let zonesHaveChanged = false; + + const changeAccessor: IViewZoneChangeAccessor = { + addZone: (zone: IViewZone): string => { + zonesHaveChanged = true; + return this._addZone(whitespaceAccessor, zone); + }, + removeZone: (id: string): void => { + if (!id) { + return; + } + zonesHaveChanged = this._removeZone(whitespaceAccessor, id) || zonesHaveChanged; + }, + layoutZone: (id: string): void => { + if (!id) { + return; + } + zonesHaveChanged = this._layoutZone(whitespaceAccessor, id) || zonesHaveChanged; + } + }; + + safeInvoke1Arg(callback, changeAccessor); + + // Invalidate changeAccessor + changeAccessor.addZone = invalidFunc; + changeAccessor.removeZone = invalidFunc; + changeAccessor.layoutZone = invalidFunc; + + return zonesHaveChanged; + }); + } + + private _addZone(whitespaceAccessor: IWhitespaceChangeAccessor, zone: IViewZone): string { const props = this._computeWhitespaceProps(zone); - const whitespaceId = this._context.viewLayout.addWhitespace(props.afterViewLineNumber, this._getZoneOrdinal(zone), props.heightInPx, props.minWidthInPx); + const whitespaceId = whitespaceAccessor.insertWhitespace(props.afterViewLineNumber, this._getZoneOrdinal(zone), props.heightInPx, props.minWidthInPx); const myZone: IMyViewZone = { whitespaceId: whitespaceId, @@ -224,11 +269,11 @@ export class ViewZones extends ViewPart { return myZone.whitespaceId; } - public removeZone(id: string): boolean { + private _removeZone(whitespaceAccessor: IWhitespaceChangeAccessor, id: string): boolean { if (this._zones.hasOwnProperty(id)) { const zone = this._zones[id]; delete this._zones[id]; - this._context.viewLayout.removeWhitespace(zone.whitespaceId); + whitespaceAccessor.removeWhitespace(zone.whitespaceId); zone.domNode.removeAttribute('monaco-visible-view-zone'); zone.domNode.removeAttribute('monaco-view-zone'); @@ -247,21 +292,20 @@ export class ViewZones extends ViewPart { return false; } - public layoutZone(id: string): boolean { - let changed = false; + private _layoutZone(whitespaceAccessor: IWhitespaceChangeAccessor, id: string): boolean { if (this._zones.hasOwnProperty(id)) { const zone = this._zones[id]; const props = this._computeWhitespaceProps(zone.delegate); // const newOrdinal = this._getZoneOrdinal(zone.delegate); - changed = this._context.viewLayout.changeWhitespace(zone.whitespaceId, props.afterViewLineNumber, props.heightInPx) || changed; + whitespaceAccessor.changeOneWhitespace(zone.whitespaceId, props.afterViewLineNumber, props.heightInPx); // TODO@Alex: change `newOrdinal` too - if (changed) { - this._safeCallOnComputedHeight(zone.delegate, props.heightInPx); - this.setShouldRender(); - } + this._safeCallOnComputedHeight(zone.delegate, props.heightInPx); + this.setShouldRender(); + + return true; } - return changed; + return false; } public shouldSuppressMouseDownOnViewZone(id: string): boolean { @@ -365,3 +409,11 @@ export class ViewZones extends ViewPart { } } } + +function safeInvoke1Arg(func: Function, arg1: any): any { + try { + return func(arg1); + } catch (e) { + onUnexpectedError(e); + } +} diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index 92f09858521e..101667d0e646 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/editor'; -import 'vs/css!./media/tokens'; import * as nls from 'vs/nls'; import * as dom from 'vs/base/browser/dom'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; @@ -41,7 +40,7 @@ import * as modes from 'vs/editor/common/modes'; import { editorUnnecessaryCodeBorder, editorUnnecessaryCodeOpacity } from 'vs/editor/common/view/editorColorRegistry'; import { editorErrorBorder, editorErrorForeground, editorHintBorder, editorHintForeground, editorInfoBorder, editorInfoForeground, editorWarningBorder, editorWarningForeground, editorForeground } from 'vs/platform/theme/common/colorRegistry'; import { VerticalRevealType } from 'vs/editor/common/view/viewEvents'; -import { IEditorWhitespace } from 'vs/editor/common/viewLayout/whitespaceComputer'; +import { IEditorWhitespace } from 'vs/editor/common/viewLayout/linesLayout'; import { ViewModel } from 'vs/editor/common/viewModel/viewModelImpl'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -870,9 +869,11 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE } public onVisible(): void { + this._modelData?.view.refreshFocusState(); } public onHide(): void { + this._modelData?.view.refreshFocusState(); } public getContribution(id: string): T { @@ -1371,6 +1372,9 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE const e2: ICursorSelectionChangedEvent = { selection: e.selections[0], secondarySelections: e.selections.slice(1), + modelVersionId: e.modelVersionId, + oldSelections: e.oldSelections, + oldModelVersionId: e.oldModelVersionId, source: e.source, reason: e.reason }; diff --git a/src/vs/editor/browser/widget/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditorWidget.ts index 5db54a763161..5febc3cf15bd 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget.ts @@ -32,7 +32,7 @@ import { IDiffComputationResult, IEditorWorkerService } from 'vs/editor/common/s import { OverviewRulerZone } from 'vs/editor/common/view/overviewZoneManager'; import { LineDecoration } from 'vs/editor/common/viewLayout/lineDecorations'; import { RenderLineInput, renderViewLine } from 'vs/editor/common/viewLayout/viewLineRenderer'; -import { IEditorWhitespace } from 'vs/editor/common/viewLayout/whitespaceComputer'; +import { IEditorWhitespace } from 'vs/editor/common/viewLayout/linesLayout'; import { InlineDecoration, InlineDecorationType, ViewLineRenderingData } from 'vs/editor/common/viewModel/viewModel'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -1394,12 +1394,16 @@ abstract class ViewZonesComputer { private readonly lineChanges: editorCommon.ILineChange[]; private readonly originalForeignVZ: IEditorWhitespace[]; + private readonly originalLineHeight: number; private readonly modifiedForeignVZ: IEditorWhitespace[]; + private readonly modifiedLineHeight: number; - constructor(lineChanges: editorCommon.ILineChange[], originalForeignVZ: IEditorWhitespace[], modifiedForeignVZ: IEditorWhitespace[]) { + constructor(lineChanges: editorCommon.ILineChange[], originalForeignVZ: IEditorWhitespace[], originalLineHeight: number, modifiedForeignVZ: IEditorWhitespace[], modifiedLineHeight: number) { this.lineChanges = lineChanges; this.originalForeignVZ = originalForeignVZ; + this.originalLineHeight = originalLineHeight; this.modifiedForeignVZ = modifiedForeignVZ; + this.modifiedLineHeight = modifiedLineHeight; } public getViewZones(): IEditorsZones { @@ -1474,7 +1478,7 @@ abstract class ViewZonesComputer { stepOriginal.push({ afterLineNumber: viewZoneLineNumber, - heightInLines: modifiedForeignVZ.current.heightInLines, + heightInLines: modifiedForeignVZ.current.height / this.modifiedLineHeight, domNode: null, marginDomNode: marginDomNode }); @@ -1491,7 +1495,7 @@ abstract class ViewZonesComputer { } stepModified.push({ afterLineNumber: viewZoneLineNumber, - heightInLines: originalForeignVZ.current.heightInLines, + heightInLines: originalForeignVZ.current.height / this.originalLineHeight, domNode: null }); originalForeignVZ.advance(); @@ -1750,7 +1754,7 @@ class DiffEditorWidgetSideBySide extends DiffEditorWidgetStyle implements IDiffE } protected _getViewZones(lineChanges: editorCommon.ILineChange[], originalForeignVZ: IEditorWhitespace[], modifiedForeignVZ: IEditorWhitespace[], originalEditor: editorBrowser.ICodeEditor, modifiedEditor: editorBrowser.ICodeEditor): IEditorsZones { - let c = new SideBySideViewZonesComputer(lineChanges, originalForeignVZ, modifiedForeignVZ); + let c = new SideBySideViewZonesComputer(lineChanges, originalForeignVZ, originalEditor.getOption(EditorOption.lineHeight), modifiedForeignVZ, modifiedEditor.getOption(EditorOption.lineHeight)); return c.getViewZones(); } @@ -1877,8 +1881,8 @@ class DiffEditorWidgetSideBySide extends DiffEditorWidgetStyle implements IDiffE class SideBySideViewZonesComputer extends ViewZonesComputer { - constructor(lineChanges: editorCommon.ILineChange[], originalForeignVZ: IEditorWhitespace[], modifiedForeignVZ: IEditorWhitespace[]) { - super(lineChanges, originalForeignVZ, modifiedForeignVZ); + constructor(lineChanges: editorCommon.ILineChange[], originalForeignVZ: IEditorWhitespace[], originalLineHeight: number, modifiedForeignVZ: IEditorWhitespace[], modifiedLineHeight: number) { + super(lineChanges, originalForeignVZ, originalLineHeight, modifiedForeignVZ, modifiedLineHeight); } protected _createOriginalMarginDomNodeForModifiedForeignViewZoneInAddedRegion(): HTMLDivElement | null { @@ -2038,7 +2042,7 @@ class InlineViewZonesComputer extends ViewZonesComputer { private readonly renderIndicators: boolean; constructor(lineChanges: editorCommon.ILineChange[], originalForeignVZ: IEditorWhitespace[], modifiedForeignVZ: IEditorWhitespace[], originalEditor: editorBrowser.ICodeEditor, modifiedEditor: editorBrowser.ICodeEditor, renderIndicators: boolean) { - super(lineChanges, originalForeignVZ, modifiedForeignVZ); + super(lineChanges, originalForeignVZ, originalEditor.getOption(EditorOption.lineHeight), modifiedForeignVZ, modifiedEditor.getOption(EditorOption.lineHeight)); this.originalModel = originalEditor.getModel()!; this.modifiedEditorOptions = modifiedEditor.getOptions(); this.modifiedEditorTabSize = modifiedEditor.getModel()!.getOptions().tabSize; diff --git a/src/vs/editor/browser/widget/diffNavigator.ts b/src/vs/editor/browser/widget/diffNavigator.ts index 5e956dba0f3c..da6788e23ae9 100644 --- a/src/vs/editor/browser/widget/diffNavigator.ts +++ b/src/vs/editor/browser/widget/diffNavigator.ts @@ -30,10 +30,17 @@ const defaultOptions: Options = { alwaysRevealFirst: true }; +export interface IDiffNavigator { + canNavigate(): boolean; + next(): void; + previous(): void; + dispose(): void; +} + /** * Create a new diff navigator for the provided diff editor. */ -export class DiffNavigator extends Disposable { +export class DiffNavigator extends Disposable implements IDiffNavigator { private readonly _editor: IDiffEditor; private readonly _options: Options; diff --git a/src/vs/editor/browser/widget/diffReview.ts b/src/vs/editor/browser/widget/diffReview.ts index a3082b9cded0..6d7a9c20fc9a 100644 --- a/src/vs/editor/browser/widget/diffReview.ts +++ b/src/vs/editor/browser/widget/diffReview.ts @@ -695,7 +695,7 @@ export class DiffReview extends Disposable { if (originalLine !== 0) { originalLineNumber.appendChild(document.createTextNode(String(originalLine))); } else { - originalLineNumber.innerHTML = ' '; + originalLineNumber.innerHTML = ' '; } cell.appendChild(originalLineNumber); @@ -707,13 +707,13 @@ export class DiffReview extends Disposable { if (modifiedLine !== 0) { modifiedLineNumber.appendChild(document.createTextNode(String(modifiedLine))); } else { - modifiedLineNumber.innerHTML = ' '; + modifiedLineNumber.innerHTML = ' '; } cell.appendChild(modifiedLineNumber); const spacer = document.createElement('span'); spacer.className = spacerClassName; - spacer.innerHTML = '  '; + spacer.innerHTML = '  '; cell.appendChild(spacer); let lineContent: string; diff --git a/src/vs/editor/browser/widget/media/diffReview.css b/src/vs/editor/browser/widget/media/diffReview.css index f96044361e1b..1c4a1a7b0f19 100644 --- a/src/vs/editor/browser/widget/media/diffReview.css +++ b/src/vs/editor/browser/widget/media/diffReview.css @@ -10,12 +10,9 @@ .monaco-diff-editor .diff-review { position: absolute; + user-select: none; -webkit-user-select: none; -ms-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -o-user-select: none; - user-select: none; } .monaco-diff-editor .diff-review-summary { @@ -67,4 +64,4 @@ .monaco-diff-editor.hc-black .action-label.icon.close-diff-review, .monaco-diff-editor.vs-dark .action-label.icon.close-diff-review { background: url('close-dark.svg') center center no-repeat; -} \ No newline at end of file +} diff --git a/src/vs/editor/common/commands/shiftCommand.ts b/src/vs/editor/common/commands/shiftCommand.ts index da4e75c846d6..413bc86f124e 100644 --- a/src/vs/editor/common/commands/shiftCommand.ts +++ b/src/vs/editor/common/commands/shiftCommand.ts @@ -11,6 +11,7 @@ import { Selection, SelectionDirection } from 'vs/editor/common/core/selection'; import { ICommand, ICursorStateComputerData, IEditOperationBuilder } from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; +import { EditorAutoIndentStrategy } from 'vs/editor/common/config/editorOptions'; export interface IShiftCommandOpts { isUnshift: boolean; @@ -18,6 +19,7 @@ export interface IShiftCommandOpts { indentSize: number; insertSpaces: boolean; useTabStops: boolean; + autoIndent: EditorAutoIndentStrategy; } const repeatCache: { [str: string]: string[]; } = Object.create(null); @@ -137,7 +139,7 @@ export class ShiftCommand implements ICommand { // The current line is "miss-aligned", so let's see if this is expected... // This can only happen when it has trailing commas in the indent if (model.isCheapToTokenize(lineNumber - 1)) { - let enterAction = LanguageConfigurationRegistry.getRawEnterActionAtPosition(model, lineNumber - 1, model.getLineMaxColumn(lineNumber - 1)); + let enterAction = LanguageConfigurationRegistry.getEnterAction(this._opts.autoIndent, model, new Range(lineNumber - 1, model.getLineMaxColumn(lineNumber - 1), lineNumber - 1, model.getLineMaxColumn(lineNumber - 1))); if (enterAction) { extraSpaces = previousLineExtraSpaces; if (enterAction.appendText) { diff --git a/src/vs/editor/common/config/commonEditorConfig.ts b/src/vs/editor/common/config/commonEditorConfig.ts index cb5d69d5cff2..bc1a4ebf6074 100644 --- a/src/vs/editor/common/config/commonEditorConfig.ts +++ b/src/vs/editor/common/config/commonEditorConfig.ts @@ -15,6 +15,7 @@ import * as editorCommon from 'vs/editor/common/editorCommon'; import { ConfigurationScope, Extensions, IConfigurationNode, IConfigurationRegistry, IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; +import { forEach } from 'vs/base/common/collections'; /** * Control what pressing Tab does. @@ -197,6 +198,43 @@ function migrateOptions(options: IEditorOptions): void { options.tabCompletion = 'onlySnippets'; } + const suggest = options.suggest; + if (suggest && typeof (suggest).filteredTypes === 'object' && (suggest).filteredTypes) { + const mapping: Record = {}; + mapping['method'] = 'showMethods'; + mapping['function'] = 'showFunctions'; + mapping['constructor'] = 'showConstructors'; + mapping['field'] = 'showFields'; + mapping['variable'] = 'showVariables'; + mapping['class'] = 'showClasses'; + mapping['struct'] = 'showStructs'; + mapping['interface'] = 'showInterfaces'; + mapping['module'] = 'showModules'; + mapping['property'] = 'showProperties'; + mapping['event'] = 'showEvents'; + mapping['operator'] = 'showOperators'; + mapping['unit'] = 'showUnits'; + mapping['value'] = 'showValues'; + mapping['constant'] = 'showConstants'; + mapping['enum'] = 'showEnums'; + mapping['enumMember'] = 'showEnumMembers'; + mapping['keyword'] = 'showKeywords'; + mapping['text'] = 'showWords'; + mapping['color'] = 'showColors'; + mapping['file'] = 'showFiles'; + mapping['reference'] = 'showReferences'; + mapping['folder'] = 'showFolders'; + mapping['typeParameter'] = 'showTypeParameters'; + mapping['snippet'] = 'showSnippets'; + forEach(mapping, entry => { + const value = (suggest).filteredTypes[entry.key]; + if (value === false) { + (suggest)[entry.value] = value; + } + }); + // delete (suggest).filteredTypes; + } + const hover = options.hover; if (hover === true) { options.hover = { @@ -218,6 +256,13 @@ function migrateOptions(options: IEditorOptions): void { enabled: false }; } + + const autoIndent = options.autoIndent; + if (autoIndent === true) { + options.autoIndent = 'full'; + } else if (autoIndent === false) { + options.autoIndent = 'advanced'; + } } function deepCloneAndMigrateOptions(_options: IEditorOptions): IEditorOptions { @@ -304,18 +349,6 @@ export abstract class CommonEditorConfiguration extends Disposable implements ed return EditorConfiguration2.computeOptions(this._validatedOptions, env); } - private static _primitiveArrayEquals(a: any[], b: any[]): boolean { - if (a.length !== b.length) { - return false; - } - for (let i = 0; i < a.length; i++) { - if (a[i] !== b[i]) { - return false; - } - } - return true; - } - private static _subsetEquals(base: { [key: string]: any }, subset: { [key: string]: any }): boolean { for (const key in subset) { if (hasOwnProperty.call(subset, key)) { @@ -326,7 +359,7 @@ export abstract class CommonEditorConfiguration extends Disposable implements ed continue; } if (Array.isArray(baseValue) && Array.isArray(subsetValue)) { - if (!this._primitiveArrayEquals(baseValue, subsetValue)) { + if (!arrays.equals(baseValue, subsetValue)) { return false; } continue; @@ -387,14 +420,18 @@ export abstract class CommonEditorConfiguration extends Disposable implements ed } -const configurationRegistry = Registry.as(Extensions.Configuration); -const editorConfiguration: IConfigurationNode = { +export const editorConfigurationBaseNode = Object.freeze({ id: 'editor', order: 5, type: 'object', title: nls.localize('editorConfigurationTitle', "Editor"), overridable: true, scope: ConfigurationScope.RESOURCE, +}); + +const configurationRegistry = Registry.as(Extensions.Configuration); +const editorConfiguration: IConfigurationNode = { + ...editorConfigurationBaseNode, properties: { 'editor.tabSize': { type: 'number', @@ -451,29 +488,6 @@ const editorConfiguration: IConfigurationNode = { default: 20_000, description: nls.localize('maxTokenizationLineLength', "Lines above this length will not be tokenized for performance reasons") }, - 'editor.codeActionsOnSave': { - type: 'object', - properties: { - 'source.organizeImports': { - type: 'boolean', - description: nls.localize('codeActionsOnSave.organizeImports', "Controls whether organize imports action should be run on file save.") - }, - 'source.fixAll': { - type: 'boolean', - description: nls.localize('codeActionsOnSave.fixAll', "Controls whether auto fix action should be run on file save.") - } - }, - 'additionalProperties': { - type: 'boolean' - }, - default: {}, - description: nls.localize('codeActionsOnSave', "Code action kinds to be run on save.") - }, - 'editor.codeActionsOnSaveTimeout': { - type: 'number', - default: 750, - description: nls.localize('codeActionsOnSaveTimeout', "Timeout in milliseconds after which the code actions that are run on save are cancelled.") - }, 'diffEditor.maxComputationTime': { type: 'number', default: 5000, diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index a7d8622ea701..4958674ce99a 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -10,9 +10,9 @@ import { FontInfo } from 'vs/editor/common/config/fontInfo'; import { Constants } from 'vs/base/common/uint'; import { USUAL_WORD_SEPARATORS } from 'vs/editor/common/model/wordHelper'; import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; -import { isObject } from 'vs/base/common/types'; import { IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry'; import { IDimension } from 'vs/editor/common/editorCommon'; +import { IJSONSchema } from 'vs/base/common/jsonSchema'; //#region typed options @@ -31,6 +31,18 @@ export type EditorAutoSurroundStrategy = 'languageDefined' | 'quotes' | 'bracket */ export type EditorAutoClosingOvertypeStrategy = 'always' | 'auto' | 'never'; +/** + * Configuration options for auto indentation in the editor + * @internal + */ +export const enum EditorAutoIndentStrategy { + None = 0, + Keep = 1, + Brackets = 2, + Advanced = 3, + Full = 4 +} + /** * Configuration options for the editor. */ @@ -71,6 +83,12 @@ export interface IEditorOptions { * Defaults to 0. */ cursorSurroundingLines?: number; + /** + * Controls when `cursorSurroundingLines` should be enforced + * Defaults to `default`, `cursorSurroundingLines` is not enforced when cursor position is changed + * by mouse. + */ + cursorSurroundingLinesStyle?: 'default' | 'all'; /** * Render last line number when the file ends with a newline. * Defaults to true. @@ -137,7 +155,7 @@ export interface IEditorOptions { fixedOverflowWidgets?: boolean; /** * The number of vertical lanes the overview ruler should render. - * Defaults to 2. + * Defaults to 3. */ overviewRulerLanes?: number; /** @@ -180,8 +198,8 @@ export interface IEditorOptions { */ fontLigatures?: boolean | string; /** - * Disable the use of `will-change` for the editor margin and lines layers. - * The usage of `will-change` acts as a hint for browsers to create an extra layer. + * Disable the use of `transform: translate3d(0px, 0px, 0px)` for the editor margin and lines layers. + * The usage of `transform: translate3d(0px, 0px, 0px)` acts as a hint for browsers to create an extra layer. * Defaults to false. */ disableLayerHinting?: boolean; @@ -313,6 +331,10 @@ export interface IEditorOptions { * Defaults to 'auto'. It is best to leave this to 'auto'. */ accessibilitySupport?: 'auto' | 'off' | 'on'; + /** + * Controls the number of lines in the editor that can be read out by a screen reader + */ + accessibilityPageSize?: number; /** * Suggest options. */ @@ -358,7 +380,7 @@ export interface IEditorOptions { * Enable auto indentation adjustment. * Defaults to false. */ - autoIndent?: boolean; + autoIndent?: 'none' | 'keep' | 'brackets' | 'advanced' | 'full'; /** * Enable format on type. * Defaults to false. @@ -524,6 +546,12 @@ export interface IEditorConstructionOptions extends IEditorOptions { dimension?: IDimension; } +/** + * @internal + * The width of the minimap gutter, in pixels. + */ +export const MINIMAP_GUTTER_WIDTH = 8; + /** * Configuration options for the diff editor. */ @@ -909,6 +937,20 @@ class EditorEnumOption extends Bas //#endregion +//#region autoIndent + +function _autoIndentFromString(autoIndent: 'none' | 'keep' | 'brackets' | 'advanced' | 'full'): EditorAutoIndentStrategy { + switch (autoIndent) { + case 'none': return EditorAutoIndentStrategy.None; + case 'keep': return EditorAutoIndentStrategy.Keep; + case 'brackets': return EditorAutoIndentStrategy.Brackets; + case 'advanced': return EditorAutoIndentStrategy.Advanced; + case 'full': return EditorAutoIndentStrategy.Full; + } +} + +//#endregion + //#region accessibilitySupport class EditorAccessibilitySupport extends BaseEditorOption { @@ -1109,9 +1151,9 @@ export interface IEditorFindOptions { */ seedSearchStringFromSelection?: boolean; /** - * Controls if Find in Selection flag is turned on when multiple lines of text are selected in the editor. + * Controls if Find in Selection flag is turned on in the editor. */ - autoFindInSelection?: boolean; + autoFindInSelection?: 'never' | 'always' | 'multiline'; /* * Controls whether the Find Widget should add extra lines on top of the editor. */ @@ -1130,7 +1172,7 @@ class EditorFind extends BaseEditorOption constructor() { const defaults: EditorFindOptions = { seedSearchStringFromSelection: true, - autoFindInSelection: false, + autoFindInSelection: 'never', globalFindClipboard: false, addExtraSpaceOnTop: true }; @@ -1143,8 +1185,14 @@ class EditorFind extends BaseEditorOption description: nls.localize('find.seedSearchStringFromSelection', "Controls whether the search string in the Find Widget is seeded from the editor selection.") }, 'editor.find.autoFindInSelection': { - type: 'boolean', + type: 'string', + enum: ['never', 'always', 'multiline'], default: defaults.autoFindInSelection, + enumDescriptions: [ + nls.localize('editor.find.autoFindInSelection.never', 'Never turn on Find in selection automatically (default)'), + nls.localize('editor.find.autoFindInSelection.always', 'Always turn on Find in selection automatically'), + nls.localize('editor.find.autoFindInSelection.multiline', 'Turn on Find in selection automatically when multiple lines of content are selected.') + ], description: nls.localize('find.autoFindInSelection', "Controls whether the find operation is carried out on selected text or the entire file in the editor.") }, 'editor.find.globalFindClipboard': { @@ -1169,7 +1217,9 @@ class EditorFind extends BaseEditorOption const input = _input as IEditorFindOptions; return { seedSearchStringFromSelection: EditorBooleanOption.boolean(input.seedSearchStringFromSelection, this.defaultValue.seedSearchStringFromSelection), - autoFindInSelection: EditorBooleanOption.boolean(input.autoFindInSelection, this.defaultValue.autoFindInSelection), + autoFindInSelection: typeof _input.autoFindInSelection === 'boolean' + ? (_input.autoFindInSelection ? 'always' : 'never') + : EditorStringEnumOption.stringSet<'never' | 'always' | 'multiline'>(input.autoFindInSelection, this.defaultValue.autoFindInSelection, ['never', 'always', 'multiline']), globalFindClipboard: EditorBooleanOption.boolean(input.globalFindClipboard, this.defaultValue.globalFindClipboard), addExtraSpaceOnTop: EditorBooleanOption.boolean(input.addExtraSpaceOnTop, this.defaultValue.addExtraSpaceOnTop) }; @@ -1202,6 +1252,7 @@ export class EditorFontLigatures extends BaseEditorOption { EditorOption.fontSize, 'fontSize', EDITOR_FONT_DEFAULTS.fontSize, { type: 'number', + minimum: 6, + maximum: 100, default: EDITOR_FONT_DEFAULTS.fontSize, description: nls.localize('fontSize', "Controls the font size in pixels.") } @@ -1264,7 +1317,7 @@ class EditorFontSize extends SimpleEditorOption { if (r === 0) { return EDITOR_FONT_DEFAULTS.fontSize; } - return EditorFloatOption.clamp(r, 8, 100); + return EditorFloatOption.clamp(r, 6, 100); } public compute(env: IEnvironmentalOptions, options: IComputedEditorOptions, value: number): number { // The final fontSize respects the editor zoom level. @@ -1277,14 +1330,26 @@ class EditorFontSize extends SimpleEditorOption { //#region gotoLocation +export type GoToLocationValues = 'peek' | 'gotoAndPeek' | 'goto'; + /** * Configuration options for go to location */ export interface IGotoLocationOptions { - /** - * Control how goto-command work when having multiple results. - */ - multiple?: 'peek' | 'gotoAndPeek' | 'goto'; + + multiple?: GoToLocationValues; + + multipleDefinitions?: GoToLocationValues; + multipleTypeDefinitions?: GoToLocationValues; + multipleDeclarations?: GoToLocationValues; + multipleImplementations?: GoToLocationValues; + multipleReferences?: GoToLocationValues; + + alternativeDefinitionCommand?: string; + alternativeTypeDefinitionCommand?: string; + alternativeDeclarationCommand?: string; + alternativeImplementationCommand?: string; + alternativeReferenceCommand?: string; } export type GoToLocationOptions = Readonly>; @@ -1292,20 +1357,79 @@ export type GoToLocationOptions = Readonly>; class EditorGoToLocation extends BaseEditorOption { constructor() { - const defaults: GoToLocationOptions = { multiple: 'peek' }; + const defaults: GoToLocationOptions = { + multiple: 'peek', + multipleDefinitions: 'peek', + multipleTypeDefinitions: 'peek', + multipleDeclarations: 'peek', + multipleImplementations: 'peek', + multipleReferences: 'peek', + alternativeDefinitionCommand: 'editor.action.goToReferences', + alternativeTypeDefinitionCommand: 'editor.action.goToReferences', + alternativeDeclarationCommand: 'editor.action.goToReferences', + alternativeImplementationCommand: '', + alternativeReferenceCommand: '', + }; + const jsonSubset: IJSONSchema = { + type: 'string', + enum: ['peek', 'gotoAndPeek', 'goto'], + default: defaults.multiple, + enumDescriptions: [ + nls.localize('editor.gotoLocation.multiple.peek', 'Show peek view of the results (default)'), + nls.localize('editor.gotoLocation.multiple.gotoAndPeek', 'Go to the primary result and show a peek view'), + nls.localize('editor.gotoLocation.multiple.goto', 'Go to the primary result and enable peek-less navigation to others') + ] + }; super( EditorOption.gotoLocation, 'gotoLocation', defaults, { 'editor.gotoLocation.multiple': { - description: nls.localize('editor.gotoLocation.multiple', "Controls the behavior of 'Go To' commands, like Go To Definition, when multiple target locations exist."), + deprecationMessage: nls.localize('editor.gotoLocation.multiple.deprecated', "This setting is deprecated, please use separate settings like 'editor.editor.gotoLocation.multipleDefinitions' or 'editor.editor.gotoLocation.multipleImplementations' instead."), + }, + 'editor.gotoLocation.multipleDefinitions': { + description: nls.localize('editor.editor.gotoLocation.multipleDefinitions', "Controls the behavior the 'Go to Definition'-command when multiple target locations exist."), + ...jsonSubset, + }, + 'editor.gotoLocation.multipleTypeDefinitions': { + description: nls.localize('editor.editor.gotoLocation.multipleTypeDefinitions', "Controls the behavior the 'Go to Type Definition'-command when multiple target locations exist."), + ...jsonSubset, + }, + 'editor.gotoLocation.multipleDeclarations': { + description: nls.localize('editor.editor.gotoLocation.multipleDeclarations', "Controls the behavior the 'Go to Declaration'-command when multiple target locations exist."), + ...jsonSubset, + }, + 'editor.gotoLocation.multipleImplementations': { + description: nls.localize('editor.editor.gotoLocation.multipleImplemenattions', "Controls the behavior the 'Go to Implementations'-command when multiple target locations exist."), + ...jsonSubset, + }, + 'editor.gotoLocation.multipleReferences': { + description: nls.localize('editor.editor.gotoLocation.multipleReferences', "Controls the behavior the 'Go to References'-command when multiple target locations exist."), + ...jsonSubset, + }, + 'editor.gotoLocation.alternativeDefinitionCommand': { type: 'string', - enum: ['peek', 'gotoAndPeek', 'goto'], - default: defaults.multiple, - enumDescriptions: [ - nls.localize('editor.gotoLocation.multiple.peek', 'Show peek view of the results (default)'), - nls.localize('editor.gotoLocation.multiple.gotoAndPeek', 'Go to the primary result and show a peek view'), - nls.localize('editor.gotoLocation.multiple.goto', 'Go to the primary result and enable peek-less navigation to others') - ] + default: defaults.alternativeDefinitionCommand, + description: nls.localize('alternativeDefinitionCommand', "Alternative command id that is being executed when the result of 'Go to Definition' is the current location.") + }, + 'editor.gotoLocation.alternativeTypeDefinitionCommand': { + type: 'string', + default: defaults.alternativeTypeDefinitionCommand, + description: nls.localize('alternativeTypeDefinitionCommand', "Alternative command id that is being executed when the result of 'Go to Type Definition' is the current location.") + }, + 'editor.gotoLocation.alternativeDeclarationCommand': { + type: 'string', + default: defaults.alternativeDeclarationCommand, + description: nls.localize('alternativeDeclarationCommand', "Alternative command id that is being executed when the result of 'Go to Declaration' is the current location.") + }, + 'editor.gotoLocation.alternativeImplementationCommand': { + type: 'string', + default: defaults.alternativeImplementationCommand, + description: nls.localize('alternativeImplementationCommand', "Alternative command id that is being executed when the result of 'Go to Implementation' is the current location.") + }, + 'editor.gotoLocation.alternativeReferenceCommand': { + type: 'string', + default: defaults.alternativeReferenceCommand, + description: nls.localize('alternativeReferenceCommand', "Alternative command id that is being executed when the result of 'Go to Reference' is the current location.") }, } ); @@ -1317,7 +1441,17 @@ class EditorGoToLocation extends BaseEditorOption(input.multiple, this.defaultValue.multiple, ['peek', 'gotoAndPeek', 'goto']) + multiple: EditorStringEnumOption.stringSet(input.multiple, this.defaultValue.multiple!, ['peek', 'gotoAndPeek', 'goto']), + multipleDefinitions: input.multipleDefinitions ?? EditorStringEnumOption.stringSet(input.multipleDefinitions, 'peek', ['peek', 'gotoAndPeek', 'goto']), + multipleTypeDefinitions: input.multipleTypeDefinitions ?? EditorStringEnumOption.stringSet(input.multipleTypeDefinitions, 'peek', ['peek', 'gotoAndPeek', 'goto']), + multipleDeclarations: input.multipleDeclarations ?? EditorStringEnumOption.stringSet(input.multipleDeclarations, 'peek', ['peek', 'gotoAndPeek', 'goto']), + multipleImplementations: input.multipleImplementations ?? EditorStringEnumOption.stringSet(input.multipleImplementations, 'peek', ['peek', 'gotoAndPeek', 'goto']), + multipleReferences: input.multipleReferences ?? EditorStringEnumOption.stringSet(input.multipleReferences, 'peek', ['peek', 'gotoAndPeek', 'goto']), + alternativeDefinitionCommand: EditorStringOption.string(input.alternativeDefinitionCommand, this.defaultValue.alternativeDefinitionCommand), + alternativeTypeDefinitionCommand: EditorStringOption.string(input.alternativeTypeDefinitionCommand, this.defaultValue.alternativeTypeDefinitionCommand), + alternativeDeclarationCommand: EditorStringOption.string(input.alternativeDeclarationCommand, this.defaultValue.alternativeDeclarationCommand), + alternativeImplementationCommand: EditorStringOption.string(input.alternativeImplementationCommand, this.defaultValue.alternativeImplementationCommand), + alternativeReferenceCommand: EditorStringOption.string(input.alternativeReferenceCommand, this.defaultValue.alternativeReferenceCommand), }; } } @@ -1646,7 +1780,7 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption minimapMaxColumn) { minimapWidth = Math.floor(minimapMaxColumn * minimapCharWidth); @@ -2295,7 +2429,11 @@ export interface ISuggestOptions { /** * Overwrite word ends on accept. Default to false. */ - overwriteOnAccept?: boolean; + insertMode?: 'insert' | 'replace'; + /** + * Show a highlight when suggestion replaces or keep text after the cursor. Defaults to false. + */ + insertHighlight?: boolean; /** * Enable graceful matching. Defaults to true. */ @@ -2321,9 +2459,105 @@ export interface ISuggestOptions { */ maxVisibleSuggestions?: number; /** - * Names of suggestion types to filter. + * Show method-suggestions. + */ + showMethods?: boolean; + /** + * Show function-suggestions. + */ + showFunctions?: boolean; + /** + * Show constructor-suggestions. + */ + showConstructors?: boolean; + /** + * Show field-suggestions. */ - filteredTypes?: Record; + showFields?: boolean; + /** + * Show variable-suggestions. + */ + showVariables?: boolean; + /** + * Show class-suggestions. + */ + showClasses?: boolean; + /** + * Show struct-suggestions. + */ + showStructs?: boolean; + /** + * Show interface-suggestions. + */ + showInterfaces?: boolean; + /** + * Show module-suggestions. + */ + showModules?: boolean; + /** + * Show property-suggestions. + */ + showProperties?: boolean; + /** + * Show event-suggestions. + */ + showEvents?: boolean; + /** + * Show operator-suggestions. + */ + showOperators?: boolean; + /** + * Show unit-suggestions. + */ + showUnits?: boolean; + /** + * Show value-suggestions. + */ + showValues?: boolean; + /** + * Show constant-suggestions. + */ + showConstants?: boolean; + /** + * Show enum-suggestions. + */ + showEnums?: boolean; + /** + * Show enumMember-suggestions. + */ + showEnumMembers?: boolean; + /** + * Show keyword-suggestions. + */ + showKeywords?: boolean; + /** + * Show text-suggestions. + */ + showWords?: boolean; + /** + * Show color-suggestions. + */ + showColors?: boolean; + /** + * Show file-suggestions. + */ + showFiles?: boolean; + /** + * Show reference-suggestions. + */ + showReferences?: boolean; + /** + * Show folder-suggestions. + */ + showFolders?: boolean; + /** + * Show typeParameter-suggestions. + */ + showTypeParameters?: boolean; + /** + * Show snippet-suggestions. + */ + showSnippets?: boolean; } export type InternalSuggestOptions = Readonly>; @@ -2332,22 +2566,57 @@ class EditorSuggest extends BaseEditorOption !newCursorState.modelState.equals(oldState.cursorState[i].modelState)) ) { - this._onDidChange.fire(new CursorStateChangedEvent(selections, source || 'keyboard', reason)); + const oldSelections = oldState ? oldState.cursorState.map(s => s.modelState.selection) : null; + const oldModelVersionId = oldState ? oldState.modelVersionId : 0; + this._onDidChange.fire(new CursorStateChangedEvent(selections, newState.modelVersionId, oldSelections, oldModelVersionId, source || 'keyboard', reason)); } return true; diff --git a/src/vs/editor/common/controller/cursorCommon.ts b/src/vs/editor/common/controller/cursorCommon.ts index 7b40ac0ced2e..df52c1bf20e3 100644 --- a/src/vs/editor/common/controller/cursorCommon.ts +++ b/src/vs/editor/common/controller/cursorCommon.ts @@ -6,7 +6,7 @@ import { CharCode } from 'vs/base/common/charCode'; import { onUnexpectedError } from 'vs/base/common/errors'; import * as strings from 'vs/base/common/strings'; -import { EditorAutoClosingStrategy, EditorAutoSurroundStrategy, ConfigurationChangedEvent, EditorAutoClosingOvertypeStrategy, EditorOption } from 'vs/editor/common/config/editorOptions'; +import { EditorAutoClosingStrategy, EditorAutoSurroundStrategy, ConfigurationChangedEvent, EditorAutoClosingOvertypeStrategy, EditorOption, EditorAutoIndentStrategy } from 'vs/editor/common/config/editorOptions'; import { CursorChangeReason } from 'vs/editor/common/controller/cursorEvents'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; @@ -103,7 +103,7 @@ export class CursorConfiguration { public readonly autoClosingQuotes: EditorAutoClosingStrategy; public readonly autoClosingOvertype: EditorAutoClosingOvertypeStrategy; public readonly autoSurround: EditorAutoSurroundStrategy; - public readonly autoIndent: boolean; + public readonly autoIndent: EditorAutoIndentStrategy; public readonly autoClosingPairsOpen2: Map; public readonly autoClosingPairsClose2: Map; public readonly surroundingPairs: CharacterMap; @@ -523,12 +523,15 @@ export class CursorColumns { if (codePoint === CharCode.Tab) { result = CursorColumns.nextRenderTabStop(result, tabSize); } else { + let graphemeBreakType = strings.getGraphemeBreakType(codePoint); while (i < endOffset) { const nextCodePoint = strings.getNextCodePoint(lineContent, endOffset, i); - if (!strings.isUnicodeMark(nextCodePoint)) { + const nextGraphemeBreakType = strings.getGraphemeBreakType(nextCodePoint); + if (strings.breakBetweenGraphemeBreakType(graphemeBreakType, nextGraphemeBreakType)) { break; } i += (nextCodePoint >= Constants.UNICODE_SUPPLEMENTARY_PLANE_BEGIN ? 2 : 1); + graphemeBreakType = nextGraphemeBreakType; } if (strings.isFullWidthCharacter(codePoint) || strings.isEmojiImprecise(codePoint)) { result = result + 2; @@ -582,12 +585,15 @@ export class CursorColumns { if (codePoint === CharCode.Tab) { afterVisibleColumn = CursorColumns.nextRenderTabStop(beforeVisibleColumn, tabSize); } else { + let graphemeBreakType = strings.getGraphemeBreakType(codePoint); while (i < lineLength) { const nextCodePoint = strings.getNextCodePoint(lineContent, lineLength, i); - if (!strings.isUnicodeMark(nextCodePoint)) { + const nextGraphemeBreakType = strings.getGraphemeBreakType(nextCodePoint); + if (strings.breakBetweenGraphemeBreakType(graphemeBreakType, nextGraphemeBreakType)) { break; } i += (nextCodePoint >= Constants.UNICODE_SUPPLEMENTARY_PLANE_BEGIN ? 2 : 1); + graphemeBreakType = nextGraphemeBreakType; } if (strings.isFullWidthCharacter(codePoint) || strings.isEmojiImprecise(codePoint)) { afterVisibleColumn = beforeVisibleColumn + 2; diff --git a/src/vs/editor/common/controller/cursorEvents.ts b/src/vs/editor/common/controller/cursorEvents.ts index 6ebd1e4a28d7..562ecb540903 100644 --- a/src/vs/editor/common/controller/cursorEvents.ts +++ b/src/vs/editor/common/controller/cursorEvents.ts @@ -72,6 +72,18 @@ export interface ICursorSelectionChangedEvent { * The secondary selections. */ readonly secondarySelections: Selection[]; + /** + * The model version id. + */ + readonly modelVersionId: number; + /** + * The old selections. + */ + readonly oldSelections: Selection[] | null; + /** + * The model version id the that `oldSelections` refer to. + */ + readonly oldModelVersionId: number; /** * Source of the call that caused the event. */ diff --git a/src/vs/editor/common/controller/cursorTypeOperations.ts b/src/vs/editor/common/controller/cursorTypeOperations.ts index 8461aeb72153..fe3dfd7ac06f 100644 --- a/src/vs/editor/common/controller/cursorTypeOperations.ts +++ b/src/vs/editor/common/controller/cursorTypeOperations.ts @@ -19,6 +19,7 @@ import { ITextModel } from 'vs/editor/common/model'; import { EnterAction, IndentAction, StandardAutoClosingPairConditional } from 'vs/editor/common/modes/languageConfiguration'; import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { IElectricAction } from 'vs/editor/common/modes/supports/electricCharacter'; +import { EditorAutoIndentStrategy } from 'vs/editor/common/config/editorOptions'; export class TypeOperations { @@ -34,7 +35,8 @@ export class TypeOperations { tabSize: config.tabSize, indentSize: config.indentSize, insertSpaces: config.insertSpaces, - useTabStops: config.useTabStops + useTabStops: config.useTabStops, + autoIndent: config.autoIndent }); } return commands; @@ -48,7 +50,8 @@ export class TypeOperations { tabSize: config.tabSize, indentSize: config.indentSize, insertSpaces: config.insertSpaces, - useTabStops: config.useTabStops + useTabStops: config.useTabStops, + autoIndent: config.autoIndent }); } return commands; @@ -149,15 +152,15 @@ export class TypeOperations { let action: IndentAction | EnterAction | null = null; let indentation: string = ''; - let expectedIndentAction = config.autoIndent ? LanguageConfigurationRegistry.getInheritIndentForLine(model, lineNumber, false) : null; + const expectedIndentAction = LanguageConfigurationRegistry.getInheritIndentForLine(config.autoIndent, model, lineNumber, false); if (expectedIndentAction) { action = expectedIndentAction.action; indentation = expectedIndentAction.indentation; } else if (lineNumber > 1) { let lastLineNumber: number; for (lastLineNumber = lineNumber - 1; lastLineNumber >= 1; lastLineNumber--) { - let lineText = model.getLineContent(lastLineNumber); - let nonWhitespaceIdx = strings.lastNonWhitespaceIndex(lineText); + const lineText = model.getLineContent(lastLineNumber); + const nonWhitespaceIdx = strings.lastNonWhitespaceIndex(lineText); if (nonWhitespaceIdx >= 0) { break; } @@ -168,14 +171,10 @@ export class TypeOperations { return null; } - let maxColumn = model.getLineMaxColumn(lastLineNumber); - let expectedEnterAction = LanguageConfigurationRegistry.getEnterAction(model, new Range(lastLineNumber, maxColumn, lastLineNumber, maxColumn)); + const maxColumn = model.getLineMaxColumn(lastLineNumber); + const expectedEnterAction = LanguageConfigurationRegistry.getEnterAction(config.autoIndent, model, new Range(lastLineNumber, maxColumn, lastLineNumber, maxColumn)); if (expectedEnterAction) { - indentation = expectedEnterAction.indentation; - action = expectedEnterAction.enterAction; - if (action) { - indentation += action.appendText; - } + indentation = expectedEnterAction.indentation + expectedEnterAction.appendText; } } @@ -251,7 +250,8 @@ export class TypeOperations { tabSize: config.tabSize, indentSize: config.indentSize, insertSpaces: config.insertSpaces, - useTabStops: config.useTabStops + useTabStops: config.useTabStops, + autoIndent: config.autoIndent }); } } @@ -289,104 +289,97 @@ export class TypeOperations { } private static _enter(config: CursorConfiguration, model: ITextModel, keepPosition: boolean, range: Range): ICommand { - if (!model.isCheapToTokenize(range.getStartPosition().lineNumber)) { + if (config.autoIndent === EditorAutoIndentStrategy.None) { + return TypeOperations._typeCommand(range, '\n', keepPosition); + } + if (!model.isCheapToTokenize(range.getStartPosition().lineNumber) || config.autoIndent === EditorAutoIndentStrategy.Keep) { let lineText = model.getLineContent(range.startLineNumber); let indentation = strings.getLeadingWhitespace(lineText).substring(0, range.startColumn - 1); return TypeOperations._typeCommand(range, '\n' + config.normalizeIndentation(indentation), keepPosition); } - let r = LanguageConfigurationRegistry.getEnterAction(model, range); + const r = LanguageConfigurationRegistry.getEnterAction(config.autoIndent, model, range); if (r) { - let enterAction = r.enterAction; - let indentation = r.indentation; - - if (enterAction.indentAction === IndentAction.None) { + if (r.indentAction === IndentAction.None) { // Nothing special - return TypeOperations._typeCommand(range, '\n' + config.normalizeIndentation(indentation + enterAction.appendText), keepPosition); + return TypeOperations._typeCommand(range, '\n' + config.normalizeIndentation(r.indentation + r.appendText), keepPosition); - } else if (enterAction.indentAction === IndentAction.Indent) { + } else if (r.indentAction === IndentAction.Indent) { // Indent once - return TypeOperations._typeCommand(range, '\n' + config.normalizeIndentation(indentation + enterAction.appendText), keepPosition); + return TypeOperations._typeCommand(range, '\n' + config.normalizeIndentation(r.indentation + r.appendText), keepPosition); - } else if (enterAction.indentAction === IndentAction.IndentOutdent) { + } else if (r.indentAction === IndentAction.IndentOutdent) { // Ultra special - let normalIndent = config.normalizeIndentation(indentation); - let increasedIndent = config.normalizeIndentation(indentation + enterAction.appendText); + const normalIndent = config.normalizeIndentation(r.indentation); + const increasedIndent = config.normalizeIndentation(r.indentation + r.appendText); - let typeText = '\n' + increasedIndent + '\n' + normalIndent; + const typeText = '\n' + increasedIndent + '\n' + normalIndent; if (keepPosition) { return new ReplaceCommandWithoutChangingPosition(range, typeText, true); } else { return new ReplaceCommandWithOffsetCursorState(range, typeText, -1, increasedIndent.length - normalIndent.length, true); } - } else if (enterAction.indentAction === IndentAction.Outdent) { - let actualIndentation = TypeOperations.unshiftIndent(config, indentation); - return TypeOperations._typeCommand(range, '\n' + config.normalizeIndentation(actualIndentation + enterAction.appendText), keepPosition); + } else if (r.indentAction === IndentAction.Outdent) { + const actualIndentation = TypeOperations.unshiftIndent(config, r.indentation); + return TypeOperations._typeCommand(range, '\n' + config.normalizeIndentation(actualIndentation + r.appendText), keepPosition); } } - // no enter rules applied, we should check indentation rules then. - if (!config.autoIndent) { - // Nothing special - let lineText = model.getLineContent(range.startLineNumber); - let indentation = strings.getLeadingWhitespace(lineText).substring(0, range.startColumn - 1); - return TypeOperations._typeCommand(range, '\n' + config.normalizeIndentation(indentation), keepPosition); - } + const lineText = model.getLineContent(range.startLineNumber); + const indentation = strings.getLeadingWhitespace(lineText).substring(0, range.startColumn - 1); - let ir = LanguageConfigurationRegistry.getIndentForEnter(model, range, { - unshiftIndent: (indent) => { - return TypeOperations.unshiftIndent(config, indent); - }, - shiftIndent: (indent) => { - return TypeOperations.shiftIndent(config, indent); - }, - normalizeIndentation: (indent) => { - return config.normalizeIndentation(indent); - } - }, config.autoIndent); - - let lineText = model.getLineContent(range.startLineNumber); - let indentation = strings.getLeadingWhitespace(lineText).substring(0, range.startColumn - 1); + if (config.autoIndent >= EditorAutoIndentStrategy.Full) { + const ir = LanguageConfigurationRegistry.getIndentForEnter(config.autoIndent, model, range, { + unshiftIndent: (indent) => { + return TypeOperations.unshiftIndent(config, indent); + }, + shiftIndent: (indent) => { + return TypeOperations.shiftIndent(config, indent); + }, + normalizeIndentation: (indent) => { + return config.normalizeIndentation(indent); + } + }); - if (ir) { - let oldEndViewColumn = CursorColumns.visibleColumnFromColumn2(config, model, range.getEndPosition()); - let oldEndColumn = range.endColumn; + if (ir) { + let oldEndViewColumn = CursorColumns.visibleColumnFromColumn2(config, model, range.getEndPosition()); + const oldEndColumn = range.endColumn; - let beforeText = '\n'; - if (indentation !== config.normalizeIndentation(ir.beforeEnter)) { - beforeText = config.normalizeIndentation(ir.beforeEnter) + lineText.substring(indentation.length, range.startColumn - 1) + '\n'; - range = new Range(range.startLineNumber, 1, range.endLineNumber, range.endColumn); - } + let beforeText = '\n'; + if (indentation !== config.normalizeIndentation(ir.beforeEnter)) { + beforeText = config.normalizeIndentation(ir.beforeEnter) + lineText.substring(indentation.length, range.startColumn - 1) + '\n'; + range = new Range(range.startLineNumber, 1, range.endLineNumber, range.endColumn); + } - let newLineContent = model.getLineContent(range.endLineNumber); - let firstNonWhitespace = strings.firstNonWhitespaceIndex(newLineContent); - if (firstNonWhitespace >= 0) { - range = range.setEndPosition(range.endLineNumber, Math.max(range.endColumn, firstNonWhitespace + 1)); - } else { - range = range.setEndPosition(range.endLineNumber, model.getLineMaxColumn(range.endLineNumber)); - } + const newLineContent = model.getLineContent(range.endLineNumber); + const firstNonWhitespace = strings.firstNonWhitespaceIndex(newLineContent); + if (firstNonWhitespace >= 0) { + range = range.setEndPosition(range.endLineNumber, Math.max(range.endColumn, firstNonWhitespace + 1)); + } else { + range = range.setEndPosition(range.endLineNumber, model.getLineMaxColumn(range.endLineNumber)); + } - if (keepPosition) { - return new ReplaceCommandWithoutChangingPosition(range, beforeText + config.normalizeIndentation(ir.afterEnter), true); - } else { - let offset = 0; - if (oldEndColumn <= firstNonWhitespace + 1) { - if (!config.insertSpaces) { - oldEndViewColumn = Math.ceil(oldEndViewColumn / config.indentSize); + if (keepPosition) { + return new ReplaceCommandWithoutChangingPosition(range, beforeText + config.normalizeIndentation(ir.afterEnter), true); + } else { + let offset = 0; + if (oldEndColumn <= firstNonWhitespace + 1) { + if (!config.insertSpaces) { + oldEndViewColumn = Math.ceil(oldEndViewColumn / config.indentSize); + } + offset = Math.min(oldEndViewColumn + 1 - config.normalizeIndentation(ir.afterEnter).length - 1, 0); } - offset = Math.min(oldEndViewColumn + 1 - config.normalizeIndentation(ir.afterEnter).length - 1, 0); + return new ReplaceCommandWithOffsetCursorState(range, beforeText + config.normalizeIndentation(ir.afterEnter), 0, offset, true); } - return new ReplaceCommandWithOffsetCursorState(range, beforeText + config.normalizeIndentation(ir.afterEnter), 0, offset, true); } - - } else { - return TypeOperations._typeCommand(range, '\n' + config.normalizeIndentation(indentation), keepPosition); } + + return TypeOperations._typeCommand(range, '\n' + config.normalizeIndentation(indentation), keepPosition); } private static _isAutoIndentType(config: CursorConfiguration, model: ITextModel, selections: Selection[]): boolean { - if (!config.autoIndent) { + if (config.autoIndent < EditorAutoIndentStrategy.Full) { return false; } @@ -400,8 +393,8 @@ export class TypeOperations { } private static _runAutoIndentType(config: CursorConfiguration, model: ITextModel, range: Range, ch: string): ICommand | null { - let currentIndentation = LanguageConfigurationRegistry.getIndentationAtPosition(model, range.startLineNumber, range.startColumn); - let actualIndentation = LanguageConfigurationRegistry.getIndentActionForType(model, range, ch, { + const currentIndentation = LanguageConfigurationRegistry.getIndentationAtPosition(model, range.startLineNumber, range.startColumn); + const actualIndentation = LanguageConfigurationRegistry.getIndentActionForType(config.autoIndent, model, range, ch, { shiftIndent: (indentation) => { return TypeOperations.shiftIndent(config, indentation); }, @@ -415,7 +408,7 @@ export class TypeOperations { } if (actualIndentation !== config.normalizeIndentation(currentIndentation)) { - let firstNonWhitespace = model.getLineFirstNonWhitespaceColumn(range.startLineNumber); + const firstNonWhitespace = model.getLineFirstNonWhitespaceColumn(range.startLineNumber); if (firstNonWhitespace === 0) { return TypeOperations._typeCommand( new Range(range.startLineNumber, 0, range.endLineNumber, range.endColumn), @@ -459,9 +452,10 @@ export class TypeOperations { return false; } - // Do not over-type after a backslash + // Do not over-type quotes after a backslash + const chIsQuote = isQuote(ch); const beforeCharacter = position.column > 2 ? lineText.charCodeAt(position.column - 2) : CharCode.Null; - if (beforeCharacter === CharCode.Backslash) { + if (beforeCharacter === CharCode.Backslash && chIsQuote) { return false; } diff --git a/src/vs/editor/common/controller/cursorWordOperations.ts b/src/vs/editor/common/controller/cursorWordOperations.ts index d3ddf0fe9a4d..32fdbf2028a6 100644 --- a/src/vs/editor/common/controller/cursorWordOperations.ts +++ b/src/vs/editor/common/controller/cursorWordOperations.ts @@ -289,17 +289,26 @@ export class WordOperations { column = model.getLineMaxColumn(lineNumber); } } else if (wordNavigationType === WordNavigationType.WordAccessibility) { + if (movedDown) { + // If we move to the next line, pretend that the cursor is right before the first character. + // This is needed when the first word starts right at the first character - and in order not to miss it, + // we need to start before. + column = 0; + } while ( nextWordOnLine - && nextWordOnLine.wordType === WordType.Separator + && (nextWordOnLine.wordType === WordType.Separator + || nextWordOnLine.start + 1 <= column + ) ) { // Skip over a word made up of one single separator + // Also skip over word if it begins before current cursor position to ascertain we're moving forward at least 1 character. nextWordOnLine = WordOperations._findNextWordOnLine(wordSeparators, model, new Position(lineNumber, nextWordOnLine.end + 1)); } if (nextWordOnLine) { - column = nextWordOnLine.end + 1; + column = nextWordOnLine.start + 1; } else { column = model.getLineMaxColumn(lineNumber); } diff --git a/src/vs/editor/common/core/lineTokens.ts b/src/vs/editor/common/core/lineTokens.ts index 8100e22068e8..816b865e9068 100644 --- a/src/vs/editor/common/core/lineTokens.ts +++ b/src/vs/editor/common/core/lineTokens.ts @@ -67,6 +67,11 @@ export class LineTokens implements IViewLineTokens { return 0; } + public getMetadata(tokenIndex: number): number { + const metadata = this._tokens[(tokenIndex << 1) + 1]; + return metadata; + } + public getLanguageId(tokenIndex: number): LanguageId { const metadata = this._tokens[(tokenIndex << 1) + 1]; return TokenMetadata.getLanguageId(metadata); @@ -132,8 +137,8 @@ export class LineTokens implements IViewLineTokens { while (low < high) { - let mid = low + Math.floor((high - low) / 2); - let endOffset = tokens[(mid << 1)]; + const mid = low + Math.floor((high - low) / 2); + const endOffset = tokens[(mid << 1)]; if (endOffset === desiredIndex) { return mid + 1; diff --git a/src/vs/editor/common/model.ts b/src/vs/editor/common/model.ts index 6f6bc288be5d..21e4f4c8e227 100644 --- a/src/vs/editor/common/model.ts +++ b/src/vs/editor/common/model.ts @@ -14,7 +14,7 @@ import { IModelContentChange, IModelContentChangedEvent, IModelDecorationsChange import { SearchData } from 'vs/editor/common/model/textModelSearch'; import { LanguageId, LanguageIdentifier, FormattingOptions } from 'vs/editor/common/modes'; import { ThemeColor } from 'vs/platform/theme/common/themeService'; -import { MultilineTokens } from 'vs/editor/common/model/tokensStore'; +import { MultilineTokens, MultilineTokens2 } from 'vs/editor/common/model/tokensStore'; /** * Vertical Lane in the overview ruler of the editor. @@ -30,7 +30,8 @@ export enum OverviewRulerLane { * Position in the minimap to render the decoration. */ export enum MinimapPosition { - Inline = 1 + Inline = 1, + Gutter = 2 } export interface IDecorationOptions { @@ -791,6 +792,11 @@ export interface ITextModel { */ setTokens(tokens: MultilineTokens[]): void; + /** + * @internal + */ + setSemanticTokens(tokens: MultilineTokens2[] | null): void; + /** * Flush all tokenization state. * @internal @@ -1192,6 +1198,13 @@ export interface ITextBufferFactory { getFirstLineText(lengthLimit: number): string; } +/** + * @internal + */ +export const enum ModelConstants { + FIRST_LINE_DETECTION_LENGTH_LIMIT = 1000 +} + /** * @internal */ diff --git a/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeBase.ts b/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeBase.ts index 525b5565caa9..764ca730fe14 100644 --- a/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeBase.ts +++ b/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeBase.ts @@ -577,23 +577,34 @@ export class PieceTreeBase { let m: RegExpExecArray | null; // Reset regex to search from the beginning - searcher.reset(start); let ret: BufferCursor = { line: 0, column: 0 }; + let searchText: string; + let offsetInBuffer: (offset: number) => number; + + if (searcher._wordSeparators) { + searchText = buffer.buffer.substring(start, end); + offsetInBuffer = (offset: number) => offset + start; + searcher.reset(-1); + } else { + searchText = buffer.buffer; + offsetInBuffer = (offset: number) => offset; + searcher.reset(start); + } do { - m = searcher.next(buffer.buffer); + m = searcher.next(searchText); if (m) { - if (m.index >= end) { + if (offsetInBuffer(m.index) >= end) { return resultLen; } - this.positionInBuffer(node, m.index - startOffsetInBuffer, ret); + this.positionInBuffer(node, offsetInBuffer(m.index) - startOffsetInBuffer, ret); let lineFeedCnt = this.getLineFeedCnt(node.piece.bufferIndex, startCursor, ret); let retStartColumn = ret.line === startCursor.line ? ret.column - startCursor.column + startColumn : ret.column + 1; let retEndColumn = retStartColumn + m[0].length; result[resultLen++] = createFindMatch(new Range(startLineNumber + lineFeedCnt, retStartColumn, startLineNumber + lineFeedCnt, retEndColumn), m, captureMatches); - if (m.index + m[0].length >= end) { + if (offsetInBuffer(m.index) + m[0].length >= end) { return resultLen; } if (resultLen >= limitResultCount) { diff --git a/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBufferBuilder.ts b/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBufferBuilder.ts index 744b6f2f307f..3654c267d376 100644 --- a/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBufferBuilder.ts +++ b/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBufferBuilder.ts @@ -57,7 +57,7 @@ export class PieceTreeTextBufferFactory implements ITextBufferFactory { } public getFirstLineText(lengthLimit: number): string { - return this._chunks[0].buffer.substr(0, 100).split(/\r\n|\r|\n/)[0]; + return this._chunks[0].buffer.substr(0, lengthLimit).split(/\r\n|\r|\n/)[0]; } } diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index 04a368d18746..3e6b13803f04 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -32,7 +32,7 @@ import { BracketsUtils, RichEditBracket, RichEditBrackets } from 'vs/editor/comm import { ITheme, ThemeColor } from 'vs/platform/theme/common/themeService'; import { withUndefinedAsNull } from 'vs/base/common/types'; import { VSBufferReadableStream, VSBuffer } from 'vs/base/common/buffer'; -import { TokensStore, MultilineTokens, countEOL } from 'vs/editor/common/model/tokensStore'; +import { TokensStore, MultilineTokens, countEOL, MultilineTokens2, TokensStore2 } from 'vs/editor/common/model/tokensStore'; import { Color } from 'vs/base/common/color'; function createTextBufferBuilder() { @@ -276,6 +276,7 @@ export class TextModel extends Disposable implements model.ITextModel { private _languageIdentifier: LanguageIdentifier; private readonly _languageRegistryListener: IDisposable; private readonly _tokens: TokensStore; + private readonly _tokens2: TokensStore2; private readonly _tokenization: TextModelTokenization; //#endregion @@ -339,6 +340,7 @@ export class TextModel extends Disposable implements model.ITextModel { this._trimAutoWhitespaceLines = null; this._tokens = new TokensStore(); + this._tokens2 = new TokensStore2(); this._tokenization = new TextModelTokenization(this); } @@ -414,6 +416,7 @@ export class TextModel extends Disposable implements model.ITextModel { // Flush all tokens this._tokens.flush(); + this._tokens2.flush(); // Destroy all my decorations this._decorations = Object.create(null); @@ -1262,8 +1265,9 @@ export class TextModel extends Disposable implements model.ITextModel { let lineCount = oldLineCount; for (let i = 0, len = contentChanges.length; i < len; i++) { const change = contentChanges[i]; - const [eolCount, firstLineLength] = countEOL(change.text); + const [eolCount, firstLineLength, lastLineLength] = countEOL(change.text); this._tokens.acceptEdit(change.range, eolCount, firstLineLength); + this._tokens2.acceptEdit(change.range, eolCount, firstLineLength, lastLineLength, change.text.length > 0 ? change.text.charCodeAt(0) : CharCode.Null); this._onDidChangeDecorations.fire(); this._decorationsTree.acceptReplace(change.rangeOffset, change.rangeLength, change.text.length, change.forceMoveMarkers); @@ -1717,6 +1721,15 @@ export class TextModel extends Disposable implements model.ITextModel { }); } + public setSemanticTokens(tokens: MultilineTokens2[] | null): void { + this._tokens2.set(tokens); + + this._emitModelTokensChangedEvent({ + tokenizationSupportChanged: false, + ranges: [{ fromLineNumber: 1, toLineNumber: this.getLineCount() }] + }); + } + public tokenizeViewport(startLineNumber: number, endLineNumber: number): void { startLineNumber = Math.max(1, startLineNumber); endLineNumber = Math.min(this._buffer.getLineCount(), endLineNumber); @@ -1734,6 +1747,15 @@ export class TextModel extends Disposable implements model.ITextModel { }); } + public clearSemanticTokens(): void { + this._tokens2.flush(); + + this._emitModelTokensChangedEvent({ + tokenizationSupportChanged: false, + ranges: [{ fromLineNumber: 1, toLineNumber: this.getLineCount() }] + }); + } + private _emitModelTokensChangedEvent(e: IModelTokensChangedEvent): void { if (!this._isDisposing) { this._onDidChangeTokens.fire(e); @@ -1772,7 +1794,8 @@ export class TextModel extends Disposable implements model.ITextModel { private _getLineTokens(lineNumber: number): LineTokens { const lineText = this.getLineContent(lineNumber); - return this._tokens.getTokens(this._languageIdentifier.id, lineNumber - 1, lineText); + const syntacticTokens = this._tokens.getTokens(this._languageIdentifier.id, lineNumber - 1, lineText); + return this._tokens2.addSemanticTokens(lineNumber, syntacticTokens); } public getLanguageIdentifier(): LanguageIdentifier { diff --git a/src/vs/editor/common/model/textModelSearch.ts b/src/vs/editor/common/model/textModelSearch.ts index b8d0685b31b3..37ff0d86fb8e 100644 --- a/src/vs/editor/common/model/textModelSearch.ts +++ b/src/vs/editor/common/model/textModelSearch.ts @@ -510,7 +510,7 @@ export function isValidMatch(wordSeparators: WordCharacterClassifier, text: stri } export class Searcher { - private readonly _wordSeparators: WordCharacterClassifier | null; + public readonly _wordSeparators: WordCharacterClassifier | null; private readonly _searchRegex: RegExp; private _prevMatchStartIndex: number; private _prevMatchLength: number; diff --git a/src/vs/editor/common/model/tokensStore.ts b/src/vs/editor/common/model/tokensStore.ts index 589faf6a1769..5e9cb8f86fcb 100644 --- a/src/vs/editor/common/model/tokensStore.ts +++ b/src/vs/editor/common/model/tokensStore.ts @@ -11,9 +11,10 @@ import { ColorId, FontStyle, LanguageId, MetadataConsts, StandardTokenType, Toke import { writeUInt32BE, readUInt32BE } from 'vs/base/common/buffer'; import { CharCode } from 'vs/base/common/charCode'; -export function countEOL(text: string): [number, number] { +export function countEOL(text: string): [number, number, number] { let eolCount = 0; let firstLineLength = 0; + let lastLineStart = 0; for (let i = 0, len = text.length; i < len; i++) { const chr = text.charCodeAt(i); @@ -28,17 +29,19 @@ export function countEOL(text: string): [number, number] { } else { // \r... case } + lastLineStart = i + 1; } else if (chr === CharCode.LineFeed) { if (eolCount === 0) { firstLineLength = i; } eolCount++; + lastLineStart = i + 1; } } if (eolCount === 0) { firstLineLength = text.length; } - return [eolCount, firstLineLength]; + return [eolCount, firstLineLength, text.length - lastLineStart]; } function getDefaultMetadata(topLevelLanguageId: LanguageId): number { @@ -109,6 +112,453 @@ export class MultilineTokensBuilder { } } +export interface IEncodedTokens { + getTokenCount(): number; + getDeltaLine(tokenIndex: number): number; + getMaxDeltaLine(): number; + getStartCharacter(tokenIndex: number): number; + getEndCharacter(tokenIndex: number): number; + getMetadata(tokenIndex: number): number; + + clear(): void; + acceptDeleteRange(startDeltaLine: number, startCharacter: number, endDeltaLine: number, endCharacter: number): void; + acceptInsertText(deltaLine: number, character: number, eolCount: number, firstLineLength: number, lastLineLength: number, firstCharCode: number): void; +} + +export class SparseEncodedTokens implements IEncodedTokens { + /** + * The encoding of tokens is: + * 4*i deltaLine (from `startLineNumber`) + * 4*i+1 startCharacter (from the line start) + * 4*i+2 endCharacter (from the line start) + * 4*i+3 metadata + */ + private _tokens: Uint32Array; + private _tokenCount: number; + + constructor(tokens: Uint32Array) { + this._tokens = tokens; + this._tokenCount = tokens.length / 4; + } + + public getMaxDeltaLine(): number { + const tokenCount = this.getTokenCount(); + if (tokenCount === 0) { + return -1; + } + return this.getDeltaLine(tokenCount - 1); + } + + public getTokenCount(): number { + return this._tokenCount; + } + + public getDeltaLine(tokenIndex: number): number { + return this._tokens[4 * tokenIndex]; + } + + public getStartCharacter(tokenIndex: number): number { + return this._tokens[4 * tokenIndex + 1]; + } + + public getEndCharacter(tokenIndex: number): number { + return this._tokens[4 * tokenIndex + 2]; + } + + public getMetadata(tokenIndex: number): number { + return this._tokens[4 * tokenIndex + 3]; + } + + public clear(): void { + this._tokenCount = 0; + } + + public acceptDeleteRange(startDeltaLine: number, startCharacter: number, endDeltaLine: number, endCharacter: number): void { + // This is a bit complex, here are the cases I used to think about this: + // + // 1. The token starts before the deletion range + // 1a. The token is completely before the deletion range + // ----------- + // xxxxxxxxxxx + // 1b. The token starts before, the deletion range ends after the token + // ----------- + // xxxxxxxxxxx + // 1c. The token starts before, the deletion range ends precisely with the token + // --------------- + // xxxxxxxx + // 1d. The token starts before, the deletion range is inside the token + // --------------- + // xxxxx + // + // 2. The token starts at the same position with the deletion range + // 2a. The token starts at the same position, and ends inside the deletion range + // ------- + // xxxxxxxxxxx + // 2b. The token starts at the same position, and ends at the same position as the deletion range + // ---------- + // xxxxxxxxxx + // 2c. The token starts at the same position, and ends after the deletion range + // ------------- + // xxxxxxx + // + // 3. The token starts inside the deletion range + // 3a. The token is inside the deletion range + // ------- + // xxxxxxxxxxxxx + // 3b. The token starts inside the deletion range, and ends at the same position as the deletion range + // ---------- + // xxxxxxxxxxxxx + // 3c. The token starts inside the deletion range, and ends after the deletion range + // ------------ + // xxxxxxxxxxx + // + // 4. The token starts after the deletion range + // ----------- + // xxxxxxxx + // + const tokens = this._tokens; + const tokenCount = this._tokenCount; + const deletedLineCount = (endDeltaLine - startDeltaLine); + let newTokenCount = 0; + let hasDeletedTokens = false; + for (let i = 0; i < tokenCount; i++) { + const srcOffset = 4 * i; + let tokenDeltaLine = tokens[srcOffset]; + let tokenStartCharacter = tokens[srcOffset + 1]; + let tokenEndCharacter = tokens[srcOffset + 2]; + const tokenMetadata = tokens[srcOffset + 3]; + + if (tokenDeltaLine < startDeltaLine || (tokenDeltaLine === startDeltaLine && tokenEndCharacter <= startCharacter)) { + // 1a. The token is completely before the deletion range + // => nothing to do + newTokenCount++; + continue; + } else if (tokenDeltaLine === startDeltaLine && tokenStartCharacter < startCharacter) { + // 1b, 1c, 1d + // => the token survives, but it needs to shrink + if (tokenDeltaLine === endDeltaLine && tokenEndCharacter > endCharacter) { + // 1d. The token starts before, the deletion range is inside the token + // => the token shrinks by the deletion character count + tokenEndCharacter -= (endCharacter - startCharacter); + } else { + // 1b. The token starts before, the deletion range ends after the token + // 1c. The token starts before, the deletion range ends precisely with the token + // => the token shrinks its ending to the deletion start + tokenEndCharacter = startCharacter; + } + } else if (tokenDeltaLine === startDeltaLine && tokenStartCharacter === startCharacter) { + // 2a, 2b, 2c + if (tokenDeltaLine === endDeltaLine && tokenEndCharacter > endCharacter) { + // 2c. The token starts at the same position, and ends after the deletion range + // => the token shrinks by the deletion character count + tokenEndCharacter -= (endCharacter - startCharacter); + } else { + // 2a. The token starts at the same position, and ends inside the deletion range + // 2b. The token starts at the same position, and ends at the same position as the deletion range + // => the token is deleted + hasDeletedTokens = true; + continue; + } + } else if (tokenDeltaLine < endDeltaLine || (tokenDeltaLine === endDeltaLine && tokenStartCharacter < endCharacter)) { + // 3a, 3b, 3c + if (tokenDeltaLine === endDeltaLine && tokenEndCharacter > endCharacter) { + // 3c. The token starts inside the deletion range, and ends after the deletion range + // => the token moves left and shrinks + if (tokenDeltaLine === startDeltaLine) { + // the deletion started on the same line as the token + // => the token moves left and shrinks + tokenStartCharacter = startCharacter; + tokenEndCharacter = tokenStartCharacter + (tokenEndCharacter - endCharacter); + } else { + // the deletion started on a line above the token + // => the token moves to the beginning of the line + tokenStartCharacter = 0; + tokenEndCharacter = tokenStartCharacter + (tokenEndCharacter - endCharacter); + } + } else { + // 3a. The token is inside the deletion range + // 3b. The token starts inside the deletion range, and ends at the same position as the deletion range + // => the token is deleted + hasDeletedTokens = true; + continue; + } + } else if (tokenDeltaLine > endDeltaLine) { + // 4. (partial) The token starts after the deletion range, on a line below... + if (deletedLineCount === 0 && !hasDeletedTokens) { + // early stop, there is no need to walk all the tokens and do nothing... + newTokenCount = tokenCount; + break; + } + tokenDeltaLine -= deletedLineCount; + } else if (tokenDeltaLine === endDeltaLine && tokenStartCharacter >= endCharacter) { + // 4. (continued) The token starts after the deletion range, on the last line where a deletion occurs + tokenDeltaLine -= deletedLineCount; + tokenStartCharacter -= endCharacter; + tokenEndCharacter -= endCharacter; + } else { + throw new Error(`Not possible!`); + } + + const destOffset = 4 * newTokenCount; + tokens[destOffset] = tokenDeltaLine; + tokens[destOffset + 1] = tokenStartCharacter; + tokens[destOffset + 2] = tokenEndCharacter; + tokens[destOffset + 3] = tokenMetadata; + newTokenCount++; + } + + this._tokenCount = newTokenCount; + } + + public acceptInsertText(deltaLine: number, character: number, eolCount: number, firstLineLength: number, lastLineLength: number, firstCharCode: number): void { + // Here are the cases I used to think about this: + // + // 1. The token is completely before the insertion point + // ----------- | + // 2. The token ends precisely at the insertion point + // -----------| + // 3. The token contains the insertion point + // -----|------ + // 4. The token starts precisely at the insertion point + // |----------- + // 5. The token is completely after the insertion point + // | ----------- + // + const isInsertingPreciselyOneWordCharacter = ( + eolCount === 0 + && firstLineLength === 1 + && ( + (firstCharCode >= CharCode.Digit0 && firstCharCode <= CharCode.Digit9) + || (firstCharCode >= CharCode.A && firstCharCode <= CharCode.Z) + || (firstCharCode >= CharCode.a && firstCharCode <= CharCode.z) + ) + ); + const tokens = this._tokens; + const tokenCount = this._tokenCount; + for (let i = 0; i < tokenCount; i++) { + const offset = 4 * i; + let tokenDeltaLine = tokens[offset]; + let tokenStartCharacter = tokens[offset + 1]; + let tokenEndCharacter = tokens[offset + 2]; + + if (tokenDeltaLine < deltaLine || (tokenDeltaLine === deltaLine && tokenEndCharacter < character)) { + // 1. The token is completely before the insertion point + // => nothing to do + continue; + } else if (tokenDeltaLine === deltaLine && tokenEndCharacter === character) { + // 2. The token ends precisely at the insertion point + // => expand the end character only if inserting precisely one character that is a word character + if (isInsertingPreciselyOneWordCharacter) { + tokenEndCharacter += 1; + } else { + continue; + } + } else if (tokenDeltaLine === deltaLine && tokenStartCharacter < character && character < tokenEndCharacter) { + // 3. The token contains the insertion point + if (eolCount === 0) { + // => just expand the end character + tokenEndCharacter += firstLineLength; + } else { + // => cut off the token + tokenEndCharacter = character; + } + } else { + // 4. or 5. + if (tokenDeltaLine === deltaLine && tokenStartCharacter === character) { + // 4. The token starts precisely at the insertion point + // => grow the token (by keeping its start constant) only if inserting precisely one character that is a word character + // => otherwise behave as in case 5. + if (isInsertingPreciselyOneWordCharacter) { + continue; + } + } + // => the token must move and keep its size constant + tokenDeltaLine += eolCount; + if (tokenDeltaLine === deltaLine) { + // this token is on the line where the insertion is taking place + if (eolCount === 0) { + tokenStartCharacter += firstLineLength; + tokenEndCharacter += firstLineLength; + } else { + const tokenLength = tokenEndCharacter - tokenStartCharacter; + tokenStartCharacter = lastLineLength + (tokenStartCharacter - character); + tokenEndCharacter = tokenStartCharacter + tokenLength; + } + } + } + + tokens[offset] = tokenDeltaLine; + tokens[offset + 1] = tokenStartCharacter; + tokens[offset + 2] = tokenEndCharacter; + } + } +} + +export class LineTokens2 { + + private readonly _actual: IEncodedTokens; + private readonly _startTokenIndex: number; + private readonly _endTokenIndex: number; + + constructor(actual: IEncodedTokens, startTokenIndex: number, endTokenIndex: number) { + this._actual = actual; + this._startTokenIndex = startTokenIndex; + this._endTokenIndex = endTokenIndex; + } + + public getCount(): number { + return this._endTokenIndex - this._startTokenIndex + 1; + } + + public getStartCharacter(tokenIndex: number): number { + return this._actual.getStartCharacter(this._startTokenIndex + tokenIndex); + } + + public getEndCharacter(tokenIndex: number): number { + return this._actual.getEndCharacter(this._startTokenIndex + tokenIndex); + } + + public getMetadata(tokenIndex: number): number { + return this._actual.getMetadata(this._startTokenIndex + tokenIndex); + } +} + +export class MultilineTokens2 { + + public startLineNumber: number; + public endLineNumber: number; + public tokens: IEncodedTokens; + + constructor(startLineNumber: number, tokens: IEncodedTokens) { + this.startLineNumber = startLineNumber; + this.tokens = tokens; + this.endLineNumber = this.startLineNumber + this.tokens.getMaxDeltaLine(); + } + + private _updateEndLineNumber(): void { + this.endLineNumber = this.startLineNumber + this.tokens.getMaxDeltaLine(); + } + + public getLineTokens(lineNumber: number): LineTokens2 | null { + if (this.startLineNumber <= lineNumber && lineNumber <= this.endLineNumber) { + const findResult = MultilineTokens2._findTokensWithLine(this.tokens, lineNumber - this.startLineNumber); + if (findResult) { + const [startTokenIndex, endTokenIndex] = findResult; + return new LineTokens2(this.tokens, startTokenIndex, endTokenIndex); + } + } + return null; + } + + private static _findTokensWithLine(tokens: IEncodedTokens, deltaLine: number): [number, number] | null { + let low = 0; + let high = tokens.getTokenCount() - 1; + + while (low < high) { + const mid = low + Math.floor((high - low) / 2); + const midDeltaLine = tokens.getDeltaLine(mid); + + if (midDeltaLine < deltaLine) { + low = mid + 1; + } else if (midDeltaLine > deltaLine) { + high = mid - 1; + } else { + let min = mid; + while (min > low && tokens.getDeltaLine(min - 1) === deltaLine) { + min--; + } + let max = mid; + while (max < high && tokens.getDeltaLine(max + 1) === deltaLine) { + max++; + } + return [min, max]; + } + } + + if (tokens.getDeltaLine(low) === deltaLine) { + return [low, low]; + } + + return null; + } + + public applyEdit(range: IRange, text: string): void { + const [eolCount, firstLineLength, lastLineLength] = countEOL(text); + this.acceptEdit(range, eolCount, firstLineLength, lastLineLength, text.length > 0 ? text.charCodeAt(0) : CharCode.Null); + } + + public acceptEdit(range: IRange, eolCount: number, firstLineLength: number, lastLineLength: number, firstCharCode: number): void { + this._acceptDeleteRange(range); + this._acceptInsertText(new Position(range.startLineNumber, range.startColumn), eolCount, firstLineLength, lastLineLength, firstCharCode); + this._updateEndLineNumber(); + } + + private _acceptDeleteRange(range: IRange): void { + if (range.startLineNumber === range.endLineNumber && range.startColumn === range.endColumn) { + // Nothing to delete + return; + } + + const firstLineIndex = range.startLineNumber - this.startLineNumber; + const lastLineIndex = range.endLineNumber - this.startLineNumber; + + if (lastLineIndex < 0) { + // this deletion occurs entirely before this block, so we only need to adjust line numbers + const deletedLinesCount = lastLineIndex - firstLineIndex; + this.startLineNumber -= deletedLinesCount; + return; + } + + const tokenMaxDeltaLine = this.tokens.getMaxDeltaLine(); + + if (firstLineIndex >= tokenMaxDeltaLine + 1) { + // this deletion occurs entirely after this block, so there is nothing to do + return; + } + + if (firstLineIndex < 0 && lastLineIndex >= tokenMaxDeltaLine + 1) { + // this deletion completely encompasses this block + this.startLineNumber = 0; + this.tokens.clear(); + return; + } + + if (firstLineIndex < 0) { + const deletedBefore = -firstLineIndex; + this.startLineNumber -= deletedBefore; + + this.tokens.acceptDeleteRange(0, 0, lastLineIndex, range.endColumn - 1); + } else { + this.tokens.acceptDeleteRange(firstLineIndex, range.startColumn - 1, lastLineIndex, range.endColumn - 1); + } + } + + private _acceptInsertText(position: Position, eolCount: number, firstLineLength: number, lastLineLength: number, firstCharCode: number): void { + + if (eolCount === 0 && firstLineLength === 0) { + // Nothing to insert + return; + } + + const lineIndex = position.lineNumber - this.startLineNumber; + + if (lineIndex < 0) { + // this insertion occurs before this block, so we only need to adjust line numbers + this.startLineNumber += eolCount; + return; + } + + const tokenMaxDeltaLine = this.tokens.getMaxDeltaLine(); + + if (lineIndex >= tokenMaxDeltaLine + 1) { + // this insertion occurs after this block, so there is nothing to do + return; + } + + this.tokens.acceptInsertText(lineIndex, position.column - 1, eolCount, firstLineLength, lastLineLength, firstCharCode); + } +} + export class MultilineTokens { public startLineNumber: number; @@ -193,6 +643,7 @@ export class MultilineTokens { // this deletion completely encompasses this block this.startLineNumber = 0; this.tokens = []; + return; } if (firstLineIndex === lastLineIndex) { @@ -289,6 +740,120 @@ function toUint32Array(arr: Uint32Array | ArrayBuffer): Uint32Array { } } +export class TokensStore2 { + + private _pieces: MultilineTokens2[]; + + constructor() { + this._pieces = []; + } + + public flush(): void { + this._pieces = []; + } + + public set(pieces: MultilineTokens2[] | null) { + this._pieces = pieces || []; + } + + public addSemanticTokens(lineNumber: number, aTokens: LineTokens): LineTokens { + const pieces = this._pieces; + + if (pieces.length === 0) { + return aTokens; + } + + const pieceIndex = TokensStore2._findFirstPieceWithLine(pieces, lineNumber); + const bTokens = this._pieces[pieceIndex].getLineTokens(lineNumber); + + if (!bTokens) { + return aTokens; + } + + const aLen = aTokens.getCount(); + const bLen = bTokens.getCount(); + + let aIndex = 0; + let result: number[] = [], resultLen = 0; + for (let bIndex = 0; bIndex < bLen; bIndex++) { + const bStartCharacter = bTokens.getStartCharacter(bIndex); + const bEndCharacter = bTokens.getEndCharacter(bIndex); + const bMetadata = bTokens.getMetadata(bIndex); + + // push any token from `a` that is before `b` + while (aIndex < aLen && aTokens.getEndOffset(aIndex) <= bStartCharacter) { + result[resultLen++] = aTokens.getEndOffset(aIndex); + result[resultLen++] = aTokens.getMetadata(aIndex); + aIndex++; + } + + // push the token from `a` if it intersects the token from `b` + if (aIndex < aLen && aTokens.getStartOffset(aIndex) < bStartCharacter) { + result[resultLen++] = bStartCharacter; + result[resultLen++] = aTokens.getMetadata(aIndex); + } + + // skip any tokens from `a` that are contained inside `b` + while (aIndex < aLen && aTokens.getEndOffset(aIndex) <= bEndCharacter) { + aIndex++; + } + + const aMetadata = aTokens.getMetadata(aIndex - 1 > 0 ? aIndex - 1 : aIndex); + const languageId = TokenMetadata.getLanguageId(aMetadata); + const tokenType = TokenMetadata.getTokenType(aMetadata); + + // push the token from `b` + result[resultLen++] = bEndCharacter; + result[resultLen++] = ( + (bMetadata & MetadataConsts.LANG_TTYPE_CMPL) + | ((languageId << MetadataConsts.LANGUAGEID_OFFSET) >>> 0) + | ((tokenType << MetadataConsts.TOKEN_TYPE_OFFSET) >>> 0) + ); + } + + // push the remaining tokens from `a` + while (aIndex < aLen) { + result[resultLen++] = aTokens.getEndOffset(aIndex); + result[resultLen++] = aTokens.getMetadata(aIndex); + aIndex++; + } + + return new LineTokens(new Uint32Array(result), aTokens.getLineContent()); + } + + private static _findFirstPieceWithLine(pieces: MultilineTokens2[], lineNumber: number): number { + let low = 0; + let high = pieces.length - 1; + + while (low < high) { + let mid = low + Math.floor((high - low) / 2); + + if (pieces[mid].endLineNumber < lineNumber) { + low = mid + 1; + } else if (pieces[mid].startLineNumber > lineNumber) { + high = mid - 1; + } else { + while (mid > low && pieces[mid - 1].startLineNumber <= lineNumber && lineNumber <= pieces[mid - 1].endLineNumber) { + mid--; + } + return mid; + } + } + + return low; + } + + //#region Editing + + public acceptEdit(range: IRange, eolCount: number, firstLineLength: number, lastLineLength: number, firstCharCode: number): void { + for (const piece of this._pieces) { + piece.acceptEdit(range, eolCount, firstLineLength, lastLineLength, firstCharCode); + } + } + + //#endregion +} + export class TokensStore { private _lineTokens: (Uint32Array | ArrayBuffer | null)[]; private _len: number; diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index eebbf316576a..c05b63fc23cf 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -19,7 +19,6 @@ import { LanguageFeatureRegistry } from 'vs/editor/common/modes/languageFeatureR import { TokenizationRegistryImpl } from 'vs/editor/common/modes/tokenizationRegistry'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { IMarkerData } from 'vs/platform/markers/common/markers'; -import { keys } from 'vs/base/common/map'; /** * Open ended enum at runtime @@ -126,6 +125,8 @@ export const enum MetadataConsts { FOREGROUND_MASK = 0b00000000011111111100000000000000, BACKGROUND_MASK = 0b11111111100000000000000000000000, + LANG_TTYPE_CMPL = 0b11111111111111111111100000000000, + LANGUAGEID_OFFSET = 0, TOKEN_TYPE_OFFSET = 8, FONT_STYLE_OFFSET = 11, @@ -453,7 +454,7 @@ export interface CompletionItem { * *Note:* The range must be a [single line](#Range.isSingleLine) and it must * [contain](#Range.contains) the position at which completion has been [requested](#CompletionItemProvider.provideCompletionItems). */ - range: IRange; + range: IRange | { insert: IRange, replace: IRange }; /** * An optional set of characters that when pressed while this completion is active will accept it first and * then type that character. *Note* that all commit characters should have `length=1` and that superfluous @@ -547,6 +548,7 @@ export interface CodeAction { diagnostics?: IMarkerData[]; kind?: string; isPreferred?: boolean; + disabled?: string; } /** @@ -938,12 +940,6 @@ export namespace SymbolKinds { export function fromString(value: string): SymbolKind | undefined { return byName.get(value); } - /** - * @internal - */ - export function names(): readonly string[] { - return keys(byName); - } /** * @internal */ @@ -1451,14 +1447,6 @@ export interface IWebviewPanelOptions { readonly retainContextWhenHidden?: boolean; } -/** - * @internal - */ -export const enum WebviewContentState { - Readonly = 1, - Unchanged = 2, - Dirty = 3, -} export interface CodeLens { range: IRange; @@ -1477,6 +1465,33 @@ export interface CodeLensProvider { resolveCodeLens?(model: model.ITextModel, codeLens: CodeLens, token: CancellationToken): ProviderResult; } +export interface SemanticTokensLegend { + readonly tokenTypes: string[]; + readonly tokenModifiers: string[]; +} + +export interface SemanticTokens { + readonly resultId?: string; + readonly data: Uint32Array; +} + +export interface SemanticTokensEdit { + readonly start: number; + readonly deleteCount: number; + readonly data?: Uint32Array; +} + +export interface SemanticTokensEdits { + readonly resultId?: string; + readonly edits: SemanticTokensEdit[]; +} + +export interface SemanticTokensProvider { + getLegend(): SemanticTokensLegend; + provideSemanticTokens(model: model.ITextModel, lastResultId: string | null, ranges: Range[] | null, token: CancellationToken): ProviderResult; + releaseSemanticTokens(resultId: string | undefined): void; +} + // --- feature registries ------ /** @@ -1579,6 +1594,11 @@ export const SelectionRangeRegistry = new LanguageFeatureRegistry(); +/** + * @internal + */ +export const SemanticTokensProviderRegistry = new LanguageFeatureRegistry(); + /** * @internal */ diff --git a/src/vs/editor/common/modes/languageConfiguration.ts b/src/vs/editor/common/modes/languageConfiguration.ts index ce8479a25404..c2aac2feee92 100644 --- a/src/vs/editor/common/modes/languageConfiguration.ts +++ b/src/vs/editor/common/modes/languageConfiguration.ts @@ -228,6 +228,28 @@ export interface EnterAction { removeText?: number; } +/** + * @internal + */ +export interface CompleteEnterAction { + /** + * Describe what to do with the indentation. + */ + indentAction: IndentAction; + /** + * Describes text to be appended after the new line and after the indentation. + */ + appendText: string; + /** + * Describes the number of characters to remove from the new line's indentation. + */ + removeText: number; + /** + * The line's indentation minus removeText + */ + indentation: string; +} + /** * @internal */ diff --git a/src/vs/editor/common/modes/languageConfigurationRegistry.ts b/src/vs/editor/common/modes/languageConfigurationRegistry.ts index 513189706eae..477fb1f41ecc 100644 --- a/src/vs/editor/common/modes/languageConfigurationRegistry.ts +++ b/src/vs/editor/common/modes/languageConfigurationRegistry.ts @@ -3,7 +3,6 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { onUnexpectedError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import * as strings from 'vs/base/common/strings'; @@ -12,13 +11,14 @@ import { Range } from 'vs/editor/common/core/range'; import { ITextModel } from 'vs/editor/common/model'; import { DEFAULT_WORD_REGEXP, ensureValidWordDefinition } from 'vs/editor/common/model/wordHelper'; import { LanguageId, LanguageIdentifier } from 'vs/editor/common/modes'; -import { EnterAction, FoldingRules, IAutoClosingPair, IndentAction, IndentationRule, LanguageConfiguration, StandardAutoClosingPairConditional } from 'vs/editor/common/modes/languageConfiguration'; -import { createScopedLineTokens } from 'vs/editor/common/modes/supports'; +import { EnterAction, FoldingRules, IAutoClosingPair, IndentAction, IndentationRule, LanguageConfiguration, StandardAutoClosingPairConditional, CompleteEnterAction } from 'vs/editor/common/modes/languageConfiguration'; +import { createScopedLineTokens, ScopedLineTokens } from 'vs/editor/common/modes/supports'; import { CharacterPairSupport } from 'vs/editor/common/modes/supports/characterPair'; import { BracketElectricCharacterSupport, IElectricAction } from 'vs/editor/common/modes/supports/electricCharacter'; import { IndentConsts, IndentRulesSupport } from 'vs/editor/common/modes/supports/indentRules'; -import { IOnEnterSupportOptions, OnEnterSupport } from 'vs/editor/common/modes/supports/onEnter'; +import { OnEnterSupport } from 'vs/editor/common/modes/supports/onEnter'; import { RichEditBrackets } from 'vs/editor/common/modes/supports/richEditBrackets'; +import { EditorAutoIndentStrategy } from 'vs/editor/common/config/editorOptions'; /** * Interface used to support insertion of mode specific comments. @@ -48,11 +48,11 @@ export class RichEditSupport { private readonly _languageIdentifier: LanguageIdentifier; private _brackets: RichEditBrackets | null; private _electricCharacter: BracketElectricCharacterSupport | null; + private readonly _onEnterSupport: OnEnterSupport | null; public readonly comments: ICommentsConfiguration | null; public readonly characterPair: CharacterPairSupport; public readonly wordDefinition: RegExp; - public readonly onEnter: OnEnterSupport | null; public readonly indentRulesSupport: IndentRulesSupport | null; public readonly indentationRules: IndentationRule | undefined; public readonly foldingRules: FoldingRules; @@ -70,8 +70,7 @@ export class RichEditSupport { this._conf = RichEditSupport._mergeConf(prev, rawConf); - this.onEnter = RichEditSupport._handleOnEnter(this._conf); - + this._onEnterSupport = (this._conf.brackets || this._conf.indentationRules || this._conf.onEnterRules ? new OnEnterSupport(this._conf) : null); this.comments = RichEditSupport._handleComments(this._conf); this.characterPair = new CharacterPairSupport(this._conf); @@ -102,6 +101,13 @@ export class RichEditSupport { return this._electricCharacter; } + public onEnter(autoIndent: EditorAutoIndentStrategy, oneLineAboveText: string, beforeEnterText: string, afterEnterText: string): EnterAction | null { + if (!this._onEnterSupport) { + return null; + } + return this._onEnterSupport.onEnter(autoIndent, oneLineAboveText, beforeEnterText, afterEnterText); + } + private static _mergeConf(prev: LanguageConfiguration | null, current: LanguageConfiguration): LanguageConfiguration { return { comments: (prev ? current.comments || prev.comments : current.comments), @@ -117,29 +123,6 @@ export class RichEditSupport { }; } - private static _handleOnEnter(conf: LanguageConfiguration): OnEnterSupport | null { - // on enter - let onEnter: IOnEnterSupportOptions = {}; - let empty = true; - - if (conf.brackets) { - empty = false; - onEnter.brackets = conf.brackets; - } - if (conf.indentationRules) { - empty = false; - } - if (conf.onEnterRules) { - empty = false; - onEnter.regExpRules = conf.onEnterRules; - } - - if (!empty) { - return new OnEnterSupport(onEnter); - } - return null; - } - private static _handleComments(conf: LanguageConfiguration): ICommentsConfiguration | null { let commentRule = conf.comments; if (!commentRule) { @@ -351,8 +334,12 @@ export class LanguageConfigurationRegistryImpl { * * This function only return the inherited indent based on above lines, it doesn't check whether current line should decrease or not. */ - public getInheritIndentForLine(model: IVirtualModel, lineNumber: number, honorIntentialIndent: boolean = true): { indentation: string; action: IndentAction | null; line?: number; } | null { - let indentRulesSupport = this.getIndentRulesSupport(model.getLanguageIdentifier().id); + public getInheritIndentForLine(autoIndent: EditorAutoIndentStrategy, model: IVirtualModel, lineNumber: number, honorIntentialIndent: boolean = true): { indentation: string; action: IndentAction | null; line?: number; } | null { + if (autoIndent < EditorAutoIndentStrategy.Full) { + return null; + } + + const indentRulesSupport = this.getIndentRulesSupport(model.getLanguageIdentifier().id); if (!indentRulesSupport) { return null; } @@ -364,7 +351,7 @@ export class LanguageConfigurationRegistryImpl { }; } - let precedingUnIgnoredLine = this.getPrecedingValidLine(model, lineNumber, indentRulesSupport); + const precedingUnIgnoredLine = this.getPrecedingValidLine(model, lineNumber, indentRulesSupport); if (precedingUnIgnoredLine < 0) { return null; } else if (precedingUnIgnoredLine < 1) { @@ -374,8 +361,7 @@ export class LanguageConfigurationRegistryImpl { }; } - let precedingUnIgnoredLineContent = model.getLineContent(precedingUnIgnoredLine); - + const precedingUnIgnoredLineContent = model.getLineContent(precedingUnIgnoredLine); if (indentRulesSupport.shouldIncrease(precedingUnIgnoredLineContent) || indentRulesSupport.shouldIndentNextLine(precedingUnIgnoredLineContent)) { return { indentation: strings.getLeadingWhitespace(precedingUnIgnoredLineContent), @@ -402,9 +388,9 @@ export class LanguageConfigurationRegistryImpl { }; } - let previousLine = precedingUnIgnoredLine - 1; + const previousLine = precedingUnIgnoredLine - 1; - let previousLineIndentMetadata = indentRulesSupport.getIndentMetadata(model.getLineContent(previousLine)); + const previousLineIndentMetadata = indentRulesSupport.getIndentMetadata(model.getLineContent(previousLine)); if (!(previousLineIndentMetadata & (IndentConsts.INCREASE_MASK | IndentConsts.DECREASE_MASK)) && (previousLineIndentMetadata & IndentConsts.INDENT_NEXTLINE_MASK)) { let stopLine = 0; @@ -432,7 +418,7 @@ export class LanguageConfigurationRegistryImpl { } else { // search from precedingUnIgnoredLine until we find one whose indent is not temporary for (let i = precedingUnIgnoredLine; i > 0; i--) { - let lineContent = model.getLineContent(i); + const lineContent = model.getLineContent(i); if (indentRulesSupport.shouldIncrease(lineContent)) { return { indentation: strings.getLeadingWhitespace(lineContent), @@ -472,27 +458,28 @@ export class LanguageConfigurationRegistryImpl { } } - public getGoodIndentForLine(virtualModel: IVirtualModel, languageId: LanguageId, lineNumber: number, indentConverter: IIndentConverter): string | null { - let indentRulesSupport = this.getIndentRulesSupport(languageId); + public getGoodIndentForLine(autoIndent: EditorAutoIndentStrategy, virtualModel: IVirtualModel, languageId: LanguageId, lineNumber: number, indentConverter: IIndentConverter): string | null { + if (autoIndent < EditorAutoIndentStrategy.Full) { + return null; + } + + const richEditSupport = this._getRichEditSupport(languageId); + if (!richEditSupport) { + return null; + } + + const indentRulesSupport = this.getIndentRulesSupport(languageId); if (!indentRulesSupport) { return null; } - let indent = this.getInheritIndentForLine(virtualModel, lineNumber); - let lineContent = virtualModel.getLineContent(lineNumber); + const indent = this.getInheritIndentForLine(autoIndent, virtualModel, lineNumber); + const lineContent = virtualModel.getLineContent(lineNumber); if (indent) { - let inheritLine = indent.line; + const inheritLine = indent.line; if (inheritLine !== undefined) { - let onEnterSupport = this._getOnEnterSupport(languageId); - let enterResult: EnterAction | null = null; - try { - if (onEnterSupport) { - enterResult = onEnterSupport.onEnter('', virtualModel.getLineContent(inheritLine), ''); - } - } catch (e) { - onUnexpectedError(e); - } + const enterResult = richEditSupport.onEnter(autoIndent, '', virtualModel.getLineContent(inheritLine), ''); if (enterResult) { let indentation = strings.getLeadingWhitespace(virtualModel.getLineContent(inheritLine)); @@ -539,16 +526,17 @@ export class LanguageConfigurationRegistryImpl { return null; } - public getIndentForEnter(model: ITextModel, range: Range, indentConverter: IIndentConverter, autoIndent: boolean): { beforeEnter: string, afterEnter: string } | null { + public getIndentForEnter(autoIndent: EditorAutoIndentStrategy, model: ITextModel, range: Range, indentConverter: IIndentConverter): { beforeEnter: string, afterEnter: string } | null { + if (autoIndent < EditorAutoIndentStrategy.Full) { + return null; + } model.forceTokenization(range.startLineNumber); - let lineTokens = model.getLineTokens(range.startLineNumber); - - let beforeEnterText; - let afterEnterText; - let scopedLineTokens = createScopedLineTokens(lineTokens, range.startColumn - 1); - let scopedLineText = scopedLineTokens.getLineContent(); + const lineTokens = model.getLineTokens(range.startLineNumber); + const scopedLineTokens = createScopedLineTokens(lineTokens, range.startColumn - 1); + const scopedLineText = scopedLineTokens.getLineContent(); let embeddedLanguage = false; + let beforeEnterText: string; if (scopedLineTokens.firstCharOffset > 0 && lineTokens.getLanguageId(0) !== scopedLineTokens.languageId) { // we are in the embeded language content embeddedLanguage = true; // if embeddedLanguage is true, then we don't touch the indentation of current line @@ -557,6 +545,7 @@ export class LanguageConfigurationRegistryImpl { beforeEnterText = lineTokens.getLineContent().substring(0, range.startColumn - 1); } + let afterEnterText: string; if (range.isEmpty()) { afterEnterText = scopedLineText.substr(range.startColumn - 1 - scopedLineTokens.firstCharOffset); } else { @@ -564,31 +553,15 @@ export class LanguageConfigurationRegistryImpl { afterEnterText = endScopedLineTokens.getLineContent().substr(range.endColumn - 1 - scopedLineTokens.firstCharOffset); } - let indentRulesSupport = this.getIndentRulesSupport(scopedLineTokens.languageId); - + const indentRulesSupport = this.getIndentRulesSupport(scopedLineTokens.languageId); if (!indentRulesSupport) { return null; } - let beforeEnterResult = beforeEnterText; - let beforeEnterIndent = strings.getLeadingWhitespace(beforeEnterText); - - if (!autoIndent && !embeddedLanguage) { - let beforeEnterIndentAction = this.getInheritIndentForLine(model, range.startLineNumber); - - if (indentRulesSupport.shouldDecrease(beforeEnterText)) { - if (beforeEnterIndentAction) { - beforeEnterIndent = beforeEnterIndentAction.indentation; - if (beforeEnterIndentAction.action !== IndentAction.Indent) { - beforeEnterIndent = indentConverter.unshiftIndent(beforeEnterIndent); - } - } - } - - beforeEnterResult = beforeEnterIndent + strings.ltrim(strings.ltrim(beforeEnterText, ' '), '\t'); - } + const beforeEnterResult = beforeEnterText; + const beforeEnterIndent = strings.getLeadingWhitespace(beforeEnterText); - let virtualModel: IVirtualModel = { + const virtualModel: IVirtualModel = { getLineTokens: (lineNumber: number) => { return model.getLineTokens(lineNumber); }, @@ -607,10 +580,10 @@ export class LanguageConfigurationRegistryImpl { } }; - let currentLineIndent = strings.getLeadingWhitespace(lineTokens.getLineContent()); - let afterEnterAction = this.getInheritIndentForLine(virtualModel, range.startLineNumber + 1); + const currentLineIndent = strings.getLeadingWhitespace(lineTokens.getLineContent()); + const afterEnterAction = this.getInheritIndentForLine(autoIndent, virtualModel, range.startLineNumber + 1); if (!afterEnterAction) { - let beforeEnter = embeddedLanguage ? currentLineIndent : beforeEnterIndent; + const beforeEnter = embeddedLanguage ? currentLineIndent : beforeEnterIndent; return { beforeEnter: beforeEnter, afterEnter: beforeEnter @@ -637,18 +610,21 @@ export class LanguageConfigurationRegistryImpl { * We should always allow intentional indentation. It means, if users change the indentation of `lineNumber` and the content of * this line doesn't match decreaseIndentPattern, we should not adjust the indentation. */ - public getIndentActionForType(model: ITextModel, range: Range, ch: string, indentConverter: IIndentConverter): string | null { - let scopedLineTokens = this.getScopedLineTokens(model, range.startLineNumber, range.startColumn); - let indentRulesSupport = this.getIndentRulesSupport(scopedLineTokens.languageId); + public getIndentActionForType(autoIndent: EditorAutoIndentStrategy, model: ITextModel, range: Range, ch: string, indentConverter: IIndentConverter): string | null { + if (autoIndent < EditorAutoIndentStrategy.Full) { + return null; + } + const scopedLineTokens = this.getScopedLineTokens(model, range.startLineNumber, range.startColumn); + const indentRulesSupport = this.getIndentRulesSupport(scopedLineTokens.languageId); if (!indentRulesSupport) { return null; } - let scopedLineText = scopedLineTokens.getLineContent(); - let beforeTypeText = scopedLineText.substr(0, range.startColumn - 1 - scopedLineTokens.firstCharOffset); - let afterTypeText; + const scopedLineText = scopedLineTokens.getLineContent(); + const beforeTypeText = scopedLineText.substr(0, range.startColumn - 1 - scopedLineTokens.firstCharOffset); // selection support + let afterTypeText: string; if (range.isEmpty()) { afterTypeText = scopedLineText.substr(range.startColumn - 1 - scopedLineTokens.firstCharOffset); } else { @@ -661,13 +637,12 @@ export class LanguageConfigurationRegistryImpl { if (!indentRulesSupport.shouldDecrease(beforeTypeText + afterTypeText) && indentRulesSupport.shouldDecrease(beforeTypeText + ch + afterTypeText)) { // after typing `ch`, the content matches decreaseIndentPattern, we should adjust the indent to a good manner. // 1. Get inherited indent action - let r = this.getInheritIndentForLine(model, range.startLineNumber, false); + const r = this.getInheritIndentForLine(autoIndent, model, range.startLineNumber, false); if (!r) { return null; } let indentation = r.indentation; - if (r.action !== IndentAction.Indent) { indentation = indentConverter.unshiftIndent(indentation); } @@ -679,15 +654,13 @@ export class LanguageConfigurationRegistryImpl { } public getIndentMetadata(model: ITextModel, lineNumber: number): number | null { - let indentRulesSupport = this.getIndentRulesSupport(model.getLanguageIdentifier().id); + const indentRulesSupport = this.getIndentRulesSupport(model.getLanguageIdentifier().id); if (!indentRulesSupport) { return null; } - if (lineNumber < 1 || lineNumber > model.getLineCount()) { return null; } - return indentRulesSupport.getIndentMetadata(model.getLineContent(lineNumber)); } @@ -695,34 +668,18 @@ export class LanguageConfigurationRegistryImpl { // begin onEnter - private _getOnEnterSupport(languageId: LanguageId): OnEnterSupport | null { - let value = this._getRichEditSupport(languageId); - if (!value) { + public getEnterAction(autoIndent: EditorAutoIndentStrategy, model: ITextModel, range: Range): CompleteEnterAction | null { + const scopedLineTokens = this.getScopedLineTokens(model, range.startLineNumber, range.startColumn); + const richEditSupport = this._getRichEditSupport(scopedLineTokens.languageId); + if (!richEditSupport) { return null; } - return value.onEnter || null; - } - - public getRawEnterActionAtPosition(model: ITextModel, lineNumber: number, column: number): EnterAction | null { - let r = this.getEnterAction(model, new Range(lineNumber, column, lineNumber, column)); - return r ? r.enterAction : null; - } - - public getEnterAction(model: ITextModel, range: Range): { enterAction: EnterAction; indentation: string; } | null { - let indentation = this.getIndentationAtPosition(model, range.startLineNumber, range.startColumn); - - let scopedLineTokens = this.getScopedLineTokens(model, range.startLineNumber, range.startColumn); - let onEnterSupport = this._getOnEnterSupport(scopedLineTokens.languageId); - if (!onEnterSupport) { - return null; - } - - let scopedLineText = scopedLineTokens.getLineContent(); - let beforeEnterText = scopedLineText.substr(0, range.startColumn - 1 - scopedLineTokens.firstCharOffset); - let afterEnterText; + const scopedLineText = scopedLineTokens.getLineContent(); + const beforeEnterText = scopedLineText.substr(0, range.startColumn - 1 - scopedLineTokens.firstCharOffset); // selection support + let afterEnterText: string; if (range.isEmpty()) { afterEnterText = scopedLineText.substr(range.startColumn - 1 - scopedLineTokens.firstCharOffset); } else { @@ -730,73 +687,70 @@ export class LanguageConfigurationRegistryImpl { afterEnterText = endScopedLineTokens.getLineContent().substr(range.endColumn - 1 - scopedLineTokens.firstCharOffset); } - let lineNumber = range.startLineNumber; let oneLineAboveText = ''; - - if (lineNumber > 1 && scopedLineTokens.firstCharOffset === 0) { + if (range.startLineNumber > 1 && scopedLineTokens.firstCharOffset === 0) { // This is not the first line and the entire line belongs to this mode - let oneLineAboveScopedLineTokens = this.getScopedLineTokens(model, lineNumber - 1); + const oneLineAboveScopedLineTokens = this.getScopedLineTokens(model, range.startLineNumber - 1); if (oneLineAboveScopedLineTokens.languageId === scopedLineTokens.languageId) { // The line above ends with text belonging to the same mode oneLineAboveText = oneLineAboveScopedLineTokens.getLineContent(); } } - let enterResult: EnterAction | null = null; - try { - enterResult = onEnterSupport.onEnter(oneLineAboveText, beforeEnterText, afterEnterText); - } catch (e) { - onUnexpectedError(e); - } - + const enterResult = richEditSupport.onEnter(autoIndent, oneLineAboveText, beforeEnterText, afterEnterText); if (!enterResult) { return null; - } else { - // Here we add `\t` to appendText first because enterAction is leveraging appendText and removeText to change indentation. - if (!enterResult.appendText) { - if ( - (enterResult.indentAction === IndentAction.Indent) || - (enterResult.indentAction === IndentAction.IndentOutdent) - ) { - enterResult.appendText = '\t'; - } else { - enterResult.appendText = ''; - } + } + + const indentAction = enterResult.indentAction; + let appendText = enterResult.appendText; + const removeText = enterResult.removeText || 0; + + // Here we add `\t` to appendText first because enterAction is leveraging appendText and removeText to change indentation. + if (!appendText) { + if ( + (indentAction === IndentAction.Indent) || + (indentAction === IndentAction.IndentOutdent) + ) { + appendText = '\t'; + } else { + appendText = ''; } } - if (enterResult.removeText) { - indentation = indentation.substring(0, indentation.length - enterResult.removeText); + let indentation = this.getIndentationAtPosition(model, range.startLineNumber, range.startColumn); + if (removeText) { + indentation = indentation.substring(0, indentation.length - removeText); } return { - enterAction: enterResult, - indentation: indentation, + indentAction: indentAction, + appendText: appendText, + removeText: removeText, + indentation: indentation }; } public getIndentationAtPosition(model: ITextModel, lineNumber: number, column: number): string { - let lineText = model.getLineContent(lineNumber); + const lineText = model.getLineContent(lineNumber); let indentation = strings.getLeadingWhitespace(lineText); if (indentation.length > column - 1) { indentation = indentation.substring(0, column - 1); } - return indentation; } - private getScopedLineTokens(model: ITextModel, lineNumber: number, columnNumber?: number) { + private getScopedLineTokens(model: ITextModel, lineNumber: number, columnNumber?: number): ScopedLineTokens { model.forceTokenization(lineNumber); - let lineTokens = model.getLineTokens(lineNumber); - let column = (typeof columnNumber === 'undefined' ? model.getLineMaxColumn(lineNumber) - 1 : columnNumber - 1); - let scopedLineTokens = createScopedLineTokens(lineTokens, column); - return scopedLineTokens; + const lineTokens = model.getLineTokens(lineNumber); + const column = (typeof columnNumber === 'undefined' ? model.getLineMaxColumn(lineNumber) - 1 : columnNumber - 1); + return createScopedLineTokens(lineTokens, column); } // end onEnter public getBracketsSupport(languageId: LanguageId): RichEditBrackets | null { - let value = this._getRichEditSupport(languageId); + const value = this._getRichEditSupport(languageId); if (!value) { return null; } diff --git a/src/vs/editor/common/modes/linkComputer.ts b/src/vs/editor/common/modes/linkComputer.ts index c3a62b262ba6..6c427272d7e4 100644 --- a/src/vs/editor/common/modes/linkComputer.ts +++ b/src/vs/editor/common/modes/linkComputer.ts @@ -264,6 +264,10 @@ export class LinkComputer { case CharCode.BackTick: chClass = (linkBeginChCode === CharCode.SingleQuote || linkBeginChCode === CharCode.DoubleQuote) ? CharacterClass.None : CharacterClass.ForceTermination; break; + case CharCode.Asterisk: + // `*` terminates a link if the link began with `*` + chClass = (linkBeginChCode === CharCode.Asterisk) ? CharacterClass.ForceTermination : CharacterClass.None; + break; default: chClass = classifier.get(chCode); } diff --git a/src/vs/editor/common/modes/supports/onEnter.ts b/src/vs/editor/common/modes/supports/onEnter.ts index 358b7c855323..d317c77a52ec 100644 --- a/src/vs/editor/common/modes/supports/onEnter.ts +++ b/src/vs/editor/common/modes/supports/onEnter.ts @@ -6,10 +6,11 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import * as strings from 'vs/base/common/strings'; import { CharacterPair, EnterAction, IndentAction, OnEnterRule } from 'vs/editor/common/modes/languageConfiguration'; +import { EditorAutoIndentStrategy } from 'vs/editor/common/config/editorOptions'; export interface IOnEnterSupportOptions { brackets?: CharacterPair[]; - regExpRules?: OnEnterRule[]; + onEnterRules?: OnEnterRule[]; } interface IProcessedBracketPair { @@ -24,7 +25,7 @@ export class OnEnterSupport { private readonly _brackets: IProcessedBracketPair[]; private readonly _regExpRules: OnEnterRule[]; - constructor(opts?: IOnEnterSupportOptions) { + constructor(opts: IOnEnterSupportOptions) { opts = opts || {}; opts.brackets = opts.brackets || [ ['(', ')'], @@ -45,49 +46,54 @@ export class OnEnterSupport { }); } }); - this._regExpRules = opts.regExpRules || []; + this._regExpRules = opts.onEnterRules || []; } - public onEnter(oneLineAboveText: string, beforeEnterText: string, afterEnterText: string): EnterAction | null { + public onEnter(autoIndent: EditorAutoIndentStrategy, oneLineAboveText: string, beforeEnterText: string, afterEnterText: string): EnterAction | null { // (1): `regExpRules` - for (let i = 0, len = this._regExpRules.length; i < len; i++) { - let rule = this._regExpRules[i]; - const regResult = [{ - reg: rule.beforeText, - text: beforeEnterText - }, { - reg: rule.afterText, - text: afterEnterText - }, { - reg: rule.oneLineAboveText, - text: oneLineAboveText - }].every((obj): boolean => { - return obj.reg ? obj.reg.test(obj.text) : true; - }); - - if (regResult) { - return rule.action; + if (autoIndent >= EditorAutoIndentStrategy.Advanced) { + for (let i = 0, len = this._regExpRules.length; i < len; i++) { + let rule = this._regExpRules[i]; + const regResult = [{ + reg: rule.beforeText, + text: beforeEnterText + }, { + reg: rule.afterText, + text: afterEnterText + }, { + reg: rule.oneLineAboveText, + text: oneLineAboveText + }].every((obj): boolean => { + return obj.reg ? obj.reg.test(obj.text) : true; + }); + + if (regResult) { + return rule.action; + } } } - // (2): Special indent-outdent - if (beforeEnterText.length > 0 && afterEnterText.length > 0) { - for (let i = 0, len = this._brackets.length; i < len; i++) { - let bracket = this._brackets[i]; - if (bracket.openRegExp.test(beforeEnterText) && bracket.closeRegExp.test(afterEnterText)) { - return { indentAction: IndentAction.IndentOutdent }; + if (autoIndent >= EditorAutoIndentStrategy.Brackets) { + if (beforeEnterText.length > 0 && afterEnterText.length > 0) { + for (let i = 0, len = this._brackets.length; i < len; i++) { + let bracket = this._brackets[i]; + if (bracket.openRegExp.test(beforeEnterText) && bracket.closeRegExp.test(afterEnterText)) { + return { indentAction: IndentAction.IndentOutdent }; + } } } } // (4): Open bracket based logic - if (beforeEnterText.length > 0) { - for (let i = 0, len = this._brackets.length; i < len; i++) { - let bracket = this._brackets[i]; - if (bracket.openRegExp.test(beforeEnterText)) { - return { indentAction: IndentAction.Indent }; + if (autoIndent >= EditorAutoIndentStrategy.Brackets) { + if (beforeEnterText.length > 0) { + for (let i = 0, len = this._brackets.length; i < len; i++) { + let bracket = this._brackets[i]; + if (bracket.openRegExp.test(beforeEnterText)) { + return { indentAction: IndentAction.Indent }; + } } } } diff --git a/src/vs/editor/common/modes/textToHtmlTokenizer.ts b/src/vs/editor/common/modes/textToHtmlTokenizer.ts index f9988db0153a..d45497ec20af 100644 --- a/src/vs/editor/common/modes/textToHtmlTokenizer.ts +++ b/src/vs/editor/common/modes/textToHtmlTokenizer.ts @@ -46,7 +46,7 @@ export function tokenizeLineToHTML(text: string, viewLineTokens: IViewLineTokens let insertSpacesCount = tabSize - (charIndex + tabsCharDelta) % tabSize; tabsCharDelta += insertSpacesCount - 1; while (insertSpacesCount > 0) { - partContent += useNbsp ? ' ' : ' '; + partContent += useNbsp ? ' ' : ' '; insertSpacesCount--; } break; @@ -78,7 +78,7 @@ export function tokenizeLineToHTML(text: string, viewLineTokens: IViewLineTokens break; case CharCode.Space: - partContent += useNbsp ? ' ' : ' '; + partContent += useNbsp ? ' ' : ' '; break; default: diff --git a/src/vs/editor/common/services/editorSimpleWorker.ts b/src/vs/editor/common/services/editorSimpleWorker.ts index 94e101d93c7b..1a4eb9d34972 100644 --- a/src/vs/editor/common/services/editorSimpleWorker.ts +++ b/src/vs/editor/common/services/editorSimpleWorker.ts @@ -17,7 +17,7 @@ import * as editorCommon from 'vs/editor/common/editorCommon'; import { EndOfLineSequence, IWordAtPosition } from 'vs/editor/common/model'; import { IModelChangedEvent, MirrorTextModel as BaseMirrorModel } from 'vs/editor/common/model/mirrorTextModel'; import { ensureValidWordDefinition, getWordAtText } from 'vs/editor/common/model/wordHelper'; -import { CompletionItem, CompletionItemKind, CompletionList, IInplaceReplaceSupportResult, ILink, TextEdit } from 'vs/editor/common/modes'; +import { IInplaceReplaceSupportResult, ILink, TextEdit } from 'vs/editor/common/modes'; import { ILinkComputerTarget, computeLinks } from 'vs/editor/common/modes/linkComputer'; import { BasicInplaceReplace } from 'vs/editor/common/modes/supports/inplaceReplaceSupport'; import { IDiffComputationResult } from 'vs/editor/common/services/editorWorkerService'; @@ -529,44 +529,38 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable { private static readonly _suggestionsLimit = 10000; - public async textualSuggest(modelUrl: string, position: IPosition, wordDef: string, wordDefFlags: string): Promise { + public async textualSuggest(modelUrl: string, position: IPosition, wordDef: string, wordDefFlags: string): Promise { const model = this._getModel(modelUrl); if (!model) { return null; } - const seen: Record = Object.create(null); - const suggestions: CompletionItem[] = []; + + const words: string[] = []; + const seen = new Set(); const wordDefRegExp = new RegExp(wordDef, wordDefFlags); - const wordUntil = model.getWordUntilPosition(position, wordDefRegExp); const wordAt = model.getWordAtPosition(position, wordDefRegExp); if (wordAt) { - seen[model.getValueInRange(wordAt)] = true; + seen.add(model.getValueInRange(wordAt)); } for ( let iter = model.createWordIterator(wordDefRegExp), e = iter.next(); - !e.done && suggestions.length <= EditorSimpleWorker._suggestionsLimit; + !e.done && seen.size <= EditorSimpleWorker._suggestionsLimit; e = iter.next() ) { const word = e.value; - if (seen[word]) { + if (seen.has(word)) { continue; } - seen[word] = true; + seen.add(word); if (!isNaN(Number(word))) { continue; } - - suggestions.push({ - kind: CompletionItemKind.Text, - label: word, - insertText: word, - range: { startLineNumber: position.lineNumber, startColumn: wordUntil.startColumn, endLineNumber: position.lineNumber, endColumn: wordUntil.endColumn } - }); + words.push(word); } - return { suggestions }; + return words; } diff --git a/src/vs/editor/common/services/editorWorkerServiceImpl.ts b/src/vs/editor/common/services/editorWorkerServiceImpl.ts index bd362844abfa..3fb21fdc2f91 100644 --- a/src/vs/editor/common/services/editorWorkerServiceImpl.ts +++ b/src/vs/editor/common/services/editorWorkerServiceImpl.ts @@ -9,7 +9,7 @@ import { URI } from 'vs/base/common/uri'; import { SimpleWorkerClient, logOnceWebWorkerWarning, IWorkerClient } from 'vs/base/common/worker/simpleWorker'; import { DefaultWorkerFactory } from 'vs/base/worker/defaultWorkerFactory'; import { IPosition, Position } from 'vs/editor/common/core/position'; -import { IRange } from 'vs/editor/common/core/range'; +import { IRange, Range } from 'vs/editor/common/core/range'; import * as editorCommon from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; import * as modes from 'vs/editor/common/modes'; @@ -144,7 +144,7 @@ class WordBasedCompletionItemProvider implements modes.CompletionItemProvider { this._modelService = modelService; } - provideCompletionItems(model: ITextModel, position: Position): Promise | undefined { + async provideCompletionItems(model: ITextModel, position: Position): Promise { const { wordBasedSuggestions } = this._configurationService.getValue<{ wordBasedSuggestions?: boolean }>(model.uri, position, 'editor'); if (!wordBasedSuggestions) { return undefined; @@ -152,7 +152,27 @@ class WordBasedCompletionItemProvider implements modes.CompletionItemProvider { if (!canSyncModel(this._modelService, model.uri)) { return undefined; // File too large } - return this._workerManager.withWorker().then(client => client.textualSuggest(model.uri, position)); + + const word = model.getWordAtPosition(position); + const replace = !word ? Range.fromPositions(position) : new Range(position.lineNumber, word.startColumn, position.lineNumber, word.endColumn); + const insert = replace.setEndPosition(position.lineNumber, position.column); + + const client = await this._workerManager.withWorker(); + const words = await client.textualSuggest(model.uri, position); + if (!words) { + return undefined; + } + + return { + suggestions: words.map((word): modes.CompletionItem => { + return { + kind: modes.CompletionItemKind.Text, + label: word, + insertText: word, + range: { insert, replace } + }; + }) + }; } } @@ -433,7 +453,7 @@ export class EditorWorkerClient extends Disposable { }); } - public textualSuggest(resource: URI, position: IPosition): Promise { + public textualSuggest(resource: URI, position: IPosition): Promise { return this._withSyncedResources([resource]).then(proxy => { let model = this._modelService.getModel(resource); if (!model) { diff --git a/src/vs/editor/common/services/markerDecorationsServiceImpl.ts b/src/vs/editor/common/services/markerDecorationsServiceImpl.ts index a6517e1f55b1..625ebc08ca34 100644 --- a/src/vs/editor/common/services/markerDecorationsServiceImpl.ts +++ b/src/vs/editor/common/services/markerDecorationsServiceImpl.ts @@ -6,7 +6,7 @@ import { IMarkerService, IMarker, MarkerSeverity, MarkerTag } from 'vs/platform/markers/common/markers'; import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; -import { IModelDeltaDecoration, ITextModel, IModelDecorationOptions, TrackedRangeStickiness, OverviewRulerLane, IModelDecoration } from 'vs/editor/common/model'; +import { IModelDeltaDecoration, ITextModel, IModelDecorationOptions, TrackedRangeStickiness, OverviewRulerLane, IModelDecoration, MinimapPosition, IModelDecorationMinimapOptions } from 'vs/editor/common/model'; import { ClassName } from 'vs/editor/common/model/intervalTree'; import { themeColorFromId, ThemeColor } from 'vs/platform/theme/common/themeService'; import { overviewRulerWarning, overviewRulerInfo, overviewRulerError } from 'vs/editor/common/view/editorColorRegistry'; @@ -17,6 +17,7 @@ import { IMarkerDecorationsService } from 'vs/editor/common/services/markersDeco import { Schemas } from 'vs/base/common/network'; import { Emitter, Event } from 'vs/base/common/event'; import { withUndefinedAsNull } from 'vs/base/common/types'; +import { minimapWarning, minimapError } from 'vs/platform/theme/common/colorRegistry'; function MODEL_ID(resource: URI): string { return resource.toString(); @@ -189,6 +190,7 @@ export class MarkerDecorationsService extends Disposable implements IMarkerDecor let color: ThemeColor | undefined = undefined; let zIndex: number; let inlineClassName: string | undefined = undefined; + let minimap: IModelDecorationMinimapOptions | undefined; switch (marker.severity) { case MarkerSeverity.Hint: @@ -203,6 +205,10 @@ export class MarkerDecorationsService extends Disposable implements IMarkerDecor className = ClassName.EditorWarningDecoration; color = themeColorFromId(overviewRulerWarning); zIndex = 20; + minimap = { + color: themeColorFromId(minimapWarning), + position: MinimapPosition.Inline + }; break; case MarkerSeverity.Info: className = ClassName.EditorInfoDecoration; @@ -214,6 +220,10 @@ export class MarkerDecorationsService extends Disposable implements IMarkerDecor className = ClassName.EditorErrorDecoration; color = themeColorFromId(overviewRulerError); zIndex = 30; + minimap = { + color: themeColorFromId(minimapError), + position: MinimapPosition.Inline + }; break; } @@ -234,6 +244,7 @@ export class MarkerDecorationsService extends Disposable implements IMarkerDecor color, position: OverviewRulerLane.Right }, + minimap, zIndex, inlineClassName, }; diff --git a/src/vs/editor/common/services/modelServiceImpl.ts b/src/vs/editor/common/services/modelServiceImpl.ts index 772ed30880b4..ef023724ae99 100644 --- a/src/vs/editor/common/services/modelServiceImpl.ts +++ b/src/vs/editor/common/services/modelServiceImpl.ts @@ -6,19 +6,24 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import * as platform from 'vs/base/common/platform'; +import * as errors from 'vs/base/common/errors'; import { URI } from 'vs/base/common/uri'; import { EDITOR_MODEL_DEFAULTS } from 'vs/editor/common/config/editorOptions'; import { EditOperation } from 'vs/editor/common/core/editOperation'; import { Range } from 'vs/editor/common/core/range'; import { DefaultEndOfLine, EndOfLinePreference, EndOfLineSequence, IIdentifiedSingleEditOperation, ITextBuffer, ITextBufferFactory, ITextModel, ITextModelCreationOptions } from 'vs/editor/common/model'; import { TextModel, createTextBuffer } from 'vs/editor/common/model/textModel'; -import { IModelLanguageChangedEvent } from 'vs/editor/common/model/textModelEvents'; -import { LanguageIdentifier } from 'vs/editor/common/modes'; +import { IModelLanguageChangedEvent, IModelContentChangedEvent } from 'vs/editor/common/model/textModelEvents'; +import { LanguageIdentifier, SemanticTokensProviderRegistry, SemanticTokensProvider, SemanticTokensLegend, SemanticTokens, SemanticTokensEdits } from 'vs/editor/common/modes'; import { PLAINTEXT_LANGUAGE_IDENTIFIER } from 'vs/editor/common/modes/modesRegistry'; import { ILanguageSelection } from 'vs/editor/common/services/modeService'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { RunOnceScheduler } from 'vs/base/common/async'; +import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { SparseEncodedTokens, MultilineTokens2 } from 'vs/editor/common/model/tokensStore'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; function MODEL_ID(resource: URI): string { return resource.toString(); @@ -114,7 +119,8 @@ export class ModelServiceImpl extends Disposable implements IModelService { constructor( @IConfigurationService configurationService: IConfigurationService, - @ITextResourcePropertiesService resourcePropertiesService: ITextResourcePropertiesService + @ITextResourcePropertiesService resourcePropertiesService: ITextResourcePropertiesService, + @IThemeService themeService: IThemeService ) { super(); this._configurationService = configurationService; @@ -124,6 +130,8 @@ export class ModelServiceImpl extends Disposable implements IModelService { this._configurationServiceSubscription = this._configurationService.onDidChangeConfiguration(e => this._updateModelOptions()); this._updateModelOptions(); + + this._register(new SemanticColoringFeature(this, themeService)); } private static _readModelOptions(config: IRawConfig, isForSimpleWidget: boolean): ITextModelCreationOptions { @@ -430,3 +438,467 @@ export class ModelServiceImpl extends Disposable implements IModelService { export interface ILineSequence { getLineContent(lineNumber: number): string; } + +class SemanticColoringFeature extends Disposable { + private _watchers: Record; + private _semanticStyling: SemanticStyling; + + constructor(modelService: IModelService, themeService: IThemeService) { + super(); + this._watchers = Object.create(null); + this._semanticStyling = this._register(new SemanticStyling(themeService)); + this._register(modelService.onModelAdded((model) => { + this._watchers[model.uri.toString()] = new ModelSemanticColoring(model, themeService, this._semanticStyling); + })); + this._register(modelService.onModelRemoved((model) => { + this._watchers[model.uri.toString()].dispose(); + delete this._watchers[model.uri.toString()]; + })); + } +} + +class SemanticStyling extends Disposable { + + private _caches: WeakMap; + + constructor( + private readonly _themeService: IThemeService + ) { + super(); + this._caches = new WeakMap(); + if (this._themeService) { + // workaround for tests which use undefined... :/ + this._register(this._themeService.onThemeChange(() => { + this._caches = new WeakMap(); + })); + } + } + + public get(provider: SemanticTokensProvider): SemanticColoringProviderStyling { + if (!this._caches.has(provider)) { + this._caches.set(provider, new SemanticColoringProviderStyling(provider.getLegend(), this._themeService)); + } + return this._caches.get(provider)!; + } +} + +const enum Constants { + NO_STYLING = 0b01111111111111111111111111111111 +} + +class HashTableEntry { + public readonly tokenTypeIndex: number; + public readonly tokenModifierSet: number; + public readonly metadata: number; + public next: HashTableEntry | null; + + constructor(tokenTypeIndex: number, tokenModifierSet: number, metadata: number) { + this.tokenTypeIndex = tokenTypeIndex; + this.tokenModifierSet = tokenModifierSet; + this.metadata = metadata; + this.next = null; + } +} + +class HashTable { + + private static _SIZES = [3, 7, 13, 31, 61, 127, 251, 509, 1021, 2039, 4093, 8191, 16381, 32749, 65521, 131071, 262139, 524287, 1048573, 2097143]; + + private _elementsCount: number; + private _currentLengthIndex: number; + private _currentLength: number; + private _growCount: number; + private _elements: (HashTableEntry | null)[]; + + constructor() { + this._elementsCount = 0; + this._currentLengthIndex = 0; + this._currentLength = HashTable._SIZES[this._currentLengthIndex]; + this._growCount = Math.round(this._currentLengthIndex + 1 < HashTable._SIZES.length ? 2 / 3 * this._currentLength : 0); + this._elements = []; + HashTable._nullOutEntries(this._elements, this._currentLength); + } + + private static _nullOutEntries(entries: (HashTableEntry | null)[], length: number): void { + for (let i = 0; i < length; i++) { + entries[i] = null; + } + } + + private _hashFunc(tokenTypeIndex: number, tokenModifierSet: number): number { + return ((((tokenTypeIndex << 5) - tokenTypeIndex) + tokenModifierSet) | 0) % this._currentLength; // tokenTypeIndex * 31 + tokenModifierSet, keep as int32 + } + + public get(tokenTypeIndex: number, tokenModifierSet: number): HashTableEntry | null { + const hash = this._hashFunc(tokenTypeIndex, tokenModifierSet); + + let p = this._elements[hash]; + while (p) { + if (p.tokenTypeIndex === tokenTypeIndex && p.tokenModifierSet === tokenModifierSet) { + return p; + } + p = p.next; + } + + return null; + } + + public add(tokenTypeIndex: number, tokenModifierSet: number, metadata: number): void { + this._elementsCount++; + if (this._growCount !== 0 && this._elementsCount >= this._growCount) { + // expand! + const oldElements = this._elements; + + this._currentLengthIndex++; + this._currentLength = HashTable._SIZES[this._currentLengthIndex]; + this._growCount = Math.round(this._currentLengthIndex + 1 < HashTable._SIZES.length ? 2 / 3 * this._currentLength : 0); + this._elements = []; + HashTable._nullOutEntries(this._elements, this._currentLength); + + for (const first of oldElements) { + let p = first; + while (p) { + const oldNext = p.next; + p.next = null; + this._add(p); + p = oldNext; + } + } + } + this._add(new HashTableEntry(tokenTypeIndex, tokenModifierSet, metadata)); + } + + private _add(element: HashTableEntry): void { + const hash = this._hashFunc(element.tokenTypeIndex, element.tokenModifierSet); + element.next = this._elements[hash]; + this._elements[hash] = element; + } +} + +class SemanticColoringProviderStyling { + + private readonly _hashTable: HashTable; + + constructor( + private readonly _legend: SemanticTokensLegend, + private readonly _themeService: IThemeService + ) { + this._hashTable = new HashTable(); + } + + public getMetadata(tokenTypeIndex: number, tokenModifierSet: number): number { + const entry = this._hashTable.get(tokenTypeIndex, tokenModifierSet); + if (entry) { + return entry.metadata; + } + + const tokenType = this._legend.tokenTypes[tokenTypeIndex]; + const tokenModifiers: string[] = []; + for (let modifierIndex = 0; tokenModifierSet !== 0 && modifierIndex < this._legend.tokenModifiers.length; modifierIndex++) { + if (tokenModifierSet & 1) { + tokenModifiers.push(this._legend.tokenModifiers[modifierIndex]); + } + tokenModifierSet = tokenModifierSet >> 1; + } + + let metadata = this._themeService.getTheme().getTokenStyleMetadata(tokenType, tokenModifiers); + if (typeof metadata === 'undefined') { + metadata = Constants.NO_STYLING; + } + + this._hashTable.add(tokenTypeIndex, tokenModifierSet, metadata); + return metadata; + } +} + +const enum SemanticColoringConstants { + /** + * Let's aim at having 8KB buffers if possible... + * So that would be 8192 / (5 * 4) = 409.6 tokens per area + */ + DesiredTokensPerArea = 400, + + /** + * Try to keep the total number of areas under 1024 if possible, + * simply compensate by having more tokens per area... + */ + DesiredMaxAreas = 1024, +} + +class SemanticTokensResponse { + constructor( + private readonly _provider: SemanticTokensProvider, + public readonly resultId: string | undefined, + public readonly data: Uint32Array + ) { } + + public dispose(): void { + this._provider.releaseSemanticTokens(this.resultId); + } +} + +class ModelSemanticColoring extends Disposable { + + private _isDisposed: boolean; + private readonly _model: ITextModel; + private readonly _semanticStyling: SemanticStyling; + private readonly _fetchSemanticTokens: RunOnceScheduler; + private _currentResponse: SemanticTokensResponse | null; + private _currentRequestCancellationTokenSource: CancellationTokenSource | null; + + constructor(model: ITextModel, themeService: IThemeService, stylingProvider: SemanticStyling) { + super(); + + this._isDisposed = false; + this._model = model; + this._semanticStyling = stylingProvider; + this._fetchSemanticTokens = this._register(new RunOnceScheduler(() => this._fetchSemanticTokensNow(), 500)); + this._currentResponse = null; + this._currentRequestCancellationTokenSource = null; + + this._register(this._model.onDidChangeContent(e => this._fetchSemanticTokens.schedule())); + this._register(SemanticTokensProviderRegistry.onDidChange(e => this._fetchSemanticTokens.schedule())); + if (themeService) { + // workaround for tests which use undefined... :/ + this._register(themeService.onThemeChange(_ => { + // clear out existing tokens + this._setSemanticTokens(null, null, null, []); + this._fetchSemanticTokens.schedule(); + })); + } + this._fetchSemanticTokens.schedule(0); + } + + public dispose(): void { + this._isDisposed = true; + if (this._currentResponse) { + this._currentResponse.dispose(); + this._currentResponse = null; + } + if (this._currentRequestCancellationTokenSource) { + this._currentRequestCancellationTokenSource.cancel(); + this._currentRequestCancellationTokenSource = null; + } + super.dispose(); + } + + private _fetchSemanticTokensNow(): void { + if (this._currentRequestCancellationTokenSource) { + // there is already a request running, let it finish... + return; + } + const provider = this._getSemanticColoringProvider(); + if (!provider) { + return; + } + this._currentRequestCancellationTokenSource = new CancellationTokenSource(); + + const pendingChanges: IModelContentChangedEvent[] = []; + const contentChangeListener = this._model.onDidChangeContent((e) => { + pendingChanges.push(e); + }); + + const styling = this._semanticStyling.get(provider); + + const lastResultId = this._currentResponse ? this._currentResponse.resultId || null : null; + const request = Promise.resolve(provider.provideSemanticTokens(this._model, lastResultId, null, this._currentRequestCancellationTokenSource.token)); + + request.then((res) => { + this._currentRequestCancellationTokenSource = null; + contentChangeListener.dispose(); + this._setSemanticTokens(provider, res || null, styling, pendingChanges); + }, (err) => { + errors.onUnexpectedError(err); + this._currentRequestCancellationTokenSource = null; + contentChangeListener.dispose(); + this._setSemanticTokens(provider, null, styling, pendingChanges); + }); + } + + private static _isSemanticTokens(v: SemanticTokens | SemanticTokensEdits): v is SemanticTokens { + return v && !!((v).data); + } + + private static _isSemanticTokensEdits(v: SemanticTokens | SemanticTokensEdits): v is SemanticTokensEdits { + return v && Array.isArray((v).edits); + } + + private static _copy(src: Uint32Array, srcOffset: number, dest: Uint32Array, destOffset: number, length: number): void { + for (let i = 0; i < length; i++) { + dest[destOffset + i] = src[srcOffset + i]; + } + } + + private _setSemanticTokens(provider: SemanticTokensProvider | null, tokens: SemanticTokens | SemanticTokensEdits | null, styling: SemanticColoringProviderStyling | null, pendingChanges: IModelContentChangedEvent[]): void { + const currentResponse = this._currentResponse; + if (this._currentResponse) { + this._currentResponse.dispose(); + this._currentResponse = null; + } + if (this._isDisposed) { + // disposed! + if (provider && tokens) { + provider.releaseSemanticTokens(tokens.resultId); + } + return; + } + if (!provider || !tokens || !styling) { + this._model.setSemanticTokens(null); + return; + } + + if (ModelSemanticColoring._isSemanticTokensEdits(tokens)) { + if (!currentResponse) { + // not possible! + this._model.setSemanticTokens(null); + return; + } + if (tokens.edits.length === 0) { + // nothing to do! + tokens = { + resultId: tokens.resultId, + data: currentResponse.data + }; + } else { + let deltaLength = 0; + for (const edit of tokens.edits) { + deltaLength += (edit.data ? edit.data.length : 0) - edit.deleteCount; + } + + const srcData = currentResponse.data; + const destData = new Uint32Array(srcData.length + deltaLength); + + let srcLastStart = srcData.length; + let destLastStart = destData.length; + for (let i = tokens.edits.length - 1; i >= 0; i--) { + const edit = tokens.edits[i]; + + const copyCount = srcLastStart - (edit.start + edit.deleteCount); + if (copyCount > 0) { + ModelSemanticColoring._copy(srcData, srcLastStart - copyCount, destData, destLastStart - copyCount, copyCount); + destLastStart -= copyCount; + } + + if (edit.data) { + ModelSemanticColoring._copy(edit.data, 0, destData, destLastStart - edit.data.length, edit.data.length); + destLastStart -= edit.data.length; + } + + srcLastStart = edit.start; + } + + if (srcLastStart > 0) { + ModelSemanticColoring._copy(srcData, 0, destData, 0, srcLastStart); + } + + tokens = { + resultId: tokens.resultId, + data: destData + }; + } + } + + if (ModelSemanticColoring._isSemanticTokens(tokens)) { + + this._currentResponse = new SemanticTokensResponse(provider, tokens.resultId, tokens.data); + + const srcData = tokens.data; + const tokenCount = (tokens.data.length / 5) | 0; + const tokensPerArea = Math.max(Math.ceil(tokenCount / SemanticColoringConstants.DesiredMaxAreas), SemanticColoringConstants.DesiredTokensPerArea); + + const result: MultilineTokens2[] = []; + + let tokenIndex = 0; + let lastLineNumber = 1; + let lastStartCharacter = 0; + while (tokenIndex < tokenCount) { + const tokenStartIndex = tokenIndex; + let tokenEndIndex = Math.min(tokenStartIndex + tokensPerArea, tokenCount); + + // Keep tokens on the same line in the same area... + if (tokenEndIndex < tokenCount) { + + let smallTokenEndIndex = tokenEndIndex; + while (smallTokenEndIndex - 1 > tokenStartIndex && srcData[5 * smallTokenEndIndex] === 0) { + smallTokenEndIndex--; + } + + if (smallTokenEndIndex - 1 === tokenStartIndex) { + // there are so many tokens on this line that our area would be empty, we must now go right + let bigTokenEndIndex = tokenEndIndex; + while (bigTokenEndIndex + 1 < tokenCount && srcData[5 * bigTokenEndIndex] === 0) { + bigTokenEndIndex++; + } + tokenEndIndex = bigTokenEndIndex; + } else { + tokenEndIndex = smallTokenEndIndex; + } + } + + let destData = new Uint32Array((tokenEndIndex - tokenStartIndex) * 4); + let destOffset = 0; + let areaLine = 0; + while (tokenIndex < tokenEndIndex) { + const srcOffset = 5 * tokenIndex; + const deltaLine = srcData[srcOffset]; + const deltaCharacter = srcData[srcOffset + 1]; + const lineNumber = lastLineNumber + deltaLine; + const startCharacter = (deltaLine === 0 ? lastStartCharacter + deltaCharacter : deltaCharacter); + const length = srcData[srcOffset + 2]; + const tokenTypeIndex = srcData[srcOffset + 3]; + const tokenModifierSet = srcData[srcOffset + 4]; + const metadata = styling.getMetadata(tokenTypeIndex, tokenModifierSet); + + if (metadata !== Constants.NO_STYLING) { + if (areaLine === 0) { + areaLine = lineNumber; + } + destData[destOffset] = lineNumber - areaLine; + destData[destOffset + 1] = startCharacter; + destData[destOffset + 2] = startCharacter + length; + destData[destOffset + 3] = metadata; + destOffset += 4; + } + + lastLineNumber = lineNumber; + lastStartCharacter = startCharacter; + tokenIndex++; + } + + if (destOffset !== destData.length) { + destData = destData.subarray(0, destOffset); + } + + const tokens = new MultilineTokens2(areaLine, new SparseEncodedTokens(destData)); + result.push(tokens); + } + + // Adjust incoming semantic tokens + if (pendingChanges.length > 0) { + // More changes occurred while the request was running + // We need to: + // 1. Adjust incoming semantic tokens + // 2. Request them again + for (const change of pendingChanges) { + for (const area of result) { + for (const singleChange of change.changes) { + area.applyEdit(singleChange.range, singleChange.text); + } + } + } + + this._fetchSemanticTokens.schedule(); + } + + this._model.setSemanticTokens(result); + return; + } + + this._model.setSemanticTokens(null); + } + + private _getSemanticColoringProvider(): SemanticTokensProvider | null { + const result = SemanticTokensProviderRegistry.ordered(this._model); + return (result.length > 0 ? result[0] : null); + } +} diff --git a/src/vs/editor/common/services/resolverService.ts b/src/vs/editor/common/services/resolverService.ts index 92e21e898412..588ec89aba4d 100644 --- a/src/vs/editor/common/services/resolverService.ts +++ b/src/vs/editor/common/services/resolverService.ts @@ -52,6 +52,9 @@ export interface ITextEditorModel extends IEditorModel { createSnapshot(this: IResolvedTextEditorModel): ITextSnapshot; createSnapshot(this: ITextEditorModel): ITextSnapshot | null; + /** + * Signals if this model is readonly or not. + */ isReadonly(): boolean; } diff --git a/src/vs/editor/common/standalone/standaloneEnums.ts b/src/vs/editor/common/standalone/standaloneEnums.ts index 5f771526d306..28c9a1302e38 100644 --- a/src/vs/editor/common/standalone/standaloneEnums.ts +++ b/src/vs/editor/common/standalone/standaloneEnums.ts @@ -233,7 +233,8 @@ export enum OverviewRulerLane { * Position in the minimap to render the decoration. */ export enum MinimapPosition { - Inline = 1 + Inline = 1, + Gutter = 2 } /** diff --git a/src/vs/editor/common/view/editorColorRegistry.ts b/src/vs/editor/common/view/editorColorRegistry.ts index ab355a699bc3..b3db7499974d 100644 --- a/src/vs/editor/common/view/editorColorRegistry.ts +++ b/src/vs/editor/common/view/editorColorRegistry.ts @@ -15,6 +15,8 @@ export const editorLineHighlight = registerColor('editor.lineHighlightBackground export const editorLineHighlightBorder = registerColor('editor.lineHighlightBorder', { dark: '#282828', light: '#eeeeee', hc: '#f38518' }, nls.localize('lineHighlightBorderBox', 'Background color for the border around the line at the cursor position.')); export const editorRangeHighlight = registerColor('editor.rangeHighlightBackground', { dark: '#ffffff0b', light: '#fdff0033', hc: null }, nls.localize('rangeHighlight', 'Background color of highlighted ranges, like by quick open and find features. The color must not be opaque so as not to hide underlying decorations.'), true); export const editorRangeHighlightBorder = registerColor('editor.rangeHighlightBorder', { dark: null, light: null, hc: activeContrastBorder }, nls.localize('rangeHighlightBorder', 'Background color of the border around highlighted ranges.'), true); +export const editorSymbolHighlight = registerColor('editor.symbolHighlightBackground', { dark: editorRangeHighlight, light: editorRangeHighlight, hc: null }, nls.localize('symbolHighlight', 'Background color of highlighted symbol, like for go to definition or go next/previous symbol. The color must not be opaque so as not to hide underlying decorations.'), true); +export const editorSymbolHighlightBorder = registerColor('editor.symbolHighlightBorder', { dark: null, light: null, hc: activeContrastBorder }, nls.localize('symbolHighlightBorder', 'Background color of the border around highlighted symbols.'), true); export const editorCursorForeground = registerColor('editorCursor.foreground', { dark: '#AEAFAD', light: Color.black, hc: Color.white }, nls.localize('caret', 'Color of the editor cursor.')); export const editorCursorBackground = registerColor('editorCursor.background', null, nls.localize('editorCursorBackground', 'The background color of the editor cursor. Allows customizing the color of a character overlapped by a block cursor.')); @@ -73,6 +75,16 @@ registerThemingParticipant((theme, collector) => { collector.addRule(`.monaco-editor .rangeHighlight { border: 1px ${theme.type === 'hc' ? 'dotted' : 'solid'} ${rangeHighlightBorder}; }`); } + const symbolHighlight = theme.getColor(editorSymbolHighlight); + if (symbolHighlight) { + collector.addRule(`.monaco-editor .symbolHighlight { background-color: ${symbolHighlight}; }`); + } + + const symbolHighlightBorder = theme.getColor(editorSymbolHighlightBorder); + if (symbolHighlightBorder) { + collector.addRule(`.monaco-editor .symbolHighlight { border: 1px ${theme.type === 'hc' ? 'dotted' : 'solid'} ${symbolHighlightBorder}; }`); + } + const invisibles = theme.getColor(editorWhitespaces); if (invisibles) { collector.addRule(`.vs-whitespace { color: ${invisibles} !important; }`); diff --git a/src/vs/editor/common/view/renderingContext.ts b/src/vs/editor/common/view/renderingContext.ts index c5aaa36030cf..a28b7d0b1246 100644 --- a/src/vs/editor/common/view/renderingContext.ts +++ b/src/vs/editor/common/view/renderingContext.ts @@ -10,7 +10,7 @@ import { IViewLayout, ViewModelDecoration } from 'vs/editor/common/viewModel/vie export interface IViewLines { linesVisibleRangesForRange(range: Range, includeNewLines: boolean): LineVisibleRanges[] | null; - visibleRangeForPosition(position: Position): HorizontalRange | null; + visibleRangeForPosition(position: Position): HorizontalPosition | null; } export abstract class RestrictedRenderingContext { @@ -77,26 +77,20 @@ export class RenderingContext extends RestrictedRenderingContext { return this._viewLines.linesVisibleRangesForRange(range, includeNewLines); } - public visibleRangeForPosition(position: Position): HorizontalRange | null { + public visibleRangeForPosition(position: Position): HorizontalPosition | null { return this._viewLines.visibleRangeForPosition(position); } } export class LineVisibleRanges { - _lineVisibleRangesBrand: void; - - public lineNumber: number; - public ranges: HorizontalRange[]; - - constructor(lineNumber: number, ranges: HorizontalRange[]) { - this.lineNumber = lineNumber; - this.ranges = ranges; - } + constructor( + public readonly outsideRenderedLine: boolean, + public readonly lineNumber: number, + public readonly ranges: HorizontalRange[] + ) { } } export class HorizontalRange { - _horizontalRangeBrand: void; - public left: number; public width: number; @@ -109,3 +103,21 @@ export class HorizontalRange { return `[${this.left},${this.width}]`; } } + +export class HorizontalPosition { + public outsideRenderedLine: boolean; + public left: number; + + constructor(outsideRenderedLine: boolean, left: number) { + this.outsideRenderedLine = outsideRenderedLine; + this.left = Math.round(left); + } +} + +export class VisibleRanges { + constructor( + public readonly outsideRenderedLine: boolean, + public readonly ranges: HorizontalRange[] + ) { + } +} diff --git a/src/vs/editor/common/viewLayout/linesLayout.ts b/src/vs/editor/common/viewLayout/linesLayout.ts index 673c17833146..be71fcb11634 100644 --- a/src/vs/editor/common/viewLayout/linesLayout.ts +++ b/src/vs/editor/common/viewLayout/linesLayout.ts @@ -4,44 +4,157 @@ *--------------------------------------------------------------------------------------------*/ import { IPartialViewLinesViewportData } from 'vs/editor/common/viewLayout/viewLinesViewportData'; -import { IEditorWhitespace, WhitespaceComputer } from 'vs/editor/common/viewLayout/whitespaceComputer'; import { IViewWhitespaceViewportData } from 'vs/editor/common/viewModel/viewModel'; +import * as strings from 'vs/base/common/strings'; + +export interface IEditorWhitespace { + readonly id: string; + readonly afterLineNumber: number; + readonly height: number; +} + +/** + * An accessor that allows for whtiespace to be added, removed or changed in bulk. + */ +export interface IWhitespaceChangeAccessor { + insertWhitespace(afterLineNumber: number, ordinal: number, heightInPx: number, minWidth: number): string; + changeOneWhitespace(id: string, newAfterLineNumber: number, newHeight: number): void; + removeWhitespace(id: string): void; +} + +interface IPendingChange { id: string; newAfterLineNumber: number; newHeight: number; } +interface IPendingRemove { id: string; } + +class PendingChanges { + private _hasPending: boolean; + private _inserts: EditorWhitespace[]; + private _changes: IPendingChange[]; + private _removes: IPendingRemove[]; + + constructor() { + this._hasPending = false; + this._inserts = []; + this._changes = []; + this._removes = []; + } + + public insert(x: EditorWhitespace): void { + this._hasPending = true; + this._inserts.push(x); + } + + public change(x: IPendingChange): void { + this._hasPending = true; + this._changes.push(x); + } + + public remove(x: IPendingRemove): void { + this._hasPending = true; + this._removes.push(x); + } + + public mustCommit(): boolean { + return this._hasPending; + } + + public commit(linesLayout: LinesLayout): void { + if (!this._hasPending) { + return; + } + + const inserts = this._inserts; + const changes = this._changes; + const removes = this._removes; + + this._hasPending = false; + this._inserts = []; + this._changes = []; + this._removes = []; + + linesLayout._commitPendingChanges(inserts, changes, removes); + } +} + +export class EditorWhitespace implements IEditorWhitespace { + public id: string; + public afterLineNumber: number; + public ordinal: number; + public height: number; + public minWidth: number; + public prefixSum: number; + + constructor(id: string, afterLineNumber: number, ordinal: number, height: number, minWidth: number) { + this.id = id; + this.afterLineNumber = afterLineNumber; + this.ordinal = ordinal; + this.height = height; + this.minWidth = minWidth; + this.prefixSum = 0; + } +} /** * Layouting of objects that take vertical space (by having a height) and push down other objects. * * These objects are basically either text (lines) or spaces between those lines (whitespaces). * This provides commodity operations for working with lines that contain whitespace that pushes lines lower (vertically). - * This is written with no knowledge of an editor in mind. */ export class LinesLayout { - /** - * Keep track of the total number of lines. - * This is useful for doing binary searches or for doing hit-testing. - */ - private _lineCount: number; + private static INSTANCE_COUNT = 0; - /** - * The height of a line in pixels. - */ + private readonly _instanceId: string; + private readonly _pendingChanges: PendingChanges; + private _lastWhitespaceId: number; + private _arr: EditorWhitespace[]; + private _prefixSumValidIndex: number; + private _minWidth: number; + private _lineCount: number; private _lineHeight: number; - /** - * Contains whitespace information in pixels - */ - private readonly _whitespaces: WhitespaceComputer; - constructor(lineCount: number, lineHeight: number) { + this._instanceId = strings.singleLetterHash(++LinesLayout.INSTANCE_COUNT); + this._pendingChanges = new PendingChanges(); + this._lastWhitespaceId = 0; + this._arr = []; + this._prefixSumValidIndex = -1; + this._minWidth = -1; /* marker for not being computed */ this._lineCount = lineCount; this._lineHeight = lineHeight; - this._whitespaces = new WhitespaceComputer(); + } + + /** + * Find the insertion index for a new value inside a sorted array of values. + * If the value is already present in the sorted array, the insertion index will be after the already existing value. + */ + public static findInsertionIndex(arr: EditorWhitespace[], afterLineNumber: number, ordinal: number): number { + let low = 0; + let high = arr.length; + + while (low < high) { + const mid = ((low + high) >>> 1); + + if (afterLineNumber === arr[mid].afterLineNumber) { + if (ordinal < arr[mid].ordinal) { + high = mid; + } else { + low = mid + 1; + } + } else if (afterLineNumber < arr[mid].afterLineNumber) { + high = mid; + } else { + low = mid + 1; + } + } + + return low; } /** * Change the height of a line in pixels. */ public setLineHeight(lineHeight: number): void { + this._checkPendingChanges(); this._lineHeight = lineHeight; } @@ -51,37 +164,153 @@ export class LinesLayout { * @param lineCount New number of lines. */ public onFlushed(lineCount: number): void { + this._checkPendingChanges(); this._lineCount = lineCount; } - /** - * Insert a new whitespace of a certain height after a line number. - * The whitespace has a "sticky" characteristic. - * Irrespective of edits above or below `afterLineNumber`, the whitespace will follow the initial line. - * - * @param afterLineNumber The conceptual position of this whitespace. The whitespace will follow this line as best as possible even when deleting/inserting lines above/below. - * @param heightInPx The height of the whitespace, in pixels. - * @return An id that can be used later to mutate or delete the whitespace - */ - public insertWhitespace(afterLineNumber: number, ordinal: number, heightInPx: number, minWidth: number): string { - return this._whitespaces.insertWhitespace(afterLineNumber, ordinal, heightInPx, minWidth); + public changeWhitespace(callback: (accessor: IWhitespaceChangeAccessor) => T): T { + try { + const accessor = { + insertWhitespace: (afterLineNumber: number, ordinal: number, heightInPx: number, minWidth: number): string => { + afterLineNumber = afterLineNumber | 0; + ordinal = ordinal | 0; + heightInPx = heightInPx | 0; + minWidth = minWidth | 0; + + const id = this._instanceId + (++this._lastWhitespaceId); + this._pendingChanges.insert(new EditorWhitespace(id, afterLineNumber, ordinal, heightInPx, minWidth)); + return id; + }, + changeOneWhitespace: (id: string, newAfterLineNumber: number, newHeight: number): void => { + newAfterLineNumber = newAfterLineNumber | 0; + newHeight = newHeight | 0; + + this._pendingChanges.change({ id, newAfterLineNumber, newHeight }); + }, + removeWhitespace: (id: string): void => { + this._pendingChanges.remove({ id }); + } + }; + return callback(accessor); + } finally { + this._pendingChanges.commit(this); + } } - /** - * Change properties associated with a certain whitespace. - */ - public changeWhitespace(id: string, newAfterLineNumber: number, newHeight: number): boolean { - return this._whitespaces.changeWhitespace(id, newAfterLineNumber, newHeight); + public _commitPendingChanges(inserts: EditorWhitespace[], changes: IPendingChange[], removes: IPendingRemove[]): void { + if (inserts.length > 0 || removes.length > 0) { + this._minWidth = -1; /* marker for not being computed */ + } + + if (inserts.length + changes.length + removes.length <= 1) { + // when only one thing happened, handle it "delicately" + for (const insert of inserts) { + this._insertWhitespace(insert); + } + for (const change of changes) { + this._changeOneWhitespace(change.id, change.newAfterLineNumber, change.newHeight); + } + for (const remove of removes) { + const index = this._findWhitespaceIndex(remove.id); + if (index === -1) { + continue; + } + this._removeWhitespace(index); + } + return; + } + + // simply rebuild the entire datastructure + + const toRemove = new Set(); + for (const remove of removes) { + toRemove.add(remove.id); + } + + const toChange = new Map(); + for (const change of changes) { + toChange.set(change.id, change); + } + + const applyRemoveAndChange = (whitespaces: EditorWhitespace[]): EditorWhitespace[] => { + let result: EditorWhitespace[] = []; + for (const whitespace of whitespaces) { + if (toRemove.has(whitespace.id)) { + continue; + } + if (toChange.has(whitespace.id)) { + const change = toChange.get(whitespace.id)!; + whitespace.afterLineNumber = change.newAfterLineNumber; + whitespace.height = change.newHeight; + } + result.push(whitespace); + } + return result; + }; + + const result = applyRemoveAndChange(this._arr).concat(applyRemoveAndChange(inserts)); + result.sort((a, b) => { + if (a.afterLineNumber === b.afterLineNumber) { + return a.ordinal - b.ordinal; + } + return a.afterLineNumber - b.afterLineNumber; + }); + + this._arr = result; + this._prefixSumValidIndex = -1; } - /** - * Remove an existing whitespace. - * - * @param id The whitespace to remove - * @return Returns true if the whitespace is found and it is removed. - */ - public removeWhitespace(id: string): boolean { - return this._whitespaces.removeWhitespace(id); + private _checkPendingChanges(): void { + if (this._pendingChanges.mustCommit()) { + console.warn(`Commiting pending changes before change accessor leaves due to read access.`); + this._pendingChanges.commit(this); + } + } + + private _insertWhitespace(whitespace: EditorWhitespace): void { + const insertIndex = LinesLayout.findInsertionIndex(this._arr, whitespace.afterLineNumber, whitespace.ordinal); + this._arr.splice(insertIndex, 0, whitespace); + this._prefixSumValidIndex = Math.min(this._prefixSumValidIndex, insertIndex - 1); + } + + private _findWhitespaceIndex(id: string): number { + const arr = this._arr; + for (let i = 0, len = arr.length; i < len; i++) { + if (arr[i].id === id) { + return i; + } + } + return -1; + } + + private _changeOneWhitespace(id: string, newAfterLineNumber: number, newHeight: number): void { + const index = this._findWhitespaceIndex(id); + if (index === -1) { + return; + } + if (this._arr[index].height !== newHeight) { + this._arr[index].height = newHeight; + this._prefixSumValidIndex = Math.min(this._prefixSumValidIndex, index - 1); + } + if (this._arr[index].afterLineNumber !== newAfterLineNumber) { + // `afterLineNumber` changed for this whitespace + + // Record old whitespace + const whitespace = this._arr[index]; + + // Since changing `afterLineNumber` can trigger a reordering, we're gonna remove this whitespace + this._removeWhitespace(index); + + whitespace.afterLineNumber = newAfterLineNumber; + + // And add it again + this._insertWhitespace(whitespace); + } + } + + private _removeWhitespace(removeIndex: number): void { + this._arr.splice(removeIndex, 1); + this._prefixSumValidIndex = Math.min(this._prefixSumValidIndex, removeIndex - 1); } /** @@ -91,8 +320,24 @@ export class LinesLayout { * @param toLineNumber The line number at which the deletion ended, inclusive */ public onLinesDeleted(fromLineNumber: number, toLineNumber: number): void { + this._checkPendingChanges(); + fromLineNumber = fromLineNumber | 0; + toLineNumber = toLineNumber | 0; + this._lineCount -= (toLineNumber - fromLineNumber + 1); - this._whitespaces.onLinesDeleted(fromLineNumber, toLineNumber); + for (let i = 0, len = this._arr.length; i < len; i++) { + const afterLineNumber = this._arr[i].afterLineNumber; + + if (fromLineNumber <= afterLineNumber && afterLineNumber <= toLineNumber) { + // The line this whitespace was after has been deleted + // => move whitespace to before first deleted line + this._arr[i].afterLineNumber = fromLineNumber - 1; + } else if (afterLineNumber > toLineNumber) { + // The line this whitespace was after has been moved up + // => move whitespace up + this._arr[i].afterLineNumber -= (toLineNumber - fromLineNumber + 1); + } + } } /** @@ -102,8 +347,53 @@ export class LinesLayout { * @param toLineNumber The line number at which the insertion ended, inclusive. */ public onLinesInserted(fromLineNumber: number, toLineNumber: number): void { + this._checkPendingChanges(); + fromLineNumber = fromLineNumber | 0; + toLineNumber = toLineNumber | 0; + this._lineCount += (toLineNumber - fromLineNumber + 1); - this._whitespaces.onLinesInserted(fromLineNumber, toLineNumber); + for (let i = 0, len = this._arr.length; i < len; i++) { + const afterLineNumber = this._arr[i].afterLineNumber; + + if (fromLineNumber <= afterLineNumber) { + this._arr[i].afterLineNumber += (toLineNumber - fromLineNumber + 1); + } + } + } + + /** + * Get the sum of all the whitespaces. + */ + public getWhitespacesTotalHeight(): number { + this._checkPendingChanges(); + if (this._arr.length === 0) { + return 0; + } + return this.getWhitespacesAccumulatedHeight(this._arr.length - 1); + } + + /** + * Return the sum of the heights of the whitespaces at [0..index]. + * This includes the whitespace at `index`. + * + * @param index The index of the whitespace. + * @return The sum of the heights of all whitespaces before the one at `index`, including the one at `index`. + */ + public getWhitespacesAccumulatedHeight(index: number): number { + this._checkPendingChanges(); + index = index | 0; + + let startIndex = Math.max(0, this._prefixSumValidIndex + 1); + if (startIndex === 0) { + this._arr[0].prefixSum = this._arr[0].height; + startIndex++; + } + + for (let i = startIndex; i <= index; i++) { + this._arr[i].prefixSum = this._arr[i - 1].prefixSum + this._arr[i].height; + } + this._prefixSumValidIndex = Math.max(this._prefixSumValidIndex, index); + return this._arr[index].prefixSum; } /** @@ -112,11 +402,81 @@ export class LinesLayout { * @return The sum of heights for all objects. */ public getLinesTotalHeight(): number { - let linesHeight = this._lineHeight * this._lineCount; - let whitespacesHeight = this._whitespaces.getTotalHeight(); + this._checkPendingChanges(); + const linesHeight = this._lineHeight * this._lineCount; + const whitespacesHeight = this.getWhitespacesTotalHeight(); return linesHeight + whitespacesHeight; } + /** + * Returns the accumulated height of whitespaces before the given line number. + * + * @param lineNumber The line number + */ + public getWhitespaceAccumulatedHeightBeforeLineNumber(lineNumber: number): number { + this._checkPendingChanges(); + lineNumber = lineNumber | 0; + + const lastWhitespaceBeforeLineNumber = this._findLastWhitespaceBeforeLineNumber(lineNumber); + + if (lastWhitespaceBeforeLineNumber === -1) { + return 0; + } + + return this.getWhitespacesAccumulatedHeight(lastWhitespaceBeforeLineNumber); + } + + private _findLastWhitespaceBeforeLineNumber(lineNumber: number): number { + lineNumber = lineNumber | 0; + + // Find the whitespace before line number + const arr = this._arr; + let low = 0; + let high = arr.length - 1; + + while (low <= high) { + const delta = (high - low) | 0; + const halfDelta = (delta / 2) | 0; + const mid = (low + halfDelta) | 0; + + if (arr[mid].afterLineNumber < lineNumber) { + if (mid + 1 >= arr.length || arr[mid + 1].afterLineNumber >= lineNumber) { + return mid; + } else { + low = (mid + 1) | 0; + } + } else { + high = (mid - 1) | 0; + } + } + + return -1; + } + + private _findFirstWhitespaceAfterLineNumber(lineNumber: number): number { + lineNumber = lineNumber | 0; + + const lastWhitespaceBeforeLineNumber = this._findLastWhitespaceBeforeLineNumber(lineNumber); + const firstWhitespaceAfterLineNumber = lastWhitespaceBeforeLineNumber + 1; + + if (firstWhitespaceAfterLineNumber < this._arr.length) { + return firstWhitespaceAfterLineNumber; + } + + return -1; + } + + /** + * Find the index of the first whitespace which has `afterLineNumber` >= `lineNumber`. + * @return The index of the first whitespace with `afterLineNumber` >= `lineNumber` or -1 if no whitespace is found. + */ + public getFirstWhitespaceIndexAfterLineNumber(lineNumber: number): number { + this._checkPendingChanges(); + lineNumber = lineNumber | 0; + + return this._findFirstWhitespaceAfterLineNumber(lineNumber); + } + /** * Get the vertical offset (the sum of heights for all objects above) a certain line number. * @@ -124,6 +484,7 @@ export class LinesLayout { * @return The sum of heights for all objects above `lineNumber`. */ public getVerticalOffsetForLineNumber(lineNumber: number): number { + this._checkPendingChanges(); lineNumber = lineNumber | 0; let previousLinesHeight: number; @@ -133,36 +494,40 @@ export class LinesLayout { previousLinesHeight = 0; } - let previousWhitespacesHeight = this._whitespaces.getAccumulatedHeightBeforeLineNumber(lineNumber); + const previousWhitespacesHeight = this.getWhitespaceAccumulatedHeightBeforeLineNumber(lineNumber); return previousLinesHeight + previousWhitespacesHeight; } - /** - * Returns the accumulated height of whitespaces before the given line number. - * - * @param lineNumber The line number - */ - public getWhitespaceAccumulatedHeightBeforeLineNumber(lineNumber: number): number { - return this._whitespaces.getAccumulatedHeightBeforeLineNumber(lineNumber); - } - /** * Returns if there is any whitespace in the document. */ public hasWhitespace(): boolean { - return this._whitespaces.getCount() > 0; + this._checkPendingChanges(); + return this.getWhitespacesCount() > 0; } + /** + * The maximum min width for all whitespaces. + */ public getWhitespaceMinWidth(): number { - return this._whitespaces.getMinWidth(); + this._checkPendingChanges(); + if (this._minWidth === -1) { + let minWidth = 0; + for (let i = 0, len = this._arr.length; i < len; i++) { + minWidth = Math.max(minWidth, this._arr[i].minWidth); + } + this._minWidth = minWidth; + } + return this._minWidth; } /** * Check if `verticalOffset` is below all lines. */ public isAfterLines(verticalOffset: number): boolean { - let totalHeight = this.getLinesTotalHeight(); + this._checkPendingChanges(); + const totalHeight = this.getLinesTotalHeight(); return verticalOffset > totalHeight; } @@ -175,6 +540,7 @@ export class LinesLayout { * @return The line number at or after vertical offset `verticalOffset`. */ public getLineNumberAtOrAfterVerticalOffset(verticalOffset: number): number { + this._checkPendingChanges(); verticalOffset = verticalOffset | 0; if (verticalOffset < 0) { @@ -187,9 +553,9 @@ export class LinesLayout { let maxLineNumber = linesCount; while (minLineNumber < maxLineNumber) { - let midLineNumber = ((minLineNumber + maxLineNumber) / 2) | 0; + const midLineNumber = ((minLineNumber + maxLineNumber) / 2) | 0; - let midLineNumberVerticalOffset = this.getVerticalOffsetForLineNumber(midLineNumber) | 0; + const midLineNumberVerticalOffset = this.getVerticalOffsetForLineNumber(midLineNumber) | 0; if (verticalOffset >= midLineNumberVerticalOffset + lineHeight) { // vertical offset is after mid line number @@ -218,6 +584,7 @@ export class LinesLayout { * @return A structure describing the lines positioned between `verticalOffset1` and `verticalOffset2`. */ public getLinesViewportData(verticalOffset1: number, verticalOffset2: number): IPartialViewLinesViewportData { + this._checkPendingChanges(); verticalOffset1 = verticalOffset1 | 0; verticalOffset2 = verticalOffset2 | 0; const lineHeight = this._lineHeight; @@ -230,8 +597,8 @@ export class LinesLayout { let endLineNumber = this._lineCount | 0; // Also keep track of what whitespace we've got - let whitespaceIndex = this._whitespaces.getFirstWhitespaceIndexAfterLineNumber(startLineNumber) | 0; - const whitespaceCount = this._whitespaces.getCount() | 0; + let whitespaceIndex = this.getFirstWhitespaceIndexAfterLineNumber(startLineNumber) | 0; + const whitespaceCount = this.getWhitespacesCount() | 0; let currentWhitespaceHeight: number; let currentWhitespaceAfterLineNumber: number; @@ -240,8 +607,8 @@ export class LinesLayout { currentWhitespaceAfterLineNumber = endLineNumber + 1; currentWhitespaceHeight = 0; } else { - currentWhitespaceAfterLineNumber = this._whitespaces.getAfterLineNumberForWhitespaceIndex(whitespaceIndex) | 0; - currentWhitespaceHeight = this._whitespaces.getHeightForWhitespaceIndex(whitespaceIndex) | 0; + currentWhitespaceAfterLineNumber = this.getAfterLineNumberForWhitespaceIndex(whitespaceIndex) | 0; + currentWhitespaceHeight = this.getHeightForWhitespaceIndex(whitespaceIndex) | 0; } let currentVerticalOffset = startLineNumberVerticalOffset; @@ -258,7 +625,7 @@ export class LinesLayout { currentLineRelativeOffset -= bigNumbersDelta; } - let linesOffsets: number[] = []; + const linesOffsets: number[] = []; const verticalCenter = verticalOffset1 + (verticalOffset2 - verticalOffset1) / 2; let centeredLineNumber = -1; @@ -267,8 +634,8 @@ export class LinesLayout { for (let lineNumber = startLineNumber; lineNumber <= endLineNumber; lineNumber++) { if (centeredLineNumber === -1) { - let currentLineTop = currentVerticalOffset; - let currentLineBottom = currentVerticalOffset + lineHeight; + const currentLineTop = currentVerticalOffset; + const currentLineBottom = currentVerticalOffset + lineHeight; if ((currentLineTop <= verticalCenter && verticalCenter < currentLineBottom) || currentLineTop > verticalCenter) { centeredLineNumber = lineNumber; } @@ -291,8 +658,8 @@ export class LinesLayout { if (whitespaceIndex >= whitespaceCount) { currentWhitespaceAfterLineNumber = endLineNumber + 1; } else { - currentWhitespaceAfterLineNumber = this._whitespaces.getAfterLineNumberForWhitespaceIndex(whitespaceIndex) | 0; - currentWhitespaceHeight = this._whitespaces.getHeightForWhitespaceIndex(whitespaceIndex) | 0; + currentWhitespaceAfterLineNumber = this.getAfterLineNumberForWhitespaceIndex(whitespaceIndex) | 0; + currentWhitespaceHeight = this.getHeightForWhitespaceIndex(whitespaceIndex) | 0; } } @@ -335,9 +702,10 @@ export class LinesLayout { } public getVerticalOffsetForWhitespaceIndex(whitespaceIndex: number): number { + this._checkPendingChanges(); whitespaceIndex = whitespaceIndex | 0; - let afterLineNumber = this._whitespaces.getAfterLineNumberForWhitespaceIndex(whitespaceIndex); + const afterLineNumber = this.getAfterLineNumberForWhitespaceIndex(whitespaceIndex); let previousLinesHeight: number; if (afterLineNumber >= 1) { @@ -348,7 +716,7 @@ export class LinesLayout { let previousWhitespacesHeight: number; if (whitespaceIndex > 0) { - previousWhitespacesHeight = this._whitespaces.getAccumulatedHeight(whitespaceIndex - 1); + previousWhitespacesHeight = this.getWhitespacesAccumulatedHeight(whitespaceIndex - 1); } else { previousWhitespacesHeight = 0; } @@ -356,30 +724,28 @@ export class LinesLayout { } public getWhitespaceIndexAtOrAfterVerticallOffset(verticalOffset: number): number { + this._checkPendingChanges(); verticalOffset = verticalOffset | 0; - let midWhitespaceIndex: number, - minWhitespaceIndex = 0, - maxWhitespaceIndex = this._whitespaces.getCount() - 1, - midWhitespaceVerticalOffset: number, - midWhitespaceHeight: number; + let minWhitespaceIndex = 0; + let maxWhitespaceIndex = this.getWhitespacesCount() - 1; if (maxWhitespaceIndex < 0) { return -1; } // Special case: nothing to be found - let maxWhitespaceVerticalOffset = this.getVerticalOffsetForWhitespaceIndex(maxWhitespaceIndex); - let maxWhitespaceHeight = this._whitespaces.getHeightForWhitespaceIndex(maxWhitespaceIndex); + const maxWhitespaceVerticalOffset = this.getVerticalOffsetForWhitespaceIndex(maxWhitespaceIndex); + const maxWhitespaceHeight = this.getHeightForWhitespaceIndex(maxWhitespaceIndex); if (verticalOffset >= maxWhitespaceVerticalOffset + maxWhitespaceHeight) { return -1; } while (minWhitespaceIndex < maxWhitespaceIndex) { - midWhitespaceIndex = Math.floor((minWhitespaceIndex + maxWhitespaceIndex) / 2); + const midWhitespaceIndex = Math.floor((minWhitespaceIndex + maxWhitespaceIndex) / 2); - midWhitespaceVerticalOffset = this.getVerticalOffsetForWhitespaceIndex(midWhitespaceIndex); - midWhitespaceHeight = this._whitespaces.getHeightForWhitespaceIndex(midWhitespaceIndex); + const midWhitespaceVerticalOffset = this.getVerticalOffsetForWhitespaceIndex(midWhitespaceIndex); + const midWhitespaceHeight = this.getHeightForWhitespaceIndex(midWhitespaceIndex); if (verticalOffset >= midWhitespaceVerticalOffset + midWhitespaceHeight) { // vertical offset is after whitespace @@ -402,27 +768,28 @@ export class LinesLayout { * @return Precisely the whitespace that is layouted at `verticaloffset` or null. */ public getWhitespaceAtVerticalOffset(verticalOffset: number): IViewWhitespaceViewportData | null { + this._checkPendingChanges(); verticalOffset = verticalOffset | 0; - let candidateIndex = this.getWhitespaceIndexAtOrAfterVerticallOffset(verticalOffset); + const candidateIndex = this.getWhitespaceIndexAtOrAfterVerticallOffset(verticalOffset); if (candidateIndex < 0) { return null; } - if (candidateIndex >= this._whitespaces.getCount()) { + if (candidateIndex >= this.getWhitespacesCount()) { return null; } - let candidateTop = this.getVerticalOffsetForWhitespaceIndex(candidateIndex); + const candidateTop = this.getVerticalOffsetForWhitespaceIndex(candidateIndex); if (candidateTop > verticalOffset) { return null; } - let candidateHeight = this._whitespaces.getHeightForWhitespaceIndex(candidateIndex); - let candidateId = this._whitespaces.getIdForWhitespaceIndex(candidateIndex); - let candidateAfterLineNumber = this._whitespaces.getAfterLineNumberForWhitespaceIndex(candidateIndex); + const candidateHeight = this.getHeightForWhitespaceIndex(candidateIndex); + const candidateId = this.getIdForWhitespaceIndex(candidateIndex); + const candidateAfterLineNumber = this.getAfterLineNumberForWhitespaceIndex(candidateIndex); return { id: candidateId, @@ -440,11 +807,12 @@ export class LinesLayout { * @return An array with all the whitespaces in the viewport. If no whitespace is in viewport, the array is empty. */ public getWhitespaceViewportData(verticalOffset1: number, verticalOffset2: number): IViewWhitespaceViewportData[] { + this._checkPendingChanges(); verticalOffset1 = verticalOffset1 | 0; verticalOffset2 = verticalOffset2 | 0; - let startIndex = this.getWhitespaceIndexAtOrAfterVerticallOffset(verticalOffset1); - let endIndex = this._whitespaces.getCount() - 1; + const startIndex = this.getWhitespaceIndexAtOrAfterVerticallOffset(verticalOffset1); + const endIndex = this.getWhitespacesCount() - 1; if (startIndex < 0) { return []; @@ -452,15 +820,15 @@ export class LinesLayout { let result: IViewWhitespaceViewportData[] = []; for (let i = startIndex; i <= endIndex; i++) { - let top = this.getVerticalOffsetForWhitespaceIndex(i); - let height = this._whitespaces.getHeightForWhitespaceIndex(i); + const top = this.getVerticalOffsetForWhitespaceIndex(i); + const height = this.getHeightForWhitespaceIndex(i); if (top >= verticalOffset2) { break; } result.push({ - id: this._whitespaces.getIdForWhitespaceIndex(i), - afterLineNumber: this._whitespaces.getAfterLineNumberForWhitespaceIndex(i), + id: this.getIdForWhitespaceIndex(i), + afterLineNumber: this.getAfterLineNumberForWhitespaceIndex(i), verticalOffset: top, height: height }); @@ -473,6 +841,54 @@ export class LinesLayout { * Get all whitespaces. */ public getWhitespaces(): IEditorWhitespace[] { - return this._whitespaces.getWhitespaces(this._lineHeight); + this._checkPendingChanges(); + return this._arr.slice(0); + } + + /** + * The number of whitespaces. + */ + public getWhitespacesCount(): number { + this._checkPendingChanges(); + return this._arr.length; + } + + /** + * Get the `id` for whitespace at index `index`. + * + * @param index The index of the whitespace. + * @return `id` of whitespace at `index`. + */ + public getIdForWhitespaceIndex(index: number): string { + this._checkPendingChanges(); + index = index | 0; + + return this._arr[index].id; + } + + /** + * Get the `afterLineNumber` for whitespace at index `index`. + * + * @param index The index of the whitespace. + * @return `afterLineNumber` of whitespace at `index`. + */ + public getAfterLineNumberForWhitespaceIndex(index: number): number { + this._checkPendingChanges(); + index = index | 0; + + return this._arr[index].afterLineNumber; + } + + /** + * Get the `height` for whitespace at index `index`. + * + * @param index The index of the whitespace. + * @return `height` of whitespace at `index`. + */ + public getHeightForWhitespaceIndex(index: number): number { + this._checkPendingChanges(); + index = index | 0; + + return this._arr[index].height; } } diff --git a/src/vs/editor/common/viewLayout/viewLayout.ts b/src/vs/editor/common/viewLayout/viewLayout.ts index a3c61eb31bc1..79f8a995a712 100644 --- a/src/vs/editor/common/viewLayout/viewLayout.ts +++ b/src/vs/editor/common/viewLayout/viewLayout.ts @@ -5,12 +5,11 @@ import { Event } from 'vs/base/common/event'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; -import { IScrollDimensions, IScrollPosition, ScrollEvent, Scrollable, ScrollbarVisibility } from 'vs/base/common/scrollable'; +import { IScrollPosition, ScrollEvent, Scrollable, ScrollbarVisibility } from 'vs/base/common/scrollable'; import { ConfigurationChangedEvent, EditorOption } from 'vs/editor/common/config/editorOptions'; import * as editorCommon from 'vs/editor/common/editorCommon'; -import { LinesLayout } from 'vs/editor/common/viewLayout/linesLayout'; +import { LinesLayout, IEditorWhitespace, IWhitespaceChangeAccessor } from 'vs/editor/common/viewLayout/linesLayout'; import { IPartialViewLinesViewportData } from 'vs/editor/common/viewLayout/viewLinesViewportData'; -import { IEditorWhitespace } from 'vs/editor/common/viewLayout/whitespaceComputer'; import { IViewLayout, IViewWhitespaceViewportData, Viewport } from 'vs/editor/common/viewModel/viewModel'; const SMOOTH_SCROLLING_TIME = 125; @@ -65,15 +64,23 @@ export class ViewLayout extends Disposable implements IViewLayout { } if (e.hasChanged(EditorOption.layoutInfo)) { const layoutInfo = options.get(EditorOption.layoutInfo); + const width = layoutInfo.contentWidth; + const height = layoutInfo.contentHeight; + const scrollDimensions = this.scrollable.getScrollDimensions(); + const scrollWidth = scrollDimensions.scrollWidth; + const scrollHeight = this._getTotalHeight(width, height, scrollWidth); + this.scrollable.setScrollDimensions({ - width: layoutInfo.contentWidth, - height: layoutInfo.contentHeight + width: width, + height: height, + scrollHeight: scrollHeight }); + } else { + this._updateHeight(); } if (e.hasChanged(EditorOption.smoothScrolling)) { this._configureSmoothScrollDuration(); } - this._updateHeight(); } public onFlushed(lineCount: number): void { this._linesLayout.onFlushed(lineCount); @@ -87,37 +94,41 @@ export class ViewLayout extends Disposable implements IViewLayout { // ---- end view event handlers - private _getHorizontalScrollbarHeight(scrollDimensions: IScrollDimensions): number { + private _getHorizontalScrollbarHeight(width: number, scrollWidth: number): number { const options = this._configuration.options; const scrollbar = options.get(EditorOption.scrollbar); if (scrollbar.horizontal === ScrollbarVisibility.Hidden) { // horizontal scrollbar not visible return 0; } - if (scrollDimensions.width >= scrollDimensions.scrollWidth) { + if (width >= scrollWidth) { // horizontal scrollbar not visible return 0; } return scrollbar.horizontalScrollbarSize; } - private _getTotalHeight(): number { + private _getTotalHeight(width: number, height: number, scrollWidth: number): number { const options = this._configuration.options; - const scrollDimensions = this.scrollable.getScrollDimensions(); let result = this._linesLayout.getLinesTotalHeight(); if (options.get(EditorOption.scrollBeyondLastLine)) { - result += scrollDimensions.height - options.get(EditorOption.lineHeight); + result += height - options.get(EditorOption.lineHeight); } else { - result += this._getHorizontalScrollbarHeight(scrollDimensions); + result += this._getHorizontalScrollbarHeight(width, scrollWidth); } - return Math.max(scrollDimensions.height, result); + return Math.max(height, result); } private _updateHeight(): void { + const scrollDimensions = this.scrollable.getScrollDimensions(); + const width = scrollDimensions.width; + const height = scrollDimensions.height; + const scrollWidth = scrollDimensions.scrollWidth; + const scrollHeight = this._getTotalHeight(width, height, scrollWidth); this.scrollable.setScrollDimensions({ - scrollHeight: this._getTotalHeight() + scrollHeight: scrollHeight }); } @@ -182,15 +193,8 @@ export class ViewLayout extends Disposable implements IViewLayout { } // ---- IVerticalLayoutProvider - - public addWhitespace(afterLineNumber: number, ordinal: number, height: number, minWidth: number): string { - return this._linesLayout.insertWhitespace(afterLineNumber, ordinal, height, minWidth); - } - public changeWhitespace(id: string, newAfterLineNumber: number, newHeight: number): boolean { - return this._linesLayout.changeWhitespace(id, newAfterLineNumber, newHeight); - } - public removeWhitespace(id: string): boolean { - return this._linesLayout.removeWhitespace(id); + public changeWhitespace(callback: (accessor: IWhitespaceChangeAccessor) => T): T { + return this._linesLayout.changeWhitespace(callback); } public getVerticalOffsetForLineNumber(lineNumber: number): number { return this._linesLayout.getVerticalOffsetForLineNumber(lineNumber); diff --git a/src/vs/editor/common/viewLayout/viewLineRenderer.ts b/src/vs/editor/common/viewLayout/viewLineRenderer.ts index 801192a1435d..d7aeddd3d93b 100644 --- a/src/vs/editor/common/viewLayout/viewLineRenderer.ts +++ b/src/vs/editor/common/viewLayout/viewLineRenderer.ts @@ -783,7 +783,7 @@ function _renderLine(input: ResolvedRenderLineInput, sb: IStringBuilder): Render if (!fontIsMonospace) { const partIsOnlyWhitespace = (partType === 'vs-whitespace'); if (partIsOnlyWhitespace || !containsForeignElements) { - sb.appendASCIIString(' style="width:'); + sb.appendASCIIString(' style="display:inline-block;width:'); sb.appendASCIIString(String(spaceWidth * partContentCnt)); sb.appendASCIIString('px"'); } diff --git a/src/vs/editor/common/viewLayout/whitespaceComputer.ts b/src/vs/editor/common/viewLayout/whitespaceComputer.ts deleted file mode 100644 index 914aaecd5e01..000000000000 --- a/src/vs/editor/common/viewLayout/whitespaceComputer.ts +++ /dev/null @@ -1,493 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as strings from 'vs/base/common/strings'; - -export interface IEditorWhitespace { - readonly id: string; - readonly afterLineNumber: number; - readonly heightInLines: number; -} - -/** - * Represent whitespaces in between lines and provide fast CRUD management methods. - * The whitespaces are sorted ascending by `afterLineNumber`. - */ -export class WhitespaceComputer { - - private static INSTANCE_COUNT = 0; - - private readonly _instanceId: string; - - /** - * heights[i] is the height in pixels for whitespace at index i - */ - private readonly _heights: number[]; - - /** - * minWidths[i] is the min width in pixels for whitespace at index i - */ - private readonly _minWidths: number[]; - - /** - * afterLineNumbers[i] is the line number whitespace at index i is after - */ - private readonly _afterLineNumbers: number[]; - - /** - * ordinals[i] is the orinal of the whitespace at index i - */ - private readonly _ordinals: number[]; - - /** - * prefixSum[i] = SUM(heights[j]), 1 <= j <= i - */ - private readonly _prefixSum: number[]; - - /** - * prefixSum[i], 1 <= i <= prefixSumValidIndex can be trusted - */ - private _prefixSumValidIndex: number; - - /** - * ids[i] is the whitespace id of whitespace at index i - */ - private readonly _ids: string[]; - - /** - * index at which a whitespace is positioned (inside heights, afterLineNumbers, prefixSum members) - */ - private readonly _whitespaceId2Index: { - [id: string]: number; - }; - - /** - * last whitespace id issued - */ - private _lastWhitespaceId: number; - - private _minWidth: number; - - constructor() { - this._instanceId = strings.singleLetterHash(++WhitespaceComputer.INSTANCE_COUNT); - this._heights = []; - this._minWidths = []; - this._ids = []; - this._afterLineNumbers = []; - this._ordinals = []; - this._prefixSum = []; - this._prefixSumValidIndex = -1; - this._whitespaceId2Index = {}; - this._lastWhitespaceId = 0; - this._minWidth = -1; /* marker for not being computed */ - } - - /** - * Find the insertion index for a new value inside a sorted array of values. - * If the value is already present in the sorted array, the insertion index will be after the already existing value. - */ - public static findInsertionIndex(sortedArray: number[], value: number, ordinals: number[], valueOrdinal: number): number { - let low = 0; - let high = sortedArray.length; - - while (low < high) { - let mid = ((low + high) >>> 1); - - if (value === sortedArray[mid]) { - if (valueOrdinal < ordinals[mid]) { - high = mid; - } else { - low = mid + 1; - } - } else if (value < sortedArray[mid]) { - high = mid; - } else { - low = mid + 1; - } - } - - return low; - } - - /** - * Insert a new whitespace of a certain height after a line number. - * The whitespace has a "sticky" characteristic. - * Irrespective of edits above or below `afterLineNumber`, the whitespace will follow the initial line. - * - * @param afterLineNumber The conceptual position of this whitespace. The whitespace will follow this line as best as possible even when deleting/inserting lines above/below. - * @param heightInPx The height of the whitespace, in pixels. - * @return An id that can be used later to mutate or delete the whitespace - */ - public insertWhitespace(afterLineNumber: number, ordinal: number, heightInPx: number, minWidth: number): string { - afterLineNumber = afterLineNumber | 0; - ordinal = ordinal | 0; - heightInPx = heightInPx | 0; - minWidth = minWidth | 0; - - let id = this._instanceId + (++this._lastWhitespaceId); - let insertionIndex = WhitespaceComputer.findInsertionIndex(this._afterLineNumbers, afterLineNumber, this._ordinals, ordinal); - this._insertWhitespaceAtIndex(id, insertionIndex, afterLineNumber, ordinal, heightInPx, minWidth); - this._minWidth = -1; /* marker for not being computed */ - return id; - } - - private _insertWhitespaceAtIndex(id: string, insertIndex: number, afterLineNumber: number, ordinal: number, heightInPx: number, minWidth: number): void { - insertIndex = insertIndex | 0; - afterLineNumber = afterLineNumber | 0; - ordinal = ordinal | 0; - heightInPx = heightInPx | 0; - minWidth = minWidth | 0; - - this._heights.splice(insertIndex, 0, heightInPx); - this._minWidths.splice(insertIndex, 0, minWidth); - this._ids.splice(insertIndex, 0, id); - this._afterLineNumbers.splice(insertIndex, 0, afterLineNumber); - this._ordinals.splice(insertIndex, 0, ordinal); - this._prefixSum.splice(insertIndex, 0, 0); - - let keys = Object.keys(this._whitespaceId2Index); - for (let i = 0, len = keys.length; i < len; i++) { - let sid = keys[i]; - let oldIndex = this._whitespaceId2Index[sid]; - if (oldIndex >= insertIndex) { - this._whitespaceId2Index[sid] = oldIndex + 1; - } - } - - this._whitespaceId2Index[id] = insertIndex; - this._prefixSumValidIndex = Math.min(this._prefixSumValidIndex, insertIndex - 1); - } - - /** - * Change properties associated with a certain whitespace. - */ - public changeWhitespace(id: string, newAfterLineNumber: number, newHeight: number): boolean { - newAfterLineNumber = newAfterLineNumber | 0; - newHeight = newHeight | 0; - - let hasChanges = false; - hasChanges = this.changeWhitespaceHeight(id, newHeight) || hasChanges; - hasChanges = this.changeWhitespaceAfterLineNumber(id, newAfterLineNumber) || hasChanges; - return hasChanges; - } - - /** - * Change the height of an existing whitespace - * - * @param id The whitespace to change - * @param newHeightInPx The new height of the whitespace, in pixels - * @return Returns true if the whitespace is found and if the new height is different than the old height - */ - public changeWhitespaceHeight(id: string, newHeightInPx: number): boolean { - newHeightInPx = newHeightInPx | 0; - - if (this._whitespaceId2Index.hasOwnProperty(id)) { - let index = this._whitespaceId2Index[id]; - if (this._heights[index] !== newHeightInPx) { - this._heights[index] = newHeightInPx; - this._prefixSumValidIndex = Math.min(this._prefixSumValidIndex, index - 1); - return true; - } - } - return false; - } - - /** - * Change the line number after which an existing whitespace flows. - * - * @param id The whitespace to change - * @param newAfterLineNumber The new line number the whitespace will follow - * @return Returns true if the whitespace is found and if the new line number is different than the old line number - */ - public changeWhitespaceAfterLineNumber(id: string, newAfterLineNumber: number): boolean { - newAfterLineNumber = newAfterLineNumber | 0; - - if (this._whitespaceId2Index.hasOwnProperty(id)) { - let index = this._whitespaceId2Index[id]; - if (this._afterLineNumbers[index] !== newAfterLineNumber) { - // `afterLineNumber` changed for this whitespace - - // Record old ordinal - let ordinal = this._ordinals[index]; - - // Record old height - let heightInPx = this._heights[index]; - - // Record old min width - let minWidth = this._minWidths[index]; - - // Since changing `afterLineNumber` can trigger a reordering, we're gonna remove this whitespace - this.removeWhitespace(id); - - // And add it again - let insertionIndex = WhitespaceComputer.findInsertionIndex(this._afterLineNumbers, newAfterLineNumber, this._ordinals, ordinal); - this._insertWhitespaceAtIndex(id, insertionIndex, newAfterLineNumber, ordinal, heightInPx, minWidth); - - return true; - } - } - return false; - } - - /** - * Remove an existing whitespace. - * - * @param id The whitespace to remove - * @return Returns true if the whitespace is found and it is removed. - */ - public removeWhitespace(id: string): boolean { - if (this._whitespaceId2Index.hasOwnProperty(id)) { - let index = this._whitespaceId2Index[id]; - delete this._whitespaceId2Index[id]; - this._removeWhitespaceAtIndex(index); - this._minWidth = -1; /* marker for not being computed */ - return true; - } - - return false; - } - - private _removeWhitespaceAtIndex(removeIndex: number): void { - removeIndex = removeIndex | 0; - - this._heights.splice(removeIndex, 1); - this._minWidths.splice(removeIndex, 1); - this._ids.splice(removeIndex, 1); - this._afterLineNumbers.splice(removeIndex, 1); - this._ordinals.splice(removeIndex, 1); - this._prefixSum.splice(removeIndex, 1); - this._prefixSumValidIndex = Math.min(this._prefixSumValidIndex, removeIndex - 1); - - let keys = Object.keys(this._whitespaceId2Index); - for (let i = 0, len = keys.length; i < len; i++) { - let sid = keys[i]; - let oldIndex = this._whitespaceId2Index[sid]; - if (oldIndex >= removeIndex) { - this._whitespaceId2Index[sid] = oldIndex - 1; - } - } - } - - /** - * Notify the computer that lines have been deleted (a continuous zone of lines). - * This gives it a chance to update `afterLineNumber` for whitespaces, giving the "sticky" characteristic. - * - * @param fromLineNumber The line number at which the deletion started, inclusive - * @param toLineNumber The line number at which the deletion ended, inclusive - */ - public onLinesDeleted(fromLineNumber: number, toLineNumber: number): void { - fromLineNumber = fromLineNumber | 0; - toLineNumber = toLineNumber | 0; - - for (let i = 0, len = this._afterLineNumbers.length; i < len; i++) { - let afterLineNumber = this._afterLineNumbers[i]; - - if (fromLineNumber <= afterLineNumber && afterLineNumber <= toLineNumber) { - // The line this whitespace was after has been deleted - // => move whitespace to before first deleted line - this._afterLineNumbers[i] = fromLineNumber - 1; - } else if (afterLineNumber > toLineNumber) { - // The line this whitespace was after has been moved up - // => move whitespace up - this._afterLineNumbers[i] -= (toLineNumber - fromLineNumber + 1); - } - } - } - - /** - * Notify the computer that lines have been inserted (a continuous zone of lines). - * This gives it a chance to update `afterLineNumber` for whitespaces, giving the "sticky" characteristic. - * - * @param fromLineNumber The line number at which the insertion started, inclusive - * @param toLineNumber The line number at which the insertion ended, inclusive. - */ - public onLinesInserted(fromLineNumber: number, toLineNumber: number): void { - fromLineNumber = fromLineNumber | 0; - toLineNumber = toLineNumber | 0; - - for (let i = 0, len = this._afterLineNumbers.length; i < len; i++) { - let afterLineNumber = this._afterLineNumbers[i]; - - if (fromLineNumber <= afterLineNumber) { - this._afterLineNumbers[i] += (toLineNumber - fromLineNumber + 1); - } - } - } - - /** - * Get the sum of all the whitespaces. - */ - public getTotalHeight(): number { - if (this._heights.length === 0) { - return 0; - } - return this.getAccumulatedHeight(this._heights.length - 1); - } - - /** - * Return the sum of the heights of the whitespaces at [0..index]. - * This includes the whitespace at `index`. - * - * @param index The index of the whitespace. - * @return The sum of the heights of all whitespaces before the one at `index`, including the one at `index`. - */ - public getAccumulatedHeight(index: number): number { - index = index | 0; - - let startIndex = Math.max(0, this._prefixSumValidIndex + 1); - if (startIndex === 0) { - this._prefixSum[0] = this._heights[0]; - startIndex++; - } - - for (let i = startIndex; i <= index; i++) { - this._prefixSum[i] = this._prefixSum[i - 1] + this._heights[i]; - } - this._prefixSumValidIndex = Math.max(this._prefixSumValidIndex, index); - return this._prefixSum[index]; - } - - /** - * Find all whitespaces with `afterLineNumber` < `lineNumber` and return the sum of their heights. - * - * @param lineNumber The line number whitespaces should be before. - * @return The sum of the heights of the whitespaces before `lineNumber`. - */ - public getAccumulatedHeightBeforeLineNumber(lineNumber: number): number { - lineNumber = lineNumber | 0; - - let lastWhitespaceBeforeLineNumber = this._findLastWhitespaceBeforeLineNumber(lineNumber); - - if (lastWhitespaceBeforeLineNumber === -1) { - return 0; - } - - return this.getAccumulatedHeight(lastWhitespaceBeforeLineNumber); - } - - private _findLastWhitespaceBeforeLineNumber(lineNumber: number): number { - lineNumber = lineNumber | 0; - - // Find the whitespace before line number - let afterLineNumbers = this._afterLineNumbers; - let low = 0; - let high = afterLineNumbers.length - 1; - - while (low <= high) { - let delta = (high - low) | 0; - let halfDelta = (delta / 2) | 0; - let mid = (low + halfDelta) | 0; - - if (afterLineNumbers[mid] < lineNumber) { - if (mid + 1 >= afterLineNumbers.length || afterLineNumbers[mid + 1] >= lineNumber) { - return mid; - } else { - low = (mid + 1) | 0; - } - } else { - high = (mid - 1) | 0; - } - } - - return -1; - } - - private _findFirstWhitespaceAfterLineNumber(lineNumber: number): number { - lineNumber = lineNumber | 0; - - let lastWhitespaceBeforeLineNumber = this._findLastWhitespaceBeforeLineNumber(lineNumber); - let firstWhitespaceAfterLineNumber = lastWhitespaceBeforeLineNumber + 1; - - if (firstWhitespaceAfterLineNumber < this._heights.length) { - return firstWhitespaceAfterLineNumber; - } - - return -1; - } - - /** - * Find the index of the first whitespace which has `afterLineNumber` >= `lineNumber`. - * @return The index of the first whitespace with `afterLineNumber` >= `lineNumber` or -1 if no whitespace is found. - */ - public getFirstWhitespaceIndexAfterLineNumber(lineNumber: number): number { - lineNumber = lineNumber | 0; - - return this._findFirstWhitespaceAfterLineNumber(lineNumber); - } - - /** - * The number of whitespaces. - */ - public getCount(): number { - return this._heights.length; - } - - /** - * The maximum min width for all whitespaces. - */ - public getMinWidth(): number { - if (this._minWidth === -1) { - let minWidth = 0; - for (let i = 0, len = this._minWidths.length; i < len; i++) { - minWidth = Math.max(minWidth, this._minWidths[i]); - } - this._minWidth = minWidth; - } - return this._minWidth; - } - - /** - * Get the `afterLineNumber` for whitespace at index `index`. - * - * @param index The index of the whitespace. - * @return `afterLineNumber` of whitespace at `index`. - */ - public getAfterLineNumberForWhitespaceIndex(index: number): number { - index = index | 0; - - return this._afterLineNumbers[index]; - } - - /** - * Get the `id` for whitespace at index `index`. - * - * @param index The index of the whitespace. - * @return `id` of whitespace at `index`. - */ - public getIdForWhitespaceIndex(index: number): string { - index = index | 0; - - return this._ids[index]; - } - - /** - * Get the `height` for whitespace at index `index`. - * - * @param index The index of the whitespace. - * @return `height` of whitespace at `index`. - */ - public getHeightForWhitespaceIndex(index: number): number { - index = index | 0; - - return this._heights[index]; - } - - /** - * Get all whitespaces. - */ - public getWhitespaces(deviceLineHeight: number): IEditorWhitespace[] { - deviceLineHeight = deviceLineHeight | 0; - - let result: IEditorWhitespace[] = []; - for (let i = 0; i < this._heights.length; i++) { - result.push({ - id: this._ids[i], - afterLineNumber: this._afterLineNumbers[i], - heightInLines: this._heights[i] / deviceLineHeight - }); - } - return result; - } -} diff --git a/src/vs/editor/common/viewModel/splitLinesCollection.ts b/src/vs/editor/common/viewModel/splitLinesCollection.ts index 93e435b15b38..88fd11546369 100644 --- a/src/vs/editor/common/viewModel/splitLinesCollection.ts +++ b/src/vs/editor/common/viewModel/splitLinesCollection.ts @@ -129,9 +129,7 @@ export class CoordinatesConverter implements ICoordinatesConverter { } public convertModelRangeToViewRange(modelRange: Range): Range { - let start = this._lines.convertModelPositionToViewPosition(modelRange.startLineNumber, modelRange.startColumn); - let end = this._lines.convertModelPositionToViewPosition(modelRange.endLineNumber, modelRange.endColumn); - return new Range(start.lineNumber, start.column, end.lineNumber, end.column); + return this._lines.convertModelRangeToViewRange(modelRange); } public modelPositionIsVisible(modelPosition: Position): boolean { @@ -737,9 +735,9 @@ export class SplitLinesCollection implements IViewModelLinesCollection { public convertModelPositionToViewPosition(_modelLineNumber: number, _modelColumn: number): Position { this._ensureValidState(); - let validPosition = this.model.validatePosition(new Position(_modelLineNumber, _modelColumn)); - let inputLineNumber = validPosition.lineNumber; - let inputColumn = validPosition.column; + const validPosition = this.model.validatePosition(new Position(_modelLineNumber, _modelColumn)); + const inputLineNumber = validPosition.lineNumber; + const inputColumn = validPosition.column; let lineIndex = inputLineNumber - 1, lineIndexChanged = false; while (lineIndex > 0 && !this.lines[lineIndex].isVisible()) { @@ -751,7 +749,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection { // console.log('in -> out ' + inputLineNumber + ',' + inputColumn + ' ===> ' + 1 + ',' + 1); return new Position(1, 1); } - let deltaLineNumber = 1 + (lineIndex === 0 ? 0 : this.prefixSumComputer.getAccumulatedValue(lineIndex - 1)); + const deltaLineNumber = 1 + (lineIndex === 0 ? 0 : this.prefixSumComputer.getAccumulatedValue(lineIndex - 1)); let r: Position; if (lineIndexChanged) { @@ -764,6 +762,19 @@ export class SplitLinesCollection implements IViewModelLinesCollection { return r; } + public convertModelRangeToViewRange(modelRange: Range): Range { + let start = this.convertModelPositionToViewPosition(modelRange.startLineNumber, modelRange.startColumn); + let end = this.convertModelPositionToViewPosition(modelRange.endLineNumber, modelRange.endColumn); + if (modelRange.startLineNumber === modelRange.endLineNumber && start.lineNumber !== end.lineNumber) { + // This is a single line range that ends up taking more lines due to wrapping + if (end.column === this.getViewLineMinColumn(end.lineNumber)) { + // the end column lands on the first column of the next line + return new Range(start.lineNumber, start.column, end.lineNumber - 1, this.getViewLineMaxColumn(end.lineNumber - 1)); + } + } + return new Range(start.lineNumber, start.column, end.lineNumber, end.column); + } + private _getViewLineNumberForModelPosition(inputLineNumber: number, inputColumn: number): number { let lineIndex = inputLineNumber - 1; if (this.lines[lineIndex].isVisible()) { diff --git a/src/vs/editor/common/viewModel/viewModel.ts b/src/vs/editor/common/viewModel/viewModel.ts index 18be24c1eba6..2aaa893db170 100644 --- a/src/vs/editor/common/viewModel/viewModel.ts +++ b/src/vs/editor/common/viewModel/viewModel.ts @@ -13,7 +13,7 @@ import { INewScrollPosition } from 'vs/editor/common/editorCommon'; import { EndOfLinePreference, IActiveIndentGuideInfo, IModelDecorationOptions, TextModelResolvedOptions } from 'vs/editor/common/model'; import { IViewEventListener } from 'vs/editor/common/view/viewEvents'; import { IPartialViewLinesViewportData } from 'vs/editor/common/viewLayout/viewLinesViewportData'; -import { IEditorWhitespace } from 'vs/editor/common/viewLayout/whitespaceComputer'; +import { IEditorWhitespace, IWhitespaceChangeAccessor } from 'vs/editor/common/viewLayout/linesLayout'; import { ITheme } from 'vs/platform/theme/common/themeService'; export interface IViewWhitespaceViewportData { @@ -69,20 +69,8 @@ export interface IViewLayout { getWhitespaceAtVerticalOffset(verticalOffset: number): IViewWhitespaceViewportData | null; // --------------- Begin vertical whitespace management + changeWhitespace(callback: (accessor: IWhitespaceChangeAccessor) => T): T; - /** - * Reserve rendering space. - * @return an identifier that can be later used to remove or change the whitespace. - */ - addWhitespace(afterLineNumber: number, ordinal: number, height: number, minWidth: number): string; - /** - * Change the properties of a whitespace. - */ - changeWhitespace(id: string, newAfterLineNumber: number, newHeight: number): boolean; - /** - * Remove rendering space - */ - removeWhitespace(id: string): boolean; /** * Get the layout information for whitespaces currently in the viewport */ diff --git a/src/vs/editor/contrib/clipboard/clipboard.ts b/src/vs/editor/contrib/clipboard/clipboard.ts index 6ab11c4f69f6..8f573c6efc03 100644 --- a/src/vs/editor/contrib/clipboard/clipboard.ts +++ b/src/vs/editor/contrib/clipboard/clipboard.ts @@ -77,11 +77,11 @@ class ExecCommandCutAction extends ExecCommandAction { alias: 'Cut', precondition: EditorContextKeys.writable, kbOpts: kbOpts, - menuOpts: { + contextMenuOpts: { group: CLIPBOARD_CONTEXT_MENU_GROUP, order: 1 }, - menubarOpts: { + menuOpts: { menuId: MenuId.MenubarEditMenu, group: '2_ccp', title: nls.localize({ key: 'miCut', comment: ['&& denotes a mnemonic'] }, "Cu&&t"), @@ -126,11 +126,11 @@ class ExecCommandCopyAction extends ExecCommandAction { alias: 'Copy', precondition: undefined, kbOpts: kbOpts, - menuOpts: { + contextMenuOpts: { group: CLIPBOARD_CONTEXT_MENU_GROUP, order: 2 }, - menubarOpts: { + menuOpts: { menuId: MenuId.MenubarEditMenu, group: '2_ccp', title: nls.localize({ key: 'miCopy', comment: ['&& denotes a mnemonic'] }, "&&Copy"), @@ -175,11 +175,11 @@ class ExecCommandPasteAction extends ExecCommandAction { alias: 'Paste', precondition: EditorContextKeys.writable, kbOpts: kbOpts, - menuOpts: { + contextMenuOpts: { group: CLIPBOARD_CONTEXT_MENU_GROUP, order: 3 }, - menubarOpts: { + menuOpts: { menuId: MenuId.MenubarEditMenu, group: '2_ccp', title: nls.localize({ key: 'miPaste', comment: ['&& denotes a mnemonic'] }, "&&Paste"), diff --git a/src/vs/editor/contrib/codeAction/codeAction.ts b/src/vs/editor/contrib/codeAction/codeAction.ts index 31c6373e08e2..408e429a3287 100644 --- a/src/vs/editor/contrib/codeAction/codeAction.ts +++ b/src/vs/editor/contrib/codeAction/codeAction.ts @@ -13,12 +13,19 @@ import { Selection } from 'vs/editor/common/core/selection'; import { ITextModel } from 'vs/editor/common/model'; import { CodeAction, CodeActionContext, CodeActionProviderRegistry, CodeActionTrigger as CodeActionTriggerKind } from 'vs/editor/common/modes'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { CodeActionFilter, CodeActionKind, CodeActionTrigger, filtersAction, mayIncludeActionsOfKind } from './codeActionTrigger'; +import { CodeActionFilter, CodeActionKind, CodeActionTrigger, filtersAction, mayIncludeActionsOfKind } from './types'; import { TextModelCancellationTokenSource } from 'vs/editor/browser/core/editorState'; import { DisposableStore, IDisposable, Disposable } from 'vs/base/common/lifecycle'; +export const codeActionCommandId = 'editor.action.codeAction'; +export const refactorCommandId = 'editor.action.refactor'; +export const sourceActionCommandId = 'editor.action.sourceAction'; +export const organizeImportsCommandId = 'editor.action.organizeImports'; +export const fixAllCommandId = 'editor.action.fixAll'; + export interface CodeActionSet extends IDisposable { - readonly actions: readonly CodeAction[]; + readonly validActions: readonly CodeAction[]; + readonly allActions: readonly CodeAction[]; readonly hasAutoFix: boolean; } @@ -38,16 +45,18 @@ class ManagedCodeActionSet extends Disposable implements CodeActionSet { } } - public readonly actions: readonly CodeAction[]; + public readonly validActions: readonly CodeAction[]; + public readonly allActions: readonly CodeAction[]; public constructor(actions: readonly CodeAction[], disposables: DisposableStore) { super(); this._register(disposables); - this.actions = mergeSort([...actions], ManagedCodeActionSet.codeActionsComparator); + this.allActions = mergeSort([...actions], ManagedCodeActionSet.codeActionsComparator); + this.validActions = this.allActions.filter(action => !action.disabled); } public get hasAutoFix() { - return this.actions.some(fix => !!fix.kind && CodeActionKind.QuickFix.contains(new CodeActionKind(fix.kind)) && !!fix.isPreferred); + return this.validActions.some(fix => !!fix.kind && CodeActionKind.QuickFix.contains(new CodeActionKind(fix.kind)) && !!fix.isPreferred); } } @@ -60,7 +69,7 @@ export function getCodeActions( const filter = trigger.filter || {}; const codeActionContext: CodeActionContext = { - only: filter.kind ? filter.kind.value : undefined, + only: filter.include?.value, trigger: trigger.type === 'manual' ? CodeActionTriggerKind.Manual : CodeActionTriggerKind.Automatic }; @@ -68,21 +77,21 @@ export function getCodeActions( const providers = getCodeActionProviders(model, filter); const disposables = new DisposableStore(); - const promises = providers.map(provider => { - return Promise.resolve(provider.provideCodeActions(model, rangeOrSelection, codeActionContext, cts.token)).then(providedCodeActions => { + const promises = providers.map(async provider => { + try { + const providedCodeActions = await provider.provideCodeActions(model, rangeOrSelection, codeActionContext, cts.token); if (cts.token.isCancellationRequested || !providedCodeActions) { return []; } disposables.add(providedCodeActions); return providedCodeActions.actions.filter(action => action && filtersAction(filter, action)); - }, (err): CodeAction[] => { + } catch (err) { if (isPromiseCanceledError(err)) { throw err; } - onUnexpectedExternalError(err); return []; - }); + } }); const listener = CodeActionProviderRegistry.onDidChange(() => { @@ -140,9 +149,9 @@ registerLanguageCommand('_executeCodeActionProvider', async function (accessor, const codeActionSet = await getCodeActions( model, validatedRangeOrSelection, - { type: 'manual', filter: { includeSourceActions: true, kind: kind && kind.value ? new CodeActionKind(kind.value) : undefined } }, + { type: 'manual', filter: { includeSourceActions: true, include: kind && kind.value ? new CodeActionKind(kind.value) : undefined } }, CancellationToken.None); setTimeout(() => codeActionSet.dispose(), 100); - return codeActionSet.actions; + return codeActionSet.validActions; }); diff --git a/src/vs/editor/contrib/codeAction/codeActionCommands.ts b/src/vs/editor/contrib/codeAction/codeActionCommands.ts index 71902363c6ea..f984998d89d0 100644 --- a/src/vs/editor/contrib/codeAction/codeActionCommands.ts +++ b/src/vs/editor/contrib/codeAction/codeActionCommands.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IAnchor } from 'vs/base/browser/ui/contextview/contextview'; +import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Lazy } from 'vs/base/common/lazy'; import { Disposable } from 'vs/base/common/lifecycle'; @@ -15,7 +16,7 @@ import { IPosition } from 'vs/editor/common/core/position'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { CodeAction } from 'vs/editor/common/modes'; -import { CodeActionSet } from 'vs/editor/contrib/codeAction/codeAction'; +import { CodeActionSet, refactorCommandId, sourceActionCommandId, codeActionCommandId, organizeImportsCommandId, fixAllCommandId } from 'vs/editor/contrib/codeAction/codeAction'; import { CodeActionUi } from 'vs/editor/contrib/codeAction/codeActionUi'; import { MessageController } from 'vs/editor/contrib/message/messageController'; import * as nls from 'vs/nls'; @@ -29,7 +30,7 @@ import { IMarkerService } from 'vs/platform/markers/common/markers'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IEditorProgressService } from 'vs/platform/progress/common/progress'; import { CodeActionModel, CodeActionsState, SUPPORTED_CODE_ACTIONS } from './codeActionModel'; -import { CodeActionAutoApply, CodeActionFilter, CodeActionKind, CodeActionTrigger } from './codeActionTrigger'; +import { CodeActionAutoApply, CodeActionFilter, CodeActionKind, CodeActionTrigger, CodeActionCommandArgs } from './types'; function contextKeyForSupportedActions(kind: CodeActionKind) { return ContextKeyExpr.regex( @@ -37,6 +38,33 @@ function contextKeyForSupportedActions(kind: CodeActionKind) { new RegExp('(\\s|^)' + escapeRegExpCharacters(kind.value) + '\\b')); } +const argsSchema: IJSONSchema = { + type: 'object', + required: ['kind'], + defaultSnippets: [{ body: { kind: '' } }], + properties: { + 'kind': { + type: 'string', + description: nls.localize('args.schema.kind', "Kind of the code action to run."), + }, + 'apply': { + type: 'string', + description: nls.localize('args.schema.apply', "Controls when the returned actions are applied."), + default: CodeActionAutoApply.IfSingle, + enum: [CodeActionAutoApply.First, CodeActionAutoApply.IfSingle, CodeActionAutoApply.Never], + enumDescriptions: [ + nls.localize('args.schema.apply.first', "Always apply the first returned code action."), + nls.localize('args.schema.apply.ifSingle', "Apply the first returned code action if it is the only one."), + nls.localize('args.schema.apply.never', "Do not apply the returned code actions."), + ] + }, + 'preferred': { + type: 'boolean', + default: false, + description: nls.localize('args.schema.preferred', "Controls if only preferred code actions should be returned."), + } + } +}; export class QuickFixController extends Disposable implements IEditorContribution { @@ -78,7 +106,7 @@ export class QuickFixController extends Disposable implements IEditorContributio } } } - }, contextMenuService, keybindingService)) + }, this._instantiationService)) ); } @@ -87,7 +115,7 @@ export class QuickFixController extends Disposable implements IEditorContributio } public showCodeActions(actions: CodeActionSet, at: IAnchor | IPosition) { - return this._ui.getValue().showCodeActionList(actions, at); + return this._ui.getValue().showCodeActionList(actions, at, { includeDisabledActions: false }); } public manualTriggerAtCurrentPosition( @@ -185,85 +213,34 @@ export class QuickFixAction extends EditorAction { } } - -class CodeActionCommandArgs { - public static fromUser(arg: any, defaults: { kind: CodeActionKind, apply: CodeActionAutoApply }): CodeActionCommandArgs { - if (!arg || typeof arg !== 'object') { - return new CodeActionCommandArgs(defaults.kind, defaults.apply, false); - } - return new CodeActionCommandArgs( - CodeActionCommandArgs.getKindFromUser(arg, defaults.kind), - CodeActionCommandArgs.getApplyFromUser(arg, defaults.apply), - CodeActionCommandArgs.getPreferredUser(arg)); - } - - private static getApplyFromUser(arg: any, defaultAutoApply: CodeActionAutoApply) { - switch (typeof arg.apply === 'string' ? arg.apply.toLowerCase() : '') { - case 'first': return CodeActionAutoApply.First; - case 'never': return CodeActionAutoApply.Never; - case 'ifsingle': return CodeActionAutoApply.IfSingle; - default: return defaultAutoApply; - } - } - - private static getKindFromUser(arg: any, defaultKind: CodeActionKind) { - return typeof arg.kind === 'string' - ? new CodeActionKind(arg.kind) - : defaultKind; - } - - private static getPreferredUser(arg: any): boolean { - return typeof arg.preferred === 'boolean' - ? arg.preferred - : false; - } - - private constructor( - public readonly kind: CodeActionKind, - public readonly apply: CodeActionAutoApply, - public readonly preferred: boolean, - ) { } -} - export class CodeActionCommand extends EditorCommand { - static readonly Id = 'editor.action.codeAction'; - constructor() { super({ - id: CodeActionCommand.Id, + id: codeActionCommandId, precondition: ContextKeyExpr.and(EditorContextKeys.writable, EditorContextKeys.hasCodeActionsProvider), description: { - description: `Trigger a code action`, - args: [{ - name: 'args', - schema: { - 'type': 'object', - 'required': ['kind'], - 'properties': { - 'kind': { - 'type': 'string' - }, - 'apply': { - 'type': 'string', - 'default': 'ifSingle', - 'enum': ['first', 'ifSingle', 'never'] - } - } - } - }] + description: 'Trigger a code action', + args: [{ name: 'args', schema: argsSchema, }] } }); } - public runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor, userArg: any) { - const args = CodeActionCommandArgs.fromUser(userArg, { + public runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor, userArgs: any) { + const args = CodeActionCommandArgs.fromUser(userArgs, { kind: CodeActionKind.Empty, apply: CodeActionAutoApply.IfSingle, }); - return triggerCodeActionsForEditorSelection(editor, nls.localize('editor.action.quickFix.noneMessage', "No code actions available"), + return triggerCodeActionsForEditorSelection(editor, + typeof userArgs?.kind === 'string' + ? args.preferred + ? nls.localize('editor.action.codeAction.noneMessage.preferred.kind', "No preferred code actions for '{0}' available", userArgs.kind) + : nls.localize('editor.action.codeAction.noneMessage.kind', "No code actions for '{0}' available", userArgs.kind) + : args.preferred + ? nls.localize('editor.action.codeAction.noneMessage.preferred', "No preferred code actions available") + : nls.localize('editor.action.codeAction.noneMessage', "No code actions available"), { - kind: args.kind, + include: args.kind, includeSourceActions: true, onlyIncludePreferredActions: args.preferred, }, @@ -274,11 +251,9 @@ export class CodeActionCommand extends EditorCommand { export class RefactorAction extends EditorAction { - static readonly Id = 'editor.action.refactor'; - constructor() { super({ - id: RefactorAction.Id, + id: refactorCommandId, label: nls.localize('refactor.label', "Refactor..."), alias: 'Refactor...', precondition: ContextKeyExpr.and(EditorContextKeys.writable, EditorContextKeys.hasCodeActionsProvider), @@ -290,7 +265,7 @@ export class RefactorAction extends EditorAction { }, weight: KeybindingWeight.EditorContrib }, - menuOpts: { + contextMenuOpts: { group: '1_modification', order: 2, when: ContextKeyExpr.and( @@ -299,53 +274,41 @@ export class RefactorAction extends EditorAction { }, description: { description: 'Refactor...', - args: [{ - name: 'args', - schema: { - 'type': 'object', - 'properties': { - 'kind': { - 'type': 'string' - }, - 'apply': { - 'type': 'string', - 'default': 'never', - 'enum': ['first', 'ifSingle', 'never'] - } - } - } - }] + args: [{ name: 'args', schema: argsSchema }] } }); } - public run(_accessor: ServicesAccessor, editor: ICodeEditor, userArg: any): void { - const args = CodeActionCommandArgs.fromUser(userArg, { + public run(_accessor: ServicesAccessor, editor: ICodeEditor, userArgs: any): void { + const args = CodeActionCommandArgs.fromUser(userArgs, { kind: CodeActionKind.Refactor, apply: CodeActionAutoApply.Never }); return triggerCodeActionsForEditorSelection(editor, - nls.localize('editor.action.refactor.noneMessage', "No refactorings available"), + typeof userArgs?.kind === 'string' + ? args.preferred + ? nls.localize('editor.action.refactor.noneMessage.preferred.kind', "No preferred refactorings for '{0}' available", userArgs.kind) + : nls.localize('editor.action.refactor.noneMessage.kind', "No refactorings for '{0}' available", userArgs.kind) + : args.preferred + ? nls.localize('editor.action.refactor.noneMessage.preferred', "No preferred refactorings available") + : nls.localize('editor.action.refactor.noneMessage', "No refactorings available"), { - kind: CodeActionKind.Refactor.contains(args.kind) ? args.kind : CodeActionKind.Empty, + include: CodeActionKind.Refactor.contains(args.kind) ? args.kind : CodeActionKind.None, onlyIncludePreferredActions: args.preferred, }, args.apply); } } - export class SourceAction extends EditorAction { - static readonly Id = 'editor.action.sourceAction'; - constructor() { super({ - id: SourceAction.Id, + id: sourceActionCommandId, label: nls.localize('source.label', "Source Action..."), alias: 'Source Action...', precondition: ContextKeyExpr.and(EditorContextKeys.writable, EditorContextKeys.hasCodeActionsProvider), - menuOpts: { + contextMenuOpts: { group: '1_modification', order: 2.1, when: ContextKeyExpr.and( @@ -354,35 +317,26 @@ export class SourceAction extends EditorAction { }, description: { description: 'Source Action...', - args: [{ - name: 'args', - schema: { - 'type': 'object', - 'properties': { - 'kind': { - 'type': 'string' - }, - 'apply': { - 'type': 'string', - 'default': 'never', - 'enum': ['first', 'ifSingle', 'never'] - } - } - } - }] + args: [{ name: 'args', schema: argsSchema }] } }); } - public run(_accessor: ServicesAccessor, editor: ICodeEditor, userArg: any): void { - const args = CodeActionCommandArgs.fromUser(userArg, { + public run(_accessor: ServicesAccessor, editor: ICodeEditor, userArgs: any): void { + const args = CodeActionCommandArgs.fromUser(userArgs, { kind: CodeActionKind.Source, apply: CodeActionAutoApply.Never }); return triggerCodeActionsForEditorSelection(editor, - nls.localize('editor.action.source.noneMessage', "No source actions available"), + typeof userArgs?.kind === 'string' + ? args.preferred + ? nls.localize('editor.action.source.noneMessage.preferred.kind', "No preferred source actions for '{0}' available", userArgs.kind) + : nls.localize('editor.action.source.noneMessage.kind', "No source actions for '{0}' available", userArgs.kind) + : args.preferred + ? nls.localize('editor.action.source.noneMessage.preferred', "No preferred source actions available") + : nls.localize('editor.action.source.noneMessage', "No source actions available"), { - kind: CodeActionKind.Source.contains(args.kind) ? args.kind : CodeActionKind.Empty, + include: CodeActionKind.Source.contains(args.kind) ? args.kind : CodeActionKind.None, includeSourceActions: true, onlyIncludePreferredActions: args.preferred, }, @@ -392,11 +346,9 @@ export class SourceAction extends EditorAction { export class OrganizeImportsAction extends EditorAction { - static readonly Id = 'editor.action.organizeImports'; - constructor() { super({ - id: OrganizeImportsAction.Id, + id: organizeImportsCommandId, label: nls.localize('organizeImports.label', "Organize Imports"), alias: 'Organize Imports', precondition: ContextKeyExpr.and( @@ -406,25 +358,23 @@ export class OrganizeImportsAction extends EditorAction { kbExpr: EditorContextKeys.editorTextFocus, primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_O, weight: KeybindingWeight.EditorContrib - } + }, }); } public run(_accessor: ServicesAccessor, editor: ICodeEditor): void { return triggerCodeActionsForEditorSelection(editor, nls.localize('editor.action.organize.noneMessage', "No organize imports action available"), - { kind: CodeActionKind.SourceOrganizeImports, includeSourceActions: true }, + { include: CodeActionKind.SourceOrganizeImports, includeSourceActions: true }, CodeActionAutoApply.IfSingle); } } export class FixAllAction extends EditorAction { - static readonly Id = 'editor.action.fixAll'; - constructor() { super({ - id: FixAllAction.Id, + id: fixAllCommandId, label: nls.localize('fixAll.label', "Fix All"), alias: 'Fix All', precondition: ContextKeyExpr.and( @@ -436,7 +386,7 @@ export class FixAllAction extends EditorAction { public run(_accessor: ServicesAccessor, editor: ICodeEditor): void { return triggerCodeActionsForEditorSelection(editor, nls.localize('fixAll.noneMessage', "No fix all action available"), - { kind: CodeActionKind.SourceFixAll, includeSourceActions: true }, + { include: CodeActionKind.SourceFixAll, includeSourceActions: true }, CodeActionAutoApply.IfSingle); } } @@ -468,7 +418,7 @@ export class AutoFixAction extends EditorAction { return triggerCodeActionsForEditorSelection(editor, nls.localize('editor.action.autoFix.noneMessage', "No auto fixes available"), { - kind: CodeActionKind.QuickFix, + include: CodeActionKind.QuickFix, onlyIncludePreferredActions: true }, CodeActionAutoApply.IfSingle); diff --git a/src/vs/editor/contrib/codeAction/codeActionMenu.ts b/src/vs/editor/contrib/codeAction/codeActionMenu.ts new file mode 100644 index 000000000000..b30ec28bc72b --- /dev/null +++ b/src/vs/editor/contrib/codeAction/codeActionMenu.ts @@ -0,0 +1,195 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { getDomNodePagePosition } from 'vs/base/browser/dom'; +import { IAnchor } from 'vs/base/browser/ui/contextview/contextview'; +import { Action } from 'vs/base/common/actions'; +import { canceled } from 'vs/base/common/errors'; +import { ResolvedKeybinding } from 'vs/base/common/keyCodes'; +import { Lazy } from 'vs/base/common/lazy'; +import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { IPosition, Position } from 'vs/editor/common/core/position'; +import { ScrollType } from 'vs/editor/common/editorCommon'; +import { CodeAction } from 'vs/editor/common/modes'; +import { CodeActionSet, refactorCommandId, sourceActionCommandId, codeActionCommandId, organizeImportsCommandId, fixAllCommandId } from 'vs/editor/contrib/codeAction/codeAction'; +import { CodeActionAutoApply, CodeActionCommandArgs, CodeActionKind } from 'vs/editor/contrib/codeAction/types'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; + +interface CodeActionWidgetDelegate { + onSelectCodeAction: (action: CodeAction) => Promise; +} + +interface ResolveCodeActionKeybinding { + readonly kind: CodeActionKind; + readonly preferred: boolean; + readonly resolvedKeybinding: ResolvedKeybinding; +} + +class CodeActionAction extends Action { + constructor( + public readonly action: CodeAction, + callback: () => Promise, + ) { + super(action.command ? action.command.id : action.title, action.title, undefined, !action.disabled, callback); + } +} + +export interface CodeActionShowOptions { + readonly includeDisabledActions: boolean; +} + +export class CodeActionMenu extends Disposable { + + private _visible: boolean = false; + private readonly _showingActions = this._register(new MutableDisposable()); + + private readonly _keybindingResolver: CodeActionKeybindingResolver; + + constructor( + private readonly _editor: ICodeEditor, + private readonly _delegate: CodeActionWidgetDelegate, + @IContextMenuService private readonly _contextMenuService: IContextMenuService, + @IKeybindingService keybindingService: IKeybindingService, + ) { + super(); + + this._keybindingResolver = new CodeActionKeybindingResolver({ + getKeybindings: () => keybindingService.getKeybindings() + }); + } + + get isVisible(): boolean { + return this._visible; + } + + public async show(codeActions: CodeActionSet, at: IAnchor | IPosition, options: CodeActionShowOptions): Promise { + const actionsToShow = options.includeDisabledActions ? codeActions.allActions : codeActions.validActions; + if (!actionsToShow.length) { + this._visible = false; + return; + } + + if (!this._editor.getDomNode()) { + // cancel when editor went off-dom + this._visible = false; + throw canceled(); + } + + this._visible = true; + this._showingActions.value = codeActions; + + const menuActions = actionsToShow.map(action => + new CodeActionAction(action, () => this._delegate.onSelectCodeAction(action))); + + const anchor = Position.isIPosition(at) ? this._toCoords(at) : at || { x: 0, y: 0 }; + const resolver = this._keybindingResolver.getResolver(); + + this._contextMenuService.showContextMenu({ + getAnchor: () => anchor, + getActions: () => menuActions, + onHide: () => { + this._visible = false; + this._editor.focus(); + }, + autoSelectFirstItem: true, + getKeyBinding: action => action instanceof CodeActionAction ? resolver(action.action) : undefined, + }); + } + + private _toCoords(position: IPosition): { x: number, y: number } { + if (!this._editor.hasModel()) { + return { x: 0, y: 0 }; + } + this._editor.revealPosition(position, ScrollType.Immediate); + this._editor.render(); + + // Translate to absolute editor position + const cursorCoords = this._editor.getScrolledVisiblePosition(position); + const editorCoords = getDomNodePagePosition(this._editor.getDomNode()); + const x = editorCoords.left + cursorCoords.left; + const y = editorCoords.top + cursorCoords.top + cursorCoords.height; + + return { x, y }; + } +} + +export class CodeActionKeybindingResolver { + private static readonly codeActionCommands: readonly string[] = [ + refactorCommandId, + codeActionCommandId, + sourceActionCommandId, + organizeImportsCommandId, + fixAllCommandId + ]; + + constructor( + private readonly _keybindingProvider: { + getKeybindings(): readonly ResolvedKeybindingItem[], + }, + ) { } + + public getResolver(): (action: CodeAction) => ResolvedKeybinding | undefined { + // Lazy since we may not actually ever read the value + const allCodeActionBindings = new Lazy(() => + this._keybindingProvider.getKeybindings() + .filter(item => CodeActionKeybindingResolver.codeActionCommands.indexOf(item.command!) >= 0) + .filter(item => item.resolvedKeybinding) + .map((item): ResolveCodeActionKeybinding => { + // Special case these commands since they come built-in with VS Code and don't use 'commandArgs' + let commandArgs = item.commandArgs; + if (item.command === organizeImportsCommandId) { + commandArgs = { kind: CodeActionKind.SourceOrganizeImports.value }; + } else if (item.command === fixAllCommandId) { + commandArgs = { kind: CodeActionKind.SourceFixAll.value }; + } + + return { + resolvedKeybinding: item.resolvedKeybinding!, + ...CodeActionCommandArgs.fromUser(commandArgs, { + kind: CodeActionKind.None, + apply: CodeActionAutoApply.Never + }) + }; + })); + + return (action) => { + if (action.kind) { + const binding = this.bestKeybindingForCodeAction(action, allCodeActionBindings.getValue()); + return binding?.resolvedKeybinding; + } + return undefined; + }; + } + + private bestKeybindingForCodeAction( + action: CodeAction, + candidates: readonly ResolveCodeActionKeybinding[], + ): ResolveCodeActionKeybinding | undefined { + if (!action.kind) { + return undefined; + } + const kind = new CodeActionKind(action.kind); + + return candidates + .filter(candidate => candidate.kind.contains(kind)) + .filter(candidate => { + if (candidate.preferred) { + // If the candidate keybinding only applies to preferred actions, the this action must also be preferred + return action.isPreferred; + } + return true; + }) + .reduceRight((currentBest, candidate) => { + if (!currentBest) { + return candidate; + } + // Select the more specific binding + return currentBest.kind.contains(candidate.kind) ? candidate : currentBest; + }, undefined as ResolveCodeActionKeybinding | undefined); + } +} diff --git a/src/vs/editor/contrib/codeAction/codeActionModel.ts b/src/vs/editor/contrib/codeAction/codeActionModel.ts index 520eeee395d9..b6203ae7f80a 100644 --- a/src/vs/editor/contrib/codeAction/codeActionModel.ts +++ b/src/vs/editor/contrib/codeAction/codeActionModel.ts @@ -16,7 +16,7 @@ import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/cont import { IMarkerService } from 'vs/platform/markers/common/markers'; import { IEditorProgressService } from 'vs/platform/progress/common/progress'; import { getCodeActions, CodeActionSet } from './codeAction'; -import { CodeActionTrigger } from './codeActionTrigger'; +import { CodeActionTrigger } from './types'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { isEqual } from 'vs/base/common/resources'; @@ -73,10 +73,12 @@ class CodeActionOracle extends Disposable { return undefined; } for (const marker of this._markerService.read({ resource: model.uri })) { - if (Range.intersectRanges(marker, selection)) { - return Range.lift(marker); + const markerRange = model.validateRange(marker); + if (Range.intersectRanges(markerRange, selection)) { + return Range.lift(markerRange); } } + return undefined; } @@ -140,7 +142,7 @@ export namespace CodeActionsState { Triggered, } - export const Empty = new class { readonly type = Type.Empty; }; + export const Empty = { type: Type.Empty } as const; export class Triggered { readonly type = Type.Triggered; diff --git a/src/vs/editor/contrib/codeAction/codeActionTrigger.ts b/src/vs/editor/contrib/codeAction/codeActionTrigger.ts deleted file mode 100644 index 382a4bea1ab3..000000000000 --- a/src/vs/editor/contrib/codeAction/codeActionTrigger.ts +++ /dev/null @@ -1,98 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { startsWith } from 'vs/base/common/strings'; -import { CodeAction } from 'vs/editor/common/modes'; -import { Position } from 'vs/editor/common/core/position'; - -export class CodeActionKind { - private static readonly sep = '.'; - - public static readonly Empty = new CodeActionKind(''); - public static readonly QuickFix = new CodeActionKind('quickfix'); - public static readonly Refactor = new CodeActionKind('refactor'); - public static readonly Source = new CodeActionKind('source'); - public static readonly SourceOrganizeImports = new CodeActionKind('source.organizeImports'); - public static readonly SourceFixAll = new CodeActionKind('source.fixAll'); - - constructor( - public readonly value: string - ) { } - - public equals(other: CodeActionKind): boolean { - return this.value === other.value; - } - - public contains(other: CodeActionKind): boolean { - return this.equals(other) || startsWith(other.value, this.value + CodeActionKind.sep); - } - - public intersects(other: CodeActionKind): boolean { - return this.contains(other) || other.contains(this); - } -} - -export const enum CodeActionAutoApply { - IfSingle, - First, - Never, -} - -export interface CodeActionFilter { - readonly kind?: CodeActionKind; - readonly includeSourceActions?: boolean; - readonly onlyIncludePreferredActions?: boolean; -} - -export function mayIncludeActionsOfKind(filter: CodeActionFilter, providedKind: CodeActionKind): boolean { - // A provided kind may be a subset or superset of our filtered kind. - if (filter.kind && !filter.kind.intersects(providedKind)) { - return false; - } - - // Don't return source actions unless they are explicitly requested - if (CodeActionKind.Source.contains(providedKind) && !filter.includeSourceActions) { - return false; - } - - return true; -} - - -export function filtersAction(filter: CodeActionFilter, action: CodeAction): boolean { - const actionKind = action.kind ? new CodeActionKind(action.kind) : undefined; - - // Filter out actions by kind - if (filter.kind) { - if (!actionKind || !filter.kind.contains(actionKind)) { - return false; - } - } - - // Don't return source actions unless they are explicitly requested - if (!filter.includeSourceActions) { - if (actionKind && CodeActionKind.Source.contains(actionKind)) { - return false; - } - } - - if (filter.onlyIncludePreferredActions) { - if (!action.isPreferred) { - return false; - } - } - - return true; -} - -export interface CodeActionTrigger { - readonly type: 'auto' | 'manual'; - readonly filter?: CodeActionFilter; - readonly autoApply?: CodeActionAutoApply; - readonly context?: { - readonly notAvailableMessage: string; - readonly position: Position; - }; -} \ No newline at end of file diff --git a/src/vs/editor/contrib/codeAction/codeActionUi.ts b/src/vs/editor/contrib/codeAction/codeActionUi.ts index 734f71f502c3..5aa397081427 100644 --- a/src/vs/editor/contrib/codeAction/codeActionUi.ts +++ b/src/vs/editor/contrib/codeAction/codeActionUi.ts @@ -3,25 +3,25 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { IAnchor } from 'vs/base/browser/ui/contextview/contextview'; +import { find } from 'vs/base/common/arrays'; import { onUnexpectedError } from 'vs/base/common/errors'; +import { Lazy } from 'vs/base/common/lazy'; import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { IPosition } from 'vs/editor/common/core/position'; import { CodeAction } from 'vs/editor/common/modes'; import { CodeActionSet } from 'vs/editor/contrib/codeAction/codeAction'; import { MessageController } from 'vs/editor/contrib/message/messageController'; -import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { CodeActionsState } from './codeActionModel'; -import { CodeActionAutoApply } from './codeActionTrigger'; -import { CodeActionWidget } from './codeActionWidget'; +import { CodeActionMenu, CodeActionShowOptions } from './codeActionMenu'; import { LightBulbWidget } from './lightBulbWidget'; -import { IPosition } from 'vs/editor/common/core/position'; -import { IAnchor } from 'vs/base/browser/ui/contextview/contextview'; -import { Lazy } from 'vs/base/common/lazy'; +import { CodeActionAutoApply, CodeActionTrigger } from './types'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; export class CodeActionUi extends Disposable { - private readonly _codeActionWidget: Lazy; + private readonly _codeActionWidget: Lazy; private readonly _lightBulbWidget: Lazy; private readonly _activeCodeActions = this._register(new MutableDisposable()); @@ -30,15 +30,14 @@ export class CodeActionUi extends Disposable { quickFixActionId: string, preferredFixActionId: string, private readonly delegate: { - applyCodeAction: (action: CodeAction, regtriggerAfterApply: boolean) => void + applyCodeAction: (action: CodeAction, regtriggerAfterApply: boolean) => Promise }, - @IContextMenuService contextMenuService: IContextMenuService, - @IKeybindingService keybindingService: IKeybindingService, + @IInstantiationService instantiationService: IInstantiationService, ) { super(); this._codeActionWidget = new Lazy(() => { - return this._register(new CodeActionWidget(this._editor, contextMenuService, { + return this._register(instantiationService.createInstance(CodeActionMenu, this._editor, { onSelectCodeAction: async (action) => { this.delegate.applyCodeAction(action, /* retrigger */ true); } @@ -46,17 +45,15 @@ export class CodeActionUi extends Disposable { }); this._lightBulbWidget = new Lazy(() => { - const widget = this._register(new LightBulbWidget(this._editor, quickFixActionId, preferredFixActionId, keybindingService)); - this._register(widget.onClick(this._handleLightBulbSelect, this)); + const widget = this._register(instantiationService.createInstance(LightBulbWidget, this._editor, quickFixActionId, preferredFixActionId)); + this._register(widget.onClick(e => this.showCodeActionList(e.actions, e, { includeDisabledActions: false }))); return widget; }); } public async update(newState: CodeActionsState.State): Promise { if (newState.type !== CodeActionsState.Type.Triggered) { - if (this._lightBulbWidget.hasValue()) { - this._lightBulbWidget.getValue().hide(); - } + this._lightBulbWidget.rawValue?.hide(); return; } @@ -70,29 +67,43 @@ export class CodeActionUi extends Disposable { this._lightBulbWidget.getValue().update(actions, newState.position); - if (!actions.actions.length && newState.trigger.context) { - MessageController.get(this._editor).showMessage(newState.trigger.context.notAvailableMessage, newState.trigger.context.position); - this._activeCodeActions.value = actions; - return; - } - if (newState.trigger.type === 'manual') { - if (newState.trigger.filter && newState.trigger.filter.kind) { - // Triggered for specific scope - if (actions.actions.length > 0) { - // Apply if we only have one action or requested autoApply - if (newState.trigger.autoApply === CodeActionAutoApply.First || (newState.trigger.autoApply === CodeActionAutoApply.IfSingle && actions.actions.length === 1)) { - try { - await this.delegate.applyCodeAction(actions.actions[0], false); - } finally { - actions.dispose(); - } + if (newState.trigger.filter?.include) { // Triggered for specific scope + // Check to see if we want to auto apply. + + const validActionToApply = this.tryGetValidActionToApply(newState.trigger, actions); + if (validActionToApply) { + try { + await this.delegate.applyCodeAction(validActionToApply, false); + } finally { + actions.dispose(); + } + return; + } + + // Check to see if there is an action that we would have applied were it not invalid + if (newState.trigger.context) { + const invalidAction = this.getInvalidActionThatWouldHaveBeenApplied(newState.trigger, actions); + if (invalidAction && invalidAction.disabled) { + MessageController.get(this._editor).showMessage(invalidAction.disabled, newState.trigger.context.position); + actions.dispose(); return; } } } + + const includeDisabledActions = !!newState.trigger.filter?.include; + if (newState.trigger.context) { + if (!actions.allActions.length || !includeDisabledActions && !actions.validActions.length) { + MessageController.get(this._editor).showMessage(newState.trigger.context.notAvailableMessage, newState.trigger.context.position); + this._activeCodeActions.value = actions; + actions.dispose(); + return; + } + } + this._activeCodeActions.value = actions; - this._codeActionWidget.getValue().show(actions, newState.position); + this._codeActionWidget.getValue().show(actions, newState.position, { includeDisabledActions }); } else { // auto magically triggered if (this._codeActionWidget.getValue().isVisible) { @@ -104,11 +115,35 @@ export class CodeActionUi extends Disposable { } } - public async showCodeActionList(actions: CodeActionSet, at?: IAnchor | IPosition): Promise { - this._codeActionWidget.getValue().show(actions, at); + private getInvalidActionThatWouldHaveBeenApplied(trigger: CodeActionTrigger, actions: CodeActionSet): CodeAction | undefined { + if (!actions.allActions.length) { + return undefined; + } + + if ((trigger.autoApply === CodeActionAutoApply.First && actions.validActions.length === 0) + || (trigger.autoApply === CodeActionAutoApply.IfSingle && actions.allActions.length === 1) + ) { + return find(actions.allActions, action => action.disabled); + } + + return undefined; + } + + private tryGetValidActionToApply(trigger: CodeActionTrigger, actions: CodeActionSet): CodeAction | undefined { + if (!actions.validActions.length) { + return undefined; + } + + if ((trigger.autoApply === CodeActionAutoApply.First && actions.validActions.length > 0) + || (trigger.autoApply === CodeActionAutoApply.IfSingle && actions.validActions.length === 1) + ) { + return actions.validActions[0]; + } + + return undefined; } - private _handleLightBulbSelect(e: { x: number, y: number, actions: CodeActionSet }): void { - this._codeActionWidget.getValue().show(e.actions, e); + public async showCodeActionList(actions: CodeActionSet, at: IAnchor | IPosition, options: CodeActionShowOptions): Promise { + this._codeActionWidget.getValue().show(actions, at, options); } } diff --git a/src/vs/editor/contrib/codeAction/codeActionWidget.ts b/src/vs/editor/contrib/codeAction/codeActionWidget.ts deleted file mode 100644 index 4a922ad8950d..000000000000 --- a/src/vs/editor/contrib/codeAction/codeActionWidget.ts +++ /dev/null @@ -1,91 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { getDomNodePagePosition } from 'vs/base/browser/dom'; -import { Action } from 'vs/base/common/actions'; -import { canceled } from 'vs/base/common/errors'; -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { Position, IPosition } from 'vs/editor/common/core/position'; -import { ScrollType } from 'vs/editor/common/editorCommon'; -import { CodeAction } from 'vs/editor/common/modes'; -import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { CodeActionSet } from 'vs/editor/contrib/codeAction/codeAction'; -import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; -import { IAnchor } from 'vs/base/browser/ui/contextview/contextview'; - -interface CodeActionWidgetDelegate { - onSelectCodeAction: (action: CodeAction) => Promise; -} - -export class CodeActionWidget extends Disposable { - - private _visible: boolean = false; - private readonly _showingActions = this._register(new MutableDisposable()); - - constructor( - private readonly _editor: ICodeEditor, - private readonly _contextMenuService: IContextMenuService, - private readonly _delegate: CodeActionWidgetDelegate, - ) { - super(); - } - - public async show(codeActions: CodeActionSet, at?: IAnchor | IPosition): Promise { - if (!codeActions.actions.length) { - this._visible = false; - return; - } - if (!this._editor.getDomNode()) { - // cancel when editor went off-dom - this._visible = false; - return Promise.reject(canceled()); - } - - this._visible = true; - const actions = codeActions.actions.map(action => this.codeActionToAction(action)); - - this._showingActions.value = codeActions; - this._contextMenuService.showContextMenu({ - getAnchor: () => { - if (Position.isIPosition(at)) { - at = this._toCoords(at); - } - return at || { x: 0, y: 0 }; - }, - getActions: () => actions, - onHide: () => { - this._visible = false; - this._editor.focus(); - }, - autoSelectFirstItem: true - }); - } - - private codeActionToAction(action: CodeAction): Action { - const id = action.command ? action.command.id : action.title; - const title = action.title; - return new Action(id, title, undefined, true, () => this._delegate.onSelectCodeAction(action)); - } - - get isVisible(): boolean { - return this._visible; - } - - private _toCoords(position: IPosition): { x: number, y: number } { - if (!this._editor.hasModel()) { - return { x: 0, y: 0 }; - } - this._editor.revealPosition(position, ScrollType.Immediate); - this._editor.render(); - - // Translate to absolute editor position - const cursorCoords = this._editor.getScrolledVisiblePosition(position); - const editorCoords = getDomNodePagePosition(this._editor.getDomNode()); - const x = editorCoords.left + cursorCoords.left; - const y = editorCoords.top + cursorCoords.top + cursorCoords.height; - - return { x, y }; - } -} diff --git a/src/vs/editor/contrib/codeAction/lightBulbWidget.ts b/src/vs/editor/contrib/codeAction/lightBulbWidget.ts index 2e0d2d85b1ed..e68b5e45c454 100644 --- a/src/vs/editor/contrib/codeAction/lightBulbWidget.ts +++ b/src/vs/editor/contrib/codeAction/lightBulbWidget.ts @@ -17,6 +17,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; import { editorLightBulbForeground, editorLightBulbAutoFixForeground } from 'vs/platform/theme/common/colorRegistry'; +import { Gesture } from 'vs/base/browser/touch'; namespace LightBulbState { @@ -25,7 +26,7 @@ namespace LightBulbState { Showing, } - export const Hidden = new class { readonly type = Type.Hidden; }; + export const Hidden = { type: Type.Hidden } as const; export class Showing { readonly type = Type.Showing; @@ -71,7 +72,9 @@ export class LightBulbWidget extends Disposable implements IContentWidget { this.hide(); } })); - this._register(dom.addStandardDisposableListener(this._domNode, 'mousedown', e => { + + Gesture.ignoreTarget(this._domNode); + this._register(dom.addStandardDisposableGenericMouseDownListner(this._domNode, e => { if (this.state.type !== LightBulbState.Type.Showing) { return; } @@ -137,7 +140,7 @@ export class LightBulbWidget extends Disposable implements IContentWidget { } public update(actions: CodeActionSet, atPosition: IPosition) { - if (actions.actions.length <= 0) { + if (actions.validActions.length <= 0) { return this.hide(); } diff --git a/src/vs/editor/contrib/codeAction/test/codeAction.test.ts b/src/vs/editor/contrib/codeAction/test/codeAction.test.ts index 07c0b11d41df..fd3010577c91 100644 --- a/src/vs/editor/contrib/codeAction/test/codeAction.test.ts +++ b/src/vs/editor/contrib/codeAction/test/codeAction.test.ts @@ -9,7 +9,7 @@ import { Range } from 'vs/editor/common/core/range'; import { TextModel } from 'vs/editor/common/model/textModel'; import * as modes from 'vs/editor/common/modes'; import { getCodeActions } from 'vs/editor/contrib/codeAction/codeAction'; -import { CodeActionKind } from 'vs/editor/contrib/codeAction/codeActionTrigger'; +import { CodeActionKind } from 'vs/editor/contrib/codeAction/types'; import { IMarkerData, MarkerSeverity } from 'vs/platform/markers/common/markers'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -125,7 +125,7 @@ suite('CodeAction', () => { testData.tsLint.abc ]; - const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'manual' }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'manual' }, CancellationToken.None); assert.equal(actions.length, 6); assert.deepEqual(actions, expected); }); @@ -140,20 +140,20 @@ suite('CodeAction', () => { disposables.add(modes.CodeActionProviderRegistry.register('fooLang', provider)); { - const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: new CodeActionKind('a') } }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { include: new CodeActionKind('a') } }, CancellationToken.None); assert.equal(actions.length, 2); assert.strictEqual(actions[0].title, 'a'); assert.strictEqual(actions[1].title, 'a.b'); } { - const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: new CodeActionKind('a.b') } }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { include: new CodeActionKind('a.b') } }, CancellationToken.None); assert.equal(actions.length, 1); assert.strictEqual(actions[0].title, 'a.b'); } { - const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: new CodeActionKind('a.b.c') } }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { include: new CodeActionKind('a.b.c') } }, CancellationToken.None); assert.equal(actions.length, 0); } }); @@ -172,7 +172,7 @@ suite('CodeAction', () => { disposables.add(modes.CodeActionProviderRegistry.register('fooLang', provider)); - const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: new CodeActionKind('a') } }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { include: new CodeActionKind('a') } }, CancellationToken.None); assert.equal(actions.length, 1); assert.strictEqual(actions[0].title, 'a'); }); @@ -186,18 +186,40 @@ suite('CodeAction', () => { disposables.add(modes.CodeActionProviderRegistry.register('fooLang', provider)); { - const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto' }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto' }, CancellationToken.None); assert.equal(actions.length, 1); assert.strictEqual(actions[0].title, 'b'); } { - const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: CodeActionKind.Source, includeSourceActions: true } }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { include: CodeActionKind.Source, includeSourceActions: true } }, CancellationToken.None); assert.equal(actions.length, 1); assert.strictEqual(actions[0].title, 'a'); } }); + test('getCodeActions should support filtering out some requested source code actions #84602', async function () { + const provider = staticCodeActionProvider( + { title: 'a', kind: CodeActionKind.Source.value }, + { title: 'b', kind: CodeActionKind.Source.append('test').value }, + { title: 'c', kind: 'c' } + ); + + disposables.add(modes.CodeActionProviderRegistry.register('fooLang', provider)); + + { + const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { + type: 'auto', filter: { + include: CodeActionKind.Source.append('test'), + excludes: [CodeActionKind.Source], + includeSourceActions: true, + } + }, CancellationToken.None); + assert.equal(actions.length, 1); + assert.strictEqual(actions[0].title, 'b'); + } + }); + test('getCodeActions should not invoke code action providers filtered out by providedCodeActionKinds', async function () { let wasInvoked = false; const provider = new class implements modes.CodeActionProvider { @@ -211,10 +233,10 @@ suite('CodeAction', () => { disposables.add(modes.CodeActionProviderRegistry.register('fooLang', provider)); - const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { + const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { - kind: CodeActionKind.QuickFix + include: CodeActionKind.QuickFix } }, CancellationToken.None); assert.strictEqual(actions.length, 0); diff --git a/src/vs/editor/contrib/codeAction/test/codeActionKeybindingResolver.test.ts b/src/vs/editor/contrib/codeAction/test/codeActionKeybindingResolver.test.ts new file mode 100644 index 000000000000..34f3313611e9 --- /dev/null +++ b/src/vs/editor/contrib/codeAction/test/codeActionKeybindingResolver.test.ts @@ -0,0 +1,94 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { ChordKeybinding, KeyCode, SimpleKeybinding } from 'vs/base/common/keyCodes'; +import { OperatingSystem } from 'vs/base/common/platform'; +import { refactorCommandId, organizeImportsCommandId } from 'vs/editor/contrib/codeAction/codeAction'; +import { CodeActionKind } from 'vs/editor/contrib/codeAction/types'; +import { CodeActionKeybindingResolver } from 'vs/editor/contrib/codeAction/codeActionMenu'; +import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; +import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayoutResolvedKeybinding'; + +suite('CodeActionKeybindingResolver', () => { + const refactorKeybinding = createCodeActionKeybinding( + KeyCode.KEY_A, + refactorCommandId, + { kind: CodeActionKind.Refactor.value }); + + const refactorExtractKeybinding = createCodeActionKeybinding( + KeyCode.KEY_B, + refactorCommandId, + { kind: CodeActionKind.Refactor.append('extract').value }); + + const organizeImportsKeybinding = createCodeActionKeybinding( + KeyCode.KEY_C, + organizeImportsCommandId, + undefined); + + test('Should match refactor keybindings', async function () { + const resolver = new CodeActionKeybindingResolver({ + getKeybindings: (): readonly ResolvedKeybindingItem[] => { + return [refactorKeybinding]; + }, + }).getResolver(); + + assert.equal( + resolver({ title: '' }), + undefined); + + assert.equal( + resolver({ title: '', kind: CodeActionKind.Refactor.value }), + refactorKeybinding.resolvedKeybinding); + + assert.equal( + resolver({ title: '', kind: CodeActionKind.Refactor.append('extract').value }), + refactorKeybinding.resolvedKeybinding); + + assert.equal( + resolver({ title: '', kind: CodeActionKind.QuickFix.value }), + undefined); + }); + + test('Should prefer most specific keybinding', async function () { + const resolver = new CodeActionKeybindingResolver({ + getKeybindings: (): readonly ResolvedKeybindingItem[] => { + return [refactorKeybinding, refactorExtractKeybinding, organizeImportsKeybinding]; + }, + }).getResolver(); + + assert.equal( + resolver({ title: '', kind: CodeActionKind.Refactor.value }), + refactorKeybinding.resolvedKeybinding); + + assert.equal( + resolver({ title: '', kind: CodeActionKind.Refactor.append('extract').value }), + refactorExtractKeybinding.resolvedKeybinding); + }); + + test('Organize imports should still return a keybinding even though it does not have args', async function () { + const resolver = new CodeActionKeybindingResolver({ + getKeybindings: (): readonly ResolvedKeybindingItem[] => { + return [refactorKeybinding, refactorExtractKeybinding, organizeImportsKeybinding]; + }, + }).getResolver(); + + assert.equal( + resolver({ title: '', kind: CodeActionKind.SourceOrganizeImports.value }), + organizeImportsKeybinding.resolvedKeybinding); + }); +}); + +function createCodeActionKeybinding(keycode: KeyCode, command: string, commandArgs: any) { + return new ResolvedKeybindingItem( + new USLayoutResolvedKeybinding( + new ChordKeybinding([new SimpleKeybinding(false, true, false, false, keycode)]), + OperatingSystem.Linux), + command, + commandArgs, + undefined, + false); +} + diff --git a/src/vs/editor/contrib/codeAction/test/codeActionModel.test.ts b/src/vs/editor/contrib/codeAction/test/codeActionModel.test.ts index 59b98899d9ce..d0b480a33f68 100644 --- a/src/vs/editor/contrib/codeAction/test/codeActionModel.test.ts +++ b/src/vs/editor/contrib/codeAction/test/codeActionModel.test.ts @@ -5,6 +5,7 @@ import * as assert from 'assert'; import { DisposableStore } from 'vs/base/common/lifecycle'; +import { assertType } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { Selection } from 'vs/editor/common/core/selection'; @@ -55,13 +56,15 @@ suite('CodeActionModel', () => { const contextKeys = new MockContextKeyService(); const model = disposables.add(new CodeActionModel(editor, markerService, contextKeys, undefined)); - disposables.add(model.onDidChangeState((e: CodeActionsState.Triggered) => { - assert.equal(e.trigger.type, 'auto'); + disposables.add(model.onDidChangeState((e: CodeActionsState.State) => { + assertType(e.type === CodeActionsState.Type.Triggered); + + assert.strictEqual(e.trigger.type, 'auto'); assert.ok(e.actions); e.actions.then(fixes => { model.dispose(); - assert.equal(fixes.actions.length, 1); + assert.equal(fixes.validActions.length, 1); done(); }, done); })); @@ -94,12 +97,14 @@ suite('CodeActionModel', () => { return new Promise((resolve, reject) => { const contextKeys = new MockContextKeyService(); const model = disposables.add(new CodeActionModel(editor, markerService, contextKeys, undefined)); - disposables.add(model.onDidChangeState((e: CodeActionsState.Triggered) => { + disposables.add(model.onDidChangeState((e: CodeActionsState.State) => { + assertType(e.type === CodeActionsState.Type.Triggered); + assert.equal(e.trigger.type, 'auto'); assert.ok(e.actions); e.actions.then(fixes => { model.dispose(); - assert.equal(fixes.actions.length, 1); + assert.equal(fixes.validActions.length, 1); resolve(undefined); }, reject); })); @@ -130,7 +135,9 @@ suite('CodeActionModel', () => { await new Promise(resolve => { const contextKeys = new MockContextKeyService(); const model = disposables.add(new CodeActionModel(editor, markerService, contextKeys, undefined)); - disposables.add(model.onDidChangeState((e: CodeActionsState.Triggered) => { + disposables.add(model.onDidChangeState((e: CodeActionsState.State) => { + assertType(e.type === CodeActionsState.Type.Triggered); + assert.equal(e.trigger.type, 'auto'); const selection = e.rangeOrSelection; assert.deepEqual(selection.selectionStartLineNumber, 1); @@ -153,7 +160,9 @@ suite('CodeActionModel', () => { let triggerCount = 0; const contextKeys = new MockContextKeyService(); const model = disposables.add(new CodeActionModel(editor, markerService, contextKeys, undefined)); - disposables.add(model.onDidChangeState((e: CodeActionsState.Triggered) => { + disposables.add(model.onDidChangeState((e: CodeActionsState.State) => { + assertType(e.type === CodeActionsState.Type.Triggered); + assert.equal(e.trigger.type, 'auto'); ++triggerCount; diff --git a/src/vs/editor/contrib/codeAction/types.ts b/src/vs/editor/contrib/codeAction/types.ts new file mode 100644 index 000000000000..c4ade7f15227 --- /dev/null +++ b/src/vs/editor/contrib/codeAction/types.ts @@ -0,0 +1,151 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { startsWith } from 'vs/base/common/strings'; +import { CodeAction } from 'vs/editor/common/modes'; +import { Position } from 'vs/editor/common/core/position'; + +export class CodeActionKind { + private static readonly sep = '.'; + + public static readonly None = new CodeActionKind('@@none@@'); // Special code action that contains nothing + public static readonly Empty = new CodeActionKind(''); + public static readonly QuickFix = new CodeActionKind('quickfix'); + public static readonly Refactor = new CodeActionKind('refactor'); + public static readonly Source = new CodeActionKind('source'); + public static readonly SourceOrganizeImports = CodeActionKind.Source.append('organizeImports'); + public static readonly SourceFixAll = CodeActionKind.Source.append('fixAll'); + + constructor( + public readonly value: string + ) { } + + public equals(other: CodeActionKind): boolean { + return this.value === other.value; + } + + public contains(other: CodeActionKind): boolean { + return this.equals(other) || this.value === '' || startsWith(other.value, this.value + CodeActionKind.sep); + } + + public intersects(other: CodeActionKind): boolean { + return this.contains(other) || other.contains(this); + } + + public append(part: string): CodeActionKind { + return new CodeActionKind(this.value + CodeActionKind.sep + part); + } +} + +export const enum CodeActionAutoApply { + IfSingle = 'ifSingle', + First = 'first', + Never = 'never', +} + +export interface CodeActionFilter { + readonly include?: CodeActionKind; + readonly excludes?: readonly CodeActionKind[]; + readonly includeSourceActions?: boolean; + readonly onlyIncludePreferredActions?: boolean; +} + +export function mayIncludeActionsOfKind(filter: CodeActionFilter, providedKind: CodeActionKind): boolean { + // A provided kind may be a subset or superset of our filtered kind. + if (filter.include && !filter.include.intersects(providedKind)) { + return false; + } + + // Don't return source actions unless they are explicitly requested + if (!filter.includeSourceActions && CodeActionKind.Source.contains(providedKind)) { + return false; + } + + return true; +} + +export function filtersAction(filter: CodeActionFilter, action: CodeAction): boolean { + const actionKind = action.kind ? new CodeActionKind(action.kind) : undefined; + + // Filter out actions by kind + if (filter.include) { + if (!actionKind || !filter.include.contains(actionKind)) { + return false; + } + } + + if (filter.excludes) { + if (actionKind && filter.excludes.some(exclude => { + // Excludes are overwritten by includes + return exclude.contains(actionKind) && (!filter.include || !filter.include.contains(actionKind)); + })) { + return false; + } + } + + // Don't return source actions unless they are explicitly requested + if (!filter.includeSourceActions) { + if (actionKind && CodeActionKind.Source.contains(actionKind)) { + return false; + } + } + + if (filter.onlyIncludePreferredActions) { + if (!action.isPreferred) { + return false; + } + } + + return true; +} + +export interface CodeActionTrigger { + readonly type: 'auto' | 'manual'; + readonly filter?: CodeActionFilter; + readonly autoApply?: CodeActionAutoApply; + readonly context?: { + readonly notAvailableMessage: string; + readonly position: Position; + }; +} + +export class CodeActionCommandArgs { + public static fromUser(arg: any, defaults: { kind: CodeActionKind, apply: CodeActionAutoApply }): CodeActionCommandArgs { + if (!arg || typeof arg !== 'object') { + return new CodeActionCommandArgs(defaults.kind, defaults.apply, false); + } + return new CodeActionCommandArgs( + CodeActionCommandArgs.getKindFromUser(arg, defaults.kind), + CodeActionCommandArgs.getApplyFromUser(arg, defaults.apply), + CodeActionCommandArgs.getPreferredUser(arg)); + } + + private static getApplyFromUser(arg: any, defaultAutoApply: CodeActionAutoApply) { + switch (typeof arg.apply === 'string' ? arg.apply.toLowerCase() : '') { + case 'first': return CodeActionAutoApply.First; + case 'never': return CodeActionAutoApply.Never; + case 'ifsingle': return CodeActionAutoApply.IfSingle; + default: return defaultAutoApply; + } + } + + private static getKindFromUser(arg: any, defaultKind: CodeActionKind) { + return typeof arg.kind === 'string' + ? new CodeActionKind(arg.kind) + : defaultKind; + } + + private static getPreferredUser(arg: any): boolean { + return typeof arg.preferred === 'boolean' + ? arg.preferred + : false; + } + + private constructor( + public readonly kind: CodeActionKind, + public readonly apply: CodeActionAutoApply, + public readonly preferred: boolean, + ) { } +} diff --git a/src/vs/editor/contrib/codelens/codeLensCache.ts b/src/vs/editor/contrib/codelens/codeLensCache.ts index abf40be2fecb..b2ba7b2753f7 100644 --- a/src/vs/editor/contrib/codelens/codeLensCache.ts +++ b/src/vs/editor/contrib/codelens/codeLensCache.ts @@ -68,11 +68,18 @@ export class CodeLensCache implements ICodeLensCache { } put(model: ITextModel, data: CodeLensModel): void { + // create a copy of the model that is without command-ids + // but with comand-labels + const copyItems = data.lenses.map(item => { + return { + range: item.symbol.range, + command: item.symbol.command && { id: '', title: item.symbol.command?.title }, + }; + }); + const copyModel = new CodeLensModel(); + copyModel.add({ lenses: copyItems, dispose: () => { } }, this._fakeProvider); - const lensModel = new CodeLensModel(); - lensModel.add({ lenses: data.lenses.map(v => v.symbol), dispose() { } }, this._fakeProvider); - - const item = new CacheItem(model.getLineCount(), lensModel); + const item = new CacheItem(model.getLineCount(), copyModel); this._cache.set(model.uri.toString(), item); } diff --git a/src/vs/editor/contrib/codelens/codelensController.ts b/src/vs/editor/contrib/codelens/codelensController.ts index 5e0ef96870a3..fc914f859ad2 100644 --- a/src/vs/editor/contrib/codelens/codelensController.ts +++ b/src/vs/editor/contrib/codelens/codelensController.ts @@ -18,6 +18,8 @@ import { ICommandService } from 'vs/platform/commands/common/commands'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { ICodeLensCache } from 'vs/editor/contrib/codelens/codeLensCache'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { createStyleSheet } from 'vs/base/browser/dom'; +import { hash } from 'vs/base/common/hash'; export class CodeLensContribution implements editorCommon.IEditorContribution { @@ -27,6 +29,8 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { private readonly _globalToDispose = new DisposableStore(); private readonly _localToDispose = new DisposableStore(); + private readonly _styleElement: HTMLStyleElement; + private readonly _styleClassName: string; private _lenses: CodeLensWidget[] = []; private _currentFindCodeLensSymbolsPromise: CancelablePromise | undefined; private _oldCodeLensModels = new DisposableStore(); @@ -53,7 +57,16 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { } })); this._globalToDispose.add(CodeLensProviderRegistry.onDidChange(this._onModelChange, this)); + this._globalToDispose.add(this._editor.onDidChangeConfiguration(e => { + if (e.hasChanged(EditorOption.fontInfo)) { + this._updateLensStyle(); + } + })); this._onModelChange(); + + this._styleClassName = hash(this._editor.getId()).toString(16); + this._styleElement = createStyleSheet(); + this._updateLensStyle(); } dispose(): void { @@ -63,6 +76,15 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { dispose(this._currentCodeLensModel); } + private _updateLensStyle(): void { + const options = this._editor.getOptions(); + const fontInfo = options.get(EditorOption.fontInfo); + const lineHeight = options.get(EditorOption.lineHeight); + + const newStyle = `.monaco-editor .codelens-decoration.${this._styleClassName} { height: ${Math.round(lineHeight * 1.1)}px; line-height: ${lineHeight}px; font-size: ${Math.round(fontInfo.fontSize * 0.9)}px; padding-right: ${Math.round(fontInfo.fontSize * 0.45)}px;}`; + this._styleElement.innerHTML = newStyle; + } + private _localDispose(): void { if (this._currentFindCodeLensSymbolsPromise) { this._currentFindCodeLensSymbolsPromise.cancel(); @@ -200,17 +222,17 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { this._disposeAllLenses(undefined, undefined); } })); - this._localToDispose.add(this._editor.onDidChangeConfiguration(e => { - if (e.hasChanged(EditorOption.fontInfo)) { - for (const lens of this._lenses) { - lens.updateHeight(); - } - } - })); this._localToDispose.add(this._editor.onMouseUp(e => { - if (e.target.type === editorBrowser.MouseTargetType.CONTENT_WIDGET && e.target.element && e.target.element.tagName === 'A') { + if (e.target.type !== editorBrowser.MouseTargetType.CONTENT_WIDGET) { + return; + } + let target = e.target.element; + if (target?.tagName === 'SPAN') { + target = target.parentElement; + } + if (target?.tagName === 'A') { for (const lens of this._lenses) { - let command = lens.getCommand(e.target.element as HTMLLinkElement); + let command = lens.getCommand(target as HTMLLinkElement); if (command) { this._commandService.executeCommand(command.id, ...(command.arguments || [])).catch(err => this._notificationService.error(err)); break; @@ -222,8 +244,10 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { } private _disposeAllLenses(decChangeAccessor: IModelDecorationsChangeAccessor | undefined, viewZoneChangeAccessor: editorBrowser.IViewZoneChangeAccessor | undefined): void { - let helper = new CodeLensHelper(); - this._lenses.forEach((lens) => lens.dispose(helper, viewZoneChangeAccessor)); + const helper = new CodeLensHelper(); + for (const lens of this._lenses) { + lens.dispose(helper, viewZoneChangeAccessor); + } if (decChangeAccessor) { helper.commit(decChangeAccessor); } @@ -276,7 +300,7 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { groupsIndex++; codeLensIndex++; } else { - this._lenses.splice(codeLensIndex, 0, new CodeLensWidget(groups[groupsIndex], this._editor, helper, viewZoneAccessor, () => this._detectVisibleLenses && this._detectVisibleLenses.schedule())); + this._lenses.splice(codeLensIndex, 0, new CodeLensWidget(groups[groupsIndex], this._editor, this._styleClassName, helper, viewZoneAccessor, () => this._detectVisibleLenses && this._detectVisibleLenses.schedule())); codeLensIndex++; groupsIndex++; } @@ -290,7 +314,7 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { // Create extra symbols while (groupsIndex < groups.length) { - this._lenses.push(new CodeLensWidget(groups[groupsIndex], this._editor, helper, viewZoneAccessor, () => this._detectVisibleLenses && this._detectVisibleLenses.schedule())); + this._lenses.push(new CodeLensWidget(groups[groupsIndex], this._editor, this._styleClassName, helper, viewZoneAccessor, () => this._detectVisibleLenses && this._detectVisibleLenses.schedule())); groupsIndex++; } @@ -326,7 +350,7 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { return; } - this._currentResolveCodeLensSymbolsPromise = createCancelablePromise(token => { + const resolvePromise = createCancelablePromise(token => { const promises = toResolve.map((request, i) => { @@ -343,7 +367,7 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { }); return Promise.all(promises).then(() => { - if (!token.isCancellationRequested) { + if (!token.isCancellationRequested && !lenses[i].isDisposed()) { lenses[i].updateCommands(resolvedSymbols); } }); @@ -351,13 +375,21 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { return Promise.all(promises); }); + this._currentResolveCodeLensSymbolsPromise = resolvePromise; this._currentResolveCodeLensSymbolsPromise.then(() => { + if (this._currentCodeLensModel) { // update the cached state with new resolved items + this._codeLensCache.put(model, this._currentCodeLensModel); + } this._oldCodeLensModels.clear(); // dispose old models once we have updated the UI with the current model - this._currentResolveCodeLensSymbolsPromise = undefined; + if (resolvePromise === this._currentResolveCodeLensSymbolsPromise) { + this._currentResolveCodeLensSymbolsPromise = undefined; + } }, err => { onUnexpectedError(err); // can also be cancellation! - this._currentResolveCodeLensSymbolsPromise = undefined; + if (resolvePromise === this._currentResolveCodeLensSymbolsPromise) { + this._currentResolveCodeLensSymbolsPromise = undefined; + } }); } } diff --git a/src/vs/editor/contrib/codelens/codelensWidget.css b/src/vs/editor/contrib/codelens/codelensWidget.css index 6234ee695884..d09173886dad 100644 --- a/src/vs/editor/contrib/codelens/codelensWidget.css +++ b/src/vs/editor/contrib/codelens/codelensWidget.css @@ -11,10 +11,9 @@ .monaco-editor .codelens-decoration > span, .monaco-editor .codelens-decoration > a { - -moz-user-select: none; + user-select: none; -webkit-user-select: none; -ms-user-select: none; - user-select: none; white-space: nowrap; vertical-align: sub; } @@ -28,10 +27,6 @@ cursor: pointer; } -.monaco-editor .codelens-decoration.invisible-cl { - opacity: 0; -} - @keyframes fadein { 0% { opacity: 0; visibility: visible;} 100% { opacity: 1; } diff --git a/src/vs/editor/contrib/codelens/codelensWidget.ts b/src/vs/editor/contrib/codelens/codelensWidget.ts index faacba4fd984..04433943e9fe 100644 --- a/src/vs/editor/contrib/codelens/codelensWidget.ts +++ b/src/vs/editor/contrib/codelens/codelensWidget.ts @@ -5,7 +5,6 @@ import 'vs/css!./codelensWidget'; import * as dom from 'vs/base/browser/dom'; -import { coalesce, isFalsyOrEmpty } from 'vs/base/common/arrays'; import { renderCodicons } from 'vs/base/browser/ui/codiconLabel/codiconLabel'; import * as editorBrowser from 'vs/editor/browser/editorBrowser'; import { Range } from 'vs/editor/common/core/range'; @@ -16,7 +15,6 @@ import { editorCodeLensForeground } from 'vs/editor/common/view/editorColorRegis import { CodeLensItem } from 'vs/editor/contrib/codelens/codelens'; import { editorActiveLinkForeground } from 'vs/platform/theme/common/colorRegistry'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; -import { EditorOption } from 'vs/editor/common/config/editorOptions'; class CodeLensViewZone implements editorBrowser.IViewZone { @@ -58,69 +56,65 @@ class CodeLensContentWidget implements editorBrowser.IContentWidget { private readonly _id: string; private readonly _domNode: HTMLElement; - private readonly _editor: editorBrowser.ICodeEditor; + private readonly _editor: editorBrowser.IActiveCodeEditor; private readonly _commands = new Map(); private _widgetPosition?: editorBrowser.IContentWidgetPosition; + private _isEmpty: boolean = true; constructor( - editor: editorBrowser.ICodeEditor, - symbolRange: Range, - data: CodeLensItem[] + editor: editorBrowser.IActiveCodeEditor, + className: string, + line: number, ) { - this._id = 'codeLensWidget' + (++CodeLensContentWidget._idPool); this._editor = editor; + this._id = `codelens.widget-${(CodeLensContentWidget._idPool++)}`; - this.setSymbolRange(symbolRange); + this.updatePosition(line); this._domNode = document.createElement('span'); - this._domNode.innerHTML = ' '; - dom.addClass(this._domNode, 'codelens-decoration'); - this.updateHeight(); - this.withCommands(data.map(data => data.symbol), false); + this._domNode.className = `codelens-decoration ${className}`; } - updateHeight(): void { - const options = this._editor.getOptions(); - const fontInfo = options.get(EditorOption.fontInfo); - const lineHeight = options.get(EditorOption.lineHeight); - this._domNode.style.height = `${Math.round(lineHeight * 1.1)}px`; - this._domNode.style.lineHeight = `${lineHeight}px`; - this._domNode.style.fontSize = `${Math.round(fontInfo.fontSize * 0.9)}px`; - this._domNode.style.paddingRight = `${Math.round(fontInfo.fontSize * 0.45)}px`; - this._domNode.innerHTML = ' '; - } - - withCommands(inSymbols: Array, animate: boolean): void { + withCommands(lenses: Array, animate: boolean): void { this._commands.clear(); - const symbols = coalesce(inSymbols); - if (isFalsyOrEmpty(symbols)) { - this._domNode.innerHTML = 'no commands'; - return; - } - - let html: string[] = []; - for (let i = 0; i < symbols.length; i++) { - const command = symbols[i].command; - if (command) { - const title = renderCodicons(command.title); - let part: string; - if (command.id) { - part = `${title}`; - this._commands.set(String(i), command); + let innerHtml = ''; + let hasSymbol = false; + for (let i = 0; i < lenses.length; i++) { + const lens = lenses[i]; + if (!lens) { + continue; + } + hasSymbol = true; + if (lens.command) { + const title = renderCodicons(lens.command.title); + if (lens.command.id) { + innerHtml += `${title}`; + this._commands.set(String(i), lens.command); } else { - part = `${title}`; + innerHtml += `${title}`; + } + if (i + 1 < lenses.length) { + innerHtml += ' | '; } - html.push(part); } } - const wasEmpty = this._domNode.innerHTML === '' || this._domNode.innerHTML === ' '; - this._domNode.innerHTML = html.join(' | '); - this._editor.layoutContentWidget(this); - if (wasEmpty && animate) { - dom.addClass(this._domNode, 'fadein'); + if (!hasSymbol) { + // symbols but no commands + this._domNode.innerHTML = 'no commands'; + + } else { + // symbols and commands + if (!innerHtml) { + innerHtml = ' '; + } + this._domNode.innerHTML = innerHtml; + if (this._isEmpty && animate) { + dom.addClass(this._domNode, 'fadein'); + } + this._isEmpty = false; } } @@ -138,14 +132,10 @@ class CodeLensContentWidget implements editorBrowser.IContentWidget { return this._domNode; } - setSymbolRange(range: Range): void { - if (!this._editor.hasModel()) { - return; - } - const lineNumber = range.startLineNumber; - const column = this._editor.getModel().getLineFirstNonWhitespaceColumn(lineNumber); + updatePosition(line: number): void { + const column = this._editor.getModel().getLineFirstNonWhitespaceColumn(line); this._widgetPosition = { - position: { lineNumber: lineNumber, column: column }, + position: { lineNumber: line, column: column }, preference: [editorBrowser.ContentWidgetPositionPreference.ABOVE] }; } @@ -153,10 +143,6 @@ class CodeLensContentWidget implements editorBrowser.IContentWidget { getPosition(): editorBrowser.IContentWidgetPosition | null { return this._widgetPosition || null; } - - isVisible(): boolean { - return this._domNode.hasAttribute('monaco-visible-content-widget'); - } } export interface IDecorationIdCallback { @@ -194,27 +180,40 @@ export class CodeLensHelper { export class CodeLensWidget { - private readonly _editor: editorBrowser.ICodeEditor; + private readonly _editor: editorBrowser.IActiveCodeEditor; + private readonly _className: string; private readonly _viewZone!: CodeLensViewZone; private readonly _viewZoneId!: string; - private readonly _contentWidget!: CodeLensContentWidget; + + private _contentWidget?: CodeLensContentWidget; private _decorationIds: string[]; private _data: CodeLensItem[]; + private _isDisposed: boolean = false; constructor( data: CodeLensItem[], - editor: editorBrowser.ICodeEditor, + editor: editorBrowser.IActiveCodeEditor, + className: string, helper: CodeLensHelper, viewZoneChangeAccessor: editorBrowser.IViewZoneChangeAccessor, updateCallback: Function ) { this._editor = editor; + this._className = className; this._data = data; - this._decorationIds = new Array(this._data.length); + // create combined range, track all ranges with decorations, + // check if there is already something to render + this._decorationIds = []; let range: Range | undefined; + let lenses: CodeLens[] = []; + this._data.forEach((codeLensData, i) => { + if (codeLensData.symbol.command) { + lenses.push(codeLensData.symbol); + } + helper.addDecoration({ range: codeLensData.symbol.range, options: ModelDecorationOptions.EMPTY @@ -228,43 +227,51 @@ export class CodeLensWidget { } }); - if (range) { - this._contentWidget = new CodeLensContentWidget(editor, range, this._data); - this._viewZone = new CodeLensViewZone(range.startLineNumber - 1, updateCallback); + this._viewZone = new CodeLensViewZone(range!.startLineNumber - 1, updateCallback); + this._viewZoneId = viewZoneChangeAccessor.addZone(this._viewZone); - this._viewZoneId = viewZoneChangeAccessor.addZone(this._viewZone); - this._editor.addContentWidget(this._contentWidget); + if (lenses.length > 0) { + this._createContentWidgetIfNecessary(); + this._contentWidget!.withCommands(lenses, false); } } - dispose(helper: CodeLensHelper, viewZoneChangeAccessor?: editorBrowser.IViewZoneChangeAccessor): void { - while (this._decorationIds.length) { - helper.removeDecoration(this._decorationIds.pop()!); + private _createContentWidgetIfNecessary(): void { + if (!this._contentWidget) { + this._contentWidget = new CodeLensContentWidget(this._editor, this._className, this._viewZone.afterLineNumber + 1); + this._editor.addContentWidget(this._contentWidget!); } + } + + dispose(helper: CodeLensHelper, viewZoneChangeAccessor?: editorBrowser.IViewZoneChangeAccessor): void { + this._decorationIds.forEach(helper.removeDecoration, helper); + this._decorationIds = []; if (viewZoneChangeAccessor) { viewZoneChangeAccessor.removeZone(this._viewZoneId); } - this._editor.removeContentWidget(this._contentWidget); + if (this._contentWidget) { + this._editor.removeContentWidget(this._contentWidget); + this._contentWidget = undefined; + } + this._isDisposed = true; + } + + isDisposed(): boolean { + return this._isDisposed; } isValid(): boolean { - if (!this._editor.hasModel()) { - return false; - } - const model = this._editor.getModel(); return this._decorationIds.some((id, i) => { - const range = model.getDecorationRange(id); + const range = this._editor.getModel().getDecorationRange(id); const symbol = this._data[i].symbol; return !!(range && Range.isEmpty(symbol.range) === range.isEmpty()); }); } updateCodeLensSymbols(data: CodeLensItem[], helper: CodeLensHelper): void { - while (this._decorationIds.length) { - helper.removeDecoration(this._decorationIds.pop()!); - } + this._decorationIds.forEach(helper.removeDecoration, helper); + this._decorationIds = []; this._data = data; - this._decorationIds = new Array(this._data.length); this._data.forEach((codeLensData, i) => { helper.addDecoration({ range: codeLensData.symbol.range, @@ -274,7 +281,7 @@ export class CodeLensWidget { } computeIfNecessary(model: ITextModel): CodeLensItem[] | null { - if (!this._contentWidget.isVisible()) { + if (!this._viewZone.domNode.hasAttribute('monaco-visible-view-zone')) { return null; } @@ -289,7 +296,10 @@ export class CodeLensWidget { } updateCommands(symbols: Array): void { - this._contentWidget.withCommands(symbols, true); + + this._createContentWidgetIfNecessary(); + this._contentWidget!.withCommands(symbols, true); + for (let i = 0; i < this._data.length; i++) { const resolved = symbols[i]; if (resolved) { @@ -299,33 +309,29 @@ export class CodeLensWidget { } } - updateHeight(): void { - this._contentWidget.updateHeight(); - } - getCommand(link: HTMLLinkElement): Command | undefined { - return this._contentWidget.getCommand(link); + return this._contentWidget?.getCommand(link); } getLineNumber(): number { - if (this._editor.hasModel()) { - const range = this._editor.getModel().getDecorationRange(this._decorationIds[0]); - if (range) { - return range.startLineNumber; - } + const range = this._editor.getModel().getDecorationRange(this._decorationIds[0]); + if (range) { + return range.startLineNumber; } return -1; } update(viewZoneChangeAccessor: editorBrowser.IViewZoneChangeAccessor): void { - if (this.isValid() && this._editor.hasModel()) { + if (this.isValid()) { const range = this._editor.getModel().getDecorationRange(this._decorationIds[0]); if (range) { this._viewZone.afterLineNumber = range.startLineNumber - 1; viewZoneChangeAccessor.layoutZone(this._viewZoneId); - this._contentWidget.setSymbolRange(range); - this._editor.layoutContentWidget(this._contentWidget); + if (this._contentWidget) { + this._contentWidget.updatePosition(range.startLineNumber); + this._editor.layoutContentWidget(this._contentWidget); + } } } } diff --git a/src/vs/editor/contrib/colorPicker/colorPicker.css b/src/vs/editor/contrib/colorPicker/colorPicker.css index d929b4dd475e..28da1f267175 100644 --- a/src/vs/editor/contrib/colorPicker/colorPicker.css +++ b/src/vs/editor/contrib/colorPicker/colorPicker.css @@ -6,6 +6,8 @@ .colorpicker-widget { height: 190px; user-select: none; + -webkit-user-select: none; + -ms-user-select: none; } .monaco-editor .colorpicker-hover:focus { @@ -115,4 +117,4 @@ .colorpicker-body .strip .overlay { height: 150px; pointer-events: none; -} \ No newline at end of file +} diff --git a/src/vs/editor/contrib/colorPicker/colorPickerWidget.ts b/src/vs/editor/contrib/colorPicker/colorPickerWidget.ts index 29bb526c5fa5..e8f689e6bfd4 100644 --- a/src/vs/editor/contrib/colorPicker/colorPickerWidget.ts +++ b/src/vs/editor/contrib/colorPicker/colorPickerWidget.ts @@ -150,7 +150,7 @@ class SaturationBox extends Disposable { this.layout(); - this._register(dom.addDisposableListener(this.domNode, dom.EventType.MOUSE_DOWN, e => this.onMouseDown(e))); + this._register(dom.addDisposableGenericMouseDownListner(this.domNode, e => this.onMouseDown(e))); this._register(this.model.onDidChangeColor(this.onDidChangeColor, this)); this.monitor = null; } @@ -165,7 +165,7 @@ class SaturationBox extends Disposable { this.monitor.startMonitoring(standardMouseMoveMerger, event => this.onDidChangePosition(event.posx - origin.left, event.posy - origin.top), () => null); - const mouseUpListener = dom.addDisposableListener(document, dom.EventType.MOUSE_UP, () => { + const mouseUpListener = dom.addDisposableGenericMouseUpListner(document, () => { this._onColorFlushed.fire(); mouseUpListener.dispose(); if (this.monitor) { @@ -250,7 +250,7 @@ abstract class Strip extends Disposable { this.slider = dom.append(this.domNode, $('.slider')); this.slider.style.top = `0px`; - this._register(dom.addDisposableListener(this.domNode, dom.EventType.MOUSE_DOWN, e => this.onMouseDown(e))); + this._register(dom.addDisposableGenericMouseDownListner(this.domNode, e => this.onMouseDown(e))); this.layout(); } @@ -272,7 +272,7 @@ abstract class Strip extends Disposable { monitor.startMonitoring(standardMouseMoveMerger, event => this.onDidChangeTop(event.posy - origin.top), () => null); - const mouseUpListener = dom.addDisposableListener(document, dom.EventType.MOUSE_UP, () => { + const mouseUpListener = dom.addDisposableGenericMouseUpListner(document, () => { this._onColorFlushed.fire(); mouseUpListener.dispose(); monitor.stopMonitoring(true); diff --git a/src/vs/editor/contrib/comment/comment.ts b/src/vs/editor/contrib/comment/comment.ts index f8129f86ccf1..a11f92dd1711 100644 --- a/src/vs/editor/contrib/comment/comment.ts +++ b/src/vs/editor/contrib/comment/comment.ts @@ -56,13 +56,12 @@ class ToggleCommentLineAction extends CommentLineAction { primary: KeyMod.CtrlCmd | KeyCode.US_SLASH, weight: KeybindingWeight.EditorContrib }, - // {{SQL CARBON EDIT}} - Remove from menu - // menubarOpts: { - // menuId: MenuId.MenubarEditMenu, - // group: '5_insert', - // title: nls.localize({ key: 'miToggleLineComment', comment: ['&& denotes a mnemonic'] }, "&&Toggle Line Comment"), - // order: 1 - // } + /*menuOpts: { {{SQL CARBON EDIT}} - Remove from menu + menuId: MenuId.MenubarEditMenu, + group: '5_insert', + title: nls.localize({ key: 'miToggleLineComment', comment: ['&& denotes a mnemonic'] }, "&&Toggle Line Comment"), + order: 1 + }*/ }); } } @@ -113,13 +112,12 @@ class BlockCommentAction extends EditorAction { linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_A }, weight: KeybindingWeight.EditorContrib }, - // {{SQL CARBON EDIT}} - Remove from menu - // menubarOpts: { - // menuId: MenuId.MenubarEditMenu, - // group: '5_insert', - // title: nls.localize({ key: 'miToggleBlockComment', comment: ['&& denotes a mnemonic'] }, "Toggle &&Block Comment"), - // order: 2 - // } + /*menuOpts: { {{SQL CARBON EDIT}} - Remove from menu + menuId: MenuId.MenubarEditMenu, + group: '5_insert', + title: nls.localize({ key: 'miToggleBlockComment', comment: ['&& denotes a mnemonic'] }, "Toggle &&Block Comment"), + order: 2 + }*/ }); } diff --git a/src/vs/editor/contrib/contextmenu/contextmenu.ts b/src/vs/editor/contrib/contextmenu/contextmenu.ts index f219f43ce15b..06b332c8a976 100644 --- a/src/vs/editor/contrib/contextmenu/contextmenu.ts +++ b/src/vs/editor/contrib/contextmenu/contextmenu.ts @@ -15,7 +15,7 @@ import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/brows import { EditorAction, ServicesAccessor, registerEditorAction, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { IEditorContribution, ScrollType } from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; +import { IMenuService, MenuId, SubmenuItemAction } from 'vs/platform/actions/common/actions'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -23,6 +23,7 @@ import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegis import { ITextModel } from 'vs/editor/common/model'; import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { ContextSubMenu } from 'vs/base/browser/contextmenu'; export class ContextMenuController implements IEditorContribution { @@ -128,7 +129,7 @@ export class ContextMenuController implements IEditorContribution { } // Find actions available for menu - const menuActions = this._getMenuActions(this._editor.getModel()); + const menuActions = this._getMenuActions(this._editor.getModel(), MenuId.EditorContext); // Show menu if we have actions to show if (menuActions.length > 0) { @@ -136,16 +137,27 @@ export class ContextMenuController implements IEditorContribution { } } - private _getMenuActions(model: ITextModel): ReadonlyArray { + private _getMenuActions(model: ITextModel, menuId: MenuId): IAction[] { const result: IAction[] = []; - let contextMenu = this._menuService.createMenu(MenuId.EditorContext, this._contextKeyService); - const groups = contextMenu.getActions({ arg: model.uri }); - contextMenu.dispose(); + // get menu groups + const menu = this._menuService.createMenu(menuId, this._contextKeyService); + const groups = menu.getActions({ arg: model.uri }); + menu.dispose(); + // translate them into other actions for (let group of groups) { const [, actions] = group; - result.push(...actions); + for (const action of actions) { + if (action instanceof SubmenuItemAction) { + const subActions = this._getMenuActions(model, action.item.submenu); + if (subActions.length > 0) { + result.push(new ContextSubMenu(action.label, subActions)); + } + } else { + result.push(action); + } + } result.push(new Separator()); } result.pop(); // remove last separator diff --git a/src/vs/editor/contrib/cursorUndo/cursorUndo.ts b/src/vs/editor/contrib/cursorUndo/cursorUndo.ts index b0bfe023b389..991917216657 100644 --- a/src/vs/editor/contrib/cursorUndo/cursorUndo.ts +++ b/src/vs/editor/contrib/cursorUndo/cursorUndo.ts @@ -48,7 +48,6 @@ export class CursorUndoRedoController extends Disposable implements IEditorContr private _undoStack: CursorState[]; private _redoStack: CursorState[]; - private _prevState: CursorState | null; constructor(editor: ICodeEditor) { super(); @@ -57,38 +56,36 @@ export class CursorUndoRedoController extends Disposable implements IEditorContr this._undoStack = []; this._redoStack = []; - this._prevState = null; this._register(editor.onDidChangeModel((e) => { this._undoStack = []; this._redoStack = []; - this._prevState = null; })); this._register(editor.onDidChangeModelContent((e) => { this._undoStack = []; this._redoStack = []; - this._prevState = null; - this._pushStateIfNecessary(); })); - this._register(editor.onDidChangeCursorSelection(() => this._pushStateIfNecessary())); - } - - private _pushStateIfNecessary(): void { - const newState = new CursorState(this._editor.getSelections()!); - - if (!this._isCursorUndoRedo && this._prevState) { - const isEqualToLastUndoStack = (this._undoStack.length > 0 && this._undoStack[this._undoStack.length - 1].equals(this._prevState)); + this._register(editor.onDidChangeCursorSelection((e) => { + if (this._isCursorUndoRedo) { + return; + } + if (!e.oldSelections) { + return; + } + if (e.oldModelVersionId !== e.modelVersionId) { + return; + } + const prevState = new CursorState(e.oldSelections); + const isEqualToLastUndoStack = (this._undoStack.length > 0 && this._undoStack[this._undoStack.length - 1].equals(prevState)); if (!isEqualToLastUndoStack) { - this._undoStack.push(this._prevState); + this._undoStack.push(prevState); this._redoStack = []; if (this._undoStack.length > 50) { // keep the cursor undo stack bounded this._undoStack.shift(); } } - } - - this._prevState = newState; + })); } public cursorUndo(): void { diff --git a/src/vs/editor/contrib/cursorUndo/test/cursorUndo.test.ts b/src/vs/editor/contrib/cursorUndo/test/cursorUndo.test.ts new file mode 100644 index 000000000000..3f9f85f55492 --- /dev/null +++ b/src/vs/editor/contrib/cursorUndo/test/cursorUndo.test.ts @@ -0,0 +1,63 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { Selection } from 'vs/editor/common/core/selection'; +import { withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; +import { CursorUndo, CursorUndoRedoController } from 'vs/editor/contrib/cursorUndo/cursorUndo'; +import { Handler } from 'vs/editor/common/editorCommon'; +import { CoreNavigationCommands, CoreEditingCommands } from 'vs/editor/browser/controller/coreCommands'; + +suite('FindController', () => { + + const cursorUndoAction = new CursorUndo(); + + test('issue #82535: Edge case with cursorUndo', () => { + withTestCodeEditor([ + '' + ], {}, (editor) => { + + editor.registerAndInstantiateContribution(CursorUndoRedoController.ID, CursorUndoRedoController); + + // type hello + editor.trigger('test', Handler.Type, { text: 'hello' }); + + // press left + CoreNavigationCommands.CursorLeft.runEditorCommand(null, editor, {}); + + // press Delete + CoreEditingCommands.DeleteRight.runEditorCommand(null, editor, {}); + assert.deepEqual(editor.getValue(), 'hell'); + assert.deepEqual(editor.getSelections(), [new Selection(1, 5, 1, 5)]); + + // press left + CoreNavigationCommands.CursorLeft.runEditorCommand(null, editor, {}); + assert.deepEqual(editor.getSelections(), [new Selection(1, 4, 1, 4)]); + + // press Ctrl+U + cursorUndoAction.run(null!, editor, {}); + assert.deepEqual(editor.getSelections(), [new Selection(1, 5, 1, 5)]); + }); + }); + + test('issue #82535: Edge case with cursorUndo (reverse)', () => { + withTestCodeEditor([ + '' + ], {}, (editor) => { + + editor.registerAndInstantiateContribution(CursorUndoRedoController.ID, CursorUndoRedoController); + + // type hello + editor.trigger('test', Handler.Type, { text: 'hell' }); + editor.trigger('test', Handler.Type, { text: 'o' }); + assert.deepEqual(editor.getValue(), 'hello'); + assert.deepEqual(editor.getSelections(), [new Selection(1, 6, 1, 6)]); + + // press Ctrl+U + cursorUndoAction.run(null!, editor, {}); + assert.deepEqual(editor.getSelections(), [new Selection(1, 6, 1, 6)]); + }); + }); +}); diff --git a/src/vs/editor/contrib/dnd/dnd.ts b/src/vs/editor/contrib/dnd/dnd.ts index 06119ec2c3b9..c8c2d20ba676 100644 --- a/src/vs/editor/contrib/dnd/dnd.ts +++ b/src/vs/editor/contrib/dnd/dnd.ts @@ -8,7 +8,7 @@ import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { Disposable } from 'vs/base/common/lifecycle'; import { isMacintosh } from 'vs/base/common/platform'; import { KeyCode } from 'vs/base/common/keyCodes'; -import { ICodeEditor, IEditorMouseEvent, IMouseTarget, MouseTargetType } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditor, IEditorMouseEvent, IMouseTarget, MouseTargetType, IPartialEditorMouseEvent } from 'vs/editor/browser/editorBrowser'; import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import * as editorCommon from 'vs/editor/common/editorCommon'; import { Position } from 'vs/editor/common/core/position'; @@ -50,7 +50,7 @@ export class DragAndDropController extends Disposable implements editorCommon.IE this._register(this._editor.onMouseDown((e: IEditorMouseEvent) => this._onEditorMouseDown(e))); this._register(this._editor.onMouseUp((e: IEditorMouseEvent) => this._onEditorMouseUp(e))); this._register(this._editor.onMouseDrag((e: IEditorMouseEvent) => this._onEditorMouseDrag(e))); - this._register(this._editor.onMouseDrop((e: IEditorMouseEvent) => this._onEditorMouseDrop(e))); + this._register(this._editor.onMouseDrop((e: IPartialEditorMouseEvent) => this._onEditorMouseDrop(e))); this._register(this._editor.onKeyDown((e: IKeyboardEvent) => this.onEditorKeyDown(e))); this._register(this._editor.onKeyUp((e: IKeyboardEvent) => this.onEditorKeyUp(e))); this._register(this._editor.onDidBlurEditorWidget(() => this.onEditorBlur())); @@ -143,7 +143,7 @@ export class DragAndDropController extends Disposable implements editorCommon.IE } } - private _onEditorMouseDrop(mouseEvent: IEditorMouseEvent): void { + private _onEditorMouseDrop(mouseEvent: IPartialEditorMouseEvent): void { if (mouseEvent.target && (this._hitContent(mouseEvent.target) || this._hitMargin(mouseEvent.target)) && mouseEvent.target.position) { let newCursorPosition = new Position(mouseEvent.target.position.lineNumber, mouseEvent.target.position.column); diff --git a/src/vs/editor/contrib/documentSymbols/outlineTree.ts b/src/vs/editor/contrib/documentSymbols/outlineTree.ts index d56f906b9e57..fc2703d14348 100644 --- a/src/vs/editor/contrib/documentSymbols/outlineTree.ts +++ b/src/vs/editor/contrib/documentSymbols/outlineTree.ts @@ -22,6 +22,8 @@ import { MarkerSeverity } from 'vs/platform/markers/common/markers'; import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; import { registerColor, listErrorForeground, listWarningForeground, foreground } from 'vs/platform/theme/common/colorRegistry'; import { IdleValue } from 'vs/base/common/async'; +import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; +import { URI } from 'vs/base/common/uri'; export type OutlineItem = OutlineGroup | OutlineElement; @@ -220,26 +222,84 @@ export const enum OutlineSortOrder { export class OutlineFilter implements ITreeFilter { - private readonly _filteredTypes = new Set(); + static readonly configNameToKind = Object.freeze({ + ['showFiles']: SymbolKind.File, + ['showModules']: SymbolKind.Module, + ['showNamespaces']: SymbolKind.Namespace, + ['showPackages']: SymbolKind.Package, + ['showClasses']: SymbolKind.Class, + ['showMethods']: SymbolKind.Method, + ['showProperties']: SymbolKind.Property, + ['showFields']: SymbolKind.Field, + ['showConstructors']: SymbolKind.Constructor, + ['showEnums']: SymbolKind.Enum, + ['showInterfaces']: SymbolKind.Interface, + ['showFunctions']: SymbolKind.Function, + ['showVariables']: SymbolKind.Variable, + ['showConstants']: SymbolKind.Constant, + ['showStrings']: SymbolKind.String, + ['showNumbers']: SymbolKind.Number, + ['showBooleans']: SymbolKind.Boolean, + ['showArrays']: SymbolKind.Array, + ['showObjects']: SymbolKind.Object, + ['showKeys']: SymbolKind.Key, + ['showNull']: SymbolKind.Null, + ['showEnumMembers']: SymbolKind.EnumMember, + ['showStructs']: SymbolKind.Struct, + ['showEvents']: SymbolKind.Event, + ['showOperators']: SymbolKind.Operator, + ['showTypeParameters']: SymbolKind.TypeParameter, + }); + + static readonly kindToConfigName = Object.freeze({ + [SymbolKind.File]: 'showFiles', + [SymbolKind.Module]: 'showModules', + [SymbolKind.Namespace]: 'showNamespaces', + [SymbolKind.Package]: 'showPackages', + [SymbolKind.Class]: 'showClasses', + [SymbolKind.Method]: 'showMethods', + [SymbolKind.Property]: 'showProperties', + [SymbolKind.Field]: 'showFields', + [SymbolKind.Constructor]: 'showConstructors', + [SymbolKind.Enum]: 'showEnums', + [SymbolKind.Interface]: 'showInterfaces', + [SymbolKind.Function]: 'showFunctions', + [SymbolKind.Variable]: 'showVariables', + [SymbolKind.Constant]: 'showConstants', + [SymbolKind.String]: 'showStrings', + [SymbolKind.Number]: 'showNumbers', + [SymbolKind.Boolean]: 'showBooleans', + [SymbolKind.Array]: 'showArrays', + [SymbolKind.Object]: 'showObjects', + [SymbolKind.Key]: 'showKeys', + [SymbolKind.Null]: 'showNull', + [SymbolKind.EnumMember]: 'showEnumMembers', + [SymbolKind.Struct]: 'showStructs', + [SymbolKind.Event]: 'showEvents', + [SymbolKind.Operator]: 'showOperators', + [SymbolKind.TypeParameter]: 'showTypeParameters', + }); constructor( private readonly _prefix: string, - @IConfigurationService private readonly _configService: IConfigurationService, - ) { + @ITextResourceConfigurationService private readonly _textResourceConfigService: ITextResourceConfigurationService, + ) { } - } + filter(element: OutlineItem): boolean { + const outline = OutlineModel.get(element); + let uri: URI | undefined; - update() { - this._filteredTypes.clear(); - for (const name of SymbolKinds.names()) { - if (!this._configService.getValue(`${this._prefix}.${name}`)) { - this._filteredTypes.add(SymbolKinds.fromString(name) || -1); - } + if (outline) { + uri = outline.textModel.uri; } - } - filter(element: OutlineItem): boolean { - return !(element instanceof OutlineElement) || !this._filteredTypes.has(element.symbol.kind); + if (!(element instanceof OutlineElement)) { + return true; + } + + const configName = OutlineFilter.kindToConfigName[element.symbol.kind]; + const configKey = `${this._prefix}.${configName}`; + return this._textResourceConfigService.getValue(uri, configKey); } } @@ -296,11 +356,17 @@ export const SYMBOL_ICON_CLASS_FOREGROUND = registerColor('symbolIcon.classForeg hc: '#EE9D28' }, localize('symbolIcon.classForeground', 'The foreground color for class symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); -export const SYMBOL_ICON_CONSTANT_FOREGROUND = registerColor('symbolIcon.contstantForeground', { +export const SYMBOL_ICON_COLOR_FOREGROUND = registerColor('symbolIcon.colorForeground', { + dark: foreground, + light: foreground, + hc: foreground +}, localize('symbolIcon.colorForeground', 'The foreground color for color symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); + +export const SYMBOL_ICON_CONSTANT_FOREGROUND = registerColor('symbolIcon.constantForeground', { dark: foreground, light: foreground, hc: foreground -}, localize('symbolIcon.contstantForeground', 'The foreground color for contstant symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); +}, localize('symbolIcon.constantForeground', 'The foreground color for constant symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); export const SYMBOL_ICON_CONSTRUCTOR_FOREGROUND = registerColor('symbolIcon.constructorForeground', { dark: '#B180D7', @@ -338,6 +404,12 @@ export const SYMBOL_ICON_FILE_FOREGROUND = registerColor('symbolIcon.fileForegro hc: foreground }, localize('symbolIcon.fileForeground', 'The foreground color for file symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); +export const SYMBOL_ICON_FOLDER_FOREGROUND = registerColor('symbolIcon.folderForeground', { + dark: foreground, + light: foreground, + hc: foreground +}, localize('symbolIcon.folderForeground', 'The foreground color for folder symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); + export const SYMBOL_ICON_FUNCTION_FOREGROUND = registerColor('symbolIcon.functionForeground', { dark: '#B180D7', light: '#652D90', @@ -356,6 +428,12 @@ export const SYMBOL_ICON_KEY_FOREGROUND = registerColor('symbolIcon.keyForegroun hc: foreground }, localize('symbolIcon.keyForeground', 'The foreground color for key symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); +export const SYMBOL_ICON_KEYWORD_FOREGROUND = registerColor('symbolIcon.keywordForeground', { + dark: foreground, + light: foreground, + hc: foreground +}, localize('symbolIcon.keywordForeground', 'The foreground color for keyword symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); + export const SYMBOL_ICON_METHOD_FOREGROUND = registerColor('symbolIcon.methodForeground', { dark: '#B180D7', light: '#652D90', @@ -410,6 +488,18 @@ export const SYMBOL_ICON_PROPERTY_FOREGROUND = registerColor('symbolIcon.propert hc: foreground }, localize('symbolIcon.propertyForeground', 'The foreground color for property symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); +export const SYMBOL_ICON_REFERENCE_FOREGROUND = registerColor('symbolIcon.referenceForeground', { + dark: foreground, + light: foreground, + hc: foreground +}, localize('symbolIcon.referenceForeground', 'The foreground color for reference symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); + +export const SYMBOL_ICON_SNIPPET_FOREGROUND = registerColor('symbolIcon.snippetForeground', { + dark: foreground, + light: foreground, + hc: foreground +}, localize('symbolIcon.snippetForeground', 'The foreground color for snippet symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); + export const SYMBOL_ICON_STRING_FOREGROUND = registerColor('symbolIcon.stringForeground', { dark: foreground, light: foreground, @@ -422,12 +512,24 @@ export const SYMBOL_ICON_STRUCT_FOREGROUND = registerColor('symbolIcon.structFor hc: foreground }, localize('symbolIcon.structForeground', 'The foreground color for struct symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); +export const SYMBOL_ICON_TEXT_FOREGROUND = registerColor('symbolIcon.textForeground', { + dark: foreground, + light: foreground, + hc: foreground +}, localize('symbolIcon.textForeground', 'The foreground color for text symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); + export const SYMBOL_ICON_TYPEPARAMETER_FOREGROUND = registerColor('symbolIcon.typeParameterForeground', { dark: foreground, light: foreground, hc: foreground }, localize('symbolIcon.typeParameterForeground', 'The foreground color for type parameter symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); +export const SYMBOL_ICON_UNIT_FOREGROUND = registerColor('symbolIcon.unitForeground', { + dark: foreground, + light: foreground, + hc: foreground +}, localize('symbolIcon.unitForeground', 'The foreground color for unit symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); + export const SYMBOL_ICON_VARIABLE_FOREGROUND = registerColor('symbolIcon.variableForeground', { dark: '#75BEFF', light: '#007ACC', @@ -472,6 +574,15 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { `); } + const symbolIconColorColor = theme.getColor(SYMBOL_ICON_COLOR_FOREGROUND); + if (symbolIconColorColor) { + collector.addRule(` + .monaco-workbench .codicon-symbol-color { + color: ${symbolIconColorColor} !important; + } + `); + } + const symbolIconConstantColor = theme.getColor(SYMBOL_ICON_CONSTANT_FOREGROUND); if (symbolIconConstantColor) { collector.addRule(` @@ -536,6 +647,15 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { `); } + const symbolIconFolderColor = theme.getColor(SYMBOL_ICON_FOLDER_FOREGROUND); + if (symbolIconFolderColor) { + collector.addRule(` + .monaco-workbench .codicon-symbol-folder { + color: ${symbolIconFolderColor} !important; + } + `); + } + const symbolIconFunctionColor = theme.getColor(SYMBOL_ICON_FUNCTION_FOREGROUND); if (symbolIconFunctionColor) { collector.addRule(` @@ -563,6 +683,15 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { `); } + const symbolIconKeywordColor = theme.getColor(SYMBOL_ICON_KEYWORD_FOREGROUND); + if (symbolIconKeywordColor) { + collector.addRule(` + .monaco-workbench .codicon-symbol-keyword { + color: ${symbolIconKeywordColor} !important; + } + `); + } + const symbolIconModuleColor = theme.getColor(SYMBOL_ICON_MODULE_FOREGROUND); if (symbolIconModuleColor) { collector.addRule(` @@ -635,6 +764,24 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { `); } + const symbolIconReferenceColor = theme.getColor(SYMBOL_ICON_REFERENCE_FOREGROUND); + if (symbolIconReferenceColor) { + collector.addRule(` + .monaco-workbench .codicon-symbol-reference { + color: ${symbolIconReferenceColor} !important; + } + `); + } + + const symbolIconSnippetColor = theme.getColor(SYMBOL_ICON_SNIPPET_FOREGROUND); + if (symbolIconSnippetColor) { + collector.addRule(` + .monaco-workbench .codicon-symbol-snippet { + color: ${symbolIconSnippetColor} !important; + } + `); + } + const symbolIconStringColor = theme.getColor(SYMBOL_ICON_STRING_FOREGROUND); if (symbolIconStringColor) { collector.addRule(` @@ -653,6 +800,15 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { `); } + const symbolIconTextColor = theme.getColor(SYMBOL_ICON_TEXT_FOREGROUND); + if (symbolIconTextColor) { + collector.addRule(` + .monaco-workbench .codicon-symbol-text { + color: ${symbolIconTextColor} !important; + } + `); + } + const symbolIconTypeParameterColor = theme.getColor(SYMBOL_ICON_TYPEPARAMETER_FOREGROUND); if (symbolIconTypeParameterColor) { collector.addRule(` @@ -662,6 +818,15 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { `); } + const symbolIconUnitColor = theme.getColor(SYMBOL_ICON_UNIT_FOREGROUND); + if (symbolIconUnitColor) { + collector.addRule(` + .monaco-workbench .codicon-symbol-unit { + color: ${symbolIconUnitColor} !important; + } + `); + } + const symbolIconVariableColor = theme.getColor(SYMBOL_ICON_VARIABLE_FOREGROUND); if (symbolIconVariableColor) { collector.addRule(` diff --git a/src/vs/editor/contrib/find/findController.ts b/src/vs/editor/contrib/find/findController.ts index 5699df5548dd..c57a8e426ce9 100644 --- a/src/vs/editor/contrib/find/findController.ts +++ b/src/vs/editor/contrib/find/findController.ts @@ -395,11 +395,27 @@ export class FindController extends CommonFindController implements IFindControl this._createFindWidget(); } - if (!this._widget!.getPosition() && this._editor.getOption(EditorOption.find).autoFindInSelection) { - // not visible yet so we need to set search scope if `editor.find.autoFindInSelection` is `true` - opts.updateSearchScope = true; + const selection = this._editor.getSelection(); + let updateSearchScope = false; + + switch (this._editor.getOption(EditorOption.find).autoFindInSelection) { + case 'always': + updateSearchScope = true; + break; + case 'never': + updateSearchScope = false; + break; + case 'multiline': + const isSelectionMultipleLine = !!selection && selection.startLineNumber !== selection.endLineNumber; + updateSearchScope = isSelectionMultipleLine; + break; + + default: + break; } + opts.updateSearchScope = updateSearchScope; + super._start(opts); if (opts.shouldFocus === FindStartFocusAction.FocusReplaceInput) { @@ -439,7 +455,7 @@ export class StartFindAction extends EditorAction { primary: KeyMod.CtrlCmd | KeyCode.KEY_F, weight: KeybindingWeight.EditorContrib }, - menubarOpts: { + menuOpts: { menuId: MenuId.MenubarEditMenu, group: '3_find', title: nls.localize({ key: 'miFind', comment: ['&& denotes a mnemonic'] }, "&&Find"), @@ -489,7 +505,7 @@ export class StartFindWithSelectionAction extends EditorAction { forceRevealReplace: false, seedSearchStringFromSelection: true, seedSearchStringFromGlobalClipboard: false, - shouldFocus: FindStartFocusAction.FocusFindInput, + shouldFocus: FindStartFocusAction.NoFocusChange, shouldAnimate: true, updateSearchScope: false }); @@ -685,7 +701,7 @@ export class StartFindReplaceAction extends EditorAction { mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_F }, weight: KeybindingWeight.EditorContrib }, - menubarOpts: { + menuOpts: { menuId: MenuId.MenubarEditMenu, group: '3_find', title: nls.localize({ key: 'miReplace', comment: ['&& denotes a mnemonic'] }, "&&Replace"), diff --git a/src/vs/editor/contrib/find/findOptionsWidget.ts b/src/vs/editor/contrib/find/findOptionsWidget.ts index eeb61ffa09ca..a74bd30cd624 100644 --- a/src/vs/editor/contrib/find/findOptionsWidget.ts +++ b/src/vs/editor/contrib/find/findOptionsWidget.ts @@ -11,7 +11,7 @@ import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition, OverlayWidgetPosit import { FIND_IDS } from 'vs/editor/contrib/find/findModel'; import { FindReplaceState } from 'vs/editor/contrib/find/findState'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { contrastBorder, editorWidgetBackground, inputActiveOptionBorder, inputActiveOptionBackground, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; +import { contrastBorder, editorWidgetBackground, inputActiveOptionBorder, inputActiveOptionBackground, widgetShadow, editorWidgetForeground } from 'vs/platform/theme/common/colorRegistry'; import { ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; export class FindOptionsWidget extends Widget implements IOverlayWidget { @@ -200,6 +200,12 @@ registerThemingParticipant((theme, collector) => { collector.addRule(`.monaco-editor .findOptionsWidget { background-color: ${widgetBackground}; }`); } + const widgetForeground = theme.getColor(editorWidgetForeground); + if (widgetForeground) { + collector.addRule(`.monaco-editor .findOptionsWidget { color: ${widgetForeground}; }`); + } + + const widgetShadowColor = theme.getColor(widgetShadow); if (widgetShadowColor) { collector.addRule(`.monaco-editor .findOptionsWidget { box-shadow: 0 2px 8px ${widgetShadowColor}; }`); @@ -209,4 +215,4 @@ registerThemingParticipant((theme, collector) => { if (hcBorder) { collector.addRule(`.monaco-editor .findOptionsWidget { border: 2px solid ${hcBorder}; }`); } -}); \ No newline at end of file +}); diff --git a/src/vs/editor/contrib/find/findState.ts b/src/vs/editor/contrib/find/findState.ts index 42fec44612e2..f4f7d02018cc 100644 --- a/src/vs/editor/contrib/find/findState.ts +++ b/src/vs/editor/contrib/find/findState.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Emitter, Event } from 'vs/base/common/event'; -import { IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable } from 'vs/base/common/lifecycle'; import { Range } from 'vs/editor/common/core/range'; export interface FindReplaceStateChangedEvent { @@ -57,7 +57,7 @@ function effectiveOptionValue(override: FindOptionOverride, value: boolean): boo return value; } -export class FindReplaceState implements IDisposable { +export class FindReplaceState extends Disposable { private _searchString: string; private _replaceString: string; private _isRevealed: boolean; @@ -74,7 +74,7 @@ export class FindReplaceState implements IDisposable { private _matchesPosition: number; private _matchesCount: number; private _currentMatch: Range | null; - private readonly _onFindReplaceStateChange = new Emitter(); + private readonly _onFindReplaceStateChange = this._register(new Emitter()); public get searchString(): string { return this._searchString; } public get replaceString(): string { return this._replaceString; } @@ -97,6 +97,7 @@ export class FindReplaceState implements IDisposable { public readonly onFindReplaceStateChange: Event = this._onFindReplaceStateChange.event; constructor() { + super(); this._searchString = ''; this._replaceString = ''; this._isRevealed = false; @@ -115,9 +116,6 @@ export class FindReplaceState implements IDisposable { this._currentMatch = null; } - public dispose(): void { - } - public changeMatchInfo(matchesPosition: number, matchesCount: number, currentMatch: Range | undefined): void { let changeEvent: FindReplaceStateChangedEvent = { moveCursor: false, diff --git a/src/vs/editor/contrib/find/findWidget.css b/src/vs/editor/contrib/find/findWidget.css index d19194e0e173..11ccae515038 100644 --- a/src/vs/editor/contrib/find/findWidget.css +++ b/src/vs/editor/contrib/find/findWidget.css @@ -32,13 +32,17 @@ .monaco-editor .find-widget { position: absolute; z-index: 10; - top: -44px; height: 33px; overflow: hidden; line-height: 19px; - transition: top 200ms linear; + transition: transform 200ms linear; padding: 0 4px; box-sizing: border-box; + transform: translateY(Calc(-100% - 10px)); /* shadow (10px) */ +} + +.monaco-editor .find-widget textarea { + margin: 0px; } .monaco-editor .find-widget.hiddenEditor { @@ -46,30 +50,12 @@ } /* Find widget when replace is toggled on */ -.monaco-editor .find-widget.replaceToggled { - top: -74px; /* find input height + replace input height + shadow (10px) */ -} .monaco-editor .find-widget.replaceToggled > .replace-part { display: flex; - display: -webkit-flex; -} - -.monaco-editor .find-widget.visible, -.monaco-editor .find-widget.replaceToggled.visible { - top: 0; } -/* Multiple line find widget */ - -.monaco-editor .find-widget.multipleline { - top: unset; - bottom: 10px; -} - -.monaco-editor .find-widget.multipleline.visible, -.monaco-editor .find-widget.multipleline.replaceToggled.visible { - top: 0px; - bottom: unset; +.monaco-editor .find-widget.visible { + transform: translateY(0); } .monaco-editor .find-widget .monaco-inputbox.synthetic-focus { @@ -79,7 +65,6 @@ .monaco-editor .find-widget .monaco-inputbox .input { background-color: transparent; - /* Style to compensate for //winjs */ min-height: 0; } @@ -92,7 +77,6 @@ margin: 4px 0 0 17px; font-size: 12px; display: flex; - display: -webkit-flex; } .monaco-editor .find-widget > .find-part .monaco-inputbox, @@ -128,7 +112,6 @@ .monaco-editor .find-widget .monaco-findInput { vertical-align: middle; display: flex; - display: -webkit-flex; flex:1; } @@ -144,7 +127,6 @@ .monaco-editor .find-widget .matchesCount { display: flex; - display: -webkit-flex; flex: initial; margin: 0 0 0 3px; padding: 2px 0 0 2px; @@ -156,11 +138,9 @@ } .monaco-editor .find-widget .button { - min-width: 20px; width: 20px; height: 20px; display: flex; - display: -webkit-flex; flex: initial; margin-left: 3px; background-position: center center; @@ -189,14 +169,10 @@ .monaco-editor .find-widget .button.toggle { position: absolute; top: 0; - left: 0; + left: 3px; width: 18px; height: 100%; - -webkit-box-sizing: border-box; - -o-box-sizing: border-box; - -moz-box-sizing: border-box; - -ms-box-sizing: border-box; - box-sizing: border-box; + box-sizing: border-box; } .monaco-editor .find-widget .button.toggle.disabled { @@ -249,7 +225,6 @@ .monaco-editor .find-widget > .replace-part > .monaco-findInput { position: relative; display: flex; - display: -webkit-flex; vertical-align: middle; flex: auto; flex-grow: 0; @@ -287,12 +262,6 @@ } .monaco-editor .findMatch { - -webkit-animation-duration: 0; - -webkit-animation-name: inherit !important; - -moz-animation-duration: 0; - -moz-animation-name: inherit !important; - -ms-animation-duration: 0; - -ms-animation-name: inherit !important; animation-duration: 0; animation-name: inherit !important; } diff --git a/src/vs/editor/contrib/find/findWidget.ts b/src/vs/editor/contrib/find/findWidget.ts index d3a5fe67583b..6383ed61864d 100644 --- a/src/vs/editor/contrib/find/findWidget.ts +++ b/src/vs/editor/contrib/find/findWidget.ts @@ -10,6 +10,7 @@ import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { IMouseEvent } from 'vs/base/browser/mouseEvent'; import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; import { alert as alertFn } from 'vs/base/browser/ui/aria/aria'; +import { Checkbox } from 'vs/base/browser/ui/checkbox/checkbox'; import { FindInput, IFindInputStyles } from 'vs/base/browser/ui/findinput/findInput'; import { IMessage as InputBoxMessage } from 'vs/base/browser/ui/inputbox/inputBox'; import { ReplaceInput } from 'vs/base/browser/ui/findinput/replaceInput'; @@ -120,7 +121,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas private _matchesCount!: HTMLElement; private _prevBtn!: SimpleButton; private _nextBtn!: SimpleButton; - private _toggleSelectionFind!: SimpleCheckbox; + private _toggleSelectionFind!: Checkbox; private _closeBtn!: SimpleButton; private _replaceBtn!: SimpleButton; private _replaceAllBtn!: SimpleButton; @@ -290,12 +291,6 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas private _onStateChanged(e: FindReplaceStateChangedEvent): void { if (e.searchString) { - if (this._state.searchString.indexOf('\n') >= 0) { - dom.addClass(this._domNode, 'multipleline'); - } else { - dom.removeClass(this._domNode, 'multipleline'); - } - try { this._ignoreChangeEvent = true; this._findInput.setValue(this._state.searchString); @@ -436,7 +431,11 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas let isSelection = selection ? (selection.startLineNumber !== selection.endLineNumber || selection.startColumn !== selection.endColumn) : false; let isChecked = this._toggleSelectionFind.checked; - this._toggleSelectionFind.setEnabled(this._isVisible && (isChecked || isSelection)); + if (this._isVisible && (isChecked || isSelection)) { + this._toggleSelectionFind.enable(); + } else { + this._toggleSelectionFind.disable(); + } } private _updateButtons(): void { @@ -466,12 +465,23 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas this._isVisible = true; const selection = this._codeEditor.getSelection(); - const isSelection = selection ? (selection.startLineNumber !== selection.endLineNumber || selection.startColumn !== selection.endColumn) : false; - if (isSelection && this._codeEditor.getOption(EditorOption.find).autoFindInSelection) { - this._toggleSelectionFind.checked = true; - } else { - this._toggleSelectionFind.checked = false; + + switch (this._codeEditor.getOption(EditorOption.find).autoFindInSelection) { + case 'always': + this._toggleSelectionFind.checked = true; + break; + case 'never': + this._toggleSelectionFind.checked = false; + break; + case 'multiline': + const isSelectionMultipleLine = !!selection && selection.startLineNumber !== selection.endLineNumber; + this._toggleSelectionFind.checked = isSelectionMultipleLine; + break; + + default: + break; } + this._tryUpdateWidgetWidth(); this._updateButtons(); @@ -636,6 +646,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas }; this._findInput.style(inputStyles); this._replaceInput.style(inputStyles); + this._toggleSelectionFind.style(inputStyles); } private _tryUpdateWidgetWidth() { @@ -981,26 +992,30 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas actionsContainer.appendChild(this._nextBtn.domNode); // Toggle selection button - this._toggleSelectionFind = this._register(new SimpleCheckbox({ - parent: actionsContainer, + this._toggleSelectionFind = this._register(new Checkbox({ + actionClassName: 'codicon codicon-selection', title: NLS_TOGGLE_SELECTION_FIND_TITLE + this._keybindingLabelFor(FIND_IDS.ToggleSearchScopeCommand), - onChange: () => { - if (this._toggleSelectionFind.checked) { - if (this._codeEditor.hasModel()) { - let selection = this._codeEditor.getSelection(); - if (selection.endColumn === 1 && selection.endLineNumber > selection.startLineNumber) { - selection = selection.setEndPosition(selection.endLineNumber - 1, this._codeEditor.getModel().getLineMaxColumn(selection.endLineNumber - 1)); - } - if (!selection.isEmpty()) { - this._state.change({ searchScope: selection }, true); - } + isChecked: false + })); + + this._register(this._toggleSelectionFind.onChange(() => { + if (this._toggleSelectionFind.checked) { + if (this._codeEditor.hasModel()) { + let selection = this._codeEditor.getSelection(); + if (selection.endColumn === 1 && selection.endLineNumber > selection.startLineNumber) { + selection = selection.setEndPosition(selection.endLineNumber - 1, this._codeEditor.getModel().getLineMaxColumn(selection.endLineNumber - 1)); + } + if (!selection.isEmpty()) { + this._state.change({ searchScope: selection }, true); } - } else { - this._state.change({ searchScope: null }, true); } + } else { + this._state.change({ searchScope: null }, true); } })); + actionsContainer.appendChild(this._toggleSelectionFind.domNode); + // Close button this._closeBtn = this._register(new SimpleButton({ label: NLS_CLOSE_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.CloseFindWidgetCommand), @@ -1054,7 +1069,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas this._prevBtn.focus(); } else if (this._nextBtn.isEnabled()) { this._nextBtn.focus(); - } else if (this._toggleSelectionFind.isEnabled()) { + } else if (this._toggleSelectionFind.enabled) { this._toggleSelectionFind.focus(); } else if (this._closeBtn.isEnabled()) { this._closeBtn.focus(); @@ -1200,91 +1215,6 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas } } -interface ISimpleCheckboxOpts { - readonly parent: HTMLElement; - readonly title: string; - readonly onChange: () => void; -} - -class SimpleCheckbox extends Widget { - - private static _COUNTER = 0; - - private readonly _opts: ISimpleCheckboxOpts; - private readonly _domNode: HTMLElement; - private readonly _checkbox: HTMLInputElement; - private readonly _label: HTMLLabelElement; - - constructor(opts: ISimpleCheckboxOpts) { - super(); - this._opts = opts; - - this._domNode = document.createElement('div'); - this._domNode.className = 'monaco-checkbox'; - this._domNode.title = this._opts.title; - this._domNode.tabIndex = 0; - - this._checkbox = document.createElement('input'); - this._checkbox.type = 'checkbox'; - this._checkbox.className = 'checkbox'; - this._checkbox.id = 'checkbox-' + SimpleCheckbox._COUNTER++; - this._checkbox.tabIndex = -1; - - this._label = document.createElement('label'); - this._label.className = 'codicon codicon-selection'; - // Connect the label and the checkbox. Checkbox will get checked when the label receives a click. - this._label.htmlFor = this._checkbox.id; - this._label.tabIndex = -1; - - this._domNode.appendChild(this._checkbox); - this._domNode.appendChild(this._label); - - this._opts.parent.appendChild(this._domNode); - - this.onchange(this._checkbox, () => { - this._opts.onChange(); - }); - } - - public get domNode(): HTMLElement { - return this._domNode; - } - - public isEnabled(): boolean { - return (this._domNode.tabIndex >= 0); - } - - public get checked(): boolean { - return this._checkbox.checked; - } - - public set checked(newValue: boolean) { - this._checkbox.checked = newValue; - } - - public focus(): void { - this._domNode.focus(); - } - - private enable(): void { - this._checkbox.removeAttribute('disabled'); - } - - private disable(): void { - this._checkbox.disabled = true; - } - - public setEnabled(enabled: boolean): void { - if (enabled) { - this.enable(); - this.domNode.tabIndex = 0; - } else { - this.disable(); - this.domNode.tabIndex = -1; - } - } -} - export interface ISimpleButtonOpts { readonly label: string; readonly className: string; @@ -1390,7 +1320,7 @@ registerThemingParticipant((theme, collector) => { const hcBorder = theme.getColor(contrastBorder); if (hcBorder) { - collector.addRule(`.monaco-editor .find-widget { border: 2px solid ${hcBorder}; }`); + collector.addRule(`.monaco-editor .find-widget { border: 1px solid ${hcBorder}; }`); } const foreground = theme.getColor(editorWidgetForeground); diff --git a/src/vs/editor/contrib/find/test/find.test.ts b/src/vs/editor/contrib/find/test/find.test.ts index d0b6fc9bdcfa..4884dbdc1f41 100644 --- a/src/vs/editor/contrib/find/test/find.test.ts +++ b/src/vs/editor/contrib/find/test/find.test.ts @@ -75,7 +75,7 @@ suite('Find', () => { let searchStringSelectionTwoLines = getSelectionSearchString(editor); assert.equal(searchStringSelectionTwoLines, null); - // Select end of first line newline and and chunk of second + // Select end of first line newline and chunk of second editor.setSelection(new Range(1, 7, 2, 4)); let searchStringSelectionSpanLines = getSelectionSearchString(editor); assert.equal(searchStringSelectionSpanLines, null); diff --git a/src/vs/editor/contrib/find/test/findController.test.ts b/src/vs/editor/contrib/find/test/findController.test.ts index 9bfa18156b3f..2d580562bd17 100644 --- a/src/vs/editor/contrib/find/test/findController.test.ts +++ b/src/vs/editor/contrib/find/test/findController.test.ts @@ -519,7 +519,7 @@ suite('FindController query options persistence', () => { 'var x = (3 * 5)', 'var y = (3 * 5)', 'var z = (3 * 5)', - ], { serviceCollection: serviceCollection, find: { autoFindInSelection: true, globalFindClipboard: false } }, (editor, cursor) => { + ], { serviceCollection: serviceCollection, find: { autoFindInSelection: 'always', globalFindClipboard: false } }, (editor, cursor) => { // clipboardState = ''; editor.setSelection(new Range(1, 1, 2, 1)); let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); @@ -542,7 +542,7 @@ suite('FindController query options persistence', () => { 'var x = (3 * 5)', 'var y = (3 * 5)', 'var z = (3 * 5)', - ], { serviceCollection: serviceCollection, find: { autoFindInSelection: true, globalFindClipboard: false } }, (editor, cursor) => { + ], { serviceCollection: serviceCollection, find: { autoFindInSelection: 'always', globalFindClipboard: false } }, (editor, cursor) => { // clipboardState = ''; editor.setSelection(new Range(1, 2, 1, 2)); let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); @@ -565,7 +565,7 @@ suite('FindController query options persistence', () => { 'var x = (3 * 5)', 'var y = (3 * 5)', 'var z = (3 * 5)', - ], { serviceCollection: serviceCollection, find: { autoFindInSelection: true, globalFindClipboard: false } }, (editor, cursor) => { + ], { serviceCollection: serviceCollection, find: { autoFindInSelection: 'always', globalFindClipboard: false } }, (editor, cursor) => { // clipboardState = ''; editor.setSelection(new Range(1, 2, 1, 3)); let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); @@ -582,4 +582,28 @@ suite('FindController query options persistence', () => { assert.deepEqual(findController.getState().searchScope, new Selection(1, 2, 1, 3)); }); }); + + + test('issue #27083: Find in selection when multiple lines are selected', () => { + withTestCodeEditor([ + 'var x = (3 * 5)', + 'var y = (3 * 5)', + 'var z = (3 * 5)', + ], { serviceCollection: serviceCollection, find: { autoFindInSelection: 'multiline', globalFindClipboard: false } }, (editor, cursor) => { + // clipboardState = ''; + editor.setSelection(new Range(1, 6, 2, 1)); + let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); + + findController.start({ + forceRevealReplace: false, + seedSearchStringFromSelection: false, + seedSearchStringFromGlobalClipboard: false, + shouldFocus: FindStartFocusAction.NoFocusChange, + shouldAnimate: false, + updateSearchScope: true + }); + + assert.deepEqual(findController.getState().searchScope, new Selection(1, 6, 2, 1)); + }); + }); }); diff --git a/src/vs/editor/contrib/find/test/replacePattern.test.ts b/src/vs/editor/contrib/find/test/replacePattern.test.ts index d7c8c391fdfd..d9d876ecb4a5 100644 --- a/src/vs/editor/contrib/find/test/replacePattern.test.ts +++ b/src/vs/editor/contrib/find/test/replacePattern.test.ts @@ -156,115 +156,58 @@ suite('Replace Pattern test', () => { }); test('buildReplaceStringWithCasePreserved test', () => { - let replacePattern = 'Def'; - let actual: string | string[] = 'abc'; - - assert.equal(buildReplaceStringWithCasePreserved([actual], replacePattern), 'def'); - actual = 'Abc'; - assert.equal(buildReplaceStringWithCasePreserved([actual], replacePattern), 'Def'); - actual = 'ABC'; - assert.equal(buildReplaceStringWithCasePreserved([actual], replacePattern), 'DEF'); - - actual = ['abc', 'Abc']; - assert.equal(buildReplaceStringWithCasePreserved(actual, replacePattern), 'def'); - actual = ['Abc', 'abc']; - assert.equal(buildReplaceStringWithCasePreserved(actual, replacePattern), 'Def'); - actual = ['ABC', 'abc']; - assert.equal(buildReplaceStringWithCasePreserved(actual, replacePattern), 'DEF'); - - actual = ['AbC']; - assert.equal(buildReplaceStringWithCasePreserved(actual, replacePattern), 'Def'); - actual = ['aBC']; - assert.equal(buildReplaceStringWithCasePreserved(actual, replacePattern), 'Def'); - - actual = ['Foo-Bar']; - assert.equal(buildReplaceStringWithCasePreserved(actual, 'newfoo-newbar'), 'Newfoo-Newbar'); - actual = ['Foo-Bar-Abc']; - assert.equal(buildReplaceStringWithCasePreserved(actual, 'newfoo-newbar-newabc'), 'Newfoo-Newbar-Newabc'); - actual = ['Foo-Bar-abc']; - assert.equal(buildReplaceStringWithCasePreserved(actual, 'newfoo-newbar'), 'Newfoo-newbar'); - actual = ['foo-Bar']; - assert.equal(buildReplaceStringWithCasePreserved(actual, 'newfoo-newbar'), 'newfoo-Newbar'); - actual = ['foo-BAR']; - assert.equal(buildReplaceStringWithCasePreserved(actual, 'newfoo-newbar'), 'newfoo-NEWBAR'); - - actual = ['Foo_Bar']; - assert.equal(buildReplaceStringWithCasePreserved(actual, 'newfoo_newbar'), 'Newfoo_Newbar'); - actual = ['Foo_Bar_Abc']; - assert.equal(buildReplaceStringWithCasePreserved(actual, 'newfoo_newbar_newabc'), 'Newfoo_Newbar_Newabc'); - actual = ['Foo_Bar_abc']; - assert.equal(buildReplaceStringWithCasePreserved(actual, 'newfoo_newbar'), 'Newfoo_newbar'); - actual = ['Foo_Bar-abc']; - assert.equal(buildReplaceStringWithCasePreserved(actual, 'newfoo_newbar-abc'), 'Newfoo_newbar-abc'); - actual = ['foo_Bar']; - assert.equal(buildReplaceStringWithCasePreserved(actual, 'newfoo_newbar'), 'newfoo_Newbar'); - actual = ['Foo_BAR']; - assert.equal(buildReplaceStringWithCasePreserved(actual, 'newfoo_newbar'), 'Newfoo_NEWBAR'); + function assertReplace(target: string[], replaceString: string, expected: string): void { + let actual: string = ''; + actual = buildReplaceStringWithCasePreserved(target, replaceString); + assert.equal(actual, expected); + } + + assertReplace(['abc'], 'Def', 'def'); + assertReplace(['Abc'], 'Def', 'Def'); + assertReplace(['ABC'], 'Def', 'DEF'); + assertReplace(['abc', 'Abc'], 'Def', 'def'); + assertReplace(['Abc', 'abc'], 'Def', 'Def'); + assertReplace(['ABC', 'abc'], 'Def', 'DEF'); + assertReplace(['AbC'], 'Def', 'Def'); + assertReplace(['aBC'], 'Def', 'Def'); + assertReplace(['Foo-Bar'], 'newfoo-newbar', 'Newfoo-Newbar'); + assertReplace(['Foo-Bar-Abc'], 'newfoo-newbar-newabc', 'Newfoo-Newbar-Newabc'); + assertReplace(['Foo-Bar-abc'], 'newfoo-newbar', 'Newfoo-newbar'); + assertReplace(['foo-Bar'], 'newfoo-newbar', 'newfoo-Newbar'); + assertReplace(['foo-BAR'], 'newfoo-newbar', 'newfoo-NEWBAR'); + assertReplace(['Foo_Bar'], 'newfoo_newbar', 'Newfoo_Newbar'); + assertReplace(['Foo_Bar_Abc'], 'newfoo_newbar_newabc', 'Newfoo_Newbar_Newabc'); + assertReplace(['Foo_Bar_abc'], 'newfoo_newbar', 'Newfoo_newbar'); + assertReplace(['Foo_Bar-abc'], 'newfoo_newbar-abc', 'Newfoo_newbar-abc'); + assertReplace(['foo_Bar'], 'newfoo_newbar', 'newfoo_Newbar'); + assertReplace(['Foo_BAR'], 'newfoo_newbar', 'Newfoo_NEWBAR'); }); test('preserve case', () => { - let replacePattern = parseReplaceString('Def'); - let actual = replacePattern.buildReplaceString(['abc'], true); - assert.equal(actual, 'def'); - actual = replacePattern.buildReplaceString(['Abc'], true); - assert.equal(actual, 'Def'); - actual = replacePattern.buildReplaceString(['ABC'], true); - assert.equal(actual, 'DEF'); - - actual = replacePattern.buildReplaceString(['abc', 'Abc'], true); - assert.equal(actual, 'def'); - actual = replacePattern.buildReplaceString(['Abc', 'abc'], true); - assert.equal(actual, 'Def'); - actual = replacePattern.buildReplaceString(['ABC', 'abc'], true); - assert.equal(actual, 'DEF'); - - actual = replacePattern.buildReplaceString(['AbC'], true); - assert.equal(actual, 'Def'); - actual = replacePattern.buildReplaceString(['aBC'], true); - assert.equal(actual, 'Def'); - - replacePattern = parseReplaceString('newfoo-newbar'); - actual = replacePattern.buildReplaceString(['Foo-Bar'], true); - assert.equal(actual, 'Newfoo-Newbar'); - - replacePattern = parseReplaceString('newfoo-newbar-newabc'); - actual = replacePattern.buildReplaceString(['Foo-Bar-Abc'], true); - assert.equal(actual, 'Newfoo-Newbar-Newabc'); - - replacePattern = parseReplaceString('newfoo-newbar'); - actual = replacePattern.buildReplaceString(['Foo-Bar-abc'], true); - assert.equal(actual, 'Newfoo-newbar'); - - replacePattern = parseReplaceString('newfoo-newbar'); - actual = replacePattern.buildReplaceString(['foo-Bar'], true); - assert.equal(actual, 'newfoo-Newbar'); - - replacePattern = parseReplaceString('newfoo-newbar'); - actual = replacePattern.buildReplaceString(['foo-BAR'], true); - assert.equal(actual, 'newfoo-NEWBAR'); - - replacePattern = parseReplaceString('newfoo_newbar'); - actual = replacePattern.buildReplaceString(['Foo_Bar'], true); - assert.equal(actual, 'Newfoo_Newbar'); - - replacePattern = parseReplaceString('newfoo_newbar_newabc'); - actual = replacePattern.buildReplaceString(['Foo_Bar_Abc'], true); - assert.equal(actual, 'Newfoo_Newbar_Newabc'); - - replacePattern = parseReplaceString('newfoo_newbar'); - actual = replacePattern.buildReplaceString(['Foo_Bar_abc'], true); - assert.equal(actual, 'Newfoo_newbar'); - - replacePattern = parseReplaceString('newfoo_newbar-abc'); - actual = replacePattern.buildReplaceString(['Foo_Bar-abc'], true); - assert.equal(actual, 'Newfoo_newbar-abc'); - - replacePattern = parseReplaceString('newfoo_newbar'); - actual = replacePattern.buildReplaceString(['foo_Bar'], true); - assert.equal(actual, 'newfoo_Newbar'); - - replacePattern = parseReplaceString('newfoo_newbar'); - actual = replacePattern.buildReplaceString(['foo_BAR'], true); - assert.equal(actual, 'newfoo_NEWBAR'); + function assertReplace(target: string[], replaceString: string, expected: string): void { + let replacePattern = parseReplaceString(replaceString); + let actual = replacePattern.buildReplaceString(target, true); + assert.equal(actual, expected); + } + + assertReplace(['abc'], 'Def', 'def'); + assertReplace(['Abc'], 'Def', 'Def'); + assertReplace(['ABC'], 'Def', 'DEF'); + assertReplace(['abc', 'Abc'], 'Def', 'def'); + assertReplace(['Abc', 'abc'], 'Def', 'Def'); + assertReplace(['ABC', 'abc'], 'Def', 'DEF'); + assertReplace(['AbC'], 'Def', 'Def'); + assertReplace(['aBC'], 'Def', 'Def'); + assertReplace(['Foo-Bar'], 'newfoo-newbar', 'Newfoo-Newbar'); + assertReplace(['Foo-Bar-Abc'], 'newfoo-newbar-newabc', 'Newfoo-Newbar-Newabc'); + assertReplace(['Foo-Bar-abc'], 'newfoo-newbar', 'Newfoo-newbar'); + assertReplace(['foo-Bar'], 'newfoo-newbar', 'newfoo-Newbar'); + assertReplace(['foo-BAR'], 'newfoo-newbar', 'newfoo-NEWBAR'); + assertReplace(['Foo_Bar'], 'newfoo_newbar', 'Newfoo_Newbar'); + assertReplace(['Foo_Bar_Abc'], 'newfoo_newbar_newabc', 'Newfoo_Newbar_Newabc'); + assertReplace(['Foo_Bar_abc'], 'newfoo_newbar', 'Newfoo_newbar'); + assertReplace(['Foo_Bar-abc'], 'newfoo_newbar-abc', 'Newfoo_newbar-abc'); + assertReplace(['foo_Bar'], 'newfoo_newbar', 'newfoo_Newbar'); + assertReplace(['foo_BAR'], 'newfoo_newbar', 'newfoo_NEWBAR'); }); }); diff --git a/src/vs/editor/contrib/folding/folding.ts b/src/vs/editor/contrib/folding/folding.ts index fe119dab628a..8241502b64f7 100644 --- a/src/vs/editor/contrib/folding/folding.ts +++ b/src/vs/editor/contrib/folding/folding.ts @@ -423,7 +423,7 @@ export class FoldingController extends Disposable implements IEditorContribution if (iconClicked || isCollapsed) { let toToggle = [region]; if (e.event.middleButton || e.event.shiftKey) { - toToggle.push(...foldingModel.getRegionsInside(region, r => r.isCollapsed === isCollapsed)); + toToggle.push(...foldingModel.getRegionsInside(region, (r: FoldingRegion) => r.isCollapsed === isCollapsed)); } foldingModel.toggleCollapseState(toToggle); this.reveal({ lineNumber, column: 1 }); diff --git a/src/vs/editor/contrib/folding/foldingModel.ts b/src/vs/editor/contrib/folding/foldingModel.ts index 0e8d76a170d1..e51f00c636f0 100644 --- a/src/vs/editor/contrib/folding/foldingModel.ts +++ b/src/vs/editor/contrib/folding/foldingModel.ts @@ -204,7 +204,7 @@ export class FoldingModel { return null; } - getRegionsInside(region: FoldingRegion | null, filter?: (r: FoldingRegion, level?: number) => boolean): FoldingRegion[] { + getRegionsInside(region: FoldingRegion | null, filter?: RegionFilter | RegionFilterWithLevel): FoldingRegion[] { let result: FoldingRegion[] = []; let index = region ? region.regionIndex + 1 : 0; let endLineNumber = region ? region.endLineNumber : Number.MAX_VALUE; @@ -229,7 +229,7 @@ export class FoldingModel { for (let i = index, len = this._regions.length; i < len; i++) { let current = this._regions.toRegion(i); if (this._regions.getStartLineNumber(i) < endLineNumber) { - if (!filter || filter(current)) { + if (!filter || (filter as RegionFilter)(current)) { result.push(current); } } else { @@ -242,6 +242,10 @@ export class FoldingModel { } +type RegionFilter = (r: FoldingRegion) => boolean; +type RegionFilterWithLevel = (r: FoldingRegion, level: number) => boolean; + + /** * Collapse or expand the regions at the given locations * @param levels The number of levels. Use 1 to only impact the regions at the location, use Number.MAX_VALUE for all levels. diff --git a/src/vs/editor/contrib/format/formatActions.ts b/src/vs/editor/contrib/format/formatActions.ts index 89a55d374190..6851ebeefa5f 100644 --- a/src/vs/editor/contrib/format/formatActions.ts +++ b/src/vs/editor/contrib/format/formatActions.ts @@ -219,7 +219,7 @@ class FormatDocumentAction extends EditorAction { linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_I }, weight: KeybindingWeight.EditorContrib }, - menuOpts: { + contextMenuOpts: { when: EditorContextKeys.hasDocumentFormattingProvider, group: '1_modification', order: 1.3 @@ -248,7 +248,7 @@ class FormatSelectionAction extends EditorAction { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_F), weight: KeybindingWeight.EditorContrib }, - menuOpts: { + contextMenuOpts: { when: ContextKeyExpr.and(EditorContextKeys.hasDocumentSelectionFormattingProvider, EditorContextKeys.hasNonEmptySelection), group: '1_modification', order: 1.31 diff --git a/src/vs/editor/contrib/goToDefinition/goToDefinitionCommands.ts b/src/vs/editor/contrib/goToDefinition/goToDefinitionCommands.ts deleted file mode 100644 index c463125622c4..000000000000 --- a/src/vs/editor/contrib/goToDefinition/goToDefinitionCommands.ts +++ /dev/null @@ -1,505 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { alert } from 'vs/base/browser/ui/aria/aria'; -import { createCancelablePromise, raceCancellation } from 'vs/base/common/async'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import * as platform from 'vs/base/common/platform'; -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { EditorAction, IActionOptions, registerEditorAction, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; -import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import * as corePosition from 'vs/editor/common/core/position'; -import { Range, IRange } from 'vs/editor/common/core/range'; -import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { ITextModel, IWordAtPosition } from 'vs/editor/common/model'; -import { LocationLink, Location, isLocationLink } from 'vs/editor/common/modes'; -import { MessageController } from 'vs/editor/contrib/message/messageController'; -import { PeekContext } from 'vs/editor/contrib/referenceSearch/peekViewWidget'; -import { ReferencesController } from 'vs/editor/contrib/referenceSearch/referencesController'; -import { ReferencesModel } from 'vs/editor/contrib/referenceSearch/referencesModel'; -import * as nls from 'vs/nls'; -import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; -import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { INotificationService } from 'vs/platform/notification/common/notification'; -import { IEditorProgressService } from 'vs/platform/progress/common/progress'; -import { getDefinitionsAtPosition, getImplementationsAtPosition, getTypeDefinitionsAtPosition, getDeclarationsAtPosition } from './goToDefinition'; -import { CommandsRegistry } from 'vs/platform/commands/common/commands'; -import { EditorStateCancellationTokenSource, CodeEditorStateFlag } from 'vs/editor/browser/core/editorState'; -import { ISymbolNavigationService } from 'vs/editor/contrib/goToDefinition/goToDefinitionResultsNavigation'; -import { EditorOption } from 'vs/editor/common/config/editorOptions'; -import { isEqual } from 'vs/base/common/resources'; - -export class DefinitionActionConfig { - - constructor( - public readonly openToSide = false, - public readonly openInPeek = false, - public readonly filterCurrent = true, - public readonly showMessage = true, - ) { - // - } -} - -export class DefinitionAction extends EditorAction { - - private readonly _configuration: DefinitionActionConfig; - - constructor(configuration: DefinitionActionConfig, opts: IActionOptions) { - super(opts); - this._configuration = configuration; - } - - public run(accessor: ServicesAccessor, editor: ICodeEditor): Promise { - if (!editor.hasModel()) { - return Promise.resolve(undefined); - } - const notificationService = accessor.get(INotificationService); - const editorService = accessor.get(ICodeEditorService); - const progressService = accessor.get(IEditorProgressService); - const symbolNavService = accessor.get(ISymbolNavigationService); - - const model = editor.getModel(); - const pos = editor.getPosition(); - - const cts = new EditorStateCancellationTokenSource(editor, CodeEditorStateFlag.Value | CodeEditorStateFlag.Position); - - const definitionPromise = raceCancellation(this._getTargetLocationForPosition(model, pos, cts.token), cts.token).then(async references => { - - if (!references || model.isDisposed()) { - // new model, no more model - return; - } - - // * remove falsy references - // * find reference at the current pos - let idxOfCurrent = -1; - const result: LocationLink[] = []; - for (const reference of references) { - if (!reference || !reference.range) { - continue; - } - const newLen = result.push(reference); - if (this._configuration.filterCurrent - && isEqual(reference.uri, model.uri) - && Range.containsPosition(reference.range, pos) - && idxOfCurrent === -1 - ) { - idxOfCurrent = newLen - 1; - } - } - - if (result.length === 0) { - // no result -> show message - if (this._configuration.showMessage) { - const info = model.getWordAtPosition(pos); - MessageController.get(editor).showMessage(this._getNoResultFoundMessage(info), pos); - } - } else if (result.length === 1 && idxOfCurrent !== -1) { - // only the position at which we are -> adjust selection - let [current] = result; - return this._openReference(editor, editorService, current, false).then(() => undefined); - - } else { - // handle multile results - return this._onResult(editorService, symbolNavService, editor, new ReferencesModel(result)); - } - - }, (err) => { - // report an error - notificationService.error(err); - }).finally(() => { - cts.dispose(); - }); - - progressService.showWhile(definitionPromise, 250); - return definitionPromise; - } - - protected _getTargetLocationForPosition(model: ITextModel, position: corePosition.Position, token: CancellationToken): Promise { - return getDefinitionsAtPosition(model, position, token); - } - - protected _getNoResultFoundMessage(info: IWordAtPosition | null): string { - return info && info.word - ? nls.localize('noResultWord', "No definition found for '{0}'", info.word) - : nls.localize('generic.noResults', "No definition found"); - } - - protected _getMetaTitle(model: ReferencesModel): string { - return model.references.length > 1 ? nls.localize('meta.title', " – {0} definitions", model.references.length) : ''; - } - - private async _onResult(editorService: ICodeEditorService, symbolNavService: ISymbolNavigationService, editor: ICodeEditor, model: ReferencesModel): Promise { - - const msg = model.getAriaMessage(); - alert(msg); - - const gotoLocation = editor.getOption(EditorOption.gotoLocation); - if (this._configuration.openInPeek || (gotoLocation.multiple === 'peek' && model.references.length > 1)) { - this._openInPeek(editorService, editor, model); - - } else if (editor.hasModel()) { - const next = model.firstReference(); - if (!next) { - return; - } - const targetEditor = await this._openReference(editor, editorService, next, this._configuration.openToSide); - if (targetEditor && model.references.length > 1 && gotoLocation.multiple === 'gotoAndPeek') { - this._openInPeek(editorService, targetEditor, model); - } else { - model.dispose(); - } - - // keep remaining locations around when using - // 'goto'-mode - if (gotoLocation.multiple === 'goto') { - symbolNavService.put(next); - } - } - } - - private _openReference(editor: ICodeEditor, editorService: ICodeEditorService, reference: Location | LocationLink, sideBySide: boolean): Promise { - // range is the target-selection-range when we have one - // and the the fallback is the 'full' range - let range: IRange | undefined = undefined; - if (isLocationLink(reference)) { - range = reference.targetSelectionRange; - } - if (!range) { - range = reference.range; - } - - return editorService.openCodeEditor({ - resource: reference.uri, - options: { - selection: Range.collapseToStart(range), - revealInCenterIfOutsideViewport: true - } - }, editor, sideBySide); - } - - private _openInPeek(editorService: ICodeEditorService, target: ICodeEditor, model: ReferencesModel) { - let controller = ReferencesController.get(target); - if (controller && target.hasModel()) { - controller.toggleWidget(target.getSelection(), createCancelablePromise(_ => Promise.resolve(model)), { - getMetaTitle: (model) => { - return this._getMetaTitle(model); - }, - onGoto: (reference) => { - controller.closeWidget(); - return this._openReference(target, editorService, reference, false); - } - }); - } else { - model.dispose(); - } - } -} - -const goToDefinitionKb = platform.isWeb - ? KeyMod.CtrlCmd | KeyCode.F12 - : KeyCode.F12; - -export class GoToDefinitionAction extends DefinitionAction { - - static readonly id = 'editor.action.revealDefinition'; - - constructor() { - super(new DefinitionActionConfig(), { - id: GoToDefinitionAction.id, - label: nls.localize('actions.goToDecl.label', "Go to Definition"), - alias: 'Go to Definition', - precondition: ContextKeyExpr.and( - EditorContextKeys.hasDefinitionProvider, - EditorContextKeys.isInEmbeddedEditor.toNegated()), - kbOpts: { - kbExpr: EditorContextKeys.editorTextFocus, - primary: goToDefinitionKb, - weight: KeybindingWeight.EditorContrib - }, - menuOpts: { - group: 'navigation', - order: 1.1 - } - }); - CommandsRegistry.registerCommandAlias('editor.action.goToDeclaration', GoToDefinitionAction.id); - } -} - -export class OpenDefinitionToSideAction extends DefinitionAction { - - static readonly id = 'editor.action.revealDefinitionAside'; - - constructor() { - super(new DefinitionActionConfig(true), { - id: OpenDefinitionToSideAction.id, - label: nls.localize('actions.goToDeclToSide.label', "Open Definition to the Side"), - alias: 'Open Definition to the Side', - precondition: ContextKeyExpr.and( - EditorContextKeys.hasDefinitionProvider, - EditorContextKeys.isInEmbeddedEditor.toNegated()), - kbOpts: { - kbExpr: EditorContextKeys.editorTextFocus, - primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, goToDefinitionKb), - weight: KeybindingWeight.EditorContrib - } - }); - CommandsRegistry.registerCommandAlias('editor.action.openDeclarationToTheSide', OpenDefinitionToSideAction.id); - } -} - -export class PeekDefinitionAction extends DefinitionAction { - - static readonly id = 'editor.action.peekDefinition'; - - constructor() { - super(new DefinitionActionConfig(undefined, true, false), { - id: PeekDefinitionAction.id, - label: nls.localize('actions.previewDecl.label', "Peek Definition"), - alias: 'Peek Definition', - precondition: ContextKeyExpr.and( - EditorContextKeys.hasDefinitionProvider, - PeekContext.notInPeekEditor, - EditorContextKeys.isInEmbeddedEditor.toNegated()), - kbOpts: { - kbExpr: EditorContextKeys.editorTextFocus, - primary: KeyMod.Alt | KeyCode.F12, - linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.F10 }, - weight: KeybindingWeight.EditorContrib - }, - menuOpts: { - group: 'navigation', - order: 1.2 - } - }); - CommandsRegistry.registerCommandAlias('editor.action.previewDeclaration', PeekDefinitionAction.id); - } -} - -export class DeclarationAction extends DefinitionAction { - - protected _getTargetLocationForPosition(model: ITextModel, position: corePosition.Position, token: CancellationToken): Promise { - return getDeclarationsAtPosition(model, position, token); - } - - protected _getNoResultFoundMessage(info: IWordAtPosition | null): string { - return info && info.word - ? nls.localize('decl.noResultWord', "No declaration found for '{0}'", info.word) - : nls.localize('decl.generic.noResults', "No declaration found"); - } - - protected _getMetaTitle(model: ReferencesModel): string { - return model.references.length > 1 ? nls.localize('decl.meta.title', " – {0} declarations", model.references.length) : ''; - } -} - -export class GoToDeclarationAction extends DeclarationAction { - - static readonly id = 'editor.action.revealDeclaration'; - - constructor() { - super(new DefinitionActionConfig(), { - id: GoToDeclarationAction.id, - label: nls.localize('actions.goToDeclaration.label', "Go to Declaration"), - alias: 'Go to Declaration', - precondition: ContextKeyExpr.and( - EditorContextKeys.hasDeclarationProvider, - EditorContextKeys.isInEmbeddedEditor.toNegated()), - menuOpts: { - group: 'navigation', - order: 1.3 - } - }); - } - - protected _getNoResultFoundMessage(info: IWordAtPosition | null): string { - return info && info.word - ? nls.localize('decl.noResultWord', "No declaration found for '{0}'", info.word) - : nls.localize('decl.generic.noResults', "No declaration found"); - } - - protected _getMetaTitle(model: ReferencesModel): string { - return model.references.length > 1 ? nls.localize('decl.meta.title', " – {0} declarations", model.references.length) : ''; - } -} - -export class PeekDeclarationAction extends DeclarationAction { - constructor() { - super(new DefinitionActionConfig(undefined, true, false), { - id: 'editor.action.peekDeclaration', - label: nls.localize('actions.peekDecl.label', "Peek Declaration"), - alias: 'Peek Declaration', - precondition: ContextKeyExpr.and( - EditorContextKeys.hasDeclarationProvider, - PeekContext.notInPeekEditor, - EditorContextKeys.isInEmbeddedEditor.toNegated()), - menuOpts: { - group: 'navigation', - order: 1.31 - } - }); - } -} - -export class ImplementationAction extends DefinitionAction { - protected _getTargetLocationForPosition(model: ITextModel, position: corePosition.Position, token: CancellationToken): Promise { - return getImplementationsAtPosition(model, position, token); - } - - protected _getNoResultFoundMessage(info: IWordAtPosition | null): string { - return info && info.word - ? nls.localize('goToImplementation.noResultWord', "No implementation found for '{0}'", info.word) - : nls.localize('goToImplementation.generic.noResults', "No implementation found"); - } - - protected _getMetaTitle(model: ReferencesModel): string { - return model.references.length > 1 ? nls.localize('meta.implementations.title', " – {0} implementations", model.references.length) : ''; - } -} - -export class GoToImplementationAction extends ImplementationAction { - - public static readonly ID = 'editor.action.goToImplementation'; - - constructor() { - super(new DefinitionActionConfig(), { - id: GoToImplementationAction.ID, - label: nls.localize('actions.goToImplementation.label', "Go to Implementation"), - alias: 'Go to Implementation', - precondition: ContextKeyExpr.and( - EditorContextKeys.hasImplementationProvider, - EditorContextKeys.isInEmbeddedEditor.toNegated()), - kbOpts: { - kbExpr: EditorContextKeys.editorTextFocus, - primary: KeyMod.CtrlCmd | KeyCode.F12, - weight: KeybindingWeight.EditorContrib - } - }); - } -} - -export class PeekImplementationAction extends ImplementationAction { - - public static readonly ID = 'editor.action.peekImplementation'; - - constructor() { - super(new DefinitionActionConfig(false, true, false), { - id: PeekImplementationAction.ID, - label: nls.localize('actions.peekImplementation.label', "Peek Implementation"), - alias: 'Peek Implementation', - precondition: ContextKeyExpr.and( - EditorContextKeys.hasImplementationProvider, - EditorContextKeys.isInEmbeddedEditor.toNegated()), - kbOpts: { - kbExpr: EditorContextKeys.editorTextFocus, - primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.F12, - weight: KeybindingWeight.EditorContrib - } - }); - } -} - -export class TypeDefinitionAction extends DefinitionAction { - protected _getTargetLocationForPosition(model: ITextModel, position: corePosition.Position, token: CancellationToken): Promise { - return getTypeDefinitionsAtPosition(model, position, token); - } - - protected _getNoResultFoundMessage(info: IWordAtPosition | null): string { - return info && info.word - ? nls.localize('goToTypeDefinition.noResultWord', "No type definition found for '{0}'", info.word) - : nls.localize('goToTypeDefinition.generic.noResults', "No type definition found"); - } - - protected _getMetaTitle(model: ReferencesModel): string { - return model.references.length > 1 ? nls.localize('meta.typeDefinitions.title', " – {0} type definitions", model.references.length) : ''; - } -} - -export class GoToTypeDefinitionAction extends TypeDefinitionAction { - - public static readonly ID = 'editor.action.goToTypeDefinition'; - - constructor() { - super(new DefinitionActionConfig(), { - id: GoToTypeDefinitionAction.ID, - label: nls.localize('actions.goToTypeDefinition.label', "Go to Type Definition"), - alias: 'Go to Type Definition', - precondition: ContextKeyExpr.and( - EditorContextKeys.hasTypeDefinitionProvider, - EditorContextKeys.isInEmbeddedEditor.toNegated()), - kbOpts: { - kbExpr: EditorContextKeys.editorTextFocus, - primary: 0, - weight: KeybindingWeight.EditorContrib - }, - menuOpts: { - group: 'navigation', - order: 1.4 - } - }); - } -} - -export class PeekTypeDefinitionAction extends TypeDefinitionAction { - - public static readonly ID = 'editor.action.peekTypeDefinition'; - - constructor() { - super(new DefinitionActionConfig(false, true, false), { - id: PeekTypeDefinitionAction.ID, - label: nls.localize('actions.peekTypeDefinition.label', "Peek Type Definition"), - alias: 'Peek Type Definition', - precondition: ContextKeyExpr.and( - EditorContextKeys.hasTypeDefinitionProvider, - EditorContextKeys.isInEmbeddedEditor.toNegated()), - kbOpts: { - kbExpr: EditorContextKeys.editorTextFocus, - primary: 0, - weight: KeybindingWeight.EditorContrib - } - }); - } -} - -registerEditorAction(GoToDefinitionAction); -registerEditorAction(OpenDefinitionToSideAction); -registerEditorAction(PeekDefinitionAction); -registerEditorAction(GoToDeclarationAction); -registerEditorAction(PeekDeclarationAction); -registerEditorAction(GoToImplementationAction); -registerEditorAction(PeekImplementationAction); -registerEditorAction(GoToTypeDefinitionAction); -registerEditorAction(PeekTypeDefinitionAction); - -// Go to menu -MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, { - group: '4_symbol_nav', - command: { - id: 'editor.action.goToDeclaration', - title: nls.localize({ key: 'miGotoDefinition', comment: ['&& denotes a mnemonic'] }, "Go to &&Definition") - }, - order: 2 -}); - -MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, { - group: '4_symbol_nav', - command: { - id: 'editor.action.goToTypeDefinition', - title: nls.localize({ key: 'miGotoTypeDefinition', comment: ['&& denotes a mnemonic'] }, "Go to &&Type Definition") - }, - order: 3 -}); - -MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, { - group: '4_symbol_nav', - command: { - id: 'editor.action.goToImplementation', - title: nls.localize({ key: 'miGotoImplementation', comment: ['&& denotes a mnemonic'] }, "Go to &&Implementation") - }, - order: 4 -}); diff --git a/src/vs/editor/contrib/gotoError/gotoError.ts b/src/vs/editor/contrib/gotoError/gotoError.ts index a30ebdc1747d..60c3621d37e8 100644 --- a/src/vs/editor/contrib/gotoError/gotoError.ts +++ b/src/vs/editor/contrib/gotoError/gotoError.ts @@ -43,7 +43,7 @@ class MarkerModel { this._markers = []; this._nextIdx = -1; this._ignoreSelectionChange = false; - this._onCurrentMarkerChanged = new Emitter(); + this._onCurrentMarkerChanged = new Emitter(); this._onMarkerSetChanged = new Emitter(); this.setMarkers(markers); @@ -245,7 +245,7 @@ export class MarkerController implements editorCommon.IEditorContribution { ]; this._widget = new MarkerNavigationWidget(this._editor, actions, this._themeService); this._widgetVisible.set(true); - this._widget.onDidClose(() => this._cleanUp(), this, this._disposeOnClose); + this._widget.onDidClose(() => this.closeMarkersNavigation(), this, this._disposeOnClose); this._disposeOnClose.add(this._model); this._disposeOnClose.add(this._widget); diff --git a/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts b/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts index 4049d8a19958..f71872e33a73 100644 --- a/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts +++ b/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts @@ -20,11 +20,10 @@ import { ScrollType } from 'vs/editor/common/editorCommon'; import { getBaseLabel, getPathLabel } from 'vs/base/common/labels'; import { isNonEmptyArray } from 'vs/base/common/arrays'; import { Event, Emitter } from 'vs/base/common/event'; -import { PeekViewWidget } from 'vs/editor/contrib/referenceSearch/peekViewWidget'; +import { PeekViewWidget, peekViewTitleForeground, peekViewTitleInfoForeground } from 'vs/editor/contrib/peekView/peekView'; import { basename } from 'vs/base/common/resources'; import { IAction } from 'vs/base/common/actions'; import { IActionBarOptions, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; -import { peekViewTitleForeground, peekViewTitleInfoForeground } from 'vs/editor/contrib/referenceSearch/referencesWidget'; import { SeverityIcon } from 'vs/platform/severityIcon/common/severityIcon'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; diff --git a/src/vs/editor/contrib/gotoError/media/gotoErrorWidget.css b/src/vs/editor/contrib/gotoError/media/gotoErrorWidget.css index 1a0be0be1aac..016634b51417 100644 --- a/src/vs/editor/contrib/gotoError/media/gotoErrorWidget.css +++ b/src/vs/editor/contrib/gotoError/media/gotoErrorWidget.css @@ -29,8 +29,9 @@ .monaco-editor .marker-widget .descriptioncontainer { position: absolute; white-space: pre; - -webkit-user-select: text; user-select: text; + -webkit-user-select: text; + -ms-user-select: text; padding: 8px 12px 0px 20px; } diff --git a/src/vs/editor/contrib/gotoSymbol/goToCommands.ts b/src/vs/editor/contrib/gotoSymbol/goToCommands.ts new file mode 100644 index 000000000000..f25cdaf89f71 --- /dev/null +++ b/src/vs/editor/contrib/gotoSymbol/goToCommands.ts @@ -0,0 +1,781 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { alert } from 'vs/base/browser/ui/aria/aria'; +import { createCancelablePromise, raceCancellation } from 'vs/base/common/async'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { isWeb } from 'vs/base/common/platform'; +import { ICodeEditor, isCodeEditor, IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; +import { EditorAction, IActionOptions, registerEditorAction, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import * as corePosition from 'vs/editor/common/core/position'; +import { Range, IRange } from 'vs/editor/common/core/range'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { ITextModel, IWordAtPosition } from 'vs/editor/common/model'; +import { LocationLink, Location, isLocationLink } from 'vs/editor/common/modes'; +import { MessageController } from 'vs/editor/contrib/message/messageController'; +import { PeekContext } from 'vs/editor/contrib/peekView/peekView'; +import { ReferencesController } from 'vs/editor/contrib/gotoSymbol/peek/referencesController'; +import { ReferencesModel } from 'vs/editor/contrib/gotoSymbol/referencesModel'; +import * as nls from 'vs/nls'; +import { MenuId, MenuRegistry, ISubmenuItem } from 'vs/platform/actions/common/actions'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { IEditorProgressService } from 'vs/platform/progress/common/progress'; +import { getDefinitionsAtPosition, getImplementationsAtPosition, getTypeDefinitionsAtPosition, getDeclarationsAtPosition, getReferencesAtPosition } from './goToSymbol'; +import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { EditorStateCancellationTokenSource, CodeEditorStateFlag } from 'vs/editor/browser/core/editorState'; +import { ISymbolNavigationService } from 'vs/editor/contrib/gotoSymbol/symbolNavigation'; +import { EditorOption, GoToLocationValues } from 'vs/editor/common/config/editorOptions'; +import { isStandalone } from 'vs/base/browser/browser'; +import { URI } from 'vs/base/common/uri'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ScrollType, IEditorAction } from 'vs/editor/common/editorCommon'; +import { assertType } from 'vs/base/common/types'; + + +MenuRegistry.appendMenuItem(MenuId.EditorContext, { + submenu: MenuId.EditorContextPeek, + title: nls.localize('peek.submenu', "Peek"), + group: 'navigation', + order: 100 +}); + +export interface SymbolNavigationActionConfig { + openToSide: boolean; + openInPeek: boolean; + muteMessage: boolean; +} + +abstract class SymbolNavigationAction extends EditorAction { + + private readonly _configuration: SymbolNavigationActionConfig; + + constructor(configuration: SymbolNavigationActionConfig, opts: IActionOptions) { + super(opts); + this._configuration = configuration; + } + + run(accessor: ServicesAccessor, editor: ICodeEditor): Promise { + if (!editor.hasModel()) { + return Promise.resolve(undefined); + } + const notificationService = accessor.get(INotificationService); + const editorService = accessor.get(ICodeEditorService); + const progressService = accessor.get(IEditorProgressService); + const symbolNavService = accessor.get(ISymbolNavigationService); + + const model = editor.getModel(); + const pos = editor.getPosition(); + + const cts = new EditorStateCancellationTokenSource(editor, CodeEditorStateFlag.Value | CodeEditorStateFlag.Position); + + const promise = raceCancellation(this._getLocationModel(model, pos, cts.token), cts.token).then(async references => { + + if (!references || cts.token.isCancellationRequested) { + return; + } + + alert(references.ariaMessage); + + let altAction: IEditorAction | null | undefined; + if (references.referenceAt(model.uri, pos)) { + const altActionId = this._getAlternativeCommand(editor); + if (altActionId !== this.id) { + altAction = editor.getAction(altActionId); + } + } + + const referenceCount = references.references.length; + + if (referenceCount === 0) { + // no result -> show message + if (!this._configuration.muteMessage) { + const info = model.getWordAtPosition(pos); + MessageController.get(editor).showMessage(this._getNoResultFoundMessage(info), pos); + } + } else if (referenceCount === 1 && altAction) { + // already at the only result, run alternative + altAction.run(); + + } else { + // normal results handling + return this._onResult(editorService, symbolNavService, editor, references); + } + + }, (err) => { + // report an error + notificationService.error(err); + }).finally(() => { + cts.dispose(); + }); + + progressService.showWhile(promise, 250); + return promise; + } + + protected abstract _getLocationModel(model: ITextModel, position: corePosition.Position, token: CancellationToken): Promise; + + protected abstract _getNoResultFoundMessage(info: IWordAtPosition | null): string; + + protected abstract _getAlternativeCommand(editor: IActiveCodeEditor): string; + + protected abstract _getGoToPreference(editor: IActiveCodeEditor): GoToLocationValues; + + private async _onResult(editorService: ICodeEditorService, symbolNavService: ISymbolNavigationService, editor: IActiveCodeEditor, model: ReferencesModel): Promise { + + const gotoLocation = this._getGoToPreference(editor); + if (this._configuration.openInPeek || (gotoLocation === 'peek' && model.references.length > 1)) { + this._openInPeek(editor, model); + + } else { + const next = model.firstReference()!; + const peek = model.references.length > 1 && gotoLocation === 'gotoAndPeek'; + const targetEditor = await this._openReference(editor, editorService, next, this._configuration.openToSide, !peek); + if (peek && targetEditor) { + this._openInPeek(targetEditor, model); + } else { + model.dispose(); + } + + // keep remaining locations around when using + // 'goto'-mode + if (gotoLocation === 'goto') { + symbolNavService.put(next); + } + } + } + + private async _openReference(editor: ICodeEditor, editorService: ICodeEditorService, reference: Location | LocationLink, sideBySide: boolean, highlight: boolean): Promise { + // range is the target-selection-range when we have one + // and the fallback is the 'full' range + let range: IRange | undefined = undefined; + if (isLocationLink(reference)) { + range = reference.targetSelectionRange; + } + if (!range) { + range = reference.range; + } + + const targetEditor = await editorService.openCodeEditor({ + resource: reference.uri, + options: { + selection: Range.collapseToStart(range), + revealInCenterIfOutsideViewport: true + } + }, editor, sideBySide); + + if (!targetEditor) { + return undefined; + } + + if (highlight) { + const modelNow = targetEditor.getModel(); + const ids = targetEditor.deltaDecorations([], [{ range, options: { className: 'symbolHighlight' } }]); + setTimeout(() => { + if (targetEditor.getModel() === modelNow) { + targetEditor.deltaDecorations(ids, []); + } + }, 350); + } + + return targetEditor; + } + + private _openInPeek(target: ICodeEditor, model: ReferencesModel) { + let controller = ReferencesController.get(target); + if (controller && target.hasModel()) { + controller.toggleWidget(target.getSelection(), createCancelablePromise(_ => Promise.resolve(model)), this._configuration.openInPeek); + } else { + model.dispose(); + } + } +} + +//#region --- DEFINITION + +export class DefinitionAction extends SymbolNavigationAction { + + protected async _getLocationModel(model: ITextModel, position: corePosition.Position, token: CancellationToken): Promise { + return new ReferencesModel(await getDefinitionsAtPosition(model, position, token), nls.localize('def.title', 'Definitions')); + } + + protected _getNoResultFoundMessage(info: IWordAtPosition | null): string { + return info && info.word + ? nls.localize('noResultWord', "No definition found for '{0}'", info.word) + : nls.localize('generic.noResults', "No definition found"); + } + + protected _getAlternativeCommand(editor: IActiveCodeEditor): string { + return editor.getOption(EditorOption.gotoLocation).alternativeDefinitionCommand; + } + + protected _getGoToPreference(editor: IActiveCodeEditor): GoToLocationValues { + return editor.getOption(EditorOption.gotoLocation).multipleDefinitions; + } +} + +const goToDefinitionKb = isWeb && !isStandalone + ? KeyMod.CtrlCmd | KeyCode.F12 + : KeyCode.F12; + +registerEditorAction(class GoToDefinitionAction extends DefinitionAction { + + static readonly id = 'editor.action.revealDefinition'; + + constructor() { + super({ + openToSide: false, + openInPeek: false, + muteMessage: false + }, { + id: GoToDefinitionAction.id, + label: nls.localize('actions.goToDecl.label', "Go to Definition"), + alias: 'Go to Definition', + precondition: ContextKeyExpr.and( + EditorContextKeys.hasDefinitionProvider, + EditorContextKeys.isInEmbeddedEditor.toNegated()), + kbOpts: { + kbExpr: EditorContextKeys.editorTextFocus, + primary: goToDefinitionKb, + weight: KeybindingWeight.EditorContrib + }, + contextMenuOpts: { + group: 'navigation', + order: 1.1 + }, + /*menuOpts: { {{SQL CARBON EDIT}} remove entry + menuId: MenuId.MenubarGoMenu, + group: '4_symbol_nav', + order: 2, + title: nls.localize({ key: 'miGotoDefinition', comment: ['&& denotes a mnemonic'] }, "Go to &&Definition") + }*/ + }); + CommandsRegistry.registerCommandAlias('editor.action.goToDeclaration', GoToDefinitionAction.id); + } +}); + +registerEditorAction(class OpenDefinitionToSideAction extends DefinitionAction { + + static readonly id = 'editor.action.revealDefinitionAside'; + + constructor() { + super({ + openToSide: true, + openInPeek: false, + muteMessage: false + }, { + id: OpenDefinitionToSideAction.id, + label: nls.localize('actions.goToDeclToSide.label', "Open Definition to the Side"), + alias: 'Open Definition to the Side', + precondition: ContextKeyExpr.and( + EditorContextKeys.hasDefinitionProvider, + EditorContextKeys.isInEmbeddedEditor.toNegated()), + kbOpts: { + kbExpr: EditorContextKeys.editorTextFocus, + primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, goToDefinitionKb), + weight: KeybindingWeight.EditorContrib + } + }); + CommandsRegistry.registerCommandAlias('editor.action.openDeclarationToTheSide', OpenDefinitionToSideAction.id); + } +}); + +registerEditorAction(class PeekDefinitionAction extends DefinitionAction { + + static readonly id = 'editor.action.peekDefinition'; + + constructor() { + super({ + openToSide: false, + openInPeek: true, + muteMessage: false + }, { + id: PeekDefinitionAction.id, + label: nls.localize('actions.previewDecl.label', "Peek Definition"), + alias: 'Peek Definition', + precondition: ContextKeyExpr.and( + EditorContextKeys.hasDefinitionProvider, + PeekContext.notInPeekEditor, + EditorContextKeys.isInEmbeddedEditor.toNegated() + ), + kbOpts: { + kbExpr: EditorContextKeys.editorTextFocus, + primary: KeyMod.Alt | KeyCode.F12, + linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.F10 }, + weight: KeybindingWeight.EditorContrib + }, + contextMenuOpts: { + menuId: MenuId.EditorContextPeek, + group: 'peek', + order: 2 + } + }); + CommandsRegistry.registerCommandAlias('editor.action.previewDeclaration', PeekDefinitionAction.id); + } +}); + +//#endregion + +//#region --- DECLARATION + +class DeclarationAction extends SymbolNavigationAction { + + protected async _getLocationModel(model: ITextModel, position: corePosition.Position, token: CancellationToken): Promise { + return new ReferencesModel(await getDeclarationsAtPosition(model, position, token), nls.localize('decl.title', 'Declarations')); + } + + protected _getNoResultFoundMessage(info: IWordAtPosition | null): string { + return info && info.word + ? nls.localize('decl.noResultWord', "No declaration found for '{0}'", info.word) + : nls.localize('decl.generic.noResults', "No declaration found"); + } + + protected _getAlternativeCommand(editor: IActiveCodeEditor): string { + return editor.getOption(EditorOption.gotoLocation).alternativeDeclarationCommand; + } + + protected _getGoToPreference(editor: IActiveCodeEditor): GoToLocationValues { + return editor.getOption(EditorOption.gotoLocation).multipleDeclarations; + } +} + +registerEditorAction(class GoToDeclarationAction extends DeclarationAction { + + static readonly id = 'editor.action.revealDeclaration'; + + constructor() { + super({ + openToSide: false, + openInPeek: false, + muteMessage: false + }, { + id: GoToDeclarationAction.id, + label: nls.localize('actions.goToDeclaration.label', "Go to Declaration"), + alias: 'Go to Declaration', + precondition: ContextKeyExpr.and( + EditorContextKeys.hasDeclarationProvider, + EditorContextKeys.isInEmbeddedEditor.toNegated() + ), + contextMenuOpts: { + group: 'navigation', + order: 1.3 + }, + /*menuOpts: { {{SQL CARBON EDIT}} remove entry + menuId: MenuId.MenubarGoMenu, + group: '4_symbol_nav', + order: 3, + title: nls.localize({ key: 'miGotoDeclaration', comment: ['&& denotes a mnemonic'] }, "Go to &&Declaration") + },*/ + }); + } + + protected _getNoResultFoundMessage(info: IWordAtPosition | null): string { + return info && info.word + ? nls.localize('decl.noResultWord', "No declaration found for '{0}'", info.word) + : nls.localize('decl.generic.noResults', "No declaration found"); + } +}); + +registerEditorAction(class PeekDeclarationAction extends DeclarationAction { + constructor() { + super({ + openToSide: false, + openInPeek: true, + muteMessage: false + }, { + id: 'editor.action.peekDeclaration', + label: nls.localize('actions.peekDecl.label', "Peek Declaration"), + alias: 'Peek Declaration', + precondition: ContextKeyExpr.and( + EditorContextKeys.hasDeclarationProvider, + PeekContext.notInPeekEditor, + EditorContextKeys.isInEmbeddedEditor.toNegated() + ), + contextMenuOpts: { + menuId: MenuId.EditorContextPeek, + group: 'peek', + order: 3 + } + }); + } +}); + +//#endregion + +//#region --- TYPE DEFINITION + +class TypeDefinitionAction extends SymbolNavigationAction { + + protected async _getLocationModel(model: ITextModel, position: corePosition.Position, token: CancellationToken): Promise { + return new ReferencesModel(await getTypeDefinitionsAtPosition(model, position, token), nls.localize('typedef.title', 'Type Definitions')); + } + + protected _getNoResultFoundMessage(info: IWordAtPosition | null): string { + return info && info.word + ? nls.localize('goToTypeDefinition.noResultWord', "No type definition found for '{0}'", info.word) + : nls.localize('goToTypeDefinition.generic.noResults', "No type definition found"); + } + + protected _getAlternativeCommand(editor: IActiveCodeEditor): string { + return editor.getOption(EditorOption.gotoLocation).alternativeTypeDefinitionCommand; + } + + protected _getGoToPreference(editor: IActiveCodeEditor): GoToLocationValues { + return editor.getOption(EditorOption.gotoLocation).multipleTypeDefinitions; + } +} + +registerEditorAction(class GoToTypeDefinitionAction extends TypeDefinitionAction { + + public static readonly ID = 'editor.action.goToTypeDefinition'; + + constructor() { + super({ + openToSide: false, + openInPeek: false, + muteMessage: false + }, { + id: GoToTypeDefinitionAction.ID, + label: nls.localize('actions.goToTypeDefinition.label', "Go to Type Definition"), + alias: 'Go to Type Definition', + precondition: ContextKeyExpr.and( + EditorContextKeys.hasTypeDefinitionProvider, + EditorContextKeys.isInEmbeddedEditor.toNegated()), + kbOpts: { + kbExpr: EditorContextKeys.editorTextFocus, + primary: 0, + weight: KeybindingWeight.EditorContrib + }, + contextMenuOpts: { + group: 'navigation', + order: 1.4 + }, + /*menuOpts: { {{SQL CARBON EDIT}} remove entry + menuId: MenuId.MenubarGoMenu, + group: '4_symbol_nav', + order: 3, + title: nls.localize({ key: 'miGotoTypeDefinition', comment: ['&& denotes a mnemonic'] }, "Go to &&Type Definition") + }*/ + }); + } +}); + +registerEditorAction(class PeekTypeDefinitionAction extends TypeDefinitionAction { + + public static readonly ID = 'editor.action.peekTypeDefinition'; + + constructor() { + super({ + openToSide: false, + openInPeek: true, + muteMessage: false + }, { + id: PeekTypeDefinitionAction.ID, + label: nls.localize('actions.peekTypeDefinition.label', "Peek Type Definition"), + alias: 'Peek Type Definition', + precondition: ContextKeyExpr.and( + EditorContextKeys.hasTypeDefinitionProvider, + PeekContext.notInPeekEditor, + EditorContextKeys.isInEmbeddedEditor.toNegated() + ), + contextMenuOpts: { + menuId: MenuId.EditorContextPeek, + group: 'peek', + order: 4 + } + }); + } +}); + +//#endregion + +//#region --- IMPLEMENTATION + +class ImplementationAction extends SymbolNavigationAction { + + protected async _getLocationModel(model: ITextModel, position: corePosition.Position, token: CancellationToken): Promise { + return new ReferencesModel(await getImplementationsAtPosition(model, position, token), nls.localize('impl.title', 'Implementations')); + } + + protected _getNoResultFoundMessage(info: IWordAtPosition | null): string { + return info && info.word + ? nls.localize('goToImplementation.noResultWord', "No implementation found for '{0}'", info.word) + : nls.localize('goToImplementation.generic.noResults', "No implementation found"); + } + + protected _getAlternativeCommand(editor: IActiveCodeEditor): string { + return editor.getOption(EditorOption.gotoLocation).alternativeImplementationCommand; + } + + protected _getGoToPreference(editor: IActiveCodeEditor): GoToLocationValues { + return editor.getOption(EditorOption.gotoLocation).multipleImplementations; + } +} + +registerEditorAction(class GoToImplementationAction extends ImplementationAction { + + public static readonly ID = 'editor.action.goToImplementation'; + + constructor() { + super({ + openToSide: false, + openInPeek: false, + muteMessage: false + }, { + id: GoToImplementationAction.ID, + label: nls.localize('actions.goToImplementation.label', "Go to Implementations"), + alias: 'Go to Implementations', + precondition: ContextKeyExpr.and( + EditorContextKeys.hasImplementationProvider, + EditorContextKeys.isInEmbeddedEditor.toNegated()), + kbOpts: { + kbExpr: EditorContextKeys.editorTextFocus, + primary: KeyMod.CtrlCmd | KeyCode.F12, + weight: KeybindingWeight.EditorContrib + }, + /*menuOpts: { {{SQL CARBON EDIT}} remove entry + menuId: MenuId.MenubarGoMenu, + group: '4_symbol_nav', + order: 4, + title: nls.localize({ key: 'miGotoImplementation', comment: ['&& denotes a mnemonic'] }, "Go to &&Implementations") + },*/ + contextMenuOpts: { + group: 'navigation', + order: 1.45 + } + }); + } +}); + +registerEditorAction(class PeekImplementationAction extends ImplementationAction { + + public static readonly ID = 'editor.action.peekImplementation'; + + constructor() { + super({ + openToSide: false, + openInPeek: true, + muteMessage: false + }, { + id: PeekImplementationAction.ID, + label: nls.localize('actions.peekImplementation.label', "Peek Implementations"), + alias: 'Peek Implementations', + precondition: ContextKeyExpr.and( + EditorContextKeys.hasImplementationProvider, + PeekContext.notInPeekEditor, + EditorContextKeys.isInEmbeddedEditor.toNegated() + ), + kbOpts: { + kbExpr: EditorContextKeys.editorTextFocus, + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.F12, + weight: KeybindingWeight.EditorContrib + }, + contextMenuOpts: { + menuId: MenuId.EditorContextPeek, + group: 'peek', + order: 5 + } + }); + } +}); + +//#endregion + +//#region --- REFERENCES + +abstract class ReferencesAction extends SymbolNavigationAction { + + protected _getNoResultFoundMessage(info: IWordAtPosition | null): string { + return info + ? nls.localize('references.no', "No references found for '{0}'", info.word) + : nls.localize('references.noGeneric', "No references found"); + } + + protected _getAlternativeCommand(editor: IActiveCodeEditor): string { + return editor.getOption(EditorOption.gotoLocation).alternativeReferenceCommand; + } + + protected _getGoToPreference(editor: IActiveCodeEditor): GoToLocationValues { + return editor.getOption(EditorOption.gotoLocation).multipleReferences; + } +} + +registerEditorAction(class GoToReferencesAction extends ReferencesAction { + + constructor() { + super({ + openToSide: false, + openInPeek: false, + muteMessage: false + }, { + id: 'editor.action.goToReferences', + label: nls.localize('goToReferences.label', "Go to References"), + alias: 'Go to References', + precondition: ContextKeyExpr.and( + EditorContextKeys.hasReferenceProvider, + PeekContext.notInPeekEditor, + EditorContextKeys.isInEmbeddedEditor.toNegated() + ), + kbOpts: { + kbExpr: EditorContextKeys.editorTextFocus, + primary: KeyMod.Shift | KeyCode.F12, + weight: KeybindingWeight.EditorContrib + }, + contextMenuOpts: { + group: 'navigation', + order: 1.45 + }, + /*menuOpts: { {{SQL CARBON EDIT}} remove entry + menuId: MenuId.MenubarGoMenu, + group: '4_symbol_nav', + order: 5, + title: nls.localize({ key: 'miGotoReference', comment: ['&& denotes a mnemonic'] }, "Go to &&References") + },*/ + }); + } + + protected async _getLocationModel(model: ITextModel, position: corePosition.Position, token: CancellationToken): Promise { + return new ReferencesModel(await getReferencesAtPosition(model, position, true, token), nls.localize('ref.title', 'References')); + } +}); + +registerEditorAction(class PeekReferencesAction extends ReferencesAction { + + constructor() { + super({ + openToSide: false, + openInPeek: true, + muteMessage: false + }, { + id: 'editor.action.referenceSearch.trigger', + label: nls.localize('references.action.label', "Peek References"), + alias: 'Peek References', + precondition: ContextKeyExpr.and( + EditorContextKeys.hasReferenceProvider, + PeekContext.notInPeekEditor, + EditorContextKeys.isInEmbeddedEditor.toNegated() + ), + contextMenuOpts: { + menuId: MenuId.EditorContextPeek, + group: 'peek', + order: 6 + } + }); + } + + protected async _getLocationModel(model: ITextModel, position: corePosition.Position, token: CancellationToken): Promise { + return new ReferencesModel(await getReferencesAtPosition(model, position, false, token), nls.localize('ref.title', 'References')); + } +}); + +//#endregion + + +//#region --- GENERIC goto symbols command + +class GenericGoToLocationAction extends SymbolNavigationAction { + + constructor( + private readonly _references: Location[], + private readonly _gotoMultipleBehaviour: GoToLocationValues | undefined + ) { + super({ + muteMessage: true, + openInPeek: false, + openToSide: false + }, { + id: 'editor.action.goToLocation', + label: nls.localize('label.generic', "Go To Any Symbol"), + alias: 'Go To Any Symbol', + precondition: ContextKeyExpr.and( + PeekContext.notInPeekEditor, + EditorContextKeys.isInEmbeddedEditor.toNegated() + ), + }); + } + + protected async _getLocationModel(_model: ITextModel, _position: corePosition.Position, _token: CancellationToken): Promise { + return new ReferencesModel(this._references, nls.localize('generic.title', 'Locations')); + } + + protected _getNoResultFoundMessage(info: IWordAtPosition | null): string { + return info && nls.localize('generic.noResult', "No results for '{0}'", info.word) || ''; + } + + protected _getGoToPreference(editor: IActiveCodeEditor): GoToLocationValues { + return this._gotoMultipleBehaviour ?? editor.getOption(EditorOption.gotoLocation).multipleReferences; + } + + protected _getAlternativeCommand() { return ''; } +} + +CommandsRegistry.registerCommand({ + id: 'editor.action.goToLocations', + description: { + description: 'Go to locations from a position in a file', + args: [ + { name: 'uri', description: 'The text document in which to start', constraint: URI }, + { name: 'position', description: 'The position at which to start', constraint: corePosition.Position.isIPosition }, + { name: 'locations', description: 'An array of locations.', constraint: Array }, + { name: 'multiple', description: 'Define what to do when having multiple results, either `peek`, `gotoAndPeek`, or `goto' }, + ] + }, + handler: async (accessor: ServicesAccessor, resource: any, position: any, references: any, multiple?: any) => { + assertType(URI.isUri(resource)); + assertType(corePosition.Position.isIPosition(position)); + assertType(Array.isArray(references)); + assertType(typeof multiple === 'undefined' || typeof multiple === 'string'); + + const editorService = accessor.get(ICodeEditorService); + const editor = await editorService.openCodeEditor({ resource }, editorService.getFocusedCodeEditor()); + + if (isCodeEditor(editor)) { + editor.setPosition(position); + editor.revealPositionInCenterIfOutsideViewport(position, ScrollType.Smooth); + + return editor.invokeWithinContext(accessor => { + const command = new GenericGoToLocationAction(references, multiple as GoToLocationValues); + accessor.get(IInstantiationService).invokeFunction(command.run.bind(command), editor); + }); + } + } +}); + +//#endregion + + +//#region --- REFERENCE search special commands + +CommandsRegistry.registerCommand({ + id: 'editor.action.findReferences', + handler: (accessor: ServicesAccessor, resource: any, position: any) => { + assertType(URI.isUri(resource)); + assertType(corePosition.Position.isIPosition(position)); + + const codeEditorService = accessor.get(ICodeEditorService); + return codeEditorService.openCodeEditor({ resource }, codeEditorService.getFocusedCodeEditor()).then(control => { + if (!isCodeEditor(control) || !control.hasModel()) { + return undefined; + } + + const controller = ReferencesController.get(control); + if (!controller) { + return undefined; + } + + const references = createCancelablePromise(token => getReferencesAtPosition(control.getModel(), corePosition.Position.lift(position), false, token).then(references => new ReferencesModel(references, nls.localize('ref.title', 'References')))); + const range = new Range(position.lineNumber, position.column, position.lineNumber, position.column); + return Promise.resolve(controller.toggleWidget(range, references, false)); + }); + } +}); + +// use NEW command +CommandsRegistry.registerCommandAlias('editor.action.showReferences', 'editor.action.goToLocations'); + +//#endregion diff --git a/src/vs/editor/contrib/goToDefinition/goToDefinition.ts b/src/vs/editor/contrib/gotoSymbol/goToSymbol.ts similarity index 68% rename from src/vs/editor/contrib/goToDefinition/goToDefinition.ts rename to src/vs/editor/contrib/gotoSymbol/goToSymbol.ts index bc5de99e2a48..fe0444bdddbe 100644 --- a/src/vs/editor/contrib/goToDefinition/goToDefinition.ts +++ b/src/vs/editor/contrib/gotoSymbol/goToSymbol.ts @@ -9,11 +9,11 @@ import { onUnexpectedExternalError } from 'vs/base/common/errors'; import { registerDefaultLanguageCommand } from 'vs/editor/browser/editorExtensions'; import { Position } from 'vs/editor/common/core/position'; import { ITextModel } from 'vs/editor/common/model'; -import { LocationLink, DefinitionProviderRegistry, ImplementationProviderRegistry, TypeDefinitionProviderRegistry, DeclarationProviderRegistry, ProviderResult } from 'vs/editor/common/modes'; +import { LocationLink, DefinitionProviderRegistry, ImplementationProviderRegistry, TypeDefinitionProviderRegistry, DeclarationProviderRegistry, ProviderResult, ReferenceProviderRegistry } from 'vs/editor/common/modes'; import { LanguageFeatureRegistry } from 'vs/editor/common/modes/languageFeatureRegistry'; -function getDefinitions( +function getLocationLinks( model: ITextModel, position: Position, registry: LanguageFeatureRegistry, @@ -35,30 +35,45 @@ function getDefinitions( export function getDefinitionsAtPosition(model: ITextModel, position: Position, token: CancellationToken): Promise { - return getDefinitions(model, position, DefinitionProviderRegistry, (provider, model, position) => { + return getLocationLinks(model, position, DefinitionProviderRegistry, (provider, model, position) => { return provider.provideDefinition(model, position, token); }); } export function getDeclarationsAtPosition(model: ITextModel, position: Position, token: CancellationToken): Promise { - return getDefinitions(model, position, DeclarationProviderRegistry, (provider, model, position) => { + return getLocationLinks(model, position, DeclarationProviderRegistry, (provider, model, position) => { return provider.provideDeclaration(model, position, token); }); } export function getImplementationsAtPosition(model: ITextModel, position: Position, token: CancellationToken): Promise { - return getDefinitions(model, position, ImplementationProviderRegistry, (provider, model, position) => { + return getLocationLinks(model, position, ImplementationProviderRegistry, (provider, model, position) => { return provider.provideImplementation(model, position, token); }); } export function getTypeDefinitionsAtPosition(model: ITextModel, position: Position, token: CancellationToken): Promise { - return getDefinitions(model, position, TypeDefinitionProviderRegistry, (provider, model, position) => { + return getLocationLinks(model, position, TypeDefinitionProviderRegistry, (provider, model, position) => { return provider.provideTypeDefinition(model, position, token); }); } +export function getReferencesAtPosition(model: ITextModel, position: Position, compact: boolean, token: CancellationToken): Promise { + return getLocationLinks(model, position, ReferenceProviderRegistry, async (provider, model, position) => { + const result = await provider.provideReferences(model, position, { includeDeclaration: true }, token); + if (!compact || !result || result.length !== 2) { + return result; + } + const resultWithoutDeclaration = await provider.provideReferences(model, position, { includeDeclaration: false }, token); + if (resultWithoutDeclaration && resultWithoutDeclaration.length === 1) { + return resultWithoutDeclaration; + } + return result; + }); +} + registerDefaultLanguageCommand('_executeDefinitionProvider', (model, position) => getDefinitionsAtPosition(model, position, CancellationToken.None)); registerDefaultLanguageCommand('_executeDeclarationProvider', (model, position) => getDeclarationsAtPosition(model, position, CancellationToken.None)); registerDefaultLanguageCommand('_executeImplementationProvider', (model, position) => getImplementationsAtPosition(model, position, CancellationToken.None)); registerDefaultLanguageCommand('_executeTypeDefinitionProvider', (model, position) => getTypeDefinitionsAtPosition(model, position, CancellationToken.None)); +registerDefaultLanguageCommand('_executeReferenceProvider', (model, position) => getReferencesAtPosition(model, position, false, CancellationToken.None)); diff --git a/src/vs/editor/contrib/goToDefinition/clickLinkGesture.ts b/src/vs/editor/contrib/gotoSymbol/link/clickLinkGesture.ts similarity index 99% rename from src/vs/editor/contrib/goToDefinition/clickLinkGesture.ts rename to src/vs/editor/contrib/gotoSymbol/link/clickLinkGesture.ts index c6c55e1bb56a..4c3252ea7a51 100644 --- a/src/vs/editor/contrib/goToDefinition/clickLinkGesture.ts +++ b/src/vs/editor/contrib/gotoSymbol/link/clickLinkGesture.ts @@ -3,7 +3,6 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!./goToDefinitionMouse'; import { KeyCode } from 'vs/base/common/keyCodes'; import * as browser from 'vs/base/browser/browser'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; diff --git a/src/vs/editor/contrib/goToDefinition/goToDefinitionMouse.css b/src/vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition.css similarity index 100% rename from src/vs/editor/contrib/goToDefinition/goToDefinitionMouse.css rename to src/vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition.css diff --git a/src/vs/editor/contrib/goToDefinition/goToDefinitionMouse.ts b/src/vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition.ts similarity index 67% rename from src/vs/editor/contrib/goToDefinition/goToDefinitionMouse.ts rename to src/vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition.ts index 81f95ab59f80..69160c41c2f3 100644 --- a/src/vs/editor/contrib/goToDefinition/goToDefinitionMouse.ts +++ b/src/vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!./goToDefinitionMouse'; +import 'vs/css!./goToDefinitionAtPosition'; import * as nls from 'vs/nls'; import { createCancelablePromise, CancelablePromise } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -13,29 +13,31 @@ import { IModeService } from 'vs/editor/common/services/modeService'; import { Range, IRange } from 'vs/editor/common/core/range'; import * as editorCommon from 'vs/editor/common/editorCommon'; import { DefinitionProviderRegistry, LocationLink } from 'vs/editor/common/modes'; -import { ICodeEditor, IMouseTarget, MouseTargetType } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditor, MouseTargetType } from 'vs/editor/browser/editorBrowser'; import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; -import { getDefinitionsAtPosition } from './goToDefinition'; +import { getDefinitionsAtPosition } from '../goToSymbol'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { editorActiveLinkForeground } from 'vs/platform/theme/common/colorRegistry'; import { EditorState, CodeEditorStateFlag } from 'vs/editor/browser/core/editorState'; -import { DefinitionAction, DefinitionActionConfig } from './goToDefinitionCommands'; -import { ClickLinkGesture, ClickLinkMouseEvent, ClickLinkKeyboardEvent } from 'vs/editor/contrib/goToDefinition/clickLinkGesture'; +import { DefinitionAction } from '../goToCommands'; +import { ClickLinkGesture, ClickLinkMouseEvent, ClickLinkKeyboardEvent } from 'vs/editor/contrib/gotoSymbol/link/clickLinkGesture'; import { IWordAtPosition, IModelDeltaDecoration, ITextModel, IFoundBracket } from 'vs/editor/common/model'; import { Position } from 'vs/editor/common/core/position'; import { withNullAsUndefined } from 'vs/base/common/types'; +import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorContribution { +export class GotoDefinitionAtPositionEditorContribution implements editorCommon.IEditorContribution { - public static readonly ID = 'editor.contrib.gotodefinitionwithmouse'; + public static readonly ID = 'editor.contrib.gotodefinitionatposition'; static readonly MAX_SOURCE_PREVIEW_LINES = 8; private readonly editor: ICodeEditor; private readonly toUnhook = new DisposableStore(); - private decorations: string[] = []; - private currentWordUnderMouse: IWordAtPosition | null = null; + private readonly toUnhookForKeyboard = new DisposableStore(); + private linkDecorations: string[] = []; + private currentWordAtPosition: IWordAtPosition | null = null; private previousPromise: CancelablePromise | null = null; constructor( @@ -49,55 +51,96 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC this.toUnhook.add(linkGesture); this.toUnhook.add(linkGesture.onMouseMoveOrRelevantKeyDown(([mouseEvent, keyboardEvent]) => { - this.startFindDefinition(mouseEvent, withNullAsUndefined(keyboardEvent)); + this.startFindDefinitionFromMouse(mouseEvent, withNullAsUndefined(keyboardEvent)); })); this.toUnhook.add(linkGesture.onExecute((mouseEvent: ClickLinkMouseEvent) => { if (this.isEnabled(mouseEvent)) { - this.gotoDefinition(mouseEvent.target, mouseEvent.hasSideBySideModifier).then(() => { - this.removeDecorations(); + this.gotoDefinition(mouseEvent.target.position!, mouseEvent.hasSideBySideModifier).then(() => { + this.removeLinkDecorations(); }, (error: Error) => { - this.removeDecorations(); + this.removeLinkDecorations(); onUnexpectedError(error); }); } })); this.toUnhook.add(linkGesture.onCancel(() => { - this.removeDecorations(); - this.currentWordUnderMouse = null; + this.removeLinkDecorations(); + this.currentWordAtPosition = null; })); + } + + static get(editor: ICodeEditor): GotoDefinitionAtPositionEditorContribution { + return editor.getContribution(GotoDefinitionAtPositionEditorContribution.ID); + } + startFindDefinitionFromCursor(position: Position) { + // For issue: https://github.com/microsoft/vscode/issues/46257 + // equivalent to mouse move with meta/ctrl key + + // First find the definition and add decorations + // to the editor to be shown with the content hover widget + return this.startFindDefinition(position).then(() => { + + // Add listeners for editor cursor move and key down events + // Dismiss the "extended" editor decorations when the user hides + // the hover widget. There is no event for the widget itself so these + // serve as a best effort. After removing the link decorations, the hover + // widget is clean and will only show declarations per next request. + this.toUnhookForKeyboard.add(this.editor.onDidChangeCursorPosition(() => { + this.currentWordAtPosition = null; + this.removeLinkDecorations(); + this.toUnhookForKeyboard.clear(); + })); + + this.toUnhookForKeyboard.add(this.editor.onKeyDown((e: IKeyboardEvent) => { + if (e) { + this.currentWordAtPosition = null; + this.removeLinkDecorations(); + this.toUnhookForKeyboard.clear(); + } + })); + }); } - private startFindDefinition(mouseEvent: ClickLinkMouseEvent, withKey?: ClickLinkKeyboardEvent): void { + private startFindDefinitionFromMouse(mouseEvent: ClickLinkMouseEvent, withKey?: ClickLinkKeyboardEvent): void { // check if we are active and on a content widget - if (mouseEvent.target.type === MouseTargetType.CONTENT_WIDGET && this.decorations.length > 0) { + if (mouseEvent.target.type === MouseTargetType.CONTENT_WIDGET && this.linkDecorations.length > 0) { return; } if (!this.editor.hasModel() || !this.isEnabled(mouseEvent, withKey)) { - this.currentWordUnderMouse = null; - this.removeDecorations(); + this.currentWordAtPosition = null; + this.removeLinkDecorations(); return; } + const position = mouseEvent.target.position!; + + this.startFindDefinition(position); + } + + private startFindDefinition(position: Position): Promise { + + // Dispose listeners for updating decorations when using keyboard to show definition hover + this.toUnhookForKeyboard.clear(); + // Find word at mouse position - const word = mouseEvent.target.position ? this.editor.getModel().getWordAtPosition(mouseEvent.target.position) : null; + const word = position ? this.editor.getModel()?.getWordAtPosition(position) : null; if (!word) { - this.currentWordUnderMouse = null; - this.removeDecorations(); - return; + this.currentWordAtPosition = null; + this.removeLinkDecorations(); + return Promise.resolve(0); } - const position = mouseEvent.target.position!; // Return early if word at position is still the same - if (this.currentWordUnderMouse && this.currentWordUnderMouse.startColumn === word.startColumn && this.currentWordUnderMouse.endColumn === word.endColumn && this.currentWordUnderMouse.word === word.word) { - return; + if (this.currentWordAtPosition && this.currentWordAtPosition.startColumn === word.startColumn && this.currentWordAtPosition.endColumn === word.endColumn && this.currentWordAtPosition.word === word.word) { + return Promise.resolve(0); } - this.currentWordUnderMouse = word; + this.currentWordAtPosition = word; // Find definition and decorate word if found let state = new EditorState(this.editor, CodeEditorStateFlag.Position | CodeEditorStateFlag.Value | CodeEditorStateFlag.Selection | CodeEditorStateFlag.Scroll); @@ -107,11 +150,11 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC this.previousPromise = null; } - this.previousPromise = createCancelablePromise(token => this.findDefinition(mouseEvent.target, token)); + this.previousPromise = createCancelablePromise(token => this.findDefinition(position, token)); - this.previousPromise.then(results => { + return this.previousPromise.then(results => { if (!results || !results.length || !state.validate(this.editor)) { - this.removeDecorations(); + this.removeLinkDecorations(); return; } @@ -170,7 +213,7 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC private getPreviewValue(textEditorModel: ITextModel, startLineNumber: number, result: LocationLink) { let rangeToUse = result.targetSelectionRange ? result.range : this.getPreviewRangeBasedOnBrackets(textEditorModel, startLineNumber); const numberOfLinesInRange = rangeToUse.endLineNumber - rangeToUse.startLineNumber; - if (numberOfLinesInRange >= GotoDefinitionWithMouseEditorContribution.MAX_SOURCE_PREVIEW_LINES) { + if (numberOfLinesInRange >= GotoDefinitionAtPositionEditorContribution.MAX_SOURCE_PREVIEW_LINES) { rangeToUse = this.getPreviewRangeBasedOnIndentation(textEditorModel, startLineNumber); } @@ -193,7 +236,7 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC private getPreviewRangeBasedOnIndentation(textEditorModel: ITextModel, startLineNumber: number) { const startIndent = textEditorModel.getLineFirstNonWhitespaceColumn(startLineNumber); - const maxLineNumber = Math.min(textEditorModel.getLineCount(), startLineNumber + GotoDefinitionWithMouseEditorContribution.MAX_SOURCE_PREVIEW_LINES); + const maxLineNumber = Math.min(textEditorModel.getLineCount(), startLineNumber + GotoDefinitionAtPositionEditorContribution.MAX_SOURCE_PREVIEW_LINES); let endLineNumber = startLineNumber + 1; for (; endLineNumber < maxLineNumber; endLineNumber++) { @@ -208,7 +251,7 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC } private getPreviewRangeBasedOnBrackets(textEditorModel: ITextModel, startLineNumber: number) { - const maxLineNumber = Math.min(textEditorModel.getLineCount(), startLineNumber + GotoDefinitionWithMouseEditorContribution.MAX_SOURCE_PREVIEW_LINES); + const maxLineNumber = Math.min(textEditorModel.getLineCount(), startLineNumber + GotoDefinitionAtPositionEditorContribution.MAX_SOURCE_PREVIEW_LINES); const brackets: IFoundBracket[] = []; @@ -263,12 +306,12 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC } }; - this.decorations = this.editor.deltaDecorations(this.decorations, [newDecorations]); + this.linkDecorations = this.editor.deltaDecorations(this.linkDecorations, [newDecorations]); } - private removeDecorations(): void { - if (this.decorations.length > 0) { - this.decorations = this.editor.deltaDecorations(this.decorations, []); + private removeLinkDecorations(): void { + if (this.linkDecorations.length > 0) { + this.linkDecorations = this.editor.deltaDecorations(this.linkDecorations, []); } } @@ -280,18 +323,18 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC DefinitionProviderRegistry.has(this.editor.getModel()); } - private findDefinition(target: IMouseTarget, token: CancellationToken): Promise { + private findDefinition(position: Position, token: CancellationToken): Promise { const model = this.editor.getModel(); if (!model) { return Promise.resolve(null); } - return getDefinitionsAtPosition(model, target.position!, token); + return getDefinitionsAtPosition(model, position, token); } - private gotoDefinition(target: IMouseTarget, sideBySide: boolean): Promise { - this.editor.setPosition(target.position!); - const action = new DefinitionAction(new DefinitionActionConfig(sideBySide, false, true, false), { alias: '', label: '', id: '', precondition: undefined }); + private gotoDefinition(position: Position, openToSide: boolean): Promise { + this.editor.setPosition(position); + const action = new DefinitionAction({ openToSide, openInPeek: false, muteMessage: true }, { alias: '', label: '', id: '', precondition: undefined }); return this.editor.invokeWithinContext(accessor => action.run(accessor, this.editor)); } @@ -300,7 +343,7 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC } } -registerEditorContribution(GotoDefinitionWithMouseEditorContribution.ID, GotoDefinitionWithMouseEditorContribution); +registerEditorContribution(GotoDefinitionAtPositionEditorContribution.ID, GotoDefinitionAtPositionEditorContribution); registerThemingParticipant((theme, collector) => { const activeLinkForeground = theme.getColor(editorActiveLinkForeground); diff --git a/src/vs/editor/contrib/referenceSearch/referencesController.ts b/src/vs/editor/contrib/gotoSymbol/peek/referencesController.ts similarity index 52% rename from src/vs/editor/contrib/referenceSearch/referencesController.ts rename to src/vs/editor/contrib/gotoSymbol/peek/referencesController.ts index 1195948b7f2e..27c1ead87b17 100644 --- a/src/vs/editor/contrib/referenceSearch/referencesController.ts +++ b/src/vs/editor/contrib/gotoSymbol/peek/referencesController.ts @@ -7,47 +7,47 @@ import * as nls from 'vs/nls'; import { onUnexpectedError } from 'vs/base/common/errors'; import { dispose, DisposableStore } from 'vs/base/common/lifecycle'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IContextKey, IContextKeyService, RawContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import * as editorCommon from 'vs/editor/common/editorCommon'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { ReferencesModel } from './referencesModel'; +import { ReferencesModel, OneReference } from '../referencesModel'; import { ReferenceWidget, LayoutData } from './referencesWidget'; import { Range } from 'vs/editor/common/core/range'; import { Position } from 'vs/editor/common/core/position'; import { Location } from 'vs/editor/common/modes'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { CancelablePromise } from 'vs/base/common/async'; +import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; +import { getOuterEditor, PeekContext } from 'vs/editor/contrib/peekView/peekView'; +import { IListService, WorkbenchListFocusContextKey } from 'vs/platform/list/browser/listService'; +import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; export const ctxReferenceSearchVisible = new RawContextKey('referenceSearchVisible', false); -export interface RequestOptions { - getMetaTitle(model: ReferencesModel): string; - onGoto?: (reference: Location) => Promise; -} - export abstract class ReferencesController implements editorCommon.IEditorContribution { - public static readonly ID = 'editor.contrib.referencesController'; + static readonly ID = 'editor.contrib.referencesController'; private readonly _disposables = new DisposableStore(); - private readonly _editor: ICodeEditor; + private _widget?: ReferenceWidget; private _model?: ReferencesModel; + private _peekMode?: boolean; private _requestIdPool = 0; private _ignoreModelChangeEvent = false; private readonly _referenceSearchVisible: IContextKey; - public static get(editor: ICodeEditor): ReferencesController { + static get(editor: ICodeEditor): ReferencesController { return editor.getContribution(ReferencesController.ID); } - public constructor( + constructor( private readonly _defaultTreeKeyboardSupport: boolean, - editor: ICodeEditor, + private readonly _editor: ICodeEditor, @IContextKeyService contextKeyService: IContextKeyService, @ICodeEditorService private readonly _editorService: ICodeEditorService, @INotificationService private readonly _notificationService: INotificationService, @@ -55,24 +55,20 @@ export abstract class ReferencesController implements editorCommon.IEditorContri @IStorageService private readonly _storageService: IStorageService, @IConfigurationService private readonly _configurationService: IConfigurationService, ) { - this._editor = editor; + this._referenceSearchVisible = ctxReferenceSearchVisible.bindTo(contextKeyService); } - public dispose(): void { + dispose(): void { this._referenceSearchVisible.reset(); - dispose(this._disposables); - if (this._widget) { - dispose(this._widget); - this._widget = undefined; - } - if (this._model) { - dispose(this._model); - this._model = undefined; - } + this._disposables.dispose(); + dispose(this._widget); + dispose(this._model); + this._widget = undefined; + this._model = undefined; } - public toggleWidget(range: Range, modelPromise: CancelablePromise, options: RequestOptions): void { + toggleWidget(range: Range, modelPromise: CancelablePromise, peekMode: boolean): void { // close current widget and return early is position didn't change let widgetPosition: Position | undefined; @@ -84,6 +80,7 @@ export abstract class ReferencesController implements editorCommon.IEditorContri return; } + this._peekMode = peekMode; this._referenceSearchVisible.set(true); // close the widget on model/mode changes @@ -110,27 +107,25 @@ export abstract class ReferencesController implements editorCommon.IEditorContri this._disposables.add(this._widget.onDidSelectReference(event => { let { element, kind } = event; + if (!element) { + return; + } switch (kind) { case 'open': - if (event.source === 'editor' - && this._configurationService.getValue('editor.stablePeek')) { - + if (event.source !== 'editor' || !this._configurationService.getValue('editor.stablePeek')) { // when stable peek is configured we don't close // the peek window on selecting the editor - break; + this.openReference(element, false); } + break; case 'side': - if (element) { - this.openReference(element, kind === 'side'); - } + this.openReference(element, true); break; case 'goto': - if (element) { - if (options.onGoto) { - options.onGoto(element); - } else { - this._gotoReference(element); - } + if (peekMode) { + this._gotoReference(element); + } else { + this.openReference(element, false); } break; } @@ -154,8 +149,13 @@ export abstract class ReferencesController implements editorCommon.IEditorContri // show widget return this._widget.setModel(this._model).then(() => { if (this._widget && this._model && this._editor.hasModel()) { // might have been closed + // set title - this._widget.setMetaTitle(options.getMetaTitle(this._model)); + if (!this._model.isEmpty) { + this._widget.setMetaTitle(nls.localize('metaTitle.N', "{0} ({1})", this._model.title, this._model.references.length)); + } else { + this._widget.setMetaTitle(''); + } // set 'best' selection let uri = this._editor.getModel().uri; @@ -173,7 +173,7 @@ export abstract class ReferencesController implements editorCommon.IEditorContri }); } - public async goToNextOrPreviousReference(fwd: boolean) { + async goToNextOrPreviousReference(fwd: boolean) { if (!this._editor.hasModel() || !this._model || !this._widget) { // can be called while still resolving... return; @@ -195,17 +195,13 @@ export abstract class ReferencesController implements editorCommon.IEditorContri } } - public closeWidget(): void { - if (this._widget) { - dispose(this._widget); - this._widget = undefined; - } + closeWidget(): void { this._referenceSearchVisible.reset(); this._disposables.clear(); - if (this._model) { - dispose(this._model); - this._model = undefined; - } + dispose(this._widget); + dispose(this._model); + this._widget = undefined; + this._model = undefined; this._editor.focus(); this._requestIdPool += 1; // Cancel pending requests } @@ -224,21 +220,31 @@ export abstract class ReferencesController implements editorCommon.IEditorContri }, this._editor).then(openedEditor => { this._ignoreModelChangeEvent = false; - if (!openedEditor || openedEditor !== this._editor) { - // TODO@Alex TODO@Joh - // when opening the current reference we might end up - // in a different editor instance. that means we also have - // a different instance of this reference search controller - // and cannot hold onto the widget (which likely doesn't - // exist). Instead of bailing out we should find the - // 'sister' action and pass our current model on to it. + if (!openedEditor || !this._widget) { + // something went wrong... this.closeWidget(); return; } - if (this._widget) { + if (this._editor === openedEditor) { + // this._widget.show(range); this._widget.focus(); + + } else { + // we opened a different editor instance which means a different controller instance. + // therefore we stop with this controller and continue with the other + const other = ReferencesController.get(openedEditor); + const model = this._model!.clone(); + + this.closeWidget(); + openedEditor.focus(); + + other.toggleWidget( + range, + createCancelablePromise(_ => Promise.resolve(model)), + this._peekMode ?? false + ); } }, (err) => { @@ -247,7 +253,7 @@ export abstract class ReferencesController implements editorCommon.IEditorContri }); } - public openReference(ref: Location, sideBySide: boolean): void { + openReference(ref: Location, sideBySide: boolean): void { // clear stage if (!sideBySide) { this.closeWidget(); @@ -260,3 +266,105 @@ export abstract class ReferencesController implements editorCommon.IEditorContri }, this._editor, sideBySide); } } + +function withController(accessor: ServicesAccessor, fn: (controller: ReferencesController) => void): void { + const outerEditor = getOuterEditor(accessor); + if (!outerEditor) { + return; + } + let controller = ReferencesController.get(outerEditor); + if (controller) { + fn(controller); + } +} + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'goToNextReference', + weight: KeybindingWeight.WorkbenchContrib + 50, + primary: KeyCode.F4, + secondary: [KeyCode.F12], + when: ctxReferenceSearchVisible, + handler(accessor) { + withController(accessor, controller => { + controller.goToNextOrPreviousReference(true); + }); + } +}); + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'goToNextReferenceFromEmbeddedEditor', + weight: KeybindingWeight.EditorContrib + 50, + primary: KeyCode.F4, + secondary: [KeyCode.F12], + when: PeekContext.inPeekEditor, + handler(accessor) { + withController(accessor, controller => { + controller.goToNextOrPreviousReference(true); + }); + } +}); + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'goToPreviousReference', + weight: KeybindingWeight.WorkbenchContrib + 50, + primary: KeyMod.Shift | KeyCode.F4, + secondary: [KeyMod.Shift | KeyCode.F12], + when: ctxReferenceSearchVisible, + handler(accessor) { + withController(accessor, controller => { + controller.goToNextOrPreviousReference(false); + }); + } +}); + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'goToPreviousReferenceFromEmbeddedEditor', + weight: KeybindingWeight.EditorContrib + 50, + primary: KeyMod.Shift | KeyCode.F4, + secondary: [KeyMod.Shift | KeyCode.F12], + when: PeekContext.inPeekEditor, + handler(accessor) { + withController(accessor, controller => { + controller.goToNextOrPreviousReference(false); + }); + } +}); + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'closeReferenceSearch', + weight: KeybindingWeight.WorkbenchContrib + 50, + primary: KeyCode.Escape, + secondary: [KeyMod.Shift | KeyCode.Escape], + when: ContextKeyExpr.and(ctxReferenceSearchVisible, ContextKeyExpr.not('config.editor.stablePeek')), + handler(accessor: ServicesAccessor) { + withController(accessor, controller => controller.closeWidget()); + } +}); + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'closeReferenceSearchEditor', + weight: KeybindingWeight.EditorContrib - 101, + primary: KeyCode.Escape, + secondary: [KeyMod.Shift | KeyCode.Escape], + when: ContextKeyExpr.and(PeekContext.inPeekEditor, ContextKeyExpr.not('config.editor.stablePeek')), + handler(accessor: ServicesAccessor) { + withController(accessor, controller => controller.closeWidget()); + } +}); + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'openReferenceToSide', + weight: KeybindingWeight.EditorContrib, + primary: KeyMod.CtrlCmd | KeyCode.Enter, + mac: { + primary: KeyMod.WinCtrl | KeyCode.Enter + }, + when: ContextKeyExpr.and(ctxReferenceSearchVisible, WorkbenchListFocusContextKey), + handler(accessor: ServicesAccessor) { + const listService = accessor.get(IListService); + const focus = listService.lastFocusedList?.getFocus(); + if (Array.isArray(focus) && focus[0] instanceof OneReference) { + withController(accessor, controller => controller.openReference(focus[0], true)); + } + } +}); diff --git a/src/vs/editor/contrib/referenceSearch/referencesTree.ts b/src/vs/editor/contrib/gotoSymbol/peek/referencesTree.ts similarity index 97% rename from src/vs/editor/contrib/referenceSearch/referencesTree.ts rename to src/vs/editor/contrib/gotoSymbol/peek/referencesTree.ts index d10cbd643016..b0150bf9b6be 100644 --- a/src/vs/editor/contrib/referenceSearch/referencesTree.ts +++ b/src/vs/editor/contrib/gotoSymbol/peek/referencesTree.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ReferencesModel, FileReferences, OneReference } from './referencesModel'; +import { ReferencesModel, FileReferences, OneReference } from '../referencesModel'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { ITreeRenderer, ITreeNode, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree'; import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; @@ -101,7 +101,7 @@ export class StringRepresentationProvider implements IKeyboardNavigationLabelPro export class IdentityProvider implements IIdentityProvider { getId(element: TreeElement): { toString(): string; } { - return element.id; + return element instanceof OneReference ? element.id : element.uri; } } @@ -216,12 +216,6 @@ export class OneReferenceRenderer implements ITreeRenderer { getAriaLabel(element: FileReferences | OneReference): string | null { - if (element instanceof FileReferences) { - return element.getAriaMessage(); - } else if (element instanceof OneReference) { - return element.getAriaMessage(); - } else { - return null; - } + return element.ariaMessage; } } diff --git a/src/vs/editor/contrib/referenceSearch/media/referencesWidget.css b/src/vs/editor/contrib/gotoSymbol/peek/referencesWidget.css similarity index 100% rename from src/vs/editor/contrib/referenceSearch/media/referencesWidget.css rename to src/vs/editor/contrib/gotoSymbol/peek/referencesWidget.css diff --git a/src/vs/editor/contrib/referenceSearch/referencesWidget.ts b/src/vs/editor/contrib/gotoSymbol/peek/referencesWidget.ts similarity index 72% rename from src/vs/editor/contrib/referenceSearch/referencesWidget.ts rename to src/vs/editor/contrib/gotoSymbol/peek/referencesWidget.ts index 5c66be069197..3c887377fccd 100644 --- a/src/vs/editor/contrib/referenceSearch/referencesWidget.ts +++ b/src/vs/editor/contrib/gotoSymbol/peek/referencesWidget.ts @@ -3,15 +3,16 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import 'vs/css!./referencesWidget'; import * as dom from 'vs/base/browser/dom'; import { IMouseEvent } from 'vs/base/browser/mouseEvent'; +import { GestureEvent } from 'vs/base/browser/touch'; import { Orientation } from 'vs/base/browser/ui/sash/sash'; import { Color } from 'vs/base/common/color'; import { Emitter, Event } from 'vs/base/common/event'; import { dispose, IDisposable, IReference, DisposableStore } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { basenameOrAuthority, dirname, isEqual } from 'vs/base/common/resources'; -import 'vs/css!./media/referencesWidget'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; @@ -21,19 +22,15 @@ import { IModelDeltaDecoration, TrackedRangeStickiness } from 'vs/editor/common/ import { ModelDecorationOptions, TextModel } from 'vs/editor/common/model/textModel'; import { Location } from 'vs/editor/common/modes'; import { ITextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService'; -import { AriaProvider, DataSource, Delegate, FileReferencesRenderer, OneReferenceRenderer, TreeElement, StringRepresentationProvider, IdentityProvider } from 'vs/editor/contrib/referenceSearch/referencesTree'; +import { AriaProvider, DataSource, Delegate, FileReferencesRenderer, OneReferenceRenderer, TreeElement, StringRepresentationProvider, IdentityProvider } from 'vs/editor/contrib/gotoSymbol/peek/referencesTree'; import * as nls from 'vs/nls'; -import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILabelService } from 'vs/platform/label/common/label'; -import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; -import { activeContrastBorder, contrastBorder, registerColor } from 'vs/platform/theme/common/colorRegistry'; +import { WorkbenchAsyncDataTree, IWorkbenchAsyncDataTreeOptions } from 'vs/platform/list/browser/listService'; +import { activeContrastBorder } from 'vs/platform/theme/common/colorRegistry'; import { ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; -import { PeekViewWidget, IPeekViewService } from './peekViewWidget'; -import { FileReferences, OneReference, ReferencesModel } from './referencesModel'; -import { ITreeRenderer, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree'; -import { IAsyncDataTreeOptions } from 'vs/base/browser/ui/tree/asyncDataTree'; -import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; +import * as peekView from 'vs/editor/contrib/peekView/peekView'; +import { FileReferences, OneReference, ReferencesModel } from '../referencesModel'; import { FuzzyScore } from 'vs/base/common/filters'; import { SplitView, Sizing } from 'vs/base/browser/ui/splitview/splitview'; @@ -55,7 +52,7 @@ class DecorationsManager implements IDisposable { this._onModelChanged(); } - public dispose(): void { + dispose(): void { this._callOnModelChange.dispose(); this._callOnDispose.dispose(); this.removeDecorations(); @@ -147,7 +144,7 @@ class DecorationsManager implements IDisposable { this._editor.deltaDecorations(toRemove, []); } - public removeDecorations(): void { + removeDecorations(): void { let toRemove: string[] = []; this._decorations.forEach((value, key) => { toRemove.push(key); @@ -158,8 +155,8 @@ class DecorationsManager implements IDisposable { } export class LayoutData { - public ratio: number = 0.7; - public heightInLines: number = 18; + ratio: number = 0.7; + heightInLines: number = 18; static fromJSON(raw: string): LayoutData { let ratio: number | undefined; @@ -184,19 +181,19 @@ export interface SelectionEvent { readonly element?: Location; } -export const ctxReferenceWidgetSearchTreeFocused = new RawContextKey('referenceSearchTreeFocused', true); - /** * ZoneWidget that is shown inside the editor */ -export class ReferenceWidget extends PeekViewWidget { +export class ReferenceWidget extends peekView.PeekViewWidget { private _model?: ReferencesModel; private _decorationsManager?: DecorationsManager; private readonly _disposeOnNewModel = new DisposableStore(); private readonly _callOnDispose = new DisposableStore(); + private readonly _onDidSelectReference = new Emitter(); + readonly onDidSelectReference = this._onDidSelectReference.event; private _tree!: WorkbenchAsyncDataTree; private _treeContainer!: HTMLElement; @@ -215,7 +212,7 @@ export class ReferenceWidget extends PeekViewWidget { @IThemeService themeService: IThemeService, @ITextModelService private readonly _textModelResolverService: ITextModelService, @IInstantiationService private readonly _instantiationService: IInstantiationService, - @IPeekViewService private readonly _peekViewService: IPeekViewService, + @peekView.IPeekViewService private readonly _peekViewService: peekView.IPeekViewService, @ILabelService private readonly _uriLabel: ILabelService ) { super(editor, { showFrame: false, showArrow: true, isResizeable: true, isAccessible: true }); @@ -239,20 +236,16 @@ export class ReferenceWidget extends PeekViewWidget { } private _applyTheme(theme: ITheme) { - const borderColor = theme.getColor(peekViewBorder) || Color.transparent; + const borderColor = theme.getColor(peekView.peekViewBorder) || Color.transparent; this.style({ arrowColor: borderColor, frameColor: borderColor, - headerBackgroundColor: theme.getColor(peekViewTitleBackground) || Color.transparent, - primaryHeadingColor: theme.getColor(peekViewTitleForeground), - secondaryHeadingColor: theme.getColor(peekViewTitleInfoForeground) + headerBackgroundColor: theme.getColor(peekView.peekViewTitleBackground) || Color.transparent, + primaryHeadingColor: theme.getColor(peekView.peekViewTitleForeground), + secondaryHeadingColor: theme.getColor(peekView.peekViewTitleInfoForeground) }); } - get onDidSelectReference(): Event { - return this._onDidSelectReference.event; - } - show(where: IRange) { this.editor.revealRangeInCenterIfOutsideViewport(where, editorCommon.ScrollType.Smooth); super.show(where, this.layoutData.heightInLines || 18); @@ -304,14 +297,17 @@ export class ReferenceWidget extends PeekViewWidget { // tree this._treeContainer = dom.append(containerElement, dom.$('div.ref-tree.inline')); - const treeOptions: IAsyncDataTreeOptions = { + const treeOptions: IWorkbenchAsyncDataTreeOptions = { ariaLabel: nls.localize('treeAriaLabel', "References"), keyboardSupport: this._defaultTreeKeyboardSupport, accessibilityProvider: new AriaProvider(), keyboardNavigationLabelProvider: this._instantiationService.createInstance(StringRepresentationProvider), - identityProvider: new IdentityProvider() + identityProvider: new IdentityProvider(), + overrideStyles: { + listBackground: peekView.peekViewResultsBackground + } }; - this._tree = this._instantiationService.createInstance, ITreeRenderer[], IAsyncDataSource, IAsyncDataTreeOptions, WorkbenchAsyncDataTree>( + this._tree = this._instantiationService.createInstance>( WorkbenchAsyncDataTree, 'ReferencesWidget', this._treeContainer, @@ -321,9 +317,8 @@ export class ReferenceWidget extends PeekViewWidget { this._instantiationService.createInstance(OneReferenceRenderer), ], this._instantiationService.createInstance(DataSource), - treeOptions + treeOptions, ); - ctxReferenceWidgetSearchTreeFocused.bindTo(this._tree.contextKeyService); // split stuff this._splitView.addView({ @@ -367,17 +362,17 @@ export class ReferenceWidget extends PeekViewWidget { onEvent(e.elements[0], 'show'); }); this._tree.onDidOpen(e => { - const aside = (e.browserEvent instanceof MouseEvent) && (e.browserEvent.ctrlKey || e.browserEvent.metaKey || e.browserEvent.altKey); - let goto = !e.browserEvent || ((e.browserEvent instanceof MouseEvent) && e.browserEvent.detail === 2); - if (e.browserEvent instanceof KeyboardEvent) { - // todo@joh make this a command - goto = true; - } - if (aside) { + if (e.browserEvent instanceof MouseEvent && (e.browserEvent.ctrlKey || e.browserEvent.metaKey || e.browserEvent.altKey)) { + // modifier-click -> open to the side onEvent(e.elements[0], 'side'); - } else if (goto) { + } else if (e.browserEvent instanceof KeyboardEvent || (e.browserEvent instanceof MouseEvent && e.browserEvent.detail === 2) || (e.browserEvent).tapCount === 2) { + // keybinding (list service command) + // OR double click + // OR double tap + // -> close widget and goto target onEvent(e.elements[0], 'goto'); } else { + // preview location onEvent(e.elements[0], 'show'); } }); @@ -399,7 +394,7 @@ export class ReferenceWidget extends PeekViewWidget { this._splitView.resizeView(0, widthInPixel * this.layoutData.ratio); } - public setSelection(selection: OneReference): Promise { + setSelection(selection: OneReference): Promise { return this._revealReference(selection, true).then(() => { if (!this._model) { // disposed @@ -411,7 +406,7 @@ export class ReferenceWidget extends PeekViewWidget { }); } - public setModel(newModel: ReferencesModel | undefined): Promise { + setModel(newModel: ReferencesModel | undefined): Promise { // clean up this._disposeOnNewModel.clear(); this._model = newModel; @@ -426,7 +421,7 @@ export class ReferenceWidget extends PeekViewWidget { return Promise.resolve(undefined); } - if (this._model.empty) { + if (this._model.isEmpty) { this.setTitle(''); this._messageContainer.innerHTML = nls.localize('noResults', "No results"); dom.show(this._messageContainer); @@ -537,34 +532,17 @@ export class ReferenceWidget extends PeekViewWidget { // theming -export const peekViewTitleBackground = registerColor('peekViewTitle.background', { dark: '#1E1E1E', light: '#FFFFFF', hc: '#0C141F' }, nls.localize('peekViewTitleBackground', 'Background color of the peek view title area.')); -export const peekViewTitleForeground = registerColor('peekViewTitleLabel.foreground', { dark: '#FFFFFF', light: '#333333', hc: '#FFFFFF' }, nls.localize('peekViewTitleForeground', 'Color of the peek view title.')); -export const peekViewTitleInfoForeground = registerColor('peekViewTitleDescription.foreground', { dark: '#ccccccb3', light: '#6c6c6cb3', hc: '#FFFFFF99' }, nls.localize('peekViewTitleInfoForeground', 'Color of the peek view title info.')); -export const peekViewBorder = registerColor('peekView.border', { dark: '#007acc', light: '#007acc', hc: contrastBorder }, nls.localize('peekViewBorder', 'Color of the peek view borders and arrow.')); - -export const peekViewResultsBackground = registerColor('peekViewResult.background', { dark: '#252526', light: '#F3F3F3', hc: Color.black }, nls.localize('peekViewResultsBackground', 'Background color of the peek view result list.')); -export const peekViewResultsMatchForeground = registerColor('peekViewResult.lineForeground', { dark: '#bbbbbb', light: '#646465', hc: Color.white }, nls.localize('peekViewResultsMatchForeground', 'Foreground color for line nodes in the peek view result list.')); -export const peekViewResultsFileForeground = registerColor('peekViewResult.fileForeground', { dark: Color.white, light: '#1E1E1E', hc: Color.white }, nls.localize('peekViewResultsFileForeground', 'Foreground color for file nodes in the peek view result list.')); -export const peekViewResultsSelectionBackground = registerColor('peekViewResult.selectionBackground', { dark: '#3399ff33', light: '#3399ff33', hc: null }, nls.localize('peekViewResultsSelectionBackground', 'Background color of the selected entry in the peek view result list.')); -export const peekViewResultsSelectionForeground = registerColor('peekViewResult.selectionForeground', { dark: Color.white, light: '#6C6C6C', hc: Color.white }, nls.localize('peekViewResultsSelectionForeground', 'Foreground color of the selected entry in the peek view result list.')); -export const peekViewEditorBackground = registerColor('peekViewEditor.background', { dark: '#001F33', light: '#F2F8FC', hc: Color.black }, nls.localize('peekViewEditorBackground', 'Background color of the peek view editor.')); -export const peekViewEditorGutterBackground = registerColor('peekViewEditorGutter.background', { dark: peekViewEditorBackground, light: peekViewEditorBackground, hc: peekViewEditorBackground }, nls.localize('peekViewEditorGutterBackground', 'Background color of the gutter in the peek view editor.')); - -export const peekViewResultsMatchHighlight = registerColor('peekViewResult.matchHighlightBackground', { dark: '#ea5c004d', light: '#ea5c004d', hc: null }, nls.localize('peekViewResultsMatchHighlight', 'Match highlight color in the peek view result list.')); -export const peekViewEditorMatchHighlight = registerColor('peekViewEditor.matchHighlightBackground', { dark: '#ff8f0099', light: '#f5d802de', hc: null }, nls.localize('peekViewEditorMatchHighlight', 'Match highlight color in the peek view editor.')); -export const peekViewEditorMatchHighlightBorder = registerColor('peekViewEditor.matchHighlightBorder', { dark: null, light: null, hc: activeContrastBorder }, nls.localize('peekViewEditorMatchHighlightBorder', 'Match highlight border in the peek view editor.')); - registerThemingParticipant((theme, collector) => { - const findMatchHighlightColor = theme.getColor(peekViewResultsMatchHighlight); + const findMatchHighlightColor = theme.getColor(peekView.peekViewResultsMatchHighlight); if (findMatchHighlightColor) { collector.addRule(`.monaco-editor .reference-zone-widget .ref-tree .referenceMatch .highlight { background-color: ${findMatchHighlightColor}; }`); } - const referenceHighlightColor = theme.getColor(peekViewEditorMatchHighlight); + const referenceHighlightColor = theme.getColor(peekView.peekViewEditorMatchHighlight); if (referenceHighlightColor) { collector.addRule(`.monaco-editor .reference-zone-widget .preview .reference-decoration { background-color: ${referenceHighlightColor}; }`); } - const referenceHighlightBorder = theme.getColor(peekViewEditorMatchHighlightBorder); + const referenceHighlightBorder = theme.getColor(peekView.peekViewEditorMatchHighlightBorder); if (referenceHighlightBorder) { collector.addRule(`.monaco-editor .reference-zone-widget .preview .reference-decoration { border: 2px solid ${referenceHighlightBorder}; box-sizing: border-box; }`); } @@ -572,27 +550,27 @@ registerThemingParticipant((theme, collector) => { if (hcOutline) { collector.addRule(`.monaco-editor .reference-zone-widget .ref-tree .referenceMatch .highlight { border: 1px dotted ${hcOutline}; box-sizing: border-box; }`); } - const resultsBackground = theme.getColor(peekViewResultsBackground); + const resultsBackground = theme.getColor(peekView.peekViewResultsBackground); if (resultsBackground) { collector.addRule(`.monaco-editor .reference-zone-widget .ref-tree { background-color: ${resultsBackground}; }`); } - const resultsMatchForeground = theme.getColor(peekViewResultsMatchForeground); + const resultsMatchForeground = theme.getColor(peekView.peekViewResultsMatchForeground); if (resultsMatchForeground) { collector.addRule(`.monaco-editor .reference-zone-widget .ref-tree { color: ${resultsMatchForeground}; }`); } - const resultsFileForeground = theme.getColor(peekViewResultsFileForeground); + const resultsFileForeground = theme.getColor(peekView.peekViewResultsFileForeground); if (resultsFileForeground) { collector.addRule(`.monaco-editor .reference-zone-widget .ref-tree .reference-file { color: ${resultsFileForeground}; }`); } - const resultsSelectedBackground = theme.getColor(peekViewResultsSelectionBackground); + const resultsSelectedBackground = theme.getColor(peekView.peekViewResultsSelectionBackground); if (resultsSelectedBackground) { collector.addRule(`.monaco-editor .reference-zone-widget .ref-tree .monaco-list:focus .monaco-list-rows > .monaco-list-row.selected:not(.highlighted) { background-color: ${resultsSelectedBackground}; }`); } - const resultsSelectedForeground = theme.getColor(peekViewResultsSelectionForeground); + const resultsSelectedForeground = theme.getColor(peekView.peekViewResultsSelectionForeground); if (resultsSelectedForeground) { collector.addRule(`.monaco-editor .reference-zone-widget .ref-tree .monaco-list:focus .monaco-list-rows > .monaco-list-row.selected:not(.highlighted) { color: ${resultsSelectedForeground} !important; }`); } - const editorBackground = theme.getColor(peekViewEditorBackground); + const editorBackground = theme.getColor(peekView.peekViewEditorBackground); if (editorBackground) { collector.addRule( `.monaco-editor .reference-zone-widget .preview .monaco-editor .monaco-editor-background,` + @@ -600,7 +578,7 @@ registerThemingParticipant((theme, collector) => { ` background-color: ${editorBackground};` + `}`); } - const editorGutterBackground = theme.getColor(peekViewEditorGutterBackground); + const editorGutterBackground = theme.getColor(peekView.peekViewEditorGutterBackground); if (editorGutterBackground) { collector.addRule( `.monaco-editor .reference-zone-widget .preview .monaco-editor .margin {` + diff --git a/src/vs/editor/contrib/referenceSearch/referencesModel.ts b/src/vs/editor/contrib/gotoSymbol/referencesModel.ts similarity index 78% rename from src/vs/editor/contrib/referenceSearch/referencesModel.ts rename to src/vs/editor/contrib/gotoSymbol/referencesModel.ts index 7135aae7c475..ce83d9b8e870 100644 --- a/src/vs/editor/contrib/referenceSearch/referencesModel.ts +++ b/src/vs/editor/contrib/gotoSymbol/referencesModel.ts @@ -18,18 +18,15 @@ import { IMatch } from 'vs/base/common/filters'; import { Constants } from 'vs/base/common/uint'; export class OneReference { - readonly id: string; - private readonly _onRefChanged = new Emitter(); - readonly onRefChanged: Event = this._onRefChanged.event; + readonly id: string = defaultGenerator.nextId(); constructor( + readonly isProviderFirst: boolean, readonly parent: FileReferences, private _range: IRange, - readonly isProviderFirst: boolean - ) { - this.id = defaultGenerator.nextId(); - } + private _rangeCallback: (ref: OneReference) => void + ) { } get uri(): URI { return this.parent.uri; @@ -41,10 +38,10 @@ export class OneReference { set range(value: IRange) { this._range = value; - this._onRefChanged.fire(this); + this._rangeCallback(this); } - getAriaMessage(): string { + get ariaMessage(): string { return localize( 'aria.oneReference', "symbol in {0} on line {1} at column {2}", basename(this.uri), this.range.startLineNumber, this.range.startColumn @@ -56,11 +53,10 @@ export class FilePreview implements IDisposable { constructor( private readonly _modelReference: IReference - ) { - } + ) { } dispose(): void { - dispose(this._modelReference); + this._modelReference.dispose(); } preview(range: IRange, n: number = 8): { value: string; highlight: IMatch } | undefined { @@ -88,29 +84,20 @@ export class FilePreview implements IDisposable { export class FileReferences implements IDisposable { - private _children: OneReference[]; + readonly children: OneReference[] = []; + private _preview?: FilePreview; private _resolved?: boolean; - private _loadFailure: any; + private _loadFailure?: any; - constructor(private readonly _parent: ReferencesModel, private readonly _uri: URI) { - this._children = []; - } - - get id(): string { - return this._uri.toString(); - } - - get parent(): ReferencesModel { - return this._parent; - } - - get children(): OneReference[] { - return this._children; - } + constructor( + readonly parent: ReferencesModel, + readonly uri: URI + ) { } - get uri(): URI { - return this._uri; + dispose(): void { + dispose(this._preview); + this._preview = undefined; } get preview(): FilePreview | undefined { @@ -121,7 +108,7 @@ export class FileReferences implements IDisposable { return this._loadFailure; } - getAriaMessage(): string { + get ariaMessage(): string { const len = this.children.length; if (len === 1) { return localize('aria.fileReferences.1', "1 symbol in {0}, full path {1}", basename(this.uri), this.uri.fsPath); @@ -136,7 +123,7 @@ export class FileReferences implements IDisposable { return Promise.resolve(this); } - return Promise.resolve(textModelResolverService.createModelReference(this._uri).then(modelReference => { + return Promise.resolve(textModelResolverService.createModelReference(this.uri).then(modelReference => { const model = modelReference.object; if (!model) { @@ -150,62 +137,76 @@ export class FileReferences implements IDisposable { }, err => { // something wrong here - this._children = []; + this.children.length = 0; this._resolved = true; this._loadFailure = err; return this; })); } - - dispose(): void { - if (this._preview) { - this._preview.dispose(); - this._preview = undefined; - } - } } export class ReferencesModel implements IDisposable { private readonly _disposables = new DisposableStore(); + private readonly _links: LocationLink[]; + private readonly _title: string; + readonly groups: FileReferences[] = []; readonly references: OneReference[] = []; readonly _onDidChangeReferenceRange = new Emitter(); readonly onDidChangeReferenceRange: Event = this._onDidChangeReferenceRange.event; - constructor(references: LocationLink[]) { + constructor(links: LocationLink[], title: string) { + this._links = links; + this._title = title; // grouping and sorting - const [providersFirst] = references; - references.sort(ReferencesModel._compareReferences); + const [providersFirst] = links; + links.sort(ReferencesModel._compareReferences); let current: FileReferences | undefined; - for (let ref of references) { - if (!current || current.uri.toString() !== ref.uri.toString()) { + for (let link of links) { + if (!current || current.uri.toString() !== link.uri.toString()) { // new group - current = new FileReferences(this, ref.uri); + current = new FileReferences(this, link.uri); this.groups.push(current); } // append, check for equality first! - if (current.children.length === 0 - || !Range.equalsRange(ref.range, current.children[current.children.length - 1].range)) { + if (current.children.length === 0 || !Range.equalsRange(link.range, current.children[current.children.length - 1].range)) { - let oneRef = new OneReference(current, ref.targetSelectionRange || ref.range, providersFirst === ref); - this._disposables.add(oneRef.onRefChanged((e) => this._onDidChangeReferenceRange.fire(e))); + const oneRef = new OneReference( + providersFirst === link, current, link.targetSelectionRange || link.range, + ref => this._onDidChangeReferenceRange.fire(ref) + ); this.references.push(oneRef); current.children.push(oneRef); } } } - get empty(): boolean { + dispose(): void { + dispose(this.groups); + this._disposables.dispose(); + this._onDidChangeReferenceRange.dispose(); + this.groups.length = 0; + } + + clone(): ReferencesModel { + return new ReferencesModel(this._links, this._title); + } + + get title(): string { + return this._title; + } + + get isEmpty(): boolean { return this.groups.length === 0; } - getAriaMessage(): string { - if (this.empty) { + get ariaMessage(): string { + if (this.isEmpty) { return localize('aria.result.0', "No results found"); } else if (this.references.length === 1) { return localize('aria.result.1', "Found 1 symbol in {0}", this.references[0].uri.fsPath); @@ -272,6 +273,17 @@ export class ReferencesModel implements IDisposable { return undefined; } + referenceAt(resource: URI, position: Position): OneReference | undefined { + for (const ref of this.references) { + if (ref.uri.toString() === resource.toString()) { + if (Range.containsPosition(ref.range, position)) { + return ref; + } + } + } + return undefined; + } + firstReference(): OneReference | undefined { for (const ref of this.references) { if (ref.isProviderFirst) { @@ -281,21 +293,7 @@ export class ReferencesModel implements IDisposable { return this.references[0]; } - dispose(): void { - dispose(this.groups); - this._disposables.dispose(); - this.groups.length = 0; - } - private static _compareReferences(a: Location, b: Location): number { - const auri = a.uri.toString(); - const buri = b.uri.toString(); - if (auri < buri) { - return -1; - } else if (auri > buri) { - return 1; - } else { - return Range.compareRangesUsingStarts(a.range, b.range); - } + return strings.compare(a.uri.toString(), b.uri.toString()) || Range.compareRangesUsingStarts(a.range, b.range); } } diff --git a/src/vs/editor/contrib/goToDefinition/goToDefinitionResultsNavigation.ts b/src/vs/editor/contrib/gotoSymbol/symbolNavigation.ts similarity index 99% rename from src/vs/editor/contrib/goToDefinition/goToDefinitionResultsNavigation.ts rename to src/vs/editor/contrib/gotoSymbol/symbolNavigation.ts index 495cf11f11d9..85b00f975ec4 100644 --- a/src/vs/editor/contrib/goToDefinition/goToDefinitionResultsNavigation.ts +++ b/src/vs/editor/contrib/gotoSymbol/symbolNavigation.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ReferencesModel, OneReference } from 'vs/editor/contrib/referenceSearch/referencesModel'; +import { ReferencesModel, OneReference } from 'vs/editor/contrib/gotoSymbol/referencesModel'; import { RawContextKey, IContextKeyService, IContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { createDecorator, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; diff --git a/src/vs/editor/contrib/referenceSearch/test/referencesModel.test.ts b/src/vs/editor/contrib/gotoSymbol/test/referencesModel.test.ts similarity index 93% rename from src/vs/editor/contrib/referenceSearch/test/referencesModel.test.ts rename to src/vs/editor/contrib/gotoSymbol/test/referencesModel.test.ts index 1f8f64c1d839..742c6d8b2241 100644 --- a/src/vs/editor/contrib/referenceSearch/test/referencesModel.test.ts +++ b/src/vs/editor/contrib/gotoSymbol/test/referencesModel.test.ts @@ -2,11 +2,12 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ + import * as assert from 'assert'; import { URI } from 'vs/base/common/uri'; import { Range } from 'vs/editor/common/core/range'; import { Position } from 'vs/editor/common/core/position'; -import { ReferencesModel } from 'vs/editor/contrib/referenceSearch/referencesModel'; +import { ReferencesModel } from 'vs/editor/contrib/gotoSymbol/referencesModel'; suite('references', function () { @@ -20,7 +21,7 @@ suite('references', function () { }, { uri: URI.file('/src/can'), range: new Range(1, 1, 1, 1) - }]); + }], 'FOO'); let ref = model.nearestReference(URI.file('/src/can'), new Position(1, 1)); assert.equal(ref!.uri.path, '/src/can'); diff --git a/src/vs/editor/contrib/hover/hover.css b/src/vs/editor/contrib/hover/hover.css index b1a4f32e9722..4984d8370769 100644 --- a/src/vs/editor/contrib/hover/hover.css +++ b/src/vs/editor/contrib/hover/hover.css @@ -8,12 +8,9 @@ position: absolute; overflow: hidden; z-index: 50; + user-select: text; -webkit-user-select: text; -ms-user-select: text; - -khtml-user-select: text; - -moz-user-select: text; - -o-user-select: text; - user-select: text; box-sizing: initial; animation: fadein 100ms linear; line-height: 1.5em; @@ -32,6 +29,10 @@ word-wrap: break-word; } +.monaco-editor-hover .markdown-hover > .hover-contents:not(.code-hover-contents) hr { + min-width: 100vw; +} + .monaco-editor-hover p, .monaco-editor-hover ul { margin: 8px 0; @@ -103,3 +104,9 @@ .monaco-editor-hover .hover-row.status-bar .actions .action-container .action .icon { padding-right: 4px; } + +.monaco-editor-hover .markdown-hover .hover-contents .codicon { + color: inherit; + font-size: inherit; + vertical-align: middle; +} diff --git a/src/vs/editor/contrib/hover/hover.ts b/src/vs/editor/contrib/hover/hover.ts index c97001873777..5642e4c6d5ee 100644 --- a/src/vs/editor/contrib/hover/hover.ts +++ b/src/vs/editor/contrib/hover/hover.ts @@ -21,11 +21,12 @@ import { ModesContentHoverWidget } from 'vs/editor/contrib/hover/modesContentHov import { ModesGlyphHoverWidget } from 'vs/editor/contrib/hover/modesGlyphHover'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { editorHoverBackground, editorHoverBorder, editorHoverHighlight, textCodeBlockBackground, textLinkForeground, editorHoverStatusBarBackground } from 'vs/platform/theme/common/colorRegistry'; +import { editorHoverBackground, editorHoverBorder, editorHoverHighlight, textCodeBlockBackground, textLinkForeground, editorHoverStatusBarBackground, editorHoverForeground } from 'vs/platform/theme/common/colorRegistry'; import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { IMarkerDecorationsService } from 'vs/editor/common/services/markersDecorationService'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; +import { GotoDefinitionAtPositionEditorContribution } from 'vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition'; export class ModesHoverController implements IEditorContribution { @@ -258,8 +259,50 @@ class ShowHoverAction extends EditorAction { } } +class ShowDefinitionPreviewHoverAction extends EditorAction { + + constructor() { + super({ + id: 'editor.action.showDefinitionPreviewHover', + label: nls.localize({ + key: 'showDefinitionPreviewHover', + comment: [ + 'Label for action that will trigger the showing of definition preview hover in the editor.', + 'This allows for users to show the definition preview hover without using the mouse.' + ] + }, "Show Definition Preview Hover"), + alias: 'Show Definition Preview Hover', + precondition: undefined + }); + } + + public run(accessor: ServicesAccessor, editor: ICodeEditor): void { + let controller = ModesHoverController.get(editor); + if (!controller) { + return; + } + const position = editor.getPosition(); + + if (!position) { + return; + } + + const range = new Range(position.lineNumber, position.column, position.lineNumber, position.column); + const goto = GotoDefinitionAtPositionEditorContribution.get(editor); + const promise = goto.startFindDefinitionFromCursor(position); + if (promise) { + promise.then(() => { + controller.showContentHover(range, HoverStartMode.Immediate, true); + }); + } else { + controller.showContentHover(range, HoverStartMode.Immediate, true); + } + } +} + registerEditorContribution(ModesHoverController.ID, ModesHoverController); registerEditorAction(ShowHoverAction); +registerEditorAction(ShowDefinitionPreviewHoverAction); // theming registerThemingParticipant((theme, collector) => { @@ -282,6 +325,10 @@ registerThemingParticipant((theme, collector) => { if (link) { collector.addRule(`.monaco-editor .monaco-editor-hover a { color: ${link}; }`); } + const hoverForeground = theme.getColor(editorHoverForeground); + if (hoverForeground) { + collector.addRule(`.monaco-editor .monaco-editor-hover { color: ${hoverForeground}; }`); + } const actionsBackground = theme.getColor(editorHoverStatusBarBackground); if (actionsBackground) { collector.addRule(`.monaco-editor .monaco-editor-hover .hover-row .actions { background-color: ${actionsBackground}; }`); diff --git a/src/vs/editor/contrib/hover/modesContentHover.ts b/src/vs/editor/contrib/hover/modesContentHover.ts index c44fe4dd2b6a..dd2d5bff2566 100644 --- a/src/vs/editor/contrib/hover/modesContentHover.ts +++ b/src/vs/editor/contrib/hover/modesContentHover.ts @@ -34,7 +34,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; import { getCodeActions, CodeActionSet } from 'vs/editor/contrib/codeAction/codeAction'; import { QuickFixAction, QuickFixController } from 'vs/editor/contrib/codeAction/codeActionCommands'; -import { CodeActionKind } from 'vs/editor/contrib/codeAction/codeActionTrigger'; +import { CodeActionKind } from 'vs/editor/contrib/codeAction/types'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IIdentifiedSingleEditOperation } from 'vs/editor/common/model'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; @@ -207,7 +207,7 @@ export class ModesContentHoverWidget extends ContentHoverWidget { private readonly _themeService: IThemeService, private readonly _keybindingService: IKeybindingService, private readonly _modeService: IModeService, - private readonly _openerService: IOpenerService | null = NullOpenerService, + private readonly _openerService: IOpenerService = NullOpenerService, ) { super(ModesContentHoverWidget.ID, editor); @@ -354,7 +354,7 @@ export class ModesContentHoverWidget extends ContentHoverWidget { containColorPicker = true; const { red, green, blue, alpha } = msg.color; - const rgba = new RGBA(red * 255, green * 255, blue * 255, alpha); + const rgba = new RGBA(Math.round(red * 255), Math.round(green * 255), Math.round(blue * 255), alpha); const color = new Color(rgba); if (!this._editor.hasModel()) { @@ -548,7 +548,7 @@ export class ModesContentHoverWidget extends ContentHoverWidget { quickfixPlaceholderElement.style.transition = ''; quickfixPlaceholderElement.style.opacity = '1'; - if (!actions.actions.length) { + if (!actions.validActions.length) { actions.dispose(); quickfixPlaceholderElement.textContent = nls.localize('noQuickFixes', "No quick fixes available"); return; @@ -586,7 +586,7 @@ export class ModesContentHoverWidget extends ContentHoverWidget { return getCodeActions( this._editor.getModel()!, new Range(marker.startLineNumber, marker.startColumn, marker.endLineNumber, marker.endColumn), - { type: 'manual', filter: { kind: CodeActionKind.QuickFix } }, + { type: 'manual', filter: { include: CodeActionKind.QuickFix } }, cancellationToken); }); } diff --git a/src/vs/editor/contrib/hover/modesGlyphHover.ts b/src/vs/editor/contrib/hover/modesGlyphHover.ts index 2975d685b24e..06afb72578e9 100644 --- a/src/vs/editor/contrib/hover/modesGlyphHover.ts +++ b/src/vs/editor/contrib/hover/modesGlyphHover.ts @@ -97,7 +97,7 @@ export class ModesGlyphHoverWidget extends GlyphHoverWidget { constructor( editor: ICodeEditor, modeService: IModeService, - openerService: IOpenerService | null = NullOpenerService, + openerService: IOpenerService = NullOpenerService, ) { super(ModesGlyphHoverWidget.ID, editor); diff --git a/src/vs/editor/contrib/indentation/indentation.ts b/src/vs/editor/contrib/indentation/indentation.ts index 97f773336fbc..2be294e28bbe 100644 --- a/src/vs/editor/contrib/indentation/indentation.ts +++ b/src/vs/editor/contrib/indentation/indentation.ts @@ -22,7 +22,7 @@ import { IndentConsts } from 'vs/editor/common/modes/supports/indentRules'; import { IModelService } from 'vs/editor/common/services/modelService'; import * as indentUtils from 'vs/editor/contrib/indentation/indentUtils'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; -import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { EditorOption, EditorAutoIndentStrategy } from 'vs/editor/common/config/editorOptions'; export function getReindentEditOperations(model: ITextModel, startLineNumber: number, endLineNumber: number, inheritedIndent?: string): IIdentifiedSingleEditOperation[] { if (model.getLineCount() === 1 && model.getLineMaxColumn(1) === 1) { @@ -442,7 +442,7 @@ export class AutoIndentOnPaste implements IEditorContribution { this.callOnModel.clear(); // we are disabled - if (!this.editor.getOption(EditorOption.autoIndent) || this.editor.getOption(EditorOption.formatOnPaste)) { + if (this.editor.getOption(EditorOption.autoIndent) < EditorAutoIndentStrategy.Full || this.editor.getOption(EditorOption.formatOnPaste)) { return; } @@ -470,6 +470,7 @@ export class AutoIndentOnPaste implements IEditorContribution { if (!model.isCheapToTokenize(range.getStartPosition().lineNumber)) { return; } + const autoIndent = this.editor.getOption(EditorOption.autoIndent); const { tabSize, indentSize, insertSpaces } = model.getOptions(); this.editor.pushUndoStop(); let textEdits: TextEdit[] = []; @@ -499,7 +500,7 @@ export class AutoIndentOnPaste implements IEditorContribution { let firstLineText = model.getLineContent(startLineNumber); if (!/\S/.test(firstLineText.substring(0, range.startColumn - 1))) { - let indentOfFirstLine = LanguageConfigurationRegistry.getGoodIndentForLine(model, model.getLanguageIdentifier().id, startLineNumber, indentConverter); + let indentOfFirstLine = LanguageConfigurationRegistry.getGoodIndentForLine(autoIndent, model, model.getLanguageIdentifier().id, startLineNumber, indentConverter); if (indentOfFirstLine !== null) { let oldIndentation = strings.getLeadingWhitespace(firstLineText); @@ -557,7 +558,7 @@ export class AutoIndentOnPaste implements IEditorContribution { } } }; - let indentOfSecondLine = LanguageConfigurationRegistry.getGoodIndentForLine(virtualModel, model.getLanguageIdentifier().id, startLineNumber + 1, indentConverter); + let indentOfSecondLine = LanguageConfigurationRegistry.getGoodIndentForLine(autoIndent, virtualModel, model.getLanguageIdentifier().id, startLineNumber + 1, indentConverter); if (indentOfSecondLine !== null) { let newSpaceCntOfSecondLine = indentUtils.getSpaceCnt(indentOfSecondLine, tabSize); let oldSpaceCntOfSecondLine = indentUtils.getSpaceCnt(strings.getLeadingWhitespace(model.getLineContent(startLineNumber + 1)), tabSize); diff --git a/src/vs/editor/contrib/linesOperations/linesOperations.ts b/src/vs/editor/contrib/linesOperations/linesOperations.ts index 6416da84d468..087cb8d30375 100644 --- a/src/vs/editor/contrib/linesOperations/linesOperations.ts +++ b/src/vs/editor/contrib/linesOperations/linesOperations.ts @@ -64,7 +64,7 @@ class CopyLinesUpAction extends AbstractCopyLinesAction { linux: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyMod.Shift | KeyCode.UpArrow }, weight: KeybindingWeight.EditorContrib }, - menubarOpts: { + menuOpts: { menuId: MenuId.MenubarSelectionMenu, group: '2_line', title: nls.localize({ key: 'miCopyLinesUp', comment: ['&& denotes a mnemonic'] }, "&&Copy Line Up"), @@ -87,7 +87,7 @@ class CopyLinesDownAction extends AbstractCopyLinesAction { linux: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyMod.Shift | KeyCode.DownArrow }, weight: KeybindingWeight.EditorContrib }, - menubarOpts: { + menuOpts: { menuId: MenuId.MenubarSelectionMenu, group: '2_line', title: nls.localize({ key: 'miCopyLinesDown', comment: ['&& denotes a mnemonic'] }, "Co&&py Line Down"), @@ -105,7 +105,7 @@ export class DuplicateSelectionAction extends EditorAction { label: nls.localize('duplicateSelection', "Duplicate Selection"), alias: 'Duplicate Selection', precondition: EditorContextKeys.writable, - menubarOpts: { + menuOpts: { menuId: MenuId.MenubarSelectionMenu, group: '2_line', title: nls.localize({ key: 'miDuplicateSelection', comment: ['&& denotes a mnemonic'] }, "&&Duplicate Selection"), @@ -178,7 +178,7 @@ class MoveLinesUpAction extends AbstractMoveLinesAction { linux: { primary: KeyMod.Alt | KeyCode.UpArrow }, weight: KeybindingWeight.EditorContrib }, - menubarOpts: { + menuOpts: { menuId: MenuId.MenubarSelectionMenu, group: '2_line', title: nls.localize({ key: 'miMoveLinesUp', comment: ['&& denotes a mnemonic'] }, "Mo&&ve Line Up"), @@ -201,7 +201,7 @@ class MoveLinesDownAction extends AbstractMoveLinesAction { linux: { primary: KeyMod.Alt | KeyCode.DownArrow }, weight: KeybindingWeight.EditorContrib }, - menubarOpts: { + menuOpts: { menuId: MenuId.MenubarSelectionMenu, group: '2_line', title: nls.localize({ key: 'miMoveLinesDown', comment: ['&& denotes a mnemonic'] }, "Move &&Line Down"), diff --git a/src/vs/editor/contrib/linesOperations/moveLinesCommand.ts b/src/vs/editor/contrib/linesOperations/moveLinesCommand.ts index 6ff27f71a5df..0319b62a6414 100644 --- a/src/vs/editor/contrib/linesOperations/moveLinesCommand.ts +++ b/src/vs/editor/contrib/linesOperations/moveLinesCommand.ts @@ -13,18 +13,19 @@ import { IndentAction } from 'vs/editor/common/modes/languageConfiguration'; import { IIndentConverter, LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { IndentConsts } from 'vs/editor/common/modes/supports/indentRules'; import * as indentUtils from 'vs/editor/contrib/indentation/indentUtils'; +import { EditorAutoIndentStrategy } from 'vs/editor/common/config/editorOptions'; export class MoveLinesCommand implements ICommand { private readonly _selection: Selection; private readonly _isMovingDown: boolean; - private readonly _autoIndent: boolean; + private readonly _autoIndent: EditorAutoIndentStrategy; private _selectionId: string | null; private _moveEndPositionDown?: boolean; private _moveEndLineSelectionShrink: boolean; - constructor(selection: Selection, isMovingDown: boolean, autoIndent: boolean) { + constructor(selection: Selection, isMovingDown: boolean, autoIndent: EditorAutoIndentStrategy) { this._selection = selection; this._isMovingDown = isMovingDown; this._autoIndent = autoIndent; @@ -117,7 +118,7 @@ export class MoveLinesCommand implements ICommand { return model.getLineContent(lineNumber); } }; - let indentOfMovingLine = LanguageConfigurationRegistry.getGoodIndentForLine(virtualModel, model.getLanguageIdAtPosition( + let indentOfMovingLine = LanguageConfigurationRegistry.getGoodIndentForLine(this._autoIndent, virtualModel, model.getLanguageIdAtPosition( movingLineNumber, 1), s.startLineNumber, indentConverter); if (indentOfMovingLine !== null) { let oldIndentation = strings.getLeadingWhitespace(model.getLineContent(movingLineNumber)); @@ -152,7 +153,7 @@ export class MoveLinesCommand implements ICommand { } }; - let newIndentatOfMovingBlock = LanguageConfigurationRegistry.getGoodIndentForLine(virtualModel, model.getLanguageIdAtPosition( + let newIndentatOfMovingBlock = LanguageConfigurationRegistry.getGoodIndentForLine(this._autoIndent, virtualModel, model.getLanguageIdAtPosition( movingLineNumber, 1), s.startLineNumber + 1, indentConverter); if (newIndentatOfMovingBlock !== null) { @@ -197,7 +198,7 @@ export class MoveLinesCommand implements ICommand { } } else { // it doesn't match any onEnter rule, let's check indentation rules then. - let indentOfFirstLine = LanguageConfigurationRegistry.getGoodIndentForLine(virtualModel, model.getLanguageIdAtPosition(s.startLineNumber, 1), movingLineNumber, indentConverter); + let indentOfFirstLine = LanguageConfigurationRegistry.getGoodIndentForLine(this._autoIndent, virtualModel, model.getLanguageIdAtPosition(s.startLineNumber, 1), movingLineNumber, indentConverter); if (indentOfFirstLine !== null) { // adjust the indentation of the moving block let oldIndent = strings.getLeadingWhitespace(model.getLineContent(s.startLineNumber)); @@ -251,20 +252,19 @@ export class MoveLinesCommand implements ICommand { } let maxColumn = model.getLineMaxColumn(validPrecedingLine); - let enter = LanguageConfigurationRegistry.getEnterAction(model, new Range(validPrecedingLine, maxColumn, validPrecedingLine, maxColumn)); + let enter = LanguageConfigurationRegistry.getEnterAction(this._autoIndent, model, new Range(validPrecedingLine, maxColumn, validPrecedingLine, maxColumn)); if (enter) { let enterPrefix = enter.indentation; - let enterAction = enter.enterAction; - if (enterAction.indentAction === IndentAction.None) { - enterPrefix = enter.indentation + enterAction.appendText; - } else if (enterAction.indentAction === IndentAction.Indent) { - enterPrefix = enter.indentation + enterAction.appendText; - } else if (enterAction.indentAction === IndentAction.IndentOutdent) { + if (enter.indentAction === IndentAction.None) { + enterPrefix = enter.indentation + enter.appendText; + } else if (enter.indentAction === IndentAction.Indent) { + enterPrefix = enter.indentation + enter.appendText; + } else if (enter.indentAction === IndentAction.IndentOutdent) { enterPrefix = enter.indentation; - } else if (enterAction.indentAction === IndentAction.Outdent) { - enterPrefix = indentConverter.unshiftIndent(enter.indentation) + enterAction.appendText; + } else if (enter.indentAction === IndentAction.Outdent) { + enterPrefix = indentConverter.unshiftIndent(enter.indentation) + enter.appendText; } let movingLineText = model.getLineContent(line); if (this.trimLeft(movingLineText).indexOf(this.trimLeft(enterPrefix)) >= 0) { @@ -288,7 +288,7 @@ export class MoveLinesCommand implements ICommand { } private shouldAutoIndent(model: ITextModel, selection: Selection) { - if (!this._autoIndent) { + if (this._autoIndent < EditorAutoIndentStrategy.Full) { return false; } // if it's not easy to tokenize, we stop auto indent. diff --git a/src/vs/editor/contrib/linesOperations/sortLinesCommand.ts b/src/vs/editor/contrib/linesOperations/sortLinesCommand.ts index 3dad41df20e6..48b63cfc05b3 100644 --- a/src/vs/editor/contrib/linesOperations/sortLinesCommand.ts +++ b/src/vs/editor/contrib/linesOperations/sortLinesCommand.ts @@ -11,6 +11,14 @@ import { IIdentifiedSingleEditOperation, ITextModel } from 'vs/editor/common/mod export class SortLinesCommand implements editorCommon.ICommand { + private static _COLLATOR: Intl.Collator | null = null; + public static getCollator(): Intl.Collator { + if (!SortLinesCommand._COLLATOR) { + SortLinesCommand._COLLATOR = new Intl.Collator(); + } + return SortLinesCommand._COLLATOR; + } + private readonly selection: Selection; private readonly descending: boolean; private selectionId: string | null; @@ -76,9 +84,7 @@ function getSortData(model: ITextModel, selection: Selection, descending: boolea } let sorted = linesToSort.slice(0); - sorted.sort((a, b) => { - return a.toLowerCase().localeCompare(b.toLowerCase()); - }); + sorted.sort(SortLinesCommand.getCollator().compare); // If descending, reverse the order. if (descending === true) { diff --git a/src/vs/editor/contrib/linesOperations/test/moveLinesCommand.test.ts b/src/vs/editor/contrib/linesOperations/test/moveLinesCommand.test.ts index ca6ef0bc440a..cfc8d7aa32eb 100644 --- a/src/vs/editor/contrib/linesOperations/test/moveLinesCommand.test.ts +++ b/src/vs/editor/contrib/linesOperations/test/moveLinesCommand.test.ts @@ -9,21 +9,22 @@ import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageCo import { MoveLinesCommand } from 'vs/editor/contrib/linesOperations/moveLinesCommand'; import { testCommand } from 'vs/editor/test/browser/testCommand'; import { MockMode } from 'vs/editor/test/common/mocks/mockMode'; +import { EditorAutoIndentStrategy } from 'vs/editor/common/config/editorOptions'; function testMoveLinesDownCommand(lines: string[], selection: Selection, expectedLines: string[], expectedSelection: Selection): void { - testCommand(lines, null, selection, (sel) => new MoveLinesCommand(sel, true, false), expectedLines, expectedSelection); + testCommand(lines, null, selection, (sel) => new MoveLinesCommand(sel, true, EditorAutoIndentStrategy.Advanced), expectedLines, expectedSelection); } function testMoveLinesUpCommand(lines: string[], selection: Selection, expectedLines: string[], expectedSelection: Selection): void { - testCommand(lines, null, selection, (sel) => new MoveLinesCommand(sel, false, false), expectedLines, expectedSelection); + testCommand(lines, null, selection, (sel) => new MoveLinesCommand(sel, false, EditorAutoIndentStrategy.Advanced), expectedLines, expectedSelection); } function testMoveLinesDownWithIndentCommand(languageId: LanguageIdentifier, lines: string[], selection: Selection, expectedLines: string[], expectedSelection: Selection): void { - testCommand(lines, languageId, selection, (sel) => new MoveLinesCommand(sel, true, true), expectedLines, expectedSelection); + testCommand(lines, languageId, selection, (sel) => new MoveLinesCommand(sel, true, EditorAutoIndentStrategy.Full), expectedLines, expectedSelection); } function testMoveLinesUpWithIndentCommand(languageId: LanguageIdentifier, lines: string[], selection: Selection, expectedLines: string[], expectedSelection: Selection): void { - testCommand(lines, languageId, selection, (sel) => new MoveLinesCommand(sel, false, true), expectedLines, expectedSelection); + testCommand(lines, languageId, selection, (sel) => new MoveLinesCommand(sel, false, EditorAutoIndentStrategy.Full), expectedLines, expectedSelection); } suite('Editor Contrib - Move Lines Command', () => { diff --git a/src/vs/editor/contrib/links/getLinks.ts b/src/vs/editor/contrib/links/getLinks.ts index 883b00ba04f7..2dbd50bf8a68 100644 --- a/src/vs/editor/contrib/links/getLinks.ts +++ b/src/vs/editor/contrib/links/getLinks.ts @@ -44,17 +44,9 @@ export class Link implements ILink { return this._link.tooltip; } - resolve(token: CancellationToken): Promise { + async resolve(token: CancellationToken): Promise { if (this._link.url) { - try { - if (typeof this._link.url === 'string') { - return Promise.resolve(URI.parse(this._link.url)); - } else { - return Promise.resolve(this._link.url); - } - } catch (e) { - return Promise.reject(new Error('invalid')); - } + return this._link.url; } if (typeof this._provider.resolveLink === 'function') { diff --git a/src/vs/editor/contrib/links/links.ts b/src/vs/editor/contrib/links/links.ts index d6b1646443f8..19b1df1e1222 100644 --- a/src/vs/editor/contrib/links/links.ts +++ b/src/vs/editor/contrib/links/links.ts @@ -18,7 +18,7 @@ import * as editorCommon from 'vs/editor/common/editorCommon'; import { IModelDecorationsChangeAccessor, IModelDeltaDecoration, TrackedRangeStickiness } from 'vs/editor/common/model'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { LinkProviderRegistry } from 'vs/editor/common/modes'; -import { ClickLinkGesture, ClickLinkKeyboardEvent, ClickLinkMouseEvent } from 'vs/editor/contrib/goToDefinition/clickLinkGesture'; +import { ClickLinkGesture, ClickLinkKeyboardEvent, ClickLinkMouseEvent } from 'vs/editor/contrib/gotoSymbol/link/clickLinkGesture'; import { Link, getLinks, LinksList } from 'vs/editor/contrib/links/getLinks'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IOpenerService } from 'vs/platform/opener/common/opener'; diff --git a/src/vs/editor/contrib/markdown/markdownRenderer.ts b/src/vs/editor/contrib/markdown/markdownRenderer.ts index d11fa34ca484..d280470bb43b 100644 --- a/src/vs/editor/contrib/markdown/markdownRenderer.ts +++ b/src/vs/editor/contrib/markdown/markdownRenderer.ts @@ -7,7 +7,6 @@ import { IMarkdownString } from 'vs/base/common/htmlContent'; import { renderMarkdown, MarkdownRenderOptions } from 'vs/base/browser/markdownRenderer'; import { IOpenerService, NullOpenerService } from 'vs/platform/opener/common/opener'; import { IModeService } from 'vs/editor/common/services/modeService'; -import { URI } from 'vs/base/common/uri'; import { onUnexpectedError } from 'vs/base/common/errors'; import { tokenizeToString } from 'vs/editor/common/modes/textToHtmlTokenizer'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; @@ -29,7 +28,7 @@ export class MarkdownRenderer extends Disposable { constructor( private readonly _editor: ICodeEditor, @IModeService private readonly _modeService: IModeService, - @optional(IOpenerService) private readonly _openerService: IOpenerService | null = NullOpenerService, + @optional(IOpenerService) private readonly _openerService: IOpenerService = NullOpenerService, ) { super(); } @@ -64,15 +63,7 @@ export class MarkdownRenderer extends Disposable { codeBlockRenderCallback: () => this._onDidRenderCodeBlock.fire(), actionHandler: { callback: (content) => { - let uri: URI | undefined; - try { - uri = URI.parse(content); - } catch { - // ignore - } - if (uri && this._openerService) { - this._openerService.open(uri, { fromUserGesture: true }).catch(onUnexpectedError); - } + this._openerService.open(content, { fromUserGesture: true }).catch(onUnexpectedError); }, disposeables } diff --git a/src/vs/editor/contrib/multicursor/multicursor.ts b/src/vs/editor/contrib/multicursor/multicursor.ts index 4e5db3b092f7..69e303ab9980 100644 --- a/src/vs/editor/contrib/multicursor/multicursor.ts +++ b/src/vs/editor/contrib/multicursor/multicursor.ts @@ -46,7 +46,7 @@ export class InsertCursorAbove extends EditorAction { }, weight: KeybindingWeight.EditorContrib }, - menubarOpts: { + menuOpts: { menuId: MenuId.MenubarSelectionMenu, group: '3_multi', title: nls.localize({ key: 'miInsertCursorAbove', comment: ['&& denotes a mnemonic'] }, "&&Add Cursor Above"), @@ -95,7 +95,7 @@ export class InsertCursorBelow extends EditorAction { }, weight: KeybindingWeight.EditorContrib }, - menubarOpts: { + menuOpts: { menuId: MenuId.MenubarSelectionMenu, group: '3_multi', title: nls.localize({ key: 'miInsertCursorBelow', comment: ['&& denotes a mnemonic'] }, "A&&dd Cursor Below"), @@ -140,7 +140,7 @@ class InsertCursorAtEndOfEachLineSelected extends EditorAction { primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_I, weight: KeybindingWeight.EditorContrib }, - menubarOpts: { + menuOpts: { menuId: MenuId.MenubarSelectionMenu, group: '3_multi', title: nls.localize({ key: 'miInsertCursorAtEndOfEachLineSelected', comment: ['&& denotes a mnemonic'] }, "Add C&&ursors to Line Ends"), @@ -603,6 +603,17 @@ export class MultiCursorSelectionController extends Disposable implements IEdito matches = this._session.selectAll(); } + if (findState.searchScope) { + const state = findState.searchScope; + let inSelection: FindMatch[] | null = []; + for (let i = 0; i < matches.length; i++) { + if (matches[i].range.endLineNumber <= state.endLineNumber && matches[i].range.startLineNumber >= state.startLineNumber) { + inSelection.push(matches[i]); + } + } + matches = inSelection; + } + if (matches.length > 0) { const editorSelection = this._editor.getSelection(); // Have the primary cursor remain the one where the action was invoked @@ -620,6 +631,12 @@ export class MultiCursorSelectionController extends Disposable implements IEdito this._setSelections(matches.map(m => new Selection(m.range.startLineNumber, m.range.startColumn, m.range.endLineNumber, m.range.endColumn))); } } + + public selectAllUsingSelections(selections: Selection[]): void { + if (selections.length > 0) { + this._setSelections(selections); + } + } } export abstract class MultiCursorSelectionControllerAction extends EditorAction { @@ -651,7 +668,7 @@ export class AddSelectionToNextFindMatchAction extends MultiCursorSelectionContr primary: KeyMod.CtrlCmd | KeyCode.KEY_D, weight: KeybindingWeight.EditorContrib }, - menubarOpts: { + menuOpts: { menuId: MenuId.MenubarSelectionMenu, group: '3_multi', title: nls.localize({ key: 'miAddSelectionToNextFindMatch', comment: ['&& denotes a mnemonic'] }, "Add &&Next Occurrence"), @@ -671,7 +688,7 @@ export class AddSelectionToPreviousFindMatchAction extends MultiCursorSelectionC label: nls.localize('addSelectionToPreviousFindMatch', "Add Selection To Previous Find Match"), alias: 'Add Selection To Previous Find Match', precondition: undefined, - menubarOpts: { + menuOpts: { menuId: MenuId.MenubarSelectionMenu, group: '3_multi', title: nls.localize({ key: 'miAddSelectionToPreviousFindMatch', comment: ['&& denotes a mnemonic'] }, "Add P&&revious Occurrence"), @@ -729,7 +746,7 @@ export class SelectHighlightsAction extends MultiCursorSelectionControllerAction primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_L, weight: KeybindingWeight.EditorContrib }, - menubarOpts: { + menuOpts: { menuId: MenuId.MenubarSelectionMenu, group: '3_multi', title: nls.localize({ key: 'miSelectHighlights', comment: ['&& denotes a mnemonic'] }, "Select All &&Occurrences"), @@ -754,7 +771,7 @@ export class CompatChangeAll extends MultiCursorSelectionControllerAction { primary: KeyMod.CtrlCmd | KeyCode.F2, weight: KeybindingWeight.EditorContrib }, - menuOpts: { + contextMenuOpts: { group: '1_modification', order: 1.2 } diff --git a/src/vs/editor/contrib/parameterHints/parameterHints.css b/src/vs/editor/contrib/parameterHints/parameterHints.css index c63bd0381b98..7420daafd3a1 100644 --- a/src/vs/editor/contrib/parameterHints/parameterHints.css +++ b/src/vs/editor/contrib/parameterHints/parameterHints.css @@ -13,12 +13,12 @@ .monaco-editor .parameter-hints-widget > .wrapper { max-width: 440px; display: flex; - flex-direction: column; + flex-direction: row; } .monaco-editor .parameter-hints-widget.multiple { min-height: 3.3em; - padding: 0 0 0 1.9em; + padding: 0; } .monaco-editor .parameter-hints-widget.visible { @@ -62,20 +62,19 @@ padding: 0 0.4em; } -.monaco-editor .parameter-hints-widget .buttons { - position: absolute; +.monaco-editor .parameter-hints-widget .controls { display: none; - bottom: 0; - left: 0; + flex-direction: column; + align-items: center; + min-width: 22px; + justify-content: flex-end; } -.monaco-editor .parameter-hints-widget.multiple .buttons { - display: block; +.monaco-editor .parameter-hints-widget.multiple .controls { + display: flex; } .monaco-editor .parameter-hints-widget.multiple .button { - position: absolute; - left: 2px; width: 16px; height: 16px; background-repeat: no-repeat; @@ -88,26 +87,16 @@ } .monaco-editor .parameter-hints-widget .button.next { - bottom: 0; background-image: url('arrow-down.svg'); } .monaco-editor .parameter-hints-widget .overloads { - position: absolute; - display: none; text-align: center; - bottom: 14px; - left: 0; - width: 22px; height: 12px; line-height: 12px; opacity: 0.5; } -.monaco-editor .parameter-hints-widget.multiple .overloads { - display: block; -} - .monaco-editor .parameter-hints-widget .signature .parameter.active { font-weight: bold; text-decoration: underline; diff --git a/src/vs/editor/contrib/parameterHints/parameterHintsModel.ts b/src/vs/editor/contrib/parameterHints/parameterHintsModel.ts index d6b7720afe39..623d7a4859c1 100644 --- a/src/vs/editor/contrib/parameterHints/parameterHintsModel.ts +++ b/src/vs/editor/contrib/parameterHints/parameterHintsModel.ts @@ -26,7 +26,7 @@ namespace ParameterHintState { Pending, } - export const Default = new class { readonly type = Type.Default; }; + export const Default = { type: Type.Default } as const; export class Pending { readonly type = Type.Pending; diff --git a/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts b/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts index 5b7bdee9d545..9765ac757bc5 100644 --- a/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts +++ b/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts @@ -8,7 +8,7 @@ import { domEvent, stop } from 'vs/base/browser/event'; import * as aria from 'vs/base/browser/ui/aria/aria'; import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { Event } from 'vs/base/common/event'; -import { IDisposable, Disposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import 'vs/css!./parameterHints'; import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser'; import { ConfigurationChangedEvent, EditorOption } from 'vs/editor/common/config/editorOptions'; @@ -19,19 +19,20 @@ import { Context } from 'vs/editor/contrib/parameterHints/provideSignatureHelp'; import * as nls from 'vs/nls'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { editorHoverBackground, editorHoverBorder, textCodeBlockBackground, textLinkForeground } from 'vs/platform/theme/common/colorRegistry'; +import { editorHoverBackground, editorHoverBorder, textCodeBlockBackground, textLinkForeground, editorHoverForeground } from 'vs/platform/theme/common/colorRegistry'; import { HIGH_CONTRAST, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { ParameterHintsModel, TriggerContext } from 'vs/editor/contrib/parameterHints/parameterHintsModel'; +import { pad } from 'vs/base/common/strings'; const $ = dom.$; -export class ParameterHintsWidget extends Disposable implements IContentWidget, IDisposable { +export class ParameterHintsWidget extends Disposable implements IContentWidget { private static readonly ID = 'editor.widget.parameterHintsWidget'; private readonly markdownRenderer: MarkdownRenderer; private readonly renderDisposeables = this._register(new DisposableStore()); - private readonly model = this._register(new MutableDisposable()); + private readonly model: ParameterHintsModel; private readonly keyVisible: IContextKey; private readonly keyMultipleSignatures: IContextKey; @@ -57,11 +58,11 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget, ) { super(); this.markdownRenderer = this._register(new MarkdownRenderer(editor, modeService, openerService)); - this.model.value = new ParameterHintsModel(editor); + this.model = this._register(new ParameterHintsModel(editor)); this.keyVisible = Context.Visible.bindTo(contextKeyService); this.keyMultipleSignatures = Context.MultipleSignatures.bindTo(contextKeyService); - this._register(this.model.value.onChangedHints(newParameterHints => { + this._register(this.model.onChangedHints(newParameterHints => { if (newParameterHints) { this.show(); this.render(newParameterHints); @@ -76,9 +77,10 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget, const wrapper = dom.append(element, $('.wrapper')); wrapper.tabIndex = -1; - const buttons = dom.append(wrapper, $('.buttons')); - const previous = dom.append(buttons, $('.button.previous')); - const next = dom.append(buttons, $('.button.next')); + const controls = dom.append(wrapper, $('.controls')); + const previous = dom.append(controls, $('.button.previous')); + const overloads = dom.append(controls, $('.overloads')); + const next = dom.append(controls, $('.button.next')); const onPreviousClick = stop(domEvent(previous, 'click')); this._register(onPreviousClick(this.previous, this)); @@ -86,8 +88,6 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget, const onNextClick = stop(domEvent(next, 'click')); this._register(onNextClick(this.next, this)); - const overloads = dom.append(wrapper, $('.overloads')); - const body = $('.body'); const scrollbar = new DomScrollableElement(body, {}); this._register(scrollbar); @@ -134,7 +134,7 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget, } private show(): void { - if (!this.model || this.visible) { + if (this.visible) { return; } @@ -153,7 +153,7 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget, } private hide(): void { - if (!this.model || !this.visible) { + if (!this.visible) { return; } @@ -189,7 +189,6 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget, this.domNodes.docs.innerHTML = ''; const signature = hints.signatures[hints.activeSignature]; - if (!signature) { return; } @@ -204,14 +203,13 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget, if (!hasParameters) { const label = dom.append(code, $('span')); label.textContent = signature.label; - } else { this.renderParameters(code, signature, hints.activeParameter); } this.renderDisposeables.clear(); - const activeParameter = signature.parameters[hints.activeParameter]; + const activeParameter: modes.ParameterInformation | undefined = signature.parameters[hints.activeParameter]; if (activeParameter && activeParameter.documentation) { const documentation = $('span.documentation'); @@ -236,30 +234,13 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget, dom.append(this.domNodes.docs, renderedContents.element); } - let hasDocs = false; - if (activeParameter && typeof (activeParameter.documentation) === 'string' && activeParameter.documentation.length > 0) { - hasDocs = true; - } - if (activeParameter && typeof (activeParameter.documentation) === 'object' && activeParameter.documentation.value.length > 0) { - hasDocs = true; - } - if (typeof (signature.documentation) === 'string' && signature.documentation.length > 0) { - hasDocs = true; - } - if (typeof (signature.documentation) === 'object' && signature.documentation.value.length > 0) { - hasDocs = true; - } + const hasDocs = this.hasDocs(signature, activeParameter); dom.toggleClass(this.domNodes.signature, 'has-docs', hasDocs); dom.toggleClass(this.domNodes.docs, 'empty', !hasDocs); - let currentOverload = String(hints.activeSignature + 1); - - if (hints.signatures.length < 10) { - currentOverload += `/${hints.signatures.length}`; - } - - this.domNodes.overloads.textContent = currentOverload; + this.domNodes.overloads.textContent = + pad(hints.activeSignature + 1, hints.signatures.length.toString().length) + '/' + hints.signatures.length; if (activeParameter) { const labelToAnnounce = this.getParameterLabel(signature, hints.activeParameter); @@ -276,8 +257,23 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget, this.domNodes.scrollbar.scanDomNode(); } - private renderParameters(parent: HTMLElement, signature: modes.SignatureInformation, currentParameter: number): void { + private hasDocs(signature: modes.SignatureInformation, activeParameter: modes.ParameterInformation | undefined): boolean { + if (activeParameter && typeof (activeParameter.documentation) === 'string' && activeParameter.documentation.length > 0) { + return true; + } + if (activeParameter && typeof (activeParameter.documentation) === 'object' && activeParameter.documentation.value.length > 0) { + return true; + } + if (typeof (signature.documentation) === 'string' && signature.documentation.length > 0) { + return true; + } + if (typeof (signature.documentation) === 'object' && signature.documentation.value.length > 0) { + return true; + } + return false; + } + private renderParameters(parent: HTMLElement, signature: modes.SignatureInformation, currentParameter: number): void { const [start, end] = this.getParameterLabelOffsets(signature, currentParameter); const beforeSpan = document.createElement('span'); @@ -317,23 +313,17 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget, } next(): void { - if (this.model.value) { - this.editor.focus(); - this.model.value.next(); - } + this.editor.focus(); + this.model.next(); } previous(): void { - if (this.model.value) { - this.editor.focus(); - this.model.value.previous(); - } + this.editor.focus(); + this.model.previous(); } cancel(): void { - if (this.model.value) { - this.model.value.cancel(); - } + this.model.cancel(); } getDomNode(): HTMLElement { @@ -348,9 +338,7 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget, } trigger(context: TriggerContext): void { - if (this.model.value) { - this.model.value.trigger(context, 0); - } + this.model.trigger(context, 0); } private updateMaxHeight(): void { @@ -385,6 +373,11 @@ registerThemingParticipant((theme, collector) => { collector.addRule(`.monaco-editor .parameter-hints-widget a { color: ${link}; }`); } + const foreground = theme.getColor(editorHoverForeground); + if (foreground) { + collector.addRule(`.monaco-editor .parameter-hints-widget { color: ${foreground}; }`); + } + const codeBackground = theme.getColor(textCodeBlockBackground); if (codeBackground) { collector.addRule(`.monaco-editor .parameter-hints-widget code { background-color: ${codeBackground}; }`); diff --git a/src/vs/editor/contrib/referenceSearch/media/peekViewWidget.css b/src/vs/editor/contrib/peekView/media/peekViewWidget.css similarity index 87% rename from src/vs/editor/contrib/referenceSearch/media/peekViewWidget.css rename to src/vs/editor/contrib/peekView/media/peekViewWidget.css index 11a1d68eb01d..966475cbb384 100644 --- a/src/vs/editor/contrib/referenceSearch/media/peekViewWidget.css +++ b/src/vs/editor/contrib/peekView/media/peekViewWidget.css @@ -4,16 +4,13 @@ *--------------------------------------------------------------------------------------------*/ .monaco-editor .peekview-widget .head { - -webkit-box-sizing: border-box; - -o-box-sizing: border-box; - -moz-box-sizing: border-box; - -ms-box-sizing: border-box; box-sizing: border-box; display: flex; } .monaco-editor .peekview-widget .head .peekview-title { - display: inline-block; + display: flex; + align-items: center; font-size: 13px; margin-left: 20px; cursor: pointer; @@ -24,6 +21,15 @@ margin-left: 0.5em; } +.monaco-editor .peekview-widget .head .peekview-title .meta { + white-space: nowrap; +} + +.monaco-editor .peekview-widget .head .peekview-title .meta:not(:empty)::before { + content: '-'; + padding: 0 0.3em; +} + .monaco-editor .peekview-widget .head .peekview-actions { flex: 1; text-align: right; diff --git a/src/vs/editor/contrib/referenceSearch/peekViewWidget.ts b/src/vs/editor/contrib/peekView/peekView.ts similarity index 61% rename from src/vs/editor/contrib/referenceSearch/peekViewWidget.ts rename to src/vs/editor/contrib/peekView/peekView.ts index 973c90449bf6..6030a54d9a29 100644 --- a/src/vs/editor/contrib/referenceSearch/peekViewWidget.ts +++ b/src/vs/editor/contrib/peekView/peekView.ts @@ -3,25 +3,28 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import 'vs/css!./media/peekViewWidget'; import * as dom from 'vs/base/browser/dom'; import { IMouseEvent } from 'vs/base/browser/mouseEvent'; import { ActionBar, IActionBarOptions } from 'vs/base/browser/ui/actionbar/actionbar'; import { Action } from 'vs/base/common/actions'; import { Color } from 'vs/base/common/color'; -import { Emitter, Event } from 'vs/base/common/event'; +import { Emitter } from 'vs/base/common/event'; import * as objects from 'vs/base/common/objects'; import * as strings from 'vs/base/common/strings'; -import 'vs/css!./media/peekViewWidget'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; import { IOptions, IStyles, ZoneWidget } from 'vs/editor/contrib/zoneWidget/zoneWidget'; import * as nls from 'vs/nls'; -import { ContextKeyExpr, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, RawContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ServicesAccessor, createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IDisposable } from 'vs/base/common/lifecycle'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; +import { IEditorContribution } from 'vs/editor/common/editorCommon'; +import { registerColor, contrastBorder, activeContrastBorder } from 'vs/platform/theme/common/colorRegistry'; export const IPeekViewService = createDecorator('IPeekViewService'); @@ -33,7 +36,7 @@ export interface IPeekViewService { registerSingleton(IPeekViewService, class implements IPeekViewService { _serviceBrand: undefined; - private _widgets = new Map(); + private readonly _widgets = new Map(); addExclusiveWidget(editor: ICodeEditor, widget: PeekViewWidget): void { const existing = this._widgets.get(editor); @@ -57,6 +60,24 @@ export namespace PeekContext { export const notInPeekEditor: ContextKeyExpr = inPeekEditor.toNegated(); } +class PeekContextController implements IEditorContribution { + + static readonly ID = 'editor.contrib.referenceController'; + + constructor( + editor: ICodeEditor, + @IContextKeyService contextKeyService: IContextKeyService + ) { + if (editor instanceof EmbeddedCodeEditorWidget) { + PeekContext.inPeekEditor.bindTo(contextKeyService); + } + } + + dispose(): void { } +} + +registerEditorContribution(PeekContextController.ID, PeekContextController); + export function getOuterEditor(accessor: ServicesAccessor): ICodeEditor | null { let editor = accessor.get(ICodeEditorService).getFocusedCodeEditor(); if (editor instanceof EmbeddedCodeEditorWidget) { @@ -81,9 +102,10 @@ const defaultOptions: IPeekViewOptions = { export abstract class PeekViewWidget extends ZoneWidget { - public _serviceBrand: undefined; + _serviceBrand: undefined; private readonly _onDidClose = new Emitter(); + readonly onDidClose = this._onDidClose.event; protected _headElement?: HTMLDivElement; protected _primaryHeading?: HTMLElement; @@ -97,16 +119,12 @@ export abstract class PeekViewWidget extends ZoneWidget { objects.mixin(this.options, defaultOptions, false); } - public dispose(): void { + dispose(): void { super.dispose(); this._onDidClose.fire(this); } - public get onDidClose(): Event { - return this._onDidClose.event; - } - - public style(styles: IPeekViewStyles): void { + style(styles: IPeekViewStyles): void { let options = this.options; if (styles.headerBackgroundColor) { options.headerBackgroundColor = styles.headerBackgroundColor; @@ -185,7 +203,7 @@ export abstract class PeekViewWidget extends ZoneWidget { // implement me } - public setTitle(primaryHeading: string, secondaryHeading?: string): void { + setTitle(primaryHeading: string, secondaryHeading?: string): void { if (this._primaryHeading && this._secondaryHeading) { this._primaryHeading.innerHTML = strings.escape(primaryHeading); this._primaryHeading.setAttribute('aria-label', primaryHeading); @@ -197,19 +215,20 @@ export abstract class PeekViewWidget extends ZoneWidget { } } - public setMetaTitle(value: string): void { + setMetaTitle(value: string): void { if (this._metaHeading) { if (value) { this._metaHeading.innerHTML = strings.escape(value); + dom.show(this._metaHeading); } else { - dom.clearNode(this._metaHeading); + dom.hide(this._metaHeading); } } } protected abstract _fillBody(container: HTMLElement): void; - public _doLayout(heightInPixel: number, widthInPixel: number): void { + protected _doLayout(heightInPixel: number, widthInPixel: number): void { if (!this._isShowing && heightInPixel < 0) { // Looks like the view zone got folded away! @@ -237,3 +256,21 @@ export abstract class PeekViewWidget extends ZoneWidget { } } } + + +export const peekViewTitleBackground = registerColor('peekViewTitle.background', { dark: '#1E1E1E', light: '#FFFFFF', hc: '#0C141F' }, nls.localize('peekViewTitleBackground', 'Background color of the peek view title area.')); +export const peekViewTitleForeground = registerColor('peekViewTitleLabel.foreground', { dark: '#FFFFFF', light: '#333333', hc: '#FFFFFF' }, nls.localize('peekViewTitleForeground', 'Color of the peek view title.')); +export const peekViewTitleInfoForeground = registerColor('peekViewTitleDescription.foreground', { dark: '#ccccccb3', light: '#6c6c6cb3', hc: '#FFFFFF99' }, nls.localize('peekViewTitleInfoForeground', 'Color of the peek view title info.')); +export const peekViewBorder = registerColor('peekView.border', { dark: '#007acc', light: '#007acc', hc: contrastBorder }, nls.localize('peekViewBorder', 'Color of the peek view borders and arrow.')); + +export const peekViewResultsBackground = registerColor('peekViewResult.background', { dark: '#252526', light: '#F3F3F3', hc: Color.black }, nls.localize('peekViewResultsBackground', 'Background color of the peek view result list.')); +export const peekViewResultsMatchForeground = registerColor('peekViewResult.lineForeground', { dark: '#bbbbbb', light: '#646465', hc: Color.white }, nls.localize('peekViewResultsMatchForeground', 'Foreground color for line nodes in the peek view result list.')); +export const peekViewResultsFileForeground = registerColor('peekViewResult.fileForeground', { dark: Color.white, light: '#1E1E1E', hc: Color.white }, nls.localize('peekViewResultsFileForeground', 'Foreground color for file nodes in the peek view result list.')); +export const peekViewResultsSelectionBackground = registerColor('peekViewResult.selectionBackground', { dark: '#3399ff33', light: '#3399ff33', hc: null }, nls.localize('peekViewResultsSelectionBackground', 'Background color of the selected entry in the peek view result list.')); +export const peekViewResultsSelectionForeground = registerColor('peekViewResult.selectionForeground', { dark: Color.white, light: '#6C6C6C', hc: Color.white }, nls.localize('peekViewResultsSelectionForeground', 'Foreground color of the selected entry in the peek view result list.')); +export const peekViewEditorBackground = registerColor('peekViewEditor.background', { dark: '#001F33', light: '#F2F8FC', hc: Color.black }, nls.localize('peekViewEditorBackground', 'Background color of the peek view editor.')); +export const peekViewEditorGutterBackground = registerColor('peekViewEditorGutter.background', { dark: peekViewEditorBackground, light: peekViewEditorBackground, hc: peekViewEditorBackground }, nls.localize('peekViewEditorGutterBackground', 'Background color of the gutter in the peek view editor.')); + +export const peekViewResultsMatchHighlight = registerColor('peekViewResult.matchHighlightBackground', { dark: '#ea5c004d', light: '#ea5c004d', hc: null }, nls.localize('peekViewResultsMatchHighlight', 'Match highlight color in the peek view result list.')); +export const peekViewEditorMatchHighlight = registerColor('peekViewEditor.matchHighlightBackground', { dark: '#ff8f0099', light: '#f5d802de', hc: null }, nls.localize('peekViewEditorMatchHighlight', 'Match highlight color in the peek view editor.')); +export const peekViewEditorMatchHighlightBorder = registerColor('peekViewEditor.matchHighlightBorder', { dark: null, light: null, hc: activeContrastBorder }, nls.localize('peekViewEditorMatchHighlightBorder', 'Match highlight border in the peek view editor.')); diff --git a/src/vs/editor/contrib/referenceSearch/referenceSearch.ts b/src/vs/editor/contrib/referenceSearch/referenceSearch.ts deleted file mode 100644 index cfc11e26cc6f..000000000000 --- a/src/vs/editor/contrib/referenceSearch/referenceSearch.ts +++ /dev/null @@ -1,290 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as nls from 'vs/nls'; -import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { Position, IPosition } from 'vs/editor/common/core/position'; -import * as editorCommon from 'vs/editor/common/editorCommon'; -import { registerEditorAction, ServicesAccessor, EditorAction, registerEditorContribution, registerDefaultLanguageCommand } from 'vs/editor/browser/editorExtensions'; -import { Location, ReferenceProviderRegistry } from 'vs/editor/common/modes'; -import { Range } from 'vs/editor/common/core/range'; -import { PeekContext, getOuterEditor } from './peekViewWidget'; -import { ReferencesController, RequestOptions, ctxReferenceSearchVisible } from './referencesController'; -import { ReferencesModel, OneReference } from './referencesModel'; -import { createCancelablePromise } from 'vs/base/common/async'; -import { onUnexpectedExternalError } from 'vs/base/common/errors'; -import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; -import { ICodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser'; -import { ITextModel } from 'vs/editor/common/model'; -import { IListService } from 'vs/platform/list/browser/listService'; -import { ctxReferenceWidgetSearchTreeFocused } from 'vs/editor/contrib/referenceSearch/referencesWidget'; -import { CommandsRegistry, ICommandHandler } from 'vs/platform/commands/common/commands'; -import { URI } from 'vs/base/common/uri'; -import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { coalesce, flatten } from 'vs/base/common/arrays'; - -export const defaultReferenceSearchOptions: RequestOptions = { - getMetaTitle(model) { - return model.references.length > 1 ? nls.localize('meta.titleReference', " – {0} references", model.references.length) : ''; - } -}; - -export class ReferenceController implements editorCommon.IEditorContribution { - - public static readonly ID = 'editor.contrib.referenceController'; - - constructor( - editor: ICodeEditor, - @IContextKeyService contextKeyService: IContextKeyService - ) { - if (editor instanceof EmbeddedCodeEditorWidget) { - PeekContext.inPeekEditor.bindTo(contextKeyService); - } - } - - public dispose(): void { - } -} - -export class ReferenceAction extends EditorAction { - - constructor() { - super({ - id: 'editor.action.referenceSearch.trigger', - label: nls.localize('references.action.label', "Peek References"), - alias: 'Peek References', - precondition: ContextKeyExpr.and( - EditorContextKeys.hasReferenceProvider, - PeekContext.notInPeekEditor, - EditorContextKeys.isInEmbeddedEditor.toNegated()), - kbOpts: { - kbExpr: EditorContextKeys.editorTextFocus, - primary: KeyMod.Shift | KeyCode.F12, - weight: KeybindingWeight.EditorContrib - }, - menuOpts: { - group: 'navigation', - order: 1.5 - } - }); - } - - public run(_accessor: ServicesAccessor, editor: ICodeEditor): void { - let controller = ReferencesController.get(editor); - if (!controller) { - return; - } - if (editor.hasModel()) { - const range = editor.getSelection(); - const model = editor.getModel(); - const references = createCancelablePromise(token => provideReferences(model, range.getStartPosition(), token).then(references => new ReferencesModel(references))); - controller.toggleWidget(range, references, defaultReferenceSearchOptions); - } - } -} - -registerEditorContribution(ReferenceController.ID, ReferenceController); - -registerEditorAction(ReferenceAction); - -let findReferencesCommand: ICommandHandler = (accessor: ServicesAccessor, resource: URI, position: IPosition) => { - if (!(resource instanceof URI)) { - throw new Error('illegal argument, uri'); - } - if (!position) { - throw new Error('illegal argument, position'); - } - - const codeEditorService = accessor.get(ICodeEditorService); - return codeEditorService.openCodeEditor({ resource }, codeEditorService.getFocusedCodeEditor()).then(control => { - if (!isCodeEditor(control) || !control.hasModel()) { - return undefined; - } - - let controller = ReferencesController.get(control); - if (!controller) { - return undefined; - } - - let references = createCancelablePromise(token => provideReferences(control.getModel(), Position.lift(position), token).then(references => new ReferencesModel(references))); - let range = new Range(position.lineNumber, position.column, position.lineNumber, position.column); - return Promise.resolve(controller.toggleWidget(range, references, defaultReferenceSearchOptions)); - }); -}; - -let showReferencesCommand: ICommandHandler = (accessor: ServicesAccessor, resource: URI, position: IPosition, references: Location[]) => { - if (!(resource instanceof URI)) { - throw new Error('illegal argument, uri expected'); - } - - if (!references) { - throw new Error('missing references'); - } - - const codeEditorService = accessor.get(ICodeEditorService); - return codeEditorService.openCodeEditor({ resource }, codeEditorService.getFocusedCodeEditor()).then(control => { - if (!isCodeEditor(control)) { - return undefined; - } - - let controller = ReferencesController.get(control); - if (!controller) { - return undefined; - } - - return controller.toggleWidget( - new Range(position.lineNumber, position.column, position.lineNumber, position.column), - createCancelablePromise(_ => Promise.resolve(new ReferencesModel(references))), - defaultReferenceSearchOptions - ); - }); -}; - -// register commands - -CommandsRegistry.registerCommand({ - id: 'editor.action.findReferences', - handler: findReferencesCommand -}); - -CommandsRegistry.registerCommand({ - id: 'editor.action.showReferences', - handler: showReferencesCommand, - description: { - description: 'Show references at a position in a file', - args: [ - { name: 'uri', description: 'The text document in which to show references', constraint: URI }, - { name: 'position', description: 'The position at which to show', constraint: Position.isIPosition }, - { name: 'locations', description: 'An array of locations.', constraint: Array }, - ] - } -}); - -function closeActiveReferenceSearch(accessor: ServicesAccessor, args: any) { - withController(accessor, controller => controller.closeWidget()); -} - -function openReferenceToSide(accessor: ServicesAccessor, args: any) { - const listService = accessor.get(IListService); - - const focus = listService.lastFocusedList && listService.lastFocusedList.getFocus(); - if (focus instanceof OneReference) { - withController(accessor, controller => controller.openReference(focus, true)); - } -} - -function withController(accessor: ServicesAccessor, fn: (controller: ReferencesController) => void): void { - const outerEditor = getOuterEditor(accessor); - if (!outerEditor) { - return; - } - - let controller = ReferencesController.get(outerEditor); - if (!controller) { - return; - } - - fn(controller); -} - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: 'goToNextReference', - weight: KeybindingWeight.WorkbenchContrib + 50, - primary: KeyCode.F4, - when: ctxReferenceSearchVisible, - handler(accessor) { - withController(accessor, controller => { - controller.goToNextOrPreviousReference(true); - }); - } -}); - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: 'goToNextReferenceFromEmbeddedEditor', - weight: KeybindingWeight.EditorContrib + 50, - primary: KeyCode.F4, - when: PeekContext.inPeekEditor, - handler(accessor) { - withController(accessor, controller => { - controller.goToNextOrPreviousReference(true); - }); - } -}); - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: 'goToPreviousReference', - weight: KeybindingWeight.WorkbenchContrib + 50, - primary: KeyMod.Shift | KeyCode.F4, - when: ctxReferenceSearchVisible, - handler(accessor) { - withController(accessor, controller => { - controller.goToNextOrPreviousReference(false); - }); - } -}); - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: 'goToPreviousReferenceFromEmbeddedEditor', - weight: KeybindingWeight.EditorContrib + 50, - primary: KeyMod.Shift | KeyCode.F4, - when: PeekContext.inPeekEditor, - handler(accessor) { - withController(accessor, controller => { - controller.goToNextOrPreviousReference(false); - }); - } -}); - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: 'closeReferenceSearch', - weight: KeybindingWeight.WorkbenchContrib + 50, - primary: KeyCode.Escape, - secondary: [KeyMod.Shift | KeyCode.Escape], - when: ContextKeyExpr.and(ctxReferenceSearchVisible, ContextKeyExpr.not('config.editor.stablePeek')), - handler: closeActiveReferenceSearch -}); - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: 'closeReferenceSearchEditor', - weight: KeybindingWeight.EditorContrib - 101, - primary: KeyCode.Escape, - secondary: [KeyMod.Shift | KeyCode.Escape], - when: ContextKeyExpr.and(PeekContext.inPeekEditor, ContextKeyExpr.not('config.editor.stablePeek')), - handler: closeActiveReferenceSearch -}); - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: 'openReferenceToSide', - weight: KeybindingWeight.EditorContrib, - primary: KeyMod.CtrlCmd | KeyCode.Enter, - mac: { - primary: KeyMod.WinCtrl | KeyCode.Enter - }, - when: ContextKeyExpr.and(ctxReferenceSearchVisible, ctxReferenceWidgetSearchTreeFocused), - handler: openReferenceToSide -}); - -export function provideReferences(model: ITextModel, position: Position, token: CancellationToken): Promise { - - // collect references from all providers - const promises = ReferenceProviderRegistry.ordered(model).map(provider => { - return Promise.resolve(provider.provideReferences(model, position, { includeDeclaration: true }, token)).then(result => { - if (Array.isArray(result)) { - return result; - } - return undefined; - }, err => { - onUnexpectedExternalError(err); - }); - }); - - return Promise.all(promises).then(references => flatten(coalesce(references))); -} - -registerDefaultLanguageCommand('_executeReferenceProvider', (model, position) => provideReferences(model, position, CancellationToken.None)); diff --git a/src/vs/editor/contrib/rename/rename.ts b/src/vs/editor/contrib/rename/rename.ts index 47cb4d1b97c9..56ae606fa022 100644 --- a/src/vs/editor/contrib/rename/rename.ts +++ b/src/vs/editor/contrib/rename/rename.ts @@ -238,7 +238,7 @@ export class RenameAction extends EditorAction { primary: KeyCode.F2, weight: KeybindingWeight.EditorContrib }, - menuOpts: { + contextMenuOpts: { group: '1_modification', order: 1.1 } diff --git a/src/vs/editor/contrib/smartSelect/smartSelect.ts b/src/vs/editor/contrib/smartSelect/smartSelect.ts index e285feac8953..12a442950d0a 100644 --- a/src/vs/editor/contrib/smartSelect/smartSelect.ts +++ b/src/vs/editor/contrib/smartSelect/smartSelect.ts @@ -167,7 +167,7 @@ class GrowSelectionAction extends AbstractSmartSelect { }, weight: KeybindingWeight.EditorContrib }, - menubarOpts: { + menuOpts: { menuId: MenuId.MenubarSelectionMenu, group: '1_basic', title: nls.localize({ key: 'miSmartSelectGrow', comment: ['&& denotes a mnemonic'] }, "&&Expand Selection"), @@ -196,7 +196,7 @@ class ShrinkSelectionAction extends AbstractSmartSelect { }, weight: KeybindingWeight.EditorContrib }, - menubarOpts: { + menuOpts: { menuId: MenuId.MenubarSelectionMenu, group: '1_basic', title: nls.localize({ key: 'miSmartSelectShrink', comment: ['&& denotes a mnemonic'] }, "&&Shrink Selection"), diff --git a/src/vs/editor/contrib/smartSelect/test/smartSelect.test.ts b/src/vs/editor/contrib/smartSelect/test/smartSelect.test.ts index 0aa8ff26736b..12482fa7aecc 100644 --- a/src/vs/editor/contrib/smartSelect/test/smartSelect.test.ts +++ b/src/vs/editor/contrib/smartSelect/test/smartSelect.test.ts @@ -17,6 +17,7 @@ import { provideSelectionRanges } from 'vs/editor/contrib/smartSelect/smartSelec import { CancellationToken } from 'vs/base/common/cancellation'; import { WordSelectionRangeProvider } from 'vs/editor/contrib/smartSelect/wordSelections'; import { TestTextResourcePropertiesService } from 'vs/editor/test/common/services/modelService.test'; +import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; class MockJSMode extends MockMode { @@ -45,7 +46,7 @@ suite('SmartSelect', () => { setup(() => { const configurationService = new TestConfigurationService(); - modelService = new ModelServiceImpl(configurationService, new TestTextResourcePropertiesService(configurationService)); + modelService = new ModelServiceImpl(configurationService, new TestTextResourcePropertiesService(configurationService), new TestThemeService()); mode = new MockJSMode(); }); diff --git a/src/vs/editor/contrib/snippet/snippetSession.ts b/src/vs/editor/contrib/snippet/snippetSession.ts index 9ae417fd36a7..8547662cc676 100644 --- a/src/vs/editor/contrib/snippet/snippetSession.ts +++ b/src/vs/editor/contrib/snippet/snippetSession.ts @@ -333,7 +333,7 @@ const _defaultOptions: ISnippetSessionInsertOptions = { export class SnippetSession { - static adjustWhitespace(model: ITextModel, position: IPosition, snippet: TextmateSnippet): void { + static adjustWhitespace(model: ITextModel, position: IPosition, snippet: TextmateSnippet, adjustIndentation: boolean, adjustNewlines: boolean): void { const line = model.getLineContent(position.lineNumber); const lineLeadingWhitespace = getLeadingWhitespace(line, 0, position.column - 1); @@ -342,13 +342,19 @@ export class SnippetSession { // adjust indentation of text markers, except for choise elements // which get adjusted when being selected const lines = marker.value.split(/\r\n|\r|\n/); - for (let i = 1; i < lines.length; i++) { - let templateLeadingWhitespace = getLeadingWhitespace(lines[i]); - lines[i] = model.normalizeIndentation(lineLeadingWhitespace + templateLeadingWhitespace) + lines[i].substr(templateLeadingWhitespace.length); + + if (adjustIndentation) { + for (let i = 1; i < lines.length; i++) { + let templateLeadingWhitespace = getLeadingWhitespace(lines[i]); + lines[i] = model.normalizeIndentation(lineLeadingWhitespace + templateLeadingWhitespace) + lines[i].substr(templateLeadingWhitespace.length); + } } - const newValue = lines.join(model.getEOL()); - if (newValue !== marker.value) { - marker.parent.replace(marker, [new Text(newValue)]); + + if (adjustNewlines) { + const newValue = lines.join(model.getEOL()); + if (newValue !== marker.value) { + marker.parent.replace(marker, [new Text(newValue)]); + } } } return true; @@ -439,9 +445,11 @@ export class SnippetSession { // happens when being asked for (default) or when this is a secondary // cursor and the leading whitespace is different const start = snippetSelection.getStartPosition(); - if (adjustWhitespace || (idx > 0 && firstLineFirstNonWhitespace !== model.getLineFirstNonWhitespaceColumn(selection.positionLineNumber))) { - SnippetSession.adjustWhitespace(model, start, snippet); - } + SnippetSession.adjustWhitespace( + model, start, snippet, + adjustWhitespace || (idx > 0 && firstLineFirstNonWhitespace !== model.getLineFirstNonWhitespaceColumn(selection.positionLineNumber)), + true + ); snippet.resolveVariables(new CompositeSnippetVariableResolver([ modelBasedVariableResolver, diff --git a/src/vs/editor/contrib/snippet/test/snippetSession.test.ts b/src/vs/editor/contrib/snippet/test/snippetSession.test.ts index b3527cd01410..4b6b41a7e79b 100644 --- a/src/vs/editor/contrib/snippet/test/snippetSession.test.ts +++ b/src/vs/editor/contrib/snippet/test/snippetSession.test.ts @@ -41,7 +41,7 @@ suite('SnippetSession', function () { function assertNormalized(position: IPosition, input: string, expected: string): void { const snippet = new SnippetParser().parse(input); - SnippetSession.adjustWhitespace(model, position, snippet); + SnippetSession.adjustWhitespace(model, position, snippet, true, true); assert.equal(snippet.toTextmateString(), expected); } diff --git a/src/vs/editor/contrib/suggest/completionModel.ts b/src/vs/editor/contrib/suggest/completionModel.ts index 8d93593f0d3d..8f756495de8f 100644 --- a/src/vs/editor/contrib/suggest/completionModel.ts +++ b/src/vs/editor/contrib/suggest/completionModel.ts @@ -160,7 +160,7 @@ export class CompletionModel { // 'word' is that remainder of the current line that we // filter and score against. In theory each suggestion uses a // different word, but in practice not - that's why we cache - const overwriteBefore = item.position.column - item.completion.range.startColumn; + const overwriteBefore = item.position.column - item.editStart.column; const wordLen = overwriteBefore + characterCountDelta - (item.position.column - this._column); if (word.length !== wordLen) { word = wordLen === 0 ? '' : leadingLineContent.slice(-wordLen); diff --git a/src/vs/editor/contrib/suggest/media/suggest.css b/src/vs/editor/contrib/suggest/media/suggest.css index 001e1819f74e..0472b8bdc537 100644 --- a/src/vs/editor/contrib/suggest/media/suggest.css +++ b/src/vs/editor/contrib/suggest/media/suggest.css @@ -68,12 +68,9 @@ } .monaco-editor .suggest-widget .monaco-list { + user-select: none; -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: -moz-none; -ms-user-select: none; - -o-user-select: none; - user-select: none; } /** Styles for each row in the list element **/ @@ -279,3 +276,11 @@ border-radius: 3px; padding: 0 0.4em; } + + +/* replace/insert decorations */ + +.monaco-editor .suggest-insert-unexpected { + font-style: italic; +} + diff --git a/src/vs/editor/contrib/suggest/suggest.ts b/src/vs/editor/contrib/suggest/suggest.ts index 83acb3f39f52..3dd590a444fb 100644 --- a/src/vs/editor/contrib/suggest/suggest.ts +++ b/src/vs/editor/contrib/suggest/suggest.ts @@ -31,6 +31,11 @@ export class CompletionItem { readonly resolve: (token: CancellationToken) => Promise; + // + readonly editStart: IPosition; + readonly editInsertEnd: IPosition; + readonly editReplaceEnd: IPosition; + // perf readonly labelLow: string; readonly sortTextLow?: string; @@ -54,6 +59,17 @@ export class CompletionItem { this.sortTextLow = completion.sortText && completion.sortText.toLowerCase(); this.filterTextLow = completion.filterText && completion.filterText.toLowerCase(); + // normalize ranges + if (Range.isIRange(completion.range)) { + this.editStart = new Position(completion.range.startLineNumber, completion.range.startColumn); + this.editInsertEnd = new Position(completion.range.endLineNumber, completion.range.endColumn); + this.editReplaceEnd = new Position(completion.range.endLineNumber, completion.range.endColumn); + } else { + this.editStart = new Position(completion.range.insert.startLineNumber, completion.range.insert.startColumn); + this.editInsertEnd = new Position(completion.range.insert.endLineNumber, completion.range.insert.endColumn); + this.editReplaceEnd = new Position(completion.range.replace.endLineNumber, completion.range.replace.endColumn); + } + // create the suggestion resolver const { resolveCompletionItem } = provider; if (typeof resolveCompletionItem !== 'function') { @@ -122,8 +138,12 @@ export function provideSuggestionItems( token: CancellationToken = CancellationToken.None ): Promise { - const wordUntil = model.getWordUntilPosition(position); - const defaultRange = new Range(position.lineNumber, wordUntil.startColumn, position.lineNumber, wordUntil.endColumn); + const word = model.getWordAtPosition(position); + const defaultReplaceRange = word ? new Range(position.lineNumber, word.startColumn, position.lineNumber, word.endColumn) : Range.fromPositions(position); + const defaultInsertRange = defaultReplaceRange.setEndPosition(position.lineNumber, position.column); + + // const wordUntil = model.getWordUntilPosition(position); + // const defaultRange = new Range(position.lineNumber, wordUntil.startColumn, position.lineNumber, wordUntil.endColumn); position = position.clone(); @@ -159,7 +179,7 @@ export function provideSuggestionItems( // fill in default range when missing if (!suggestion.range) { - suggestion.range = defaultRange; + suggestion.range = { insert: defaultInsertRange, replace: defaultReplaceRange }; } // fill in default sortText when missing if (!suggestion.sortText) { diff --git a/src/vs/editor/contrib/suggest/suggestCommitCharacters.ts b/src/vs/editor/contrib/suggest/suggestCommitCharacters.ts index 8d02ff482d4d..dd75bf641f49 100644 --- a/src/vs/editor/contrib/suggest/suggestCommitCharacters.ts +++ b/src/vs/editor/contrib/suggest/suggestCommitCharacters.ts @@ -26,7 +26,7 @@ export class CommitCharacterController { this._disposables.add(widget.onDidHide(this.reset, this)); this._disposables.add(editor.onWillType(text => { - if (this._active) { + if (this._active && !widget.isFrozen()) { const ch = text.charCodeAt(text.length - 1); if (this._active.acceptCharacters.has(ch) && editor.getOption(EditorOption.acceptSuggestionOnCommitCharacter)) { accept(this._active.item); diff --git a/src/vs/editor/contrib/suggest/suggestController.ts b/src/vs/editor/contrib/suggest/suggestController.ts index 61c1cafff315..e1e7a785abe3 100644 --- a/src/vs/editor/contrib/suggest/suggestController.ts +++ b/src/vs/editor/contrib/suggest/suggestController.ts @@ -31,12 +31,13 @@ import { WordContextKey } from 'vs/editor/contrib/suggest/wordContextKey'; import { Event } from 'vs/base/common/event'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; import { IdleValue } from 'vs/base/common/async'; -import { isObject } from 'vs/base/common/types'; +import { isObject, assertType } from 'vs/base/common/types'; import { CommitCharacterController } from './suggestCommitCharacters'; import { IPosition } from 'vs/editor/common/core/position'; import { TrackedRangeStickiness, ITextModel } from 'vs/editor/common/model'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import * as platform from 'vs/base/common/platform'; +import { SuggestRangeHighlighter } from 'vs/editor/contrib/suggest/suggestRangeHighlighter'; /** * Stop suggest widget from disappearing when clicking into other areas @@ -101,33 +102,36 @@ export class SuggestController implements IEditorContribution { return editor.getContribution(SuggestController.ID); } - private readonly _model: SuggestModel; - private readonly _widget: IdleValue; + readonly editor: ICodeEditor; + readonly model: SuggestModel; + readonly widget: IdleValue; + private readonly _alternatives: IdleValue; private readonly _lineSuffix = new MutableDisposable(); private readonly _toDispose = new DisposableStore(); constructor( - private _editor: ICodeEditor, + editor: ICodeEditor, @IEditorWorkerService editorWorker: IEditorWorkerService, @ISuggestMemoryService private readonly _memoryService: ISuggestMemoryService, @ICommandService private readonly _commandService: ICommandService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, @IInstantiationService private readonly _instantiationService: IInstantiationService, ) { - this._model = new SuggestModel(this._editor, editorWorker); + this.editor = editor; + this.model = new SuggestModel(this.editor, editorWorker); - this._widget = new IdleValue(() => { + this.widget = new IdleValue(() => { - const widget = this._instantiationService.createInstance(SuggestWidget, this._editor); + const widget = this._instantiationService.createInstance(SuggestWidget, this.editor); this._toDispose.add(widget); this._toDispose.add(widget.onDidSelect(item => this._insertSuggestion(item, 0), this)); // Wire up logic to accept a suggestion on certain characters - const commitCharacterController = new CommitCharacterController(this._editor, widget, item => this._insertSuggestion(item, InsertFlags.NoAfterUndoStop)); + const commitCharacterController = new CommitCharacterController(this.editor, widget, item => this._insertSuggestion(item, InsertFlags.NoAfterUndoStop)); this._toDispose.add(commitCharacterController); - this._toDispose.add(this._model.onDidSuggest(e => { + this._toDispose.add(this.model.onDidSuggest(e => { if (e.completionModel.items.length === 0) { commitCharacterController.reset(); } @@ -137,19 +141,19 @@ export class SuggestController implements IEditorContribution { let makesTextEdit = SuggestContext.MakesTextEdit.bindTo(this._contextKeyService); this._toDispose.add(widget.onDidFocus(({ item }) => { - const position = this._editor.getPosition()!; - const startColumn = item.completion.range.startColumn; + const position = this.editor.getPosition()!; + const startColumn = item.editStart.column; const endColumn = position.column; let value = true; if ( - this._editor.getOption(EditorOption.acceptSuggestionOnEnter) === 'smart' - && this._model.state === State.Auto + this.editor.getOption(EditorOption.acceptSuggestionOnEnter) === 'smart' + && this.model.state === State.Auto && !item.completion.command && !item.completion.additionalTextEdits && !(item.completion.insertTextRules! & CompletionItemInsertTextRule.InsertAsSnippet) && endColumn - startColumn === item.completion.insertText.length ) { - const oldText = this._editor.getModel()!.getValueInRange({ + const oldText = this.editor.getModel()!.getValueInRange({ startLineNumber: position.lineNumber, startColumn, endLineNumber: position.lineNumber, @@ -161,38 +165,40 @@ export class SuggestController implements IEditorContribution { })); this._toDispose.add(toDisposable(() => makesTextEdit.reset())); + + return widget; }); this._alternatives = new IdleValue(() => { - return this._toDispose.add(new SuggestAlternatives(this._editor, this._contextKeyService)); + return this._toDispose.add(new SuggestAlternatives(this.editor, this._contextKeyService)); }); - this._toDispose.add(_instantiationService.createInstance(WordContextKey, _editor)); + this._toDispose.add(_instantiationService.createInstance(WordContextKey, editor)); - this._toDispose.add(this._model.onDidTrigger(e => { - this._widget.getValue().showTriggered(e.auto, e.shy ? 250 : 50); - this._lineSuffix.value = new LineSuffix(this._editor.getModel()!, e.position); + this._toDispose.add(this.model.onDidTrigger(e => { + this.widget.getValue().showTriggered(e.auto, e.shy ? 250 : 50); + this._lineSuffix.value = new LineSuffix(this.editor.getModel()!, e.position); })); - this._toDispose.add(this._model.onDidSuggest(e => { + this._toDispose.add(this.model.onDidSuggest(e => { if (!e.shy) { - let index = this._memoryService.select(this._editor.getModel()!, this._editor.getPosition()!, e.completionModel.items); - this._widget.getValue().showSuggestions(e.completionModel, index, e.isFrozen, e.auto); + let index = this._memoryService.select(this.editor.getModel()!, this.editor.getPosition()!, e.completionModel.items); + this.widget.getValue().showSuggestions(e.completionModel, index, e.isFrozen, e.auto); } })); - this._toDispose.add(this._model.onDidCancel(e => { + this._toDispose.add(this.model.onDidCancel(e => { if (!e.retrigger) { - this._widget.getValue().hideWidget(); + this.widget.getValue().hideWidget(); } })); - this._toDispose.add(this._editor.onDidBlurEditorWidget(() => { + this._toDispose.add(this.editor.onDidBlurEditorWidget(() => { if (!_sticky) { - this._model.cancel(); - this._model.clear(); + this.model.cancel(); + this.model.clear(); } })); - this._toDispose.add(this._widget.getValue().onDetailsKeyDown(e => { + this._toDispose.add(this.widget.getValue().onDetailsKeyDown(e => { // cmd + c on macOS, ctrl + c on Win / Linux if ( e.toKeybinding().equals(new SimpleKeybinding(true, false, false, false, KeyCode.KEY_C)) || @@ -203,25 +209,28 @@ export class SuggestController implements IEditorContribution { } if (!e.toKeybinding().isModifierKey()) { - this._editor.focus(); + this.editor.focus(); } })); // Manage the acceptSuggestionsOnEnter context key let acceptSuggestionsOnEnter = SuggestContext.AcceptSuggestionsOnEnter.bindTo(_contextKeyService); let updateFromConfig = () => { - const acceptSuggestionOnEnter = this._editor.getOption(EditorOption.acceptSuggestionOnEnter); + const acceptSuggestionOnEnter = this.editor.getOption(EditorOption.acceptSuggestionOnEnter); acceptSuggestionsOnEnter.set(acceptSuggestionOnEnter === 'on' || acceptSuggestionOnEnter === 'smart'); }; - this._toDispose.add(this._editor.onDidChangeConfiguration(() => updateFromConfig())); + this._toDispose.add(this.editor.onDidChangeConfiguration(() => updateFromConfig())); updateFromConfig(); + + // create range highlighter + this._toDispose.add(new SuggestRangeHighlighter(this)); } dispose(): void { this._alternatives.dispose(); this._toDispose.dispose(); - this._widget.dispose(); - this._model.dispose(); + this.widget.dispose(); + this.model.dispose(); this._lineSuffix.dispose(); } @@ -231,85 +240,66 @@ export class SuggestController implements IEditorContribution { ): void { if (!event || !event.item) { this._alternatives.getValue().reset(); - this._model.cancel(); - this._model.clear(); + this.model.cancel(); + this.model.clear(); return; } - if (!this._editor.hasModel()) { + if (!this.editor.hasModel()) { return; } - const model = this._editor.getModel(); + const model = this.editor.getModel(); const modelVersionNow = model.getAlternativeVersionId(); - const { completion: suggestion, position } = event.item; - const columnDelta = this._editor.getPosition().column - position.column; + const { item } = event; + const { completion: suggestion } = item; // pushing undo stops *before* additional text edits and // *after* the main edit if (!(flags & InsertFlags.NoBeforeUndoStop)) { - this._editor.pushUndoStop(); + this.editor.pushUndoStop(); } if (Array.isArray(suggestion.additionalTextEdits)) { - this._editor.executeEdits('suggestController.additionalTextEdits', suggestion.additionalTextEdits.map(edit => EditOperation.replace(Range.lift(edit.range), edit.text))); + this.editor.executeEdits('suggestController.additionalTextEdits', suggestion.additionalTextEdits.map(edit => EditOperation.replace(Range.lift(edit.range), edit.text))); } // keep item in memory - this._memoryService.memorize(model, this._editor.getPosition(), event.item); + this._memoryService.memorize(model, this.editor.getPosition(), item); let { insertText } = suggestion; if (!(suggestion.insertTextRules! & CompletionItemInsertTextRule.InsertAsSnippet)) { insertText = SnippetParser.escape(insertText); } - let overwriteBefore = position.column - suggestion.range.startColumn; - let overwriteAfter = suggestion.range.endColumn - position.column; - let suffixDelta = this._lineSuffix.value ? this._lineSuffix.value.delta(this._editor.getPosition()) : 0; - let word = model.getWordAtPosition(this._editor.getPosition()); - - const overwriteConfig = flags & InsertFlags.AlternativeOverwriteConfig - ? !this._editor.getOption(EditorOption.suggest).overwriteOnAccept - : this._editor.getOption(EditorOption.suggest).overwriteOnAccept; - if (!overwriteConfig) { - if (overwriteAfter > 0 && word && suggestion.range.endColumn === word.endColumn) { - // don't overwrite anything right of the cursor, overrule extension even when the - // completion only replaces a word... - overwriteAfter = 0; - } - } else { - if (overwriteAfter === 0 && word) { - // compute fallback overwrite length - overwriteAfter = word.endColumn - this._editor.getPosition().column; - } - } + const info = this.getOverwriteInfo(item, Boolean(flags & InsertFlags.AlternativeOverwriteConfig)); - SnippetController2.get(this._editor).insert(insertText, { - overwriteBefore: overwriteBefore + columnDelta, - overwriteAfter: overwriteAfter + suffixDelta, + SnippetController2.get(this.editor).insert(insertText, { + overwriteBefore: info.overwriteBefore, + overwriteAfter: info.overwriteAfter, undoStopBefore: false, undoStopAfter: false, adjustWhitespace: !(suggestion.insertTextRules! & CompletionItemInsertTextRule.KeepWhitespace) }); if (!(flags & InsertFlags.NoAfterUndoStop)) { - this._editor.pushUndoStop(); + this.editor.pushUndoStop(); } if (!suggestion.command) { // done - this._model.cancel(); - this._model.clear(); + this.model.cancel(); + this.model.clear(); } else if (suggestion.command.id === TriggerSuggestAction.id) { // retigger - this._model.trigger({ auto: true, shy: false }, true); + this.model.trigger({ auto: true, shy: false }, true); } else { // exec command, done this._commandService.executeCommand(suggestion.command.id, ...(suggestion.command.arguments ? [...suggestion.command.arguments] : [])) .catch(onUnexpectedError) - .finally(() => this._model.clear()); // <- clear only now, keep commands alive - this._model.cancel(); + .finally(() => this.model.clear()); // <- clear only now, keep commands alive + this.model.cancel(); } if (flags & InsertFlags.KeepAlternativeSuggestions) { @@ -333,6 +323,24 @@ export class SuggestController implements IEditorContribution { this._alertCompletionItem(event.item); } + getOverwriteInfo(item: CompletionItem, toggleMode: boolean): { overwriteBefore: number, overwriteAfter: number } { + assertType(this.editor.hasModel()); + + let replace = this.editor.getOption(EditorOption.suggest).insertMode === 'replace'; + if (toggleMode) { + replace = !replace; + } + const overwriteBefore = item.position.column - item.editStart.column; + const overwriteAfter = (replace ? item.editReplaceEnd.column : item.editInsertEnd.column) - item.position.column; + const columnDelta = this.editor.getPosition().column - item.position.column; + const suffixDelta = this._lineSuffix.value ? this._lineSuffix.value.delta(this.editor.getPosition()) : 0; + + return { + overwriteBefore: overwriteBefore + columnDelta, + overwriteAfter: overwriteAfter + suffixDelta + }; + } + private _alertCompletionItem({ completion: suggestion }: CompletionItem): void { if (isNonEmptyArray(suggestion.additionalTextEdits)) { let msg = nls.localize('arai.alert.snippet', "Accepting '{0}' made {1} additional edits", suggestion.label, suggestion.additionalTextEdits.length); @@ -341,22 +349,22 @@ export class SuggestController implements IEditorContribution { } triggerSuggest(onlyFrom?: Set): void { - if (this._editor.hasModel()) { - this._model.trigger({ auto: false, shy: false }, false, onlyFrom); - this._editor.revealLine(this._editor.getPosition().lineNumber, ScrollType.Smooth); - this._editor.focus(); + if (this.editor.hasModel()) { + this.model.trigger({ auto: false, shy: false }, false, onlyFrom); + this.editor.revealLine(this.editor.getPosition().lineNumber, ScrollType.Smooth); + this.editor.focus(); } } triggerSuggestAndAcceptBest(arg: { fallback: string }): void { - if (!this._editor.hasModel()) { + if (!this.editor.hasModel()) { return; } - const positionNow = this._editor.getPosition(); + const positionNow = this.editor.getPosition(); const fallback = () => { - if (positionNow.equals(this._editor.getPosition()!)) { + if (positionNow.equals(this.editor.getPosition()!)) { this._commandService.executeCommand(arg.fallback); } }; @@ -366,14 +374,14 @@ export class SuggestController implements IEditorContribution { // snippet, other editor -> makes edit return true; } - const position = this._editor.getPosition()!; - const startColumn = item.completion.range.startColumn; + const position = this.editor.getPosition()!; + const startColumn = item.editStart.column; const endColumn = position.column; if (endColumn - startColumn !== item.completion.insertText.length) { // unequal lengths -> makes edit return true; } - const textNow = this._editor.getModel()!.getValueInRange({ + const textNow = this.editor.getModel()!.getValueInRange({ startLineNumber: position.lineNumber, startColumn, endLineNumber: position.lineNumber, @@ -383,41 +391,41 @@ export class SuggestController implements IEditorContribution { return textNow !== item.completion.insertText; }; - Event.once(this._model.onDidTrigger)(_ => { + Event.once(this.model.onDidTrigger)(_ => { // wait for trigger because only then the cancel-event is trustworthy let listener: IDisposable[] = []; - Event.any(this._model.onDidTrigger, this._model.onDidCancel)(() => { + Event.any(this.model.onDidTrigger, this.model.onDidCancel)(() => { // retrigger or cancel -> try to type default text dispose(listener); fallback(); }, undefined, listener); - this._model.onDidSuggest(({ completionModel }) => { + this.model.onDidSuggest(({ completionModel }) => { dispose(listener); if (completionModel.items.length === 0) { fallback(); return; } - const index = this._memoryService.select(this._editor.getModel()!, this._editor.getPosition()!, completionModel.items); + const index = this._memoryService.select(this.editor.getModel()!, this.editor.getPosition()!, completionModel.items); const item = completionModel.items[index]; if (!makesTextEdit(item)) { fallback(); return; } - this._editor.pushUndoStop(); + this.editor.pushUndoStop(); this._insertSuggestion({ index, item, model: completionModel }, InsertFlags.KeepAlternativeSuggestions | InsertFlags.NoBeforeUndoStop | InsertFlags.NoAfterUndoStop); }, undefined, listener); }); - this._model.trigger({ auto: false, shy: true }); - this._editor.revealLine(positionNow.lineNumber, ScrollType.Smooth); - this._editor.focus(); + this.model.trigger({ auto: false, shy: true }); + this.editor.revealLine(positionNow.lineNumber, ScrollType.Smooth); + this.editor.focus(); } acceptSelectedSuggestion(keepAlternativeSuggestions: boolean, alternativeOverwriteConfig: boolean): void { - const item = this._widget.getValue().getFocusedItem(); + const item = this.widget.getValue().getFocusedItem(); let flags = 0; if (keepAlternativeSuggestions) { flags |= InsertFlags.KeepAlternativeSuggestions; @@ -437,45 +445,45 @@ export class SuggestController implements IEditorContribution { } cancelSuggestWidget(): void { - this._model.cancel(); - this._model.clear(); - this._widget.getValue().hideWidget(); + this.model.cancel(); + this.model.clear(); + this.widget.getValue().hideWidget(); } selectNextSuggestion(): void { - this._widget.getValue().selectNext(); + this.widget.getValue().selectNext(); } selectNextPageSuggestion(): void { - this._widget.getValue().selectNextPage(); + this.widget.getValue().selectNextPage(); } selectLastSuggestion(): void { - this._widget.getValue().selectLast(); + this.widget.getValue().selectLast(); } selectPrevSuggestion(): void { - this._widget.getValue().selectPrevious(); + this.widget.getValue().selectPrevious(); } selectPrevPageSuggestion(): void { - this._widget.getValue().selectPreviousPage(); + this.widget.getValue().selectPreviousPage(); } selectFirstSuggestion(): void { - this._widget.getValue().selectFirst(); + this.widget.getValue().selectFirst(); } toggleSuggestionDetails(): void { - this._widget.getValue().toggleDetails(); + this.widget.getValue().toggleDetails(); } toggleExplainMode(): void { - this._widget.getValue().toggleExplainMode(); + this.widget.getValue().toggleExplainMode(); } toggleSuggestionFocus(): void { - this._widget.getValue().toggleDetailsFocus(); + this.widget.getValue().toggleDetailsFocus(); } } @@ -492,7 +500,7 @@ export class TriggerSuggestAction extends EditorAction { kbOpts: { kbExpr: EditorContextKeys.textInputFocus, primary: KeyMod.CtrlCmd | KeyCode.Space, - mac: { primary: KeyMod.WinCtrl | KeyCode.Space }, + mac: { primary: KeyMod.WinCtrl | KeyCode.Space, secondary: [KeyMod.Alt | KeyCode.Escape] }, weight: KeybindingWeight.EditorContrib } }); diff --git a/src/vs/editor/contrib/suggest/suggestModel.ts b/src/vs/editor/contrib/suggest/suggestModel.ts index 512a63f58c23..43a9cae6ff7b 100644 --- a/src/vs/editor/contrib/suggest/suggestModel.ts +++ b/src/vs/editor/contrib/suggest/suggestModel.ts @@ -13,7 +13,7 @@ import { CursorChangeReason, ICursorSelectionChangedEvent } from 'vs/editor/comm import { Position, IPosition } from 'vs/editor/common/core/position'; import { Selection } from 'vs/editor/common/core/selection'; import { ITextModel, IWordAtPosition } from 'vs/editor/common/model'; -import { CompletionItemProvider, StandardTokenType, CompletionContext, CompletionProviderRegistry, CompletionTriggerKind, CompletionItemKind, completionKindFromString } from 'vs/editor/common/modes'; +import { CompletionItemProvider, StandardTokenType, CompletionContext, CompletionProviderRegistry, CompletionTriggerKind, CompletionItemKind } from 'vs/editor/common/modes'; import { CompletionModel } from './completionModel'; import { CompletionItem, getSuggestionComparator, provideSuggestionItems, getSnippetSuggestSupport, SnippetSortOrder, CompletionOptions } from './suggest'; import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2'; @@ -388,9 +388,7 @@ export class SuggestModel implements IDisposable { this._requestToken = new CancellationTokenSource(); // kind filter and snippet sort rules - const suggestOptions = this._editor.getOption(EditorOption.suggest); const snippetSuggestions = this._editor.getOption(EditorOption.snippetSuggestions); - let itemKindFilter = new Set(); let snippetSortOrder = SnippetSortOrder.Inline; switch (snippetSuggestions) { case 'top': @@ -403,19 +401,9 @@ export class SuggestModel implements IDisposable { case 'bottom': snippetSortOrder = SnippetSortOrder.Bottom; break; - case 'none': - itemKindFilter.add(CompletionItemKind.Snippet); - break; - } - - // kind filter - for (const key in suggestOptions.filteredTypes) { - const kind = completionKindFromString(key, true); - if (typeof kind !== 'undefined' && suggestOptions.filteredTypes[key] === false) { - itemKindFilter.add(kind); - } } + let itemKindFilter = SuggestModel._createItemKindFilter(this._editor); let wordDistance = WordDistance.create(this._editorWorker, this._editor); let items = provideSuggestionItems( @@ -467,6 +455,48 @@ export class SuggestModel implements IDisposable { }).catch(onUnexpectedError); } + private static _createItemKindFilter(editor: ICodeEditor): Set { + // kind filter and snippet sort rules + const result = new Set(); + + // snippet setting + const snippetSuggestions = editor.getOption(EditorOption.snippetSuggestions); + if (snippetSuggestions === 'none') { + result.add(CompletionItemKind.Snippet); + } + + // type setting + const suggestOptions = editor.getOption(EditorOption.suggest); + if (!suggestOptions.showMethods) { result.add(CompletionItemKind.Method); } + if (!suggestOptions.showFunctions) { result.add(CompletionItemKind.Function); } + if (!suggestOptions.showConstructors) { result.add(CompletionItemKind.Constructor); } + if (!suggestOptions.showFields) { result.add(CompletionItemKind.Field); } + if (!suggestOptions.showVariables) { result.add(CompletionItemKind.Variable); } + if (!suggestOptions.showClasses) { result.add(CompletionItemKind.Class); } + if (!suggestOptions.showStructs) { result.add(CompletionItemKind.Struct); } + if (!suggestOptions.showInterfaces) { result.add(CompletionItemKind.Interface); } + if (!suggestOptions.showModules) { result.add(CompletionItemKind.Module); } + if (!suggestOptions.showProperties) { result.add(CompletionItemKind.Property); } + if (!suggestOptions.showEvents) { result.add(CompletionItemKind.Event); } + if (!suggestOptions.showOperators) { result.add(CompletionItemKind.Operator); } + if (!suggestOptions.showUnits) { result.add(CompletionItemKind.Unit); } + if (!suggestOptions.showValues) { result.add(CompletionItemKind.Value); } + if (!suggestOptions.showConstants) { result.add(CompletionItemKind.Constant); } + if (!suggestOptions.showEnums) { result.add(CompletionItemKind.Enum); } + if (!suggestOptions.showEnumMembers) { result.add(CompletionItemKind.EnumMember); } + if (!suggestOptions.showKeywords) { result.add(CompletionItemKind.Keyword); } + if (!suggestOptions.showWords) { result.add(CompletionItemKind.Text); } + if (!suggestOptions.showColors) { result.add(CompletionItemKind.Color); } + if (!suggestOptions.showFiles) { result.add(CompletionItemKind.File); } + if (!suggestOptions.showReferences) { result.add(CompletionItemKind.Reference); } + if (!suggestOptions.showColors) { result.add(CompletionItemKind.Customcolor); } + if (!suggestOptions.showFolders) { result.add(CompletionItemKind.Folder); } + if (!suggestOptions.showTypeParameters) { result.add(CompletionItemKind.TypeParameter); } + if (!suggestOptions.showSnippets) { result.add(CompletionItemKind.Snippet); } + + return result; + } + private _onNewContext(ctx: LineContext): void { if (!this._context) { diff --git a/src/vs/editor/contrib/suggest/suggestRangeHighlighter.ts b/src/vs/editor/contrib/suggest/suggestRangeHighlighter.ts new file mode 100644 index 000000000000..282fc71baaa9 --- /dev/null +++ b/src/vs/editor/contrib/suggest/suggestRangeHighlighter.ts @@ -0,0 +1,125 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { Range } from 'vs/editor/common/core/range'; +import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { CompletionItem } from 'vs/editor/contrib/suggest/suggest'; +import { IModelDeltaDecoration } from 'vs/editor/common/model'; +import { SuggestController } from 'vs/editor/contrib/suggest/suggestController'; +import { Emitter } from 'vs/base/common/event'; +import { domEvent } from 'vs/base/browser/event'; + +export class SuggestRangeHighlighter { + + private readonly _disposables = new DisposableStore(); + + private _decorations: string[] = []; + private _widgetListener?: IDisposable; + private _shiftKeyListener?: IDisposable; + private _currentItem?: CompletionItem; + + constructor(private readonly _controller: SuggestController) { + + this._disposables.add(_controller.model.onDidSuggest(e => { + if (!e.shy) { + const widget = this._controller.widget.getValue(); + const focused = widget.getFocusedItem(); + if (focused) { + this._highlight(focused.item); + } + if (!this._widgetListener) { + this._widgetListener = widget.onDidFocus(e => this._highlight(e.item)); + } + } + })); + + this._disposables.add(_controller.model.onDidCancel(() => { + this._reset(); + })); + } + + dispose(): void { + this._reset(); + this._disposables.dispose(); + dispose(this._widgetListener); + dispose(this._shiftKeyListener); + } + + private _reset(): void { + this._decorations = this._controller.editor.deltaDecorations(this._decorations, []); + if (this._shiftKeyListener) { + this._shiftKeyListener.dispose(); + this._shiftKeyListener = undefined; + } + } + + private _highlight(item: CompletionItem) { + + this._currentItem = item; + const opts = this._controller.editor.getOption(EditorOption.suggest); + let newDeco: IModelDeltaDecoration[] = []; + + if (opts.insertHighlight) { + if (!this._shiftKeyListener) { + this._shiftKeyListener = shiftKey.event(() => this._highlight(this._currentItem!)); + } + + const info = this._controller.getOverwriteInfo(item, shiftKey.isPressed); + const position = this._controller.editor.getPosition()!; + + if (opts.insertMode === 'insert' && info.overwriteAfter > 0) { + // wants inserts but got replace-mode -> highlight AFTER range + newDeco = [{ + range: new Range(position.lineNumber, position.column, position.lineNumber, position.column + info.overwriteAfter), + options: { inlineClassName: 'suggest-insert-unexpected' } + }]; + + } else if (opts.insertMode === 'replace' && info.overwriteAfter === 0) { + // want replace but likely got insert -> highlight AFTER range + const wordInfo = this._controller.editor.getModel()?.getWordAtPosition(position); + if (wordInfo && wordInfo.endColumn > position.column) { + newDeco = [{ + range: new Range(position.lineNumber, position.column, position.lineNumber, wordInfo.endColumn), + options: { inlineClassName: 'suggest-insert-unexpected' } + }]; + } + } + } + + // update editor decorations + this._decorations = this._controller.editor.deltaDecorations(this._decorations, newDeco); + } +} + +const shiftKey = new class ShiftKey extends Emitter { + + private readonly _subscriptions = new DisposableStore(); + private _isPressed: boolean = false; + + constructor() { + super(); + this._subscriptions.add(domEvent(document.body, 'keydown')(e => this.isPressed = e.shiftKey)); + this._subscriptions.add(domEvent(document.body, 'keyup')(() => this.isPressed = false)); + this._subscriptions.add(domEvent(document.body, 'mouseleave')(() => this.isPressed = false)); + this._subscriptions.add(domEvent(document.body, 'blur')(() => this.isPressed = false)); + } + + get isPressed(): boolean { + return this._isPressed; + } + + set isPressed(value: boolean) { + if (this._isPressed !== value) { + this._isPressed = value; + this.fire(value); + } + } + + dispose() { + this._subscriptions.dispose(); + super.dispose(); + } +}; diff --git a/src/vs/editor/contrib/suggest/suggestWidget.ts b/src/vs/editor/contrib/suggest/suggestWidget.ts index ff2edb3aa767..6cf2b36d4315 100644 --- a/src/vs/editor/contrib/suggest/suggestWidget.ts +++ b/src/vs/editor/contrib/suggest/suggestWidget.ts @@ -172,7 +172,7 @@ class Renderer implements IListRenderer if (suggestion.kind === CompletionItemKind.Color && extractColor(element, color)) { // special logic for 'color' completion items data.icon.className = 'icon customcolor'; - data.iconContainer.className = 'icon customcolor'; + data.iconContainer.className = 'icon hide'; data.colorspan.style.backgroundColor = color[0]; } else if (suggestion.kind === CompletionItemKind.File && this._themeService.getIconTheme().hasFileIcons) { @@ -1074,6 +1074,10 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate{ + insertMode: 'insert', + snippetsPreventQuickSuggestions: true, + filterGraceful: true, + localityBonus: false, + shareSuggestSelections: false, + showIcons: true, + maxVisibleSuggestions: 12, + showMethods: true, + showFunctions: true, + showConstructors: true, + showFields: true, + showVariables: true, + showClasses: true, + showStructs: true, + showInterfaces: true, + showModules: true, + showProperties: true, + showEvents: true, + showOperators: true, + showUnits: true, + showValues: true, + showConstants: true, + showEnums: true, + showEnumMembers: true, + showKeywords: true, + showWords: true, + showColors: true, + showFiles: true, + showReferences: true, + showFolders: true, + showTypeParameters: true, + showSnippets: true, + }; let model: CompletionModel; @@ -158,16 +192,7 @@ suite('CompletionModel', function () { ], 1, { leadingLineContent: 's', characterCountDelta: 0 - }, WordDistance.None, { - overwriteOnAccept: false, - snippetsPreventQuickSuggestions: true, - filterGraceful: true, - localityBonus: false, - shareSuggestSelections: false, - showIcons: true, - maxVisibleSuggestions: 12, - filteredTypes: Object.create(null) - }, 'top'); + }, WordDistance.None, defaultOptions, 'top'); assert.equal(model.items.length, 2); const [a, b] = model.items; @@ -186,16 +211,7 @@ suite('CompletionModel', function () { ], 1, { leadingLineContent: 's', characterCountDelta: 0 - }, WordDistance.None, { - overwriteOnAccept: false, - snippetsPreventQuickSuggestions: true, - filterGraceful: true, - localityBonus: false, - shareSuggestSelections: false, - showIcons: true, - maxVisibleSuggestions: 12, - filteredTypes: Object.create(null) - }, 'bottom'); + }, WordDistance.None, defaultOptions, 'bottom'); assert.equal(model.items.length, 2); const [a, b] = model.items; @@ -213,16 +229,7 @@ suite('CompletionModel', function () { ], 1, { leadingLineContent: 's', characterCountDelta: 0 - }, WordDistance.None, { - overwriteOnAccept: false, - snippetsPreventQuickSuggestions: true, - filterGraceful: true, - localityBonus: false, - shareSuggestSelections: false, - showIcons: true, - maxVisibleSuggestions: 12, - filteredTypes: Object.create(null) - }, 'inline'); + }, WordDistance.None, defaultOptions, 'inline'); assert.equal(model.items.length, 2); const [a, b] = model.items; diff --git a/src/vs/editor/contrib/wordOperations/test/wordOperations.test.ts b/src/vs/editor/contrib/wordOperations/test/wordOperations.test.ts index 41aa36e8abea..80f870c72c38 100644 --- a/src/vs/editor/contrib/wordOperations/test/wordOperations.test.ts +++ b/src/vs/editor/contrib/wordOperations/test/wordOperations.test.ts @@ -351,7 +351,7 @@ suite('WordOperations', () => { }); test('cursorWordAccessibilityRight', () => { - const EXPECTED = [' /* Just| some| more| text| a|+= 3| +5|-3| + 7| */ |'].join('\n'); + const EXPECTED = [' /* |Just |some |more |text |a+= |3 +|5-|3 + |7 */ |'].join('\n'); const [text,] = deserializePipePositions(EXPECTED); const actualStops = testRepeatedActionAndExtractPositions( text, diff --git a/src/vs/editor/editor.all.ts b/src/vs/editor/editor.all.ts index 6d03ec42c27e..40321e61e358 100644 --- a/src/vs/editor/editor.all.ts +++ b/src/vs/editor/editor.all.ts @@ -22,8 +22,8 @@ import 'vs/editor/contrib/find/findController'; import 'vs/editor/contrib/folding/folding'; import 'vs/editor/contrib/fontZoom/fontZoom'; import 'vs/editor/contrib/format/formatActions'; -import 'vs/editor/contrib/goToDefinition/goToDefinitionCommands'; -import 'vs/editor/contrib/goToDefinition/goToDefinitionMouse'; +import 'vs/editor/contrib/gotoSymbol/goToCommands'; +import 'vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition'; import 'vs/editor/contrib/gotoError/gotoError'; import 'vs/editor/contrib/hover/hover'; import 'vs/editor/contrib/inPlaceReplace/inPlaceReplace'; @@ -31,7 +31,6 @@ import 'vs/editor/contrib/linesOperations/linesOperations'; import 'vs/editor/contrib/links/links'; import 'vs/editor/contrib/multicursor/multicursor'; import 'vs/editor/contrib/parameterHints/parameterHints'; -import 'vs/editor/contrib/referenceSearch/referenceSearch'; import 'vs/editor/contrib/rename/rename'; import 'vs/editor/contrib/smartSelect/smartSelect'; import 'vs/editor/contrib/snippet/snippetController2'; diff --git a/src/vs/editor/editor.api.ts b/src/vs/editor/editor.api.ts index f5abe0c59212..9379fc3d34c5 100644 --- a/src/vs/editor/editor.api.ts +++ b/src/vs/editor/editor.api.ts @@ -13,7 +13,7 @@ const global: any = self; // Set defaults for standalone editor (EditorOptions.wrappingIndent).defaultValue = WrappingIndent.None; (EditorOptions.glyphMargin).defaultValue = false; -(EditorOptions.autoIndent).defaultValue = false; +(EditorOptions.autoIndent).defaultValue = 'advanced'; (EditorOptions.overviewRulerLanes).defaultValue = 2; const api = createMonacoBaseAPI(); diff --git a/src/vs/editor/standalone/browser/inspectTokens/inspectTokens.css b/src/vs/editor/standalone/browser/inspectTokens/inspectTokens.css index a7bc05744da3..8c91b0f9541f 100644 --- a/src/vs/editor/standalone/browser/inspectTokens/inspectTokens.css +++ b/src/vs/editor/standalone/browser/inspectTokens/inspectTokens.css @@ -5,12 +5,9 @@ .monaco-editor .tokens-inspect-widget { z-index: 50; + user-select: text; -webkit-user-select: text; -ms-user-select: text; - -khtml-user-select: text; - -moz-user-select: text; - -o-user-select: text; - user-select: text; padding: 10px; } diff --git a/src/vs/editor/standalone/browser/inspectTokens/inspectTokens.ts b/src/vs/editor/standalone/browser/inspectTokens/inspectTokens.ts index 3b75c9a16b6b..71ab88a4c18b 100644 --- a/src/vs/editor/standalone/browser/inspectTokens/inspectTokens.ts +++ b/src/vs/editor/standalone/browser/inspectTokens/inspectTokens.ts @@ -18,7 +18,7 @@ import { FontStyle, IState, ITokenizationSupport, LanguageIdentifier, StandardTo import { NULL_STATE, nullTokenize, nullTokenize2 } from 'vs/editor/common/modes/nullMode'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IStandaloneThemeService } from 'vs/editor/standalone/common/standaloneThemeService'; -import { editorHoverBackground, editorHoverBorder } from 'vs/platform/theme/common/colorRegistry'; +import { editorHoverBackground, editorHoverBorder, editorHoverForeground } from 'vs/platform/theme/common/colorRegistry'; import { HIGH_CONTRAST, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { InspectTokensNLS } from 'vs/editor/common/standaloneStrings'; @@ -335,4 +335,8 @@ registerThemingParticipant((theme, collector) => { if (background) { collector.addRule(`.monaco-editor .tokens-inspect-widget { background-color: ${background}; }`); } + const foreground = theme.getColor(editorHoverForeground); + if (foreground) { + collector.addRule(`.monaco-editor .tokens-inspect-widget { color: ${foreground}; }`); + } }); diff --git a/src/vs/editor/standalone/browser/quickOpen/quickCommand.ts b/src/vs/editor/standalone/browser/quickOpen/quickCommand.ts index 8f1f0fbce704..2d6254d0c2ea 100644 --- a/src/vs/editor/standalone/browser/quickOpen/quickCommand.ts +++ b/src/vs/editor/standalone/browser/quickOpen/quickCommand.ts @@ -88,7 +88,7 @@ export class QuickCommandAction extends BaseEditorQuickOpenAction { primary: (browser.isIE ? KeyMod.Alt | KeyCode.F1 : KeyCode.F1), weight: KeybindingWeight.EditorContrib }, - menuOpts: { + contextMenuOpts: { group: 'z_commands', order: 1 } diff --git a/src/vs/editor/standalone/browser/quickOpen/quickOutline.ts b/src/vs/editor/standalone/browser/quickOpen/quickOutline.ts index 580e0ce76658..f7363952ac4c 100644 --- a/src/vs/editor/standalone/browser/quickOpen/quickOutline.ts +++ b/src/vs/editor/standalone/browser/quickOpen/quickOutline.ts @@ -121,7 +121,7 @@ export class QuickOutlineAction extends BaseEditorQuickOpenAction { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_O, weight: KeybindingWeight.EditorContrib }, - menuOpts: { + contextMenuOpts: { group: 'navigation', order: 3 } diff --git a/src/vs/editor/standalone/browser/referenceSearch/standaloneReferenceSearch.ts b/src/vs/editor/standalone/browser/referenceSearch/standaloneReferenceSearch.ts index df140715d405..c49100316296 100644 --- a/src/vs/editor/standalone/browser/referenceSearch/standaloneReferenceSearch.ts +++ b/src/vs/editor/standalone/browser/referenceSearch/standaloneReferenceSearch.ts @@ -6,7 +6,7 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { ReferencesController } from 'vs/editor/contrib/referenceSearch/referencesController'; +import { ReferencesController } from 'vs/editor/contrib/gotoSymbol/peek/referencesController'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; diff --git a/src/vs/editor/standalone/browser/simpleServices.ts b/src/vs/editor/standalone/browser/simpleServices.ts index 7dbe8f57c587..347747d31132 100644 --- a/src/vs/editor/standalone/browser/simpleServices.ts +++ b/src/vs/editor/standalone/browser/simpleServices.ts @@ -31,7 +31,7 @@ import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/commo import { IConfirmation, IConfirmationResult, IDialogOptions, IDialogService, IShowResult } from 'vs/platform/dialogs/common/dialogs'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { AbstractKeybindingService } from 'vs/platform/keybinding/common/abstractKeybindingService'; -import { IKeybindingEvent, IKeyboardEvent, KeybindingSource } from 'vs/platform/keybinding/common/keybinding'; +import { IKeybindingEvent, IKeyboardEvent, KeybindingSource, KeybindingsSchemaContribution } from 'vs/platform/keybinding/common/keybinding'; import { KeybindingResolver } from 'vs/platform/keybinding/common/keybindingResolver'; import { IKeybindingItem, KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; @@ -99,8 +99,13 @@ function withTypedEditor(widget: editorCommon.IEditor, codeEditorCallback: (e export class SimpleEditorModelResolverService implements ITextModelService { public _serviceBrand: undefined; + private readonly modelService: IModelService | undefined; private editor?: editorCommon.IEditor; + constructor(modelService: IModelService | undefined) { + this.modelService = modelService; + } + public setEditor(editor: editorCommon.IEditor): void { this.editor = editor; } @@ -132,7 +137,7 @@ export class SimpleEditorModelResolverService implements ITextModelService { } private findModel(editor: ICodeEditor, resource: URI): ITextModel | null { - let model = editor.getModel(); + let model = this.modelService ? this.modelService.getModel(resource) : editor.getModel(); if (model && model.uri.toString() !== resource.toString()) { return null; } @@ -409,6 +414,10 @@ export class StandaloneKeybindingService extends AbstractKeybindingService { public _dumpDebugInfoJSON(): string { return ''; } + + public registerSchemaContribution(contribution: KeybindingsSchemaContribution): void { + // noop + } } function isConfigurationOverrides(thing: any): thing is IConfigurationOverrides { @@ -648,7 +657,9 @@ export class SimpleBulkEditService implements IBulkEditService { let totalEdits = 0; let totalFiles = 0; edits.forEach((edits, model) => { - model.applyEdits(edits.map(edit => EditOperation.replaceMove(Range.lift(edit.range), edit.text))); + model.pushStackElement(); + model.pushEditOperations([], edits.map((e) => EditOperation.replaceMove(Range.lift(e.range), e.text)), () => []); + model.pushStackElement(); totalFiles += 1; totalEdits += edits.length; }); diff --git a/src/vs/editor/standalone/browser/standaloneEditor.ts b/src/vs/editor/standalone/browser/standaloneEditor.ts index 41faa97e6216..34b4c74f6607 100644 --- a/src/vs/editor/standalone/browser/standaloneEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneEditor.ts @@ -9,7 +9,7 @@ import { URI } from 'vs/base/common/uri'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { OpenerService } from 'vs/editor/browser/services/openerService'; -import { DiffNavigator } from 'vs/editor/browser/widget/diffNavigator'; +import { DiffNavigator, IDiffNavigator } from 'vs/editor/browser/widget/diffNavigator'; import { ConfigurationChangedEvent } from 'vs/editor/common/config/editorOptions'; import { BareFontInfo, FontInfo } from 'vs/editor/common/config/fontInfo'; import { Token } from 'vs/editor/common/core/token'; @@ -47,7 +47,7 @@ function withAllStandaloneServices(domElement: H let simpleEditorModelResolverService: SimpleEditorModelResolverService | null = null; if (!services.has(ITextModelService)) { - simpleEditorModelResolverService = new SimpleEditorModelResolverService(); + simpleEditorModelResolverService = new SimpleEditorModelResolverService(StaticServices.modelService.get()); services.set(ITextModelService, simpleEditorModelResolverService); } @@ -127,13 +127,6 @@ export function createDiffEditor(domElement: HTMLElement, options?: IDiffEditorC }); } -export interface IDiffNavigator { - canNavigate(): boolean; - next(): void; - previous(): void; - dispose(): void; -} - export interface IDiffNavigatorOptions { readonly followsCaret?: boolean; readonly ignoreCharChanges?: boolean; diff --git a/src/vs/editor/standalone/browser/standaloneServices.ts b/src/vs/editor/standalone/browser/standaloneServices.ts index 3e9486a6e3f1..f1767ea51f66 100644 --- a/src/vs/editor/standalone/browser/standaloneServices.ts +++ b/src/vs/editor/standalone/browser/standaloneServices.ts @@ -89,7 +89,7 @@ export module StaticServices { let _all: LazyStaticService[] = []; - function define(serviceId: ServiceIdentifier, factory: (overrides: IEditorOverrideServices) => T): LazyStaticService { + function define(serviceId: ServiceIdentifier, factory: (overrides: IEditorOverrideServices | undefined) => T): LazyStaticService { let r = new LazyStaticService(serviceId, factory); _all.push(r); return r; @@ -144,11 +144,11 @@ export module StaticServices { export const modeService = define(IModeService, (o) => new ModeServiceImpl()); - export const modelService = define(IModelService, (o) => new ModelServiceImpl(configurationService.get(o), resourcePropertiesService.get(o))); + export const standaloneThemeService = define(IStandaloneThemeService, () => new StandaloneThemeServiceImpl()); - export const markerDecorationsService = define(IMarkerDecorationsService, (o) => new MarkerDecorationsService(modelService.get(o), markerService.get(o))); + export const modelService = define(IModelService, (o) => new ModelServiceImpl(configurationService.get(o), resourcePropertiesService.get(o), standaloneThemeService.get(o))); - export const standaloneThemeService = define(IStandaloneThemeService, () => new StandaloneThemeServiceImpl()); + export const markerDecorationsService = define(IMarkerDecorationsService, (o) => new MarkerDecorationsService(modelService.get(o), markerService.get(o))); export const codeEditorService = define(ICodeEditorService, (o) => new StandaloneCodeEditorServiceImpl(standaloneThemeService.get(o))); @@ -194,7 +194,7 @@ export class DynamicStandaloneServices extends Disposable { ensure(IAccessibilityService, () => new BrowserAccessibilityService(contextKeyService, configurationService)); - ensure(IListService, () => new ListService(contextKeyService)); + ensure(IListService, () => new ListService(themeService)); let commandService = ensure(ICommandService, () => new StandaloneCommandService(this._instantiationService)); diff --git a/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts b/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts index 0cc58b663a06..2f184b926fba 100644 --- a/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts +++ b/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts @@ -23,6 +23,7 @@ const colorRegistry = Registry.as(Extensions.ColorContribution); const themingRegistry = Registry.as(ThemingExtensions.ThemingContribution); class StandaloneTheme implements IStandaloneTheme { + public readonly id: string; public readonly themeName: string; @@ -128,6 +129,14 @@ class StandaloneTheme implements IStandaloneTheme { } return this._tokenTheme; } + + public getTokenStyleMetadata(type: string, modifiers: string[]): number | undefined { + return undefined; + } + + public get tokenColorMap(): string[] { + return []; + } } function isBuiltinTheme(themeName: string): themeName is BuiltinTheme { diff --git a/src/vs/editor/standalone/common/monarch/monarchLexer.ts b/src/vs/editor/standalone/common/monarch/monarchLexer.ts index 2aaad1bf06e8..ec4c87d1e595 100644 --- a/src/vs/editor/standalone/common/monarch/monarchLexer.ts +++ b/src/vs/editor/standalone/common/monarch/monarchLexer.ts @@ -421,24 +421,22 @@ export class MonarchTokenizer implements modes.ITokenizationSupport { public getLoadStatus(): ILoadStatus { let promises: Thenable[] = []; for (let nestedModeId in this._embeddedModes) { - if (this._embeddedModes.hasOwnProperty(nestedModeId)) { - const tokenizationSupport = modes.TokenizationRegistry.get(nestedModeId); - if (tokenizationSupport) { - // The nested mode is already loaded - if (tokenizationSupport instanceof MonarchTokenizer) { - const nestedModeStatus = tokenizationSupport.getLoadStatus(); - if (nestedModeStatus.loaded === false) { - promises.push(nestedModeStatus.promise); - } + const tokenizationSupport = modes.TokenizationRegistry.get(nestedModeId); + if (tokenizationSupport) { + // The nested mode is already loaded + if (tokenizationSupport instanceof MonarchTokenizer) { + const nestedModeStatus = tokenizationSupport.getLoadStatus(); + if (nestedModeStatus.loaded === false) { + promises.push(nestedModeStatus.promise); } - continue; } + continue; + } - const tokenizationSupportPromise = modes.TokenizationRegistry.getPromise(nestedModeId); - if (tokenizationSupportPromise) { - // The nested mode is in the process of being loaded - promises.push(tokenizationSupportPromise); - } + const tokenizationSupportPromise = modes.TokenizationRegistry.getPromise(nestedModeId); + if (tokenizationSupportPromise) { + // The nested mode is in the process of being loaded + promises.push(tokenizationSupportPromise); } } diff --git a/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts b/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts index 6beba732e7cd..f054a393a7ed 100644 --- a/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts +++ b/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts @@ -54,9 +54,16 @@ suite('TokenizationSupport2Adapter', () => { defines: (color: ColorIdentifier): boolean => { throw new Error('Not implemented'); - } + }, + + getTokenStyleMetadata: (type: string, modifiers: string[]): number | undefined => { + return undefined; + }, + + tokenColorMap: [] }; } + public getIconTheme(): IIconTheme { return { hasFileIcons: false, diff --git a/src/vs/editor/test/browser/commands/shiftCommand.test.ts b/src/vs/editor/test/browser/commands/shiftCommand.test.ts index 265786c631bc..b709eb0a7567 100644 --- a/src/vs/editor/test/browser/commands/shiftCommand.test.ts +++ b/src/vs/editor/test/browser/commands/shiftCommand.test.ts @@ -14,6 +14,7 @@ import { getEditOperation, testCommand } from 'vs/editor/test/browser/testComman import { withEditorModel } from 'vs/editor/test/common/editorTestUtils'; import { MockMode } from 'vs/editor/test/common/mocks/mockMode'; import { javascriptOnEnterRules } from 'vs/editor/test/common/modes/supports/javascriptOnEnterRules'; +import { EditorAutoIndentStrategy } from 'vs/editor/common/config/editorOptions'; /** * Create single edit operation @@ -50,6 +51,7 @@ function testShiftCommand(lines: string[], languageIdentifier: LanguageIdentifie indentSize: 4, insertSpaces: false, useTabStops: useTabStops, + autoIndent: EditorAutoIndentStrategy.Full, }), expectedLines, expectedSelection); } @@ -60,6 +62,7 @@ function testUnshiftCommand(lines: string[], languageIdentifier: LanguageIdentif indentSize: 4, insertSpaces: false, useTabStops: useTabStops, + autoIndent: EditorAutoIndentStrategy.Full, }), expectedLines, expectedSelection); } @@ -672,7 +675,8 @@ suite('Editor Commands - ShiftCommand', () => { tabSize: 4, indentSize: 4, insertSpaces: true, - useTabStops: false + useTabStops: false, + autoIndent: EditorAutoIndentStrategy.Full, }), [ ' Written | Numeric', @@ -717,7 +721,8 @@ suite('Editor Commands - ShiftCommand', () => { tabSize: 4, indentSize: 4, insertSpaces: true, - useTabStops: false + useTabStops: false, + autoIndent: EditorAutoIndentStrategy.Full, }), [ ' Written | Numeric', @@ -762,7 +767,8 @@ suite('Editor Commands - ShiftCommand', () => { tabSize: 4, indentSize: 4, insertSpaces: false, - useTabStops: false + useTabStops: false, + autoIndent: EditorAutoIndentStrategy.Full, }), [ ' Written | Numeric', @@ -807,7 +813,8 @@ suite('Editor Commands - ShiftCommand', () => { tabSize: 4, indentSize: 4, insertSpaces: true, - useTabStops: false + useTabStops: false, + autoIndent: EditorAutoIndentStrategy.Full, }), [ ' Written | Numeric', @@ -841,7 +848,8 @@ suite('Editor Commands - ShiftCommand', () => { tabSize: 4, indentSize: 4, insertSpaces: false, - useTabStops: true + useTabStops: true, + autoIndent: EditorAutoIndentStrategy.Full, }), [ '\tHello world!', @@ -951,7 +959,8 @@ suite('Editor Commands - ShiftCommand', () => { tabSize: tabSize, indentSize: indentSize, insertSpaces: insertSpaces, - useTabStops: true + useTabStops: true, + autoIndent: EditorAutoIndentStrategy.Full, }); let actual = getEditOperation(model, op); assert.deepEqual(actual, expected); @@ -965,7 +974,8 @@ suite('Editor Commands - ShiftCommand', () => { tabSize: tabSize, indentSize: indentSize, insertSpaces: insertSpaces, - useTabStops: true + useTabStops: true, + autoIndent: EditorAutoIndentStrategy.Full, }); let actual = getEditOperation(model, op); assert.deepEqual(actual, expected); diff --git a/src/vs/editor/test/browser/controller/cursor.test.ts b/src/vs/editor/test/browser/controller/cursor.test.ts index 4aff37d49cb2..7dc1ba121317 100644 --- a/src/vs/editor/test/browser/controller/cursor.test.ts +++ b/src/vs/editor/test/browser/controller/cursor.test.ts @@ -726,7 +726,7 @@ suite('Editor Controller - Cursor', () => { }); }); - test('combining marks', () => { + test('grapheme breaking', () => { withTestCodeEditor([ 'abcabc', 'ãããããã', @@ -2834,7 +2834,7 @@ suite('Editor Controller - Indentation Rules', () => { ], languageIdentifier: mode.getLanguageIdentifier(), modelOpts: { insertSpaces: false }, - editorOpts: { autoIndent: true } + editorOpts: { autoIndent: 'full' } }, (model, cursor) => { moveTo(cursor, 1, 12, false); assertCursor(cursor, new Selection(1, 12, 1, 12)); @@ -2857,7 +2857,7 @@ suite('Editor Controller - Indentation Rules', () => { '\t' ], languageIdentifier: mode.getLanguageIdentifier(), - editorOpts: { autoIndent: true } + editorOpts: { autoIndent: 'full' } }, (model, cursor) => { moveTo(cursor, 2, 2, false); assertCursor(cursor, new Selection(2, 2, 2, 2)); @@ -2876,7 +2876,7 @@ suite('Editor Controller - Indentation Rules', () => { ], languageIdentifier: mode.getLanguageIdentifier(), modelOpts: { insertSpaces: false }, - editorOpts: { autoIndent: true } + editorOpts: { autoIndent: 'full' } }, (model, cursor) => { moveTo(cursor, 2, 15, false); assertCursor(cursor, new Selection(2, 15, 2, 15)); @@ -2896,7 +2896,7 @@ suite('Editor Controller - Indentation Rules', () => { ], languageIdentifier: mode.getLanguageIdentifier(), modelOpts: { insertSpaces: false }, - editorOpts: { autoIndent: true } + editorOpts: { autoIndent: 'full' } }, (model, cursor) => { moveTo(cursor, 2, 14, false); assertCursor(cursor, new Selection(2, 14, 2, 14)); @@ -2924,7 +2924,7 @@ suite('Editor Controller - Indentation Rules', () => { mode.getLanguageIdentifier() ); - withTestCodeEditor(null, { model: model, autoIndent: true }, (editor, cursor) => { + withTestCodeEditor(null, { model: model, autoIndent: 'full' }, (editor, cursor) => { moveTo(cursor, 2, 11, false); assertCursor(cursor, new Selection(2, 11, 2, 11)); @@ -2948,7 +2948,7 @@ suite('Editor Controller - Indentation Rules', () => { '}}' ], languageIdentifier: mode.getLanguageIdentifier(), - editorOpts: { autoIndent: true } + editorOpts: { autoIndent: 'full' } }, (model, cursor) => { moveTo(cursor, 3, 13, false); assertCursor(cursor, new Selection(3, 13, 3, 13)); @@ -3084,7 +3084,7 @@ suite('Editor Controller - Indentation Rules', () => { ], languageIdentifier: mode.getLanguageIdentifier(), modelOpts: { insertSpaces: false }, - editorOpts: { autoIndent: true } + editorOpts: { autoIndent: 'full' } }, (model, cursor) => { moveTo(cursor, 5, 4, false); assertCursor(cursor, new Selection(5, 4, 5, 4)); @@ -3554,7 +3554,7 @@ suite('Editor Controller - Indentation Rules', () => { rubyMode.getLanguageIdentifier() ); - withTestCodeEditor(null, { model: model, autoIndent: true }, (editor, cursor) => { + withTestCodeEditor(null, { model: model, autoIndent: 'full' }, (editor, cursor) => { moveTo(cursor, 4, 7, false); assertCursor(cursor, new Selection(4, 7, 4, 7)); @@ -3615,7 +3615,7 @@ suite('Editor Controller - Indentation Rules', () => { '\t\t' ], languageIdentifier: mode.getLanguageIdentifier(), - editorOpts: { autoIndent: true } + editorOpts: { autoIndent: 'full' } }, (model, cursor) => { moveTo(cursor, 3, 3, false); assertCursor(cursor, new Selection(3, 3, 3, 3)); @@ -3664,7 +3664,7 @@ suite('Editor Controller - Indentation Rules', () => { mode.getLanguageIdentifier() ); - withTestCodeEditor(null, { model: model, autoIndent: false }, (editor, cursor) => { + withTestCodeEditor(null, { model: model, autoIndent: 'advanced' }, (editor, cursor) => { moveTo(cursor, 7, 6, false); assertCursor(cursor, new Selection(7, 6, 7, 6)); @@ -3728,7 +3728,7 @@ suite('Editor Controller - Indentation Rules', () => { mode.getLanguageIdentifier() ); - withTestCodeEditor(null, { model: model, autoIndent: false }, (editor, cursor) => { + withTestCodeEditor(null, { model: model, autoIndent: 'advanced' }, (editor, cursor) => { moveTo(cursor, 8, 1, false); assertCursor(cursor, new Selection(8, 1, 8, 1)); @@ -3791,7 +3791,7 @@ suite('Editor Controller - Indentation Rules', () => { mode.getLanguageIdentifier() ); - withTestCodeEditor(null, { model: model, autoIndent: true }, (editor, cursor) => { + withTestCodeEditor(null, { model: model, autoIndent: 'full' }, (editor, cursor) => { moveTo(cursor, 3, 19, false); assertCursor(cursor, new Selection(3, 19, 3, 19)); @@ -4936,6 +4936,35 @@ suite('autoClosingPairs', () => { mode.dispose(); }); + test('issue #84998: Overtyping Brackets doesn\'t work after backslash', () => { + let mode = new AutoClosingMode(); + usingCursor({ + text: [ + '' + ], + languageIdentifier: mode.getLanguageIdentifier() + }, (model, cursor) => { + + cursor.setSelections('test', [new Selection(1, 1, 1, 1)]); + + cursorCommand(cursor, H.Type, { text: '\\' }, 'keyboard'); + assert.equal(model.getValue(), '\\'); + + cursorCommand(cursor, H.Type, { text: '(' }, 'keyboard'); + assert.equal(model.getValue(), '\\()'); + + cursorCommand(cursor, H.Type, { text: 'abc' }, 'keyboard'); + assert.equal(model.getValue(), '\\(abc)'); + + cursorCommand(cursor, H.Type, { text: '\\' }, 'keyboard'); + assert.equal(model.getValue(), '\\(abc\\)'); + + cursorCommand(cursor, H.Type, { text: ')' }, 'keyboard'); + assert.equal(model.getValue(), '\\(abc\\)'); + }); + mode.dispose(); + }); + test('issue #2773: Accents (´`¨^, others?) are inserted in the wrong position (Mac)', () => { let mode = new AutoClosingMode(); usingCursor({ diff --git a/src/vs/editor/test/browser/controller/imeTester.ts b/src/vs/editor/test/browser/controller/imeTester.ts index 67d317cdc528..43b3620abe06 100644 --- a/src/vs/editor/test/browser/controller/imeTester.ts +++ b/src/vs/editor/test/browser/controller/imeTester.ts @@ -103,7 +103,7 @@ function doCreateTest(description: string, inputStr: string, expectedStr: string const selection = new Range(1, 1 + cursorOffset, 1, 1 + cursorOffset + cursorLength); - return PagedScreenReaderStrategy.fromEditorSelection(currentState, model, selection, true); + return PagedScreenReaderStrategy.fromEditorSelection(currentState, model, selection, 10, true); }, deduceModelPosition: (viewAnchorPosition: Position, deltaOffset: number, lineFeedCnt: number): Position => { return null!; diff --git a/src/vs/editor/test/browser/controller/textAreaState.test.ts b/src/vs/editor/test/browser/controller/textAreaState.test.ts index f8a8ec061fec..2029e8fe7f04 100644 --- a/src/vs/editor/test/browser/controller/textAreaState.test.ts +++ b/src/vs/editor/test/browser/controller/textAreaState.test.ts @@ -535,7 +535,7 @@ suite('TextAreaState', () => { function testPagedScreenReaderStrategy(lines: string[], selection: Selection, expected: TextAreaState): void { const model = TextModel.createFromString(lines.join('\n')); - const actual = PagedScreenReaderStrategy.fromEditorSelection(TextAreaState.EMPTY, model, selection, true); + const actual = PagedScreenReaderStrategy.fromEditorSelection(TextAreaState.EMPTY, model, selection, 10, true); assert.ok(equalsTextAreaState(actual, expected)); model.dispose(); } diff --git a/src/vs/editor/test/browser/services/openerService.test.ts b/src/vs/editor/test/browser/services/openerService.test.ts index c16eb4898bc8..332806420d14 100644 --- a/src/vs/editor/test/browser/services/openerService.test.ts +++ b/src/vs/editor/test/browser/services/openerService.test.ts @@ -8,6 +8,7 @@ import { URI } from 'vs/base/common/uri'; import { OpenerService } from 'vs/editor/browser/services/openerService'; import { TestCodeEditorService } from 'vs/editor/test/browser/editorTestServices'; import { CommandsRegistry, ICommandService, NullCommandService } from 'vs/platform/commands/common/commands'; +import { matchesScheme } from 'vs/platform/opener/common/opener'; suite('OpenerService', function () { const editorService = new TestCodeEditorService(); @@ -28,27 +29,27 @@ suite('OpenerService', function () { lastCommand = undefined; }); - test('delegate to editorService, scheme:///fff', function () { + test('delegate to editorService, scheme:///fff', async function () { const openerService = new OpenerService(editorService, NullCommandService); - openerService.open(URI.parse('another:///somepath')); + await openerService.open(URI.parse('another:///somepath')); assert.equal(editorService.lastInput!.options!.selection, undefined); }); - test('delegate to editorService, scheme:///fff#L123', function () { + test('delegate to editorService, scheme:///fff#L123', async function () { const openerService = new OpenerService(editorService, NullCommandService); - openerService.open(URI.parse('file:///somepath#L23')); + await openerService.open(URI.parse('file:///somepath#L23')); assert.equal(editorService.lastInput!.options!.selection!.startLineNumber, 23); assert.equal(editorService.lastInput!.options!.selection!.startColumn, 1); assert.equal(editorService.lastInput!.options!.selection!.endLineNumber, undefined); assert.equal(editorService.lastInput!.options!.selection!.endColumn, undefined); assert.equal(editorService.lastInput!.resource.fragment, ''); - openerService.open(URI.parse('another:///somepath#L23')); + await openerService.open(URI.parse('another:///somepath#L23')); assert.equal(editorService.lastInput!.options!.selection!.startLineNumber, 23); assert.equal(editorService.lastInput!.options!.selection!.startColumn, 1); - openerService.open(URI.parse('another:///somepath#L23,45')); + await openerService.open(URI.parse('another:///somepath#L23,45')); assert.equal(editorService.lastInput!.options!.selection!.startLineNumber, 23); assert.equal(editorService.lastInput!.options!.selection!.startColumn, 45); assert.equal(editorService.lastInput!.options!.selection!.endLineNumber, undefined); @@ -56,17 +57,17 @@ suite('OpenerService', function () { assert.equal(editorService.lastInput!.resource.fragment, ''); }); - test('delegate to editorService, scheme:///fff#123,123', function () { + test('delegate to editorService, scheme:///fff#123,123', async function () { const openerService = new OpenerService(editorService, NullCommandService); - openerService.open(URI.parse('file:///somepath#23')); + await openerService.open(URI.parse('file:///somepath#23')); assert.equal(editorService.lastInput!.options!.selection!.startLineNumber, 23); assert.equal(editorService.lastInput!.options!.selection!.startColumn, 1); assert.equal(editorService.lastInput!.options!.selection!.endLineNumber, undefined); assert.equal(editorService.lastInput!.options!.selection!.endColumn, undefined); assert.equal(editorService.lastInput!.resource.fragment, ''); - openerService.open(URI.parse('file:///somepath#23,45')); + await openerService.open(URI.parse('file:///somepath#23,45')); assert.equal(editorService.lastInput!.options!.selection!.startLineNumber, 23); assert.equal(editorService.lastInput!.options!.selection!.startColumn, 45); assert.equal(editorService.lastInput!.options!.selection!.endLineNumber, undefined); @@ -74,22 +75,22 @@ suite('OpenerService', function () { assert.equal(editorService.lastInput!.resource.fragment, ''); }); - test('delegate to commandsService, command:someid', function () { + test('delegate to commandsService, command:someid', async function () { const openerService = new OpenerService(editorService, commandService); const id = `aCommand${Math.random()}`; CommandsRegistry.registerCommand(id, function () { }); - openerService.open(URI.parse('command:' + id)); + await openerService.open(URI.parse('command:' + id)); assert.equal(lastCommand!.id, id); assert.equal(lastCommand!.args.length, 0); - openerService.open(URI.parse('command:' + id).with({ query: '123' })); + await openerService.open(URI.parse('command:' + id).with({ query: '123' })); assert.equal(lastCommand!.id, id); assert.equal(lastCommand!.args.length, 1); assert.equal(lastCommand!.args[0], '123'); - openerService.open(URI.parse('command:' + id).with({ query: JSON.stringify([12, true]) })); + await openerService.open(URI.parse('command:' + id).with({ query: JSON.stringify([12, true]) })); assert.equal(lastCommand!.id, id); assert.equal(lastCommand!.args.length, 2); assert.equal(lastCommand!.args[0], 12); @@ -199,4 +200,18 @@ suite('OpenerService', function () { assert.equal(v1, 2); assert.equal(v2, 0); }); + + test('matchesScheme', function () { + assert.ok(matchesScheme('https://microsoft.com', 'https')); + assert.ok(matchesScheme('http://microsoft.com', 'http')); + assert.ok(matchesScheme('hTTPs://microsoft.com', 'https')); + assert.ok(matchesScheme('httP://microsoft.com', 'http')); + assert.ok(matchesScheme(URI.parse('https://microsoft.com'), 'https')); + assert.ok(matchesScheme(URI.parse('http://microsoft.com'), 'http')); + assert.ok(matchesScheme(URI.parse('hTTPs://microsoft.com'), 'https')); + assert.ok(matchesScheme(URI.parse('httP://microsoft.com'), 'http')); + assert.ok(!matchesScheme(URI.parse('https://microsoft.com'), 'http')); + assert.ok(!matchesScheme(URI.parse('htt://microsoft.com'), 'http')); + assert.ok(!matchesScheme(URI.parse('z://microsoft.com'), 'http')); + }); }); diff --git a/src/vs/editor/test/browser/view/minimapCharRenderer.test.ts b/src/vs/editor/test/browser/view/minimapCharRenderer.test.ts index cba210f9c9b5..7728e2e48399 100644 --- a/src/vs/editor/test/browser/view/minimapCharRenderer.test.ts +++ b/src/vs/editor/test/browser/view/minimapCharRenderer.test.ts @@ -86,7 +86,7 @@ suite('MinimapCharRenderer', () => { imageData.data[4 * i + 2] = background.b; imageData.data[4 * i + 3] = 255; } - renderer.renderChar(imageData, 0, 0, 'd'.charCodeAt(0), color, background, false); + renderer.renderChar(imageData, 0, 0, 'd'.charCodeAt(0), color, background, 2, false); let actual: number[] = []; for (let i = 0; i < imageData.data.length; i++) { @@ -116,7 +116,7 @@ suite('MinimapCharRenderer', () => { imageData.data[4 * i + 3] = 255; } - renderer.renderChar(imageData, 0, 0, 'd'.charCodeAt(0), color, background, false); + renderer.renderChar(imageData, 0, 0, 'd'.charCodeAt(0), color, background, 1, false); let actual: number[] = []; for (let i = 0; i < imageData.data.length; i++) { diff --git a/src/vs/editor/test/browser/view/minimapFontCreator.html b/src/vs/editor/test/browser/view/minimapFontCreator.html deleted file mode 100644 index 9ddd334797aa..000000000000 --- a/src/vs/editor/test/browser/view/minimapFontCreator.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/vs/editor/test/browser/view/minimapFontCreator.ts b/src/vs/editor/test/browser/view/minimapFontCreator.ts deleted file mode 100644 index b52adb977f5a..000000000000 --- a/src/vs/editor/test/browser/view/minimapFontCreator.ts +++ /dev/null @@ -1,119 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { RGBA8 } from 'vs/editor/common/core/rgba'; -import { MinimapCharRenderer } from 'vs/editor/browser/viewParts/minimap/minimapCharRenderer'; -import { Constants } from 'vs/editor/browser/viewParts/minimap/minimapCharSheet'; -import { MinimapCharRendererFactory } from 'vs/editor/browser/viewParts/minimap/minimapCharRendererFactory'; - -let sampleData = MinimapCharRendererFactory.createSampleData('monospace'); -let minimapCharRenderer1x = MinimapCharRendererFactory.createFromSampleData(sampleData.data, 1); -let minimapCharRenderer2x = MinimapCharRendererFactory.createFromSampleData(sampleData.data, 2); -let minimapCharRenderer4x = MinimapCharRendererFactory.createFromSampleData(sampleData.data, 4); -let minimapCharRenderer6x = MinimapCharRendererFactory.createFromSampleData(sampleData.data, 6); - -renderImageData(sampleData, 10, 100); -renderMinimapCharRenderer(minimapCharRenderer1x, 400, 1); -renderMinimapCharRenderer(minimapCharRenderer2x, 500, 2); -renderMinimapCharRenderer(minimapCharRenderer4x, 600, 4); -renderMinimapCharRenderer(minimapCharRenderer6x, 750, 8); - -function createFakeImageData(width: number, height: number): ImageData { - return { - width: width, - height: height, - data: new Uint8ClampedArray(width * height * Constants.RGBA_CHANNELS_CNT) - }; -} - -function renderMinimapCharRenderer(minimapCharRenderer: MinimapCharRenderer, y: number, scale: number): void { - let background = new RGBA8(0, 0, 0, 255); - let color = new RGBA8(255, 255, 255, 255); - - { - let x2 = createFakeImageData( - Constants.BASE_CHAR_WIDTH * scale * Constants.CHAR_COUNT, - Constants.BASE_CHAR_HEIGHT * scale - ); - // set the background color - for (let i = 0, len = x2.data.length / 4; i < len; i++) { - x2.data[4 * i + 0] = background.r; - x2.data[4 * i + 1] = background.g; - x2.data[4 * i + 2] = background.b; - x2.data[4 * i + 3] = 255; - } - let dx = 0; - for (let chCode = Constants.START_CH_CODE; chCode <= Constants.END_CH_CODE; chCode++) { - minimapCharRenderer.renderChar(x2, dx, 0, chCode, color, background, false); - dx += Constants.BASE_CHAR_WIDTH * scale; - } - renderImageData(x2, 10, y); - } -} - -(function () { - let r = 'let x2Data = [', - offset = 0; - for (let charIndex = 0; charIndex < Constants.CHAR_COUNT; charIndex++) { - let charCode = charIndex + Constants.START_CH_CODE; - r += '\n\n// ' + String.fromCharCode(charCode); - - for (let i = 0; i < Constants.BASE_CHAR_HEIGHT * 2; i++) { - if (i % 2 === 0) { - r += '\n'; - } - r += (minimapCharRenderer2x as any).charDataNormal[offset] + ','; - offset++; - } - } - r += '\n\n]'; - console.log(r); -})(); - -(function () { - let r = 'let x1Data = [', - offset = 0; - for (let charIndex = 0; charIndex < Constants.CHAR_COUNT; charIndex++) { - let charCode = charIndex + Constants.START_CH_CODE; - r += '\n\n// ' + String.fromCharCode(charCode); - - for (let i = 0; i < Constants.BASE_CHAR_HEIGHT * Constants.BASE_CHAR_WIDTH; i++) { - r += '\n'; - r += (minimapCharRenderer1x as any).charDataNormal[offset] + ','; - offset++; - } - } - r += '\n\n]'; - console.log(r); -})(); - -function renderImageData(imageData: ImageData, left: number, top: number): void { - let output = ''; - let offset = 0; - let PX_SIZE = 15; - for (let i = 0; i < imageData.height; i++) { - for (let j = 0; j < imageData.width; j++) { - let R = imageData.data[offset]; - let G = imageData.data[offset + 1]; - let B = imageData.data[offset + 2]; - let A = imageData.data[offset + 3]; - offset += 4; - - output += `
`; - } - } - - let domNode = document.createElement('div'); - domNode.style.position = 'absolute'; - domNode.style.top = top + 'px'; - domNode.style.left = left + 'px'; - domNode.style.width = imageData.width * PX_SIZE + 'px'; - domNode.style.height = imageData.height * PX_SIZE + 'px'; - domNode.style.border = '1px solid #ccc'; - domNode.style.background = '#000000'; - domNode.innerHTML = output; - document.body.appendChild(domNode); -} diff --git a/src/vs/editor/test/common/model/textModel.test.ts b/src/vs/editor/test/common/model/textModel.test.ts index 84021b31ebcc..07dd91eee3de 100644 --- a/src/vs/editor/test/common/model/textModel.test.ts +++ b/src/vs/editor/test/common/model/textModel.test.ts @@ -755,7 +755,7 @@ suite('Editor Model - TextModel', () => { assert.deepEqual(actual, expected, `validateRange for ${input}, got ${actual}, expected ${expected}`); } - test('combining marks', () => { + test('grapheme breaking', () => { const m = TextModel.createFromString([ 'abcabc', 'ãããããã', diff --git a/src/vs/editor/test/common/modes/linkComputer.test.ts b/src/vs/editor/test/common/modes/linkComputer.test.ts index 5036e4272857..165d1f6caa12 100644 --- a/src/vs/editor/test/common/modes/linkComputer.test.ts +++ b/src/vs/editor/test/common/modes/linkComputer.test.ts @@ -202,4 +202,11 @@ suite('Editor Modes - Link Computer', () => { ' http://[::1]:5000/connect/token ' ); }); + + test('issue #70254: bold links dont open in markdown file using editor mode with ctrl + click', () => { + assertLink( + '2. Navigate to **https://portal.azure.com**', + ' https://portal.azure.com ' + ); + }); }); diff --git a/src/vs/editor/test/common/modes/supports/onEnter.test.ts b/src/vs/editor/test/common/modes/supports/onEnter.test.ts index cdef0c609f40..e2407184f46a 100644 --- a/src/vs/editor/test/common/modes/supports/onEnter.test.ts +++ b/src/vs/editor/test/common/modes/supports/onEnter.test.ts @@ -6,6 +6,7 @@ import * as assert from 'assert'; import { CharacterPair, IndentAction } from 'vs/editor/common/modes/languageConfiguration'; import { OnEnterSupport } from 'vs/editor/common/modes/supports/onEnter'; import { javascriptOnEnterRules } from 'vs/editor/test/common/modes/supports/javascriptOnEnterRules'; +import { EditorAutoIndentStrategy } from 'vs/editor/common/config/editorOptions'; suite('OnEnter', () => { @@ -18,7 +19,7 @@ suite('OnEnter', () => { brackets: brackets }); let testIndentAction = (beforeText: string, afterText: string, expected: IndentAction) => { - let actual = support.onEnter('', beforeText, afterText); + let actual = support.onEnter(EditorAutoIndentStrategy.Advanced, '', beforeText, afterText); if (expected === IndentAction.None) { assert.equal(actual, null); } else { @@ -48,10 +49,10 @@ suite('OnEnter', () => { test('uses regExpRules', () => { let support = new OnEnterSupport({ - regExpRules: javascriptOnEnterRules + onEnterRules: javascriptOnEnterRules }); let testIndentAction = (oneLineAboveText: string, beforeText: string, afterText: string, expectedIndentAction: IndentAction | null, expectedAppendText: string | null, removeText: number = 0) => { - let actual = support.onEnter(oneLineAboveText, beforeText, afterText); + let actual = support.onEnter(EditorAutoIndentStrategy.Advanced, oneLineAboveText, beforeText, afterText); if (expectedIndentAction === null) { assert.equal(actual, null, 'isNull:' + beforeText); } else { @@ -132,4 +133,4 @@ suite('OnEnter', () => { testIndentAction('', ' * test() {', '', IndentAction.Indent, null, 0); testIndentAction(' ', ' * test() {', '', IndentAction.Indent, null, 0); }); -}); \ No newline at end of file +}); diff --git a/src/vs/editor/test/common/modes/textToHtmlTokenizer.test.ts b/src/vs/editor/test/common/modes/textToHtmlTokenizer.test.ts index 5409458a6df2..7de605f9a3bc 100644 --- a/src/vs/editor/test/common/modes/textToHtmlTokenizer.test.ts +++ b/src/vs/editor/test/common/modes/textToHtmlTokenizer.test.ts @@ -109,9 +109,9 @@ suite('Editor Modes - textToHtmlTokenizer', () => { [ '
', 'Ciao', - ' ', + ' ', 'hello', - ' ', + ' ', 'world!', '
' ].join('') @@ -122,9 +122,9 @@ suite('Editor Modes - textToHtmlTokenizer', () => { [ '
', 'Ciao', - ' ', + ' ', 'hello', - ' ', + ' ', 'w', '
' ].join('') @@ -135,9 +135,9 @@ suite('Editor Modes - textToHtmlTokenizer', () => { [ '
', 'Ciao', - ' ', + ' ', 'hello', - ' ', + ' ', '
' ].join('') ); @@ -147,9 +147,9 @@ suite('Editor Modes - textToHtmlTokenizer', () => { [ '
', 'iao', - ' ', + ' ', 'hello', - ' ', + ' ', '
' ].join('') ); @@ -158,9 +158,9 @@ suite('Editor Modes - textToHtmlTokenizer', () => { tokenizeLineToHTML(text, lineTokens, colorMap, 4, 11, 4, true), [ '
', - ' ', + ' ', 'hello', - ' ', + ' ', '
' ].join('') ); @@ -170,7 +170,7 @@ suite('Editor Modes - textToHtmlTokenizer', () => { [ '
', 'hello', - ' ', + ' ', '
' ].join('') ); @@ -241,11 +241,11 @@ suite('Editor Modes - textToHtmlTokenizer', () => { tokenizeLineToHTML(text, lineTokens, colorMap, 0, 21, 4, true), [ '
', - '  ', + '  ', 'Ciao', - '   ', + '   ', 'hello', - ' ', + ' ', 'world!', '
' ].join('') @@ -255,11 +255,11 @@ suite('Editor Modes - textToHtmlTokenizer', () => { tokenizeLineToHTML(text, lineTokens, colorMap, 0, 17, 4, true), [ '
', - '  ', + '  ', 'Ciao', - '   ', + '   ', 'hello', - ' ', + ' ', 'wo', '
' ].join('') @@ -269,7 +269,7 @@ suite('Editor Modes - textToHtmlTokenizer', () => { tokenizeLineToHTML(text, lineTokens, colorMap, 0, 3, 4, true), [ '
', - '  ', + '  ', 'C', '
' ].join('') diff --git a/src/vs/editor/test/common/services/editorSimpleWorker.test.ts b/src/vs/editor/test/common/services/editorSimpleWorker.test.ts index 0c034204cbaa..93eb5e988d2b 100644 --- a/src/vs/editor/test/common/services/editorSimpleWorker.test.ts +++ b/src/vs/editor/test/common/services/editorSimpleWorker.test.ts @@ -167,9 +167,8 @@ suite('EditorSimpleWorker', () => { assert.ok(false); return; } - const { suggestions } = result; - assert.equal(suggestions.length, 1); - assert.equal(suggestions[0].label, 'foobar'); + assert.equal(result.length, 1); + assert.equal(result, 'foobar'); }); }); diff --git a/src/vs/editor/test/common/services/modelService.test.ts b/src/vs/editor/test/common/services/modelService.test.ts index 7d657c12c2d0..e01ff62ff15f 100644 --- a/src/vs/editor/test/common/services/modelService.test.ts +++ b/src/vs/editor/test/common/services/modelService.test.ts @@ -16,6 +16,7 @@ import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; +import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; const GENERATE_TESTS = false; @@ -27,7 +28,7 @@ suite('ModelService', () => { configService.setUserConfiguration('files', { 'eol': '\n' }); configService.setUserConfiguration('files', { 'eol': '\r\n' }, URI.file(platform.isWindows ? 'c:\\myroot' : '/myroot')); - modelService = new ModelServiceImpl(configService, new TestTextResourcePropertiesService(configService)); + modelService = new ModelServiceImpl(configService, new TestTextResourcePropertiesService(configService), new TestThemeService()); }); teardown(() => { diff --git a/src/vs/editor/test/common/viewLayout/editorLayoutProvider.test.ts b/src/vs/editor/test/common/viewLayout/editorLayoutProvider.test.ts index 4a7b9fdbc7fb..516c85bf2172 100644 --- a/src/vs/editor/test/common/viewLayout/editorLayoutProvider.test.ts +++ b/src/vs/editor/test/common/viewLayout/editorLayoutProvider.test.ts @@ -702,12 +702,12 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { decorationsHeight: 800, contentLeft: 10, - contentWidth: 901, + contentWidth: 893, contentHeight: 800, renderMinimap: RenderMinimap.Text, - minimapLeft: 911, - minimapWidth: 89, + minimapLeft: 903, + minimapWidth: 97, viewportColumn: 89, verticalScrollbarWidth: 0, @@ -760,12 +760,12 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { decorationsHeight: 800, contentLeft: 10, - contentWidth: 901, + contentWidth: 893, contentHeight: 800, renderMinimap: RenderMinimap.Text, - minimapLeft: 911, - minimapWidth: 89, + minimapLeft: 903, + minimapWidth: 97, viewportColumn: 89, verticalScrollbarWidth: 0, @@ -818,13 +818,13 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { decorationsHeight: 800, contentLeft: 10, - contentWidth: 943, + contentWidth: 935, contentHeight: 800, renderMinimap: RenderMinimap.Text, - minimapLeft: 953, - minimapWidth: 47, - viewportColumn: 94, + minimapLeft: 945, + minimapWidth: 55, + viewportColumn: 93, verticalScrollbarWidth: 0, horizontalScrollbarHeight: 0, @@ -863,26 +863,26 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { width: 1000, height: 800, - glyphMarginLeft: 47, + glyphMarginLeft: 55, glyphMarginWidth: 0, glyphMarginHeight: 800, - lineNumbersLeft: 47, + lineNumbersLeft: 55, lineNumbersWidth: 0, lineNumbersHeight: 800, - decorationsLeft: 47, + decorationsLeft: 55, decorationsWidth: 10, decorationsHeight: 800, - contentLeft: 57, - contentWidth: 943, + contentLeft: 65, + contentWidth: 935, contentHeight: 800, renderMinimap: RenderMinimap.Text, minimapLeft: 0, - minimapWidth: 47, - viewportColumn: 94, + minimapWidth: 55, + viewportColumn: 93, verticalScrollbarWidth: 0, horizontalScrollbarHeight: 0, @@ -934,12 +934,12 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { decorationsHeight: 422, contentLeft: 92, - contentWidth: 1026, + contentWidth: 1018, contentHeight: 422, renderMinimap: RenderMinimap.Text, - minimapLeft: 1104, - minimapWidth: 83, + minimapLeft: 1096, + minimapWidth: 91, viewportColumn: 83, verticalScrollbarWidth: 14, diff --git a/src/vs/editor/test/common/viewLayout/linesLayout.test.ts b/src/vs/editor/test/common/viewLayout/linesLayout.test.ts index 512b76254c0d..471b7853a012 100644 --- a/src/vs/editor/test/common/viewLayout/linesLayout.test.ts +++ b/src/vs/editor/test/common/viewLayout/linesLayout.test.ts @@ -3,10 +3,28 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { LinesLayout } from 'vs/editor/common/viewLayout/linesLayout'; +import { LinesLayout, EditorWhitespace } from 'vs/editor/common/viewLayout/linesLayout'; suite('Editor ViewLayout - LinesLayout', () => { + function insertWhitespace(linesLayout: LinesLayout, afterLineNumber: number, ordinal: number, heightInPx: number, minWidth: number): string { + return linesLayout.changeWhitespace((accessor) => { + return accessor.insertWhitespace(afterLineNumber, ordinal, heightInPx, minWidth); + }); + } + + function changeOneWhitespace(linesLayout: LinesLayout, id: string, newAfterLineNumber: number, newHeight: number): void { + linesLayout.changeWhitespace((accessor) => { + accessor.changeOneWhitespace(id, newAfterLineNumber, newHeight); + }); + } + + function removeWhitespace(linesLayout: LinesLayout, id: string): void { + linesLayout.changeWhitespace((accessor) => { + accessor.removeWhitespace(id); + }); + } + test('LinesLayout 1', () => { // Start off with 10 lines @@ -39,7 +57,7 @@ suite('Editor ViewLayout - LinesLayout', () => { assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(29), 3); // Add whitespace of height 5px after 2nd line - linesLayout.insertWhitespace(2, 0, 5, 0); + insertWhitespace(linesLayout, 2, 0, 5, 0); // lines: [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] // whitespace: a(2,5) assert.equal(linesLayout.getLinesTotalHeight(), 105); @@ -63,8 +81,8 @@ suite('Editor ViewLayout - LinesLayout', () => { assert.equal(linesLayout.getLineNumberAtOrAfterVerticalOffset(105), 10); // Add two more whitespaces of height 5px - linesLayout.insertWhitespace(3, 0, 5, 0); - linesLayout.insertWhitespace(4, 0, 5, 0); + insertWhitespace(linesLayout, 3, 0, 5, 0); + insertWhitespace(linesLayout, 4, 0, 5, 0); // lines: [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] // whitespace: a(2,5), b(3, 5), c(4, 5) assert.equal(linesLayout.getLinesTotalHeight(), 115); @@ -120,7 +138,7 @@ suite('Editor ViewLayout - LinesLayout', () => { // Start off with 10 lines and one whitespace after line 2, of height 5 let linesLayout = new LinesLayout(10, 1); - let a = linesLayout.insertWhitespace(2, 0, 5, 0); + let a = insertWhitespace(linesLayout, 2, 0, 5, 0); // 10 lines // whitespace: - a(2,5) @@ -139,7 +157,7 @@ suite('Editor ViewLayout - LinesLayout', () => { // Change whitespace height // 10 lines // whitespace: - a(2,10) - linesLayout.changeWhitespace(a, 2, 10); + changeOneWhitespace(linesLayout, a, 2, 10); assert.equal(linesLayout.getLinesTotalHeight(), 20); assert.equal(linesLayout.getVerticalOffsetForLineNumber(1), 0); assert.equal(linesLayout.getVerticalOffsetForLineNumber(2), 1); @@ -155,7 +173,7 @@ suite('Editor ViewLayout - LinesLayout', () => { // Change whitespace position // 10 lines // whitespace: - a(5,10) - linesLayout.changeWhitespace(a, 5, 10); + changeOneWhitespace(linesLayout, a, 5, 10); assert.equal(linesLayout.getLinesTotalHeight(), 20); assert.equal(linesLayout.getVerticalOffsetForLineNumber(1), 0); assert.equal(linesLayout.getVerticalOffsetForLineNumber(2), 1); @@ -200,7 +218,7 @@ suite('Editor ViewLayout - LinesLayout', () => { // Remove whitespace // 10 lines - linesLayout.removeWhitespace(a); + removeWhitespace(linesLayout, a); assert.equal(linesLayout.getLinesTotalHeight(), 10); assert.equal(linesLayout.getVerticalOffsetForLineNumber(1), 0); assert.equal(linesLayout.getVerticalOffsetForLineNumber(2), 1); @@ -216,7 +234,7 @@ suite('Editor ViewLayout - LinesLayout', () => { test('LinesLayout getLineNumberAtOrAfterVerticalOffset', () => { let linesLayout = new LinesLayout(10, 1); - linesLayout.insertWhitespace(6, 0, 10, 0); + insertWhitespace(linesLayout, 6, 0, 10, 0); // 10 lines // whitespace: - a(6,10) @@ -265,7 +283,7 @@ suite('Editor ViewLayout - LinesLayout', () => { test('LinesLayout getCenteredLineInViewport', () => { let linesLayout = new LinesLayout(10, 1); - linesLayout.insertWhitespace(6, 0, 10, 0); + insertWhitespace(linesLayout, 6, 0, 10, 0); // 10 lines // whitespace: - a(6,10) @@ -348,7 +366,7 @@ suite('Editor ViewLayout - LinesLayout', () => { test('LinesLayout getLinesViewportData 1', () => { let linesLayout = new LinesLayout(10, 10); - linesLayout.insertWhitespace(6, 0, 100, 0); + insertWhitespace(linesLayout, 6, 0, 100, 0); // 10 lines // whitespace: - a(6,100) @@ -479,11 +497,10 @@ suite('Editor ViewLayout - LinesLayout', () => { assert.deepEqual(viewportData.relativeVerticalOffset, [160, 170, 180, 190]); }); - test('LinesLayout getLinesViewportData 2 & getWhitespaceViewportData', () => { let linesLayout = new LinesLayout(10, 10); - let a = linesLayout.insertWhitespace(6, 0, 100, 0); - let b = linesLayout.insertWhitespace(7, 0, 50, 0); + let a = insertWhitespace(linesLayout, 6, 0, 100, 0); + let b = insertWhitespace(linesLayout, 7, 0, 50, 0); // 10 lines // whitespace: - a(6,100), b(7, 50) @@ -553,8 +570,8 @@ suite('Editor ViewLayout - LinesLayout', () => { test('LinesLayout getWhitespaceAtVerticalOffset', () => { let linesLayout = new LinesLayout(10, 10); - let a = linesLayout.insertWhitespace(6, 0, 100, 0); - let b = linesLayout.insertWhitespace(7, 0, 50, 0); + let a = insertWhitespace(linesLayout, 6, 0, 100, 0); + let b = insertWhitespace(linesLayout, 7, 0, 50, 0); let whitespace = linesLayout.getWhitespaceAtVerticalOffset(0); assert.equal(whitespace, null); @@ -592,4 +609,536 @@ suite('Editor ViewLayout - LinesLayout', () => { whitespace = linesLayout.getWhitespaceAtVerticalOffset(220); assert.equal(whitespace, null); }); + + test('LinesLayout', () => { + + const linesLayout = new LinesLayout(100, 20); + + // Insert a whitespace after line number 2, of height 10 + const a = insertWhitespace(linesLayout, 2, 0, 10, 0); + // whitespaces: a(2, 10) + assert.equal(linesLayout.getWhitespacesCount(), 1); + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 2); + assert.equal(linesLayout.getHeightForWhitespaceIndex(0), 10); + assert.equal(linesLayout.getWhitespacesAccumulatedHeight(0), 10); + assert.equal(linesLayout.getWhitespacesTotalHeight(), 10); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(1), 0); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(2), 0); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(3), 10); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(4), 10); + + // Insert a whitespace again after line number 2, of height 20 + let b = insertWhitespace(linesLayout, 2, 0, 20, 0); + // whitespaces: a(2, 10), b(2, 20) + assert.equal(linesLayout.getWhitespacesCount(), 2); + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 2); + assert.equal(linesLayout.getHeightForWhitespaceIndex(0), 10); + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 2); + assert.equal(linesLayout.getHeightForWhitespaceIndex(1), 20); + assert.equal(linesLayout.getWhitespacesAccumulatedHeight(0), 10); + assert.equal(linesLayout.getWhitespacesAccumulatedHeight(1), 30); + assert.equal(linesLayout.getWhitespacesTotalHeight(), 30); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(1), 0); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(2), 0); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(3), 30); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(4), 30); + + // Change last inserted whitespace height to 30 + changeOneWhitespace(linesLayout, b, 2, 30); + // whitespaces: a(2, 10), b(2, 30) + assert.equal(linesLayout.getWhitespacesCount(), 2); + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 2); + assert.equal(linesLayout.getHeightForWhitespaceIndex(0), 10); + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 2); + assert.equal(linesLayout.getHeightForWhitespaceIndex(1), 30); + assert.equal(linesLayout.getWhitespacesAccumulatedHeight(0), 10); + assert.equal(linesLayout.getWhitespacesAccumulatedHeight(1), 40); + assert.equal(linesLayout.getWhitespacesTotalHeight(), 40); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(1), 0); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(2), 0); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(3), 40); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(4), 40); + + // Remove last inserted whitespace + removeWhitespace(linesLayout, b); + // whitespaces: a(2, 10) + assert.equal(linesLayout.getWhitespacesCount(), 1); + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 2); + assert.equal(linesLayout.getHeightForWhitespaceIndex(0), 10); + assert.equal(linesLayout.getWhitespacesAccumulatedHeight(0), 10); + assert.equal(linesLayout.getWhitespacesTotalHeight(), 10); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(1), 0); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(2), 0); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(3), 10); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(4), 10); + + // Add a whitespace before the first line of height 50 + b = insertWhitespace(linesLayout, 0, 0, 50, 0); + // whitespaces: b(0, 50), a(2, 10) + assert.equal(linesLayout.getWhitespacesCount(), 2); + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 0); + assert.equal(linesLayout.getHeightForWhitespaceIndex(0), 50); + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 2); + assert.equal(linesLayout.getHeightForWhitespaceIndex(1), 10); + assert.equal(linesLayout.getWhitespacesAccumulatedHeight(0), 50); + assert.equal(linesLayout.getWhitespacesAccumulatedHeight(1), 60); + assert.equal(linesLayout.getWhitespacesTotalHeight(), 60); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(1), 50); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(2), 50); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(3), 60); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(4), 60); + + // Add a whitespace after line 4 of height 20 + insertWhitespace(linesLayout, 4, 0, 20, 0); + // whitespaces: b(0, 50), a(2, 10), c(4, 20) + assert.equal(linesLayout.getWhitespacesCount(), 3); + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 0); + assert.equal(linesLayout.getHeightForWhitespaceIndex(0), 50); + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 2); + assert.equal(linesLayout.getHeightForWhitespaceIndex(1), 10); + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(2), 4); + assert.equal(linesLayout.getHeightForWhitespaceIndex(2), 20); + assert.equal(linesLayout.getWhitespacesAccumulatedHeight(0), 50); + assert.equal(linesLayout.getWhitespacesAccumulatedHeight(1), 60); + assert.equal(linesLayout.getWhitespacesAccumulatedHeight(2), 80); + assert.equal(linesLayout.getWhitespacesTotalHeight(), 80); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(1), 50); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(2), 50); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(3), 60); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(4), 60); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(5), 80); + + // Add a whitespace after line 3 of height 30 + insertWhitespace(linesLayout, 3, 0, 30, 0); + // whitespaces: b(0, 50), a(2, 10), d(3, 30), c(4, 20) + assert.equal(linesLayout.getWhitespacesCount(), 4); + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 0); + assert.equal(linesLayout.getHeightForWhitespaceIndex(0), 50); + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 2); + assert.equal(linesLayout.getHeightForWhitespaceIndex(1), 10); + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(2), 3); + assert.equal(linesLayout.getHeightForWhitespaceIndex(2), 30); + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(3), 4); + assert.equal(linesLayout.getHeightForWhitespaceIndex(3), 20); + assert.equal(linesLayout.getWhitespacesAccumulatedHeight(0), 50); + assert.equal(linesLayout.getWhitespacesAccumulatedHeight(1), 60); + assert.equal(linesLayout.getWhitespacesAccumulatedHeight(2), 90); + assert.equal(linesLayout.getWhitespacesAccumulatedHeight(3), 110); + assert.equal(linesLayout.getWhitespacesTotalHeight(), 110); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(1), 50); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(2), 50); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(3), 60); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(4), 90); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(5), 110); + + // Change whitespace after line 2 to height of 100 + changeOneWhitespace(linesLayout, a, 2, 100); + // whitespaces: b(0, 50), a(2, 100), d(3, 30), c(4, 20) + assert.equal(linesLayout.getWhitespacesCount(), 4); + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 0); + assert.equal(linesLayout.getHeightForWhitespaceIndex(0), 50); + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 2); + assert.equal(linesLayout.getHeightForWhitespaceIndex(1), 100); + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(2), 3); + assert.equal(linesLayout.getHeightForWhitespaceIndex(2), 30); + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(3), 4); + assert.equal(linesLayout.getHeightForWhitespaceIndex(3), 20); + assert.equal(linesLayout.getWhitespacesAccumulatedHeight(0), 50); + assert.equal(linesLayout.getWhitespacesAccumulatedHeight(1), 150); + assert.equal(linesLayout.getWhitespacesAccumulatedHeight(2), 180); + assert.equal(linesLayout.getWhitespacesAccumulatedHeight(3), 200); + assert.equal(linesLayout.getWhitespacesTotalHeight(), 200); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(1), 50); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(2), 50); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(3), 150); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(4), 180); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(5), 200); + + // Remove whitespace after line 2 + removeWhitespace(linesLayout, a); + // whitespaces: b(0, 50), d(3, 30), c(4, 20) + assert.equal(linesLayout.getWhitespacesCount(), 3); + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 0); + assert.equal(linesLayout.getHeightForWhitespaceIndex(0), 50); + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 3); + assert.equal(linesLayout.getHeightForWhitespaceIndex(1), 30); + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(2), 4); + assert.equal(linesLayout.getHeightForWhitespaceIndex(2), 20); + assert.equal(linesLayout.getWhitespacesAccumulatedHeight(0), 50); + assert.equal(linesLayout.getWhitespacesAccumulatedHeight(1), 80); + assert.equal(linesLayout.getWhitespacesAccumulatedHeight(2), 100); + assert.equal(linesLayout.getWhitespacesTotalHeight(), 100); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(1), 50); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(2), 50); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(3), 50); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(4), 80); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(5), 100); + + // Remove whitespace before line 1 + removeWhitespace(linesLayout, b); + // whitespaces: d(3, 30), c(4, 20) + assert.equal(linesLayout.getWhitespacesCount(), 2); + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 3); + assert.equal(linesLayout.getHeightForWhitespaceIndex(0), 30); + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 4); + assert.equal(linesLayout.getHeightForWhitespaceIndex(1), 20); + assert.equal(linesLayout.getWhitespacesAccumulatedHeight(0), 30); + assert.equal(linesLayout.getWhitespacesAccumulatedHeight(1), 50); + assert.equal(linesLayout.getWhitespacesTotalHeight(), 50); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(1), 0); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(2), 0); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(3), 0); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(4), 30); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(5), 50); + + // Delete line 1 + linesLayout.onLinesDeleted(1, 1); + // whitespaces: d(2, 30), c(3, 20) + assert.equal(linesLayout.getWhitespacesCount(), 2); + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 2); + assert.equal(linesLayout.getHeightForWhitespaceIndex(0), 30); + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 3); + assert.equal(linesLayout.getHeightForWhitespaceIndex(1), 20); + assert.equal(linesLayout.getWhitespacesAccumulatedHeight(0), 30); + assert.equal(linesLayout.getWhitespacesAccumulatedHeight(1), 50); + assert.equal(linesLayout.getWhitespacesTotalHeight(), 50); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(1), 0); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(2), 0); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(3), 30); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(4), 50); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(5), 50); + + // Insert a line before line 1 + linesLayout.onLinesInserted(1, 1); + // whitespaces: d(3, 30), c(4, 20) + assert.equal(linesLayout.getWhitespacesCount(), 2); + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 3); + assert.equal(linesLayout.getHeightForWhitespaceIndex(0), 30); + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 4); + assert.equal(linesLayout.getHeightForWhitespaceIndex(1), 20); + assert.equal(linesLayout.getWhitespacesAccumulatedHeight(0), 30); + assert.equal(linesLayout.getWhitespacesAccumulatedHeight(1), 50); + assert.equal(linesLayout.getWhitespacesTotalHeight(), 50); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(1), 0); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(2), 0); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(3), 0); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(4), 30); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(5), 50); + + // Delete line 4 + linesLayout.onLinesDeleted(4, 4); + // whitespaces: d(3, 30), c(3, 20) + assert.equal(linesLayout.getWhitespacesCount(), 2); + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 3); + assert.equal(linesLayout.getHeightForWhitespaceIndex(0), 30); + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 3); + assert.equal(linesLayout.getHeightForWhitespaceIndex(1), 20); + assert.equal(linesLayout.getWhitespacesAccumulatedHeight(0), 30); + assert.equal(linesLayout.getWhitespacesAccumulatedHeight(1), 50); + assert.equal(linesLayout.getWhitespacesTotalHeight(), 50); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(1), 0); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(2), 0); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(3), 0); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(4), 50); + assert.equal(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(5), 50); + }); + + test('LinesLayout findInsertionIndex', () => { + + const makeInternalWhitespace = (afterLineNumbers: number[], ordinal: number = 0) => { + return afterLineNumbers.map((afterLineNumber) => new EditorWhitespace('', afterLineNumber, ordinal, 0, 0)); + }; + + let arr: EditorWhitespace[]; + + arr = makeInternalWhitespace([]); + assert.equal(LinesLayout.findInsertionIndex(arr, 0, 0), 0); + assert.equal(LinesLayout.findInsertionIndex(arr, 1, 0), 0); + assert.equal(LinesLayout.findInsertionIndex(arr, 2, 0), 0); + + arr = makeInternalWhitespace([1]); + assert.equal(LinesLayout.findInsertionIndex(arr, 0, 0), 0); + assert.equal(LinesLayout.findInsertionIndex(arr, 1, 0), 1); + assert.equal(LinesLayout.findInsertionIndex(arr, 2, 0), 1); + + arr = makeInternalWhitespace([1, 3]); + assert.equal(LinesLayout.findInsertionIndex(arr, 0, 0), 0); + assert.equal(LinesLayout.findInsertionIndex(arr, 1, 0), 1); + assert.equal(LinesLayout.findInsertionIndex(arr, 2, 0), 1); + assert.equal(LinesLayout.findInsertionIndex(arr, 3, 0), 2); + assert.equal(LinesLayout.findInsertionIndex(arr, 4, 0), 2); + + arr = makeInternalWhitespace([1, 3, 5]); + assert.equal(LinesLayout.findInsertionIndex(arr, 0, 0), 0); + assert.equal(LinesLayout.findInsertionIndex(arr, 1, 0), 1); + assert.equal(LinesLayout.findInsertionIndex(arr, 2, 0), 1); + assert.equal(LinesLayout.findInsertionIndex(arr, 3, 0), 2); + assert.equal(LinesLayout.findInsertionIndex(arr, 4, 0), 2); + assert.equal(LinesLayout.findInsertionIndex(arr, 5, 0), 3); + assert.equal(LinesLayout.findInsertionIndex(arr, 6, 0), 3); + + arr = makeInternalWhitespace([1, 3, 5], 3); + assert.equal(LinesLayout.findInsertionIndex(arr, 0, 0), 0); + assert.equal(LinesLayout.findInsertionIndex(arr, 1, 0), 0); + assert.equal(LinesLayout.findInsertionIndex(arr, 2, 0), 1); + assert.equal(LinesLayout.findInsertionIndex(arr, 3, 0), 1); + assert.equal(LinesLayout.findInsertionIndex(arr, 4, 0), 2); + assert.equal(LinesLayout.findInsertionIndex(arr, 5, 0), 2); + assert.equal(LinesLayout.findInsertionIndex(arr, 6, 0), 3); + + arr = makeInternalWhitespace([1, 3, 5, 7]); + assert.equal(LinesLayout.findInsertionIndex(arr, 0, 0), 0); + assert.equal(LinesLayout.findInsertionIndex(arr, 1, 0), 1); + assert.equal(LinesLayout.findInsertionIndex(arr, 2, 0), 1); + assert.equal(LinesLayout.findInsertionIndex(arr, 3, 0), 2); + assert.equal(LinesLayout.findInsertionIndex(arr, 4, 0), 2); + assert.equal(LinesLayout.findInsertionIndex(arr, 5, 0), 3); + assert.equal(LinesLayout.findInsertionIndex(arr, 6, 0), 3); + assert.equal(LinesLayout.findInsertionIndex(arr, 7, 0), 4); + assert.equal(LinesLayout.findInsertionIndex(arr, 8, 0), 4); + + arr = makeInternalWhitespace([1, 3, 5, 7, 9]); + assert.equal(LinesLayout.findInsertionIndex(arr, 0, 0), 0); + assert.equal(LinesLayout.findInsertionIndex(arr, 1, 0), 1); + assert.equal(LinesLayout.findInsertionIndex(arr, 2, 0), 1); + assert.equal(LinesLayout.findInsertionIndex(arr, 3, 0), 2); + assert.equal(LinesLayout.findInsertionIndex(arr, 4, 0), 2); + assert.equal(LinesLayout.findInsertionIndex(arr, 5, 0), 3); + assert.equal(LinesLayout.findInsertionIndex(arr, 6, 0), 3); + assert.equal(LinesLayout.findInsertionIndex(arr, 7, 0), 4); + assert.equal(LinesLayout.findInsertionIndex(arr, 8, 0), 4); + assert.equal(LinesLayout.findInsertionIndex(arr, 9, 0), 5); + assert.equal(LinesLayout.findInsertionIndex(arr, 10, 0), 5); + + arr = makeInternalWhitespace([1, 3, 5, 7, 9, 11]); + assert.equal(LinesLayout.findInsertionIndex(arr, 0, 0), 0); + assert.equal(LinesLayout.findInsertionIndex(arr, 1, 0), 1); + assert.equal(LinesLayout.findInsertionIndex(arr, 2, 0), 1); + assert.equal(LinesLayout.findInsertionIndex(arr, 3, 0), 2); + assert.equal(LinesLayout.findInsertionIndex(arr, 4, 0), 2); + assert.equal(LinesLayout.findInsertionIndex(arr, 5, 0), 3); + assert.equal(LinesLayout.findInsertionIndex(arr, 6, 0), 3); + assert.equal(LinesLayout.findInsertionIndex(arr, 7, 0), 4); + assert.equal(LinesLayout.findInsertionIndex(arr, 8, 0), 4); + assert.equal(LinesLayout.findInsertionIndex(arr, 9, 0), 5); + assert.equal(LinesLayout.findInsertionIndex(arr, 10, 0), 5); + assert.equal(LinesLayout.findInsertionIndex(arr, 11, 0), 6); + assert.equal(LinesLayout.findInsertionIndex(arr, 12, 0), 6); + + arr = makeInternalWhitespace([1, 3, 5, 7, 9, 11, 13]); + assert.equal(LinesLayout.findInsertionIndex(arr, 0, 0), 0); + assert.equal(LinesLayout.findInsertionIndex(arr, 1, 0), 1); + assert.equal(LinesLayout.findInsertionIndex(arr, 2, 0), 1); + assert.equal(LinesLayout.findInsertionIndex(arr, 3, 0), 2); + assert.equal(LinesLayout.findInsertionIndex(arr, 4, 0), 2); + assert.equal(LinesLayout.findInsertionIndex(arr, 5, 0), 3); + assert.equal(LinesLayout.findInsertionIndex(arr, 6, 0), 3); + assert.equal(LinesLayout.findInsertionIndex(arr, 7, 0), 4); + assert.equal(LinesLayout.findInsertionIndex(arr, 8, 0), 4); + assert.equal(LinesLayout.findInsertionIndex(arr, 9, 0), 5); + assert.equal(LinesLayout.findInsertionIndex(arr, 10, 0), 5); + assert.equal(LinesLayout.findInsertionIndex(arr, 11, 0), 6); + assert.equal(LinesLayout.findInsertionIndex(arr, 12, 0), 6); + assert.equal(LinesLayout.findInsertionIndex(arr, 13, 0), 7); + assert.equal(LinesLayout.findInsertionIndex(arr, 14, 0), 7); + + arr = makeInternalWhitespace([1, 3, 5, 7, 9, 11, 13, 15]); + assert.equal(LinesLayout.findInsertionIndex(arr, 0, 0), 0); + assert.equal(LinesLayout.findInsertionIndex(arr, 1, 0), 1); + assert.equal(LinesLayout.findInsertionIndex(arr, 2, 0), 1); + assert.equal(LinesLayout.findInsertionIndex(arr, 3, 0), 2); + assert.equal(LinesLayout.findInsertionIndex(arr, 4, 0), 2); + assert.equal(LinesLayout.findInsertionIndex(arr, 5, 0), 3); + assert.equal(LinesLayout.findInsertionIndex(arr, 6, 0), 3); + assert.equal(LinesLayout.findInsertionIndex(arr, 7, 0), 4); + assert.equal(LinesLayout.findInsertionIndex(arr, 8, 0), 4); + assert.equal(LinesLayout.findInsertionIndex(arr, 9, 0), 5); + assert.equal(LinesLayout.findInsertionIndex(arr, 10, 0), 5); + assert.equal(LinesLayout.findInsertionIndex(arr, 11, 0), 6); + assert.equal(LinesLayout.findInsertionIndex(arr, 12, 0), 6); + assert.equal(LinesLayout.findInsertionIndex(arr, 13, 0), 7); + assert.equal(LinesLayout.findInsertionIndex(arr, 14, 0), 7); + assert.equal(LinesLayout.findInsertionIndex(arr, 15, 0), 8); + assert.equal(LinesLayout.findInsertionIndex(arr, 16, 0), 8); + }); + + test('LinesLayout changeWhitespaceAfterLineNumber & getFirstWhitespaceIndexAfterLineNumber', () => { + const linesLayout = new LinesLayout(100, 20); + + const a = insertWhitespace(linesLayout, 0, 0, 1, 0); + const b = insertWhitespace(linesLayout, 7, 0, 1, 0); + const c = insertWhitespace(linesLayout, 3, 0, 1, 0); + + assert.equal(linesLayout.getIdForWhitespaceIndex(0), a); // 0 + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 0); + assert.equal(linesLayout.getIdForWhitespaceIndex(1), c); // 3 + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 3); + assert.equal(linesLayout.getIdForWhitespaceIndex(2), b); // 7 + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(2), 7); + + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(1), 1); // c + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(2), 1); // c + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(3), 1); // c + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(4), 2); // b + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(5), 2); // b + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(6), 2); // b + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(7), 2); // b + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(8), -1); // -- + + // Do not really move a + changeOneWhitespace(linesLayout, a, 1, 1); + + assert.equal(linesLayout.getIdForWhitespaceIndex(0), a); // 1 + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 1); + assert.equal(linesLayout.getIdForWhitespaceIndex(1), c); // 3 + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 3); + assert.equal(linesLayout.getIdForWhitespaceIndex(2), b); // 7 + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(2), 7); + + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(1), 0); // a + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(2), 1); // c + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(3), 1); // c + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(4), 2); // b + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(5), 2); // b + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(6), 2); // b + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(7), 2); // b + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(8), -1); // -- + + + // Do not really move a + changeOneWhitespace(linesLayout, a, 2, 1); + + assert.equal(linesLayout.getIdForWhitespaceIndex(0), a); // 2 + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 2); + assert.equal(linesLayout.getIdForWhitespaceIndex(1), c); // 3 + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 3); + assert.equal(linesLayout.getIdForWhitespaceIndex(2), b); // 7 + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(2), 7); + + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(1), 0); // a + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(2), 0); // a + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(3), 1); // c + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(4), 2); // b + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(5), 2); // b + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(6), 2); // b + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(7), 2); // b + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(8), -1); // -- + + + // Change a to conflict with c => a gets placed after c + changeOneWhitespace(linesLayout, a, 3, 1); + + assert.equal(linesLayout.getIdForWhitespaceIndex(0), c); // 3 + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 3); + assert.equal(linesLayout.getIdForWhitespaceIndex(1), a); // 3 + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 3); + assert.equal(linesLayout.getIdForWhitespaceIndex(2), b); // 7 + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(2), 7); + + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(1), 0); // c + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(2), 0); // c + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(3), 0); // c + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(4), 2); // b + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(5), 2); // b + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(6), 2); // b + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(7), 2); // b + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(8), -1); // -- + + + // Make a no-op + changeOneWhitespace(linesLayout, c, 3, 1); + + assert.equal(linesLayout.getIdForWhitespaceIndex(0), c); // 3 + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 3); + assert.equal(linesLayout.getIdForWhitespaceIndex(1), a); // 3 + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 3); + assert.equal(linesLayout.getIdForWhitespaceIndex(2), b); // 7 + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(2), 7); + + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(1), 0); // c + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(2), 0); // c + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(3), 0); // c + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(4), 2); // b + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(5), 2); // b + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(6), 2); // b + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(7), 2); // b + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(8), -1); // -- + + + + // Conflict c with b => c gets placed after b + changeOneWhitespace(linesLayout, c, 7, 1); + + assert.equal(linesLayout.getIdForWhitespaceIndex(0), a); // 3 + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 3); + assert.equal(linesLayout.getIdForWhitespaceIndex(1), b); // 7 + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(1), 7); + assert.equal(linesLayout.getIdForWhitespaceIndex(2), c); // 7 + assert.equal(linesLayout.getAfterLineNumberForWhitespaceIndex(2), 7); + + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(1), 0); // a + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(2), 0); // a + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(3), 0); // a + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(4), 1); // b + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(5), 1); // b + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(6), 1); // b + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(7), 1); // b + assert.equal(linesLayout.getFirstWhitespaceIndexAfterLineNumber(8), -1); // -- + }); + + test('LinesLayout Bug', () => { + const linesLayout = new LinesLayout(100, 20); + + const a = insertWhitespace(linesLayout, 0, 0, 1, 0); + const b = insertWhitespace(linesLayout, 7, 0, 1, 0); + + assert.equal(linesLayout.getIdForWhitespaceIndex(0), a); // 0 + assert.equal(linesLayout.getIdForWhitespaceIndex(1), b); // 7 + + const c = insertWhitespace(linesLayout, 3, 0, 1, 0); + + assert.equal(linesLayout.getIdForWhitespaceIndex(0), a); // 0 + assert.equal(linesLayout.getIdForWhitespaceIndex(1), c); // 3 + assert.equal(linesLayout.getIdForWhitespaceIndex(2), b); // 7 + + const d = insertWhitespace(linesLayout, 2, 0, 1, 0); + assert.equal(linesLayout.getIdForWhitespaceIndex(0), a); // 0 + assert.equal(linesLayout.getIdForWhitespaceIndex(1), d); // 2 + assert.equal(linesLayout.getIdForWhitespaceIndex(2), c); // 3 + assert.equal(linesLayout.getIdForWhitespaceIndex(3), b); // 7 + + const e = insertWhitespace(linesLayout, 8, 0, 1, 0); + assert.equal(linesLayout.getIdForWhitespaceIndex(0), a); // 0 + assert.equal(linesLayout.getIdForWhitespaceIndex(1), d); // 2 + assert.equal(linesLayout.getIdForWhitespaceIndex(2), c); // 3 + assert.equal(linesLayout.getIdForWhitespaceIndex(3), b); // 7 + assert.equal(linesLayout.getIdForWhitespaceIndex(4), e); // 8 + + const f = insertWhitespace(linesLayout, 11, 0, 1, 0); + assert.equal(linesLayout.getIdForWhitespaceIndex(0), a); // 0 + assert.equal(linesLayout.getIdForWhitespaceIndex(1), d); // 2 + assert.equal(linesLayout.getIdForWhitespaceIndex(2), c); // 3 + assert.equal(linesLayout.getIdForWhitespaceIndex(3), b); // 7 + assert.equal(linesLayout.getIdForWhitespaceIndex(4), e); // 8 + assert.equal(linesLayout.getIdForWhitespaceIndex(5), f); // 11 + + const g = insertWhitespace(linesLayout, 10, 0, 1, 0); + assert.equal(linesLayout.getIdForWhitespaceIndex(0), a); // 0 + assert.equal(linesLayout.getIdForWhitespaceIndex(1), d); // 2 + assert.equal(linesLayout.getIdForWhitespaceIndex(2), c); // 3 + assert.equal(linesLayout.getIdForWhitespaceIndex(3), b); // 7 + assert.equal(linesLayout.getIdForWhitespaceIndex(4), e); // 8 + assert.equal(linesLayout.getIdForWhitespaceIndex(5), g); // 10 + assert.equal(linesLayout.getIdForWhitespaceIndex(6), f); // 11 + + const h = insertWhitespace(linesLayout, 0, 0, 1, 0); + assert.equal(linesLayout.getIdForWhitespaceIndex(0), a); // 0 + assert.equal(linesLayout.getIdForWhitespaceIndex(1), h); // 0 + assert.equal(linesLayout.getIdForWhitespaceIndex(2), d); // 2 + assert.equal(linesLayout.getIdForWhitespaceIndex(3), c); // 3 + assert.equal(linesLayout.getIdForWhitespaceIndex(4), b); // 7 + assert.equal(linesLayout.getIdForWhitespaceIndex(5), e); // 8 + assert.equal(linesLayout.getIdForWhitespaceIndex(6), g); // 10 + assert.equal(linesLayout.getIdForWhitespaceIndex(7), f); // 11 + }); }); diff --git a/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts b/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts index 6e3fbb979e49..a33a5ac526ed 100644 --- a/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts +++ b/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts @@ -189,8 +189,8 @@ suite('viewLineRenderer.renderLine', () => { createPart(48, 12), ]); let expectedOutput = [ - '\u2192\u00a0\u00a0\u00a0', - '\u00b7\u00b7\u00b7\u00b7', + '\u2192\u00a0\u00a0\u00a0', + '\u00b7\u00b7\u00b7\u00b7', 'export', '\u00a0', 'class', @@ -201,8 +201,8 @@ suite('viewLineRenderer.renderLine', () => { '\u00a0', '//\u00a0', 'http://test.com', - '\u00b7\u00b7', - '\u00b7\u00b7\u00b7' + '\u00b7\u00b7', + '\u00b7\u00b7\u00b7' ].join(''); let expectedOffsetsArr = [ [0], @@ -867,10 +867,10 @@ suite('viewLineRenderer.renderLine 2', () => { null, [ '', - '\u00b7\u00b7\u00b7\u00b7', + '\u00b7\u00b7\u00b7\u00b7', 'He', 'llo\u00a0world!', - '\u00b7\u00b7\u00b7\u00b7', + '\u00b7\u00b7\u00b7\u00b7', '', ].join('') ); @@ -889,12 +889,12 @@ suite('viewLineRenderer.renderLine 2', () => { null, [ '', - '\u00b7\u00b7\u00b7\u00b7', - '\u00b7\u00b7\u00b7\u00b7', + '\u00b7\u00b7\u00b7\u00b7', + '\u00b7\u00b7\u00b7\u00b7', 'He', 'llo\u00a0world!', - '\u00b7\u00b7\u00b7\u00b7', - '\u00b7\u00b7\u00b7\u00b7', + '\u00b7\u00b7\u00b7\u00b7', + '\u00b7\u00b7\u00b7\u00b7', '', ].join('') ); @@ -913,11 +913,11 @@ suite('viewLineRenderer.renderLine 2', () => { null, [ '', - '\u2192\u00a0\u00a0\u00a0', - '\u2192\u00a0\u00a0\u00a0', + '\u2192\u00a0\u00a0\u00a0', + '\u2192\u00a0\u00a0\u00a0', 'He', 'llo\u00a0world!', - '\u2192\u00a0\u00a0\u00a0', + '\u2192\u00a0\u00a0\u00a0', '', ].join('') ); @@ -936,15 +936,15 @@ suite('viewLineRenderer.renderLine 2', () => { null, [ '', - '\u00b7\u00b7\u2192\u00a0', - '\u2192\u00a0\u00a0\u00a0', - '\u00b7\u00b7', + '\u00b7\u00b7\u2192\u00a0', + '\u2192\u00a0\u00a0\u00a0', + '\u00b7\u00b7', 'He', 'llo\u00a0world!', - '\u00b7\uffeb', - '\u00b7\u00b7\u2192\u00a0', - '\u00b7\u00b7\u00b7\uffeb', - '\u00b7\u00b7\u00b7\u00b7', + '\u00b7\uffeb', + '\u00b7\u00b7\u2192\u00a0', + '\u00b7\u00b7\u00b7\uffeb', + '\u00b7\u00b7\u00b7\u00b7', '', ].join('') ); @@ -965,13 +965,13 @@ suite('viewLineRenderer.renderLine 2', () => { [ '', '\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0', - '\u00b7\u00b7', + '\u00b7\u00b7', 'He', 'llo\u00a0world!', - '\u00b7\uffeb', - '\u00b7\u00b7\u2192\u00a0', - '\u00b7\u00b7\u00b7\uffeb', - '\u00b7\u00b7\u00b7\u00b7', + '\u00b7\uffeb', + '\u00b7\u00b7\u2192\u00a0', + '\u00b7\u00b7\u00b7\uffeb', + '\u00b7\u00b7\u00b7\u00b7', '', ].join('') ); @@ -1016,11 +1016,11 @@ suite('viewLineRenderer.renderLine 2', () => { [ '', 'it', - '\u00b7\u00b7', + '\u00b7\u00b7', 'it', '\u00a0', 'it', - '\u00b7\u00b7', + '\u00b7\u00b7', 'it', '', ].join('') @@ -1041,12 +1041,12 @@ suite('viewLineRenderer.renderLine 2', () => { null, [ '', - '\u00b7', + '\u00b7', 'Hel', 'lo', - '\u00b7', + '\u00b7', 'world!', - '\u2192\u00a0\u00a0', + '\u2192\u00a0\u00a0', '', ].join('') ); @@ -1088,12 +1088,12 @@ suite('viewLineRenderer.renderLine 2', () => { [new LineRange(0, 14)], [ '', - '\u00b7', + '\u00b7', 'Hel', 'lo', - '\u00b7', + '\u00b7', 'world!', - '\u2192\u00a0\u00a0', + '\u2192\u00a0\u00a0', '', ].join('') ); @@ -1113,7 +1113,7 @@ suite('viewLineRenderer.renderLine 2', () => { [new LineRange(0, 5)], [ '', - '\u00b7', + '\u00b7', 'Hel', 'lo', '\u00a0world!\u00a0\u00a0\u00a0', @@ -1137,11 +1137,11 @@ suite('viewLineRenderer.renderLine 2', () => { [new LineRange(0, 5), new LineRange(9, 14)], [ '', - '\u00b7', + '\u00b7', 'Hel', 'lo', '\u00a0world!', - '\u2192\u00a0\u00a0', + '\u2192\u00a0\u00a0', '', ].join('') ); @@ -1162,11 +1162,11 @@ suite('viewLineRenderer.renderLine 2', () => { [new LineRange(9, 14), new LineRange(0, 5)], [ '', - '\u00b7', + '\u00b7', 'Hel', 'lo', '\u00a0world!', - '\u2192\u00a0\u00a0', + '\u2192\u00a0\u00a0', '', ].join('') ); @@ -1184,9 +1184,9 @@ suite('viewLineRenderer.renderLine 2', () => { [new LineRange(0, 1), new LineRange(1, 2), new LineRange(2, 3)], [ '', - '\u00b7', + '\u00b7', '*', - '\u00b7', + '\u00b7', 'S', '', ].join('') @@ -1293,7 +1293,7 @@ suite('viewLineRenderer.renderLine 2', () => { let expected = [ '', - '\u2192\u00a0\u00a0\u00a0', + '\u2192\u00a0\u00a0\u00a0', 'b', 'la', '' diff --git a/src/vs/editor/test/common/viewLayout/whitespaceComputer.test.ts b/src/vs/editor/test/common/viewLayout/whitespaceComputer.test.ts deleted file mode 100644 index 0a26122441a7..000000000000 --- a/src/vs/editor/test/common/viewLayout/whitespaceComputer.test.ts +++ /dev/null @@ -1,558 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; -import { WhitespaceComputer } from 'vs/editor/common/viewLayout/whitespaceComputer'; - -suite('Editor ViewLayout - WhitespaceComputer', () => { - - test('WhitespaceComputer', () => { - - let whitespaceComputer = new WhitespaceComputer(); - - // Insert a whitespace after line number 2, of height 10 - let a = whitespaceComputer.insertWhitespace(2, 0, 10, 0); - // whitespaces: a(2, 10) - assert.equal(whitespaceComputer.getCount(), 1); - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(0), 2); - assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(0), 10); - assert.equal(whitespaceComputer.getAccumulatedHeight(0), 10); - assert.equal(whitespaceComputer.getTotalHeight(), 10); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(1), 0); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(2), 0); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(3), 10); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(4), 10); - - // Insert a whitespace again after line number 2, of height 20 - let b = whitespaceComputer.insertWhitespace(2, 0, 20, 0); - // whitespaces: a(2, 10), b(2, 20) - assert.equal(whitespaceComputer.getCount(), 2); - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(0), 2); - assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(0), 10); - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(1), 2); - assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(1), 20); - assert.equal(whitespaceComputer.getAccumulatedHeight(0), 10); - assert.equal(whitespaceComputer.getAccumulatedHeight(1), 30); - assert.equal(whitespaceComputer.getTotalHeight(), 30); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(1), 0); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(2), 0); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(3), 30); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(4), 30); - - // Change last inserted whitespace height to 30 - whitespaceComputer.changeWhitespaceHeight(b, 30); - // whitespaces: a(2, 10), b(2, 30) - assert.equal(whitespaceComputer.getCount(), 2); - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(0), 2); - assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(0), 10); - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(1), 2); - assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(1), 30); - assert.equal(whitespaceComputer.getAccumulatedHeight(0), 10); - assert.equal(whitespaceComputer.getAccumulatedHeight(1), 40); - assert.equal(whitespaceComputer.getTotalHeight(), 40); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(1), 0); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(2), 0); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(3), 40); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(4), 40); - - // Remove last inserted whitespace - whitespaceComputer.removeWhitespace(b); - // whitespaces: a(2, 10) - assert.equal(whitespaceComputer.getCount(), 1); - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(0), 2); - assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(0), 10); - assert.equal(whitespaceComputer.getAccumulatedHeight(0), 10); - assert.equal(whitespaceComputer.getTotalHeight(), 10); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(1), 0); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(2), 0); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(3), 10); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(4), 10); - - // Add a whitespace before the first line of height 50 - b = whitespaceComputer.insertWhitespace(0, 0, 50, 0); - // whitespaces: b(0, 50), a(2, 10) - assert.equal(whitespaceComputer.getCount(), 2); - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(0), 0); - assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(0), 50); - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(1), 2); - assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(1), 10); - assert.equal(whitespaceComputer.getAccumulatedHeight(0), 50); - assert.equal(whitespaceComputer.getAccumulatedHeight(1), 60); - assert.equal(whitespaceComputer.getTotalHeight(), 60); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(1), 50); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(2), 50); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(3), 60); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(4), 60); - - // Add a whitespace after line 4 of height 20 - whitespaceComputer.insertWhitespace(4, 0, 20, 0); - // whitespaces: b(0, 50), a(2, 10), c(4, 20) - assert.equal(whitespaceComputer.getCount(), 3); - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(0), 0); - assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(0), 50); - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(1), 2); - assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(1), 10); - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(2), 4); - assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(2), 20); - assert.equal(whitespaceComputer.getAccumulatedHeight(0), 50); - assert.equal(whitespaceComputer.getAccumulatedHeight(1), 60); - assert.equal(whitespaceComputer.getAccumulatedHeight(2), 80); - assert.equal(whitespaceComputer.getTotalHeight(), 80); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(1), 50); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(2), 50); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(3), 60); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(4), 60); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(5), 80); - - // Add a whitespace after line 3 of height 30 - whitespaceComputer.insertWhitespace(3, 0, 30, 0); - // whitespaces: b(0, 50), a(2, 10), d(3, 30), c(4, 20) - assert.equal(whitespaceComputer.getCount(), 4); - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(0), 0); - assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(0), 50); - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(1), 2); - assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(1), 10); - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(2), 3); - assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(2), 30); - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(3), 4); - assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(3), 20); - assert.equal(whitespaceComputer.getAccumulatedHeight(0), 50); - assert.equal(whitespaceComputer.getAccumulatedHeight(1), 60); - assert.equal(whitespaceComputer.getAccumulatedHeight(2), 90); - assert.equal(whitespaceComputer.getAccumulatedHeight(3), 110); - assert.equal(whitespaceComputer.getTotalHeight(), 110); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(1), 50); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(2), 50); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(3), 60); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(4), 90); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(5), 110); - - // Change whitespace after line 2 to height of 100 - whitespaceComputer.changeWhitespaceHeight(a, 100); - // whitespaces: b(0, 50), a(2, 100), d(3, 30), c(4, 20) - assert.equal(whitespaceComputer.getCount(), 4); - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(0), 0); - assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(0), 50); - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(1), 2); - assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(1), 100); - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(2), 3); - assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(2), 30); - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(3), 4); - assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(3), 20); - assert.equal(whitespaceComputer.getAccumulatedHeight(0), 50); - assert.equal(whitespaceComputer.getAccumulatedHeight(1), 150); - assert.equal(whitespaceComputer.getAccumulatedHeight(2), 180); - assert.equal(whitespaceComputer.getAccumulatedHeight(3), 200); - assert.equal(whitespaceComputer.getTotalHeight(), 200); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(1), 50); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(2), 50); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(3), 150); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(4), 180); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(5), 200); - - // Remove whitespace after line 2 - whitespaceComputer.removeWhitespace(a); - // whitespaces: b(0, 50), d(3, 30), c(4, 20) - assert.equal(whitespaceComputer.getCount(), 3); - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(0), 0); - assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(0), 50); - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(1), 3); - assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(1), 30); - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(2), 4); - assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(2), 20); - assert.equal(whitespaceComputer.getAccumulatedHeight(0), 50); - assert.equal(whitespaceComputer.getAccumulatedHeight(1), 80); - assert.equal(whitespaceComputer.getAccumulatedHeight(2), 100); - assert.equal(whitespaceComputer.getTotalHeight(), 100); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(1), 50); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(2), 50); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(3), 50); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(4), 80); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(5), 100); - - // Remove whitespace before line 1 - whitespaceComputer.removeWhitespace(b); - // whitespaces: d(3, 30), c(4, 20) - assert.equal(whitespaceComputer.getCount(), 2); - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(0), 3); - assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(0), 30); - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(1), 4); - assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(1), 20); - assert.equal(whitespaceComputer.getAccumulatedHeight(0), 30); - assert.equal(whitespaceComputer.getAccumulatedHeight(1), 50); - assert.equal(whitespaceComputer.getTotalHeight(), 50); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(1), 0); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(2), 0); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(3), 0); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(4), 30); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(5), 50); - - // Delete line 1 - whitespaceComputer.onLinesDeleted(1, 1); - // whitespaces: d(2, 30), c(3, 20) - assert.equal(whitespaceComputer.getCount(), 2); - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(0), 2); - assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(0), 30); - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(1), 3); - assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(1), 20); - assert.equal(whitespaceComputer.getAccumulatedHeight(0), 30); - assert.equal(whitespaceComputer.getAccumulatedHeight(1), 50); - assert.equal(whitespaceComputer.getTotalHeight(), 50); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(1), 0); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(2), 0); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(3), 30); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(4), 50); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(5), 50); - - // Insert a line before line 1 - whitespaceComputer.onLinesInserted(1, 1); - // whitespaces: d(3, 30), c(4, 20) - assert.equal(whitespaceComputer.getCount(), 2); - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(0), 3); - assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(0), 30); - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(1), 4); - assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(1), 20); - assert.equal(whitespaceComputer.getAccumulatedHeight(0), 30); - assert.equal(whitespaceComputer.getAccumulatedHeight(1), 50); - assert.equal(whitespaceComputer.getTotalHeight(), 50); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(1), 0); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(2), 0); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(3), 0); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(4), 30); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(5), 50); - - // Delete line 4 - whitespaceComputer.onLinesDeleted(4, 4); - // whitespaces: d(3, 30), c(3, 20) - assert.equal(whitespaceComputer.getCount(), 2); - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(0), 3); - assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(0), 30); - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(1), 3); - assert.equal(whitespaceComputer.getHeightForWhitespaceIndex(1), 20); - assert.equal(whitespaceComputer.getAccumulatedHeight(0), 30); - assert.equal(whitespaceComputer.getAccumulatedHeight(1), 50); - assert.equal(whitespaceComputer.getTotalHeight(), 50); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(1), 0); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(2), 0); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(3), 0); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(4), 50); - assert.equal(whitespaceComputer.getAccumulatedHeightBeforeLineNumber(5), 50); - }); - - test('WhitespaceComputer findInsertionIndex', () => { - - let makeArray = (size: number, fillValue: number) => { - let r: number[] = []; - for (let i = 0; i < size; i++) { - r[i] = fillValue; - } - return r; - }; - - let arr: number[]; - let ordinals: number[]; - - arr = []; - ordinals = makeArray(arr.length, 0); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 0, ordinals, 0), 0); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 1, ordinals, 0), 0); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 2, ordinals, 0), 0); - - arr = [1]; - ordinals = makeArray(arr.length, 0); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 0, ordinals, 0), 0); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 1, ordinals, 0), 1); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 2, ordinals, 0), 1); - - arr = [1, 3]; - ordinals = makeArray(arr.length, 0); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 0, ordinals, 0), 0); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 1, ordinals, 0), 1); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 2, ordinals, 0), 1); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 3, ordinals, 0), 2); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 4, ordinals, 0), 2); - - arr = [1, 3, 5]; - ordinals = makeArray(arr.length, 0); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 0, ordinals, 0), 0); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 1, ordinals, 0), 1); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 2, ordinals, 0), 1); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 3, ordinals, 0), 2); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 4, ordinals, 0), 2); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 5, ordinals, 0), 3); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 6, ordinals, 0), 3); - - arr = [1, 3, 5]; - ordinals = makeArray(arr.length, 3); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 0, ordinals, 0), 0); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 1, ordinals, 0), 0); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 2, ordinals, 0), 1); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 3, ordinals, 0), 1); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 4, ordinals, 0), 2); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 5, ordinals, 0), 2); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 6, ordinals, 0), 3); - - arr = [1, 3, 5, 7]; - ordinals = makeArray(arr.length, 0); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 0, ordinals, 0), 0); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 1, ordinals, 0), 1); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 2, ordinals, 0), 1); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 3, ordinals, 0), 2); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 4, ordinals, 0), 2); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 5, ordinals, 0), 3); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 6, ordinals, 0), 3); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 7, ordinals, 0), 4); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 8, ordinals, 0), 4); - - arr = [1, 3, 5, 7, 9]; - ordinals = makeArray(arr.length, 0); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 0, ordinals, 0), 0); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 1, ordinals, 0), 1); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 2, ordinals, 0), 1); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 3, ordinals, 0), 2); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 4, ordinals, 0), 2); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 5, ordinals, 0), 3); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 6, ordinals, 0), 3); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 7, ordinals, 0), 4); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 8, ordinals, 0), 4); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 9, ordinals, 0), 5); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 10, ordinals, 0), 5); - - arr = [1, 3, 5, 7, 9, 11]; - ordinals = makeArray(arr.length, 0); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 0, ordinals, 0), 0); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 1, ordinals, 0), 1); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 2, ordinals, 0), 1); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 3, ordinals, 0), 2); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 4, ordinals, 0), 2); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 5, ordinals, 0), 3); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 6, ordinals, 0), 3); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 7, ordinals, 0), 4); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 8, ordinals, 0), 4); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 9, ordinals, 0), 5); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 10, ordinals, 0), 5); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 11, ordinals, 0), 6); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 12, ordinals, 0), 6); - - arr = [1, 3, 5, 7, 9, 11, 13]; - ordinals = makeArray(arr.length, 0); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 0, ordinals, 0), 0); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 1, ordinals, 0), 1); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 2, ordinals, 0), 1); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 3, ordinals, 0), 2); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 4, ordinals, 0), 2); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 5, ordinals, 0), 3); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 6, ordinals, 0), 3); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 7, ordinals, 0), 4); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 8, ordinals, 0), 4); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 9, ordinals, 0), 5); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 10, ordinals, 0), 5); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 11, ordinals, 0), 6); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 12, ordinals, 0), 6); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 13, ordinals, 0), 7); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 14, ordinals, 0), 7); - - arr = [1, 3, 5, 7, 9, 11, 13, 15]; - ordinals = makeArray(arr.length, 0); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 0, ordinals, 0), 0); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 1, ordinals, 0), 1); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 2, ordinals, 0), 1); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 3, ordinals, 0), 2); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 4, ordinals, 0), 2); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 5, ordinals, 0), 3); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 6, ordinals, 0), 3); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 7, ordinals, 0), 4); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 8, ordinals, 0), 4); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 9, ordinals, 0), 5); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 10, ordinals, 0), 5); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 11, ordinals, 0), 6); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 12, ordinals, 0), 6); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 13, ordinals, 0), 7); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 14, ordinals, 0), 7); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 15, ordinals, 0), 8); - assert.equal(WhitespaceComputer.findInsertionIndex(arr, 16, ordinals, 0), 8); - }); - - test('WhitespaceComputer changeWhitespaceAfterLineNumber & getFirstWhitespaceIndexAfterLineNumber', () => { - let whitespaceComputer = new WhitespaceComputer(); - - let a = whitespaceComputer.insertWhitespace(0, 0, 1, 0); - let b = whitespaceComputer.insertWhitespace(7, 0, 1, 0); - let c = whitespaceComputer.insertWhitespace(3, 0, 1, 0); - - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(0), a); // 0 - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(0), 0); - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(1), c); // 3 - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(1), 3); - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(2), b); // 7 - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(2), 7); - - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(1), 1); // c - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(2), 1); // c - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(3), 1); // c - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(4), 2); // b - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(5), 2); // b - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(6), 2); // b - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(7), 2); // b - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(8), -1); // -- - - // Do not really move a - whitespaceComputer.changeWhitespaceAfterLineNumber(a, 1); - - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(0), a); // 1 - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(0), 1); - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(1), c); // 3 - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(1), 3); - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(2), b); // 7 - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(2), 7); - - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(1), 0); // a - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(2), 1); // c - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(3), 1); // c - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(4), 2); // b - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(5), 2); // b - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(6), 2); // b - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(7), 2); // b - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(8), -1); // -- - - - // Do not really move a - whitespaceComputer.changeWhitespaceAfterLineNumber(a, 2); - - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(0), a); // 2 - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(0), 2); - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(1), c); // 3 - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(1), 3); - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(2), b); // 7 - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(2), 7); - - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(1), 0); // a - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(2), 0); // a - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(3), 1); // c - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(4), 2); // b - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(5), 2); // b - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(6), 2); // b - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(7), 2); // b - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(8), -1); // -- - - - // Change a to conflict with c => a gets placed after c - whitespaceComputer.changeWhitespaceAfterLineNumber(a, 3); - - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(0), c); // 3 - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(0), 3); - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(1), a); // 3 - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(1), 3); - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(2), b); // 7 - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(2), 7); - - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(1), 0); // c - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(2), 0); // c - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(3), 0); // c - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(4), 2); // b - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(5), 2); // b - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(6), 2); // b - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(7), 2); // b - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(8), -1); // -- - - - // Make a no-op - whitespaceComputer.changeWhitespaceAfterLineNumber(c, 3); - - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(0), c); // 3 - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(0), 3); - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(1), a); // 3 - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(1), 3); - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(2), b); // 7 - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(2), 7); - - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(1), 0); // c - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(2), 0); // c - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(3), 0); // c - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(4), 2); // b - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(5), 2); // b - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(6), 2); // b - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(7), 2); // b - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(8), -1); // -- - - - - // Conflict c with b => c gets placed after b - whitespaceComputer.changeWhitespaceAfterLineNumber(c, 7); - - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(0), a); // 3 - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(0), 3); - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(1), b); // 7 - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(1), 7); - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(2), c); // 7 - assert.equal(whitespaceComputer.getAfterLineNumberForWhitespaceIndex(2), 7); - - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(1), 0); // a - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(2), 0); // a - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(3), 0); // a - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(4), 1); // b - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(5), 1); // b - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(6), 1); // b - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(7), 1); // b - assert.equal(whitespaceComputer.getFirstWhitespaceIndexAfterLineNumber(8), -1); // -- - }); - - - test('WhitespaceComputer Bug', () => { - let whitespaceComputer = new WhitespaceComputer(); - - let a = whitespaceComputer.insertWhitespace(0, 0, 1, 0); - let b = whitespaceComputer.insertWhitespace(7, 0, 1, 0); - - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(0), a); // 0 - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(1), b); // 7 - - let c = whitespaceComputer.insertWhitespace(3, 0, 1, 0); - - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(0), a); // 0 - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(1), c); // 3 - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(2), b); // 7 - - let d = whitespaceComputer.insertWhitespace(2, 0, 1, 0); - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(0), a); // 0 - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(1), d); // 2 - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(2), c); // 3 - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(3), b); // 7 - - let e = whitespaceComputer.insertWhitespace(8, 0, 1, 0); - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(0), a); // 0 - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(1), d); // 2 - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(2), c); // 3 - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(3), b); // 7 - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(4), e); // 8 - - let f = whitespaceComputer.insertWhitespace(11, 0, 1, 0); - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(0), a); // 0 - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(1), d); // 2 - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(2), c); // 3 - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(3), b); // 7 - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(4), e); // 8 - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(5), f); // 11 - - let g = whitespaceComputer.insertWhitespace(10, 0, 1, 0); - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(0), a); // 0 - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(1), d); // 2 - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(2), c); // 3 - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(3), b); // 7 - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(4), e); // 8 - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(5), g); // 10 - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(6), f); // 11 - - let h = whitespaceComputer.insertWhitespace(0, 0, 1, 0); - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(0), a); // 0 - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(1), h); // 0 - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(2), d); // 2 - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(3), c); // 3 - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(4), b); // 7 - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(5), e); // 8 - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(6), g); // 10 - assert.equal(whitespaceComputer.getIdForWhitespaceIndex(7), f); // 11 - }); -}); - diff --git a/src/vs/editor/test/common/viewModel/viewModelDecorations.test.ts b/src/vs/editor/test/common/viewModel/viewModelDecorations.test.ts index a09d7a668abd..f2f57e34fa4e 100644 --- a/src/vs/editor/test/common/viewModel/viewModelDecorations.test.ts +++ b/src/vs/editor/test/common/viewModel/viewModelDecorations.test.ts @@ -103,16 +103,6 @@ suite('ViewModelDecorations', () => { // view line 2: (1,14 -> 1,24) assert.deepEqual(inlineDecorations1, [ - { - range: new Range(1, 2, 2, 1), - inlineClassName: 'i-dec2', - type: InlineDecorationType.Regular - }, - { - range: new Range(2, 1, 2, 1), - inlineClassName: 'a-dec2', - type: InlineDecorationType.After - }, { range: new Range(1, 2, 2, 2), inlineClassName: 'i-dec3', @@ -124,7 +114,7 @@ suite('ViewModelDecorations', () => { type: InlineDecorationType.After }, { - range: new Range(1, 2, 4, 1), + range: new Range(1, 2, 3, 13), inlineClassName: 'i-dec4', type: InlineDecorationType.Regular }, @@ -164,7 +154,7 @@ suite('ViewModelDecorations', () => { type: InlineDecorationType.After }, { - range: new Range(2, 1, 4, 1), + range: new Range(2, 1, 3, 13), inlineClassName: 'i-dec8', type: InlineDecorationType.Regular }, @@ -199,7 +189,7 @@ suite('ViewModelDecorations', () => { type: InlineDecorationType.After }, { - range: new Range(2, 3, 4, 1), + range: new Range(2, 3, 3, 13), inlineClassName: 'i-dec11', type: InlineDecorationType.Regular }, @@ -228,30 +218,45 @@ suite('ViewModelDecorations', () => { // view line 3 (24 -> 36) assert.deepEqual(inlineDecorations2, [ { - range: new Range(1, 2, 4, 1), + range: new Range(1, 2, 3, 13), inlineClassName: 'i-dec4', type: InlineDecorationType.Regular }, + { + range: new Range(3, 13, 3, 13), + inlineClassName: 'a-dec4', + type: InlineDecorationType.After + }, { range: new Range(1, 2, 5, 8), inlineClassName: 'i-dec5', type: InlineDecorationType.Regular }, { - range: new Range(2, 1, 4, 1), + range: new Range(2, 1, 3, 13), inlineClassName: 'i-dec8', type: InlineDecorationType.Regular }, + { + range: new Range(3, 13, 3, 13), + inlineClassName: 'a-dec8', + type: InlineDecorationType.After + }, { range: new Range(2, 1, 5, 8), inlineClassName: 'i-dec9', type: InlineDecorationType.Regular }, { - range: new Range(2, 3, 4, 1), + range: new Range(2, 3, 3, 13), inlineClassName: 'i-dec11', type: InlineDecorationType.Regular }, + { + range: new Range(3, 13, 3, 13), + inlineClassName: 'a-dec11', + type: InlineDecorationType.After + }, { range: new Range(2, 3, 5, 8), inlineClassName: 'i-dec12', diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 24a87d587428..626de975fd7d 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -131,7 +131,7 @@ declare namespace monaco { * * @param value A string which represents an Uri (see `Uri#toString`). */ - static parse(value: string): Uri; + static parse(value: string, _strict?: boolean): Uri; /** * Creates a new Uri from a file system path, e.g. `c:\my\files`, * `/usr/home`, or `\\server\share\some\path`. @@ -802,6 +802,12 @@ declare namespace monaco { declare namespace monaco.editor { + export interface IDiffNavigator { + canNavigate(): boolean; + next(): void; + previous(): void; + dispose(): void; + } /** * Create a new editor under `domElement`. @@ -824,13 +830,6 @@ declare namespace monaco.editor { */ export function createDiffEditor(domElement: HTMLElement, options?: IDiffEditorConstructionOptions, override?: IEditorOverrideServices): IStandaloneDiffEditor; - export interface IDiffNavigator { - canNavigate(): boolean; - next(): void; - previous(): void; - dispose(): void; - } - export interface IDiffNavigatorOptions { readonly followsCaret?: boolean; readonly ignoreCharChanges?: boolean; @@ -1193,7 +1192,8 @@ declare namespace monaco.editor { * Position in the minimap to render the decoration. */ export enum MinimapPosition { - Inline = 1 + Inline = 1, + Gutter = 2 } export interface IDecorationOptions { @@ -2396,6 +2396,18 @@ declare namespace monaco.editor { * The secondary selections. */ readonly secondarySelections: Selection[]; + /** + * The model version id. + */ + readonly modelVersionId: number; + /** + * The old selections. + */ + readonly oldSelections: Selection[] | null; + /** + * The model version id the that `oldSelections` refer to. + */ + readonly oldModelVersionId: number; /** * Source of the call that caused the event. */ @@ -2470,6 +2482,12 @@ declare namespace monaco.editor { * Defaults to 0. */ cursorSurroundingLines?: number; + /** + * Controls when `cursorSurroundingLines` should be enforced + * Defaults to `default`, `cursorSurroundingLines` is not enforced when cursor position is changed + * by mouse. + */ + cursorSurroundingLinesStyle?: 'default' | 'all'; /** * Render last line number when the file ends with a newline. * Defaults to true. @@ -2536,7 +2554,7 @@ declare namespace monaco.editor { fixedOverflowWidgets?: boolean; /** * The number of vertical lanes the overview ruler should render. - * Defaults to 2. + * Defaults to 3. */ overviewRulerLanes?: number; /** @@ -2579,8 +2597,8 @@ declare namespace monaco.editor { */ fontLigatures?: boolean | string; /** - * Disable the use of `will-change` for the editor margin and lines layers. - * The usage of `will-change` acts as a hint for browsers to create an extra layer. + * Disable the use of `transform: translate3d(0px, 0px, 0px)` for the editor margin and lines layers. + * The usage of `transform: translate3d(0px, 0px, 0px)` acts as a hint for browsers to create an extra layer. * Defaults to false. */ disableLayerHinting?: boolean; @@ -2712,6 +2730,10 @@ declare namespace monaco.editor { * Defaults to 'auto'. It is best to leave this to 'auto'. */ accessibilitySupport?: 'auto' | 'off' | 'on'; + /** + * Controls the number of lines in the editor that can be read out by a screen reader + */ + accessibilityPageSize?: number; /** * Suggest options. */ @@ -2757,7 +2779,7 @@ declare namespace monaco.editor { * Enable auto indentation adjustment. * Defaults to false. */ - autoIndent?: boolean; + autoIndent?: 'none' | 'keep' | 'brackets' | 'advanced' | 'full'; /** * Enable format on type. * Defaults to false. @@ -3038,22 +3060,31 @@ declare namespace monaco.editor { */ seedSearchStringFromSelection?: boolean; /** - * Controls if Find in Selection flag is turned on when multiple lines of text are selected in the editor. + * Controls if Find in Selection flag is turned on in the editor. */ - autoFindInSelection?: boolean; + autoFindInSelection?: 'never' | 'always' | 'multiline'; addExtraSpaceOnTop?: boolean; } export type EditorFindOptions = Readonly>; + export type GoToLocationValues = 'peek' | 'gotoAndPeek' | 'goto'; + /** * Configuration options for go to location */ export interface IGotoLocationOptions { - /** - * Control how goto-command work when having multiple results. - */ - multiple?: 'peek' | 'gotoAndPeek' | 'goto'; + multiple?: GoToLocationValues; + multipleDefinitions?: GoToLocationValues; + multipleTypeDefinitions?: GoToLocationValues; + multipleDeclarations?: GoToLocationValues; + multipleImplementations?: GoToLocationValues; + multipleReferences?: GoToLocationValues; + alternativeDefinitionCommand?: string; + alternativeTypeDefinitionCommand?: string; + alternativeDeclarationCommand?: string; + alternativeImplementationCommand?: string; + alternativeReferenceCommand?: string; } export type GoToLocationOptions = Readonly>; @@ -3375,7 +3406,11 @@ declare namespace monaco.editor { /** * Overwrite word ends on accept. Default to false. */ - overwriteOnAccept?: boolean; + insertMode?: 'insert' | 'replace'; + /** + * Show a highlight when suggestion replaces or keep text after the cursor. Defaults to false. + */ + insertHighlight?: boolean; /** * Enable graceful matching. Defaults to true. */ @@ -3401,9 +3436,105 @@ declare namespace monaco.editor { */ maxVisibleSuggestions?: number; /** - * Names of suggestion types to filter. + * Show method-suggestions. + */ + showMethods?: boolean; + /** + * Show function-suggestions. + */ + showFunctions?: boolean; + /** + * Show constructor-suggestions. + */ + showConstructors?: boolean; + /** + * Show field-suggestions. + */ + showFields?: boolean; + /** + * Show variable-suggestions. + */ + showVariables?: boolean; + /** + * Show class-suggestions. + */ + showClasses?: boolean; + /** + * Show struct-suggestions. + */ + showStructs?: boolean; + /** + * Show interface-suggestions. + */ + showInterfaces?: boolean; + /** + * Show module-suggestions. + */ + showModules?: boolean; + /** + * Show property-suggestions. */ - filteredTypes?: Record; + showProperties?: boolean; + /** + * Show event-suggestions. + */ + showEvents?: boolean; + /** + * Show operator-suggestions. + */ + showOperators?: boolean; + /** + * Show unit-suggestions. + */ + showUnits?: boolean; + /** + * Show value-suggestions. + */ + showValues?: boolean; + /** + * Show constant-suggestions. + */ + showConstants?: boolean; + /** + * Show enum-suggestions. + */ + showEnums?: boolean; + /** + * Show enumMember-suggestions. + */ + showEnumMembers?: boolean; + /** + * Show keyword-suggestions. + */ + showKeywords?: boolean; + /** + * Show text-suggestions. + */ + showWords?: boolean; + /** + * Show color-suggestions. + */ + showColors?: boolean; + /** + * Show file-suggestions. + */ + showFiles?: boolean; + /** + * Show reference-suggestions. + */ + showReferences?: boolean; + /** + * Show folder-suggestions. + */ + showFolders?: boolean; + /** + * Show typeParameter-suggestions. + */ + showTypeParameters?: boolean; + /** + * Show snippet-suggestions. + */ + showSnippets?: boolean; } export type InternalSuggestOptions = Readonly>; @@ -4116,6 +4247,10 @@ declare namespace monaco.editor { * If the diff computation is not finished or the model is missing, will return null. */ getDiffLineInformationForModified(lineNumber: number): IDiffLineInformation | null; + /** + * Update the editor's options after the editor has been created. + */ + updateOptions(newOptions: IDiffEditorOptions): void; } export class FontInfo extends BareFontInfo { @@ -4769,7 +4904,10 @@ declare namespace monaco.languages { * *Note:* The range must be a [single line](#Range.isSingleLine) and it must * [contain](#Range.contains) the position at which completion has been [requested](#CompletionItemProvider.provideCompletionItems). */ - range: IRange; + range: IRange | { + insert: IRange; + replace: IRange; + }; /** * An optional set of characters that when pressed while this completion is active will accept it first and * then type that character. *Note* that all commit characters should have `length=1` and that superfluous @@ -4853,6 +4991,7 @@ declare namespace monaco.languages { diagnostics?: editor.IMarkerData[]; kind?: string; isPreferred?: boolean; + disabled?: string; } export interface CodeActionList extends IDisposable { @@ -5440,6 +5579,33 @@ declare namespace monaco.languages { resolveCodeLens?(model: editor.ITextModel, codeLens: CodeLens, token: CancellationToken): ProviderResult; } + export interface SemanticTokensLegend { + readonly tokenTypes: string[]; + readonly tokenModifiers: string[]; + } + + export interface SemanticTokens { + readonly resultId?: string; + readonly data: Uint32Array; + } + + export interface SemanticTokensEdit { + readonly start: number; + readonly deleteCount: number; + readonly data?: Uint32Array; + } + + export interface SemanticTokensEdits { + readonly resultId?: string; + readonly edits: SemanticTokensEdit[]; + } + + export interface SemanticTokensProvider { + getLegend(): SemanticTokensLegend; + provideSemanticTokens(model: editor.ITextModel, lastResultId: string | null, ranges: Range[] | null, token: CancellationToken): ProviderResult; + releaseSemanticTokens(resultId: string | undefined): void; + } + export interface ILanguageExtensionPoint { id: string; extensions?: string[]; diff --git a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts index c7bbf4f26ee4..210bcdefe655 100644 --- a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts +++ b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts @@ -16,6 +16,7 @@ import { ICommandAction, IMenu, IMenuActionOptions, MenuItemAction, SubmenuItemA import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { INotificationService } from 'vs/platform/notification/common/notification'; +import { ThemeIcon } from 'vs/platform/theme/common/themeService'; // The alternative key on all platforms is alt. On windows we also support shift as an alternative key #44136 class AlternativeKeyEmitter extends Emitter { @@ -148,7 +149,7 @@ export class MenuEntryActionViewItem extends ActionViewItem { @INotificationService protected _notificationService: INotificationService, @IContextMenuService _contextMenuService: IContextMenuService ) { - super(undefined, _action, { icon: !!(_action.class || _action.item.iconLocation), label: !_action.class && !_action.item.iconLocation }); + super(undefined, _action, { icon: !!(_action.class || _action.item.icon), label: !_action.class && !_action.item.icon }); this._altKey = AlternativeKeyEmitter.getInstance(_contextMenuService); } @@ -237,28 +238,45 @@ export class MenuEntryActionViewItem extends ActionViewItem { _updateItemClass(item: ICommandAction): void { this._itemClassDispose.value = undefined; - if (item.iconLocation) { - let iconClass: string; - - const iconPathMapKey = item.iconLocation.dark.toString(); - - if (MenuEntryActionViewItem.ICON_PATH_TO_CSS_RULES.has(iconPathMapKey)) { - iconClass = MenuEntryActionViewItem.ICON_PATH_TO_CSS_RULES.get(iconPathMapKey)!; - } else { - iconClass = ids.nextId(); - createCSSRule(`.icon.${iconClass}`, `background-image: ${asCSSUrl(item.iconLocation.light || item.iconLocation.dark)}`); - createCSSRule(`.vs-dark .icon.${iconClass}, .hc-black .icon.${iconClass}`, `background-image: ${asCSSUrl(item.iconLocation.dark)}`); - MenuEntryActionViewItem.ICON_PATH_TO_CSS_RULES.set(iconPathMapKey, iconClass); - } - - if (this.label) { - addClasses(this.label, 'icon', iconClass); + if (ThemeIcon.isThemeIcon(item.icon)) { + // theme icons + const iconClass = ThemeIcon.asClassName(item.icon); + if (this.label && iconClass) { + addClasses(this.label, iconClass); this._itemClassDispose.value = toDisposable(() => { if (this.label) { - removeClasses(this.label, 'icon', iconClass); + removeClasses(this.label, iconClass); } }); } + + } else if (item.icon) { + // icon path + let iconClass: string; + + if (item.icon?.dark?.scheme) { + + const iconPathMapKey = item.icon.dark.toString(); + + if (MenuEntryActionViewItem.ICON_PATH_TO_CSS_RULES.has(iconPathMapKey)) { + iconClass = MenuEntryActionViewItem.ICON_PATH_TO_CSS_RULES.get(iconPathMapKey)!; + } else { + iconClass = ids.nextId(); + createCSSRule(`.icon.${iconClass}`, `background-image: ${asCSSUrl(item.icon.light || item.icon.dark)}`); + createCSSRule(`.vs-dark .icon.${iconClass}, .hc-black .icon.${iconClass}`, `background-image: ${asCSSUrl(item.icon.dark)}`); + MenuEntryActionViewItem.ICON_PATH_TO_CSS_RULES.set(iconPathMapKey, iconClass); + } + + if (this.label) { + + addClasses(this.label, 'icon', iconClass); + this._itemClassDispose.value = toDisposable(() => { + if (this.label) { + removeClasses(this.label, 'icon', iconClass); + } + }); + } + } } } } @@ -304,17 +322,19 @@ export class LabeledMenuItemActionItem extends MenuEntryActionViewItem { dispose(this._labeledItemClassDispose); this._labeledItemClassDispose = undefined; - if (item.iconLocation) { + if (ThemeIcon.isThemeIcon(item.icon)) { + // TODO + } else if (item.icon) { let iconClass: string; - const iconPathMapKey = item.iconLocation.dark.toString(); + const iconPathMapKey = item.icon.dark.toString(); if (MenuEntryActionViewItem.ICON_PATH_TO_CSS_RULES.has(iconPathMapKey)) { iconClass = MenuEntryActionViewItem.ICON_PATH_TO_CSS_RULES.get(iconPathMapKey)!; } else { iconClass = ids.nextId(); - createCSSRule(`.icon.${iconClass}`, `background-image: ${asCSSUrl(item.iconLocation.light || item.iconLocation.dark)}`); - createCSSRule(`.vs-dark .icon.${iconClass}, .hc-black .icon.${iconClass}`, `background-image: ${asCSSUrl(item.iconLocation.dark)}`); + createCSSRule(`.icon.${iconClass}`, `background-image: ${asCSSUrl(item.icon.light || item.icon.dark)}`); + createCSSRule(`.vs-dark .icon.${iconClass}, .hc-black .icon.${iconClass}`, `background-image: ${asCSSUrl(item.icon.dark)}`); MenuEntryActionViewItem.ICON_PATH_TO_CSS_RULES.set(iconPathMapKey, iconClass); } diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 3849589d389c..af1fcb171068 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -5,13 +5,14 @@ import { Action } from 'vs/base/common/actions'; import { SyncDescriptor0, createSyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -import { IConstructorSignature2, createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IConstructorSignature2, createDecorator, BrandedService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindings, KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ICommandService, ICommandHandler, CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IDisposable } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; import { URI, UriComponents } from 'vs/base/common/uri'; +import { ThemeIcon } from 'vs/platform/theme/common/themeService'; export interface ILocalizedString { value: string; @@ -22,7 +23,7 @@ export interface ICommandAction { id: string; title: string | ILocalizedString; category?: string | ILocalizedString; - iconLocation?: { dark: URI; light?: URI; }; + icon?: { dark?: URI; light?: URI; } | ThemeIcon; precondition?: ContextKeyExpr; toggled?: ContextKeyExpr; } @@ -64,6 +65,7 @@ export const enum MenuId { DebugWatchContext, DebugToolBar, EditorContext, + EditorContextPeek, EditorTitle, EditorTitleContext, EmptyEditorGroupContext, @@ -95,6 +97,9 @@ export const enum MenuId { StatusBarWindowIndicatorMenu, TouchBarContext, TitleBarContext, + TunnelContext, + TunnelInline, + TunnelTitle, ViewItemContext, ViewTitle, ObjectExplorerItemContext, // {{SQL CARBON EDIT}} @@ -300,7 +305,13 @@ export class SyncActionDescriptor { private readonly _keybindingContext: ContextKeyExpr | undefined; private readonly _keybindingWeight: number | undefined; - constructor(ctor: IConstructorSignature2, + public static create(ctor: { new(id: string, label: string, ...services: Services): Action }, + id: string, label: string | undefined, keybindings?: IKeybindings, keybindingContext?: ContextKeyExpr, keybindingWeight?: number + ): SyncActionDescriptor { + return new SyncActionDescriptor(ctor as IConstructorSignature2, id, label, keybindings, keybindingContext, keybindingWeight); + } + + private constructor(ctor: IConstructorSignature2, id: string, label: string | undefined, keybindings?: IKeybindings, keybindingContext?: ContextKeyExpr, keybindingWeight?: number ) { this._id = id; diff --git a/src/vs/platform/actions/common/menuService.ts b/src/vs/platform/actions/common/menuService.ts index 9329a30998fe..2368d78c9a47 100644 --- a/src/vs/platform/actions/common/menuService.ts +++ b/src/vs/platform/actions/common/menuService.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { IMenu, IMenuActionOptions, IMenuItem, IMenuService, isIMenuItem, ISubmenuItem, MenuId, MenuItemAction, MenuRegistry, SubmenuItemAction } from 'vs/platform/actions/common/actions'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { IMenu, IMenuActionOptions, IMenuItem, IMenuService, isIMenuItem, ISubmenuItem, MenuId, MenuItemAction, MenuRegistry, SubmenuItemAction, ILocalizedString } from 'vs/platform/actions/common/actions'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { ContextKeyExpr, IContextKeyService, IContextKeyChangeEvent } from 'vs/platform/contextkey/common/contextkey'; @@ -27,24 +27,24 @@ export class MenuService implements IMenuService { type MenuItemGroup = [string, Array]; -class Menu extends Disposable implements IMenu { +class Menu implements IMenu { - private readonly _onDidChange = this._register(new Emitter()); + private readonly _onDidChange = new Emitter(); + private readonly _dispoables = new DisposableStore(); - private _menuGroups!: MenuItemGroup[]; - private _contextKeys!: Set; + private _menuGroups: MenuItemGroup[] = []; + private _contextKeys: Set = new Set(); constructor( private readonly _id: MenuId, @ICommandService private readonly _commandService: ICommandService, @IContextKeyService private readonly _contextKeyService: IContextKeyService ) { - super(); this._build(); // rebuild this menu whenever the menu registry reports an // event for this MenuId - this._register(Event.debounce( + this._dispoables.add(Event.debounce( Event.filter(MenuRegistry.onDidChangeMenu, menuId => menuId === this._id), () => { }, 50 @@ -52,18 +52,23 @@ class Menu extends Disposable implements IMenu { // when context keys change we need to check if the menu also // has changed - this._register(Event.debounce( + this._dispoables.add(Event.debounce( this._contextKeyService.onDidChangeContext, (last, event) => last || event.affectsSome(this._contextKeys), 50 )(e => e && this._onDidChange.fire(undefined), this)); } + dispose(): void { + this._dispoables.dispose(); + this._onDidChange.dispose(); + } + private _build(): void { // reset - this._menuGroups = []; - this._contextKeys = new Set(); + this._menuGroups.length = 0; + this._contextKeys.clear(); const menuItems = MenuRegistry.getMenuItems(this._id); @@ -106,7 +111,10 @@ class Menu extends Disposable implements IMenu { const activeActions: Array = []; for (const item of items) { if (this._contextKeyService.contextMatchesRules(item.when)) { - const action = isIMenuItem(item) ? new MenuItemAction(item.command, item.alt, options, this._contextKeyService, this._commandService) : new SubmenuItemAction(item); + const action = isIMenuItem(item) + ? new MenuItemAction(item.command, item.alt, options, this._contextKeyService, this._commandService) + : new SubmenuItemAction(item); + activeActions.push(action); } } @@ -125,7 +133,7 @@ class Menu extends Disposable implements IMenu { } } - private static _compareMenuItems(a: IMenuItem, b: IMenuItem): number { + private static _compareMenuItems(a: IMenuItem | ISubmenuItem, b: IMenuItem | ISubmenuItem): number { let aGroup = a.group; let bGroup = b.group; @@ -163,8 +171,15 @@ class Menu extends Disposable implements IMenu { } // sort on titles - const aTitle = typeof a.command.title === 'string' ? a.command.title : a.command.title.value; - const bTitle = typeof b.command.title === 'string' ? b.command.title : b.command.title.value; - return aTitle.localeCompare(bTitle); + return Menu._compareTitles( + isIMenuItem(a) ? a.command.title : a.title, + isIMenuItem(b) ? b.command.title : b.title + ); + } + + private static _compareTitles(a: string | ILocalizedString, b: string | ILocalizedString) { + const aStr = typeof a === 'string' ? a : a.value; + const bStr = typeof b === 'string' ? b : b.value; + return aStr.localeCompare(bStr); } } diff --git a/src/vs/platform/auth/common/auth.css b/src/vs/platform/auth/common/auth.css new file mode 100644 index 000000000000..09f75d98830e --- /dev/null +++ b/src/vs/platform/auth/common/auth.css @@ -0,0 +1,100 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +html { + height: 100%; +} + +body { + box-sizing: border-box; + min-height: 100%; + margin: 0; + padding: 15px 30px; + display: flex; + flex-direction: column; + color: white; + font-family: "Segoe UI","Helvetica Neue","Helvetica",Arial,sans-serif; + background-color: #373277; +} + +.branding { + background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+PGRlZnM+PHN0eWxlPi5pY29uLWNhbnZhcy10cmFuc3BhcmVudHtmaWxsOiNmNmY2ZjY7b3BhY2l0eTowO30uaWNvbi13aGl0ZXtmaWxsOiNmZmY7fTwvc3R5bGU+PC9kZWZzPjx0aXRsZT5CcmFuZFZpc3VhbFN0dWRpb0NvZGUyMDE3UlRXXzI0eF93aGl0ZV8yNHg8L3RpdGxlPjxwYXRoIGNsYXNzPSJpY29uLWNhbnZhcy10cmFuc3BhcmVudCIgZD0iTTI0LDBWMjRIMFYwWiIvPjxwYXRoIGNsYXNzPSJpY29uLXdoaXRlIiBkPSJNMjQsMi41VjIxLjVMMTgsMjQsMCwxOC41di0uNTYxbDE4LDEuNTQ1VjBaTTEsMTMuMTExLDQuMzg1LDEwLDEsNi44ODlsMS40MTgtLjgyN0w1Ljg1Myw4LjY1LDEyLDNsMywxLjQ1NlYxNS41NDRMMTIsMTcsNS44NTMsMTEuMzUsMi40MTksMTMuOTM5Wk03LjY0NCwxMCwxMiwxMy4yODNWNi43MTdaIi8+PC9zdmc+"); + background-size: 24px; + background-repeat: no-repeat; + background-position: left 50%; + padding-left: 36px; + font-size: 20px; + letter-spacing: -0.04rem; + font-weight: 400; + color: white; + text-decoration: none; +} + +.message-container { + flex-grow: 1; + display: flex; + align-items: center; + justify-content: center; + margin: 0 30px; +} + +.message { + font-weight: 300; + font-size: 1.3rem; +} + +body.error .message { + display: none; +} + +body.error .error-message { + display: block; +} + +.error-message { + display: none; + font-weight: 300; + font-size: 1.3rem; +} + +.error-text { + color: red; + font-size: 1rem; +} + +@font-face { + font-family: 'Segoe UI'; + src: url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/light/latest.eot"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/light/latest.eot?#iefix") format("embedded-opentype"); + src: local("Segoe UI Light"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/light/latest.woff2") format("woff2"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/light/latest.woff") format("woff"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/light/latest.ttf") format("truetype"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/light/latest.svg#web") format("svg"); + font-weight: 200 +} + +@font-face { + font-family: 'Segoe UI'; + src: url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/semilight/latest.eot"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/semilight/latest.eot?#iefix") format("embedded-opentype"); + src: local("Segoe UI Semilight"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/semilight/latest.woff2") format("woff2"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/semilight/latest.woff") format("woff"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/semilight/latest.ttf") format("truetype"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/semilight/latest.svg#web") format("svg"); + font-weight: 300 +} + +@font-face { + font-family: 'Segoe UI'; + src: url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/normal/latest.eot"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/normal/latest.eot?#iefix") format("embedded-opentype"); + src: local("Segoe UI"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/normal/latest.woff2") format("woff"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/normal/latest.woff") format("woff"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/normal/latest.ttf") format("truetype"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/normal/latest.svg#web") format("svg"); + font-weight: 400 +} + +@font-face { + font-family: 'Segoe UI'; + src: url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/semibold/latest.eot"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/semibold/latest.eot?#iefix") format("embedded-opentype"); + src: local("Segoe UI Semibold"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/semibold/latest.woff2") format("woff"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/semibold/latest.woff") format("woff"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/semibold/latest.ttf") format("truetype"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/semibold/latest.svg#web") format("svg"); + font-weight: 600 +} + +@font-face { + font-family: 'Segoe UI'; + src: url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/bold/latest.eot"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/bold/latest.eot?#iefix") format("embedded-opentype"); + src: local("Segoe UI Bold"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/bold/latest.woff2") format("woff"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/bold/latest.woff") format("woff"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/bold/latest.ttf") format("truetype"),url("https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/bold/latest.svg#web") format("svg"); + font-weight: 700 +} diff --git a/src/vs/platform/auth/common/auth.html b/src/vs/platform/auth/common/auth.html new file mode 100644 index 000000000000..8fe3e50e7b7c --- /dev/null +++ b/src/vs/platform/auth/common/auth.html @@ -0,0 +1,35 @@ + + + + + + + Azure Account - Sign In + + + + + + Visual Studio Code + +
+
+ You are signed in now and can close this page. +
+
+ An error occurred while signing in: +
+
+
+ + + diff --git a/src/vs/platform/auth/common/auth.ts b/src/vs/platform/auth/common/auth.ts index 466357e2936d..7ca970155da7 100644 --- a/src/vs/platform/auth/common/auth.ts +++ b/src/vs/platform/auth/common/auth.ts @@ -4,12 +4,15 @@ *--------------------------------------------------------------------------------------------*/ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { Event } from 'vs/base/common/event'; +import { Event, Emitter } from 'vs/base/common/event'; +import { URI } from 'vs/base/common/uri'; export const enum AuthTokenStatus { - Disabled = 'Disabled', - Inactive = 'Inactive', - Active = 'Active' + Initializing = 'Initializing', + SignedOut = 'SignedOut', + SignedIn = 'SignedIn', + SigningIn = 'SigningIn', + RefreshingToken = 'RefreshingToken' } export const IAuthTokenService = createDecorator('IAuthTokenService'); @@ -19,11 +22,10 @@ export interface IAuthTokenService { readonly status: AuthTokenStatus; readonly onDidChangeStatus: Event; + readonly _onDidGetCallback: Emitter; - getToken(): Promise; - updateToken(token: string): Promise; + getToken(): Promise; refreshToken(): Promise; - deleteToken(): Promise; - + login(): Promise; + logout(): Promise; } - diff --git a/src/vs/platform/auth/common/authTokenIpc.ts b/src/vs/platform/auth/common/authTokenIpc.ts index e6c0a1625011..7e6323b19296 100644 --- a/src/vs/platform/auth/common/authTokenIpc.ts +++ b/src/vs/platform/auth/common/authTokenIpc.ts @@ -22,9 +22,9 @@ export class AuthTokenChannel implements IServerChannel { switch (command) { case '_getInitialStatus': return Promise.resolve(this.service.status); case 'getToken': return this.service.getToken(); - case 'updateToken': return this.service.updateToken(args[0]); case 'refreshToken': return this.service.refreshToken(); - case 'deleteToken': return this.service.deleteToken(); + case 'login': return this.service.login(); + case 'logout': return this.service.logout(); } throw new Error('Invalid call'); } diff --git a/src/vs/platform/auth/electron-browser/authServer.ts b/src/vs/platform/auth/electron-browser/authServer.ts new file mode 100644 index 000000000000..aed2ccf204ea --- /dev/null +++ b/src/vs/platform/auth/electron-browser/authServer.ts @@ -0,0 +1,165 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as http from 'http'; +import * as url from 'url'; +import * as fs from 'fs'; +import * as net from 'net'; +import { getPathFromAmdModule } from 'vs/base/common/amd'; +import { assertIsDefined } from 'vs/base/common/types'; + +interface Deferred { + resolve: (result: T | Promise) => void; + reject: (reason: any) => void; +} + +export function createTerminateServer(server: http.Server) { + const sockets: Record = {}; + let socketCount = 0; + server.on('connection', socket => { + const id = socketCount++; + sockets[id] = socket; + socket.on('close', () => { + delete sockets[id]; + }); + }); + return async () => { + const result = new Promise(resolve => server.close(resolve)); + for (const id in sockets) { + sockets[id].destroy(); + } + return result; + }; +} + +export async function startServer(server: http.Server): Promise { + let portTimer: NodeJS.Timer; + + function cancelPortTimer() { + clearTimeout(portTimer); + } + + const port = new Promise((resolve, reject) => { + portTimer = setTimeout(() => { + reject(new Error('Timeout waiting for port')); + }, 5000); + + server.on('listening', () => { + const address = server.address(); + if (typeof address === 'string') { + resolve(address); + } else { + resolve(assertIsDefined(address).port.toString()); + } + }); + + server.on('error', err => { + reject(err); + }); + + server.on('close', () => { + reject(new Error('Closed')); + }); + + server.listen(0); + }); + + port.then(cancelPortTimer, cancelPortTimer); + return port; +} + +function sendFile(res: http.ServerResponse, filepath: string, contentType: string) { + fs.readFile(filepath, (err, body) => { + if (err) { + console.error(err); + res.writeHead(404); + res.end(); + } else { + res.writeHead(200, { + 'Content-Length': body.length, + 'Content-Type': contentType + }); + res.end(body); + } + }); +} + +async function callback(nonce: string, reqUrl: url.Url): Promise { + const query = reqUrl.query; + if (!query || typeof query === 'string') { + throw new Error('No query received.'); + } + + let error = query.error_description || query.error; + + if (!error) { + const state = (query.state as string) || ''; + const receivedNonce = (state.split(',')[1] || '').replace(/ /g, '+'); + if (receivedNonce !== nonce) { + error = 'Nonce does not match.'; + } + } + + const code = query.code as string; + if (!error && code) { + return code; + } + + throw new Error((error as string) || 'No code received.'); +} + +export function createServer(nonce: string) { + type RedirectResult = { req: http.IncomingMessage; res: http.ServerResponse; } | { err: any; res: http.ServerResponse; }; + let deferredRedirect: Deferred; + const redirectPromise = new Promise((resolve, reject) => deferredRedirect = { resolve, reject }); + + type CodeResult = { code: string; res: http.ServerResponse; } | { err: any; res: http.ServerResponse; }; + let deferredCode: Deferred; + const codePromise = new Promise((resolve, reject) => deferredCode = { resolve, reject }); + + const codeTimer = setTimeout(() => { + deferredCode.reject(new Error('Timeout waiting for code')); + }, 5 * 60 * 1000); + + function cancelCodeTimer() { + clearTimeout(codeTimer); + } + + const server = http.createServer(function (req, res) { + const reqUrl = url.parse(req.url!, /* parseQueryString */ true); + switch (reqUrl.pathname) { + case '/signin': + const receivedNonce = ((reqUrl.query.nonce as string) || '').replace(/ /g, '+'); + if (receivedNonce === nonce) { + deferredRedirect.resolve({ req, res }); + } else { + const err = new Error('Nonce does not match.'); + deferredRedirect.resolve({ err, res }); + } + break; + case '/': + sendFile(res, getPathFromAmdModule(require, '../common/auth.html'), 'text/html; charset=utf-8'); + break; + case '/auth.css': + sendFile(res, getPathFromAmdModule(require, '../common/auth.css'), 'text/css; charset=utf-8'); + break; + case '/callback': + deferredCode.resolve(callback(nonce, reqUrl) + .then(code => ({ code, res }), err => ({ err, res }))); + break; + default: + res.writeHead(404); + res.end(); + break; + } + }); + + codePromise.then(cancelCodeTimer, cancelCodeTimer); + return { + server, + redirectPromise, + codePromise + }; +} diff --git a/src/vs/platform/auth/electron-browser/authTokenService.ts b/src/vs/platform/auth/electron-browser/authTokenService.ts new file mode 100644 index 000000000000..e299ea0f4f75 --- /dev/null +++ b/src/vs/platform/auth/electron-browser/authTokenService.ts @@ -0,0 +1,276 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as crypto from 'crypto'; +import * as https from 'https'; +import { Event, Emitter } from 'vs/base/common/event'; +import { IAuthTokenService, AuthTokenStatus } from 'vs/platform/auth/common/auth'; +import { ICredentialsService } from 'vs/platform/credentials/common/credentials'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { URI } from 'vs/base/common/uri'; +import { generateUuid } from 'vs/base/common/uuid'; +import { shell } from 'electron'; +import { createServer, startServer } from 'vs/platform/auth/electron-browser/authServer'; +import { IProductService } from 'vs/platform/product/common/productService'; + +const SERVICE_NAME = 'VS Code'; +const ACCOUNT = 'MyAccount'; + +const activeDirectoryResourceId = 'https://management.core.windows.net/'; + +function toQuery(obj: any): string { + return Object.keys(obj).map(key => `${key}=${obj[key]}`).join('&'); +} + +function toBase64UrlEncoding(base64string: string) { + return base64string.replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_'); // Need to use base64url encoding +} + +export interface IToken { + expiresIn: string; // How long access token is valid, in seconds + expiresOn: string; // When the access token expires in epoch time + accessToken: string; + refreshToken: string; +} + +export class AuthTokenService extends Disposable implements IAuthTokenService { + _serviceBrand: undefined; + + private _status: AuthTokenStatus = AuthTokenStatus.Initializing; + get status(): AuthTokenStatus { return this._status; } + private _onDidChangeStatus: Emitter = this._register(new Emitter()); + readonly onDidChangeStatus: Event = this._onDidChangeStatus.event; + + public readonly _onDidGetCallback: Emitter = this._register(new Emitter()); + readonly onDidGetCallback: Event = this._onDidGetCallback.event; + + private _activeToken: IToken | undefined; + + constructor( + @ICredentialsService private readonly credentialsService: ICredentialsService, + @IProductService private readonly productService: IProductService + ) { + super(); + if (!this.productService.auth) { + return; + } + + this.credentialsService.getPassword(SERVICE_NAME, ACCOUNT).then(storedRefreshToken => { + if (storedRefreshToken) { + this.refresh(storedRefreshToken); + } else { + this.setStatus(AuthTokenStatus.SignedOut); + } + }); + } + + public async login(): Promise { + if (!this.productService.auth) { + throw new Error('Authentication is not configured.'); + } + + this.setStatus(AuthTokenStatus.SigningIn); + + const nonce = generateUuid(); + const { server, redirectPromise, codePromise } = createServer(nonce); + + try { + const port = await startServer(server); + shell.openExternal(`http://localhost:${port}/signin?nonce=${encodeURIComponent(nonce)}`); + + const redirectReq = await redirectPromise; + if ('err' in redirectReq) { + const { err, res } = redirectReq; + res.writeHead(302, { Location: `/?error=${encodeURIComponent(err && err.message || 'Unkown error')}` }); + res.end(); + throw err; + } + + const host = redirectReq.req.headers.host || ''; + const updatedPortStr = (/^[^:]+:(\d+)$/.exec(Array.isArray(host) ? host[0] : host) || [])[1]; + const updatedPort = updatedPortStr ? parseInt(updatedPortStr, 10) : port; + + const state = `${updatedPort},${encodeURIComponent(nonce)}`; + + const codeVerifier = toBase64UrlEncoding(crypto.randomBytes(32).toString('base64')); + const codeChallenge = toBase64UrlEncoding(crypto.createHash('sha256').update(codeVerifier).digest('base64')); + + let uri = URI.parse(this.productService.auth.loginUrl); + uri = uri.with({ + query: `response_type=code&client_id=${encodeURIComponent(this.productService.auth.clientId)}&redirect_uri=${this.productService.auth.redirectUrl}&state=${encodeURIComponent(state)}&resource=${activeDirectoryResourceId}&prompt=select_account&code_challenge_method=S256&code_challenge=${codeChallenge}` + }); + + await redirectReq.res.writeHead(302, { Location: uri.toString(true) }); + redirectReq.res.end(); + + const codeRes = await codePromise; + const res = codeRes.res; + + try { + if ('err' in codeRes) { + throw codeRes.err; + } + const token = await this.exchangeCodeForToken(codeRes.code, codeVerifier); + this.setToken(token); + res.writeHead(302, { Location: '/' }); + res.end(); + } catch (err) { + res.writeHead(302, { Location: `/?error=${encodeURIComponent(err && err.message || 'Unkown error')}` }); + res.end(); + } + } finally { + setTimeout(() => { + server.close(); + }, 5000); + } + + } + + public getToken(): Promise { + return Promise.resolve(this._activeToken?.accessToken); + } + + public async refreshToken(): Promise { + if (!this._activeToken) { + throw new Error('No token to refresh'); + } + + this.refresh(this._activeToken.refreshToken); + } + + private setToken(token: IToken) { + this._activeToken = token; + this.credentialsService.setPassword(SERVICE_NAME, ACCOUNT, token.refreshToken); + this.setStatus(AuthTokenStatus.SignedIn); + } + + private exchangeCodeForToken(code: string, codeVerifier: string): Promise { + return new Promise((resolve: (value: IToken) => void, reject) => { + try { + if (!this.productService.auth) { + throw new Error('Authentication is not configured.'); + } + + const postData = toQuery({ + grant_type: 'authorization_code', + code: code, + client_id: this.productService.auth?.clientId, + code_verifier: codeVerifier, + redirect_uri: this.productService.auth?.redirectUrl + }); + + const tokenUrl = URI.parse(this.productService.auth.tokenUrl); + + const post = https.request({ + host: tokenUrl.authority, + path: tokenUrl.path, + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Content-Length': postData.length + } + }, result => { + const buffer: Buffer[] = []; + result.on('data', (chunk: Buffer) => { + buffer.push(chunk); + }); + result.on('end', () => { + if (result.statusCode === 200) { + const json = JSON.parse(Buffer.concat(buffer).toString()); + resolve({ + expiresIn: json.access_token, + expiresOn: json.expires_on, + accessToken: json.access_token, + refreshToken: json.refresh_token + }); + } else { + reject(new Error('Bad!')); + } + }); + }); + + post.write(postData); + + post.end(); + post.on('error', err => { + reject(err); + }); + + } catch (e) { + reject(e); + } + }); + } + + private async refresh(refreshToken: string): Promise { + return new Promise((resolve, reject) => { + if (!this.productService.auth) { + throw new Error('Authentication is not configured.'); + } + + this.setStatus(AuthTokenStatus.RefreshingToken); + const postData = toQuery({ + refresh_token: refreshToken, + client_id: this.productService.auth?.clientId, + grant_type: 'refresh_token', + resource: activeDirectoryResourceId + }); + + const tokenUrl = URI.parse(this.productService.auth.tokenUrl); + + const post = https.request({ + host: tokenUrl.authority, + path: tokenUrl.path, + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Content-Length': postData.length + } + }, result => { + const buffer: Buffer[] = []; + result.on('data', (chunk: Buffer) => { + buffer.push(chunk); + }); + result.on('end', () => { + if (result.statusCode === 200) { + const json = JSON.parse(Buffer.concat(buffer).toString()); + this.setToken({ + expiresIn: json.access_token, + expiresOn: json.expires_on, + accessToken: json.access_token, + refreshToken: json.refresh_token + }); + resolve(); + } else { + reject(new Error('Refreshing token failed.')); + } + }); + }); + + post.write(postData); + + post.end(); + post.on('error', err => { + this.setStatus(AuthTokenStatus.SignedOut); + reject(err); + }); + }); + } + + async logout(): Promise { + await this.credentialsService.deletePassword(SERVICE_NAME, ACCOUNT); + this._activeToken = undefined; + this.setStatus(AuthTokenStatus.SignedOut); + } + + private setStatus(status: AuthTokenStatus): void { + if (this._status !== status) { + this._status = status; + this._onDidChangeStatus.fire(status); + } + } + +} + diff --git a/src/vs/platform/configuration/test/node/configurationService.test.ts b/src/vs/platform/configuration/test/node/configurationService.test.ts index 565e7851d1ef..70c333e69a8e 100644 --- a/src/vs/platform/configuration/test/node/configurationService.test.ts +++ b/src/vs/platform/configuration/test/node/configurationService.test.ts @@ -14,6 +14,8 @@ import * as uuid from 'vs/base/common/uuid'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; import { testFile } from 'vs/base/test/node/utils'; import { URI } from 'vs/base/common/uri'; +import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; +import { Event } from 'vs/base/common/event'; suite('ConfigurationService - Node', () => { @@ -94,10 +96,11 @@ suite('ConfigurationService - Node', () => { const service = new ConfigurationService(URI.file(res.testFile)); await service.initialize(); return new Promise((c, e) => { - const disposable = service.onDidChangeConfiguration(() => { + const disposable = Event.filter(service.onDidChangeConfiguration, e => e.source === ConfigurationTarget.USER)(async (e) => { disposable.dispose(); assert.equal(service.getValue('foo'), 'bar'); service.dispose(); + await res.cleanUp(); c(); }); fs.writeFileSync(res.testFile, '{ "foo": "bar" }'); diff --git a/src/vs/platform/contextkey/common/contextkey.ts b/src/vs/platform/contextkey/common/contextkey.ts index abe9e168aa10..df7ad84d1d65 100644 --- a/src/vs/platform/contextkey/common/contextkey.ts +++ b/src/vs/platform/contextkey/common/contextkey.ts @@ -640,7 +640,7 @@ export class ContextKeyAndExpr implements ContextKeyExpr { if (e instanceof ContextKeyOrExpr) { // Not allowed, because we don't have parens! - throw new Error(`It is not allowed to have an or expression here due to lack of parens!`); + throw new Error(`It is not allowed to have an or expression here due to lack of parens! For example "a && (b||c)" is not supported, use "(a&&b) || (a&&c)" instead.`); } expr.push(e); diff --git a/src/vs/platform/contextview/browser/contextView.ts b/src/vs/platform/contextview/browser/contextView.ts index c3e3f463f322..64981ed584ff 100644 --- a/src/vs/platform/contextview/browser/contextView.ts +++ b/src/vs/platform/contextview/browser/contextView.ts @@ -7,11 +7,11 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import { Event } from 'vs/base/common/event'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IContextMenuDelegate } from 'vs/base/browser/contextmenu'; -import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; +import { AnchorAlignment, IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; export const IContextViewService = createDecorator('contextViewService'); -export interface IContextViewService { +export interface IContextViewService extends IContextViewProvider { _serviceBrand: undefined; @@ -41,4 +41,4 @@ export interface IContextMenuService { showContextMenu(delegate: IContextMenuDelegate): void; onDidContextMenu: Event; // TODO@isidor these event should be removed once we get async context menus -} \ No newline at end of file +} diff --git a/src/vs/platform/debug/common/extensionHostDebugIpc.ts b/src/vs/platform/debug/common/extensionHostDebugIpc.ts index 1da4777adc53..89cf5f446ed5 100644 --- a/src/vs/platform/debug/common/extensionHostDebugIpc.ts +++ b/src/vs/platform/debug/common/extensionHostDebugIpc.ts @@ -102,8 +102,6 @@ export class ExtensionHostDebugChannelClient extends Disposable implements IExte } openExtensionDevelopmentHostWindow(args: string[], env: IProcessEnvironment): Promise { - // TODO@Isidor - //return this.channel.call('openExtensionDevelopmentHostWindow', [args, env]); - return Promise.resolve(); + return this.channel.call('openExtensionDevelopmentHostWindow', [args, env]); } } diff --git a/src/vs/platform/diagnostics/node/diagnosticsService.ts b/src/vs/platform/diagnostics/node/diagnosticsService.ts index 19f00a3fcefb..7bd6075d32e6 100644 --- a/src/vs/platform/diagnostics/node/diagnosticsService.ts +++ b/src/vs/platform/diagnostics/node/diagnosticsService.ts @@ -7,7 +7,7 @@ import { virtualMachineHint } from 'vs/base/node/id'; import { IMachineInfo, WorkspaceStats, WorkspaceStatItem, PerformanceInfo, SystemInfo, IRemoteDiagnosticInfo, IRemoteDiagnosticError, isRemoteDiagnosticError, IWorkspaceInformation } from 'vs/platform/diagnostics/common/diagnostics'; import { readdir, stat, exists, readFile } from 'fs'; import { join, basename } from 'vs/base/common/path'; -import { parse, ParseError } from 'vs/base/common/json'; +import { parse, ParseError, getNodeType } from 'vs/base/common/json'; import { listProcesses } from 'vs/base/node/ps'; import product from 'vs/platform/product/common/product'; import { repeat, pad } from 'vs/base/common/strings'; @@ -223,7 +223,7 @@ export function collectLaunchConfigs(folder: string): Promise('dialogService'); export interface IDialogOptions { @@ -240,23 +239,34 @@ export interface IFileDialogService { */ showSaveDialog(options: ISaveDialogOptions): Promise; + /** + * Shows a confirm dialog for saving 1-N files. + */ + showSaveConfirm(fileNamesOrResources: (string | URI)[]): Promise; + /** * Shows a open file dialog and returns the chosen file URI. */ showOpenDialog(options: IOpenDialogOptions): Promise; } +export const enum ConfirmResult { + SAVE, + DONT_SAVE, + CANCEL +} + const MAX_CONFIRM_FILES = 10; -export function getConfirmMessage(start: string, resourcesToConfirm: readonly URI[]): string { +export function getConfirmMessage(start: string, fileNamesOrResources: readonly (string | URI)[]): string { const message = [start]; message.push(''); - message.push(...resourcesToConfirm.slice(0, MAX_CONFIRM_FILES).map(r => basename(r))); + message.push(...fileNamesOrResources.slice(0, MAX_CONFIRM_FILES).map(fileNameOrResource => typeof fileNameOrResource === 'string' ? fileNameOrResource : basename(fileNameOrResource))); - if (resourcesToConfirm.length > MAX_CONFIRM_FILES) { - if (resourcesToConfirm.length - MAX_CONFIRM_FILES === 1) { + if (fileNamesOrResources.length > MAX_CONFIRM_FILES) { + if (fileNamesOrResources.length - MAX_CONFIRM_FILES === 1) { message.push(localize('moreFile', "...1 additional file not shown")); } else { - message.push(localize('moreFiles', "...{0} additional files not shown", resourcesToConfirm.length - MAX_CONFIRM_FILES)); + message.push(localize('moreFiles', "...{0} additional files not shown", fileNamesOrResources.length - MAX_CONFIRM_FILES)); } } diff --git a/src/vs/platform/download/common/downloadService.ts b/src/vs/platform/download/common/downloadService.ts index 3da98d5e1a77..2553aa542916 100644 --- a/src/vs/platform/download/common/downloadService.ts +++ b/src/vs/platform/download/common/downloadService.ts @@ -30,7 +30,7 @@ export class DownloadService implements IDownloadService { await this.fileService.writeFile(target, context.stream); } else { const message = await asText(context); - return Promise.reject(new Error(`Expected 200, got back ${context.res.statusCode} instead.\n\n${message}`)); + throw new Error(`Expected 200, got back ${context.res.statusCode} instead.\n\n${message}`); } } } diff --git a/src/vs/platform/driver/electron-main/driver.ts b/src/vs/platform/driver/electron-main/driver.ts index 85c811d7f5dc..f5d4c9247686 100644 --- a/src/vs/platform/driver/electron-main/driver.ts +++ b/src/vs/platform/driver/electron-main/driver.ts @@ -39,7 +39,7 @@ export class Driver implements IDriver, IWindowDriverRegistry { private options: IDriverOptions, @IWindowsMainService private readonly windowsMainService: IWindowsMainService, @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, - @IElectronMainService private readonly electronMainService: any // {{SQL CARBON EDIT}} remove interface, naster work around + @IElectronMainService private readonly electronMainService: IElectronMainService ) { } async registerWindowDriver(windowId: number): Promise { @@ -212,7 +212,7 @@ export async function serve( instantiationService: IInstantiationService ): Promise { const verbose = environmentService.driverVerbose; - const driver = instantiationService.createInstance(Driver, windowServer, { verbose }); + const driver = instantiationService.createInstance(Driver as any, windowServer, { verbose }) as Driver; // {{SQL CARBON EDIT}} strict-null-check...i guess? const windowDriverRegistryChannel = new WindowDriverRegistryChannel(driver); windowServer.registerChannel('windowDriverRegistry', windowDriverRegistryChannel); diff --git a/src/vs/platform/electron/electron-main/electronMainService.ts b/src/vs/platform/electron/electron-main/electronMainService.ts index fe42cd56727b..b50795b04571 100644 --- a/src/vs/platform/electron/electron-main/electronMainService.ts +++ b/src/vs/platform/electron/electron-main/electronMainService.ts @@ -10,7 +10,7 @@ import { INativeOpenWindowOptions } from 'vs/platform/windows/node/window'; import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { IOpenedWindow, OpenContext, IWindowOpenable, IOpenEmptyWindowOptions } from 'vs/platform/windows/common/windows'; import { INativeOpenDialogOptions } from 'vs/platform/dialogs/node/dialogs'; -import { isMacintosh, IProcessEnvironment } from 'vs/base/common/platform'; +import { isMacintosh } from 'vs/base/common/platform'; import { IElectronService } from 'vs/platform/electron/node/electron'; import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @@ -18,7 +18,6 @@ import { AddFirstParameterToFunctions } from 'vs/base/common/types'; import { IDialogMainService } from 'vs/platform/dialogs/electron-main/dialogs'; import { dirExists } from 'vs/base/node/pfs'; import { URI } from 'vs/base/common/uri'; -import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv'; import { ITelemetryData, ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; @@ -100,6 +99,7 @@ export class ElectronMainService implements IElectronMainService { cli: this.environmentService.args, forceNewWindow: options.forceNewWindow, forceReuseWindow: options.forceReuseWindow, + preferNewWindow: options.preferNewWindow, diffMode: options.diffMode, addMode: options.addMode, gotoLineMode: options.gotoLineMode, @@ -407,24 +407,6 @@ export class ElectronMainService implements IElectronMainService { //#endregion - //#region Debug - - // TODO@Isidor move into debug IPC channel (https://github.com/microsoft/vscode/issues/81060) - - async openExtensionDevelopmentHostWindow(windowId: number, args: string[], env: IProcessEnvironment): Promise { - const pargs = parseArgs(args, OPTIONS); - const extDevPaths = pargs.extensionDevelopmentPath; - if (extDevPaths) { - this.windowsMainService.openExtensionDevelopmentHostWindow(extDevPaths, { - context: OpenContext.API, - cli: pargs, - userEnv: Object.keys(env).length > 0 ? env : undefined - }); - } - } - - //#endregion - private windowById(windowId: number | undefined): ICodeWindow | undefined { if (typeof windowId !== 'number') { return undefined; diff --git a/src/vs/platform/electron/node/electron.ts b/src/vs/platform/electron/node/electron.ts index 32f10149ee91..2002a6495538 100644 --- a/src/vs/platform/electron/node/electron.ts +++ b/src/vs/platform/electron/node/electron.ts @@ -9,7 +9,6 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' import { IWindowOpenable, IOpenEmptyWindowOptions, IOpenedWindow } from 'vs/platform/windows/common/windows'; import { INativeOpenDialogOptions } from 'vs/platform/dialogs/node/dialogs'; import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; -import { IProcessEnvironment } from 'vs/base/common/platform'; import { INativeOpenWindowOptions } from 'vs/platform/windows/node/window'; export const IElectronService = createDecorator('electronService'); @@ -85,7 +84,4 @@ export interface IElectronService { // Connectivity resolveProxy(url: string): Promise; - - // Debug (TODO@Isidor move into debug IPC channel (https://github.com/microsoft/vscode/issues/81060) - openExtensionDevelopmentHostWindow(args: string[], env: IProcessEnvironment): Promise; } diff --git a/src/vs/platform/environment/common/environment.ts b/src/vs/platform/environment/common/environment.ts index 4a7dcf510998..c6136d7eefaa 100644 --- a/src/vs/platform/environment/common/environment.ts +++ b/src/vs/platform/environment/common/environment.ts @@ -5,6 +5,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { URI } from 'vs/base/common/uri'; +import { IUserHomeProvider } from 'vs/base/common/labels'; export interface ParsedArgs { _: string[]; @@ -91,6 +92,8 @@ export interface ParsedArgs { 'js-flags'?: string; 'disable-gpu'?: boolean; 'nolazy'?: boolean; + 'force-device-scale-factor'?: string; + 'force-renderer-accessibility'?: boolean; } export const IEnvironmentService = createDecorator('environmentService'); @@ -106,7 +109,7 @@ export interface IExtensionHostDebugParams extends IDebugParams { export const BACKUPS = 'Backups'; -export interface IEnvironmentService { +export interface IEnvironmentService extends IUserHomeProvider { _serviceBrand: undefined; @@ -119,8 +122,6 @@ export interface IEnvironmentService { userHome: string; userDataPath: string; - appNameLong: string; - appQuality?: string; appSettingsHome: URI; // user roaming data @@ -133,6 +134,7 @@ export interface IEnvironmentService { // sync resources userDataSyncLogResource: URI; settingsSyncPreviewResource: URI; + keybindingsSyncPreviewResource: URI; machineSettingsHome: URI; machineSettingsResource: URI; @@ -151,6 +153,7 @@ export interface IEnvironmentService { extensionsPath?: string; extensionDevelopmentLocationURI?: URI[]; extensionTestsLocationURI?: URI; + logExtensionHostCommunication?: boolean; debugExtensionHost: IExtensionHostDebugParams; diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index f8c031d79488..0eee4a872a6b 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -7,13 +7,10 @@ import * as minimist from 'vscode-minimist'; import * as os from 'os'; import { localize } from 'vs/nls'; import { ParsedArgs } from 'vs/platform/environment/common/environment'; -import { join } from 'vs/base/common/path'; -import { writeFileSync } from 'vs/base/node/pfs'; /** * This code is also used by standalone cli's. Avoid adding any other dependencies. */ - const helpCategories = { o: localize('optionsUpperCase', "Options"), e: localize('extensionsManagement', "Extensions Management"), @@ -129,6 +126,8 @@ export const OPTIONS: OptionDescriptions> = { 'inspect': { type: 'string' }, 'inspect-brk': { type: 'string' }, 'nolazy': { type: 'boolean' }, // node inspect + 'force-device-scale-factor': { type: 'string' }, + 'force-renderer-accessibility': { type: 'boolean' }, '_urls': { type: 'string[]' }, _: { type: 'string[]' } // main arguments @@ -170,7 +169,7 @@ export function parseArgs(args: string[], options: OptionDescriptions, err } } } - // remote aliases to avoid confusion + // remove aliases to avoid confusion const parsedArgs = minimist(args, { string, boolean, alias }); const cleanedArgs: any = {}; @@ -193,7 +192,7 @@ export function parseArgs(args: string[], options: OptionDescriptions, err delete parsedArgs[o.deprecates]; } - if (val) { + if (typeof val !== 'undefined') { if (o.type === 'string[]') { if (val && !Array.isArray(val)) { val = [val]; @@ -319,33 +318,3 @@ export function buildVersionMessage(version: string | undefined, commit: string return `${version || localize('unknownVersion', "Unknown version")}\n${commit || localize('unknownCommit', "Unknown commit")}\n${process.arch}`; } -export function addArg(argv: string[], ...args: string[]): string[] { - const endOfArgsMarkerIndex = argv.indexOf('--'); - if (endOfArgsMarkerIndex === -1) { - argv.push(...args); - } else { - // if the we have an argument "--" (end of argument marker) - // we cannot add arguments at the end. rather, we add - // arguments before the "--" marker. - argv.splice(endOfArgsMarkerIndex, 0, ...args); - } - - return argv; -} - -export function createWaitMarkerFile(verbose?: boolean): string | undefined { - const randomWaitMarkerPath = join(os.tmpdir(), Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 10)); - - try { - writeFileSync(randomWaitMarkerPath, ''); - if (verbose) { - console.log(`Marker file for --wait created: ${randomWaitMarkerPath}`); - } - return randomWaitMarkerPath; - } catch (err) { - if (verbose) { - console.error(`Failed to create marker file for --wait: ${err}`); - } - return undefined; - } -} diff --git a/src/vs/platform/environment/node/argvHelper.ts b/src/vs/platform/environment/node/argvHelper.ts index c215d1b996b4..98f36be4516f 100644 --- a/src/vs/platform/environment/node/argvHelper.ts +++ b/src/vs/platform/environment/node/argvHelper.ts @@ -69,3 +69,17 @@ export function parseCLIProcessArgv(processArgv: string[]): ParsedArgs { return parseAndValidate(args, true); } + +export function addArg(argv: string[], ...args: string[]): string[] { + const endOfArgsMarkerIndex = argv.indexOf('--'); + if (endOfArgsMarkerIndex === -1) { + argv.push(...args); + } else { + // if the we have an argument "--" (end of argument marker) + // we cannot add arguments at the end. rather, we add + // arguments before the "--" marker. + argv.splice(endOfArgsMarkerIndex, 0, ...args); + } + + return argv; +} diff --git a/src/vs/platform/environment/node/environmentService.ts b/src/vs/platform/environment/node/environmentService.ts index 7c168b9fd4be..da6a392b872a 100644 --- a/src/vs/platform/environment/node/environmentService.ts +++ b/src/vs/platform/environment/node/environmentService.ts @@ -102,10 +102,6 @@ export class EnvironmentService implements IEnvironmentService { return parseUserDataDir(this._args, process); } - get appNameLong(): string { return product.nameLong; } - - get appQuality(): string | undefined { return product.quality; } - @memoize get appSettingsHome(): URI { return URI.file(path.join(this.userDataPath, 'User')); } @@ -118,6 +114,9 @@ export class EnvironmentService implements IEnvironmentService { @memoize get settingsSyncPreviewResource(): URI { return resources.joinPath(this.userRoamingDataHome, '.settings.json'); } + @memoize + get keybindingsSyncPreviewResource(): URI { return resources.joinPath(this.userRoamingDataHome, '.keybindings.json'); } + @memoize get userDataSyncLogResource(): URI { return URI.file(path.join(this.logsPath, 'userDataSync.log')); } @@ -239,6 +238,8 @@ export class EnvironmentService implements IEnvironmentService { @memoize get debugExtensionHost(): IExtensionHostDebugParams { return parseExtensionHostPort(this._args, this.isBuilt); } + @memoize + get logExtensionHostCommunication(): boolean { return !!this.args.logExtensionHostCommunication; } get isBuilt(): boolean { return !process.env['VSCODE_DEV']; } get verbose(): boolean { return !!this._args.verbose; } diff --git a/src/vs/platform/environment/node/waitMarkerFile.ts b/src/vs/platform/environment/node/waitMarkerFile.ts new file mode 100644 index 000000000000..015468a87435 --- /dev/null +++ b/src/vs/platform/environment/node/waitMarkerFile.ts @@ -0,0 +1,28 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/** + * This code is also used by standalone cli's. Avoid adding dependencies to keep the size of the cli small. + */ +import * as path from 'vs/base/common/path'; +import * as os from 'os'; +import * as fs from 'fs'; + +export function createWaitMarkerFile(verbose?: boolean): string | undefined { + const randomWaitMarkerPath = path.join(os.tmpdir(), Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 10)); + + try { + fs.writeFileSync(randomWaitMarkerPath, ''); // use built-in fs to avoid dragging in more dependencies + if (verbose) { + console.log(`Marker file for --wait created: ${randomWaitMarkerPath}`); + } + return randomWaitMarkerPath; + } catch (err) { + if (verbose) { + console.error(`Failed to create marker file for --wait: ${err}`); + } + return undefined; + } +} diff --git a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts index 3dc618b9d600..acf92c6165b2 100644 --- a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts @@ -746,7 +746,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService { if (extension.assets.manifest) { return this.getAsset(extension.assets.manifest, {}, token) .then(asText) - .then(JSON.parse); + .then(text => text ? JSON.parse(text) : null); } return Promise.resolve(null); } @@ -756,7 +756,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService { if (asset) { return this.getAsset(asset[1]) .then(asText) - .then(JSON.parse); + .then(text => text ? JSON.parse(text) : null); } return Promise.resolve(null); } @@ -823,17 +823,6 @@ export class ExtensionGalleryService implements IExtensionGalleryService { } const message = getErrorMessage(err); - type GalleryServiceRequestErrorClassification = { - url: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; - cdn: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; - message: { classification: 'CallstackOrException', purpose: 'FeatureInsight' }; - }; - type GalleryServiceRequestErrorEvent = { - url: string; - cdn: boolean; - message: string; - }; - this.telemetryService.publicLog2('galleryService:requestError', { url, cdn: true, message }); type GalleryServiceCDNFallbackClassification = { url: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; message: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; @@ -845,15 +834,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService { this.telemetryService.publicLog2('galleryService:cdnFallback', { url, message }); const fallbackOptions = assign({}, options, { url: fallbackUrl }); - return this.requestService.request(fallbackOptions, token).then(undefined, err => { - if (isPromiseCanceledError(err)) { - return Promise.reject(err); - } - - const message = getErrorMessage(err); - this.telemetryService.publicLog2('galleryService:requestError', { url: fallbackUrl, cdn: false, message }); - return Promise.reject(err); - }); + return this.requestService.request(fallbackOptions, token); }); }); } diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index 157f7aae981a..6c622ea14ee9 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -186,6 +186,7 @@ export interface DidUninstallExtensionEvent { error?: string; } +export const INSTALL_ERROR_NOT_SUPPORTED = 'notsupported'; export const INSTALL_ERROR_MALICIOUS = 'malicious'; export const INSTALL_ERROR_INCOMPATIBLE = 'incompatible'; diff --git a/src/vs/platform/extensionManagement/node/extensionLifecycle.ts b/src/vs/platform/extensionManagement/node/extensionLifecycle.ts index 1bf0c687983b..01f899740972 100644 --- a/src/vs/platform/extensionManagement/node/extensionLifecycle.ts +++ b/src/vs/platform/extensionManagement/node/extensionLifecycle.ts @@ -98,11 +98,11 @@ export class ExtensionsLifecycle extends Disposable { // Catch all output coming from the process type Output = { data: string, format: string[] }; - extensionUninstallProcess.stdout.setEncoding('utf8'); - extensionUninstallProcess.stderr.setEncoding('utf8'); + extensionUninstallProcess.stdout!.setEncoding('utf8'); + extensionUninstallProcess.stderr!.setEncoding('utf8'); - const onStdout = Event.fromNodeEventEmitter(extensionUninstallProcess.stdout, 'data'); - const onStderr = Event.fromNodeEventEmitter(extensionUninstallProcess.stderr, 'data'); + const onStdout = Event.fromNodeEventEmitter(extensionUninstallProcess.stdout!, 'data'); + const onStderr = Event.fromNodeEventEmitter(extensionUninstallProcess.stderr!, 'data'); // Log output onStdout(data => this.logService.info(extension.identifier.id, extension.manifest.version, `post-${lifecycleType}`, data)); diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index 95bf99c4775d..bb8babc46733 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -153,7 +153,7 @@ export class ExtensionManagementService extends Disposable implements IExtension this.logService.trace('ExtensionManagementService#zip', extension.identifier.id); return this.collectFiles(extension) .then(files => zip(path.join(tmpdir(), generateUuid()), files)) - .then(path => URI.file(path)); + .then(path => URI.file(path)); } unzip(zipLocation: URI, type: ExtensionType): Promise { @@ -222,6 +222,16 @@ export class ExtensionManagementService extends Disposable implements IExtension } else if (semver.gt(existing.manifest.version, manifest.version)) { return this.uninstall(existing, true); } + } else { + // Remove the extension with same version if it is already uninstalled. + // Installing a VSIX extension shall replace the existing extension always. + return this.unsetUninstalledAndGetLocal(identifierWithVersion) + .then(existing => { + if (existing) { + return this.removeExtension(existing, 'existing').then(null, e => Promise.reject(new Error(nls.localize('restartCode', "Please restart Azure Data Studio before reinstalling {0}.", manifest.displayName || manifest.name)))); + } + return undefined; + }); } return undefined; }) @@ -390,7 +400,7 @@ export class ExtensionManagementService extends Disposable implements IExtension return this.setUninstalled(extension) .then(() => this.removeUninstalledExtension(extension) .then( - () => this.installFromGallery(galleryExtension), + () => this.installFromGallery(galleryExtension).then(), e => Promise.reject(new Error(nls.localize('removeError', "Error while removing the extension: {0}. Please Quit and Start VS Code before trying again.", toErrorMessage(e)))))); } return Promise.reject(new Error(nls.localize('Not a Marketplace extension', "Only Marketplace Extensions can be reinstalled"))); @@ -541,10 +551,10 @@ export class ExtensionManagementService extends Disposable implements IExtension .then(galleryResult => { const extensionsToInstall = galleryResult.firstPage; return Promise.all(extensionsToInstall.map(e => this.installFromGallery(e))) - .then(() => null, errors => this.rollback(extensionsToInstall).then(() => Promise.reject(errors), () => Promise.reject(errors))); + .then(undefined, errors => this.rollback(extensionsToInstall).then(() => Promise.reject(errors), () => Promise.reject(errors))); }); } - return null; + return undefined; // {{SQL CARBON EDIT}} strict-null-checks }); } } @@ -565,7 +575,7 @@ export class ExtensionManagementService extends Disposable implements IExtension .then(installed => { const extensionToUninstall = installed.filter(e => areSameExtensions(e.identifier, extension.identifier))[0]; if (extensionToUninstall) { - return this.checkForDependenciesAndUninstall(extensionToUninstall, installed).then(() => null, error => Promise.reject(this.joinErrors(error))); + return this.checkForDependenciesAndUninstall(extensionToUninstall, installed).then(undefined, error => Promise.reject(this.joinErrors(error))); } else { return Promise.reject(new Error(nls.localize('notInstalled', "Extension '{0}' is not installed.", extension.manifest.displayName || extension.manifest.name))); } diff --git a/src/vs/platform/extensions/common/extensions.ts b/src/vs/platform/extensions/common/extensions.ts index cf4112879412..f1e3b860ed38 100644 --- a/src/vs/platform/extensions/common/extensions.ts +++ b/src/vs/platform/extensions/common/extensions.ts @@ -103,6 +103,17 @@ export interface IWebviewEditor { }[]; } +export interface ICodeActionContributionAction { + readonly kind: string; + readonly title: string; + readonly description?: string; +} + +export interface ICodeActionContribution { + readonly languages: readonly string[]; + readonly actions: readonly ICodeActionContributionAction[]; +} + export interface IExtensionContributions { commands?: ICommand[]; configuration?: IConfiguration | IConfiguration[]; @@ -120,6 +131,7 @@ export interface IExtensionContributions { colors?: IColor[]; localizations?: ILocalization[]; readonly webviewEditors?: readonly IWebviewEditor[]; + readonly codeActions?: readonly ICodeActionContribution[]; } export type ExtensionKind = 'ui' | 'workspace' | 'web'; @@ -151,7 +163,7 @@ export interface IExtensionManifest { readonly activationEvents?: string[]; readonly extensionDependencies?: string[]; readonly extensionPack?: string[]; - readonly extensionKind?: ExtensionKind; + readonly extensionKind?: ExtensionKind | ExtensionKind[]; readonly contributes?: IExtensionContributions; readonly repository?: { url: string; }; readonly bugs?: { url: string; }; diff --git a/src/vs/platform/files/common/fileService.ts b/src/vs/platform/files/common/fileService.ts index 25f0036e3150..f962be772c72 100644 --- a/src/vs/platform/files/common/fileService.ts +++ b/src/vs/platform/files/common/fileService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable, IDisposable, toDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle'; -import { IFileService, IResolveFileOptions, FileChangesEvent, FileOperationEvent, IFileSystemProviderRegistrationEvent, IFileSystemProvider, IFileStat, IResolveFileResult, ICreateFileOptions, IFileSystemProviderActivationEvent, FileOperationError, FileOperationResult, FileOperation, FileSystemProviderCapabilities, FileType, toFileSystemProviderErrorCode, FileSystemProviderErrorCode, IStat, IFileStatWithMetadata, IResolveMetadataFileOptions, etag, hasReadWriteCapability, hasFileFolderCopyCapability, hasOpenReadWriteCloseCapability, toFileOperationResult, IFileSystemProviderWithOpenReadWriteCloseCapability, IFileSystemProviderWithFileReadWriteCapability, IResolveFileResultWithMetadata, IWatchOptions, IWriteFileOptions, IReadFileOptions, IFileStreamContent, IFileContent, ETAG_DISABLED } from 'vs/platform/files/common/files'; +import { IFileService, IResolveFileOptions, FileChangesEvent, FileOperationEvent, IFileSystemProviderRegistrationEvent, IFileSystemProvider, IFileStat, IResolveFileResult, ICreateFileOptions, IFileSystemProviderActivationEvent, FileOperationError, FileOperationResult, FileOperation, FileSystemProviderCapabilities, FileType, toFileSystemProviderErrorCode, FileSystemProviderErrorCode, IStat, IFileStatWithMetadata, IResolveMetadataFileOptions, etag, hasReadWriteCapability, hasFileFolderCopyCapability, hasOpenReadWriteCloseCapability, toFileOperationResult, IFileSystemProviderWithOpenReadWriteCloseCapability, IFileSystemProviderWithFileReadWriteCapability, IResolveFileResultWithMetadata, IWatchOptions, IWriteFileOptions, IReadFileOptions, IFileStreamContent, IFileContent, ETAG_DISABLED, hasFileReadStreamCapability, IFileSystemProviderWithFileReadStreamCapability, ensureFileSystemProviderError } from 'vs/platform/files/common/files'; import { URI } from 'vs/base/common/uri'; import { Event, Emitter } from 'vs/base/common/event'; import { isAbsolutePath, dirname, basename, joinPath, isEqual, isEqualOrParent } from 'vs/base/common/resources'; @@ -13,10 +13,13 @@ import { TernarySearchTree } from 'vs/base/common/map'; import { isNonEmptyArray, coalesce } from 'vs/base/common/arrays'; import { getBaseLabel } from 'vs/base/common/labels'; import { ILogService } from 'vs/platform/log/common/log'; -import { VSBuffer, VSBufferReadable, readableToBuffer, bufferToReadable, streamToBuffer, bufferToStream, VSBufferReadableStream, writeableBufferStream, VSBufferWriteableStream, isVSBufferReadableStream } from 'vs/base/common/buffer'; +import { VSBuffer, VSBufferReadable, readableToBuffer, bufferToReadable, streamToBuffer, bufferToStream, VSBufferReadableStream } from 'vs/base/common/buffer'; +import { isReadableStream, transform, ReadableStreamEvents, consumeReadableWithLimit, consumeStreamWithLimit } from 'vs/base/common/stream'; import { Queue } from 'vs/base/common/async'; import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cancellation'; import { Schemas } from 'vs/base/common/network'; +import { assign } from 'vs/base/common/objects'; +import { createReadStream } from 'vs/platform/files/common/io'; export class FileService extends Disposable implements IFileService { @@ -118,14 +121,24 @@ export class FileService extends Disposable implements IFileService { return provider; } - private async withReadWriteProvider(resource: URI): Promise { + private async withReadProvider(resource: URI): Promise { + const provider = await this.withProvider(resource); + + if (hasOpenReadWriteCloseCapability(provider) || hasReadWriteCapability(provider) || hasFileReadStreamCapability(provider)) { + return provider; + } + + throw new Error('Provider neither has FileReadWrite, FileReadStream nor FileOpenReadWriteClose capability which is needed for the read operation.'); + } + + private async withWriteProvider(resource: URI): Promise { const provider = await this.withProvider(resource); if (hasOpenReadWriteCloseCapability(provider) || hasReadWriteCapability(provider)) { return provider; } - throw new Error('Provider neither has FileReadWrite nor FileOpenReadWriteClose capability which is needed for the operation.'); + throw new Error('Provider neither has FileReadWrite nor FileOpenReadWriteClose capability which is needed for the write operation.'); } //#endregion @@ -154,7 +167,7 @@ export class FileService extends Disposable implements IFileService { } // Bubble up any other error as is - throw this.ensureError(error); + throw ensureFileSystemProviderError(error); } } @@ -196,16 +209,19 @@ export class FileService extends Disposable implements IFileService { }); } + private async toFileStat(provider: IFileSystemProvider, resource: URI, stat: IStat | { type: FileType } & Partial, siblings: number | undefined, resolveMetadata: boolean, recurse: (stat: IFileStat, siblings?: number) => boolean): Promise; + private async toFileStat(provider: IFileSystemProvider, resource: URI, stat: IStat, siblings: number | undefined, resolveMetadata: true, recurse: (stat: IFileStat, siblings?: number) => boolean): Promise; private async toFileStat(provider: IFileSystemProvider, resource: URI, stat: IStat | { type: FileType } & Partial, siblings: number | undefined, resolveMetadata: boolean, recurse: (stat: IFileStat, siblings?: number) => boolean): Promise { // convert to file stat const fileStat: IFileStat = { resource, name: getBaseLabel(resource), + isFile: (stat.type & FileType.File) !== 0, isDirectory: (stat.type & FileType.Directory) !== 0, isSymbolicLink: (stat.type & FileType.SymbolicLink) !== 0, - isReadonly: !!(provider.capabilities & FileSystemProviderCapabilities.Readonly), mtime: stat.mtime, + ctime: stat.ctime, size: stat.size, etag: etag({ mtime: stat.mtime, size: stat.size }) }; @@ -288,7 +304,7 @@ export class FileService extends Disposable implements IFileService { } async writeFile(resource: URI, bufferOrReadableOrStream: VSBuffer | VSBufferReadable | VSBufferReadableStream, options?: IWriteFileOptions): Promise { - const provider = this.throwIfFileSystemIsReadonly(await this.withReadWriteProvider(resource)); + const provider = this.throwIfFileSystemIsReadonly(await this.withWriteProvider(resource)); try { @@ -300,17 +316,29 @@ export class FileService extends Disposable implements IFileService { await this.mkdirp(provider, dirname(resource)); } - // write file: buffered - if (hasOpenReadWriteCloseCapability(provider)) { - await this.doWriteBuffered(provider, resource, bufferOrReadableOrStream instanceof VSBuffer ? bufferToReadable(bufferOrReadableOrStream) : bufferOrReadableOrStream); + // optimization: if the provider has unbuffered write capability and the data + // to write is a Readable, we consume up to 3 chunks and try to write the data + // unbuffered to reduce the overhead. If the Readable has more data to provide + // we continue to write buffered. + if (hasReadWriteCapability(provider) && !(bufferOrReadableOrStream instanceof VSBuffer)) { + if (isReadableStream(bufferOrReadableOrStream)) { + bufferOrReadableOrStream = await consumeStreamWithLimit(bufferOrReadableOrStream, data => VSBuffer.concat(data), 3); + } else { + bufferOrReadableOrStream = consumeReadableWithLimit(bufferOrReadableOrStream, data => VSBuffer.concat(data), 3); + } } - // write file: unbuffered - else { + // write file: unbuffered (only if data to write is a buffer, or the provider has no buffered write capability) + if (!hasOpenReadWriteCloseCapability(provider) || (hasReadWriteCapability(provider) && bufferOrReadableOrStream instanceof VSBuffer)) { await this.doWriteUnbuffered(provider, resource, bufferOrReadableOrStream); } + + // write file: buffered + else { + await this.doWriteBuffered(provider, resource, bufferOrReadableOrStream instanceof VSBuffer ? bufferToReadable(bufferOrReadableOrStream) : bufferOrReadableOrStream); + } } catch (error) { - throw new FileOperationError(localize('err.write', "Unable to write file ({0})", this.ensureError(error).toString()), toFileOperationResult(error), options); + throw new FileOperationError(localize('err.write', "Unable to write file ({0})", ensureFileSystemProviderError(error).toString()), toFileOperationResult(error), options); } return this.resolve(resource, { resolveMetadata: true }); @@ -333,7 +361,7 @@ export class FileService extends Disposable implements IFileService { // mtime and etag, we bail out to prevent dirty writing. // // First, we check for a mtime that is in the future before we do more checks. The assumption is - // that only the mtime is an indicator for a file that has changd on disk. + // that only the mtime is an indicator for a file that has changed on disk. // // Second, if the mtime has advanced, we compare the size of the file on disk with our previous // one using the etag() function. Relying only on the mtime check has prooven to produce false @@ -353,7 +381,16 @@ export class FileService extends Disposable implements IFileService { } async readFile(resource: URI, options?: IReadFileOptions): Promise { - const stream = await this.readFileStream(resource, options); + const provider = await this.withReadProvider(resource); + + const stream = await this.doReadAsFileStream(provider, resource, assign({ + // optimization: since we know that the caller does not + // care about buffering, we indicate this to the reader. + // this reduces all the overhead the buffered reading + // has (open, read, close) if the provider supports + // unbuffered reading. + preferUnbuffered: true + }, options || Object.create(null))); return { ...stream, @@ -362,7 +399,12 @@ export class FileService extends Disposable implements IFileService { } async readFileStream(resource: URI, options?: IReadFileOptions): Promise { - const provider = await this.withReadWriteProvider(resource); + const provider = await this.withReadProvider(resource); + + return this.doReadAsFileStream(provider, resource, options); + } + + private async doReadAsFileStream(provider: IFileSystemProviderWithFileReadWriteCapability | IFileSystemProviderWithOpenReadWriteCloseCapability | IFileSystemProviderWithFileReadStreamCapability, resource: URI, options?: IReadFileOptions & { preferUnbuffered?: boolean }): Promise { // install a cancellation token that gets cancelled // when any error occurs. this allows us to resolve @@ -389,14 +431,19 @@ export class FileService extends Disposable implements IFileService { let fileStreamPromise: Promise; - // read buffered - if (hasOpenReadWriteCloseCapability(provider)) { - fileStreamPromise = Promise.resolve(this.readFileBuffered(provider, resource, cancellableSource.token, options)); + // read unbuffered (only if either preferred, or the provider has no buffered read capability) + if (!(hasOpenReadWriteCloseCapability(provider) || hasFileReadStreamCapability(provider)) || (hasReadWriteCapability(provider) && options?.preferUnbuffered)) { + fileStreamPromise = this.readFileUnbuffered(provider, resource, options); } - // read unbuffered + // read streamed (always prefer over primitive buffered read) + else if (hasFileReadStreamCapability(provider)) { + fileStreamPromise = Promise.resolve(this.readFileStreamed(provider, resource, cancellableSource.token, options)); + } + + // read buffered else { - fileStreamPromise = this.readFileUnbuffered(provider, resource, options); + fileStreamPromise = Promise.resolve(this.readFileBuffered(provider, resource, cancellableSource.token, options)); } const [fileStat, fileStream] = await Promise.all([statPromise, fileStreamPromise]); @@ -406,74 +453,30 @@ export class FileService extends Disposable implements IFileService { value: fileStream }; } catch (error) { - throw new FileOperationError(localize('err.read', "Unable to read file ({0})", this.ensureError(error).toString()), toFileOperationResult(error), options); + throw new FileOperationError(localize('err.read', "Unable to read file ({0})", ensureFileSystemProviderError(error).toString()), toFileOperationResult(error), options); } } - private readFileBuffered(provider: IFileSystemProviderWithOpenReadWriteCloseCapability, resource: URI, token: CancellationToken, options?: IReadFileOptions): VSBufferReadableStream { - const stream = writeableBufferStream(); - - // do not await reading but simply return - // the stream directly since it operates - // via events. finally end the stream and - // send through the possible error - let error: Error | undefined = undefined; - this.doReadFileBuffered(provider, resource, stream, token, options).then(undefined, err => error = err).finally(() => stream.end(error)); + private readFileStreamed(provider: IFileSystemProviderWithFileReadStreamCapability, resource: URI, token: CancellationToken, options: IReadFileOptions = Object.create(null)): VSBufferReadableStream { + const fileStream = provider.readFileStream(resource, options, token); - return stream; + return this.transformFileReadStream(fileStream, options); } - private async doReadFileBuffered(provider: IFileSystemProviderWithOpenReadWriteCloseCapability, resource: URI, stream: VSBufferWriteableStream, token: CancellationToken, options?: IReadFileOptions): Promise { - - // open handle through provider - const handle = await provider.open(resource, { create: false }); - - try { - let totalBytesRead = 0; - let bytesRead = 0; - let allowedRemainingBytes = (options && typeof options.length === 'number') ? options.length : undefined; - - let buffer = VSBuffer.alloc(Math.min(this.BUFFER_SIZE, typeof allowedRemainingBytes === 'number' ? allowedRemainingBytes : this.BUFFER_SIZE)); - - let posInFile = options && typeof options.position === 'number' ? options.position : 0; - let posInBuffer = 0; - do { - // read from source (handle) at current position (pos) into buffer (buffer) at - // buffer position (posInBuffer) up to the size of the buffer (buffer.byteLength). - bytesRead = await provider.read(handle, posInFile, buffer.buffer, posInBuffer, buffer.byteLength - posInBuffer); - - posInFile += bytesRead; - posInBuffer += bytesRead; - totalBytesRead += bytesRead; + private readFileBuffered(provider: IFileSystemProviderWithOpenReadWriteCloseCapability, resource: URI, token: CancellationToken, options: IReadFileOptions = Object.create(null)): VSBufferReadableStream { + const fileStream = createReadStream(provider, resource, { + ...options, + bufferSize: this.BUFFER_SIZE + }, token); - if (typeof allowedRemainingBytes === 'number') { - allowedRemainingBytes -= bytesRead; - } - - // when buffer full, create a new one and emit it through stream - if (posInBuffer === buffer.byteLength) { - stream.write(buffer); - - buffer = VSBuffer.alloc(Math.min(this.BUFFER_SIZE, typeof allowedRemainingBytes === 'number' ? allowedRemainingBytes : this.BUFFER_SIZE)); - - posInBuffer = 0; - } - } while (bytesRead > 0 && (typeof allowedRemainingBytes !== 'number' || allowedRemainingBytes > 0) && this.throwIfCancelled(token) && this.throwIfTooLarge(totalBytesRead, options)); - - // wrap up with last buffer (also respect maxBytes if provided) - if (posInBuffer > 0) { - let lastChunkLength = posInBuffer; - if (typeof allowedRemainingBytes === 'number') { - lastChunkLength = Math.min(posInBuffer, allowedRemainingBytes); - } + return this.transformFileReadStream(fileStream, options); + } - stream.write(buffer.slice(0, lastChunkLength)); - } - } catch (error) { - throw this.ensureError(error); - } finally { - await provider.close(handle); - } + private transformFileReadStream(stream: ReadableStreamEvents, options: IReadFileOptions): VSBufferReadableStream { + return transform(stream, { + data: data => data instanceof VSBuffer ? data : VSBuffer.wrap(data), + error: error => new FileOperationError(localize('err.read', "Unable to read file ({0})", ensureFileSystemProviderError(error).toString()), toFileOperationResult(error), options) + }, data => VSBuffer.concat(data)); } private async readFileUnbuffered(provider: IFileSystemProviderWithFileReadWriteCapability, resource: URI, options?: IReadFileOptions): Promise { @@ -489,34 +492,47 @@ export class FileService extends Disposable implements IFileService { buffer = buffer.slice(0, options.length); } + // Throw if file is too large to load + this.validateReadFileLimits(buffer.byteLength, options); + return bufferToStream(VSBuffer.wrap(buffer)); } private async validateReadFile(resource: URI, options?: IReadFileOptions): Promise { const stat = await this.resolve(resource, { resolveMetadata: true }); - // Return early if resource is a directory + // Throw if resource is a directory if (stat.isDirectory) { throw new FileOperationError(localize('fileIsDirectoryError', "Expected file {0} is actually a directory", this.resourceForError(resource)), FileOperationResult.FILE_IS_DIRECTORY, options); } - // Return early if file not modified since (unless disabled) + // Throw if file not modified since (unless disabled) if (options && typeof options.etag === 'string' && options.etag !== ETAG_DISABLED && options.etag === stat.etag) { throw new FileOperationError(localize('fileNotModifiedError', "File not modified since"), FileOperationResult.FILE_NOT_MODIFIED_SINCE, options); } - // Return early if file is too large to load + // Throw if file is too large to load + this.validateReadFileLimits(stat.size, options); + + return stat; + } + + private validateReadFileLimits(size: number, options?: IReadFileOptions): void { if (options?.limits) { - if (typeof options.limits.memory === 'number' && stat.size > options.limits.memory) { - throw new FileOperationError(localize('fileTooLargeForHeapError', "To open a file of this size, you need to restart and allow it to use more memory"), FileOperationResult.FILE_EXCEED_MEMORY_LIMIT); + let tooLargeErrorResult: FileOperationResult | undefined = undefined; + + if (typeof options.limits.memory === 'number' && size > options.limits.memory) { + tooLargeErrorResult = FileOperationResult.FILE_EXCEEDS_MEMORY_LIMIT; } - if (typeof options.limits.size === 'number' && stat.size > options.limits.size) { - throw new FileOperationError(localize('fileTooLargeError', "File is too large to open"), FileOperationResult.FILE_TOO_LARGE); + if (typeof options.limits.size === 'number' && size > options.limits.size) { + tooLargeErrorResult = FileOperationResult.FILE_TOO_LARGE; } - } - return stat; + if (typeof tooLargeErrorResult === 'number') { + throw new FileOperationError(localize('fileTooLargeError', "File is too large to open"), tooLargeErrorResult); + } + } } //#endregion @@ -524,8 +540,8 @@ export class FileService extends Disposable implements IFileService { //#region Move/Copy/Delete/Create Folder async move(source: URI, target: URI, overwrite?: boolean): Promise { - const sourceProvider = this.throwIfFileSystemIsReadonly(await this.withReadWriteProvider(source)); - const targetProvider = this.throwIfFileSystemIsReadonly(await this.withReadWriteProvider(target)); + const sourceProvider = this.throwIfFileSystemIsReadonly(await this.withWriteProvider(source)); + const targetProvider = this.throwIfFileSystemIsReadonly(await this.withWriteProvider(target)); // move const mode = await this.doMoveCopy(sourceProvider, source, targetProvider, target, 'move', !!overwrite); @@ -538,8 +554,8 @@ export class FileService extends Disposable implements IFileService { } async copy(source: URI, target: URI, overwrite?: boolean): Promise { - const sourceProvider = await this.withReadWriteProvider(source); - const targetProvider = this.throwIfFileSystemIsReadonly(await this.withReadWriteProvider(target)); + const sourceProvider = await this.withReadProvider(source); + const targetProvider = this.throwIfFileSystemIsReadonly(await this.withWriteProvider(target)); // copy const mode = await this.doMoveCopy(sourceProvider, source, targetProvider, target, 'copy', !!overwrite); @@ -551,7 +567,7 @@ export class FileService extends Disposable implements IFileService { return fileStat; } - private async doMoveCopy(sourceProvider: IFileSystemProviderWithFileReadWriteCapability | IFileSystemProviderWithOpenReadWriteCloseCapability, source: URI, targetProvider: IFileSystemProviderWithFileReadWriteCapability | IFileSystemProviderWithOpenReadWriteCloseCapability, target: URI, mode: 'move' | 'copy', overwrite: boolean): Promise<'move' | 'copy'> { + private async doMoveCopy(sourceProvider: IFileSystemProvider, source: URI, targetProvider: IFileSystemProvider, target: URI, mode: 'move' | 'copy', overwrite: boolean): Promise<'move' | 'copy'> { if (source.toString() === target.toString()) { return mode; // simulate node.js behaviour here and do a no-op if paths match } @@ -666,7 +682,7 @@ export class FileService extends Disposable implements IFileService { } if (!isSameResourceWithDifferentPathCase && isEqualOrParent(target, source, !isPathCaseSensitive)) { - throw new Error(localize('unableToMoveCopyError2', "Unable to move/copy when source is parent of target")); + throw new Error(localize('unableToMoveCopyError2', "Unable to move/copy when source is parent of target.")); } } @@ -676,7 +692,7 @@ export class FileService extends Disposable implements IFileService { // Bail out if target exists and we are not about to overwrite if (!overwrite) { - throw new FileOperationError(localize('unableToMoveCopyError3', "Unable to move/copy. File already exists at destination."), FileOperationResult.FILE_MOVE_CONFLICT); + throw new FileOperationError(localize('unableToMoveCopyError3', "Unable to move/copy since a file already exists at destination."), FileOperationResult.FILE_MOVE_CONFLICT); } // Special case: if the target is a parent of the source, we cannot delete @@ -684,7 +700,7 @@ export class FileService extends Disposable implements IFileService { if (sourceProvider === targetProvider) { const isPathCaseSensitive = !!(sourceProvider.capabilities & FileSystemProviderCapabilities.PathCaseSensitive); if (isEqualOrParent(source, target, !isPathCaseSensitive)) { - throw new Error(localize('unableToMoveCopyError4', "Unable to move/copy. File would replace folder it is contained in.")); + throw new Error(localize('unableToMoveCopyError4', "Unable to move/copy since a file would replace the folder it is contained in.")); } } } @@ -871,13 +887,13 @@ export class FileService extends Disposable implements IFileService { // write into handle until all bytes from buffer have been written try { - if (isVSBufferReadableStream(readableOrStream)) { + if (isReadableStream(readableOrStream)) { await this.doWriteStreamBufferedQueued(provider, handle, readableOrStream); } else { await this.doWriteReadableBufferedQueued(provider, handle, readableOrStream); } } catch (error) { - throw this.ensureError(error); + throw ensureFileSystemProviderError(error); } finally { // close handle always @@ -919,7 +935,7 @@ export class FileService extends Disposable implements IFileService { let posInFile = 0; let chunk: VSBuffer | null; - while (chunk = readable.read()) { + while ((chunk = readable.read()) !== null) { await this.doWriteBuffer(provider, handle, chunk, chunk.byteLength, posInFile, 0); posInFile += chunk.byteLength; @@ -942,7 +958,7 @@ export class FileService extends Disposable implements IFileService { let buffer: VSBuffer; if (bufferOrReadableOrStream instanceof VSBuffer) { buffer = bufferOrReadableOrStream; - } else if (isVSBufferReadableStream(bufferOrReadableOrStream)) { + } else if (isReadableStream(bufferOrReadableOrStream)) { buffer = await streamToBuffer(bufferOrReadableOrStream); } else { buffer = readableToBuffer(bufferOrReadableOrStream); @@ -988,7 +1004,7 @@ export class FileService extends Disposable implements IFileService { } } while (bytesRead > 0); } catch (error) { - throw this.ensureError(error); + throw ensureFileSystemProviderError(error); } finally { await Promise.all([ typeof sourceHandle === 'number' ? sourceProvider.close(sourceHandle) : Promise.resolve(), @@ -1019,7 +1035,7 @@ export class FileService extends Disposable implements IFileService { const buffer = await sourceProvider.readFile(source); await this.doWriteBuffer(targetProvider, targetHandle, VSBuffer.wrap(buffer), buffer.byteLength, 0, 0); } catch (error) { - throw this.ensureError(error); + throw ensureFileSystemProviderError(error); } finally { await targetProvider.close(targetHandle); } @@ -1042,38 +1058,6 @@ export class FileService extends Disposable implements IFileService { return provider; } - private throwIfCancelled(token: CancellationToken): boolean { - if (token.isCancellationRequested) { - throw new Error('cancelled'); - } - - return true; - } - - private ensureError(error?: Error): Error { - if (!error) { - return new Error(localize('unknownError', "Unknown Error")); // https://github.com/Microsoft/vscode/issues/72798 - } - - return error; - } - - private throwIfTooLarge(totalBytesRead: number, options?: IReadFileOptions): boolean { - - // Return early if file is too large to load - if (options?.limits) { - if (typeof options.limits.memory === 'number' && totalBytesRead > options.limits.memory) { - throw new FileOperationError(localize('fileTooLargeForHeapError', "To open a file of this size, you need to restart and allow it to use more memory"), FileOperationResult.FILE_EXCEED_MEMORY_LIMIT); - } - - if (typeof options.limits.size === 'number' && totalBytesRead > options.limits.size) { - throw new FileOperationError(localize('fileTooLargeError', "File is too large to open"), FileOperationResult.FILE_TOO_LARGE); - } - } - - return true; - } - private resourceForError(resource: URI): string { if (resource.scheme === Schemas.file) { return resource.fsPath; diff --git a/src/vs/platform/files/common/files.ts b/src/vs/platform/files/common/files.ts index 176f0799ba5a..23a2a5fb4b10 100644 --- a/src/vs/platform/files/common/files.ts +++ b/src/vs/platform/files/common/files.ts @@ -3,6 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { localize } from 'vs/nls'; import { sep } from 'vs/base/common/path'; import { URI } from 'vs/base/common/uri'; import * as glob from 'vs/base/common/glob'; @@ -13,6 +14,8 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import { isEqualOrParent, isEqual } from 'vs/base/common/resources'; import { isUndefinedOrNull } from 'vs/base/common/types'; import { VSBuffer, VSBufferReadable, VSBufferReadableStream } from 'vs/base/common/buffer'; +import { ReadableStreamEvents } from 'vs/base/common/stream'; +import { CancellationToken } from 'vs/base/common/cancellation'; export const IFileService = createDecorator('fileService'); @@ -158,6 +161,29 @@ export interface FileOverwriteOptions { overwrite: boolean; } +export interface FileReadStreamOptions { + + /** + * Is an integer specifying where to begin reading from in the file. If position is undefined, + * data will be read from the current file position. + */ + readonly position?: number; + + /** + * Is an integer specifying how many bytes to read from the file. By default, all bytes + * will be read. + */ + readonly length?: number; + + /** + * If provided, the size of the file will be checked against the limits. + */ + limits?: { + readonly size?: number; + readonly memory?: number; + }; +} + export interface FileWriteOptions { overwrite: boolean; create: boolean; @@ -181,8 +207,17 @@ export enum FileType { export interface IStat { type: FileType; + + /** + * The last modification date represented as millis from unix epoch. + */ mtime: number; + + /** + * The creation date represented as millis from unix epoch. + */ ctime: number; + size: number; } @@ -194,6 +229,8 @@ export interface IWatchOptions { export const enum FileSystemProviderCapabilities { FileReadWrite = 1 << 1, FileOpenReadWriteClose = 1 << 2, + FileReadStream = 1 << 4, + FileFolderCopy = 1 << 3, PathCaseSensitive = 1 << 10, @@ -223,6 +260,8 @@ export interface IFileSystemProvider { readFile?(resource: URI): Promise; writeFile?(resource: URI, content: Uint8Array, opts: FileWriteOptions): Promise; + readFileStream?(resource: URI, opts: FileReadStreamOptions, token?: CancellationToken): ReadableStreamEvents; + open?(resource: URI, opts: FileOpenOptions): Promise; close?(fd: number): Promise; read?(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise; @@ -257,11 +296,21 @@ export function hasOpenReadWriteCloseCapability(provider: IFileSystemProvider): return !!(provider.capabilities & FileSystemProviderCapabilities.FileOpenReadWriteClose); } +export interface IFileSystemProviderWithFileReadStreamCapability extends IFileSystemProvider { + readFileStream(resource: URI, opts: FileReadStreamOptions, token?: CancellationToken): ReadableStreamEvents; +} + +export function hasFileReadStreamCapability(provider: IFileSystemProvider): provider is IFileSystemProviderWithFileReadStreamCapability { + return !!(provider.capabilities & FileSystemProviderCapabilities.FileReadStream); +} + export enum FileSystemProviderErrorCode { FileExists = 'EntryExists', FileNotFound = 'EntryNotFound', FileNotADirectory = 'EntryNotADirectory', FileIsADirectory = 'EntryIsADirectory', + FileExceedsMemoryLimit = 'EntryExceedsMemoryLimit', + FileTooLarge = 'EntryTooLarge', NoPermissions = 'NoPermissions', Unavailable = 'Unavailable', Unknown = 'Unknown' @@ -274,13 +323,21 @@ export class FileSystemProviderError extends Error { } } -export function createFileSystemProviderError(error: Error, code: FileSystemProviderErrorCode): FileSystemProviderError { +export function createFileSystemProviderError(error: Error | string, code: FileSystemProviderErrorCode): FileSystemProviderError { const providerError = new FileSystemProviderError(error.toString(), code); markAsFileSystemProviderError(providerError, code); return providerError; } +export function ensureFileSystemProviderError(error?: Error): Error { + if (!error) { + return createFileSystemProviderError(localize('unknownError', "Unknown Error"), FileSystemProviderErrorCode.Unknown); // https://github.com/Microsoft/vscode/issues/72798 + } + + return error; +} + export function markAsFileSystemProviderError(error: Error, code: FileSystemProviderErrorCode): Error { error.name = code ? `${code} (FileSystemError)` : `FileSystemError`; @@ -311,6 +368,8 @@ export function toFileSystemProviderErrorCode(error: Error | undefined | null): case FileSystemProviderErrorCode.FileIsADirectory: return FileSystemProviderErrorCode.FileIsADirectory; case FileSystemProviderErrorCode.FileNotADirectory: return FileSystemProviderErrorCode.FileNotADirectory; case FileSystemProviderErrorCode.FileNotFound: return FileSystemProviderErrorCode.FileNotFound; + case FileSystemProviderErrorCode.FileExceedsMemoryLimit: return FileSystemProviderErrorCode.FileExceedsMemoryLimit; + case FileSystemProviderErrorCode.FileTooLarge: return FileSystemProviderErrorCode.FileTooLarge; case FileSystemProviderErrorCode.NoPermissions: return FileSystemProviderErrorCode.NoPermissions; case FileSystemProviderErrorCode.Unavailable: return FileSystemProviderErrorCode.Unavailable; } @@ -335,7 +394,10 @@ export function toFileOperationResult(error: Error): FileOperationResult { return FileOperationResult.FILE_PERMISSION_DENIED; case FileSystemProviderErrorCode.FileExists: return FileOperationResult.FILE_MOVE_CONFLICT; - case FileSystemProviderErrorCode.FileNotADirectory: + case FileSystemProviderErrorCode.FileExceedsMemoryLimit: + return FileOperationResult.FILE_EXCEEDS_MEMORY_LIMIT; + case FileSystemProviderErrorCode.FileTooLarge: + return FileOperationResult.FILE_TOO_LARGE; default: return FileOperationResult.FILE_OTHER_ERROR; } @@ -530,8 +592,7 @@ interface IBaseStat { size?: number; /** - * The last modification date represented - * as millis from unix epoch. + * The last modification date represented as millis from unix epoch. * * The value may or may not be resolved as * it is optional. @@ -539,22 +600,26 @@ interface IBaseStat { mtime?: number; /** - * A unique identifier thet represents the - * current state of the file or directory. + * The creation date represented as millis from unix epoch. * * The value may or may not be resolved as * it is optional. */ - etag?: string; + ctime?: number; /** - * The resource is readonly. + * A unique identifier thet represents the + * current state of the file or directory. + * + * The value may or may not be resolved as + * it is optional. */ - isReadonly?: boolean; + etag?: string; } export interface IBaseStatWithMetadata extends IBaseStat { mtime: number; + ctime: number; etag: string; size: number; } @@ -565,14 +630,19 @@ export interface IBaseStatWithMetadata extends IBaseStat { export interface IFileStat extends IBaseStat { /** - * The resource is a directory + * The resource is a file. + */ + isFile: boolean; + + /** + * The resource is a directory. */ isDirectory: boolean; /** * The resource is a symbolic link. */ - isSymbolicLink?: boolean; + isSymbolicLink: boolean; /** * The children of the file stat or undefined if none. @@ -582,6 +652,7 @@ export interface IFileStat extends IBaseStat { export interface IFileStatWithMetadata extends IFileStat, IBaseStatWithMetadata { mtime: number; + ctime: number; etag: string; size: number; children?: IFileStatWithMetadata[]; @@ -612,7 +683,7 @@ export interface IFileStreamContent extends IBaseStatWithMetadata { value: VSBufferReadableStream; } -export interface IReadFileOptions { +export interface IReadFileOptions extends FileReadStreamOptions { /** * The optional etag parameter allows to return early from resolving the resource if @@ -621,26 +692,6 @@ export interface IReadFileOptions { * It is the task of the caller to makes sure to handle this error case from the promise. */ readonly etag?: string; - - /** - * Is an integer specifying where to begin reading from in the file. If position is null, - * data will be read from the current file position. - */ - readonly position?: number; - - /** - * Is an integer specifying how many bytes to read from the file. By default, all bytes - * will be read. - */ - readonly length?: number; - - /** - * If provided, the size of the file will be checked against the limits. - */ - limits?: { - readonly size?: number; - readonly memory?: number; - }; } export interface IWriteFileOptions { @@ -670,7 +721,7 @@ export interface IResolveFileOptions { readonly resolveSingleChildDescendants?: boolean; /** - * Will resolve mtime, size and etag of files if enabled. This can have a negative impact + * Will resolve mtime, ctime, size and etag of files if enabled. This can have a negative impact * on performance and thus should only be used when these values are required. */ readonly resolveMetadata?: boolean; @@ -709,7 +760,7 @@ export const enum FileOperationResult { FILE_PERMISSION_DENIED, FILE_TOO_LARGE, FILE_INVALID_PATH, - FILE_EXCEED_MEMORY_LIMIT, + FILE_EXCEEDS_MEMORY_LIMIT, FILE_OTHER_ERROR } diff --git a/src/vs/platform/files/common/io.ts b/src/vs/platform/files/common/io.ts new file mode 100644 index 000000000000..fba64799b272 --- /dev/null +++ b/src/vs/platform/files/common/io.ts @@ -0,0 +1,114 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { localize } from 'vs/nls'; +import { URI } from 'vs/base/common/uri'; +import { VSBuffer, VSBufferWriteableStream, newWriteableBufferStream, VSBufferReadableStream } from 'vs/base/common/buffer'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { IFileSystemProviderWithOpenReadWriteCloseCapability, FileReadStreamOptions, createFileSystemProviderError, FileSystemProviderErrorCode, ensureFileSystemProviderError } from 'vs/platform/files/common/files'; +import { canceled } from 'vs/base/common/errors'; + +export interface ICreateReadStreamOptions extends FileReadStreamOptions { + + /** + * The size of the buffer to use before sending to the stream. + */ + bufferSize: number; +} + +export function createReadStream(provider: IFileSystemProviderWithOpenReadWriteCloseCapability, resource: URI, options: ICreateReadStreamOptions, token?: CancellationToken): VSBufferReadableStream { + const stream = newWriteableBufferStream(); + + // do not await reading but simply return the stream directly since it operates + // via events. finally end the stream and send through the possible error + let error: Error | undefined = undefined; + + doReadFileIntoStream(provider, resource, stream, options, token).then(undefined, err => error = err).finally(() => stream.end(error)); + + return stream; +} + +async function doReadFileIntoStream(provider: IFileSystemProviderWithOpenReadWriteCloseCapability, resource: URI, stream: VSBufferWriteableStream, options: ICreateReadStreamOptions, token?: CancellationToken): Promise { + + // Check for cancellation + throwIfCancelled(token); + + // open handle through provider + const handle = await provider.open(resource, { create: false }); + + // Check for cancellation + throwIfCancelled(token); + + try { + let totalBytesRead = 0; + let bytesRead = 0; + let allowedRemainingBytes = (options && typeof options.length === 'number') ? options.length : undefined; + + let buffer = VSBuffer.alloc(Math.min(options.bufferSize, typeof allowedRemainingBytes === 'number' ? allowedRemainingBytes : options.bufferSize)); + + let posInFile = options && typeof options.position === 'number' ? options.position : 0; + let posInBuffer = 0; + do { + // read from source (handle) at current position (pos) into buffer (buffer) at + // buffer position (posInBuffer) up to the size of the buffer (buffer.byteLength). + bytesRead = await provider.read(handle, posInFile, buffer.buffer, posInBuffer, buffer.byteLength - posInBuffer); + + posInFile += bytesRead; + posInBuffer += bytesRead; + totalBytesRead += bytesRead; + + if (typeof allowedRemainingBytes === 'number') { + allowedRemainingBytes -= bytesRead; + } + + // when buffer full, create a new one and emit it through stream + if (posInBuffer === buffer.byteLength) { + stream.write(buffer); + + buffer = VSBuffer.alloc(Math.min(options.bufferSize, typeof allowedRemainingBytes === 'number' ? allowedRemainingBytes : options.bufferSize)); + + posInBuffer = 0; + } + } while (bytesRead > 0 && (typeof allowedRemainingBytes !== 'number' || allowedRemainingBytes > 0) && throwIfCancelled(token) && throwIfTooLarge(totalBytesRead, options)); + + // wrap up with last buffer (also respect maxBytes if provided) + if (posInBuffer > 0) { + let lastChunkLength = posInBuffer; + if (typeof allowedRemainingBytes === 'number') { + lastChunkLength = Math.min(posInBuffer, allowedRemainingBytes); + } + + stream.write(buffer.slice(0, lastChunkLength)); + } + } catch (error) { + throw ensureFileSystemProviderError(error); + } finally { + await provider.close(handle); + } +} + +function throwIfCancelled(token?: CancellationToken): boolean { + if (token && token.isCancellationRequested) { + throw canceled(); + } + + return true; +} + +function throwIfTooLarge(totalBytesRead: number, options: ICreateReadStreamOptions): boolean { + + // Return early if file is too large to load and we have configured limits + if (options?.limits) { + if (typeof options.limits.memory === 'number' && totalBytesRead > options.limits.memory) { + throw createFileSystemProviderError(localize('fileTooLargeForHeapError', "To open a file of this size, you need to restart and allow it to use more memory"), FileSystemProviderErrorCode.FileExceedsMemoryLimit); + } + + if (typeof options.limits.size === 'number' && totalBytesRead > options.limits.size) { + throw createFileSystemProviderError(localize('fileTooLargeError', "File is too large to open"), FileSystemProviderErrorCode.FileTooLarge); + } + } + + return true; +} diff --git a/src/vs/platform/files/node/diskFileSystemProvider.ts b/src/vs/platform/files/node/diskFileSystemProvider.ts index a0e8c1059c39..338c095afa68 100644 --- a/src/vs/platform/files/node/diskFileSystemProvider.ts +++ b/src/vs/platform/files/node/diskFileSystemProvider.ts @@ -6,7 +6,7 @@ import { mkdir, open, close, read, write, fdatasync, Dirent, Stats } from 'fs'; import { promisify } from 'util'; import { IDisposable, Disposable, toDisposable, dispose, combinedDisposable } from 'vs/base/common/lifecycle'; -import { IFileSystemProvider, FileSystemProviderCapabilities, IFileChange, IWatchOptions, IStat, FileType, FileDeleteOptions, FileOverwriteOptions, FileWriteOptions, FileOpenOptions, FileSystemProviderErrorCode, createFileSystemProviderError, FileSystemProviderError } from 'vs/platform/files/common/files'; +import { FileSystemProviderCapabilities, IFileChange, IWatchOptions, IStat, FileType, FileDeleteOptions, FileOverwriteOptions, FileWriteOptions, FileOpenOptions, FileSystemProviderErrorCode, createFileSystemProviderError, FileSystemProviderError, IFileSystemProviderWithFileReadWriteCapability, IFileSystemProviderWithFileReadStreamCapability, IFileSystemProviderWithOpenReadWriteCloseCapability, FileReadStreamOptions, IFileSystemProviderWithFileFolderCopyCapability } from 'vs/platform/files/common/files'; import { URI } from 'vs/base/common/uri'; import { Event, Emitter } from 'vs/base/common/event'; import { isLinux, isWindows } from 'vs/base/common/platform'; @@ -22,15 +22,30 @@ import { FileWatcher as UnixWatcherService } from 'vs/platform/files/node/watche import { FileWatcher as WindowsWatcherService } from 'vs/platform/files/node/watcher/win32/watcherService'; import { FileWatcher as NsfwWatcherService } from 'vs/platform/files/node/watcher/nsfw/watcherService'; import { FileWatcher as NodeJSWatcherService } from 'vs/platform/files/node/watcher/nodejs/watcherService'; +import { VSBuffer } from 'vs/base/common/buffer'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { ReadableStreamEvents, transform } from 'vs/base/common/stream'; +import { createReadStream } from 'vs/platform/files/common/io'; export interface IWatcherOptions { pollingInterval?: number; usePolling: boolean; } -export class DiskFileSystemProvider extends Disposable implements IFileSystemProvider { +export interface IDiskFileSystemProviderOptions { + bufferSize?: number; + watcher?: IWatcherOptions; +} + +export class DiskFileSystemProvider extends Disposable implements + IFileSystemProviderWithFileReadWriteCapability, + IFileSystemProviderWithOpenReadWriteCloseCapability, + IFileSystemProviderWithFileReadStreamCapability, + IFileSystemProviderWithFileFolderCopyCapability { + + private readonly BUFFER_SIZE = this.options?.bufferSize || 64 * 1024; - constructor(private logService: ILogService, private watcherOptions?: IWatcherOptions) { + constructor(private logService: ILogService, private options?: IDiskFileSystemProviderOptions) { super(); } @@ -44,6 +59,7 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro this._capabilities = FileSystemProviderCapabilities.FileReadWrite | FileSystemProviderCapabilities.FileOpenReadWriteClose | + FileSystemProviderCapabilities.FileReadStream | FileSystemProviderCapabilities.FileFolderCopy; if (isLinux) { @@ -64,7 +80,7 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro return { type: this.toType(stat, isSymbolicLink), - ctime: stat.ctime.getTime(), + ctime: stat.birthtime.getTime(), // intentionally not using ctime here, we want the creation time mtime: stat.mtime.getTime(), size: stat.size }; @@ -121,17 +137,32 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro } } + readFileStream(resource: URI, opts: FileReadStreamOptions, token?: CancellationToken): ReadableStreamEvents { + const fileStream = createReadStream(this, resource, { + ...opts, + bufferSize: this.BUFFER_SIZE + }, token); + + return transform(fileStream, { data: data => data.buffer }, data => VSBuffer.concat(data.map(data => VSBuffer.wrap(data))).buffer); + } + async writeFile(resource: URI, content: Uint8Array, opts: FileWriteOptions): Promise { let handle: number | undefined = undefined; try { const filePath = this.toFilePath(resource); - // Validate target - const fileExists = await exists(filePath); - if (fileExists && !opts.overwrite) { - throw createFileSystemProviderError(new Error(localize('fileExists', "File already exists")), FileSystemProviderErrorCode.FileExists); - } else if (!fileExists && !opts.create) { - throw createFileSystemProviderError(new Error(localize('fileNotExists', "File does not exist")), FileSystemProviderErrorCode.FileNotFound); + // Validate target unless { create: true, overwrite: true } + if (!opts.create || !opts.overwrite) { + const fileExists = await exists(filePath); + if (fileExists) { + if (!opts.overwrite) { + throw createFileSystemProviderError(localize('fileExists', "File already exists"), FileSystemProviderErrorCode.FileExists); + } + } else { + if (!opts.create) { + throw createFileSystemProviderError(localize('fileNotExists', "File does not exist"), FileSystemProviderErrorCode.FileNotFound); + } + } } // Open @@ -435,13 +466,13 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro } if (isSameResourceWithDifferentPathCase && mode === 'copy') { - throw createFileSystemProviderError(new Error('File cannot be copied to same path with different path case'), FileSystemProviderErrorCode.FileExists); + throw createFileSystemProviderError(localize('fileCopyErrorPathCase', "'File cannot be copied to same path with different path case"), FileSystemProviderErrorCode.FileExists); } // handle existing target (unless this is a case change) if (!isSameResourceWithDifferentPathCase && await exists(toFilePath)) { if (!overwrite) { - throw createFileSystemProviderError(new Error('File at target already exists'), FileSystemProviderErrorCode.FileExists); + throw createFileSystemProviderError(localize('fileCopyErrorExists', "File at target already exists"), FileSystemProviderErrorCode.FileExists); } // Delete target @@ -532,9 +563,9 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro let watcherOptions: IWatcherOptions | undefined = undefined; // requires a polling watcher - if (this.watcherOptions && this.watcherOptions.usePolling) { + if (this.options?.watcher?.usePolling) { watcherImpl = UnixWatcherService; - watcherOptions = this.watcherOptions; + watcherOptions = this.options?.watcher; } // Single Folder Watcher diff --git a/src/vs/platform/files/node/watcher/nsfw/nsfwWatcherService.ts b/src/vs/platform/files/node/watcher/nsfw/nsfwWatcherService.ts index 7ad5efcdf547..cf5324e194bf 100644 --- a/src/vs/platform/files/node/watcher/nsfw/nsfwWatcherService.ts +++ b/src/vs/platform/files/node/watcher/nsfw/nsfwWatcherService.ts @@ -8,7 +8,7 @@ import * as extpath from 'vs/base/common/extpath'; import * as path from 'vs/base/common/path'; import * as platform from 'vs/base/common/platform'; import { IDiskFileChange, normalizeFileChanges, ILogMessage } from 'vs/platform/files/node/watcher/watcher'; -import * as nsfw from 'nsfw'; +import * as nsfw from 'vscode-nsfw'; import { IWatcherService, IWatcherRequest, IWatcherOptions } from 'vs/platform/files/node/watcher/nsfw/watcher'; import { ThrottledDelayer } from 'vs/base/common/async'; import { FileChangeType } from 'vs/platform/files/common/files'; diff --git a/src/vs/platform/files/node/watcher/win32/csharpWatcherService.ts b/src/vs/platform/files/node/watcher/win32/csharpWatcherService.ts index 77d4251922a8..801f852077c8 100644 --- a/src/vs/platform/files/node/watcher/win32/csharpWatcherService.ts +++ b/src/vs/platform/files/node/watcher/win32/csharpWatcherService.ts @@ -55,7 +55,7 @@ export class OutOfProcessWin32FolderWatcher { const stdoutLineDecoder = new decoder.LineDecoder(); // Events over stdout - this.handle.stdout.on('data', (data: Buffer) => { + this.handle.stdout!.on('data', (data: Buffer) => { // Collect raw events from output const rawEvents: IDiskFileChange[] = []; @@ -99,7 +99,7 @@ export class OutOfProcessWin32FolderWatcher { // Errors this.handle.on('error', (error: Error) => this.onError(error)); - this.handle.stderr.on('data', (data: Buffer) => this.onError(data)); + this.handle.stderr!.on('data', (data: Buffer) => this.onError(data)); // Exit this.handle.on('exit', (code: number, signal: string) => this.onExit(code, signal)); diff --git a/src/vs/platform/files/test/node/diskFileService.test.ts b/src/vs/platform/files/test/node/diskFileService.test.ts index 4ef1b3d20b83..9b0a27b91fab 100644 --- a/src/vs/platform/files/test/node/diskFileService.test.ts +++ b/src/vs/platform/files/test/node/diskFileService.test.ts @@ -20,13 +20,14 @@ import { NullLogService } from 'vs/platform/log/common/log'; import { isLinux, isWindows } from 'vs/base/common/platform'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { isEqual } from 'vs/base/common/resources'; -import { VSBuffer, VSBufferReadable, toVSBufferReadableStream, VSBufferReadableStream, bufferToReadable, bufferToStream } from 'vs/base/common/buffer'; +import { VSBuffer, VSBufferReadable, streamToBufferReadableStream, VSBufferReadableStream, bufferToReadable, bufferToStream, streamToBuffer } from 'vs/base/common/buffer'; import { find } from 'vs/base/common/arrays'; function getByName(root: IFileStat, name: string): IFileStat | undefined { if (root.children === undefined) { return undefined; } + return find(root.children, child => child.name === name); } @@ -57,6 +58,7 @@ export class TestDiskFileSystemProvider extends DiskFileSystemProvider { totalBytesRead: number = 0; private invalidStatSize: boolean = false; + private smallStatSize: boolean = false; private _testCapabilities!: FileSystemProviderCapabilities; get capabilities(): FileSystemProviderCapabilities { @@ -64,6 +66,7 @@ export class TestDiskFileSystemProvider extends DiskFileSystemProvider { this._testCapabilities = FileSystemProviderCapabilities.FileReadWrite | FileSystemProviderCapabilities.FileOpenReadWriteClose | + FileSystemProviderCapabilities.FileReadStream | FileSystemProviderCapabilities.FileFolderCopy; if (isLinux) { @@ -78,8 +81,12 @@ export class TestDiskFileSystemProvider extends DiskFileSystemProvider { this._testCapabilities = capabilities; } - setInvalidStatSize(disabled: boolean): void { - this.invalidStatSize = disabled; + setInvalidStatSize(enabled: boolean): void { + this.invalidStatSize = enabled; + } + + setSmallStatSize(enabled: boolean): void { + this.smallStatSize = enabled; } async stat(resource: URI): Promise { @@ -87,6 +94,8 @@ export class TestDiskFileSystemProvider extends DiskFileSystemProvider { if (this.invalidStatSize) { res.size = String(res.size) as any; // for https://github.com/Microsoft/vscode/issues/72909 + } else if (this.smallStatSize) { + res.size = 1; } return res; @@ -174,7 +183,7 @@ suite('Disk File Service', function () { assert.equal(event!.target!.isDirectory, true); }); - test('createFolder: creating multiple folders at once', async function () { + test('createFolder: creating multiple folders at once', async () => { let event: FileOperationEvent; disposables.add(service.onAfterOperation(e => event = e)); @@ -204,23 +213,35 @@ suite('Disk File Service', function () { assert.equal(exists, false); }); - test('resolve', async () => { - const resolved = await service.resolve(URI.file(testDir), { resolveTo: [URI.file(join(testDir, 'deep'))] }); - assert.equal(resolved.children!.length, 8); + test('resolve - file', async () => { + const resource = URI.file(getPathFromAmdModule(require, './fixtures/resolver/index.html')); + const resolved = await service.resolve(resource); - const deep = (getByName(resolved, 'deep')!); - assert.equal(deep.children!.length, 4); + assert.equal(resolved.name, 'index.html'); + assert.equal(resolved.isFile, true); + assert.equal(resolved.isDirectory, false); + assert.equal(resolved.isSymbolicLink, false); + assert.equal(resolved.resource.toString(), resource.toString()); + assert.equal(resolved.children, undefined); + assert.ok(resolved.mtime! > 0); + assert.ok(resolved.ctime! > 0); + assert.ok(resolved.size! > 0); }); test('resolve - directory', async () => { const testsElements = ['examples', 'other', 'index.html', 'site.css']; - const result = await service.resolve(URI.file(getPathFromAmdModule(require, './fixtures/resolver'))); + const resource = URI.file(getPathFromAmdModule(require, './fixtures/resolver')); + const result = await service.resolve(resource); assert.ok(result); + assert.equal(result.resource.toString(), resource.toString()); + assert.equal(result.name, 'resolver'); assert.ok(result.children); assert.ok(result.children!.length > 0); assert.ok(result!.isDirectory); + assert.ok(result.mtime! > 0); + assert.ok(result.ctime! > 0); assert.equal(result.children!.length, testsElements.length); assert.ok(result.children!.every(entry => { @@ -233,12 +254,18 @@ suite('Disk File Service', function () { assert.ok(basename(value.resource.fsPath)); if (['examples', 'other'].indexOf(basename(value.resource.fsPath)) >= 0) { assert.ok(value.isDirectory); + assert.equal(value.mtime, undefined); + assert.equal(value.ctime, undefined); } else if (basename(value.resource.fsPath) === 'index.html') { assert.ok(!value.isDirectory); assert.ok(!value.children); + assert.equal(value.mtime, undefined); + assert.equal(value.ctime, undefined); } else if (basename(value.resource.fsPath) === 'site.css') { assert.ok(!value.isDirectory); assert.ok(!value.children); + assert.equal(value.mtime, undefined); + assert.equal(value.ctime, undefined); } else { assert.ok(!'Unexpected value ' + basename(value.resource.fsPath)); } @@ -251,9 +278,12 @@ suite('Disk File Service', function () { const result = await service.resolve(URI.file(getPathFromAmdModule(require, './fixtures/resolver')), { resolveMetadata: true }); assert.ok(result); + assert.equal(result.name, 'resolver'); assert.ok(result.children); assert.ok(result.children!.length > 0); assert.ok(result!.isDirectory); + assert.ok(result.mtime! > 0); + assert.ok(result.ctime! > 0); assert.equal(result.children!.length, testsElements.length); assert.ok(result.children!.every(entry => { @@ -268,18 +298,32 @@ suite('Disk File Service', function () { assert.ok(basename(value.resource.fsPath)); if (['examples', 'other'].indexOf(basename(value.resource.fsPath)) >= 0) { assert.ok(value.isDirectory); + assert.ok(value.mtime! > 0); + assert.ok(value.ctime! > 0); } else if (basename(value.resource.fsPath) === 'index.html') { assert.ok(!value.isDirectory); assert.ok(!value.children); + assert.ok(value.mtime! > 0); + assert.ok(value.ctime! > 0); } else if (basename(value.resource.fsPath) === 'site.css') { assert.ok(!value.isDirectory); assert.ok(!value.children); + assert.ok(value.mtime! > 0); + assert.ok(value.ctime! > 0); } else { assert.ok(!'Unexpected value ' + basename(value.resource.fsPath)); } }); }); + test('resolve - directory with resolveTo', async () => { + const resolved = await service.resolve(URI.file(testDir), { resolveTo: [URI.file(join(testDir, 'deep'))] }); + assert.equal(resolved.children!.length, 8); + + const deep = (getByName(resolved, 'deep')!); + assert.equal(deep.children!.length, 4); + }); + test('resolve - directory - resolveTo single directory', async () => { const resolverFixturesPath = getPathFromAmdModule(require, './fixtures/resolver'); const result = await service.resolve(URI.file(resolverFixturesPath), { resolveTo: [URI.file(join(resolverFixturesPath, 'other/deep'))] }); @@ -419,6 +463,7 @@ suite('Disk File Service', function () { await service.del(source.resource); assert.equal(existsSync(source.resource.fsPath), false); + assert.ok(event!); assert.equal(event!.resource.fsPath, resource.fsPath); assert.equal(event!.operation, FileOperation.DELETE); @@ -481,56 +526,56 @@ suite('Disk File Service', function () { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); setCapabilities(testProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); - await testMoveAcrossProviders(); + return testMoveAcrossProviders(); }); test('move - across providers (unbuffered => unbuffered)', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); setCapabilities(testProvider, FileSystemProviderCapabilities.FileReadWrite); - await testMoveAcrossProviders(); + return testMoveAcrossProviders(); }); test('move - across providers (buffered => unbuffered)', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); setCapabilities(testProvider, FileSystemProviderCapabilities.FileReadWrite); - await testMoveAcrossProviders(); + return testMoveAcrossProviders(); }); test('move - across providers (unbuffered => buffered)', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); setCapabilities(testProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); - await testMoveAcrossProviders(); + return testMoveAcrossProviders(); }); test('move - across providers - large (buffered => buffered)', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); setCapabilities(testProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); - await testMoveAcrossProviders('lorem.txt'); + return testMoveAcrossProviders('lorem.txt'); }); test('move - across providers - large (unbuffered => unbuffered)', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); setCapabilities(testProvider, FileSystemProviderCapabilities.FileReadWrite); - await testMoveAcrossProviders('lorem.txt'); + return testMoveAcrossProviders('lorem.txt'); }); test('move - across providers - large (buffered => unbuffered)', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); setCapabilities(testProvider, FileSystemProviderCapabilities.FileReadWrite); - await testMoveAcrossProviders('lorem.txt'); + return testMoveAcrossProviders('lorem.txt'); }); test('move - across providers - large (unbuffered => buffered)', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); setCapabilities(testProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); - await testMoveAcrossProviders('lorem.txt'); + return testMoveAcrossProviders('lorem.txt'); }); async function testMoveAcrossProviders(sourceFile = 'index.html'): Promise { @@ -596,28 +641,28 @@ suite('Disk File Service', function () { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); setCapabilities(testProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); - await testMoveFolderAcrossProviders(); + return testMoveFolderAcrossProviders(); }); test('move - directory - across providers (unbuffered => unbuffered)', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); setCapabilities(testProvider, FileSystemProviderCapabilities.FileReadWrite); - await testMoveFolderAcrossProviders(); + return testMoveFolderAcrossProviders(); }); test('move - directory - across providers (buffered => unbuffered)', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); setCapabilities(testProvider, FileSystemProviderCapabilities.FileReadWrite); - await testMoveFolderAcrossProviders(); + return testMoveFolderAcrossProviders(); }); - test('move - directory - across providers (unbuffered => buffered)', async function () { + test('move - directory - across providers (unbuffered => buffered)', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); setCapabilities(testProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); - await testMoveFolderAcrossProviders(); + return testMoveFolderAcrossProviders(); }); async function testMoveFolderAcrossProviders(): Promise { @@ -992,6 +1037,10 @@ suite('Disk File Service', function () { assert.equal(source.size, copied.size); }); + test('readFile - small file - default', () => { + return testReadFile(URI.file(join(testDir, 'small.txt'))); + }); + test('readFile - small file - buffered', () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); @@ -1016,6 +1065,22 @@ suite('Disk File Service', function () { return testReadFile(URI.file(join(testDir, 'small.txt'))); }); + test('readFile - small file - streamed', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadStream); + + return testReadFile(URI.file(join(testDir, 'small.txt'))); + }); + + test('readFile - small file - streamed / readonly', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadStream | FileSystemProviderCapabilities.Readonly); + + return testReadFile(URI.file(join(testDir, 'small.txt'))); + }); + + test('readFile - large file - default', async () => { + return testReadFile(URI.file(join(testDir, 'lorem.txt'))); + }); + test('readFile - large file - buffered', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); @@ -1028,35 +1093,69 @@ suite('Disk File Service', function () { return testReadFile(URI.file(join(testDir, 'lorem.txt'))); }); + test('readFile - large file - streamed', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadStream); + + return testReadFile(URI.file(join(testDir, 'lorem.txt'))); + }); + async function testReadFile(resource: URI): Promise { const content = await service.readFile(resource); assert.equal(content.value.toString(), readFileSync(resource.fsPath)); } - test('readFile - Files are intermingled #38331 - buffered', async () => { + test('readFileStream - small file - default', () => { + return testReadFileStream(URI.file(join(testDir, 'small.txt'))); + }); + + test('readFileStream - small file - buffered', () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); - let resource1 = URI.file(join(testDir, 'lorem.txt')); - let resource2 = URI.file(join(testDir, 'some_utf16le.css')); + return testReadFileStream(URI.file(join(testDir, 'small.txt'))); + }); - // load in sequence and keep data - const value1 = await service.readFile(resource1); - const value2 = await service.readFile(resource2); + test('readFileStream - small file - unbuffered', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); - // load in parallel in expect the same result - const result = await Promise.all([ - service.readFile(resource1), - service.readFile(resource2) - ]); + return testReadFileStream(URI.file(join(testDir, 'small.txt'))); + }); - assert.equal(result[0].value.toString(), value1.value.toString()); - assert.equal(result[1].value.toString(), value2.value.toString()); + test('readFileStream - small file - streamed', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadStream); + + return testReadFileStream(URI.file(join(testDir, 'small.txt'))); + }); + + async function testReadFileStream(resource: URI): Promise { + const content = await service.readFileStream(resource); + + assert.equal((await streamToBuffer(content.value)).toString(), readFileSync(resource.fsPath)); + } + + test('readFile - Files are intermingled #38331 - default', async () => { + return testFilesNotIntermingled(); + }); + + test('readFile - Files are intermingled #38331 - buffered', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); + + return testFilesNotIntermingled(); }); test('readFile - Files are intermingled #38331 - unbuffered', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); + return testFilesNotIntermingled(); + }); + + test('readFile - Files are intermingled #38331 - streamed', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadStream); + + return testFilesNotIntermingled(); + }); + + async function testFilesNotIntermingled() { let resource1 = URI.file(join(testDir, 'lorem.txt')); let resource2 = URI.file(join(testDir, 'some_utf16le.css')); @@ -1072,109 +1171,150 @@ suite('Disk File Service', function () { assert.equal(result[0].value.toString(), value1.value.toString()); assert.equal(result[1].value.toString(), value2.value.toString()); + } + + test('readFile - from position (ASCII) - default', async () => { + return testReadFileFromPositionAscii(); }); test('readFile - from position (ASCII) - buffered', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); + return testReadFileFromPositionAscii(); + }); + + test('readFile - from position (ASCII) - unbuffered', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); + + return testReadFileFromPositionAscii(); + }); + + test('readFile - from position (ASCII) - streamed', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadStream); + + return testReadFileFromPositionAscii(); + }); + + async function testReadFileFromPositionAscii() { const resource = URI.file(join(testDir, 'small.txt')); const contents = await service.readFile(resource, { position: 6 }); assert.equal(contents.value.toString(), 'File'); + } + + test('readFile - from position (with umlaut) - default', async () => { + return testReadFileFromPositionUmlaut(); }); test('readFile - from position (with umlaut) - buffered', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); - const resource = URI.file(join(testDir, 'small_umlaut.txt')); - - const contents = await service.readFile(resource, { position: Buffer.from('Small File with Ü').length }); - - assert.equal(contents.value.toString(), 'mlaut'); + return testReadFileFromPositionUmlaut(); }); - test('readFile - from position (ASCII) - unbuffered', async () => { + test('readFile - from position (with umlaut) - unbuffered', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); - const resource = URI.file(join(testDir, 'small.txt')); + return testReadFileFromPositionUmlaut(); + }); - const contents = await service.readFile(resource, { position: 6 }); + test('readFile - from position (with umlaut) - streamed', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadStream); - assert.equal(contents.value.toString(), 'File'); + return testReadFileFromPositionUmlaut(); }); - test('readFile - from position (with umlaut) - unbuffered', async () => { - setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); - + async function testReadFileFromPositionUmlaut() { const resource = URI.file(join(testDir, 'small_umlaut.txt')); const contents = await service.readFile(resource, { position: Buffer.from('Small File with Ü').length }); assert.equal(contents.value.toString(), 'mlaut'); - }); + } + test('readFile - 3 bytes (ASCII) - default', async () => { + return testReadThreeBytesFromFile(); + }); test('readFile - 3 bytes (ASCII) - buffered', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); - const resource = URI.file(join(testDir, 'small.txt')); - - const contents = await service.readFile(resource, { length: 3 }); - - assert.equal(contents.value.toString(), 'Sma'); + return testReadThreeBytesFromFile(); }); test('readFile - 3 bytes (ASCII) - unbuffered', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); + return testReadThreeBytesFromFile(); + }); + + test('readFile - 3 bytes (ASCII) - streamed', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadStream); + + return testReadThreeBytesFromFile(); + }); + + async function testReadThreeBytesFromFile() { const resource = URI.file(join(testDir, 'small.txt')); const contents = await service.readFile(resource, { length: 3 }); assert.equal(contents.value.toString(), 'Sma'); + } + + test('readFile - 20000 bytes (large) - default', async () => { + return readLargeFileWithLength(20000); }); test('readFile - 20000 bytes (large) - buffered', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); - const resource = URI.file(join(testDir, 'lorem.txt')); - - const contents = await service.readFile(resource, { length: 20000 }); - - assert.equal(contents.value.byteLength, 20000); + return readLargeFileWithLength(20000); }); test('readFile - 20000 bytes (large) - unbuffered', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); - const resource = URI.file(join(testDir, 'lorem.txt')); + return readLargeFileWithLength(20000); + }); - const contents = await service.readFile(resource, { length: 20000 }); + test('readFile - 20000 bytes (large) - streamed', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadStream); + + return readLargeFileWithLength(20000); + }); - assert.equal(contents.value.byteLength, 20000); + test('readFile - 80000 bytes (large) - default', async () => { + return readLargeFileWithLength(80000); }); test('readFile - 80000 bytes (large) - buffered', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); - const resource = URI.file(join(testDir, 'lorem.txt')); - - const contents = await service.readFile(resource, { length: 80000 }); - - assert.equal(contents.value.byteLength, 80000); + return readLargeFileWithLength(80000); }); test('readFile - 80000 bytes (large) - unbuffered', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); - const resource = URI.file(join(testDir, 'lorem.txt')); + return readLargeFileWithLength(80000); + }); - const contents = await service.readFile(resource, { length: 80000 }); + test('readFile - 80000 bytes (large) - streamed', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadStream); - assert.equal(contents.value.byteLength, 80000); + return readLargeFileWithLength(80000); }); + async function readLargeFileWithLength(length: number) { + const resource = URI.file(join(testDir, 'lorem.txt')); + + const contents = await service.readFile(resource, { length }); + + assert.equal(contents.value.byteLength, length); + } + test('readFile - FILE_IS_DIRECTORY', async () => { const resource = URI.file(join(testDir, 'deep')); @@ -1203,9 +1343,29 @@ suite('Disk File Service', function () { assert.equal(error!.fileOperationResult, FileOperationResult.FILE_NOT_FOUND); }); + test('readFile - FILE_NOT_MODIFIED_SINCE - default', async () => { + return testNotModifiedSince(); + }); + test('readFile - FILE_NOT_MODIFIED_SINCE - buffered', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); + return testNotModifiedSince(); + }); + + test('readFile - FILE_NOT_MODIFIED_SINCE - unbuffered', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); + + return testNotModifiedSince(); + }); + + test('readFile - FILE_NOT_MODIFIED_SINCE - streamed', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadStream); + + return testNotModifiedSince(); + }); + + async function testNotModifiedSince() { const resource = URI.file(join(testDir, 'index.html')); const contents = await service.readFile(resource); @@ -1221,10 +1381,9 @@ suite('Disk File Service', function () { assert.ok(error); assert.equal(error!.fileOperationResult, FileOperationResult.FILE_NOT_MODIFIED_SINCE); assert.equal(fileProvider.totalBytesRead, 0); - }); + } test('readFile - FILE_NOT_MODIFIED_SINCE does not fire wrongly - https://github.com/Microsoft/vscode/issues/72909', async () => { - setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); fileProvider.setInvalidStatSize(true); const resource = URI.file(join(testDir, 'index.html')); @@ -1241,45 +1400,37 @@ suite('Disk File Service', function () { assert.ok(!error); }); - test('readFile - FILE_NOT_MODIFIED_SINCE - unbuffered', async () => { - setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); - - const resource = URI.file(join(testDir, 'index.html')); - - const contents = await service.readFile(resource); - fileProvider.totalBytesRead = 0; + test('readFile - FILE_EXCEEDS_MEMORY_LIMIT - default', async () => { + return testFileExceedsMemoryLimit(); + }); - let error: FileOperationError | undefined = undefined; - try { - await service.readFile(resource, { etag: contents.etag }); - } catch (err) { - error = err; - } + test('readFile - FILE_EXCEEDS_MEMORY_LIMIT - buffered', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); - assert.ok(error); - assert.equal(error!.fileOperationResult, FileOperationResult.FILE_NOT_MODIFIED_SINCE); - assert.equal(fileProvider.totalBytesRead, 0); + return testFileExceedsMemoryLimit(); }); - test('readFile - FILE_EXCEED_MEMORY_LIMIT - buffered', async () => { - setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); + test('readFile - FILE_EXCEEDS_MEMORY_LIMIT - unbuffered', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); - const resource = URI.file(join(testDir, 'index.html')); + return testFileExceedsMemoryLimit(); + }); - let error: FileOperationError | undefined = undefined; - try { - await service.readFile(resource, { limits: { memory: 10 } }); - } catch (err) { - error = err; - } + test('readFile - FILE_EXCEEDS_MEMORY_LIMIT - streamed', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadStream); - assert.ok(error); - assert.equal(error!.fileOperationResult, FileOperationResult.FILE_EXCEED_MEMORY_LIMIT); + return testFileExceedsMemoryLimit(); }); - test('readFile - FILE_EXCEED_MEMORY_LIMIT - unbuffered', async () => { - setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); + async function testFileExceedsMemoryLimit() { + await doTestFileExceedsMemoryLimit(); + // Also test when the stat size is wrong + fileProvider.setSmallStatSize(true); + return doTestFileExceedsMemoryLimit(false); + } + + async function doTestFileExceedsMemoryLimit(testTotalBytesRead = true) { const resource = URI.file(join(testDir, 'index.html')); let error: FileOperationError | undefined = undefined; @@ -1290,28 +1441,44 @@ suite('Disk File Service', function () { } assert.ok(error); - assert.equal(error!.fileOperationResult, FileOperationResult.FILE_EXCEED_MEMORY_LIMIT); + assert.equal(error!.fileOperationResult, FileOperationResult.FILE_EXCEEDS_MEMORY_LIMIT); + + if (testTotalBytesRead) { + assert.equal(fileProvider.totalBytesRead, 0); + } + } + + test('readFile - FILE_TOO_LARGE - default', async () => { + return testFileTooLarge(); }); test('readFile - FILE_TOO_LARGE - buffered', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); - const resource = URI.file(join(testDir, 'index.html')); - - let error: FileOperationError | undefined = undefined; - try { - await service.readFile(resource, { limits: { size: 10 } }); - } catch (err) { - error = err; - } - - assert.ok(error); - assert.equal(error!.fileOperationResult, FileOperationResult.FILE_TOO_LARGE); + return testFileTooLarge(); }); test('readFile - FILE_TOO_LARGE - unbuffered', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); + return testFileTooLarge(); + }); + + test('readFile - FILE_TOO_LARGE - streamed', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadStream); + + return testFileTooLarge(); + }); + + async function testFileTooLarge() { + await doTestFileExceedsMemoryLimit(); + + // Also test when the stat size is wrong + fileProvider.setSmallStatSize(true); + return doTestFileTooLarge(); + } + + async function doTestFileTooLarge() { const resource = URI.file(join(testDir, 'index.html')); let error: FileOperationError | undefined = undefined; @@ -1323,18 +1490,18 @@ suite('Disk File Service', function () { assert.ok(error); assert.equal(error!.fileOperationResult, FileOperationResult.FILE_TOO_LARGE); - }); + } test('createFile', async () => { - assertCreateFile(contents => VSBuffer.fromString(contents)); + return assertCreateFile(contents => VSBuffer.fromString(contents)); }); test('createFile (readable)', async () => { - assertCreateFile(contents => bufferToReadable(VSBuffer.fromString(contents))); + return assertCreateFile(contents => bufferToReadable(VSBuffer.fromString(contents))); }); test('createFile (stream)', async () => { - assertCreateFile(contents => bufferToStream(VSBuffer.fromString(contents))); + return assertCreateFile(contents => bufferToStream(VSBuffer.fromString(contents))); }); async function assertCreateFile(converter: (content: string) => VSBuffer | VSBufferReadable | VSBufferReadableStream): Promise { @@ -1390,71 +1557,51 @@ suite('Disk File Service', function () { assert.equal(event!.target!.resource.fsPath, resource.fsPath); }); - test('writeFile - buffered', async () => { - setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); - - const resource = URI.file(join(testDir, 'small.txt')); - - const content = readFileSync(resource.fsPath); - assert.equal(content, 'Small File'); - - const newContent = 'Updates to the small file'; - await service.writeFile(resource, VSBuffer.fromString(newContent)); - - assert.equal(readFileSync(resource.fsPath), newContent); + test('writeFile - default', async () => { + return testWriteFile(); }); - test('writeFile (large file) - buffered', async () => { + test('writeFile - buffered', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); - const resource = URI.file(join(testDir, 'lorem.txt')); - - const content = readFileSync(resource.fsPath); - const newContent = content.toString() + content.toString(); + return testWriteFile(); + }); - const fileStat = await service.writeFile(resource, VSBuffer.fromString(newContent)); - assert.equal(fileStat.name, 'lorem.txt'); + test('writeFile - unbuffered', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); - assert.equal(readFileSync(resource.fsPath), newContent); + return testWriteFile(); }); - test('writeFile - buffered - readonly throws', async () => { - setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose | FileSystemProviderCapabilities.Readonly); - + async function testWriteFile() { const resource = URI.file(join(testDir, 'small.txt')); const content = readFileSync(resource.fsPath); assert.equal(content, 'Small File'); const newContent = 'Updates to the small file'; + await service.writeFile(resource, VSBuffer.fromString(newContent)); - let error: Error; - try { - await service.writeFile(resource, VSBuffer.fromString(newContent)); - } catch (err) { - error = err; - } + assert.equal(readFileSync(resource.fsPath), newContent); + } - assert.ok(error!); + test('writeFile (large file) - default', async () => { + return testWriteFileLarge(); }); - test('writeFile - unbuffered', async () => { - setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); - - const resource = URI.file(join(testDir, 'small.txt')); - - const content = readFileSync(resource.fsPath); - assert.equal(content, 'Small File'); - - const newContent = 'Updates to the small file'; - await service.writeFile(resource, VSBuffer.fromString(newContent)); + test('writeFile (large file) - buffered', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); - assert.equal(readFileSync(resource.fsPath), newContent); + return testWriteFileLarge(); }); test('writeFile (large file) - unbuffered', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); + return testWriteFileLarge(); + }); + + async function testWriteFileLarge() { const resource = URI.file(join(testDir, 'lorem.txt')); const content = readFileSync(resource.fsPath); @@ -1464,11 +1611,21 @@ suite('Disk File Service', function () { assert.equal(fileStat.name, 'lorem.txt'); assert.equal(readFileSync(resource.fsPath), newContent); + } + + test('writeFile - buffered - readonly throws', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose | FileSystemProviderCapabilities.Readonly); + + return testWriteFileReadonlyThrows(); }); test('writeFile - unbuffered - readonly throws', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite | FileSystemProviderCapabilities.Readonly); + return testWriteFileReadonlyThrows(); + }); + + async function testWriteFileReadonlyThrows() { const resource = URI.file(join(testDir, 'small.txt')); const content = readFileSync(resource.fsPath); @@ -1484,7 +1641,7 @@ suite('Disk File Service', function () { } assert.ok(error!); - }); + } test('writeFile (large file) - multiple parallel writes queue up', async () => { const resource = URI.file(join(testDir, 'lorem.txt')); @@ -1501,37 +1658,23 @@ suite('Disk File Service', function () { assert.ok(['0', '00', '000', '0000', '00000'].some(offset => fileContent === offset + newContent)); }); - test('writeFile (readable) - buffered', async () => { - setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); - - const resource = URI.file(join(testDir, 'small.txt')); - - const content = readFileSync(resource.fsPath); - assert.equal(content, 'Small File'); - - const newContent = 'Updates to the small file'; - await service.writeFile(resource, toLineByLineReadable(newContent)); - - assert.equal(readFileSync(resource.fsPath), newContent); + test('writeFile (readable) - default', async () => { + return testWriteFileReadable(); }); - test('writeFile (large file - readable) - buffered', async () => { + test('writeFile (readable) - buffered', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); - const resource = URI.file(join(testDir, 'lorem.txt')); - - const content = readFileSync(resource.fsPath); - const newContent = content.toString() + content.toString(); - - const fileStat = await service.writeFile(resource, toLineByLineReadable(newContent)); - assert.equal(fileStat.name, 'lorem.txt'); - - assert.equal(readFileSync(resource.fsPath), newContent); + return testWriteFileReadable(); }); test('writeFile (readable) - unbuffered', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); + return testWriteFileReadable(); + }); + + async function testWriteFileReadable() { const resource = URI.file(join(testDir, 'small.txt')); const content = readFileSync(resource.fsPath); @@ -1541,11 +1684,25 @@ suite('Disk File Service', function () { await service.writeFile(resource, toLineByLineReadable(newContent)); assert.equal(readFileSync(resource.fsPath), newContent); + } + + test('writeFile (large file - readable) - default', async () => { + return testWriteFileLargeReadable(); + }); + + test('writeFile (large file - readable) - buffered', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); + + return testWriteFileLargeReadable(); }); test('writeFile (large file - readable) - unbuffered', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); + return testWriteFileLargeReadable(); + }); + + async function testWriteFileLargeReadable() { const resource = URI.file(join(testDir, 'lorem.txt')); const content = readFileSync(resource.fsPath); @@ -1555,55 +1712,59 @@ suite('Disk File Service', function () { assert.equal(fileStat.name, 'lorem.txt'); assert.equal(readFileSync(resource.fsPath), newContent); + } + + test('writeFile (stream) - default', async () => { + return testWriteFileStream(); }); test('writeFile (stream) - buffered', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); + return testWriteFileStream(); + }); + + test('writeFile (stream) - unbuffered', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); + + return testWriteFileStream(); + }); + + async function testWriteFileStream() { const source = URI.file(join(testDir, 'small.txt')); const target = URI.file(join(testDir, 'small-copy.txt')); - const fileStat = await service.writeFile(target, toVSBufferReadableStream(createReadStream(source.fsPath))); + const fileStat = await service.writeFile(target, streamToBufferReadableStream(createReadStream(source.fsPath))); assert.equal(fileStat.name, 'small-copy.txt'); assert.equal(readFileSync(source.fsPath).toString(), readFileSync(target.fsPath).toString()); + } + + test('writeFile (large file - stream) - default', async () => { + return testWriteFileLargeStream(); }); test('writeFile (large file - stream) - buffered', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); - const source = URI.file(join(testDir, 'lorem.txt')); - const target = URI.file(join(testDir, 'lorem-copy.txt')); - - const fileStat = await service.writeFile(target, toVSBufferReadableStream(createReadStream(source.fsPath))); - assert.equal(fileStat.name, 'lorem-copy.txt'); - - assert.equal(readFileSync(source.fsPath).toString(), readFileSync(target.fsPath).toString()); + return testWriteFileLargeStream(); }); - test('writeFile (stream) - unbuffered', async () => { + test('writeFile (large file - stream) - unbuffered', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); - const source = URI.file(join(testDir, 'small.txt')); - const target = URI.file(join(testDir, 'small-copy.txt')); - - const fileStat = await service.writeFile(target, toVSBufferReadableStream(createReadStream(source.fsPath))); - assert.equal(fileStat.name, 'small-copy.txt'); - - assert.equal(readFileSync(source.fsPath).toString(), readFileSync(target.fsPath).toString()); + return testWriteFileLargeStream(); }); - test('writeFile (large file - stream) - unbuffered', async () => { - setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); - + async function testWriteFileLargeStream() { const source = URI.file(join(testDir, 'lorem.txt')); const target = URI.file(join(testDir, 'lorem-copy.txt')); - const fileStat = await service.writeFile(target, toVSBufferReadableStream(createReadStream(source.fsPath))); + const fileStat = await service.writeFile(target, streamToBufferReadableStream(createReadStream(source.fsPath))); assert.equal(fileStat.name, 'lorem-copy.txt'); assert.equal(readFileSync(source.fsPath).toString(), readFileSync(target.fsPath).toString()); - }); + } test('writeFile (file is created including parents)', async () => { const resource = URI.file(join(testDir, 'other', 'newfile.txt')); diff --git a/src/vs/platform/files/test/node/fixtures/service/lorem.txt b/src/vs/platform/files/test/node/fixtures/service/lorem.txt index 9d348ac0901e..88c7aaee6b0d 100644 --- a/src/vs/platform/files/test/node/fixtures/service/lorem.txt +++ b/src/vs/platform/files/test/node/fixtures/service/lorem.txt @@ -280,4 +280,856 @@ Cras aliquet metus ut purus sagittis, vel venenatis ante consectetur. Pellentesq Donec vehicula mauris eget lacus mollis venenatis et sed nibh. Nam sodales ligula ipsum, scelerisque lacinia ligula sagittis in. Nam sit amet ipsum at erat malesuada congue. Aenean ut sollicitudin sapien. Etiam at tempor odio. Mauris vitae purus ut magna suscipit consequat. Vivamus quis sapien neque. Nulla vulputate sem sit amet massa pellentesque, eleifend tristique ligula egestas. Suspendisse tincidunt gravida mi, in pulvinar lectus egestas non. Aenean imperdiet ex sit amet nunc sollicitudin porta. Integer justo odio, ultricies at interdum in, rhoncus vitae sem. Sed porttitor arcu quis purus aliquet hendrerit. Praesent tempor tortor at dolor dictum pulvinar. Nulla aliquet nunc non ligula scelerisque accumsan. Donec nulla justo, congue vitae massa in, faucibus hendrerit magna. Donec non egestas purus. -Vivamus iaculis, lacus efficitur faucibus porta, dui nulla facilisis ligula, ut sodales odio nunc id sapien. Cras viverra auctor ipsum, dapibus mattis neque dictum sed. Sed convallis fermentum molestie. Nulla facilisi turpis duis. \ No newline at end of file +Vivamus iaculis, lacus efficitur faucibus porta, dui nulla facilisis ligula, ut sodales odio nunc id sapien. Cras viverra auctor ipsum, dapibu + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur vulputate, ipsum quis interdum fermentum, lorem sem fermentum eros, vitae auctor neque lacus in nisi. Suspendisse potenti. Maecenas et scelerisque elit, in tincidunt quam. Sed eu tincidunt quam. Nullam justo ex, imperdiet a imperdiet et, fermentum sit amet eros. Aenean quis tempus sem. Pellentesque accumsan magna mi, ut mollis velit sagittis id. Etiam quis ipsum orci. Fusce purus ante, accumsan a lobortis at, venenatis eu nisl. Praesent ornare sed ante placerat accumsan. Suspendisse tempus dignissim fermentum. Nunc a leo ac lacus sodales iaculis eu vitae mi. In feugiat ante at massa finibus cursus. Suspendisse posuere fringilla ornare. Mauris elementum ac quam id convallis. Vestibulum non elit quis urna volutpat aliquam a eu lacus. + +Aliquam vestibulum imperdiet neque, suscipit aliquam elit ultrices bibendum. Suspendisse ultrices pulvinar cursus. Morbi risus nisi, cursus consequat rutrum vitae, molestie sed dui. Fusce posuere, augue quis dignissim aliquam, nisi ipsum porttitor ante, quis fringilla nisl turpis ac nisi. Nulla varius enim eget lorem vehicula gravida. Donec finibus malesuada leo nec semper. Proin ac enim eros. Vivamus non tincidunt nisi, vel tristique lorem. + +Nunc consequat ex id eros dignissim, id rutrum risus laoreet. Sed euismod non erat eu ultricies. Etiam vehicula gravida lacus ut porta. Vestibulum eu eros quis nunc aliquet luctus. Cras quis semper ligula. Nullam gravida vehicula quam sed porta. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. In porta cursus vulputate. Quisque porta a nisi eget cursus. Aliquam risus leo, luctus ac magna in, efficitur cursus magna. In condimentum non mi id semper. Donec interdum ante eget commodo maximus. + +Vivamus sit amet vestibulum lectus. Fusce tincidunt mi sapien, dictum sollicitudin diam vulputate in. Integer fringilla consequat mollis. Cras aliquet consequat felis eget feugiat. Nunc tempor cursus arcu, vitae ornare nunc varius et. Vestibulum et tortor vel ante viverra porttitor. Nam at tortor ullamcorper, facilisis augue quis, tristique erat. Aenean ut euismod nibh. Quisque eu tincidunt est, nec euismod eros. + +Proin vehicula nibh non viverra egestas. Phasellus sem dolor, ultricies ac sagittis tristique, lacinia a purus. Vestibulum in ante eros. Pellentesque lacus nulla, tristique vitae interdum vel, malesuada ac diam. Aenean bibendum posuere turpis in accumsan. Ut est nulla, ullamcorper quis turpis at, viverra sagittis mauris. Sed in interdum purus. Praesent scelerisque nibh eget sem euismod, ut imperdiet mi venenatis. Vivamus pulvinar orci sed dapibus auctor. Nulla facilisi. Vestibulum tincidunt erat nec porttitor egestas. Mauris quis risus ante. Nulla facilisi. + +Aliquam ullamcorper ornare lobortis. Phasellus quis sem et ipsum mollis malesuada sed in ex. Ut aliquam ex eget metus finibus maximus. Proin suscipit mauris eu nibh lacinia, quis feugiat dui dapibus. Nam sed libero est. Aenean vulputate orci sit amet diam faucibus, eu sagittis sapien volutpat. Nam imperdiet felis turpis, at pretium odio pulvinar in. Sed vestibulum id eros nec ultricies. Sed quis aliquam tortor, vitae ullamcorper tellus. Donec egestas laoreet eros, id suscipit est rutrum nec. Sed auctor nulla eget metus aliquam, ut condimentum enim elementum. + +Aliquam suscipit non turpis sit amet bibendum. Fusce velit ligula, euismod et maximus at, luctus sed neque. Quisque pretium, nisl at ullamcorper finibus, lectus leo mattis sapien, vel euismod mauris diam ullamcorper ex. Nulla ut risus finibus, lacinia ligula at, auctor erat. Mauris consectetur sagittis ligula vel dapibus. Nullam libero libero, lobortis aliquam libero vel, venenatis ultricies leo. Duis porttitor, nibh congue fermentum posuere, erat libero pulvinar tortor, a pellentesque nunc ipsum vel sem. Nullam volutpat, eros sit amet facilisis consectetur, ipsum est vehicula massa, non vestibulum neque elit in mauris. Nunc hendrerit ipsum non enim bibendum, vitae rhoncus mi egestas. Etiam ullamcorper massa vel nisl sagittis, nec bibendum arcu malesuada. Aenean aliquet turpis justo, a consectetur arcu mollis convallis. Etiam tellus ipsum, ultricies vitae lorem et, ornare facilisis orci. Praesent fringilla justo urna, vel mollis neque pulvinar vestibulum. + +Donec non iaculis erat. Aliquam et mi sed nunc pulvinar ultricies in ut ipsum. Interdum et malesuada fames ac ante ipsum primis in faucibus. Praesent feugiat lacus ac dignissim semper. Phasellus vitae quam nisi. Morbi vel diam ultricies risus lobortis ornare. Fusce maximus et ligula quis iaculis. Sed congue ex eget felis convallis, sit amet hendrerit elit tempor. Donec vehicula blandit ante eget commodo. Vestibulum eleifend diam at feugiat euismod. Etiam magna tellus, dignissim eget fermentum vel, vestibulum vitae mauris. Nam accumsan et erat id sagittis. Donec lacinia, odio ut ornare ultricies, dolor velit accumsan tortor, non finibus erat tellus quis ligula. Nunc quis metus in leo volutpat ornare vulputate eu nisl. + +Donec quis viverra ex. Nullam id feugiat mauris, eu fringilla nulla. Vestibulum id maximus elit. Cras elementum elit sed felis lobortis, eget sagittis nisi hendrerit. Vivamus vitae elit neque. Donec vulputate lacus ut libero ultrices accumsan. Vivamus accumsan nulla orci, in dignissim est laoreet sagittis. Proin at commodo velit. Curabitur in velit felis. Aliquam erat volutpat. Sed consequat, nulla et cursus sodales, nisi lacus mattis risus, quis eleifend erat ex nec turpis. Sed suscipit ultrices lorem in hendrerit. + +Morbi vitae lacus nec libero ornare tempus eu et diam. Suspendisse magna ipsum, fermentum vel odio quis, molestie aliquam urna. Fusce mollis turpis a eros accumsan porttitor. Pellentesque rhoncus dolor sit amet magna rutrum, et dapibus justo tempor. Sed purus nisi, maximus vitae fringilla eu, molestie nec urna. Fusce malesuada finibus pretium. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec sed aliquet eros. Pellentesque luctus diam ante, eget euismod nisl aliquet eu. Sed accumsan elit purus, tempor varius ligula tempus nec. Curabitur ornare leo suscipit suscipit fermentum. Morbi eget nulla est. Maecenas faucibus interdum tristique. + +Etiam ut elit eros. Nulla pharetra suscipit molestie. Nulla facilisis bibendum nisl non molestie. Curabitur turpis lectus, facilisis vel diam non, vulputate ultrices mauris. Aenean placerat aliquam convallis. Suspendisse sed scelerisque tellus. Vivamus lacinia neque eget risus cursus suscipit. Proin consequat dolor vel neque tempor, eu aliquam sem scelerisque. Duis non eros a purus malesuada pharetra non et nulla. Suspendisse potenti. Mauris libero eros, finibus vel nulla id, sagittis dapibus ante. Proin iaculis sed nunc et cursus. + +Quisque accumsan lorem sit amet lorem aliquet euismod. Curabitur fermentum rutrum posuere. Etiam ultricies, sem id pellentesque suscipit, urna magna lacinia eros, quis efficitur risus nisl at lacus. Nulla quis lacus tortor. Mauris placerat ex in dolor tincidunt, vel aliquet nisi pretium. Cras iaculis risus vitae pellentesque aliquet. Quisque a enim imperdiet, ullamcorper arcu vitae, rutrum risus. Nullam consectetur libero at felis fringilla, nec congue nibh dignissim. Nam et lobortis felis, eu pellentesque ligula. Aenean facilisis, ligula non imperdiet maximus, massa orci gravida sapien, at sagittis lacus nisl in lacus. Nulla quis mauris luctus, scelerisque felis consequat, tempus risus. Fusce auctor nisl non nulla luctus molestie. Maecenas sapien nisl, auctor non dolor et, iaculis scelerisque lorem. Suspendisse egestas enim aliquet, accumsan mauris nec, posuere quam. Nulla iaculis dui dui, sit amet vestibulum erat ultricies ac. + +Cras eget dolor erat. Proin at nisl ut leo consectetur ultricies vel ut arcu. Nulla in felis malesuada, ullamcorper tortor et, convallis massa. Nunc urna justo, ornare in nibh vitae, hendrerit condimentum libero. Etiam vitae libero in purus venenatis fringilla. Nullam velit nulla, consequat ut turpis non, egestas hendrerit nibh. Duis tortor turpis, interdum non ante ac, cursus accumsan lectus. Cras pharetra bibendum augue quis dictum. Sed euismod vestibulum justo. Proin porta lobortis purus. Duis venenatis diam tortor, sit amet condimentum eros rhoncus a. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nunc at magna nec diam lobortis efficitur sit amet ut lacus. Nulla quis orci tortor. Pellentesque tempus velit a odio finibus porta. + +Proin feugiat mauris a tellus scelerisque convallis. Maecenas libero magna, blandit nec ultrices id, congue vel mi. Aliquam lacinia, quam vel condimentum convallis, tortor turpis aliquam odio, sed blandit libero lacus et eros. In eleifend iaculis magna ac finibus. Praesent auctor facilisis tellus in congue. Sed molestie lobortis dictum. Nam quis dignissim augue, vel euismod lorem. Curabitur posuere dapibus luctus. Donec ultricies dictum lectus, quis blandit arcu commodo ac. Aenean tincidunt ligula in nunc imperdiet dignissim. Curabitur egestas sollicitudin sapien ut semper. Aenean nec dignissim lacus. + +Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec aliquam dictum vehicula. Donec tortor est, volutpat non nisi nec, varius gravida ex. Nunc vel tristique nunc, vitae mattis nisi. Nunc nec luctus ex, vitae tincidunt lectus. In hac habitasse platea dictumst. Curabitur lobortis ex eget tincidunt tempor. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Ut a vehicula mi. + +Fusce eu libero finibus, interdum nulla a, placerat neque. Cras bibendum tempor libero nec feugiat. Cras ut sodales eros. Proin viverra, massa sit amet viverra egestas, neque nisl porta ex, sit amet hendrerit libero ligula vel urna. Mauris suscipit lacus id justo rhoncus suscipit. Etiam vel libero tellus. Maecenas non diam molestie, condimentum tellus a, bibendum enim. Mauris aliquet imperdiet tellus, eget sagittis dolor. Sed blandit in neque et luctus. Cras elementum sagittis nunc, vel mollis lorem euismod et. Donec posuere at lacus eget suscipit. + +Nulla nunc mi, pretium non massa vel, tempor semper magna. Nunc a leo pulvinar, tincidunt nunc at, dignissim mi. Aliquam erat volutpat. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Ut viverra nulla a nisl finibus, at hendrerit ligula ullamcorper. Donec a lorem semper, tempor magna et, lobortis libero. Mauris id sapien leo. Donec dignissim, quam vitae porttitor dignissim, quam justo mattis dui, vel consequat odio elit quis orci. Etiam nec pretium neque, sit amet pretium orci. Duis ac tortor venenatis, feugiat purus non, feugiat nunc. Proin scelerisque nisl in turpis aliquam vulputate. + +Praesent sed est semper, fringilla lorem vitae, tincidunt nibh. Cras eros metus, auctor at mauris sit amet, sodales semper orci. Nunc a ornare ex. Curabitur bibendum arcu congue urna vulputate egestas. Vestibulum finibus id risus et accumsan. Aenean ut volutpat tellus. Aenean tincidunt malesuada urna sit amet vestibulum. Mauris vel tellus dictum, varius lacus quis, dictum arcu. + +Aenean quis metus eu erat feugiat cursus vel at ligula. Proin dapibus sodales urna, id euismod lectus tempus id. Pellentesque ex ligula, convallis et erat vel, vulputate condimentum nisl. Pellentesque pharetra nulla quis massa eleifend hendrerit. Praesent sed massa ipsum. Maecenas vehicula dolor massa, id sodales urna faucibus et. Mauris ac quam non massa tincidunt feugiat et at lacus. Fusce libero massa, vulputate vel scelerisque non, mollis in leo. Ut sit amet ultricies odio. Suspendisse in sapien viverra, facilisis purus ut, pretium libero. + +Vivamus tristique pharetra molestie. Nam a volutpat purus. Praesent consequat gravida nisi, ac blandit nisi suscipit ut. Quisque posuere, ligula a ultrices laoreet, ligula nunc vulputate libero, ut rutrum erat odio tincidunt justo. Sed vitae leo at leo fringilla bibendum. Vestibulum ut augue nec dolor auctor accumsan. Praesent laoreet id eros pulvinar commodo. Suspendisse potenti. Ut pharetra, mauris vitae blandit fringilla, odio ante tincidunt lorem, sit amet tempor metus diam ut turpis. + +Praesent quis egestas arcu. Nullam at porta arcu. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Morbi vulputate ligula malesuada ligula luctus, vulputate tempus erat bibendum. Nunc ullamcorper non lectus at euismod. Etiam nibh felis, tincidunt a metus vel, pellentesque rhoncus neque. Etiam at diam in erat luctus interdum. Nunc vel ipsum pulvinar, sollicitudin lacus ac, tempus urna. Etiam vel lacinia sapien. Pellentesque sagittis velit vel mi efficitur iaculis. Integer euismod sit amet urna in sagittis. Cras eleifend ut nibh in facilisis. Donec et lacus vitae nunc placerat sodales. Nulla sed hendrerit ligula, at dapibus sapien. + +Praesent at iaculis ex. Curabitur est purus, cursus a faucibus quis, dictum id velit. Donec dignissim fringilla viverra. Nunc mauris felis, laoreet sit amet sagittis at, vestibulum in libero. Maecenas quis orci turpis. Quisque ut nibh vitae magna mollis consequat id at mauris. Aliquam eu odio eget nulla bibendum sodales. Quisque vel orci eleifend nisi pretium lacinia. Suspendisse eget risus eget mi volutpat molestie eget quis lacus. Duis nisi libero, tincidunt nec nulla id, faucibus cursus felis. + +Donec tempor eget risus pellentesque molestie. Phasellus porta neque vel arcu egestas, nec blandit velit fringilla. Nullam porta faucibus justo vitae laoreet. Pellentesque viverra id nunc eu varius. Nulla pulvinar lobortis iaculis. Etiam vestibulum odio nec velit tristique, a tristique nisi mattis. In sed fringilla orci, vitae efficitur odio. Quisque dui odio, ornare eget velit at, lacinia consequat libero. Quisque lectus nulla, aliquet eu leo in, porta rutrum diam. Donec nec mattis neque. Nam rutrum, odio ac eleifend bibendum, dolor arcu rutrum neque, eget porta elit tellus a lacus. Sed massa metus, sollicitudin et sapien eu, finibus tempus orci. Proin et sapien sit amet erat molestie interdum. In quis rutrum velit, faucibus ultrices tellus. + +Sed sagittis sed justo eget tincidunt. Maecenas ut leo sagittis, feugiat magna et, viverra velit. Maecenas ex arcu, feugiat at consequat vitae, auctor eu massa. Integer egestas, enim vitae maximus convallis, est lectus pretium mauris, ac posuere lectus nisl quis quam. Aliquam tempus laoreet mi, vitae dapibus dolor varius dapibus. Suspendisse potenti. Donec sit amet purus nec libero dapibus tristique. Pellentesque viverra bibendum ligula. Donec sed felis et ex lobortis laoreet. Phasellus a fringilla libero, vitae malesuada nulla. Pellentesque blandit mattis lacus, et blandit tortor laoreet consequat. Suspendisse libero nunc, viverra sed fermentum in, accumsan egestas arcu. Proin in placerat elit. Sed interdum imperdiet malesuada. Suspendisse aliquet quis mauris eget sollicitudin. + +Vivamus accumsan tellus non erat volutpat, quis dictum dolor feugiat. Praesent rutrum nunc ac est mollis cursus. Fusce semper volutpat dui ut egestas. Curabitur sit amet posuere massa. Cras tincidunt nulla et mi mollis imperdiet. Suspendisse scelerisque ex id sodales vulputate. In nunc augue, pharetra in placerat eu, mattis id tellus. Vivamus cursus efficitur vehicula. Nulla aliquet vehicula aliquet. + +Sed cursus tellus sed porta pulvinar. Sed vitae nisi neque. Nullam aliquet, lorem et efficitur scelerisque, arcu diam aliquam felis, sed pulvinar lorem odio et turpis. Praesent convallis pulvinar turpis eu iaculis. Aliquam nec gravida mi. Curabitur eu nibh tempor, blandit justo in, ultrices felis. Fusce placerat metus non mi sagittis rutrum. Morbi sed dui fringilla, sagittis mauris eget, imperdiet nunc. Phasellus hendrerit sem elit, id hendrerit libero auctor sit amet. Integer sodales elit sit amet consequat cursus. + +Nam semper est eget nunc mollis, in pellentesque lectus fringilla. In finibus vel diam id semper. Nunc mattis quis erat eu consectetur. In hac habitasse platea dictumst. Nullam et ipsum vestibulum ex pulvinar ultricies sit amet id velit. Aenean suscipit mi tortor, a lobortis magna viverra non. Nulla condimentum aliquet ante et ullamcorper. Pellentesque porttitor arcu a posuere tempus. Aenean lacus quam, imperdiet eu justo vitae, pretium efficitur ex. Duis id purus id magna rhoncus ultrices id eu risus. Nunc dignissim et libero id dictum. + +Quisque a tincidunt neque. Phasellus commodo mi sit amet tempor fringilla. Ut rhoncus, neque non porttitor elementum, libero nulla egestas augue, sed fringilla sapien felis ac velit. Phasellus viverra rhoncus mollis. Nam ullamcorper leo vel erat laoreet luctus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Vivamus semper a metus a cursus. Nulla sed orci egestas, efficitur purus ac, malesuada tellus. Aenean rutrum velit at tellus fermentum mollis. Aliquam eleifend euismod metus. + +In hac habitasse platea dictumst. Vestibulum volutpat neque vitae porttitor laoreet. Nam at tellus consequat, sodales quam in, pulvinar arcu. Maecenas varius convallis diam, ac lobortis tellus pellentesque quis. Maecenas eget augue massa. Nullam volutpat nibh ac justo rhoncus, ut iaculis tellus rutrum. Fusce efficitur efficitur libero quis condimentum. Curabitur congue neque non tincidunt tristique. Fusce eget tempor ex, at pellentesque odio. Praesent luctus dictum vestibulum. Etiam non orci nunc. Vivamus vitae laoreet purus, a lobortis velit. Curabitur tincidunt purus ac lectus elementum pellentesque. Quisque sed tincidunt est. + +Sed vel ultrices massa, vitae ultricies justo. Cras finibus mauris nec lacus tempus dignissim. Cras faucibus maximus velit, eget faucibus orci luctus vehicula. Nulla massa nunc, porta ac consequat eget, rhoncus non tellus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Fusce sed maximus metus, vel imperdiet ipsum. Ut scelerisque lectus at blandit porttitor. Ut vulputate nunc pharetra, aliquet sapien ac, sollicitudin sapien. Aenean eget ante lorem. Nam accumsan venenatis tellus id dignissim. + +Curabitur fringilla, magna non maximus dapibus, nulla sapien vestibulum lectus, sit amet semper dolor neque vitae nisl. Nunc ultrices vehicula augue sed iaculis. Maecenas nec diam mollis, suscipit orci et, vestibulum ante. Pellentesque eu nisl tortor. Nunc eleifend, lacus quis volutpat volutpat, nisi mi molestie sem, quis mollis ipsum libero a tellus. Ut viverra dolor mattis convallis interdum. Sed tempus nisl at nunc scelerisque aliquet. Quisque tempor tempor lorem id feugiat. Nullam blandit lectus velit, vitae porta lacus tincidunt a. Vivamus sit amet arcu ultrices, tincidunt mi quis, viverra quam. Aenean fringilla libero elementum lorem semper, quis pulvinar eros gravida. Nullam sodales blandit mauris, sed fermentum velit fermentum sit amet. Donec malesuada mauris in augue sodales vulputate. Vestibulum gravida turpis id elit rhoncus dignissim. Integer non congue lorem, eu viverra orci. + +Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec at dolor magna. Aliquam consectetur erat augue, id iaculis velit pharetra ac. Integer rutrum venenatis dignissim. Integer non sodales elit. Curabitur ut magna ut nibh feugiat aliquam ac ut risus. Morbi nibh quam, aliquam id placerat nec, vestibulum eget velit. Suspendisse at dignissim quam. Vivamus aliquet sem sed nisl volutpat, ut cursus orci ultrices. Aliquam ultrices lacinia enim, vitae aliquet neque. + +Quisque scelerisque finibus diam in mattis. Cras cursus auctor velit. Aliquam sem leo, fermentum et maximus et, molestie a libero. Aenean justo elit, rutrum a ornare id, egestas eget enim. Aenean auctor tristique erat. Curabitur condimentum libero lacus, nec consequat orci vestibulum sed. Fusce elit ligula, blandit vitae sapien vitae, dictum ultrices risus. Nam laoreet suscipit sapien, at interdum velit faucibus sit amet. Duis quis metus egestas lectus elementum posuere non nec libero. Aliquam a dolor bibendum, facilisis nunc a, maximus diam. Vestibulum suscipit tristique magna, non dignissim turpis sodales sed. Nunc ornare, velit ac facilisis fringilla, dolor mi consectetur lorem, vitae finibus erat justo suscipit urna. Maecenas sit amet eros erat. Nunc non arcu ornare, suscipit lorem eget, sodales mauris. Aliquam tincidunt, quam nec mollis lacinia, nisi orci fermentum libero, consequat eleifend lectus quam et sapien. Vestibulum a quam urna. + +Cras arcu leo, euismod ac ullamcorper at, faucibus sed massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus porttitor velit in enim interdum, non commodo metus ornare. Morbi vel lorem quis nisl luctus tristique quis vitae nisl. Suspendisse condimentum tortor enim, nec eleifend ipsum euismod et. Sed gravida quam ut tristique lacinia. Mauris eu interdum ipsum, ac ultrices odio. Nullam auctor tellus a risus porttitor vehicula. Nulla blandit euismod dictum. In pharetra, enim iaculis pulvinar interdum, dui nunc placerat nunc, sit amet pretium lectus nulla vitae quam. Phasellus quis enim sollicitudin, varius nulla id, ornare purus. Donec quam lacus, vestibulum quis nunc ac, mollis dictum nisi. Cras ut mollis elit. Maecenas ultrices ligula at risus faucibus scelerisque. Etiam vitae porttitor purus. Curabitur blandit lectus urna, ut hendrerit tortor feugiat ut. + +Phasellus fringilla, sapien pellentesque commodo pharetra, ante libero aliquam tellus, ut consectetur augue libero a sapien. Maecenas blandit luctus nisl eget aliquet. Maecenas vitae porta dolor, faucibus laoreet sapien. Suspendisse lobortis, ipsum sed vehicula aliquam, elit purus scelerisque dui, rutrum consectetur diam odio et lorem. In nec lacinia metus. Donec viverra libero est, vel bibendum erat condimentum quis. Donec feugiat purus leo. In laoreet vitae felis a porttitor. Mauris ullamcorper, lacus id condimentum suscipit, neque magna pellentesque arcu, eget cursus neque tellus id metus. Curabitur volutpat ac orci vel ultricies. + +Sed ut finibus erat. Sed diam purus, varius non tincidunt quis, ultrices sit amet ipsum. Donec et egestas nulla. Suspendisse placerat nisi at dui laoreet iaculis. Aliquam aliquet leo at augue faucibus molestie. Nullam lacus augue, hendrerit sed nisi eu, faucibus porta est. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam ut leo aliquet sem fermentum rutrum quis ac justo. Integer placerat aliquam nisl ut sagittis. Proin erat orci, lobortis et sem eget, eleifend fringilla augue. Mauris varius laoreet arcu, sed tincidunt felis. Pellentesque venenatis lorem odio, id pulvinar velit molestie feugiat. Donec mattis lacus sed eleifend pulvinar. + +Sed condimentum ex in tincidunt hendrerit. Etiam eget risus lacinia, euismod nibh eu, pellentesque quam. Proin elit eros, convallis id mauris ac, bibendum ultrices lectus. Morbi venenatis, purus id fermentum consequat, nunc libero tincidunt ligula, non dictum ligula orci nec quam. Nulla nec ultrices lorem. Aenean maximus augue vel dictum pharetra. Etiam turpis urna, pellentesque quis malesuada eu, molestie faucibus felis. + +Vestibulum pharetra augue ut quam blandit congue in nec risus. Proin eu nibh eu dui eleifend porta vitae id lectus. Proin lacus nibh, lobortis sed ligula vitae, interdum lobortis erat. Suspendisse potenti. In sollicitudin quis sapien ut aliquet. Mauris ac nulla arcu. Fusce tristique justo quis lectus mollis, eu volutpat lectus finibus. Vivamus venenatis facilisis ex ut vestibulum. + +Etiam varius lobortis purus, in hendrerit elit tristique at. In tempus, augue vestibulum fermentum gravida, ligula tellus vulputate arcu, eu molestie ex sapien at purus. Vestibulum nec egestas metus. Duis pulvinar quam nec consequat interdum. Aenean non dapibus lacus. Aliquam sit amet aliquet nulla. Sed venenatis volutpat purus nec convallis. Phasellus aliquet semper sodales. Cras risus sapien, condimentum auctor urna a, pulvinar ornare nisl. Sed tincidunt felis elit, ut elementum est bibendum ac. Morbi interdum justo vel dui faucibus condimentum. + +Sed convallis eu sem at tincidunt. Nullam at auctor est, et ullamcorper ipsum. Pellentesque eget ante ante. Interdum et malesuada fames ac ante ipsum primis in faucibus. Integer euismod, sapien sed dapibus ornare, nibh enim maximus lacus, lacinia placerat urna quam quis felis. Morbi accumsan id nisl ut condimentum. Donec bibendum nisi est, sed volutpat lorem rhoncus in. Vestibulum ac lacinia nunc, eget volutpat magna. Integer aliquam pharetra ipsum, id placerat nunc volutpat quis. Etiam urna diam, rhoncus sit amet varius vel, euismod vel sem. Nullam vel molestie urna. Vivamus ornare erat at venenatis euismod. Suspendisse potenti. Fusce diam justo, tincidunt vel sem at, commodo faucibus nisl. Duis gravida efficitur diam, vel sagittis erat pulvinar ut. + +Quisque vel pharetra felis. Duis efficitur tortor dolor, vitae porttitor erat fermentum sed. Sed eu mi purus. Etiam dignissim tortor eu tempus molestie. Aenean pretium erat enim, in hendrerit ante hendrerit at. Sed ut risus vel nunc venenatis ultricies quis in lacus. Pellentesque vitae purus euismod, placerat risus non, ullamcorper augue. Quisque varius quam ligula, nec aliquet ex faucibus vitae. Quisque rhoncus sit amet leo tincidunt mattis. Cras id mauris eget purus pretium gravida sit amet eu augue. Aliquam dapibus odio augue, id lacinia velit pulvinar eu. + +Mauris fringilla, tellus nec pharetra iaculis, neque nisi ultrices massa, et tincidunt sem dui sed mi. Curabitur erat lorem, venenatis quis tempus lacinia, tempus sit amet nunc. Aliquam at neque ac metus commodo dictum quis vitae justo. Phasellus eget lacus tempus, blandit lorem vel, rutrum est. Aenean pharetra sem ut augue lobortis dignissim. Sed rhoncus at nulla id ultrices. Cras id condimentum felis. In suscipit luctus vulputate. Donec tincidunt lacus nec enim tincidunt sollicitudin ut quis enim. Nam at libero urna. Praesent sit amet massa vitae massa ullamcorper vehicula. + +Nullam bibendum augue ut turpis condimentum bibendum. Proin sit amet urna hendrerit, sodales tortor a, lobortis lectus. Integer sagittis velit turpis, et tincidunt nisi commodo eget. Duis tincidunt elit finibus accumsan cursus. Aenean dignissim scelerisque felis vel lacinia. Nunc lacinia maximus luctus. In hac habitasse platea dictumst. Vestibulum eget urna et enim tempor tempor. Nam feugiat, felis vel vestibulum tempus, orci justo viverra diam, id dapibus lorem justo in ligula. + +Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In ac pellentesque sem. Vestibulum lacinia magna dui, eu lacinia augue placerat et. Maecenas pulvinar congue est. Pellentesque commodo dui non pulvinar scelerisque. Etiam interdum est posuere sem bibendum, ac commodo magna dictum. Cras ipsum turpis, rhoncus nec posuere vitae, laoreet a arcu. Integer ac massa sit amet enim placerat lacinia sed ultrices arcu. Suspendisse sem nibh, luctus sit amet volutpat in, pellentesque eu metus. Ut gravida neque eget mi accumsan tempus. Nam sit amet aliquet nibh. + +Pellentesque a purus cursus nulla hendrerit congue quis et odio. Aenean hendrerit, leo ullamcorper sagittis hendrerit, erat dui molestie quam, sed condimentum lacus risus sed tellus. Morbi a dapibus lectus, ut feugiat ex. Phasellus pretium quam et sapien mollis, vel iaculis dui dignissim. Sed ullamcorper est turpis, a viverra lorem consectetur in. Aenean aliquet nibh non cursus rutrum. Suspendisse at tristique urna, id lobortis urna. In hac habitasse platea dictumst. Phasellus libero velit, rutrum sed tellus nec, dapibus tincidunt ligula. Quisque vel dui venenatis, consequat nisl ut, lacinia ipsum. Phasellus vitae magna pellentesque, lobortis est id, faucibus quam. Nam eleifend faucibus dui vel pellentesque. + +Etiam ut est non lacus tincidunt interdum. Maecenas sed massa urna. Quisque ut nibh tortor. Pellentesque felis ipsum, tempor finibus ipsum et, euismod pretium metus. Donec sit amet est ipsum. Quisque rhoncus justo non finibus elementum. Nulla nec lectus ac tortor placerat fringilla. Phasellus ac ultrices nunc, eu efficitur nisl. Nulla rhoncus nunc vitae ante dictum tincidunt. Nunc ultrices, massa sit amet malesuada dignissim, lectus lacus consequat sapien, non eleifend metus sem in eros. Phasellus mauris ante, dictum sit amet suscipit ac, rhoncus eget nisi. Phasellus at orci mollis, imperdiet neque eget, faucibus nulla. In at purus massa. Pellentesque quis rutrum lectus. + +Integer eu faucibus turpis, sit amet mollis massa. Vestibulum id nulla commodo, rutrum ipsum sed, semper ante. Phasellus condimentum orci nec nibh convallis, ac maximus orci ullamcorper. Maecenas vitae sollicitudin mi. Integer et finibus lectus, et condimentum ligula. Donec elementum tristique quam vitae dapibus. Morbi euismod ipsum in tristique ullamcorper. + +Duis fermentum non enim eu auctor. Quisque lacinia nibh vehicula nibh posuere, eu volutpat turpis facilisis. Ut ac faucibus nulla. Sed eleifend quis ex et pellentesque. Vestibulum sollicitudin in libero id fringilla. Phasellus dignissim purus consequat, condimentum dui sit amet, condimentum ante. Pellentesque ac consectetur massa, quis sagittis est. Nulla maximus tristique risus accumsan convallis. Curabitur imperdiet ac lacus a ultrices. Nulla facilisi. Sed quis quam quis lectus placerat lobortis vel sed turpis. In mollis dui id neque iaculis, ut aliquet tellus malesuada. Proin at luctus odio, vel blandit sapien. Praesent dignissim tortor vehicula libero fringilla, nec ultrices erat suscipit. Maecenas scelerisque purus in dapibus fermentum. + +Curabitur magna odio, mattis in tortor ut, porttitor congue est. Vestibulum mollis lacinia elementum. Fusce maximus erat vitae nunc rutrum lobortis. Integer ligula eros, auctor vel elit non, posuere luctus lacus. Maecenas quis auctor massa. Ut ipsum lacus, efficitur posuere euismod et, hendrerit efficitur est. Phasellus fringilla, quam id tincidunt pretium, nunc dui sollicitudin orci, eu dignissim nisi metus ut magna. Integer lobortis interdum dolor, non bibendum purus posuere et. Donec non lectus aliquet, pretium dolor eu, cursus massa. Sed ut dui sapien. In sed vestibulum massa. Pellentesque blandit, dui non sodales vehicula, orci metus mollis nunc, non pharetra ex tellus ac est. Mauris sagittis metus et fermentum pretium. Nulla facilisi. Quisque quis ante ut nulla placerat mattis ut quis nisi. + +Sed quis nulla ligula. Quisque dignissim ligula urna, sed aliquam purus semper at. Suspendisse potenti. Nunc massa lectus, pharetra vehicula arcu bibendum, imperdiet sodales ipsum. Nam ac sapien diam. Mauris iaculis fringilla mattis. Pellentesque tempus eros sit amet justo volutpat mollis. Phasellus ac turpis ipsum. Morbi vel ante elit. Aenean posuere quam consequat velit varius suscipit. Donec tempor quam ut nibh cursus efficitur. + +Morbi molestie dolor nec sem egestas suscipit. Etiam placerat pharetra lectus, et ullamcorper risus tristique in. Sed faucibus ullamcorper lectus eget fringilla. Maecenas malesuada hendrerit congue. Sed eget neque a erat placerat tincidunt. Aliquam vitae dignissim turpis. Fusce at placerat magna, a laoreet lectus. Maecenas a purus nec diam gravida fringilla. Nam malesuada euismod ante non vehicula. In faucibus bibendum leo, faucibus posuere nisl pretium quis. Fusce finibus bibendum finibus. Vestibulum eu justo maximus, hendrerit diam nec, dignissim sapien. Aenean dolor lacus, malesuada quis vestibulum ac, venenatis ac ipsum. Cras a est id nunc finibus facilisis. Cras lacinia neque et interdum vehicula. Suspendisse vulputate tellus elit, eget tempor dui finibus vel. + +Cras sed pretium odio. Proin hendrerit elementum felis in tincidunt. Nam sed turpis vel justo molestie accumsan condimentum eu nunc. Praesent lobortis euismod rhoncus. Nulla vitae euismod nibh, quis mattis mi. Fusce ultrices placerat porttitor. Duis sem ipsum, pellentesque sit amet odio a, molestie vulputate mauris. + +Duis blandit mollis ligula, sit amet mattis ligula finibus sit amet. Nunc a leo molestie, placerat diam et, vestibulum leo. Suspendisse facilisis neque purus, nec pellentesque ligula fermentum nec. Aenean malesuada mauris lorem, eu blandit arcu pulvinar quis. Duis laoreet urna lacus, non maximus arcu rutrum ultricies. Nulla augue dolor, suscipit eu mollis eu, aliquam condimentum diam. Ut semper orci luctus, pharetra turpis at, euismod mi. Nulla leo diam, finibus sit amet purus sed, maximus dictum lorem. Integer eu mi id turpis laoreet rhoncus. + +Integer a mauris tincidunt, finibus orci ut, pretium mauris. Nulla molestie nunc mi, id finibus lorem elementum sed. Proin quis laoreet ante. Integer nulla augue, commodo id molestie quis, rutrum ut turpis. Suspendisse et tortor turpis. Sed ut pharetra massa. Pellentesque elementum blandit sem, ut elementum tellus egestas a. Fusce eu purus nibh. + +Cras dignissim ligula scelerisque magna faucibus ullamcorper. Proin at condimentum risus, auctor malesuada quam. Nullam interdum interdum egestas. Nulla aliquam nisi vitae felis mollis dictum. Suspendisse dapibus consectetur tortor. Ut ut nisi non sem bibendum tincidunt. Vivamus suscipit leo quis gravida dignissim. + +Aliquam interdum, leo id vehicula mollis, eros eros rhoncus diam, non mollis ligula mi eu mauris. Sed ultrices vel velit sollicitudin tincidunt. Nunc auctor metus at ligula gravida elementum. Praesent interdum eu elit et mollis. Duis egestas quam sit amet velit dignissim consequat. Aliquam ac turpis nec nunc convallis sagittis. Fusce blandit, erat ac fringilla consectetur, dolor eros sodales leo, vel aliquet risus nisl et diam. Aliquam luctus felis vitae est eleifend euismod facilisis et lacus. Sed leo tellus, auctor eu arcu in, volutpat sagittis nisl. Pellentesque nisl ligula, placerat vel ullamcorper at, vulputate ac odio. Morbi ac faucibus orci, et tempus nulla. Proin rhoncus rutrum dolor, in venenatis mauris. Suspendisse a fermentum augue, non semper mi. Nunc eget pretium neque. Phasellus augue erat, feugiat ac aliquam congue, rutrum non sapien. Pellentesque ac diam gravida, consectetur felis at, ornare neque. + +Nullam interdum mattis sapien quis porttitor. Interdum et malesuada fames ac ante ipsum primis in faucibus. Phasellus aliquet rutrum ipsum id euismod. Maecenas consectetur massa et mi porta viverra. Nunc quam nibh, dignissim vitae maximus et, ullamcorper nec lorem. Nunc vitae justo dapibus, luctus lacus vitae, pretium elit. Maecenas et efficitur leo. Curabitur mauris lectus, placerat quis vehicula vitae, auctor ut urna. Quisque rhoncus pharetra luctus. In hac habitasse platea dictumst. Integer sit amet metus nec eros malesuada aliquam. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Morbi hendrerit mi ac leo aliquam, sit amet ultricies libero commodo. Mauris dapibus purus metus, sit amet viverra nibh imperdiet et. Nullam porta nulla tellus, quis vehicula diam imperdiet non. Vivamus enim massa, bibendum in fermentum in, ultrices at ex. + +Suspendisse fermentum id nibh eget accumsan. Duis dapibus bibendum erat ut sollicitudin. Aliquam nec felis risus. Pellentesque rhoncus ligula id sem maximus mollis sed nec massa. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus ipsum ipsum, sodales sed enim id, convallis faucibus eros. Donec ultricies dictum tincidunt. Cras vitae nibh arcu. Pellentesque cursus, sapien nec consequat fermentum, ipsum ante suscipit dui, imperdiet hendrerit est nisl eu massa. Quisque vitae sem ligula. Aenean iaculis metus ut mauris interdum laoreet. Vivamus sed gravida dolor. + +Morbi nulla metus, porttitor sed eros sit amet, efficitur efficitur est. In vel nisl urna. Ut aliquet tellus at congue convallis. Phasellus imperdiet lobortis sollicitudin. Integer sodales, sem eu ultricies pharetra, erat erat porttitor odio, eget dapibus libero ipsum eget velit. Phasellus gravida nulla nisl, eu pharetra mi auctor vel. Sed blandit pharetra velit, ut egestas libero placerat non. Aliquam a interdum quam. Proin at tortor nec dui sollicitudin tempus sed vestibulum elit. Nunc non sollicitudin velit. + +Aenean consequat diam velit, sed rutrum tortor faucibus dictum. Quisque at semper augue. Duis ut est eget mi ornare bibendum id et ligula. Phasellus consequat tortor non leo pulvinar posuere. Proin vestibulum eleifend felis, in hendrerit tortor sollicitudin eu. Phasellus hendrerit, lacus vel laoreet interdum, dui tortor consequat justo, commodo ultricies arcu felis vitae enim. Vivamus eu sapien at leo suscipit rutrum eu at justo. Aenean et dolor a libero ullamcorper posuere. Integer laoreet placerat nisi in vulputate. Mauris laoreet eget risus sed cursus. Donec scelerisque neque a libero eleifend hendrerit. Nulla varius condimentum nunc sit amet fermentum. Aliquam lorem ex, varius nec mollis ut, ultrices in neque. Morbi sit amet porta leo. Integer iaculis fermentum lacus in vestibulum. + +Ut gravida, tellus ut maximus ultrices, erat est venenatis nisl, vitae pretium massa ex ac magna. Sed non purus eget ligula aliquet volutpat non quis arcu. Nam aliquam tincidunt risus, sit amet fringilla sapien vulputate ut. Mauris luctus suscipit pellentesque. Nunc porttitor dapibus ex quis tempus. Ut ullamcorper metus a eros vulputate, vitae viverra lectus convallis. Mauris semper imperdiet augue quis tincidunt. Integer porta pretium magna, sed cursus sem scelerisque sollicitudin. Nam efficitur, nibh pretium eleifend vestibulum, purus diam posuere sem, in egestas mauris augue sit amet urna. + +Vestibulum tincidunt euismod massa in congue. Duis interdum metus non laoreet fringilla. Donec at ligula congue, tincidunt nunc non, scelerisque nunc. Donec bibendum magna non est scelerisque feugiat at nec neque. Ut orci tortor, tempus eget massa non, dignissim faucibus dolor. Nam odio risus, accumsan pretium neque eget, accumsan dignissim dui. In ut neque auctor, scelerisque tellus sed, ullamcorper nisi. Suspendisse varius cursus quam at hendrerit. Vivamus elit libero, sagittis vitae sem ac, vulputate iaculis ligula. + +Sed lobortis laoreet purus sit amet rutrum. Pellentesque feugiat non leo vel lacinia. Quisque feugiat nisl a orci bibendum vestibulum. In et sollicitudin urna. Morbi a arcu ac metus faucibus tempus. Nam eu imperdiet sapien, suscipit mattis tortor. Aenean blandit ipsum nisi, a eleifend ligula euismod at. Integer tincidunt pharetra felis, mollis placerat mauris hendrerit at. Curabitur convallis, est sit amet luctus volutpat, massa lacus cursus augue, sed eleifend magna quam et risus. Aliquam lobortis tincidunt metus vitae porttitor. Suspendisse potenti. Aenean ullamcorper, neque id commodo luctus, nulla nunc lobortis quam, id dapibus neque dui nec mauris. Etiam quis lorem quis elit commodo ornare. Ut pharetra purus ultricies enim ultrices efficitur. Proin vehicula tincidunt molestie. Mauris et placerat sem. + +Aliquam erat volutpat. Suspendisse velit turpis, posuere ac lacus eu, lacinia laoreet velit. Sed interdum felis neque, id blandit sem malesuada sit amet. Ut sagittis justo erat, efficitur semper orci tempor sed. Donec enim massa, posuere varius lectus egestas, pellentesque posuere mi. Cras tincidunt ut libero sed mattis. Suspendisse quis magna et tellus posuere interdum vel at purus. Pellentesque fringilla tristique neque, id aliquet tellus ultricies non. Duis ut tellus vel odio lobortis vulputate. + +Integer at magna ac erat convallis vestibulum. Sed lobortis porttitor mauris. Fusce varius lorem et volutpat pulvinar. Aenean ac vulputate lectus, vitae consequat velit. Suspendisse ex dui, varius ut risus ut, dictum scelerisque sem. Vivamus urna orci, volutpat ut convallis ac, venenatis vitae urna. In hac habitasse platea dictumst. Etiam eu purus arcu. Aenean vulputate leo urna, vel tristique dui sagittis euismod. Suspendisse non tellus efficitur ante rhoncus volutpat at et sapien. + +Sed dapibus accumsan porttitor. Phasellus facilisis lectus finibus ligula dignissim, id pulvinar lectus feugiat. Nullam egestas commodo nisi posuere aliquet. Morbi sit amet tortor sagittis, rutrum dui nec, dapibus sapien. Sed posuere tortor tortor, interdum auctor magna varius vitae. Vestibulum id sagittis augue. Curabitur fermentum arcu sem, eu condimentum quam rutrum non. Phasellus rutrum nibh quis lectus rhoncus pretium. Curabitur dictum interdum elit. Vestibulum maximus sodales imperdiet. Mauris auctor nec purus sed venenatis. In in urna purus. + +Duis placerat molestie suscipit. Morbi a elit id purus efficitur consequat. Nunc ac commodo turpis. Etiam sit amet lacus a ipsum tempus venenatis sed vel nibh. Duis elementum aliquam mi sed tristique. Morbi ligula tortor, semper ac est vel, lobortis maximus erat. Curabitur ipsum felis, laoreet vel condimentum eget, ullamcorper sit amet mauris. Nulla facilisi. Nam at purus sed mi egestas placerat vitae vel magna. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Suspendisse at dignissim diam. Phasellus consectetur eget neque vel viverra. Donec sollicitudin mattis dolor vel malesuada. Vivamus vehicula leo neque, vitae fermentum leo posuere et. Praesent dui est, finibus sit amet tristique quis, pharetra vel nibh. + +Duis nulla leo, accumsan eu odio eget, sagittis semper orci. Quisque ullamcorper ligula quam, commodo porttitor mauris ullamcorper eu. Cras varius sagittis felis in aliquam. Duis sodales risus ac justo vehicula, nec mattis diam lacinia. Cras eget lectus ipsum. Ut commodo, enim vitae malesuada hendrerit, ex dolor egestas lectus, sit amet hendrerit metus diam nec est. Vestibulum tortor metus, lobortis sit amet ante eget, tempor molestie lacus. In molestie et urna et semper. Mauris mollis, sem non hendrerit condimentum, sapien nisi cursus est, non suscipit quam justo non metus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam enim est, porta ac feugiat vitae, rutrum in lorem. Duis vehicula tortor ut posuere maximus. + +Nullam vestibulum non tellus sed commodo. Quisque mattis elit sit amet sapien sollicitudin, ut condimentum nisl congue. Aenean sagittis massa vel elit faucibus fermentum. Donec tincidunt nisi nec nisl sodales pellentesque. Mauris congue congue ligula ut suscipit. Vivamus velit tortor, tempor et gravida eget, fermentum sit amet ante. Nullam fringilla, lorem at ultrices cursus, urna neque ornare dolor, eu lacinia orci enim sed nibh. Ut a ullamcorper lectus, id mattis purus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aenean maximus sollicitudin posuere. Nunc at augue lacus. Aenean efficitur leo sit amet lacinia efficitur. + +Quisque venenatis quam mi, in pharetra odio vulputate eu. In vel nisl pulvinar, pulvinar ligula ut, sodales risus. Sed efficitur lectus at vestibulum tincidunt. Vestibulum eu ullamcorper elit. Fusce vestibulum magna enim, et tempor lacus posuere vitae. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Integer leo elit, luctus nec mattis sit amet, sollicitudin in turpis. + +Proin convallis venenatis leo, vitae tristique erat iaculis nec. Nulla facilisi. Duis porttitor, sapien et bibendum vulputate, sem libero sodales lacus, non malesuada felis erat ut libero. Nam non felis semper, finibus est a, mattis mauris. Praesent nec eros quam. Nulla hendrerit, augue consectetur eleifend ultricies, purus mi condimentum nulla, eget dapibus est nunc sed libero. Nullam elementum dui erat, vitae luctus libero sollicitudin et. Nulla odio magna, placerat in augue eu, dapibus imperdiet odio. Suspendisse imperdiet metus sit amet rhoncus dapibus. Cras at enim et urna vehicula cursus eu a mauris. Integer magna ante, eleifend ac placerat vitae, porta at nisi. Cras eget malesuada orci. Curabitur nunc est, vulputate id viverra et, dignissim sed odio. Curabitur non mattis sem. Sed bibendum, turpis vitae vehicula faucibus, nunc quam ultricies lectus, vitae viverra felis turpis at libero. + +Nullam ut egestas ligula. Proin hendrerit justo a lectus commodo venenatis. Nulla facilisi. Ut cursus lorem quis est bibendum condimentum. Aenean in tristique odio. Fusce tempor hendrerit ipsum. Curabitur mollis felis justo, quis dapibus erat auctor vel. Sed augue lectus, finibus ut urna quis, ullamcorper vestibulum dui. Etiam molestie aliquam tempor. Integer mattis sollicitudin erat, et tristique elit varius vel. Mauris a ex justo. + +Nam eros est, imperdiet non volutpat rutrum, pellentesque accumsan ligula. Duis sit amet turpis metus. Aenean in rhoncus metus, ac fringilla ex. Suspendisse condimentum egestas purus, ut pharetra odio vulputate vel. Duis tincidunt massa a placerat ultrices. Mauris ultricies nibh sit amet condimentum malesuada. Duis tincidunt id ipsum sed congue. + +Praesent eu ex augue. Nullam in porta ligula. In tincidunt accumsan arcu, in pellentesque magna tristique in. Mauris eleifend libero ac nisl viverra faucibus. Nam sollicitudin dolor in commodo hendrerit. Cras at orci metus. Ut quis laoreet orci. Vivamus ultrices leo pellentesque tempor aliquet. Maecenas ut eros vitae purus placerat vestibulum. Etiam vitae gravida dolor, quis rhoncus diam. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. + +Suspendisse fringilla lacinia sagittis. Integer tincidunt consectetur tristique. Morbi non orci convallis, congue sapien quis, vulputate nunc. Donec a libero vel magna elementum facilisis non quis mi. Mauris posuere tellus non ipsum ultrices elementum. Vivamus massa velit, facilisis quis placerat aliquet, aliquet nec leo. Praesent a maximus sem. Sed neque elit, feugiat vel quam non, molestie sagittis nunc. Etiam luctus nunc ac mauris scelerisque, nec rhoncus lacus convallis. Nunc pharetra, nunc ac pulvinar aliquam, ex ipsum euismod augue, nec porttitor lacus turpis vitae neque. Fusce bibendum odio id tortor faucibus pellentesque. Sed ac porta nibh, eu gravida erat. + +Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aliquam quis ullamcorper felis. Nulla mattis sagittis ante ac tincidunt. Integer ac felis efficitur, viverra libero et, facilisis ligula. Suspendisse a metus a massa rhoncus posuere. Phasellus suscipit ligula ut lacus facilisis, ac pellentesque ex tempor. Quisque consectetur massa mi, ac molestie libero dictum quis. Proin porttitor ligula quis erat tincidunt venenatis. Proin congue nunc sed elit gravida, nec consectetur lectus sodales. Etiam tincidunt convallis ipsum at vestibulum. Quisque maximus enim et mauris porttitor, et molestie magna tristique. Morbi vitae metus elit. Maecenas sed volutpat turpis. Aliquam vitae dolor vestibulum, elementum purus eget, dapibus nibh. Nullam egestas dui ac rutrum semper. + +Etiam hendrerit est metus, et condimentum metus aliquam ac. Pellentesque id neque id ipsum rhoncus vulputate. Aliquam erat nisl, posuere sit amet ligula ac, fermentum blandit felis. Vivamus fermentum mi risus, non lacinia purus viverra id. Aenean ac sapien consequat, finibus mauris nec, porta sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed quis consectetur ex, dignissim bibendum nulla. Phasellus ac libero at quam vehicula euismod non eu leo. Phasellus a sapien augue. + +Maecenas ligula dui, bibendum vitae mauris et, auctor laoreet felis. Duis non libero a mi semper mattis. Quisque consequat luctus massa, quis tristique eros auctor feugiat. Maecenas sodales euismod neque vitae facilisis. Nullam laoreet imperdiet velit at pellentesque. Etiam massa odio, facilisis a consequat vitae, placerat vel magna. Nunc sagittis eros nec urna fringilla, pulvinar vestibulum nibh scelerisque. Sed magna metus, cursus eu consequat et, pharetra a est. Suspendisse elementum neque a dui malesuada lacinia. Donec sed ipsum volutpat, cursus urna id, ullamcorper arcu. Maecenas laoreet nisl eget velit egestas sollicitudin. Etiam nisl turpis, mollis id dignissim vitae, tristique vehicula ante. Maecenas eget placerat est, at rutrum augue. Vivamus faucibus lacinia ullamcorper. Sed pulvinar urna sodales ante sodales, at gravida leo dictum. + +Morbi maximus, quam a lobortis bibendum, enim felis varius elit, ac vehicula elit nisl ut lacus. Quisque ut arcu augue. Praesent id turpis quam. Sed sed arcu eros. Maecenas at cursus lorem, ac eleifend nisi. Fusce mattis felis at commodo pharetra. Praesent ac commodo ipsum. Quisque finibus et eros vitae tincidunt. In hac habitasse platea dictumst. Praesent purus ipsum, luctus lobortis ornare quis, auctor eget justo. Nam vel enim sollicitudin, faucibus tortor eu, sagittis eros. Ut nec consectetur erat. Donec ultricies malesuada ligula, a hendrerit sapien volutpat in. Maecenas sed enim vitae sapien pulvinar faucibus. + +Proin semper nunc nibh, non consequat neque ullamcorper vel. Maecenas lobortis sagittis blandit. Aenean et arcu ultricies turpis malesuada malesuada. Ut quam ex, laoreet ut blandit cursus, feugiat vitae dolor. Etiam ex lacus, scelerisque vel erat vel, efficitur tincidunt magna. Morbi tristique lacinia dolor, in egestas magna ultrices vitae. Integer ultrices leo ac tempus venenatis. Praesent ac porta tortor. Vivamus ornare blandit tristique. Nulla rutrum finibus pellentesque. In non dui elementum, fermentum ipsum vel, varius magna. Pellentesque euismod tortor risus, ac pellentesque nisl faucibus eget. + +Vivamus eu enim purus. Cras ultrices rutrum egestas. Sed mollis erat nibh, at posuere nisl luctus nec. Nunc vulputate, sapien id auctor molestie, nisi diam tristique ante, non convallis tellus nibh at orci. Morbi a posuere purus, in ullamcorper ligula. Etiam elementum sit amet dui imperdiet iaculis. Proin vitae tincidunt ipsum, sit amet placerat lectus. Curabitur commodo sapien quam, et accumsan lectus fringilla non. Nullam eget accumsan enim, ac pharetra mauris. Sed quis tristique velit, vitae commodo nisi. Duis turpis dui, maximus ut risus at, finibus consequat nunc. Maecenas sed est accumsan, aliquet diam in, facilisis risus. Curabitur vehicula rutrum auctor. Nam iaculis risus pulvinar maximus viverra. Nulla vel augue et ex sagittis blandit. + +Ut sem nulla, porta ac ante ac, posuere laoreet eros. Donec sodales posuere justo a auctor. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Cras mollis at orci hendrerit porta. Nullam sodales tortor tortor, non lacinia diam finibus id. Duis libero orci, suscipit ac odio et, dictum consequat ipsum. Pellentesque eu ligula sagittis, volutpat eros at, lacinia lorem. Cras euismod tellus in iaculis tempor. Quisque accumsan, magna a congue venenatis, ante ipsum aliquam lectus, at egestas enim nunc at justo. Quisque sem purus, viverra ut tristique ut, maximus id enim. Etiam quis placerat sem. In sollicitudin, lacus eu rutrum mollis, nulla eros luctus elit, vel dapibus urna purus nec urna. Phasellus egestas massa quam, ac molestie erat hendrerit a. Praesent ultrices neque ut turpis molestie auctor. Etiam molestie placerat purus, et euismod erat aliquam in. Morbi id suscipit justo. + +Proin est ante, consequat at varius a, mattis quis felis. Sed accumsan nibh sit amet ipsum elementum posuere. Vestibulum bibendum id diam sit amet gravida. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Morbi nec dolor vel ipsum dignissim hendrerit vel non ipsum. Praesent facilisis orci quis elit auctor lobortis. Phasellus cursus risus lectus, vel lobortis libero dapibus in. Quisque tristique tempus leo a pulvinar. Pellentesque a magna tincidunt, pellentesque massa nec, laoreet orci. Morbi congue ornare dolor quis commodo. Phasellus massa nisi, tincidunt at eros dictum, hendrerit lobortis urna. Maecenas porta, magna id mattis molestie, nibh tellus lobortis sem, eget tincidunt ipsum quam eu turpis. + +Ut gravida orci risus, vel rutrum mauris vehicula id. Etiam bibendum, neque a placerat condimentum, ex orci imperdiet lectus, quis dapibus arcu lacus eget lectus. Sed consequat non mi sit amet venenatis. Fusce vestibulum erat libero, eget hendrerit risus vulputate sollicitudin. Integer sed eleifend felis. Donec commodo, sem eu mattis placerat, urna odio aliquam tellus, et laoreet justo tellus eget erat. Fusce sed suscipit tortor. Nam hendrerit nibh ac nunc auctor lacinia. Pellentesque placerat condimentum ipsum, eget semper tortor hendrerit vel. Nullam non urna eu lacus pellentesque congue ut id eros. + +Nunc finibus leo in rhoncus tristique. Sed eu ipsum nec nisl egestas faucibus eget a felis. Pellentesque vitae nisi in nulla accumsan fermentum. Sed venenatis feugiat eleifend. Fusce porttitor varius placerat. Aliquam aliquet lacus sit amet mattis mollis. Sed vel nulla quis dolor suscipit vehicula ac viverra lorem. Duis viverra ipsum eget nulla ullamcorper fermentum. Mauris tincidunt arcu quis quam fringilla ornare. Donec et iaculis tortor. Nam ultricies libero vel ipsum aliquet efficitur. Morbi eget dolor aliquam, tempus sapien eget, viverra ante. Donec varius mollis ex, sed efficitur purus euismod interdum. Quisque vel sapien non neque tincidunt semper. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. + +Suspendisse sit amet purus leo. Fusce lectus lorem, aliquam ac nulla eget, imperdiet ornare eros. Nullam sem augue, varius in nisi non, sollicitudin pellentesque ante. Etiam eu odio condimentum, tempor libero et, egestas arcu. Cras pellentesque eleifend aliquet. Pellentesque non blandit ligula. Ut congue viverra rhoncus. Phasellus mattis mi ac eros placerat, eu feugiat tellus ultrices. Aenean mollis laoreet libero eu imperdiet. Cras sed pulvinar mi, ac vehicula ligula. Vestibulum sit amet ex massa. In a egestas eros. + +Mauris pretium ipsum risus, venenatis cursus ante imperdiet id. Praesent eu turpis nec risus feugiat maximus ullamcorper ac lectus. Integer placerat at mi vel dapibus. Vestibulum fermentum turpis sit amet turpis viverra, id aliquet diam suscipit. Nam nec ex sed ante ullamcorper pharetra quis sit amet risus. Sed ac faucibus velit, id feugiat nibh. Nullam eget ipsum ex. Vivamus tincidunt non nunc non faucibus. Quisque bibendum viverra facilisis. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur at nisi hendrerit quam suscipit egestas. Curabitur laoreet maximus ultricies. Duis ut tellus ac augue molestie dictum. + +Suspendisse rhoncus iaculis erat, ut ullamcorper est tristique eget. Donec auctor nec risus at gravida. Vivamus volutpat vulputate tellus, vel ultricies eros suscipit eget. Ut pulvinar id mi eu tempus. Morbi malesuada augue in dui varius, nec blandit neque vehicula. Donec ornare nec nisl in mollis. Morbi enim nisi, rhoncus nec est id, dapibus tempus urna. Ut id elit a felis vestibulum consectetur. Duis lectus quam, pharetra sit amet diam sed, posuere vestibulum erat. Fusce vitae maximus massa. Nullam id metus tempus, iaculis risus eu, lobortis urna. Quisque in congue urna. Pellentesque placerat neque in augue dapibus, non varius ex malesuada. Curabitur ut eleifend libero. Fusce vitae ligula luctus, fermentum enim vitae, ultrices erat. + +Sed viverra augue turpis, scelerisque egestas sapien mattis eu. Duis laoreet magna at ex pharetra dapibus. Praesent eget odio vel quam venenatis dictum. Nulla in sollicitudin dolor. Mauris lobortis nec eros vel rhoncus. Vestibulum porta viverra venenatis. Curabitur vel scelerisque quam, a egestas velit. Praesent volutpat tincidunt magna at laoreet. + +Cras nec lorem odio. Pellentesque quis dui urna. Praesent at tellus ac lectus scelerisque placerat nec eu risus. Vestibulum sit amet mattis ligula. Vivamus sed nisi at leo elementum accumsan at sit amet arcu. Aenean mattis tellus nec leo gravida, eget hendrerit nisl faucibus. Mauris pellentesque luctus condimentum. Maecenas pretium sapien nunc, eget commodo dolor maximus id. Mauris vestibulum accumsan massa a dictum. Phasellus interdum quam ligula, ut maximus diam blandit aliquam. Nunc vitae ex eu erat condimentum consectetur. Maecenas interdum condimentum volutpat. + +Donec et enim a libero rutrum laoreet. Praesent a condimentum sem, at tincidunt quam. In vel molestie risus. Sed urna dui, molestie vitae mollis laoreet, tempor quis lectus. Praesent vitae auctor est, et aliquet nunc. Curabitur vulputate blandit nulla, at gravida metus. Maecenas gravida dui eu iaculis tristique. Pellentesque posuere turpis nec auctor eleifend. Suspendisse bibendum diam eu tellus lobortis, et laoreet quam congue. In hac habitasse platea dictumst. Morbi dictum neque velit, eget rutrum eros ultrices sit amet. + +Phasellus fermentum risus pharetra consectetur bibendum. Donec magna tortor, lacinia vitae nibh quis, aliquet pretium lorem. Donec turpis nisi, pretium eu enim volutpat, mattis malesuada augue. Nullam vel tellus iaculis, sollicitudin elit eget, tincidunt lacus. Fusce elementum elementum felis et iaculis. Suspendisse porta eros nec neque malesuada, in malesuada ante sollicitudin. Vivamus bibendum viverra molestie. + +Integer feugiat, erat nec convallis aliquam, velit felis congue erat, molestie eleifend tellus erat in tellus. Nunc et justo purus. Donec egestas fermentum dui non feugiat. Quisque in sapien sagittis, gravida quam id, iaculis lectus. Cras sagittis rhoncus bibendum. Fusce quis metus in velit scelerisque tincidunt at non ipsum. Vivamus efficitur ante eu odio vulputate, vitae ultricies risus vehicula. Proin eget odio eu sem tincidunt feugiat vel id lorem. + +Vestibulum sit amet nulla dignissim, euismod mi in, fermentum tortor. Donec ut aliquet libero, lacinia accumsan velit. Donec et nulla quam. Nullam laoreet odio nec nunc imperdiet, a congue eros venenatis. Quisque nec tellus sit amet neque interdum posuere. Duis quis mi gravida, tincidunt diam convallis, ultricies augue. Mauris consequat risus non porttitor congue. Ut in ligula consequat, viverra nunc a, eleifend enim. Duis ligula urna, imperdiet nec facilisis et, ornare eu ex. Proin lobortis lectus a lobortis porttitor. Nulla leo metus, egestas eu libero sed, pretium faucibus felis. Vestibulum non sem tortor. Nam cursus est leo. Vivamus luctus enim odio, non interdum sem dapibus a. Aenean accumsan consequat lectus in imperdiet. + +Donec vehicula laoreet ipsum in posuere. Quisque vel quam imperdiet, sollicitudin nisi quis, suscipit velit. Morbi id sodales mauris. Curabitur tellus arcu, feugiat sed dui sit amet, sodales sagittis libero. Aenean vel suscipit metus, non placerat leo. Vestibulum quis nulla elit. Proin scelerisque non ante ut commodo. Interdum et malesuada fames ac ante ipsum primis in faucibus. + +Sed non urna dolor. Suspendisse convallis mi porta pulvinar ultrices. Suspendisse quam ipsum, hendrerit non scelerisque molestie, interdum dictum nunc. Morbi condimentum condimentum turpis eu luctus. Pellentesque sagittis sollicitudin odio, sed ultricies felis ornare sit amet. Sed ultrices ex leo, a tincidunt nisl gravida sed. Nullam ornare accumsan porta. Praesent consectetur id est nec sollicitudin. + +In hac habitasse platea dictumst. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed sed ultrices nibh. Duis accumsan suscipit eros, a dictum odio tempus sit amet. Aenean imperdiet erat ac lacus finibus, scelerisque cursus massa imperdiet. Mauris molestie risus ut lacinia posuere. Nulla et sodales purus. Maecenas orci erat, placerat in tristique quis, placerat in mi. + +Donec sollicitudin pellentesque odio in feugiat. Morbi eu dolor ut mauris congue sollicitudin. Aliquam erat volutpat. Nulla id varius dui. Curabitur finibus urna ante, consectetur interdum nisi volutpat a. Quisque quis mi tristique, consequat tellus eget, rutrum sapien. Vivamus vitae tellus vulputate, rutrum ex eu, vulputate sem. Suspendisse viverra lorem tellus, vel interdum orci gravida quis. Ut laoreet arcu at mi ullamcorper finibus. Duis porta sagittis vestibulum. Sed commodo nisl vitae urna sollicitudin, nec lacinia est sodales. Curabitur imperdiet sodales dui sed iaculis. Sed ac tellus maximus, eleifend quam sit amet, feugiat elit. Aenean viverra, dui at mattis varius, est odio vestibulum sapien, sit amet mollis libero massa nec velit. Etiam quis sodales justo. + +Ut ultricies, sem eget sodales feugiat, nunc arcu congue elit, ac tempor justo massa nec purus. Maecenas enim nunc, pharetra eget dictum sit amet, tempus pellentesque velit. Suspendisse venenatis ligula in nulla mattis, et imperdiet ex tincidunt. Etiam vulputate, tellus et ultrices suscipit, enim velit laoreet massa, vitae congue odio enim ac urna. Morbi quam lorem, iaculis ac varius sagittis, euismod quis dolor. In ut dui eu purus feugiat consectetur. Vestibulum cursus velit quis lacus pellentesque iaculis. Cras in risus sed mauris porta rutrum. Nulla facilisi. Nullam eu bibendum est, non pellentesque lectus. Sed imperdiet feugiat lorem, quis convallis ante auctor in. Maecenas justo magna, scelerisque sit amet tellus eget, varius elementum risus. Duis placerat et quam sed varius. + +Duis nec nibh vitae nibh dignissim mollis quis sed felis. Curabitur vitae quam placerat, venenatis purus ut, euismod nisl. Curabitur porttitor nibh eu pulvinar ullamcorper. Suspendisse posuere nec ipsum ac dapibus. Cras convallis consectetur urna. Phasellus a nibh in dolor lacinia posuere id eget augue. In eu pharetra lorem, vitae cursus lacus. Aliquam tincidunt nibh lectus. Aenean facilisis ultricies posuere. Sed ut placerat orci. Curabitur scelerisque gravida blandit. Maecenas placerat ligula eget suscipit fringilla. Mauris a tortor justo. Aliquam hendrerit semper mollis. Phasellus et tincidunt libero. Etiam vel quam libero. + +Quisque aliquet tempor ex. Ut ante sem, vehicula at enim vel, gravida porta elit. Etiam vitae lacus a neque lobortis consectetur. Mauris sed interdum odio. Mauris elementum ex blandit tempor cursus. Integer in enim in leo viverra elementum. Fusce consectetur metus et sem rutrum, mattis euismod diam semper. Nunc sed ipsum vel urna consequat vehicula. Donec cursus pretium lorem, vestibulum pretium felis commodo sit amet. Nam blandit felis enim, eget gravida ex faucibus a. In nec neque massa. Etiam laoreet posuere ipsum. Praesent volutpat nunc dolor, ac vulputate magna facilisis non. Aenean congue turpis vel lectus sollicitudin tristique. Sed nec consequat purus, non vehicula quam. Etiam ultricies, est ac dictum tincidunt, turpis turpis pretium massa, a vulputate libero justo at nibh. + +Aliquam erat volutpat. Cras ultrices augue ac sollicitudin lobortis. Curabitur et aliquet purus. Duis feugiat semper facilisis. Phasellus lobortis cursus velit, a sollicitudin tortor. Nam feugiat sapien non dapibus condimentum. Morbi at mi bibendum, commodo quam at, laoreet enim. Integer eu ultrices enim. Sed vestibulum eu urna ut dictum. Curabitur at mattis leo, sed cursus massa. Aliquam porttitor, felis quis fermentum porttitor, justo velit feugiat nulla, eget condimentum sem dui ut sapien. + +In fringilla elit eu orci aliquam consequat. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Ut eget fringilla tellus. Curabitur fermentum, mi et condimentum suscipit, elit neque bibendum dui, et hendrerit nunc metus id ipsum. Morbi placerat mi in hendrerit congue. Ut feugiat mauris eget scelerisque viverra. Vivamus sit amet erat dictum, sagittis lectus nec, pulvinar lorem. Sed non enim ac dui sollicitudin aliquet. Quisque ut lacus dolor. Fusce hendrerit malesuada euismod. Nulla faucibus vel mauris eu mollis. Mauris est diam, fringilla ac arcu feugiat, efficitur volutpat turpis. Aliquam venenatis cursus massa sed porttitor. Ut ac finibus enim, in tincidunt sapien. + +Nunc faucibus semper turpis a lacinia. Phasellus gravida, libero vel pulvinar ornare, ex sem tincidunt lectus, sit amet convallis augue risus at tortor. Quisque sit amet ipsum id nulla posuere vestibulum. Pellentesque scelerisque mauris vel leo viverra sodales. Nulla viverra aliquam ex, ut rutrum enim fermentum venenatis. Aenean eget dapibus ex, eget faucibus metus. Vestibulum volutpat leo in diam semper, eget porta magna suscipit. Sed sit amet nulla blandit, aliquam dolor ac, gravida velit. Sed vel velit viverra, maximus est id, convallis justo. + +Curabitur nulla ante, vulputate at libero vel, ullamcorper rutrum nibh. Pellentesque porttitor eu mauris id mattis. Duis vulputate augue elit, eget interdum justo pretium vel. Maecenas eu vulputate arcu, eget posuere purus. Suspendisse viverra a velit dictum eleifend. Suspendisse vitae dapibus diam. Donec vehicula justo in ante interdum, eu luctus diam placerat. Vivamus convallis ipsum eu orci suscipit, sed fermentum enim euismod. Maecenas faucibus elit vitae ex ornare tristique. Donec vestibulum nec elit sit amet porttitor. Aenean tempor lectus eget tortor hendrerit luctus. Nullam interdum vitae lectus vel feugiat. Cras in risus non magna consectetur lobortis. Sed faucibus enim quis gravida convallis. + +Phasellus eget massa sit amet libero ultrices suscipit. Vivamus at risus sapien. Nam mollis nunc eget velit dictum maximus. Sed pellentesque, nunc ac fringilla lacinia, quam enim mattis ex, sed euismod tortor metus eu neque. Ut mattis nisl ut lectus rhoncus, sodales bibendum eros porta. Nulla porttitor enim nec diam sagittis, eget porta velit efficitur. Vestibulum ultricies eros neque. Phasellus rutrum suscipit enim, in interdum ante gravida vitae. Sed in sagittis diam, non commodo velit. + +Morbi hendrerit odio orci, nec tincidunt odio rhoncus nec. Mauris neque velit, vehicula a lorem at, suscipit tristique dui. Sed finibus, nisl in mattis convallis, turpis neque sodales lacus, eu porta enim magna non diam. Nam commodo sodales risus consectetur malesuada. In eget elementum justo. Phasellus sit amet massa imperdiet, dapibus nunc sit amet, suscipit orci. Fusce condimentum laoreet feugiat. Ut ut viverra ante. Praesent bibendum interdum commodo. Nulla mollis nisi a est ornare volutpat. Sed at ligula eu nisi dapibus tempus. Proin cursus vestibulum justo, nec efficitur justo dignissim vel. Nunc quis maximus eros. + +Cras viverra, diam a tristique mattis, libero felis vulputate tellus, a ornare felis leo a dui. Nulla ante nulla, finibus ut tellus ut, blandit pharetra nibh. Proin eleifend fermentum ex, eget auctor libero vulputate in. Nullam ultricies, mauris placerat pretium placerat, leo urna lobortis leo, vel placerat arcu libero sed mauris. Aliquam mauris ligula, ornare at urna at, eleifend gravida ligula. Vestibulum consectetur ut nulla non scelerisque. Donec ornare, sem nec elementum aliquam, urna nulla bibendum metus, eu euismod dui ligula ac est. Fusce laoreet erat eu ex lobortis, quis bibendum ligula interdum. Sed vel mi erat. Vivamus id lacus ac enim mattis tempor. Nunc ultricies pellentesque enim sed euismod. Fusce tincidunt convallis elit quis aliquam. Mauris nulla ipsum, sollicitudin quis diam ac, feugiat volutpat tellus. In nibh nibh, vulputate quis tincidunt quis, pulvinar eget magna. Pellentesque quis finibus dolor. Suspendisse viverra vitae lectus non eleifend. + +Nunc ut orci et sapien maximus semper. Nulla dignissim sem urna, ac varius lectus ultricies id. Quisque aliquet pulvinar pretium. In ultricies molestie tellus vehicula porta. Nam enim lorem, aliquam eget ex et, hendrerit volutpat quam. Maecenas diam lacus, pellentesque eget tempus ac, pharetra eu elit. Donec vel eros a sem facilisis vulputate. Nullam ac nisi vulputate, laoreet nisl ac, eleifend sem. Nullam mi massa, rhoncus sed pharetra interdum, tincidunt eget nunc. Aliquam viverra mattis posuere. Mauris et dui sed nisl sollicitudin fermentum quis ut arcu. Nam placerat eget orci at tincidunt. Curabitur vel turpis metus. Phasellus nibh nulla, fermentum scelerisque sem vel, gravida tincidunt velit. Pellentesque vel quam tempor, finibus massa pellentesque, condimentum dui. + +Donec at mattis neque. Etiam velit diam, consequat auctor mauris id, hendrerit faucibus metus. Maecenas ullamcorper eros a est sodales, ac consectetur odio scelerisque. Donec leo metus, imperdiet at pellentesque vel, feugiat id erat. Suspendisse at magna enim. Vestibulum placerat sodales lorem id sollicitudin. Aenean at euismod ligula, eget mollis diam. Phasellus pulvinar, orci nec pretium condimentum, est erat facilisis purus, quis feugiat augue elit aliquam nulla. Aenean vitae tortor id risus congue tincidunt. Sed dolor enim, mattis a ullamcorper id, volutpat ac leo. + +Proin vehicula feugiat augue, id feugiat quam sodales quis. Donec et ultricies massa, a lacinia nulla. Duis aliquam augue ornare euismod viverra. Ut lectus risus, rutrum sit amet efficitur a, luctus nec nisl. Cras volutpat ullamcorper congue. Sed vitae odio metus. Phasellus aliquet euismod varius. + +Nullam sem ex, malesuada ut magna ut, pretium mollis arcu. Nam porttitor eros cursus mi lacinia faucibus. Suspendisse aliquet eleifend iaculis. Maecenas sit amet viverra tortor. Nunc a mollis risus. Etiam tempus dolor in tortor malesuada mattis. Ut tincidunt venenatis est sit amet dignissim. Vestibulum massa enim, tristique sed scelerisque eu, fringilla ac velit. Donec efficitur quis urna sit amet malesuada. Vestibulum consequat ac ligula in dapibus. Maecenas massa massa, molestie non posuere nec, elementum ut magna. In nisi erat, mollis non venenatis eu, faucibus in justo. Morbi gravida non ex non egestas. Pellentesque finibus laoreet diam, eu commodo augue congue vitae. + +Aenean sem mi, ullamcorper dapibus lobortis vitae, interdum tincidunt tortor. Vivamus eget vulputate libero. Ut bibendum posuere lectus, vel tincidunt tortor aliquet at. Phasellus malesuada orci et bibendum accumsan. Aliquam quis libero vel leo mollis porta. Sed sagittis leo ac lacus dictum, ac malesuada elit finibus. Suspendisse pharetra luctus commodo. Vivamus ultricies a odio non interdum. Vivamus scelerisque tincidunt turpis quis tempor. Pellentesque tortor ligula, varius non nunc eu, blandit sollicitudin neque. Nunc imperdiet, diam et tristique luctus, ipsum ex condimentum nunc, sit amet aliquam justo velit sed libero. Duis vel suscipit ligula. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed tincidunt neque vel massa ultricies, id dictum leo consequat. Curabitur lobortis ultricies tellus, eget mattis nisl aliquam sit amet. + +Proin at suscipit justo. Vivamus ut vestibulum nisl. Pellentesque enim odio, pharetra non magna sed, efficitur auctor magna. Praesent tincidunt ante quis ante hendrerit viverra. Pellentesque vel ipsum id magna vulputate efficitur. Sed nec neque accumsan, pulvinar sapien quis, euismod mauris. Donec condimentum laoreet sapien quis gravida. Quisque sed mattis purus. Vestibulum placerat vel neque maximus scelerisque. + +Vestibulum mattis quam quis efficitur elementum. Duis dictum dolor ac scelerisque commodo. Fusce sollicitudin nisi sit amet dictum placerat. Suspendisse euismod pharetra eleifend. In eros nisl, porttitor sed mauris at, consectetur aliquet mauris. Donec euismod viverra neque sed fermentum. Phasellus libero magna, accumsan ut ultricies vitae, dignissim eget metus. Donec tellus turpis, interdum eget maximus nec, hendrerit eget massa. Curabitur auctor ligula in iaculis auctor. In ultrices quam suscipit cursus finibus. Aenean id mi at dolor interdum iaculis vitae ut lorem. Nullam sed nibh fringilla, lacinia odio nec, placerat erat. In dui libero, viverra ac viverra ac, pellentesque sit amet turpis. + +Nulla in enim ex. Sed feugiat est et consectetur venenatis. Cras varius facilisis dui vel convallis. Vestibulum et elit eget tellus feugiat pellentesque. In ut ante eu purus aliquet posuere. Nulla nec ornare sem, sed luctus lorem. Nam varius iaculis odio, eget faucibus nisl ullamcorper in. Sed eget cursus felis, nec efficitur nisi. + +Vivamus commodo et sem quis pulvinar. Pellentesque libero ante, venenatis vitae ligula sit amet, ornare sollicitudin nulla. Mauris eget tellus hendrerit, pulvinar metus quis, tempor nisi. Proin magna ex, laoreet sed tortor quis, varius fermentum enim. Integer eu dolor dictum, vulputate tortor et, aliquet ligula. Vestibulum vitae justo id mauris luctus sollicitudin. Suspendisse eget auctor neque, sodales egestas lorem. Vestibulum lacinia egestas metus vitae euismod. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus ex tellus, volutpat nec pulvinar sit amet, condimentum vitae dui. Curabitur vel felis sodales, lacinia nunc iaculis, ullamcorper augue. Pellentesque consequat dolor quis eros efficitur malesuada. Nulla ut malesuada lectus. + +Morbi et tristique ante. Aliquam erat volutpat. Vivamus vitae dui nec turpis pellentesque fermentum. Quisque eget velit massa. Pellentesque tristique aliquam nisl, eu sollicitudin justo venenatis sed. Duis eleifend sem eros, ut aliquam libero porttitor id. Sed non nunc consequat, rhoncus diam eu, commodo erat. Praesent fermentum in lectus id blandit. Donec quis ipsum at justo volutpat finibus. Nulla blandit justo nulla, at mollis lacus consequat eget. Aenean sollicitudin quis eros ut ullamcorper. + +Pellentesque venenatis nulla ut mi aliquet feugiat. Cras semper vel magna nec pharetra. Integer mattis felis et sapien commodo imperdiet. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Duis quis luctus felis. Vestibulum justo nibh, aliquam non lectus vitae, molestie placerat justo. Donec lorem nibh, gravida sit amet hendrerit ac, maximus id ipsum. Nunc ac libero sodales risus eleifend sagittis. Phasellus est massa, lobortis elementum ex sed, scelerisque consectetur neque. Nunc faucibus neque id lorem malesuada, eget convallis ex mattis. + +Sed turpis tortor, fermentum non turpis id, posuere varius nibh. Donec iaculis lorem dui. Etiam eros ante, sodales eget venenatis at, consectetur eget risus. Curabitur non aliquam ante, a pretium justo. Maecenas tempor nisl tortor, vitae dictum nisi ultrices eu. Duis eget dui ultrices, porttitor lacus sed, lobortis purus. Quisque mattis elit nec neque sagittis, sed commodo leo blandit. Mauris sodales interdum eleifend. Vestibulum condimentum consectetur augue, id luctus diam convallis et. + +Nunc suscipit risus in justo accumsan, a placerat magna tincidunt. Proin a nisl ipsum. Sed libero dui, tristique in augue quis, auctor tristique risus. Sed porttitor ex augue, eu porta augue molestie a. Duis rhoncus purus libero, eu tempus turpis condimentum at. Sed mollis nisi id lectus placerat tincidunt. Maecenas non scelerisque elit, quis rutrum orci. Donec in tellus pharetra urna ornare lobortis. Phasellus id risus at nisi varius rutrum eu ut turpis. + +Duis dictum justo quis nisl porta, eget tincidunt magna suscipit. Sed velit massa, ullamcorper eu sodales ac, pretium a massa. Duis et rutrum tortor. Nulla accumsan hendrerit sapien, cursus volutpat eros egestas eget. Donec sollicitudin at ante quis sollicitudin. Aenean blandit feugiat diam, id feugiat eros faucibus eget. Donec viverra dolor vel justo scelerisque dignissim. Nulla semper sem nunc, rhoncus semper tellus ultricies sed. Duis in ornare diam. Donec vehicula feugiat varius. Maecenas ut suscipit est. Vivamus sem sem, finibus at dolor sit amet, euismod dapibus ligula. Vestibulum fringilla odio dapibus, congue massa eget, congue sem. Donec feugiat magna eget tortor lacinia scelerisque non et ipsum. + +Suspendisse potenti. Nunc convallis sollicitudin ex eget venenatis. Sed iaculis nibh ex, vel ornare ligula congue dignissim. Quisque sollicitudin dolor ac dui vestibulum, sit amet molestie nisi aliquet. Donec at risus felis. Aenean sollicitudin metus a feugiat porta. Aenean a tortor ut dolor cursus sagittis. Vivamus consectetur porttitor nunc in facilisis. Proin sit amet mi vel lectus consectetur ultrices. + +Sed cursus lectus vitae nunc tristique, nec commodo turpis dapibus. Pellentesque luctus ex id facilisis ornare. Morbi quis placerat dolor. Donec in lectus in arcu mattis porttitor ac sit amet metus. Cras congue mauris non risus sodales, vitae feugiat ipsum bibendum. Nulla venenatis urna sed libero elementum, a cursus lorem commodo. Mauris faucibus lobortis eros nec commodo. + +Nullam suscipit ligula ullamcorper lorem commodo blandit. Nulla porta nibh quis pulvinar placerat. Vivamus eu arcu justo. Vestibulum imperdiet est ut fermentum porttitor. Pellentesque consectetur libero in sapien efficitur scelerisque. Curabitur ac erat sit amet odio aliquet dignissim. Pellentesque mi sem, rhoncus et luctus at, porttitor rutrum lectus. Vestibulum sollicitudin sollicitudin suscipit. Aenean efficitur dolor non ultrices imperdiet. Donec vel sem ex. + +Sed convallis mauris aliquam rutrum cursus. Ut tempor porttitor sodales. Etiam eu risus ac augue gravida egestas et eu dolor. Proin id magna ex. Suspendisse quis lectus quis lorem ultricies tempus. Donec porttitor velit vitae tincidunt faucibus. Aliquam vitae semper nisi. Morbi ultrices, leo non pretium dapibus, dui libero pellentesque ex, vel placerat enim ante vitae dui. Nunc varius, sem sit amet sagittis lobortis, lectus odio scelerisque mauris, ut vestibulum orci magna quis neque. Sed id congue justo. Interdum et malesuada fames ac ante ipsum primis in faucibus. Mauris congue nisi est, malesuada mollis elit tincidunt sed. Curabitur sed ex sit amet felis tristique elementum vitae vel nibh. + +Etiam mollis pretium lobortis. Mauris augue lacus, efficitur at lacus sed, mollis tincidunt lectus. Aliquam erat volutpat. Donec at euismod elit, et mattis felis. Sed id lobortis urna. Morbi imperdiet vestibulum leo, sed maximus leo blandit eu. Aliquam semper lorem neque, nec euismod turpis mattis mollis. Quisque lobortis urna ultrices odio pretium, ac venenatis orci faucibus. Suspendisse bibendum odio ligula, sed lobortis massa pharetra nec. Donec turpis justo, iaculis at dictum ac, finibus eu libero. Maecenas quis porttitor mi, sit amet aliquet neque. + +Vivamus auctor vulputate ante, at egestas lorem. Donec eu risus in nulla mollis ultricies at et urna. Duis accumsan porta egestas. Ut vel euismod augue. Fusce convallis nulla ante, nec fringilla velit aliquet at. Nam malesuada dapibus ligula, a aliquam nibh scelerisque ac. Praesent malesuada neque et pellentesque interdum. Curabitur volutpat at turpis vitae tristique. Vivamus porttitor semper congue. Quisque suscipit lacus mi, rhoncus ultrices tortor auctor quis. Maecenas neque neque, molestie ac facilisis eget, luctus ac lorem. In ut odio ut lacus suscipit pulvinar vitae sed elit. Nulla imperdiet, sem quis euismod sagittis, dui erat luctus dolor, faucibus faucibus erat sem eget nunc. Nam accumsan placerat malesuada. Maecenas convallis finibus pulvinar. + +Cras at placerat tortor. Morbi facilisis auctor felis sit amet molestie. Donec sodales sed lorem vitae suscipit. Etiam fermentum pharetra ipsum, nec luctus orci gravida eu. Pellentesque gravida, est non condimentum tempus, mauris ligula molestie est, in congue dolor nisl vel sapien. Duis congue tempor augue, id rutrum eros porta dapibus. Etiam rutrum eget est eget vestibulum. Aenean mollis arcu vel consequat varius. Praesent at condimentum felis. Duis nec interdum nisl. Donec commodo lorem sed sapien scelerisque malesuada non eu urna. In blandit non ipsum at porta. Nam lobortis leo vitae dui auctor, non feugiat quam bibendum. Donec auctor lectus sagittis laoreet maximus. Maecenas rhoncus laoreet porttitor. Vestibulum porttitor augue ut lectus hendrerit, eget posuere mi gravida. + +Sed mattis ex in erat pulvinar, eu imperdiet magna dapibus. Etiam nisi nibh, tempus non tellus sit amet, mattis tempor odio. Quisque nec lorem feugiat, lobortis odio et, commodo nunc. Maecenas semper purus nisi, nec vehicula nibh eleifend vitae. Nulla fermentum a lectus at maximus. Phasellus finibus metus non euismod ultrices. Etiam a pulvinar ante. Quisque convallis nec metus sit amet facilisis. Praesent laoreet massa et sollicitudin laoreet. Vestibulum in mauris aliquet, convallis mi ut, elementum purus. Nulla purus nulla, sodales at hendrerit quis, tempus sed lectus. + +Nam ut laoreet neque, ut maximus nibh. Maecenas quis justo pellentesque, sollicitudin elit at, venenatis velit. Aenean nunc velit, vehicula scelerisque odio at, consectetur laoreet purus. Duis dui purus, malesuada quis ipsum sit amet, tempor interdum libero. Curabitur porta scelerisque sapien, vitae cursus diam condimentum eu. Phasellus sed orci quam. Nullam vitae dui quis purus tincidunt vestibulum. Curabitur quis nulla porta, cursus arcu non, auctor enim. Etiam sollicitudin ex id sem vehicula mollis. Morbi viverra laoreet tincidunt. Praesent ut semper dui. Nam sit amet pretium neque. Mauris vitae luctus diam, in lacinia purus. Maecenas ut placerat justo, ut porta felis. Integer eu mauris ante. + +Aenean porttitor tellus diam, tempor consequat metus efficitur id. Suspendisse ut felis at erat tempor dictum at nec sapien. Sed vestibulum interdum felis, ac mattis mauris porta in. Nunc et condimentum massa. Sed cursus dictum justo et luctus. Integer convallis enim nisl, a rutrum lectus ultricies in. Donec dapibus lacus at nulla dapibus, id sollicitudin velit hendrerit. Fusce a magna at orci mollis rutrum ac a dolor. Aliquam erat volutpat. Morbi varius porta nunc, sit amet sodales ex hendrerit commodo. Donec tincidunt tortor sapien, vitae egestas sapien vehicula eget. + +Suspendisse potenti. Donec pulvinar felis nec leo malesuada interdum. Integer posuere placerat maximus. Donec nibh ipsum, tincidunt vitae luctus vitae, bibendum at leo. Sed cursus nisl ut ex faucibus aliquet sed nec eros. Curabitur molestie posuere felis. Integer faucibus velit eget consequat iaculis. Mauris sed vulputate odio. Phasellus maximus, elit a pharetra egestas, lorem magna semper tellus, vestibulum semper diam felis at sapien. Suspendisse facilisis, nisl sit amet euismod vehicula, libero nulla vehicula dolor, quis fermentum nibh elit sit amet diam. + +Morbi lorem enim, euismod eu varius ut, scelerisque quis odio. Nam tempus vitae eros id molestie. Nunc pretium in nulla eget accumsan. Quisque mattis est ut semper aliquet. Maecenas eget diam elementum, fermentum ipsum a, euismod sapien. Duis quam ligula, cursus et velit nec, ullamcorper tincidunt magna. Donec vulputate nisl est, et ullamcorper urna tempor sit amet. + +Proin lacinia dui non turpis congue pretium. Morbi posuere metus vel purus imperdiet interdum. Morbi venenatis vel eros non ultricies. Nulla vel semper elit. Ut quis purus tincidunt, auctor justo ut, faucibus turpis. Proin quis mattis erat, at faucibus ligula. Mauris in mauris enim. Donec facilisis enim at est feugiat hendrerit. Nam vel nisi lorem. Fusce ultricies convallis diam, in feugiat tortor luctus quis. Donec tempor, leo vitae volutpat aliquam, magna elit feugiat leo, quis placerat sapien felis eget arcu. Donec ornare fermentum eleifend. Integer a est orci. + +Proin rhoncus egestas leo. Nulla ultricies porta elit quis ornare. Nunc fermentum interdum vehicula. In in ligula lorem. Donec nec arcu sit amet orci lobortis iaculis. Mauris at mollis erat, sit amet mollis tortor. Mauris laoreet justo ullamcorper porttitor auctor. Aenean sit amet aliquam lectus, id fermentum eros. Praesent urna sem, vehicula ac fermentum id, dapibus ut purus. Vestibulum vitae tempus nunc. Donec at nunc ornare metus volutpat porta at eget magna. Donec varius aliquet metus, eu lobortis risus aliquam sed. Ut dapibus fermentum velit, ac tincidunt libero faucibus at. + +In in purus auctor, feugiat massa quis, facilisis nisi. Donec dolor purus, gravida eget dolor ac, porttitor imperdiet urna. Donec faucibus placerat erat, a sagittis ante finibus ac. Sed venenatis dignissim elit, in iaculis felis posuere faucibus. Praesent sed viverra dolor. Mauris sed nulla consectetur nunc laoreet molestie in ut metus. Proin ac ex sit amet magna vulputate hendrerit ac condimentum urna. Proin ligula metus, gravida et sollicitudin facilisis, iaculis ut odio. Cras tincidunt urna et augue varius, ut facilisis urna consequat. Aenean vehicula finibus quam. Ut iaculis eu diam ac mollis. Nam mi lorem, tristique eget varius at, sodales at urna. + +Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Proin vitae dictum erat, et auctor ipsum. Nullam nunc nunc, sollicitudin quis magna a, vestibulum fermentum mauris. Praesent at erat dolor. Proin laoreet tristique nulla vel efficitur. Nam sed ultrices nibh, id rutrum nunc. Curabitur eleifend a erat sit amet sollicitudin. Nullam metus quam, laoreet vitae dapibus id, placerat sed leo. Aliquam erat volutpat. Donec turpis nisl, cursus eu ex sit amet, lacinia pellentesque nisl. Sed id ipsum massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec interdum scelerisque lorem eu mattis. + +Vivamus ac tristique massa, nec facilisis nisl. Nam ipsum neque, tincidunt vel urna in, cursus imperdiet enim. Nam pellentesque egestas tempus. Morbi facilisis imperdiet libero vitae fringilla. Nam lacinia ligula at sapien facilisis malesuada. Nullam accumsan pulvinar sem, et cursus libero porta sit amet. Curabitur vulputate erat elit, ut pulvinar erat maximus vel. + +Cras aliquet metus ut purus sagittis, vel venenatis ante consectetur. Pellentesque nulla lacus, viverra viverra mattis non, placerat vitae nibh. Donec enim turpis, accumsan sit amet tincidunt eu, imperdiet non metus. Morbi ipsum eros, tincidunt vel est ac, tristique porttitor nibh. Praesent ut ullamcorper mauris. Sed laoreet sit amet diam congue venenatis. Integer porta purus nec orci sagittis posuere. + +Donec vehicula mauris eget lacus mollis venenatis et sed nibh. Nam sodales ligula ipsum, scelerisque lacinia ligula sagittis in. Nam sit amet ipsum at erat malesuada congue. Aenean ut sollicitudin sapien. Etiam at tempor odio. Mauris vitae purus ut magna suscipit consequat. Vivamus quis sapien neque. Nulla vulputate sem sit amet massa pellentesque, eleifend tristique ligula egestas. Suspendisse tincidunt gravida mi, in pulvinar lectus egestas non. Aenean imperdiet ex sit amet nunc sollicitudin porta. Integer justo odio, ultricies at interdum in, rhoncus vitae sem. Sed porttitor arcu quis purus aliquet hendrerit. Praesent tempor tortor at dolor dictum pulvinar. Nulla aliquet nunc non ligula scelerisque accumsan. Donec nulla justo, congue vitae massa in, faucibus hendrerit magna. Donec non egestas purus. + +Vivamus iaculis, lacus efficitur faucibus porta, dui nulla facilisis ligula, ut sodales odio nunc id sapien. Cras viverra auctor ipsum, dapibus mattis neque dictum sed. Sed convallis fermentum molestie. Nulla facilisi turpis duis. + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur vulputate, ipsum quis interdum fermentum, lorem sem fermentum eros, vitae auctor neque lacus in nisi. Suspendisse potenti. Maecenas et scelerisque elit, in tincidunt quam. Sed eu tincidunt quam. Nullam justo ex, imperdiet a imperdiet et, fermentum sit amet eros. Aenean quis tempus sem. Pellentesque accumsan magna mi, ut mollis velit sagittis id. Etiam quis ipsum orci. Fusce purus ante, accumsan a lobortis at, venenatis eu nisl. Praesent ornare sed ante placerat accumsan. Suspendisse tempus dignissim fermentum. Nunc a leo ac lacus sodales iaculis eu vitae mi. In feugiat ante at massa finibus cursus. Suspendisse posuere fringilla ornare. Mauris elementum ac quam id convallis. Vestibulum non elit quis urna volutpat aliquam a eu lacus. + +Aliquam vestibulum imperdiet neque, suscipit aliquam elit ultrices bibendum. Suspendisse ultrices pulvinar cursus. Morbi risus nisi, cursus consequat rutrum vitae, molestie sed dui. Fusce posuere, augue quis dignissim aliquam, nisi ipsum porttitor ante, quis fringilla nisl turpis ac nisi. Nulla varius enim eget lorem vehicula gravida. Donec finibus malesuada leo nec semper. Proin ac enim eros. Vivamus non tincidunt nisi, vel tristique lorem. + +Nunc consequat ex id eros dignissim, id rutrum risus laoreet. Sed euismod non erat eu ultricies. Etiam vehicula gravida lacus ut porta. Vestibulum eu eros quis nunc aliquet luctus. Cras quis semper ligula. Nullam gravida vehicula quam sed porta. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. In porta cursus vulputate. Quisque porta a nisi eget cursus. Aliquam risus leo, luctus ac magna in, efficitur cursus magna. In condimentum non mi id semper. Donec interdum ante eget commodo maximus. + +Vivamus sit amet vestibulum lectus. Fusce tincidunt mi sapien, dictum sollicitudin diam vulputate in. Integer fringilla consequat mollis. Cras aliquet consequat felis eget feugiat. Nunc tempor cursus arcu, vitae ornare nunc varius et. Vestibulum et tortor vel ante viverra porttitor. Nam at tortor ullamcorper, facilisis augue quis, tristique erat. Aenean ut euismod nibh. Quisque eu tincidunt est, nec euismod eros. + +Proin vehicula nibh non viverra egestas. Phasellus sem dolor, ultricies ac sagittis tristique, lacinia a purus. Vestibulum in ante eros. Pellentesque lacus nulla, tristique vitae interdum vel, malesuada ac diam. Aenean bibendum posuere turpis in accumsan. Ut est nulla, ullamcorper quis turpis at, viverra sagittis mauris. Sed in interdum purus. Praesent scelerisque nibh eget sem euismod, ut imperdiet mi venenatis. Vivamus pulvinar orci sed dapibus auctor. Nulla facilisi. Vestibulum tincidunt erat nec porttitor egestas. Mauris quis risus ante. Nulla facilisi. + +Aliquam ullamcorper ornare lobortis. Phasellus quis sem et ipsum mollis malesuada sed in ex. Ut aliquam ex eget metus finibus maximus. Proin suscipit mauris eu nibh lacinia, quis feugiat dui dapibus. Nam sed libero est. Aenean vulputate orci sit amet diam faucibus, eu sagittis sapien volutpat. Nam imperdiet felis turpis, at pretium odio pulvinar in. Sed vestibulum id eros nec ultricies. Sed quis aliquam tortor, vitae ullamcorper tellus. Donec egestas laoreet eros, id suscipit est rutrum nec. Sed auctor nulla eget metus aliquam, ut condimentum enim elementum. + +Aliquam suscipit non turpis sit amet bibendum. Fusce velit ligula, euismod et maximus at, luctus sed neque. Quisque pretium, nisl at ullamcorper finibus, lectus leo mattis sapien, vel euismod mauris diam ullamcorper ex. Nulla ut risus finibus, lacinia ligula at, auctor erat. Mauris consectetur sagittis ligula vel dapibus. Nullam libero libero, lobortis aliquam libero vel, venenatis ultricies leo. Duis porttitor, nibh congue fermentum posuere, erat libero pulvinar tortor, a pellentesque nunc ipsum vel sem. Nullam volutpat, eros sit amet facilisis consectetur, ipsum est vehicula massa, non vestibulum neque elit in mauris. Nunc hendrerit ipsum non enim bibendum, vitae rhoncus mi egestas. Etiam ullamcorper massa vel nisl sagittis, nec bibendum arcu malesuada. Aenean aliquet turpis justo, a consectetur arcu mollis convallis. Etiam tellus ipsum, ultricies vitae lorem et, ornare facilisis orci. Praesent fringilla justo urna, vel mollis neque pulvinar vestibulum. + +Donec non iaculis erat. Aliquam et mi sed nunc pulvinar ultricies in ut ipsum. Interdum et malesuada fames ac ante ipsum primis in faucibus. Praesent feugiat lacus ac dignissim semper. Phasellus vitae quam nisi. Morbi vel diam ultricies risus lobortis ornare. Fusce maximus et ligula quis iaculis. Sed congue ex eget felis convallis, sit amet hendrerit elit tempor. Donec vehicula blandit ante eget commodo. Vestibulum eleifend diam at feugiat euismod. Etiam magna tellus, dignissim eget fermentum vel, vestibulum vitae mauris. Nam accumsan et erat id sagittis. Donec lacinia, odio ut ornare ultricies, dolor velit accumsan tortor, non finibus erat tellus quis ligula. Nunc quis metus in leo volutpat ornare vulputate eu nisl. + +Donec quis viverra ex. Nullam id feugiat mauris, eu fringilla nulla. Vestibulum id maximus elit. Cras elementum elit sed felis lobortis, eget sagittis nisi hendrerit. Vivamus vitae elit neque. Donec vulputate lacus ut libero ultrices accumsan. Vivamus accumsan nulla orci, in dignissim est laoreet sagittis. Proin at commodo velit. Curabitur in velit felis. Aliquam erat volutpat. Sed consequat, nulla et cursus sodales, nisi lacus mattis risus, quis eleifend erat ex nec turpis. Sed suscipit ultrices lorem in hendrerit. + +Morbi vitae lacus nec libero ornare tempus eu et diam. Suspendisse magna ipsum, fermentum vel odio quis, molestie aliquam urna. Fusce mollis turpis a eros accumsan porttitor. Pellentesque rhoncus dolor sit amet magna rutrum, et dapibus justo tempor. Sed purus nisi, maximus vitae fringilla eu, molestie nec urna. Fusce malesuada finibus pretium. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec sed aliquet eros. Pellentesque luctus diam ante, eget euismod nisl aliquet eu. Sed accumsan elit purus, tempor varius ligula tempus nec. Curabitur ornare leo suscipit suscipit fermentum. Morbi eget nulla est. Maecenas faucibus interdum tristique. + +Etiam ut elit eros. Nulla pharetra suscipit molestie. Nulla facilisis bibendum nisl non molestie. Curabitur turpis lectus, facilisis vel diam non, vulputate ultrices mauris. Aenean placerat aliquam convallis. Suspendisse sed scelerisque tellus. Vivamus lacinia neque eget risus cursus suscipit. Proin consequat dolor vel neque tempor, eu aliquam sem scelerisque. Duis non eros a purus malesuada pharetra non et nulla. Suspendisse potenti. Mauris libero eros, finibus vel nulla id, sagittis dapibus ante. Proin iaculis sed nunc et cursus. + +Quisque accumsan lorem sit amet lorem aliquet euismod. Curabitur fermentum rutrum posuere. Etiam ultricies, sem id pellentesque suscipit, urna magna lacinia eros, quis efficitur risus nisl at lacus. Nulla quis lacus tortor. Mauris placerat ex in dolor tincidunt, vel aliquet nisi pretium. Cras iaculis risus vitae pellentesque aliquet. Quisque a enim imperdiet, ullamcorper arcu vitae, rutrum risus. Nullam consectetur libero at felis fringilla, nec congue nibh dignissim. Nam et lobortis felis, eu pellentesque ligula. Aenean facilisis, ligula non imperdiet maximus, massa orci gravida sapien, at sagittis lacus nisl in lacus. Nulla quis mauris luctus, scelerisque felis consequat, tempus risus. Fusce auctor nisl non nulla luctus molestie. Maecenas sapien nisl, auctor non dolor et, iaculis scelerisque lorem. Suspendisse egestas enim aliquet, accumsan mauris nec, posuere quam. Nulla iaculis dui dui, sit amet vestibulum erat ultricies ac. + +Cras eget dolor erat. Proin at nisl ut leo consectetur ultricies vel ut arcu. Nulla in felis malesuada, ullamcorper tortor et, convallis massa. Nunc urna justo, ornare in nibh vitae, hendrerit condimentum libero. Etiam vitae libero in purus venenatis fringilla. Nullam velit nulla, consequat ut turpis non, egestas hendrerit nibh. Duis tortor turpis, interdum non ante ac, cursus accumsan lectus. Cras pharetra bibendum augue quis dictum. Sed euismod vestibulum justo. Proin porta lobortis purus. Duis venenatis diam tortor, sit amet condimentum eros rhoncus a. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nunc at magna nec diam lobortis efficitur sit amet ut lacus. Nulla quis orci tortor. Pellentesque tempus velit a odio finibus porta. + +Proin feugiat mauris a tellus scelerisque convallis. Maecenas libero magna, blandit nec ultrices id, congue vel mi. Aliquam lacinia, quam vel condimentum convallis, tortor turpis aliquam odio, sed blandit libero lacus et eros. In eleifend iaculis magna ac finibus. Praesent auctor facilisis tellus in congue. Sed molestie lobortis dictum. Nam quis dignissim augue, vel euismod lorem. Curabitur posuere dapibus luctus. Donec ultricies dictum lectus, quis blandit arcu commodo ac. Aenean tincidunt ligula in nunc imperdiet dignissim. Curabitur egestas sollicitudin sapien ut semper. Aenean nec dignissim lacus. + +Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec aliquam dictum vehicula. Donec tortor est, volutpat non nisi nec, varius gravida ex. Nunc vel tristique nunc, vitae mattis nisi. Nunc nec luctus ex, vitae tincidunt lectus. In hac habitasse platea dictumst. Curabitur lobortis ex eget tincidunt tempor. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Ut a vehicula mi. + +Fusce eu libero finibus, interdum nulla a, placerat neque. Cras bibendum tempor libero nec feugiat. Cras ut sodales eros. Proin viverra, massa sit amet viverra egestas, neque nisl porta ex, sit amet hendrerit libero ligula vel urna. Mauris suscipit lacus id justo rhoncus suscipit. Etiam vel libero tellus. Maecenas non diam molestie, condimentum tellus a, bibendum enim. Mauris aliquet imperdiet tellus, eget sagittis dolor. Sed blandit in neque et luctus. Cras elementum sagittis nunc, vel mollis lorem euismod et. Donec posuere at lacus eget suscipit. + +Nulla nunc mi, pretium non massa vel, tempor semper magna. Nunc a leo pulvinar, tincidunt nunc at, dignissim mi. Aliquam erat volutpat. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Ut viverra nulla a nisl finibus, at hendrerit ligula ullamcorper. Donec a lorem semper, tempor magna et, lobortis libero. Mauris id sapien leo. Donec dignissim, quam vitae porttitor dignissim, quam justo mattis dui, vel consequat odio elit quis orci. Etiam nec pretium neque, sit amet pretium orci. Duis ac tortor venenatis, feugiat purus non, feugiat nunc. Proin scelerisque nisl in turpis aliquam vulputate. + +Praesent sed est semper, fringilla lorem vitae, tincidunt nibh. Cras eros metus, auctor at mauris sit amet, sodales semper orci. Nunc a ornare ex. Curabitur bibendum arcu congue urna vulputate egestas. Vestibulum finibus id risus et accumsan. Aenean ut volutpat tellus. Aenean tincidunt malesuada urna sit amet vestibulum. Mauris vel tellus dictum, varius lacus quis, dictum arcu. + +Aenean quis metus eu erat feugiat cursus vel at ligula. Proin dapibus sodales urna, id euismod lectus tempus id. Pellentesque ex ligula, convallis et erat vel, vulputate condimentum nisl. Pellentesque pharetra nulla quis massa eleifend hendrerit. Praesent sed massa ipsum. Maecenas vehicula dolor massa, id sodales urna faucibus et. Mauris ac quam non massa tincidunt feugiat et at lacus. Fusce libero massa, vulputate vel scelerisque non, mollis in leo. Ut sit amet ultricies odio. Suspendisse in sapien viverra, facilisis purus ut, pretium libero. + +Vivamus tristique pharetra molestie. Nam a volutpat purus. Praesent consequat gravida nisi, ac blandit nisi suscipit ut. Quisque posuere, ligula a ultrices laoreet, ligula nunc vulputate libero, ut rutrum erat odio tincidunt justo. Sed vitae leo at leo fringilla bibendum. Vestibulum ut augue nec dolor auctor accumsan. Praesent laoreet id eros pulvinar commodo. Suspendisse potenti. Ut pharetra, mauris vitae blandit fringilla, odio ante tincidunt lorem, sit amet tempor metus diam ut turpis. + +Praesent quis egestas arcu. Nullam at porta arcu. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Morbi vulputate ligula malesuada ligula luctus, vulputate tempus erat bibendum. Nunc ullamcorper non lectus at euismod. Etiam nibh felis, tincidunt a metus vel, pellentesque rhoncus neque. Etiam at diam in erat luctus interdum. Nunc vel ipsum pulvinar, sollicitudin lacus ac, tempus urna. Etiam vel lacinia sapien. Pellentesque sagittis velit vel mi efficitur iaculis. Integer euismod sit amet urna in sagittis. Cras eleifend ut nibh in facilisis. Donec et lacus vitae nunc placerat sodales. Nulla sed hendrerit ligula, at dapibus sapien. + +Praesent at iaculis ex. Curabitur est purus, cursus a faucibus quis, dictum id velit. Donec dignissim fringilla viverra. Nunc mauris felis, laoreet sit amet sagittis at, vestibulum in libero. Maecenas quis orci turpis. Quisque ut nibh vitae magna mollis consequat id at mauris. Aliquam eu odio eget nulla bibendum sodales. Quisque vel orci eleifend nisi pretium lacinia. Suspendisse eget risus eget mi volutpat molestie eget quis lacus. Duis nisi libero, tincidunt nec nulla id, faucibus cursus felis. + +Donec tempor eget risus pellentesque molestie. Phasellus porta neque vel arcu egestas, nec blandit velit fringilla. Nullam porta faucibus justo vitae laoreet. Pellentesque viverra id nunc eu varius. Nulla pulvinar lobortis iaculis. Etiam vestibulum odio nec velit tristique, a tristique nisi mattis. In sed fringilla orci, vitae efficitur odio. Quisque dui odio, ornare eget velit at, lacinia consequat libero. Quisque lectus nulla, aliquet eu leo in, porta rutrum diam. Donec nec mattis neque. Nam rutrum, odio ac eleifend bibendum, dolor arcu rutrum neque, eget porta elit tellus a lacus. Sed massa metus, sollicitudin et sapien eu, finibus tempus orci. Proin et sapien sit amet erat molestie interdum. In quis rutrum velit, faucibus ultrices tellus. + +Sed sagittis sed justo eget tincidunt. Maecenas ut leo sagittis, feugiat magna et, viverra velit. Maecenas ex arcu, feugiat at consequat vitae, auctor eu massa. Integer egestas, enim vitae maximus convallis, est lectus pretium mauris, ac posuere lectus nisl quis quam. Aliquam tempus laoreet mi, vitae dapibus dolor varius dapibus. Suspendisse potenti. Donec sit amet purus nec libero dapibus tristique. Pellentesque viverra bibendum ligula. Donec sed felis et ex lobortis laoreet. Phasellus a fringilla libero, vitae malesuada nulla. Pellentesque blandit mattis lacus, et blandit tortor laoreet consequat. Suspendisse libero nunc, viverra sed fermentum in, accumsan egestas arcu. Proin in placerat elit. Sed interdum imperdiet malesuada. Suspendisse aliquet quis mauris eget sollicitudin. + +Vivamus accumsan tellus non erat volutpat, quis dictum dolor feugiat. Praesent rutrum nunc ac est mollis cursus. Fusce semper volutpat dui ut egestas. Curabitur sit amet posuere massa. Cras tincidunt nulla et mi mollis imperdiet. Suspendisse scelerisque ex id sodales vulputate. In nunc augue, pharetra in placerat eu, mattis id tellus. Vivamus cursus efficitur vehicula. Nulla aliquet vehicula aliquet. + +Sed cursus tellus sed porta pulvinar. Sed vitae nisi neque. Nullam aliquet, lorem et efficitur scelerisque, arcu diam aliquam felis, sed pulvinar lorem odio et turpis. Praesent convallis pulvinar turpis eu iaculis. Aliquam nec gravida mi. Curabitur eu nibh tempor, blandit justo in, ultrices felis. Fusce placerat metus non mi sagittis rutrum. Morbi sed dui fringilla, sagittis mauris eget, imperdiet nunc. Phasellus hendrerit sem elit, id hendrerit libero auctor sit amet. Integer sodales elit sit amet consequat cursus. + +Nam semper est eget nunc mollis, in pellentesque lectus fringilla. In finibus vel diam id semper. Nunc mattis quis erat eu consectetur. In hac habitasse platea dictumst. Nullam et ipsum vestibulum ex pulvinar ultricies sit amet id velit. Aenean suscipit mi tortor, a lobortis magna viverra non. Nulla condimentum aliquet ante et ullamcorper. Pellentesque porttitor arcu a posuere tempus. Aenean lacus quam, imperdiet eu justo vitae, pretium efficitur ex. Duis id purus id magna rhoncus ultrices id eu risus. Nunc dignissim et libero id dictum. + +Quisque a tincidunt neque. Phasellus commodo mi sit amet tempor fringilla. Ut rhoncus, neque non porttitor elementum, libero nulla egestas augue, sed fringilla sapien felis ac velit. Phasellus viverra rhoncus mollis. Nam ullamcorper leo vel erat laoreet luctus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Vivamus semper a metus a cursus. Nulla sed orci egestas, efficitur purus ac, malesuada tellus. Aenean rutrum velit at tellus fermentum mollis. Aliquam eleifend euismod metus. + +In hac habitasse platea dictumst. Vestibulum volutpat neque vitae porttitor laoreet. Nam at tellus consequat, sodales quam in, pulvinar arcu. Maecenas varius convallis diam, ac lobortis tellus pellentesque quis. Maecenas eget augue massa. Nullam volutpat nibh ac justo rhoncus, ut iaculis tellus rutrum. Fusce efficitur efficitur libero quis condimentum. Curabitur congue neque non tincidunt tristique. Fusce eget tempor ex, at pellentesque odio. Praesent luctus dictum vestibulum. Etiam non orci nunc. Vivamus vitae laoreet purus, a lobortis velit. Curabitur tincidunt purus ac lectus elementum pellentesque. Quisque sed tincidunt est. + +Sed vel ultrices massa, vitae ultricies justo. Cras finibus mauris nec lacus tempus dignissim. Cras faucibus maximus velit, eget faucibus orci luctus vehicula. Nulla massa nunc, porta ac consequat eget, rhoncus non tellus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Fusce sed maximus metus, vel imperdiet ipsum. Ut scelerisque lectus at blandit porttitor. Ut vulputate nunc pharetra, aliquet sapien ac, sollicitudin sapien. Aenean eget ante lorem. Nam accumsan venenatis tellus id dignissim. + +Curabitur fringilla, magna non maximus dapibus, nulla sapien vestibulum lectus, sit amet semper dolor neque vitae nisl. Nunc ultrices vehicula augue sed iaculis. Maecenas nec diam mollis, suscipit orci et, vestibulum ante. Pellentesque eu nisl tortor. Nunc eleifend, lacus quis volutpat volutpat, nisi mi molestie sem, quis mollis ipsum libero a tellus. Ut viverra dolor mattis convallis interdum. Sed tempus nisl at nunc scelerisque aliquet. Quisque tempor tempor lorem id feugiat. Nullam blandit lectus velit, vitae porta lacus tincidunt a. Vivamus sit amet arcu ultrices, tincidunt mi quis, viverra quam. Aenean fringilla libero elementum lorem semper, quis pulvinar eros gravida. Nullam sodales blandit mauris, sed fermentum velit fermentum sit amet. Donec malesuada mauris in augue sodales vulputate. Vestibulum gravida turpis id elit rhoncus dignissim. Integer non congue lorem, eu viverra orci. + +Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec at dolor magna. Aliquam consectetur erat augue, id iaculis velit pharetra ac. Integer rutrum venenatis dignissim. Integer non sodales elit. Curabitur ut magna ut nibh feugiat aliquam ac ut risus. Morbi nibh quam, aliquam id placerat nec, vestibulum eget velit. Suspendisse at dignissim quam. Vivamus aliquet sem sed nisl volutpat, ut cursus orci ultrices. Aliquam ultrices lacinia enim, vitae aliquet neque. + +Quisque scelerisque finibus diam in mattis. Cras cursus auctor velit. Aliquam sem leo, fermentum et maximus et, molestie a libero. Aenean justo elit, rutrum a ornare id, egestas eget enim. Aenean auctor tristique erat. Curabitur condimentum libero lacus, nec consequat orci vestibulum sed. Fusce elit ligula, blandit vitae sapien vitae, dictum ultrices risus. Nam laoreet suscipit sapien, at interdum velit faucibus sit amet. Duis quis metus egestas lectus elementum posuere non nec libero. Aliquam a dolor bibendum, facilisis nunc a, maximus diam. Vestibulum suscipit tristique magna, non dignissim turpis sodales sed. Nunc ornare, velit ac facilisis fringilla, dolor mi consectetur lorem, vitae finibus erat justo suscipit urna. Maecenas sit amet eros erat. Nunc non arcu ornare, suscipit lorem eget, sodales mauris. Aliquam tincidunt, quam nec mollis lacinia, nisi orci fermentum libero, consequat eleifend lectus quam et sapien. Vestibulum a quam urna. + +Cras arcu leo, euismod ac ullamcorper at, faucibus sed massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus porttitor velit in enim interdum, non commodo metus ornare. Morbi vel lorem quis nisl luctus tristique quis vitae nisl. Suspendisse condimentum tortor enim, nec eleifend ipsum euismod et. Sed gravida quam ut tristique lacinia. Mauris eu interdum ipsum, ac ultrices odio. Nullam auctor tellus a risus porttitor vehicula. Nulla blandit euismod dictum. In pharetra, enim iaculis pulvinar interdum, dui nunc placerat nunc, sit amet pretium lectus nulla vitae quam. Phasellus quis enim sollicitudin, varius nulla id, ornare purus. Donec quam lacus, vestibulum quis nunc ac, mollis dictum nisi. Cras ut mollis elit. Maecenas ultrices ligula at risus faucibus scelerisque. Etiam vitae porttitor purus. Curabitur blandit lectus urna, ut hendrerit tortor feugiat ut. + +Phasellus fringilla, sapien pellentesque commodo pharetra, ante libero aliquam tellus, ut consectetur augue libero a sapien. Maecenas blandit luctus nisl eget aliquet. Maecenas vitae porta dolor, faucibus laoreet sapien. Suspendisse lobortis, ipsum sed vehicula aliquam, elit purus scelerisque dui, rutrum consectetur diam odio et lorem. In nec lacinia metus. Donec viverra libero est, vel bibendum erat condimentum quis. Donec feugiat purus leo. In laoreet vitae felis a porttitor. Mauris ullamcorper, lacus id condimentum suscipit, neque magna pellentesque arcu, eget cursus neque tellus id metus. Curabitur volutpat ac orci vel ultricies. + +Sed ut finibus erat. Sed diam purus, varius non tincidunt quis, ultrices sit amet ipsum. Donec et egestas nulla. Suspendisse placerat nisi at dui laoreet iaculis. Aliquam aliquet leo at augue faucibus molestie. Nullam lacus augue, hendrerit sed nisi eu, faucibus porta est. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam ut leo aliquet sem fermentum rutrum quis ac justo. Integer placerat aliquam nisl ut sagittis. Proin erat orci, lobortis et sem eget, eleifend fringilla augue. Mauris varius laoreet arcu, sed tincidunt felis. Pellentesque venenatis lorem odio, id pulvinar velit molestie feugiat. Donec mattis lacus sed eleifend pulvinar. + +Sed condimentum ex in tincidunt hendrerit. Etiam eget risus lacinia, euismod nibh eu, pellentesque quam. Proin elit eros, convallis id mauris ac, bibendum ultrices lectus. Morbi venenatis, purus id fermentum consequat, nunc libero tincidunt ligula, non dictum ligula orci nec quam. Nulla nec ultrices lorem. Aenean maximus augue vel dictum pharetra. Etiam turpis urna, pellentesque quis malesuada eu, molestie faucibus felis. + +Vestibulum pharetra augue ut quam blandit congue in nec risus. Proin eu nibh eu dui eleifend porta vitae id lectus. Proin lacus nibh, lobortis sed ligula vitae, interdum lobortis erat. Suspendisse potenti. In sollicitudin quis sapien ut aliquet. Mauris ac nulla arcu. Fusce tristique justo quis lectus mollis, eu volutpat lectus finibus. Vivamus venenatis facilisis ex ut vestibulum. + +Etiam varius lobortis purus, in hendrerit elit tristique at. In tempus, augue vestibulum fermentum gravida, ligula tellus vulputate arcu, eu molestie ex sapien at purus. Vestibulum nec egestas metus. Duis pulvinar quam nec consequat interdum. Aenean non dapibus lacus. Aliquam sit amet aliquet nulla. Sed venenatis volutpat purus nec convallis. Phasellus aliquet semper sodales. Cras risus sapien, condimentum auctor urna a, pulvinar ornare nisl. Sed tincidunt felis elit, ut elementum est bibendum ac. Morbi interdum justo vel dui faucibus condimentum. + +Sed convallis eu sem at tincidunt. Nullam at auctor est, et ullamcorper ipsum. Pellentesque eget ante ante. Interdum et malesuada fames ac ante ipsum primis in faucibus. Integer euismod, sapien sed dapibus ornare, nibh enim maximus lacus, lacinia placerat urna quam quis felis. Morbi accumsan id nisl ut condimentum. Donec bibendum nisi est, sed volutpat lorem rhoncus in. Vestibulum ac lacinia nunc, eget volutpat magna. Integer aliquam pharetra ipsum, id placerat nunc volutpat quis. Etiam urna diam, rhoncus sit amet varius vel, euismod vel sem. Nullam vel molestie urna. Vivamus ornare erat at venenatis euismod. Suspendisse potenti. Fusce diam justo, tincidunt vel sem at, commodo faucibus nisl. Duis gravida efficitur diam, vel sagittis erat pulvinar ut. + +Quisque vel pharetra felis. Duis efficitur tortor dolor, vitae porttitor erat fermentum sed. Sed eu mi purus. Etiam dignissim tortor eu tempus molestie. Aenean pretium erat enim, in hendrerit ante hendrerit at. Sed ut risus vel nunc venenatis ultricies quis in lacus. Pellentesque vitae purus euismod, placerat risus non, ullamcorper augue. Quisque varius quam ligula, nec aliquet ex faucibus vitae. Quisque rhoncus sit amet leo tincidunt mattis. Cras id mauris eget purus pretium gravida sit amet eu augue. Aliquam dapibus odio augue, id lacinia velit pulvinar eu. + +Mauris fringilla, tellus nec pharetra iaculis, neque nisi ultrices massa, et tincidunt sem dui sed mi. Curabitur erat lorem, venenatis quis tempus lacinia, tempus sit amet nunc. Aliquam at neque ac metus commodo dictum quis vitae justo. Phasellus eget lacus tempus, blandit lorem vel, rutrum est. Aenean pharetra sem ut augue lobortis dignissim. Sed rhoncus at nulla id ultrices. Cras id condimentum felis. In suscipit luctus vulputate. Donec tincidunt lacus nec enim tincidunt sollicitudin ut quis enim. Nam at libero urna. Praesent sit amet massa vitae massa ullamcorper vehicula. + +Nullam bibendum augue ut turpis condimentum bibendum. Proin sit amet urna hendrerit, sodales tortor a, lobortis lectus. Integer sagittis velit turpis, et tincidunt nisi commodo eget. Duis tincidunt elit finibus accumsan cursus. Aenean dignissim scelerisque felis vel lacinia. Nunc lacinia maximus luctus. In hac habitasse platea dictumst. Vestibulum eget urna et enim tempor tempor. Nam feugiat, felis vel vestibulum tempus, orci justo viverra diam, id dapibus lorem justo in ligula. + +Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In ac pellentesque sem. Vestibulum lacinia magna dui, eu lacinia augue placerat et. Maecenas pulvinar congue est. Pellentesque commodo dui non pulvinar scelerisque. Etiam interdum est posuere sem bibendum, ac commodo magna dictum. Cras ipsum turpis, rhoncus nec posuere vitae, laoreet a arcu. Integer ac massa sit amet enim placerat lacinia sed ultrices arcu. Suspendisse sem nibh, luctus sit amet volutpat in, pellentesque eu metus. Ut gravida neque eget mi accumsan tempus. Nam sit amet aliquet nibh. + +Pellentesque a purus cursus nulla hendrerit congue quis et odio. Aenean hendrerit, leo ullamcorper sagittis hendrerit, erat dui molestie quam, sed condimentum lacus risus sed tellus. Morbi a dapibus lectus, ut feugiat ex. Phasellus pretium quam et sapien mollis, vel iaculis dui dignissim. Sed ullamcorper est turpis, a viverra lorem consectetur in. Aenean aliquet nibh non cursus rutrum. Suspendisse at tristique urna, id lobortis urna. In hac habitasse platea dictumst. Phasellus libero velit, rutrum sed tellus nec, dapibus tincidunt ligula. Quisque vel dui venenatis, consequat nisl ut, lacinia ipsum. Phasellus vitae magna pellentesque, lobortis est id, faucibus quam. Nam eleifend faucibus dui vel pellentesque. + +Etiam ut est non lacus tincidunt interdum. Maecenas sed massa urna. Quisque ut nibh tortor. Pellentesque felis ipsum, tempor finibus ipsum et, euismod pretium metus. Donec sit amet est ipsum. Quisque rhoncus justo non finibus elementum. Nulla nec lectus ac tortor placerat fringilla. Phasellus ac ultrices nunc, eu efficitur nisl. Nulla rhoncus nunc vitae ante dictum tincidunt. Nunc ultrices, massa sit amet malesuada dignissim, lectus lacus consequat sapien, non eleifend metus sem in eros. Phasellus mauris ante, dictum sit amet suscipit ac, rhoncus eget nisi. Phasellus at orci mollis, imperdiet neque eget, faucibus nulla. In at purus massa. Pellentesque quis rutrum lectus. + +Integer eu faucibus turpis, sit amet mollis massa. Vestibulum id nulla commodo, rutrum ipsum sed, semper ante. Phasellus condimentum orci nec nibh convallis, ac maximus orci ullamcorper. Maecenas vitae sollicitudin mi. Integer et finibus lectus, et condimentum ligula. Donec elementum tristique quam vitae dapibus. Morbi euismod ipsum in tristique ullamcorper. + +Duis fermentum non enim eu auctor. Quisque lacinia nibh vehicula nibh posuere, eu volutpat turpis facilisis. Ut ac faucibus nulla. Sed eleifend quis ex et pellentesque. Vestibulum sollicitudin in libero id fringilla. Phasellus dignissim purus consequat, condimentum dui sit amet, condimentum ante. Pellentesque ac consectetur massa, quis sagittis est. Nulla maximus tristique risus accumsan convallis. Curabitur imperdiet ac lacus a ultrices. Nulla facilisi. Sed quis quam quis lectus placerat lobortis vel sed turpis. In mollis dui id neque iaculis, ut aliquet tellus malesuada. Proin at luctus odio, vel blandit sapien. Praesent dignissim tortor vehicula libero fringilla, nec ultrices erat suscipit. Maecenas scelerisque purus in dapibus fermentum. + +Curabitur magna odio, mattis in tortor ut, porttitor congue est. Vestibulum mollis lacinia elementum. Fusce maximus erat vitae nunc rutrum lobortis. Integer ligula eros, auctor vel elit non, posuere luctus lacus. Maecenas quis auctor massa. Ut ipsum lacus, efficitur posuere euismod et, hendrerit efficitur est. Phasellus fringilla, quam id tincidunt pretium, nunc dui sollicitudin orci, eu dignissim nisi metus ut magna. Integer lobortis interdum dolor, non bibendum purus posuere et. Donec non lectus aliquet, pretium dolor eu, cursus massa. Sed ut dui sapien. In sed vestibulum massa. Pellentesque blandit, dui non sodales vehicula, orci metus mollis nunc, non pharetra ex tellus ac est. Mauris sagittis metus et fermentum pretium. Nulla facilisi. Quisque quis ante ut nulla placerat mattis ut quis nisi. + +Sed quis nulla ligula. Quisque dignissim ligula urna, sed aliquam purus semper at. Suspendisse potenti. Nunc massa lectus, pharetra vehicula arcu bibendum, imperdiet sodales ipsum. Nam ac sapien diam. Mauris iaculis fringilla mattis. Pellentesque tempus eros sit amet justo volutpat mollis. Phasellus ac turpis ipsum. Morbi vel ante elit. Aenean posuere quam consequat velit varius suscipit. Donec tempor quam ut nibh cursus efficitur. + +Morbi molestie dolor nec sem egestas suscipit. Etiam placerat pharetra lectus, et ullamcorper risus tristique in. Sed faucibus ullamcorper lectus eget fringilla. Maecenas malesuada hendrerit congue. Sed eget neque a erat placerat tincidunt. Aliquam vitae dignissim turpis. Fusce at placerat magna, a laoreet lectus. Maecenas a purus nec diam gravida fringilla. Nam malesuada euismod ante non vehicula. In faucibus bibendum leo, faucibus posuere nisl pretium quis. Fusce finibus bibendum finibus. Vestibulum eu justo maximus, hendrerit diam nec, dignissim sapien. Aenean dolor lacus, malesuada quis vestibulum ac, venenatis ac ipsum. Cras a est id nunc finibus facilisis. Cras lacinia neque et interdum vehicula. Suspendisse vulputate tellus elit, eget tempor dui finibus vel. + +Cras sed pretium odio. Proin hendrerit elementum felis in tincidunt. Nam sed turpis vel justo molestie accumsan condimentum eu nunc. Praesent lobortis euismod rhoncus. Nulla vitae euismod nibh, quis mattis mi. Fusce ultrices placerat porttitor. Duis sem ipsum, pellentesque sit amet odio a, molestie vulputate mauris. + +Duis blandit mollis ligula, sit amet mattis ligula finibus sit amet. Nunc a leo molestie, placerat diam et, vestibulum leo. Suspendisse facilisis neque purus, nec pellentesque ligula fermentum nec. Aenean malesuada mauris lorem, eu blandit arcu pulvinar quis. Duis laoreet urna lacus, non maximus arcu rutrum ultricies. Nulla augue dolor, suscipit eu mollis eu, aliquam condimentum diam. Ut semper orci luctus, pharetra turpis at, euismod mi. Nulla leo diam, finibus sit amet purus sed, maximus dictum lorem. Integer eu mi id turpis laoreet rhoncus. + +Integer a mauris tincidunt, finibus orci ut, pretium mauris. Nulla molestie nunc mi, id finibus lorem elementum sed. Proin quis laoreet ante. Integer nulla augue, commodo id molestie quis, rutrum ut turpis. Suspendisse et tortor turpis. Sed ut pharetra massa. Pellentesque elementum blandit sem, ut elementum tellus egestas a. Fusce eu purus nibh. + +Cras dignissim ligula scelerisque magna faucibus ullamcorper. Proin at condimentum risus, auctor malesuada quam. Nullam interdum interdum egestas. Nulla aliquam nisi vitae felis mollis dictum. Suspendisse dapibus consectetur tortor. Ut ut nisi non sem bibendum tincidunt. Vivamus suscipit leo quis gravida dignissim. + +Aliquam interdum, leo id vehicula mollis, eros eros rhoncus diam, non mollis ligula mi eu mauris. Sed ultrices vel velit sollicitudin tincidunt. Nunc auctor metus at ligula gravida elementum. Praesent interdum eu elit et mollis. Duis egestas quam sit amet velit dignissim consequat. Aliquam ac turpis nec nunc convallis sagittis. Fusce blandit, erat ac fringilla consectetur, dolor eros sodales leo, vel aliquet risus nisl et diam. Aliquam luctus felis vitae est eleifend euismod facilisis et lacus. Sed leo tellus, auctor eu arcu in, volutpat sagittis nisl. Pellentesque nisl ligula, placerat vel ullamcorper at, vulputate ac odio. Morbi ac faucibus orci, et tempus nulla. Proin rhoncus rutrum dolor, in venenatis mauris. Suspendisse a fermentum augue, non semper mi. Nunc eget pretium neque. Phasellus augue erat, feugiat ac aliquam congue, rutrum non sapien. Pellentesque ac diam gravida, consectetur felis at, ornare neque. + +Nullam interdum mattis sapien quis porttitor. Interdum et malesuada fames ac ante ipsum primis in faucibus. Phasellus aliquet rutrum ipsum id euismod. Maecenas consectetur massa et mi porta viverra. Nunc quam nibh, dignissim vitae maximus et, ullamcorper nec lorem. Nunc vitae justo dapibus, luctus lacus vitae, pretium elit. Maecenas et efficitur leo. Curabitur mauris lectus, placerat quis vehicula vitae, auctor ut urna. Quisque rhoncus pharetra luctus. In hac habitasse platea dictumst. Integer sit amet metus nec eros malesuada aliquam. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Morbi hendrerit mi ac leo aliquam, sit amet ultricies libero commodo. Mauris dapibus purus metus, sit amet viverra nibh imperdiet et. Nullam porta nulla tellus, quis vehicula diam imperdiet non. Vivamus enim massa, bibendum in fermentum in, ultrices at ex. + +Suspendisse fermentum id nibh eget accumsan. Duis dapibus bibendum erat ut sollicitudin. Aliquam nec felis risus. Pellentesque rhoncus ligula id sem maximus mollis sed nec massa. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus ipsum ipsum, sodales sed enim id, convallis faucibus eros. Donec ultricies dictum tincidunt. Cras vitae nibh arcu. Pellentesque cursus, sapien nec consequat fermentum, ipsum ante suscipit dui, imperdiet hendrerit est nisl eu massa. Quisque vitae sem ligula. Aenean iaculis metus ut mauris interdum laoreet. Vivamus sed gravida dolor. + +Morbi nulla metus, porttitor sed eros sit amet, efficitur efficitur est. In vel nisl urna. Ut aliquet tellus at congue convallis. Phasellus imperdiet lobortis sollicitudin. Integer sodales, sem eu ultricies pharetra, erat erat porttitor odio, eget dapibus libero ipsum eget velit. Phasellus gravida nulla nisl, eu pharetra mi auctor vel. Sed blandit pharetra velit, ut egestas libero placerat non. Aliquam a interdum quam. Proin at tortor nec dui sollicitudin tempus sed vestibulum elit. Nunc non sollicitudin velit. + +Aenean consequat diam velit, sed rutrum tortor faucibus dictum. Quisque at semper augue. Duis ut est eget mi ornare bibendum id et ligula. Phasellus consequat tortor non leo pulvinar posuere. Proin vestibulum eleifend felis, in hendrerit tortor sollicitudin eu. Phasellus hendrerit, lacus vel laoreet interdum, dui tortor consequat justo, commodo ultricies arcu felis vitae enim. Vivamus eu sapien at leo suscipit rutrum eu at justo. Aenean et dolor a libero ullamcorper posuere. Integer laoreet placerat nisi in vulputate. Mauris laoreet eget risus sed cursus. Donec scelerisque neque a libero eleifend hendrerit. Nulla varius condimentum nunc sit amet fermentum. Aliquam lorem ex, varius nec mollis ut, ultrices in neque. Morbi sit amet porta leo. Integer iaculis fermentum lacus in vestibulum. + +Ut gravida, tellus ut maximus ultrices, erat est venenatis nisl, vitae pretium massa ex ac magna. Sed non purus eget ligula aliquet volutpat non quis arcu. Nam aliquam tincidunt risus, sit amet fringilla sapien vulputate ut. Mauris luctus suscipit pellentesque. Nunc porttitor dapibus ex quis tempus. Ut ullamcorper metus a eros vulputate, vitae viverra lectus convallis. Mauris semper imperdiet augue quis tincidunt. Integer porta pretium magna, sed cursus sem scelerisque sollicitudin. Nam efficitur, nibh pretium eleifend vestibulum, purus diam posuere sem, in egestas mauris augue sit amet urna. + +Vestibulum tincidunt euismod massa in congue. Duis interdum metus non laoreet fringilla. Donec at ligula congue, tincidunt nunc non, scelerisque nunc. Donec bibendum magna non est scelerisque feugiat at nec neque. Ut orci tortor, tempus eget massa non, dignissim faucibus dolor. Nam odio risus, accumsan pretium neque eget, accumsan dignissim dui. In ut neque auctor, scelerisque tellus sed, ullamcorper nisi. Suspendisse varius cursus quam at hendrerit. Vivamus elit libero, sagittis vitae sem ac, vulputate iaculis ligula. + +Sed lobortis laoreet purus sit amet rutrum. Pellentesque feugiat non leo vel lacinia. Quisque feugiat nisl a orci bibendum vestibulum. In et sollicitudin urna. Morbi a arcu ac metus faucibus tempus. Nam eu imperdiet sapien, suscipit mattis tortor. Aenean blandit ipsum nisi, a eleifend ligula euismod at. Integer tincidunt pharetra felis, mollis placerat mauris hendrerit at. Curabitur convallis, est sit amet luctus volutpat, massa lacus cursus augue, sed eleifend magna quam et risus. Aliquam lobortis tincidunt metus vitae porttitor. Suspendisse potenti. Aenean ullamcorper, neque id commodo luctus, nulla nunc lobortis quam, id dapibus neque dui nec mauris. Etiam quis lorem quis elit commodo ornare. Ut pharetra purus ultricies enim ultrices efficitur. Proin vehicula tincidunt molestie. Mauris et placerat sem. + +Aliquam erat volutpat. Suspendisse velit turpis, posuere ac lacus eu, lacinia laoreet velit. Sed interdum felis neque, id blandit sem malesuada sit amet. Ut sagittis justo erat, efficitur semper orci tempor sed. Donec enim massa, posuere varius lectus egestas, pellentesque posuere mi. Cras tincidunt ut libero sed mattis. Suspendisse quis magna et tellus posuere interdum vel at purus. Pellentesque fringilla tristique neque, id aliquet tellus ultricies non. Duis ut tellus vel odio lobortis vulputate. + +Integer at magna ac erat convallis vestibulum. Sed lobortis porttitor mauris. Fusce varius lorem et volutpat pulvinar. Aenean ac vulputate lectus, vitae consequat velit. Suspendisse ex dui, varius ut risus ut, dictum scelerisque sem. Vivamus urna orci, volutpat ut convallis ac, venenatis vitae urna. In hac habitasse platea dictumst. Etiam eu purus arcu. Aenean vulputate leo urna, vel tristique dui sagittis euismod. Suspendisse non tellus efficitur ante rhoncus volutpat at et sapien. + +Sed dapibus accumsan porttitor. Phasellus facilisis lectus finibus ligula dignissim, id pulvinar lectus feugiat. Nullam egestas commodo nisi posuere aliquet. Morbi sit amet tortor sagittis, rutrum dui nec, dapibus sapien. Sed posuere tortor tortor, interdum auctor magna varius vitae. Vestibulum id sagittis augue. Curabitur fermentum arcu sem, eu condimentum quam rutrum non. Phasellus rutrum nibh quis lectus rhoncus pretium. Curabitur dictum interdum elit. Vestibulum maximus sodales imperdiet. Mauris auctor nec purus sed venenatis. In in urna purus. + +Duis placerat molestie suscipit. Morbi a elit id purus efficitur consequat. Nunc ac commodo turpis. Etiam sit amet lacus a ipsum tempus venenatis sed vel nibh. Duis elementum aliquam mi sed tristique. Morbi ligula tortor, semper ac est vel, lobortis maximus erat. Curabitur ipsum felis, laoreet vel condimentum eget, ullamcorper sit amet mauris. Nulla facilisi. Nam at purus sed mi egestas placerat vitae vel magna. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Suspendisse at dignissim diam. Phasellus consectetur eget neque vel viverra. Donec sollicitudin mattis dolor vel malesuada. Vivamus vehicula leo neque, vitae fermentum leo posuere et. Praesent dui est, finibus sit amet tristique quis, pharetra vel nibh. + +Duis nulla leo, accumsan eu odio eget, sagittis semper orci. Quisque ullamcorper ligula quam, commodo porttitor mauris ullamcorper eu. Cras varius sagittis felis in aliquam. Duis sodales risus ac justo vehicula, nec mattis diam lacinia. Cras eget lectus ipsum. Ut commodo, enim vitae malesuada hendrerit, ex dolor egestas lectus, sit amet hendrerit metus diam nec est. Vestibulum tortor metus, lobortis sit amet ante eget, tempor molestie lacus. In molestie et urna et semper. Mauris mollis, sem non hendrerit condimentum, sapien nisi cursus est, non suscipit quam justo non metus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam enim est, porta ac feugiat vitae, rutrum in lorem. Duis vehicula tortor ut posuere maximus. + +Nullam vestibulum non tellus sed commodo. Quisque mattis elit sit amet sapien sollicitudin, ut condimentum nisl congue. Aenean sagittis massa vel elit faucibus fermentum. Donec tincidunt nisi nec nisl sodales pellentesque. Mauris congue congue ligula ut suscipit. Vivamus velit tortor, tempor et gravida eget, fermentum sit amet ante. Nullam fringilla, lorem at ultrices cursus, urna neque ornare dolor, eu lacinia orci enim sed nibh. Ut a ullamcorper lectus, id mattis purus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aenean maximus sollicitudin posuere. Nunc at augue lacus. Aenean efficitur leo sit amet lacinia efficitur. + +Quisque venenatis quam mi, in pharetra odio vulputate eu. In vel nisl pulvinar, pulvinar ligula ut, sodales risus. Sed efficitur lectus at vestibulum tincidunt. Vestibulum eu ullamcorper elit. Fusce vestibulum magna enim, et tempor lacus posuere vitae. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Integer leo elit, luctus nec mattis sit amet, sollicitudin in turpis. + +Proin convallis venenatis leo, vitae tristique erat iaculis nec. Nulla facilisi. Duis porttitor, sapien et bibendum vulputate, sem libero sodales lacus, non malesuada felis erat ut libero. Nam non felis semper, finibus est a, mattis mauris. Praesent nec eros quam. Nulla hendrerit, augue consectetur eleifend ultricies, purus mi condimentum nulla, eget dapibus est nunc sed libero. Nullam elementum dui erat, vitae luctus libero sollicitudin et. Nulla odio magna, placerat in augue eu, dapibus imperdiet odio. Suspendisse imperdiet metus sit amet rhoncus dapibus. Cras at enim et urna vehicula cursus eu a mauris. Integer magna ante, eleifend ac placerat vitae, porta at nisi. Cras eget malesuada orci. Curabitur nunc est, vulputate id viverra et, dignissim sed odio. Curabitur non mattis sem. Sed bibendum, turpis vitae vehicula faucibus, nunc quam ultricies lectus, vitae viverra felis turpis at libero. + +Nullam ut egestas ligula. Proin hendrerit justo a lectus commodo venenatis. Nulla facilisi. Ut cursus lorem quis est bibendum condimentum. Aenean in tristique odio. Fusce tempor hendrerit ipsum. Curabitur mollis felis justo, quis dapibus erat auctor vel. Sed augue lectus, finibus ut urna quis, ullamcorper vestibulum dui. Etiam molestie aliquam tempor. Integer mattis sollicitudin erat, et tristique elit varius vel. Mauris a ex justo. + +Nam eros est, imperdiet non volutpat rutrum, pellentesque accumsan ligula. Duis sit amet turpis metus. Aenean in rhoncus metus, ac fringilla ex. Suspendisse condimentum egestas purus, ut pharetra odio vulputate vel. Duis tincidunt massa a placerat ultrices. Mauris ultricies nibh sit amet condimentum malesuada. Duis tincidunt id ipsum sed congue. + +Praesent eu ex augue. Nullam in porta ligula. In tincidunt accumsan arcu, in pellentesque magna tristique in. Mauris eleifend libero ac nisl viverra faucibus. Nam sollicitudin dolor in commodo hendrerit. Cras at orci metus. Ut quis laoreet orci. Vivamus ultrices leo pellentesque tempor aliquet. Maecenas ut eros vitae purus placerat vestibulum. Etiam vitae gravida dolor, quis rhoncus diam. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. + +Suspendisse fringilla lacinia sagittis. Integer tincidunt consectetur tristique. Morbi non orci convallis, congue sapien quis, vulputate nunc. Donec a libero vel magna elementum facilisis non quis mi. Mauris posuere tellus non ipsum ultrices elementum. Vivamus massa velit, facilisis quis placerat aliquet, aliquet nec leo. Praesent a maximus sem. Sed neque elit, feugiat vel quam non, molestie sagittis nunc. Etiam luctus nunc ac mauris scelerisque, nec rhoncus lacus convallis. Nunc pharetra, nunc ac pulvinar aliquam, ex ipsum euismod augue, nec porttitor lacus turpis vitae neque. Fusce bibendum odio id tortor faucibus pellentesque. Sed ac porta nibh, eu gravida erat. + +Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aliquam quis ullamcorper felis. Nulla mattis sagittis ante ac tincidunt. Integer ac felis efficitur, viverra libero et, facilisis ligula. Suspendisse a metus a massa rhoncus posuere. Phasellus suscipit ligula ut lacus facilisis, ac pellentesque ex tempor. Quisque consectetur massa mi, ac molestie libero dictum quis. Proin porttitor ligula quis erat tincidunt venenatis. Proin congue nunc sed elit gravida, nec consectetur lectus sodales. Etiam tincidunt convallis ipsum at vestibulum. Quisque maximus enim et mauris porttitor, et molestie magna tristique. Morbi vitae metus elit. Maecenas sed volutpat turpis. Aliquam vitae dolor vestibulum, elementum purus eget, dapibus nibh. Nullam egestas dui ac rutrum semper. + +Etiam hendrerit est metus, et condimentum metus aliquam ac. Pellentesque id neque id ipsum rhoncus vulputate. Aliquam erat nisl, posuere sit amet ligula ac, fermentum blandit felis. Vivamus fermentum mi risus, non lacinia purus viverra id. Aenean ac sapien consequat, finibus mauris nec, porta sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed quis consectetur ex, dignissim bibendum nulla. Phasellus ac libero at quam vehicula euismod non eu leo. Phasellus a sapien augue. + +Maecenas ligula dui, bibendum vitae mauris et, auctor laoreet felis. Duis non libero a mi semper mattis. Quisque consequat luctus massa, quis tristique eros auctor feugiat. Maecenas sodales euismod neque vitae facilisis. Nullam laoreet imperdiet velit at pellentesque. Etiam massa odio, facilisis a consequat vitae, placerat vel magna. Nunc sagittis eros nec urna fringilla, pulvinar vestibulum nibh scelerisque. Sed magna metus, cursus eu consequat et, pharetra a est. Suspendisse elementum neque a dui malesuada lacinia. Donec sed ipsum volutpat, cursus urna id, ullamcorper arcu. Maecenas laoreet nisl eget velit egestas sollicitudin. Etiam nisl turpis, mollis id dignissim vitae, tristique vehicula ante. Maecenas eget placerat est, at rutrum augue. Vivamus faucibus lacinia ullamcorper. Sed pulvinar urna sodales ante sodales, at gravida leo dictum. + +Morbi maximus, quam a lobortis bibendum, enim felis varius elit, ac vehicula elit nisl ut lacus. Quisque ut arcu augue. Praesent id turpis quam. Sed sed arcu eros. Maecenas at cursus lorem, ac eleifend nisi. Fusce mattis felis at commodo pharetra. Praesent ac commodo ipsum. Quisque finibus et eros vitae tincidunt. In hac habitasse platea dictumst. Praesent purus ipsum, luctus lobortis ornare quis, auctor eget justo. Nam vel enim sollicitudin, faucibus tortor eu, sagittis eros. Ut nec consectetur erat. Donec ultricies malesuada ligula, a hendrerit sapien volutpat in. Maecenas sed enim vitae sapien pulvinar faucibus. + +Proin semper nunc nibh, non consequat neque ullamcorper vel. Maecenas lobortis sagittis blandit. Aenean et arcu ultricies turpis malesuada malesuada. Ut quam ex, laoreet ut blandit cursus, feugiat vitae dolor. Etiam ex lacus, scelerisque vel erat vel, efficitur tincidunt magna. Morbi tristique lacinia dolor, in egestas magna ultrices vitae. Integer ultrices leo ac tempus venenatis. Praesent ac porta tortor. Vivamus ornare blandit tristique. Nulla rutrum finibus pellentesque. In non dui elementum, fermentum ipsum vel, varius magna. Pellentesque euismod tortor risus, ac pellentesque nisl faucibus eget. + +Vivamus eu enim purus. Cras ultrices rutrum egestas. Sed mollis erat nibh, at posuere nisl luctus nec. Nunc vulputate, sapien id auctor molestie, nisi diam tristique ante, non convallis tellus nibh at orci. Morbi a posuere purus, in ullamcorper ligula. Etiam elementum sit amet dui imperdiet iaculis. Proin vitae tincidunt ipsum, sit amet placerat lectus. Curabitur commodo sapien quam, et accumsan lectus fringilla non. Nullam eget accumsan enim, ac pharetra mauris. Sed quis tristique velit, vitae commodo nisi. Duis turpis dui, maximus ut risus at, finibus consequat nunc. Maecenas sed est accumsan, aliquet diam in, facilisis risus. Curabitur vehicula rutrum auctor. Nam iaculis risus pulvinar maximus viverra. Nulla vel augue et ex sagittis blandit. + +Ut sem nulla, porta ac ante ac, posuere laoreet eros. Donec sodales posuere justo a auctor. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Cras mollis at orci hendrerit porta. Nullam sodales tortor tortor, non lacinia diam finibus id. Duis libero orci, suscipit ac odio et, dictum consequat ipsum. Pellentesque eu ligula sagittis, volutpat eros at, lacinia lorem. Cras euismod tellus in iaculis tempor. Quisque accumsan, magna a congue venenatis, ante ipsum aliquam lectus, at egestas enim nunc at justo. Quisque sem purus, viverra ut tristique ut, maximus id enim. Etiam quis placerat sem. In sollicitudin, lacus eu rutrum mollis, nulla eros luctus elit, vel dapibus urna purus nec urna. Phasellus egestas massa quam, ac molestie erat hendrerit a. Praesent ultrices neque ut turpis molestie auctor. Etiam molestie placerat purus, et euismod erat aliquam in. Morbi id suscipit justo. + +Proin est ante, consequat at varius a, mattis quis felis. Sed accumsan nibh sit amet ipsum elementum posuere. Vestibulum bibendum id diam sit amet gravida. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Morbi nec dolor vel ipsum dignissim hendrerit vel non ipsum. Praesent facilisis orci quis elit auctor lobortis. Phasellus cursus risus lectus, vel lobortis libero dapibus in. Quisque tristique tempus leo a pulvinar. Pellentesque a magna tincidunt, pellentesque massa nec, laoreet orci. Morbi congue ornare dolor quis commodo. Phasellus massa nisi, tincidunt at eros dictum, hendrerit lobortis urna. Maecenas porta, magna id mattis molestie, nibh tellus lobortis sem, eget tincidunt ipsum quam eu turpis. + +Ut gravida orci risus, vel rutrum mauris vehicula id. Etiam bibendum, neque a placerat condimentum, ex orci imperdiet lectus, quis dapibus arcu lacus eget lectus. Sed consequat non mi sit amet venenatis. Fusce vestibulum erat libero, eget hendrerit risus vulputate sollicitudin. Integer sed eleifend felis. Donec commodo, sem eu mattis placerat, urna odio aliquam tellus, et laoreet justo tellus eget erat. Fusce sed suscipit tortor. Nam hendrerit nibh ac nunc auctor lacinia. Pellentesque placerat condimentum ipsum, eget semper tortor hendrerit vel. Nullam non urna eu lacus pellentesque congue ut id eros. + +Nunc finibus leo in rhoncus tristique. Sed eu ipsum nec nisl egestas faucibus eget a felis. Pellentesque vitae nisi in nulla accumsan fermentum. Sed venenatis feugiat eleifend. Fusce porttitor varius placerat. Aliquam aliquet lacus sit amet mattis mollis. Sed vel nulla quis dolor suscipit vehicula ac viverra lorem. Duis viverra ipsum eget nulla ullamcorper fermentum. Mauris tincidunt arcu quis quam fringilla ornare. Donec et iaculis tortor. Nam ultricies libero vel ipsum aliquet efficitur. Morbi eget dolor aliquam, tempus sapien eget, viverra ante. Donec varius mollis ex, sed efficitur purus euismod interdum. Quisque vel sapien non neque tincidunt semper. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. + +Suspendisse sit amet purus leo. Fusce lectus lorem, aliquam ac nulla eget, imperdiet ornare eros. Nullam sem augue, varius in nisi non, sollicitudin pellentesque ante. Etiam eu odio condimentum, tempor libero et, egestas arcu. Cras pellentesque eleifend aliquet. Pellentesque non blandit ligula. Ut congue viverra rhoncus. Phasellus mattis mi ac eros placerat, eu feugiat tellus ultrices. Aenean mollis laoreet libero eu imperdiet. Cras sed pulvinar mi, ac vehicula ligula. Vestibulum sit amet ex massa. In a egestas eros. + +Mauris pretium ipsum risus, venenatis cursus ante imperdiet id. Praesent eu turpis nec risus feugiat maximus ullamcorper ac lectus. Integer placerat at mi vel dapibus. Vestibulum fermentum turpis sit amet turpis viverra, id aliquet diam suscipit. Nam nec ex sed ante ullamcorper pharetra quis sit amet risus. Sed ac faucibus velit, id feugiat nibh. Nullam eget ipsum ex. Vivamus tincidunt non nunc non faucibus. Quisque bibendum viverra facilisis. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur at nisi hendrerit quam suscipit egestas. Curabitur laoreet maximus ultricies. Duis ut tellus ac augue molestie dictum. + +Suspendisse rhoncus iaculis erat, ut ullamcorper est tristique eget. Donec auctor nec risus at gravida. Vivamus volutpat vulputate tellus, vel ultricies eros suscipit eget. Ut pulvinar id mi eu tempus. Morbi malesuada augue in dui varius, nec blandit neque vehicula. Donec ornare nec nisl in mollis. Morbi enim nisi, rhoncus nec est id, dapibus tempus urna. Ut id elit a felis vestibulum consectetur. Duis lectus quam, pharetra sit amet diam sed, posuere vestibulum erat. Fusce vitae maximus massa. Nullam id metus tempus, iaculis risus eu, lobortis urna. Quisque in congue urna. Pellentesque placerat neque in augue dapibus, non varius ex malesuada. Curabitur ut eleifend libero. Fusce vitae ligula luctus, fermentum enim vitae, ultrices erat. + +Sed viverra augue turpis, scelerisque egestas sapien mattis eu. Duis laoreet magna at ex pharetra dapibus. Praesent eget odio vel quam venenatis dictum. Nulla in sollicitudin dolor. Mauris lobortis nec eros vel rhoncus. Vestibulum porta viverra venenatis. Curabitur vel scelerisque quam, a egestas velit. Praesent volutpat tincidunt magna at laoreet. + +Cras nec lorem odio. Pellentesque quis dui urna. Praesent at tellus ac lectus scelerisque placerat nec eu risus. Vestibulum sit amet mattis ligula. Vivamus sed nisi at leo elementum accumsan at sit amet arcu. Aenean mattis tellus nec leo gravida, eget hendrerit nisl faucibus. Mauris pellentesque luctus condimentum. Maecenas pretium sapien nunc, eget commodo dolor maximus id. Mauris vestibulum accumsan massa a dictum. Phasellus interdum quam ligula, ut maximus diam blandit aliquam. Nunc vitae ex eu erat condimentum consectetur. Maecenas interdum condimentum volutpat. + +Donec et enim a libero rutrum laoreet. Praesent a condimentum sem, at tincidunt quam. In vel molestie risus. Sed urna dui, molestie vitae mollis laoreet, tempor quis lectus. Praesent vitae auctor est, et aliquet nunc. Curabitur vulputate blandit nulla, at gravida metus. Maecenas gravida dui eu iaculis tristique. Pellentesque posuere turpis nec auctor eleifend. Suspendisse bibendum diam eu tellus lobortis, et laoreet quam congue. In hac habitasse platea dictumst. Morbi dictum neque velit, eget rutrum eros ultrices sit amet. + +Phasellus fermentum risus pharetra consectetur bibendum. Donec magna tortor, lacinia vitae nibh quis, aliquet pretium lorem. Donec turpis nisi, pretium eu enim volutpat, mattis malesuada augue. Nullam vel tellus iaculis, sollicitudin elit eget, tincidunt lacus. Fusce elementum elementum felis et iaculis. Suspendisse porta eros nec neque malesuada, in malesuada ante sollicitudin. Vivamus bibendum viverra molestie. + +Integer feugiat, erat nec convallis aliquam, velit felis congue erat, molestie eleifend tellus erat in tellus. Nunc et justo purus. Donec egestas fermentum dui non feugiat. Quisque in sapien sagittis, gravida quam id, iaculis lectus. Cras sagittis rhoncus bibendum. Fusce quis metus in velit scelerisque tincidunt at non ipsum. Vivamus efficitur ante eu odio vulputate, vitae ultricies risus vehicula. Proin eget odio eu sem tincidunt feugiat vel id lorem. + +Vestibulum sit amet nulla dignissim, euismod mi in, fermentum tortor. Donec ut aliquet libero, lacinia accumsan velit. Donec et nulla quam. Nullam laoreet odio nec nunc imperdiet, a congue eros venenatis. Quisque nec tellus sit amet neque interdum posuere. Duis quis mi gravida, tincidunt diam convallis, ultricies augue. Mauris consequat risus non porttitor congue. Ut in ligula consequat, viverra nunc a, eleifend enim. Duis ligula urna, imperdiet nec facilisis et, ornare eu ex. Proin lobortis lectus a lobortis porttitor. Nulla leo metus, egestas eu libero sed, pretium faucibus felis. Vestibulum non sem tortor. Nam cursus est leo. Vivamus luctus enim odio, non interdum sem dapibus a. Aenean accumsan consequat lectus in imperdiet. + +Donec vehicula laoreet ipsum in posuere. Quisque vel quam imperdiet, sollicitudin nisi quis, suscipit velit. Morbi id sodales mauris. Curabitur tellus arcu, feugiat sed dui sit amet, sodales sagittis libero. Aenean vel suscipit metus, non placerat leo. Vestibulum quis nulla elit. Proin scelerisque non ante ut commodo. Interdum et malesuada fames ac ante ipsum primis in faucibus. + +Sed non urna dolor. Suspendisse convallis mi porta pulvinar ultrices. Suspendisse quam ipsum, hendrerit non scelerisque molestie, interdum dictum nunc. Morbi condimentum condimentum turpis eu luctus. Pellentesque sagittis sollicitudin odio, sed ultricies felis ornare sit amet. Sed ultrices ex leo, a tincidunt nisl gravida sed. Nullam ornare accumsan porta. Praesent consectetur id est nec sollicitudin. + +In hac habitasse platea dictumst. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed sed ultrices nibh. Duis accumsan suscipit eros, a dictum odio tempus sit amet. Aenean imperdiet erat ac lacus finibus, scelerisque cursus massa imperdiet. Mauris molestie risus ut lacinia posuere. Nulla et sodales purus. Maecenas orci erat, placerat in tristique quis, placerat in mi. + +Donec sollicitudin pellentesque odio in feugiat. Morbi eu dolor ut mauris congue sollicitudin. Aliquam erat volutpat. Nulla id varius dui. Curabitur finibus urna ante, consectetur interdum nisi volutpat a. Quisque quis mi tristique, consequat tellus eget, rutrum sapien. Vivamus vitae tellus vulputate, rutrum ex eu, vulputate sem. Suspendisse viverra lorem tellus, vel interdum orci gravida quis. Ut laoreet arcu at mi ullamcorper finibus. Duis porta sagittis vestibulum. Sed commodo nisl vitae urna sollicitudin, nec lacinia est sodales. Curabitur imperdiet sodales dui sed iaculis. Sed ac tellus maximus, eleifend quam sit amet, feugiat elit. Aenean viverra, dui at mattis varius, est odio vestibulum sapien, sit amet mollis libero massa nec velit. Etiam quis sodales justo. + +Ut ultricies, sem eget sodales feugiat, nunc arcu congue elit, ac tempor justo massa nec purus. Maecenas enim nunc, pharetra eget dictum sit amet, tempus pellentesque velit. Suspendisse venenatis ligula in nulla mattis, et imperdiet ex tincidunt. Etiam vulputate, tellus et ultrices suscipit, enim velit laoreet massa, vitae congue odio enim ac urna. Morbi quam lorem, iaculis ac varius sagittis, euismod quis dolor. In ut dui eu purus feugiat consectetur. Vestibulum cursus velit quis lacus pellentesque iaculis. Cras in risus sed mauris porta rutrum. Nulla facilisi. Nullam eu bibendum est, non pellentesque lectus. Sed imperdiet feugiat lorem, quis convallis ante auctor in. Maecenas justo magna, scelerisque sit amet tellus eget, varius elementum risus. Duis placerat et quam sed varius. + +Duis nec nibh vitae nibh dignissim mollis quis sed felis. Curabitur vitae quam placerat, venenatis purus ut, euismod nisl. Curabitur porttitor nibh eu pulvinar ullamcorper. Suspendisse posuere nec ipsum ac dapibus. Cras convallis consectetur urna. Phasellus a nibh in dolor lacinia posuere id eget augue. In eu pharetra lorem, vitae cursus lacus. Aliquam tincidunt nibh lectus. Aenean facilisis ultricies posuere. Sed ut placerat orci. Curabitur scelerisque gravida blandit. Maecenas placerat ligula eget suscipit fringilla. Mauris a tortor justo. Aliquam hendrerit semper mollis. Phasellus et tincidunt libero. Etiam vel quam libero. + +Quisque aliquet tempor ex. Ut ante sem, vehicula at enim vel, gravida porta elit. Etiam vitae lacus a neque lobortis consectetur. Mauris sed interdum odio. Mauris elementum ex blandit tempor cursus. Integer in enim in leo viverra elementum. Fusce consectetur metus et sem rutrum, mattis euismod diam semper. Nunc sed ipsum vel urna consequat vehicula. Donec cursus pretium lorem, vestibulum pretium felis commodo sit amet. Nam blandit felis enim, eget gravida ex faucibus a. In nec neque massa. Etiam laoreet posuere ipsum. Praesent volutpat nunc dolor, ac vulputate magna facilisis non. Aenean congue turpis vel lectus sollicitudin tristique. Sed nec consequat purus, non vehicula quam. Etiam ultricies, est ac dictum tincidunt, turpis turpis pretium massa, a vulputate libero justo at nibh. + +Aliquam erat volutpat. Cras ultrices augue ac sollicitudin lobortis. Curabitur et aliquet purus. Duis feugiat semper facilisis. Phasellus lobortis cursus velit, a sollicitudin tortor. Nam feugiat sapien non dapibus condimentum. Morbi at mi bibendum, commodo quam at, laoreet enim. Integer eu ultrices enim. Sed vestibulum eu urna ut dictum. Curabitur at mattis leo, sed cursus massa. Aliquam porttitor, felis quis fermentum porttitor, justo velit feugiat nulla, eget condimentum sem dui ut sapien. + +In fringilla elit eu orci aliquam consequat. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Ut eget fringilla tellus. Curabitur fermentum, mi et condimentum suscipit, elit neque bibendum dui, et hendrerit nunc metus id ipsum. Morbi placerat mi in hendrerit congue. Ut feugiat mauris eget scelerisque viverra. Vivamus sit amet erat dictum, sagittis lectus nec, pulvinar lorem. Sed non enim ac dui sollicitudin aliquet. Quisque ut lacus dolor. Fusce hendrerit malesuada euismod. Nulla faucibus vel mauris eu mollis. Mauris est diam, fringilla ac arcu feugiat, efficitur volutpat turpis. Aliquam venenatis cursus massa sed porttitor. Ut ac finibus enim, in tincidunt sapien. + +Nunc faucibus semper turpis a lacinia. Phasellus gravida, libero vel pulvinar ornare, ex sem tincidunt lectus, sit amet convallis augue risus at tortor. Quisque sit amet ipsum id nulla posuere vestibulum. Pellentesque scelerisque mauris vel leo viverra sodales. Nulla viverra aliquam ex, ut rutrum enim fermentum venenatis. Aenean eget dapibus ex, eget faucibus metus. Vestibulum volutpat leo in diam semper, eget porta magna suscipit. Sed sit amet nulla blandit, aliquam dolor ac, gravida velit. Sed vel velit viverra, maximus est id, convallis justo. + +Curabitur nulla ante, vulputate at libero vel, ullamcorper rutrum nibh. Pellentesque porttitor eu mauris id mattis. Duis vulputate augue elit, eget interdum justo pretium vel. Maecenas eu vulputate arcu, eget posuere purus. Suspendisse viverra a velit dictum eleifend. Suspendisse vitae dapibus diam. Donec vehicula justo in ante interdum, eu luctus diam placerat. Vivamus convallis ipsum eu orci suscipit, sed fermentum enim euismod. Maecenas faucibus elit vitae ex ornare tristique. Donec vestibulum nec elit sit amet porttitor. Aenean tempor lectus eget tortor hendrerit luctus. Nullam interdum vitae lectus vel feugiat. Cras in risus non magna consectetur lobortis. Sed faucibus enim quis gravida convallis. + +Phasellus eget massa sit amet libero ultrices suscipit. Vivamus at risus sapien. Nam mollis nunc eget velit dictum maximus. Sed pellentesque, nunc ac fringilla lacinia, quam enim mattis ex, sed euismod tortor metus eu neque. Ut mattis nisl ut lectus rhoncus, sodales bibendum eros porta. Nulla porttitor enim nec diam sagittis, eget porta velit efficitur. Vestibulum ultricies eros neque. Phasellus rutrum suscipit enim, in interdum ante gravida vitae. Sed in sagittis diam, non commodo velit. + +Morbi hendrerit odio orci, nec tincidunt odio rhoncus nec. Mauris neque velit, vehicula a lorem at, suscipit tristique dui. Sed finibus, nisl in mattis convallis, turpis neque sodales lacus, eu porta enim magna non diam. Nam commodo sodales risus consectetur malesuada. In eget elementum justo. Phasellus sit amet massa imperdiet, dapibus nunc sit amet, suscipit orci. Fusce condimentum laoreet feugiat. Ut ut viverra ante. Praesent bibendum interdum commodo. Nulla mollis nisi a est ornare volutpat. Sed at ligula eu nisi dapibus tempus. Proin cursus vestibulum justo, nec efficitur justo dignissim vel. Nunc quis maximus eros. + +Cras viverra, diam a tristique mattis, libero felis vulputate tellus, a ornare felis leo a dui. Nulla ante nulla, finibus ut tellus ut, blandit pharetra nibh. Proin eleifend fermentum ex, eget auctor libero vulputate in. Nullam ultricies, mauris placerat pretium placerat, leo urna lobortis leo, vel placerat arcu libero sed mauris. Aliquam mauris ligula, ornare at urna at, eleifend gravida ligula. Vestibulum consectetur ut nulla non scelerisque. Donec ornare, sem nec elementum aliquam, urna nulla bibendum metus, eu euismod dui ligula ac est. Fusce laoreet erat eu ex lobortis, quis bibendum ligula interdum. Sed vel mi erat. Vivamus id lacus ac enim mattis tempor. Nunc ultricies pellentesque enim sed euismod. Fusce tincidunt convallis elit quis aliquam. Mauris nulla ipsum, sollicitudin quis diam ac, feugiat volutpat tellus. In nibh nibh, vulputate quis tincidunt quis, pulvinar eget magna. Pellentesque quis finibus dolor. Suspendisse viverra vitae lectus non eleifend. + +Nunc ut orci et sapien maximus semper. Nulla dignissim sem urna, ac varius lectus ultricies id. Quisque aliquet pulvinar pretium. In ultricies molestie tellus vehicula porta. Nam enim lorem, aliquam eget ex et, hendrerit volutpat quam. Maecenas diam lacus, pellentesque eget tempus ac, pharetra eu elit. Donec vel eros a sem facilisis vulputate. Nullam ac nisi vulputate, laoreet nisl ac, eleifend sem. Nullam mi massa, rhoncus sed pharetra interdum, tincidunt eget nunc. Aliquam viverra mattis posuere. Mauris et dui sed nisl sollicitudin fermentum quis ut arcu. Nam placerat eget orci at tincidunt. Curabitur vel turpis metus. Phasellus nibh nulla, fermentum scelerisque sem vel, gravida tincidunt velit. Pellentesque vel quam tempor, finibus massa pellentesque, condimentum dui. + +Donec at mattis neque. Etiam velit diam, consequat auctor mauris id, hendrerit faucibus metus. Maecenas ullamcorper eros a est sodales, ac consectetur odio scelerisque. Donec leo metus, imperdiet at pellentesque vel, feugiat id erat. Suspendisse at magna enim. Vestibulum placerat sodales lorem id sollicitudin. Aenean at euismod ligula, eget mollis diam. Phasellus pulvinar, orci nec pretium condimentum, est erat facilisis purus, quis feugiat augue elit aliquam nulla. Aenean vitae tortor id risus congue tincidunt. Sed dolor enim, mattis a ullamcorper id, volutpat ac leo. + +Proin vehicula feugiat augue, id feugiat quam sodales quis. Donec et ultricies massa, a lacinia nulla. Duis aliquam augue ornare euismod viverra. Ut lectus risus, rutrum sit amet efficitur a, luctus nec nisl. Cras volutpat ullamcorper congue. Sed vitae odio metus. Phasellus aliquet euismod varius. + +Nullam sem ex, malesuada ut magna ut, pretium mollis arcu. Nam porttitor eros cursus mi lacinia faucibus. Suspendisse aliquet eleifend iaculis. Maecenas sit amet viverra tortor. Nunc a mollis risus. Etiam tempus dolor in tortor malesuada mattis. Ut tincidunt venenatis est sit amet dignissim. Vestibulum massa enim, tristique sed scelerisque eu, fringilla ac velit. Donec efficitur quis urna sit amet malesuada. Vestibulum consequat ac ligula in dapibus. Maecenas massa massa, molestie non posuere nec, elementum ut magna. In nisi erat, mollis non venenatis eu, faucibus in justo. Morbi gravida non ex non egestas. Pellentesque finibus laoreet diam, eu commodo augue congue vitae. + +Aenean sem mi, ullamcorper dapibus lobortis vitae, interdum tincidunt tortor. Vivamus eget vulputate libero. Ut bibendum posuere lectus, vel tincidunt tortor aliquet at. Phasellus malesuada orci et bibendum accumsan. Aliquam quis libero vel leo mollis porta. Sed sagittis leo ac lacus dictum, ac malesuada elit finibus. Suspendisse pharetra luctus commodo. Vivamus ultricies a odio non interdum. Vivamus scelerisque tincidunt turpis quis tempor. Pellentesque tortor ligula, varius non nunc eu, blandit sollicitudin neque. Nunc imperdiet, diam et tristique luctus, ipsum ex condimentum nunc, sit amet aliquam justo velit sed libero. Duis vel suscipit ligula. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed tincidunt neque vel massa ultricies, id dictum leo consequat. Curabitur lobortis ultricies tellus, eget mattis nisl aliquam sit amet. + +Proin at suscipit justo. Vivamus ut vestibulum nisl. Pellentesque enim odio, pharetra non magna sed, efficitur auctor magna. Praesent tincidunt ante quis ante hendrerit viverra. Pellentesque vel ipsum id magna vulputate efficitur. Sed nec neque accumsan, pulvinar sapien quis, euismod mauris. Donec condimentum laoreet sapien quis gravida. Quisque sed mattis purus. Vestibulum placerat vel neque maximus scelerisque. + +Vestibulum mattis quam quis efficitur elementum. Duis dictum dolor ac scelerisque commodo. Fusce sollicitudin nisi sit amet dictum placerat. Suspendisse euismod pharetra eleifend. In eros nisl, porttitor sed mauris at, consectetur aliquet mauris. Donec euismod viverra neque sed fermentum. Phasellus libero magna, accumsan ut ultricies vitae, dignissim eget metus. Donec tellus turpis, interdum eget maximus nec, hendrerit eget massa. Curabitur auctor ligula in iaculis auctor. In ultrices quam suscipit cursus finibus. Aenean id mi at dolor interdum iaculis vitae ut lorem. Nullam sed nibh fringilla, lacinia odio nec, placerat erat. In dui libero, viverra ac viverra ac, pellentesque sit amet turpis. + +Nulla in enim ex. Sed feugiat est et consectetur venenatis. Cras varius facilisis dui vel convallis. Vestibulum et elit eget tellus feugiat pellentesque. In ut ante eu purus aliquet posuere. Nulla nec ornare sem, sed luctus lorem. Nam varius iaculis odio, eget faucibus nisl ullamcorper in. Sed eget cursus felis, nec efficitur nisi. + +Vivamus commodo et sem quis pulvinar. Pellentesque libero ante, venenatis vitae ligula sit amet, ornare sollicitudin nulla. Mauris eget tellus hendrerit, pulvinar metus quis, tempor nisi. Proin magna ex, laoreet sed tortor quis, varius fermentum enim. Integer eu dolor dictum, vulputate tortor et, aliquet ligula. Vestibulum vitae justo id mauris luctus sollicitudin. Suspendisse eget auctor neque, sodales egestas lorem. Vestibulum lacinia egestas metus vitae euismod. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus ex tellus, volutpat nec pulvinar sit amet, condimentum vitae dui. Curabitur vel felis sodales, lacinia nunc iaculis, ullamcorper augue. Pellentesque consequat dolor quis eros efficitur malesuada. Nulla ut malesuada lectus. + +Morbi et tristique ante. Aliquam erat volutpat. Vivamus vitae dui nec turpis pellentesque fermentum. Quisque eget velit massa. Pellentesque tristique aliquam nisl, eu sollicitudin justo venenatis sed. Duis eleifend sem eros, ut aliquam libero porttitor id. Sed non nunc consequat, rhoncus diam eu, commodo erat. Praesent fermentum in lectus id blandit. Donec quis ipsum at justo volutpat finibus. Nulla blandit justo nulla, at mollis lacus consequat eget. Aenean sollicitudin quis eros ut ullamcorper. + +Pellentesque venenatis nulla ut mi aliquet feugiat. Cras semper vel magna nec pharetra. Integer mattis felis et sapien commodo imperdiet. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Duis quis luctus felis. Vestibulum justo nibh, aliquam non lectus vitae, molestie placerat justo. Donec lorem nibh, gravida sit amet hendrerit ac, maximus id ipsum. Nunc ac libero sodales risus eleifend sagittis. Phasellus est massa, lobortis elementum ex sed, scelerisque consectetur neque. Nunc faucibus neque id lorem malesuada, eget convallis ex mattis. + +Sed turpis tortor, fermentum non turpis id, posuere varius nibh. Donec iaculis lorem dui. Etiam eros ante, sodales eget venenatis at, consectetur eget risus. Curabitur non aliquam ante, a pretium justo. Maecenas tempor nisl tortor, vitae dictum nisi ultrices eu. Duis eget dui ultrices, porttitor lacus sed, lobortis purus. Quisque mattis elit nec neque sagittis, sed commodo leo blandit. Mauris sodales interdum eleifend. Vestibulum condimentum consectetur augue, id luctus diam convallis et. + +Nunc suscipit risus in justo accumsan, a placerat magna tincidunt. Proin a nisl ipsum. Sed libero dui, tristique in augue quis, auctor tristique risus. Sed porttitor ex augue, eu porta augue molestie a. Duis rhoncus purus libero, eu tempus turpis condimentum at. Sed mollis nisi id lectus placerat tincidunt. Maecenas non scelerisque elit, quis rutrum orci. Donec in tellus pharetra urna ornare lobortis. Phasellus id risus at nisi varius rutrum eu ut turpis. + +Duis dictum justo quis nisl porta, eget tincidunt magna suscipit. Sed velit massa, ullamcorper eu sodales ac, pretium a massa. Duis et rutrum tortor. Nulla accumsan hendrerit sapien, cursus volutpat eros egestas eget. Donec sollicitudin at ante quis sollicitudin. Aenean blandit feugiat diam, id feugiat eros faucibus eget. Donec viverra dolor vel justo scelerisque dignissim. Nulla semper sem nunc, rhoncus semper tellus ultricies sed. Duis in ornare diam. Donec vehicula feugiat varius. Maecenas ut suscipit est. Vivamus sem sem, finibus at dolor sit amet, euismod dapibus ligula. Vestibulum fringilla odio dapibus, congue massa eget, congue sem. Donec feugiat magna eget tortor lacinia scelerisque non et ipsum. + +Suspendisse potenti. Nunc convallis sollicitudin ex eget venenatis. Sed iaculis nibh ex, vel ornare ligula congue dignissim. Quisque sollicitudin dolor ac dui vestibulum, sit amet molestie nisi aliquet. Donec at risus felis. Aenean sollicitudin metus a feugiat porta. Aenean a tortor ut dolor cursus sagittis. Vivamus consectetur porttitor nunc in facilisis. Proin sit amet mi vel lectus consectetur ultrices. + +Sed cursus lectus vitae nunc tristique, nec commodo turpis dapibus. Pellentesque luctus ex id facilisis ornare. Morbi quis placerat dolor. Donec in lectus in arcu mattis porttitor ac sit amet metus. Cras congue mauris non risus sodales, vitae feugiat ipsum bibendum. Nulla venenatis urna sed libero elementum, a cursus lorem commodo. Mauris faucibus lobortis eros nec commodo. + +Nullam suscipit ligula ullamcorper lorem commodo blandit. Nulla porta nibh quis pulvinar placerat. Vivamus eu arcu justo. Vestibulum imperdiet est ut fermentum porttitor. Pellentesque consectetur libero in sapien efficitur scelerisque. Curabitur ac erat sit amet odio aliquet dignissim. Pellentesque mi sem, rhoncus et luctus at, porttitor rutrum lectus. Vestibulum sollicitudin sollicitudin suscipit. Aenean efficitur dolor non ultrices imperdiet. Donec vel sem ex. + +Sed convallis mauris aliquam rutrum cursus. Ut tempor porttitor sodales. Etiam eu risus ac augue gravida egestas et eu dolor. Proin id magna ex. Suspendisse quis lectus quis lorem ultricies tempus. Donec porttitor velit vitae tincidunt faucibus. Aliquam vitae semper nisi. Morbi ultrices, leo non pretium dapibus, dui libero pellentesque ex, vel placerat enim ante vitae dui. Nunc varius, sem sit amet sagittis lobortis, lectus odio scelerisque mauris, ut vestibulum orci magna quis neque. Sed id congue justo. Interdum et malesuada fames ac ante ipsum primis in faucibus. Mauris congue nisi est, malesuada mollis elit tincidunt sed. Curabitur sed ex sit amet felis tristique elementum vitae vel nibh. + +Etiam mollis pretium lobortis. Mauris augue lacus, efficitur at lacus sed, mollis tincidunt lectus. Aliquam erat volutpat. Donec at euismod elit, et mattis felis. Sed id lobortis urna. Morbi imperdiet vestibulum leo, sed maximus leo blandit eu. Aliquam semper lorem neque, nec euismod turpis mattis mollis. Quisque lobortis urna ultrices odio pretium, ac venenatis orci faucibus. Suspendisse bibendum odio ligula, sed lobortis massa pharetra nec. Donec turpis justo, iaculis at dictum ac, finibus eu libero. Maecenas quis porttitor mi, sit amet aliquet neque. + +Vivamus auctor vulputate ante, at egestas lorem. Donec eu risus in nulla mollis ultricies at et urna. Duis accumsan porta egestas. Ut vel euismod augue. Fusce convallis nulla ante, nec fringilla velit aliquet at. Nam malesuada dapibus ligula, a aliquam nibh scelerisque ac. Praesent malesuada neque et pellentesque interdum. Curabitur volutpat at turpis vitae tristique. Vivamus porttitor semper congue. Quisque suscipit lacus mi, rhoncus ultrices tortor auctor quis. Maecenas neque neque, molestie ac facilisis eget, luctus ac lorem. In ut odio ut lacus suscipit pulvinar vitae sed elit. Nulla imperdiet, sem quis euismod sagittis, dui erat luctus dolor, faucibus faucibus erat sem eget nunc. Nam accumsan placerat malesuada. Maecenas convallis finibus pulvinar. + +Cras at placerat tortor. Morbi facilisis auctor felis sit amet molestie. Donec sodales sed lorem vitae suscipit. Etiam fermentum pharetra ipsum, nec luctus orci gravida eu. Pellentesque gravida, est non condimentum tempus, mauris ligula molestie est, in congue dolor nisl vel sapien. Duis congue tempor augue, id rutrum eros porta dapibus. Etiam rutrum eget est eget vestibulum. Aenean mollis arcu vel consequat varius. Praesent at condimentum felis. Duis nec interdum nisl. Donec commodo lorem sed sapien scelerisque malesuada non eu urna. In blandit non ipsum at porta. Nam lobortis leo vitae dui auctor, non feugiat quam bibendum. Donec auctor lectus sagittis laoreet maximus. Maecenas rhoncus laoreet porttitor. Vestibulum porttitor augue ut lectus hendrerit, eget posuere mi gravida. + +Sed mattis ex in erat pulvinar, eu imperdiet magna dapibus. Etiam nisi nibh, tempus non tellus sit amet, mattis tempor odio. Quisque nec lorem feugiat, lobortis odio et, commodo nunc. Maecenas semper purus nisi, nec vehicula nibh eleifend vitae. Nulla fermentum a lectus at maximus. Phasellus finibus metus non euismod ultrices. Etiam a pulvinar ante. Quisque convallis nec metus sit amet facilisis. Praesent laoreet massa et sollicitudin laoreet. Vestibulum in mauris aliquet, convallis mi ut, elementum purus. Nulla purus nulla, sodales at hendrerit quis, tempus sed lectus. + +Nam ut laoreet neque, ut maximus nibh. Maecenas quis justo pellentesque, sollicitudin elit at, venenatis velit. Aenean nunc velit, vehicula scelerisque odio at, consectetur laoreet purus. Duis dui purus, malesuada quis ipsum sit amet, tempor interdum libero. Curabitur porta scelerisque sapien, vitae cursus diam condimentum eu. Phasellus sed orci quam. Nullam vitae dui quis purus tincidunt vestibulum. Curabitur quis nulla porta, cursus arcu non, auctor enim. Etiam sollicitudin ex id sem vehicula mollis. Morbi viverra laoreet tincidunt. Praesent ut semper dui. Nam sit amet pretium neque. Mauris vitae luctus diam, in lacinia purus. Maecenas ut placerat justo, ut porta felis. Integer eu mauris ante. + +Aenean porttitor tellus diam, tempor consequat metus efficitur id. Suspendisse ut felis at erat tempor dictum at nec sapien. Sed vestibulum interdum felis, ac mattis mauris porta in. Nunc et condimentum massa. Sed cursus dictum justo et luctus. Integer convallis enim nisl, a rutrum lectus ultricies in. Donec dapibus lacus at nulla dapibus, id sollicitudin velit hendrerit. Fusce a magna at orci mollis rutrum ac a dolor. Aliquam erat volutpat. Morbi varius porta nunc, sit amet sodales ex hendrerit commodo. Donec tincidunt tortor sapien, vitae egestas sapien vehicula eget. + +Suspendisse potenti. Donec pulvinar felis nec leo malesuada interdum. Integer posuere placerat maximus. Donec nibh ipsum, tincidunt vitae luctus vitae, bibendum at leo. Sed cursus nisl ut ex faucibus aliquet sed nec eros. Curabitur molestie posuere felis. Integer faucibus velit eget consequat iaculis. Mauris sed vulputate odio. Phasellus maximus, elit a pharetra egestas, lorem magna semper tellus, vestibulum semper diam felis at sapien. Suspendisse facilisis, nisl sit amet euismod vehicula, libero nulla vehicula dolor, quis fermentum nibh elit sit amet diam. + +Morbi lorem enim, euismod eu varius ut, scelerisque quis odio. Nam tempus vitae eros id molestie. Nunc pretium in nulla eget accumsan. Quisque mattis est ut semper aliquet. Maecenas eget diam elementum, fermentum ipsum a, euismod sapien. Duis quam ligula, cursus et velit nec, ullamcorper tincidunt magna. Donec vulputate nisl est, et ullamcorper urna tempor sit amet. + +Proin lacinia dui non turpis congue pretium. Morbi posuere metus vel purus imperdiet interdum. Morbi venenatis vel eros non ultricies. Nulla vel semper elit. Ut quis purus tincidunt, auctor justo ut, faucibus turpis. Proin quis mattis erat, at faucibus ligula. Mauris in mauris enim. Donec facilisis enim at est feugiat hendrerit. Nam vel nisi lorem. Fusce ultricies convallis diam, in feugiat tortor luctus quis. Donec tempor, leo vitae volutpat aliquam, magna elit feugiat leo, quis placerat sapien felis eget arcu. Donec ornare fermentum eleifend. Integer a est orci. + +Proin rhoncus egestas leo. Nulla ultricies porta elit quis ornare. Nunc fermentum interdum vehicula. In in ligula lorem. Donec nec arcu sit amet orci lobortis iaculis. Mauris at mollis erat, sit amet mollis tortor. Mauris laoreet justo ullamcorper porttitor auctor. Aenean sit amet aliquam lectus, id fermentum eros. Praesent urna sem, vehicula ac fermentum id, dapibus ut purus. Vestibulum vitae tempus nunc. Donec at nunc ornare metus volutpat porta at eget magna. Donec varius aliquet metus, eu lobortis risus aliquam sed. Ut dapibus fermentum velit, ac tincidunt libero faucibus at. + +In in purus auctor, feugiat massa quis, facilisis nisi. Donec dolor purus, gravida eget dolor ac, porttitor imperdiet urna. Donec faucibus placerat erat, a sagittis ante finibus ac. Sed venenatis dignissim elit, in iaculis felis posuere faucibus. Praesent sed viverra dolor. Mauris sed nulla consectetur nunc laoreet molestie in ut metus. Proin ac ex sit amet magna vulputate hendrerit ac condimentum urna. Proin ligula metus, gravida et sollicitudin facilisis, iaculis ut odio. Cras tincidunt urna et augue varius, ut facilisis urna consequat. Aenean vehicula finibus quam. Ut iaculis eu diam ac mollis. Nam mi lorem, tristique eget varius at, sodales at urna. + +Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Proin vitae dictum erat, et auctor ipsum. Nullam nunc nunc, sollicitudin quis magna a, vestibulum fermentum mauris. Praesent at erat dolor. Proin laoreet tristique nulla vel efficitur. Nam sed ultrices nibh, id rutrum nunc. Curabitur eleifend a erat sit amet sollicitudin. Nullam metus quam, laoreet vitae dapibus id, placerat sed leo. Aliquam erat volutpat. Donec turpis nisl, cursus eu ex sit amet, lacinia pellentesque nisl. Sed id ipsum massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec interdum scelerisque lorem eu mattis. + +Vivamus ac tristique massa, nec facilisis nisl. Nam ipsum neque, tincidunt vel urna in, cursus imperdiet enim. Nam pellentesque egestas tempus. Morbi facilisis imperdiet libero vitae fringilla. Nam lacinia ligula at sapien facilisis malesuada. Nullam accumsan pulvinar sem, et cursus libero porta sit amet. Curabitur vulputate erat elit, ut pulvinar erat maximus vel. + +Cras aliquet metus ut purus sagittis, vel venenatis ante consectetur. Pellentesque nulla lacus, viverra viverra mattis non, placerat vitae nibh. Donec enim turpis, accumsan sit amet tincidunt eu, imperdiet non metus. Morbi ipsum eros, tincidunt vel est ac, tristique porttitor nibh. Praesent ut ullamcorper mauris. Sed laoreet sit amet diam congue venenatis. Integer porta purus nec orci sagittis posuere. + +Donec vehicula mauris eget lacus mollis venenatis et sed nibh. Nam sodales ligula ipsum, scelerisque lacinia ligula sagittis in. Nam sit amet ipsum at erat malesuada congue. Aenean ut sollicitudin sapien. Etiam at tempor odio. Mauris vitae purus ut magna suscipit consequat. Vivamus quis sapien neque. Nulla vulputate sem sit amet massa pellentesque, eleifend tristique ligula egestas. Suspendisse tincidunt gravida mi, in pulvinar lectus egestas non. Aenean imperdiet ex sit amet nunc sollicitudin porta. Integer justo odio, ultricies at interdum in, rhoncus vitae sem. Sed porttitor arcu quis purus aliquet hendrerit. Praesent tempor tortor at dolor dictum pulvinar. Nulla aliquet nunc non ligula scelerisque accumsan. Donec nulla justo, congue vitae massa in, faucibus hendrerit magna. Donec non egestas purus. + +Vivamus iaculis, lacus efficitur faucibus porta, dui nulla facilisis ligula, ut sodales odio nunc id sapien. Cras viverra auctor ipsum, dapibus mattis neque dictum sed. Sed convallis fermentum molestie. Nulla facilisi turpis duis. + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur vulputate, ipsum quis interdum fermentum, lorem sem fermentum eros, vitae auctor neque lacus in nisi. Suspendisse potenti. Maecenas et scelerisque elit, in tincidunt quam. Sed eu tincidunt quam. Nullam justo ex, imperdiet a imperdiet et, fermentum sit amet eros. Aenean quis tempus sem. Pellentesque accumsan magna mi, ut mollis velit sagittis id. Etiam quis ipsum orci. Fusce purus ante, accumsan a lobortis at, venenatis eu nisl. Praesent ornare sed ante placerat accumsan. Suspendisse tempus dignissim fermentum. Nunc a leo ac lacus sodales iaculis eu vitae mi. In feugiat ante at massa finibus cursus. Suspendisse posuere fringilla ornare. Mauris elementum ac quam id convallis. Vestibulum non elit quis urna volutpat aliquam a eu lacus. + +Aliquam vestibulum imperdiet neque, suscipit aliquam elit ultrices bibendum. Suspendisse ultrices pulvinar cursus. Morbi risus nisi, cursus consequat rutrum vitae, molestie sed dui. Fusce posuere, augue quis dignissim aliquam, nisi ipsum porttitor ante, quis fringilla nisl turpis ac nisi. Nulla varius enim eget lorem vehicula gravida. Donec finibus malesuada leo nec semper. Proin ac enim eros. Vivamus non tincidunt nisi, vel tristique lorem. + +Nunc consequat ex id eros dignissim, id rutrum risus laoreet. Sed euismod non erat eu ultricies. Etiam vehicula gravida lacus ut porta. Vestibulum eu eros quis nunc aliquet luctus. Cras quis semper ligula. Nullam gravida vehicula quam sed porta. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. In porta cursus vulputate. Quisque porta a nisi eget cursus. Aliquam risus leo, luctus ac magna in, efficitur cursus magna. In condimentum non mi id semper. Donec interdum ante eget commodo maximus. + +Vivamus sit amet vestibulum lectus. Fusce tincidunt mi sapien, dictum sollicitudin diam vulputate in. Integer fringilla consequat mollis. Cras aliquet consequat felis eget feugiat. Nunc tempor cursus arcu, vitae ornare nunc varius et. Vestibulum et tortor vel ante viverra porttitor. Nam at tortor ullamcorper, facilisis augue quis, tristique erat. Aenean ut euismod nibh. Quisque eu tincidunt est, nec euismod eros. + +Proin vehicula nibh non viverra egestas. Phasellus sem dolor, ultricies ac sagittis tristique, lacinia a purus. Vestibulum in ante eros. Pellentesque lacus nulla, tristique vitae interdum vel, malesuada ac diam. Aenean bibendum posuere turpis in accumsan. Ut est nulla, ullamcorper quis turpis at, viverra sagittis mauris. Sed in interdum purus. Praesent scelerisque nibh eget sem euismod, ut imperdiet mi venenatis. Vivamus pulvinar orci sed dapibus auctor. Nulla facilisi. Vestibulum tincidunt erat nec porttitor egestas. Mauris quis risus ante. Nulla facilisi. + +Aliquam ullamcorper ornare lobortis. Phasellus quis sem et ipsum mollis malesuada sed in ex. Ut aliquam ex eget metus finibus maximus. Proin suscipit mauris eu nibh lacinia, quis feugiat dui dapibus. Nam sed libero est. Aenean vulputate orci sit amet diam faucibus, eu sagittis sapien volutpat. Nam imperdiet felis turpis, at pretium odio pulvinar in. Sed vestibulum id eros nec ultricies. Sed quis aliquam tortor, vitae ullamcorper tellus. Donec egestas laoreet eros, id suscipit est rutrum nec. Sed auctor nulla eget metus aliquam, ut condimentum enim elementum. + +Aliquam suscipit non turpis sit amet bibendum. Fusce velit ligula, euismod et maximus at, luctus sed neque. Quisque pretium, nisl at ullamcorper finibus, lectus leo mattis sapien, vel euismod mauris diam ullamcorper ex. Nulla ut risus finibus, lacinia ligula at, auctor erat. Mauris consectetur sagittis ligula vel dapibus. Nullam libero libero, lobortis aliquam libero vel, venenatis ultricies leo. Duis porttitor, nibh congue fermentum posuere, erat libero pulvinar tortor, a pellentesque nunc ipsum vel sem. Nullam volutpat, eros sit amet facilisis consectetur, ipsum est vehicula massa, non vestibulum neque elit in mauris. Nunc hendrerit ipsum non enim bibendum, vitae rhoncus mi egestas. Etiam ullamcorper massa vel nisl sagittis, nec bibendum arcu malesuada. Aenean aliquet turpis justo, a consectetur arcu mollis convallis. Etiam tellus ipsum, ultricies vitae lorem et, ornare facilisis orci. Praesent fringilla justo urna, vel mollis neque pulvinar vestibulum. + +Donec non iaculis erat. Aliquam et mi sed nunc pulvinar ultricies in ut ipsum. Interdum et malesuada fames ac ante ipsum primis in faucibus. Praesent feugiat lacus ac dignissim semper. Phasellus vitae quam nisi. Morbi vel diam ultricies risus lobortis ornare. Fusce maximus et ligula quis iaculis. Sed congue ex eget felis convallis, sit amet hendrerit elit tempor. Donec vehicula blandit ante eget commodo. Vestibulum eleifend diam at feugiat euismod. Etiam magna tellus, dignissim eget fermentum vel, vestibulum vitae mauris. Nam accumsan et erat id sagittis. Donec lacinia, odio ut ornare ultricies, dolor velit accumsan tortor, non finibus erat tellus quis ligula. Nunc quis metus in leo volutpat ornare vulputate eu nisl. + +Donec quis viverra ex. Nullam id feugiat mauris, eu fringilla nulla. Vestibulum id maximus elit. Cras elementum elit sed felis lobortis, eget sagittis nisi hendrerit. Vivamus vitae elit neque. Donec vulputate lacus ut libero ultrices accumsan. Vivamus accumsan nulla orci, in dignissim est laoreet sagittis. Proin at commodo velit. Curabitur in velit felis. Aliquam erat volutpat. Sed consequat, nulla et cursus sodales, nisi lacus mattis risus, quis eleifend erat ex nec turpis. Sed suscipit ultrices lorem in hendrerit. + +Morbi vitae lacus nec libero ornare tempus eu et diam. Suspendisse magna ipsum, fermentum vel odio quis, molestie aliquam urna. Fusce mollis turpis a eros accumsan porttitor. Pellentesque rhoncus dolor sit amet magna rutrum, et dapibus justo tempor. Sed purus nisi, maximus vitae fringilla eu, molestie nec urna. Fusce malesuada finibus pretium. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec sed aliquet eros. Pellentesque luctus diam ante, eget euismod nisl aliquet eu. Sed accumsan elit purus, tempor varius ligula tempus nec. Curabitur ornare leo suscipit suscipit fermentum. Morbi eget nulla est. Maecenas faucibus interdum tristique. + +Etiam ut elit eros. Nulla pharetra suscipit molestie. Nulla facilisis bibendum nisl non molestie. Curabitur turpis lectus, facilisis vel diam non, vulputate ultrices mauris. Aenean placerat aliquam convallis. Suspendisse sed scelerisque tellus. Vivamus lacinia neque eget risus cursus suscipit. Proin consequat dolor vel neque tempor, eu aliquam sem scelerisque. Duis non eros a purus malesuada pharetra non et nulla. Suspendisse potenti. Mauris libero eros, finibus vel nulla id, sagittis dapibus ante. Proin iaculis sed nunc et cursus. + +Quisque accumsan lorem sit amet lorem aliquet euismod. Curabitur fermentum rutrum posuere. Etiam ultricies, sem id pellentesque suscipit, urna magna lacinia eros, quis efficitur risus nisl at lacus. Nulla quis lacus tortor. Mauris placerat ex in dolor tincidunt, vel aliquet nisi pretium. Cras iaculis risus vitae pellentesque aliquet. Quisque a enim imperdiet, ullamcorper arcu vitae, rutrum risus. Nullam consectetur libero at felis fringilla, nec congue nibh dignissim. Nam et lobortis felis, eu pellentesque ligula. Aenean facilisis, ligula non imperdiet maximus, massa orci gravida sapien, at sagittis lacus nisl in lacus. Nulla quis mauris luctus, scelerisque felis consequat, tempus risus. Fusce auctor nisl non nulla luctus molestie. Maecenas sapien nisl, auctor non dolor et, iaculis scelerisque lorem. Suspendisse egestas enim aliquet, accumsan mauris nec, posuere quam. Nulla iaculis dui dui, sit amet vestibulum erat ultricies ac. + +Cras eget dolor erat. Proin at nisl ut leo consectetur ultricies vel ut arcu. Nulla in felis malesuada, ullamcorper tortor et, convallis massa. Nunc urna justo, ornare in nibh vitae, hendrerit condimentum libero. Etiam vitae libero in purus venenatis fringilla. Nullam velit nulla, consequat ut turpis non, egestas hendrerit nibh. Duis tortor turpis, interdum non ante ac, cursus accumsan lectus. Cras pharetra bibendum augue quis dictum. Sed euismod vestibulum justo. Proin porta lobortis purus. Duis venenatis diam tortor, sit amet condimentum eros rhoncus a. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nunc at magna nec diam lobortis efficitur sit amet ut lacus. Nulla quis orci tortor. Pellentesque tempus velit a odio finibus porta. + +Proin feugiat mauris a tellus scelerisque convallis. Maecenas libero magna, blandit nec ultrices id, congue vel mi. Aliquam lacinia, quam vel condimentum convallis, tortor turpis aliquam odio, sed blandit libero lacus et eros. In eleifend iaculis magna ac finibus. Praesent auctor facilisis tellus in congue. Sed molestie lobortis dictum. Nam quis dignissim augue, vel euismod lorem. Curabitur posuere dapibus luctus. Donec ultricies dictum lectus, quis blandit arcu commodo ac. Aenean tincidunt ligula in nunc imperdiet dignissim. Curabitur egestas sollicitudin sapien ut semper. Aenean nec dignissim lacus. + +Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec aliquam dictum vehicula. Donec tortor est, volutpat non nisi nec, varius gravida ex. Nunc vel tristique nunc, vitae mattis nisi. Nunc nec luctus ex, vitae tincidunt lectus. In hac habitasse platea dictumst. Curabitur lobortis ex eget tincidunt tempor. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Ut a vehicula mi. + +Fusce eu libero finibus, interdum nulla a, placerat neque. Cras bibendum tempor libero nec feugiat. Cras ut sodales eros. Proin viverra, massa sit amet viverra egestas, neque nisl porta ex, sit amet hendrerit libero ligula vel urna. Mauris suscipit lacus id justo rhoncus suscipit. Etiam vel libero tellus. Maecenas non diam molestie, condimentum tellus a, bibendum enim. Mauris aliquet imperdiet tellus, eget sagittis dolor. Sed blandit in neque et luctus. Cras elementum sagittis nunc, vel mollis lorem euismod et. Donec posuere at lacus eget suscipit. + +Nulla nunc mi, pretium non massa vel, tempor semper magna. Nunc a leo pulvinar, tincidunt nunc at, dignissim mi. Aliquam erat volutpat. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Ut viverra nulla a nisl finibus, at hendrerit ligula ullamcorper. Donec a lorem semper, tempor magna et, lobortis libero. Mauris id sapien leo. Donec dignissim, quam vitae porttitor dignissim, quam justo mattis dui, vel consequat odio elit quis orci. Etiam nec pretium neque, sit amet pretium orci. Duis ac tortor venenatis, feugiat purus non, feugiat nunc. Proin scelerisque nisl in turpis aliquam vulputate. + +Praesent sed est semper, fringilla lorem vitae, tincidunt nibh. Cras eros metus, auctor at mauris sit amet, sodales semper orci. Nunc a ornare ex. Curabitur bibendum arcu congue urna vulputate egestas. Vestibulum finibus id risus et accumsan. Aenean ut volutpat tellus. Aenean tincidunt malesuada urna sit amet vestibulum. Mauris vel tellus dictum, varius lacus quis, dictum arcu. + +Aenean quis metus eu erat feugiat cursus vel at ligula. Proin dapibus sodales urna, id euismod lectus tempus id. Pellentesque ex ligula, convallis et erat vel, vulputate condimentum nisl. Pellentesque pharetra nulla quis massa eleifend hendrerit. Praesent sed massa ipsum. Maecenas vehicula dolor massa, id sodales urna faucibus et. Mauris ac quam non massa tincidunt feugiat et at lacus. Fusce libero massa, vulputate vel scelerisque non, mollis in leo. Ut sit amet ultricies odio. Suspendisse in sapien viverra, facilisis purus ut, pretium libero. + +Vivamus tristique pharetra molestie. Nam a volutpat purus. Praesent consequat gravida nisi, ac blandit nisi suscipit ut. Quisque posuere, ligula a ultrices laoreet, ligula nunc vulputate libero, ut rutrum erat odio tincidunt justo. Sed vitae leo at leo fringilla bibendum. Vestibulum ut augue nec dolor auctor accumsan. Praesent laoreet id eros pulvinar commodo. Suspendisse potenti. Ut pharetra, mauris vitae blandit fringilla, odio ante tincidunt lorem, sit amet tempor metus diam ut turpis. + +Praesent quis egestas arcu. Nullam at porta arcu. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Morbi vulputate ligula malesuada ligula luctus, vulputate tempus erat bibendum. Nunc ullamcorper non lectus at euismod. Etiam nibh felis, tincidunt a metus vel, pellentesque rhoncus neque. Etiam at diam in erat luctus interdum. Nunc vel ipsum pulvinar, sollicitudin lacus ac, tempus urna. Etiam vel lacinia sapien. Pellentesque sagittis velit vel mi efficitur iaculis. Integer euismod sit amet urna in sagittis. Cras eleifend ut nibh in facilisis. Donec et lacus vitae nunc placerat sodales. Nulla sed hendrerit ligula, at dapibus sapien. + +Praesent at iaculis ex. Curabitur est purus, cursus a faucibus quis, dictum id velit. Donec dignissim fringilla viverra. Nunc mauris felis, laoreet sit amet sagittis at, vestibulum in libero. Maecenas quis orci turpis. Quisque ut nibh vitae magna mollis consequat id at mauris. Aliquam eu odio eget nulla bibendum sodales. Quisque vel orci eleifend nisi pretium lacinia. Suspendisse eget risus eget mi volutpat molestie eget quis lacus. Duis nisi libero, tincidunt nec nulla id, faucibus cursus felis. + +Donec tempor eget risus pellentesque molestie. Phasellus porta neque vel arcu egestas, nec blandit velit fringilla. Nullam porta faucibus justo vitae laoreet. Pellentesque viverra id nunc eu varius. Nulla pulvinar lobortis iaculis. Etiam vestibulum odio nec velit tristique, a tristique nisi mattis. In sed fringilla orci, vitae efficitur odio. Quisque dui odio, ornare eget velit at, lacinia consequat libero. Quisque lectus nulla, aliquet eu leo in, porta rutrum diam. Donec nec mattis neque. Nam rutrum, odio ac eleifend bibendum, dolor arcu rutrum neque, eget porta elit tellus a lacus. Sed massa metus, sollicitudin et sapien eu, finibus tempus orci. Proin et sapien sit amet erat molestie interdum. In quis rutrum velit, faucibus ultrices tellus. + +Sed sagittis sed justo eget tincidunt. Maecenas ut leo sagittis, feugiat magna et, viverra velit. Maecenas ex arcu, feugiat at consequat vitae, auctor eu massa. Integer egestas, enim vitae maximus convallis, est lectus pretium mauris, ac posuere lectus nisl quis quam. Aliquam tempus laoreet mi, vitae dapibus dolor varius dapibus. Suspendisse potenti. Donec sit amet purus nec libero dapibus tristique. Pellentesque viverra bibendum ligula. Donec sed felis et ex lobortis laoreet. Phasellus a fringilla libero, vitae malesuada nulla. Pellentesque blandit mattis lacus, et blandit tortor laoreet consequat. Suspendisse libero nunc, viverra sed fermentum in, accumsan egestas arcu. Proin in placerat elit. Sed interdum imperdiet malesuada. Suspendisse aliquet quis mauris eget sollicitudin. + +Vivamus accumsan tellus non erat volutpat, quis dictum dolor feugiat. Praesent rutrum nunc ac est mollis cursus. Fusce semper volutpat dui ut egestas. Curabitur sit amet posuere massa. Cras tincidunt nulla et mi mollis imperdiet. Suspendisse scelerisque ex id sodales vulputate. In nunc augue, pharetra in placerat eu, mattis id tellus. Vivamus cursus efficitur vehicula. Nulla aliquet vehicula aliquet. + +Sed cursus tellus sed porta pulvinar. Sed vitae nisi neque. Nullam aliquet, lorem et efficitur scelerisque, arcu diam aliquam felis, sed pulvinar lorem odio et turpis. Praesent convallis pulvinar turpis eu iaculis. Aliquam nec gravida mi. Curabitur eu nibh tempor, blandit justo in, ultrices felis. Fusce placerat metus non mi sagittis rutrum. Morbi sed dui fringilla, sagittis mauris eget, imperdiet nunc. Phasellus hendrerit sem elit, id hendrerit libero auctor sit amet. Integer sodales elit sit amet consequat cursus. + +Nam semper est eget nunc mollis, in pellentesque lectus fringilla. In finibus vel diam id semper. Nunc mattis quis erat eu consectetur. In hac habitasse platea dictumst. Nullam et ipsum vestibulum ex pulvinar ultricies sit amet id velit. Aenean suscipit mi tortor, a lobortis magna viverra non. Nulla condimentum aliquet ante et ullamcorper. Pellentesque porttitor arcu a posuere tempus. Aenean lacus quam, imperdiet eu justo vitae, pretium efficitur ex. Duis id purus id magna rhoncus ultrices id eu risus. Nunc dignissim et libero id dictum. + +Quisque a tincidunt neque. Phasellus commodo mi sit amet tempor fringilla. Ut rhoncus, neque non porttitor elementum, libero nulla egestas augue, sed fringilla sapien felis ac velit. Phasellus viverra rhoncus mollis. Nam ullamcorper leo vel erat laoreet luctus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Vivamus semper a metus a cursus. Nulla sed orci egestas, efficitur purus ac, malesuada tellus. Aenean rutrum velit at tellus fermentum mollis. Aliquam eleifend euismod metus. + +In hac habitasse platea dictumst. Vestibulum volutpat neque vitae porttitor laoreet. Nam at tellus consequat, sodales quam in, pulvinar arcu. Maecenas varius convallis diam, ac lobortis tellus pellentesque quis. Maecenas eget augue massa. Nullam volutpat nibh ac justo rhoncus, ut iaculis tellus rutrum. Fusce efficitur efficitur libero quis condimentum. Curabitur congue neque non tincidunt tristique. Fusce eget tempor ex, at pellentesque odio. Praesent luctus dictum vestibulum. Etiam non orci nunc. Vivamus vitae laoreet purus, a lobortis velit. Curabitur tincidunt purus ac lectus elementum pellentesque. Quisque sed tincidunt est. + +Sed vel ultrices massa, vitae ultricies justo. Cras finibus mauris nec lacus tempus dignissim. Cras faucibus maximus velit, eget faucibus orci luctus vehicula. Nulla massa nunc, porta ac consequat eget, rhoncus non tellus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Fusce sed maximus metus, vel imperdiet ipsum. Ut scelerisque lectus at blandit porttitor. Ut vulputate nunc pharetra, aliquet sapien ac, sollicitudin sapien. Aenean eget ante lorem. Nam accumsan venenatis tellus id dignissim. + +Curabitur fringilla, magna non maximus dapibus, nulla sapien vestibulum lectus, sit amet semper dolor neque vitae nisl. Nunc ultrices vehicula augue sed iaculis. Maecenas nec diam mollis, suscipit orci et, vestibulum ante. Pellentesque eu nisl tortor. Nunc eleifend, lacus quis volutpat volutpat, nisi mi molestie sem, quis mollis ipsum libero a tellus. Ut viverra dolor mattis convallis interdum. Sed tempus nisl at nunc scelerisque aliquet. Quisque tempor tempor lorem id feugiat. Nullam blandit lectus velit, vitae porta lacus tincidunt a. Vivamus sit amet arcu ultrices, tincidunt mi quis, viverra quam. Aenean fringilla libero elementum lorem semper, quis pulvinar eros gravida. Nullam sodales blandit mauris, sed fermentum velit fermentum sit amet. Donec malesuada mauris in augue sodales vulputate. Vestibulum gravida turpis id elit rhoncus dignissim. Integer non congue lorem, eu viverra orci. + +Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec at dolor magna. Aliquam consectetur erat augue, id iaculis velit pharetra ac. Integer rutrum venenatis dignissim. Integer non sodales elit. Curabitur ut magna ut nibh feugiat aliquam ac ut risus. Morbi nibh quam, aliquam id placerat nec, vestibulum eget velit. Suspendisse at dignissim quam. Vivamus aliquet sem sed nisl volutpat, ut cursus orci ultrices. Aliquam ultrices lacinia enim, vitae aliquet neque. + +Quisque scelerisque finibus diam in mattis. Cras cursus auctor velit. Aliquam sem leo, fermentum et maximus et, molestie a libero. Aenean justo elit, rutrum a ornare id, egestas eget enim. Aenean auctor tristique erat. Curabitur condimentum libero lacus, nec consequat orci vestibulum sed. Fusce elit ligula, blandit vitae sapien vitae, dictum ultrices risus. Nam laoreet suscipit sapien, at interdum velit faucibus sit amet. Duis quis metus egestas lectus elementum posuere non nec libero. Aliquam a dolor bibendum, facilisis nunc a, maximus diam. Vestibulum suscipit tristique magna, non dignissim turpis sodales sed. Nunc ornare, velit ac facilisis fringilla, dolor mi consectetur lorem, vitae finibus erat justo suscipit urna. Maecenas sit amet eros erat. Nunc non arcu ornare, suscipit lorem eget, sodales mauris. Aliquam tincidunt, quam nec mollis lacinia, nisi orci fermentum libero, consequat eleifend lectus quam et sapien. Vestibulum a quam urna. + +Cras arcu leo, euismod ac ullamcorper at, faucibus sed massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus porttitor velit in enim interdum, non commodo metus ornare. Morbi vel lorem quis nisl luctus tristique quis vitae nisl. Suspendisse condimentum tortor enim, nec eleifend ipsum euismod et. Sed gravida quam ut tristique lacinia. Mauris eu interdum ipsum, ac ultrices odio. Nullam auctor tellus a risus porttitor vehicula. Nulla blandit euismod dictum. In pharetra, enim iaculis pulvinar interdum, dui nunc placerat nunc, sit amet pretium lectus nulla vitae quam. Phasellus quis enim sollicitudin, varius nulla id, ornare purus. Donec quam lacus, vestibulum quis nunc ac, mollis dictum nisi. Cras ut mollis elit. Maecenas ultrices ligula at risus faucibus scelerisque. Etiam vitae porttitor purus. Curabitur blandit lectus urna, ut hendrerit tortor feugiat ut. + +Phasellus fringilla, sapien pellentesque commodo pharetra, ante libero aliquam tellus, ut consectetur augue libero a sapien. Maecenas blandit luctus nisl eget aliquet. Maecenas vitae porta dolor, faucibus laoreet sapien. Suspendisse lobortis, ipsum sed vehicula aliquam, elit purus scelerisque dui, rutrum consectetur diam odio et lorem. In nec lacinia metus. Donec viverra libero est, vel bibendum erat condimentum quis. Donec feugiat purus leo. In laoreet vitae felis a porttitor. Mauris ullamcorper, lacus id condimentum suscipit, neque magna pellentesque arcu, eget cursus neque tellus id metus. Curabitur volutpat ac orci vel ultricies. + +Sed ut finibus erat. Sed diam purus, varius non tincidunt quis, ultrices sit amet ipsum. Donec et egestas nulla. Suspendisse placerat nisi at dui laoreet iaculis. Aliquam aliquet leo at augue faucibus molestie. Nullam lacus augue, hendrerit sed nisi eu, faucibus porta est. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam ut leo aliquet sem fermentum rutrum quis ac justo. Integer placerat aliquam nisl ut sagittis. Proin erat orci, lobortis et sem eget, eleifend fringilla augue. Mauris varius laoreet arcu, sed tincidunt felis. Pellentesque venenatis lorem odio, id pulvinar velit molestie feugiat. Donec mattis lacus sed eleifend pulvinar. + +Sed condimentum ex in tincidunt hendrerit. Etiam eget risus lacinia, euismod nibh eu, pellentesque quam. Proin elit eros, convallis id mauris ac, bibendum ultrices lectus. Morbi venenatis, purus id fermentum consequat, nunc libero tincidunt ligula, non dictum ligula orci nec quam. Nulla nec ultrices lorem. Aenean maximus augue vel dictum pharetra. Etiam turpis urna, pellentesque quis malesuada eu, molestie faucibus felis. + +Vestibulum pharetra augue ut quam blandit congue in nec risus. Proin eu nibh eu dui eleifend porta vitae id lectus. Proin lacus nibh, lobortis sed ligula vitae, interdum lobortis erat. Suspendisse potenti. In sollicitudin quis sapien ut aliquet. Mauris ac nulla arcu. Fusce tristique justo quis lectus mollis, eu volutpat lectus finibus. Vivamus venenatis facilisis ex ut vestibulum. + +Etiam varius lobortis purus, in hendrerit elit tristique at. In tempus, augue vestibulum fermentum gravida, ligula tellus vulputate arcu, eu molestie ex sapien at purus. Vestibulum nec egestas metus. Duis pulvinar quam nec consequat interdum. Aenean non dapibus lacus. Aliquam sit amet aliquet nulla. Sed venenatis volutpat purus nec convallis. Phasellus aliquet semper sodales. Cras risus sapien, condimentum auctor urna a, pulvinar ornare nisl. Sed tincidunt felis elit, ut elementum est bibendum ac. Morbi interdum justo vel dui faucibus condimentum. + +Sed convallis eu sem at tincidunt. Nullam at auctor est, et ullamcorper ipsum. Pellentesque eget ante ante. Interdum et malesuada fames ac ante ipsum primis in faucibus. Integer euismod, sapien sed dapibus ornare, nibh enim maximus lacus, lacinia placerat urna quam quis felis. Morbi accumsan id nisl ut condimentum. Donec bibendum nisi est, sed volutpat lorem rhoncus in. Vestibulum ac lacinia nunc, eget volutpat magna. Integer aliquam pharetra ipsum, id placerat nunc volutpat quis. Etiam urna diam, rhoncus sit amet varius vel, euismod vel sem. Nullam vel molestie urna. Vivamus ornare erat at venenatis euismod. Suspendisse potenti. Fusce diam justo, tincidunt vel sem at, commodo faucibus nisl. Duis gravida efficitur diam, vel sagittis erat pulvinar ut. + +Quisque vel pharetra felis. Duis efficitur tortor dolor, vitae porttitor erat fermentum sed. Sed eu mi purus. Etiam dignissim tortor eu tempus molestie. Aenean pretium erat enim, in hendrerit ante hendrerit at. Sed ut risus vel nunc venenatis ultricies quis in lacus. Pellentesque vitae purus euismod, placerat risus non, ullamcorper augue. Quisque varius quam ligula, nec aliquet ex faucibus vitae. Quisque rhoncus sit amet leo tincidunt mattis. Cras id mauris eget purus pretium gravida sit amet eu augue. Aliquam dapibus odio augue, id lacinia velit pulvinar eu. + +Mauris fringilla, tellus nec pharetra iaculis, neque nisi ultrices massa, et tincidunt sem dui sed mi. Curabitur erat lorem, venenatis quis tempus lacinia, tempus sit amet nunc. Aliquam at neque ac metus commodo dictum quis vitae justo. Phasellus eget lacus tempus, blandit lorem vel, rutrum est. Aenean pharetra sem ut augue lobortis dignissim. Sed rhoncus at nulla id ultrices. Cras id condimentum felis. In suscipit luctus vulputate. Donec tincidunt lacus nec enim tincidunt sollicitudin ut quis enim. Nam at libero urna. Praesent sit amet massa vitae massa ullamcorper vehicula. + +Nullam bibendum augue ut turpis condimentum bibendum. Proin sit amet urna hendrerit, sodales tortor a, lobortis lectus. Integer sagittis velit turpis, et tincidunt nisi commodo eget. Duis tincidunt elit finibus accumsan cursus. Aenean dignissim scelerisque felis vel lacinia. Nunc lacinia maximus luctus. In hac habitasse platea dictumst. Vestibulum eget urna et enim tempor tempor. Nam feugiat, felis vel vestibulum tempus, orci justo viverra diam, id dapibus lorem justo in ligula. + +Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In ac pellentesque sem. Vestibulum lacinia magna dui, eu lacinia augue placerat et. Maecenas pulvinar congue est. Pellentesque commodo dui non pulvinar scelerisque. Etiam interdum est posuere sem bibendum, ac commodo magna dictum. Cras ipsum turpis, rhoncus nec posuere vitae, laoreet a arcu. Integer ac massa sit amet enim placerat lacinia sed ultrices arcu. Suspendisse sem nibh, luctus sit amet volutpat in, pellentesque eu metus. Ut gravida neque eget mi accumsan tempus. Nam sit amet aliquet nibh. + +Pellentesque a purus cursus nulla hendrerit congue quis et odio. Aenean hendrerit, leo ullamcorper sagittis hendrerit, erat dui molestie quam, sed condimentum lacus risus sed tellus. Morbi a dapibus lectus, ut feugiat ex. Phasellus pretium quam et sapien mollis, vel iaculis dui dignissim. Sed ullamcorper est turpis, a viverra lorem consectetur in. Aenean aliquet nibh non cursus rutrum. Suspendisse at tristique urna, id lobortis urna. In hac habitasse platea dictumst. Phasellus libero velit, rutrum sed tellus nec, dapibus tincidunt ligula. Quisque vel dui venenatis, consequat nisl ut, lacinia ipsum. Phasellus vitae magna pellentesque, lobortis est id, faucibus quam. Nam eleifend faucibus dui vel pellentesque. + +Etiam ut est non lacus tincidunt interdum. Maecenas sed massa urna. Quisque ut nibh tortor. Pellentesque felis ipsum, tempor finibus ipsum et, euismod pretium metus. Donec sit amet est ipsum. Quisque rhoncus justo non finibus elementum. Nulla nec lectus ac tortor placerat fringilla. Phasellus ac ultrices nunc, eu efficitur nisl. Nulla rhoncus nunc vitae ante dictum tincidunt. Nunc ultrices, massa sit amet malesuada dignissim, lectus lacus consequat sapien, non eleifend metus sem in eros. Phasellus mauris ante, dictum sit amet suscipit ac, rhoncus eget nisi. Phasellus at orci mollis, imperdiet neque eget, faucibus nulla. In at purus massa. Pellentesque quis rutrum lectus. + +Integer eu faucibus turpis, sit amet mollis massa. Vestibulum id nulla commodo, rutrum ipsum sed, semper ante. Phasellus condimentum orci nec nibh convallis, ac maximus orci ullamcorper. Maecenas vitae sollicitudin mi. Integer et finibus lectus, et condimentum ligula. Donec elementum tristique quam vitae dapibus. Morbi euismod ipsum in tristique ullamcorper. + +Duis fermentum non enim eu auctor. Quisque lacinia nibh vehicula nibh posuere, eu volutpat turpis facilisis. Ut ac faucibus nulla. Sed eleifend quis ex et pellentesque. Vestibulum sollicitudin in libero id fringilla. Phasellus dignissim purus consequat, condimentum dui sit amet, condimentum ante. Pellentesque ac consectetur massa, quis sagittis est. Nulla maximus tristique risus accumsan convallis. Curabitur imperdiet ac lacus a ultrices. Nulla facilisi. Sed quis quam quis lectus placerat lobortis vel sed turpis. In mollis dui id neque iaculis, ut aliquet tellus malesuada. Proin at luctus odio, vel blandit sapien. Praesent dignissim tortor vehicula libero fringilla, nec ultrices erat suscipit. Maecenas scelerisque purus in dapibus fermentum. + +Curabitur magna odio, mattis in tortor ut, porttitor congue est. Vestibulum mollis lacinia elementum. Fusce maximus erat vitae nunc rutrum lobortis. Integer ligula eros, auctor vel elit non, posuere luctus lacus. Maecenas quis auctor massa. Ut ipsum lacus, efficitur posuere euismod et, hendrerit efficitur est. Phasellus fringilla, quam id tincidunt pretium, nunc dui sollicitudin orci, eu dignissim nisi metus ut magna. Integer lobortis interdum dolor, non bibendum purus posuere et. Donec non lectus aliquet, pretium dolor eu, cursus massa. Sed ut dui sapien. In sed vestibulum massa. Pellentesque blandit, dui non sodales vehicula, orci metus mollis nunc, non pharetra ex tellus ac est. Mauris sagittis metus et fermentum pretium. Nulla facilisi. Quisque quis ante ut nulla placerat mattis ut quis nisi. + +Sed quis nulla ligula. Quisque dignissim ligula urna, sed aliquam purus semper at. Suspendisse potenti. Nunc massa lectus, pharetra vehicula arcu bibendum, imperdiet sodales ipsum. Nam ac sapien diam. Mauris iaculis fringilla mattis. Pellentesque tempus eros sit amet justo volutpat mollis. Phasellus ac turpis ipsum. Morbi vel ante elit. Aenean posuere quam consequat velit varius suscipit. Donec tempor quam ut nibh cursus efficitur. + +Morbi molestie dolor nec sem egestas suscipit. Etiam placerat pharetra lectus, et ullamcorper risus tristique in. Sed faucibus ullamcorper lectus eget fringilla. Maecenas malesuada hendrerit congue. Sed eget neque a erat placerat tincidunt. Aliquam vitae dignissim turpis. Fusce at placerat magna, a laoreet lectus. Maecenas a purus nec diam gravida fringilla. Nam malesuada euismod ante non vehicula. In faucibus bibendum leo, faucibus posuere nisl pretium quis. Fusce finibus bibendum finibus. Vestibulum eu justo maximus, hendrerit diam nec, dignissim sapien. Aenean dolor lacus, malesuada quis vestibulum ac, venenatis ac ipsum. Cras a est id nunc finibus facilisis. Cras lacinia neque et interdum vehicula. Suspendisse vulputate tellus elit, eget tempor dui finibus vel. + +Cras sed pretium odio. Proin hendrerit elementum felis in tincidunt. Nam sed turpis vel justo molestie accumsan condimentum eu nunc. Praesent lobortis euismod rhoncus. Nulla vitae euismod nibh, quis mattis mi. Fusce ultrices placerat porttitor. Duis sem ipsum, pellentesque sit amet odio a, molestie vulputate mauris. + +Duis blandit mollis ligula, sit amet mattis ligula finibus sit amet. Nunc a leo molestie, placerat diam et, vestibulum leo. Suspendisse facilisis neque purus, nec pellentesque ligula fermentum nec. Aenean malesuada mauris lorem, eu blandit arcu pulvinar quis. Duis laoreet urna lacus, non maximus arcu rutrum ultricies. Nulla augue dolor, suscipit eu mollis eu, aliquam condimentum diam. Ut semper orci luctus, pharetra turpis at, euismod mi. Nulla leo diam, finibus sit amet purus sed, maximus dictum lorem. Integer eu mi id turpis laoreet rhoncus. + +Integer a mauris tincidunt, finibus orci ut, pretium mauris. Nulla molestie nunc mi, id finibus lorem elementum sed. Proin quis laoreet ante. Integer nulla augue, commodo id molestie quis, rutrum ut turpis. Suspendisse et tortor turpis. Sed ut pharetra massa. Pellentesque elementum blandit sem, ut elementum tellus egestas a. Fusce eu purus nibh. + +Cras dignissim ligula scelerisque magna faucibus ullamcorper. Proin at condimentum risus, auctor malesuada quam. Nullam interdum interdum egestas. Nulla aliquam nisi vitae felis mollis dictum. Suspendisse dapibus consectetur tortor. Ut ut nisi non sem bibendum tincidunt. Vivamus suscipit leo quis gravida dignissim. + +Aliquam interdum, leo id vehicula mollis, eros eros rhoncus diam, non mollis ligula mi eu mauris. Sed ultrices vel velit sollicitudin tincidunt. Nunc auctor metus at ligula gravida elementum. Praesent interdum eu elit et mollis. Duis egestas quam sit amet velit dignissim consequat. Aliquam ac turpis nec nunc convallis sagittis. Fusce blandit, erat ac fringilla consectetur, dolor eros sodales leo, vel aliquet risus nisl et diam. Aliquam luctus felis vitae est eleifend euismod facilisis et lacus. Sed leo tellus, auctor eu arcu in, volutpat sagittis nisl. Pellentesque nisl ligula, placerat vel ullamcorper at, vulputate ac odio. Morbi ac faucibus orci, et tempus nulla. Proin rhoncus rutrum dolor, in venenatis mauris. Suspendisse a fermentum augue, non semper mi. Nunc eget pretium neque. Phasellus augue erat, feugiat ac aliquam congue, rutrum non sapien. Pellentesque ac diam gravida, consectetur felis at, ornare neque. + +Nullam interdum mattis sapien quis porttitor. Interdum et malesuada fames ac ante ipsum primis in faucibus. Phasellus aliquet rutrum ipsum id euismod. Maecenas consectetur massa et mi porta viverra. Nunc quam nibh, dignissim vitae maximus et, ullamcorper nec lorem. Nunc vitae justo dapibus, luctus lacus vitae, pretium elit. Maecenas et efficitur leo. Curabitur mauris lectus, placerat quis vehicula vitae, auctor ut urna. Quisque rhoncus pharetra luctus. In hac habitasse platea dictumst. Integer sit amet metus nec eros malesuada aliquam. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Morbi hendrerit mi ac leo aliquam, sit amet ultricies libero commodo. Mauris dapibus purus metus, sit amet viverra nibh imperdiet et. Nullam porta nulla tellus, quis vehicula diam imperdiet non. Vivamus enim massa, bibendum in fermentum in, ultrices at ex. + +Suspendisse fermentum id nibh eget accumsan. Duis dapibus bibendum erat ut sollicitudin. Aliquam nec felis risus. Pellentesque rhoncus ligula id sem maximus mollis sed nec massa. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus ipsum ipsum, sodales sed enim id, convallis faucibus eros. Donec ultricies dictum tincidunt. Cras vitae nibh arcu. Pellentesque cursus, sapien nec consequat fermentum, ipsum ante suscipit dui, imperdiet hendrerit est nisl eu massa. Quisque vitae sem ligula. Aenean iaculis metus ut mauris interdum laoreet. Vivamus sed gravida dolor. + +Morbi nulla metus, porttitor sed eros sit amet, efficitur efficitur est. In vel nisl urna. Ut aliquet tellus at congue convallis. Phasellus imperdiet lobortis sollicitudin. Integer sodales, sem eu ultricies pharetra, erat erat porttitor odio, eget dapibus libero ipsum eget velit. Phasellus gravida nulla nisl, eu pharetra mi auctor vel. Sed blandit pharetra velit, ut egestas libero placerat non. Aliquam a interdum quam. Proin at tortor nec dui sollicitudin tempus sed vestibulum elit. Nunc non sollicitudin velit. + +Aenean consequat diam velit, sed rutrum tortor faucibus dictum. Quisque at semper augue. Duis ut est eget mi ornare bibendum id et ligula. Phasellus consequat tortor non leo pulvinar posuere. Proin vestibulum eleifend felis, in hendrerit tortor sollicitudin eu. Phasellus hendrerit, lacus vel laoreet interdum, dui tortor consequat justo, commodo ultricies arcu felis vitae enim. Vivamus eu sapien at leo suscipit rutrum eu at justo. Aenean et dolor a libero ullamcorper posuere. Integer laoreet placerat nisi in vulputate. Mauris laoreet eget risus sed cursus. Donec scelerisque neque a libero eleifend hendrerit. Nulla varius condimentum nunc sit amet fermentum. Aliquam lorem ex, varius nec mollis ut, ultrices in neque. Morbi sit amet porta leo. Integer iaculis fermentum lacus in vestibulum. + +Ut gravida, tellus ut maximus ultrices, erat est venenatis nisl, vitae pretium massa ex ac magna. Sed non purus eget ligula aliquet volutpat non quis arcu. Nam aliquam tincidunt risus, sit amet fringilla sapien vulputate ut. Mauris luctus suscipit pellentesque. Nunc porttitor dapibus ex quis tempus. Ut ullamcorper metus a eros vulputate, vitae viverra lectus convallis. Mauris semper imperdiet augue quis tincidunt. Integer porta pretium magna, sed cursus sem scelerisque sollicitudin. Nam efficitur, nibh pretium eleifend vestibulum, purus diam posuere sem, in egestas mauris augue sit amet urna. + +Vestibulum tincidunt euismod massa in congue. Duis interdum metus non laoreet fringilla. Donec at ligula congue, tincidunt nunc non, scelerisque nunc. Donec bibendum magna non est scelerisque feugiat at nec neque. Ut orci tortor, tempus eget massa non, dignissim faucibus dolor. Nam odio risus, accumsan pretium neque eget, accumsan dignissim dui. In ut neque auctor, scelerisque tellus sed, ullamcorper nisi. Suspendisse varius cursus quam at hendrerit. Vivamus elit libero, sagittis vitae sem ac, vulputate iaculis ligula. + +Sed lobortis laoreet purus sit amet rutrum. Pellentesque feugiat non leo vel lacinia. Quisque feugiat nisl a orci bibendum vestibulum. In et sollicitudin urna. Morbi a arcu ac metus faucibus tempus. Nam eu imperdiet sapien, suscipit mattis tortor. Aenean blandit ipsum nisi, a eleifend ligula euismod at. Integer tincidunt pharetra felis, mollis placerat mauris hendrerit at. Curabitur convallis, est sit amet luctus volutpat, massa lacus cursus augue, sed eleifend magna quam et risus. Aliquam lobortis tincidunt metus vitae porttitor. Suspendisse potenti. Aenean ullamcorper, neque id commodo luctus, nulla nunc lobortis quam, id dapibus neque dui nec mauris. Etiam quis lorem quis elit commodo ornare. Ut pharetra purus ultricies enim ultrices efficitur. Proin vehicula tincidunt molestie. Mauris et placerat sem. + +Aliquam erat volutpat. Suspendisse velit turpis, posuere ac lacus eu, lacinia laoreet velit. Sed interdum felis neque, id blandit sem malesuada sit amet. Ut sagittis justo erat, efficitur semper orci tempor sed. Donec enim massa, posuere varius lectus egestas, pellentesque posuere mi. Cras tincidunt ut libero sed mattis. Suspendisse quis magna et tellus posuere interdum vel at purus. Pellentesque fringilla tristique neque, id aliquet tellus ultricies non. Duis ut tellus vel odio lobortis vulputate. + +Integer at magna ac erat convallis vestibulum. Sed lobortis porttitor mauris. Fusce varius lorem et volutpat pulvinar. Aenean ac vulputate lectus, vitae consequat velit. Suspendisse ex dui, varius ut risus ut, dictum scelerisque sem. Vivamus urna orci, volutpat ut convallis ac, venenatis vitae urna. In hac habitasse platea dictumst. Etiam eu purus arcu. Aenean vulputate leo urna, vel tristique dui sagittis euismod. Suspendisse non tellus efficitur ante rhoncus volutpat at et sapien. + +Sed dapibus accumsan porttitor. Phasellus facilisis lectus finibus ligula dignissim, id pulvinar lectus feugiat. Nullam egestas commodo nisi posuere aliquet. Morbi sit amet tortor sagittis, rutrum dui nec, dapibus sapien. Sed posuere tortor tortor, interdum auctor magna varius vitae. Vestibulum id sagittis augue. Curabitur fermentum arcu sem, eu condimentum quam rutrum non. Phasellus rutrum nibh quis lectus rhoncus pretium. Curabitur dictum interdum elit. Vestibulum maximus sodales imperdiet. Mauris auctor nec purus sed venenatis. In in urna purus. + +Duis placerat molestie suscipit. Morbi a elit id purus efficitur consequat. Nunc ac commodo turpis. Etiam sit amet lacus a ipsum tempus venenatis sed vel nibh. Duis elementum aliquam mi sed tristique. Morbi ligula tortor, semper ac est vel, lobortis maximus erat. Curabitur ipsum felis, laoreet vel condimentum eget, ullamcorper sit amet mauris. Nulla facilisi. Nam at purus sed mi egestas placerat vitae vel magna. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Suspendisse at dignissim diam. Phasellus consectetur eget neque vel viverra. Donec sollicitudin mattis dolor vel malesuada. Vivamus vehicula leo neque, vitae fermentum leo posuere et. Praesent dui est, finibus sit amet tristique quis, pharetra vel nibh. + +Duis nulla leo, accumsan eu odio eget, sagittis semper orci. Quisque ullamcorper ligula quam, commodo porttitor mauris ullamcorper eu. Cras varius sagittis felis in aliquam. Duis sodales risus ac justo vehicula, nec mattis diam lacinia. Cras eget lectus ipsum. Ut commodo, enim vitae malesuada hendrerit, ex dolor egestas lectus, sit amet hendrerit metus diam nec est. Vestibulum tortor metus, lobortis sit amet ante eget, tempor molestie lacus. In molestie et urna et semper. Mauris mollis, sem non hendrerit condimentum, sapien nisi cursus est, non suscipit quam justo non metus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam enim est, porta ac feugiat vitae, rutrum in lorem. Duis vehicula tortor ut posuere maximus. + +Nullam vestibulum non tellus sed commodo. Quisque mattis elit sit amet sapien sollicitudin, ut condimentum nisl congue. Aenean sagittis massa vel elit faucibus fermentum. Donec tincidunt nisi nec nisl sodales pellentesque. Mauris congue congue ligula ut suscipit. Vivamus velit tortor, tempor et gravida eget, fermentum sit amet ante. Nullam fringilla, lorem at ultrices cursus, urna neque ornare dolor, eu lacinia orci enim sed nibh. Ut a ullamcorper lectus, id mattis purus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aenean maximus sollicitudin posuere. Nunc at augue lacus. Aenean efficitur leo sit amet lacinia efficitur. + +Quisque venenatis quam mi, in pharetra odio vulputate eu. In vel nisl pulvinar, pulvinar ligula ut, sodales risus. Sed efficitur lectus at vestibulum tincidunt. Vestibulum eu ullamcorper elit. Fusce vestibulum magna enim, et tempor lacus posuere vitae. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Integer leo elit, luctus nec mattis sit amet, sollicitudin in turpis. + +Proin convallis venenatis leo, vitae tristique erat iaculis nec. Nulla facilisi. Duis porttitor, sapien et bibendum vulputate, sem libero sodales lacus, non malesuada felis erat ut libero. Nam non felis semper, finibus est a, mattis mauris. Praesent nec eros quam. Nulla hendrerit, augue consectetur eleifend ultricies, purus mi condimentum nulla, eget dapibus est nunc sed libero. Nullam elementum dui erat, vitae luctus libero sollicitudin et. Nulla odio magna, placerat in augue eu, dapibus imperdiet odio. Suspendisse imperdiet metus sit amet rhoncus dapibus. Cras at enim et urna vehicula cursus eu a mauris. Integer magna ante, eleifend ac placerat vitae, porta at nisi. Cras eget malesuada orci. Curabitur nunc est, vulputate id viverra et, dignissim sed odio. Curabitur non mattis sem. Sed bibendum, turpis vitae vehicula faucibus, nunc quam ultricies lectus, vitae viverra felis turpis at libero. + +Nullam ut egestas ligula. Proin hendrerit justo a lectus commodo venenatis. Nulla facilisi. Ut cursus lorem quis est bibendum condimentum. Aenean in tristique odio. Fusce tempor hendrerit ipsum. Curabitur mollis felis justo, quis dapibus erat auctor vel. Sed augue lectus, finibus ut urna quis, ullamcorper vestibulum dui. Etiam molestie aliquam tempor. Integer mattis sollicitudin erat, et tristique elit varius vel. Mauris a ex justo. + +Nam eros est, imperdiet non volutpat rutrum, pellentesque accumsan ligula. Duis sit amet turpis metus. Aenean in rhoncus metus, ac fringilla ex. Suspendisse condimentum egestas purus, ut pharetra odio vulputate vel. Duis tincidunt massa a placerat ultrices. Mauris ultricies nibh sit amet condimentum malesuada. Duis tincidunt id ipsum sed congue. + +Praesent eu ex augue. Nullam in porta ligula. In tincidunt accumsan arcu, in pellentesque magna tristique in. Mauris eleifend libero ac nisl viverra faucibus. Nam sollicitudin dolor in commodo hendrerit. Cras at orci metus. Ut quis laoreet orci. Vivamus ultrices leo pellentesque tempor aliquet. Maecenas ut eros vitae purus placerat vestibulum. Etiam vitae gravida dolor, quis rhoncus diam. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. + +Suspendisse fringilla lacinia sagittis. Integer tincidunt consectetur tristique. Morbi non orci convallis, congue sapien quis, vulputate nunc. Donec a libero vel magna elementum facilisis non quis mi. Mauris posuere tellus non ipsum ultrices elementum. Vivamus massa velit, facilisis quis placerat aliquet, aliquet nec leo. Praesent a maximus sem. Sed neque elit, feugiat vel quam non, molestie sagittis nunc. Etiam luctus nunc ac mauris scelerisque, nec rhoncus lacus convallis. Nunc pharetra, nunc ac pulvinar aliquam, ex ipsum euismod augue, nec porttitor lacus turpis vitae neque. Fusce bibendum odio id tortor faucibus pellentesque. Sed ac porta nibh, eu gravida erat. + +Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aliquam quis ullamcorper felis. Nulla mattis sagittis ante ac tincidunt. Integer ac felis efficitur, viverra libero et, facilisis ligula. Suspendisse a metus a massa rhoncus posuere. Phasellus suscipit ligula ut lacus facilisis, ac pellentesque ex tempor. Quisque consectetur massa mi, ac molestie libero dictum quis. Proin porttitor ligula quis erat tincidunt venenatis. Proin congue nunc sed elit gravida, nec consectetur lectus sodales. Etiam tincidunt convallis ipsum at vestibulum. Quisque maximus enim et mauris porttitor, et molestie magna tristique. Morbi vitae metus elit. Maecenas sed volutpat turpis. Aliquam vitae dolor vestibulum, elementum purus eget, dapibus nibh. Nullam egestas dui ac rutrum semper. + +Etiam hendrerit est metus, et condimentum metus aliquam ac. Pellentesque id neque id ipsum rhoncus vulputate. Aliquam erat nisl, posuere sit amet ligula ac, fermentum blandit felis. Vivamus fermentum mi risus, non lacinia purus viverra id. Aenean ac sapien consequat, finibus mauris nec, porta sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed quis consectetur ex, dignissim bibendum nulla. Phasellus ac libero at quam vehicula euismod non eu leo. Phasellus a sapien augue. + +Maecenas ligula dui, bibendum vitae mauris et, auctor laoreet felis. Duis non libero a mi semper mattis. Quisque consequat luctus massa, quis tristique eros auctor feugiat. Maecenas sodales euismod neque vitae facilisis. Nullam laoreet imperdiet velit at pellentesque. Etiam massa odio, facilisis a consequat vitae, placerat vel magna. Nunc sagittis eros nec urna fringilla, pulvinar vestibulum nibh scelerisque. Sed magna metus, cursus eu consequat et, pharetra a est. Suspendisse elementum neque a dui malesuada lacinia. Donec sed ipsum volutpat, cursus urna id, ullamcorper arcu. Maecenas laoreet nisl eget velit egestas sollicitudin. Etiam nisl turpis, mollis id dignissim vitae, tristique vehicula ante. Maecenas eget placerat est, at rutrum augue. Vivamus faucibus lacinia ullamcorper. Sed pulvinar urna sodales ante sodales, at gravida leo dictum. + +Morbi maximus, quam a lobortis bibendum, enim felis varius elit, ac vehicula elit nisl ut lacus. Quisque ut arcu augue. Praesent id turpis quam. Sed sed arcu eros. Maecenas at cursus lorem, ac eleifend nisi. Fusce mattis felis at commodo pharetra. Praesent ac commodo ipsum. Quisque finibus et eros vitae tincidunt. In hac habitasse platea dictumst. Praesent purus ipsum, luctus lobortis ornare quis, auctor eget justo. Nam vel enim sollicitudin, faucibus tortor eu, sagittis eros. Ut nec consectetur erat. Donec ultricies malesuada ligula, a hendrerit sapien volutpat in. Maecenas sed enim vitae sapien pulvinar faucibus. + +Proin semper nunc nibh, non consequat neque ullamcorper vel. Maecenas lobortis sagittis blandit. Aenean et arcu ultricies turpis malesuada malesuada. Ut quam ex, laoreet ut blandit cursus, feugiat vitae dolor. Etiam ex lacus, scelerisque vel erat vel, efficitur tincidunt magna. Morbi tristique lacinia dolor, in egestas magna ultrices vitae. Integer ultrices leo ac tempus venenatis. Praesent ac porta tortor. Vivamus ornare blandit tristique. Nulla rutrum finibus pellentesque. In non dui elementum, fermentum ipsum vel, varius magna. Pellentesque euismod tortor risus, ac pellentesque nisl faucibus eget. + +Vivamus eu enim purus. Cras ultrices rutrum egestas. Sed mollis erat nibh, at posuere nisl luctus nec. Nunc vulputate, sapien id auctor molestie, nisi diam tristique ante, non convallis tellus nibh at orci. Morbi a posuere purus, in ullamcorper ligula. Etiam elementum sit amet dui imperdiet iaculis. Proin vitae tincidunt ipsum, sit amet placerat lectus. Curabitur commodo sapien quam, et accumsan lectus fringilla non. Nullam eget accumsan enim, ac pharetra mauris. Sed quis tristique velit, vitae commodo nisi. Duis turpis dui, maximus ut risus at, finibus consequat nunc. Maecenas sed est accumsan, aliquet diam in, facilisis risus. Curabitur vehicula rutrum auctor. Nam iaculis risus pulvinar maximus viverra. Nulla vel augue et ex sagittis blandit. + +Ut sem nulla, porta ac ante ac, posuere laoreet eros. Donec sodales posuere justo a auctor. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Cras mollis at orci hendrerit porta. Nullam sodales tortor tortor, non lacinia diam finibus id. Duis libero orci, suscipit ac odio et, dictum consequat ipsum. Pellentesque eu ligula sagittis, volutpat eros at, lacinia lorem. Cras euismod tellus in iaculis tempor. Quisque accumsan, magna a congue venenatis, ante ipsum aliquam lectus, at egestas enim nunc at justo. Quisque sem purus, viverra ut tristique ut, maximus id enim. Etiam quis placerat sem. In sollicitudin, lacus eu rutrum mollis, nulla eros luctus elit, vel dapibus urna purus nec urna. Phasellus egestas massa quam, ac molestie erat hendrerit a. Praesent ultrices neque ut turpis molestie auctor. Etiam molestie placerat purus, et euismod erat aliquam in. Morbi id suscipit justo. + +Proin est ante, consequat at varius a, mattis quis felis. Sed accumsan nibh sit amet ipsum elementum posuere. Vestibulum bibendum id diam sit amet gravida. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Morbi nec dolor vel ipsum dignissim hendrerit vel non ipsum. Praesent facilisis orci quis elit auctor lobortis. Phasellus cursus risus lectus, vel lobortis libero dapibus in. Quisque tristique tempus leo a pulvinar. Pellentesque a magna tincidunt, pellentesque massa nec, laoreet orci. Morbi congue ornare dolor quis commodo. Phasellus massa nisi, tincidunt at eros dictum, hendrerit lobortis urna. Maecenas porta, magna id mattis molestie, nibh tellus lobortis sem, eget tincidunt ipsum quam eu turpis. + +Ut gravida orci risus, vel rutrum mauris vehicula id. Etiam bibendum, neque a placerat condimentum, ex orci imperdiet lectus, quis dapibus arcu lacus eget lectus. Sed consequat non mi sit amet venenatis. Fusce vestibulum erat libero, eget hendrerit risus vulputate sollicitudin. Integer sed eleifend felis. Donec commodo, sem eu mattis placerat, urna odio aliquam tellus, et laoreet justo tellus eget erat. Fusce sed suscipit tortor. Nam hendrerit nibh ac nunc auctor lacinia. Pellentesque placerat condimentum ipsum, eget semper tortor hendrerit vel. Nullam non urna eu lacus pellentesque congue ut id eros. + +Nunc finibus leo in rhoncus tristique. Sed eu ipsum nec nisl egestas faucibus eget a felis. Pellentesque vitae nisi in nulla accumsan fermentum. Sed venenatis feugiat eleifend. Fusce porttitor varius placerat. Aliquam aliquet lacus sit amet mattis mollis. Sed vel nulla quis dolor suscipit vehicula ac viverra lorem. Duis viverra ipsum eget nulla ullamcorper fermentum. Mauris tincidunt arcu quis quam fringilla ornare. Donec et iaculis tortor. Nam ultricies libero vel ipsum aliquet efficitur. Morbi eget dolor aliquam, tempus sapien eget, viverra ante. Donec varius mollis ex, sed efficitur purus euismod interdum. Quisque vel sapien non neque tincidunt semper. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. + +Suspendisse sit amet purus leo. Fusce lectus lorem, aliquam ac nulla eget, imperdiet ornare eros. Nullam sem augue, varius in nisi non, sollicitudin pellentesque ante. Etiam eu odio condimentum, tempor libero et, egestas arcu. Cras pellentesque eleifend aliquet. Pellentesque non blandit ligula. Ut congue viverra rhoncus. Phasellus mattis mi ac eros placerat, eu feugiat tellus ultrices. Aenean mollis laoreet libero eu imperdiet. Cras sed pulvinar mi, ac vehicula ligula. Vestibulum sit amet ex massa. In a egestas eros. + +Mauris pretium ipsum risus, venenatis cursus ante imperdiet id. Praesent eu turpis nec risus feugiat maximus ullamcorper ac lectus. Integer placerat at mi vel dapibus. Vestibulum fermentum turpis sit amet turpis viverra, id aliquet diam suscipit. Nam nec ex sed ante ullamcorper pharetra quis sit amet risus. Sed ac faucibus velit, id feugiat nibh. Nullam eget ipsum ex. Vivamus tincidunt non nunc non faucibus. Quisque bibendum viverra facilisis. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur at nisi hendrerit quam suscipit egestas. Curabitur laoreet maximus ultricies. Duis ut tellus ac augue molestie dictum. + +Suspendisse rhoncus iaculis erat, ut ullamcorper est tristique eget. Donec auctor nec risus at gravida. Vivamus volutpat vulputate tellus, vel ultricies eros suscipit eget. Ut pulvinar id mi eu tempus. Morbi malesuada augue in dui varius, nec blandit neque vehicula. Donec ornare nec nisl in mollis. Morbi enim nisi, rhoncus nec est id, dapibus tempus urna. Ut id elit a felis vestibulum consectetur. Duis lectus quam, pharetra sit amet diam sed, posuere vestibulum erat. Fusce vitae maximus massa. Nullam id metus tempus, iaculis risus eu, lobortis urna. Quisque in congue urna. Pellentesque placerat neque in augue dapibus, non varius ex malesuada. Curabitur ut eleifend libero. Fusce vitae ligula luctus, fermentum enim vitae, ultrices erat. + +Sed viverra augue turpis, scelerisque egestas sapien mattis eu. Duis laoreet magna at ex pharetra dapibus. Praesent eget odio vel quam venenatis dictum. Nulla in sollicitudin dolor. Mauris lobortis nec eros vel rhoncus. Vestibulum porta viverra venenatis. Curabitur vel scelerisque quam, a egestas velit. Praesent volutpat tincidunt magna at laoreet. + +Cras nec lorem odio. Pellentesque quis dui urna. Praesent at tellus ac lectus scelerisque placerat nec eu risus. Vestibulum sit amet mattis ligula. Vivamus sed nisi at leo elementum accumsan at sit amet arcu. Aenean mattis tellus nec leo gravida, eget hendrerit nisl faucibus. Mauris pellentesque luctus condimentum. Maecenas pretium sapien nunc, eget commodo dolor maximus id. Mauris vestibulum accumsan massa a dictum. Phasellus interdum quam ligula, ut maximus diam blandit aliquam. Nunc vitae ex eu erat condimentum consectetur. Maecenas interdum condimentum volutpat. + +Donec et enim a libero rutrum laoreet. Praesent a condimentum sem, at tincidunt quam. In vel molestie risus. Sed urna dui, molestie vitae mollis laoreet, tempor quis lectus. Praesent vitae auctor est, et aliquet nunc. Curabitur vulputate blandit nulla, at gravida metus. Maecenas gravida dui eu iaculis tristique. Pellentesque posuere turpis nec auctor eleifend. Suspendisse bibendum diam eu tellus lobortis, et laoreet quam congue. In hac habitasse platea dictumst. Morbi dictum neque velit, eget rutrum eros ultrices sit amet. + +Phasellus fermentum risus pharetra consectetur bibendum. Donec magna tortor, lacinia vitae nibh quis, aliquet pretium lorem. Donec turpis nisi, pretium eu enim volutpat, mattis malesuada augue. Nullam vel tellus iaculis, sollicitudin elit eget, tincidunt lacus. Fusce elementum elementum felis et iaculis. Suspendisse porta eros nec neque malesuada, in malesuada ante sollicitudin. Vivamus bibendum viverra molestie. + +Integer feugiat, erat nec convallis aliquam, velit felis congue erat, molestie eleifend tellus erat in tellus. Nunc et justo purus. Donec egestas fermentum dui non feugiat. Quisque in sapien sagittis, gravida quam id, iaculis lectus. Cras sagittis rhoncus bibendum. Fusce quis metus in velit scelerisque tincidunt at non ipsum. Vivamus efficitur ante eu odio vulputate, vitae ultricies risus vehicula. Proin eget odio eu sem tincidunt feugiat vel id lorem. + +Vestibulum sit amet nulla dignissim, euismod mi in, fermentum tortor. Donec ut aliquet libero, lacinia accumsan velit. Donec et nulla quam. Nullam laoreet odio nec nunc imperdiet, a congue eros venenatis. Quisque nec tellus sit amet neque interdum posuere. Duis quis mi gravida, tincidunt diam convallis, ultricies augue. Mauris consequat risus non porttitor congue. Ut in ligula consequat, viverra nunc a, eleifend enim. Duis ligula urna, imperdiet nec facilisis et, ornare eu ex. Proin lobortis lectus a lobortis porttitor. Nulla leo metus, egestas eu libero sed, pretium faucibus felis. Vestibulum non sem tortor. Nam cursus est leo. Vivamus luctus enim odio, non interdum sem dapibus a. Aenean accumsan consequat lectus in imperdiet. + +Donec vehicula laoreet ipsum in posuere. Quisque vel quam imperdiet, sollicitudin nisi quis, suscipit velit. Morbi id sodales mauris. Curabitur tellus arcu, feugiat sed dui sit amet, sodales sagittis libero. Aenean vel suscipit metus, non placerat leo. Vestibulum quis nulla elit. Proin scelerisque non ante ut commodo. Interdum et malesuada fames ac ante ipsum primis in faucibus. + +Sed non urna dolor. Suspendisse convallis mi porta pulvinar ultrices. Suspendisse quam ipsum, hendrerit non scelerisque molestie, interdum dictum nunc. Morbi condimentum condimentum turpis eu luctus. Pellentesque sagittis sollicitudin odio, sed ultricies felis ornare sit amet. Sed ultrices ex leo, a tincidunt nisl gravida sed. Nullam ornare accumsan porta. Praesent consectetur id est nec sollicitudin. + +In hac habitasse platea dictumst. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed sed ultrices nibh. Duis accumsan suscipit eros, a dictum odio tempus sit amet. Aenean imperdiet erat ac lacus finibus, scelerisque cursus massa imperdiet. Mauris molestie risus ut lacinia posuere. Nulla et sodales purus. Maecenas orci erat, placerat in tristique quis, placerat in mi. + +Donec sollicitudin pellentesque odio in feugiat. Morbi eu dolor ut mauris congue sollicitudin. Aliquam erat volutpat. Nulla id varius dui. Curabitur finibus urna ante, consectetur interdum nisi volutpat a. Quisque quis mi tristique, consequat tellus eget, rutrum sapien. Vivamus vitae tellus vulputate, rutrum ex eu, vulputate sem. Suspendisse viverra lorem tellus, vel interdum orci gravida quis. Ut laoreet arcu at mi ullamcorper finibus. Duis porta sagittis vestibulum. Sed commodo nisl vitae urna sollicitudin, nec lacinia est sodales. Curabitur imperdiet sodales dui sed iaculis. Sed ac tellus maximus, eleifend quam sit amet, feugiat elit. Aenean viverra, dui at mattis varius, est odio vestibulum sapien, sit amet mollis libero massa nec velit. Etiam quis sodales justo. + +Ut ultricies, sem eget sodales feugiat, nunc arcu congue elit, ac tempor justo massa nec purus. Maecenas enim nunc, pharetra eget dictum sit amet, tempus pellentesque velit. Suspendisse venenatis ligula in nulla mattis, et imperdiet ex tincidunt. Etiam vulputate, tellus et ultrices suscipit, enim velit laoreet massa, vitae congue odio enim ac urna. Morbi quam lorem, iaculis ac varius sagittis, euismod quis dolor. In ut dui eu purus feugiat consectetur. Vestibulum cursus velit quis lacus pellentesque iaculis. Cras in risus sed mauris porta rutrum. Nulla facilisi. Nullam eu bibendum est, non pellentesque lectus. Sed imperdiet feugiat lorem, quis convallis ante auctor in. Maecenas justo magna, scelerisque sit amet tellus eget, varius elementum risus. Duis placerat et quam sed varius. + +Duis nec nibh vitae nibh dignissim mollis quis sed felis. Curabitur vitae quam placerat, venenatis purus ut, euismod nisl. Curabitur porttitor nibh eu pulvinar ullamcorper. Suspendisse posuere nec ipsum ac dapibus. Cras convallis consectetur urna. Phasellus a nibh in dolor lacinia posuere id eget augue. In eu pharetra lorem, vitae cursus lacus. Aliquam tincidunt nibh lectus. Aenean facilisis ultricies posuere. Sed ut placerat orci. Curabitur scelerisque gravida blandit. Maecenas placerat ligula eget suscipit fringilla. Mauris a tortor justo. Aliquam hendrerit semper mollis. Phasellus et tincidunt libero. Etiam vel quam libero. + +Quisque aliquet tempor ex. Ut ante sem, vehicula at enim vel, gravida porta elit. Etiam vitae lacus a neque lobortis consectetur. Mauris sed interdum odio. Mauris elementum ex blandit tempor cursus. Integer in enim in leo viverra elementum. Fusce consectetur metus et sem rutrum, mattis euismod diam semper. Nunc sed ipsum vel urna consequat vehicula. Donec cursus pretium lorem, vestibulum pretium felis commodo sit amet. Nam blandit felis enim, eget gravida ex faucibus a. In nec neque massa. Etiam laoreet posuere ipsum. Praesent volutpat nunc dolor, ac vulputate magna facilisis non. Aenean congue turpis vel lectus sollicitudin tristique. Sed nec consequat purus, non vehicula quam. Etiam ultricies, est ac dictum tincidunt, turpis turpis pretium massa, a vulputate libero justo at nibh. + +Aliquam erat volutpat. Cras ultrices augue ac sollicitudin lobortis. Curabitur et aliquet purus. Duis feugiat semper facilisis. Phasellus lobortis cursus velit, a sollicitudin tortor. Nam feugiat sapien non dapibus condimentum. Morbi at mi bibendum, commodo quam at, laoreet enim. Integer eu ultrices enim. Sed vestibulum eu urna ut dictum. Curabitur at mattis leo, sed cursus massa. Aliquam porttitor, felis quis fermentum porttitor, justo velit feugiat nulla, eget condimentum sem dui ut sapien. + +In fringilla elit eu orci aliquam consequat. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Ut eget fringilla tellus. Curabitur fermentum, mi et condimentum suscipit, elit neque bibendum dui, et hendrerit nunc metus id ipsum. Morbi placerat mi in hendrerit congue. Ut feugiat mauris eget scelerisque viverra. Vivamus sit amet erat dictum, sagittis lectus nec, pulvinar lorem. Sed non enim ac dui sollicitudin aliquet. Quisque ut lacus dolor. Fusce hendrerit malesuada euismod. Nulla faucibus vel mauris eu mollis. Mauris est diam, fringilla ac arcu feugiat, efficitur volutpat turpis. Aliquam venenatis cursus massa sed porttitor. Ut ac finibus enim, in tincidunt sapien. + +Nunc faucibus semper turpis a lacinia. Phasellus gravida, libero vel pulvinar ornare, ex sem tincidunt lectus, sit amet convallis augue risus at tortor. Quisque sit amet ipsum id nulla posuere vestibulum. Pellentesque scelerisque mauris vel leo viverra sodales. Nulla viverra aliquam ex, ut rutrum enim fermentum venenatis. Aenean eget dapibus ex, eget faucibus metus. Vestibulum volutpat leo in diam semper, eget porta magna suscipit. Sed sit amet nulla blandit, aliquam dolor ac, gravida velit. Sed vel velit viverra, maximus est id, convallis justo. + +Curabitur nulla ante, vulputate at libero vel, ullamcorper rutrum nibh. Pellentesque porttitor eu mauris id mattis. Duis vulputate augue elit, eget interdum justo pretium vel. Maecenas eu vulputate arcu, eget posuere purus. Suspendisse viverra a velit dictum eleifend. Suspendisse vitae dapibus diam. Donec vehicula justo in ante interdum, eu luctus diam placerat. Vivamus convallis ipsum eu orci suscipit, sed fermentum enim euismod. Maecenas faucibus elit vitae ex ornare tristique. Donec vestibulum nec elit sit amet porttitor. Aenean tempor lectus eget tortor hendrerit luctus. Nullam interdum vitae lectus vel feugiat. Cras in risus non magna consectetur lobortis. Sed faucibus enim quis gravida convallis. + +Phasellus eget massa sit amet libero ultrices suscipit. Vivamus at risus sapien. Nam mollis nunc eget velit dictum maximus. Sed pellentesque, nunc ac fringilla lacinia, quam enim mattis ex, sed euismod tortor metus eu neque. Ut mattis nisl ut lectus rhoncus, sodales bibendum eros porta. Nulla porttitor enim nec diam sagittis, eget porta velit efficitur. Vestibulum ultricies eros neque. Phasellus rutrum suscipit enim, in interdum ante gravida vitae. Sed in sagittis diam, non commodo velit. + +Morbi hendrerit odio orci, nec tincidunt odio rhoncus nec. Mauris neque velit, vehicula a lorem at, suscipit tristique dui. Sed finibus, nisl in mattis convallis, turpis neque sodales lacus, eu porta enim magna non diam. Nam commodo sodales risus consectetur malesuada. In eget elementum justo. Phasellus sit amet massa imperdiet, dapibus nunc sit amet, suscipit orci. Fusce condimentum laoreet feugiat. Ut ut viverra ante. Praesent bibendum interdum commodo. Nulla mollis nisi a est ornare volutpat. Sed at ligula eu nisi dapibus tempus. Proin cursus vestibulum justo, nec efficitur justo dignissim vel. Nunc quis maximus eros. + +Cras viverra, diam a tristique mattis, libero felis vulputate tellus, a ornare felis leo a dui. Nulla ante nulla, finibus ut tellus ut, blandit pharetra nibh. Proin eleifend fermentum ex, eget auctor libero vulputate in. Nullam ultricies, mauris placerat pretium placerat, leo urna lobortis leo, vel placerat arcu libero sed mauris. Aliquam mauris ligula, ornare at urna at, eleifend gravida ligula. Vestibulum consectetur ut nulla non scelerisque. Donec ornare, sem nec elementum aliquam, urna nulla bibendum metus, eu euismod dui ligula ac est. Fusce laoreet erat eu ex lobortis, quis bibendum ligula interdum. Sed vel mi erat. Vivamus id lacus ac enim mattis tempor. Nunc ultricies pellentesque enim sed euismod. Fusce tincidunt convallis elit quis aliquam. Mauris nulla ipsum, sollicitudin quis diam ac, feugiat volutpat tellus. In nibh nibh, vulputate quis tincidunt quis, pulvinar eget magna. Pellentesque quis finibus dolor. Suspendisse viverra vitae lectus non eleifend. + +Nunc ut orci et sapien maximus semper. Nulla dignissim sem urna, ac varius lectus ultricies id. Quisque aliquet pulvinar pretium. In ultricies molestie tellus vehicula porta. Nam enim lorem, aliquam eget ex et, hendrerit volutpat quam. Maecenas diam lacus, pellentesque eget tempus ac, pharetra eu elit. Donec vel eros a sem facilisis vulputate. Nullam ac nisi vulputate, laoreet nisl ac, eleifend sem. Nullam mi massa, rhoncus sed pharetra interdum, tincidunt eget nunc. Aliquam viverra mattis posuere. Mauris et dui sed nisl sollicitudin fermentum quis ut arcu. Nam placerat eget orci at tincidunt. Curabitur vel turpis metus. Phasellus nibh nulla, fermentum scelerisque sem vel, gravida tincidunt velit. Pellentesque vel quam tempor, finibus massa pellentesque, condimentum dui. + +Donec at mattis neque. Etiam velit diam, consequat auctor mauris id, hendrerit faucibus metus. Maecenas ullamcorper eros a est sodales, ac consectetur odio scelerisque. Donec leo metus, imperdiet at pellentesque vel, feugiat id erat. Suspendisse at magna enim. Vestibulum placerat sodales lorem id sollicitudin. Aenean at euismod ligula, eget mollis diam. Phasellus pulvinar, orci nec pretium condimentum, est erat facilisis purus, quis feugiat augue elit aliquam nulla. Aenean vitae tortor id risus congue tincidunt. Sed dolor enim, mattis a ullamcorper id, volutpat ac leo. + +Proin vehicula feugiat augue, id feugiat quam sodales quis. Donec et ultricies massa, a lacinia nulla. Duis aliquam augue ornare euismod viverra. Ut lectus risus, rutrum sit amet efficitur a, luctus nec nisl. Cras volutpat ullamcorper congue. Sed vitae odio metus. Phasellus aliquet euismod varius. + +Nullam sem ex, malesuada ut magna ut, pretium mollis arcu. Nam porttitor eros cursus mi lacinia faucibus. Suspendisse aliquet eleifend iaculis. Maecenas sit amet viverra tortor. Nunc a mollis risus. Etiam tempus dolor in tortor malesuada mattis. Ut tincidunt venenatis est sit amet dignissim. Vestibulum massa enim, tristique sed scelerisque eu, fringilla ac velit. Donec efficitur quis urna sit amet malesuada. Vestibulum consequat ac ligula in dapibus. Maecenas massa massa, molestie non posuere nec, elementum ut magna. In nisi erat, mollis non venenatis eu, faucibus in justo. Morbi gravida non ex non egestas. Pellentesque finibus laoreet diam, eu commodo augue congue vitae. + +Aenean sem mi, ullamcorper dapibus lobortis vitae, interdum tincidunt tortor. Vivamus eget vulputate libero. Ut bibendum posuere lectus, vel tincidunt tortor aliquet at. Phasellus malesuada orci et bibendum accumsan. Aliquam quis libero vel leo mollis porta. Sed sagittis leo ac lacus dictum, ac malesuada elit finibus. Suspendisse pharetra luctus commodo. Vivamus ultricies a odio non interdum. Vivamus scelerisque tincidunt turpis quis tempor. Pellentesque tortor ligula, varius non nunc eu, blandit sollicitudin neque. Nunc imperdiet, diam et tristique luctus, ipsum ex condimentum nunc, sit amet aliquam justo velit sed libero. Duis vel suscipit ligula. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed tincidunt neque vel massa ultricies, id dictum leo consequat. Curabitur lobortis ultricies tellus, eget mattis nisl aliquam sit amet. + +Proin at suscipit justo. Vivamus ut vestibulum nisl. Pellentesque enim odio, pharetra non magna sed, efficitur auctor magna. Praesent tincidunt ante quis ante hendrerit viverra. Pellentesque vel ipsum id magna vulputate efficitur. Sed nec neque accumsan, pulvinar sapien quis, euismod mauris. Donec condimentum laoreet sapien quis gravida. Quisque sed mattis purus. Vestibulum placerat vel neque maximus scelerisque. + +Vestibulum mattis quam quis efficitur elementum. Duis dictum dolor ac scelerisque commodo. Fusce sollicitudin nisi sit amet dictum placerat. Suspendisse euismod pharetra eleifend. In eros nisl, porttitor sed mauris at, consectetur aliquet mauris. Donec euismod viverra neque sed fermentum. Phasellus libero magna, accumsan ut ultricies vitae, dignissim eget metus. Donec tellus turpis, interdum eget maximus nec, hendrerit eget massa. Curabitur auctor ligula in iaculis auctor. In ultrices quam suscipit cursus finibus. Aenean id mi at dolor interdum iaculis vitae ut lorem. Nullam sed nibh fringilla, lacinia odio nec, placerat erat. In dui libero, viverra ac viverra ac, pellentesque sit amet turpis. + +Nulla in enim ex. Sed feugiat est et consectetur venenatis. Cras varius facilisis dui vel convallis. Vestibulum et elit eget tellus feugiat pellentesque. In ut ante eu purus aliquet posuere. Nulla nec ornare sem, sed luctus lorem. Nam varius iaculis odio, eget faucibus nisl ullamcorper in. Sed eget cursus felis, nec efficitur nisi. + +Vivamus commodo et sem quis pulvinar. Pellentesque libero ante, venenatis vitae ligula sit amet, ornare sollicitudin nulla. Mauris eget tellus hendrerit, pulvinar metus quis, tempor nisi. Proin magna ex, laoreet sed tortor quis, varius fermentum enim. Integer eu dolor dictum, vulputate tortor et, aliquet ligula. Vestibulum vitae justo id mauris luctus sollicitudin. Suspendisse eget auctor neque, sodales egestas lorem. Vestibulum lacinia egestas metus vitae euismod. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus ex tellus, volutpat nec pulvinar sit amet, condimentum vitae dui. Curabitur vel felis sodales, lacinia nunc iaculis, ullamcorper augue. Pellentesque consequat dolor quis eros efficitur malesuada. Nulla ut malesuada lectus. + +Morbi et tristique ante. Aliquam erat volutpat. Vivamus vitae dui nec turpis pellentesque fermentum. Quisque eget velit massa. Pellentesque tristique aliquam nisl, eu sollicitudin justo venenatis sed. Duis eleifend sem eros, ut aliquam libero porttitor id. Sed non nunc consequat, rhoncus diam eu, commodo erat. Praesent fermentum in lectus id blandit. Donec quis ipsum at justo volutpat finibus. Nulla blandit justo nulla, at mollis lacus consequat eget. Aenean sollicitudin quis eros ut ullamcorper. + +Pellentesque venenatis nulla ut mi aliquet feugiat. Cras semper vel magna nec pharetra. Integer mattis felis et sapien commodo imperdiet. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Duis quis luctus felis. Vestibulum justo nibh, aliquam non lectus vitae, molestie placerat justo. Donec lorem nibh, gravida sit amet hendrerit ac, maximus id ipsum. Nunc ac libero sodales risus eleifend sagittis. Phasellus est massa, lobortis elementum ex sed, scelerisque consectetur neque. Nunc faucibus neque id lorem malesuada, eget convallis ex mattis. + +Sed turpis tortor, fermentum non turpis id, posuere varius nibh. Donec iaculis lorem dui. Etiam eros ante, sodales eget venenatis at, consectetur eget risus. Curabitur non aliquam ante, a pretium justo. Maecenas tempor nisl tortor, vitae dictum nisi ultrices eu. Duis eget dui ultrices, porttitor lacus sed, lobortis purus. Quisque mattis elit nec neque sagittis, sed commodo leo blandit. Mauris sodales interdum eleifend. Vestibulum condimentum consectetur augue, id luctus diam convallis et. + +Nunc suscipit risus in justo accumsan, a placerat magna tincidunt. Proin a nisl ipsum. Sed libero dui, tristique in augue quis, auctor tristique risus. Sed porttitor ex augue, eu porta augue molestie a. Duis rhoncus purus libero, eu tempus turpis condimentum at. Sed mollis nisi id lectus placerat tincidunt. Maecenas non scelerisque elit, quis rutrum orci. Donec in tellus pharetra urna ornare lobortis. Phasellus id risus at nisi varius rutrum eu ut turpis. + +Duis dictum justo quis nisl porta, eget tincidunt magna suscipit. Sed velit massa, ullamcorper eu sodales ac, pretium a massa. Duis et rutrum tortor. Nulla accumsan hendrerit sapien, cursus volutpat eros egestas eget. Donec sollicitudin at ante quis sollicitudin. Aenean blandit feugiat diam, id feugiat eros faucibus eget. Donec viverra dolor vel justo scelerisque dignissim. Nulla semper sem nunc, rhoncus semper tellus ultricies sed. Duis in ornare diam. Donec vehicula feugiat varius. Maecenas ut suscipit est. Vivamus sem sem, finibus at dolor sit amet, euismod dapibus ligula. Vestibulum fringilla odio dapibus, congue massa eget, congue sem. Donec feugiat magna eget tortor lacinia scelerisque non et ipsum. + +Suspendisse potenti. Nunc convallis sollicitudin ex eget venenatis. Sed iaculis nibh ex, vel ornare ligula congue dignissim. Quisque sollicitudin dolor ac dui vestibulum, sit amet molestie nisi aliquet. Donec at risus felis. Aenean sollicitudin metus a feugiat porta. Aenean a tortor ut dolor cursus sagittis. Vivamus consectetur porttitor nunc in facilisis. Proin sit amet mi vel lectus consectetur ultrices. + +Sed cursus lectus vitae nunc tristique, nec commodo turpis dapibus. Pellentesque luctus ex id facilisis ornare. Morbi quis placerat dolor. Donec in lectus in arcu mattis porttitor ac sit amet metus. Cras congue mauris non risus sodales, vitae feugiat ipsum bibendum. Nulla venenatis urna sed libero elementum, a cursus lorem commodo. Mauris faucibus lobortis eros nec commodo. + +Nullam suscipit ligula ullamcorper lorem commodo blandit. Nulla porta nibh quis pulvinar placerat. Vivamus eu arcu justo. Vestibulum imperdiet est ut fermentum porttitor. Pellentesque consectetur libero in sapien efficitur scelerisque. Curabitur ac erat sit amet odio aliquet dignissim. Pellentesque mi sem, rhoncus et luctus at, porttitor rutrum lectus. Vestibulum sollicitudin sollicitudin suscipit. Aenean efficitur dolor non ultrices imperdiet. Donec vel sem ex. + +Sed convallis mauris aliquam rutrum cursus. Ut tempor porttitor sodales. Etiam eu risus ac augue gravida egestas et eu dolor. Proin id magna ex. Suspendisse quis lectus quis lorem ultricies tempus. Donec porttitor velit vitae tincidunt faucibus. Aliquam vitae semper nisi. Morbi ultrices, leo non pretium dapibus, dui libero pellentesque ex, vel placerat enim ante vitae dui. Nunc varius, sem sit amet sagittis lobortis, lectus odio scelerisque mauris, ut vestibulum orci magna quis neque. Sed id congue justo. Interdum et malesuada fames ac ante ipsum primis in faucibus. Mauris congue nisi est, malesuada mollis elit tincidunt sed. Curabitur sed ex sit amet felis tristique elementum vitae vel nibh. + +Etiam mollis pretium lobortis. Mauris augue lacus, efficitur at lacus sed, mollis tincidunt lectus. Aliquam erat volutpat. Donec at euismod elit, et mattis felis. Sed id lobortis urna. Morbi imperdiet vestibulum leo, sed maximus leo blandit eu. Aliquam semper lorem neque, nec euismod turpis mattis mollis. Quisque lobortis urna ultrices odio pretium, ac venenatis orci faucibus. Suspendisse bibendum odio ligula, sed lobortis massa pharetra nec. Donec turpis justo, iaculis at dictum ac, finibus eu libero. Maecenas quis porttitor mi, sit amet aliquet neque. + +Vivamus auctor vulputate ante, at egestas lorem. Donec eu risus in nulla mollis ultricies at et urna. Duis accumsan porta egestas. Ut vel euismod augue. Fusce convallis nulla ante, nec fringilla velit aliquet at. Nam malesuada dapibus ligula, a aliquam nibh scelerisque ac. Praesent malesuada neque et pellentesque interdum. Curabitur volutpat at turpis vitae tristique. Vivamus porttitor semper congue. Quisque suscipit lacus mi, rhoncus ultrices tortor auctor quis. Maecenas neque neque, molestie ac facilisis eget, luctus ac lorem. In ut odio ut lacus suscipit pulvinar vitae sed elit. Nulla imperdiet, sem quis euismod sagittis, dui erat luctus dolor, faucibus faucibus erat sem eget nunc. Nam accumsan placerat malesuada. Maecenas convallis finibus pulvinar. + +Cras at placerat tortor. Morbi facilisis auctor felis sit amet molestie. Donec sodales sed lorem vitae suscipit. Etiam fermentum pharetra ipsum, nec luctus orci gravida eu. Pellentesque gravida, est non condimentum tempus, mauris ligula molestie est, in congue dolor nisl vel sapien. Duis congue tempor augue, id rutrum eros porta dapibus. Etiam rutrum eget est eget vestibulum. Aenean mollis arcu vel consequat varius. Praesent at condimentum felis. Duis nec interdum nisl. Donec commodo lorem sed sapien scelerisque malesuada non eu urna. In blandit non ipsum at porta. Nam lobortis leo vitae dui auctor, non feugiat quam bibendum. Donec auctor lectus sagittis laoreet maximus. Maecenas rhoncus laoreet porttitor. Vestibulum porttitor augue ut lectus hendrerit, eget posuere mi gravida. + +Sed mattis ex in erat pulvinar, eu imperdiet magna dapibus. Etiam nisi nibh, tempus non tellus sit amet, mattis tempor odio. Quisque nec lorem feugiat, lobortis odio et, commodo nunc. Maecenas semper purus nisi, nec vehicula nibh eleifend vitae. Nulla fermentum a lectus at maximus. Phasellus finibus metus non euismod ultrices. Etiam a pulvinar ante. Quisque convallis nec metus sit amet facilisis. Praesent laoreet massa et sollicitudin laoreet. Vestibulum in mauris aliquet, convallis mi ut, elementum purus. Nulla purus nulla, sodales at hendrerit quis, tempus sed lectus. + +Nam ut laoreet neque, ut maximus nibh. Maecenas quis justo pellentesque, sollicitudin elit at, venenatis velit. Aenean nunc velit, vehicula scelerisque odio at, consectetur laoreet purus. Duis dui purus, malesuada quis ipsum sit amet, tempor interdum libero. Curabitur porta scelerisque sapien, vitae cursus diam condimentum eu. Phasellus sed orci quam. Nullam vitae dui quis purus tincidunt vestibulum. Curabitur quis nulla porta, cursus arcu non, auctor enim. Etiam sollicitudin ex id sem vehicula mollis. Morbi viverra laoreet tincidunt. Praesent ut semper dui. Nam sit amet pretium neque. Mauris vitae luctus diam, in lacinia purus. Maecenas ut placerat justo, ut porta felis. Integer eu mauris ante. + +Aenean porttitor tellus diam, tempor consequat metus efficitur id. Suspendisse ut felis at erat tempor dictum at nec sapien. Sed vestibulum interdum felis, ac mattis mauris porta in. Nunc et condimentum massa. Sed cursus dictum justo et luctus. Integer convallis enim nisl, a rutrum lectus ultricies in. Donec dapibus lacus at nulla dapibus, id sollicitudin velit hendrerit. Fusce a magna at orci mollis rutrum ac a dolor. Aliquam erat volutpat. Morbi varius porta nunc, sit amet sodales ex hendrerit commodo. Donec tincidunt tortor sapien, vitae egestas sapien vehicula eget. + +Suspendisse potenti. Donec pulvinar felis nec leo malesuada interdum. Integer posuere placerat maximus. Donec nibh ipsum, tincidunt vitae luctus vitae, bibendum at leo. Sed cursus nisl ut ex faucibus aliquet sed nec eros. Curabitur molestie posuere felis. Integer faucibus velit eget consequat iaculis. Mauris sed vulputate odio. Phasellus maximus, elit a pharetra egestas, lorem magna semper tellus, vestibulum semper diam felis at sapien. Suspendisse facilisis, nisl sit amet euismod vehicula, libero nulla vehicula dolor, quis fermentum nibh elit sit amet diam. + +Morbi lorem enim, euismod eu varius ut, scelerisque quis odio. Nam tempus vitae eros id molestie. Nunc pretium in nulla eget accumsan. Quisque mattis est ut semper aliquet. Maecenas eget diam elementum, fermentum ipsum a, euismod sapien. Duis quam ligula, cursus et velit nec, ullamcorper tincidunt magna. Donec vulputate nisl est, et ullamcorper urna tempor sit amet. + +Proin lacinia dui non turpis congue pretium. Morbi posuere metus vel purus imperdiet interdum. Morbi venenatis vel eros non ultricies. Nulla vel semper elit. Ut quis purus tincidunt, auctor justo ut, faucibus turpis. Proin quis mattis erat, at faucibus ligula. Mauris in mauris enim. Donec facilisis enim at est feugiat hendrerit. Nam vel nisi lorem. Fusce ultricies convallis diam, in feugiat tortor luctus quis. Donec tempor, leo vitae volutpat aliquam, magna elit feugiat leo, quis placerat sapien felis eget arcu. Donec ornare fermentum eleifend. Integer a est orci. + +Proin rhoncus egestas leo. Nulla ultricies porta elit quis ornare. Nunc fermentum interdum vehicula. In in ligula lorem. Donec nec arcu sit amet orci lobortis iaculis. Mauris at mollis erat, sit amet mollis tortor. Mauris laoreet justo ullamcorper porttitor auctor. Aenean sit amet aliquam lectus, id fermentum eros. Praesent urna sem, vehicula ac fermentum id, dapibus ut purus. Vestibulum vitae tempus nunc. Donec at nunc ornare metus volutpat porta at eget magna. Donec varius aliquet metus, eu lobortis risus aliquam sed. Ut dapibus fermentum velit, ac tincidunt libero faucibus at. + +In in purus auctor, feugiat massa quis, facilisis nisi. Donec dolor purus, gravida eget dolor ac, porttitor imperdiet urna. Donec faucibus placerat erat, a sagittis ante finibus ac. Sed venenatis dignissim elit, in iaculis felis posuere faucibus. Praesent sed viverra dolor. Mauris sed nulla consectetur nunc laoreet molestie in ut metus. Proin ac ex sit amet magna vulputate hendrerit ac condimentum urna. Proin ligula metus, gravida et sollicitudin facilisis, iaculis ut odio. Cras tincidunt urna et augue varius, ut facilisis urna consequat. Aenean vehicula finibus quam. Ut iaculis eu diam ac mollis. Nam mi lorem, tristique eget varius at, sodales at urna. + +Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Proin vitae dictum erat, et auctor ipsum. Nullam nunc nunc, sollicitudin quis magna a, vestibulum fermentum mauris. Praesent at erat dolor. Proin laoreet tristique nulla vel efficitur. Nam sed ultrices nibh, id rutrum nunc. Curabitur eleifend a erat sit amet sollicitudin. Nullam metus quam, laoreet vitae dapibus id, placerat sed leo. Aliquam erat volutpat. Donec turpis nisl, cursus eu ex sit amet, lacinia pellentesque nisl. Sed id ipsum massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec interdum scelerisque lorem eu mattis. + +Vivamus ac tristique massa, nec facilisis nisl. Nam ipsum neque, tincidunt vel urna in, cursus imperdiet enim. Nam pellentesque egestas tempus. Morbi facilisis imperdiet libero vitae fringilla. Nam lacinia ligula at sapien facilisis malesuada. Nullam accumsan pulvinar sem, et cursus libero porta sit amet. Curabitur vulputate erat elit, ut pulvinar erat maximus vel. + +Cras aliquet metus ut purus sagittis, vel venenatis ante consectetur. Pellentesque nulla lacus, viverra viverra mattis non, placerat vitae nibh. Donec enim turpis, accumsan sit amet tincidunt eu, imperdiet non metus. Morbi ipsum eros, tincidunt vel est ac, tristique porttitor nibh. Praesent ut ullamcorper mauris. Sed laoreet sit amet diam congue venenatis. Integer porta purus nec orci sagittis posuere. + +Donec vehicula mauris eget lacus mollis venenatis et sed nibh. Nam sodales ligula ipsum, scelerisque lacinia ligula sagittis in. Nam sit amet ipsum at erat malesuada congue. Aenean ut sollicitudin sapien. Etiam at tempor odio. Mauris vitae purus ut magna suscipit consequat. Vivamus quis sapien neque. Nulla vulputate sem sit amet massa pellentesque, eleifend tristique ligula egestas. Suspendisse tincidunt gravida mi, in pulvinar lectus egestas non. Aenean imperdiet ex sit amet nunc sollicitudin porta. Integer justo odio, ultricies at interdum in, rhoncus vitae sem. Sed porttitor arcu quis purus aliquet hendrerit. Praesent tempor tortor at dolor dictum pulvinar. Nulla aliquet nunc non ligula scelerisque accumsan. Donec nulla justo, congue vitae massa in, faucibus hendrerit magna. Donec non egestas purus. + +Vivamus iaculis, lacus efficitur faucibus porta, dui nulla facilisis ligula, ut sodales odio nunc id sapien. Cras viverra auctor ipsum, dapibus mattis neque dictum sed. Sed convallis fermentum molestie. Nulla facilisi turpis duis. diff --git a/src/vs/platform/instantiation/common/extensions.ts b/src/vs/platform/instantiation/common/extensions.ts index 257054074d20..2c5bbe9646d7 100644 --- a/src/vs/platform/instantiation/common/extensions.ts +++ b/src/vs/platform/instantiation/common/extensions.ts @@ -4,11 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import { SyncDescriptor } from './descriptors'; -import { ServiceIdentifier, IConstructorSignature0 } from './instantiation'; +import { ServiceIdentifier, BrandedService } from './instantiation'; const _registry: [ServiceIdentifier, SyncDescriptor][] = []; -export function registerSingleton(id: ServiceIdentifier, ctor: IConstructorSignature0, supportsDelayedInstantiation?: boolean): void { +export function registerSingleton(id: ServiceIdentifier, ctor: { new(...services: Services): T }, supportsDelayedInstantiation?: boolean): void { _registry.push([id, new SyncDescriptor(ctor, [], supportsDelayedInstantiation)]); } diff --git a/src/vs/platform/instantiation/common/instantiation.ts b/src/vs/platform/instantiation/common/instantiation.ts index 06b635015b4e..047e5c4092ef 100644 --- a/src/vs/platform/instantiation/common/instantiation.ts +++ b/src/vs/platform/instantiation/common/instantiation.ts @@ -22,7 +22,7 @@ export namespace _util { // --- interfaces ------ -type BrandedService = { _serviceBrand: undefined }; +export type BrandedService = { _serviceBrand: undefined }; export interface IConstructorSignature0 { new(...services: BrandedService[]): T; @@ -67,6 +67,22 @@ export interface ServicesAccessor { export const IInstantiationService = createDecorator('instantiationService'); +/** + * Given a list of arguments as a tuple, attempt to extract the leading, non-service arguments + * to their own tuple. + */ +type GetLeadingNonServiceArgs = + Args extends [...BrandedService[]] ? [] + : Args extends [infer A1, ...BrandedService[]] ? [A1] + : Args extends [infer A1, infer A2, ...BrandedService[]] ? [A1, A2] + : Args extends [infer A1, infer A2, infer A3, ...BrandedService[]] ? [A1, A2, A3] + : Args extends [infer A1, infer A2, infer A3, infer A4, ...BrandedService[]] ? [A1, A2, A3, A4] + : Args extends [infer A1, infer A2, infer A3, infer A4, infer A5, ...BrandedService[]] ? [A1, A2, A3, A4, A5] + : Args extends [infer A1, infer A2, infer A3, infer A4, infer A5, infer A6, ...BrandedService[]] ? [A1, A2, A3, A4, A5, A6] + : Args extends [infer A1, infer A2, infer A3, infer A4, infer A5, infer A6, infer A7, ...BrandedService[]] ? [A1, A2, A3, A4, A5, A6, A7] + : Args extends [infer A1, infer A2, infer A3, infer A4, infer A5, infer A6, infer A7, infer A8, ...BrandedService[]] ? [A1, A2, A3, A4, A5, A6, A7, A8] + : never; + export interface IInstantiationService { _serviceBrand: undefined; @@ -85,15 +101,8 @@ export interface IInstantiationService { createInstance(descriptor: descriptors.SyncDescriptor7, a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6, a7: A7): T; createInstance(descriptor: descriptors.SyncDescriptor8, a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6, a7: A7, a8: A8): T; - createInstance(ctor: IConstructorSignature0): T; - createInstance(ctor: IConstructorSignature1, first: A1): T; - createInstance(ctor: IConstructorSignature2, first: A1, second: A2): T; - createInstance(ctor: IConstructorSignature3, first: A1, second: A2, third: A3): T; - createInstance(ctor: IConstructorSignature4, first: A1, second: A2, third: A3, fourth: A4): T; - createInstance(ctor: IConstructorSignature5, first: A1, second: A2, third: A3, fourth: A4, fifth: A5): T; - createInstance(ctor: IConstructorSignature6, first: A1, second: A2, third: A3, fourth: A4, fifth: A5, sixth: A6): T; - createInstance(ctor: IConstructorSignature7, first: A1, second: A2, third: A3, fourth: A4, fifth: A5, sixth: A6, seventh: A7): T; - createInstance(ctor: IConstructorSignature8, first: A1, second: A2, third: A3, fourth: A4, fifth: A5, sixth: A6, seventh: A7, eigth: A8): T; + createInstance any, R extends InstanceType>(t: Ctor, ...args: GetLeadingNonServiceArgs>): R; + createInstance any, R extends InstanceType>(t: Ctor): R; /** * diff --git a/src/vs/platform/instantiation/common/instantiationService.ts b/src/vs/platform/instantiation/common/instantiationService.ts index 1b259194d800..e5cae20e43ae 100644 --- a/src/vs/platform/instantiation/common/instantiationService.ts +++ b/src/vs/platform/instantiation/common/instantiationService.ts @@ -157,7 +157,7 @@ export class InstantiationService implements IInstantiationService { graph.lookupOrInsertNode(item); // a weak but working heuristic for cycle checks - if (cycleCount++ > 150) { // {{SQL CARBON EDIT}} we hit ~102 with our services + if (cycleCount++ > 150) { throw new CyclicDependencyError(graph); } diff --git a/src/vs/platform/keybinding/common/abstractKeybindingService.ts b/src/vs/platform/keybinding/common/abstractKeybindingService.ts index 5c0288b3a600..05ca3828c498 100644 --- a/src/vs/platform/keybinding/common/abstractKeybindingService.ts +++ b/src/vs/platform/keybinding/common/abstractKeybindingService.ts @@ -11,7 +11,7 @@ import { KeyCode, Keybinding, ResolvedKeybinding } from 'vs/base/common/keyCodes import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IContextKeyService, IContextKeyServiceTarget } from 'vs/platform/contextkey/common/contextkey'; -import { IKeybindingEvent, IKeybindingService, IKeyboardEvent } from 'vs/platform/keybinding/common/keybinding'; +import { IKeybindingEvent, IKeybindingService, IKeyboardEvent, KeybindingsSchemaContribution } from 'vs/platform/keybinding/common/keybinding'; import { IResolveResult, KeybindingResolver } from 'vs/platform/keybinding/common/keybindingResolver'; import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; import { INotificationService } from 'vs/platform/notification/common/notification'; @@ -57,6 +57,7 @@ export abstract class AbstractKeybindingService extends Disposable implements IK public abstract resolveKeybinding(keybinding: Keybinding): ResolvedKeybinding[]; public abstract resolveKeyboardEvent(keyboardEvent: IKeyboardEvent): ResolvedKeybinding; public abstract resolveUserBinding(userBinding: string): ResolvedKeybinding[]; + public abstract registerSchemaContribution(contribution: KeybindingsSchemaContribution): void; public abstract _dumpDebugInfo(): string; public abstract _dumpDebugInfoJSON(): string; @@ -64,11 +65,11 @@ export abstract class AbstractKeybindingService extends Disposable implements IK return ''; } - public getDefaultKeybindings(): ResolvedKeybindingItem[] { + public getDefaultKeybindings(): readonly ResolvedKeybindingItem[] { return this._getResolver().getDefaultKeybindings(); } - public getKeybindings(): ResolvedKeybindingItem[] { + public getKeybindings(): readonly ResolvedKeybindingItem[] { return this._getResolver().getKeybindings(); } diff --git a/src/vs/platform/keybinding/common/keybinding.ts b/src/vs/platform/keybinding/common/keybinding.ts index 5682946aba72..7fa513352f7b 100644 --- a/src/vs/platform/keybinding/common/keybinding.ts +++ b/src/vs/platform/keybinding/common/keybinding.ts @@ -4,7 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { Event } from 'vs/base/common/event'; -import { KeyCode, Keybinding, ResolvedKeybinding } from 'vs/base/common/keyCodes'; +import { IJSONSchema } from 'vs/base/common/jsonSchema'; +import { Keybinding, KeyCode, ResolvedKeybinding } from 'vs/base/common/keyCodes'; import { IContextKeyServiceTarget } from 'vs/platform/contextkey/common/contextkey'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IResolveResult } from 'vs/platform/keybinding/common/keybindingResolver'; @@ -38,6 +39,12 @@ export interface IKeyboardEvent { readonly code: string; } +export interface KeybindingsSchemaContribution { + readonly onDidChange?: Event; + + getSchemaAdditions(): IJSONSchema[]; +} + export const IKeybindingService = createDecorator('keybindingService'); export interface IKeybindingService { @@ -80,9 +87,9 @@ export interface IKeybindingService { getDefaultKeybindingsContent(): string; - getDefaultKeybindings(): ResolvedKeybindingItem[]; + getDefaultKeybindings(): readonly ResolvedKeybindingItem[]; - getKeybindings(): ResolvedKeybindingItem[]; + getKeybindings(): readonly ResolvedKeybindingItem[]; customKeybindingsCount(): number; @@ -92,6 +99,8 @@ export interface IKeybindingService { */ mightProducePrintableCharacter(event: IKeyboardEvent): boolean; + registerSchemaContribution(contribution: KeybindingsSchemaContribution): void; + _dumpDebugInfo(): string; _dumpDebugInfoJSON(): string; } diff --git a/src/vs/platform/keybinding/common/keybindingResolver.ts b/src/vs/platform/keybinding/common/keybindingResolver.ts index 5172a4052d5f..a1569b601f0b 100644 --- a/src/vs/platform/keybinding/common/keybindingResolver.ts +++ b/src/vs/platform/keybinding/common/keybindingResolver.ts @@ -215,11 +215,11 @@ export class KeybindingResolver { return this._defaultBoundCommands; } - public getDefaultKeybindings(): ResolvedKeybindingItem[] { + public getDefaultKeybindings(): readonly ResolvedKeybindingItem[] { return this._defaultKeybindings; } - public getKeybindings(): ResolvedKeybindingItem[] { + public getKeybindings(): readonly ResolvedKeybindingItem[] { return this._keybindings; } diff --git a/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts b/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts index 13f87aefa3c7..c1fba7c67b6d 100644 --- a/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts +++ b/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts @@ -87,6 +87,10 @@ suite('AbstractKeybindingService', () => { public _dumpDebugInfoJSON(): string { return ''; } + + public registerSchemaContribution() { + // noop + } } let createTestKeybindingService: (items: ResolvedKeybindingItem[], contextValue?: any) => TestKeybindingService = null!; diff --git a/src/vs/platform/keybinding/test/common/mockKeybindingService.ts b/src/vs/platform/keybinding/test/common/mockKeybindingService.ts index 0c1e2111b8a8..39111c9ee069 100644 --- a/src/vs/platform/keybinding/test/common/mockKeybindingService.ts +++ b/src/vs/platform/keybinding/test/common/mockKeybindingService.ts @@ -141,4 +141,8 @@ export class MockKeybindingService implements IKeybindingService { public _dumpDebugInfoJSON(): string { return ''; } + + public registerSchemaContribution() { + // noop + } } diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index 3ab9ba36f82c..b9c0fbb2f696 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -22,12 +22,12 @@ import { IEditorOptions } from 'vs/platform/editor/common/editor'; import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { Registry } from 'vs/platform/registry/common/platform'; -import { attachListStyler, computeStyles, defaultListStyles } from 'vs/platform/theme/common/styler'; +import { attachListStyler, computeStyles, defaultListStyles, IColorMapping, attachStyler } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkeys'; import { ObjectTree, IObjectTreeOptions, ICompressibleTreeRenderer, CompressibleObjectTree, ICompressibleObjectTreeOptions } from 'vs/base/browser/ui/tree/objectTree'; import { ITreeEvent, ITreeRenderer, IAsyncDataSource, IDataSource, ITreeMouseEvent } from 'vs/base/browser/ui/tree/tree'; -import { AsyncDataTree, IAsyncDataTreeOptions, CompressibleAsyncDataTree, ITreeCompressionDelegate } from 'vs/base/browser/ui/tree/asyncDataTree'; +import { AsyncDataTree, IAsyncDataTreeOptions, CompressibleAsyncDataTree, ITreeCompressionDelegate, ICompressibleAsyncDataTreeOptions } from 'vs/base/browser/ui/tree/asyncDataTree'; import { DataTree, IDataTreeOptions } from 'vs/base/browser/ui/tree/dataTree'; import { IKeyboardNavigationEventFilter, IAbstractTreeOptions, RenderIndentGuides } from 'vs/base/browser/ui/tree/abstractTree'; import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; @@ -55,6 +55,7 @@ export class ListService implements IListService { _serviceBrand: undefined; + private disposables = new DisposableStore(); private lists: IRegisteredList[] = []; private _lastFocusedWidget: ListWidget | undefined = undefined; @@ -62,7 +63,11 @@ export class ListService implements IListService { return this._lastFocusedWidget; } - constructor(@IContextKeyService contextKeyService: IContextKeyService) { } + constructor(@IThemeService themeService: IThemeService) { + // create a shared default tree style sheet for performance reasons + const styleController = new DefaultStyleController(createStyleSheet(), ''); + this.disposables.add(attachListStyler(styleController, themeService)); + } register(widget: ListWidget, extraContextKeys?: (IContextKey)[]): IDisposable { if (this.lists.some(l => l.widget === widget)) { @@ -89,6 +94,10 @@ export class ListService implements IListService { }) ); } + + dispose(): void { + this.disposables.dispose(); + } } const RawWorkbenchListFocusContextKey = new RawContextKey('listFocus', true); @@ -221,13 +230,8 @@ function toWorkbenchListOptions(options: IListOptions, configurationServic return [result, disposables]; } -let sharedListStyleSheet: HTMLStyleElement; -function getSharedListStyleSheet(): HTMLStyleElement { - if (!sharedListStyleSheet) { - sharedListStyleSheet = createStyleSheet(); - } - - return sharedListStyleSheet; +export interface IWorkbenchListOptions extends IListOptions { + readonly overrideStyles?: IColorMapping; } export class WorkbenchList extends List { @@ -246,7 +250,7 @@ export class WorkbenchList extends List { container: HTMLElement, delegate: IListVirtualDelegate, renderers: IListRenderer[], - options: IListOptions, + options: IWorkbenchListOptions, @IContextKeyService contextKeyService: IContextKeyService, @IListService listService: IListService, @IThemeService themeService: IThemeService, @@ -259,7 +263,6 @@ export class WorkbenchList extends List { super(user, container, delegate, renderers, { keyboardSupport: false, - styleController: new DefaultStyleController(getSharedListStyleSheet()), ...computeStyles(themeService.getTheme(), defaultListStyles), ...workbenchListOptions, horizontalScrolling @@ -282,7 +285,11 @@ export class WorkbenchList extends List { this.disposables.add(this.contextKeyService); this.disposables.add((listService as ListService).register(this)); - this.disposables.add(attachListStyler(this, themeService)); + + if (options.overrideStyles) { + this.disposables.add(attachStyler(themeService, options.overrideStyles, this)); + } + this.disposables.add(this.onSelectionChange(() => { const selection = this.getSelection(); const focus = this.getFocus(); @@ -328,7 +335,7 @@ export class WorkbenchPagedList extends PagedList { container: HTMLElement, delegate: IListVirtualDelegate, renderers: IPagedRenderer[], - options: IListOptions, + options: IWorkbenchListOptions, @IContextKeyService contextKeyService: IContextKeyService, @IListService listService: IListService, @IThemeService themeService: IThemeService, @@ -340,7 +347,6 @@ export class WorkbenchPagedList extends PagedList { super(user, container, delegate, renderers, { keyboardSupport: false, - styleController: new DefaultStyleController(getSharedListStyleSheet()), ...computeStyles(themeService.getTheme(), defaultListStyles), ...workbenchListOptions, horizontalScrolling @@ -360,7 +366,10 @@ export class WorkbenchPagedList extends PagedList { this.disposables.add(this.contextKeyService); this.disposables.add((listService as ListService).register(this)); - this.disposables.add(attachListStyler(this, themeService)); + + if (options.overrideStyles) { + this.disposables.add(attachStyler(themeService, options.overrideStyles, this)); + } this.registerListeners(); } @@ -777,6 +786,10 @@ function createKeyboardNavigationEventFilter(container: HTMLElement, keybindingS }; } +export interface IWorkbenchObjectTreeOptions extends IObjectTreeOptions { + readonly overrideStyles?: IColorMapping; +} + export class WorkbenchObjectTree, TFilterData = void> extends ObjectTree { private internals: WorkbenchTreeInternals; @@ -788,7 +801,7 @@ export class WorkbenchObjectTree, TFilterData = void> container: HTMLElement, delegate: IListVirtualDelegate, renderers: ITreeRenderer[], - options: IObjectTreeOptions, + options: IWorkbenchObjectTreeOptions, @IContextKeyService contextKeyService: IContextKeyService, @IListService listService: IListService, @IThemeService themeService: IThemeService, @@ -796,14 +809,18 @@ export class WorkbenchObjectTree, TFilterData = void> @IKeybindingService keybindingService: IKeybindingService, @IAccessibilityService accessibilityService: IAccessibilityService ) { - const { options: treeOptions, getAutomaticKeyboardNavigation, disposable } = workbenchTreeDataPreamble(container, options, contextKeyService, themeService, configurationService, keybindingService, accessibilityService); + const { options: treeOptions, getAutomaticKeyboardNavigation, disposable } = workbenchTreeDataPreamble(container, options, contextKeyService, configurationService, keybindingService, accessibilityService); super(user, container, delegate, renderers, treeOptions); this.disposables.add(disposable); - this.internals = new WorkbenchTreeInternals(this, treeOptions, getAutomaticKeyboardNavigation, contextKeyService, listService, themeService, configurationService, accessibilityService); + this.internals = new WorkbenchTreeInternals(this, treeOptions, getAutomaticKeyboardNavigation, options.overrideStyles, contextKeyService, listService, themeService, configurationService, accessibilityService); this.disposables.add(this.internals); } } +export interface IWorkbenchCompressibleObjectTreeOptions extends ICompressibleObjectTreeOptions { + readonly overrideStyles?: IColorMapping; +} + export class WorkbenchCompressibleObjectTree, TFilterData = void> extends CompressibleObjectTree { private internals: WorkbenchTreeInternals; @@ -815,7 +832,7 @@ export class WorkbenchCompressibleObjectTree, TFilter container: HTMLElement, delegate: IListVirtualDelegate, renderers: ICompressibleTreeRenderer[], - options: ICompressibleObjectTreeOptions, + options: IWorkbenchCompressibleObjectTreeOptions, @IContextKeyService contextKeyService: IContextKeyService, @IListService listService: IListService, @IThemeService themeService: IThemeService, @@ -823,14 +840,18 @@ export class WorkbenchCompressibleObjectTree, TFilter @IKeybindingService keybindingService: IKeybindingService, @IAccessibilityService accessibilityService: IAccessibilityService ) { - const { options: treeOptions, getAutomaticKeyboardNavigation, disposable } = workbenchTreeDataPreamble(container, options, contextKeyService, themeService, configurationService, keybindingService, accessibilityService); + const { options: treeOptions, getAutomaticKeyboardNavigation, disposable } = workbenchTreeDataPreamble(container, options, contextKeyService, configurationService, keybindingService, accessibilityService); super(user, container, delegate, renderers, treeOptions); this.disposables.add(disposable); - this.internals = new WorkbenchTreeInternals(this, treeOptions, getAutomaticKeyboardNavigation, contextKeyService, listService, themeService, configurationService, accessibilityService); + this.internals = new WorkbenchTreeInternals(this, treeOptions, getAutomaticKeyboardNavigation, options.overrideStyles, contextKeyService, listService, themeService, configurationService, accessibilityService); this.disposables.add(this.internals); } } +export interface IWorkbenchDataTreeOptions extends IDataTreeOptions { + readonly overrideStyles?: IColorMapping; +} + export class WorkbenchDataTree extends DataTree { private internals: WorkbenchTreeInternals; @@ -843,7 +864,7 @@ export class WorkbenchDataTree extends DataTree, renderers: ITreeRenderer[], dataSource: IDataSource, - options: IDataTreeOptions, + options: IWorkbenchDataTreeOptions, @IContextKeyService contextKeyService: IContextKeyService, @IListService listService: IListService, @IThemeService themeService: IThemeService, @@ -851,14 +872,18 @@ export class WorkbenchDataTree extends DataTree extends IAsyncDataTreeOptions { + readonly overrideStyles?: IColorMapping; +} + export class WorkbenchAsyncDataTree extends AsyncDataTree { private internals: WorkbenchTreeInternals; @@ -871,7 +896,7 @@ export class WorkbenchAsyncDataTree extends Async delegate: IListVirtualDelegate, renderers: ITreeRenderer[], dataSource: IAsyncDataSource, - options: IAsyncDataTreeOptions, + options: IWorkbenchAsyncDataTreeOptions, @IContextKeyService contextKeyService: IContextKeyService, @IListService listService: IListService, @IThemeService themeService: IThemeService, @@ -879,14 +904,18 @@ export class WorkbenchAsyncDataTree extends Async @IKeybindingService keybindingService: IKeybindingService, @IAccessibilityService accessibilityService: IAccessibilityService ) { - const { options: treeOptions, getAutomaticKeyboardNavigation, disposable } = workbenchTreeDataPreamble(container, options, contextKeyService, themeService, configurationService, keybindingService, accessibilityService); + const { options: treeOptions, getAutomaticKeyboardNavigation, disposable } = workbenchTreeDataPreamble(container, options, contextKeyService, configurationService, keybindingService, accessibilityService); super(user, container, delegate, renderers, dataSource, treeOptions); this.disposables.add(disposable); - this.internals = new WorkbenchTreeInternals(this, treeOptions, getAutomaticKeyboardNavigation, contextKeyService, listService, themeService, configurationService, accessibilityService); + this.internals = new WorkbenchTreeInternals(this, treeOptions, getAutomaticKeyboardNavigation, options.overrideStyles, contextKeyService, listService, themeService, configurationService, accessibilityService); this.disposables.add(this.internals); } } +export interface IWorkbenchCompressibleAsyncDataTreeOptions extends ICompressibleAsyncDataTreeOptions { + readonly overrideStyles?: IColorMapping; +} + export class WorkbenchCompressibleAsyncDataTree extends CompressibleAsyncDataTree { private internals: WorkbenchTreeInternals; @@ -900,7 +929,7 @@ export class WorkbenchCompressibleAsyncDataTree e compressionDelegate: ITreeCompressionDelegate, renderers: ICompressibleTreeRenderer[], dataSource: IAsyncDataSource, - options: IAsyncDataTreeOptions, + options: IWorkbenchCompressibleAsyncDataTreeOptions, @IContextKeyService contextKeyService: IContextKeyService, @IListService listService: IListService, @IThemeService themeService: IThemeService, @@ -908,10 +937,10 @@ export class WorkbenchCompressibleAsyncDataTree e @IKeybindingService keybindingService: IKeybindingService, @IAccessibilityService accessibilityService: IAccessibilityService ) { - const { options: treeOptions, getAutomaticKeyboardNavigation, disposable } = workbenchTreeDataPreamble(container, options, contextKeyService, themeService, configurationService, keybindingService, accessibilityService); + const { options: treeOptions, getAutomaticKeyboardNavigation, disposable } = workbenchTreeDataPreamble(container, options, contextKeyService, configurationService, keybindingService, accessibilityService); super(user, container, virtualDelegate, compressionDelegate, renderers, dataSource, treeOptions); this.disposables.add(disposable); - this.internals = new WorkbenchTreeInternals(this, treeOptions, getAutomaticKeyboardNavigation, contextKeyService, listService, themeService, configurationService, accessibilityService); + this.internals = new WorkbenchTreeInternals(this, treeOptions, getAutomaticKeyboardNavigation, options.overrideStyles, contextKeyService, listService, themeService, configurationService, accessibilityService); this.disposables.add(this.internals); } } @@ -920,7 +949,6 @@ function workbenchTreeDataPreamble(treeIndentKey), renderIndentGuides: configurationService.getValue(treeRenderIndentGuidesKey), @@ -966,7 +993,8 @@ function workbenchTreeDataPreamble { tree: WorkbenchObjectTree | CompressibleObjectTree | WorkbenchDataTree | WorkbenchAsyncDataTree | WorkbenchCompressibleAsyncDataTree, options: IAbstractTreeOptions | IAsyncDataTreeOptions, getAutomaticKeyboardNavigation: () => boolean | undefined, + overrideStyles: IColorMapping | undefined, @IContextKeyService contextKeyService: IContextKeyService, @IListService listService: IListService, @IThemeService themeService: IThemeService, @@ -1015,7 +1044,7 @@ class WorkbenchTreeInternals { this.disposables.push( this.contextKeyService, (listService as ListService).register(tree), - attachListStyler(tree, themeService), + overrideStyles ? attachStyler(themeService, overrideStyles, tree) : Disposable.None, tree.onDidChangeSelection(() => { const selection = tree.getSelection(); const focus = tree.getFocus(); diff --git a/src/vs/platform/menubar/electron-main/menubar.ts b/src/vs/platform/menubar/electron-main/menubar.ts index 25c0c9cdf082..160041e1f04e 100644 --- a/src/vs/platform/menubar/electron-main/menubar.ts +++ b/src/vs/platform/menubar/electron-main/menubar.ts @@ -560,8 +560,8 @@ export class Menubar { label: this.mnemonicLabel(nls.localize('miCheckForUpdates', "Check for &&Updates...")), click: () => setTimeout(() => { this.reportMenuActionTelemetry('CheckForUpdate'); - const focusedWindow = BrowserWindow.getFocusedWindow(); - const context = focusedWindow ? { windowId: focusedWindow.id } : null; + const window = this.windowsMainService.getLastActiveWindow(); + const context = window && `window:${window.id}`; // sessionId this.updateService.checkForUpdates(context); }, 0) })]; diff --git a/src/vs/platform/opener/common/opener.ts b/src/vs/platform/opener/common/opener.ts index 7e8abbc118c2..8f2f72cd9376 100644 --- a/src/vs/platform/opener/common/opener.ts +++ b/src/vs/platform/opener/common/opener.ts @@ -6,6 +6,7 @@ import { URI } from 'vs/base/common/uri'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; +import { equalsIgnoreCase, startsWithIgnoreCase } from 'vs/base/common/strings'; export const IOpenerService = createDecorator('openerService'); @@ -28,13 +29,22 @@ type OpenExternalOptions = { readonly openExternal?: boolean; readonly allowTunn export type OpenOptions = OpenInternalOptions & OpenExternalOptions; +export type ResolveExternalUriOptions = { readonly allowTunneling?: boolean }; + +export interface IResolvedExternalUri extends IDisposable { + resolved: URI; +} + export interface IOpener { - open(resource: URI, options?: OpenInternalOptions): Promise; - open(resource: URI, options?: OpenExternalOptions): Promise; + open(resource: URI | string, options?: OpenInternalOptions | OpenExternalOptions): Promise; +} + +export interface IExternalOpener { + openExternal(href: string): Promise; } export interface IValidator { - shouldOpen(resource: URI): Promise; + shouldOpen(resource: URI | string): Promise; } export interface IExternalUriResolver { @@ -61,16 +71,24 @@ export interface IOpenerService { */ registerExternalUriResolver(resolver: IExternalUriResolver): IDisposable; + /** + * Sets the handler for opening externally. If not provided, + * a default handler will be used. + */ + setExternalOpener(opener: IExternalOpener): void; + /** * Opens a resource, like a webaddress, a document uri, or executes command. * * @param resource A resource * @return A promise that resolves when the opening is done. */ - open(resource: URI, options?: OpenInternalOptions): Promise; - open(resource: URI, options?: OpenExternalOptions): Promise; + open(resource: URI | string, options?: OpenInternalOptions | OpenExternalOptions): Promise; - resolveExternalUri(resource: URI, options?: { readonly allowTunneling?: boolean }): Promise<{ resolved: URI, dispose(): void }>; + /** + * Resolve a resource to its external form. + */ + resolveExternalUri(resource: URI, options?: ResolveExternalUriOptions): Promise; } export const NullOpenerService: IOpenerService = Object.freeze({ @@ -78,6 +96,15 @@ export const NullOpenerService: IOpenerService = Object.freeze({ registerOpener() { return Disposable.None; }, registerValidator() { return Disposable.None; }, registerExternalUriResolver() { return Disposable.None; }, - open() { return Promise.resolve(false); }, + setExternalOpener() { }, + async open() { return false; }, async resolveExternalUri(uri: URI) { return { resolved: uri, dispose() { } }; }, }); + +export function matchesScheme(target: URI | string, scheme: string) { + if (URI.isUri(target)) { + return equalsIgnoreCase(target.scheme, scheme); + } else { + return startsWithIgnoreCase(target, scheme + ':'); + } +} diff --git a/src/vs/platform/product/common/product.ts b/src/vs/platform/product/common/product.ts index 57cd734c2afb..c6b3b6cc8135 100644 --- a/src/vs/platform/product/common/product.ts +++ b/src/vs/platform/product/common/product.ts @@ -21,10 +21,11 @@ if (isWeb) { // Running out of sources if (Object.keys(product).length === 0) { assign(product, { - version: '1.39.0-dev', + version: '1.41.0-dev', vscodeVersion: '1.39.0-dev', nameLong: 'Visual Studio Code Web Dev', - nameShort: 'VSCode Web Dev' + nameShort: 'VSCode Web Dev', + urlProtocol: 'code-oss' }); } } diff --git a/src/vs/platform/product/common/productService.ts b/src/vs/platform/product/common/productService.ts index 812a687ebcac..fb6098b8a644 100644 --- a/src/vs/platform/product/common/productService.ts +++ b/src/vs/platform/product/common/productService.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { ExtensionKind } from 'vs/platform/extensions/common/extensions'; export const IProductService = createDecorator('productService'); @@ -101,13 +102,18 @@ export interface IProductConfiguration { readonly portable?: string; - readonly uiExtensions?: readonly string[]; + readonly extensionKind?: { readonly [extensionId: string]: ExtensionKind | ExtensionKind[]; }; readonly extensionAllowedProposedApi?: readonly string[]; readonly msftInternalDomains?: string[]; readonly linkProtectionTrustedDomains?: readonly string[]; - readonly settingsSyncStoreUrl?: string; + readonly auth?: { + loginUrl: string; + tokenUrl: string; + redirectUrl: string; + clientId: string; + }; } export interface IExeBasedExtensionTip { diff --git a/src/vs/platform/quickinput/common/quickInput.ts b/src/vs/platform/quickinput/common/quickInput.ts index 9b5b23059609..73adf3269bd1 100644 --- a/src/vs/platform/quickinput/common/quickInput.ts +++ b/src/vs/platform/quickinput/common/quickInput.ts @@ -168,9 +168,9 @@ export interface IQuickPick extends IQuickInput { customButton: boolean; - customLabel: string; + customLabel: string | undefined; - customHover: string; + customHover: string | undefined; buttons: ReadonlyArray; @@ -188,6 +188,8 @@ export interface IQuickPick extends IQuickInput { matchOnLabel: boolean; + sortByLabel: boolean; + autoFocusOnList: boolean; quickNavigate: IQuickNavigateConfiguration | undefined; diff --git a/src/vs/platform/remote/common/remoteAgentConnection.ts b/src/vs/platform/remote/common/remoteAgentConnection.ts index b910f899bbe7..b75f9e0d10b2 100644 --- a/src/vs/platform/remote/common/remoteAgentConnection.ts +++ b/src/vs/platform/remote/common/remoteAgentConnection.ts @@ -89,8 +89,8 @@ async function connectToRemoteExtensionHostAgent(options: ISimpleConnectionOptio options.host, options.port, `reconnectionToken=${options.reconnectionToken}&reconnection=${options.reconnectionProtocol ? 'true' : 'false'}`, - (err: any, socket: ISocket) => { - if (err) { + (err: any, socket: ISocket | undefined) => { + if (err || !socket) { options.logService.error(`${logPrefix} socketFactory.connect() failed. Error:`); options.logService.error(err); e(err); diff --git a/src/vs/platform/remote/common/remoteAgentFileSystemChannel.ts b/src/vs/platform/remote/common/remoteAgentFileSystemChannel.ts index 92880756ab13..6604584f8560 100644 --- a/src/vs/platform/remote/common/remoteAgentFileSystemChannel.ts +++ b/src/vs/platform/remote/common/remoteAgentFileSystemChannel.ts @@ -8,10 +8,13 @@ import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle' import { URI, UriComponents } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; -import { FileChangeType, FileDeleteOptions, FileOverwriteOptions, FileSystemProviderCapabilities, FileType, IFileChange, IFileSystemProvider, IStat, IWatchOptions, FileOpenOptions } from 'vs/platform/files/common/files'; +import { FileChangeType, FileDeleteOptions, FileOverwriteOptions, FileSystemProviderCapabilities, FileType, IFileChange, IStat, IWatchOptions, FileOpenOptions, IFileSystemProviderWithFileReadWriteCapability, FileWriteOptions, IFileSystemProviderWithFileReadStreamCapability, IFileSystemProviderWithFileFolderCopyCapability, FileReadStreamOptions, IFileSystemProviderWithOpenReadWriteCloseCapability } from 'vs/platform/files/common/files'; import { VSBuffer } from 'vs/base/common/buffer'; import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment'; import { OperatingSystem } from 'vs/base/common/platform'; +import { newWriteableStream, ReadableStreamEvents, ReadableStreamEventPayload } from 'vs/base/common/stream'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { canceled } from 'vs/base/common/errors'; export const REMOTE_FILE_SYSTEM_CHANNEL_NAME = 'remotefilesystem'; @@ -20,7 +23,11 @@ export interface IFileChangeDto { type: FileChangeType; } -export class RemoteExtensionsFileSystemProvider extends Disposable implements IFileSystemProvider { +export class RemoteFileSystemProvider extends Disposable implements + IFileSystemProviderWithFileReadWriteCapability, + IFileSystemProviderWithOpenReadWriteCloseCapability, + IFileSystemProviderWithFileReadStreamCapability, + IFileSystemProviderWithFileFolderCopyCapability { private readonly session: string = generateUuid(); @@ -46,7 +53,7 @@ export class RemoteExtensionsFileSystemProvider extends Disposable implements IF } private registerListeners(): void { - this._register(this.channel.listen('filechange', [this.session])((eventsOrError) => { + this._register(this.channel.listen('filechange', [this.session])(eventsOrError => { if (Array.isArray(eventsOrError)) { const events = eventsOrError; this._onDidChange.fire(events.map(event => ({ resource: URI.revive(event.resource), type: event.type }))); @@ -59,7 +66,9 @@ export class RemoteExtensionsFileSystemProvider extends Disposable implements IF setCaseSensitive(isCaseSensitive: boolean) { let capabilities = ( - FileSystemProviderCapabilities.FileOpenReadWriteClose + FileSystemProviderCapabilities.FileReadWrite + | FileSystemProviderCapabilities.FileOpenReadWriteClose + | FileSystemProviderCapabilities.FileReadStream | FileSystemProviderCapabilities.FileFolderCopy ); @@ -97,10 +106,57 @@ export class RemoteExtensionsFileSystemProvider extends Disposable implements IF return bytesRead; } + async readFile(resource: URI): Promise { + const buff = await this.channel.call('readFile', [resource]); + + return buff.buffer; + } + + readFileStream(resource: URI, opts: FileReadStreamOptions, token?: CancellationToken): ReadableStreamEvents { + const stream = newWriteableStream(data => VSBuffer.concat(data.map(data => VSBuffer.wrap(data))).buffer); + + // Reading as file stream goes through an event to the remote side + const listener = this.channel.listen>('readFileStream', [resource, opts])(dataOrErrorOrEnd => { + if (dataOrErrorOrEnd instanceof VSBuffer) { + + // data: forward into the stream + stream.write(dataOrErrorOrEnd.buffer); + } else { + + // error / end: always end the stream on errors too + stream.end(dataOrErrorOrEnd === 'end' ? undefined : dataOrErrorOrEnd); + + // Signal to the remote side that we no longer listen + listener.dispose(); + } + }); + + // Support cancellation + if (token) { + Event.once(token.onCancellationRequested)(() => { + + // Ensure to end the stream properly with an error + // to indicate the cancellation. + stream.end(canceled()); + + // Ensure to dispose the listener upon cancellation. This will + // bubble through the remote side as event and allows to stop + // reading the file. + listener.dispose(); + }); + } + + return stream; + } + write(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise { return this.channel.call('write', [fd, pos, VSBuffer.wrap(data), offset, length]); } + writeFile(resource: URI, content: Uint8Array, opts: FileWriteOptions): Promise { + return this.channel.call('writeFile', [resource, VSBuffer.wrap(content), opts]); + } + delete(resource: URI, opts: FileDeleteOptions): Promise { return this.channel.call('delete', [resource, opts]); } diff --git a/src/vs/platform/remote/common/tunnel.ts b/src/vs/platform/remote/common/tunnel.ts index 919b2c46a18d..5689ad5ff0d2 100644 --- a/src/vs/platform/remote/common/tunnel.ts +++ b/src/vs/platform/remote/common/tunnel.ts @@ -5,13 +5,14 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { URI } from 'vs/base/common/uri'; +import { Event } from 'vs/base/common/event'; export const ITunnelService = createDecorator('tunnelService'); export interface RemoteTunnel { readonly tunnelRemotePort: number; readonly tunnelLocalPort: number; - + readonly localAddress?: string; dispose(): void; } @@ -19,8 +20,11 @@ export interface ITunnelService { _serviceBrand: undefined; readonly tunnels: Promise; + readonly onTunnelOpened: Event; + readonly onTunnelClosed: Event; - openTunnel(remotePort: number): Promise | undefined; + openTunnel(remotePort: number, localPort?: number): Promise | undefined; + closeTunnel(remotePort: number): Promise; } export function extractLocalHostUriMetaDataForPortMapping(uri: URI): { address: string, port: number } | undefined { diff --git a/src/vs/platform/remote/common/tunnelService.ts b/src/vs/platform/remote/common/tunnelService.ts index 2292d97fdcce..a3719a715efe 100644 --- a/src/vs/platform/remote/common/tunnelService.ts +++ b/src/vs/platform/remote/common/tunnelService.ts @@ -4,13 +4,19 @@ *--------------------------------------------------------------------------------------------*/ import { ITunnelService, RemoteTunnel } from 'vs/platform/remote/common/tunnel'; +import { Event, Emitter } from 'vs/base/common/event'; export class NoOpTunnelService implements ITunnelService { _serviceBrand: undefined; public readonly tunnels: Promise = Promise.resolve([]); - + private _onTunnelOpened: Emitter = new Emitter(); + public onTunnelOpened: Event = this._onTunnelOpened.event; + private _onTunnelClosed: Emitter = new Emitter(); + public onTunnelClosed: Event = this._onTunnelClosed.event; openTunnel(_remotePort: number): Promise | undefined { return undefined; } + async closeTunnel(_remotePort: number): Promise { + } } diff --git a/src/vs/platform/request/common/request.ts b/src/vs/platform/request/common/request.ts index 346dff23d1ca..e9ae973f081e 100644 --- a/src/vs/platform/request/common/request.ts +++ b/src/vs/platform/request/common/request.ts @@ -69,7 +69,7 @@ Registry.as(Extensions.Configuration) properties: { 'http.proxy': { type: 'string', - pattern: '^https?://([^:]*(:[^@]*)?@)?([^:]+)(:\\d+)?/?$|^$', + pattern: '^https?://([^:]*(:[^@]*)?@)?([^:]+|\\[[:0-9a-fA-F]+\\])(:\\d+)?/?$|^$', markdownDescription: localize('proxy', "The proxy setting to use. If not set, will be inherited from the `http_proxy` and `https_proxy` environment variables.") }, 'http.proxyStrictSSL': { diff --git a/src/vs/platform/request/node/proxy.ts b/src/vs/platform/request/node/proxy.ts index 8657157d86a6..324829df9918 100644 --- a/src/vs/platform/request/node/proxy.ts +++ b/src/vs/platform/request/node/proxy.ts @@ -39,12 +39,12 @@ export async function getProxyAgent(rawRequestURL: string, options: IOptions = { const opts = { host: proxyEndpoint.hostname || '', - port: Number(proxyEndpoint.port), + port: proxyEndpoint.port || (proxyEndpoint.protocol === 'https' ? '443' : '80'), auth: proxyEndpoint.auth, - rejectUnauthorized: isBoolean(options.strictSSL) ? options.strictSSL : true + rejectUnauthorized: isBoolean(options.strictSSL) ? options.strictSSL : true, }; return requestURL.protocol === 'http:' - ? new (await import('http-proxy-agent'))(opts) + ? new (await import('http-proxy-agent'))(opts as any as Url) : new (await import('https-proxy-agent'))(opts); } diff --git a/src/vs/platform/request/node/requestService.ts b/src/vs/platform/request/node/requestService.ts index 8fe3c942d122..986622b53cb2 100644 --- a/src/vs/platform/request/node/requestService.ts +++ b/src/vs/platform/request/node/requestService.ts @@ -5,7 +5,7 @@ import * as https from 'https'; import * as http from 'http'; -import { Stream } from 'stream'; +import * as streams from 'vs/base/common/stream'; import { createGunzip } from 'zlib'; import { parse as parseUrl } from 'url'; import { Disposable } from 'vs/base/common/lifecycle'; @@ -18,7 +18,7 @@ import { IRequestOptions, IRequestContext } from 'vs/base/parts/request/common/r import { getProxyAgent, Agent } from 'vs/platform/request/node/proxy'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILogService } from 'vs/platform/log/common/log'; -import { toVSBufferReadableStream } from 'vs/base/common/buffer'; +import { streamToBufferReadableStream } from 'vs/base/common/buffer'; export interface IRawRequestFunction { (options: http.RequestOptions, callback?: (res: http.IncomingMessage) => void): http.ClientRequest; @@ -112,13 +112,13 @@ export class RequestService extends Disposable implements IRequestService { followRedirects: followRedirects - 1 }), token).then(c, e); } else { - let stream: Stream = res; + let stream: streams.ReadableStream = res; if (res.headers['content-encoding'] === 'gzip') { - stream = stream.pipe(createGunzip()); + stream = res.pipe(createGunzip()); } - c({ res, stream: toVSBufferReadableStream(stream) } as IRequestContext); + c({ res, stream: streamToBufferReadableStream(stream) } as IRequestContext); } }); diff --git a/src/vs/platform/severityIcon/common/severityIcon.ts b/src/vs/platform/severityIcon/common/severityIcon.ts index 7cc3a81a9643..c3374a4232d3 100644 --- a/src/vs/platform/severityIcon/common/severityIcon.ts +++ b/src/vs/platform/severityIcon/common/severityIcon.ts @@ -31,7 +31,8 @@ registerThemingParticipant((theme, collector) => { collector.addRule(` .monaco-workbench .zone-widget .codicon-error, .monaco-workbench .markers-panel .marker-icon.codicon-error, - .monaco-workbench .extensions-viewlet > .extensions .codicon-error { + .monaco-workbench .extensions-viewlet > .extensions .codicon-error, + .monaco-workbench .dialog-box .dialog-message-row .codicon-error { color: ${errorIconForeground}; } `); @@ -42,7 +43,9 @@ registerThemingParticipant((theme, collector) => { collector.addRule(` .monaco-workbench .zone-widget .codicon-warning, .monaco-workbench .markers-panel .marker-icon.codicon-warning, - .monaco-workbench .extensions-viewlet > .extensions .codicon-warning { + .monaco-workbench .extensions-viewlet > .extensions .codicon-warning, + .monaco-workbench .extension-editor .codicon-warning, + .monaco-workbench .dialog-box .dialog-message-row .codicon-warning { color: ${warningIconForeground}; } `); @@ -52,7 +55,10 @@ registerThemingParticipant((theme, collector) => { if (errorIconForeground) { collector.addRule(` .monaco-workbench .zone-widget .codicon-info, - .monaco-workbench .markers-panel .marker-icon.codicon-info { + .monaco-workbench .markers-panel .marker-icon.codicon-info, + .monaco-workbench .extensions-viewlet > .extensions .codicon-info, + .monaco-workbench .extension-editor .codicon-info, + .monaco-workbench .dialog-box .dialog-message-row .codicon-info { color: ${infoIconForeground}; } `); diff --git a/src/vs/platform/storage/node/storageIpc.ts b/src/vs/platform/storage/node/storageIpc.ts index 5569dcbd49e6..a29e15205952 100644 --- a/src/vs/platform/storage/node/storageIpc.ts +++ b/src/vs/platform/storage/node/storageIpc.ts @@ -83,7 +83,7 @@ export class GlobalStorageDatabaseChannel extends Disposable implements IServerC // Listen for changes in global storage to send to listeners // that are listening. Use a debouncer to reduce IPC traffic. - this._register(Event.debounce(this.storageMainService.onDidChangeStorage, (prev: IStorageChangeEvent[], cur: IStorageChangeEvent) => { + this._register(Event.debounce(this.storageMainService.onDidChangeStorage, (prev: IStorageChangeEvent[] | undefined, cur: IStorageChangeEvent) => { if (!prev) { prev = [cur]; } else { @@ -161,7 +161,7 @@ export class GlobalStorageDatabaseChannelClient extends Disposable implements IS } private registerListeners(): void { - this.onDidChangeItemsOnMainListener = this.channel.listen('onDidChangeItems')((e: ISerializableItemsChangeEvent) => this.onDidChangeItemsOnMain(e)); + this.onDidChangeItemsOnMainListener = this.channel.listen('onDidChangeItems')((e: ISerializableItemsChangeEvent) => this.onDidChangeItemsOnMain(e)); } private onDidChangeItemsOnMain(e: ISerializableItemsChangeEvent): void { diff --git a/src/vs/platform/telemetry/browser/workbenchCommonProperties.ts b/src/vs/platform/telemetry/browser/workbenchCommonProperties.ts index 08af83cba147..d2f574382cb8 100644 --- a/src/vs/platform/telemetry/browser/workbenchCommonProperties.ts +++ b/src/vs/platform/telemetry/browser/workbenchCommonProperties.ts @@ -9,6 +9,7 @@ export const instanceStorageKey = 'telemetry.instanceId'; export const currentSessionDateStorageKey = 'telemetry.currentSessionDate'; export const firstSessionDateStorageKey = 'telemetry.firstSessionDate'; export const lastSessionDateStorageKey = 'telemetry.lastSessionDate'; +export const machineIdKey = 'telemetry.machineId'; import * as Platform from 'vs/base/common/platform'; import * as uuid from 'vs/base/common/uuid'; @@ -19,7 +20,6 @@ export async function resolveWorkbenchCommonProperties( storageService: IStorageService, commit: string | undefined, version: string | undefined, - machineId: string, remoteAuthority?: string, resolveAdditionalProperties?: () => { [key: string]: any } ): Promise<{ [name: string]: string | undefined }> { @@ -27,6 +27,12 @@ export async function resolveWorkbenchCommonProperties( const firstSessionDate = storageService.get(firstSessionDateStorageKey, StorageScope.GLOBAL)!; const lastSessionDate = storageService.get(lastSessionDateStorageKey, StorageScope.GLOBAL)!; + let machineId = storageService.get(machineIdKey, StorageScope.GLOBAL); + if (!machineId) { + machineId = uuid.generateUuid(); + storageService.store(machineIdKey, machineId, StorageScope.GLOBAL); + } + /** * Note: In the web, session date information is fetched from browser storage, so these dates are tied to a specific * browser and not the machine overall. diff --git a/src/vs/platform/telemetry/node/appInsightsAppender.ts b/src/vs/platform/telemetry/node/appInsightsAppender.ts index 59415e88d54b..ea4d8ad32d7c 100644 --- a/src/vs/platform/telemetry/node/appInsightsAppender.ts +++ b/src/vs/platform/telemetry/node/appInsightsAppender.ts @@ -42,7 +42,7 @@ export class AppInsightsAppender implements ITelemetryAppender { constructor( private _eventPrefix: string, private _defaultData: { [key: string]: any } | null, - aiKeyOrClientFactory: string | (() => appInsights.ITelemetryClient), // allow factory function for testing + aiKeyOrClientFactory: string | (() => appInsights.TelemetryClient), // allow factory function for testing @ILogService private _logService?: ILogService ) { if (!this._defaultData) { diff --git a/src/vs/platform/telemetry/test/browser/commonProperties.test.ts b/src/vs/platform/telemetry/test/browser/commonProperties.test.ts index cd571e4f1512..f26a988def7d 100644 --- a/src/vs/platform/telemetry/test/browser/commonProperties.test.ts +++ b/src/vs/platform/telemetry/test/browser/commonProperties.test.ts @@ -23,7 +23,7 @@ suite('Browser Telemetry - common properties', function () { }; }; - const props = await resolveWorkbenchCommonProperties(testStorageService, commit, version, 'someMachineId', undefined, resolveCommonTelemetryProperties); + const props = await resolveWorkbenchCommonProperties(testStorageService, commit, version, undefined, resolveCommonTelemetryProperties); assert.ok('commitHash' in props); assert.ok('sessionID' in props); @@ -53,10 +53,10 @@ suite('Browser Telemetry - common properties', function () { }); }; - const props = await resolveWorkbenchCommonProperties(testStorageService, commit, version, 'someMachineId', undefined, resolveCommonTelemetryProperties); + const props = await resolveWorkbenchCommonProperties(testStorageService, commit, version, undefined, resolveCommonTelemetryProperties); assert.equal(props['userId'], '1'); - const props2 = await resolveWorkbenchCommonProperties(testStorageService, commit, version, 'someMachineId', undefined, resolveCommonTelemetryProperties); + const props2 = await resolveWorkbenchCommonProperties(testStorageService, commit, version, undefined, resolveCommonTelemetryProperties); assert.equal(props2['userId'], '2'); }); }); diff --git a/src/vs/platform/telemetry/test/electron-browser/appInsightsAppender.test.ts b/src/vs/platform/telemetry/test/electron-browser/appInsightsAppender.test.ts index 3b0ccdf0f398..c8518a04f126 100644 --- a/src/vs/platform/telemetry/test/electron-browser/appInsightsAppender.test.ts +++ b/src/vs/platform/telemetry/test/electron-browser/appInsightsAppender.test.ts @@ -5,15 +5,19 @@ import * as assert from 'assert'; import { AppInsightsAppender } from 'vs/platform/telemetry/node/appInsightsAppender'; import { ILogService, AbstractLogService, LogLevel, DEFAULT_LOG_LEVEL } from 'vs/platform/log/common/log'; -import { ITelemetryClient, EventTelemetry } from 'applicationinsights'; +import { TelemetryClient, Contracts } from 'applicationinsights'; -class AppInsightsMock implements ITelemetryClient { +class AppInsightsMock extends TelemetryClient { public config: any; public channel: any; - public events: EventTelemetry[] = []; + public events: Contracts.EventTelemetry[] = []; public IsTrackingPageView: boolean = false; public exceptions: any[] = []; + constructor() { + super('testKey'); + } + public trackEvent(event: any) { this.events.push(event); } diff --git a/src/vs/platform/theme/common/colorRegistry.ts b/src/vs/platform/theme/common/colorRegistry.ts index a87a5b53b2d4..d97e6ea1c0a1 100644 --- a/src/vs/platform/theme/common/colorRegistry.ts +++ b/src/vs/platform/theme/common/colorRegistry.ts @@ -103,7 +103,7 @@ class ColorRegistry implements IColorRegistry { public registerColor(id: string, defaults: ColorDefaults | null, description: string, needsTransparency = false, deprecationMessage?: string): ColorIdentifier { let colorContribution: ColorContribution = { id, description, defaults, needsTransparency, deprecationMessage }; this.colorsById[id] = colorContribution; - let propertySchema: IJSONSchema = { type: 'string', description, format: 'color-hex', defaultSnippets: [{ body: '#ff0000' }] }; + let propertySchema: IJSONSchema = { type: 'string', description, format: 'color-hex', defaultSnippets: [{ body: '${1:#ff0000}' }] }; if (deprecationMessage) { propertySchema.deprecationMessage = deprecationMessage; } @@ -301,14 +301,22 @@ export const editorFindMatchBorder = registerColor('editor.findMatchBorder', { l export const editorFindMatchHighlightBorder = registerColor('editor.findMatchHighlightBorder', { light: null, dark: null, hc: activeContrastBorder }, nls.localize('findMatchHighlightBorder', "Border color of the other search matches.")); export const editorFindRangeHighlightBorder = registerColor('editor.findRangeHighlightBorder', { dark: null, light: null, hc: transparent(activeContrastBorder, 0.4) }, nls.localize('findRangeHighlightBorder', "Border color of the range limiting the search. The color must not be opaque so as not to hide underlying decorations."), true); +/** + * Search Editor query match colors. + * + * Distinct from normal editor find match to allow for better differentiation + */ +export const searchEditorFindMatch = registerColor('searchEditor.findMatchBackground', { light: transparent(editorFindMatchHighlight, 0.5), dark: transparent(editorFindMatchHighlight, 0.5), hc: editorFindMatchHighlight }, nls.localize('searchEditor.queryMatch', "Color of the Search Editor query matches.")); +export const searchEditorFindMatchBorder = registerColor('searchEditor.findMatchBorder', { light: transparent(editorFindMatchHighlightBorder, 0.5), dark: transparent(editorFindMatchHighlightBorder, 0.5), hc: editorFindMatchHighlightBorder }, nls.localize('searchEditor.editorFindMatchBorder', "Border color of the Search Editor query matches.")); + /** * Editor hover */ export const editorHoverHighlight = registerColor('editor.hoverHighlightBackground', { light: '#ADD6FF26', dark: '#264f7840', hc: '#ADD6FF26' }, nls.localize('hoverHighlight', 'Highlight below the word for which a hover is shown. The color must not be opaque so as not to hide underlying decorations.'), true); export const editorHoverBackground = registerColor('editorHoverWidget.background', { light: editorWidgetBackground, dark: editorWidgetBackground, hc: editorWidgetBackground }, nls.localize('hoverBackground', 'Background color of the editor hover.')); +export const editorHoverForeground = registerColor('editorHoverWidget.foreground', { light: editorWidgetForeground, dark: editorWidgetForeground, hc: editorWidgetForeground }, nls.localize('hoverForeground', 'Foreground color of the editor hover.')); export const editorHoverBorder = registerColor('editorHoverWidget.border', { light: editorWidgetBorder, dark: editorWidgetBorder, hc: editorWidgetBorder }, nls.localize('hoverBorder', 'Border color of the editor hover.')); export const editorHoverStatusBarBackground = registerColor('editorHoverWidget.statusBarBackground', { dark: lighten(editorHoverBackground, 0.2), light: darken(editorHoverBackground, 0.05), hc: editorWidgetBackground }, nls.localize('statusBarBackground', "Background color of the editor hover status bar.")); - /** * Editor link colors */ @@ -416,6 +424,8 @@ export const overviewRulerSelectionHighlightForeground = registerColor('editorOv export const minimapFindMatch = registerColor('minimap.findMatchHighlight', { light: '#d18616', dark: '#d18616', hc: '#AB5A00' }, nls.localize('minimapFindMatchHighlight', 'Minimap marker color for find matches.'), true); export const minimapSelection = registerColor('minimap.selectionHighlight', { light: '#ADD6FF', dark: '#264F78', hc: '#ffffff' }, nls.localize('minimapSelectionHighlight', 'Minimap marker color for the editor selection.'), true); +export const minimapError = registerColor('minimap.errorHighlight', { dark: new Color(new RGBA(255, 18, 18, 0.7)), light: new Color(new RGBA(255, 18, 18, 0.7)), hc: new Color(new RGBA(255, 50, 50, 1)) }, nls.localize('minimapError', 'Minimap marker color for errors.')); +export const minimapWarning = registerColor('minimap.warningHighlight', { dark: editorWarningForeground, light: editorWarningForeground, hc: editorWarningBorder }, nls.localize('overviewRuleWarning', 'Minimap marker color for warnings.')); export const problemsErrorIconForeground = registerColor('problemsErrorIcon.foreground', { dark: editorErrorForeground, light: editorErrorForeground, hc: editorErrorForeground }, nls.localize('problemsErrorIconForeground', "The color used for the problems error icon.")); export const problemsWarningIconForeground = registerColor('problemsWarningIcon.foreground', { dark: editorWarningForeground, light: editorWarningForeground, hc: editorWarningForeground }, nls.localize('problemsWarningIconForeground', "The color used for the problems warning icon.")); diff --git a/src/vs/platform/theme/common/styler.ts b/src/vs/platform/theme/common/styler.ts index acb757b9a48c..b030c311629f 100644 --- a/src/vs/platform/theme/common/styler.ts +++ b/src/vs/platform/theme/common/styler.ts @@ -7,18 +7,12 @@ import { ITheme, IThemeService } from 'vs/platform/theme/common/themeService'; import { focusBorder, inputBackground, inputForeground, ColorIdentifier, selectForeground, selectBackground, selectListBackground, selectBorder, inputBorder, foreground, editorBackground, contrastBorder, inputActiveOptionBorder, inputActiveOptionBackground, listFocusBackground, listFocusForeground, listActiveSelectionBackground, listActiveSelectionForeground, listInactiveSelectionForeground, listInactiveSelectionBackground, listInactiveFocusBackground, listHoverBackground, listHoverForeground, listDropBackground, pickerGroupBorder, pickerGroupForeground, widgetShadow, inputValidationInfoBorder, inputValidationInfoBackground, inputValidationWarningBorder, inputValidationWarningBackground, inputValidationErrorBorder, inputValidationErrorBackground, activeContrastBorder, buttonForeground, buttonBackground, buttonHoverBackground, ColorFunction, badgeBackground, badgeForeground, progressBarBackground, breadcrumbsForeground, breadcrumbsFocusForeground, breadcrumbsActiveSelectionForeground, breadcrumbsBackground, editorWidgetBorder, inputValidationInfoForeground, inputValidationWarningForeground, inputValidationErrorForeground, menuForeground, menuBackground, menuSelectionForeground, menuSelectionBackground, menuSelectionBorder, menuBorder, menuSeparatorBackground, darken, listFilterWidgetOutline, listFilterWidgetNoMatchesOutline, listFilterWidgetBackground, editorWidgetBackground, treeIndentGuidesStroke, editorWidgetForeground, simpleCheckboxBackground, simpleCheckboxBorder, simpleCheckboxForeground, ColorValue, resolveColorValue } from 'vs/platform/theme/common/colorRegistry'; import { IDisposable } from 'vs/base/common/lifecycle'; import { Color } from 'vs/base/common/color'; -import { mixin } from 'vs/base/common/objects'; - -export type styleFn = (colors: { [name: string]: Color | undefined }) => void; +import { IThemable, styleFn } from 'vs/base/common/styler'; export interface IStyleOverrides { [color: string]: ColorIdentifier | undefined; } -export interface IThemable { - style: styleFn; -} - export interface IColorMapping { [optionsKey: string]: ColorValue | undefined; } @@ -209,6 +203,7 @@ export function attachQuickOpenStyler(widget: IThemable, themeService: IThemeSer } export interface IListStyleOverrides extends IStyleOverrides { + listBackground?: ColorIdentifier; listFocusBackground?: ColorIdentifier; listFocusForeground?: ColorIdentifier; listActiveSelectionBackground?: ColorIdentifier; @@ -232,8 +227,8 @@ export interface IListStyleOverrides extends IStyleOverrides { treeIndentGuidesStroke?: ColorIdentifier; } -export function attachListStyler(widget: IThemable, themeService: IThemeService, overrides?: IListStyleOverrides): IDisposable { - return attachStyler(themeService, mixin(overrides || Object.create(null), defaultListStyles, false) as IListStyleOverrides, widget); +export function attachListStyler(widget: IThemable, themeService: IThemeService, overrides?: IColorMapping): IDisposable { + return attachStyler(themeService, { ...defaultListStyles, ...(overrides || {}) }, widget); } export const defaultListStyles: IColorMapping = { diff --git a/src/vs/platform/theme/common/themeService.ts b/src/vs/platform/theme/common/themeService.ts index b2e84893776b..8fe5fd85edf0 100644 --- a/src/vs/platform/theme/common/themeService.ts +++ b/src/vs/platform/theme/common/themeService.ts @@ -26,6 +26,42 @@ export interface ThemeIcon { readonly id: string; } +export namespace ThemeIcon { + export function isThemeIcon(obj: any): obj is ThemeIcon { + return obj && typeof obj === 'object' && typeof (obj).id === 'string'; + } + + const _regexFromString = /^\$\(([a-z.]+\/)?([a-z-~]+)\)$/i; + + export function fromString(str: string): ThemeIcon | undefined { + const match = _regexFromString.exec(str); + if (!match) { + return undefined; + } + let [, owner, name] = match; + if (!owner) { + owner = `codicon/`; + } + return { id: owner + name }; + } + + const _regexAsClassName = /^(codicon\/)?([a-z-]+)(~[a-z]+)?$/i; + + export function asClassName(icon: ThemeIcon): string | undefined { + // todo@martin,joh -> this should go into the ThemeService + const match = _regexAsClassName.exec(icon.id); + if (!match) { + return undefined; + } + let [, , name, modifier] = match; + let className = `codicon codicon-${name}`; + if (modifier) { + className += ` ${modifier.substr(1)}`; + } + return className; + } +} + export const FileThemeIcon = { id: 'file' }; export const FolderThemeIcon = { id: 'folder' }; @@ -59,6 +95,16 @@ export interface ITheme { * default color will be used. */ defines(color: ColorIdentifier): boolean; + + /** + * Returns the token style for a given classification. The result uses the MetadataConsts format + */ + getTokenStyleMetadata(type: string, modifiers: string[]): number | undefined; + + /** + * List of all colors used with tokens. getTokenStyleMetadata references the colors by index into this list. + */ + readonly tokenColorMap: string[]; } export interface IIconTheme { diff --git a/src/vs/platform/theme/common/tokenClassificationRegistry.ts b/src/vs/platform/theme/common/tokenClassificationRegistry.ts new file mode 100644 index 000000000000..7beb2a2b54a2 --- /dev/null +++ b/src/vs/platform/theme/common/tokenClassificationRegistry.ts @@ -0,0 +1,395 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as platform from 'vs/platform/registry/common/platform'; +import { Color } from 'vs/base/common/color'; +import { ITheme } from 'vs/platform/theme/common/themeService'; +import * as nls from 'vs/nls'; +import { Extensions as JSONExtensions, IJSONContributionRegistry } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; +import { RunOnceScheduler } from 'vs/base/common/async'; +import { Event, Emitter } from 'vs/base/common/event'; +import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema'; + +// ------ API types + +export const TOKEN_TYPE_WILDCARD = '*'; +export const TOKEN_TYPE_WILDCARD_NUM = -1; + +// qualified string [type|*](.modifier)* +export type TokenClassificationString = string; + +export interface TokenClassification { + type: number; + modifiers: number; +} + +export interface TokenTypeOrModifierContribution { + readonly num: number; + readonly id: string; + readonly description: string; + readonly deprecationMessage: string | undefined; +} + + +export interface TokenStyleData { + foreground?: Color; + bold?: boolean; + underline?: boolean; + italic?: boolean; +} + +export class TokenStyle implements Readonly { + constructor( + public readonly foreground?: Color, + public readonly bold?: boolean, + public readonly underline?: boolean, + public readonly italic?: boolean, + ) { + } +} + +export namespace TokenStyle { + export function fromData(data: { foreground?: Color, bold?: boolean, underline?: boolean, italic?: boolean }) { + return new TokenStyle(data.foreground, data.bold, data.underline, data.italic); + } +} + +export type ProbeScope = string[]; + +export interface TokenStyleFunction { + (theme: ITheme): TokenStyle | undefined; +} + +export interface TokenStyleDefaults { + scopesToProbe: ProbeScope[]; + light: TokenStyleValue | null; + dark: TokenStyleValue | null; + hc: TokenStyleValue | null; +} + +export interface TokenStylingDefaultRule { + classification: TokenClassification; + matchScore: number; + defaults: TokenStyleDefaults; +} + +export interface TokenStylingRule { + classification: TokenClassification; + matchScore: number; + value: TokenStyle; +} + +/** + * A TokenStyle Value is either a token style literal, or a TokenClassificationString + */ +export type TokenStyleValue = TokenStyle | TokenClassificationString; + +// TokenStyle registry +export const Extensions = { + TokenClassificationContribution: 'base.contributions.tokenClassification' +}; + +export interface ITokenClassificationRegistry { + + readonly onDidChangeSchema: Event; + + /** + * Register a token type to the registry. + * @param id The TokenType id as used in theme description files + * @description the description + */ + registerTokenType(id: string, description: string): void; + + /** + * Register a token modifier to the registry. + * @param id The TokenModifier id as used in theme description files + * @description the description + */ + registerTokenModifier(id: string, description: string): void; + + getTokenClassification(type: string, modifiers: string[]): TokenClassification | undefined; + + getTokenStylingRule(classification: TokenClassification, value: TokenStyle): TokenStylingRule; + + /** + * Register a TokenStyle default to the registry. + * @param selector The rule selector + * @param defaults The default values + */ + registerTokenStyleDefault(selector: TokenClassification, defaults: TokenStyleDefaults): void; + + /** + * Deregister a TokenType from the registry. + */ + deregisterTokenType(id: string): void; + + /** + * Deregister a TokenModifier from the registry. + */ + deregisterTokenModifier(id: string): void; + + /** + * Get all TokenType contributions + */ + getTokenTypes(): TokenTypeOrModifierContribution[]; + + /** + * Get all TokenModifier contributions + */ + getTokenModifiers(): TokenTypeOrModifierContribution[]; + + /** + * The styling rules to used when a schema does not define any styling rules. + */ + getTokenStylingDefaultRules(): TokenStylingDefaultRule[]; + + /** + * JSON schema for an object to assign styling to token classifications + */ + getTokenStylingSchema(): IJSONSchema; +} + +class TokenClassificationRegistry implements ITokenClassificationRegistry { + + private readonly _onDidChangeSchema = new Emitter(); + readonly onDidChangeSchema: Event = this._onDidChangeSchema.event; + + private currentTypeNumber = 0; + private currentModifierBit = 1; + + private tokenTypeById: { [key: string]: TokenTypeOrModifierContribution }; + private tokenModifierById: { [key: string]: TokenTypeOrModifierContribution }; + + private tokenStylingDefaultRules: TokenStylingDefaultRule[] = []; + + private tokenStylingSchema: IJSONSchema & { properties: IJSONSchemaMap } = { + type: 'object', + properties: {}, + additionalProperties: getStylingSchemeEntry(), + definitions: { + style: { + type: 'object', + description: nls.localize('schema.token.settings', 'Colors and styles for the token.'), + properties: { + foreground: { + type: 'string', + description: nls.localize('schema.token.foreground', 'Foreground color for the token.'), + format: 'color-hex', + default: '#ff0000' + }, + background: { + type: 'string', + deprecationMessage: nls.localize('schema.token.background.warning', 'Token background colors are currently not supported.') + }, + fontStyle: { + type: 'string', + description: nls.localize('schema.token.fontStyle', 'Font style of the rule: \'italic\', \'bold\' or \'underline\', \'-italic\', \'-bold\' or \'-underline\'or a combination. The empty string unsets inherited settings.'), + pattern: '^(\\s*(-?italic|-?bold|-?underline))*\\s*$', + patternErrorMessage: nls.localize('schema.fontStyle.error', 'Font style must be \'italic\', \'bold\' or \'underline\' to set a style or \'-italic\', \'-bold\' or \'-underline\' to unset or a combination. The empty string unsets all styles.'), + defaultSnippets: [{ label: nls.localize('schema.token.fontStyle.none', 'None (clear inherited style)'), bodyText: '""' }, { body: 'italic' }, { body: 'bold' }, { body: 'underline' }, { body: '-italic' }, { body: '-bold' }, { body: '-underline' }, { body: 'italic bold' }, { body: 'italic underline' }, { body: 'bold underline' }, { body: 'italic bold underline' }] + } + }, + additionalProperties: false, + defaultSnippets: [{ body: { foreground: '${1:#FF0000}', fontStyle: '${2:bold}' } }] + } + } + }; + + constructor() { + this.tokenTypeById = {}; + this.tokenModifierById = {}; + + this.tokenTypeById[TOKEN_TYPE_WILDCARD] = { num: TOKEN_TYPE_WILDCARD_NUM, id: TOKEN_TYPE_WILDCARD, description: '', deprecationMessage: undefined }; + } + + public registerTokenType(id: string, description: string, deprecationMessage?: string): void { + const num = this.currentTypeNumber++; + let tokenStyleContribution: TokenTypeOrModifierContribution = { num, id, description, deprecationMessage }; + this.tokenTypeById[id] = tokenStyleContribution; + + this.tokenStylingSchema.properties[id] = getStylingSchemeEntry(description, deprecationMessage); + } + + public registerTokenModifier(id: string, description: string, deprecationMessage?: string): void { + const num = this.currentModifierBit; + this.currentModifierBit = this.currentModifierBit * 2; + let tokenStyleContribution: TokenTypeOrModifierContribution = { num, id, description, deprecationMessage }; + this.tokenModifierById[id] = tokenStyleContribution; + + this.tokenStylingSchema.properties[`*.${id}`] = getStylingSchemeEntry(description, deprecationMessage); + } + + public getTokenClassification(type: string, modifiers: string[]): TokenClassification | undefined { + const tokenTypeDesc = this.tokenTypeById[type]; + if (!tokenTypeDesc) { + return undefined; + } + let allModifierBits = 0; + for (const modifier of modifiers) { + const tokenModifierDesc = this.tokenModifierById[modifier]; + if (tokenModifierDesc) { + allModifierBits |= tokenModifierDesc.num; + } + } + return { type: tokenTypeDesc.num, modifiers: allModifierBits }; + } + + public getTokenStylingRule(classification: TokenClassification, value: TokenStyle): TokenStylingRule { + return { classification, matchScore: getTokenStylingScore(classification), value }; + } + + public registerTokenStyleDefault(classification: TokenClassification, defaults: TokenStyleDefaults): void { + this.tokenStylingDefaultRules.push({ classification, matchScore: getTokenStylingScore(classification), defaults }); + } + + public deregisterTokenType(id: string): void { + delete this.tokenTypeById[id]; + delete this.tokenStylingSchema.properties[id]; + } + + public deregisterTokenModifier(id: string): void { + delete this.tokenModifierById[id]; + delete this.tokenStylingSchema.properties[`*.${id}`]; + } + + public getTokenTypes(): TokenTypeOrModifierContribution[] { + return Object.keys(this.tokenTypeById).map(id => this.tokenTypeById[id]); + } + + public getTokenModifiers(): TokenTypeOrModifierContribution[] { + return Object.keys(this.tokenModifierById).map(id => this.tokenModifierById[id]); + } + + public getTokenStylingSchema(): IJSONSchema { + return this.tokenStylingSchema; + } + + public getTokenStylingDefaultRules(): TokenStylingDefaultRule[] { + return this.tokenStylingDefaultRules; + } + + public toString() { + let sorter = (a: string, b: string) => { + let cat1 = a.indexOf('.') === -1 ? 0 : 1; + let cat2 = b.indexOf('.') === -1 ? 0 : 1; + if (cat1 !== cat2) { + return cat1 - cat2; + } + return a.localeCompare(b); + }; + + return Object.keys(this.tokenTypeById).sort(sorter).map(k => `- \`${k}\`: ${this.tokenTypeById[k].description}`).join('\n'); + } + +} + +export function matchTokenStylingRule(themeSelector: TokenStylingRule | TokenStylingDefaultRule, classification: TokenClassification): number { + const selectorType = themeSelector.classification.type; + if (selectorType !== TOKEN_TYPE_WILDCARD_NUM && selectorType !== classification.type) { + return -1; + } + const selectorModifier = themeSelector.classification.modifiers; + if ((classification.modifiers & selectorModifier) !== selectorModifier) { + return -1; + } + return themeSelector.matchScore; +} + + +const tokenClassificationRegistry = new TokenClassificationRegistry(); +platform.Registry.add(Extensions.TokenClassificationContribution, tokenClassificationRegistry); + +export function registerTokenType(id: string, description: string, scopesToProbe: ProbeScope[] = [], extendsTC: string | null = null, deprecationMessage?: string): string { + tokenClassificationRegistry.registerTokenType(id, description, deprecationMessage); + + if (scopesToProbe || extendsTC) { + const classification = tokenClassificationRegistry.getTokenClassification(id, []); + tokenClassificationRegistry.registerTokenStyleDefault(classification!, { scopesToProbe, light: extendsTC, dark: extendsTC, hc: extendsTC }); + } + return id; +} + +export function registerTokenModifier(id: string, description: string, deprecationMessage?: string): string { + tokenClassificationRegistry.registerTokenModifier(id, description, deprecationMessage); + return id; +} + +export function getTokenClassificationRegistry(): ITokenClassificationRegistry { + return tokenClassificationRegistry; +} + +export const comments = registerTokenType('comments', nls.localize('comments', "Style for comments."), [['comment']]); +export const strings = registerTokenType('strings', nls.localize('strings', "Style for strings."), [['string']]); +export const keywords = registerTokenType('keywords', nls.localize('keywords', "Style for keywords."), [['keyword.control']]); +export const numbers = registerTokenType('numbers', nls.localize('numbers', "Style for numbers."), [['constant.numeric']]); +export const regexp = registerTokenType('regexp', nls.localize('regexp', "Style for expressions."), [['constant.regexp']]); +export const operators = registerTokenType('operators', nls.localize('operator', "Style for operators."), [['keyword.operator']]); + +export const namespaces = registerTokenType('namespaces', nls.localize('namespace', "Style for namespaces."), [['entity.name.namespace']]); + +export const types = registerTokenType('types', nls.localize('types', "Style for types."), [['entity.name.type'], ['entity.name.class'], ['support.type'], ['support.class']]); +export const structs = registerTokenType('structs', nls.localize('struct', "Style for structs."), [['storage.type.struct']], types); +export const classes = registerTokenType('classes', nls.localize('class', "Style for classes."), [['entity.name.class']], types); +export const interfaces = registerTokenType('interfaces', nls.localize('interface', "Style for interfaces."), undefined, types); +export const enums = registerTokenType('enums', nls.localize('enum', "Style for enums."), undefined, types); +export const parameterTypes = registerTokenType('parameterTypes', nls.localize('parameterType', "Style for parameter types."), undefined, types); + +export const functions = registerTokenType('functions', nls.localize('functions', "Style for functions"), [['entity.name.function'], ['support.function']]); +export const macros = registerTokenType('macros', nls.localize('macro', "Style for macros."), undefined, functions); + +export const variables = registerTokenType('variables', nls.localize('variables', "Style for variables."), [['variable'], ['entity.name.variable']]); +export const constants = registerTokenType('constants', nls.localize('constants', "Style for constants."), undefined, variables); +export const parameters = registerTokenType('parameters', nls.localize('parameters', "Style for parameters."), undefined, variables); +export const property = registerTokenType('properties', nls.localize('properties', "Style for properties."), undefined, variables); + +export const labels = registerTokenType('labels', nls.localize('labels', "Style for labels. "), undefined); + +export const m_declaration = registerTokenModifier('declaration', nls.localize('declaration', "Style for all symbol declarations."), undefined); +export const m_documentation = registerTokenModifier('documentation', nls.localize('documentation', "Style to use for references in documentation."), undefined); +export const m_member = registerTokenModifier('member', nls.localize('member', "Style to use for member functions, variables (fields) and types."), undefined); +export const m_static = registerTokenModifier('static', nls.localize('static', "Style to use for symbols that are static."), undefined); +export const m_abstract = registerTokenModifier('abstract', nls.localize('abstract', "Style to use for symbols that are abstract."), undefined); +export const m_deprecated = registerTokenModifier('deprecated', nls.localize('deprecated', "Style to use for symbols that are deprecated."), undefined); +export const m_modification = registerTokenModifier('modification', nls.localize('modification', "Style to use for write accesses."), undefined); +export const m_async = registerTokenModifier('async', nls.localize('async', "Style to use for symbols that are async."), undefined); + +function bitCount(u: number) { + // https://blogs.msdn.microsoft.com/jeuge/2005/06/08/bit-fiddling-3/ + const uCount = u - ((u >> 1) & 0o33333333333) - ((u >> 2) & 0o11111111111); + return ((uCount + (uCount >> 3)) & 0o30707070707) % 63; +} + +function getTokenStylingScore(classification: TokenClassification) { + return bitCount(classification.modifiers) + ((classification.type !== TOKEN_TYPE_WILDCARD_NUM) ? 1 : 0); +} + +function getStylingSchemeEntry(description?: string, deprecationMessage?: string): IJSONSchema { + return { + description, + deprecationMessage, + defaultSnippets: [{ body: '${1:#ff0000}' }], + anyOf: [ + { + type: 'string', + format: 'color-hex' + }, + { + $ref: '#definitions/style' + } + ] + }; +} + +export const tokenStylingSchemaId = 'vscode://schemas/token-styling'; + +let schemaRegistry = platform.Registry.as(JSONExtensions.JSONContribution); +schemaRegistry.registerSchema(tokenStylingSchemaId, tokenClassificationRegistry.getTokenStylingSchema()); + +const delayer = new RunOnceScheduler(() => schemaRegistry.notifySchemaChanged(tokenStylingSchemaId), 200); +tokenClassificationRegistry.onDidChangeSchema(() => { + if (!delayer.isScheduled()) { + delayer.schedule(); + } +}); diff --git a/src/vs/platform/theme/test/common/testThemeService.ts b/src/vs/platform/theme/test/common/testThemeService.ts index 45c28936d846..444db9217843 100644 --- a/src/vs/platform/theme/test/common/testThemeService.ts +++ b/src/vs/platform/theme/test/common/testThemeService.ts @@ -23,6 +23,14 @@ export class TestTheme implements ITheme { defines(color: string): boolean { throw new Error('Method not implemented.'); } + + getTokenStyleMetadata(type: string, modifiers: string[]): number | undefined { + return undefined; + } + + get tokenColorMap(): string[] { + return []; + } } export class TestIconTheme implements IIconTheme { diff --git a/src/vs/platform/update/electron-main/updateService.linux.ts b/src/vs/platform/update/electron-main/updateService.linux.ts index 944006ed9559..05e7cac5a512 100644 --- a/src/vs/platform/update/electron-main/updateService.linux.ts +++ b/src/vs/platform/update/electron-main/updateService.linux.ts @@ -41,7 +41,7 @@ export class LinuxUpdateService extends AbstractUpdateService { this.setState(State.CheckingForUpdates(context)); this.requestService.request({ url: this.url }, CancellationToken.None) - .then(asJson) + .then(asJson) .then(update => { if (!update || !update.url || !update.version || !update.productVersion) { this.telemetryService.publicLog2<{ explicit: boolean }, UpdateNotAvailableClassification>('update:notAvailable', { explicit: !!context }); diff --git a/src/vs/platform/update/electron-main/updateService.win32.ts b/src/vs/platform/update/electron-main/updateService.win32.ts index 41b8a7e1e395..ab1e95ebe336 100644 --- a/src/vs/platform/update/electron-main/updateService.win32.ts +++ b/src/vs/platform/update/electron-main/updateService.win32.ts @@ -110,7 +110,7 @@ export class Win32UpdateService extends AbstractUpdateService { this.setState(State.CheckingForUpdates(context)); this.requestService.request({ url: this.url }, CancellationToken.None) - .then(asJson) + .then(asJson) .then(update => { const updateType = getUpdateType(); diff --git a/src/vs/platform/url/electron-main/electronUrlListener.ts b/src/vs/platform/url/electron-main/electronUrlListener.ts index 3f0316c496ae..19db80ca22d7 100644 --- a/src/vs/platform/url/electron-main/electronUrlListener.ts +++ b/src/vs/platform/url/electron-main/electronUrlListener.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Event } from 'vs/base/common/event'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IURLService } from 'vs/platform/url/common/url'; import product from 'vs/platform/product/common/product'; import { app, Event as ElectronEvent } from 'electron'; @@ -32,7 +33,8 @@ export class ElectronURLListener { constructor( initial: string | string[], @IURLService private readonly urlService: IURLService, - @IWindowsMainService windowsMainService: IWindowsMainService + @IWindowsMainService windowsMainService: IWindowsMainService, + @IEnvironmentService environmentService: IEnvironmentService ) { const globalBuffer = ((global).getOpenUrls() || []) as string[]; const rawBuffer = [ @@ -43,7 +45,9 @@ export class ElectronURLListener { this.uris = coalesce(rawBuffer.map(uriFromRawUrl)); if (isWindows) { - app.setAsDefaultProtocolClient(product.urlProtocol, process.execPath, ['--open-url', '--']); + const windowsParameters = environmentService.isBuilt ? [] : [`"${environmentService.appRoot}"`]; + windowsParameters.push('--open-url', '--'); + app.setAsDefaultProtocolClient(product.urlProtocol, process.execPath, windowsParameters); } const onOpenElectronUrl = Event.map( diff --git a/src/vs/platform/userDataSync/common/content.ts b/src/vs/platform/userDataSync/common/content.ts new file mode 100644 index 000000000000..4af6aa8237f5 --- /dev/null +++ b/src/vs/platform/userDataSync/common/content.ts @@ -0,0 +1,53 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { JSONPath } from 'vs/base/common/json'; +import { setProperty } from 'vs/base/common/jsonEdit'; +import { FormattingOptions } from 'vs/base/common/jsonFormatter'; + + +export function edit(content: string, originalPath: JSONPath, value: any, formattingOptions: FormattingOptions): string { + const edit = setProperty(content, originalPath, value, formattingOptions)[0]; + if (edit) { + content = content.substring(0, edit.offset) + edit.content + content.substring(edit.offset + edit.length); + } + return content; +} + +export function getLineStartOffset(content: string, eol: string, atOffset: number): number { + let lineStartingOffset = atOffset; + while (lineStartingOffset >= 0) { + if (content.charAt(lineStartingOffset) === eol.charAt(eol.length - 1)) { + if (eol.length === 1) { + return lineStartingOffset + 1; + } + } + lineStartingOffset--; + if (eol.length === 2) { + if (lineStartingOffset >= 0 && content.charAt(lineStartingOffset) === eol.charAt(0)) { + return lineStartingOffset + 2; + } + } + } + return 0; +} + +export function getLineEndOffset(content: string, eol: string, atOffset: number): number { + let lineEndOffset = atOffset; + while (lineEndOffset >= 0) { + if (content.charAt(lineEndOffset) === eol.charAt(eol.length - 1)) { + if (eol.length === 1) { + return lineEndOffset; + } + } + lineEndOffset++; + if (eol.length === 2) { + if (lineEndOffset >= 0 && content.charAt(lineEndOffset) === eol.charAt(1)) { + return lineEndOffset; + } + } + } + return content.length - 1; +} diff --git a/src/vs/platform/userDataSync/common/extensionsSync.ts b/src/vs/platform/userDataSync/common/extensionsSync.ts index 7acedf8262b6..f495c953598d 100644 --- a/src/vs/platform/userDataSync/common/extensionsSync.ts +++ b/src/vs/platform/userDataSync/common/extensionsSync.ts @@ -69,11 +69,14 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser } async sync(): Promise { - if (!this.configurationService.getValue('configurationSync.enableExtensions')) { + if (!this.configurationService.getValue('sync.enableExtensions')) { this.logService.trace('Extensions: Skipping synchronizing extensions as it is disabled.'); return false; } - + if (!this.extensionGalleryService.isEnabled()) { + this.logService.trace('Extensions: Skipping synchronizing extensions as gallery is disabled.'); + return false; + } if (this.status !== SyncStatus.Idle) { this.logService.trace('Extensions: Skipping synchronizing extensions as it is running already.'); return false; @@ -105,7 +108,7 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser return this.replaceQueue.queue(async () => { const remoteData = await this.userDataSyncStoreService.read(ExtensionsSynchroniser.EXTERNAL_USER_DATA_EXTENSIONS_KEY, null); const remoteExtensions: ISyncExtension[] = remoteData.content ? JSON.parse(remoteData.content) : []; - const ignoredExtensions = this.configurationService.getValue('configurationSync.extensionsToIgnore') || []; + const ignoredExtensions = this.configurationService.getValue('sync.ignoredExtensions') || []; const removedExtensions = remoteExtensions.filter(e => !ignoredExtensions.some(id => areSameExtensions({ id }, e.identifier)) && areSameExtensions(e.identifier, identifier)); if (removedExtensions.length) { for (const removedExtension of removedExtensions) { @@ -159,11 +162,11 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser * - Update remote with those local extension which are newly added or updated or removed and untouched in remote. */ private merge(localExtensions: ISyncExtension[], remoteExtensions: ISyncExtension[] | null, lastSyncExtensions: ISyncExtension[] | null): { added: ISyncExtension[], removed: IExtensionIdentifier[], updated: ISyncExtension[], remote: ISyncExtension[] | null } { - const ignoredExtensions = this.configurationService.getValue('configurationSync.extensionsToIgnore') || []; + const ignoredExtensions = this.configurationService.getValue('sync.ignoredExtensions') || []; // First time sync if (!remoteExtensions) { this.logService.info('Extensions: Remote extensions does not exist. Synchronizing extensions for the first time.'); - return { added: [], removed: [], updated: [], remote: localExtensions.filter(({ identifier }) => ignoredExtensions.some(id => id.toLowerCase() === identifier.id.toLowerCase())) }; + return { added: [], removed: [], updated: [], remote: localExtensions.filter(({ identifier }) => ignoredExtensions.every(id => id.toLowerCase() !== identifier.id.toLowerCase())) }; } const uuids: Map = new Map(); diff --git a/src/vs/platform/userDataSync/common/keybindingsMerge.ts b/src/vs/platform/userDataSync/common/keybindingsMerge.ts new file mode 100644 index 000000000000..77e977198981 --- /dev/null +++ b/src/vs/platform/userDataSync/common/keybindingsMerge.ts @@ -0,0 +1,367 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as objects from 'vs/base/common/objects'; +import { parse } from 'vs/base/common/json'; +import { values, keys } from 'vs/base/common/map'; +import { IUserFriendlyKeybinding } from 'vs/platform/keybinding/common/keybinding'; +import { firstIndex as findFirstIndex, equals } from 'vs/base/common/arrays'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import * as contentUtil from 'vs/platform/userDataSync/common/content'; +import { IStringDictionary } from 'vs/base/common/collections'; +import { FormattingOptions } from 'vs/base/common/jsonFormatter'; +import { IUserDataSyncUtilService } from 'vs/platform/userDataSync/common/userDataSync'; + +interface ICompareResult { + added: Set; + removed: Set; + updated: Set; +} + +interface IMergeResult { + hasLocalForwarded: boolean; + hasRemoteForwarded: boolean; + added: Set; + removed: Set; + updated: Set; + conflicts: Set; +} + +export async function merge(localContent: string, remoteContent: string, baseContent: string | null, formattingOptions: FormattingOptions, userDataSyncUtilService: IUserDataSyncUtilService): Promise<{ mergeContent: string, hasChanges: boolean, hasConflicts: boolean }> { + const local = parse(localContent); + const remote = parse(remoteContent); + const base = baseContent ? parse(baseContent) : null; + + const userbindings: string[] = [...local, ...remote, ...(base || [])].map(keybinding => keybinding.key); + const normalizedKeys = await userDataSyncUtilService.resolveUserBindings(userbindings); + let keybindingsMergeResult = computeMergeResultByKeybinding(local, remote, base, normalizedKeys); + + if (!keybindingsMergeResult.hasLocalForwarded && !keybindingsMergeResult.hasRemoteForwarded) { + // No changes found between local and remote. + return { mergeContent: localContent, hasChanges: false, hasConflicts: false }; + } + + if (!keybindingsMergeResult.hasLocalForwarded && keybindingsMergeResult.hasRemoteForwarded) { + return { mergeContent: remoteContent, hasChanges: true, hasConflicts: false }; + } + + if (keybindingsMergeResult.hasLocalForwarded && !keybindingsMergeResult.hasRemoteForwarded) { + // Local has moved forward and remote has not. Return local. + return { mergeContent: localContent, hasChanges: true, hasConflicts: false }; + } + + // Both local and remote has moved forward. + const localByCommand = byCommand(local); + const remoteByCommand = byCommand(remote); + const baseByCommand = base ? byCommand(base) : null; + const localToRemoteByCommand = compareByCommand(localByCommand, remoteByCommand, normalizedKeys); + const baseToLocalByCommand = baseByCommand ? compareByCommand(baseByCommand, localByCommand, normalizedKeys) : { added: keys(localByCommand).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; + const baseToRemoteByCommand = baseByCommand ? compareByCommand(baseByCommand, remoteByCommand, normalizedKeys) : { added: keys(remoteByCommand).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; + + const commandsMergeResult = computeMergeResult(localToRemoteByCommand, baseToLocalByCommand, baseToRemoteByCommand); + let mergeContent = localContent; + + // Removed commands in Remote + for (const command of values(commandsMergeResult.removed)) { + if (commandsMergeResult.conflicts.has(command)) { + continue; + } + mergeContent = removeKeybindings(mergeContent, command, formattingOptions); + } + + // Added commands in remote + for (const command of values(commandsMergeResult.added)) { + if (commandsMergeResult.conflicts.has(command)) { + continue; + } + const keybindings = remoteByCommand.get(command)!; + // Ignore negated commands + if (keybindings.some(keybinding => keybinding.command !== `-${command}` && keybindingsMergeResult.conflicts.has(normalizedKeys[keybinding.key]))) { + commandsMergeResult.conflicts.add(command); + continue; + } + mergeContent = addKeybindings(mergeContent, keybindings, formattingOptions); + } + + // Updated commands in Remote + for (const command of values(commandsMergeResult.updated)) { + if (commandsMergeResult.conflicts.has(command)) { + continue; + } + const keybindings = remoteByCommand.get(command)!; + // Ignore negated commands + if (keybindings.some(keybinding => keybinding.command !== `-${command}` && keybindingsMergeResult.conflicts.has(normalizedKeys[keybinding.key]))) { + commandsMergeResult.conflicts.add(command); + continue; + } + mergeContent = updateKeybindings(mergeContent, command, keybindings, formattingOptions); + } + + const hasConflicts = commandsMergeResult.conflicts.size > 0; + if (hasConflicts) { + mergeContent = `<<<<<<< local${formattingOptions.eol}` + + mergeContent + + `${formattingOptions.eol}=======${formattingOptions.eol}` + + remoteContent + + `${formattingOptions.eol}>>>>>>> remote`; + } + + return { mergeContent, hasChanges: true, hasConflicts }; +} + +function computeMergeResult(localToRemote: ICompareResult, baseToLocal: ICompareResult, baseToRemote: ICompareResult): { added: Set, removed: Set, updated: Set, conflicts: Set } { + const added: Set = new Set(); + const removed: Set = new Set(); + const updated: Set = new Set(); + const conflicts: Set = new Set(); + + // Removed keys in Local + for (const key of values(baseToLocal.removed)) { + // Got updated in remote + if (baseToRemote.updated.has(key)) { + conflicts.add(key); + } + } + + // Removed keys in Remote + for (const key of values(baseToRemote.removed)) { + if (conflicts.has(key)) { + continue; + } + // Got updated in local + if (baseToLocal.updated.has(key)) { + conflicts.add(key); + } else { + // remove the key + removed.add(key); + } + } + + // Added keys in Local + for (const key of values(baseToLocal.added)) { + if (conflicts.has(key)) { + continue; + } + // Got added in remote + if (baseToRemote.added.has(key)) { + // Has different value + if (localToRemote.updated.has(key)) { + conflicts.add(key); + } + } + } + + // Added keys in remote + for (const key of values(baseToRemote.added)) { + if (conflicts.has(key)) { + continue; + } + // Got added in local + if (baseToLocal.added.has(key)) { + // Has different value + if (localToRemote.updated.has(key)) { + conflicts.add(key); + } + } else { + added.add(key); + } + } + + // Updated keys in Local + for (const key of values(baseToLocal.updated)) { + if (conflicts.has(key)) { + continue; + } + // Got updated in remote + if (baseToRemote.updated.has(key)) { + // Has different value + if (localToRemote.updated.has(key)) { + conflicts.add(key); + } + } + } + + // Updated keys in Remote + for (const key of values(baseToRemote.updated)) { + if (conflicts.has(key)) { + continue; + } + // Got updated in local + if (baseToLocal.updated.has(key)) { + // Has different value + if (localToRemote.updated.has(key)) { + conflicts.add(key); + } + } else { + // updated key + updated.add(key); + } + } + return { added, removed, updated, conflicts }; +} + +function computeMergeResultByKeybinding(local: IUserFriendlyKeybinding[], remote: IUserFriendlyKeybinding[], base: IUserFriendlyKeybinding[] | null, normalizedKeys: IStringDictionary): IMergeResult { + const empty = new Set(); + const localByKeybinding = byKeybinding(local, normalizedKeys); + const remoteByKeybinding = byKeybinding(remote, normalizedKeys); + const baseByKeybinding = base ? byKeybinding(base, normalizedKeys) : null; + + const localToRemoteByKeybinding = compareByKeybinding(localByKeybinding, remoteByKeybinding); + if (localToRemoteByKeybinding.added.size === 0 && localToRemoteByKeybinding.removed.size === 0 && localToRemoteByKeybinding.updated.size === 0) { + return { hasLocalForwarded: false, hasRemoteForwarded: false, added: empty, removed: empty, updated: empty, conflicts: empty }; + } + + const baseToLocalByKeybinding = baseByKeybinding ? compareByKeybinding(baseByKeybinding, localByKeybinding) : { added: keys(localByKeybinding).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; + if (baseToLocalByKeybinding.added.size === 0 && baseToLocalByKeybinding.removed.size === 0 && baseToLocalByKeybinding.updated.size === 0) { + // Remote has moved forward and local has not. + return { hasLocalForwarded: false, hasRemoteForwarded: true, added: empty, removed: empty, updated: empty, conflicts: empty }; + } + + const baseToRemoteByKeybinding = baseByKeybinding ? compareByKeybinding(baseByKeybinding, remoteByKeybinding) : { added: keys(remoteByKeybinding).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; + if (baseToRemoteByKeybinding.added.size === 0 && baseToRemoteByKeybinding.removed.size === 0 && baseToRemoteByKeybinding.updated.size === 0) { + return { hasLocalForwarded: true, hasRemoteForwarded: false, added: empty, removed: empty, updated: empty, conflicts: empty }; + } + + const { added, removed, updated, conflicts } = computeMergeResult(localToRemoteByKeybinding, baseToLocalByKeybinding, baseToRemoteByKeybinding); + return { hasLocalForwarded: true, hasRemoteForwarded: true, added, removed, updated, conflicts }; +} + +function byKeybinding(keybindings: IUserFriendlyKeybinding[], keys: IStringDictionary) { + const map: Map = new Map(); + for (const keybinding of keybindings) { + const key = keys[keybinding.key]; + let value = map.get(key); + if (!value) { + value = []; + map.set(key, value); + } + value.push(keybinding); + + } + return map; +} + +function byCommand(keybindings: IUserFriendlyKeybinding[]): Map { + const map: Map = new Map(); + for (const keybinding of keybindings) { + const command = keybinding.command[0] === '-' ? keybinding.command.substring(1) : keybinding.command; + let value = map.get(command); + if (!value) { + value = []; + map.set(command, value); + } + value.push(keybinding); + } + return map; +} + + +function compareByKeybinding(from: Map, to: Map): ICompareResult { + const fromKeys = keys(from); + const toKeys = keys(to); + const added = toKeys.filter(key => fromKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set()); + const removed = fromKeys.filter(key => toKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set()); + const updated: Set = new Set(); + + for (const key of fromKeys) { + if (removed.has(key)) { + continue; + } + const value1: IUserFriendlyKeybinding[] = from.get(key)!.map(keybinding => ({ ...keybinding, ...{ key } })); + const value2: IUserFriendlyKeybinding[] = to.get(key)!.map(keybinding => ({ ...keybinding, ...{ key } })); + if (!equals(value1, value2, (a, b) => isSameKeybinding(a, b))) { + updated.add(key); + } + } + + return { added, removed, updated }; +} + +function compareByCommand(from: Map, to: Map, normalizedKeys: IStringDictionary): ICompareResult { + const fromKeys = keys(from); + const toKeys = keys(to); + const added = toKeys.filter(key => fromKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set()); + const removed = fromKeys.filter(key => toKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set()); + const updated: Set = new Set(); + + for (const key of fromKeys) { + if (removed.has(key)) { + continue; + } + const value1: IUserFriendlyKeybinding[] = from.get(key)!.map(keybinding => ({ ...keybinding, ...{ key: normalizedKeys[keybinding.key] } })); + const value2: IUserFriendlyKeybinding[] = to.get(key)!.map(keybinding => ({ ...keybinding, ...{ key: normalizedKeys[keybinding.key] } })); + if (!areSameKeybindingsWithSameCommand(value1, value2)) { + updated.add(key); + } + } + + return { added, removed, updated }; +} + +function areSameKeybindingsWithSameCommand(value1: IUserFriendlyKeybinding[], value2: IUserFriendlyKeybinding[]): boolean { + // Compare entries adding keybindings + if (!equals(value1.filter(({ command }) => command[0] !== '-'), value2.filter(({ command }) => command[0] !== '-'), (a, b) => isSameKeybinding(a, b))) { + return false; + } + // Compare entries removing keybindings + if (!equals(value1.filter(({ command }) => command[0] === '-'), value2.filter(({ command }) => command[0] === '-'), (a, b) => isSameKeybinding(a, b))) { + return false; + } + return true; +} + +function isSameKeybinding(a: IUserFriendlyKeybinding, b: IUserFriendlyKeybinding): boolean { + if (a.command !== b.command) { + return false; + } + if (a.key !== b.key) { + return false; + } + const whenA = ContextKeyExpr.deserialize(a.when); + const whenB = ContextKeyExpr.deserialize(b.when); + if ((whenA && !whenB) || (!whenA && whenB)) { + return false; + } + if (whenA && whenB && !whenA.equals(whenB)) { + return false; + } + if (!objects.equals(a.args, b.args)) { + return false; + } + return true; +} + +function addKeybindings(content: string, keybindings: IUserFriendlyKeybinding[], formattingOptions: FormattingOptions): string { + for (const keybinding of keybindings) { + content = contentUtil.edit(content, [-1], keybinding, formattingOptions); + } + return content; +} + +function removeKeybindings(content: string, command: string, formattingOptions: FormattingOptions): string { + const keybindings = parse(content); + for (let index = keybindings.length - 1; index >= 0; index--) { + if (keybindings[index].command === command || keybindings[index].command === `-${command}`) { + content = contentUtil.edit(content, [index], undefined, formattingOptions); + } + } + return content; +} + +function updateKeybindings(content: string, command: string, keybindings: IUserFriendlyKeybinding[], formattingOptions: FormattingOptions): string { + const allKeybindings = parse(content); + const location = findFirstIndex(allKeybindings, keybinding => keybinding.command === command || keybinding.command === `-${command}`); + // Remove all entries with this command + for (let index = allKeybindings.length - 1; index >= 0; index--) { + if (allKeybindings[index].command === command || allKeybindings[index].command === `-${command}`) { + content = contentUtil.edit(content, [index], undefined, formattingOptions); + } + } + // add all entries at the same location where the entry with this command was located. + for (let index = keybindings.length - 1; index >= 0; index--) { + content = contentUtil.edit(content, [location], keybindings[index], formattingOptions); + } + return content; +} diff --git a/src/vs/platform/userDataSync/common/keybindingsSync.ts b/src/vs/platform/userDataSync/common/keybindingsSync.ts new file mode 100644 index 000000000000..933f9e39cbc1 --- /dev/null +++ b/src/vs/platform/userDataSync/common/keybindingsSync.ts @@ -0,0 +1,348 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable } from 'vs/base/common/lifecycle'; +import { IFileService, FileSystemProviderErrorCode, FileSystemProviderError, IFileContent } from 'vs/platform/files/common/files'; +import { IUserData, UserDataSyncStoreError, UserDataSyncStoreErrorCode, ISynchroniser, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService } from 'vs/platform/userDataSync/common/userDataSync'; +import { merge } from 'vs/platform/userDataSync/common/keybindingsMerge'; +import { VSBuffer } from 'vs/base/common/buffer'; +import { parse, ParseError } from 'vs/base/common/json'; +import { localize } from 'vs/nls'; +import { Emitter, Event } from 'vs/base/common/event'; +import { CancelablePromise, createCancelablePromise, ThrottledDelayer } from 'vs/base/common/async'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { URI } from 'vs/base/common/uri'; +import { joinPath } from 'vs/base/common/resources'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { OS, OperatingSystem } from 'vs/base/common/platform'; +import { isUndefined } from 'vs/base/common/types'; + +interface ISyncContent { + mac?: string; + linux?: string; + windows?: string; + all?: string; +} + +interface ISyncPreviewResult { + readonly fileContent: IFileContent | null; + readonly remoteUserData: IUserData; + readonly hasLocalChanged: boolean; + readonly hasRemoteChanged: boolean; + readonly hasConflicts: boolean; +} + +export class KeybindingsSynchroniser extends Disposable implements ISynchroniser { + + private static EXTERNAL_USER_DATA_KEYBINDINGS_KEY: string = 'keybindings'; + + private syncPreviewResultPromise: CancelablePromise | null = null; + + private _status: SyncStatus = SyncStatus.Idle; + get status(): SyncStatus { return this._status; } + private _onDidChangStatus: Emitter = this._register(new Emitter()); + readonly onDidChangeStatus: Event = this._onDidChangStatus.event; + + private readonly throttledDelayer: ThrottledDelayer; + private _onDidChangeLocal: Emitter = this._register(new Emitter()); + readonly onDidChangeLocal: Event = this._onDidChangeLocal.event; + + private readonly lastSyncKeybindingsResource: URI; + + constructor( + @IUserDataSyncStoreService private readonly userDataSyncStoreService: IUserDataSyncStoreService, + @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @IFileService private readonly fileService: IFileService, + @IEnvironmentService private readonly environmentService: IEnvironmentService, + @IUserDataSyncUtilService private readonly userDataSyncUtilService: IUserDataSyncUtilService, + ) { + super(); + this.lastSyncKeybindingsResource = joinPath(this.environmentService.userRoamingDataHome, '.lastSyncKeybindings.json'); + this.throttledDelayer = this._register(new ThrottledDelayer(500)); + this._register(this.fileService.watch(this.environmentService.keybindingsResource)); + this._register(Event.filter(this.fileService.onFileChanges, e => e.contains(this.environmentService.keybindingsResource))(() => this.throttledDelayer.trigger(() => this.onDidChangeKeybindings()))); + } + + private async onDidChangeKeybindings(): Promise { + const localFileContent = await this.getLocalContent(); + const lastSyncData = await this.getLastSyncUserData(); + if (localFileContent && lastSyncData) { + if (localFileContent.value.toString() !== lastSyncData.content) { + this._onDidChangeLocal.fire(); + return; + } + } + if (!localFileContent || !lastSyncData) { + this._onDidChangeLocal.fire(); + return; + } + } + + private setStatus(status: SyncStatus): void { + if (this._status !== status) { + this._status = status; + this._onDidChangStatus.fire(status); + } + } + + async sync(_continue?: boolean): Promise { + if (!this.configurationService.getValue('sync.enableKeybindings')) { + this.logService.trace('Keybindings: Skipping synchronizing keybindings as it is disabled.'); + return false; + } + + if (_continue) { + this.logService.info('Keybindings: Resumed synchronizing keybindings'); + return this.continueSync(); + } + + if (this.status !== SyncStatus.Idle) { + this.logService.trace('Keybindings: Skipping synchronizing keybindings as it is running already.'); + return false; + } + + this.logService.trace('Keybindings: Started synchronizing keybindings...'); + this.setStatus(SyncStatus.Syncing); + + try { + const result = await this.getPreview(); + if (result.hasConflicts) { + this.logService.info('Keybindings: Detected conflicts while synchronizing keybindings.'); + this.setStatus(SyncStatus.HasConflicts); + return false; + } + await this.apply(); + return true; + } catch (e) { + this.syncPreviewResultPromise = null; + this.setStatus(SyncStatus.Idle); + if (e instanceof UserDataSyncStoreError && e.code === UserDataSyncStoreErrorCode.Rejected) { + // Rejected as there is a new remote version. Syncing again, + this.logService.info('Keybindings: Failed to synchronise keybindings as there is a new remote version available. Synchronizing again...'); + return this.sync(); + } + if (e instanceof FileSystemProviderError && e.code === FileSystemProviderErrorCode.FileExists) { + // Rejected as there is a new local version. Syncing again. + this.logService.info('Keybindings: Failed to synchronise keybindings as there is a new local version available. Synchronizing again...'); + return this.sync(); + } + throw e; + } + } + + stop(): void { + if (this.syncPreviewResultPromise) { + this.syncPreviewResultPromise.cancel(); + this.syncPreviewResultPromise = null; + this.logService.info('Keybindings: Stopped synchronizing keybindings.'); + } + this.fileService.del(this.environmentService.keybindingsSyncPreviewResource); + this.setStatus(SyncStatus.Idle); + } + + private async continueSync(): Promise { + if (this.status !== SyncStatus.HasConflicts) { + return false; + } + await this.apply(); + return true; + } + + private async apply(): Promise { + if (!this.syncPreviewResultPromise) { + return; + } + + if (await this.fileService.exists(this.environmentService.keybindingsSyncPreviewResource)) { + const keybindingsPreivew = await this.fileService.readFile(this.environmentService.keybindingsSyncPreviewResource); + const content = keybindingsPreivew.value.toString(); + if (this.hasErrors(content)) { + const error = new Error(localize('errorInvalidKeybindings', "Unable to sync keybindings. Please resolve conflicts without any errors/warnings and try again.")); + this.logService.error(error); + throw error; + } + + let { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged } = await this.syncPreviewResultPromise; + if (!hasLocalChanged && !hasRemoteChanged) { + this.logService.trace('Keybindings: No changes found during synchronizing keybindings.'); + } + if (hasLocalChanged) { + this.logService.info('Keybindings: Updating local keybindings'); + await this.updateLocalContent(content, fileContent); + } + if (hasRemoteChanged) { + this.logService.info('Keybindings: Updating remote keybindings'); + const remoteContents = this.updateSyncContent(content, remoteUserData.content); + const ref = await this.updateRemoteUserData(remoteContents, remoteUserData.ref); + remoteUserData = { ref, content: remoteContents }; + } + if (remoteUserData.content) { + this.logService.info('Keybindings: Updating last synchronised keybindings'); + const lastSyncContent = this.updateSyncContent(content, null); + await this.updateLastSyncUserData({ ref: remoteUserData.ref, content: lastSyncContent }); + } + + // Delete the preview + await this.fileService.del(this.environmentService.keybindingsSyncPreviewResource); + } else { + this.logService.trace('Keybindings: No changes found during synchronizing keybindings.'); + } + + this.logService.trace('Keybindings: Finised synchronizing keybindings.'); + this.syncPreviewResultPromise = null; + this.setStatus(SyncStatus.Idle); + } + + private hasErrors(content: string): boolean { + const parseErrors: ParseError[] = []; + parse(content, parseErrors, { allowEmptyContent: true, allowTrailingComma: true }); + return parseErrors.length > 0; + } + + private getPreview(): Promise { + if (!this.syncPreviewResultPromise) { + this.syncPreviewResultPromise = createCancelablePromise(token => this.generatePreview(token)); + } + return this.syncPreviewResultPromise; + } + + private async generatePreview(token: CancellationToken): Promise { + const lastSyncData = await this.getLastSyncUserData(); + const lastSyncContent = lastSyncData && lastSyncData.content ? this.getKeybindingsContentFromSyncContent(lastSyncData.content) : null; + const remoteUserData = await this.getRemoteUserData(lastSyncData); + const remoteContent = remoteUserData.content ? this.getKeybindingsContentFromSyncContent(remoteUserData.content) : null; + // Get file content last to get the latest + const fileContent = await this.getLocalContent(); + let hasLocalChanged: boolean = false; + let hasRemoteChanged: boolean = false; + let hasConflicts: boolean = false; + let previewContent = null; + + if (remoteContent) { + const localContent: string = fileContent ? fileContent.value.toString() : '[]'; + if (this.hasErrors(localContent)) { + this.logService.error('Keybindings: Unable to sync keybindings as there are errors/warning in keybindings file.'); + return { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged, hasConflicts }; + } + + if (!lastSyncContent // First time sync + || lastSyncContent !== localContent // Local has forwarded + || lastSyncContent !== remoteContent // Remote has forwarded + ) { + this.logService.trace('Keybindings: Merging remote keybindings with local keybindings...'); + const formattingOptions = await this.userDataSyncUtilService.resolveFormattingOptions(this.environmentService.keybindingsResource); + const result = await merge(localContent, remoteContent, lastSyncContent, formattingOptions, this.userDataSyncUtilService); + // Sync only if there are changes + if (result.hasChanges) { + hasLocalChanged = result.mergeContent !== localContent; + hasRemoteChanged = result.mergeContent !== remoteContent; + hasConflicts = result.hasConflicts; + previewContent = result.mergeContent; + } + } + } + + // First time syncing to remote + else if (fileContent) { + this.logService.info('Keybindings: Remote keybindings does not exist. Synchronizing keybindings for the first time.'); + hasRemoteChanged = true; + previewContent = fileContent.value.toString(); + } + + if (previewContent && !token.isCancellationRequested) { + await this.fileService.writeFile(this.environmentService.keybindingsSyncPreviewResource, VSBuffer.fromString(previewContent)); + } + + return { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged, hasConflicts }; + } + + private async getLocalContent(): Promise { + try { + return await this.fileService.readFile(this.environmentService.keybindingsResource); + } catch (error) { + return null; + } + } + + private async updateLocalContent(newContent: string, oldContent: IFileContent | null): Promise { + if (oldContent) { + // file exists already + await this.fileService.writeFile(this.environmentService.keybindingsResource, VSBuffer.fromString(newContent), oldContent); + } else { + // file does not exist + await this.fileService.createFile(this.environmentService.keybindingsResource, VSBuffer.fromString(newContent), { overwrite: false }); + } + } + + private async getLastSyncUserData(): Promise { + try { + const content = await this.fileService.readFile(this.lastSyncKeybindingsResource); + return JSON.parse(content.value.toString()); + } catch (error) { + return null; + } + } + + private async updateLastSyncUserData(remoteUserData: IUserData): Promise { + await this.fileService.writeFile(this.lastSyncKeybindingsResource, VSBuffer.fromString(JSON.stringify(remoteUserData))); + } + + private async getRemoteUserData(lastSyncData: IUserData | null): Promise { + return this.userDataSyncStoreService.read(KeybindingsSynchroniser.EXTERNAL_USER_DATA_KEYBINDINGS_KEY, lastSyncData); + } + + private async updateRemoteUserData(content: string, ref: string | null): Promise { + return this.userDataSyncStoreService.write(KeybindingsSynchroniser.EXTERNAL_USER_DATA_KEYBINDINGS_KEY, content, ref); + } + + private getKeybindingsContentFromSyncContent(syncContent: string): string | null { + try { + const parsed = JSON.parse(syncContent); + if (!this.configurationService.getValue('sync.keybindingsPerPlatform')) { + return isUndefined(parsed.all) ? null : parsed.all; + } + switch (OS) { + case OperatingSystem.Macintosh: + return isUndefined(parsed.mac) ? null : parsed.mac; + case OperatingSystem.Linux: + return isUndefined(parsed.linux) ? null : parsed.linux; + case OperatingSystem.Windows: + return isUndefined(parsed.windows) ? null : parsed.windows; + } + } catch (e) { + this.logService.error(e); + return null; + } + } + + private updateSyncContent(keybindingsContent: string, syncContent: string | null): string { + let parsed: ISyncContent = {}; + try { + parsed = JSON.parse(syncContent || '{}'); + } catch (e) { + this.logService.error(e); + } + if (!this.configurationService.getValue('sync.keybindingsPerPlatform')) { + parsed.all = keybindingsContent; + } else { + delete parsed.all; + } + switch (OS) { + case OperatingSystem.Macintosh: + parsed.mac = keybindingsContent; + break; + case OperatingSystem.Linux: + parsed.linux = keybindingsContent; + break; + case OperatingSystem.Windows: + parsed.windows = keybindingsContent; + break; + } + return JSON.stringify(parsed); + } + +} diff --git a/src/vs/platform/userDataSync/common/keybindingsSyncIpc.ts b/src/vs/platform/userDataSync/common/keybindingsSyncIpc.ts new file mode 100644 index 000000000000..02b96500f34c --- /dev/null +++ b/src/vs/platform/userDataSync/common/keybindingsSyncIpc.ts @@ -0,0 +1,45 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; +import { Event } from 'vs/base/common/event'; +import { IUserDataSyncUtilService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IStringDictionary } from 'vs/base/common/collections'; +import { URI } from 'vs/base/common/uri'; +import { FormattingOptions } from 'vs/base/common/jsonFormatter'; + +export class UserDataSycnUtilServiceChannel implements IServerChannel { + + constructor(private readonly service: IUserDataSyncUtilService) { } + + listen(_: unknown, event: string): Event { + throw new Error(`Event not found: ${event}`); + } + + call(context: any, command: string, args?: any): Promise { + switch (command) { + case 'resolveUserKeybindings': return this.service.resolveUserBindings(args[0]); + case 'resolveFormattingOptions': return this.service.resolveFormattingOptions(URI.revive(args[0])); + } + throw new Error('Invalid call'); + } +} + +export class UserDataSyncUtilServiceClient implements IUserDataSyncUtilService { + + _serviceBrand: undefined; + + constructor(private readonly channel: IChannel) { + } + + async resolveUserBindings(userbindings: string[]): Promise> { + return this.channel.call('resolveUserKeybindings', [userbindings]); + } + + async resolveFormattingOptions(file: URI): Promise { + return this.channel.call('resolveFormattingOptions', [file]); + } + +} diff --git a/src/vs/platform/userDataSync/common/settingsSync.ts b/src/vs/platform/userDataSync/common/settingsSync.ts index caaf88f550a7..ed78d72517d0 100644 --- a/src/vs/platform/userDataSync/common/settingsSync.ts +++ b/src/vs/platform/userDataSync/common/settingsSync.ts @@ -80,7 +80,7 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser { } async sync(_continue?: boolean): Promise { - if (!this.configurationService.getValue('configurationSync.enableSettings')) { + if (!this.configurationService.getValue('sync.enableSettings')) { this.logService.trace('Settings: Skipping synchronizing settings as it is disabled.'); return false; } @@ -135,10 +135,9 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser { } private async continueSync(): Promise { - if (this.status !== SyncStatus.HasConflicts) { - return false; + if (this.status === SyncStatus.HasConflicts) { + await this.apply(); } - await this.apply(); return true; } @@ -153,7 +152,7 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser { if (this.hasErrors(content)) { const error = new Error(localize('errorInvalidSettings', "Unable to sync settings. Please resolve conflicts without any errors/warnings and try again.")); this.logService.error(error); - return Promise.reject(error); + throw error; } let { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged } = await this.syncPreviewResultPromise; @@ -188,7 +187,7 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser { private hasErrors(content: string): boolean { const parseErrors: ParseError[] = []; - parse(content, parseErrors); + parse(content, parseErrors, { allowEmptyContent: true, allowTrailingComma: true }); return parseErrors.length > 0; } @@ -218,8 +217,8 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser { } if (!lastSyncData // First time sync - || lastSyncData.content !== localContent // Local has moved forwarded - || lastSyncData.content !== remoteContent // Remote has moved forwarded + || lastSyncData.content !== localContent // Local has forwarded + || lastSyncData.content !== remoteContent // Remote has forwarded ) { this.logService.trace('Settings: Merging remote settings with local settings...'); const result = await this.settingsMergeService.merge(localContent, remoteContent, lastSyncData ? lastSyncData.content : null, this.getIgnoredSettings()); @@ -248,13 +247,23 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser { } private getIgnoredSettings(settingsContent?: string): string[] { - const value: string[] = (settingsContent ? parse(settingsContent)['configurationSync.settingsToIgnore'] : this.configurationService.getValue('configurationSync.settingsToIgnore')) || []; + let value: string[] = []; + if (settingsContent) { + const setting = parse(settingsContent); + if (setting) { + value = setting['sync.ignoredSettings']; + } + } else { + value = this.configurationService.getValue('sync.ignoredSettings'); + } const added: string[] = [], removed: string[] = []; - for (const key of value) { - if (startsWith(key, '-')) { - removed.push(key.substring(1)); - } else { - added.push(key); + if (Array.isArray(value)) { + for (const key of value) { + if (startsWith(key, '-')) { + removed.push(key.substring(1)); + } else { + added.push(key); + } } } return [...DEFAULT_IGNORED_SETTINGS, ...added].filter(setting => removed.indexOf(setting) === -1); diff --git a/src/vs/platform/userDataSync/common/userDataAutoSync.ts b/src/vs/platform/userDataSync/common/userDataAutoSync.ts new file mode 100644 index 000000000000..849f9afe82b1 --- /dev/null +++ b/src/vs/platform/userDataSync/common/userDataAutoSync.ts @@ -0,0 +1,69 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IUserDataSyncService, SyncStatus, IUserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSync'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { Event } from 'vs/base/common/event'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { timeout } from 'vs/base/common/async'; +import { IAuthTokenService, AuthTokenStatus } from 'vs/platform/auth/common/auth'; + +export class UserDataAutoSync extends Disposable { + + private enabled: boolean = false; + + constructor( + @IConfigurationService private readonly configurationService: IConfigurationService, + @IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService, + @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, + @IAuthTokenService private readonly authTokenService: IAuthTokenService, + ) { + super(); + this.updateEnablement(false); + this._register(Event.any(authTokenService.onDidChangeStatus, userDataSyncService.onDidChangeStatus)(() => this.updateEnablement(true))); + this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('sync.enable'))(() => this.updateEnablement(true))); + } + + private updateEnablement(stopIfDisabled: boolean): void { + const enabled = this.isSyncEnabled(); + if (this.enabled === enabled) { + return; + } + + this.enabled = enabled; + if (this.enabled) { + this.logService.info('Syncing configuration started'); + this.sync(true); + return; + } else { + if (stopIfDisabled) { + this.userDataSyncService.stop(); + this.logService.info('Syncing configuration stopped.'); + } + } + + } + + protected async sync(loop: boolean): Promise { + if (this.enabled) { + try { + await this.userDataSyncService.sync(); + } catch (e) { + this.logService.error(e); + } + if (loop) { + await timeout(1000 * 60 * 5); // Loop sync for every 5 min. + this.sync(loop); + } + } + } + + private isSyncEnabled(): boolean { + return this.configurationService.getValue('sync.enable') + && this.userDataSyncService.status !== SyncStatus.Uninitialized + && this.authTokenService.status === AuthTokenStatus.SignedIn; + } + +} diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index dc2b337d3a58..683627ecca66 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -14,61 +14,74 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { ILogService } from 'vs/platform/log/common/log'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IStringDictionary } from 'vs/base/common/collections'; +import { FormattingOptions } from 'vs/base/common/jsonFormatter'; +import { URI } from 'vs/base/common/uri'; + +const CONFIGURATION_SYNC_STORE_KEY = 'configurationSync.store'; export const DEFAULT_IGNORED_SETTINGS = [ - 'configurationSync.enable', - 'configurationSync.enableSettings', - 'configurationSync.enableExtensions', + CONFIGURATION_SYNC_STORE_KEY, + 'sync.enable', + 'sync.enableSettings', + 'sync.enableExtensions', ]; export function registerConfiguration(): IDisposable { const ignoredSettingsSchemaId = 'vscode://schemas/ignoredSettings'; const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); configurationRegistry.registerConfiguration({ - id: 'configurationSync', + id: 'sync', order: 30, - title: localize('configurationSync', "Configuration Sync"), + title: localize('sync', "Sync"), type: 'object', properties: { - 'configurationSync.enable': { + 'sync.enable': { type: 'boolean', - description: localize('configurationSync.enable', "When enabled, synchronises configuration that includes Settings and Extensions."), - default: true, + description: localize('sync.enable', "Enable synchronization."), + default: false, scope: ConfigurationScope.APPLICATION }, - 'configurationSync.enableSettings': { + 'sync.enableSettings': { + type: 'boolean', + description: localize('sync.enableSettings', "Enable synchronizing settings."), + default: true, + scope: ConfigurationScope.APPLICATION, + }, + 'sync.enableExtensions': { type: 'boolean', - description: localize('configurationSync.enableSettings', "When enabled settings are synchronised while synchronizing configuration."), + description: localize('sync.enableExtensions', "Enable synchronizing extensions."), default: true, scope: ConfigurationScope.APPLICATION, }, - 'configurationSync.enableExtensions': { + 'sync.enableKeybindings': { type: 'boolean', - description: localize('configurationSync.enableExtensions', "When enabled extensions are synchronised while synchronizing configuration."), + description: localize('sync.enableKeybindings', "Enable synchronizing keybindings."), default: true, scope: ConfigurationScope.APPLICATION, }, - 'configurationSync.extensionsToIgnore': { + 'sync.keybindingsPerPlatform': { + type: 'boolean', + description: localize('sync.keybindingsPerPlatform', "Synchronize keybindings per platform."), + default: true, + scope: ConfigurationScope.APPLICATION, + }, + 'sync.ignoredExtensions': { 'type': 'array', - description: localize('configurationSync.extensionsToIgnore', "Configure extensions to be ignored while syncing."), + description: localize('sync.ignoredExtensions', "Configure extensions to be ignored while synchronizing."), 'default': [], 'scope': ConfigurationScope.APPLICATION, uniqueItems: true }, - 'configurationSync.settingsToIgnore': { + 'sync.ignoredSettings': { 'type': 'array', - description: localize('configurationSync.settingsToIgnore', "Configure settings to be ignored while syncing. \nDefault Ignored Settings:\n\n{0}", DEFAULT_IGNORED_SETTINGS.sort().map(setting => `- ${setting}`).join('\n')), + description: localize('sync.ignoredSettings', "Configure settings to be ignored while synchronizing. \nDefault Ignored Settings:\n\n{0}", DEFAULT_IGNORED_SETTINGS.sort().map(setting => `- ${setting}`).join('\n')), 'default': [], 'scope': ConfigurationScope.APPLICATION, $ref: ignoredSettingsSchemaId, additionalProperties: true, uniqueItems: true - }, - 'configurationSync.enableAuth': { - 'type': 'boolean', - description: localize('configurationSync.enableAuth', "Enables authentication and requires VS Code restart when changed"), - 'default': false, - 'scope': ConfigurationScope.APPLICATION } } }); @@ -104,12 +117,23 @@ export class UserDataSyncStoreError extends Error { } +export interface IUserDataSyncStore { + url: string; + name: string; + account: string; +} + +export function getUserDataSyncStore(configurationService: IConfigurationService): IUserDataSyncStore | undefined { + const value = configurationService.getValue(CONFIGURATION_SYNC_STORE_KEY); + return value && value.url && value.name && value.account ? value : undefined; +} + export const IUserDataSyncStoreService = createDecorator('IUserDataSyncStoreService'); export interface IUserDataSyncStoreService { _serviceBrand: undefined; - readonly enabled: boolean; + readonly userDataSyncStore: IUserDataSyncStore | undefined; read(key: string, oldValue: IUserData | null): Promise; write(key: string, content: string, ref: string | null): Promise; @@ -123,6 +147,7 @@ export interface ISyncExtension { export const enum SyncSource { Settings = 1, + Keybindings, Extensions } @@ -164,6 +189,18 @@ export interface ISettingsMergeService { } +export const IUserDataSyncUtilService = createDecorator('IUserDataSyncUtilService'); + +export interface IUserDataSyncUtilService { + + _serviceBrand: undefined; + + resolveUserBindings(userbindings: string[]): Promise>; + + resolveFormattingOptions(resource: URI): Promise; + +} + export const IUserDataSyncLogService = createDecorator('IUserDataSyncLogService'); export interface IUserDataSyncLogService extends ILogService { diff --git a/src/vs/platform/userDataSync/common/userDataSyncService.ts b/src/vs/platform/userDataSync/common/userDataSyncService.ts index eda99c724542..3b9245026d94 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncService.ts @@ -3,16 +3,15 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IUserDataSyncService, SyncStatus, ISynchroniser, IUserDataSyncStoreService, SyncSource, IUserDataSyncLogService, UserDataSyncStoreError, UserDataSyncStoreErrorCode } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncService, SyncStatus, ISynchroniser, IUserDataSyncStoreService, SyncSource } from 'vs/platform/userDataSync/common/userDataSync'; import { Disposable } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { SettingsSynchroniser } from 'vs/platform/userDataSync/common/settingsSync'; import { Emitter, Event } from 'vs/base/common/event'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { timeout } from 'vs/base/common/async'; import { ExtensionsSynchroniser } from 'vs/platform/userDataSync/common/extensionsSync'; import { IExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { IAuthTokenService, AuthTokenStatus } from 'vs/platform/auth/common/auth'; +import { KeybindingsSynchroniser } from 'vs/platform/userDataSync/common/keybindingsSync'; export class UserDataSyncService extends Disposable implements IUserDataSyncService { @@ -31,6 +30,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ get conflictsSource(): SyncSource | null { return this._conflictsSource; } private readonly settingsSynchroniser: SettingsSynchroniser; + private readonly keybindingsSynchroniser: KeybindingsSynchroniser; private readonly extensionsSynchroniser: ExtensionsSynchroniser; constructor( @@ -40,20 +40,25 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ ) { super(); this.settingsSynchroniser = this._register(this.instantiationService.createInstance(SettingsSynchroniser)); + this.keybindingsSynchroniser = this._register(this.instantiationService.createInstance(KeybindingsSynchroniser)); this.extensionsSynchroniser = this._register(this.instantiationService.createInstance(ExtensionsSynchroniser)); - this.synchronisers = [this.settingsSynchroniser, this.extensionsSynchroniser]; + this.synchronisers = [this.settingsSynchroniser, this.keybindingsSynchroniser, this.extensionsSynchroniser]; this.updateStatus(); - this._register(Event.any(...this.synchronisers.map(s => Event.map(s.onDidChangeStatus, () => undefined)))(() => this.updateStatus())); + + if (this.userDataSyncStoreService.userDataSyncStore) { + this._register(Event.any(...this.synchronisers.map(s => Event.map(s.onDidChangeStatus, () => undefined)))(() => this.updateStatus())); + this._register(authTokenService.onDidChangeStatus(() => this.onDidChangeAuthTokenStatus())); + } + this.onDidChangeLocal = Event.any(...this.synchronisers.map(s => s.onDidChangeLocal)); - this._register(authTokenService.onDidChangeStatus(() => this.onDidChangeAuthTokenStatus())); } async sync(_continue?: boolean): Promise { - if (!this.userDataSyncStoreService.enabled) { + if (!this.userDataSyncStoreService.userDataSyncStore) { throw new Error('Not enabled'); } - if (this.authTokenService.status === AuthTokenStatus.Inactive) { - return Promise.reject('Not Authenticated. Please sign in to start sync.'); + if (this.authTokenService.status === AuthTokenStatus.SignedOut) { + throw new Error('Not Authenticated. Please sign in to start sync.'); } for (const synchroniser of this.synchronisers) { if (!await synchroniser.sync(_continue)) { @@ -64,7 +69,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ } stop(): void { - if (!this.userDataSyncStoreService.enabled) { + if (!this.userDataSyncStoreService.userDataSyncStore) { throw new Error('Not enabled'); } for (const synchroniser of this.synchronisers) { @@ -89,7 +94,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ } private computeStatus(): SyncStatus { - if (!this.userDataSyncStoreService.enabled) { + if (!this.userDataSyncStoreService.userDataSyncStore) { return SyncStatus.Uninitialized; } if (this.synchronisers.some(s => s.status === SyncStatus.HasConflicts)) { @@ -107,83 +112,17 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ if (source instanceof SettingsSynchroniser) { return SyncSource.Settings; } + if (source instanceof KeybindingsSynchroniser) { + return SyncSource.Keybindings; + } } return null; } private onDidChangeAuthTokenStatus(): void { - if (this.authTokenService.status === AuthTokenStatus.Inactive) { + if (this.authTokenService.status === AuthTokenStatus.SignedOut) { this.stop(); } } } - -export class UserDataAutoSync extends Disposable { - - private enabled: boolean = false; - - constructor( - @IConfigurationService private readonly configurationService: IConfigurationService, - @IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService, - @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, - @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, - @IAuthTokenService private readonly authTokenService: IAuthTokenService, - ) { - super(); - this.updateEnablement(false); - this._register(Event.any(authTokenService.onDidChangeStatus, userDataSyncService.onDidChangeStatus)(() => this.updateEnablement(true))); - this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('configurationSync.enable'))(() => this.updateEnablement(true))); - - // Sync immediately if there is a local change. - this._register(Event.debounce(this.userDataSyncService.onDidChangeLocal, () => undefined, 500)(() => this.sync(false))); - } - - private updateEnablement(stopIfDisabled: boolean): void { - const enabled = this.isSyncEnabled(); - if (this.enabled === enabled) { - return; - } - - this.enabled = enabled; - if (this.enabled) { - this.logService.info('Syncing configuration started'); - this.sync(true); - return; - } else { - if (stopIfDisabled) { - this.userDataSyncService.stop(); - this.logService.info('Syncing configuration stopped.'); - } - } - - } - - private async sync(loop: boolean): Promise { - if (this.enabled) { - try { - await this.userDataSyncService.sync(); - } catch (e) { - if (e instanceof UserDataSyncStoreError && e.code === UserDataSyncStoreErrorCode.Unauthroized) { - if (e instanceof UserDataSyncStoreError && e.code === UserDataSyncStoreErrorCode.Unauthroized && this.authTokenService.status === AuthTokenStatus.Disabled) { - this.logService.error('Sync failed because the server requires authorization. Please enable authorization.'); - } else { - this.logService.error(e); - } - } - this.logService.error(e); - } - if (loop) { - await timeout(1000 * 5); // Loop sync for every 5s. - this.sync(loop); - } - } - } - - private isSyncEnabled(): boolean { - return this.configurationService.getValue('configurationSync.enable') - && this.userDataSyncService.status !== SyncStatus.Uninitialized - && this.authTokenService.status !== AuthTokenStatus.Inactive; - } - -} diff --git a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts index 62121a6acc10..06dd7512c596 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts @@ -4,35 +4,36 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable, } from 'vs/base/common/lifecycle'; -import { IUserData, IUserDataSyncStoreService, UserDataSyncStoreErrorCode, UserDataSyncStoreError } from 'vs/platform/userDataSync/common/userDataSync'; -import { IProductService } from 'vs/platform/product/common/productService'; +import { IUserData, IUserDataSyncStoreService, UserDataSyncStoreErrorCode, UserDataSyncStoreError, IUserDataSyncStore, getUserDataSyncStore } from 'vs/platform/userDataSync/common/userDataSync'; import { IRequestService, asText, isSuccess } from 'vs/platform/request/common/request'; import { URI } from 'vs/base/common/uri'; import { joinPath } from 'vs/base/common/resources'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IHeaders, IRequestOptions, IRequestContext } from 'vs/base/parts/request/common/request'; -import { IAuthTokenService, AuthTokenStatus } from 'vs/platform/auth/common/auth'; +import { IAuthTokenService } from 'vs/platform/auth/common/auth'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; export class UserDataSyncStoreService extends Disposable implements IUserDataSyncStoreService { _serviceBrand: any; - get enabled(): boolean { return !!this.productService.settingsSyncStoreUrl; } + readonly userDataSyncStore: IUserDataSyncStore | undefined; constructor( - @IProductService private readonly productService: IProductService, + @IConfigurationService configurationService: IConfigurationService, @IRequestService private readonly requestService: IRequestService, @IAuthTokenService private readonly authTokenService: IAuthTokenService, ) { super(); + this.userDataSyncStore = getUserDataSyncStore(configurationService); } async read(key: string, oldValue: IUserData | null): Promise { - if (!this.enabled) { - return Promise.reject(new Error('No settings sync store url configured.')); + if (!this.userDataSyncStore) { + throw new Error('No settings sync store url configured.'); } - const url = joinPath(URI.parse(this.productService.settingsSyncStoreUrl!), 'resource', key, 'latest').toString(); + const url = joinPath(URI.parse(this.userDataSyncStore.url), 'resource', key, 'latest').toString(); const headers: IHeaders = {}; if (oldValue) { headers['If-None-Match'] = oldValue.ref; @@ -58,11 +59,11 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn } async write(key: string, data: string, ref: string | null): Promise { - if (!this.enabled) { - return Promise.reject(new Error('No settings sync store url configured.')); + if (!this.userDataSyncStore) { + throw new Error('No settings sync store url configured.'); } - const url = joinPath(URI.parse(this.productService.settingsSyncStoreUrl!), 'resource', key).toString(); + const url = joinPath(URI.parse(this.userDataSyncStore.url), 'resource', key).toString(); const headers: IHeaders = { 'Content-Type': 'text/plain' }; if (ref) { headers['If-Match'] = ref; @@ -87,21 +88,17 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn } private async request(options: IRequestOptions, token: CancellationToken): Promise { - if (this.authTokenService.status !== AuthTokenStatus.Disabled) { - const authToken = await this.authTokenService.getToken(); - if (!authToken) { - return Promise.reject(new Error('No Auth Token Available.')); - } - options.headers = options.headers || {}; - options.headers['authorization'] = `Bearer ${authToken}`; + const authToken = await this.authTokenService.getToken(); + if (!authToken) { + throw new Error('No Auth Token Available.'); } + options.headers = options.headers || {}; + options.headers['authorization'] = `Bearer ${authToken}`; const context = await this.requestService.request(options, token); if (context.res.statusCode === 401) { - if (this.authTokenService.status !== AuthTokenStatus.Disabled) { - this.authTokenService.refreshToken(); - } + this.authTokenService.refreshToken(); // Throw Unauthorized Error throw new UserDataSyncStoreError('Unauthorized', UserDataSyncStoreErrorCode.Unauthroized); } diff --git a/src/vs/platform/userDataSync/electron-browser/userDataAutoSync.ts b/src/vs/platform/userDataSync/electron-browser/userDataAutoSync.ts new file mode 100644 index 000000000000..e9b360f57987 --- /dev/null +++ b/src/vs/platform/userDataSync/electron-browser/userDataAutoSync.ts @@ -0,0 +1,32 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IUserDataSyncService, IUserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSync'; +import { Event } from 'vs/base/common/event'; +import { IElectronService } from 'vs/platform/electron/node/electron'; +import { UserDataAutoSync as BaseUserDataAutoSync } from 'vs/platform/userDataSync/common/userDataAutoSync'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IAuthTokenService } from 'vs/platform/auth/common/auth'; + +export class UserDataAutoSync extends BaseUserDataAutoSync { + + constructor( + @IUserDataSyncService userDataSyncService: IUserDataSyncService, + @IElectronService electronService: IElectronService, + @IConfigurationService configurationService: IConfigurationService, + @IUserDataSyncLogService logService: IUserDataSyncLogService, + @IAuthTokenService authTokenService: IAuthTokenService, + ) { + super(configurationService, userDataSyncService, logService, authTokenService); + + // Sync immediately if there is a local change. + this._register(Event.debounce(Event.any( + electronService.onWindowFocus, + electronService.onWindowOpen, + userDataSyncService.onDidChangeLocal + ), () => undefined, 500)(() => this.sync(false))); + } + +} diff --git a/src/vs/platform/userDataSync/test/common/keybindingsMerge.test.ts b/src/vs/platform/userDataSync/test/common/keybindingsMerge.test.ts new file mode 100644 index 000000000000..59c706f40f2e --- /dev/null +++ b/src/vs/platform/userDataSync/test/common/keybindingsMerge.test.ts @@ -0,0 +1,735 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { merge } from 'vs/platform/userDataSync/common/keybindingsMerge'; +import { IStringDictionary } from 'vs/base/common/collections'; +import { OperatingSystem, OS } from 'vs/base/common/platform'; +import { IUserDataSyncUtilService } from 'vs/platform/userDataSync/common/userDataSync'; +import { FormattingOptions } from 'vs/base/common/jsonFormatter'; +import { URI } from 'vs/base/common/uri'; + +suite('KeybindingsMerge - No Conflicts', () => { + + test('merge when local and remote are same with one entry', async () => { + const localContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const actual = await mergeKeybindings(localContent, remoteContent, null); + assert.ok(!actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, localContent); + }); + + test('merge when local and remote are same with similar when contexts', async () => { + const localContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: '!editorReadonly && editorTextFocus' }]); + const actual = await mergeKeybindings(localContent, remoteContent, null); + assert.ok(!actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, localContent); + }); + + test('merge when local and remote has entries in different order', async () => { + const localContent = stringify([ + { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+a', command: 'a', when: 'editorTextFocus' } + ]); + const remoteContent = stringify([ + { key: 'alt+a', command: 'a', when: 'editorTextFocus' }, + { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' } + ]); + const actual = await mergeKeybindings(localContent, remoteContent, null); + assert.ok(!actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, localContent); + }); + + test('merge when local and remote are same with multiple entries', async () => { + const localContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + { key: 'cmd+c', command: 'b', args: { text: '`' } } + ]); + const remoteContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + { key: 'cmd+c', command: 'b', args: { text: '`' } } + ]); + const actual = await mergeKeybindings(localContent, remoteContent, null); + assert.ok(!actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, localContent); + }); + + test('merge when local and remote are same with different base content', async () => { + const localContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + { key: 'cmd+c', command: 'b', args: { text: '`' } } + ]); + const baseContent = stringify([ + { key: 'ctrl+c', command: 'e' }, + { key: 'shift+d', command: 'd', args: { text: '`' } } + ]); + const remoteContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + { key: 'cmd+c', command: 'b', args: { text: '`' } } + ]); + const actual = await mergeKeybindings(localContent, remoteContent, baseContent); + assert.ok(!actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, localContent); + }); + + test('merge when local and remote are same with multiple entries in different order', async () => { + const localContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + { key: 'cmd+c', command: 'b', args: { text: '`' } } + ]); + const remoteContent = stringify([ + { key: 'cmd+c', command: 'b', args: { text: '`' } }, + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + ]); + const actual = await mergeKeybindings(localContent, remoteContent, null); + assert.ok(!actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, localContent); + }); + + test('merge when local and remote are same when remove entry is in different order', async () => { + const localContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + { key: 'cmd+c', command: 'b', args: { text: '`' } } + ]); + const remoteContent = stringify([ + { key: 'alt+d', command: '-a' }, + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'cmd+c', command: 'b', args: { text: '`' } }, + ]); + const actual = await mergeKeybindings(localContent, remoteContent, null); + assert.ok(!actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, localContent); + }); + + test('merge when a new entry is added to remote', async () => { + const localContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + ]); + const remoteContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + { key: 'cmd+c', command: 'b', args: { text: '`' } }, + ]); + const actual = await mergeKeybindings(localContent, remoteContent, null); + assert.ok(actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, remoteContent); + }); + + test('merge when multiple new entries are added to remote', async () => { + const localContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + ]); + const remoteContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + { key: 'cmd+c', command: 'b', args: { text: '`' } }, + { key: 'cmd+d', command: 'c' }, + ]); + const actual = await mergeKeybindings(localContent, remoteContent, null); + assert.ok(actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, remoteContent); + }); + + test('merge when multiple new entries are added to remote from base and local has not changed', async () => { + const localContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + ]); + const remoteContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + { key: 'cmd+c', command: 'b', args: { text: '`' } }, + { key: 'cmd+d', command: 'c' }, + ]); + const actual = await mergeKeybindings(localContent, remoteContent, localContent); + assert.ok(actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, remoteContent); + }); + + test('merge when an entry is removed from remote from base and local has not changed', async () => { + const localContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + { key: 'cmd+c', command: 'b', args: { text: '`' } }, + ]); + const remoteContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + ]); + const actual = await mergeKeybindings(localContent, remoteContent, localContent); + assert.ok(actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, remoteContent); + }); + + test('merge when an entry (same command) is removed from remote from base and local has not changed', async () => { + const localContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + ]); + const remoteContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + ]); + const actual = await mergeKeybindings(localContent, remoteContent, localContent); + assert.ok(actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, remoteContent); + }); + + test('merge when an entry is updated in remote from base and local has not changed', async () => { + const localContent = stringify([ + { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + ]); + const remoteContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + ]); + const actual = await mergeKeybindings(localContent, remoteContent, localContent); + assert.ok(actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, remoteContent); + }); + + test('merge when a command with multiple entries is updated from remote from base and local has not changed', async () => { + const localContent = stringify([ + { key: 'shift+c', command: 'c' }, + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: 'b' }, + { key: 'cmd+c', command: 'a' }, + ]); + const remoteContent = stringify([ + { key: 'shift+c', command: 'c' }, + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: 'b' }, + { key: 'cmd+d', command: 'a' }, + ]); + const actual = await mergeKeybindings(localContent, remoteContent, localContent); + assert.ok(actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, remoteContent); + }); + + test('merge when remote has moved forwareded with multiple changes and local stays with base', async () => { + const localContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'cmd+c', command: 'b', args: { text: '`' } }, + { key: 'alt+d', command: '-a' }, + { key: 'cmd+e', command: 'd' }, + { key: 'cmd+d', command: 'c', when: 'context1' }, + ]); + const remoteContent = stringify([ + { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'cmd+e', command: 'd' }, + { key: 'alt+d', command: '-a' }, + { key: 'alt+f', command: 'f' }, + { key: 'alt+d', command: '-f' }, + { key: 'cmd+d', command: 'c', when: 'context1' }, + { key: 'cmd+c', command: '-c' }, + ]); + const actual = await mergeKeybindings(localContent, remoteContent, localContent); + assert.ok(actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, remoteContent); + }); + + test('merge when a new entry is added to local', async () => { + const localContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + { key: 'cmd+c', command: 'b', args: { text: '`' } }, + ]); + const remoteContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + ]); + const actual = await mergeKeybindings(localContent, remoteContent, null); + assert.ok(actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, localContent); + }); + + test('merge when multiple new entries are added to local', async () => { + const localContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + { key: 'cmd+c', command: 'b', args: { text: '`' } }, + { key: 'cmd+d', command: 'c' }, + ]); + const remoteContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + ]); + const actual = await mergeKeybindings(localContent, remoteContent, null); + assert.ok(actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, localContent); + }); + + test('merge when multiple new entries are added to local from base and remote is not changed', async () => { + const localContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + { key: 'cmd+c', command: 'b', args: { text: '`' } }, + { key: 'cmd+d', command: 'c' }, + ]); + const remoteContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + ]); + const actual = await mergeKeybindings(localContent, remoteContent, remoteContent); + assert.ok(actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, localContent); + }); + + test('merge when an entry is removed from local from base and remote has not changed', async () => { + const localContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + ]); + const remoteContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + { key: 'cmd+c', command: 'b', args: { text: '`' } }, + ]); + const actual = await mergeKeybindings(localContent, remoteContent, remoteContent); + assert.ok(actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, localContent); + }); + + test('merge when an entry (with same command) is removed from local from base and remote has not changed', async () => { + const localContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + ]); + const remoteContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: '-a' }, + ]); + const actual = await mergeKeybindings(localContent, remoteContent, remoteContent); + assert.ok(actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, localContent); + }); + + test('merge when an entry is updated in local from base and remote has not changed', async () => { + const localContent = stringify([ + { key: 'alt+d', command: 'a', when: 'editorTextFocus' }, + ]); + const remoteContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + ]); + const actual = await mergeKeybindings(localContent, remoteContent, remoteContent); + assert.ok(actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, localContent); + }); + + test('merge when a command with multiple entries is updated from local from base and remote has not changed', async () => { + const localContent = stringify([ + { key: 'shift+c', command: 'c' }, + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: 'b' }, + { key: 'cmd+c', command: 'a' }, + ]); + const remoteContent = stringify([ + { key: 'shift+c', command: 'c' }, + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+d', command: 'b' }, + { key: 'cmd+d', command: 'a' }, + ]); + const actual = await mergeKeybindings(localContent, remoteContent, remoteContent); + assert.ok(actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, localContent); + }); + + test('merge when local has moved forwareded with multiple changes and remote stays with base', async () => { + const localContent = stringify([ + { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'cmd+e', command: 'd' }, + { key: 'alt+d', command: '-a' }, + { key: 'alt+f', command: 'f' }, + { key: 'alt+d', command: '-f' }, + { key: 'cmd+d', command: 'c', when: 'context1' }, + { key: 'cmd+c', command: '-c' }, + ]); + const remoteContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'cmd+c', command: 'b', args: { text: '`' } }, + { key: 'alt+d', command: '-a' }, + { key: 'cmd+e', command: 'd' }, + { key: 'cmd+d', command: 'c', when: 'context1' }, + ]); + const expected = stringify([ + { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'cmd+e', command: 'd' }, + { key: 'alt+d', command: '-a' }, + { key: 'alt+f', command: 'f' }, + { key: 'alt+d', command: '-f' }, + { key: 'cmd+d', command: 'c', when: 'context1' }, + { key: 'cmd+c', command: '-c' }, + ]); + const actual = await mergeKeybindings(localContent, remoteContent, remoteContent); + assert.ok(actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, expected); + }); + + test('merge when local and remote has moved forwareded with conflicts', async () => { + const baseContent = stringify([ + { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'ctrl+c', command: '-a' }, + { key: 'cmd+e', command: 'd' }, + { key: 'alt+a', command: 'f' }, + { key: 'alt+d', command: '-f' }, + { key: 'cmd+d', command: 'c', when: 'context1' }, + { key: 'cmd+c', command: '-c' }, + ]); + const localContent = stringify([ + { key: 'alt+d', command: '-f' }, + { key: 'cmd+e', command: 'd' }, + { key: 'cmd+c', command: '-c' }, + { key: 'cmd+d', command: 'c', when: 'context1' }, + { key: 'alt+a', command: 'f' }, + { key: 'alt+e', command: 'e' }, + ]); + const remoteContent = stringify([ + { key: 'alt+a', command: 'f' }, + { key: 'cmd+c', command: '-c' }, + { key: 'cmd+d', command: 'd' }, + { key: 'alt+d', command: '-f' }, + { key: 'alt+c', command: 'c', when: 'context1' }, + { key: 'alt+g', command: 'g', when: 'context2' }, + ]); + const expected = stringify([ + { key: 'alt+d', command: '-f' }, + { key: 'cmd+d', command: 'd' }, + { key: 'cmd+c', command: '-c' }, + { key: 'alt+c', command: 'c', when: 'context1' }, + { key: 'alt+a', command: 'f' }, + { key: 'alt+e', command: 'e' }, + { key: 'alt+g', command: 'g', when: 'context2' }, + ]); + const actual = await mergeKeybindings(localContent, remoteContent, baseContent); + assert.ok(actual.hasChanges); + assert.ok(!actual.hasConflicts); + assert.equal(actual.mergeContent, expected); + }); + +}); + + +suite.skip('KeybindingsMerge - Conflicts', () => { + + test('merge when local and remote with one entry but different value', async () => { + const localContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const actual = await mergeKeybindings(localContent, remoteContent, null); + assert.ok(actual.hasChanges); + assert.ok(actual.hasConflicts); + assert.equal(actual.mergeContent, + `<<<<<<< local +[ + { + "key": "alt+d", + "command": "a", + "when": "editorTextFocus && !editorReadonly" + } +] +======= +[ + { + "key": "alt+c", + "command": "a", + "when": "editorTextFocus && !editorReadonly" + } +] +>>>>>>> remote`); + }); + + test('merge when local and remote with different keybinding', async () => { + const localContent = stringify([ + { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+a', command: '-a', when: 'editorTextFocus && !editorReadonly' } + ]); + const remoteContent = stringify([ + { key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+a', command: '-a', when: 'editorTextFocus && !editorReadonly' } + ]); + const actual = await mergeKeybindings(localContent, remoteContent, null); + assert.ok(actual.hasChanges); + assert.ok(actual.hasConflicts); + assert.equal(actual.mergeContent, + `<<<<<<< local +[ + { + "key": "alt+d", + "command": "a", + "when": "editorTextFocus && !editorReadonly" + }, + { + "key": "alt+a", + "command": "-a", + "when": "editorTextFocus && !editorReadonly" + } +] +======= +[ + { + "key": "alt+c", + "command": "a", + "when": "editorTextFocus && !editorReadonly" + }, + { + "key": "alt+a", + "command": "-a", + "when": "editorTextFocus && !editorReadonly" + } +] +>>>>>>> remote`); + }); + + test('merge when the entry is removed in local but updated in remote', async () => { + const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const localContent = stringify([]); + const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const actual = await mergeKeybindings(localContent, remoteContent, baseContent); + assert.ok(actual.hasChanges); + assert.ok(actual.hasConflicts); + assert.equal(actual.mergeContent, + `<<<<<<< local +[] +======= +[ + { + "key": "alt+c", + "command": "a", + "when": "editorTextFocus && !editorReadonly" + } +] +>>>>>>> remote`); + }); + + test('merge when the entry is removed in local but updated in remote and a new entry is added in local', async () => { + const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const localContent = stringify([{ key: 'alt+b', command: 'b' }]); + const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const actual = await mergeKeybindings(localContent, remoteContent, baseContent); + assert.ok(actual.hasChanges); + assert.ok(actual.hasConflicts); + assert.equal(actual.mergeContent, + `<<<<<<< local +[ + { + "key": "alt+b", + "command": "b" + } +] +======= +[ + { + "key": "alt+c", + "command": "a", + "when": "editorTextFocus && !editorReadonly" + } +] +>>>>>>> remote`); + }); + + test('merge when the entry is removed in remote but updated in local', async () => { + const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const localContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const remoteContent = stringify([]); + const actual = await mergeKeybindings(localContent, remoteContent, baseContent); + assert.ok(actual.hasChanges); + assert.ok(actual.hasConflicts); + assert.equal(actual.mergeContent, + `<<<<<<< local +[ + { + "key": "alt+c", + "command": "a", + "when": "editorTextFocus && !editorReadonly" + } +] +======= +[] +>>>>>>> remote`); + }); + + test('merge when the entry is removed in remote but updated in local and a new entry is added in remote', async () => { + const baseContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const localContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); + const remoteContent = stringify([{ key: 'alt+b', command: 'b' }]); + const actual = await mergeKeybindings(localContent, remoteContent, baseContent); + assert.ok(actual.hasChanges); + assert.ok(actual.hasConflicts); + assert.equal(actual.mergeContent, + `<<<<<<< local +[ + { + "key": "alt+c", + "command": "a", + "when": "editorTextFocus && !editorReadonly" + }, + { + "key": "alt+b", + "command": "b" + } +] +======= +[ + { + "key": "alt+b", + "command": "b" + } +] +>>>>>>> remote`); + }); + + test('merge when local and remote has moved forwareded with conflicts', async () => { + const baseContent = stringify([ + { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }, + { key: 'alt+c', command: '-a' }, + { key: 'cmd+e', command: 'd' }, + { key: 'alt+a', command: 'f' }, + { key: 'alt+d', command: '-f' }, + { key: 'cmd+d', command: 'c', when: 'context1' }, + { key: 'cmd+c', command: '-c' }, + ]); + const localContent = stringify([ + { key: 'alt+d', command: '-f' }, + { key: 'cmd+e', command: 'd' }, + { key: 'cmd+c', command: '-c' }, + { key: 'cmd+d', command: 'c', when: 'context1' }, + { key: 'alt+a', command: 'f' }, + { key: 'alt+e', command: 'e' }, + ]); + const remoteContent = stringify([ + { key: 'alt+a', command: 'f' }, + { key: 'cmd+c', command: '-c' }, + { key: 'cmd+d', command: 'd' }, + { key: 'alt+d', command: '-f' }, + { key: 'alt+c', command: 'c', when: 'context1' }, + { key: 'alt+g', command: 'g', when: 'context2' }, + ]); + const actual = await mergeKeybindings(localContent, remoteContent, baseContent); + assert.ok(actual.hasChanges); + assert.ok(actual.hasConflicts); + assert.equal(actual.mergeContent, + `<<<<<<< local +[ + { + "key": "alt+d", + "command": "-f" + }, + { + "key": "cmd+d", + "command": "d" + }, + { + "key": "cmd+c", + "command": "-c" + }, + { + "key": "cmd+d", + "command": "c", + "when": "context1" + }, + { + "key": "alt+a", + "command": "f" + }, + { + "key": "alt+e", + "command": "e" + }, + { + "key": "alt+g", + "command": "g", + "when": "context2" + } +] +======= +[ + { + "key": "alt+a", + "command": "f" + }, + { + "key": "cmd+c", + "command": "-c" + }, + { + "key": "cmd+d", + "command": "d" + }, + { + "key": "alt+d", + "command": "-f" + }, + { + "key": "alt+c", + "command": "c", + "when": "context1" + }, + { + "key": "alt+g", + "command": "g", + "when": "context2" + } +] +>>>>>>> remote`); + }); + +}); + + +async function mergeKeybindings(localContent: string, remoteContent: string, baseContent: string | null) { + const userDataSyncUtilService = new MockUserDataSyncUtilService(); + const formattingOptions = await userDataSyncUtilService.resolveFormattingOptions(); + return merge(localContent, remoteContent, baseContent, formattingOptions, userDataSyncUtilService); +} + +function stringify(value: any): string { + return JSON.stringify(value, null, '\t'); +} + +class MockUserDataSyncUtilService implements IUserDataSyncUtilService { + + _serviceBrand: any; + + async resolveUserBindings(userbindings: string[]): Promise> { + const keys: IStringDictionary = {}; + for (const keybinding of userbindings) { + keys[keybinding] = keybinding; + } + return keys; + } + + async resolveFormattingOptions(file?: URI): Promise { + return { eol: OS === OperatingSystem.Windows ? '\r\n' : '\n', insertSpaces: false, tabSize: 4 }; + } +} diff --git a/src/vs/platform/windows/common/windows.ts b/src/vs/platform/windows/common/windows.ts index 2324a228e2b1..c5a370841ee8 100644 --- a/src/vs/platform/windows/common/windows.ts +++ b/src/vs/platform/windows/common/windows.ts @@ -25,6 +25,7 @@ export interface IBaseOpenWindowsOptions { export interface IOpenWindowOptions extends IBaseOpenWindowsOptions { forceNewWindow?: boolean; + preferNewWindow?: boolean; noRecentEntry?: boolean; } @@ -219,8 +220,9 @@ export interface IAddFoldersRequest { } export interface IWindowConfiguration extends ParsedArgs { - machineId: string; - windowId: number; + machineId?: string; // NOTE: This is undefined in the web, the telemetry service directly resolves this. + windowId: number; // TODO: should we deprecate this in favor of sessionId? + sessionId: string; logLevel: LogLevel; mainPid: number; @@ -245,19 +247,14 @@ export interface IWindowConfiguration extends ParsedArgs { fullscreen?: boolean; maximized?: boolean; highContrast?: boolean; - frameless?: boolean; accessibilitySupport?: boolean; partsSplashPath?: string; - perfStartTime?: number; - perfAppReady?: number; - perfWindowLoadTime?: number; perfEntries: ExportData; filesToOpenOrCreate?: IPath[]; filesToDiff?: IPath[]; filesToWait?: IPathsToWaitFor; - termProgram?: string; } export interface IRunActionInWindowRequest { diff --git a/src/vs/platform/windows/electron-main/windowsMainService.ts b/src/vs/platform/windows/electron-main/windowsMainService.ts index ddd61b09888d..f0612bd763fd 100644 --- a/src/vs/platform/windows/electron-main/windowsMainService.ts +++ b/src/vs/platform/windows/electron-main/windowsMainService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as fs from 'fs'; -import { basename, normalize, join, } from 'vs/base/common/path'; +import { basename, normalize, join, posix } from 'vs/base/common/path'; import { localize } from 'vs/nls'; import * as arrays from 'vs/base/common/arrays'; import { assign, mixin } from 'vs/base/common/objects'; @@ -496,7 +496,8 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic // Remember in recent document list (unless this opens for extension development) // Also do not add paths when files are opened for diffing, only if opened individually - if (!usedWindows.some(window => window.isExtensionDevelopmentHost) && !openConfig.diffMode && !openConfig.noRecentEntry) { + const isDiff = fileInputs && fileInputs.filesToDiff.length > 0; + if (!usedWindows.some(window => window.isExtensionDevelopmentHost) && !isDiff && !openConfig.noRecentEntry) { const recents: IRecent[] = []; for (let pathToOpen of pathsToOpen) { if (pathToOpen.workspace) { @@ -1107,8 +1108,6 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic const remoteAuthority = options.remoteAuthority; if (remoteAuthority) { - // assume it's a folder or workspace file - const first = anyPath.charCodeAt(0); // make absolute if (first !== CharCode.Slash) { @@ -1120,11 +1119,15 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic const uri = URI.from({ scheme: Schemas.vscodeRemote, authority: remoteAuthority, path: anyPath }); - if (hasWorkspaceFileExtension(anyPath)) { - if (forceOpenWorkspaceAsFile) { + // guess the file type: If it ends with a slash it's a folder. If it has a file extension, it's a file or a workspace. By defaults it's a folder. + if (anyPath.charCodeAt(anyPath.length - 1) !== CharCode.Slash) { + if (hasWorkspaceFileExtension(anyPath)) { + if (forceOpenWorkspaceAsFile) { + return { fileUri: uri, remoteAuthority }; + } + } else if (posix.extname(anyPath).length > 0) { return { fileUri: uri, remoteAuthority }; } - return { workspace: getWorkspaceIdentifier(uri), remoteAuthority }; } return { folderUri: uri, remoteAuthority }; } diff --git a/src/vs/platform/workspace/common/workspace.ts b/src/vs/platform/workspace/common/workspace.ts index d1a376d5fbaa..49271d25b280 100644 --- a/src/vs/platform/workspace/common/workspace.ts +++ b/src/vs/platform/workspace/common/workspace.ts @@ -9,10 +9,11 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' import { TernarySearchTree } from 'vs/base/common/map'; import { Event } from 'vs/base/common/event'; import { IWorkspaceIdentifier, IStoredWorkspaceFolder, isRawFileWorkspaceFolder, isRawUriWorkspaceFolder, ISingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; +import { IWorkspaceFolderProvider } from 'vs/base/common/labels'; export const IWorkspaceContextService = createDecorator('contextService'); -export interface IWorkspaceContextService { +export interface IWorkspaceContextService extends IWorkspaceFolderProvider { _serviceBrand: undefined; /** diff --git a/src/vs/platform/workspaces/common/workspaces.ts b/src/vs/platform/workspaces/common/workspaces.ts index 9a665730a8ae..3955da58cac7 100644 --- a/src/vs/platform/workspaces/common/workspaces.ts +++ b/src/vs/platform/workspaces/common/workspaces.ts @@ -19,6 +19,7 @@ import { FormattingOptions } from 'vs/base/common/jsonFormatter'; import { getRemoteAuthority } from 'vs/platform/remote/common/remoteHosts'; import { ILogService } from 'vs/platform/log/common/log'; import { Event as CommonEvent } from 'vs/base/common/event'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; export const WORKSPACE_EXTENSION = 'code-workspace'; export const WORKSPACE_FILTER = [{ name: localize('codeWorkspace', "Code Workspace"), extensions: [WORKSPACE_EXTENSION] }]; @@ -172,6 +173,10 @@ export function toWorkspaceIdentifier(workspace: IWorkspace): IWorkspaceIdentifi return undefined; } +export function isUntitledWorkspace(path: URI, environmentService: IEnvironmentService): boolean { + return isEqualOrParent(path, environmentService.untitledWorkspacesHome); +} + export type IMultiFolderWorkspaceInitializationPayload = IWorkspaceIdentifier; export interface ISingleFolderWorkspaceInitializationPayload { id: string; folder: ISingleFolderWorkspaceIdentifier; } export interface IEmptyWorkspaceInitializationPayload { id: string; } @@ -280,12 +285,9 @@ function doParseStoredWorkspace(path: URI, contents: string): IStoredWorkspace { let storedWorkspace: IStoredWorkspace = json.parse(contents); // use fault tolerant parser // Filter out folders which do not have a path or uri set - if (Array.isArray(storedWorkspace.folders)) { + if (storedWorkspace && Array.isArray(storedWorkspace.folders)) { storedWorkspace.folders = storedWorkspace.folders.filter(folder => isStoredWorkspaceFolder(folder)); - } - - // Validate - if (!Array.isArray(storedWorkspace.folders)) { + } else { throw new Error(`${path} looks like an invalid workspace file.`); } diff --git a/src/vs/platform/workspaces/electron-main/workspacesMainService.ts b/src/vs/platform/workspaces/electron-main/workspacesMainService.ts index bc4dc061b6c4..b9f2840c7bbd 100644 --- a/src/vs/platform/workspaces/electron-main/workspacesMainService.ts +++ b/src/vs/platform/workspaces/electron-main/workspacesMainService.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IWorkspaceIdentifier, hasWorkspaceFileExtension, UNTITLED_WORKSPACE_NAME, IResolvedWorkspace, IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData, IUntitledWorkspaceInfo, getStoredWorkspaceFolder, IEnterWorkspaceResult } from 'vs/platform/workspaces/common/workspaces'; +import { IWorkspaceIdentifier, hasWorkspaceFileExtension, UNTITLED_WORKSPACE_NAME, IResolvedWorkspace, IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData, IUntitledWorkspaceInfo, getStoredWorkspaceFolder, IEnterWorkspaceResult, isUntitledWorkspace } from 'vs/platform/workspaces/common/workspaces'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { join, dirname } from 'vs/base/common/path'; import { mkdirp, writeFile, rimrafSync, readdirSync, writeFileSync } from 'vs/base/node/pfs'; @@ -17,7 +17,7 @@ import { toWorkspaceFolders } from 'vs/platform/workspace/common/workspace'; import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; import { Disposable } from 'vs/base/common/lifecycle'; -import { originalFSPath, isEqualOrParent, joinPath, isEqual, basename } from 'vs/base/common/resources'; +import { originalFSPath, joinPath, isEqual, basename } from 'vs/base/common/resources'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ICodeWindow } from 'vs/platform/windows/electron-main/windows'; import { localize } from 'vs/nls'; @@ -104,7 +104,7 @@ export class WorkspacesMainService extends Disposable implements IWorkspacesMain } private isWorkspacePath(uri: URI): boolean { - return this.isInsideWorkspacesHome(uri) || hasWorkspaceFileExtension(uri); + return isUntitledWorkspace(uri, this.environmentService) || hasWorkspaceFileExtension(uri); } private doResolveWorkspace(path: URI, contents: string): IResolvedWorkspace | null { @@ -130,22 +130,15 @@ export class WorkspacesMainService extends Disposable implements IWorkspacesMain let storedWorkspace: IStoredWorkspace = json.parse(contents); // use fault tolerant parser // Filter out folders which do not have a path or uri set - if (Array.isArray(storedWorkspace.folders)) { + if (storedWorkspace && Array.isArray(storedWorkspace.folders)) { storedWorkspace.folders = storedWorkspace.folders.filter(folder => isStoredWorkspaceFolder(folder)); - } - - // Validate - if (!Array.isArray(storedWorkspace.folders)) { + } else { throw new Error(`${path.toString()} looks like an invalid workspace file.`); } return storedWorkspace; } - private isInsideWorkspacesHome(path: URI): boolean { - return isEqualOrParent(path, this.environmentService.untitledWorkspacesHome); - } - async createUntitledWorkspace(folders?: IWorkspaceFolderCreationData[], remoteAuthority?: string): Promise { const { workspace, storedWorkspace } = this.newUntitledWorkspace(folders, remoteAuthority); const configPath = workspace.configPath.fsPath; @@ -196,7 +189,7 @@ export class WorkspacesMainService extends Disposable implements IWorkspacesMain } isUntitledWorkspace(workspace: IWorkspaceIdentifier): boolean { - return this.isInsideWorkspacesHome(workspace.configPath); + return isUntitledWorkspace(workspace.configPath, this.environmentService); } deleteUntitledWorkspaceSync(workspace: IWorkspaceIdentifier): void { diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 0a4b3c53c97e..6f181b922afa 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -128,7 +128,7 @@ declare module 'vscode' { readonly isDirty: boolean; /** - * `true` if the document have been closed. A closed document isn't synchronized anymore + * `true` if the document has been closed. A closed document isn't synchronized anymore * and won't be re-used when the same resource is opened again. */ readonly isClosed: boolean; @@ -2009,8 +2009,9 @@ declare module 'vscode' { /** * Base kind for source actions: `source` * - * Source code actions apply to the entire file and can be run on save - * using `editor.codeActionsOnSave`. They also are shown in `source` context menu. + * Source code actions apply to the entire file. They must be explicitly requested and will not show in the + * normal [light bulb](https://code.visualstudio.com/docs/editor/editingevolved#_code-action) menu. Source actions + * can be run on save using `editor.codeActionsOnSave` and are also shown in the `source` context menu. */ static readonly Source: CodeActionKind; @@ -2967,6 +2968,17 @@ declare module 'vscode' { */ appendPlaceholder(value: string | ((snippet: SnippetString) => any), number?: number): SnippetString; + /** + * Builder-function that appends a choice (`${1|a,b,c}`) to + * the [`value`](#SnippetString.value) of this snippet string. + * + * @param values The values for choices - the array of strings + * @param number The number of this tabstop, defaults to an auto-increment + * value starting at 1. + * @return This snippet string. + */ + appendChoice(values: string[], number?: number): SnippetString; + /** * Builder-function that appends a variable (`${VAR}`) to * the [`value`](#SnippetString.value) of this snippet string. @@ -3003,6 +3015,9 @@ declare module 'vscode' { * be a range or a range and a placeholder text. The placeholder text should be the identifier of the symbol * which is being renamed - when omitted the text in the returned range is used. * + * *Note: * This function should throw an error or return a rejected thenable when the provided location + * doesn't allow for a rename. + * * @param document The document in which rename will be invoked. * @param position The position at which rename will be invoked. * @param token A cancellation token. @@ -3877,6 +3892,149 @@ declare module 'vscode' { provideSelectionRanges(document: TextDocument, positions: Position[], token: CancellationToken): ProviderResult; } + /** + * Represents programming constructs like functions or constructors in the context + * of call hierarchy. + */ + export class CallHierarchyItem { + /** + * The name of this item. + */ + name: string; + + /** + * The kind of this item. + */ + kind: SymbolKind; + + /** + * Tags for this item. + */ + tags?: ReadonlyArray; + + /** + * More detail for this item, e.g. the signature of a function. + */ + detail?: string; + + /** + * The resource identifier of this item. + */ + uri: Uri; + + /** + * The range enclosing this symbol not including leading/trailing whitespace but everything else, e.g. comments and code. + */ + range: Range; + + /** + * The range that should be selected and revealed when this symbol is being picked, e.g. the name of a function. + * Must be contained by the [`range`](#CallHierarchyItem.range). + */ + selectionRange: Range; + + /** + * Creates a new call hierarchy item. + */ + constructor(kind: SymbolKind, name: string, detail: string, uri: Uri, range: Range, selectionRange: Range); + } + + /** + * Represents an incoming call, e.g. a caller of a method or constructor. + */ + export class CallHierarchyIncomingCall { + + /** + * The item that makes the call. + */ + from: CallHierarchyItem; + + /** + * The range at which at which the calls appears. This is relative to the caller + * denoted by [`this.from`](#CallHierarchyIncomingCall.from). + */ + fromRanges: Range[]; + + /** + * Create a new call object. + * + * @param item The item making the call. + * @param fromRanges The ranges at which the calls appear. + */ + constructor(item: CallHierarchyItem, fromRanges: Range[]); + } + + /** + * Represents an outgoing call, e.g. calling a getter from a method or a method from a constructor etc. + */ + export class CallHierarchyOutgoingCall { + + /** + * The item that is called. + */ + to: CallHierarchyItem; + + /** + * The range at which this item is called. This is the range relative to the caller, e.g the item + * passed to [`provideCallHierarchyOutgoingCalls`](#CallHierarchyItemProvider.provideCallHierarchyOutgoingCalls) + * and not [`this.to`](#CallHierarchyOutgoingCall.to). + */ + fromRanges: Range[]; + + /** + * Create a new call object. + * + * @param item The item being called + * @param fromRanges The ranges at which the calls appear. + */ + constructor(item: CallHierarchyItem, fromRanges: Range[]); + } + + /** + * The call hierarchy provider interface describes the constract between extensions + * and the call hierarchy feature which allows to browse calls and caller of function, + * methods, constructor etc. + */ + export interface CallHierarchyProvider { + + /** + * Bootstraps call hierarchy by returning the item that is denoted by the given document + * and position. This item will be used as entry into the call graph. Providers should + * return `undefined` or `null` when there is no item at the given location. + * + * @param document The document in which the command was invoked. + * @param position The position at which the command was invoked. + * @param token A cancellation token. + * @returns A call hierarchy item or a thenable that resolves to such. The lack of a result can be + * signaled by returning `undefined` or `null`. + */ + prepareCallHierarchy(document: TextDocument, position: Position, token: CancellationToken): ProviderResult; + + /** + * Provide all incoming calls for an item, e.g all callers for a method. In graph terms this describes directed + * and annotated edges inside the call graph, e.g the given item is the starting node and the result is the nodes + * that can be reached. + * + * @param item The hierarchy item for which incoming calls should be computed. + * @param token A cancellation token. + * @returns A set of incoming calls or a thenable that resolves to such. The lack of a result can be + * signaled by returning `undefined` or `null`. + */ + provideCallHierarchyIncomingCalls(item: CallHierarchyItem, token: CancellationToken): ProviderResult; + + /** + * Provide all outgoing calls for an item, e.g call calls to functions, methods, or constructors from the given item. In + * graph terms this describes directed and annotated edges inside the call graph, e.g the given item is the starting + * node and the result is the nodes that can be reached. + * + * @param item The hierarchy item for which outgoing calls should be computed. + * @param token A cancellation token. + * @returns A set of outgoing calls or a thenable that resolves to such. The lack of a result can be + * signaled by returning `undefined` or `null`. + */ + provideCallHierarchyOutgoingCalls(item: CallHierarchyItem, token: CancellationToken): ProviderResult; + } + /** * A tuple of two characters, like a pair of * opening and closing brackets. @@ -4080,11 +4238,11 @@ declare module 'vscode' { * - Workspace configuration (if available) * - Workspace folder configuration of the requested resource (if available) * - * *Global configuration* comes from User Settings and shadows Defaults. + * *Global configuration* comes from User Settings and overrides Defaults. * - * *Workspace configuration* comes from Workspace Settings and shadows Global configuration. + * *Workspace configuration* comes from Workspace Settings and overrides Global configuration. * - * *Workspace Folder configuration* comes from `.vscode` folder under one of the [workspace folders](#workspace.workspaceFolders). + * *Workspace Folder configuration* comes from `.vscode` folder under one of the [workspace folders](#workspace.workspaceFolders) and overrides Workspace configuration. * * *Note:* Workspace and Workspace Folder configurations contains `launch` and `tasks` settings. Their basename will be * part of the section identifier. The following snippets shows how to retrieve all configurations @@ -4133,9 +4291,9 @@ declare module 'vscode' { * a workspace-specific value and a folder-specific value. * * The *effective* value (returned by [`get`](#WorkspaceConfiguration.get)) - * is computed like this: `defaultValue` overwritten by `globalValue`, - * `globalValue` overwritten by `workspaceValue`. `workspaceValue` overwritten by `workspaceFolderValue`. - * Refer to [Settings Inheritance](https://code.visualstudio.com/docs/getstarted/settings) + * is computed like this: `defaultValue` overridden by `globalValue`, + * `globalValue` overridden by `workspaceValue`. `workspaceValue` overwridden by `workspaceFolderValue`. + * Refer to [Settings](https://code.visualstudio.com/docs/getstarted/settings) * for more information. * * *Note:* The configuration name must denote a leaf in the configuration tree @@ -4624,7 +4782,7 @@ declare module 'vscode' { * * `My text $(icon-name) contains icons like $(icon-name) this one.` * - * Where the icon-name is taken from the [octicon](https://octicons.github.com) icon set, e.g. + * Where the icon-name is taken from the [codicon](https://microsoft.github.io/vscode-codicons/dist/codicon.html) icon set, e.g. * `light-bulb`, `thumbsup`, `zap` etc. */ text: string; @@ -4688,7 +4846,7 @@ declare module 'vscode' { /** * The process ID of the shell process. */ - readonly processId: Thenable; + readonly processId: Thenable; /** * Send text to the terminal. The text is written to the stdin of the underlying pty process @@ -4766,8 +4924,8 @@ declare module 'vscode' { /** * The extension kind describes if an extension runs where the UI runs * or if an extension runs where the remote extension host runs. The extension kind - * if defined in the `package.json` file of extensions but can also be refined - * via the the `remote.extensionKind`-setting. When no remote extension host exists, + * is defined in the `package.json`-file of extensions but can also be refined + * via the `remote.extensionKind`-setting. When no remote extension host exists, * the value is [`ExtensionKind.UI`](#ExtensionKind.UI). */ extensionKind: ExtensionKind; @@ -5589,10 +5747,18 @@ declare module 'vscode' { ctime: number; /** * The modification timestamp in milliseconds elapsed since January 1, 1970 00:00:00 UTC. + * + * *Note:* If the file changed, it is important to provide an updated `mtime` that advanced + * from the previous value. Otherwise there may be optimizations in place that will not show + * the updated file contents in an editor for example. */ mtime: number; /** * The size in bytes. + * + * *Note:* If the file changed, it is important to provide an updated `size`. Otherwise there + * may be optimizations in place that will not show the updated file contents in an editor for + * example. */ size: number; } @@ -5706,6 +5872,11 @@ declare module 'vscode' { * An event to signal that a resource has been created, changed, or deleted. This * event should fire for resources that are being [watched](#FileSystemProvider.watch) * by clients of this provider. + * + * *Note:* It is important that the metadata of the file that changed provides an + * updated `mtime` that advanced from the previous value in the [stat](#FileStat) and a + * correct `size` value. Otherwise there may be optimizations in place that will not show + * the change in an editor for example. */ readonly onDidChangeFile: Event; @@ -5843,6 +6014,9 @@ declare module 'vscode' { /** * Create a new directory (Note, that new files are created via `write`-calls). * + * *Note* that missing directories are created automatically, e.g this call has + * `mkdirp` semantics. + * * @param uri The uri of the new folder. */ createDirectory(uri: Uri): Thenable; @@ -6290,22 +6464,51 @@ declare module 'vscode' { export function openExternal(target: Uri): Thenable; /** + * Resolves a uri to form that is accessible externally. Currently only supports `https:`, `http:` and + * `vscode.env.uriScheme` uris. + * + * #### `http:` or `https:` scheme + * * Resolves an *external* uri, such as a `http:` or `https:` link, from where the extension is running to a * uri to the same resource on the client machine. * - * This is a no-op if the extension is running on the client machine. Currently only supports - * `https:` and `http:` uris. + * This is a no-op if the extension is running on the client machine. * * If the extension is running remotely, this function automatically establishes a port forwarding tunnel * from the local machine to `target` on the remote and returns a local uri to the tunnel. The lifetime of * the port fowarding tunnel is managed by VS Code and the tunnel can be closed by the user. * - * Extensions should not cache the result of `asExternalUri` as the resolved uri may become invalid due to - * a system or user action — for example, in remote cases, a user may close a port forwardng tunnel - * that was opened by `asExternalUri`. + * *Note* that uris passed through `openExternal` are automatically resolved and you should not call `asExternalUri` on them. + * + * #### `vscode.env.uriScheme` + * + * Creates a uri that - if opened in a browser (e.g. via `openExternal`) - will result in a registered [UriHandler](#UriHandler) + * to trigger. + * + * Extensions should not make any assumptions about the resulting uri and should not alter it in anyway. + * Rather, extensions can e.g. use this uri in an authentication flow, by adding the uri as callback query + * argument to the server to authenticate to. * - * *Note* that uris passed through `openExternal` are automatically resolved and you should not call `asExternalUri` - * on them. + * *Note* that if the server decides to add additional query parameters to the uri (e.g. a token or secret), it + * will appear in the uri that is passed to the [UriHandler](#UriHandler). + * + * **Example** of an authentication flow: + * ```typescript + * vscode.window.registerUriHandler({ + * handleUri(uri: vscode.Uri): vscode.ProviderResult { + * if (uri.path === '/did-authenticate') { + * console.log(uri.toString()); + * } + * } + * }); + * + * const callableUri = await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://my.extension/did-authenticate`)); + * await vscode.env.openExternal(callableUri); + * ``` + * + * *Note* that extensions should not cache the result of `asExternalUri` as the resolved uri may become invalid due to + * a system or user action — for example, in remote cases, a user may close a port forwarding tunnel that was opened by + * `asExternalUri`. * * @return A uri that can be used on the client machine. */ @@ -7084,6 +7287,12 @@ declare module 'vscode' { */ message?: string; + /** + * The tree view title is initially taken from the extension package.json + * Changes to the title property will be properly reflected in the UI in the title of the view. + */ + title?: string; + /** * Reveals the given element in the tree view. * If the tree view is not visible then the tree view is shown and element is revealed. @@ -7312,6 +7521,9 @@ declare module 'vscode' { * [Terminal.sendText](#Terminal.sendText) which sends text to the underlying _process_ * (the pty "slave"), this will write the text to the terminal itself (the pty "master"). * + * Note writing `\n` will just move the cursor down 1 row, you need to write `\r` as well + * to move the cursor to the left-most cell. + * * **Example:** Write red text to the terminal * ```typescript * const writeEmitter = new vscode.EventEmitter(); @@ -8103,8 +8315,8 @@ declare module 'vscode' { * * All changes of a workspace edit are applied in the same order in which they have been added. If * multiple textual inserts are made at the same position, these strings appear in the resulting text - * in the order the 'inserts' were made. Invalid sequences like 'delete file a' -> 'insert text in file a' - * cause failure of the operation. + * in the order the 'inserts' were made, unless that are interleaved with resource edits. Invalid sequences + * like 'delete file a' -> 'insert text in file a' cause failure of the operation. * * When applying a workspace edit that consists only of text edits an 'all-or-nothing'-strategy is used. * A workspace edit with resource creations or deletions aborts the operation, e.g. consecutive edits will @@ -8693,6 +8905,15 @@ declare module 'vscode' { */ export function registerSelectionRangeProvider(selector: DocumentSelector, provider: SelectionRangeProvider): Disposable; + /** + * Register a call hierarchy provider. + * + * @param selector A selector that defines the documents this provider is applicable to. + * @param provider A call hierarchy provider. + * @return A [disposable](#Disposable) that unregisters this provider when being disposed. + */ + export function registerCallHierarchyProvider(selector: DocumentSelector, provider: CallHierarchyProvider): Disposable; + /** * Set a [language configuration](#LanguageConfiguration) for a language. * @@ -9282,6 +9503,48 @@ declare module 'vscode' { constructor(functionName: string, enabled?: boolean, condition?: string, hitCondition?: string, logMessage?: string); } + /** + * Debug console mode used by debug session, see [options](#DebugSessionOptions). + */ + export enum DebugConsoleMode { + /** + * Debug session should have a separate debug console. + */ + Separate = 0, + + /** + * Debug session should share debug console with its parent session. + * This value has no effect for sessions which do not have a parent session. + */ + MergeWithParent = 1 + } + + /** + * Options for [starting a debug session](#debug.startDebugging). + */ + export interface DebugSessionOptions { + + /** + * When specified the newly created debug session is registered as a "child" session of this + * "parent" debug session. + */ + parentSession?: DebugSession; + + /** + * Controls whether this session should have a separate debug console or share it + * with the parent session. Has no effect for sessions which do not have a parent session. + * Defaults to Separate. + */ + consoleMode?: DebugConsoleMode; + } + + /** + * A DebugProtocolSource is an opaque stand-in type for the [Source](https://microsoft.github.io/debug-adapter-protocol/specification#Types_Source) type defined in the Debug Adapter Protocol. + */ + export interface DebugProtocolSource { + // Properties: see details [here](https://microsoft.github.io/debug-adapter-protocol/specification#Types_Source). + } + /** * Namespace for debug functionality. */ @@ -9388,6 +9651,19 @@ declare module 'vscode' { * @param breakpoints The breakpoints to remove. */ export function removeBreakpoints(breakpoints: Breakpoint[]): void; + + /** + * Converts a "Source" descriptor object received via the Debug Adapter Protocol into a Uri that can be used to load its contents. + * If the source descriptor is based on a path, a file Uri is returned. + * If the source descriptor uses a reference number, a specific debug Uri (scheme 'debug') is constructed that requires a corresponding VS Code ContentProvider and a running debug session + * + * If the "Source" descriptor has insufficient information for creating the Uri, an error is thrown. + * + * @param source An object conforming to the [Source](https://microsoft.github.io/debug-adapter-protocol/specification#Types_Source) type defined in the Debug Adapter Protocol. + * @param session An optional debug session that will be used when the source descriptor uses a reference number to load the contents from an active debug session. + * @return A uri that can be used to load the contents of the source. + */ + export function asDebugSourceUri(source: DebugProtocolSource, session?: DebugSession): Uri; } /** diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 578f98b272ad..81c5ad7fae4a 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -16,89 +16,6 @@ declare module 'vscode' { - //#region Joh - call hierarchy - - export class CallHierarchyItem { - /** - * The name of this item. - */ - name: string; - - /** - * The kind of this item. - */ - kind: SymbolKind; - - /** - * Tags for this item. - */ - tags?: ReadonlyArray; - - /** - * More detail for this item, e.g. the signature of a function. - */ - detail?: string; - - /** - * The resource identifier of this item. - */ - uri: Uri; - - /** - * The range enclosing this symbol not including leading/trailing whitespace but everything else, e.g. comments and code. - */ - range: Range; - - /** - * The range that should be selected and reveal when this symbol is being picked, e.g. the name of a function. - * Must be contained by the [`range`](#CallHierarchyItem.range). - */ - selectionRange: Range; - - constructor(kind: SymbolKind, name: string, detail: string, uri: Uri, range: Range, selectionRange: Range); - } - - export class CallHierarchyIncomingCall { - from: CallHierarchyItem; - fromRanges: Range[]; - constructor(item: CallHierarchyItem, fromRanges: Range[]); - } - - export class CallHierarchyOutgoingCall { - fromRanges: Range[]; - to: CallHierarchyItem; - constructor(item: CallHierarchyItem, fromRanges: Range[]); - } - - export interface CallHierarchyItemProvider { - - /** - * Provide a list of callers for the provided item, e.g. all function calling a function. - */ - provideCallHierarchyIncomingCalls(document: TextDocument, position: Position, token: CancellationToken): ProviderResult; - - /** - * Provide a list of calls for the provided item, e.g. all functions call from a function. - */ - provideCallHierarchyOutgoingCalls(document: TextDocument, position: Position, token: CancellationToken): ProviderResult; - - // todo@joh this could return as 'prepareCallHierarchy' (similar to the RenameProvider#prepareRename) - // - // /** - // * - // * Given a document and position compute a call hierarchy item. This is justed as - // * anchor for call hierarchy and then `resolveCallHierarchyItem` is being called. - // */ - // resolveCallHierarchyItem(document: TextDocument, position: Position, token: CancellationToken): ProviderResult; - } - - export namespace languages { - export function registerCallHierarchyProvider(selector: DocumentSelector, provider: CallHierarchyItemProvider): Disposable; - } - - //#endregion - - //#region Alex - resolvers export interface RemoteAuthorityResolverContext { @@ -151,8 +68,82 @@ declare module 'vscode' { //#endregion + //#region Alex - semantic tokens - // #region Joh - code insets + export class SemanticTokensLegend { + public readonly tokenTypes: string[]; + public readonly tokenModifiers: string[]; + + constructor(tokenTypes: string[], tokenModifiers: string[]); + } + + export class SemanticTokensBuilder { + constructor(); + push(line: number, char: number, length: number, tokenType: number, tokenModifiers: number): void; + build(): Uint32Array; + } + + /** + * A certain token (at index `i` is encoded using 5 uint32 integers): + * - at index `5*i` - `deltaLine`: token line number, relative to `SemanticColoringArea.line` + * - at index `5*i+1` - `deltaStart`: token start character offset inside the line (relative to 0 or the previous token if they are on the same line) + * - at index `5*i+2` - `length`: the length of the token + * - at index `5*i+3` - `tokenType`: will be looked up in `SemanticColoringLegend.tokenTypes` + * - at index `5*i+4` - `tokenModifiers`: each set bit will be looked up in `SemanticColoringLegend.tokenModifiers` + */ + export class SemanticTokens { + readonly resultId?: string; + readonly data: Uint32Array; + + constructor(data: Uint32Array, resultId?: string); + } + + export class SemanticTokensEdits { + readonly resultId?: string; + readonly edits: SemanticTokensEdit[]; + + constructor(edits: SemanticTokensEdit[], resultId?: string); + } + + export class SemanticTokensEdit { + readonly start: number; + readonly deleteCount: number; + readonly data?: Uint32Array; + + constructor(start: number, deleteCount: number, data?: Uint32Array); + } + + export interface SemanticTokensRequestOptions { + readonly ranges?: readonly Range[]; + readonly previousResultId?: string; + } + + /** + * The semantic tokens provider interface defines the contract between extensions and + * semantic tokens. + */ + export interface SemanticTokensProvider { + provideSemanticTokens(document: TextDocument, options: SemanticTokensRequestOptions, token: CancellationToken): ProviderResult; + } + + export namespace languages { + /** + * Register a semantic tokens provider. + * + * Multiple providers can be registered for a language. In that case providers are sorted + * by their [score](#languages.match) and the best-matching provider is used. Failure + * of the selected provider will cause a failure of the whole operation. + * + * @param selector A selector that defines the documents this provider is applicable to. + * @param provider A semantic tokens provider. + * @return A [disposable](#Disposable) that unregisters this provider when being disposed. + */ + export function registerSemanticTokensProvider(selector: DocumentSelector, provider: SemanticTokensProvider, legend: SemanticTokensLegend): Disposable; + } + + //#endregion + + //#region editor insets: https://github.com/microsoft/vscode/issues/85682 export interface WebviewEditorInset { readonly editor: TextEditor; @@ -169,7 +160,7 @@ declare module 'vscode' { //#endregion - //#region Joh - read/write in chunks + //#region read/write in chunks: https://github.com/microsoft/vscode/issues/84515 export interface FileSystemProvider { open?(resource: Uri, options: { create: boolean }): number | Thenable; @@ -550,7 +541,7 @@ declare module 'vscode' { //#endregion - //#region Joao: diff command + //#region diff command: https://github.com/microsoft/vscode/issues/84899 /** * The contiguous set of modified lines in a diff. @@ -583,7 +574,7 @@ declare module 'vscode' { //#endregion - //#region Joh: decorations + //#region file-decorations: https://github.com/microsoft/vscode/issues/54938 export class Decoration { letter?: string; @@ -604,51 +595,54 @@ declare module 'vscode' { //#endregion - //#region André: debug - - // deprecated + //#region André: debug API for inline debug adapters https://github.com/microsoft/vscode/issues/85544 - export interface DebugConfigurationProvider { - /** - * Deprecated, use DebugAdapterDescriptorFactory.provideDebugAdapter instead. - * @deprecated Use DebugAdapterDescriptorFactory.createDebugAdapterDescriptor instead - */ - debugAdapterExecutable?(folder: WorkspaceFolder | undefined, token?: CancellationToken): ProviderResult; + /** + * A DebugProtocolMessage is an opaque stand-in type for the [ProtocolMessage](https://microsoft.github.io/debug-adapter-protocol/specification#Base_Protocol_ProtocolMessage) type defined in the Debug Adapter Protocol. + */ + export interface DebugProtocolMessage { + // Properties: see details [here](https://microsoft.github.io/debug-adapter-protocol/specification#Base_Protocol_ProtocolMessage). } /** - * Debug console mode used by debug session, see [options](#DebugSessionOptions). + * A debug adapter that implements the Debug Adapter Protocol can be registered with VS Code if it implements the DebugAdapter interface. */ - export enum DebugConsoleMode { + export interface DebugAdapter extends Disposable { + /** - * Debug session should have a separate debug console. + * An event which fires when the debug adapter sends a Debug Adapter Protocol message to VS Code. + * Messages can be requests, responses, or events. */ - Separate = 0, + readonly onSendMessage: Event; /** - * Debug session should share debug console with its parent session. - * This value has no effect for sessions which do not have a parent session. + * Handle a Debug Adapter Protocol message. + * Messages can be requests, responses, or events. + * Results or errors are returned via onSendMessage events. + * @param message A Debug Adapter Protocol message */ - MergeWithParent = 1 + handleMessage(message: DebugProtocolMessage): void; } /** - * Options for [starting a debug session](#debug.startDebugging). + * A debug adapter descriptor for an inline implementation. */ - export interface DebugSessionOptions { + export class DebugAdapterInlineImplementation { /** - * When specified the newly created debug session is registered as a "child" session of this - * "parent" debug session. + * Create a descriptor for an inline implementation of a debug adapter. */ - parentSession?: DebugSession; + constructor(implementation: DebugAdapter); + } + // deprecated + + export interface DebugConfigurationProvider { /** - * Controls whether this session should have a separate debug console or share it - * with the parent session. Has no effect for sessions which do not have a parent session. - * Defaults to Separate. + * Deprecated, use DebugAdapterDescriptorFactory.provideDebugAdapter instead. + * @deprecated Use DebugAdapterDescriptorFactory.createDebugAdapterDescriptor instead */ - consoleMode?: DebugConsoleMode; + debugAdapterExecutable?(folder: WorkspaceFolder | undefined, token?: CancellationToken): ProviderResult; } //#endregion @@ -764,8 +758,76 @@ declare module 'vscode' { //#endregion + //#region Terminal data write event https://github.com/microsoft/vscode/issues/78502 + + export interface TerminalDataWriteEvent { + /** + * The [terminal](#Terminal) for which the data was written. + */ + readonly terminal: Terminal; + /** + * The data being written. + */ + readonly data: string; + } + + namespace window { + /** + * An event which fires when the terminal's pty slave pseudo-device is written to. In other + * words, this provides access to the raw data stream from the process running within the + * terminal, including VT sequences. + */ + export const onDidWriteTerminalData: Event; + } + + //#endregion + + //#region Terminal exit status https://github.com/microsoft/vscode/issues/62103 + + export interface TerminalExitStatus { + /** + * The exit code that a terminal exited with, it can have the following values: + * - Zero: the terminal process or custom execution succeeded. + * - Non-zero: the terminal process or custom execution failed. + * - `undefined`: the user forcefully closed the terminal or a custom execution exited + * without providing an exit code. + */ + readonly code: number | undefined; + } + + export interface Terminal { + /** + * The exit status of the terminal, this will be undefined while the terminal is active. + * + * **Example:** Show a notification with the exit code when the terminal exits with a + * non-zero exit code. + * ```typescript + * window.onDidCloseTerminal(t => { + * if (t.exitStatus && t.exitStatus.code) { + * vscode.window.showInformationMessage(`Exit code: ${t.exitStatus.code}`); + * } + * }); + * ``` + */ + readonly exitStatus: TerminalExitStatus | undefined; + } + + //#endregion + + //#region Terminal creation options https://github.com/microsoft/vscode/issues/63052 + + export interface Terminal { + /** + * The object used to initialize the terminal, this is useful for things like detecting the + * shell type of shells not launched by the extension or detecting what folder the shell was + * launched in. + */ + readonly creationOptions: Readonly; + } + + //#endregion - //#region Terminal + //#region Terminal dimensions property and change event https://github.com/microsoft/vscode/issues/55718 /** * An [event](#Event) which fires when a [Terminal](#Terminal)'s dimensions change. @@ -781,29 +843,11 @@ declare module 'vscode' { readonly dimensions: TerminalDimensions; } - export interface TerminalDataWriteEvent { - /** - * The [terminal](#Terminal) for which the data was written. - */ - readonly terminal: Terminal; - /** - * The data being written. - */ - readonly data: string; - } - namespace window { /** * An event which fires when the [dimensions](#Terminal.dimensions) of the terminal change. */ export const onDidChangeTerminalDimensions: Event; - - /** - * An event which fires when the terminal's pty slave pseudo-device is written to. In other - * words, this provides access to the raw data stream from the process running within the - * terminal, including VT sequences. - */ - export const onDidWriteTerminalData: Event; } export interface Terminal { @@ -826,20 +870,243 @@ declare module 'vscode' { //#endregion //#region mjbvz,joh: https://github.com/Microsoft/vscode/issues/43768 - export interface FileRenameEvent { - readonly oldUri: Uri; - readonly newUri: Uri; + + /** + * An event that is fired when files are going to be created. + * + * To make modifications to the workspace before the files are created, + * call the [`waitUntil](#FileWillCreateEvent.waitUntil)-function with a + * thenable that resolves to a [workspace edit](#WorkspaceEdit). + */ + export interface FileWillCreateEvent { + + /** + * The files that are going to be created. + */ + readonly files: ReadonlyArray; + + /** + * Allows to pause the event and to apply a [workspace edit](#WorkspaceEdit). + * + * *Note:* This function can only be called during event dispatch and not + * in an asynchronous manner: + * + * ```ts + * workspace.onWillCreateFiles(event => { + * // async, will *throw* an error + * setTimeout(() => event.waitUntil(promise)); + * + * // sync, OK + * event.waitUntil(promise); + * }) + * ``` + * + * @param thenable A thenable that delays saving. + */ + waitUntil(thenable: Thenable): void; + + /** + * Allows to pause the event until the provided thenable resolves. + * + * *Note:* This function can only be called during event dispatch. + * + * @param thenable A thenable that delays saving. + */ + waitUntil(thenable: Thenable): void; } + /** + * An event that is fired after files are created. + */ + export interface FileCreateEvent { + + /** + * The files that got created. + */ + readonly files: ReadonlyArray; + } + + /** + * An event that is fired when files are going to be deleted. + * + * To make modifications to the workspace before the files are deleted, + * call the [`waitUntil](#FileWillCreateEvent.waitUntil)-function with a + * thenable that resolves to a [workspace edit](#WorkspaceEdit). + */ + export interface FileWillDeleteEvent { + + /** + * The files that are going to be deleted. + */ + readonly files: ReadonlyArray; + + /** + * Allows to pause the event and to apply a [workspace edit](#WorkspaceEdit). + * + * *Note:* This function can only be called during event dispatch and not + * in an asynchronous manner: + * + * ```ts + * workspace.onWillCreateFiles(event => { + * // async, will *throw* an error + * setTimeout(() => event.waitUntil(promise)); + * + * // sync, OK + * event.waitUntil(promise); + * }) + * ``` + * + * @param thenable A thenable that delays saving. + */ + waitUntil(thenable: Thenable): void; + + /** + * Allows to pause the event until the provided thenable resolves. + * + * *Note:* This function can only be called during event dispatch. + * + * @param thenable A thenable that delays saving. + */ + waitUntil(thenable: Thenable): void; + } + + /** + * An event that is fired after files are deleted. + */ + export interface FileDeleteEvent { + + /** + * The files that got deleted. + */ + readonly files: ReadonlyArray; + } + + /** + * An event that is fired when files are going to be renamed. + * + * To make modifications to the workspace before the files are renamed, + * call the [`waitUntil](#FileWillCreateEvent.waitUntil)-function with a + * thenable that resolves to a [workspace edit](#WorkspaceEdit). + */ export interface FileWillRenameEvent { - readonly oldUri: Uri; - readonly newUri: Uri; + + /** + * The files that are going to be renamed. + */ + readonly files: ReadonlyArray<{ oldUri: Uri, newUri: Uri }>; + + /** + * Allows to pause the event and to apply a [workspace edit](#WorkspaceEdit). + * + * *Note:* This function can only be called during event dispatch and not + * in an asynchronous manner: + * + * ```ts + * workspace.onWillCreateFiles(event => { + * // async, will *throw* an error + * setTimeout(() => event.waitUntil(promise)); + * + * // sync, OK + * event.waitUntil(promise); + * }) + * ``` + * + * @param thenable A thenable that delays saving. + */ waitUntil(thenable: Thenable): void; + + /** + * Allows to pause the event until the provided thenable resolves. + * + * *Note:* This function can only be called during event dispatch. + * + * @param thenable A thenable that delays saving. + */ + waitUntil(thenable: Thenable): void; + } + + /** + * An event that is fired after files are renamed. + */ + export interface FileRenameEvent { + + /** + * The files that got renamed. + */ + readonly files: ReadonlyArray<{ oldUri: Uri, newUri: Uri }>; } export namespace workspace { - export const onWillRenameFile: Event; - export const onDidRenameFile: Event; + + /** + * An event that is emitted when files are being created. + * + * *Note 1:* This event is triggered by user gestures, like creating a file from the + * explorer, or from the [`workspace.applyEdit`](#workspace.applyEdit)-api. This event is *not* fired when + * files change on disk, e.g triggered by another application, or when using the + * [`workspace.fs`](#FileSystem)-api. + * + * *Note 2:* When this event is fired, edits to files thare are being created cannot be applied. + */ + export const onWillCreateFiles: Event; + + /** + * An event that is emitted when files have been created. + * + * *Note:* This event is triggered by user gestures, like creating a file from the + * explorer, or from the [`workspace.applyEdit`](#workspace.applyEdit)-api, but this event is *not* fired when + * files change on disk, e.g triggered by another application, or when using the + * [`workspace.fs`](#FileSystem)-api. + */ + export const onDidCreateFiles: Event; + + /** + * An event that is emitted when files are being deleted. + * + * *Note 1:* This event is triggered by user gestures, like deleting a file from the + * explorer, or from the [`workspace.applyEdit`](#workspace.applyEdit)-api, but this event is *not* fired when + * files change on disk, e.g triggered by another application, or when using the + * [`workspace.fs`](#FileSystem)-api. + * + * *Note 2:* When deleting a folder with children only one event is fired. + */ + export const onWillDeleteFiles: Event; + + /** + * An event that is emitted when files have been deleted. + * + * *Note 1:* This event is triggered by user gestures, like deleting a file from the + * explorer, or from the [`workspace.applyEdit`](#workspace.applyEdit)-api, but this event is *not* fired when + * files change on disk, e.g triggered by another application, or when using the + * [`workspace.fs`](#FileSystem)-api. + * + * *Note 2:* When deleting a folder with children only one event is fired. + */ + export const onDidDeleteFiles: Event; + + /** + * An event that is emitted when files are being renamed. + * + * *Note 1:* This event is triggered by user gestures, like renaming a file from the + * explorer, and from the [`workspace.applyEdit`](#workspace.applyEdit)-api, but this event is *not* fired when + * files change on disk, e.g triggered by another application, or when using the + * [`workspace.fs`](#FileSystem)-api. + * + * *Note 2:* When renaming a folder with children only one event is fired. + */ + export const onWillRenameFiles: Event; + + /** + * An event that is emitted when files have been renamed. + * + * *Note 1:* This event is triggered by user gestures, like renaming a file from the + * explorer, and from the [`workspace.applyEdit`](#workspace.applyEdit)-api, but this event is *not* fired when + * files change on disk, e.g triggered by another application, or when using the + * [`workspace.fs`](#FileSystem)-api. + * + * *Note 2:* When renaming a folder with children only one event is fired. + */ + export const onDidRenameFiles: Event; } //#endregion @@ -852,16 +1119,7 @@ declare module 'vscode' { } //#endregion - //#region Tree View - - export interface TreeView { - /** - * The tree view title is initially taken from the extension package.json - * Changes to the title property will be properly reflected in the UI in the title of the view. - */ - title?: string; - } - + //#region Tree View: https://github.com/microsoft/vscode/issues/61313 /** * Label describing the [Tree item](#TreeItem) */ @@ -894,9 +1152,7 @@ declare module 'vscode' { } //#endregion - //#region CustomExecution - - + //#region CustomExecution: https://github.com/microsoft/vscode/issues/81007 /** * A task to execute */ @@ -904,6 +1160,20 @@ declare module 'vscode' { detail?: string; } + export class CustomExecution2 extends CustomExecution { + /** + * Constructs a CustomExecution task object. The callback will be executed the task is run, at which point the + * extension should return the Pseudoterminal it will "run in". The task should wait to do further execution until + * [Pseudoterminal.open](#Pseudoterminal.open) is called. Task cancellation should be handled using + * [Pseudoterminal.close](#Pseudoterminal.close). When the task is complete fire + * [Pseudoterminal.onDidClose](#Pseudoterminal.onDidClose). + * @param callback The callback that will be called when the task is started by a user. + */ + constructor(callback: (resolvedDefinition?: TaskDefinition) => Thenable); + } + //#endregion + + //#region Task presentation group: https://github.com/microsoft/vscode/issues/47265 export interface TaskPresentationOptions { /** * Controls whether the task is executed in a specific terminal group using split panes. @@ -912,7 +1182,7 @@ declare module 'vscode' { } //#endregion - // #region Ben - status bar item with ID and Name + //#region Ben - status bar item with ID and Name export namespace window { @@ -960,7 +1230,7 @@ declare module 'vscode' { //#endregion - // #region Ben - extension auth flow (desktop+web) + //#region Ben - extension auth flow (desktop+web) export interface AppUriOptions { payload?: { @@ -973,63 +1243,173 @@ declare module 'vscode' { export namespace env { /** - * Creates a Uri that - if opened in a browser - will result in a - * registered [UriHandler](#UriHandler) to fire. The handler's - * Uri will be configured with the path, query and fragment of - * [AppUriOptions](#AppUriOptions) if provided, otherwise it will be empty. + * @deprecated use `vscode.env.asExternalUri` instead. + */ + export function createAppUri(options?: AppUriOptions): Thenable; + } + + //#endregion + + //#region Custom editors: https://github.com/microsoft/vscode/issues/77131 + + /** + * Defines how a webview editor interacts with VS Code. + */ + interface WebviewEditorCapabilities { + /** + * Invoked when the resource has been renamed in VS Code. * - * Extensions should not make any assumptions about the resulting - * Uri and should not alter it in anyway. Rather, extensions can e.g. - * use this Uri in an authentication flow, by adding the Uri as - * callback query argument to the server to authenticate to. + * This is called when the resource's new name also matches the custom editor selector. * - * Note: If the server decides to add additional query parameters to the Uri - * (e.g. a token or secret), it will appear in the Uri that is passed - * to the [UriHandler](#UriHandler). + * If this is not implemented—or if the new resource name does not match the existing selector—then VS Code + * will close and reopen the editor on rename. * - * **Example** of an authentication flow: - * ```typescript - * vscode.window.registerUriHandler({ - * handleUri(uri: vscode.Uri): vscode.ProviderResult { - * if (uri.path === '/did-authenticate') { - * console.log(uri.toString()); - * } - * } - * }); + * @param newResource Full path to the resource. * - * const callableUri = await vscode.env.createAppUri({ payload: { path: '/did-authenticate' } }); - * await vscode.env.openExternal(callableUri); - * ``` + * @return Thenable that signals the save is complete. */ - export function createAppUri(options?: AppUriOptions): Thenable; + // rename?(newResource: Uri): Thenable; + + /** + * Controls the editing functionality of a webview editor. This allows the webview editor to hook into standard + * editor events such as `undo` or `save`. + * + * WebviewEditors that do not have `editingCapability` are considered to be readonly. Users can still interact + * with readonly editors, but these editors will not integrate with VS Code's standard editor functionality. + */ + readonly editingCapability?: WebviewEditorEditingCapability; } - //#endregion + /** + * Defines the editing functionality of a webview editor. This allows the webview editor to hook into standard + * editor events such as `undo` or `save`. + */ + interface WebviewEditorEditingCapability { + /** + * Persist the resource. + * + * Extensions should persist the resource + * + * @return Thenable signaling that the save has completed. + */ + save(): Thenable; + + /** + * + * @param resource Resource being saved. + * @param targetResource Location to save to. + */ + saveAs(resource: Uri, targetResource: Uri): Thenable; + + /** + * Event triggered by extensions to signal to VS Code that an edit has occurred. + * + * The edit must be a json serializable object. + */ + readonly onEdit: Event; - //#region Custom editors, mjbvz + /** + * Apply a set of edits. + * + * This is triggered on redo and when restoring a custom editor after restart. Note that is not invoked + * when `onEdit` is called as `onEdit` implies also updating the view to reflect the edit. + * + * @param edit Array of edits. Sorted from oldest to most recent. + */ + applyEdits(edits: readonly any[]): Thenable; - export interface WebviewEditor extends WebviewPanel { + /** + * Undo a set of edits. + * + * This is triggered when a user undoes an edit or when revert is called on a file. + * + * @param edit Array of edits. Sorted from most recent to oldest. + */ + undoEdits(edits: readonly any[]): Thenable; } export interface WebviewEditorProvider { /** - * Fills out a `WebviewEditor` for a given resource. - * - * The provider should take ownership of passed in `editor`. - */ + * Resolve a webview editor for a given resource. + * + * To resolve a webview editor, a provider must fill in its initial html content and hook up all + * the event listeners it is interested it. The provider should also take ownership of the passed in `WebviewPanel`. + * + * @param input Information about the resource being resolved. + * @param webview Webview being resolved. The provider should take ownership of this webview. + * + * @return Thenable to a `WebviewEditorCapabilities` indicating that the webview editor has been resolved. + * The `WebviewEditorCapabilities` defines how the custom editor interacts with VS Code. + */ resolveWebviewEditor( - resource: Uri, - editor: WebviewEditor - ): Thenable; + input: { + readonly resource: Uri + }, + webview: WebviewPanel, + ): Thenable; } namespace window { + /** + * Register a new provider for webview editors of a given type. + * + * @param viewType Type of the webview editor provider. + * @param provider Resolves webview editors. + * @param options Content settings for a webview panels the provider is given. + * + * @return Disposable that unregisters the `WebviewEditorProvider`. + */ export function registerWebviewEditorProvider( viewType: string, provider: WebviewEditorProvider, - options?: WebviewPanelOptions + options?: WebviewPanelOptions, ): Disposable; } //#endregion + + //#region insert/replace completions: https://github.com/microsoft/vscode/issues/10266 + + export interface CompletionItem { + + /** + * A range or a insert and replace range selecting the text that should be replaced by this completion item. + * + * When omitted, the range of the [current word](#TextDocument.getWordRangeAtPosition) is used as replace-range + * and as insert-range the start of the [current word](#TextDocument.getWordRangeAtPosition) to the + * current position is used. + * + * *Note 1:* A range must be a [single line](#Range.isSingleLine) and it must + * [contain](#Range.contains) the position at which completion has been [requested](#CompletionItemProvider.provideCompletionItems). + * *Note 2:* A insert range must be a prefix of a replace range, that means it must be contained and starting at the same position. + */ + range2?: Range | { inserting: Range; replacing: Range; }; + } + + //#endregion + + //#region allow QuickPicks to skip sorting: https://github.com/microsoft/vscode/issues/73904 + + export interface QuickPick extends QuickInput { + /** + * An optional flag to sort the final results by index of first query match in label. Defaults to true. + */ + sortByLabel: boolean; + } + + //#endregion + + //#region Surfacing reasons why a code action cannot be applied to users: https://github.com/microsoft/vscode/issues/85160 + + export interface CodeAction { + /** + * Marks that the code action cannot currently be applied. + * + * This should be a human readable description of why the code action is currently disabled. Disabled code actions + * will be surfaced in the refactor UI but cannot be applied. + */ + disabled?: string; + } + + //#endregion } diff --git a/src/vs/workbench/api/browser/mainThreadComments.ts b/src/vs/workbench/api/browser/mainThreadComments.ts index 95ee945efcef..55f37a6c3f9e 100644 --- a/src/vs/workbench/api/browser/mainThreadComments.ts +++ b/src/vs/workbench/api/browser/mainThreadComments.ts @@ -18,7 +18,7 @@ import { Extensions as PanelExtensions, PanelDescriptor, PanelRegistry } from 'v import { ICommentInfo, ICommentService } from 'vs/workbench/contrib/comments/browser/commentService'; import { CommentsPanel } from 'vs/workbench/contrib/comments/browser/commentsPanel'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; -import { CommentProviderFeatures, ExtHostCommentsShape, ExtHostContext, IExtHostContext, MainContext, MainThreadCommentsShape } from '../common/extHost.protocol'; +import { CommentProviderFeatures, ExtHostCommentsShape, ExtHostContext, IExtHostContext, MainContext, MainThreadCommentsShape, CommentThreadChanges } from '../common/extHost.protocol'; import { COMMENTS_PANEL_ID, COMMENTS_PANEL_TITLE } from 'vs/workbench/contrib/comments/browser/commentsTreeViewer'; @@ -116,17 +116,15 @@ export class MainThreadCommentThread implements modes.CommentThread { this._isDisposed = false; } - batchUpdate( - range: IRange, - label: string, - contextValue: string | undefined, - comments: modes.Comment[], - collapsibleState: modes.CommentThreadCollapsibleState) { - this._range = range; - this._label = label; - this._contextValue = contextValue; - this._comments = comments; - this._collapsibleState = collapsibleState; + batchUpdate(changes: CommentThreadChanges) { + const modified = (value: keyof CommentThreadChanges): boolean => + Object.prototype.hasOwnProperty.call(changes, value); + + if (modified('range')) { this._range = changes.range!; } + if (modified('label')) { this._label = changes.label; } + if (modified('contextValue')) { this._contextValue = changes.contextValue; } + if (modified('comments')) { this._comments = changes.comments; } + if (modified('collapseState')) { this._collapsibleState = changes.collapseState; } } dispose() { @@ -228,13 +226,9 @@ export class MainThreadCommentController { updateCommentThread(commentThreadHandle: number, threadId: string, resource: UriComponents, - range: IRange, - label: string, - contextValue: string | undefined, - comments: modes.Comment[], - collapsibleState: modes.CommentThreadCollapsibleState): void { + changes: CommentThreadChanges): void { let thread = this.getKnownThread(commentThreadHandle); - thread.batchUpdate(range, label, contextValue, comments, collapsibleState); + thread.batchUpdate(changes); this._commentService.updateComments(this._uniqueId, { added: [], @@ -430,18 +424,14 @@ export class MainThreadComments extends Disposable implements MainThreadComments commentThreadHandle: number, threadId: string, resource: UriComponents, - range: IRange, - label: string, - contextValue: string | undefined, - comments: modes.Comment[], - collapsibleState: modes.CommentThreadCollapsibleState): void { + changes: CommentThreadChanges): void { let provider = this._commentControllers.get(handle); if (!provider) { return undefined; } - return provider.updateCommentThread(commentThreadHandle, threadId, resource, range, label, contextValue, comments, collapsibleState); + return provider.updateCommentThread(commentThreadHandle, threadId, resource, changes); } $deleteCommentThread(handle: number, commentThreadHandle: number) { @@ -456,7 +446,7 @@ export class MainThreadComments extends Disposable implements MainThreadComments private registerPanel(commentsPanelAlreadyConstructed: boolean) { if (!commentsPanelAlreadyConstructed) { - Registry.as(PanelExtensions.Panels).registerPanel(new PanelDescriptor( + Registry.as(PanelExtensions.Panels).registerPanel(PanelDescriptor.create( CommentsPanel, COMMENTS_PANEL_ID, COMMENTS_PANEL_TITLE, diff --git a/src/vs/workbench/api/browser/mainThreadDialogs.ts b/src/vs/workbench/api/browser/mainThreadDialogs.ts index 576a7edf8047..27e06cc209d5 100644 --- a/src/vs/workbench/api/browser/mainThreadDialogs.ts +++ b/src/vs/workbench/api/browser/mainThreadDialogs.ts @@ -33,11 +33,11 @@ export class MainThreadDialogs implements MainThreadDiaglogsShape { private static _convertOpenOptions(options: MainThreadDialogOpenOptions): IOpenDialogOptions { const result: IOpenDialogOptions = { - openLabel: options.openLabel, + openLabel: options.openLabel || undefined, canSelectFiles: options.canSelectFiles || (!options.canSelectFiles && !options.canSelectFolders), canSelectFolders: options.canSelectFolders, canSelectMany: options.canSelectMany, - defaultUri: URI.revive(options.defaultUri) + defaultUri: options.defaultUri ? URI.revive(options.defaultUri) : undefined }; if (options.filters) { result.filters = []; @@ -48,8 +48,8 @@ export class MainThreadDialogs implements MainThreadDiaglogsShape { private static _convertSaveOptions(options: MainThreadDialogSaveOptions): ISaveDialogOptions { const result: ISaveDialogOptions = { - defaultUri: URI.revive(options.defaultUri), - saveLabel: options.saveLabel + defaultUri: options.defaultUri ? URI.revive(options.defaultUri) : undefined, + saveLabel: options.saveLabel || undefined }; if (options.filters) { result.filters = []; diff --git a/src/vs/workbench/api/browser/mainThreadDocuments.ts b/src/vs/workbench/api/browser/mainThreadDocuments.ts index dc94f9ab43b8..5fcdc4c61239 100644 --- a/src/vs/workbench/api/browser/mainThreadDocuments.ts +++ b/src/vs/workbench/api/browser/mainThreadDocuments.ts @@ -16,7 +16,7 @@ import { MainThreadDocumentsAndEditors } from 'vs/workbench/api/browser/mainThre import { ExtHostContext, ExtHostDocumentsShape, IExtHostContext, MainThreadDocumentsShape } from 'vs/workbench/api/common/extHost.protocol'; import { ITextEditorModel } from 'vs/workbench/common/editor'; import { ITextFileService, TextFileModelChangeEvent } from 'vs/workbench/services/textfile/common/textfiles'; -import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; +import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { toLocalResource } from 'vs/base/common/resources'; @@ -70,7 +70,7 @@ export class MainThreadDocuments implements MainThreadDocumentsShape { private readonly _textModelResolverService: ITextModelService; private readonly _textFileService: ITextFileService; private readonly _fileService: IFileService; - private readonly _untitledEditorService: IUntitledEditorService; + private readonly _untitledTextEditorService: IUntitledTextEditorService; private readonly _environmentService: IWorkbenchEnvironmentService; private readonly _toDispose = new DisposableStore(); @@ -87,14 +87,14 @@ export class MainThreadDocuments implements MainThreadDocumentsShape { @ITextFileService textFileService: ITextFileService, @IFileService fileService: IFileService, @ITextModelService textModelResolverService: ITextModelService, - @IUntitledEditorService untitledEditorService: IUntitledEditorService, + @IUntitledTextEditorService untitledTextEditorService: IUntitledTextEditorService, @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService ) { this._modelService = modelService; this._textModelResolverService = textModelResolverService; this._textFileService = textFileService; this._fileService = fileService; - this._untitledEditorService = untitledEditorService; + this._untitledTextEditorService = untitledTextEditorService; this._environmentService = environmentService; this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostDocuments); @@ -227,13 +227,13 @@ export class MainThreadDocuments implements MainThreadDocumentsShape { } private _doCreateUntitled(resource?: URI, mode?: string, initialValue?: string): Promise { - return this._untitledEditorService.loadOrCreate({ + return this._untitledTextEditorService.loadOrCreate({ resource, mode, initialValue, useResourcePath: Boolean(resource && resource.path) }).then(model => { - const resource = model.getResource(); + const resource = model.resource; if (!this._modelIsSynced.has(resource.toString())) { throw new Error(`expected URI ${resource.toString()} to have come to LIFE`); diff --git a/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts b/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts index 11bb71394b9d..ffdbdec8e216 100644 --- a/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts +++ b/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts @@ -28,7 +28,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; +import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; namespace delta { @@ -327,7 +327,7 @@ export class MainThreadDocumentsAndEditors { @IModeService modeService: IModeService, @IFileService fileService: IFileService, @ITextModelService textModelResolverService: ITextModelService, - @IUntitledEditorService untitledEditorService: IUntitledEditorService, + @IUntitledTextEditorService untitledTextEditorService: IUntitledTextEditorService, @IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService, @IBulkEditService bulkEditService: IBulkEditService, @IPanelService panelService: IPanelService, @@ -335,7 +335,7 @@ export class MainThreadDocumentsAndEditors { ) { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostDocumentsAndEditors); - const mainThreadDocuments = this._toDispose.add(new MainThreadDocuments(this, extHostContext, this._modelService, modeService, this._textFileService, fileService, textModelResolverService, untitledEditorService, environmentService)); + const mainThreadDocuments = this._toDispose.add(new MainThreadDocuments(this, extHostContext, this._modelService, modeService, this._textFileService, fileService, textModelResolverService, untitledTextEditorService, environmentService)); extHostContext.set(MainContext.MainThreadDocuments, mainThreadDocuments); const mainThreadTextEditors = this._toDispose.add(new MainThreadTextEditors(this, extHostContext, codeEditorService, bulkEditService, this._editorService, this._editorGroupService)); diff --git a/src/vs/workbench/api/browser/mainThreadFileSystem.ts b/src/vs/workbench/api/browser/mainThreadFileSystem.ts index 017ce6349379..58ff9f79a3ab 100644 --- a/src/vs/workbench/api/browser/mainThreadFileSystem.ts +++ b/src/vs/workbench/api/browser/mainThreadFileSystem.ts @@ -6,7 +6,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { FileWriteOptions, FileSystemProviderCapabilities, IFileChange, IFileService, IFileSystemProvider, IStat, IWatchOptions, FileType, FileOverwriteOptions, FileDeleteOptions, FileOpenOptions, IFileStat, FileOperationError, FileOperationResult, FileSystemProviderErrorCode } from 'vs/platform/files/common/files'; +import { FileWriteOptions, FileSystemProviderCapabilities, IFileChange, IFileService, IStat, IWatchOptions, FileType, FileOverwriteOptions, FileDeleteOptions, FileOpenOptions, IFileStat, FileOperationError, FileOperationResult, FileSystemProviderErrorCode, IFileSystemProviderWithOpenReadWriteCloseCapability, IFileSystemProviderWithFileReadWriteCapability, IFileSystemProviderWithFileFolderCopyCapability } from 'vs/platform/files/common/files'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { ExtHostContext, ExtHostFileSystemShape, IExtHostContext, IFileChangeDto, MainContext, MainThreadFileSystemShape } from '../common/extHost.protocol'; import { VSBuffer } from 'vs/base/common/buffer'; @@ -52,10 +52,10 @@ export class MainThreadFileSystem implements MainThreadFileSystemShape { $stat(uri: UriComponents): Promise { return this._fileService.resolve(URI.revive(uri), { resolveMetadata: true }).then(stat => { return { - ctime: 0, + ctime: stat.ctime, mtime: stat.mtime, size: stat.size, - type: MainThreadFileSystem._getFileType(stat) + type: MainThreadFileSystem._asFileType(stat) }; }).catch(MainThreadFileSystem._handleError); } @@ -67,12 +67,22 @@ export class MainThreadFileSystem implements MainThreadFileSystemShape { err.name = FileSystemProviderErrorCode.FileNotADirectory; throw err; } - return !stat.children ? [] : stat.children.map(child => [child.name, MainThreadFileSystem._getFileType(child)]); + return !stat.children ? [] : stat.children.map(child => [child.name, MainThreadFileSystem._asFileType(child)] as [string, FileType]); }).catch(MainThreadFileSystem._handleError); } - private static _getFileType(stat: IFileStat): FileType { - return (stat.isDirectory ? FileType.Directory : FileType.File) + (stat.isSymbolicLink ? FileType.SymbolicLink : 0); + private static _asFileType(stat: IFileStat): FileType { + let res = 0; + if (stat.isFile) { + res += FileType.File; + + } else if (stat.isDirectory) { + res += FileType.Directory; + } + if (stat.isSymbolicLink) { + res += FileType.SymbolicLink; + } + return res; } $readFile(uri: UriComponents): Promise { @@ -80,19 +90,23 @@ export class MainThreadFileSystem implements MainThreadFileSystemShape { } $writeFile(uri: UriComponents, content: VSBuffer): Promise { - return this._fileService.writeFile(URI.revive(uri), content).catch(MainThreadFileSystem._handleError); + return this._fileService.writeFile(URI.revive(uri), content) + .then(() => undefined).catch(MainThreadFileSystem._handleError); } $rename(source: UriComponents, target: UriComponents, opts: FileOverwriteOptions): Promise { - return this._fileService.move(URI.revive(source), URI.revive(target), opts.overwrite).catch(MainThreadFileSystem._handleError); + return this._fileService.move(URI.revive(source), URI.revive(target), opts.overwrite) + .then(() => undefined).catch(MainThreadFileSystem._handleError); } $copy(source: UriComponents, target: UriComponents, opts: FileOverwriteOptions): Promise { - return this._fileService.copy(URI.revive(source), URI.revive(target), opts.overwrite).catch(MainThreadFileSystem._handleError); + return this._fileService.copy(URI.revive(source), URI.revive(target), opts.overwrite) + .then(() => undefined).catch(MainThreadFileSystem._handleError); } $mkdir(uri: UriComponents): Promise { - return this._fileService.createFolder(URI.revive(uri)).catch(MainThreadFileSystem._handleError); + return this._fileService.createFolder(URI.revive(uri)) + .then(() => undefined).catch(MainThreadFileSystem._handleError); } $delete(uri: UriComponents, opts: FileDeleteOptions): Promise { @@ -121,7 +135,7 @@ export class MainThreadFileSystem implements MainThreadFileSystemShape { } } -class RemoteFileSystemProvider implements IFileSystemProvider { +class RemoteFileSystemProvider implements IFileSystemProviderWithFileReadWriteCapability, IFileSystemProviderWithOpenReadWriteCloseCapability, IFileSystemProviderWithFileFolderCopyCapability { private readonly _onDidChange = new Emitter(); private readonly _registration: IDisposable; diff --git a/src/vs/workbench/api/browser/mainThreadFileSystemEventService.ts b/src/vs/workbench/api/browser/mainThreadFileSystemEventService.ts index f6e2a42865f3..44da9c39ee3e 100644 --- a/src/vs/workbench/api/browser/mainThreadFileSystemEventService.ts +++ b/src/vs/workbench/api/browser/mainThreadFileSystemEventService.ts @@ -3,21 +3,31 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import { FileChangeType, IFileService, FileOperation } from 'vs/platform/files/common/files'; import { extHostCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { ExtHostContext, FileSystemEvents, IExtHostContext } from '../common/extHost.protocol'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; +import { localize } from 'vs/nls'; +import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ILogService } from 'vs/platform/log/common/log'; +import { CancellationTokenSource } from 'vs/base/common/cancellation'; @extHostCustomer export class MainThreadFileSystemEventService { - private readonly _listener = new Array(); + private readonly _listener = new DisposableStore(); constructor( extHostContext: IExtHostContext, @IFileService fileService: IFileService, - @ITextFileService textfileService: ITextFileService, + @ITextFileService textFileService: ITextFileService, + @IProgressService progressService: IProgressService, + @IConfigurationService configService: IConfigurationService, + @ILogService logService: ILogService, ) { const proxy = extHostContext.getProxy(ExtHostContext.ExtHostFileSystemEventService); @@ -28,7 +38,7 @@ export class MainThreadFileSystemEventService { changed: [], deleted: [] }; - fileService.onFileChanges(event => { + this._listener.add(fileService.onFileChanges(event => { for (let change of event.changes) { switch (change.type) { case FileChangeType.ADDED: @@ -47,22 +57,64 @@ export class MainThreadFileSystemEventService { events.created.length = 0; events.changed.length = 0; events.deleted.length = 0; - }, undefined, this._listener); + })); - // file operation events - (changes the editor makes) - fileService.onAfterOperation(e => { - if (e.isOperation(FileOperation.MOVE)) { - proxy.$onFileRename(e.resource, e.target.resource); + + // BEFORE file operation + const messages = new Map(); + messages.set(FileOperation.CREATE, localize('msg-create', "Running 'File Create' participants...")); + messages.set(FileOperation.DELETE, localize('msg-delete', "Running 'File Delete' participants...")); + messages.set(FileOperation.MOVE, localize('msg-rename', "Running 'File Rename' participants...")); + + + this._listener.add(textFileService.onWillRunOperation(e => { + + const timeout = configService.getValue('files.participants.timeout'); + if (timeout <= 0) { + return; // disabled } - }, undefined, this._listener); - textfileService.onWillMove(e => { - const promise = proxy.$onWillRename(e.oldResource, e.newResource); - e.waitUntil(promise); - }, undefined, this._listener); + const p = progressService.withProgress({ location: ProgressLocation.Window }, progress => { + + progress.report({ message: messages.get(e.operation) }); + + return new Promise((resolve, reject) => { + + const cts = new CancellationTokenSource(); + + const timeoutHandle = setTimeout(() => { + logService.trace('CANCELLED file participants because of timeout', timeout, e.target, e.operation); + cts.cancel(); + reject(new Error('timeout')); + }, timeout); + + proxy.$onWillRunFileOperation(e.operation, e.target, e.source, timeout, cts.token) + .then(resolve, reject) + .finally(() => clearTimeout(timeoutHandle)); + }); + + }); + + e.waitUntil(p); + })); + + // AFTER file operation + this._listener.add(textFileService.onDidRunOperation(e => proxy.$onDidRunFileOperation(e.operation, e.target, e.source))); } dispose(): void { - dispose(this._listener); + this._listener.dispose(); } } + + +Registry.as(Extensions.Configuration).registerConfiguration({ + id: 'files', + properties: { + 'files.participants.timeout': { + type: 'number', + default: 5000, + markdownDescription: localize('files.participants.timeout', "Timeout in milliseconds after which file participants for create, rename, and delete are cancelled. Use `0` to disable participants."), + } + } +}); diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index c1ca1167d46c..9c1c8659889b 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -21,6 +21,7 @@ import { Selection } from 'vs/editor/common/core/selection'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import * as callh from 'vs/workbench/contrib/callHierarchy/browser/callHierarchy'; import { mixin } from 'vs/base/common/objects'; +import { decodeSemanticTokensDto } from 'vs/workbench/api/common/shared/semanticTokens'; @extHostNamedCustomer(MainContext.MainThreadLanguageFeatures) export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesShape { @@ -324,9 +325,16 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha })); } + // --- semantic tokens + + $registerSemanticTokensProvider(handle: number, selector: IDocumentFilterDto[], legend: modes.SemanticTokensLegend): void { + this._registrations.set(handle, modes.SemanticTokensProviderRegistry.register(selector, new MainThreadSemanticTokensProvider(this._proxy, handle, legend))); + } + // --- suggest - private static _inflateSuggestDto(defaultRange: IRange, data: ISuggestDataDto): modes.CompletionItem { + private static _inflateSuggestDto(defaultRange: IRange | { insert: IRange, replace: IRange }, data: ISuggestDataDto): modes.CompletionItem { + return { label: data[ISuggestDataDtoField.label], kind: data[ISuggestDataDtoField.kind], @@ -337,8 +345,8 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha filterText: data[ISuggestDataDtoField.filterText], preselect: data[ISuggestDataDtoField.preselect], insertText: typeof data.h === 'undefined' ? data[ISuggestDataDtoField.label] : data.h, - insertTextRules: data[ISuggestDataDtoField.insertTextRules], range: data[ISuggestDataDtoField.range] || defaultRange, + insertTextRules: data[ISuggestDataDtoField.insertTextRules], commitCharacters: data[ISuggestDataDtoField.commitCharacters], additionalTextEdits: data[ISuggestDataDtoField.additionalTextEdits], command: data[ISuggestDataDtoField.command], @@ -371,6 +379,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha if (!result) { return suggestion; } + let newSuggestion = MainThreadLanguageFeatures._inflateSuggestDto(suggestion.range, result); return mixin(suggestion, newSuggestion, true); }); @@ -495,29 +504,37 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha $registerCallHierarchyProvider(handle: number, selector: IDocumentFilterDto[]): void { this._registrations.set(handle, callh.CallHierarchyProviderRegistry.register(selector, { - provideOutgoingCalls: async (model, position, token) => { - const outgoing = await this._proxy.$provideCallHierarchyOutgoingCalls(handle, model.uri, position, token); + + prepareCallHierarchy: async (document, position, token) => { + const item = await this._proxy.$prepareCallHierarchy(handle, document.uri, position, token); + if (!item) { + return undefined; + } + return { + dispose: () => this._proxy.$releaseCallHierarchy(handle, item._sessionId), + root: MainThreadLanguageFeatures._reviveCallHierarchyItemDto(item) + }; + }, + + provideOutgoingCalls: async (item, token) => { + const outgoing = await this._proxy.$provideCallHierarchyOutgoingCalls(handle, item._sessionId, item._itemId, token); if (!outgoing) { return undefined; // {{SQL CARBON EDIT}} strict-null-check } - return outgoing.map(([item, fromRanges]): callh.OutgoingCall => { - return { - to: MainThreadLanguageFeatures._reviveCallHierarchyItemDto(item), - fromRanges - }; + outgoing.forEach(value => { + value.to = MainThreadLanguageFeatures._reviveCallHierarchyItemDto(value.to); }); + return outgoing; }, - provideIncomingCalls: async (model, position, token) => { - const incoming = await this._proxy.$provideCallHierarchyIncomingCalls(handle, model.uri, position, token); + provideIncomingCalls: async (item, token) => { + const incoming = await this._proxy.$provideCallHierarchyIncomingCalls(handle, item._sessionId, item._itemId, token); if (!incoming) { return undefined; // {{SQL CARBON EDIT}} strict-null-check } - return incoming.map(([item, fromRanges]): callh.IncomingCall => { - return { - from: MainThreadLanguageFeatures._reviveCallHierarchyItemDto(item), - fromRanges - }; + incoming.forEach(value => { + value.from = MainThreadLanguageFeatures._reviveCallHierarchyItemDto(value.from); }); + return incoming; } })); } @@ -585,3 +602,45 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha } } + +export class MainThreadSemanticTokensProvider implements modes.SemanticTokensProvider { + + constructor( + private readonly _proxy: ExtHostLanguageFeaturesShape, + private readonly _handle: number, + private readonly _legend: modes.SemanticTokensLegend, + ) { + } + + public releaseSemanticTokens(resultId: string | undefined): void { + if (resultId) { + this._proxy.$releaseSemanticTokens(this._handle, parseInt(resultId, 10)); + } + } + + public getLegend(): modes.SemanticTokensLegend { + return this._legend; + } + + async provideSemanticTokens(model: ITextModel, lastResultId: string | null, ranges: EditorRange[] | null, token: CancellationToken): Promise { + const nLastResultId = lastResultId ? parseInt(lastResultId, 10) : 0; + const encodedDto = await this._proxy.$provideSemanticTokens(this._handle, model.uri, ranges, nLastResultId, token); + if (!encodedDto) { + return null; + } + if (token.isCancellationRequested) { + return null; + } + const dto = decodeSemanticTokensDto(encodedDto); + if (dto.type === 'full') { + return { + resultId: String(dto.id), + data: dto.data + }; + } + return { + resultId: String(dto.id), + edits: dto.deltas + }; + } +} diff --git a/src/vs/workbench/api/browser/mainThreadSCM.ts b/src/vs/workbench/api/browser/mainThreadSCM.ts index f69951857cdf..9e289f63fc9d 100644 --- a/src/vs/workbench/api/browser/mainThreadSCM.ts +++ b/src/vs/workbench/api/browser/mainThreadSCM.ts @@ -288,7 +288,7 @@ export class MainThreadSCM implements MainThreadSCMShape { } $registerSourceControl(handle: number, id: string, label: string, rootUri: UriComponents | undefined): void { - const provider = new MainThreadSCMProvider(this._proxy, handle, id, label, rootUri && URI.revive(rootUri), this.scmService); + const provider = new MainThreadSCMProvider(this._proxy, handle, id, label, rootUri ? URI.revive(rootUri) : undefined, this.scmService); const repository = this.scmService.registerSCMProvider(provider); this._repositories.set(handle, repository); diff --git a/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts b/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts index 142c80129993..87ab712719c0 100644 --- a/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts +++ b/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts @@ -19,7 +19,7 @@ import { CodeAction } from 'vs/editor/common/modes'; import { shouldSynchronizeModel } from 'vs/editor/common/services/modelService'; import { getCodeActions } from 'vs/editor/contrib/codeAction/codeAction'; import { applyCodeAction } from 'vs/editor/contrib/codeAction/codeActionCommands'; -import { CodeActionKind } from 'vs/editor/contrib/codeAction/codeActionTrigger'; +import { CodeActionKind } from 'vs/editor/contrib/codeAction/types'; import { formatDocumentWithSelectedProvider, FormattingMode } from 'vs/editor/contrib/format/format'; import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2'; import { localize } from 'vs/nls'; @@ -30,7 +30,8 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; import { extHostCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; -import { ISaveParticipant, SaveReason, IResolvedTextFileEditorModel, ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; // {{SQL CARBON EDIT}} +import { ISaveParticipant, IResolvedTextFileEditorModel, ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; +import { SaveReason } from 'vs/workbench/common/editor'; import { ExtHostContext, ExtHostDocumentSaveParticipantShape, IExtHostContext } from '../common/extHost.protocol'; import { INotebookService } from 'sql/workbench/services/notebook/browser/notebookService'; // {{SQL CARBON EDIT}} @@ -53,7 +54,7 @@ class NotebookUpdateParticipant implements ISaveParticipantParticipant { // {{SQ } public participate(model: ITextFileEditorModel, env: { reason: SaveReason }): Promise { - let uri = model.getResource(); + let uri = model.resource; let notebookEditor = this.notebookService.findNotebookEditor(uri); if (notebookEditor) { notebookEditor.notebookParams.input.updateModel(); @@ -76,7 +77,7 @@ class TrimWhitespaceParticipant implements ISaveParticipantParticipant { } async participate(model: IResolvedTextFileEditorModel, env: { reason: SaveReason; }): Promise { - if (this.configurationService.getValue('files.trimTrailingWhitespace', { overrideIdentifier: model.textEditorModel.getLanguageIdentifier().language, resource: model.getResource() })) { + if (this.configurationService.getValue('files.trimTrailingWhitespace', { overrideIdentifier: model.textEditorModel.getLanguageIdentifier().language, resource: model.resource })) { this.doTrimTrailingWhitespace(model.textEditorModel, env.reason === SaveReason.AUTO); } } @@ -138,7 +139,7 @@ export class FinalNewLineParticipant implements ISaveParticipantParticipant { } async participate(model: IResolvedTextFileEditorModel, env: { reason: SaveReason; }): Promise { - if (this.configurationService.getValue('files.insertFinalNewline', { overrideIdentifier: model.textEditorModel.getLanguageIdentifier().language, resource: model.getResource() })) { + if (this.configurationService.getValue('files.insertFinalNewline', { overrideIdentifier: model.textEditorModel.getLanguageIdentifier().language, resource: model.resource })) { this.doInsertFinalNewLine(model.textEditorModel); } } @@ -172,7 +173,7 @@ export class TrimFinalNewLinesParticipant implements ISaveParticipantParticipant } async participate(model: IResolvedTextFileEditorModel, env: { reason: SaveReason; }): Promise { - if (this.configurationService.getValue('files.trimFinalNewlines', { overrideIdentifier: model.textEditorModel.getLanguageIdentifier().language, resource: model.getResource() })) { + if (this.configurationService.getValue('files.trimFinalNewlines', { overrideIdentifier: model.textEditorModel.getLanguageIdentifier().language, resource: model.resource })) { this.doTrimFinalNewLines(model.textEditorModel, env.reason === SaveReason.AUTO); } } @@ -282,7 +283,7 @@ class CodeActionOnSaveParticipant implements ISaveParticipant { const model = editorModel.textEditorModel; - const settingsOverrides = { overrideIdentifier: model.getLanguageIdentifier().language, resource: editorModel.getResource() }; + const settingsOverrides = { overrideIdentifier: model.getLanguageIdentifier().language, resource: editorModel.resource }; const setting = this._configurationService.getValue('editor.codeActionsOnSave', settingsOverrides); if (!setting) { return undefined; @@ -307,6 +308,10 @@ class CodeActionOnSaveParticipant implements ISaveParticipant { return undefined; } + const excludedActions = Object.keys(setting) + .filter(x => setting[x] === false) + .map(x => new CodeActionKind(x)); + const tokenSource = new CancellationTokenSource(); const timeout = this._configurationService.getValue('editor.codeActionsOnSaveTimeout', settingsOverrides); @@ -317,17 +322,17 @@ class CodeActionOnSaveParticipant implements ISaveParticipant { tokenSource.cancel(); reject(localize('codeActionsOnSave.didTimeout', "Aborted codeActionsOnSave after {0}ms", timeout)); }, timeout)), - this.applyOnSaveActions(model, codeActionsOnSave, tokenSource.token) + this.applyOnSaveActions(model, codeActionsOnSave, excludedActions, tokenSource.token) ]).finally(() => { tokenSource.cancel(); }); } - private async applyOnSaveActions(model: ITextModel, codeActionsOnSave: CodeActionKind[], token: CancellationToken): Promise { + private async applyOnSaveActions(model: ITextModel, codeActionsOnSave: readonly CodeActionKind[], excludes: readonly CodeActionKind[], token: CancellationToken): Promise { for (const codeActionKind of codeActionsOnSave) { - const actionsToRun = await this.getActionsToRun(model, codeActionKind, token); + const actionsToRun = await this.getActionsToRun(model, codeActionKind, excludes, token); try { - await this.applyCodeActions(actionsToRun.actions); + await this.applyCodeActions(actionsToRun.validActions); } catch { // Failure to apply a code action should not block other on save actions } finally { @@ -342,10 +347,10 @@ class CodeActionOnSaveParticipant implements ISaveParticipant { } } - private getActionsToRun(model: ITextModel, codeActionKind: CodeActionKind, token: CancellationToken) { + private getActionsToRun(model: ITextModel, codeActionKind: CodeActionKind, excludes: readonly CodeActionKind[], token: CancellationToken) { return getCodeActions(model, model.getFullModelRange(), { type: 'auto', - filter: { kind: codeActionKind, includeSourceActions: true }, + filter: { include: codeActionKind, excludes: excludes, includeSourceActions: true }, }, token); } } @@ -368,7 +373,7 @@ class ExtHostSaveParticipant implements ISaveParticipantParticipant { return new Promise((resolve, reject) => { setTimeout(() => reject(localize('timeout.onWillSave', "Aborted onWillSaveTextDocument-event after 1750ms")), 1750); - this._proxy.$participateInSave(editorModel.getResource(), env.reason).then(values => { + this._proxy.$participateInSave(editorModel.resource, env.reason).then(values => { if (!values.every(success => success)) { return Promise.reject(new Error('listener failed')); } diff --git a/src/vs/workbench/api/browser/mainThreadTerminalService.ts b/src/vs/workbench/api/browser/mainThreadTerminalService.ts index 1cdfc730bc48..1357e5f21960 100644 --- a/src/vs/workbench/api/browser/mainThreadTerminalService.ts +++ b/src/vs/workbench/api/browser/mainThreadTerminalService.ts @@ -12,6 +12,7 @@ import { StopWatch } from 'vs/base/common/stopwatch'; import { ITerminalInstanceService, ITerminalService, ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { TerminalDataBufferer } from 'vs/workbench/contrib/terminal/common/terminalDataBuffering'; @extHostNamedCustomer(MainContext.MainThreadTerminalService) export class MainThreadTerminalService implements MainThreadTerminalServiceShape { @@ -162,15 +163,22 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape } private _onTerminalDisposed(terminalInstance: ITerminalInstance): void { - this._proxy.$acceptTerminalClosed(terminalInstance.id); + this._proxy.$acceptTerminalClosed(terminalInstance.id, terminalInstance.exitCode); } private _onTerminalOpened(terminalInstance: ITerminalInstance): void { + const shellLaunchConfigDto: IShellLaunchConfigDto = { + name: terminalInstance.shellLaunchConfig.name, + executable: terminalInstance.shellLaunchConfig.executable, + args: terminalInstance.shellLaunchConfig.args, + cwd: terminalInstance.shellLaunchConfig.cwd, + env: terminalInstance.shellLaunchConfig.env + }; if (terminalInstance.title) { - this._proxy.$acceptTerminalOpened(terminalInstance.id, terminalInstance.title); + this._proxy.$acceptTerminalOpened(terminalInstance.id, terminalInstance.title, shellLaunchConfigDto); } else { terminalInstance.waitForTitle().then(title => { - this._proxy.$acceptTerminalOpened(terminalInstance.id, title); + this._proxy.$acceptTerminalOpened(terminalInstance.id, title, shellLaunchConfigDto); }); } } @@ -256,7 +264,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape this._getTerminalProcess(terminalId).then(e => e.emitReady(pid, cwd)); } - public $sendProcessExit(terminalId: number, exitCode: number): void { + public $sendProcessExit(terminalId: number, exitCode: number | undefined): void { this._getTerminalProcess(terminalId).then(e => e.emitExit(exitCode)); this._terminalProcesses.delete(terminalId); } @@ -327,16 +335,23 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape * listeners are removed. */ class TerminalDataEventTracker extends Disposable { + private readonly _bufferer: TerminalDataBufferer; + constructor( private readonly _callback: (id: number, data: string) => void, @ITerminalService private readonly _terminalService: ITerminalService ) { super(); + + this._register(this._bufferer = new TerminalDataBufferer()); + this._terminalService.terminalInstances.forEach(instance => this._registerInstance(instance)); this._register(this._terminalService.onInstanceCreated(instance => this._registerInstance(instance))); + this._register(this._terminalService.onInstanceDisposed(instance => this._bufferer.stopBuffering(instance.id))); } private _registerInstance(instance: ITerminalInstance): void { - this._register(instance.onData(e => this._callback(instance.id, e))); + // Buffer data events to reduce the amount of messages going to the extension host + this._register(this._bufferer.startBuffering(instance.id, instance.onData, this._callback)); } } diff --git a/src/vs/workbench/api/browser/mainThreadTreeViews.ts b/src/vs/workbench/api/browser/mainThreadTreeViews.ts index 73a9e0fbac11..36dc52299f17 100644 --- a/src/vs/workbench/api/browser/mainThreadTreeViews.ts +++ b/src/vs/workbench/api/browser/mainThreadTreeViews.ts @@ -12,6 +12,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { isUndefinedOrNull, isNumber } from 'vs/base/common/types'; import { Registry } from 'vs/platform/registry/common/platform'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { ILogService } from 'vs/platform/log/common/log'; @extHostNamedCustomer(MainContext.MainThreadTreeViews) export class MainThreadTreeViews extends Disposable implements MainThreadTreeViewsShape { @@ -23,13 +24,16 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie extHostContext: IExtHostContext, @IViewsService private readonly viewsService: IViewsService, @INotificationService private readonly notificationService: INotificationService, - @IExtensionService private readonly extensionService: IExtensionService + @IExtensionService private readonly extensionService: IExtensionService, + @ILogService private readonly logService: ILogService ) { super(); this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTreeViews); } $registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean, canSelectMany: boolean }): void { + this.logService.trace('MainThreadTreeViews#$registerTreeViewDataProvider', treeViewId, options); + this.extensionService.whenInstalledExtensionsRegistered().then(() => { const dataProvider = new TreeViewDataProvider(treeViewId, this._proxy, this.notificationService); this._dataProviders.set(treeViewId, dataProvider); @@ -49,6 +53,8 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie } $reveal(treeViewId: string, item: ITreeItem, parentChain: ITreeItem[], options: IRevealOptions): Promise { + this.logService.trace('MainThreadTreeViews#$reveal', treeViewId, item, parentChain, options); + return this.viewsService.openView(treeViewId, options.focus) .then(() => { const viewer = this.getTreeView(treeViewId); @@ -60,6 +66,8 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie } $refresh(treeViewId: string, itemsToRefreshByHandle: { [treeItemHandle: string]: ITreeItem }): Promise { + this.logService.trace('MainThreadTreeViews#$refresh', treeViewId, itemsToRefreshByHandle); + const viewer = this.getTreeView(treeViewId); const dataProvider = this._dataProviders.get(treeViewId); if (viewer && dataProvider) { @@ -70,6 +78,8 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie } $setMessage(treeViewId: string, message: string): void { + this.logService.trace('MainThreadTreeViews#$setMessage', treeViewId, message); + const viewer = this.getTreeView(treeViewId); if (viewer) { viewer.message = message; @@ -77,6 +87,8 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie } $setTitle(treeViewId: string, title: string): void { + this.logService.trace('MainThreadTreeViews#$setTitle', treeViewId, title); + const viewer = this.getTreeView(treeViewId); if (viewer) { viewer.title = title; diff --git a/src/vs/workbench/api/browser/mainThreadUrls.ts b/src/vs/workbench/api/browser/mainThreadUrls.ts index 110b03fb5de6..6f266d61bbb1 100644 --- a/src/vs/workbench/api/browser/mainThreadUrls.ts +++ b/src/vs/workbench/api/browser/mainThreadUrls.ts @@ -68,7 +68,11 @@ export class MainThreadUrls implements MainThreadUrlsShape { return Promise.resolve(undefined); } - async $createAppUri(extensionId: ExtensionIdentifier, options?: { payload?: Partial }): Promise { + async $createAppUri(uri: UriComponents): Promise { + return this.urlService.create(uri); + } + + async $proposedCreateAppUri(extensionId: ExtensionIdentifier, options?: { payload?: Partial }): Promise { const payload: Partial = options && options.payload ? options.payload : Object.create(null); // we define the authority to be the extension ID to ensure diff --git a/src/vs/workbench/api/browser/mainThreadWebview.ts b/src/vs/workbench/api/browser/mainThreadWebview.ts index b0cf28435e88..a4f784ca551f 100644 --- a/src/vs/workbench/api/browser/mainThreadWebview.ts +++ b/src/vs/workbench/api/browser/mainThreadWebview.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { onUnexpectedError } from 'vs/base/common/errors'; -import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { isWeb } from 'vs/base/common/platform'; import { startsWith } from 'vs/base/common/strings'; @@ -20,6 +20,7 @@ import { editorGroupToViewColumn, EditorViewColumn, viewColumnToEditorGroup } fr import { IEditorInput } from 'vs/workbench/common/editor'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { CustomFileEditorInput } from 'vs/workbench/contrib/customEditor/browser/customEditorInput'; +import { ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; import { WebviewExtensionDescription } from 'vs/workbench/contrib/webview/browser/webview'; import { WebviewInput } from 'vs/workbench/contrib/webview/browser/webviewEditorInput'; import { ICreateWebViewShowOptions, IWebviewWorkbenchService, WebviewInputOptions } from 'vs/workbench/contrib/webview/browser/webviewWorkbenchService'; @@ -98,6 +99,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma constructor( context: extHostProtocol.IExtHostContext, @IExtensionService extensionService: IExtensionService, + @ICustomEditorService private readonly _customEditorService: ICustomEditorService, @IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService, @IEditorService private readonly _editorService: IEditorService, @IOpenerService private readonly _openerService: IOpenerService, @@ -168,13 +170,6 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma webview.setName(value); } - public $setState(handle: extHostProtocol.WebviewPanelHandle, state: modes.WebviewContentState): void { - const webview = this.getWebviewInput(handle); - if (webview instanceof CustomFileEditorInput) { - webview.setState(state); - } - } - public $setIconPath(handle: extHostProtocol.WebviewPanelHandle, value: { light: UriComponents, dark: UriComponents; } | undefined): void { const webview = this.getWebviewInput(handle); webview.iconPath = reviveWebviewIcon(value); @@ -268,7 +263,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma canResolve: (webviewInput) => { return webviewInput instanceof CustomFileEditorInput && webviewInput.viewType === viewType; }, - resolveWebview: async (webviewInput) => { + resolveWebview: async (webviewInput: CustomFileEditorInput) => { const handle = webviewInput.id; this._webviewInputs.add(handle, webviewInput); this.hookupWebviewEventDelegate(handle, webviewInput); @@ -276,15 +271,25 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma webviewInput.webview.options = options; webviewInput.webview.extension = extension; - if (webviewInput instanceof CustomFileEditorInput) { - webviewInput.onWillSave(e => { - e.waitUntil(this._proxy.$save(handle)); - }); - } + const model = await this._customEditorService.models.loadOrCreate(webviewInput.getResource(), webviewInput.viewType); + + model.onUndo(edits => { this._proxy.$undoEdits(handle, edits.map(x => x.data)); }); + model.onApplyEdit(edits => { + const editsToApply = edits.filter(x => x.source !== webviewInput).map(x => x.data); + if (editsToApply.length) { + this._proxy.$applyEdits(handle, editsToApply); + } + }); + model.onWillSave(e => { e.waitUntil(this._proxy.$onSave(handle)); }); + model.onWillSaveAs(e => { e.waitUntil(this._proxy.$onSaveAs(handle, e.resource.toJSON(), e.targetResource.toJSON())); }); + + webviewInput.onDisposeWebview(() => { + this._customEditorService.models.disposeModel(model); + }); try { await this._proxy.$resolveWebviewEditor( - webviewInput.getResource(), + { resource: webviewInput.getResource(), edits: model.currentEdits }, handle, viewType, webviewInput.getTitle(), @@ -294,6 +299,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma } catch (error) { onUnexpectedError(error); webviewInput.webview.html = MainThreadWebviews.getDeserializationFailedContents(viewType); + return; } } })); @@ -309,15 +315,35 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma this._editorProviders.delete(viewType); } + public $onEdit(handle: extHostProtocol.WebviewPanelHandle, editData: any): void { + const webview = this.getWebviewInput(handle); + if (!(webview instanceof CustomFileEditorInput)) { + throw new Error('Webview is not a webview editor'); + } + + const model = this._customEditorService.models.get(webview.getResource(), webview.viewType); + if (!model) { + throw new Error('Could not find model for webview editor'); + } + + model.makeEdit({ source: webview, data: editData }); + } + private hookupWebviewEventDelegate(handle: extHostProtocol.WebviewPanelHandle, input: WebviewInput) { - input.webview.onDidClickLink((uri: URI) => this.onDidClickLink(handle, uri)); - input.webview.onMessage((message: any) => this._proxy.$onMessage(handle, message)); + const disposables = new DisposableStore(); + + disposables.add(input.webview.onDidClickLink((uri) => this.onDidClickLink(handle, uri))); + disposables.add(input.webview.onMessage((message: any) => { this._proxy.$onMessage(handle, message); })); + disposables.add(input.webview.onMissingCsp((extension: ExtensionIdentifier) => this._proxy.$onMissingCsp(handle, extension.value))); + input.onDispose(() => { + disposables.dispose(); + }); + input.onDisposeWebview(() => { this._proxy.$onDidDisposeWebviewPanel(handle).finally(() => { this._webviewInputs.delete(handle); }); }); - input.webview.onMissingCsp((extension: ExtensionIdentifier) => this._proxy.$onMissingCsp(handle, extension.value)); } private updateWebviewViewStates() { @@ -361,9 +387,9 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma } } - private onDidClickLink(handle: extHostProtocol.WebviewPanelHandle, link: URI): void { + private onDidClickLink(handle: extHostProtocol.WebviewPanelHandle, link: string): void { const webview = this.getWebviewInput(handle); - if (this.isSupportedLink(webview, link)) { + if (this.isSupportedLink(webview, URI.parse(link))) { this._openerService.open(link, { fromUserGesture: true }); } } diff --git a/src/vs/workbench/api/browser/mainThreadWindow.ts b/src/vs/workbench/api/browser/mainThreadWindow.ts index ca8b2d288046..3a3f769bfb25 100644 --- a/src/vs/workbench/api/browser/mainThreadWindow.ts +++ b/src/vs/workbench/api/browser/mainThreadWindow.ts @@ -42,9 +42,17 @@ export class MainThreadWindow implements MainThreadWindowShape { return Promise.resolve(this.hostService.hasFocus); } - async $openUri(uriComponents: UriComponents, options: IOpenUriOptions): Promise { + async $openUri(uriComponents: UriComponents, uriString: string | undefined, options: IOpenUriOptions): Promise { const uri = URI.from(uriComponents); - return this.openerService.open(uri, { openExternal: true, allowTunneling: options.allowTunneling }); + let target: URI | string; + if (uriString && URI.parse(uriString).toString() === uri.toString()) { + // called with string and no transformation happened -> keep string + target = uriString; + } else { + // called with URI or transformed -> use uri + target = uri; + } + return this.openerService.open(target, { openExternal: true, allowTunneling: options.allowTunneling }); } async $asExternalUri(uriComponents: UriComponents, options: IOpenUriOptions): Promise { diff --git a/src/vs/workbench/api/browser/mainThreadWorkspace.ts b/src/vs/workbench/api/browser/mainThreadWorkspace.ts index 2d4e5d31aa21..b09c9c1f430b 100644 --- a/src/vs/workbench/api/browser/mainThreadWorkspace.ts +++ b/src/vs/workbench/api/browser/mainThreadWorkspace.ts @@ -15,11 +15,11 @@ import { IFileMatch, IPatternInfo, ISearchProgressItem, ISearchService } from 'v import { IWorkspaceContextService, WorkbenchState, IWorkspace } from 'vs/platform/workspace/common/workspace'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { ITextQueryBuilderOptions, QueryBuilder } from 'vs/workbench/contrib/search/common/queryBuilder'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing'; import { ExtHostContext, ExtHostWorkspaceShape, IExtHostContext, MainContext, MainThreadWorkspaceShape, IWorkspaceData, ITextSearchComplete } from '../common/extHost.protocol'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { isEqualOrParent } from 'vs/base/common/resources'; +import { isUntitledWorkspace } from 'vs/platform/workspaces/common/workspaces'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { withNullAsUndefined } from 'vs/base/common/types'; import { IFileService } from 'vs/platform/files/common/files'; @@ -37,7 +37,7 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { extHostContext: IExtHostContext, @ISearchService private readonly _searchService: ISearchService, @IWorkspaceContextService private readonly _contextService: IWorkspaceContextService, - @ITextFileService private readonly _textFileService: ITextFileService, + @IEditorService private readonly _editorService: IEditorService, @IWorkspaceEditingService private readonly _workspaceEditingService: IWorkspaceEditingService, @INotificationService private readonly _notificationService: INotificationService, @IRequestService private readonly _requestService: IRequestService, @@ -121,7 +121,7 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { } return { configuration: workspace.configuration || undefined, - isUntitled: workspace.configuration ? isEqualOrParent(workspace.configuration, this._environmentService.untitledWorkspacesHome) : false, + isUntitled: workspace.configuration ? isUntitledWorkspace(workspace.configuration, this._environmentService) : false, folders: workspace.folders, id: workspace.id, name: this._labelService.getWorkspaceLabel(workspace) @@ -155,13 +155,14 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { if (!isPromiseCanceledError(err)) { return Promise.reject(err); } - return undefined; + return null; }); } - $startTextSearch(pattern: IPatternInfo, options: ITextQueryBuilderOptions, requestId: number, token: CancellationToken): Promise { + $startTextSearch(pattern: IPatternInfo, _folder: UriComponents | null, options: ITextQueryBuilderOptions, requestId: number, token: CancellationToken): Promise { + const folder = URI.revive(_folder); const workspace = this._contextService.getWorkspace(); - const folders = workspace.folders.map(folder => folder.uri); + const folders = folder ? [folder] : workspace.folders.map(folder => folder.uri); const query = this._queryBuilder.text(pattern, folders, options); query._reason = 'startTextSearch'; @@ -181,7 +182,7 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { return Promise.reject(err); } - return undefined; + return null; }); return search; @@ -198,23 +199,21 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { return this._searchService.fileSearch(query, token).then( result => { - return result.limitHit; + return !!result.limitHit; }, err => { if (!isPromiseCanceledError(err)) { return Promise.reject(err); } - return undefined; + return false; }); } // --- save & edit resources --- $saveAll(includeUntitled?: boolean): Promise { - return this._textFileService.saveAll(includeUntitled).then(result => { - return result.results.every(each => each.success === true); - }); + return this._editorService.saveAll({ includeUntitled }); } $resolveProxy(url: string): Promise { diff --git a/src/vs/workbench/api/browser/viewsExtensionPoint.ts b/src/vs/workbench/api/browser/viewsExtensionPoint.ts index 62a991bedee2..2eeba105ba25 100644 --- a/src/vs/workbench/api/browser/viewsExtensionPoint.ts +++ b/src/vs/workbench/api/browser/viewsExtensionPoint.ts @@ -9,7 +9,7 @@ import { IJSONSchema } from 'vs/base/common/jsonSchema'; import * as resources from 'vs/base/common/resources'; import { ExtensionMessageCollector, ExtensionsRegistry, IExtensionPoint, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { ViewContainer, IViewsRegistry, ITreeViewDescriptor, IViewContainersRegistry, Extensions as ViewContainerExtensions, TEST_VIEW_CONTAINER_ID, IViewDescriptor } from 'vs/workbench/common/views'; -import { CustomTreeViewPanel, CustomTreeView } from 'vs/workbench/browser/parts/views/customView'; +import { CustomTreeViewPane, CustomTreeView } from 'vs/workbench/browser/parts/views/customView'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { coalesce, } from 'vs/base/common/arrays'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; @@ -37,7 +37,6 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; -import { createCSSRule, asCSSUrl } from 'vs/base/browser/dom'; export interface IUserFriendlyViewsContainerDescriptor { id: string; @@ -254,10 +253,9 @@ class ViewsExtensionHandler implements IWorkbenchContribution { private registerTestViewContainer(): void { const title = localize('test', "Test"); - const cssClass = `extensionViewlet-test`; const icon = URI.parse(require.toUrl('./media/test.svg')); - this.registerCustomViewContainer(TEST_VIEW_CONTAINER_ID, title, icon, TEST_VIEW_CONTAINER_ORDER, cssClass, undefined); + this.registerCustomViewContainer(TEST_VIEW_CONTAINER_ID, title, icon, TEST_VIEW_CONTAINER_ORDER, undefined); } private isValidViewsContainer(viewsContainersDescriptors: IUserFriendlyViewsContainerDescriptor[], collector: ExtensionMessageCollector): boolean { @@ -290,10 +288,9 @@ class ViewsExtensionHandler implements IWorkbenchContribution { private registerCustomViewContainers(containers: IUserFriendlyViewsContainerDescriptor[], extension: IExtensionDescription, order: number, existingViewContainers: ViewContainer[]): number { containers.forEach(descriptor => { - const cssClass = `extensionViewlet-${descriptor.id}`; const icon = resources.joinPath(extension.extensionLocation, descriptor.icon); const id = `workbench.view.extension.${descriptor.id}`; - const viewContainer = this.registerCustomViewContainer(id, descriptor.title, icon, order++, cssClass, extension.identifier); + const viewContainer = this.registerCustomViewContainer(id, descriptor.title, icon, order++, extension.identifier); // Move those views that belongs to this container if (existingViewContainers.length) { @@ -311,7 +308,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution { return order; } - private registerCustomViewContainer(id: string, title: string, icon: URI, order: number, cssClass: string, extensionId: ExtensionIdentifier | undefined): ViewContainer { + private registerCustomViewContainer(id: string, title: string, icon: URI, order: number, extensionId: ExtensionIdentifier | undefined): ViewContainer { let viewContainer = this.viewContainersRegistry.get(id); if (!viewContainer) { @@ -335,11 +332,11 @@ class ViewsExtensionHandler implements IWorkbenchContribution { super(id, `${id}.state`, true, configurationService, layoutService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService); } } - const viewletDescriptor = new ViewletDescriptor( + const viewletDescriptor = ViewletDescriptor.create( CustomViewlet, id, title, - cssClass, + undefined, order, icon ); @@ -359,14 +356,10 @@ class ViewsExtensionHandler implements IWorkbenchContribution { } const registry = Registry.as(ActionExtensions.WorkbenchActions); registry.registerWorkbenchAction( - new SyncActionDescriptor(OpenCustomViewletAction, id, localize('showViewlet', "Show {0}", title)), + SyncActionDescriptor.create(OpenCustomViewletAction, id, localize('showViewlet', "Show {0}", title)), `View: Show ${title}`, localize('view', "View") ); - - // Generate CSS to show the icon in the activity bar - const iconClass = `.monaco-workbench .activitybar .monaco-action-bar .action-label.${cssClass}`; - createCSSRule(iconClass, `-webkit-mask: ${asCSSUrl(icon)} no-repeat 50% 50%; -webkit-mask-size: 24px;`); } return viewContainer; @@ -429,7 +422,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution { const viewDescriptor = { id: item.id, name: item.name, - ctorDescriptor: { ctor: CustomTreeViewPanel }, + ctorDescriptor: { ctor: CustomTreeViewPane }, when: ContextKeyExpr.deserialize(item.when), canToggleVisibility: true, collapsed: this.showCollapsed(container), diff --git a/src/vs/workbench/api/common/configurationExtensionPoint.ts b/src/vs/workbench/api/common/configurationExtensionPoint.ts index e67016a30f9d..09b0214b05ab 100644 --- a/src/vs/workbench/api/common/configurationExtensionPoint.ts +++ b/src/vs/workbench/api/common/configurationExtensionPoint.ts @@ -309,7 +309,9 @@ jsonRegistry.registerSchema('vscode://schemas/workspaceConfig', { $ref: 'vscode://schemas/extensions' }, 'remoteAuthority': { - type: 'string' + type: 'string', + doNotSuggest: true, + description: nls.localize('workspaceConfig.remoteAuthority', "The remote server where the workspace is located. Only used by unsaved remote workspaces."), } }, additionalProperties: false, diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 5168fdecd9cf..8cc5597c08b9 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -106,7 +106,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const extHostOutputService = rpcProtocol.set(ExtHostContext.ExtHostOutputService, accessor.get(IExtHostOutputService)); // manually create and register addressable instances - const extHostWebviews = rpcProtocol.set(ExtHostContext.ExtHostWebviews, new ExtHostWebviews(rpcProtocol, initData.environment, extHostWorkspace)); + const extHostWebviews = rpcProtocol.set(ExtHostContext.ExtHostWebviews, new ExtHostWebviews(rpcProtocol, initData.environment, extHostWorkspace, extHostLogService)); const extHostUrls = rpcProtocol.set(ExtHostContext.ExtHostUrls, new ExtHostUrls(rpcProtocol)); const extHostDocuments = rpcProtocol.set(ExtHostContext.ExtHostDocuments, new ExtHostDocuments(rpcProtocol, extHostDocumentsAndEditors)); const extHostDocumentContentProviders = rpcProtocol.set(ExtHostContext.ExtHostDocumentContentProviders, new ExtHostDocumentContentProvider(rpcProtocol, extHostDocumentsAndEditors, extHostLogService)); @@ -114,10 +114,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const extHostEditors = rpcProtocol.set(ExtHostContext.ExtHostEditors, new ExtHostEditors(rpcProtocol, extHostDocumentsAndEditors)); const extHostTreeViews = rpcProtocol.set(ExtHostContext.ExtHostTreeViews, new ExtHostTreeViews(rpcProtocol.getProxy(MainContext.MainThreadTreeViews), extHostCommands, extHostLogService)); const extHostEditorInsets = rpcProtocol.set(ExtHostContext.ExtHostEditorInsets, new ExtHostEditorInsets(rpcProtocol.getProxy(MainContext.MainThreadEditorInsets), extHostEditors, initData.environment)); - const extHostDiagnostics = rpcProtocol.set(ExtHostContext.ExtHostDiagnostics, new ExtHostDiagnostics(rpcProtocol)); + const extHostDiagnostics = rpcProtocol.set(ExtHostContext.ExtHostDiagnostics, new ExtHostDiagnostics(rpcProtocol, extHostLogService)); const extHostLanguageFeatures = rpcProtocol.set(ExtHostContext.ExtHostLanguageFeatures, new ExtHostLanguageFeatures(rpcProtocol, uriTransformer, extHostDocuments, extHostCommands, extHostDiagnostics, extHostLogService)); const extHostFileSystem = rpcProtocol.set(ExtHostContext.ExtHostFileSystem, new ExtHostFileSystem(rpcProtocol, extHostLanguageFeatures)); - const extHostFileSystemEvent = rpcProtocol.set(ExtHostContext.ExtHostFileSystemEventService, new ExtHostFileSystemEventService(rpcProtocol, extHostDocumentsAndEditors)); + const extHostFileSystemEvent = rpcProtocol.set(ExtHostContext.ExtHostFileSystemEventService, new ExtHostFileSystemEventService(rpcProtocol, extHostLogService, extHostDocumentsAndEditors)); const extHostQuickOpen = rpcProtocol.set(ExtHostContext.ExtHostQuickOpen, new ExtHostQuickOpen(rpcProtocol, extHostWorkspace, extHostCommands)); const extHostSCM = rpcProtocol.set(ExtHostContext.ExtHostSCM, new ExtHostSCM(rpcProtocol, extHostCommands, extHostLogService)); const extHostComment = rpcProtocol.set(ExtHostContext.ExtHostComments, new ExtHostComments(rpcProtocol, extHostCommands, extHostDocuments)); @@ -133,7 +133,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I // Other instances const extHostClipboard = new ExtHostClipboard(rpcProtocol); - const extHostMessageService = new ExtHostMessageService(rpcProtocol); + const extHostMessageService = new ExtHostMessageService(rpcProtocol, extHostLogService); const extHostDialogs = new ExtHostDialogs(rpcProtocol); const extHostStatusBar = new ExtHostStatusBar(rpcProtocol); const extHostLanguages = new ExtHostLanguages(rpcProtocol, extHostDocuments); @@ -152,7 +152,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I let done = (!extension.isUnderDevelopment); function informOnce(selector: vscode.DocumentSelector) { if (!done) { - console.info(`Extension '${extension.identifier.value}' uses a document selector without scheme. Learn more about this: https://go.microsoft.com/fwlink/?linkid=872305`); + extHostLogService.info(`Extension '${extension.identifier.value}' uses a document selector without scheme. Learn more about this: https://go.microsoft.com/fwlink/?linkid=872305`); done = true; } } @@ -183,20 +183,19 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return extHostCommands.registerCommand(true, id, (...args: any[]): any => { const activeTextEditor = extHostEditors.getActiveTextEditor(); if (!activeTextEditor) { - console.warn('Cannot execute ' + id + ' because there is no active text editor.'); + extHostLogService.warn('Cannot execute ' + id + ' because there is no active text editor.'); return undefined; } return activeTextEditor.edit((edit: vscode.TextEditorEdit) => { - args.unshift(activeTextEditor, edit); - callback.apply(thisArg, args); + callback.apply(thisArg, [activeTextEditor, edit, ...args]); }).then((result) => { if (!result) { - console.warn('Edits from command ' + id + ' were not applied.'); + extHostLogService.warn('Edits from command ' + id + ' were not applied.'); } }, (err) => { - console.warn('An error occurred while running command ' + id, err); + extHostLogService.warn('An error occurred while running command ' + id, err); }); }); }, @@ -205,7 +204,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return extHostCommands.registerCommand(true, id, async (...args: any[]): Promise => { const activeTextEditor = extHostEditors.getActiveTextEditor(); if (!activeTextEditor) { - console.warn('Cannot execute ' + id + ' because there is no active text editor.'); + extHostLogService.warn('Cannot execute ' + id + ' because there is no active text editor.'); return undefined; } @@ -231,7 +230,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I get uriScheme() { return initData.environment.appUriScheme; }, createAppUri(options?) { checkProposedApiEnabled(extension); - return extHostUrls.createAppUri(extension.identifier, options); + return extHostUrls.proposedCreateAppUri(extension.identifier, options); }, get logLevel() { checkProposedApiEnabled(extension); @@ -251,6 +250,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return extHostWindow.openUri(uri, { allowTunneling: !!initData.remote.isRemote }); }, asExternalUri(uri: URI) { + if (uri.scheme === initData.environment.appUriScheme) { + return extHostUrls.createAppUri(uri); + } + return extHostWindow.asExternalUri(uri, { allowTunneling: !!initData.remote.isRemote }); }, get remoteName() { @@ -351,6 +354,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I registerOnTypeFormattingEditProvider(selector: vscode.DocumentSelector, provider: vscode.OnTypeFormattingEditProvider, firstTriggerCharacter: string, ...moreTriggerCharacters: string[]): vscode.Disposable { return extHostLanguageFeatures.registerOnTypeFormattingEditProvider(extension, checkSelector(selector), provider, [firstTriggerCharacter].concat(moreTriggerCharacters)); }, + registerSemanticTokensProvider(selector: vscode.DocumentSelector, provider: vscode.SemanticTokensProvider, legend: vscode.SemanticTokensLegend): vscode.Disposable { + checkProposedApiEnabled(extension); + return extHostLanguageFeatures.registerSemanticTokensProvider(extension, checkSelector(selector), provider, legend); + }, registerSignatureHelpProvider(selector: vscode.DocumentSelector, provider: vscode.SignatureHelpProvider, firstItem?: string | vscode.SignatureHelpProviderMetadata, ...remaining: string[]): vscode.Disposable { if (typeof firstItem === 'object') { return extHostLanguageFeatures.registerSignatureHelpProvider(extension, checkSelector(selector), provider, firstItem); @@ -372,8 +379,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I registerSelectionRangeProvider(selector: vscode.DocumentSelector, provider: vscode.SelectionRangeProvider): vscode.Disposable { return extHostLanguageFeatures.registerSelectionRangeProvider(extension, selector, provider); }, - registerCallHierarchyProvider(selector: vscode.DocumentSelector, provider: vscode.CallHierarchyItemProvider): vscode.Disposable { - checkProposedApiEnabled(extension); + registerCallHierarchyProvider(selector: vscode.DocumentSelector, provider: vscode.CallHierarchyProvider): vscode.Disposable { return extHostLanguageFeatures.registerCallHierarchyProvider(extension, selector, provider); }, setLanguageConfiguration: (language: string, configuration: vscode.LanguageConfiguration): vscode.Disposable => { @@ -698,11 +704,27 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I checkProposedApiEnabled(extension); return extHostLabelService.$registerResourceLabelFormatter(formatter); }, - onDidRenameFile: (listener: (e: vscode.FileRenameEvent) => any, thisArg?: any, disposables?: vscode.Disposable[]) => { + onDidCreateFiles: (listener, thisArg, disposables) => { + checkProposedApiEnabled(extension); + return extHostFileSystemEvent.onDidCreateFile(listener, thisArg, disposables); + }, + onDidDeleteFiles: (listener, thisArg, disposables) => { + checkProposedApiEnabled(extension); + return extHostFileSystemEvent.onDidDeleteFile(listener, thisArg, disposables); + }, + onDidRenameFiles: (listener, thisArg, disposables) => { checkProposedApiEnabled(extension); return extHostFileSystemEvent.onDidRenameFile(listener, thisArg, disposables); }, - onWillRenameFile: (listener: (e: vscode.FileWillRenameEvent) => any, thisArg?: any, disposables?: vscode.Disposable[]) => { + onWillCreateFiles: (listener: (e: vscode.FileWillCreateEvent) => any, thisArg?: any, disposables?: vscode.Disposable[]) => { + checkProposedApiEnabled(extension); + return extHostFileSystemEvent.getOnWillCreateFileEvent(extension)(listener, thisArg, disposables); + }, + onWillDeleteFiles: (listener: (e: vscode.FileWillDeleteEvent) => any, thisArg?: any, disposables?: vscode.Disposable[]) => { + checkProposedApiEnabled(extension); + return extHostFileSystemEvent.getOnWillDeleteFileEvent(extension)(listener, thisArg, disposables); + }, + onWillRenameFiles: (listener: (e: vscode.FileWillRenameEvent) => any, thisArg?: any, disposables?: vscode.Disposable[]) => { checkProposedApiEnabled(extension); return extHostFileSystemEvent.getOnWillRenameFileEvent(extension)(listener, thisArg, disposables); } @@ -770,6 +792,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I }, removeBreakpoints(breakpoints: vscode.Breakpoint[]) { return undefined; + }, + asDebugSourceUri(source: vscode.DebugProtocolSource, session?: vscode.DebugSession): vscode.Uri { + return undefined; } }; @@ -836,6 +861,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I ConfigurationTarget: extHostTypes.ConfigurationTarget, DebugAdapterExecutable: extHostTypes.DebugAdapterExecutable, DebugAdapterServer: extHostTypes.DebugAdapterServer, + DebugAdapterInlineImplementation: extHostTypes.DebugAdapterInlineImplementation, DecorationRangeBehavior: extHostTypes.DecorationRangeBehavior, Diagnostic: extHostTypes.Diagnostic, DiagnosticRelatedInformation: extHostTypes.DiagnosticRelatedInformation, @@ -850,6 +876,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I EventEmitter: Emitter, ExtensionKind: extHostTypes.ExtensionKind, CustomExecution: extHostTypes.CustomExecution, + CustomExecution2: extHostTypes.CustomExecution, FileChangeType: extHostTypes.FileChangeType, FileSystemError: extHostTypes.FileSystemError, FileType: files.FileType, @@ -871,6 +898,11 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I RelativePattern: extHostTypes.RelativePattern, ResolvedAuthority: extHostTypes.ResolvedAuthority, RemoteAuthorityResolverError: extHostTypes.RemoteAuthorityResolverError, + SemanticTokensLegend: extHostTypes.SemanticTokensLegend, + SemanticTokensBuilder: extHostTypes.SemanticTokensBuilder, + SemanticTokens: extHostTypes.SemanticTokens, + SemanticTokensEdits: extHostTypes.SemanticTokensEdits, + SemanticTokensEdit: extHostTypes.SemanticTokensEdit, Selection: extHostTypes.Selection, SelectionRange: extHostTypes.SelectionRange, ShellExecution: extHostTypes.ShellExecution, diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index fe402c9bced3..620fb2fc68ed 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -45,7 +45,7 @@ import { ITerminalDimensions, IShellLaunchConfig } from 'vs/workbench/contrib/te import { ExtensionActivationError } from 'vs/workbench/services/extensions/common/extensions'; import { createExtHostContextProxyIdentifier as createExtId, createMainContextProxyIdentifier as createMainId, IRPCProtocol } from 'vs/workbench/services/extensions/common/proxyIdentifier'; import * as search from 'vs/workbench/services/search/common/search'; -import { SaveReason } from 'vs/workbench/services/textfile/common/textfiles'; +import { SaveReason } from 'vs/workbench/common/editor'; import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtensionActivator'; // {{SQL CARBON EDIT}} @@ -74,7 +74,7 @@ export interface IStaticWorkspaceData { } export interface IWorkspaceData extends IStaticWorkspaceData { - folders: { uri: UriComponents, name: string, index: number }[]; + folders: { uri: UriComponents, name: string, index: number; }[]; } export interface IInitData { @@ -101,7 +101,7 @@ export interface IConfigurationInitData extends IConfigurationData { export interface IWorkspaceConfigurationChangeEventData { changedConfiguration: IConfigurationModel; - changedConfigurationByResource: { [folder: string]: IConfigurationModel }; + changedConfigurationByResource: { [folder: string]: IConfigurationModel; }; } export interface IExtHostContext extends IRPCProtocol { @@ -135,12 +135,20 @@ export interface CommentProviderFeatures { reactionHandler?: boolean; } +export type CommentThreadChanges = Partial<{ + range: IRange, + label: string, + contextValue: string, + comments: modes.Comment[], + collapseState: modes.CommentThreadCollapsibleState; +}>; + export interface MainThreadCommentsShape extends IDisposable { $registerCommentController(handle: number, id: string, label: string): void; $unregisterCommentController(handle: number): void; $updateCommentControllerFeatures(handle: number, features: CommentProviderFeatures): void; $createCommentThread(handle: number, commentThreadHandle: number, threadId: string, resource: UriComponents, range: IRange, extensionId: ExtensionIdentifier): modes.CommentThread | undefined; - $updateCommentThread(handle: number, commentThreadHandle: number, threadId: string, resource: UriComponents, range: IRange, label: string | undefined, contextValue: string | undefined, comments: modes.Comment[], collapseState: modes.CommentThreadCollapsibleState): void; + $updateCommentThread(handle: number, commentThreadHandle: number, threadId: string, resource: UriComponents, changes: CommentThreadChanges): void; $deleteCommentThread(handle: number, commentThreadHandle: number): void; $onDidCommentThreadsChange(handle: number, event: modes.CommentThreadChangedEvent): void; } @@ -161,13 +169,13 @@ export interface MainThreadDialogOpenOptions { canSelectFiles?: boolean; canSelectFolders?: boolean; canSelectMany?: boolean; - filters?: { [name: string]: string[] }; + filters?: { [name: string]: string[]; }; } export interface MainThreadDialogSaveOptions { defaultUri?: UriComponents; saveLabel?: string; - filters?: { [name: string]: string[] }; + filters?: { [name: string]: string[]; }; } export interface MainThreadDiaglogsShape extends IDisposable { @@ -250,8 +258,8 @@ export interface MainThreadTextEditorsShape extends IDisposable { } export interface MainThreadTreeViewsShape extends IDisposable { - $registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean, canSelectMany: boolean }): void; - $refresh(treeViewId: string, itemsToRefresh?: { [treeItemHandle: string]: ITreeItem }): Promise; + $registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean, canSelectMany: boolean; }): void; + $refresh(treeViewId: string, itemsToRefresh?: { [treeItemHandle: string]: ITreeItem; }): Promise; $reveal(treeViewId: string, treeItem: ITreeItem, parentChain: ITreeItem[], options: IRevealOptions): Promise; $setMessage(treeViewId: string, message: string): void; $setTitle(treeViewId: string, title: string): void; @@ -274,7 +282,7 @@ export interface MainThreadKeytarShape extends IDisposable { $setPassword(service: string, account: string, password: string): Promise; $deletePassword(service: string, account: string): Promise; $findPassword(service: string): Promise; - $findCredentials(service: string): Promise>; + $findCredentials(service: string): Promise>; } export interface IRegExpDto { @@ -317,7 +325,7 @@ export interface ILanguageConfigurationDto { }; } -export type GlobPattern = string | { base: string; pattern: string }; +export type GlobPattern = string | { base: string; pattern: string; }; export interface IDocumentFilterDto { $serialized: true; @@ -350,6 +358,7 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable { $registerOnTypeFormattingSupport(handle: number, selector: IDocumentFilterDto[], autoFormatTriggerCharacters: string[], extensionId: ExtensionIdentifier): void; $registerNavigateTypeSupport(handle: number): void; $registerRenameSupport(handle: number, selector: IDocumentFilterDto[], supportsResolveInitialValues: boolean): void; + $registerSemanticTokensProvider(handle: number, selector: IDocumentFilterDto[], legend: modes.SemanticTokensLegend): void; $registerSuggestSupport(handle: number, selector: IDocumentFilterDto[], triggerCharacters: string[], supportsResolveDetails: boolean, extensionId: ExtensionIdentifier): void; $registerSignatureHelpProvider(handle: number, selector: IDocumentFilterDto[], metadata: ISignatureHelpProviderMetadataDto): void; $registerDocumentLinkProvider(handle: number, selector: IDocumentFilterDto[], supportsResolve: boolean): void; @@ -396,7 +405,7 @@ export interface TerminalLaunchConfig { shellPath?: string; shellArgs?: string[] | string; cwd?: string | UriComponents; - env?: { [key: string]: string | null }; + env?: { [key: string]: string | null; }; waitOnExit?: boolean; strictEnv?: boolean; hideFromUser?: boolean; @@ -404,7 +413,7 @@ export interface TerminalLaunchConfig { } export interface MainThreadTerminalServiceShape extends IDisposable { - $createTerminal(config: TerminalLaunchConfig): Promise<{ id: number, name: string }>; + $createTerminal(config: TerminalLaunchConfig): Promise<{ id: number, name: string; }>; $dispose(terminalId: number): void; $hide(terminalId: number): void; $sendText(terminalId: number, text: string, addNewLine: boolean): void; @@ -416,7 +425,7 @@ export interface MainThreadTerminalServiceShape extends IDisposable { $sendProcessTitle(terminalId: number, title: string): void; $sendProcessData(terminalId: number, data: string): void; $sendProcessReady(terminalId: number, pid: number, cwd: string): void; - $sendProcessExit(terminalId: number, exitCode: number): void; + $sendProcessExit(terminalId: number, exitCode: number | undefined): void; $sendProcessInitialCwd(terminalId: number, cwd: string): void; $sendProcessCwd(terminalId: number, initialCwd: string): void; $sendOverrideDimensions(terminalId: number, dimensions: ITerminalDimensions | undefined): void; @@ -471,6 +480,8 @@ export interface TransferQuickPick extends BaseTransferQuickInput { matchOnDescription?: boolean; matchOnDetail?: boolean; + + sortByLabel?: boolean; } export interface TransferInputBox extends BaseTransferQuickInput { @@ -554,8 +565,7 @@ export interface MainThreadWebviewsShape extends IDisposable { $disposeWebview(handle: WebviewPanelHandle): void; $reveal(handle: WebviewPanelHandle, showOptions: WebviewPanelShowOptions): void; $setTitle(handle: WebviewPanelHandle, value: string): void; - $setState(handle: WebviewPanelHandle, state: modes.WebviewContentState): void; - $setIconPath(handle: WebviewPanelHandle, value: { light: UriComponents, dark: UriComponents } | undefined): void; + $setIconPath(handle: WebviewPanelHandle, value: { light: UriComponents, dark: UriComponents; } | undefined): void; $setHtml(handle: WebviewPanelHandle, value: string): void; $setOptions(handle: WebviewPanelHandle, options: modes.IWebviewOptions): void; @@ -567,6 +577,8 @@ export interface MainThreadWebviewsShape extends IDisposable { $registerEditorProvider(extension: WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions): void; $unregisterEditorProvider(viewType: string): void; + + $onEdit(handle: WebviewPanelHandle, editJson: any): void; } export interface WebviewPanelViewStateData { @@ -582,15 +594,22 @@ export interface ExtHostWebviewsShape { $onMissingCsp(handle: WebviewPanelHandle, extensionId: string): void; $onDidChangeWebviewPanelViewStates(newState: WebviewPanelViewStateData): void; $onDidDisposeWebviewPanel(handle: WebviewPanelHandle): Promise; + $deserializeWebviewPanel(newWebviewHandle: WebviewPanelHandle, viewType: string, title: string, state: any, position: EditorViewColumn, options: modes.IWebviewOptions & modes.IWebviewPanelOptions): Promise; - $resolveWebviewEditor(resource: UriComponents, newWebviewHandle: WebviewPanelHandle, viewType: string, title: string, position: EditorViewColumn, options: modes.IWebviewOptions & modes.IWebviewPanelOptions): Promise; - $save(handle: WebviewPanelHandle): Promise; + $resolveWebviewEditor(input: { resource: UriComponents, edits: readonly any[] }, newWebviewHandle: WebviewPanelHandle, viewType: string, title: string, position: EditorViewColumn, options: modes.IWebviewOptions & modes.IWebviewPanelOptions): Promise; + + $undoEdits(handle: WebviewPanelHandle, edits: readonly any[]): void; + $applyEdits(handle: WebviewPanelHandle, edits: readonly any[]): void; + + $onSave(handle: WebviewPanelHandle): Promise; + $onSaveAs(handle: WebviewPanelHandle, resource: UriComponents, targetResource: UriComponents): Promise; } export interface MainThreadUrlsShape extends IDisposable { $registerUriHandler(handle: number, extensionId: ExtensionIdentifier): Promise; $unregisterUriHandler(handle: number): Promise; - $createAppUri(extensionId: ExtensionIdentifier, options?: { payload?: Partial }): Promise; + $createAppUri(uri: UriComponents): Promise; + $proposedCreateAppUri(extensionId: ExtensionIdentifier, options?: { payload?: Partial; }): Promise; } export interface ExtHostUrlsShape { @@ -603,10 +622,10 @@ export interface ITextSearchComplete { export interface MainThreadWorkspaceShape extends IDisposable { $startFileSearch(includePattern: string | null, includeFolder: UriComponents | null, excludePatternOrDisregardExcludes: string | false | null, maxResults: number | null, token: CancellationToken): Promise; - $startTextSearch(query: search.IPatternInfo, options: ITextQueryBuilderOptions, requestId: number, token: CancellationToken): Promise; + $startTextSearch(query: search.IPatternInfo, folder: UriComponents | null, options: ITextQueryBuilderOptions, requestId: number, token: CancellationToken): Promise; $checkExists(folders: UriComponents[], includes: string[], token: CancellationToken): Promise; $saveAll(includeUntitled?: boolean): Promise; - $updateWorkspaceFolders(extensionName: string, index: number, deleteCount: number, workspaceFoldersToAdd: { uri: UriComponents, name?: string }[]): Promise; + $updateWorkspaceFolders(extensionName: string, index: number, deleteCount: number, workspaceFoldersToAdd: { uri: UriComponents, name?: string; }[]): Promise; $resolveProxy(url: string): Promise; } @@ -753,7 +772,7 @@ export interface IOpenUriOptions { export interface MainThreadWindowShape extends IDisposable { $getWindowVisibility(): Promise; - $openUri(uri: UriComponents, options: IOpenUriOptions): Promise; + $openUri(uri: UriComponents, uriString: string | undefined, options: IOpenUriOptions): Promise; $asExternalUri(uri: UriComponents, options: IOpenUriOptions): Promise; } @@ -761,7 +780,7 @@ export interface MainThreadWindowShape extends IDisposable { export interface ExtHostCommandsShape { $executeContributedCommand(id: string, ...args: any[]): Promise; - $getContributedCommandHandlerDescriptions(): Promise<{ [id: string]: string | ICommandHandlerDescription }>; + $getContributedCommandHandlerDescriptions(): Promise<{ [id: string]: string | ICommandHandlerDescription; }>; } export interface ExtHostConfigurationShape { @@ -896,7 +915,7 @@ export interface ExtHostExtensionServiceShape { $startExtensionHost(enabledExtensionIds: ExtensionIdentifier[]): Promise; $activateByEvent(activationEvent: string): Promise; $activate(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise; - $setRemoteEnvironment(env: { [key: string]: string | null }): Promise; + $setRemoteEnvironment(env: { [key: string]: string | null; }): Promise; $deltaExtensions(toAdd: IExtensionDescription[], toRemove: ExtensionIdentifier[]): Promise; @@ -910,10 +929,11 @@ export interface FileSystemEvents { changed: UriComponents[]; deleted: UriComponents[]; } + export interface ExtHostFileSystemEventServiceShape { $onFileEvent(events: FileSystemEvents): void; - $onFileRename(oldUri: UriComponents, newUri: UriComponents): void; - $onWillRename(oldUri: UriComponents, newUri: UriComponents): Promise; + $onWillRunFileOperation(operation: files.FileOperation, target: UriComponents, source: UriComponents | undefined, timeout: number, token: CancellationToken): Promise; + $onDidRunFileOperation(operation: files.FileOperation, target: UriComponents, source: UriComponents | undefined): void; } export interface ObjectIdentifier { @@ -975,7 +995,7 @@ export interface ISuggestDataDto { [ISuggestDataDtoField.preselect]?: boolean; [ISuggestDataDtoField.insertText]?: string; [ISuggestDataDtoField.insertTextRules]?: modes.CompletionItemInsertTextRule; - [ISuggestDataDtoField.range]?: IRange; + [ISuggestDataDtoField.range]?: IRange | { insert: IRange, replace: IRange; }; [ISuggestDataDtoField.commitCharacters]?: string[]; [ISuggestDataDtoField.additionalTextEdits]?: ISingleEditOperation[]; [ISuggestDataDtoField.command]?: modes.Command; @@ -986,7 +1006,7 @@ export interface ISuggestDataDto { export interface ISuggestResultDto { x?: number; - a: IRange; + a: { insert: IRange, replace: IRange; }; b: ISuggestDataDto[]; c?: boolean; } @@ -1075,6 +1095,7 @@ export interface ICodeActionDto { command?: ICommandDto; kind?: string; isPreferred?: boolean; + disabled?: string; } export interface ICodeActionListDto { @@ -1109,6 +1130,8 @@ export interface ICodeLensDto { } export interface ICallHierarchyItemDto { + _sessionId: string; + _itemId: string; kind: modes.SymbolKind; name: string; detail?: string; @@ -1117,6 +1140,16 @@ export interface ICallHierarchyItemDto { selectionRange: IRange; } +export interface IIncomingCallDto { + from: ICallHierarchyItemDto; + fromRanges: IRange[]; +} + +export interface IOutgoingCallDto { + fromRanges: IRange[]; + to: ICallHierarchyItemDto; +} + export interface ExtHostLanguageFeaturesShape { $provideDocumentSymbols(handle: number, resource: UriComponents, token: CancellationToken): Promise; $provideCodeLenses(handle: number, resource: UriComponents, token: CancellationToken): Promise; @@ -1139,6 +1172,8 @@ export interface ExtHostLanguageFeaturesShape { $releaseWorkspaceSymbols(handle: number, id: number): void; $provideRenameEdits(handle: number, resource: UriComponents, position: IPosition, newName: string, token: CancellationToken): Promise; $resolveRenameLocation(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; + $provideSemanticTokens(handle: number, resource: UriComponents, ranges: IRange[] | null, previousResultId: number, token: CancellationToken): Promise; + $releaseSemanticTokens(handle: number, semanticColoringResultId: number): void; $provideCompletionItems(handle: number, resource: UriComponents, position: IPosition, context: modes.CompletionContext, token: CancellationToken): Promise; $resolveCompletionItem(handle: number, resource: UriComponents, position: IPosition, id: ChainedCacheId, token: CancellationToken): Promise; $releaseCompletionItems(handle: number, id: number): void; @@ -1151,8 +1186,10 @@ export interface ExtHostLanguageFeaturesShape { $provideColorPresentations(handle: number, resource: UriComponents, colorInfo: IRawColorInfo, token: CancellationToken): Promise; $provideFoldingRanges(handle: number, resource: UriComponents, context: modes.FoldingContext, token: CancellationToken): Promise; $provideSelectionRanges(handle: number, resource: UriComponents, positions: IPosition[], token: CancellationToken): Promise; - $provideCallHierarchyIncomingCalls(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<[ICallHierarchyItemDto, IRange[]][] | undefined>; - $provideCallHierarchyOutgoingCalls(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<[ICallHierarchyItemDto, IRange[]][] | undefined>; + $prepareCallHierarchy(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; + $provideCallHierarchyIncomingCalls(handle: number, sessionId: string, itemId: string, token: CancellationToken): Promise; + $provideCallHierarchyOutgoingCalls(handle: number, sessionId: string, itemId: string, token: CancellationToken): Promise; + $releaseCallHierarchy(handle: number, sessionId: string): void; } export interface ExtHostQuickOpenShape { @@ -1171,7 +1208,7 @@ export interface IShellLaunchConfigDto { executable?: string; args?: string[] | string; cwd?: string | UriComponents; - env?: { [key: string]: string | null }; + env?: { [key: string]: string | null; }; } export interface IShellDefinitionDto { @@ -1190,8 +1227,8 @@ export interface ITerminalDimensionsDto { } export interface ExtHostTerminalServiceShape { - $acceptTerminalClosed(id: number): void; - $acceptTerminalOpened(id: number, name: string): void; + $acceptTerminalClosed(id: number, exitCode: number | undefined): void; + $acceptTerminalOpened(id: number, name: string, shellLaunchConfig: IShellLaunchConfigDto): void; $acceptActiveTerminalChanged(id: number | null): void; $acceptTerminalProcessId(id: number, processId: number): void; $acceptTerminalProcessData(id: number, data: string): void; @@ -1226,8 +1263,8 @@ export interface ExtHostTaskShape { $onDidStartTaskProcess(value: tasks.TaskProcessStartedDTO): void; $onDidEndTaskProcess(value: tasks.TaskProcessEndedDTO): void; $OnDidEndTask(execution: tasks.TaskExecutionDTO): void; - $resolveVariables(workspaceFolder: UriComponents, toResolve: { process?: { name: string; cwd?: string }, variables: string[] }): Promise<{ process?: string; variables: { [key: string]: string } }>; - $getDefaultShellAndArgs(): Thenable<{ shell: string, args: string[] | string | undefined }>; + $resolveVariables(workspaceFolder: UriComponents, toResolve: { process?: { name: string; cwd?: string; }, variables: string[]; }): Promise<{ process?: string; variables: { [key: string]: string; }; }>; + $getDefaultShellAndArgs(): Thenable<{ shell: string, args: string[] | string | undefined; }>; $jsonTasksSupported(): Thenable; } @@ -1314,7 +1351,7 @@ export interface DecorationRequest { } export type DecorationData = [number, boolean, string, string, ThemeColor]; -export type DecorationReply = { [id: number]: DecorationData }; +export type DecorationReply = { [id: number]: DecorationData; }; export interface ExtHostDecorationsShape { $provideDecorations(requests: DecorationRequest[], token: CancellationToken): Promise; diff --git a/src/vs/workbench/api/common/extHostApiCommands.ts b/src/vs/workbench/api/common/extHostApiCommands.ts index 91b750ebeebf..188180394c3c 100644 --- a/src/vs/workbench/api/common/extHostApiCommands.ts +++ b/src/vs/workbench/api/common/extHostApiCommands.ts @@ -4,11 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from 'vs/base/common/uri'; -import { DisposableStore } from 'vs/base/common/lifecycle'; +import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import * as vscode from 'vscode'; import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters'; import * as types from 'vs/workbench/api/common/extHostTypes'; -import { IRawColorInfo, IWorkspaceEditDto, ICallHierarchyItemDto } from 'vs/workbench/api/common/extHost.protocol'; +import { IRawColorInfo, IWorkspaceEditDto, ICallHierarchyItemDto, IIncomingCallDto, IOutgoingCallDto } from 'vs/workbench/api/common/extHost.protocol'; import { ISingleEditOperation } from 'vs/editor/common/model'; import * as modes from 'vs/editor/common/modes'; import * as search from 'vs/workbench/contrib/search/common/search'; @@ -19,10 +19,97 @@ import { ICommandsExecutor, OpenFolderAPICommand, DiffAPICommand, OpenAPICommand import { EditorGroupLayout } from 'vs/workbench/services/editor/common/editorGroupsService'; import { isFalsyOrEmpty } from 'vs/base/common/arrays'; import { IRange } from 'vs/editor/common/core/range'; +import { IPosition } from 'vs/editor/common/core/position'; + +//#region --- NEW world + +export class ApiCommandArgument { + + static readonly Uri = new ApiCommandArgument('uri', 'Uri of a text document', v => URI.isUri(v), v => v); + static readonly Position = new ApiCommandArgument('position', 'A position in a text document', v => types.Position.isPosition(v), typeConverters.Position.from); + + static readonly CallHierarchyItem = new ApiCommandArgument('item', 'A call hierarchy item', v => v instanceof types.CallHierarchyItem, typeConverters.CallHierarchyItem.to); + + constructor( + readonly name: string, + readonly description: string, + readonly validate: (v: V) => boolean, + readonly convert: (v: V) => O + ) { } +} + +export class ApiCommandResult { + + constructor( + readonly description: string, + readonly convert: (v: V) => O + ) { } +} + +export class ApiCommand { + + constructor( + readonly id: string, + readonly internalId: string, + readonly description: string, + readonly args: ApiCommandArgument[], + readonly result: ApiCommandResult + ) { } + + register(commands: ExtHostCommands): IDisposable { + + return commands.registerCommand(false, this.id, async (...apiArgs) => { + + const internalArgs = this.args.map((arg, i) => { + if (!arg.validate(apiArgs[i])) { + throw new Error(`Invalid argument '${arg.name}' when running '${this.id}', receieved: ${apiArgs[i]}`); + } + return arg.convert(apiArgs[i]); + }); + + const internalResult = await commands.executeCommand(this.internalId, ...internalArgs); + return this.result.convert(internalResult); + }, undefined, this._getCommandHandlerDesc()); + } + + private _getCommandHandlerDesc(): ICommandHandlerDescription { + return { + description: this.description, + args: this.args, + returns: this.result.description + }; + } +} + + +const newCommands: ApiCommand[] = [ + new ApiCommand( + 'vscode.prepareCallHierarchy', '_executePrepareCallHierarchy', 'Prepare call hierarchy at a position inside a document', + [ApiCommandArgument.Uri, ApiCommandArgument.Position], + new ApiCommandResult('A CallHierarchyItem or undefined', v => v.map(typeConverters.CallHierarchyItem.to)) + ), + new ApiCommand( + 'vscode.provideIncomingCalls', '_executeProvideIncomingCalls', 'Compute incoming calls for an item', + [ApiCommandArgument.CallHierarchyItem], + new ApiCommandResult('A CallHierarchyItem or undefined', v => v.map(typeConverters.CallHierarchyIncomingCall.to)) + ), + new ApiCommand( + 'vscode.provideOutgoingCalls', '_executeProvideOutgoingCalls', 'Compute outgoing calls for an item', + [ApiCommandArgument.CallHierarchyItem], + new ApiCommandResult('A CallHierarchyItem or undefined', v => v.map(typeConverters.CallHierarchyOutgoingCall.to)) + ), +]; + + +//#endregion + + +//#region OLD world export class ExtHostApiCommands { static register(commands: ExtHostCommands) { + newCommands.forEach(command => command.register(commands)); return new ExtHostApiCommands(commands).registerCommands(); } @@ -182,22 +269,6 @@ export class ExtHostApiCommands { ], returns: 'A promise that resolves to an array of DocumentLink-instances.' }); - this._register('vscode.executeCallHierarchyProviderIncomingCalls', this._executeCallHierarchyIncomingCallsProvider, { - description: 'Execute call hierarchy provider for incoming calls', - args: [ - { name: 'uri', description: 'Uri of a text document', constraint: URI }, - { name: 'position', description: 'Position in a text document', constraint: types.Position }, - ], - returns: 'A promise that resolves to an array of CallHierarchyIncomingCall-instances.' - }); - this._register('vscode.executeCallHierarchyProviderOutgoingCalls', this._executeCallHierarchyOutgoingCallsProvider, { - description: 'Execute call hierarchy provider for outgoing calls', - args: [ - { name: 'uri', description: 'Uri of a text document', constraint: URI }, - { name: 'position', description: 'Position in a text document', constraint: types.Position }, - ], - returns: 'A promise that resolves to an array of CallHierarchyOutgoingCall-instances.' - }); this._register('vscode.executeDocumentColorProvider', this._executeDocumentColorProvider, { description: 'Execute document color provider.', args: [ @@ -449,7 +520,7 @@ export class ExtHostApiCommands { }); } - private _executeColorPresentationProvider(color: types.Color, context: { uri: URI, range: types.Range }): Promise { + private _executeColorPresentationProvider(color: types.Color, context: { uri: URI, range: types.Range; }): Promise { const args = { resource: context.uri, color: typeConverters.Color.from(color), @@ -573,36 +644,6 @@ export class ExtHostApiCommands { return this._commands.executeCommand('_executeLinkProvider', resource) .then(tryMapWith(typeConverters.DocumentLink.to)); } - - private async _executeCallHierarchyIncomingCallsProvider(resource: URI, position: types.Position): Promise { - type IncomingCallDto = { - from: ICallHierarchyItemDto; - fromRanges: IRange[]; - }; - const args = { resource, position: typeConverters.Position.from(position) }; - const calls = await this._commands.executeCommand('_executeCallHierarchyIncomingCalls', args); - - const result: vscode.CallHierarchyIncomingCall[] = []; - for (const call of calls) { - result.push(new types.CallHierarchyIncomingCall(typeConverters.CallHierarchyItem.to(call.from), call.fromRanges.map(typeConverters.Range.to))); - } - return result; - } - - private async _executeCallHierarchyOutgoingCallsProvider(resource: URI, position: types.Position): Promise { - type OutgoingCallDto = { - fromRanges: IRange[]; - to: ICallHierarchyItemDto; - }; - const args = { resource, position: typeConverters.Position.from(position) }; - const calls = await this._commands.executeCommand('_executeCallHierarchyOutgoingCalls', args); - - const result: vscode.CallHierarchyOutgoingCall[] = []; - for (const call of calls) { - result.push(new types.CallHierarchyOutgoingCall(typeConverters.CallHierarchyItem.to(call.to), call.fromRanges.map(typeConverters.Range.to))); - } - return result; - } } function tryMapWith(f: (x: T) => R) { diff --git a/src/vs/workbench/api/common/extHostCommands.ts b/src/vs/workbench/api/common/extHostCommands.ts index b2d9a6905ce8..ade390377f3e 100644 --- a/src/vs/workbench/api/common/extHostCommands.ts +++ b/src/vs/workbench/api/common/extHostCommands.ts @@ -47,7 +47,7 @@ export class ExtHostCommands implements ExtHostCommandsShape { ) { this._proxy = extHostRpc.getProxy(MainContext.MainThreadCommands); this._logService = logService; - this._converter = new CommandsConverter(this); + this._converter = new CommandsConverter(this, logService); this._argumentProcessors = [ { processArgument(a) { @@ -218,14 +218,15 @@ export class ExtHostCommands implements ExtHostCommandsShape { export class CommandsConverter { private readonly _delegatingCommandId: string; - private readonly _commands: ExtHostCommands; private readonly _cache = new Map(); private _cachIdPool = 0; // --- conversion between internal and api commands - constructor(commands: ExtHostCommands) { + constructor( + private readonly _commands: ExtHostCommands, + private readonly _logService: ILogService + ) { this._delegatingCommandId = `_vscode_delegate_cmd_${Date.now().toString(36)}`; - this._commands = commands; this._commands.registerCommand(true, this._delegatingCommandId, this._executeConvertedCommand, this); } @@ -248,12 +249,16 @@ export class CommandsConverter { const id = ++this._cachIdPool; this._cache.set(id, command); - disposables.add(toDisposable(() => this._cache.delete(id))); + disposables.add(toDisposable(() => { + this._cache.delete(id); + this._logService.trace('CommandsConverter#DISPOSE', id); + })); result.$ident = id; result.id = this._delegatingCommandId; result.arguments = [id]; + this._logService.trace('CommandsConverter#CREATE', command.command, id); } return result; @@ -276,6 +281,8 @@ export class CommandsConverter { private _executeConvertedCommand(...args: any[]): Promise { const actualCmd = this._cache.get(args[0]); + this._logService.trace('CommandsConverter#EXECUTE', args[0], actualCmd ? actualCmd.command : 'MISSING'); + if (!actualCmd) { return Promise.reject('actual command NOT FOUND'); } diff --git a/src/vs/workbench/api/common/extHostComments.ts b/src/vs/workbench/api/common/extHostComments.ts index 31711a5c9e7e..28b6fb716c47 100644 --- a/src/vs/workbench/api/common/extHostComments.ts +++ b/src/vs/workbench/api/common/extHostComments.ts @@ -16,7 +16,7 @@ import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; import * as extHostTypeConverter from 'vs/workbench/api/common/extHostTypeConverters'; import * as types from 'vs/workbench/api/common/extHostTypes'; import * as vscode from 'vscode'; -import { ExtHostCommentsShape, IMainContext, MainContext, MainThreadCommentsShape } from './extHost.protocol'; +import { ExtHostCommentsShape, IMainContext, MainContext, MainThreadCommentsShape, CommentThreadChanges } from './extHost.protocol'; import { ExtHostCommands } from './extHostCommands'; type ProviderHandle = number; @@ -213,12 +213,21 @@ export class ExtHostComments implements ExtHostCommentsShape, IDisposable { } } +type CommentThreadModification = Partial<{ + range: vscode.Range, + label: string | undefined, + contextValue: string | undefined, + comments: vscode.Comment[], + collapsibleState: vscode.CommentThreadCollapsibleState +}>; export class ExtHostCommentThread implements vscode.CommentThread { private static _handlePool: number = 0; readonly handle = ExtHostCommentThread._handlePool++; public commentHandle: number = 0; + private modifications: CommentThreadModification = Object.create(null); + set threadId(id: string) { this._id = id; } @@ -245,6 +254,7 @@ export class ExtHostCommentThread implements vscode.CommentThread { set range(range: vscode.Range) { if (!range.isEqual(this._range)) { this._range = range; + this.modifications.range = range; this._onDidUpdateCommentThread.fire(); } } @@ -261,6 +271,7 @@ export class ExtHostCommentThread implements vscode.CommentThread { set label(label: string | undefined) { this._label = label; + this.modifications.label = label; this._onDidUpdateCommentThread.fire(); } @@ -272,6 +283,7 @@ export class ExtHostCommentThread implements vscode.CommentThread { set contextValue(context: string | undefined) { this._contextValue = context; + this.modifications.contextValue = context; this._onDidUpdateCommentThread.fire(); } @@ -281,6 +293,7 @@ export class ExtHostCommentThread implements vscode.CommentThread { set comments(newComments: vscode.Comment[]) { this._comments = newComments; + this.modifications.comments = newComments; this._onDidUpdateCommentThread.fire(); } @@ -292,6 +305,7 @@ export class ExtHostCommentThread implements vscode.CommentThread { set collapsibleState(newState: vscode.CommentThreadCollapsibleState) { this._collapseState = newState; + this.modifications.collapsibleState = newState; this._onDidUpdateCommentThread.fire(); } @@ -353,22 +367,34 @@ export class ExtHostCommentThread implements vscode.CommentThread { this._acceptInputDisposables.value = new DisposableStore(); } - const commentThreadRange = extHostTypeConverter.Range.from(this._range); - const label = this.label; - const contextValue = this.contextValue; - const comments = this._comments.map(cmt => { return convertToModeComment(this, this._commentController, cmt, this._commentsMap); }); - const collapsibleState = convertToCollapsibleState(this._collapseState); + const modified = (value: keyof CommentThreadModification): boolean => + Object.prototype.hasOwnProperty.call(this.modifications, value); + + const formattedModifications: CommentThreadChanges = {}; + if (modified('range')) { + formattedModifications.range = extHostTypeConverter.Range.from(this._range); + } + if (modified('label')) { + formattedModifications.label = this.label; + } + if (modified('contextValue')) { + formattedModifications.contextValue = this.contextValue; + } + if (modified('comments')) { + formattedModifications.comments = + this._comments.map(cmt => convertToModeComment(this, this._commentController, cmt, this._commentsMap)); + } + if (modified('collapsibleState')) { + formattedModifications.collapseState = convertToCollapsibleState(this._collapseState); + } + this.modifications = {}; this._proxy.$updateCommentThread( this._commentController.handle, this.handle, this._id!, this._uri, - commentThreadRange, - label, - contextValue, - comments, - collapsibleState + formattedModifications ); } diff --git a/src/vs/workbench/api/common/extHostConfiguration.ts b/src/vs/workbench/api/common/extHostConfiguration.ts index 476df18c519c..0d0db9608001 100644 --- a/src/vs/workbench/api/common/extHostConfiguration.ts +++ b/src/vs/workbench/api/common/extHostConfiguration.ts @@ -20,6 +20,7 @@ import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { Barrier } from 'vs/base/common/async'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; +import { ILogService } from 'vs/platform/log/common/log'; function lookUp(tree: any, key: string) { if (key) { @@ -45,16 +46,19 @@ export class ExtHostConfiguration implements ExtHostConfigurationShape { readonly _serviceBrand: undefined; private readonly _proxy: MainThreadConfigurationShape; + private readonly _logService: ILogService; private readonly _extHostWorkspace: ExtHostWorkspace; private readonly _barrier: Barrier; private _actual: ExtHostConfigProvider | null; constructor( @IExtHostRpcService extHostRpc: IExtHostRpcService, - @IExtHostWorkspace extHostWorkspace: IExtHostWorkspace + @IExtHostWorkspace extHostWorkspace: IExtHostWorkspace, + @ILogService logService: ILogService, ) { this._proxy = extHostRpc.getProxy(MainContext.MainThreadConfiguration); this._extHostWorkspace = extHostWorkspace; + this._logService = logService; this._barrier = new Barrier(); this._actual = null; } @@ -64,7 +68,7 @@ export class ExtHostConfiguration implements ExtHostConfigurationShape { } $initializeConfiguration(data: IConfigurationInitData): void { - this._actual = new ExtHostConfigProvider(this._proxy, this._extHostWorkspace, data); + this._actual = new ExtHostConfigProvider(this._proxy, this._extHostWorkspace, data, this._logService); this._barrier.open(); } @@ -80,9 +84,11 @@ export class ExtHostConfigProvider { private readonly _extHostWorkspace: ExtHostWorkspace; private _configurationScopes: Map; private _configuration: Configuration; + private _logService: ILogService; - constructor(proxy: MainThreadConfigurationShape, extHostWorkspace: ExtHostWorkspace, data: IConfigurationInitData) { + constructor(proxy: MainThreadConfigurationShape, extHostWorkspace: ExtHostWorkspace, data: IConfigurationInitData, logService: ILogService) { this._proxy = proxy; + this._logService = logService; this._extHostWorkspace = extHostWorkspace; this._configuration = ExtHostConfigProvider.parse(data); this._configurationScopes = this._toMap(data.configurationScopes); @@ -236,13 +242,13 @@ export class ExtHostConfigProvider { const extensionIdText = extensionId ? `[${extensionId.value}] ` : ''; if (ConfigurationScope.RESOURCE === scope) { if (resource === undefined) { - console.warn(`${extensionIdText}Accessing a resource scoped configuration without providing a resource is not expected. To get the effective value for '${key}', provide the URI of a resource or 'null' for any resource.`); + this._logService.warn(`${extensionIdText}Accessing a resource scoped configuration without providing a resource is not expected. To get the effective value for '${key}', provide the URI of a resource or 'null' for any resource.`); } return; } if (ConfigurationScope.WINDOW === scope) { if (resource) { - console.warn(`${extensionIdText}Accessing a window scoped configuration for a resource is not expected. To associate '${key}' to a resource, define its scope to 'resource' in configuration contributions in 'package.json'.`); + this._logService.warn(`${extensionIdText}Accessing a window scoped configuration for a resource is not expected. To associate '${key}' to a resource, define its scope to 'resource' in configuration contributions in 'package.json'.`); } return; } diff --git a/src/vs/workbench/api/common/extHostCustomers.ts b/src/vs/workbench/api/common/extHostCustomers.ts index f762302f64ed..07c3aefea07d 100644 --- a/src/vs/workbench/api/common/extHostCustomers.ts +++ b/src/vs/workbench/api/common/extHostCustomers.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IDisposable } from 'vs/base/common/lifecycle'; -import { IConstructorSignature1 } from 'vs/platform/instantiation/common/instantiation'; +import { IConstructorSignature1, BrandedService } from 'vs/platform/instantiation/common/instantiation'; import { IExtHostContext } from 'vs/workbench/api/common/extHost.protocol'; import { ProxyIdentifier } from 'vs/workbench/services/extensions/common/proxyIdentifier'; @@ -13,12 +13,12 @@ export type IExtHostNamedCustomer = [ProxyIdentifier, export type IExtHostCustomerCtor = IConstructorSignature1; export function extHostNamedCustomer(id: ProxyIdentifier) { - return function (ctor: IExtHostCustomerCtor): void { + return function (ctor: { new(context: IExtHostContext, ...services: Services): T }): void { ExtHostCustomersRegistryImpl.INSTANCE.registerNamedCustomer(id, ctor); }; } -export function extHostCustomer(ctor: IExtHostCustomerCtor): void { +export function extHostCustomer(ctor: { new(context: IExtHostContext, ...services: Services): T }): void { ExtHostCustomersRegistryImpl.INSTANCE.registerCustomer(ctor); } diff --git a/src/vs/workbench/api/common/extHostDebugService.ts b/src/vs/workbench/api/common/extHostDebugService.ts index 6180477b1884..77595d63a1f1 100644 --- a/src/vs/workbench/api/common/extHostDebugService.ts +++ b/src/vs/workbench/api/common/extHostDebugService.ts @@ -3,11 +3,35 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Event } from 'vs/base/common/event'; -import { ExtHostDebugServiceShape } from 'vs/workbench/api/common/extHost.protocol'; +import * as path from 'vs/base/common/path'; +import { URI, UriComponents } from 'vs/base/common/uri'; +import { Event, Emitter } from 'vs/base/common/event'; +import { asPromise } from 'vs/base/common/async'; +import { + MainContext, MainThreadDebugServiceShape, ExtHostDebugServiceShape, DebugSessionUUID, + IBreakpointsDeltaDto, ISourceMultiBreakpointDto, IFunctionBreakpointDto, IDebugSessionDto +} from 'vs/workbench/api/common/extHost.protocol'; +import { Disposable, Position, Location, SourceBreakpoint, FunctionBreakpoint, DebugAdapterServer, DebugAdapterExecutable, DataBreakpoint, DebugConsoleMode, DebugAdapterInlineImplementation } from 'vs/workbench/api/common/extHostTypes'; +import { AbstractDebugAdapter } from 'vs/workbench/contrib/debug/common/abstractDebugAdapter'; +import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; +import { IExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService'; +import { ExtHostDocumentsAndEditors, IExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; +import { IDebuggerContribution, IConfig, IDebugAdapter, IDebugAdapterServer, IDebugAdapterExecutable, IAdapterDescriptor, IDebugAdapterImpl } from 'vs/workbench/contrib/debug/common/debug'; +import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; +import { AbstractVariableResolverService } from 'vs/workbench/services/configurationResolver/common/variableResolver'; +import { ExtHostConfigProvider, IExtHostConfiguration } from '../common/extHostConfiguration'; +import { convertToVSCPaths, convertToDAPaths, isDebuggerMainContribution } from 'vs/workbench/contrib/debug/common/debugUtils'; +import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { IExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; +import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; +import { ISignService } from 'vs/platform/sign/common/sign'; +import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import * as vscode from 'vscode'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { withNullAsUndefined } from 'vs/base/common/types'; +import { IProcessEnvironment } from 'vs/base/common/platform'; export const IExtHostDebugService = createDecorator('IExtHostDebugService'); @@ -30,5 +54,1036 @@ export interface IExtHostDebugService extends ExtHostDebugServiceShape { registerDebugConfigurationProvider(type: string, provider: vscode.DebugConfigurationProvider): vscode.Disposable; registerDebugAdapterDescriptorFactory(extension: IExtensionDescription, type: string, factory: vscode.DebugAdapterDescriptorFactory): vscode.Disposable; registerDebugAdapterTrackerFactory(type: string, factory: vscode.DebugAdapterTrackerFactory): vscode.Disposable; + asDebugSourceUri(source: vscode.DebugProtocolSource, session?: vscode.DebugSession): vscode.Uri; } +export class ExtHostDebugServiceBase implements IExtHostDebugService, ExtHostDebugServiceShape { + + readonly _serviceBrand: undefined; + + private _configProviderHandleCounter: number; + private _configProviders: ConfigProviderTuple[]; + + private _adapterFactoryHandleCounter: number; + private _adapterFactories: DescriptorFactoryTuple[]; + + private _trackerFactoryHandleCounter: number; + private _trackerFactories: TrackerFactoryTuple[]; + + private _debugServiceProxy: MainThreadDebugServiceShape; + private _debugSessions: Map = new Map(); + + private readonly _onDidStartDebugSession: Emitter; + get onDidStartDebugSession(): Event { return this._onDidStartDebugSession.event; } + + private readonly _onDidTerminateDebugSession: Emitter; + get onDidTerminateDebugSession(): Event { return this._onDidTerminateDebugSession.event; } + + private readonly _onDidChangeActiveDebugSession: Emitter; + get onDidChangeActiveDebugSession(): Event { return this._onDidChangeActiveDebugSession.event; } + + private _activeDebugSession: ExtHostDebugSession | undefined; + get activeDebugSession(): ExtHostDebugSession | undefined { return this._activeDebugSession; } + + private readonly _onDidReceiveDebugSessionCustomEvent: Emitter; + get onDidReceiveDebugSessionCustomEvent(): Event { return this._onDidReceiveDebugSessionCustomEvent.event; } + + private _activeDebugConsole: ExtHostDebugConsole; + get activeDebugConsole(): ExtHostDebugConsole { return this._activeDebugConsole; } + + private _breakpoints: Map; + private _breakpointEventsActive: boolean; + + private readonly _onDidChangeBreakpoints: Emitter; + + private _aexCommands: Map; + private _debugAdapters: Map; + private _debugAdaptersTrackers: Map; + + private _variableResolver: IConfigurationResolverService | undefined; + + private _signService: ISignService | undefined; + + + constructor( + @IExtHostRpcService extHostRpcService: IExtHostRpcService, + @IExtHostWorkspace private _workspaceService: IExtHostWorkspace, + @IExtHostExtensionService private _extensionService: IExtHostExtensionService, + @IExtHostDocumentsAndEditors private _editorsService: IExtHostDocumentsAndEditors, + @IExtHostConfiguration protected _configurationService: IExtHostConfiguration, + @IExtHostCommands private _commandService: IExtHostCommands + ) { + this._configProviderHandleCounter = 0; + this._configProviders = []; + + this._adapterFactoryHandleCounter = 0; + this._adapterFactories = []; + + this._trackerFactoryHandleCounter = 0; + this._trackerFactories = []; + + this._aexCommands = new Map(); + this._debugAdapters = new Map(); + this._debugAdaptersTrackers = new Map(); + + this._onDidStartDebugSession = new Emitter(); + this._onDidTerminateDebugSession = new Emitter(); + this._onDidChangeActiveDebugSession = new Emitter(); + this._onDidReceiveDebugSessionCustomEvent = new Emitter(); + + this._debugServiceProxy = extHostRpcService.getProxy(MainContext.MainThreadDebugService); + + this._onDidChangeBreakpoints = new Emitter({ + onFirstListenerAdd: () => { + this.startBreakpoints(); + } + }); + + this._activeDebugConsole = new ExtHostDebugConsole(this._debugServiceProxy); + + this._breakpoints = new Map(); + this._breakpointEventsActive = false; + + this._extensionService.getExtensionRegistry().then((extensionRegistry: ExtensionDescriptionRegistry) => { + extensionRegistry.onDidChange(_ => { + this.registerAllDebugTypes(extensionRegistry); + }); + this.registerAllDebugTypes(extensionRegistry); + }); + } + + public asDebugSourceUri(src: vscode.DebugProtocolSource, session?: vscode.DebugSession): URI { + + const source = src; + + if (typeof source.sourceReference === 'number') { + // src can be retrieved via DAP's "source" request + + let debug = `debug:${encodeURIComponent(source.path || '')}`; + let sep = '?'; + + if (session) { + debug += `${sep}session=${encodeURIComponent(session.id)}`; + sep = '&'; + } + + debug += `${sep}ref=${source.sourceReference}`; + + return URI.parse(debug); + } else if (source.path) { + // src is just a local file path + return URI.file(source.path); + } else { + throw new Error(`cannot create uri from DAP 'source' object; properties 'path' and 'sourceReference' are both missing.`); + } + } + + private registerAllDebugTypes(extensionRegistry: ExtensionDescriptionRegistry) { + + const debugTypes: string[] = []; + this._aexCommands.clear(); + + for (const ed of extensionRegistry.getAllExtensionDescriptions()) { + if (ed.contributes) { + const debuggers = ed.contributes['debuggers']; + if (debuggers && debuggers.length > 0) { + for (const dbg of debuggers) { + if (isDebuggerMainContribution(dbg)) { + debugTypes.push(dbg.type); + if (dbg.adapterExecutableCommand) { + this._aexCommands.set(dbg.type, dbg.adapterExecutableCommand); + } + } + } + } + } + } + + this._debugServiceProxy.$registerDebugTypes(debugTypes); + } + + // extension debug API + + get onDidChangeBreakpoints(): Event { + return this._onDidChangeBreakpoints.event; + } + + get breakpoints(): vscode.Breakpoint[] { + + this.startBreakpoints(); + + const result: vscode.Breakpoint[] = []; + this._breakpoints.forEach(bp => result.push(bp)); + return result; + } + + public addBreakpoints(breakpoints0: vscode.Breakpoint[]): Promise { + + this.startBreakpoints(); + + // filter only new breakpoints + const breakpoints = breakpoints0.filter(bp => { + const id = bp.id; + if (!this._breakpoints.has(id)) { + this._breakpoints.set(id, bp); + return true; + } + return false; + }); + + // send notification for added breakpoints + this.fireBreakpointChanges(breakpoints, [], []); + + // convert added breakpoints to DTOs + const dtos: Array = []; + const map = new Map(); + for (const bp of breakpoints) { + if (bp instanceof SourceBreakpoint) { + let dto = map.get(bp.location.uri.toString()); + if (!dto) { + dto = { + type: 'sourceMulti', + uri: bp.location.uri, + lines: [] + }; + map.set(bp.location.uri.toString(), dto); + dtos.push(dto); + } + dto.lines.push({ + id: bp.id, + enabled: bp.enabled, + condition: bp.condition, + hitCondition: bp.hitCondition, + logMessage: bp.logMessage, + line: bp.location.range.start.line, + character: bp.location.range.start.character + }); + } else if (bp instanceof FunctionBreakpoint) { + dtos.push({ + type: 'function', + id: bp.id, + enabled: bp.enabled, + hitCondition: bp.hitCondition, + logMessage: bp.logMessage, + condition: bp.condition, + functionName: bp.functionName + }); + } + } + + // send DTOs to VS Code + return this._debugServiceProxy.$registerBreakpoints(dtos); + } + + public removeBreakpoints(breakpoints0: vscode.Breakpoint[]): Promise { + + this.startBreakpoints(); + + // remove from array + const breakpoints = breakpoints0.filter(b => this._breakpoints.delete(b.id)); + + // send notification + this.fireBreakpointChanges([], breakpoints, []); + + // unregister with VS Code + const ids = breakpoints.filter(bp => bp instanceof SourceBreakpoint).map(bp => bp.id); + const fids = breakpoints.filter(bp => bp instanceof FunctionBreakpoint).map(bp => bp.id); + const dids = breakpoints.filter(bp => bp instanceof DataBreakpoint).map(bp => bp.id); + return this._debugServiceProxy.$unregisterBreakpoints(ids, fids, dids); + } + + public startDebugging(folder: vscode.WorkspaceFolder | undefined, nameOrConfig: string | vscode.DebugConfiguration, options: vscode.DebugSessionOptions): Promise { + return this._debugServiceProxy.$startDebugging(folder ? folder.uri : undefined, nameOrConfig, { + parentSessionID: options.parentSession ? options.parentSession.id : undefined, + repl: options.consoleMode === DebugConsoleMode.MergeWithParent ? 'mergeWithParent' : 'separate' + }); + } + + public registerDebugConfigurationProvider(type: string, provider: vscode.DebugConfigurationProvider): vscode.Disposable { + + if (!provider) { + return new Disposable(() => { }); + } + + if (provider.debugAdapterExecutable) { + console.error('DebugConfigurationProvider.debugAdapterExecutable is deprecated and will be removed soon; please use DebugAdapterDescriptorFactory.createDebugAdapterDescriptor instead.'); + } + + const handle = this._configProviderHandleCounter++; + this._configProviders.push({ type, handle, provider }); + + this._debugServiceProxy.$registerDebugConfigurationProvider(type, + !!provider.provideDebugConfigurations, + !!provider.resolveDebugConfiguration, + !!provider.debugAdapterExecutable, // TODO@AW: deprecated + handle); + + return new Disposable(() => { + this._configProviders = this._configProviders.filter(p => p.provider !== provider); // remove + this._debugServiceProxy.$unregisterDebugConfigurationProvider(handle); + }); + } + + public registerDebugAdapterDescriptorFactory(extension: IExtensionDescription, type: string, factory: vscode.DebugAdapterDescriptorFactory): vscode.Disposable { + + if (!factory) { + return new Disposable(() => { }); + } + + // a DebugAdapterDescriptorFactory can only be registered in the extension that contributes the debugger + if (!this.definesDebugType(extension, type)) { + throw new Error(`a DebugAdapterDescriptorFactory can only be registered from the extension that defines the '${type}' debugger.`); + } + + // make sure that only one factory for this type is registered + if (this.getAdapterFactoryByType(type)) { + throw new Error(`a DebugAdapterDescriptorFactory can only be registered once per a type.`); + } + + const handle = this._adapterFactoryHandleCounter++; + this._adapterFactories.push({ type, handle, factory }); + + this._debugServiceProxy.$registerDebugAdapterDescriptorFactory(type, handle); + + return new Disposable(() => { + this._adapterFactories = this._adapterFactories.filter(p => p.factory !== factory); // remove + this._debugServiceProxy.$unregisterDebugAdapterDescriptorFactory(handle); + }); + } + + public registerDebugAdapterTrackerFactory(type: string, factory: vscode.DebugAdapterTrackerFactory): vscode.Disposable { + + if (!factory) { + return new Disposable(() => { }); + } + + const handle = this._trackerFactoryHandleCounter++; + this._trackerFactories.push({ type, handle, factory }); + + return new Disposable(() => { + this._trackerFactories = this._trackerFactories.filter(p => p.factory !== factory); // remove + }); + } + + // RPC methods (ExtHostDebugServiceShape) + + public async $runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments): Promise { + return Promise.resolve(undefined); + } + + protected createVariableResolver(folders: vscode.WorkspaceFolder[], editorService: ExtHostDocumentsAndEditors, configurationService: ExtHostConfigProvider): AbstractVariableResolverService { + return new ExtHostVariableResolverService(folders, editorService, configurationService); + } + + public async $substituteVariables(folderUri: UriComponents | undefined, config: IConfig): Promise { + if (!this._variableResolver) { + const [workspaceFolders, configProvider] = await Promise.all([this._workspaceService.getWorkspaceFolders2(), this._configurationService.getConfigProvider()]); + this._variableResolver = this.createVariableResolver(workspaceFolders || [], this._editorsService, configProvider!); + } + let ws: IWorkspaceFolder | undefined; + const folder = await this.getFolder(folderUri); + if (folder) { + ws = { + uri: folder.uri, + name: folder.name, + index: folder.index, + toResource: () => { + throw new Error('Not implemented'); + } + }; + } + return this._variableResolver.resolveAny(ws, config); + } + + protected createDebugAdapter(adapter: IAdapterDescriptor, session: ExtHostDebugSession): AbstractDebugAdapter | undefined { + if (adapter.type === 'implementation') { + return new DirectDebugAdapter(adapter.implementation); + } + return undefined; + } + + protected createSignService(): ISignService | undefined { + return undefined; + } + + public async $startDASession(debugAdapterHandle: number, sessionDto: IDebugSessionDto): Promise { + const mythis = this; + + const session = await this.getSession(sessionDto); + + return this.getAdapterDescriptor(this.getAdapterFactoryByType(session.type), session).then(daDescriptor => { + + const adapter = this.convertToDto(daDescriptor); + + const da: AbstractDebugAdapter | undefined = this.createDebugAdapter(adapter, session); + + const debugAdapter = da; + + if (debugAdapter) { + this._debugAdapters.set(debugAdapterHandle, debugAdapter); + + return this.getDebugAdapterTrackers(session).then(tracker => { + + if (tracker) { + this._debugAdaptersTrackers.set(debugAdapterHandle, tracker); + } + + debugAdapter.onMessage(async message => { + + if (message.type === 'request' && (message).command === 'handshake') { + + const request = message; + + const response: DebugProtocol.Response = { + type: 'response', + seq: 0, + command: request.command, + request_seq: request.seq, + success: true + }; + + if (!this._signService) { + this._signService = this.createSignService(); + } + + try { + if (this._signService) { + const signature = await this._signService.sign(request.arguments.value); + response.body = { + signature: signature + }; + debugAdapter.sendResponse(response); + } else { + throw new Error('no signer'); + } + } catch (e) { + response.success = false; + response.message = e.message; + debugAdapter.sendResponse(response); + } + } else { + if (tracker && tracker.onDidSendMessage) { + tracker.onDidSendMessage(message); + } + + // DA -> VS Code + message = convertToVSCPaths(message, true); + + mythis._debugServiceProxy.$acceptDAMessage(debugAdapterHandle, message); + } + }); + debugAdapter.onError(err => { + if (tracker && tracker.onError) { + tracker.onError(err); + } + this._debugServiceProxy.$acceptDAError(debugAdapterHandle, err.name, err.message, err.stack); + }); + debugAdapter.onExit((code: number | null) => { + if (tracker && tracker.onExit) { + tracker.onExit(withNullAsUndefined(code), undefined); + } + this._debugServiceProxy.$acceptDAExit(debugAdapterHandle, withNullAsUndefined(code), undefined); + }); + + if (tracker && tracker.onWillStartSession) { + tracker.onWillStartSession(); + } + + return debugAdapter.startSession(); + }); + + } + return undefined; + }); + } + + public $sendDAMessage(debugAdapterHandle: number, message: DebugProtocol.ProtocolMessage): void { + + // VS Code -> DA + message = convertToDAPaths(message, false); + + const tracker = this._debugAdaptersTrackers.get(debugAdapterHandle); // TODO@AW: same handle? + if (tracker && tracker.onWillReceiveMessage) { + tracker.onWillReceiveMessage(message); + } + + const da = this._debugAdapters.get(debugAdapterHandle); + if (da) { + da.sendMessage(message); + } + } + + public $stopDASession(debugAdapterHandle: number): Promise { + + const tracker = this._debugAdaptersTrackers.get(debugAdapterHandle); + this._debugAdaptersTrackers.delete(debugAdapterHandle); + if (tracker && tracker.onWillStopSession) { + tracker.onWillStopSession(); + } + + const da = this._debugAdapters.get(debugAdapterHandle); + this._debugAdapters.delete(debugAdapterHandle); + if (da) { + return da.stopSession(); + } else { + return Promise.resolve(void 0); + } + } + + public $acceptBreakpointsDelta(delta: IBreakpointsDeltaDto): void { + + const a: vscode.Breakpoint[] = []; + const r: vscode.Breakpoint[] = []; + const c: vscode.Breakpoint[] = []; + + if (delta.added) { + for (const bpd of delta.added) { + const id = bpd.id; + if (id && !this._breakpoints.has(id)) { + let bp: vscode.Breakpoint; + if (bpd.type === 'function') { + bp = new FunctionBreakpoint(bpd.functionName, bpd.enabled, bpd.condition, bpd.hitCondition, bpd.logMessage); + } else if (bpd.type === 'data') { + bp = new DataBreakpoint(bpd.label, bpd.dataId, bpd.canPersist, bpd.enabled, bpd.hitCondition, bpd.condition, bpd.logMessage); + } else { + const uri = URI.revive(bpd.uri); + bp = new SourceBreakpoint(new Location(uri, new Position(bpd.line, bpd.character)), bpd.enabled, bpd.condition, bpd.hitCondition, bpd.logMessage); + } + (bp as any)._id = id; + this._breakpoints.set(id, bp); + a.push(bp); + } + } + } + + if (delta.removed) { + for (const id of delta.removed) { + const bp = this._breakpoints.get(id); + if (bp) { + this._breakpoints.delete(id); + r.push(bp); + } + } + } + + if (delta.changed) { + for (const bpd of delta.changed) { + if (bpd.id) { + const bp = this._breakpoints.get(bpd.id); + if (bp) { + if (bp instanceof FunctionBreakpoint && bpd.type === 'function') { + const fbp = bp; + fbp.enabled = bpd.enabled; + fbp.condition = bpd.condition; + fbp.hitCondition = bpd.hitCondition; + fbp.logMessage = bpd.logMessage; + fbp.functionName = bpd.functionName; + } else if (bp instanceof SourceBreakpoint && bpd.type === 'source') { + const sbp = bp; + sbp.enabled = bpd.enabled; + sbp.condition = bpd.condition; + sbp.hitCondition = bpd.hitCondition; + sbp.logMessage = bpd.logMessage; + sbp.location = new Location(URI.revive(bpd.uri), new Position(bpd.line, bpd.character)); + } + c.push(bp); + } + } + } + } + + this.fireBreakpointChanges(a, r, c); + } + + public $provideDebugConfigurations(configProviderHandle: number, folderUri: UriComponents | undefined, token: CancellationToken): Promise { + return asPromise(async () => { + const provider = this.getConfigProviderByHandle(configProviderHandle); + if (!provider) { + throw new Error('no DebugConfigurationProvider found'); + } + if (!provider.provideDebugConfigurations) { + throw new Error('DebugConfigurationProvider has no method provideDebugConfigurations'); + } + const folder = await this.getFolder(folderUri); + return provider.provideDebugConfigurations(folder, token); + }).then(debugConfigurations => { + if (!debugConfigurations) { + throw new Error('nothing returned from DebugConfigurationProvider.provideDebugConfigurations'); + } + return debugConfigurations; + }); + } + + public $resolveDebugConfiguration(configProviderHandle: number, folderUri: UriComponents | undefined, debugConfiguration: vscode.DebugConfiguration, token: CancellationToken): Promise { + return asPromise(async () => { + const provider = this.getConfigProviderByHandle(configProviderHandle); + if (!provider) { + throw new Error('no DebugConfigurationProvider found'); + } + if (!provider.resolveDebugConfiguration) { + throw new Error('DebugConfigurationProvider has no method resolveDebugConfiguration'); + } + const folder = await this.getFolder(folderUri); + return provider.resolveDebugConfiguration(folder, debugConfiguration, token); + }); + } + + // TODO@AW deprecated and legacy + public $legacyDebugAdapterExecutable(configProviderHandle: number, folderUri: UriComponents | undefined): Promise { + return asPromise(async () => { + const provider = this.getConfigProviderByHandle(configProviderHandle); + if (!provider) { + throw new Error('no DebugConfigurationProvider found'); + } + if (!provider.debugAdapterExecutable) { + throw new Error('DebugConfigurationProvider has no method debugAdapterExecutable'); + } + const folder = await this.getFolder(folderUri); + return provider.debugAdapterExecutable(folder, CancellationToken.None); + }).then(executable => { + if (!executable) { + throw new Error('nothing returned from DebugConfigurationProvider.debugAdapterExecutable'); + } + return this.convertToDto(executable); + }); + } + + public async $provideDebugAdapter(adapterProviderHandle: number, sessionDto: IDebugSessionDto): Promise { + const adapterProvider = this.getAdapterProviderByHandle(adapterProviderHandle); + if (!adapterProvider) { + return Promise.reject(new Error('no handler found')); + } + const session = await this.getSession(sessionDto); + return this.getAdapterDescriptor(adapterProvider, session).then(x => this.convertToDto(x)); + } + + public async $acceptDebugSessionStarted(sessionDto: IDebugSessionDto): Promise { + const session = await this.getSession(sessionDto); + this._onDidStartDebugSession.fire(session); + } + + public async $acceptDebugSessionTerminated(sessionDto: IDebugSessionDto): Promise { + const session = await this.getSession(sessionDto); + if (session) { + this._onDidTerminateDebugSession.fire(session); + this._debugSessions.delete(session.id); + } + } + + public async $acceptDebugSessionActiveChanged(sessionDto: IDebugSessionDto | undefined): Promise { + this._activeDebugSession = sessionDto ? await this.getSession(sessionDto) : undefined; + this._onDidChangeActiveDebugSession.fire(this._activeDebugSession); + } + + public async $acceptDebugSessionNameChanged(sessionDto: IDebugSessionDto, name: string): Promise { + const session = await this.getSession(sessionDto); + if (session) { + session._acceptNameChanged(name); + } + } + + public async $acceptDebugSessionCustomEvent(sessionDto: IDebugSessionDto, event: any): Promise { + const session = await this.getSession(sessionDto); + const ee: vscode.DebugSessionCustomEvent = { + session: session, + event: event.event, + body: event.body + }; + this._onDidReceiveDebugSessionCustomEvent.fire(ee); + } + + // private & dto helpers + + private convertToDto(x: vscode.DebugAdapterDescriptor | undefined): IAdapterDescriptor { + + if (x instanceof DebugAdapterExecutable) { + return { + type: 'executable', + command: x.command, + args: x.args, + options: x.options + }; + } else if (x instanceof DebugAdapterServer) { + return { + type: 'server', + port: x.port, + host: x.host + }; + } else if (x instanceof DebugAdapterInlineImplementation) { + return { + type: 'implementation', + implementation: x.implementation + }; + } else { + throw new Error('convertToDto unexpected type'); + } + } + + private getAdapterFactoryByType(type: string): vscode.DebugAdapterDescriptorFactory | undefined { + const results = this._adapterFactories.filter(p => p.type === type); + if (results.length > 0) { + return results[0].factory; + } + return undefined; + } + + private getAdapterProviderByHandle(handle: number): vscode.DebugAdapterDescriptorFactory | undefined { + const results = this._adapterFactories.filter(p => p.handle === handle); + if (results.length > 0) { + return results[0].factory; + } + return undefined; + } + + private getConfigProviderByHandle(handle: number): vscode.DebugConfigurationProvider | undefined { + const results = this._configProviders.filter(p => p.handle === handle); + if (results.length > 0) { + return results[0].provider; + } + return undefined; + } + + private definesDebugType(ed: IExtensionDescription, type: string) { + if (ed.contributes) { + const debuggers = ed.contributes['debuggers']; + if (debuggers && debuggers.length > 0) { + for (const dbg of debuggers) { + // only debugger contributions with a "label" are considered a "defining" debugger contribution + if (dbg.label && dbg.type) { + if (dbg.type === type) { + return true; + } + } + } + } + } + return false; + } + + private getDebugAdapterTrackers(session: ExtHostDebugSession): Promise { + + const config = session.configuration; + const type = config.type; + + const promises = this._trackerFactories + .filter(tuple => tuple.type === type || tuple.type === '*') + .map(tuple => asPromise>(() => tuple.factory.createDebugAdapterTracker(session)).then(p => p, err => null)); + + return Promise.race([ + Promise.all(promises).then(result => { + const trackers = result.filter(t => !!t); // filter null + if (trackers.length > 0) { + return new MultiTracker(trackers); + } + return undefined; + }), + new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + clearTimeout(timeout); + reject(new Error('timeout')); + }, 1000); + }) + ]).catch(err => { + // ignore errors + return undefined; + }); + } + + private async getAdapterDescriptor(adapterProvider: vscode.DebugAdapterDescriptorFactory | undefined, session: ExtHostDebugSession): Promise { + + // a "debugServer" attribute in the launch config takes precedence + const serverPort = session.configuration.debugServer; + if (typeof serverPort === 'number') { + return Promise.resolve(new DebugAdapterServer(serverPort)); + } + + // TODO@AW legacy + const pair = this._configProviders.filter(p => p.type === session.type).pop(); + if (pair && pair.provider.debugAdapterExecutable) { + const func = pair.provider.debugAdapterExecutable; + return asPromise(() => func(session.workspaceFolder, CancellationToken.None)).then(executable => { + if (executable) { + return executable; + } + return undefined; + }); + } + + if (adapterProvider) { + const extensionRegistry = await this._extensionService.getExtensionRegistry(); + return asPromise(() => adapterProvider.createDebugAdapterDescriptor(session, this.daExecutableFromPackage(session, extensionRegistry))).then(daDescriptor => { + if (daDescriptor) { + return daDescriptor; + } + return undefined; + }); + } + + // try deprecated command based extension API "adapterExecutableCommand" to determine the executable + // TODO@AW legacy + const aex = this._aexCommands.get(session.type); + if (aex) { + const folder = session.workspaceFolder; + const rootFolder = folder ? folder.uri.toString() : undefined; + return this._commandService.executeCommand(aex, rootFolder).then((ae: { command: string, args: string[] }) => { + return new DebugAdapterExecutable(ae.command, ae.args || []); + }); + } + + // fallback: use executable information from package.json + const extensionRegistry = await this._extensionService.getExtensionRegistry(); + return Promise.resolve(this.daExecutableFromPackage(session, extensionRegistry)); + } + + protected daExecutableFromPackage(session: ExtHostDebugSession, extensionRegistry: ExtensionDescriptionRegistry): DebugAdapterExecutable | undefined { + return undefined; + } + + private startBreakpoints() { + if (!this._breakpointEventsActive) { + this._breakpointEventsActive = true; + this._debugServiceProxy.$startBreakpointEvents(); + } + } + + private fireBreakpointChanges(added: vscode.Breakpoint[], removed: vscode.Breakpoint[], changed: vscode.Breakpoint[]) { + if (added.length > 0 || removed.length > 0 || changed.length > 0) { + this._onDidChangeBreakpoints.fire(Object.freeze({ + added, + removed, + changed, + })); + } + } + + private async getSession(dto: IDebugSessionDto): Promise { + if (dto) { + if (typeof dto === 'string') { + const ds = this._debugSessions.get(dto); + if (ds) { + return ds; + } + } else { + let ds = this._debugSessions.get(dto.id); + if (!ds) { + const folder = await this.getFolder(dto.folderUri); + ds = new ExtHostDebugSession(this._debugServiceProxy, dto.id, dto.type, dto.name, folder, dto.configuration); + this._debugSessions.set(ds.id, ds); + this._debugServiceProxy.$sessionCached(ds.id); + } + return ds; + } + } + throw new Error('cannot find session'); + } + + private getFolder(_folderUri: UriComponents | undefined): Promise { + if (_folderUri) { + const folderURI = URI.revive(_folderUri); + return this._workspaceService.resolveWorkspaceFolder(folderURI); + } + return Promise.resolve(undefined); + } +} + +export class ExtHostDebugSession implements vscode.DebugSession { + + constructor( + private _debugServiceProxy: MainThreadDebugServiceShape, + private _id: DebugSessionUUID, + private _type: string, + private _name: string, + private _workspaceFolder: vscode.WorkspaceFolder | undefined, + private _configuration: vscode.DebugConfiguration) { + } + + public get id(): string { + return this._id; + } + + public get type(): string { + return this._type; + } + + public get name(): string { + return this._name; + } + + public set name(name: string) { + this._name = name; + this._debugServiceProxy.$setDebugSessionName(this._id, name); + } + + _acceptNameChanged(name: string) { + this._name = name; + } + + public get workspaceFolder(): vscode.WorkspaceFolder | undefined { + return this._workspaceFolder; + } + + public get configuration(): vscode.DebugConfiguration { + return this._configuration; + } + + public customRequest(command: string, args: any): Promise { + return this._debugServiceProxy.$customDebugAdapterRequest(this._id, command, args); + } +} + +export class ExtHostDebugConsole implements vscode.DebugConsole { + + private _debugServiceProxy: MainThreadDebugServiceShape; + + constructor(proxy: MainThreadDebugServiceShape) { + this._debugServiceProxy = proxy; + } + + append(value: string): void { + this._debugServiceProxy.$appendDebugConsole(value); + } + + appendLine(value: string): void { + this.append(value + '\n'); + } +} + +export class ExtHostVariableResolverService extends AbstractVariableResolverService { + + constructor(folders: vscode.WorkspaceFolder[], editorService: ExtHostDocumentsAndEditors, configurationService: ExtHostConfigProvider, env?: IProcessEnvironment) { + super({ + getFolderUri: (folderName: string): URI | undefined => { + const found = folders.filter(f => f.name === folderName); + if (found && found.length > 0) { + return found[0].uri; + } + return undefined; + }, + getWorkspaceFolderCount: (): number => { + return folders.length; + }, + getConfigurationValue: (folderUri: URI, section: string): string | undefined => { + return configurationService.getConfiguration(undefined, folderUri).get(section); + }, + getExecPath: (): string | undefined => { + return env ? env['VSCODE_EXEC_PATH'] : undefined; + }, + getFilePath: (): string | undefined => { + const activeEditor = editorService.activeEditor(); + if (activeEditor) { + return path.normalize(activeEditor.document.uri.fsPath); + } + return undefined; + }, + getSelectedText: (): string | undefined => { + const activeEditor = editorService.activeEditor(); + if (activeEditor && !activeEditor.selection.isEmpty) { + return activeEditor.document.getText(activeEditor.selection); + } + return undefined; + }, + getLineNumber: (): string | undefined => { + const activeEditor = editorService.activeEditor(); + if (activeEditor) { + return String(activeEditor.selection.end.line + 1); + } + return undefined; + } + }, env); + } +} + +interface ConfigProviderTuple { + type: string; + handle: number; + provider: vscode.DebugConfigurationProvider; +} + +interface DescriptorFactoryTuple { + type: string; + handle: number; + factory: vscode.DebugAdapterDescriptorFactory; +} + +interface TrackerFactoryTuple { + type: string; + handle: number; + factory: vscode.DebugAdapterTrackerFactory; +} + +class MultiTracker implements vscode.DebugAdapterTracker { + + constructor(private trackers: vscode.DebugAdapterTracker[]) { + } + + onWillStartSession(): void { + this.trackers.forEach(t => t.onWillStartSession ? t.onWillStartSession() : undefined); + } + + onWillReceiveMessage(message: any): void { + this.trackers.forEach(t => t.onWillReceiveMessage ? t.onWillReceiveMessage(message) : undefined); + } + + onDidSendMessage(message: any): void { + this.trackers.forEach(t => t.onDidSendMessage ? t.onDidSendMessage(message) : undefined); + } + + onWillStopSession(): void { + this.trackers.forEach(t => t.onWillStopSession ? t.onWillStopSession() : undefined); + } + + onError(error: Error): void { + this.trackers.forEach(t => t.onError ? t.onError(error) : undefined); + } + + onExit(code: number, signal: string): void { + this.trackers.forEach(t => t.onExit ? t.onExit(code, signal) : undefined); + } +} + +/* + * Call directly into a debug adapter implementation + */ +class DirectDebugAdapter extends AbstractDebugAdapter { + + constructor(private implementation: vscode.DebugAdapter) { + super(); + + if (this.implementation.onSendMessage) { + implementation.onSendMessage((message: DebugProtocol.ProtocolMessage) => { + this.acceptMessage(message); + }); + } + } + + startSession(): Promise { + return Promise.resolve(undefined); + } + + sendMessage(message: DebugProtocol.ProtocolMessage): void { + if (this.implementation.handleMessage) { + this.implementation.handleMessage(message); + } + } + + stopSession(): Promise { + if (this.implementation.dispose) { + this.implementation.dispose(); + } + return Promise.resolve(undefined); + } +} + + +export class WorkerExtHostDebugService extends ExtHostDebugServiceBase { + constructor( + @IExtHostRpcService extHostRpcService: IExtHostRpcService, + @IExtHostWorkspace workspaceService: IExtHostWorkspace, + @IExtHostExtensionService extensionService: IExtHostExtensionService, + @IExtHostDocumentsAndEditors editorsService: IExtHostDocumentsAndEditors, + @IExtHostConfiguration configurationService: IExtHostConfiguration, + @IExtHostCommands commandService: IExtHostCommands + ) { + super(extHostRpcService, workspaceService, extensionService, editorsService, configurationService, commandService); + } +} diff --git a/src/vs/workbench/api/common/extHostDecorations.ts b/src/vs/workbench/api/common/extHostDecorations.ts index 507eaca49096..6317bbe84e3f 100644 --- a/src/vs/workbench/api/common/extHostDecorations.ts +++ b/src/vs/workbench/api/common/extHostDecorations.ts @@ -12,6 +12,7 @@ import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { asArray } from 'vs/base/common/arrays'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; +import { ILogService } from 'vs/platform/log/common/log'; interface ProviderData { provider: vscode.DecorationProvider; @@ -28,6 +29,7 @@ export class ExtHostDecorations implements IExtHostDecorations { constructor( @IExtHostRpcService extHostRpc: IExtHostRpcService, + @ILogService private readonly _logService: ILogService, ) { this._proxy = extHostRpc.getProxy(MainContext.MainThreadDecorations); } @@ -66,10 +68,10 @@ export class ExtHostDecorations implements IExtHostDecorations { Decoration.validate(data); result[id] = [data.priority, data.bubble, data.title, data.letter, data.color]; } catch (e) { - console.warn(`INVALID decoration from extension '${extensionId.value}': ${e}`); + this._logService.warn(`INVALID decoration from extension '${extensionId.value}': ${e}`); } }, err => { - console.error(err); + this._logService.error(err); }); })).then(() => { diff --git a/src/vs/workbench/api/common/extHostDiagnostics.ts b/src/vs/workbench/api/common/extHostDiagnostics.ts index 7c810fb08fae..3c9292d5e241 100644 --- a/src/vs/workbench/api/common/extHostDiagnostics.ts +++ b/src/vs/workbench/api/common/extHostDiagnostics.ts @@ -13,6 +13,7 @@ import * as converter from './extHostTypeConverters'; import { mergeSort } from 'vs/base/common/arrays'; import { Event, Emitter } from 'vs/base/common/event'; import { keys } from 'vs/base/common/map'; +import { ILogService } from 'vs/platform/log/common/log'; export class DiagnosticCollection implements vscode.DiagnosticCollection { @@ -253,7 +254,7 @@ export class ExtHostDiagnostics implements ExtHostDiagnosticsShape { readonly onDidChangeDiagnostics: Event = Event.map(Event.debounce(this._onDidChangeDiagnostics.event, ExtHostDiagnostics._debouncer, 50), ExtHostDiagnostics._mapper); - constructor(mainContext: IMainContext) { + constructor(mainContext: IMainContext, @ILogService private readonly _logService: ILogService) { this._proxy = mainContext.getProxy(MainContext.MainThreadDiagnostics); } @@ -266,7 +267,7 @@ export class ExtHostDiagnostics implements ExtHostDiagnosticsShape { } else if (!_collections.has(name)) { owner = name; } else { - console.warn(`DiagnosticCollection with name '${name}' does already exist.`); + this._logService.warn(`DiagnosticCollection with name '${name}' does already exist.`); do { owner = name + ExtHostDiagnostics._idPool++; } while (_collections.has(owner)); diff --git a/src/vs/workbench/api/common/extHostDocumentData.ts b/src/vs/workbench/api/common/extHostDocumentData.ts index ebffa07ccf52..fb883e475acf 100644 --- a/src/vs/workbench/api/common/extHostDocumentData.ts +++ b/src/vs/workbench/api/common/extHostDocumentData.ts @@ -28,7 +28,6 @@ export class ExtHostDocumentData extends MirrorTextModel { private _languageId: string; private _isDirty: boolean; private _document?: vscode.TextDocument; - private _textLines: vscode.TextLine[] = []; private _isDisposed: boolean = false; constructor(proxy: MainThreadDocumentsShape, uri: URI, lines: string[], eol: string, @@ -130,33 +129,11 @@ export class ExtHostDocumentData extends MirrorTextModel { line = lineOrPosition; } - if (typeof line !== 'number' || line < 0 || line >= this._lines.length) { + if (typeof line !== 'number' || line < 0 || line >= this._lines.length || Math.floor(line) !== line) { throw new Error('Illegal value for `line`'); } - let result = this._textLines[line]; - if (!result || result.lineNumber !== line || result.text !== this._lines[line]) { - - const text = this._lines[line]; - const firstNonWhitespaceCharacterIndex = /^(\s*)/.exec(text)![1].length; - const range = new Range(line, 0, line, text.length); - const rangeIncludingLineBreak = line < this._lines.length - 1 - ? new Range(line, 0, line + 1, 0) - : range; - - result = Object.freeze({ - lineNumber: line, - range, - rangeIncludingLineBreak, - text, - firstNonWhitespaceCharacterIndex, //TODO@api, rename to 'leadingWhitespaceLength' - isEmptyOrWhitespace: firstNonWhitespaceCharacterIndex === text.length - }); - - this._textLines[line] = result; - } - - return result; + return new ExtHostDocumentLine(line, this._lines[line], line === this._lines.length - 1); } private _offsetAt(position: vscode.Position): number { @@ -239,8 +216,7 @@ export class ExtHostDocumentData extends MirrorTextModel { } else if (regExpLeadsToEndlessLoop(regexp)) { // use default when custom-regexp is bad - console.warn(`[getWordRangeAtPosition]: ignoring custom regexp '${regexp.source}' because it matches the empty string.`); - regexp = getWordDefinitionFor(this._languageId); + throw new Error(`[getWordRangeAtPosition]: ignoring custom regexp '${regexp.source}' because it matches the empty string.`); } const wordAtText = getWordAtText( @@ -256,3 +232,44 @@ export class ExtHostDocumentData extends MirrorTextModel { return undefined; } } + +class ExtHostDocumentLine implements vscode.TextLine { + + private readonly _line: number; + private readonly _text: string; + private readonly _isLastLine: boolean; + + constructor(line: number, text: string, isLastLine: boolean) { + this._line = line; + this._text = text; + this._isLastLine = isLastLine; + } + + public get lineNumber(): number { + return this._line; + } + + public get text(): string { + return this._text; + } + + public get range(): Range { + return new Range(this._line, 0, this._line, this._text.length); + } + + public get rangeIncludingLineBreak(): Range { + if (this._isLastLine) { + return this.range; + } + return new Range(this._line, 0, this._line + 1, 0); + } + + public get firstNonWhitespaceCharacterIndex(): number { + //TODO@api, rename to 'leadingWhitespaceLength' + return /^(\s*)/.exec(this._text)![1].length; + } + + public get isEmptyOrWhitespace(): boolean { + return this.firstNonWhitespaceCharacterIndex === this._text.length; + } +} diff --git a/src/vs/workbench/api/common/extHostDocumentSaveParticipant.ts b/src/vs/workbench/api/common/extHostDocumentSaveParticipant.ts index a5339ea50db8..f9af051daefa 100644 --- a/src/vs/workbench/api/common/extHostDocumentSaveParticipant.ts +++ b/src/vs/workbench/api/common/extHostDocumentSaveParticipant.ts @@ -11,7 +11,7 @@ import { ExtHostDocumentSaveParticipantShape, MainThreadTextEditorsShape, IResou import { TextEdit } from 'vs/workbench/api/common/extHostTypes'; import { Range, TextDocumentSaveReason, EndOfLine } from 'vs/workbench/api/common/extHostTypeConverters'; import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; -import { SaveReason } from 'vs/workbench/services/textfile/common/textfiles'; +import { SaveReason } from 'vs/workbench/common/editor'; import * as vscode from 'vscode'; import { LinkedList } from 'vs/base/common/linkedList'; import { ILogService } from 'vs/platform/log/common/log'; diff --git a/src/vs/workbench/api/common/extHostDocuments.ts b/src/vs/workbench/api/common/extHostDocuments.ts index 298957e986de..164b4d446c6f 100644 --- a/src/vs/workbench/api/common/extHostDocuments.ts +++ b/src/vs/workbench/api/common/extHostDocuments.ts @@ -12,6 +12,7 @@ import { ExtHostDocumentData, setWordDefinitionFor } from 'vs/workbench/api/comm import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import * as TypeConverters from 'vs/workbench/api/common/extHostTypeConverters'; import * as vscode from 'vscode'; +import { assertIsDefined } from 'vs/base/common/types'; export class ExtHostDocuments implements ExtHostDocumentsShape { @@ -84,7 +85,7 @@ export class ExtHostDocuments implements ExtHostDocumentsShape { if (!promise) { promise = this._proxy.$tryOpenDocument(uri).then(() => { this._documentLoader.delete(uri.toString()); - return this._documentsAndEditors.getDocument(uri); + return assertIsDefined(this._documentsAndEditors.getDocument(uri)); }, err => { this._documentLoader.delete(uri.toString()); return Promise.reject(err); diff --git a/src/vs/workbench/api/common/extHostExtensionActivator.ts b/src/vs/workbench/api/common/extHostExtensionActivator.ts index be047c9beac3..ff00ea50a587 100644 --- a/src/vs/workbench/api/common/extHostExtensionActivator.ts +++ b/src/vs/workbench/api/common/extHostExtensionActivator.ts @@ -9,6 +9,7 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { ExtensionActivationError, MissingDependencyError } from 'vs/workbench/services/extensions/common/extensions'; +import { ILogService } from 'vs/platform/log/common/log'; const NO_OP_VOID_PROMISE = Promise.resolve(undefined); @@ -182,7 +183,13 @@ export class ExtensionsActivator { */ private readonly _alreadyActivatedEvents: { [activationEvent: string]: boolean; }; - constructor(registry: ExtensionDescriptionRegistry, resolvedExtensions: ExtensionIdentifier[], hostExtensions: ExtensionIdentifier[], host: IExtensionsActivatorHost) { + constructor( + registry: ExtensionDescriptionRegistry, + resolvedExtensions: ExtensionIdentifier[], + hostExtensions: ExtensionIdentifier[], + host: IExtensionsActivatorHost, + @ILogService private readonly _logService: ILogService + ) { this._registry = registry; this._resolvedExtensionsSet = new Set(); resolvedExtensions.forEach((extensionId) => this._resolvedExtensionsSet.add(ExtensionIdentifier.toKey(extensionId))); @@ -308,7 +315,6 @@ export class ExtensionsActivator { } private _activateExtensions(extensions: ActivationIdAndReason[]): Promise { - // console.log('_activateExtensions: ', extensions.map(p => p.id.value)); if (extensions.length === 0) { return Promise.resolve(undefined); } @@ -335,9 +341,6 @@ export class ExtensionsActivator { const green = Object.keys(greenMap).map(id => greenMap[id]); - // console.log('greenExtensions: ', green.map(p => p.id.value)); - // console.log('redExtensions: ', red.map(p => p.id.value)); - if (red.length === 0) { // Finally reached only leafs! return Promise.all(green.map((p) => this._activateExtension(p.id, p.reason))).then(_ => undefined); @@ -362,8 +365,8 @@ export class ExtensionsActivator { const newlyActivatingExtension = this._host.actualActivateExtension(extensionId, reason).then(undefined, (err) => { this._host.onExtensionActivationError(extensionId, nls.localize('activationError', "Activating extension '{0}' failed: {1}.", extensionId.value, err.message)); - console.error('Activating extension `' + extensionId.value + '` failed: ', err.message); - console.log('Here is the error stack: ', err.stack); + this._logService.error(`Activating extension ${extensionId.value} failed due to an error:`); + this._logService.error(err); // Treat the extension as being empty return new FailedExtension(err); }).then((x: ActivatedExtension) => { diff --git a/src/vs/workbench/api/common/extHostExtensionService.ts b/src/vs/workbench/api/common/extHostExtensionService.ts index afcfa22e1e27..a406a9082c1f 100644 --- a/src/vs/workbench/api/common/extHostExtensionService.ts +++ b/src/vs/workbench/api/common/extHostExtensionService.ts @@ -133,20 +133,26 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio const hostExtensions = new Set(); this._initData.hostExtensions.forEach((extensionId) => hostExtensions.add(ExtensionIdentifier.toKey(extensionId))); - this._activator = new ExtensionsActivator(this._registry, this._initData.resolvedExtensions, this._initData.hostExtensions, { - onExtensionActivationError: (extensionId: ExtensionIdentifier, error: ExtensionActivationError): void => { - this._mainThreadExtensionsProxy.$onExtensionActivationError(extensionId, error); - }, - - actualActivateExtension: async (extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise => { - if (hostExtensions.has(ExtensionIdentifier.toKey(extensionId))) { - await this._mainThreadExtensionsProxy.$activateExtension(extensionId, reason); - return new HostExtension(); + this._activator = new ExtensionsActivator( + this._registry, + this._initData.resolvedExtensions, + this._initData.hostExtensions, + { + onExtensionActivationError: (extensionId: ExtensionIdentifier, error: ExtensionActivationError): void => { + this._mainThreadExtensionsProxy.$onExtensionActivationError(extensionId, error); + }, + + actualActivateExtension: async (extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise => { + if (hostExtensions.has(ExtensionIdentifier.toKey(extensionId))) { + await this._mainThreadExtensionsProxy.$activateExtension(extensionId, reason); + return new HostExtension(); + } + const extensionDescription = this._registry.getExtensionDescription(extensionId)!; + return this._activateExtension(extensionDescription, reason); } - const extensionDescription = this._registry.getExtensionDescription(extensionId)!; - return this._activateExtension(extensionDescription, reason); - } - }); + }, + this._logService + ); this._extensionPathIndex = null; this._resolvers = Object.create(null); this._started = false; @@ -405,7 +411,7 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio // Handle "eager" activation extensions private _handleEagerExtensions(): Promise { this._activateByEvent('*', true).then(undefined, (err) => { - console.error(err); + this._logService.error(err); }); this._disposables.add(this._extHostWorkspace.onDidChangeWorkspace((e) => this._handleWorkspaceContainsEagerExtensions(e.added))); @@ -467,7 +473,7 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio // the file was found return ( this._activateById(extensionId, { startup: true, extensionId, activationEvent: `workspaceContains:${fileName}` }) - .then(undefined, err => console.error(err)) + .then(undefined, err => this._logService.error(err)) ); } } @@ -488,7 +494,7 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio const timer = setTimeout(async () => { tokenSource.cancel(); this._activateById(extensionId, { startup: true, extensionId, activationEvent: `workspaceContainsTimeout:${globPatterns.join(',')}` }) - .then(undefined, err => console.error(err)); + .then(undefined, err => this._logService.error(err)); }, AbstractExtHostExtensionService.WORKSPACE_CONTAINS_TIMEOUT); let exists: boolean = false; @@ -496,7 +502,7 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio exists = await searchP; } catch (err) { if (!errors.isPromiseCanceledError(err)) { - console.error(err); + this._logService.error(err); } } @@ -507,7 +513,7 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio // a file was found matching one of the glob patterns return ( this._activateById(extensionId, { startup: true, extensionId, activationEvent: `workspaceContains:${globPatterns.join(',')}` }) - .then(undefined, err => console.error(err)) + .then(undefined, err => this._logService.error(err)) ); } diff --git a/src/vs/workbench/api/common/extHostFileSystem.ts b/src/vs/workbench/api/common/extHostFileSystem.ts index fc1908ee2f15..6af3598b401d 100644 --- a/src/vs/workbench/api/common/extHostFileSystem.ts +++ b/src/vs/workbench/api/common/extHostFileSystem.ts @@ -44,6 +44,7 @@ class FsLinkProvider { const edges: Edge[] = []; let prevScheme: string | undefined; let prevState: State; + let lastState = State.LastKnownState; let nextState = State.LastKnownState; for (const scheme of schemes) { @@ -60,6 +61,8 @@ class FsLinkProvider { // keep creating new (next) states until the // end (and the BeforeColon-state) is reached if (pos + 1 === scheme.length) { + // Save the last state here, because we need to continue for the next scheme + lastState = nextState; nextState = State.BeforeColon; } else { nextState += 1; @@ -70,6 +73,8 @@ class FsLinkProvider { } prevScheme = scheme; + // Restore the last state + nextState = lastState; } // all link must match this pattern `:/` diff --git a/src/vs/workbench/api/common/extHostFileSystemEventService.ts b/src/vs/workbench/api/common/extHostFileSystemEventService.ts index e295d4f4b3b6..898d84ac905e 100644 --- a/src/vs/workbench/api/common/extHostFileSystemEventService.ts +++ b/src/vs/workbench/api/common/extHostFileSystemEventService.ts @@ -3,16 +3,19 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { flatten } from 'vs/base/common/arrays'; -import { AsyncEmitter, Emitter, Event } from 'vs/base/common/event'; +import { AsyncEmitter, Emitter, Event, IWaitUntil } from 'vs/base/common/event'; import { IRelativePattern, parse } from 'vs/base/common/glob'; import { URI, UriComponents } from 'vs/base/common/uri'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import * as vscode from 'vscode'; -import { ExtHostFileSystemEventServiceShape, FileSystemEvents, IMainContext, MainContext, IResourceFileEditDto, IResourceTextEditDto, MainThreadTextEditorsShape } from './extHost.protocol'; +import { ExtHostFileSystemEventServiceShape, FileSystemEvents, IMainContext, MainContext, MainThreadTextEditorsShape, IResourceFileEditDto, IResourceTextEditDto } from './extHost.protocol'; import * as typeConverter from './extHostTypeConverters'; import { Disposable, WorkspaceEdit } from './extHostTypes'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { FileOperation } from 'vs/platform/files/common/files'; +import { flatten } from 'vs/base/common/arrays'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { ILogService } from 'vs/platform/log/common/log'; class FileSystemWatcher implements vscode.FileSystemWatcher { @@ -96,87 +99,132 @@ class FileSystemWatcher implements vscode.FileSystemWatcher { } } -interface WillRenameListener { +interface IExtensionListener { extension: IExtensionDescription; - (e: vscode.FileWillRenameEvent): any; + (e: E): any; } export class ExtHostFileSystemEventService implements ExtHostFileSystemEventServiceShape { - private readonly _onFileEvent = new Emitter(); + private readonly _onFileSystemEvent = new Emitter(); + private readonly _onDidRenameFile = new Emitter(); + private readonly _onDidCreateFile = new Emitter(); + private readonly _onDidDeleteFile = new Emitter(); private readonly _onWillRenameFile = new AsyncEmitter(); + private readonly _onWillCreateFile = new AsyncEmitter(); + private readonly _onWillDeleteFile = new AsyncEmitter(); readonly onDidRenameFile: Event = this._onDidRenameFile.event; + readonly onDidCreateFile: Event = this._onDidCreateFile.event; + readonly onDidDeleteFile: Event = this._onDidDeleteFile.event; + constructor( mainContext: IMainContext, + private readonly _logService: ILogService, private readonly _extHostDocumentsAndEditors: ExtHostDocumentsAndEditors, private readonly _mainThreadTextEditors: MainThreadTextEditorsShape = mainContext.getProxy(MainContext.MainThreadTextEditors) ) { // } - public createFileSystemWatcher(globPattern: string | IRelativePattern, ignoreCreateEvents?: boolean, ignoreChangeEvents?: boolean, ignoreDeleteEvents?: boolean): vscode.FileSystemWatcher { - return new FileSystemWatcher(this._onFileEvent.event, globPattern, ignoreCreateEvents, ignoreChangeEvents, ignoreDeleteEvents); + //--- file events + + createFileSystemWatcher(globPattern: string | IRelativePattern, ignoreCreateEvents?: boolean, ignoreChangeEvents?: boolean, ignoreDeleteEvents?: boolean): vscode.FileSystemWatcher { + return new FileSystemWatcher(this._onFileSystemEvent.event, globPattern, ignoreCreateEvents, ignoreChangeEvents, ignoreDeleteEvents); } $onFileEvent(events: FileSystemEvents) { - this._onFileEvent.fire(events); + this._onFileSystemEvent.fire(events); } - $onFileRename(oldUri: UriComponents, newUri: UriComponents) { - this._onDidRenameFile.fire(Object.freeze({ oldUri: URI.revive(oldUri), newUri: URI.revive(newUri) })); + + //--- file operations + + $onDidRunFileOperation(operation: FileOperation, target: UriComponents, source: UriComponents | undefined): void { + switch (operation) { + case FileOperation.MOVE: + this._onDidRenameFile.fire(Object.freeze({ files: [{ oldUri: URI.revive(source!), newUri: URI.revive(target) }] })); + break; + case FileOperation.DELETE: + this._onDidDeleteFile.fire(Object.freeze({ files: [URI.revive(target)] })); + break; + case FileOperation.CREATE: + this._onDidCreateFile.fire(Object.freeze({ files: [URI.revive(target)] })); + break; + default: + //ignore, dont send + } } + getOnWillRenameFileEvent(extension: IExtensionDescription): Event { + return this._createWillExecuteEvent(extension, this._onWillRenameFile); + } + + getOnWillCreateFileEvent(extension: IExtensionDescription): Event { + return this._createWillExecuteEvent(extension, this._onWillCreateFile); + } + + getOnWillDeleteFileEvent(extension: IExtensionDescription): Event { + return this._createWillExecuteEvent(extension, this._onWillDeleteFile); + } + + private _createWillExecuteEvent(extension: IExtensionDescription, emitter: AsyncEmitter): Event { return (listener, thisArg, disposables) => { - const wrappedListener: WillRenameListener = ((e: vscode.FileWillRenameEvent) => { - listener.call(thisArg, e); - }); + const wrappedListener: IExtensionListener = function wrapped(e: E) { listener.call(thisArg, e); }; wrappedListener.extension = extension; - return this._onWillRenameFile.event(wrappedListener, undefined, disposables); + return emitter.event(wrappedListener, undefined, disposables); }; } - $onWillRename(oldUriDto: UriComponents, newUriDto: UriComponents): Promise { - const oldUri = URI.revive(oldUriDto); - const newUri = URI.revive(newUriDto); + async $onWillRunFileOperation(operation: FileOperation, target: UriComponents, source: UriComponents | undefined, timeout: number, token: CancellationToken): Promise { + switch (operation) { + case FileOperation.MOVE: + await this._fireWillEvent(this._onWillRenameFile, { files: [{ oldUri: URI.revive(source!), newUri: URI.revive(target) }] }, timeout, token); + break; + case FileOperation.DELETE: + await this._fireWillEvent(this._onWillDeleteFile, { files: [URI.revive(target)] }, timeout, token); + break; + case FileOperation.CREATE: + await this._fireWillEvent(this._onWillCreateFile, { files: [URI.revive(target)] }, timeout, token); + break; + default: + //ignore, dont send + } + } + + private async _fireWillEvent(emitter: AsyncEmitter, data: Omit, timeout: number, token: CancellationToken): Promise { const edits: WorkspaceEdit[] = []; - return Promise.resolve(this._onWillRenameFile.fireAsync((bucket, _listener) => { - return { - oldUri, - newUri, - waitUntil: (thenable: Promise): void => { - if (Object.isFrozen(bucket)) { - throw new TypeError('waitUntil cannot be called async'); - } - const index = bucket.length; - const wrappedThenable = Promise.resolve(thenable).then(result => { - // ignore all results except for WorkspaceEdits. Those - // are stored in a spare array - if (result instanceof WorkspaceEdit) { - edits[index] = result; - } - }); - bucket.push(wrappedThenable); - } - }; - }).then((): any => { - if (edits.length === 0) { - return undefined; + + await emitter.fireAsync(data, token, async (thenable, listener: IExtensionListener) => { + // ignore all results except for WorkspaceEdits. Those are stored in an array. + const now = Date.now(); + const result = await Promise.resolve(thenable); + if (result instanceof WorkspaceEdit) { + edits.push(result); + } + + if (Date.now() - now > timeout) { + this._logService.warn('SLOW file-participant', listener.extension?.identifier); } + }); + + if (token.isCancellationRequested) { + return; + } + + if (edits.length > 0) { // flatten all WorkspaceEdits collected via waitUntil-call // and apply them in one go. const allEdits = new Array>(); for (let edit of edits) { - if (edit) { // sparse array - let { edits } = typeConverter.WorkspaceEdit.from(edit, this._extHostDocumentsAndEditors); - allEdits.push(edits); - } + let { edits } = typeConverter.WorkspaceEdit.from(edit, this._extHostDocumentsAndEditors); + allEdits.push(edits); } return this._mainThreadTextEditors.$tryApplyWorkspaceEdit({ edits: flatten(allEdits) }); - })); + } } } diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index 26e006ae984f..8560d7f70c08 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -7,7 +7,7 @@ import { URI, UriComponents } from 'vs/base/common/uri'; import { mixin } from 'vs/base/common/objects'; import * as vscode from 'vscode'; import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; -import { Range, Disposable, CompletionList, SnippetString, CodeActionKind, SymbolInformation, DocumentSymbol } from 'vs/workbench/api/common/extHostTypes'; +import { Range, Disposable, CompletionList, SnippetString, CodeActionKind, SymbolInformation, DocumentSymbol, SemanticTokensEdits } from 'vs/workbench/api/common/extHostTypes'; import { ISingleEditOperation } from 'vs/editor/common/model'; import * as modes from 'vs/editor/common/modes'; import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; @@ -26,6 +26,9 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { IURITransformer } from 'vs/base/common/uriIpc'; import { DisposableStore, dispose } from 'vs/base/common/lifecycle'; +import { VSBuffer } from 'vs/base/common/buffer'; +import { encodeSemanticTokensDto } from 'vs/workbench/api/common/shared/semanticTokens'; +import { IdGenerator } from 'vs/base/common/idGenerator'; // --- adapter @@ -348,7 +351,7 @@ class CodeActionAdapter { only: context.only ? new CodeActionKind(context.only) : undefined }; - return asPromise(() => this._provider.provideCodeActions(doc, ran, codeActionContext, token)).then(commandsOrActions => { + return asPromise(() => this._provider.provideCodeActions(doc, ran, codeActionContext, token)).then((commandsOrActions): extHostProtocol.ICodeActionListDto | undefined => { if (!isNonEmptyArray(commandsOrActions) || token.isCancellationRequested) { return undefined; } @@ -386,11 +389,12 @@ class CodeActionAdapter { edit: candidate.edit && typeConvert.WorkspaceEdit.from(candidate.edit), kind: candidate.kind && candidate.kind.value, isPreferred: candidate.isPreferred, + disabled: candidate.disabled }); } } - return { cacheId, actions }; + return { cacheId, actions }; }); } @@ -471,13 +475,13 @@ class OnTypeFormattingAdapter { class NavigateTypeAdapter { - private readonly _symbolCache: { [id: number]: vscode.SymbolInformation } = Object.create(null); - private readonly _resultCache: { [id: number]: [number, number] } = Object.create(null); - private readonly _provider: vscode.WorkspaceSymbolProvider; + private readonly _symbolCache = new Map(); + private readonly _resultCache = new Map(); - constructor(provider: vscode.WorkspaceSymbolProvider) { - this._provider = provider; - } + constructor( + private readonly _provider: vscode.WorkspaceSymbolProvider, + private readonly _logService: ILogService + ) { } provideWorkspaceSymbols(search: string, token: CancellationToken): Promise { const result: extHostProtocol.IWorkspaceSymbolsDto = extHostProtocol.IdObject.mixin({ symbols: [] }); @@ -489,44 +493,42 @@ class NavigateTypeAdapter { continue; } if (!item.name) { - console.warn('INVALID SymbolInformation, lacks name', item); + this._logService.warn('INVALID SymbolInformation, lacks name', item); continue; } const symbol = extHostProtocol.IdObject.mixin(typeConvert.WorkspaceSymbol.from(item)); - this._symbolCache[symbol._id!] = item; + this._symbolCache.set(symbol._id!, item); result.symbols.push(symbol); } } }).then(() => { if (result.symbols.length > 0) { - this._resultCache[result._id!] = [result.symbols[0]._id!, result.symbols[result.symbols.length - 1]._id!]; + this._resultCache.set(result._id!, [result.symbols[0]._id!, result.symbols[result.symbols.length - 1]._id!]); } return result; }); } - resolveWorkspaceSymbol(symbol: extHostProtocol.IWorkspaceSymbolDto, token: CancellationToken): Promise { - + async resolveWorkspaceSymbol(symbol: extHostProtocol.IWorkspaceSymbolDto, token: CancellationToken): Promise { if (typeof this._provider.resolveWorkspaceSymbol !== 'function') { - return Promise.resolve(symbol); + return symbol; } - const item = this._symbolCache[symbol._id!]; + const item = this._symbolCache.get(symbol._id!); if (item) { - return asPromise(() => this._provider.resolveWorkspaceSymbol!(item, token)).then(value => { - return value && mixin(symbol, typeConvert.WorkspaceSymbol.from(value), true); - }); + const value = await asPromise(() => this._provider.resolveWorkspaceSymbol!(item, token)); + return value && mixin(symbol, typeConvert.WorkspaceSymbol.from(value), true); } - return Promise.resolve(undefined); + return undefined; } releaseWorkspaceSymbols(id: number): any { - const range = this._resultCache[id]; + const range = this._resultCache.get(id); if (range) { for (let [from, to] = range; from <= to; from++) { - delete this._symbolCache[from]; + this._symbolCache.delete(from); } - delete this._resultCache[id]; + this._resultCache.delete(id); } } } @@ -539,7 +541,8 @@ class RenameAdapter { constructor( private readonly _documents: ExtHostDocuments, - private readonly _provider: vscode.RenameProvider + private readonly _provider: vscode.RenameProvider, + private readonly _logService: ILogService ) { } provideRenameEdits(resource: URI, position: IPosition, newName: string, token: CancellationToken): Promise { @@ -588,7 +591,7 @@ class RenameAdapter { return undefined; } if (range.start.line > pos.line || range.end.line < pos.line) { - console.warn('INVALID rename location: position line must be within range start/end lines'); + this._logService.warn('INVALID rename location: position line must be within range start/end lines'); return undefined; } return { range: typeConvert.Range.from(range), text }; @@ -613,30 +616,148 @@ class RenameAdapter { } } +class SemanticTokensPreviousResult { + constructor( + public readonly resultId: string | undefined, + public readonly tokens?: Uint32Array, + ) { } +} + +export class SemanticTokensAdapter { + + private readonly _previousResults: Map; + private _nextResultId = 1; + + constructor( + private readonly _documents: ExtHostDocuments, + private readonly _provider: vscode.SemanticTokensProvider, + ) { + this._previousResults = new Map(); + } + + provideSemanticTokens(resource: URI, ranges: IRange[] | null, previousResultId: number, token: CancellationToken): Promise { + const doc = this._documents.getDocument(resource); + const previousResult = (previousResultId !== 0 ? this._previousResults.get(previousResultId) : null); + const opts: vscode.SemanticTokensRequestOptions = { + ranges: (Array.isArray(ranges) && ranges.length > 0 ? ranges.map(typeConvert.Range.to) : undefined), + previousResultId: (previousResult ? previousResult.resultId : undefined) + }; + return asPromise(() => this._provider.provideSemanticTokens(doc, opts, token)).then(value => { + if (!value) { + return null; + } + if (previousResult) { + this._previousResults.delete(previousResultId); + } + return this._send(SemanticTokensAdapter._convertToEdits(previousResult, value), value); + }); + } + + async releaseSemanticColoring(semanticColoringResultId: number): Promise { + this._previousResults.delete(semanticColoringResultId); + } + + private static _isSemanticTokens(v: vscode.SemanticTokens | vscode.SemanticTokensEdits): v is vscode.SemanticTokens { + return v && !!((v as vscode.SemanticTokens).data); + } + + private static _isSemanticTokensEdits(v: vscode.SemanticTokens | vscode.SemanticTokensEdits): v is vscode.SemanticTokensEdits { + return v && Array.isArray((v as vscode.SemanticTokensEdits).edits); + } + + private static _convertToEdits(previousResult: SemanticTokensPreviousResult | null | undefined, newResult: vscode.SemanticTokens | vscode.SemanticTokensEdits): vscode.SemanticTokens | vscode.SemanticTokensEdits { + if (!SemanticTokensAdapter._isSemanticTokens(newResult)) { + return newResult; + } + if (!previousResult || !previousResult.tokens) { + return newResult; + } + const oldData = previousResult.tokens; + const oldLength = oldData.length; + const newData = newResult.data; + const newLength = newData.length; + + let commonPrefixLength = 0; + const maxCommonPrefixLength = Math.min(oldLength, newLength); + while (commonPrefixLength < maxCommonPrefixLength && oldData[commonPrefixLength] === newData[commonPrefixLength]) { + commonPrefixLength++; + } + + if (commonPrefixLength === oldLength && commonPrefixLength === newLength) { + // complete overlap! + return new SemanticTokensEdits([], newResult.resultId); + } + + let commonSuffixLength = 0; + const maxCommonSuffixLength = maxCommonPrefixLength - commonPrefixLength; + while (commonSuffixLength < maxCommonSuffixLength && oldData[oldLength - commonSuffixLength - 1] === newData[newLength - commonSuffixLength - 1]) { + commonSuffixLength++; + } + + return new SemanticTokensEdits([{ + start: commonPrefixLength, + deleteCount: (oldLength - commonPrefixLength - commonSuffixLength), + data: newData.subarray(commonPrefixLength, newLength - commonSuffixLength) + }], newResult.resultId); + } + + private _send(value: vscode.SemanticTokens | vscode.SemanticTokensEdits, original: vscode.SemanticTokens | vscode.SemanticTokensEdits): VSBuffer | null { + if (SemanticTokensAdapter._isSemanticTokens(value)) { + const myId = this._nextResultId++; + this._previousResults.set(myId, new SemanticTokensPreviousResult(value.resultId, value.data)); + return encodeSemanticTokensDto({ + id: myId, + type: 'full', + data: value.data + }); + } + + if (SemanticTokensAdapter._isSemanticTokensEdits(value)) { + const myId = this._nextResultId++; + if (SemanticTokensAdapter._isSemanticTokens(original)) { + // store the original + this._previousResults.set(myId, new SemanticTokensPreviousResult(original.resultId, original.data)); + } else { + this._previousResults.set(myId, new SemanticTokensPreviousResult(value.resultId)); + } + return encodeSemanticTokensDto({ + id: myId, + type: 'delta', + deltas: (value.edits || []).map(edit => ({ start: edit.start, deleteCount: edit.deleteCount, data: edit.data })) + }); + } + + return null; + } +} + class SuggestAdapter { static supportsResolving(provider: vscode.CompletionItemProvider): boolean { return typeof provider.resolveCompletionItem === 'function'; } - private _documents: ExtHostDocuments; - private _commands: CommandsConverter; - private _provider: vscode.CompletionItemProvider; - private _cache = new Cache('CompletionItem'); private _disposables = new Map(); - constructor(documents: ExtHostDocuments, commands: CommandsConverter, provider: vscode.CompletionItemProvider) { - this._documents = documents; - this._commands = commands; - this._provider = provider; - } + constructor( + private readonly _documents: ExtHostDocuments, + private readonly _commands: CommandsConverter, + private readonly _provider: vscode.CompletionItemProvider, + private readonly _logService: ILogService + ) { } provideCompletionItems(resource: URI, position: IPosition, context: modes.CompletionContext, token: CancellationToken): Promise { const doc = this._documents.getDocument(resource); const pos = typeConvert.Position.to(position); + // The default insert/replace ranges. It's important to compute them + // before asynchronously asking the provider for its results. See + // https://github.com/microsoft/vscode/issues/83400#issuecomment-546851421 + const replaceRange = doc.getWordRangeAtPosition(pos) || new Range(pos, pos); + const insertRange = replaceRange.with({ end: pos }); + return asPromise(() => this._provider.provideCompletionItems(doc, pos, token, typeConvert.CompletionContext.to(context))).then(value => { if (!value) { @@ -657,14 +778,10 @@ class SuggestAdapter { const disposables = new DisposableStore(); this._disposables.set(pid, disposables); - // the default text edit range - const wordRangeBeforePos = (doc.getWordRangeAtPosition(pos) as Range || new Range(pos, pos)) - .with({ end: pos }); - const result: extHostProtocol.ISuggestResultDto = { x: pid, b: [], - a: typeConvert.Range.from(wordRangeBeforePos), + a: { replace: typeConvert.Range.from(replaceRange), insert: typeConvert.Range.from(insertRange) }, c: list.isIncomplete || undefined }; @@ -711,7 +828,7 @@ class SuggestAdapter { private _convertCompletionItem(item: vscode.CompletionItem, position: vscode.Position, id: extHostProtocol.ChainedCacheId): extHostProtocol.ISuggestDataDto | undefined { if (typeof item.label !== 'string' || item.label.length === 0) { - console.warn('INVALID text edit -> must have at least a label'); + this._logService.warn('INVALID text edit -> must have at least a label'); return undefined; } @@ -751,21 +868,46 @@ class SuggestAdapter { } // 'overwrite[Before|After]'-logic - let range: vscode.Range | undefined; + let range: vscode.Range | { inserting: vscode.Range, replacing: vscode.Range; } | undefined; if (item.textEdit) { range = item.textEdit.range; } else if (item.range) { range = item.range; + } else if (item.range2) { + range = item.range2; } - result[extHostProtocol.ISuggestDataDtoField.range] = typeConvert.Range.from(range); - if (range && (!range.isSingleLine || range.start.line !== position.line)) { - console.warn('INVALID text edit -> must be single line and on the same line'); - return undefined; + if (range) { + if (Range.isRange(range)) { + if (!SuggestAdapter._isValidRangeForCompletion(range, position)) { + this._logService.trace('INVALID range -> must be single line and on the same line'); + return undefined; + } + result[extHostProtocol.ISuggestDataDtoField.range] = typeConvert.Range.from(range); + + } else { + if ( + !SuggestAdapter._isValidRangeForCompletion(range.inserting, position) + || !SuggestAdapter._isValidRangeForCompletion(range.replacing, position) + || !range.inserting.start.isEqual(range.replacing.start) + || !range.replacing.contains(range.inserting) + ) { + this._logService.trace('INVALID range -> must be single line, on the same line, insert range must be a prefix of replace range'); + return undefined; + } + result[extHostProtocol.ISuggestDataDtoField.range] = { + insert: typeConvert.Range.from(range.inserting), + replace: typeConvert.Range.from(range.replacing) + }; + } } return result; } + + private static _isValidRangeForCompletion(range: vscode.Range, position: vscode.Position): boolean { + return range.isSingleLine || range.start.line === position.line; + } } class SignatureHelpAdapter { @@ -966,7 +1108,8 @@ class SelectionRangeAdapter { constructor( private readonly _documents: ExtHostDocuments, - private readonly _provider: vscode.SelectionRangeProvider + private readonly _provider: vscode.SelectionRangeProvider, + private readonly _logService: ILogService ) { } provideSelectionRanges(resource: URI, pos: IPosition[], token: CancellationToken): Promise { @@ -978,7 +1121,7 @@ class SelectionRangeAdapter { return []; } if (allProviderRanges.length !== positions.length) { - console.warn('BAD selection ranges, provider must return ranges for each position'); + this._logService.warn('BAD selection ranges, provider must return ranges for each position'); return []; } @@ -1009,37 +1152,95 @@ class SelectionRangeAdapter { class CallHierarchyAdapter { + private readonly _idPool = new IdGenerator(''); + private readonly _cache = new Map>(); + constructor( private readonly _documents: ExtHostDocuments, - private readonly _provider: vscode.CallHierarchyItemProvider + private readonly _provider: vscode.CallHierarchyProvider ) { } - async provideCallsTo(uri: URI, position: IPosition, token: CancellationToken): Promise<[extHostProtocol.ICallHierarchyItemDto, IRange[]][] | undefined> { + async prepareSession(uri: URI, position: IPosition, token: CancellationToken): Promise { const doc = this._documents.getDocument(uri); const pos = typeConvert.Position.to(position); - const calls = await this._provider.provideCallHierarchyIncomingCalls(doc, pos, token); + + const item = await this._provider.prepareCallHierarchy(doc, pos, token); + if (!item) { + return undefined; + } + const sessionId = this._idPool.nextId(); + + this._cache.set(sessionId, new Map()); + return this._cacheAndConvertItem(sessionId, item); + } + + async provideCallsTo(sessionId: string, itemId: string, token: CancellationToken): Promise { + const item = this._itemFromCache(sessionId, itemId); + if (!item) { + throw new Error('missing call hierarchy item'); + } + const calls = await this._provider.provideCallHierarchyIncomingCalls(item, token); if (!calls) { return undefined; } - return calls.map(call => (<[extHostProtocol.ICallHierarchyItemDto, IRange[]]>[typeConvert.CallHierarchyItem.from(call.from), call.fromRanges.map(typeConvert.Range.from)])); + return calls.map(call => { + return { + from: this._cacheAndConvertItem(sessionId, call.from), + fromRanges: call.fromRanges.map(r => typeConvert.Range.from(r)) + }; + }); } - async provideCallsFrom(uri: URI, position: IPosition, token: CancellationToken): Promise<[extHostProtocol.ICallHierarchyItemDto, IRange[]][] | undefined> { - const doc = this._documents.getDocument(uri); - const pos = typeConvert.Position.to(position); - const calls = await this._provider.provideCallHierarchyOutgoingCalls(doc, pos, token); + async provideCallsFrom(sessionId: string, itemId: string, token: CancellationToken): Promise { + const item = this._itemFromCache(sessionId, itemId); + if (!item) { + throw new Error('missing call hierarchy item'); + } + const calls = await this._provider.provideCallHierarchyOutgoingCalls(item, token); if (!calls) { return undefined; } - return calls.map(call => (<[extHostProtocol.ICallHierarchyItemDto, IRange[]]>[typeConvert.CallHierarchyItem.from(call.to), call.fromRanges.map(typeConvert.Range.from)])); + return calls.map(call => { + return { + to: this._cacheAndConvertItem(sessionId, call.to), + fromRanges: call.fromRanges.map(r => typeConvert.Range.from(r)) + }; + }); + } + + releaseSession(sessionId: string): void { + this._cache.delete(sessionId.charAt(0)); + } + + private _cacheAndConvertItem(itemOrSessionId: string, item: vscode.CallHierarchyItem): extHostProtocol.ICallHierarchyItemDto { + const sessionId = itemOrSessionId.charAt(0); + const map = this._cache.get(sessionId)!; + const dto: extHostProtocol.ICallHierarchyItemDto = { + _sessionId: sessionId, + _itemId: map.size.toString(36), + name: item.name, + detail: item.detail, + kind: typeConvert.SymbolKind.from(item.kind), + uri: item.uri, + range: typeConvert.Range.from(item.range), + selectionRange: typeConvert.Range.from(item.selectionRange), + }; + map.set(dto._itemId, item); + return dto; + } + + private _itemFromCache(sessionId: string, itemId: string): vscode.CallHierarchyItem | undefined { + const map = this._cache.get(sessionId); + return map && map.get(itemId); } } type Adapter = DocumentSymbolAdapter | CodeLensAdapter | DefinitionAdapter | HoverAdapter | DocumentHighlightAdapter | ReferenceAdapter | CodeActionAdapter | DocumentFormattingAdapter | RangeFormattingAdapter | OnTypeFormattingAdapter | NavigateTypeAdapter | RenameAdapter - | SuggestAdapter | SignatureHelpAdapter | LinkProviderAdapter | ImplementationAdapter | TypeDefinitionAdapter - | ColorProviderAdapter | FoldingProviderAdapter | DeclarationAdapter | SelectionRangeAdapter | CallHierarchyAdapter; + | SemanticTokensAdapter | SuggestAdapter | SignatureHelpAdapter | LinkProviderAdapter + | ImplementationAdapter | TypeDefinitionAdapter | ColorProviderAdapter | FoldingProviderAdapter + | DeclarationAdapter | SelectionRangeAdapter | CallHierarchyAdapter; class AdapterData { constructor( @@ -1119,7 +1320,7 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF return ExtHostLanguageFeatures._handlePool++; } - private _withAdapter(handle: number, ctor: { new(...args: any[]): A }, callback: (adapter: A, extension: IExtensionDescription | undefined) => Promise, fallbackValue: R): Promise { + private _withAdapter(handle: number, ctor: { new(...args: any[]): A; }, callback: (adapter: A, extension: IExtensionDescription | undefined) => Promise, fallbackValue: R): Promise { const data = this._adapter.get(handle); if (!data) { return Promise.resolve(fallbackValue); @@ -1330,7 +1531,7 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF // --- navigate types registerWorkspaceSymbolProvider(extension: IExtensionDescription, provider: vscode.WorkspaceSymbolProvider): vscode.Disposable { - const handle = this._addNewAdapter(new NavigateTypeAdapter(provider), extension); + const handle = this._addNewAdapter(new NavigateTypeAdapter(provider, this._logService), extension); this._proxy.$registerNavigateTypeSupport(handle); return this._createDisposable(handle); } @@ -1350,7 +1551,7 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF // --- rename registerRenameProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.RenameProvider): vscode.Disposable { - const handle = this._addNewAdapter(new RenameAdapter(this._documents, provider), extension); + const handle = this._addNewAdapter(new RenameAdapter(this._documents, provider, this._logService), extension); this._proxy.$registerRenameSupport(handle, this._transformDocumentSelector(selector), RenameAdapter.supportsResolving(provider)); return this._createDisposable(handle); } @@ -1363,10 +1564,28 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF return this._withAdapter(handle, RenameAdapter, adapter => adapter.resolveRenameLocation(URI.revive(resource), position, token), undefined); } + //#region semantic coloring + + registerSemanticTokensProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.SemanticTokensProvider, legend: vscode.SemanticTokensLegend): vscode.Disposable { + const handle = this._addNewAdapter(new SemanticTokensAdapter(this._documents, provider), extension); + this._proxy.$registerSemanticTokensProvider(handle, this._transformDocumentSelector(selector), legend); + return this._createDisposable(handle); + } + + $provideSemanticTokens(handle: number, resource: UriComponents, ranges: IRange[] | null, previousResultId: number, token: CancellationToken): Promise { + return this._withAdapter(handle, SemanticTokensAdapter, adapter => adapter.provideSemanticTokens(URI.revive(resource), ranges, previousResultId, token), null); + } + + $releaseSemanticTokens(handle: number, semanticColoringResultId: number): void { + this._withAdapter(handle, SemanticTokensAdapter, adapter => adapter.releaseSemanticColoring(semanticColoringResultId), undefined); + } + + //#endregion + // --- suggestion registerCompletionItemProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.CompletionItemProvider, triggerCharacters: string[]): vscode.Disposable { - const handle = this._addNewAdapter(new SuggestAdapter(this._documents, this._commands.converter, provider), extension); + const handle = this._addNewAdapter(new SuggestAdapter(this._documents, this._commands.converter, provider, this._logService), extension); this._proxy.$registerSuggestSupport(handle, this._transformDocumentSelector(selector), triggerCharacters, SuggestAdapter.supportsResolving(provider), extension.identifier); return this._createDisposable(handle); } @@ -1450,7 +1669,7 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF // --- smart select registerSelectionRangeProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.SelectionRangeProvider): vscode.Disposable { - const handle = this._addNewAdapter(new SelectionRangeAdapter(this._documents, provider), extension); + const handle = this._addNewAdapter(new SelectionRangeAdapter(this._documents, provider, this._logService), extension); this._proxy.$registerSelectionRangeProvider(handle, this._transformDocumentSelector(selector)); return this._createDisposable(handle); } @@ -1461,18 +1680,26 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF // --- call hierarchy - registerCallHierarchyProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.CallHierarchyItemProvider): vscode.Disposable { + registerCallHierarchyProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.CallHierarchyProvider): vscode.Disposable { const handle = this._addNewAdapter(new CallHierarchyAdapter(this._documents, provider), extension); this._proxy.$registerCallHierarchyProvider(handle, this._transformDocumentSelector(selector)); return this._createDisposable(handle); } - $provideCallHierarchyIncomingCalls(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<[extHostProtocol.ICallHierarchyItemDto, IRange[]][] | undefined> { - return this._withAdapter(handle, CallHierarchyAdapter, adapter => adapter.provideCallsTo(URI.revive(resource), position, token), undefined); + $prepareCallHierarchy(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise { + return this._withAdapter(handle, CallHierarchyAdapter, adapter => Promise.resolve(adapter.prepareSession(URI.revive(resource), position, token)), undefined); + } + + $provideCallHierarchyIncomingCalls(handle: number, sessionId: string, itemId: string, token: CancellationToken): Promise { + return this._withAdapter(handle, CallHierarchyAdapter, adapter => adapter.provideCallsTo(sessionId, itemId, token), undefined); + } + + $provideCallHierarchyOutgoingCalls(handle: number, sessionId: string, itemId: string, token: CancellationToken): Promise { + return this._withAdapter(handle, CallHierarchyAdapter, adapter => adapter.provideCallsFrom(sessionId, itemId, token), undefined); } - $provideCallHierarchyOutgoingCalls(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<[extHostProtocol.ICallHierarchyItemDto, IRange[]][] | undefined> { - return this._withAdapter(handle, CallHierarchyAdapter, adapter => adapter.provideCallsFrom(URI.revive(resource), position, token), undefined); + $releaseCallHierarchy(handle: number, sessionId: string): void { + this._withAdapter(handle, CallHierarchyAdapter, adapter => Promise.resolve(adapter.releaseSession(sessionId)), undefined); } // --- configuration diff --git a/src/vs/workbench/api/common/extHostMessageService.ts b/src/vs/workbench/api/common/extHostMessageService.ts index db03a9f4d64c..f28190fc8210 100644 --- a/src/vs/workbench/api/common/extHostMessageService.ts +++ b/src/vs/workbench/api/common/extHostMessageService.ts @@ -7,6 +7,7 @@ import Severity from 'vs/base/common/severity'; import * as vscode from 'vscode'; import { MainContext, MainThreadMessageServiceShape, MainThreadMessageOptions, IMainContext } from './extHost.protocol'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { ILogService } from 'vs/platform/log/common/log'; function isMessageItem(item: any): item is vscode.MessageItem { return item && item.title; @@ -16,7 +17,10 @@ export class ExtHostMessageService { private _proxy: MainThreadMessageServiceShape; - constructor(mainContext: IMainContext) { + constructor( + mainContext: IMainContext, + @ILogService private readonly _logService: ILogService + ) { this._proxy = mainContext.getProxy(MainContext.MainThreadMessageService); } @@ -45,7 +49,7 @@ export class ExtHostMessageService { let { title, isCloseAffordance } = command; commands.push({ title, isCloseAffordance: !!isCloseAffordance, handle }); } else { - console.warn('Invalid message item:', command); + this._logService.warn('Invalid message item:', command); } } diff --git a/src/vs/workbench/api/common/extHostQuickOpen.ts b/src/vs/workbench/api/common/extHostQuickOpen.ts index 68c953e2981c..5b087491c8a0 100644 --- a/src/vs/workbench/api/common/extHostQuickOpen.ts +++ b/src/vs/workbench/api/common/extHostQuickOpen.ts @@ -135,7 +135,7 @@ export class ExtHostQuickOpen implements ExtHostQuickOpenShape { // ---- input - showInput(options?: InputBoxOptions, token: CancellationToken = CancellationToken.None): Promise { + showInput(options?: InputBoxOptions, token: CancellationToken = CancellationToken.None): Promise { // global validate fn used in callback below this._validateInput = options ? options.validateInput : undefined; @@ -160,7 +160,7 @@ export class ExtHostQuickOpen implements ExtHostQuickOpenShape { // ---- workspace folder picker showWorkspaceFolderPick(options?: WorkspaceFolderPickOptions, token = CancellationToken.None): Promise { - return this._commands.executeCommand('_workbench.pickWorkspaceFolder', [options]).then(async (selectedFolder: WorkspaceFolder) => { + return this._commands.executeCommand('_workbench.pickWorkspaceFolder', [options]).then(async (selectedFolder: WorkspaceFolder) => { if (!selectedFolder) { return undefined; } @@ -485,6 +485,7 @@ class ExtHostQuickPick extends ExtHostQuickInput implem private _canSelectMany = false; private _matchOnDescription = true; private _matchOnDetail = true; + private _sortByLabel = true; private _activeItems: T[] = []; private readonly _onDidChangeActiveEmitter = new Emitter(); private _selectedItems: T[] = []; @@ -550,6 +551,15 @@ class ExtHostQuickPick extends ExtHostQuickInput implem this.update({ matchOnDetail }); } + get sortByLabel() { + return this._sortByLabel; + } + + set sortByLabel(sortByLabel: boolean) { + this._sortByLabel = sortByLabel; + this.update({ sortByLabel }); + } + get activeItems() { return this._activeItems; } diff --git a/src/vs/workbench/api/common/extHostRequireInterceptor.ts b/src/vs/workbench/api/common/extHostRequireInterceptor.ts index 669f1e472588..67209e8096a5 100644 --- a/src/vs/workbench/api/common/extHostRequireInterceptor.ts +++ b/src/vs/workbench/api/common/extHostRequireInterceptor.ts @@ -18,6 +18,7 @@ import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitData import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService'; import { platform } from 'vs/base/common/process'; +import { ILogService } from 'vs/platform/log/common/log'; import { AzdataNodeModuleFactory, SqlopsNodeModuleFactory } from 'sql/workbench/api/common/extHostRequireInterceptor'; // {{SQL CARBON EDIT}} import { IExtensionApiFactory as sqlIApiFactory } from 'sql/workbench/api/common/sqlExtHost.api.impl'; // {{SQL CARBON EDIT}} @@ -44,7 +45,8 @@ export abstract class RequireInterceptor { @IInstantiationService private readonly _instaService: IInstantiationService, @IExtHostConfiguration private readonly _extHostConfiguration: IExtHostConfiguration, @IExtHostExtensionService private readonly _extHostExtensionService: IExtHostExtensionService, - @IExtHostInitDataService private readonly _initData: IExtHostInitDataService + @IExtHostInitDataService private readonly _initData: IExtHostInitDataService, + @ILogService private readonly _logService: ILogService, ) { this._factories = new Map(); this._alternatives = []; @@ -57,7 +59,7 @@ export abstract class RequireInterceptor { const configProvider = await this._extHostConfiguration.getConfigProvider(); const extensionPaths = await this._extHostExtensionService.getExtensionPathIndex(); - this.register(new VSCodeNodeModuleFactory(this._apiFactory.vscode, extensionPaths, this._extensionRegistry, configProvider)); // {{SQL CARBON EDIT}} // add node module + this.register(new VSCodeNodeModuleFactory(this._apiFactory.vscode, extensionPaths, this._extensionRegistry, configProvider, this._logService)); // {{SQL CARBON EDIT}} // add node module this.register(new AzdataNodeModuleFactory(this._apiFactory.azdata, extensionPaths)); // {{SQL CARBON EDIT}} // add node module this.register(new SqlopsNodeModuleFactory(this._apiFactory.sqlops, extensionPaths)); // {{SQL CARBON EDIT}} // add node module this.register(this._instaService.createInstance(KeytarNodeModuleFactory)); @@ -96,7 +98,8 @@ class VSCodeNodeModuleFactory implements INodeModuleFactory { private readonly _apiFactory: IExtensionApiFactory, private readonly _extensionPaths: TernarySearchTree, private readonly _extensionRegistry: ExtensionDescriptionRegistry, - private readonly _configProvider: ExtHostConfigProvider + private readonly _configProvider: ExtHostConfigProvider, + private readonly _logService: ILogService, ) { } @@ -117,7 +120,7 @@ class VSCodeNodeModuleFactory implements INodeModuleFactory { if (!this._defaultApiImpl) { let extensionPathsPretty = ''; this._extensionPaths.forEach((value, index) => extensionPathsPretty += `\t${index} -> ${value.identifier.value}\n`); - console.warn(`Could not identify extension for 'vscode' require call from ${parent.fsPath}. These are the extension path mappings: \n${extensionPathsPretty}`); + this._logService.warn(`Could not identify extension for 'vscode' require call from ${parent.fsPath}. These are the extension path mappings: \n${extensionPathsPretty}`); this._defaultApiImpl = this._apiFactory(nullExtensionDescription, this._extensionRegistry, this._configProvider); } return this._defaultApiImpl; @@ -247,9 +250,9 @@ class OpenNodeModuleFactory implements INodeModuleFactory { return this.callOriginal(target, options); } if (uri.scheme === 'http' || uri.scheme === 'https') { - return mainThreadWindow.$openUri(uri, { allowTunneling: true }); + return mainThreadWindow.$openUri(uri, target, { allowTunneling: true }); } else if (uri.scheme === 'mailto' || uri.scheme === this._appUriScheme) { - return mainThreadWindow.$openUri(uri, {}); + return mainThreadWindow.$openUri(uri, target, {}); } return this.callOriginal(target, options); }; diff --git a/src/vs/workbench/api/common/extHostSCM.ts b/src/vs/workbench/api/common/extHostSCM.ts index a2f1fe8e2703..658c93c135a0 100644 --- a/src/vs/workbench/api/common/extHostSCM.ts +++ b/src/vs/workbench/api/common/extHostSCM.ts @@ -180,8 +180,7 @@ export class ExtHostSCMInputBox implements vscode.SourceControlInputBox { } if (fn && typeof fn !== 'function') { - console.warn('Invalid SCM input box validation function'); - return; + throw new Error(`[${this._extension.identifier.value}]: Invalid SCM input box validation function`); } this._validateInput = fn; diff --git a/src/vs/workbench/api/common/extHostSearch.ts b/src/vs/workbench/api/common/extHostSearch.ts index 90fd4a5df371..6543a942d592 100644 --- a/src/vs/workbench/api/common/extHostSearch.ts +++ b/src/vs/workbench/api/common/extHostSearch.ts @@ -3,10 +3,17 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import * as vscode from 'vscode'; -import { ExtHostSearchShape } from '../common/extHost.protocol'; +import { ExtHostSearchShape, MainThreadSearchShape, MainContext } from '../common/extHost.protocol'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { FileSearchManager } from 'vs/workbench/services/search/common/fileSearchManager'; +import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; +import { IURITransformerService } from 'vs/workbench/api/common/extHostUriTransformerService'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IRawFileQuery, ISearchCompleteStats, IFileQuery, IRawTextQuery, IRawQuery, ITextQuery, IFolderQuery } from 'vs/workbench/services/search/common/search'; +import { URI, UriComponents } from 'vs/base/common/uri'; +import { TextSearchManager } from 'vs/workbench/services/search/common/textSearchManager'; export interface IExtHostSearch extends ExtHostSearchShape { registerTextSearchProvider(scheme: string, provider: vscode.TextSearchProvider): IDisposable; @@ -14,3 +21,111 @@ export interface IExtHostSearch extends ExtHostSearchShape { } export const IExtHostSearch = createDecorator('IExtHostSearch'); + +export class ExtHostSearch implements ExtHostSearchShape { + + protected readonly _proxy: MainThreadSearchShape = this.extHostRpc.getProxy(MainContext.MainThreadSearch); + protected _handlePool: number = 0; + + private readonly _textSearchProvider = new Map(); + private readonly _textSearchUsedSchemes = new Set(); + private readonly _fileSearchProvider = new Map(); + private readonly _fileSearchUsedSchemes = new Set(); + + private readonly _fileSearchManager = new FileSearchManager(); + + constructor( + @IExtHostRpcService private extHostRpc: IExtHostRpcService, + @IURITransformerService protected _uriTransformer: IURITransformerService, + @ILogService protected _logService: ILogService + ) { } + + protected _transformScheme(scheme: string): string { + return this._uriTransformer.transformOutgoingScheme(scheme); + } + + registerTextSearchProvider(scheme: string, provider: vscode.TextSearchProvider): IDisposable { + if (this._textSearchUsedSchemes.has(scheme)) { + throw new Error(`a text search provider for the scheme '${scheme}' is already registered`); + } + + this._textSearchUsedSchemes.add(scheme); + const handle = this._handlePool++; + this._textSearchProvider.set(handle, provider); + this._proxy.$registerTextSearchProvider(handle, this._transformScheme(scheme)); + return toDisposable(() => { + this._textSearchUsedSchemes.delete(scheme); + this._textSearchProvider.delete(handle); + this._proxy.$unregisterProvider(handle); + }); + } + + registerFileSearchProvider(scheme: string, provider: vscode.FileSearchProvider): IDisposable { + if (this._fileSearchUsedSchemes.has(scheme)) { + throw new Error(`a file search provider for the scheme '${scheme}' is already registered`); + } + + this._fileSearchUsedSchemes.add(scheme); + const handle = this._handlePool++; + this._fileSearchProvider.set(handle, provider); + this._proxy.$registerFileSearchProvider(handle, this._transformScheme(scheme)); + return toDisposable(() => { + this._fileSearchUsedSchemes.delete(scheme); + this._fileSearchProvider.delete(handle); + this._proxy.$unregisterProvider(handle); + }); + } + + $provideFileSearchResults(handle: number, session: number, rawQuery: IRawFileQuery, token: vscode.CancellationToken): Promise { + const query = reviveQuery(rawQuery); + const provider = this._fileSearchProvider.get(handle); + if (provider) { + return this._fileSearchManager.fileSearch(query, provider, batch => { + this._proxy.$handleFileMatch(handle, session, batch.map(p => p.resource)); + }, token); + } else { + throw new Error('unknown provider: ' + handle); + } + } + + $clearCache(cacheKey: string): Promise { + this._fileSearchManager.clearCache(cacheKey); + + return Promise.resolve(undefined); + } + + $provideTextSearchResults(handle: number, session: number, rawQuery: IRawTextQuery, token: vscode.CancellationToken): Promise { + const provider = this._textSearchProvider.get(handle); + if (!provider || !provider.provideTextSearchResults) { + throw new Error(`Unknown provider ${handle}`); + } + + const query = reviveQuery(rawQuery); + const engine = this.createTextSearchManager(query, provider); + return engine.search(progress => this._proxy.$handleTextMatch(handle, session, progress), token); + } + + protected createTextSearchManager(query: ITextQuery, provider: vscode.TextSearchProvider): TextSearchManager { + return new TextSearchManager(query, provider, { + readdir: resource => Promise.resolve([]), // TODO@rob implement + toCanonicalName: encoding => encoding + }); + } +} + +export function reviveQuery(rawQuery: U): U extends IRawTextQuery ? ITextQuery : IFileQuery { + return { + ...rawQuery, // TODO@rob ??? + ...{ + folderQueries: rawQuery.folderQueries && rawQuery.folderQueries.map(reviveFolderQuery), + extraFileResources: rawQuery.extraFileResources && rawQuery.extraFileResources.map(components => URI.revive(components)) + } + }; +} + +function reviveFolderQuery(rawFolderQuery: IFolderQuery): IFolderQuery { + return { + ...rawFolderQuery, + folder: URI.revive(rawFolderQuery.folder) + }; +} diff --git a/src/vs/workbench/api/common/extHostTask.ts b/src/vs/workbench/api/common/extHostTask.ts index 417bc4b63e57..e41b8ac5d85a 100644 --- a/src/vs/workbench/api/common/extHostTask.ts +++ b/src/vs/workbench/api/common/extHostTask.ts @@ -24,6 +24,7 @@ import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitData import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { Schemas } from 'vs/base/common/network'; import * as Platform from 'vs/base/common/platform'; +import { ILogService } from 'vs/platform/log/common/log'; export interface IExtHostTask extends ExtHostTaskShape { @@ -374,6 +375,7 @@ export abstract class ExtHostTaskBase implements ExtHostTaskShape { protected readonly _editorService: IExtHostDocumentsAndEditors; protected readonly _configurationService: IExtHostConfiguration; protected readonly _terminalService: IExtHostTerminalService; + protected readonly _logService: ILogService; protected _handleCounter: number; protected _handlers: Map; protected _taskExecutions: Map; @@ -393,7 +395,8 @@ export abstract class ExtHostTaskBase implements ExtHostTaskShape { @IExtHostWorkspace workspaceService: IExtHostWorkspace, @IExtHostDocumentsAndEditors editorService: IExtHostDocumentsAndEditors, @IExtHostConfiguration configurationService: IExtHostConfiguration, - @IExtHostTerminalService extHostTerminalService: IExtHostTerminalService + @IExtHostTerminalService extHostTerminalService: IExtHostTerminalService, + @ILogService logService: ILogService ) { this._proxy = extHostRpc.getProxy(MainContext.MainThreadTask); this._workspaceProvider = workspaceService; @@ -406,6 +409,7 @@ export abstract class ExtHostTaskBase implements ExtHostTaskShape { this._providedCustomExecutions2 = new Map(); this._notProvidedCustomExecutions = new Set(); this._activeCustomExecutions2 = new Map(); + this._logService = logService; } public registerTaskProvider(extension: IExtensionDescription, type: string, provider: vscode.TaskProvider): vscode.Disposable { @@ -457,6 +461,10 @@ export abstract class ExtHostTaskBase implements ExtHostTaskShape { return this._onDidExecuteTask.event; } + protected async resolveDefinition(uri: number | UriComponents | undefined, definition: vscode.TaskDefinition | undefined): Promise { + return definition; + } + public async $onDidStartTask(execution: tasks.TaskExecutionDTO, terminalId: number): Promise { const customExecution: types.CustomExecution | undefined = this._providedCustomExecutions2.get(execution.id); if (customExecution) { @@ -466,7 +474,7 @@ export abstract class ExtHostTaskBase implements ExtHostTaskShape { // Clone the custom execution to keep the original untouched. This is important for multiple runs of the same task. this._activeCustomExecutions2.set(execution.id, customExecution); - this._terminalService.attachPtyToTerminal(terminalId, await customExecution.callback()); + this._terminalService.attachPtyToTerminal(terminalId, await customExecution.callback(await this.resolveDefinition(execution.task?.source.scope, execution.task?.definition))); } this._lastStartedTask = execution.id; @@ -657,9 +665,10 @@ export class WorkerExtHostTask extends ExtHostTaskBase { @IExtHostWorkspace workspaceService: IExtHostWorkspace, @IExtHostDocumentsAndEditors editorService: IExtHostDocumentsAndEditors, @IExtHostConfiguration configurationService: IExtHostConfiguration, - @IExtHostTerminalService extHostTerminalService: IExtHostTerminalService + @IExtHostTerminalService extHostTerminalService: IExtHostTerminalService, + @ILogService logService: ILogService ) { - super(extHostRpc, initData, workspaceService, editorService, configurationService, extHostTerminalService); + super(extHostRpc, initData, workspaceService, editorService, configurationService, extHostTerminalService, logService); if (initData.remote.isRemote && initData.remote.authority) { this.registerTaskSystem(Schemas.vscodeRemote, { scheme: Schemas.vscodeRemote, @@ -692,7 +701,7 @@ export class WorkerExtHostTask extends ExtHostTaskBase { if (value) { for (let task of value) { if (!task.definition || !validTypes[task.definition.type]) { - console.warn(`The task [${task.source}, ${task.name}] uses an undefined task type. The task will be ignored in the future.`); + this._logService.warn(`The task [${task.source}, ${task.name}] uses an undefined task type. The task will be ignored in the future.`); } const taskDTO: tasks.TaskDTO | undefined = TaskDTO.from(task, handler.extension); @@ -703,7 +712,7 @@ export class WorkerExtHostTask extends ExtHostTaskBase { // is invoked, we have to be able to map it back to our data. taskIdPromises.push(this.addCustomExecution(taskDTO, task, true)); } else { - console.warn('Only custom execution tasks supported.'); + this._logService.warn('Only custom execution tasks supported.'); } } } @@ -717,7 +726,7 @@ export class WorkerExtHostTask extends ExtHostTaskBase { if (CustomExecutionDTO.is(resolvedTaskDTO.execution)) { return resolvedTaskDTO; } else { - console.warn('Only custom execution tasks supported.'); + this._logService.warn('Only custom execution tasks supported.'); } return undefined; } diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts index 40d279aa7a46..f093e0444e89 100644 --- a/src/vs/workbench/api/common/extHostTerminalService.ts +++ b/src/vs/workbench/api/common/extHostTerminalService.ts @@ -9,9 +9,10 @@ import { ExtHostTerminalServiceShape, MainContext, MainThreadTerminalServiceShap import { ExtHostConfigProvider } from 'vs/workbench/api/common/extHostConfiguration'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { EXT_HOST_CREATION_DELAY, ITerminalChildProcess, ITerminalDimensions } from 'vs/workbench/contrib/terminal/common/terminal'; +import { ITerminalChildProcess, ITerminalDimensions, EXT_HOST_CREATION_DELAY } from 'vs/workbench/contrib/terminal/common/terminal'; import { timeout } from 'vs/base/common/async'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; +import { TerminalDataBufferer } from 'vs/workbench/contrib/terminal/common/terminalDataBuffering'; export interface IExtHostTerminalService extends ExtHostTerminalServiceShape { @@ -96,15 +97,18 @@ export class ExtHostTerminal extends BaseExtHostTerminal implements vscode.Termi private _cols: number | undefined; private _pidPromiseComplete: ((value: number | undefined) => any) | undefined; private _rows: number | undefined; + private _exitStatus: vscode.TerminalExitStatus | undefined; public isOpen: boolean = false; constructor( proxy: MainThreadTerminalServiceShape, + private readonly _creationOptions: vscode.TerminalOptions | vscode.ExtensionTerminalOptions, private _name?: string, id?: number ) { super(proxy, id); + this._creationOptions = Object.freeze(this._creationOptions); this._pidPromise = new Promise(c => this._pidPromiseComplete = c); } @@ -137,6 +141,10 @@ export class ExtHostTerminal extends BaseExtHostTerminal implements vscode.Termi this._name = name; } + public get exitStatus(): vscode.TerminalExitStatus | undefined { + return this._exitStatus; + } + public get dimensions(): vscode.TerminalDimensions | undefined { if (this._cols === undefined || this._rows === undefined) { return undefined; @@ -147,6 +155,10 @@ export class ExtHostTerminal extends BaseExtHostTerminal implements vscode.Termi }; } + public setExitCode(code: number | undefined) { + this._exitStatus = Object.freeze({ code }); + } + public setDimensions(cols: number, rows: number): boolean { if (cols === this._cols && rows === this._rows) { // Nothing changed @@ -161,6 +173,10 @@ export class ExtHostTerminal extends BaseExtHostTerminal implements vscode.Termi return this._pidPromise; } + public get creationOptions(): Readonly { + return this._creationOptions; + } + public sendText(text: string, addNewLine: boolean = true): void { this._checkDisposed(); this._queueApiRequest(this._proxy.$sendText, [text, addNewLine]); @@ -209,8 +225,8 @@ class ApiRequest { export class ExtHostPseudoterminal implements ITerminalChildProcess { private readonly _onProcessData = new Emitter(); public readonly onProcessData: Event = this._onProcessData.event; - private readonly _onProcessExit = new Emitter(); - public readonly onProcessExit: Event = this._onProcessExit.event; + private readonly _onProcessExit = new Emitter(); + public readonly onProcessExit: Event = this._onProcessExit.event; private readonly _onProcessReady = new Emitter<{ pid: number, cwd: string }>(); public get onProcessReady(): Event<{ pid: number, cwd: string }> { return this._onProcessReady.event; } private readonly _onProcessTitleChanged = new Emitter(); @@ -252,7 +268,9 @@ export class ExtHostPseudoterminal implements ITerminalChildProcess { // Attach the listeners this._pty.onDidWrite(e => this._onProcessData.fire(e)); if (this._pty.onDidClose) { - this._pty.onDidClose(e => this._onProcessExit.fire(e || 0)); + this._pty.onDidClose((e: number | void = undefined) => { + this._onProcessExit.fire(e === void 0 ? undefined : e as number); // {{SQL CARBON EDIT}} strict-null-checks + }); } if (this._pty.onDidOverrideDimensions) { this._pty.onDidOverrideDimensions(e => this._onProcessOverrideDimensions.fire(e ? { cols: e.columns, rows: e.rows } : undefined)); // {{SQL CARBONEDIT}} strict-null-checks @@ -270,6 +288,7 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ protected _activeTerminal: ExtHostTerminal | undefined; protected _terminals: ExtHostTerminal[] = []; protected _terminalProcesses: { [id: number]: ITerminalChildProcess } = {}; + protected _extensionTerminalAwaitingStart: { [id: number]: { initialDimensions: ITerminalDimensionsDto | undefined } | undefined } = {}; protected _getTerminalPromises: { [id: number]: Promise } = {}; public get activeTerminal(): ExtHostTerminal | undefined { return this._activeTerminal; } @@ -286,9 +305,13 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ protected readonly _onDidWriteTerminalData: Emitter; public get onDidWriteTerminalData(): Event { return this._onDidWriteTerminalData && this._onDidWriteTerminalData.event; } + private readonly _bufferer: TerminalDataBufferer; + constructor( @IExtHostRpcService extHostRpc: IExtHostRpcService ) { + this._bufferer = new TerminalDataBufferer(); + this._proxy = extHostRpc.getProxy(MainContext.MainThreadTerminalService); this._onDidWriteTerminalData = new Emitter({ onFirstListenerAdd: () => this._proxy.$startSendingDataEvents(), @@ -305,7 +328,7 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ public abstract $acceptWorkspacePermissionsChanged(isAllowed: boolean): void; public createExtensionTerminal(options: vscode.ExtensionTerminalOptions): vscode.Terminal { - const terminal = new ExtHostTerminal(this._proxy, options.name); + const terminal = new ExtHostTerminal(this._proxy, options, options.name); const p = new ExtHostPseudoterminal(options.pty); terminal.createExtensionTerminal().then(id => this._setupExtHostProcessListeners(id, p)); this._terminals.push(terminal); @@ -364,7 +387,7 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ if (this._terminalProcesses[id]) { // Extension pty terminal only - when virtual process resize fires it means that the // terminal's maximum dimensions changed - this._terminalProcesses[id].resize(cols, rows); + this._terminalProcesses[id]?.resize(cols, rows); } } @@ -376,16 +399,17 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ } } - public async $acceptTerminalClosed(id: number): Promise { + public async $acceptTerminalClosed(id: number, exitCode: number | undefined): Promise { await this._getTerminalByIdEventually(id); const index = this._getTerminalObjectIndexById(this.terminals, id); if (index !== null) { const terminal = this._terminals.splice(index, 1)[0]; + terminal.setExitCode(exitCode); this._onDidCloseTerminal.fire(terminal); } } - public $acceptTerminalOpened(id: number, name: string): void { + public $acceptTerminalOpened(id: number, name: string, shellLaunchConfigDto: IShellLaunchConfigDto): void { const index = this._getTerminalObjectIndexById(this._terminals, id); if (index !== null) { // The terminal has already been created (via createTerminal*), only fire the event @@ -394,7 +418,14 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ return; } - const terminal = new ExtHostTerminal(this._proxy, name, id); + const creationOptions: vscode.TerminalOptions = { + name: shellLaunchConfigDto.name, + shellPath: shellLaunchConfigDto.executable, + shellArgs: shellLaunchConfigDto.args, + cwd: typeof shellLaunchConfigDto.cwd === 'string' ? shellLaunchConfigDto.cwd : URI.revive(shellLaunchConfigDto.cwd), + env: shellLaunchConfigDto.env + }; + const terminal = new ExtHostTerminal(this._proxy, creationOptions, name, id); this._terminals.push(terminal); this._onDidOpenTerminal.fire(terminal); terminal.isOpen = true; @@ -407,22 +438,6 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ } } - public performTerminalIdAction(id: number, callback: (terminal: ExtHostTerminal) => void): void { - // TODO: Use await this._getTerminalByIdEventually(id); - let terminal = this._getTerminalById(id); - if (terminal) { - callback(terminal); - } else { - // Retry one more time in case the terminal has not yet been initialized. - setTimeout(() => { - terminal = this._getTerminalById(id); - if (terminal) { - callback(terminal); - } - }, EXT_HOST_CREATION_DELAY * 2); - } - } - public async $startExtensionTerminal(id: number, initialDimensions: ITerminalDimensionsDto | undefined): Promise { // Make sure the ExtHostTerminal exists so onDidOpenTerminal has fired before we call // Pseudoterminal.start @@ -448,37 +463,42 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ } await openPromise; - // Processes should be initialized here for normal virtual process terminals, however for - // tasks they are responsible for attaching the virtual process to a terminal so this - // function may be called before tasks is able to attach to the terminal. - let retries = 5; - while (retries-- > 0) { - if (this._terminalProcesses[id]) { - (this._terminalProcesses[id] as ExtHostPseudoterminal).startSendingEvents(initialDimensions); - return; - } - await timeout(50); + if (this._terminalProcesses[id]) { + (this._terminalProcesses[id] as ExtHostPseudoterminal).startSendingEvents(initialDimensions); + } else { + // Defer startSendingEvents call to when _setupExtHostProcessListeners is called + this._extensionTerminalAwaitingStart[id] = { initialDimensions }; } + } protected _setupExtHostProcessListeners(id: number, p: ITerminalChildProcess): void { p.onProcessReady((e: { pid: number, cwd: string }) => this._proxy.$sendProcessReady(id, e.pid, e.cwd)); p.onProcessTitleChanged(title => this._proxy.$sendProcessTitle(id, title)); - p.onProcessData(data => this._proxy.$sendProcessData(id, data)); + + // Buffer data events to reduce the amount of messages going to the renderer + this._bufferer.startBuffering(id, p.onProcessData, this._proxy.$sendProcessData); p.onProcessExit(exitCode => this._onProcessExit(id, exitCode)); + if (p.onProcessOverrideDimensions) { p.onProcessOverrideDimensions(e => this._proxy.$sendOverrideDimensions(id, e)); } this._terminalProcesses[id] = p; + + const awaitingStart = this._extensionTerminalAwaitingStart[id]; + if (awaitingStart && p instanceof ExtHostPseudoterminal) { + p.startSendingEvents(awaitingStart.initialDimensions); + delete this._extensionTerminalAwaitingStart[id]; + } } public $acceptProcessInput(id: number, data: string): void { - this._terminalProcesses[id].input(data); + this._terminalProcesses[id]?.input(data); } public $acceptProcessResize(id: number, cols: number, rows: number): void { try { - this._terminalProcesses[id].resize(cols, rows); + this._terminalProcesses[id]?.resize(cols, rows); } catch (error) { // We tried to write to a closed pipe / channel. if (error.code !== 'EPIPE' && error.code !== 'ERR_IPC_CHANNEL_CLOSED') { @@ -488,24 +508,27 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ } public $acceptProcessShutdown(id: number, immediate: boolean): void { - this._terminalProcesses[id].shutdown(immediate); + this._terminalProcesses[id]?.shutdown(immediate); } public $acceptProcessRequestInitialCwd(id: number): void { - this._terminalProcesses[id].getInitialCwd().then(initialCwd => this._proxy.$sendProcessInitialCwd(id, initialCwd)); + this._terminalProcesses[id]?.getInitialCwd().then(initialCwd => this._proxy.$sendProcessInitialCwd(id, initialCwd)); } public $acceptProcessRequestCwd(id: number): void { - this._terminalProcesses[id].getCwd().then(cwd => this._proxy.$sendProcessCwd(id, cwd)); + this._terminalProcesses[id]?.getCwd().then(cwd => this._proxy.$sendProcessCwd(id, cwd)); } public $acceptProcessRequestLatency(id: number): number { return id; } - private _onProcessExit(id: number, exitCode: number): void { + private _onProcessExit(id: number, exitCode: number | undefined): void { + this._bufferer.stopBuffering(id); + // Remove process reference delete this._terminalProcesses[id]; + delete this._extensionTerminalAwaitingStart[id]; // Send exit event to main side this._proxy.$sendProcessExit(id, exitCode); @@ -515,10 +538,6 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ private _getTerminalByIdEventually(id: number, retries: number = 5): Promise { if (!this._getTerminalPromises[id]) { this._getTerminalPromises[id] = this._createGetTerminalPromise(id, retries); - } else { - this._getTerminalPromises[id].then(c => { - return this._createGetTerminalPromise(id, retries); - }); } return this._getTerminalPromises[id]; } @@ -536,7 +555,7 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ } else { // This should only be needed immediately after createTerminalRenderer is called as // the ExtHostTerminal has not yet been iniitalized - timeout(200).then(() => c(this._createGetTerminalPromise(id, retries - 1))); + timeout(EXT_HOST_CREATION_DELAY * 2).then(() => c(this._createGetTerminalPromise(id, retries - 1))); } }); } diff --git a/src/vs/workbench/api/common/extHostTreeViews.ts b/src/vs/workbench/api/common/extHostTreeViews.ts index d1147a7ec7c6..38992389a08a 100644 --- a/src/vs/workbench/api/common/extHostTreeViews.ts +++ b/src/vs/workbench/api/common/extHostTreeViews.ts @@ -95,12 +95,10 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape { get onDidChangeVisibility() { return treeView.onDidChangeVisibility; }, get message() { return treeView.message; }, set message(message: string) { - checkProposedApiEnabled(extension); treeView.message = message; }, get title() { return treeView.title; }, set title(title: string) { - checkProposedApiEnabled(extension); treeView.title = title; }, reveal: (element: T, options?: IRevealOptions): Promise => { @@ -202,7 +200,13 @@ export class ExtHostTreeView extends Disposable { private refreshPromise: Promise = Promise.resolve(); private refreshQueue: Promise = Promise.resolve(); - constructor(private viewId: string, options: vscode.TreeViewOptions, private proxy: MainThreadTreeViewsShape, private commands: CommandsConverter, private logService: ILogService, private extension: IExtensionDescription) { + constructor( + private viewId: string, options: vscode.TreeViewOptions, + private proxy: MainThreadTreeViewsShape, + private commands: CommandsConverter, + private logService: ILogService, + private extension: IExtensionDescription + ) { super(); if (extension.contributes && extension.contributes.views) { for (const location in extension.contributes.views) { @@ -257,7 +261,7 @@ export class ExtHostTreeView extends Disposable { getChildren(parentHandle: TreeItemHandle | Root): Promise { const parentElement = parentHandle ? this.getExtensionElement(parentHandle) : undefined; if (parentHandle && !parentElement) { - console.error(`No tree item with id \'${parentHandle}\' found.`); + this.logService.error(`No tree item with id \'${parentHandle}\' found.`); return Promise.resolve([]); } @@ -425,7 +429,7 @@ export class ExtHostTreeView extends Disposable { // check if an ancestor of extElement is already in the elements to update list let currentNode: TreeNode | undefined = elementNode; while (currentNode && currentNode.parent && !elementsToUpdate.has(currentNode.parent.item.handle)) { - const parentElement = this.elements.get(currentNode.parent.item.handle); + const parentElement: T | undefined = this.elements.get(currentNode.parent.item.handle); currentNode = parentElement ? this.nodes.get(parentElement) : undefined; } if (currentNode && !currentNode.parent) { diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 2b9bb7da9fa2..6ea563c07678 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -13,9 +13,9 @@ import { EndOfLineSequence, TrackedRangeStickiness } from 'vs/editor/common/mode import * as vscode from 'vscode'; import { URI, UriComponents } from 'vs/base/common/uri'; import { ProgressLocation as MainProgressLocation } from 'vs/platform/progress/common/progress'; -import { SaveReason } from 'vs/workbench/services/textfile/common/textfiles'; +import { SaveReason } from 'vs/workbench/common/editor'; import { IPosition } from 'vs/editor/common/core/position'; -import { IRange } from 'vs/editor/common/core/range'; +import * as editorRange from 'vs/editor/common/core/range'; import { ISelection } from 'vs/editor/common/core/selection'; import * as htmlContent from 'vs/base/common/htmlContent'; import * as languageSelector from 'vs/editor/common/modes/languageSelector'; @@ -31,7 +31,6 @@ import { LogLevel as _MainLogLevel } from 'vs/platform/log/common/log'; import { coalesce, isNonEmptyArray } from 'vs/base/common/arrays'; import { RenderLineNumbersType } from 'vs/editor/common/config/editorOptions'; - export interface PositionLike { line: number; character: number; @@ -68,9 +67,9 @@ export namespace Selection { export namespace Range { export function from(range: undefined): undefined; - export function from(range: RangeLike): IRange; - export function from(range: RangeLike | undefined): IRange | undefined; - export function from(range: RangeLike | undefined): IRange | undefined { + export function from(range: RangeLike): editorRange.IRange; + export function from(range: RangeLike | undefined): editorRange.IRange | undefined; + export function from(range: RangeLike | undefined): editorRange.IRange | undefined { if (!range) { return undefined; } @@ -84,9 +83,9 @@ export namespace Range { } export function to(range: undefined): types.Range; - export function to(range: IRange): types.Range; - export function to(range: IRange | undefined): types.Range | undefined; - export function to(range: IRange | undefined): types.Range | undefined { + export function to(range: editorRange.IRange): types.Range; + export function to(range: editorRange.IRange | undefined): types.Range | undefined; + export function to(range: editorRange.IRange | undefined): types.Range | undefined { if (!range) { return undefined; } @@ -260,7 +259,7 @@ export namespace MarkdownString { const collectUri = (href: string): string => { try { - let uri = URI.parse(href); + let uri = URI.parse(href, true); uri = uri.with({ query: _uriMassage(uri.query, resUris) }); resUris[href] = uri; } catch (e) { @@ -290,16 +289,23 @@ export namespace MarkdownString { if (!data) { return part; } + let changed = false; data = cloneAndChange(data, value => { if (URI.isUri(value)) { const key = `__uri_${Math.random().toString(16).slice(2, 8)}`; bucket[key] = value; + changed = true; return key; } else { return undefined; } }); - return encodeURIComponent(JSON.stringify(data)); + + if (!changed) { + return part; + } + + return JSON.stringify(data); } export function to(value: htmlContent.IMarkdownString): vscode.MarkdownString { @@ -627,19 +633,8 @@ export namespace DocumentSymbol { export namespace CallHierarchyItem { - export function from(item: vscode.CallHierarchyItem): extHostProtocol.ICallHierarchyItemDto { - return { - name: item.name, - detail: item.detail, - kind: SymbolKind.from(item.kind), - uri: item.uri, - range: Range.from(item.range), - selectionRange: Range.from(item.selectionRange), - }; - } - - export function to(item: extHostProtocol.ICallHierarchyItemDto): vscode.CallHierarchyItem { - return new types.CallHierarchyItem( + export function to(item: extHostProtocol.ICallHierarchyItemDto): types.CallHierarchyItem { + const result = new types.CallHierarchyItem( SymbolKind.to(item.kind), item.name, item.detail || '', @@ -647,6 +642,31 @@ export namespace CallHierarchyItem { Range.to(item.range), Range.to(item.selectionRange) ); + + result._sessionId = item._sessionId; + result._itemId = item._itemId; + + return result; + } +} + +export namespace CallHierarchyIncomingCall { + + export function to(item: extHostProtocol.IIncomingCallDto): types.CallHierarchyIncomingCall { + return new types.CallHierarchyIncomingCall( + CallHierarchyItem.to(item.from), + item.fromRanges.map(r => Range.to(r)) + ); + } +} + +export namespace CallHierarchyOutgoingCall { + + export function to(item: extHostProtocol.IOutgoingCallDto): types.CallHierarchyOutgoingCall { + return new types.CallHierarchyOutgoingCall( + CallHierarchyItem.to(item.to), + item.fromRanges.map(r => Range.to(r)) + ); } } @@ -821,14 +841,15 @@ export namespace CompletionItem { result.filterText = suggestion.filterText; result.preselect = suggestion.preselect; result.commitCharacters = suggestion.commitCharacters; - result.range = Range.to(suggestion.range); + result.range = editorRange.Range.isIRange(suggestion.range) ? Range.to(suggestion.range) : undefined; + result.range2 = editorRange.Range.isIRange(suggestion.range) ? undefined : { inserting: Range.to(suggestion.range.insert), replacing: Range.to(suggestion.range.replace) }; result.keepWhitespace = typeof suggestion.insertTextRules === 'undefined' ? false : Boolean(suggestion.insertTextRules & modes.CompletionItemInsertTextRule.KeepWhitespace); // 'inserText'-logic if (typeof suggestion.insertTextRules !== 'undefined' && suggestion.insertTextRules & modes.CompletionItemInsertTextRule.InsertAsSnippet) { result.insertText = new types.SnippetString(suggestion.insertText); } else { result.insertText = suggestion.insertText; - result.textEdit = new types.TextEdit(result.range, result.insertText); + result.textEdit = result.range instanceof types.Range ? new types.TextEdit(result.range, result.insertText) : undefined; } // TODO additionalEdits, command @@ -903,7 +924,7 @@ export namespace DocumentLink { let target: URI | undefined = undefined; if (link.url) { try { - target = typeof link.url === 'string' ? URI.parse(link.url) : URI.revive(link.url); + target = typeof link.url === 'string' ? URI.parse(link.url, true) : URI.revive(link.url); } catch (err) { // ignore } diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index d5c66e025af0..931c01e33bf8 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -29,8 +29,8 @@ function es5ClassCompat(target: Function): any { @es5ClassCompat export class Disposable { - static from(...inDisposables: { dispose(): any }[]): Disposable { - let disposables: ReadonlyArray<{ dispose(): any }> | undefined = inDisposables; + static from(...inDisposables: { dispose(): any; }[]): Disposable { + let disposables: ReadonlyArray<{ dispose(): any; }> | undefined = inDisposables; return new Disposable(function () { if (disposables) { for (const disposable of disposables) { @@ -333,9 +333,9 @@ export class Range { return this._start.line === this._end.line; } - with(change: { start?: Position, end?: Position }): Range; + with(change: { start?: Position, end?: Position; }): Range; with(start?: Position, end?: Position): Range; - with(startOrChange: Position | undefined | { start?: Position, end?: Position }, end: Position = this.end): Range { + with(startOrChange: Position | undefined | { start?: Position, end?: Position; }, end: Position = this.end): Range { if (startOrChange === null || end === null) { throw illegalArgument(); @@ -589,15 +589,15 @@ export class WorkspaceEdit implements vscode.WorkspaceEdit { private _edits = new Array(); - renameFile(from: vscode.Uri, to: vscode.Uri, options?: { overwrite?: boolean, ignoreIfExists?: boolean }): void { + renameFile(from: vscode.Uri, to: vscode.Uri, options?: { overwrite?: boolean, ignoreIfExists?: boolean; }): void { this._edits.push({ _type: 1, from, to, options }); } - createFile(uri: vscode.Uri, options?: { overwrite?: boolean, ignoreIfExists?: boolean }): void { + createFile(uri: vscode.Uri, options?: { overwrite?: boolean, ignoreIfExists?: boolean; }): void { this._edits.push({ _type: 1, from: undefined, to: uri, options }); } - deleteFile(uri: vscode.Uri, options?: { recursive?: boolean, ignoreIfNotExists?: boolean }): void { + deleteFile(uri: vscode.Uri, options?: { recursive?: boolean, ignoreIfNotExists?: boolean; }): void { this._edits.push({ _type: 1, from: uri, to: undefined, options }); } @@ -740,6 +740,18 @@ export class SnippetString { return this; } + appendChoice(values: string[], number: number = this._tabstop++): SnippetString { + const value = SnippetString._escape(values.toString()); + + this.value += '${'; + this.value += number; + this.value += '|'; + this.value += value; + this.value += '|}'; + + return this; + } + appendVariable(name: string, defaultValue?: string | ((snippet: SnippetString) => any)): SnippetString { if (typeof defaultValue === 'function') { @@ -1142,6 +1154,10 @@ export class SelectionRange { } export class CallHierarchyItem { + + _sessionId?: string; + _itemId?: string; + kind: SymbolKind; name: string; detail?: string; @@ -1344,6 +1360,7 @@ export class CompletionItem implements vscode.CompletionItem { insertText?: string | SnippetString; keepWhitespace?: boolean; range?: Range; + range2?: Range | { inserting: Range; replacing: Range; }; commitCharacters?: string[]; textEdit?: TextEdit; additionalTextEdits?: TextEdit[]; @@ -1495,7 +1512,7 @@ export class Color { } } -export type IColorFormat = string | { opaque: string, transparent: string }; +export type IColorFormat = string | { opaque: string, transparent: string; }; @es5ClassCompat export class ColorInformation { @@ -1776,20 +1793,20 @@ export enum TaskScope { Workspace = 2 } -export class CustomExecution implements vscode.CustomExecution { - private _callback: () => Thenable; - constructor(callback: () => Thenable) { +export class CustomExecution implements vscode.CustomExecution2 { + private _callback: (resolvedDefintion?: vscode.TaskDefinition) => Thenable; + constructor(callback: (resolvedDefintion?: vscode.TaskDefinition) => Thenable) { this._callback = callback; } public computeId(): string { return 'customExecution' + generateUuid(); } - public set callback(value: () => Thenable) { + public set callback(value: (resolvedDefintion?: vscode.TaskDefinition) => Thenable) { this._callback = value; } - public get callback(): (() => Thenable) { + public get callback(): ((resolvedDefintion?: vscode.TaskDefinition) => Thenable) { return this._callback; } } @@ -2050,13 +2067,13 @@ export class TreeItem { label?: string | vscode.TreeItemLabel; resourceUri?: URI; - iconPath?: string | URI | { light: string | URI; dark: string | URI }; + iconPath?: string | URI | { light: string | URI; dark: string | URI; }; command?: vscode.Command; contextValue?: string; tooltip?: string; - constructor(label: string | vscode.TreeItemLabel, collapsibleState?: vscode.TreeItemCollapsibleState) - constructor(resourceUri: URI, collapsibleState?: vscode.TreeItemCollapsibleState) + constructor(label: string | vscode.TreeItemLabel, collapsibleState?: vscode.TreeItemCollapsibleState); + constructor(resourceUri: URI, collapsibleState?: vscode.TreeItemCollapsibleState); constructor(arg1: string | vscode.TreeItemLabel | URI, public collapsibleState: vscode.TreeItemCollapsibleState = TreeItemCollapsibleState.None) { if (URI.isUri(arg1)) { this.resourceUri = arg1; @@ -2233,16 +2250,14 @@ export class DebugAdapterServer implements vscode.DebugAdapterServer { } } -/* @es5ClassCompat -export class DebugAdapterImplementation implements vscode.DebugAdapterImplementation { - readonly implementation: any; +export class DebugAdapterInlineImplementation implements vscode.DebugAdapterInlineImplementation { + readonly implementation: vscode.DebugAdapter; - constructor(transport: any) { - this.implementation = transport; + constructor(impl: vscode.DebugAdapter) { + this.implementation = impl; } } -*/ export enum LogLevel { Trace = 1, @@ -2351,6 +2366,91 @@ export enum CommentMode { //#endregion +//#region Semantic Coloring + +export class SemanticTokensLegend { + public readonly tokenTypes: string[]; + public readonly tokenModifiers: string[]; + + constructor(tokenTypes: string[], tokenModifiers: string[]) { + this.tokenTypes = tokenTypes; + this.tokenModifiers = tokenModifiers; + } +} + +export class SemanticTokensBuilder { + + private _prevLine: number; + private _prevChar: number; + private _data: number[]; + private _dataLen: number; + + constructor() { + this._prevLine = 0; + this._prevChar = 0; + this._data = []; + this._dataLen = 0; + } + + public push(line: number, char: number, length: number, tokenType: number, tokenModifiers: number): void { + let pushLine = line; + let pushChar = char; + if (this._dataLen > 0) { + pushLine -= this._prevLine; + if (pushLine === 0) { + pushChar -= this._prevChar; + } + } + + this._data[this._dataLen++] = pushLine; + this._data[this._dataLen++] = pushChar; + this._data[this._dataLen++] = length; + this._data[this._dataLen++] = tokenType; + this._data[this._dataLen++] = tokenModifiers; + + this._prevLine = line; + this._prevChar = char; + } + + public build(): Uint32Array { + return new Uint32Array(this._data); + } +} + +export class SemanticTokens { + readonly resultId?: string; + readonly data: Uint32Array; + + constructor(data: Uint32Array, resultId?: string) { + this.resultId = resultId; + this.data = data; + } +} + +export class SemanticTokensEdit { + readonly start: number; + readonly deleteCount: number; + readonly data?: Uint32Array; + + constructor(start: number, deleteCount: number, data?: Uint32Array) { + this.start = start; + this.deleteCount = deleteCount; + this.data = data; + } +} + +export class SemanticTokensEdits { + readonly resultId?: string; + readonly edits: SemanticTokensEdit[]; + + constructor(edits: SemanticTokensEdit[], resultId?: string) { + this.resultId = resultId; + this.edits = edits; + } +} + +//#endregion + //#region debug export enum DebugConsoleMode { /** diff --git a/src/vs/workbench/api/common/extHostUrls.ts b/src/vs/workbench/api/common/extHostUrls.ts index ad5d57893a99..5ea42ebb9663 100644 --- a/src/vs/workbench/api/common/extHostUrls.ts +++ b/src/vs/workbench/api/common/extHostUrls.ts @@ -56,7 +56,11 @@ export class ExtHostUrls implements ExtHostUrlsShape { return Promise.resolve(undefined); } - async createAppUri(extensionId: ExtensionIdentifier, options?: vscode.AppUriOptions): Promise { - return URI.revive(await this._proxy.$createAppUri(extensionId, options)); + async createAppUri(uri: URI): Promise { + return URI.revive(await this._proxy.$createAppUri(uri)); + } + + async proposedCreateAppUri(extensionId: ExtensionIdentifier, options?: vscode.AppUriOptions): Promise { + return URI.revive(await this._proxy.$proposedCreateAppUri(extensionId, options)); } } diff --git a/src/vs/workbench/api/common/extHostWebview.ts b/src/vs/workbench/api/common/extHostWebview.ts index a89670b11076..bf1f7917dc97 100644 --- a/src/vs/workbench/api/common/extHostWebview.ts +++ b/src/vs/workbench/api/common/extHostWebview.ts @@ -4,17 +4,20 @@ *--------------------------------------------------------------------------------------------*/ import { Emitter, Event } from 'vs/base/common/event'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { assertIsDefined } from 'vs/base/common/types'; import { URI, UriComponents } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import * as modes from 'vs/editor/common/modes'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { ILogService } from 'vs/platform/log/common/log'; import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters'; import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; import { EditorViewColumn } from 'vs/workbench/api/common/shared/editor'; import { asWebviewUri, WebviewInitData } from 'vs/workbench/api/common/shared/webview'; import * as vscode from 'vscode'; import { ExtHostWebviewsShape, IMainContext, MainContext, MainThreadWebviewsShape, WebviewPanelHandle, WebviewPanelViewStateData } from './extHost.protocol'; -import { Disposable } from './extHostTypes'; +import { Disposable as VSCodeDisposable } from './extHostTypes'; type IconPath = URI | { light: URI, dark: URI }; @@ -33,6 +36,7 @@ export class ExtHostWebview implements vscode.Webview { private readonly _initData: WebviewInitData, private readonly _workspace: IExtHostWorkspace | undefined, private readonly _extension: IExtensionDescription, + private readonly _logService: ILogService, ) { } public dispose() { @@ -61,7 +65,7 @@ export class ExtHostWebview implements vscode.Webview { if (this._initData.isExtensionDevelopmentDebug && !this._hasCalledAsWebviewUri) { if (/(["'])vscode-resource:([^\s'"]+?)(["'])/i.test(value)) { this._hasCalledAsWebviewUri = true; - console.warn(`${this._extension.identifier.value} created a webview that appears to use the vscode-resource scheme directly. Please migrate to use the 'webview.asWebviewUri' api instead: https://aka.ms/vscode-webview-use-aswebviewuri`); + this._logService.warn(`${this._extension.identifier.value} created a webview that appears to use the vscode-resource scheme directly. Please migrate to use the 'webview.asWebviewUri' api instead: https://aka.ms/vscode-webview-use-aswebviewuri`); } } this._proxy.$setHtml(this._handle, value); @@ -91,7 +95,7 @@ export class ExtHostWebview implements vscode.Webview { } } -export class ExtHostWebviewEditor implements vscode.WebviewEditor { +export class ExtHostWebviewEditor extends Disposable implements vscode.WebviewPanel { private readonly _handle: WebviewPanelHandle; private readonly _proxy: MainThreadWebviewsShape; @@ -107,12 +111,14 @@ export class ExtHostWebviewEditor implements vscode.WebviewEditor { _isDisposed: boolean = false; - readonly _onDisposeEmitter = new Emitter(); + readonly _onDisposeEmitter = this._register(new Emitter()); public readonly onDidDispose: Event = this._onDisposeEmitter.event; - readonly _onDidChangeViewStateEmitter = new Emitter(); + readonly _onDidChangeViewStateEmitter = this._register(new Emitter()); public readonly onDidChangeViewState: Event = this._onDidChangeViewStateEmitter.event; + public _capabilities?: vscode.WebviewEditorCapabilities; + constructor( handle: WebviewPanelHandle, proxy: MainThreadWebviewsShape, @@ -122,6 +128,7 @@ export class ExtHostWebviewEditor implements vscode.WebviewEditor { editorOptions: vscode.WebviewPanelOptions, webview: ExtHostWebview ) { + super(); this._handle = handle; this._proxy = proxy; this._viewType = viewType; @@ -135,16 +142,12 @@ export class ExtHostWebviewEditor implements vscode.WebviewEditor { if (this._isDisposed) { return; } - this._isDisposed = true; this._onDisposeEmitter.fire(); - this._proxy.$disposeWebview(this._handle); - this._webview.dispose(); - this._onDisposeEmitter.dispose(); - this._onDidChangeViewStateEmitter.dispose(); + super.dispose(); } get webview() { @@ -223,18 +226,6 @@ export class ExtHostWebviewEditor implements vscode.WebviewEditor { this._visible = value; } - private readonly _onWillSave = new Emitter<{ waitUntil: (thenable: Thenable) => void }>(); - public readonly onWillSave = this._onWillSave.event; - - async _save(): Promise { - const waitingOn: Thenable[] = []; - this._onWillSave.fire({ - waitUntil: (thenable: Thenable): void => { waitingOn.push(thenable); }, - }); - const result = await Promise.all(waitingOn); - return result.every(x => x); - } - public postMessage(message: any): Promise { this.assertNotDisposed(); return this._proxy.$postMessage(this._handle, message); @@ -248,6 +239,32 @@ export class ExtHostWebviewEditor implements vscode.WebviewEditor { }); } + _setCapabilities(capabilities: vscode.WebviewEditorCapabilities) { + this._capabilities = capabilities; + if (capabilities.editingCapability) { + this._register(capabilities.editingCapability.onEdit(edit => { + this._proxy.$onEdit(this._handle, edit); + })); + } + } + + _undoEdits(edits: readonly any[]): void { + assertIsDefined(this._capabilities).editingCapability?.undoEdits(edits); + } + + _redoEdits(edits: readonly any[]): void { + assertIsDefined(this._capabilities).editingCapability?.applyEdits(edits); + } + + async _onSave(): Promise { + await assertIsDefined(this._capabilities).editingCapability?.save(); + } + + + async _onSaveAs(resource: vscode.Uri, targetResource: vscode.Uri): Promise { + await assertIsDefined(this._capabilities).editingCapability?.saveAs(resource, targetResource); + } + private assertNotDisposed() { if (this._isDisposed) { throw new Error('Webview is disposed'); @@ -270,6 +287,7 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { mainContext: IMainContext, private readonly initData: WebviewInitData, private readonly workspace: IExtHostWorkspace | undefined, + private readonly _logService: ILogService, ) { this._proxy = mainContext.getProxy(MainContext.MainThreadWebviews); } @@ -290,7 +308,7 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { const handle = ExtHostWebviews.newHandle(); this._proxy.$createWebviewPanel({ id: extension.identifier, location: extension.extensionLocation }, handle, viewType, title, webviewShowOptions, convertWebviewOptions(extension, this.workspace, options)); - const webview = new ExtHostWebview(handle, this._proxy, options, this.initData, this.workspace, extension); + const webview = new ExtHostWebview(handle, this._proxy, options, this.initData, this.workspace, extension, this._logService); const panel = new ExtHostWebviewEditor(handle, this._proxy, viewType, title, viewColumn, options, webview); this._webviewPanels.set(handle, panel); return panel; @@ -308,7 +326,7 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { this._serializers.set(viewType, { serializer, extension }); this._proxy.$registerSerializer(viewType); - return new Disposable(() => { + return new VSCodeDisposable(() => { this._serializers.delete(viewType); this._proxy.$unregisterSerializer(viewType); }); @@ -327,7 +345,7 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { this._editorProviders.set(viewType, { extension, provider, }); this._proxy.$registerEditorProvider({ id: extension.identifier, location: extension.extensionLocation }, viewType, options || {}); - return new Disposable(() => { + return new VSCodeDisposable(() => { this._editorProviders.delete(viewType); this._proxy.$unregisterEditorProvider(viewType); }); @@ -347,7 +365,7 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { _handle: WebviewPanelHandle, extensionId: string ): void { - console.warn(`${extensionId} created a webview without a content security policy: https://aka.ms/vscode-webview-missing-csp`); + this._logService.warn(`${extensionId} created a webview without a content security policy: https://aka.ms/vscode-webview-missing-csp`); } public $onDidChangeWebviewPanelViewStates(newStates: WebviewPanelViewStateData): void { @@ -385,16 +403,15 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { } } - $onDidDisposeWebviewPanel(handle: WebviewPanelHandle): Promise { + async $onDidDisposeWebviewPanel(handle: WebviewPanelHandle): Promise { const panel = this.getWebviewPanel(handle); if (panel) { panel.dispose(); this._webviewPanels.delete(handle); } - return Promise.resolve(undefined); } - $deserializeWebviewPanel( + async $deserializeWebviewPanel( webviewHandle: WebviewPanelHandle, viewType: string, title: string, @@ -404,22 +421,18 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { ): Promise { const entry = this._serializers.get(viewType); if (!entry) { - return Promise.reject(new Error(`No serializer found for '${viewType}'`)); + throw new Error(`No serializer found for '${viewType}'`); } const { serializer, extension } = entry; - const webview = new ExtHostWebview(webviewHandle, this._proxy, options, this.initData, this.workspace, extension); + const webview = new ExtHostWebview(webviewHandle, this._proxy, options, this.initData, this.workspace, extension, this._logService); const revivedPanel = new ExtHostWebviewEditor(webviewHandle, this._proxy, viewType, title, typeof position === 'number' && position >= 0 ? typeConverters.ViewColumn.to(position) : undefined, options, webview); this._webviewPanels.set(webviewHandle, revivedPanel); - return Promise.resolve(serializer.deserializeWebviewPanel(revivedPanel, state)); - } - - private getWebviewPanel(handle: WebviewPanelHandle): ExtHostWebviewEditor | undefined { - return this._webviewPanels.get(handle); + await serializer.deserializeWebviewPanel(revivedPanel, state); } async $resolveWebviewEditor( - resource: UriComponents, + input: { resource: UriComponents, edits: readonly any[] }, handle: WebviewPanelHandle, viewType: string, title: string, @@ -430,19 +443,42 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { if (!entry) { return Promise.reject(new Error(`No provider found for '${viewType}'`)); } + const { provider, extension } = entry; - const webview = new ExtHostWebview(handle, this._proxy, options, this.initData, this.workspace, extension); + const webview = new ExtHostWebview(handle, this._proxy, options, this.initData, this.workspace, extension, this._logService); const revivedPanel = new ExtHostWebviewEditor(handle, this._proxy, viewType, title, typeof position === 'number' && position >= 0 ? typeConverters.ViewColumn.to(position) : undefined, options, webview); this._webviewPanels.set(handle, revivedPanel); - return Promise.resolve(provider.resolveWebviewEditor(URI.revive(resource), revivedPanel)); + const capabilities = await provider.resolveWebviewEditor({ resource: URI.revive(input.resource) }, revivedPanel); + revivedPanel._setCapabilities(capabilities); + + // TODO: the first set of edits should likely be passed when resolving + if (input.edits.length) { + revivedPanel._redoEdits(input.edits); + } } - async $save(handle: WebviewPanelHandle): Promise { + $undoEdits(handle: WebviewPanelHandle, edits: readonly any[]): void { const panel = this.getWebviewPanel(handle); - if (panel) { - return panel._save(); - } - return false; + panel?._undoEdits(edits); + } + + $applyEdits(handle: WebviewPanelHandle, edits: readonly any[]): void { + const panel = this.getWebviewPanel(handle); + panel?._redoEdits(edits); + } + + async $onSave(handle: WebviewPanelHandle): Promise { + const panel = this.getWebviewPanel(handle); + return panel?._onSave(); + } + + async $onSaveAs(handle: WebviewPanelHandle, resource: UriComponents, targetResource: UriComponents): Promise { + const panel = this.getWebviewPanel(handle); + return panel?._onSaveAs(URI.revive(resource), URI.revive(targetResource)); + } + + private getWebviewPanel(handle: WebviewPanelHandle): ExtHostWebviewEditor | undefined { + return this._webviewPanels.get(handle); } } @@ -462,7 +498,7 @@ function getDefaultLocalResourceRoots( workspace: IExtHostWorkspace | undefined, ): URI[] { return [ - ...(workspace && workspace.getWorkspaceFolders() || []).map(x => x.uri), + ...(workspace?.getWorkspaceFolders() || []).map(x => x.uri), extension.extensionLocation, ]; } diff --git a/src/vs/workbench/api/common/extHostWindow.ts b/src/vs/workbench/api/common/extHostWindow.ts index fd2958617a92..a86e55374931 100644 --- a/src/vs/workbench/api/common/extHostWindow.ts +++ b/src/vs/workbench/api/common/extHostWindow.ts @@ -39,7 +39,9 @@ export class ExtHostWindow implements ExtHostWindowShape { } openUri(stringOrUri: string | URI, options: IOpenUriOptions): Promise { + let uriAsString: string | undefined; if (typeof stringOrUri === 'string') { + uriAsString = stringOrUri; try { stringOrUri = URI.parse(stringOrUri); } catch (e) { @@ -51,7 +53,7 @@ export class ExtHostWindow implements ExtHostWindowShape { } else if (stringOrUri.scheme === Schemas.command) { return Promise.reject(`Invalid scheme '${stringOrUri.scheme}'`); } - return this._proxy.$openUri(stringOrUri, options); + return this._proxy.$openUri(stringOrUri, uriAsString, options); } async asExternalUri(uri: URI, options: IOpenUriOptions): Promise { diff --git a/src/vs/workbench/api/common/extHostWorkspace.ts b/src/vs/workbench/api/common/extHostWorkspace.ts index 227891e6e79f..cc5d6519d7da 100644 --- a/src/vs/workbench/api/common/extHostWorkspace.ts +++ b/src/vs/workbench/api/common/extHostWorkspace.ts @@ -3,31 +3,30 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { join } from 'vs/base/common/path'; import { delta as arrayDelta, mapArrayOrNot } from 'vs/base/common/arrays'; +import { Barrier } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; import { TernarySearchTree } from 'vs/base/common/map'; +import { Schemas } from 'vs/base/common/network'; import { Counter } from 'vs/base/common/numbers'; -import { basenameOrAuthority, dirname, isEqual, relativePath, basename } from 'vs/base/common/resources'; +import { basename, basenameOrAuthority, dirname, isEqual, relativePath } from 'vs/base/common/resources'; import { compare } from 'vs/base/common/strings'; +import { withUndefinedAsNull } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; +import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; import { Severity } from 'vs/platform/notification/common/notification'; -import { IRawFileMatch2, resultIsMatch } from 'vs/workbench/services/search/common/search'; import { Workspace, WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; +import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; +import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { Range, RelativePattern } from 'vs/workbench/api/common/extHostTypes'; import { ITextQueryBuilderOptions } from 'vs/workbench/contrib/search/common/queryBuilder'; +import { IRawFileMatch2, resultIsMatch } from 'vs/workbench/services/search/common/search'; import * as vscode from 'vscode'; -import { ExtHostWorkspaceShape, IWorkspaceData, MainThreadMessageServiceShape, MainThreadWorkspaceShape, MainContext } from './extHost.protocol'; -import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; -import { Barrier } from 'vs/base/common/async'; -import { Schemas } from 'vs/base/common/network'; -import { withUndefinedAsNull } from 'vs/base/common/types'; -import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; +import { ExtHostWorkspaceShape, IWorkspaceData, MainContext, MainThreadMessageServiceShape, MainThreadWorkspaceShape } from './extHost.protocol'; export interface IExtHostWorkspaceProvider { getWorkspaceFolder2(uri: vscode.Uri, resolveParent?: boolean): Promise; @@ -419,19 +418,6 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac findFiles(include: string | RelativePattern | undefined, exclude: vscode.GlobPattern | null | undefined, maxResults: number | undefined, extensionId: ExtensionIdentifier, token: vscode.CancellationToken = CancellationToken.None): Promise { this._logService.trace(`extHostWorkspace#findFiles: fileSearch, extension: ${extensionId.value}, entryPoint: findFiles`); - let includePattern: string | undefined; - let includeFolder: URI | undefined; - if (include) { - if (typeof include === 'string') { - includePattern = include; - } else { - includePattern = include.pattern; - - // include.base must be an absolute path - includeFolder = include.baseFolder || URI.file(include.base); - } - } - let excludePatternOrDisregardExcludes: string | false | undefined = undefined; if (exclude === null) { excludePatternOrDisregardExcludes = false; @@ -447,9 +433,10 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac return Promise.resolve([]); } + const { includePattern, folder } = parseSearchInclude(include); return this._proxy.$startFileSearch( withUndefinedAsNull(includePattern), - withUndefinedAsNull(includeFolder), + withUndefinedAsNull(folder), withUndefinedAsNull(excludePatternOrDisregardExcludes), withUndefinedAsNull(maxResults), token @@ -457,19 +444,11 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac .then(data => Array.isArray(data) ? data.map(d => URI.revive(d)) : []); } - findTextInFiles(query: vscode.TextSearchQuery, options: vscode.FindTextInFilesOptions, callback: (result: vscode.TextSearchResult) => void, extensionId: ExtensionIdentifier, token: vscode.CancellationToken = CancellationToken.None): Promise { + async findTextInFiles(query: vscode.TextSearchQuery, options: vscode.FindTextInFilesOptions, callback: (result: vscode.TextSearchResult) => void, extensionId: ExtensionIdentifier, token: vscode.CancellationToken = CancellationToken.None): Promise { this._logService.trace(`extHostWorkspace#findTextInFiles: textSearch, extension: ${extensionId.value}, entryPoint: findTextInFiles`); const requestId = this._requestIdProvider.getNext(); - const globPatternToString = (pattern: vscode.GlobPattern | string) => { - if (typeof pattern === 'string') { - return pattern; - } - - return join(pattern.base, pattern.pattern); - }; - const previewOptions: vscode.TextSearchPreviewOptions = typeof options.previewOptions === 'undefined' ? { matchLines: 100, @@ -477,6 +456,19 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac } : options.previewOptions; + let includePattern: string | undefined; + let folder: URI | undefined; + if (options.include) { + if (typeof options.include === 'string') { + includePattern = options.include; + } else { + includePattern = options.include.pattern; + folder = (options.include as RelativePattern).baseFolder || URI.file(options.include.base); + } + } + + const excludePattern = (typeof options.exclude === 'string') ? options.exclude : + options.exclude ? options.exclude.pattern : undefined; const queryOptions: ITextQueryBuilderOptions = { ignoreSymlinks: typeof options.followSymlinks === 'boolean' ? !options.followSymlinks : undefined, disregardIgnoreFiles: typeof options.useIgnoreFiles === 'boolean' ? !options.useIgnoreFiles : undefined, @@ -488,8 +480,8 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac afterContext: options.afterContext, beforeContext: options.beforeContext, - includePattern: options.include && globPatternToString(options.include), - excludePattern: options.exclude ? globPatternToString(options.exclude) : undefined + includePattern: includePattern, + excludePattern: excludePattern }; const isCanceled = false; @@ -525,16 +517,22 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac }; if (token.isCancellationRequested) { - return Promise.resolve(undefined); + return {}; } - return this._proxy.$startTextSearch(query, queryOptions, requestId, token).then(result => { + try { + const result = await this._proxy.$startTextSearch( + query, + withUndefinedAsNull(folder), + queryOptions, + requestId, + token); delete this._activeSearchCallbacks[requestId]; - return result; - }, err => { + return result || {}; + } catch (err) { delete this._activeSearchCallbacks[requestId]; - return Promise.reject(err); - }); + throw err; + } } $handleTextSearchResult(result: IRawFileMatch2, requestId: number): void { @@ -554,3 +552,23 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac export const IExtHostWorkspace = createDecorator('IExtHostWorkspace'); export interface IExtHostWorkspace extends ExtHostWorkspace, ExtHostWorkspaceShape, IExtHostWorkspaceProvider { } + +function parseSearchInclude(include: RelativePattern | string | undefined): { includePattern?: string, folder?: URI } { + let includePattern: string | undefined; + let includeFolder: URI | undefined; + if (include) { + if (typeof include === 'string') { + includePattern = include; + } else { + includePattern = include.pattern; + + // include.base must be an absolute path + includeFolder = include.baseFolder || URI.file(include.base); + } + } + + return { + includePattern: includePattern, + folder: includeFolder + }; +} diff --git a/src/vs/workbench/api/common/menusExtensionPoint.ts b/src/vs/workbench/api/common/menusExtensionPoint.ts index f4427b603561..4b4859ac3279 100644 --- a/src/vs/workbench/api/common/menusExtensionPoint.ts +++ b/src/vs/workbench/api/common/menusExtensionPoint.ts @@ -13,6 +13,7 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { MenuId, MenuRegistry, ILocalizedString, IMenuItem } from 'vs/platform/actions/common/actions'; import { URI } from 'vs/base/common/uri'; import { DisposableStore } from 'vs/base/common/lifecycle'; +import { ThemeIcon } from 'vs/platform/theme/common/themeService'; namespace schema { @@ -352,10 +353,14 @@ commandsExtensionPoint.setHandler(extensions => { const { icon, enablement, category, title, command } = userFriendlyCommand; - let absoluteIcon: { dark: URI; light?: URI; } | undefined; + let absoluteIcon: { dark: URI; light?: URI; } | ThemeIcon | undefined; if (icon) { if (typeof icon === 'string') { - absoluteIcon = { dark: resources.joinPath(extension.description.extensionLocation, icon) }; + if (extension.description.enableProposedApi) { + absoluteIcon = ThemeIcon.fromString(icon) || { dark: resources.joinPath(extension.description.extensionLocation, icon) }; + } else { + absoluteIcon = { dark: resources.joinPath(extension.description.extensionLocation, icon) }; + } } else { absoluteIcon = { dark: resources.joinPath(extension.description.extensionLocation, icon.dark), @@ -372,7 +377,7 @@ commandsExtensionPoint.setHandler(extensions => { title, category, precondition: ContextKeyExpr.deserialize(enablement), - iconLocation: absoluteIcon + icon: absoluteIcon }); _commandRegistrations.add(registration); } diff --git a/src/vs/workbench/api/common/shared/semanticTokens.ts b/src/vs/workbench/api/common/shared/semanticTokens.ts new file mode 100644 index 000000000000..cecc816f782b --- /dev/null +++ b/src/vs/workbench/api/common/shared/semanticTokens.ts @@ -0,0 +1,113 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { VSBuffer } from 'vs/base/common/buffer'; + +export interface IFullSemanticTokensDto { + id: number; + type: 'full'; + data: Uint32Array; +} + +export interface IDeltaSemanticTokensDto { + id: number; + type: 'delta'; + deltas: { start: number; deleteCount: number; data?: Uint32Array; }[]; +} + +export type ISemanticTokensDto = IFullSemanticTokensDto | IDeltaSemanticTokensDto; + +const enum EncodedSemanticTokensType { + Full = 1, + Delta = 2 +} + +export function encodeSemanticTokensDto(semanticTokens: ISemanticTokensDto): VSBuffer { + const buff = VSBuffer.alloc(encodedSize2(semanticTokens)); + let offset = 0; + buff.writeUInt32BE(semanticTokens.id, offset); offset += 4; + if (semanticTokens.type === 'full') { + buff.writeUInt8(EncodedSemanticTokensType.Full, offset); offset += 1; + buff.writeUInt32BE(semanticTokens.data.length, offset); offset += 4; + for (const uint of semanticTokens.data) { + buff.writeUInt32BE(uint, offset); offset += 4; + } + } else { + buff.writeUInt8(EncodedSemanticTokensType.Delta, offset); offset += 1; + buff.writeUInt32BE(semanticTokens.deltas.length, offset); offset += 4; + for (const delta of semanticTokens.deltas) { + buff.writeUInt32BE(delta.start, offset); offset += 4; + buff.writeUInt32BE(delta.deleteCount, offset); offset += 4; + if (delta.data) { + buff.writeUInt32BE(delta.data.length, offset); offset += 4; + for (const uint of delta.data) { + buff.writeUInt32BE(uint, offset); offset += 4; + } + } else { + buff.writeUInt32BE(0, offset); offset += 4; + } + } + } + return buff; +} + +function encodedSize2(semanticTokens: ISemanticTokensDto): number { + let result = 0; + result += 4; // id + result += 1; // type + if (semanticTokens.type === 'full') { + result += 4; // data length + result += semanticTokens.data.byteLength; + } else { + result += 4; // delta count + for (const delta of semanticTokens.deltas) { + result += 4; // start + result += 4; // deleteCount + result += 4; // data length + if (delta.data) { + result += delta.data.byteLength; + } + } + } + return result; +} + +export function decodeSemanticTokensDto(buff: VSBuffer): ISemanticTokensDto { + let offset = 0; + const id = buff.readUInt32BE(offset); offset += 4; + const type: EncodedSemanticTokensType = buff.readUInt8(offset); offset += 1; + if (type === EncodedSemanticTokensType.Full) { + const length = buff.readUInt32BE(offset); offset += 4; + const data = new Uint32Array(length); + for (let j = 0; j < length; j++) { + data[j] = buff.readUInt32BE(offset); offset += 4; + } + return { + id: id, + type: 'full', + data: data + }; + } + const deltaCount = buff.readUInt32BE(offset); offset += 4; + let deltas: { start: number; deleteCount: number; data?: Uint32Array; }[] = []; + for (let i = 0; i < deltaCount; i++) { + const start = buff.readUInt32BE(offset); offset += 4; + const deleteCount = buff.readUInt32BE(offset); offset += 4; + const length = buff.readUInt32BE(offset); offset += 4; + let data: Uint32Array | undefined; + if (length > 0) { + data = new Uint32Array(length); + for (let j = 0; j < length; j++) { + data[j] = buff.readUInt32BE(offset); offset += 4; + } + } + deltas[i] = { start, deleteCount, data }; + } + return { + id: id, + type: 'delta', + deltas: deltas + }; +} diff --git a/src/vs/workbench/api/node/extHost.services.ts b/src/vs/workbench/api/node/extHost.services.ts index e864993b01e2..deeb5c441dda 100644 --- a/src/vs/workbench/api/node/extHost.services.ts +++ b/src/vs/workbench/api/node/extHost.services.ts @@ -18,7 +18,7 @@ import { IExtHostTerminalService } from 'vs/workbench/api/common/extHostTerminal // import { ExtHostDebugService } from 'vs/workbench/api/node/extHostDebugService'; // import { IExtHostDebugService } from 'vs/workbench/api/common/extHostDebugService'; import { IExtHostSearch } from 'vs/workbench/api/common/extHostSearch'; -import { ExtHostSearch } from 'vs/workbench/api/node/extHostSearch'; +import { NativeExtHostSearch } from 'vs/workbench/api/node/extHostSearch'; import { ExtensionStoragePaths } from 'vs/workbench/api/node/extHostStoragePaths'; import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths'; import { IExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService'; @@ -38,7 +38,7 @@ registerSingleton(IExtHostDocumentsAndEditors, ExtHostDocumentsAndEditors); registerSingleton(IExtHostTerminalService, ExtHostTerminalService); // registerSingleton(IExtHostTask, ExtHostTask); {{SQL CABON EDIT}} disable exthost tasks // registerSingleton(IExtHostDebugService, ExtHostDebugService); {{SQL CARBON EDIT}} remove debug service -registerSingleton(IExtHostSearch, ExtHostSearch); +registerSingleton(IExtHostSearch, NativeExtHostSearch); registerSingleton(IExtensionStoragePaths, ExtensionStoragePaths); registerSingleton(IExtHostExtensionService, ExtHostExtensionService); registerSingleton(IExtHostStorage, ExtHostStorage); diff --git a/src/vs/workbench/api/node/extHostCLIServer.ts b/src/vs/workbench/api/node/extHostCLIServer.ts index bbb1a3d66d86..43fd5516a3f5 100644 --- a/src/vs/workbench/api/node/extHostCLIServer.ts +++ b/src/vs/workbench/api/node/extHostCLIServer.ts @@ -101,9 +101,6 @@ export class CLIServer { for (const s of folderURIs) { try { urisToOpen.push({ folderUri: URI.parse(s) }); - if (!addMode && !forceReuseWindow) { - forceNewWindow = true; - } } catch (e) { // ignore } @@ -114,9 +111,6 @@ export class CLIServer { try { if (hasWorkspaceFileExtension(s)) { urisToOpen.push({ workspaceUri: URI.parse(s) }); - if (!forceReuseWindow) { - forceNewWindow = true; - } } else { urisToOpen.push({ fileUri: URI.parse(s) }); } @@ -127,7 +121,8 @@ export class CLIServer { } if (urisToOpen.length) { const waitMarkerFileURI = waitMarkerFilePath ? URI.file(waitMarkerFilePath) : undefined; - const windowOpenArgs: INativeOpenWindowOptions = { forceNewWindow, diffMode, addMode, gotoLineMode, forceReuseWindow, waitMarkerFileURI }; + const preferNewWindow = !forceReuseWindow && !waitMarkerFileURI && !addMode; + const windowOpenArgs: INativeOpenWindowOptions = { forceNewWindow, diffMode, addMode, gotoLineMode, forceReuseWindow, preferNewWindow, waitMarkerFileURI }; this._commands.executeCommand('_files.windowOpen', urisToOpen, windowOpenArgs); } res.writeHead(200); diff --git a/src/vs/workbench/api/node/extHostDebugService.ts b/src/vs/workbench/api/node/extHostDebugService.ts index a77f96210b96..29668be4922d 100644 --- a/src/vs/workbench/api/node/extHostDebugService.ts +++ b/src/vs/workbench/api/node/extHostDebugService.ts @@ -3,330 +3,70 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as path from 'vs/base/common/path'; -import { Schemas } from 'vs/base/common/network'; -import { URI, UriComponents } from 'vs/base/common/uri'; -import { Event, Emitter } from 'vs/base/common/event'; -import { asPromise } from 'vs/base/common/async'; import * as nls from 'vs/nls'; -import { - MainContext, MainThreadDebugServiceShape, ExtHostDebugServiceShape, DebugSessionUUID, - IBreakpointsDeltaDto, ISourceMultiBreakpointDto, IFunctionBreakpointDto, IDebugSessionDto -} from 'vs/workbench/api/common/extHost.protocol'; import * as vscode from 'vscode'; -import { Disposable, Position, Location, SourceBreakpoint, FunctionBreakpoint, DebugAdapterServer, DebugAdapterExecutable, DataBreakpoint, DebugConsoleMode } from 'vs/workbench/api/common/extHostTypes'; +import { DebugAdapterExecutable } from 'vs/workbench/api/common/extHostTypes'; import { ExecutableDebugAdapter, SocketDebugAdapter } from 'vs/workbench/contrib/debug/node/debugAdapter'; import { AbstractDebugAdapter } from 'vs/workbench/contrib/debug/common/abstractDebugAdapter'; import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; import { IExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService'; -import { ExtHostDocumentsAndEditors, IExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; -import { IDebuggerContribution, IConfig, IDebugAdapter, IDebugAdapterServer, IDebugAdapterExecutable, IAdapterDescriptor } from 'vs/workbench/contrib/debug/common/debug'; -import { hasChildProcesses, prepareCommand, runInExternalTerminal } from 'vs/workbench/contrib/debug/node/terminals'; -import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; -import { AbstractVariableResolverService } from 'vs/workbench/services/configurationResolver/common/variableResolver'; -import { ExtHostConfigProvider, IExtHostConfiguration } from '../common/extHostConfiguration'; -import { convertToVSCPaths, convertToDAPaths, isDebuggerMainContribution } from 'vs/workbench/contrib/debug/common/debugUtils'; -import { IDisposable } from 'vs/base/common/lifecycle'; -import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; -import { CancellationToken } from 'vs/base/common/cancellation'; +import { IExtHostDocumentsAndEditors, ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; +import { IAdapterDescriptor } from 'vs/workbench/contrib/debug/common/debug'; +import { IExtHostConfiguration, ExtHostConfigProvider } from '../common/extHostConfiguration'; import { IExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; -import { IProcessEnvironment } from 'vs/base/common/platform'; -import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; -import { SignService } from 'vs/platform/sign/node/signService'; -import { ISignService } from 'vs/platform/sign/common/sign'; import { IExtHostTerminalService } from 'vs/workbench/api/common/extHostTerminalService'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; -import { IExtHostDebugService } from 'vs/workbench/api/common/extHostDebugService'; - -export class ExtHostDebugService implements IExtHostDebugService, ExtHostDebugServiceShape { - - readonly _serviceBrand: undefined; - - private _configProviderHandleCounter: number; - private _configProviders: ConfigProviderTuple[]; - - private _adapterFactoryHandleCounter: number; - private _adapterFactories: DescriptorFactoryTuple[]; - - private _trackerFactoryHandleCounter: number; - private _trackerFactories: TrackerFactoryTuple[]; - - private _debugServiceProxy: MainThreadDebugServiceShape; - private _debugSessions: Map = new Map(); - - private readonly _onDidStartDebugSession: Emitter; - get onDidStartDebugSession(): Event { return this._onDidStartDebugSession.event; } - - private readonly _onDidTerminateDebugSession: Emitter; - get onDidTerminateDebugSession(): Event { return this._onDidTerminateDebugSession.event; } - - private readonly _onDidChangeActiveDebugSession: Emitter; - get onDidChangeActiveDebugSession(): Event { return this._onDidChangeActiveDebugSession.event; } - - private _activeDebugSession: ExtHostDebugSession | undefined; - get activeDebugSession(): ExtHostDebugSession | undefined { return this._activeDebugSession; } - - private readonly _onDidReceiveDebugSessionCustomEvent: Emitter; - get onDidReceiveDebugSessionCustomEvent(): Event { return this._onDidReceiveDebugSessionCustomEvent.event; } - - private _activeDebugConsole: ExtHostDebugConsole; - get activeDebugConsole(): ExtHostDebugConsole { return this._activeDebugConsole; } - - private _breakpoints: Map; - private _breakpointEventsActive: boolean; +import { ExtHostDebugServiceBase, ExtHostDebugSession, ExtHostVariableResolverService } from 'vs/workbench/api/common/extHostDebugService'; +import { ISignService } from 'vs/platform/sign/common/sign'; +import { SignService } from 'vs/platform/sign/node/signService'; +import { hasChildProcesses, prepareCommand, runInExternalTerminal } from 'vs/workbench/contrib/debug/node/terminals'; +import { IDisposable } from 'vs/base/common/lifecycle'; +import { AbstractVariableResolverService } from 'vs/workbench/services/configurationResolver/common/variableResolver'; +import { IProcessEnvironment } from 'vs/base/common/platform'; - private readonly _onDidChangeBreakpoints: Emitter; - private _aexCommands: Map; - private _debugAdapters: Map; - private _debugAdaptersTrackers: Map; +export class ExtHostDebugService extends ExtHostDebugServiceBase { - private _variableResolver: IConfigurationResolverService | undefined; + readonly _serviceBrand: undefined; private _integratedTerminalInstance?: vscode.Terminal; private _terminalDisposedListener: IDisposable | undefined; - private _signService: ISignService | undefined; - - constructor( @IExtHostRpcService extHostRpcService: IExtHostRpcService, - @IExtHostWorkspace private _workspaceService: IExtHostWorkspace, - @IExtHostExtensionService private _extensionService: IExtHostExtensionService, - @IExtHostDocumentsAndEditors private _editorsService: IExtHostDocumentsAndEditors, - @IExtHostConfiguration private _configurationService: IExtHostConfiguration, + @IExtHostWorkspace workspaceService: IExtHostWorkspace, + @IExtHostExtensionService extensionService: IExtHostExtensionService, + @IExtHostDocumentsAndEditors editorsService: IExtHostDocumentsAndEditors, + @IExtHostConfiguration configurationService: IExtHostConfiguration, @IExtHostTerminalService private _terminalService: IExtHostTerminalService, - @IExtHostCommands private _commandService: IExtHostCommands + @IExtHostCommands commandService: IExtHostCommands ) { - this._configProviderHandleCounter = 0; - this._configProviders = []; - - this._adapterFactoryHandleCounter = 0; - this._adapterFactories = []; - - this._trackerFactoryHandleCounter = 0; - this._trackerFactories = []; - - this._aexCommands = new Map(); - this._debugAdapters = new Map(); - this._debugAdaptersTrackers = new Map(); - - this._onDidStartDebugSession = new Emitter(); - this._onDidTerminateDebugSession = new Emitter(); - this._onDidChangeActiveDebugSession = new Emitter(); - this._onDidReceiveDebugSessionCustomEvent = new Emitter(); - - this._debugServiceProxy = extHostRpcService.getProxy(MainContext.MainThreadDebugService); - - this._onDidChangeBreakpoints = new Emitter({ - onFirstListenerAdd: () => { - this.startBreakpoints(); - } - }); - - this._activeDebugConsole = new ExtHostDebugConsole(this._debugServiceProxy); - - this._breakpoints = new Map(); - this._breakpointEventsActive = false; - - this._extensionService.getExtensionRegistry().then((extensionRegistry: ExtensionDescriptionRegistry) => { - extensionRegistry.onDidChange(_ => { - this.registerAllDebugTypes(extensionRegistry); - }); - this.registerAllDebugTypes(extensionRegistry); - }); + super(extHostRpcService, workspaceService, extensionService, editorsService, configurationService, commandService); } - private registerAllDebugTypes(extensionRegistry: ExtensionDescriptionRegistry) { - - const debugTypes: string[] = []; - this._aexCommands.clear(); - - for (const ed of extensionRegistry.getAllExtensionDescriptions()) { - if (ed.contributes) { - const debuggers = ed.contributes['debuggers']; - if (debuggers && debuggers.length > 0) { - for (const dbg of debuggers) { - if (isDebuggerMainContribution(dbg)) { - debugTypes.push(dbg.type); - if (dbg.adapterExecutableCommand) { - this._aexCommands.set(dbg.type, dbg.adapterExecutableCommand); - } - } - } - } - } - } - - this._debugServiceProxy.$registerDebugTypes(debugTypes); - } - - // extension debug API - - get onDidChangeBreakpoints(): Event { - return this._onDidChangeBreakpoints.event; - } - - get breakpoints(): vscode.Breakpoint[] { - - this.startBreakpoints(); - - const result: vscode.Breakpoint[] = []; - this._breakpoints.forEach(bp => result.push(bp)); - return result; - } - - public addBreakpoints(breakpoints0: vscode.Breakpoint[]): Promise { - - this.startBreakpoints(); - - // filter only new breakpoints - const breakpoints = breakpoints0.filter(bp => { - const id = bp.id; - if (!this._breakpoints.has(id)) { - this._breakpoints.set(id, bp); - return true; - } - return false; - }); - - // send notification for added breakpoints - this.fireBreakpointChanges(breakpoints, [], []); - - // convert added breakpoints to DTOs - const dtos: Array = []; - const map = new Map(); - for (const bp of breakpoints) { - if (bp instanceof SourceBreakpoint) { - let dto = map.get(bp.location.uri.toString()); - if (!dto) { - dto = { - type: 'sourceMulti', - uri: bp.location.uri, - lines: [] - }; - map.set(bp.location.uri.toString(), dto); - dtos.push(dto); - } - dto.lines.push({ - id: bp.id, - enabled: bp.enabled, - condition: bp.condition, - hitCondition: bp.hitCondition, - logMessage: bp.logMessage, - line: bp.location.range.start.line, - character: bp.location.range.start.character - }); - } else if (bp instanceof FunctionBreakpoint) { - dtos.push({ - type: 'function', - id: bp.id, - enabled: bp.enabled, - hitCondition: bp.hitCondition, - logMessage: bp.logMessage, - condition: bp.condition, - functionName: bp.functionName - }); - } - } - - // send DTOs to VS Code - return this._debugServiceProxy.$registerBreakpoints(dtos); - } - - public removeBreakpoints(breakpoints0: vscode.Breakpoint[]): Promise { - - this.startBreakpoints(); - - // remove from array - const breakpoints = breakpoints0.filter(b => this._breakpoints.delete(b.id)); - - // send notification - this.fireBreakpointChanges([], breakpoints, []); - - // unregister with VS Code - const ids = breakpoints.filter(bp => bp instanceof SourceBreakpoint).map(bp => bp.id); - const fids = breakpoints.filter(bp => bp instanceof FunctionBreakpoint).map(bp => bp.id); - const dids = breakpoints.filter(bp => bp instanceof DataBreakpoint).map(bp => bp.id); - return this._debugServiceProxy.$unregisterBreakpoints(ids, fids, dids); - } - - public startDebugging(folder: vscode.WorkspaceFolder | undefined, nameOrConfig: string | vscode.DebugConfiguration, options: vscode.DebugSessionOptions): Promise { - return this._debugServiceProxy.$startDebugging(folder ? folder.uri : undefined, nameOrConfig, { - parentSessionID: options.parentSession ? options.parentSession.id : undefined, - repl: options.consoleMode === DebugConsoleMode.MergeWithParent ? 'mergeWithParent' : 'separate' - }); - } - - public registerDebugConfigurationProvider(type: string, provider: vscode.DebugConfigurationProvider): vscode.Disposable { - - if (!provider) { - return new Disposable(() => { }); - } - - if (provider.debugAdapterExecutable) { - console.error('DebugConfigurationProvider.debugAdapterExecutable is deprecated and will be removed soon; please use DebugAdapterDescriptorFactory.createDebugAdapterDescriptor instead.'); + protected createDebugAdapter(adapter: IAdapterDescriptor, session: ExtHostDebugSession): AbstractDebugAdapter | undefined { + switch (adapter.type) { + case 'server': + return new SocketDebugAdapter(adapter); + case 'executable': + return new ExecutableDebugAdapter(adapter, session.type); } - - const handle = this._configProviderHandleCounter++; - this._configProviders.push({ type, handle, provider }); - - this._debugServiceProxy.$registerDebugConfigurationProvider(type, - !!provider.provideDebugConfigurations, - !!provider.resolveDebugConfiguration, - !!provider.debugAdapterExecutable, // TODO@AW: deprecated - handle); - - return new Disposable(() => { - this._configProviders = this._configProviders.filter(p => p.provider !== provider); // remove - this._debugServiceProxy.$unregisterDebugConfigurationProvider(handle); - }); + return super.createDebugAdapter(adapter, session); } - public registerDebugAdapterDescriptorFactory(extension: IExtensionDescription, type: string, factory: vscode.DebugAdapterDescriptorFactory): vscode.Disposable { - - if (!factory) { - return new Disposable(() => { }); - } - - // a DebugAdapterDescriptorFactory can only be registered in the extension that contributes the debugger - if (!this.definesDebugType(extension, type)) { - throw new Error(`a DebugAdapterDescriptorFactory can only be registered from the extension that defines the '${type}' debugger.`); - } - - // make sure that only one factory for this type is registered - if (this.getAdapterFactoryByType(type)) { - throw new Error(`a DebugAdapterDescriptorFactory can only be registered once per a type.`); + protected daExecutableFromPackage(session: ExtHostDebugSession, extensionRegistry: ExtensionDescriptionRegistry): DebugAdapterExecutable | undefined { + const dae = ExecutableDebugAdapter.platformAdapterExecutable(extensionRegistry.getAllExtensionDescriptions(), session.type); + if (dae) { + return new DebugAdapterExecutable(dae.command, dae.args, dae.options); } - - const handle = this._adapterFactoryHandleCounter++; - this._adapterFactories.push({ type, handle, factory }); - - this._debugServiceProxy.$registerDebugAdapterDescriptorFactory(type, handle); - - return new Disposable(() => { - this._adapterFactories = this._adapterFactories.filter(p => p.factory !== factory); // remove - this._debugServiceProxy.$unregisterDebugAdapterDescriptorFactory(handle); - }); + return undefined; } - public registerDebugAdapterTrackerFactory(type: string, factory: vscode.DebugAdapterTrackerFactory): vscode.Disposable { - - if (!factory) { - return new Disposable(() => { }); - } - - const handle = this._trackerFactoryHandleCounter++; - this._trackerFactories.push({ type, handle, factory }); - - return new Disposable(() => { - this._trackerFactories = this._trackerFactories.filter(p => p.factory !== factory); // remove - }); + protected createSignService(): ISignService | undefined { + return new SignService(); } - // RPC methods (ExtHostDebugServiceShape) - public async $runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments): Promise { if (args.kind === 'integrated') { @@ -340,776 +80,48 @@ export class ExtHostDebugService implements IExtHostDebugService, ExtHostDebugSe }); } - return new Promise(resolve => { - if (this._integratedTerminalInstance) { - this._integratedTerminalInstance.processId.then(pid => { - resolve(hasChildProcesses(pid)); - }, err => { - resolve(true); - }); - } else { - resolve(true); - } - }).then(async needNewTerminal => { - - const configProvider = await this._configurationService.getConfigProvider(); - const shell = this._terminalService.getDefaultShell(true, configProvider); - - if (needNewTerminal || !this._integratedTerminalInstance) { - const options: vscode.TerminalOptions = { - shellPath: shell, - // shellArgs: this._terminalService._getDefaultShellArgs(configProvider), - cwd: args.cwd, - name: args.title || nls.localize('debug.terminal.title', "debuggee"), - env: args.env - }; - delete args.cwd; - delete args.env; - this._integratedTerminalInstance = this._terminalService.createTerminalFromOptions(options); - } - const terminal: vscode.Terminal = this._integratedTerminalInstance; - - terminal.show(); - - return this._integratedTerminalInstance.processId.then(shellProcessId => { - - const command = prepareCommand(args, shell, configProvider); - - terminal.sendText(command, true); - - return shellProcessId; - }); - }); - - } else if (args.kind === 'external') { - - runInExternalTerminal(args, await this._configurationService.getConfigProvider()); - } - return Promise.resolve(undefined); - } - - public async $substituteVariables(folderUri: UriComponents | undefined, config: IConfig): Promise { - if (!this._variableResolver) { - const [workspaceFolders, configProvider] = await Promise.all([this._workspaceService.getWorkspaceFolders2(), this._configurationService.getConfigProvider()]); - this._variableResolver = new ExtHostVariableResolverService(workspaceFolders || [], this._editorsService, configProvider); - } - let ws: IWorkspaceFolder | undefined; - const folder = await this.getFolder(folderUri); - if (folder) { - ws = { - uri: folder.uri, - name: folder.name, - index: folder.index, - toResource: () => { - throw new Error('Not implemented'); - } - }; - } - return this._variableResolver.resolveAny(ws, config); - } - - public async $startDASession(debugAdapterHandle: number, sessionDto: IDebugSessionDto): Promise { - const mythis = this; - - const session = await this.getSession(sessionDto); - - return this.getAdapterDescriptor(this.getAdapterFactoryByType(session.type), session).then(daDescriptor => { - - const adapter = this.convertToDto(daDescriptor); - let da: AbstractDebugAdapter | undefined = undefined; - - switch (adapter.type) { - - case 'server': - da = new SocketDebugAdapter(adapter); - break; - - case 'executable': - da = new ExecutableDebugAdapter(adapter, session.type); - break; - - case 'implementation': - da = new DirectDebugAdapter(adapter.implementation); - break; - - default: - break; - } - - const debugAdapter = da; - - if (debugAdapter) { - this._debugAdapters.set(debugAdapterHandle, debugAdapter); - - return this.getDebugAdapterTrackers(session).then(tracker => { - - if (tracker) { - this._debugAdaptersTrackers.set(debugAdapterHandle, tracker); - } - - debugAdapter.onMessage(async message => { - - if (message.type === 'request' && (message).command === 'handshake') { - - const request = message; - - const response: DebugProtocol.Response = { - type: 'response', - seq: 0, - command: request.command, - request_seq: request.seq, - success: true - }; - - if (!this._signService) { - this._signService = new SignService(); - } - - try { - const signature = await this._signService.sign(request.arguments.value); - response.body = { - signature: signature - }; - debugAdapter.sendResponse(response); - } catch (e) { - response.success = false; - response.message = e.message; - debugAdapter.sendResponse(response); - } - } else { - if (tracker && tracker.onDidSendMessage) { - tracker.onDidSendMessage(message); - } - - // DA -> VS Code - message = convertToVSCPaths(message, true); - - mythis._debugServiceProxy.$acceptDAMessage(debugAdapterHandle, message); - } - }); - debugAdapter.onError(err => { - if (tracker && tracker.onError) { - tracker.onError(err); - } - this._debugServiceProxy.$acceptDAError(debugAdapterHandle, err.name, err.message, err.stack); - }); - debugAdapter.onExit((code: number) => { - if (tracker && tracker.onExit) { - tracker.onExit(code, undefined); - } - this._debugServiceProxy.$acceptDAExit(debugAdapterHandle, code, undefined); - }); - - if (tracker && tracker.onWillStartSession) { - tracker.onWillStartSession(); - } - - return debugAdapter.startSession(); - }); - - } - return undefined; - }); - } - - public $sendDAMessage(debugAdapterHandle: number, message: DebugProtocol.ProtocolMessage): void { - - // VS Code -> DA - message = convertToDAPaths(message, false); - - const tracker = this._debugAdaptersTrackers.get(debugAdapterHandle); // TODO@AW: same handle? - if (tracker && tracker.onWillReceiveMessage) { - tracker.onWillReceiveMessage(message); - } - - const da = this._debugAdapters.get(debugAdapterHandle); - if (da) { - da.sendMessage(message); - } - } - - public $stopDASession(debugAdapterHandle: number): Promise { - - const tracker = this._debugAdaptersTrackers.get(debugAdapterHandle); - this._debugAdaptersTrackers.delete(debugAdapterHandle); - if (tracker && tracker.onWillStopSession) { - tracker.onWillStopSession(); - } - - const da = this._debugAdapters.get(debugAdapterHandle); - this._debugAdapters.delete(debugAdapterHandle); - if (da) { - return da.stopSession(); - } else { - return Promise.resolve(void 0); - } - } - - public $acceptBreakpointsDelta(delta: IBreakpointsDeltaDto): void { - - const a: vscode.Breakpoint[] = []; - const r: vscode.Breakpoint[] = []; - const c: vscode.Breakpoint[] = []; - - if (delta.added) { - for (const bpd of delta.added) { - const id = bpd.id; - if (id && !this._breakpoints.has(id)) { - let bp: vscode.Breakpoint; - if (bpd.type === 'function') { - bp = new FunctionBreakpoint(bpd.functionName, bpd.enabled, bpd.condition, bpd.hitCondition, bpd.logMessage); - } else if (bpd.type === 'data') { - bp = new DataBreakpoint(bpd.label, bpd.dataId, bpd.canPersist, bpd.enabled, bpd.hitCondition, bpd.condition, bpd.logMessage); - } else { - const uri = URI.revive(bpd.uri); - bp = new SourceBreakpoint(new Location(uri, new Position(bpd.line, bpd.character)), bpd.enabled, bpd.condition, bpd.hitCondition, bpd.logMessage); - } - (bp as any)._id = id; - this._breakpoints.set(id, bp); - a.push(bp); - } - } - } - - if (delta.removed) { - for (const id of delta.removed) { - const bp = this._breakpoints.get(id); - if (bp) { - this._breakpoints.delete(id); - r.push(bp); - } - } - } - - if (delta.changed) { - for (const bpd of delta.changed) { - if (bpd.id) { - const bp = this._breakpoints.get(bpd.id); - if (bp) { - if (bp instanceof FunctionBreakpoint && bpd.type === 'function') { - const fbp = bp; - fbp.enabled = bpd.enabled; - fbp.condition = bpd.condition; - fbp.hitCondition = bpd.hitCondition; - fbp.logMessage = bpd.logMessage; - fbp.functionName = bpd.functionName; - } else if (bp instanceof SourceBreakpoint && bpd.type === 'source') { - const sbp = bp; - sbp.enabled = bpd.enabled; - sbp.condition = bpd.condition; - sbp.hitCondition = bpd.hitCondition; - sbp.logMessage = bpd.logMessage; - sbp.location = new Location(URI.revive(bpd.uri), new Position(bpd.line, bpd.character)); - } - c.push(bp); - } - } - } - } - - this.fireBreakpointChanges(a, r, c); - } - - public $provideDebugConfigurations(configProviderHandle: number, folderUri: UriComponents | undefined, token: CancellationToken): Promise { - return asPromise(async () => { - const provider = this.getConfigProviderByHandle(configProviderHandle); - if (!provider) { - throw new Error('no DebugConfigurationProvider found'); - } - if (!provider.provideDebugConfigurations) { - throw new Error('DebugConfigurationProvider has no method provideDebugConfigurations'); - } - const folder = await this.getFolder(folderUri); - return provider.provideDebugConfigurations(folder, token); - }).then(debugConfigurations => { - if (!debugConfigurations) { - throw new Error('nothing returned from DebugConfigurationProvider.provideDebugConfigurations'); - } - return debugConfigurations; - }); - } - - public $resolveDebugConfiguration(configProviderHandle: number, folderUri: UriComponents | undefined, debugConfiguration: vscode.DebugConfiguration, token: CancellationToken): Promise { - return asPromise(async () => { - const provider = this.getConfigProviderByHandle(configProviderHandle); - if (!provider) { - throw new Error('no DebugConfigurationProvider found'); - } - if (!provider.resolveDebugConfiguration) { - throw new Error('DebugConfigurationProvider has no method resolveDebugConfiguration'); - } - const folder = await this.getFolder(folderUri); - return provider.resolveDebugConfiguration(folder, debugConfiguration, token); - }); - } - - // TODO@AW deprecated and legacy - public $legacyDebugAdapterExecutable(configProviderHandle: number, folderUri: UriComponents | undefined): Promise { - return asPromise(async () => { - const provider = this.getConfigProviderByHandle(configProviderHandle); - if (!provider) { - throw new Error('no DebugConfigurationProvider found'); - } - if (!provider.debugAdapterExecutable) { - throw new Error('DebugConfigurationProvider has no method debugAdapterExecutable'); - } - const folder = await this.getFolder(folderUri); - return provider.debugAdapterExecutable(folder, CancellationToken.None); - }).then(executable => { - if (!executable) { - throw new Error('nothing returned from DebugConfigurationProvider.debugAdapterExecutable'); - } - return this.convertToDto(executable); - }); - } - - public async $provideDebugAdapter(adapterProviderHandle: number, sessionDto: IDebugSessionDto): Promise { - const adapterProvider = this.getAdapterProviderByHandle(adapterProviderHandle); - if (!adapterProvider) { - return Promise.reject(new Error('no handler found')); - } - const session = await this.getSession(sessionDto); - return this.getAdapterDescriptor(adapterProvider, session).then(x => this.convertToDto(x)); - } - - public async $acceptDebugSessionStarted(sessionDto: IDebugSessionDto): Promise { - const session = await this.getSession(sessionDto); - this._onDidStartDebugSession.fire(session); - } - - public async $acceptDebugSessionTerminated(sessionDto: IDebugSessionDto): Promise { - const session = await this.getSession(sessionDto); - if (session) { - this._onDidTerminateDebugSession.fire(session); - this._debugSessions.delete(session.id); - } - } - - public async $acceptDebugSessionActiveChanged(sessionDto: IDebugSessionDto | undefined): Promise { - this._activeDebugSession = sessionDto ? await this.getSession(sessionDto) : undefined; - this._onDidChangeActiveDebugSession.fire(this._activeDebugSession); - } - - public async $acceptDebugSessionNameChanged(sessionDto: IDebugSessionDto, name: string): Promise { - const session = await this.getSession(sessionDto); - if (session) { - session._acceptNameChanged(name); - } - } - - public async $acceptDebugSessionCustomEvent(sessionDto: IDebugSessionDto, event: any): Promise { - const session = await this.getSession(sessionDto); - const ee: vscode.DebugSessionCustomEvent = { - session: session, - event: event.event, - body: event.body - }; - this._onDidReceiveDebugSessionCustomEvent.fire(ee); - } - - // private & dto helpers - - private convertToDto(x: vscode.DebugAdapterDescriptor | undefined): IAdapterDescriptor { - if (x instanceof DebugAdapterExecutable) { - return { - type: 'executable', - command: x.command, - args: x.args, - options: x.options - }; - } else if (x instanceof DebugAdapterServer) { - return { - type: 'server', - port: x.port, - host: x.host - }; - } else /* if (x instanceof DebugAdapterImplementation) { - return { - type: 'implementation', - implementation: x.implementation - }; - } else */ { - throw new Error('convertToDto unexpected type'); - } - } - - private getAdapterFactoryByType(type: string): vscode.DebugAdapterDescriptorFactory | undefined { - const results = this._adapterFactories.filter(p => p.type === type); - if (results.length > 0) { - return results[0].factory; - } - return undefined; - } - - private getAdapterProviderByHandle(handle: number): vscode.DebugAdapterDescriptorFactory | undefined { - const results = this._adapterFactories.filter(p => p.handle === handle); - if (results.length > 0) { - return results[0].factory; - } - return undefined; - } - - private getConfigProviderByHandle(handle: number): vscode.DebugConfigurationProvider | undefined { - const results = this._configProviders.filter(p => p.handle === handle); - if (results.length > 0) { - return results[0].provider; - } - return undefined; - } - - private definesDebugType(ed: IExtensionDescription, type: string) { - if (ed.contributes) { - const debuggers = ed.contributes['debuggers']; - if (debuggers && debuggers.length > 0) { - for (const dbg of debuggers) { - // only debugger contributions with a "label" are considered a "defining" debugger contribution - if (dbg.label && dbg.type) { - if (dbg.type === type) { - return true; - } - } - } - } - } - return false; - } - - private getDebugAdapterTrackers(session: ExtHostDebugSession): Promise { - - const config = session.configuration; - const type = config.type; - - const promises = this._trackerFactories - .filter(tuple => tuple.type === type || tuple.type === '*') - .map(tuple => asPromise>(() => tuple.factory.createDebugAdapterTracker(session)).then(p => p, err => null)); - - return Promise.race([ - Promise.all(promises).then(result => { - const trackers = result.filter(t => !!t); // filter null - if (trackers.length > 0) { - return new MultiTracker(trackers); - } - return undefined; - }), - new Promise((resolve, reject) => { - const timeout = setTimeout(() => { - clearTimeout(timeout); - reject(new Error('timeout')); - }, 1000); - }) - ]).catch(err => { - // ignore errors - return undefined; - }); - } - - private async getAdapterDescriptor(adapterProvider: vscode.DebugAdapterDescriptorFactory | undefined, session: ExtHostDebugSession): Promise { - - // a "debugServer" attribute in the launch config takes precedence - const serverPort = session.configuration.debugServer; - if (typeof serverPort === 'number') { - return Promise.resolve(new DebugAdapterServer(serverPort)); - } - - // TODO@AW legacy - const pair = this._configProviders.filter(p => p.type === session.type).pop(); - if (pair && pair.provider.debugAdapterExecutable) { - const func = pair.provider.debugAdapterExecutable; - return asPromise(() => func(session.workspaceFolder, CancellationToken.None)).then(executable => { - if (executable) { - return executable; - } - return undefined; - }); - } - - if (adapterProvider) { - const extensionRegistry = await this._extensionService.getExtensionRegistry(); - return asPromise(() => adapterProvider.createDebugAdapterDescriptor(session, this.daExecutableFromPackage(session, extensionRegistry))).then(daDescriptor => { - if (daDescriptor) { - return daDescriptor; - } - return undefined; - }); - } - - // try deprecated command based extension API "adapterExecutableCommand" to determine the executable - // TODO@AW legacy - const aex = this._aexCommands.get(session.type); - if (aex) { - const folder = session.workspaceFolder; - const rootFolder = folder ? folder.uri.toString() : undefined; - return this._commandService.executeCommand(aex, rootFolder).then((ae: { command: string, args: string[] }) => { - return new DebugAdapterExecutable(ae.command, ae.args || []); - }); - } - - // fallback: use executable information from package.json - const extensionRegistry = await this._extensionService.getExtensionRegistry(); - return Promise.resolve(this.daExecutableFromPackage(session, extensionRegistry)); - } - - private daExecutableFromPackage(session: ExtHostDebugSession, extensionRegistry: ExtensionDescriptionRegistry): DebugAdapterExecutable | undefined { - const dae = ExecutableDebugAdapter.platformAdapterExecutable(extensionRegistry.getAllExtensionDescriptions(), session.type); - if (dae) { - return new DebugAdapterExecutable(dae.command, dae.args, dae.options); - } - return undefined; - } - - private startBreakpoints() { - if (!this._breakpointEventsActive) { - this._breakpointEventsActive = true; - this._debugServiceProxy.$startBreakpointEvents(); - } - } - - private fireBreakpointChanges(added: vscode.Breakpoint[], removed: vscode.Breakpoint[], changed: vscode.Breakpoint[]) { - if (added.length > 0 || removed.length > 0 || changed.length > 0) { - this._onDidChangeBreakpoints.fire(Object.freeze({ - added, - removed, - changed, - })); - } - } - - private async getSession(dto: IDebugSessionDto): Promise { - if (dto) { - if (typeof dto === 'string') { - const ds = this._debugSessions.get(dto); - if (ds) { - return ds; - } - } else { - let ds = this._debugSessions.get(dto.id); - if (!ds) { - const folder = await this.getFolder(dto.folderUri); - ds = new ExtHostDebugSession(this._debugServiceProxy, dto.id, dto.type, dto.name, folder, dto.configuration); - this._debugSessions.set(ds.id, ds); - this._debugServiceProxy.$sessionCached(ds.id); - } - return ds; + let needNewTerminal = true; // be pessimistic + if (this._integratedTerminalInstance) { + const pid = await this._integratedTerminalInstance.processId; + needNewTerminal = await hasChildProcesses(pid); // if no processes running in terminal reuse terminal } - } - throw new Error('cannot find session'); - } - - private getFolder(_folderUri: UriComponents | undefined): Promise { - if (_folderUri) { - const folderURI = URI.revive(_folderUri); - return this._workspaceService.resolveWorkspaceFolder(folderURI); - } - return Promise.resolve(undefined); - } -} -export class ExtHostDebugSession implements vscode.DebugSession { - - constructor( - private _debugServiceProxy: MainThreadDebugServiceShape, - private _id: DebugSessionUUID, - private _type: string, - private _name: string, - private _workspaceFolder: vscode.WorkspaceFolder | undefined, - private _configuration: vscode.DebugConfiguration) { - } - - public get id(): string { - return this._id; - } + const configProvider = await this._configurationService.getConfigProvider(); + const shell = this._terminalService.getDefaultShell(true, configProvider); - public get type(): string { - return this._type; - } - - public get name(): string { - return this._name; - } - - public set name(name: string) { - this._name = name; - this._debugServiceProxy.$setDebugSessionName(this._id, name); - } - - _acceptNameChanged(name: string) { - this._name = name; - } - - public get workspaceFolder(): vscode.WorkspaceFolder | undefined { - return this._workspaceFolder; - } - - public get configuration(): vscode.DebugConfiguration { - return this._configuration; - } - - public customRequest(command: string, args: any): Promise { - return this._debugServiceProxy.$customDebugAdapterRequest(this._id, command, args); - } -} - -export class ExtHostDebugConsole implements vscode.DebugConsole { - - private _debugServiceProxy: MainThreadDebugServiceShape; - - constructor(proxy: MainThreadDebugServiceShape) { - this._debugServiceProxy = proxy; - } - - append(value: string): void { - this._debugServiceProxy.$appendDebugConsole(value); - } + if (needNewTerminal || !this._integratedTerminalInstance) { - appendLine(value: string): void { - this.append(value + '\n'); - } -} - -export class ExtHostVariableResolverService extends AbstractVariableResolverService { - - constructor(folders: vscode.WorkspaceFolder[], editorService: ExtHostDocumentsAndEditors, configurationService: ExtHostConfigProvider) { - super({ - getFolderUri: (folderName: string): URI | undefined => { - const found = folders.filter(f => f.name === folderName); - if (found && found.length > 0) { - return found[0].uri; - } - return undefined; - }, - getWorkspaceFolderCount: (): number => { - return folders.length; - }, - getConfigurationValue: (folderUri: URI, section: string): string | undefined => { - return configurationService.getConfiguration(undefined, folderUri).get(section); - }, - getExecPath: (): string | undefined => { - return process.env['VSCODE_EXEC_PATH']; - }, - getFilePath: (): string | undefined => { - const activeEditor = editorService.activeEditor(); - if (activeEditor) { - const resource = activeEditor.document.uri; - if (resource.scheme === Schemas.file) { - return path.normalize(resource.fsPath); - } - } - return undefined; - }, - getSelectedText: (): string | undefined => { - const activeEditor = editorService.activeEditor(); - if (activeEditor && !activeEditor.selection.isEmpty) { - return activeEditor.document.getText(activeEditor.selection); - } - return undefined; - }, - getLineNumber: (): string | undefined => { - const activeEditor = editorService.activeEditor(); - if (activeEditor) { - return String(activeEditor.selection.end.line + 1); - } - return undefined; + const options: vscode.TerminalOptions = { + shellPath: shell, + // shellArgs: this._terminalService._getDefaultShellArgs(configProvider), + cwd: args.cwd, + name: args.title || nls.localize('debug.terminal.title', "debuggee"), + env: args.env + }; + delete args.cwd; + delete args.env; + this._integratedTerminalInstance = this._terminalService.createTerminalFromOptions(options); } - }, process.env as IProcessEnvironment); - } -} - -interface ConfigProviderTuple { - type: string; - handle: number; - provider: vscode.DebugConfigurationProvider; -} - -interface DescriptorFactoryTuple { - type: string; - handle: number; - factory: vscode.DebugAdapterDescriptorFactory; -} - -interface TrackerFactoryTuple { - type: string; - handle: number; - factory: vscode.DebugAdapterTrackerFactory; -} - -class MultiTracker implements vscode.DebugAdapterTracker { - - constructor(private trackers: vscode.DebugAdapterTracker[]) { - } - - onWillStartSession(): void { - this.trackers.forEach(t => t.onWillStartSession ? t.onWillStartSession() : undefined); - } - - onWillReceiveMessage(message: any): void { - this.trackers.forEach(t => t.onWillReceiveMessage ? t.onWillReceiveMessage(message) : undefined); - } - onDidSendMessage(message: any): void { - this.trackers.forEach(t => t.onDidSendMessage ? t.onDidSendMessage(message) : undefined); - } - - onWillStopSession(): void { - this.trackers.forEach(t => t.onWillStopSession ? t.onWillStopSession() : undefined); - } - - onError(error: Error): void { - this.trackers.forEach(t => t.onError ? t.onError(error) : undefined); - } + const terminal = this._integratedTerminalInstance; - onExit(code: number, signal: string): void { - this.trackers.forEach(t => t.onExit ? t.onExit(code, signal) : undefined); - } -} + terminal.show(); -interface IDapTransport { - start(cb: (msg: DebugProtocol.ProtocolMessage) => void, errorcb: (event: DebugProtocol.Event) => void): void; - send(message: DebugProtocol.ProtocolMessage): void; - stop(): void; -} + const shellProcessId = await this._integratedTerminalInstance.processId; + const command = prepareCommand(args, shell, configProvider); + terminal.sendText(command, true); -class DirectDebugAdapter extends AbstractDebugAdapter implements IDapTransport { + return shellProcessId; + } else if (args.kind === 'external') { - private _sendUp!: (msg: DebugProtocol.ProtocolMessage) => void; - - constructor(implementation: any) { - super(); - if (implementation.__setTransport) { - implementation.__setTransport(this); + runInExternalTerminal(args, await this._configurationService.getConfigProvider()); } + return super.$runInTerminal(args); } - // IDapTransport - start(cb: (msg: DebugProtocol.ProtocolMessage) => void, errorcb: (event: DebugProtocol.Event) => void) { - this._sendUp = cb; - } - - // AbstractDebugAdapter - startSession(): Promise { - return Promise.resolve(undefined); - } - - // AbstractDebugAdapter - // VSCode -> DA - sendMessage(message: DebugProtocol.ProtocolMessage): void { - this._sendUp(message); + protected createVariableResolver(folders: vscode.WorkspaceFolder[], editorService: ExtHostDocumentsAndEditors, configurationService: ExtHostConfigProvider): AbstractVariableResolverService { + return new ExtHostVariableResolverService(folders, editorService, configurationService, process.env as IProcessEnvironment); } - // AbstractDebugAdapter - stopSession(): Promise { - this.stop(); - return Promise.resolve(undefined); - } - - // IDapTransport - // DA -> VSCode - send(message: DebugProtocol.ProtocolMessage) { - this.acceptMessage(message); - } - - // IDapTransport - stop(): void { - throw new Error('Method not implemented.'); - } } diff --git a/src/vs/workbench/api/node/extHostExtensionService.ts b/src/vs/workbench/api/node/extHostExtensionService.ts index e3e0cd936b7c..5e66335bb044 100644 --- a/src/vs/workbench/api/node/extHostExtensionService.ts +++ b/src/vs/workbench/api/node/extHostExtensionService.ts @@ -72,6 +72,7 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService { return nativeProcessSend.apply(process, args); } mainThreadConsole.$logExtensionHostMessage(args[0]); + return false; }; } diff --git a/src/vs/workbench/api/node/extHostSearch.ts b/src/vs/workbench/api/node/extHostSearch.ts index 94a5194023f8..cb9669501d08 100644 --- a/src/vs/workbench/api/node/extHostSearch.ts +++ b/src/vs/workbench/api/node/extHostSearch.ts @@ -3,47 +3,37 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CancellationToken } from 'vs/base/common/cancellation'; import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { URI, UriComponents } from 'vs/base/common/uri'; +import { URI } from 'vs/base/common/uri'; import * as pfs from 'vs/base/node/pfs'; import { ILogService } from 'vs/platform/log/common/log'; -import { IFileQuery, IFolderQuery, IRawFileQuery, IRawQuery, IRawTextQuery, ISearchCompleteStats, ITextQuery, isSerializedFileMatch, ISerializedSearchProgressItem } from 'vs/workbench/services/search/common/search'; -import { FileSearchManager } from 'vs/workbench/services/search/node/fileSearchManager'; +import { IFileQuery, IRawFileQuery, ISearchCompleteStats, isSerializedFileMatch, ISerializedSearchProgressItem, ITextQuery } from 'vs/workbench/services/search/common/search'; import { SearchService } from 'vs/workbench/services/search/node/rawSearchService'; import { RipgrepSearchProvider } from 'vs/workbench/services/search/node/ripgrepSearchProvider'; import { OutputChannel } from 'vs/workbench/services/search/node/ripgrepSearchUtils'; -import { TextSearchManager } from 'vs/workbench/services/search/node/textSearchManager'; import * as vscode from 'vscode'; -import { ExtHostSearchShape, MainContext, MainThreadSearchShape } from '../common/extHost.protocol'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { IURITransformerService } from 'vs/workbench/api/common/extHostUriTransformerService'; import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; +import { ExtHostSearch, reviveQuery } from 'vs/workbench/api/common/extHostSearch'; +import { Schemas } from 'vs/base/common/network'; +import { NativeTextSearchManager } from 'vs/workbench/services/search/node/textSearchManager'; +import { TextSearchManager } from 'vs/workbench/services/search/common/textSearchManager'; -export class ExtHostSearch implements ExtHostSearchShape { +export class NativeExtHostSearch extends ExtHostSearch { - private readonly _proxy: MainThreadSearchShape; - private readonly _textSearchProvider = new Map(); - private readonly _textSearchUsedSchemes = new Set(); - private readonly _fileSearchProvider = new Map(); - private readonly _fileSearchUsedSchemes = new Set(); - private _handlePool: number = 0; + protected _pfs: typeof pfs = pfs; // allow extending for tests private _internalFileSearchHandle: number = -1; private _internalFileSearchProvider: SearchService | null = null; - private _fileSearchManager: FileSearchManager; - - protected _pfs: typeof pfs = pfs; // allow extending for tests - constructor( @IExtHostRpcService extHostRpc: IExtHostRpcService, @IExtHostInitDataService initData: IExtHostInitDataService, - @IURITransformerService private _uriTransformer: IURITransformerService, - @ILogService private _logService: ILogService, + @IURITransformerService _uriTransformer: IURITransformerService, + @ILogService _logService: ILogService, ) { - this._proxy = extHostRpc.getProxy(MainContext.MainThreadSearch); - this._fileSearchManager = new FileSearchManager(); + super(extHostRpc, _uriTransformer, _logService); if (initData.remote.isRemote && initData.remote.authority) { this._registerEHSearchProviders(); @@ -52,47 +42,11 @@ export class ExtHostSearch implements ExtHostSearchShape { private _registerEHSearchProviders(): void { const outputChannel = new OutputChannel(this._logService); - this.registerTextSearchProvider('file', new RipgrepSearchProvider(outputChannel)); - this.registerInternalFileSearchProvider('file', new SearchService()); - } - - private _transformScheme(scheme: string): string { - return this._uriTransformer.transformOutgoingScheme(scheme); - } - - registerTextSearchProvider(scheme: string, provider: vscode.TextSearchProvider): IDisposable { - if (this._textSearchUsedSchemes.has(scheme)) { - throw new Error(`a text search provider for the scheme '${scheme}' is already registered`); - } - - this._textSearchUsedSchemes.add(scheme); - const handle = this._handlePool++; - this._textSearchProvider.set(handle, provider); - this._proxy.$registerTextSearchProvider(handle, this._transformScheme(scheme)); - return toDisposable(() => { - this._textSearchUsedSchemes.delete(scheme); - this._textSearchProvider.delete(handle); - this._proxy.$unregisterProvider(handle); - }); - } - - registerFileSearchProvider(scheme: string, provider: vscode.FileSearchProvider): IDisposable { - if (this._fileSearchUsedSchemes.has(scheme)) { - throw new Error(`a file search provider for the scheme '${scheme}' is already registered`); - } - - this._fileSearchUsedSchemes.add(scheme); - const handle = this._handlePool++; - this._fileSearchProvider.set(handle, provider); - this._proxy.$registerFileSearchProvider(handle, this._transformScheme(scheme)); - return toDisposable(() => { - this._fileSearchUsedSchemes.delete(scheme); - this._fileSearchProvider.delete(handle); - this._proxy.$unregisterProvider(handle); - }); + this.registerTextSearchProvider(Schemas.file, new RipgrepSearchProvider(outputChannel)); + this.registerInternalFileSearchProvider(Schemas.file, new SearchService()); } - registerInternalFileSearchProvider(scheme: string, provider: SearchService): IDisposable { + private registerInternalFileSearchProvider(scheme: string, provider: SearchService): IDisposable { const handle = this._handlePool++; this._internalFileSearchProvider = provider; this._internalFileSearchHandle = handle; @@ -103,23 +57,16 @@ export class ExtHostSearch implements ExtHostSearchShape { }); } - $provideFileSearchResults(handle: number, session: number, rawQuery: IRawFileQuery, token: CancellationToken): Promise { + $provideFileSearchResults(handle: number, session: number, rawQuery: IRawFileQuery, token: vscode.CancellationToken): Promise { const query = reviveQuery(rawQuery); if (handle === this._internalFileSearchHandle) { return this.doInternalFileSearch(handle, session, query, token); - } else { - const provider = this._fileSearchProvider.get(handle); - if (provider) { - return this._fileSearchManager.fileSearch(query, provider, batch => { - this._proxy.$handleFileMatch(handle, session, batch.map(p => p.resource)); - }, token); - } else { - throw new Error('unknown provider: ' + handle); - } } + + return super.$provideFileSearchResults(handle, session, rawQuery, token); } - private doInternalFileSearch(handle: number, session: number, rawQuery: IFileQuery, token: CancellationToken): Promise { + private doInternalFileSearch(handle: number, session: number, rawQuery: IFileQuery, token: vscode.CancellationToken): Promise { const onResult = (ev: ISerializedSearchProgressItem) => { if (isSerializedFileMatch(ev)) { ev = [ev]; @@ -147,37 +94,11 @@ export class ExtHostSearch implements ExtHostSearchShape { this._internalFileSearchProvider.clearCache(cacheKey); } - this._fileSearchManager.clearCache(cacheKey); - - return Promise.resolve(undefined); + return super.$clearCache(cacheKey); } - $provideTextSearchResults(handle: number, session: number, rawQuery: IRawTextQuery, token: CancellationToken): Promise { - const provider = this._textSearchProvider.get(handle); - if (!provider || !provider.provideTextSearchResults) { - throw new Error(`Unknown provider ${handle}`); - } - - const query = reviveQuery(rawQuery); - const engine = new TextSearchManager(query, provider, this._pfs); - return engine.search(progress => this._proxy.$handleTextMatch(handle, session, progress), token); + protected createTextSearchManager(query: ITextQuery, provider: vscode.TextSearchProvider): TextSearchManager { + return new NativeTextSearchManager(query, provider); } } -function reviveQuery(rawQuery: U): U extends IRawTextQuery ? ITextQuery : IFileQuery { - return { - ...rawQuery, // TODO - ...{ - folderQueries: rawQuery.folderQueries && rawQuery.folderQueries.map(reviveFolderQuery), - extraFileResources: rawQuery.extraFileResources && rawQuery.extraFileResources.map(components => URI.revive(components)) - } - }; -} - -function reviveFolderQuery(rawFolderQuery: IFolderQuery): IFolderQuery { - return { - ...rawFolderQuery, - folder: URI.revive(rawFolderQuery.folder) - }; -} - diff --git a/src/vs/workbench/api/node/extHostStoragePaths.ts b/src/vs/workbench/api/node/extHostStoragePaths.ts index 19cf948cc340..df911131c65a 100644 --- a/src/vs/workbench/api/node/extHostStoragePaths.ts +++ b/src/vs/workbench/api/node/extHostStoragePaths.ts @@ -11,6 +11,7 @@ import { IExtensionDescription } from 'vs/platform/extensions/common/extensions' import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths'; import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; import { withNullAsUndefined } from 'vs/base/common/types'; +import { ILogService } from 'vs/platform/log/common/log'; export class ExtensionStoragePaths implements IExtensionStoragePaths { @@ -22,7 +23,10 @@ export class ExtensionStoragePaths implements IExtensionStoragePaths { readonly whenReady: Promise; private _value?: string; - constructor(@IExtHostInitDataService initData: IExtHostInitDataService) { + constructor( + @IExtHostInitDataService initData: IExtHostInitDataService, + @ILogService private readonly _logService: ILogService, + ) { this._workspace = withNullAsUndefined(initData.workspace); this._environment = initData.environment; this.whenReady = this._getOrCreateWorkspaceStoragePath().then(value => this._value = value); @@ -69,7 +73,7 @@ export class ExtensionStoragePaths implements IExtensionStoragePaths { return storagePath; } catch (e) { - console.error(e); + this._logService.error(e); return undefined; } } diff --git a/src/vs/workbench/api/node/extHostTask.ts b/src/vs/workbench/api/node/extHostTask.ts index e1f3430781ca..0e451c87069e 100644 --- a/src/vs/workbench/api/node/extHostTask.ts +++ b/src/vs/workbench/api/node/extHostTask.ts @@ -5,24 +5,28 @@ // import * as path from 'vs/base/common/path'; -import { /*URI,*/ UriComponents } from 'vs/base/common/uri'; +import { URI, UriComponents } from 'vs/base/common/uri'; // import { win32 } from 'vs/base/node/processes'; import * as types from 'vs/workbench/api/common/extHostTypes'; import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; import * as vscode from 'vscode'; import * as tasks from '../common/shared/tasks'; -// import { ExtHostVariableResolverService } from 'vs/workbench/api/node/extHostDebugService'; +import * as Objects from 'vs/base/common/objects'; +import { ExtHostVariableResolverService } from 'vs/workbench/api/common/extHostDebugService'; import { IExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import { IExtHostConfiguration } from 'vs/workbench/api/common/extHostConfiguration'; -// import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; +import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { IExtHostTerminalService } from 'vs/workbench/api/common/extHostTerminalService'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; import { ExtHostTaskBase, TaskHandleDTO, TaskDTO, CustomExecutionDTO, HandlerData } from 'vs/workbench/api/common/extHostTask'; import { Schemas } from 'vs/base/common/network'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IProcessEnvironment } from 'vs/base/common/platform'; export class ExtHostTask extends ExtHostTaskBase { + private _variableResolver: ExtHostVariableResolverService | undefined; constructor( @IExtHostRpcService extHostRpc: IExtHostRpcService, @@ -30,9 +34,10 @@ export class ExtHostTask extends ExtHostTaskBase { @IExtHostWorkspace workspaceService: IExtHostWorkspace, @IExtHostDocumentsAndEditors editorService: IExtHostDocumentsAndEditors, @IExtHostConfiguration configurationService: IExtHostConfiguration, - @IExtHostTerminalService extHostTerminalService: IExtHostTerminalService + @IExtHostTerminalService extHostTerminalService: IExtHostTerminalService, + @ILogService logService: ILogService ) { - super(extHostRpc, initData, workspaceService, editorService, configurationService, extHostTerminalService); + super(extHostRpc, initData, workspaceService, editorService, configurationService, extHostTerminalService, logService); if (initData.remote.isRemote && initData.remote.authority) { this.registerTaskSystem(Schemas.vscodeRemote, { scheme: Schemas.vscodeRemote, @@ -69,7 +74,7 @@ export class ExtHostTask extends ExtHostTaskBase { if (value) { for (let task of value) { if (!task.definition || !validTypes[task.definition.type]) { - console.warn(`The task [${task.source}, ${task.name}] uses an undefined task type. The task will be ignored in the future.`); + this._logService.warn(`The task [${task.source}, ${task.name}] uses an undefined task type. The task will be ignored in the future.`); } const taskDTO: tasks.TaskDTO | undefined = TaskDTO.from(task, handler.extension); @@ -95,9 +100,42 @@ export class ExtHostTask extends ExtHostTaskBase { return resolvedTaskDTO; } + private async getVariableResolver(workspaceFolders: vscode.WorkspaceFolder[]): Promise { + if (this._variableResolver === undefined) { + const configProvider = await this._configurationService.getConfigProvider(); + this._variableResolver = new ExtHostVariableResolverService(workspaceFolders, this._editorService, configProvider, process.env as IProcessEnvironment); + } + return this._variableResolver; + } + + protected async resolveDefinition(uri: number | UriComponents | undefined, definition: vscode.TaskDefinition | undefined): Promise { + if (!uri || (typeof uri === 'number') || !definition) { + return definition; + } + const workspaceFolder = await this._workspaceProvider.resolveWorkspaceFolder(URI.revive(uri)); + const workspaceFolders = await this._workspaceProvider.getWorkspaceFolders2(); + if (!workspaceFolders || !workspaceFolder) { + return definition; + } + const resolver = await this.getVariableResolver(workspaceFolders); + const ws: IWorkspaceFolder = { + uri: workspaceFolder.uri, + name: workspaceFolder.name, + index: workspaceFolder.index, + toResource: () => { + throw new Error('Not implemented'); + } + }; + const resolvedDefinition = Objects.deepClone(definition); + for (const key in resolvedDefinition) { + resolvedDefinition[key] = resolver.resolve(ws, resolvedDefinition[key]); + } + + return resolvedDefinition; + } + public async $resolveVariables(uriComponents: UriComponents, toResolve: { process?: { name: string; cwd?: string; path?: string }, variables: string[] }): Promise<{ process?: string, variables: { [key: string]: string; } }> { - /*const configProvider = await this._configurationService.getConfigProvider(); - const uri: URI = URI.revive(uriComponents); + /*const uri: URI = URI.revive(uriComponents); const result = { process: undefined as string, variables: Object.create(null) @@ -107,7 +145,7 @@ export class ExtHostTask extends ExtHostTaskBase { if (!workspaceFolders || !workspaceFolder) { throw new Error('Unexpected: Tasks can only be run in a workspace folder'); } - const resolver = new ExtHostVariableResolverService(workspaceFolders, this._editorService, configProvider); + const resolver = await this.getVariableResolver(workspaceFolders); const ws: IWorkspaceFolder = { uri: workspaceFolder.uri, name: workspaceFolder.name, diff --git a/src/vs/workbench/api/node/extHostTerminalService.ts b/src/vs/workbench/api/node/extHostTerminalService.ts index e0c74f33f542..e2f96f5efe0c 100644 --- a/src/vs/workbench/api/node/extHostTerminalService.ts +++ b/src/vs/workbench/api/node/extHostTerminalService.ts @@ -16,7 +16,7 @@ import { IShellLaunchConfig, ITerminalEnvironment } from 'vs/workbench/contrib/t import { TerminalProcess } from 'vs/workbench/contrib/terminal/node/terminalProcess'; import { ExtHostWorkspace, IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; -import { ExtHostVariableResolverService } from 'vs/workbench/api/node/extHostDebugService'; +import { ExtHostVariableResolverService } from 'vs/workbench/api/common/extHostDebugService'; import { ExtHostDocumentsAndEditors, IExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import { getSystemShell, detectAvailableShells } from 'vs/workbench/contrib/terminal/node/terminal'; import { getMainProcessParentEnv } from 'vs/workbench/contrib/terminal/node/terminalEnvironment'; @@ -45,14 +45,14 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService { } public createTerminal(name?: string, shellPath?: string, shellArgs?: string[] | string): vscode.Terminal { - const terminal = new ExtHostTerminal(this._proxy, name); + const terminal = new ExtHostTerminal(this._proxy, { name, shellPath, shellArgs }, name); terminal.create(shellPath, shellArgs); this._terminals.push(terminal); return terminal; } public createTerminalFromOptions(options: vscode.TerminalOptions): vscode.Terminal { - const terminal = new ExtHostTerminal(this._proxy, options.name); + const terminal = new ExtHostTerminal(this._proxy, options, options.name); terminal.create(options.shellPath, options.shellArgs, options.cwd, options.env, /*options.waitOnExit*/ undefined, options.strictEnv, options.hideFromUser); this._terminals.push(terminal); return terminal; @@ -120,7 +120,7 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService { private async _updateVariableResolver(): Promise { const configProvider = await this._extHostConfiguration.getConfigProvider(); const workspaceFolders = await this._extHostWorkspace.getWorkspaceFolders2(); - this._variableResolver = new ExtHostVariableResolverService(workspaceFolders || [], this._extHostDocumentsAndEditors, configProvider); + this._variableResolver = new ExtHostVariableResolverService(workspaceFolders || [], this._extHostDocumentsAndEditors, configProvider, process.env as platform.IProcessEnvironment); } public async $spawnExtHostProcess(id: number, shellLaunchConfigDto: IShellLaunchConfigDto, activeWorkspaceRootUriComponents: UriComponents | undefined, cols: number, rows: number, isWorkspaceShellAllowed: boolean): Promise { diff --git a/src/vs/workbench/browser/actions.ts b/src/vs/workbench/browser/actions.ts index e796de7bcbca..1e36b69aea66 100644 --- a/src/vs/workbench/browser/actions.ts +++ b/src/vs/workbench/browser/actions.ts @@ -7,7 +7,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IAction } from 'vs/base/common/actions'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { ITree, IActionProvider } from 'vs/base/parts/tree/browser/tree'; -import { IInstantiationService, IConstructorSignature0, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, IConstructorSignature0, ServicesAccessor, BrandedService } from 'vs/platform/instantiation/common/instantiation'; /** * The action bar contributor allows to add actions to an actionbar in a given context. @@ -134,7 +134,7 @@ export interface IActionBarRegistry { * Registers an Actionbar contributor. It will be called to contribute actions to all the action bars * that are used in the Workbench in the given scope. */ - registerActionBarContributor(scope: string, ctor: IConstructorSignature0): void; + registerActionBarContributor(scope: string, ctor: { new(...services: Services): ActionBarContributor }): void; /** * Returns an array of registered action bar contributors known to the workbench for the given scope. diff --git a/src/vs/workbench/browser/actions/developerActions.ts b/src/vs/workbench/browser/actions/developerActions.ts index db02240757b8..864ff68b5468 100644 --- a/src/vs/workbench/browser/actions/developerActions.ts +++ b/src/vs/workbench/browser/actions/developerActions.ts @@ -152,7 +152,7 @@ class ToggleScreencastModeAction extends Action { } })); - const onKeyDown = domEvent(container, 'keydown', true); + const onKeyDown = domEvent(window, 'keydown', true); let keyboardTimeout: IDisposable = Disposable.None; let length = 0; @@ -214,9 +214,9 @@ class LogStorageAction extends Action { const developerCategory = nls.localize('developer', "Developer"); const registry = Registry.as(Extensions.WorkbenchActions); -registry.registerWorkbenchAction(new SyncActionDescriptor(InspectContextKeysAction, InspectContextKeysAction.ID, InspectContextKeysAction.LABEL), 'Developer: Inspect Context Keys', developerCategory); -registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleScreencastModeAction, ToggleScreencastModeAction.ID, ToggleScreencastModeAction.LABEL), 'Developer: Toggle Screencast Mode', developerCategory); -registry.registerWorkbenchAction(new SyncActionDescriptor(LogStorageAction, LogStorageAction.ID, LogStorageAction.LABEL), 'Developer: Log Storage Database Contents', developerCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(InspectContextKeysAction, InspectContextKeysAction.ID, InspectContextKeysAction.LABEL), 'Developer: Inspect Context Keys', developerCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleScreencastModeAction, ToggleScreencastModeAction.ID, ToggleScreencastModeAction.LABEL), 'Developer: Toggle Screencast Mode', developerCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(LogStorageAction, LogStorageAction.ID, LogStorageAction.LABEL), 'Developer: Log Storage Database Contents', developerCategory); // Screencast Mode const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); diff --git a/src/vs/workbench/browser/actions/helpActions.ts b/src/vs/workbench/browser/actions/helpActions.ts index 7c901cfb84b6..43f94c26560c 100644 --- a/src/vs/workbench/browser/actions/helpActions.ts +++ b/src/vs/workbench/browser/actions/helpActions.ts @@ -248,39 +248,39 @@ const registry = Registry.as(Extensions.WorkbenchActio const helpCategory = nls.localize('help', "Help"); if (KeybindingsReferenceAction.AVAILABLE) { - registry.registerWorkbenchAction(new SyncActionDescriptor(KeybindingsReferenceAction, KeybindingsReferenceAction.ID, KeybindingsReferenceAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_R) }), 'Help: Keyboard Shortcuts Reference', helpCategory); + registry.registerWorkbenchAction(SyncActionDescriptor.create(KeybindingsReferenceAction, KeybindingsReferenceAction.ID, KeybindingsReferenceAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_R) }), 'Help: Keyboard Shortcuts Reference', helpCategory); } if (OpenDocumentationUrlAction.AVAILABLE) { - registry.registerWorkbenchAction(new SyncActionDescriptor(OpenDocumentationUrlAction, OpenDocumentationUrlAction.ID, OpenDocumentationUrlAction.LABEL), 'Help: Documentation', helpCategory); + registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenDocumentationUrlAction, OpenDocumentationUrlAction.ID, OpenDocumentationUrlAction.LABEL), 'Help: Documentation', helpCategory); } if (OpenIntroductoryVideosUrlAction.AVAILABLE) { - registry.registerWorkbenchAction(new SyncActionDescriptor(OpenIntroductoryVideosUrlAction, OpenIntroductoryVideosUrlAction.ID, OpenIntroductoryVideosUrlAction.LABEL), 'Help: Introductory Videos', helpCategory); + registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenIntroductoryVideosUrlAction, OpenIntroductoryVideosUrlAction.ID, OpenIntroductoryVideosUrlAction.LABEL), 'Help: Introductory Videos', helpCategory); } if (OpenTipsAndTricksUrlAction.AVAILABLE) { - registry.registerWorkbenchAction(new SyncActionDescriptor(OpenTipsAndTricksUrlAction, OpenTipsAndTricksUrlAction.ID, OpenTipsAndTricksUrlAction.LABEL), 'Help: Tips and Tricks', helpCategory); + registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenTipsAndTricksUrlAction, OpenTipsAndTricksUrlAction.ID, OpenTipsAndTricksUrlAction.LABEL), 'Help: Tips and Tricks', helpCategory); } if (OpenNewsletterSignupUrlAction.AVAILABLE) { - registry.registerWorkbenchAction(new SyncActionDescriptor(OpenNewsletterSignupUrlAction, OpenNewsletterSignupUrlAction.ID, OpenNewsletterSignupUrlAction.LABEL), 'Help: Tips and Tricks', helpCategory); + registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenNewsletterSignupUrlAction, OpenNewsletterSignupUrlAction.ID, OpenNewsletterSignupUrlAction.LABEL), 'Help: Tips and Tricks', helpCategory); } if (OpenTwitterUrlAction.AVAILABLE) { - registry.registerWorkbenchAction(new SyncActionDescriptor(OpenTwitterUrlAction, OpenTwitterUrlAction.ID, OpenTwitterUrlAction.LABEL), 'Help: Join Us on Twitter', helpCategory); + registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenTwitterUrlAction, OpenTwitterUrlAction.ID, OpenTwitterUrlAction.LABEL), 'Help: Join Us on Twitter', helpCategory); } if (OpenRequestFeatureUrlAction.AVAILABLE) { - registry.registerWorkbenchAction(new SyncActionDescriptor(OpenRequestFeatureUrlAction, OpenRequestFeatureUrlAction.ID, OpenRequestFeatureUrlAction.LABEL), 'Help: Search Feature Requests', helpCategory); + registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenRequestFeatureUrlAction, OpenRequestFeatureUrlAction.ID, OpenRequestFeatureUrlAction.LABEL), 'Help: Search Feature Requests', helpCategory); } if (OpenLicenseUrlAction.AVAILABLE) { - registry.registerWorkbenchAction(new SyncActionDescriptor(OpenLicenseUrlAction, OpenLicenseUrlAction.ID, OpenLicenseUrlAction.LABEL), 'Help: View License', helpCategory); + registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenLicenseUrlAction, OpenLicenseUrlAction.ID, OpenLicenseUrlAction.LABEL), 'Help: View License', helpCategory); } if (OpenPrivacyStatementUrlAction.AVAILABE) { - registry.registerWorkbenchAction(new SyncActionDescriptor(OpenPrivacyStatementUrlAction, OpenPrivacyStatementUrlAction.ID, OpenPrivacyStatementUrlAction.LABEL), 'Help: Privacy Statement', helpCategory); + registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenPrivacyStatementUrlAction, OpenPrivacyStatementUrlAction.ID, OpenPrivacyStatementUrlAction.LABEL), 'Help: Privacy Statement', helpCategory); } // --- Menu Registration @@ -297,15 +297,6 @@ if (OpenDocumentationUrlAction.AVAILABLE) { order: 3 }); } -/* // {{SQL CARBON EDIT}} - Disable unused menu item -MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { - group: '1_welcome', - command: { - id: 'update.showCurrentReleaseNotes', - title: nls.localize({ key: 'miReleaseNotes', comment: ['&& denotes a mnemonic'] }, "&&Release Notes") - }, - order: 4 -}); // Reference if (KeybindingsReferenceAction.AVAILABLE) { @@ -362,7 +353,7 @@ if (OpenRequestFeatureUrlAction.AVAILABLE) { }, order: 2 }); -}*/ +} // Legal if (OpenLicenseUrlAction.AVAILABLE) { diff --git a/src/vs/workbench/browser/actions/layoutActions.ts b/src/vs/workbench/browser/actions/layoutActions.ts index fc21cc0785b6..d2aad3396f49 100644 --- a/src/vs/workbench/browser/actions/layoutActions.ts +++ b/src/vs/workbench/browser/actions/layoutActions.ts @@ -56,7 +56,7 @@ export class ToggleActivityBarVisibilityAction extends Action { } } -registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleActivityBarVisibilityAction, ToggleActivityBarVisibilityAction.ID, ToggleActivityBarVisibilityAction.LABEL), 'View: Toggle Activity Bar Visibility', viewCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleActivityBarVisibilityAction, ToggleActivityBarVisibilityAction.ID, ToggleActivityBarVisibilityAction.LABEL), 'View: Toggle Activity Bar Visibility', viewCategory); MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { group: '2_workbench_layout', @@ -91,7 +91,7 @@ class ToggleCenteredLayout extends Action { } } -registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleCenteredLayout, ToggleCenteredLayout.ID, ToggleCenteredLayout.LABEL), 'View: Toggle Centered Layout', viewCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleCenteredLayout, ToggleCenteredLayout.ID, ToggleCenteredLayout.LABEL), 'View: Toggle Centered Layout', viewCategory); MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { group: '1_toggle_view', @@ -143,7 +143,7 @@ export class ToggleEditorLayoutAction extends Action { } const group = viewCategory; -registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleEditorLayoutAction, ToggleEditorLayoutAction.ID, ToggleEditorLayoutAction.LABEL, { primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_0, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_0 } }), 'View: Toggle Vertical/Horizontal Editor Layout', group); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleEditorLayoutAction, ToggleEditorLayoutAction.ID, ToggleEditorLayoutAction.LABEL, { primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_0, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_0 } }), 'View: Toggle Vertical/Horizontal Editor Layout', group); MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { group: 'z_flip', @@ -186,7 +186,7 @@ export class ToggleSidebarPositionAction extends Action { } } -registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleSidebarPositionAction, ToggleSidebarPositionAction.ID, ToggleSidebarPositionAction.LABEL), 'View: Toggle Side Bar Position', viewCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleSidebarPositionAction, ToggleSidebarPositionAction.ID, ToggleSidebarPositionAction.LABEL), 'View: Toggle Side Bar Position', viewCategory); MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { group: '3_workbench_layout_move', @@ -231,7 +231,7 @@ export class ToggleEditorVisibilityAction extends Action { } } -registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleEditorVisibilityAction, ToggleEditorVisibilityAction.ID, ToggleEditorVisibilityAction.LABEL), 'View: Toggle Editor Area Visibility', viewCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleEditorVisibilityAction, ToggleEditorVisibilityAction.ID, ToggleEditorVisibilityAction.LABEL), 'View: Toggle Editor Area Visibility', viewCategory); MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { group: '2_workbench_layout', @@ -266,7 +266,7 @@ export class ToggleSidebarVisibilityAction extends Action { } } -registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleSidebarVisibilityAction, ToggleSidebarVisibilityAction.ID, ToggleSidebarVisibilityAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_B }), 'View: Toggle Side Bar Visibility', viewCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleSidebarVisibilityAction, ToggleSidebarVisibilityAction.ID, ToggleSidebarVisibilityAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_B }), 'View: Toggle Side Bar Visibility', viewCategory); MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { group: '2_appearance', @@ -313,7 +313,7 @@ export class ToggleStatusbarVisibilityAction extends Action { } } -registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleStatusbarVisibilityAction, ToggleStatusbarVisibilityAction.ID, ToggleStatusbarVisibilityAction.LABEL), 'View: Toggle Status Bar Visibility', viewCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleStatusbarVisibilityAction, ToggleStatusbarVisibilityAction.ID, ToggleStatusbarVisibilityAction.LABEL), 'View: Toggle Status Bar Visibility', viewCategory); MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { group: '2_workbench_layout', @@ -350,7 +350,7 @@ class ToggleTabsVisibilityAction extends Action { } } -registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleTabsVisibilityAction, ToggleTabsVisibilityAction.ID, ToggleTabsVisibilityAction.LABEL, { +registry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleTabsVisibilityAction, ToggleTabsVisibilityAction.ID, ToggleTabsVisibilityAction.LABEL, { primary: undefined, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KEY_W, }, linux: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KEY_W, } @@ -379,7 +379,7 @@ class ToggleZenMode extends Action { } } -registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleZenMode, ToggleZenMode.ID, ToggleZenMode.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_Z) }), 'View: Toggle Zen Mode', viewCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleZenMode, ToggleZenMode.ID, ToggleZenMode.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_Z) }), 'View: Toggle Zen Mode', viewCategory); MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { group: '1_toggle_view', @@ -442,7 +442,7 @@ export class ToggleMenuBarAction extends Action { } if (isWindows || isLinux || isWeb) { - registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleMenuBarAction, ToggleMenuBarAction.ID, ToggleMenuBarAction.LABEL), 'View: Toggle Menu Bar', viewCategory); + registry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleMenuBarAction, ToggleMenuBarAction.ID, ToggleMenuBarAction.LABEL), 'View: Toggle Menu Bar', viewCategory); } MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { @@ -529,5 +529,5 @@ export class DecreaseViewSizeAction extends BaseResizeViewAction { } } -registry.registerWorkbenchAction(new SyncActionDescriptor(IncreaseViewSizeAction, IncreaseViewSizeAction.ID, IncreaseViewSizeAction.LABEL, undefined), 'View: Increase Current View Size', viewCategory); -registry.registerWorkbenchAction(new SyncActionDescriptor(DecreaseViewSizeAction, DecreaseViewSizeAction.ID, DecreaseViewSizeAction.LABEL, undefined), 'View: Decrease Current View Size', viewCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(IncreaseViewSizeAction, IncreaseViewSizeAction.ID, IncreaseViewSizeAction.LABEL, undefined), 'View: Increase Current View Size', viewCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(DecreaseViewSizeAction, DecreaseViewSizeAction.ID, DecreaseViewSizeAction.LABEL, undefined), 'View: Decrease Current View Size', viewCategory); diff --git a/src/vs/workbench/browser/actions/listCommands.ts b/src/vs/workbench/browser/actions/listCommands.ts index db5f82dbc40a..ba6418a543cb 100644 --- a/src/vs/workbench/browser/actions/listCommands.ts +++ b/src/vs/workbench/browser/actions/listCommands.ts @@ -872,10 +872,9 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ }); KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: 'list.scrollLeft', + id: '84256', weight: KeybindingWeight.WorkbenchContrib, when: WorkbenchListFocusContextKey, - primary: KeyMod.CtrlCmd | KeyCode.LeftArrow, handler: accessor => { const focused = accessor.get(IListService).lastFocusedList; @@ -891,7 +890,6 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ id: 'list.scrollRight', weight: KeybindingWeight.WorkbenchContrib, when: WorkbenchListFocusContextKey, - primary: KeyMod.CtrlCmd | KeyCode.RightArrow, handler: accessor => { const focused = accessor.get(IListService).lastFocusedList; diff --git a/src/vs/workbench/browser/actions/navigationActions.ts b/src/vs/workbench/browser/actions/navigationActions.ts index d697091687bc..8aa734cb4ec7 100644 --- a/src/vs/workbench/browser/actions/navigationActions.ts +++ b/src/vs/workbench/browser/actions/navigationActions.ts @@ -280,7 +280,7 @@ class NavigateDownAction extends BaseNavigationAction { const registry = Registry.as(Extensions.WorkbenchActions); const viewCategory = nls.localize('view', "View"); -registry.registerWorkbenchAction(new SyncActionDescriptor(NavigateUpAction, NavigateUpAction.ID, NavigateUpAction.LABEL, undefined), 'View: Navigate to the View Above', viewCategory); -registry.registerWorkbenchAction(new SyncActionDescriptor(NavigateDownAction, NavigateDownAction.ID, NavigateDownAction.LABEL, undefined), 'View: Navigate to the View Below', viewCategory); -registry.registerWorkbenchAction(new SyncActionDescriptor(NavigateLeftAction, NavigateLeftAction.ID, NavigateLeftAction.LABEL, undefined), 'View: Navigate to the View on the Left', viewCategory); -registry.registerWorkbenchAction(new SyncActionDescriptor(NavigateRightAction, NavigateRightAction.ID, NavigateRightAction.LABEL, undefined), 'View: Navigate to the View on the Right', viewCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(NavigateUpAction, NavigateUpAction.ID, NavigateUpAction.LABEL, undefined), 'View: Navigate to the View Above', viewCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(NavigateDownAction, NavigateDownAction.ID, NavigateDownAction.LABEL, undefined), 'View: Navigate to the View Below', viewCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(NavigateLeftAction, NavigateLeftAction.ID, NavigateLeftAction.LABEL, undefined), 'View: Navigate to the View on the Left', viewCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(NavigateRightAction, NavigateRightAction.ID, NavigateRightAction.LABEL, undefined), 'View: Navigate to the View on the Right', viewCategory); diff --git a/src/vs/workbench/browser/actions/windowActions.ts b/src/vs/workbench/browser/actions/windowActions.ts index bc5a96f7f741..665e962eb98c 100644 --- a/src/vs/workbench/browser/actions/windowActions.ts +++ b/src/vs/workbench/browser/actions/windowActions.ts @@ -269,18 +269,18 @@ const registry = Registry.as(Extensions.WorkbenchActio // --- Actions Registration const fileCategory = nls.localize('file', "File"); -registry.registerWorkbenchAction(new SyncActionDescriptor(NewWindowAction, NewWindowAction.ID, NewWindowAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_N }), 'New Window'); -registry.registerWorkbenchAction(new SyncActionDescriptor(QuickOpenRecentAction, QuickOpenRecentAction.ID, QuickOpenRecentAction.LABEL), 'File: Quick Open Recent...', fileCategory); -registry.registerWorkbenchAction(new SyncActionDescriptor(OpenRecentAction, OpenRecentAction.ID, OpenRecentAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_R, mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_R } }), 'File: Open Recent...', fileCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(NewWindowAction, NewWindowAction.ID, NewWindowAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_N }), 'New Window'); +registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickOpenRecentAction, QuickOpenRecentAction.ID, QuickOpenRecentAction.LABEL), 'File: Quick Open Recent...', fileCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenRecentAction, OpenRecentAction.ID, OpenRecentAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_R, mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_R } }), 'File: Open Recent...', fileCategory); const viewCategory = nls.localize('view', "View"); -registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleFullScreenAction, ToggleFullScreenAction.ID, ToggleFullScreenAction.LABEL, { primary: KeyCode.F11, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KEY_F } }), 'View: Toggle Full Screen', viewCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleFullScreenAction, ToggleFullScreenAction.ID, ToggleFullScreenAction.LABEL, { primary: KeyCode.F11, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KEY_F } }), 'View: Toggle Full Screen', viewCategory); const developerCategory = nls.localize('developer', "Developer"); -registry.registerWorkbenchAction(new SyncActionDescriptor(ReloadWindowAction, ReloadWindowAction.ID, ReloadWindowAction.LABEL), 'Developer: Reload Window', developerCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ReloadWindowAction, ReloadWindowAction.ID, ReloadWindowAction.LABEL), 'Developer: Reload Window', developerCategory); const helpCategory = nls.localize('help', "Help"); -registry.registerWorkbenchAction(new SyncActionDescriptor(ShowAboutDialogAction, ShowAboutDialogAction.ID, ShowAboutDialogAction.LABEL), `Help: About`, helpCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ShowAboutDialogAction, ShowAboutDialogAction.ID, ShowAboutDialogAction.LABEL), `Help: About`, helpCategory); // --- Commands/Keybindings Registration diff --git a/src/vs/workbench/browser/actions/workspaceActions.ts b/src/vs/workbench/browser/actions/workspaceActions.ts index 0916b1619367..0d56d6bfbf4f 100644 --- a/src/vs/workbench/browser/actions/workspaceActions.ts +++ b/src/vs/workbench/browser/actions/workspaceActions.ts @@ -22,7 +22,7 @@ import { IHostService } from 'vs/workbench/services/host/browser/host'; import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; +import { IWorkspacesService, hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces'; export class OpenFileAction extends Action { @@ -213,7 +213,7 @@ export class SaveWorkspaceAsAction extends Action { async run(): Promise { const configPathUri = await this.workspaceEditingService.pickNewWorkspacePath(); - if (configPathUri) { + if (configPathUri && hasWorkspaceFileExtension(configPathUri)) { switch (this.contextService.getWorkbenchState()) { case WorkbenchState.EMPTY: case WorkbenchState.FOLDER: @@ -259,11 +259,11 @@ export class DuplicateWorkspaceInNewWindowAction extends Action { const registry = Registry.as(Extensions.WorkbenchActions); const workspacesCategory = nls.localize('workspaces', "Workspaces"); -registry.registerWorkbenchAction(new SyncActionDescriptor(AddRootFolderAction, AddRootFolderAction.ID, AddRootFolderAction.LABEL), 'Workspaces: Add Folder to Workspace...', workspacesCategory); -registry.registerWorkbenchAction(new SyncActionDescriptor(GlobalRemoveRootFolderAction, GlobalRemoveRootFolderAction.ID, GlobalRemoveRootFolderAction.LABEL), 'Workspaces: Remove Folder from Workspace...', workspacesCategory); -registry.registerWorkbenchAction(new SyncActionDescriptor(CloseWorkspaceAction, CloseWorkspaceAction.ID, CloseWorkspaceAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_F) }), 'Workspaces: Close Workspace', workspacesCategory); -registry.registerWorkbenchAction(new SyncActionDescriptor(SaveWorkspaceAsAction, SaveWorkspaceAsAction.ID, SaveWorkspaceAsAction.LABEL), 'Workspaces: Save Workspace As...', workspacesCategory); -registry.registerWorkbenchAction(new SyncActionDescriptor(DuplicateWorkspaceInNewWindowAction, DuplicateWorkspaceInNewWindowAction.ID, DuplicateWorkspaceInNewWindowAction.LABEL), 'Workspaces: Duplicate Workspace in New Window', workspacesCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(AddRootFolderAction, AddRootFolderAction.ID, AddRootFolderAction.LABEL), 'Workspaces: Add Folder to Workspace...', workspacesCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(GlobalRemoveRootFolderAction, GlobalRemoveRootFolderAction.ID, GlobalRemoveRootFolderAction.LABEL), 'Workspaces: Remove Folder from Workspace...', workspacesCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(CloseWorkspaceAction, CloseWorkspaceAction.ID, CloseWorkspaceAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_F) }), 'Workspaces: Close Workspace', workspacesCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(SaveWorkspaceAsAction, SaveWorkspaceAsAction.ID, SaveWorkspaceAsAction.LABEL), 'Workspaces: Save Workspace As...', workspacesCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(DuplicateWorkspaceInNewWindowAction, DuplicateWorkspaceInNewWindowAction.ID, DuplicateWorkspaceInNewWindowAction.LABEL), 'Workspaces: Duplicate Workspace in New Window', workspacesCategory); // --- Menu Registration diff --git a/src/vs/workbench/browser/composite.ts b/src/vs/workbench/browser/composite.ts index a472d79a12d3..f96a2548957e 100644 --- a/src/vs/workbench/browser/composite.ts +++ b/src/vs/workbench/browser/composite.ts @@ -31,11 +31,11 @@ import { find } from 'vs/base/common/arrays'; */ export abstract class Composite extends Component implements IComposite { - private readonly _onTitleAreaUpdate: Emitter = this._register(new Emitter()); - readonly onTitleAreaUpdate: Event = this._onTitleAreaUpdate.event; + private readonly _onTitleAreaUpdate = this._register(new Emitter()); + readonly onTitleAreaUpdate = this._onTitleAreaUpdate.event; - private readonly _onDidChangeVisibility: Emitter = this._register(new Emitter()); - readonly onDidChangeVisibility: Event = this._onDidChangeVisibility.event; + private readonly _onDidChangeVisibility = this._register(new Emitter()); + readonly onDidChangeVisibility = this._onDidChangeVisibility.event; private _onDidFocus: Emitter | undefined; get onDidFocus(): Event { @@ -250,11 +250,11 @@ export abstract class CompositeDescriptor { export abstract class CompositeRegistry extends Disposable { - private readonly _onDidRegister: Emitter> = this._register(new Emitter>()); - get onDidRegister(): Event> { return this._onDidRegister.event; } + private readonly _onDidRegister = this._register(new Emitter>()); + readonly onDidRegister = this._onDidRegister.event; - private readonly _onDidDeregister: Emitter> = this._register(new Emitter>()); - get onDidDeregister(): Event> { return this._onDidDeregister.event; } + private readonly _onDidDeregister = this._register(new Emitter>()); + readonly onDidDeregister = this._onDidDeregister.event; private composites: CompositeDescriptor[] = []; diff --git a/src/vs/workbench/browser/contextkeys.ts b/src/vs/workbench/browser/contextkeys.ts index ff6b983fc163..17919e7177a5 100644 --- a/src/vs/workbench/browser/contextkeys.ts +++ b/src/vs/workbench/browser/contextkeys.ts @@ -8,7 +8,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { IContextKeyService, IContextKey, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { InputFocusedContext } from 'vs/platform/contextkey/common/contextkeys'; import { IWindowsConfiguration } from 'vs/platform/windows/common/windows'; -import { ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, TEXT_DIFF_EDITOR_ID, SplitEditorsVertically, InEditorZenModeContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorIsSaveableContext, toResource, SideBySideEditor, EditorAreaVisibleContext } from 'vs/workbench/common/editor'; +import { ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, TEXT_DIFF_EDITOR_ID, SplitEditorsVertically, InEditorZenModeContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorIsReadonlyContext, EditorAreaVisibleContext, DirtyWorkingCopiesContext } from 'vs/workbench/common/editor'; import { trackFocus, addDisposableListener, EventType } from 'vs/base/browser/dom'; import { preferredSideBySideGroupDirection, GroupDirection, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -21,8 +21,7 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { isMacintosh, isLinux, isWindows, isWeb } from 'vs/base/common/platform'; import { PanelPositionContext } from 'vs/workbench/common/panel'; import { getRemoteName } from 'vs/platform/remote/common/remoteHosts'; -import { IFileService } from 'vs/platform/files/common/files'; -import { Schemas } from 'vs/base/common/network'; +import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; export const IsMacContext = new RawContextKey('isMac', isMacintosh); export const IsLinuxContext = new RawContextKey('isLinux', isLinux); @@ -51,8 +50,10 @@ export const IsFullscreenContext = new RawContextKey('isFullscreen', fa export class WorkbenchContextKeysHandler extends Disposable { private inputFocusedContext: IContextKey; + private dirtyWorkingCopiesContext: IContextKey; + private activeEditorContext: IContextKey; - private activeEditorIsSaveable: IContextKey; + private activeEditorIsReadonly: IContextKey; private activeEditorGroupEmpty: IContextKey; private activeEditorGroupIndex: IContextKey; @@ -75,19 +76,18 @@ export class WorkbenchContextKeysHandler extends Disposable { private panelPositionContext: IContextKey; constructor( - @IContextKeyService private contextKeyService: IContextKeyService, - @IWorkspaceContextService private contextService: IWorkspaceContextService, - @IConfigurationService private configurationService: IConfigurationService, - @IWorkbenchEnvironmentService private environmentService: IWorkbenchEnvironmentService, - @IEditorService private editorService: IEditorService, - @IEditorGroupsService private editorGroupService: IEditorGroupsService, - @IWorkbenchLayoutService private layoutService: IWorkbenchLayoutService, - @IViewletService private viewletService: IViewletService, - @IFileService private fileService: IFileService + @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, + @IEditorService private readonly editorService: IEditorService, + @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, + @IViewletService private readonly viewletService: IViewletService, + @IWorkingCopyService private readonly workingCopyService: IWorkingCopyService ) { super(); - // Platform IsMacContext.bindTo(this.contextKeyService); IsLinuxContext.bindTo(this.contextKeyService); @@ -107,7 +107,7 @@ export class WorkbenchContextKeysHandler extends Disposable { // Editors this.activeEditorContext = ActiveEditorContext.bindTo(this.contextKeyService); - this.activeEditorIsSaveable = ActiveEditorIsSaveableContext.bindTo(this.contextKeyService); + this.activeEditorIsReadonly = ActiveEditorIsReadonlyContext.bindTo(this.contextKeyService); this.editorsVisibleContext = EditorsVisibleContext.bindTo(this.contextKeyService); this.textCompareEditorVisibleContext = TextCompareEditorVisibleContext.bindTo(this.contextKeyService); this.textCompareEditorActiveContext = TextCompareEditorActiveContext.bindTo(this.contextKeyService); @@ -116,6 +116,9 @@ export class WorkbenchContextKeysHandler extends Disposable { this.activeEditorGroupLast = ActiveEditorGroupLastContext.bindTo(this.contextKeyService); this.multipleEditorGroupsContext = MultipleEditorGroupsContext.bindTo(this.contextKeyService); + // Working Copies + this.dirtyWorkingCopiesContext = DirtyWorkingCopiesContext.bindTo(this.contextKeyService); + // Inputs this.inputFocusedContext = InputFocusedContext.bindTo(this.contextKeyService); @@ -183,6 +186,8 @@ export class WorkbenchContextKeysHandler extends Disposable { this._register(this.viewletService.onDidViewletOpen(() => this.updateSideBarContextKeys())); this._register(this.layoutService.onPartVisibilityChange(() => this.editorAreaVisibleContext.set(this.layoutService.isVisible(Parts.EDITOR_PART)))); + + this._register(this.workingCopyService.onDidChangeDirty(w => this.dirtyWorkingCopiesContext.set(w.isDirty() || this.workingCopyService.hasDirty))); } private updateEditorContextKeys(): void { @@ -217,13 +222,10 @@ export class WorkbenchContextKeysHandler extends Disposable { if (activeControl) { this.activeEditorContext.set(activeControl.getId()); - - const resource = toResource(activeControl.input, { supportSideBySide: SideBySideEditor.MASTER }); - const canSave = resource ? this.fileService.canHandleResource(resource) || resource.scheme === Schemas.untitled : false; - this.activeEditorIsSaveable.set(canSave); + this.activeEditorIsReadonly.set(activeControl.input.isReadonly()); } else { this.activeEditorContext.reset(); - this.activeEditorIsSaveable.reset(); + this.activeEditorIsReadonly.reset(); } } diff --git a/src/vs/workbench/browser/dnd.ts b/src/vs/workbench/browser/dnd.ts index 6e88607f3a3c..80f25c474da5 100644 --- a/src/vs/workbench/browser/dnd.ts +++ b/src/vs/workbench/browser/dnd.ts @@ -12,7 +12,7 @@ import { URI } from 'vs/base/common/uri'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; import { Schemas } from 'vs/base/common/network'; -import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; +import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { DefaultEndOfLine } from 'vs/editor/common/model'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEditorViewState } from 'vs/editor/common/editorCommon'; @@ -20,18 +20,18 @@ import { DataTransfers } from 'vs/base/browser/dnd'; import { DragMouseEvent } from 'vs/base/browser/mouseEvent'; import { normalizeDriveLetter } from 'vs/base/common/labels'; import { MIME_BINARY } from 'vs/base/common/mime'; -import { isWindows, isLinux, isWeb } from 'vs/base/common/platform'; +import { isWindows, isWeb } from 'vs/base/common/platform'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { IEditorIdentifier, GroupIdentifier } from 'vs/workbench/common/editor'; import { IEditorService, IResourceEditor } from 'vs/workbench/services/editor/common/editorService'; import { Disposable } from 'vs/base/common/lifecycle'; -import { addDisposableListener, EventType } from 'vs/base/browser/dom'; +import { addDisposableListener, EventType, asDomUri } from 'vs/base/browser/dom'; import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing'; import { withNullAsUndefined } from 'vs/base/common/types'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IHostService } from 'vs/workbench/services/host/browser/host'; +import { isStandalone } from 'vs/base/browser/browser'; export interface IDraggedResource { resource: URI; @@ -163,7 +163,7 @@ export class ResourcesDropHandler { @IWorkspacesService private readonly workspacesService: IWorkspacesService, @ITextFileService private readonly textFileService: ITextFileService, @IBackupFileService private readonly backupFileService: IBackupFileService, - @IUntitledEditorService private readonly untitledEditorService: IUntitledEditorService, + @IUntitledTextEditorService private readonly untitledTextEditorService: IUntitledTextEditorService, @IEditorService private readonly editorService: IEditorService, @IConfigurationService private readonly configurationService: IConfigurationService, @IWorkspaceEditingService private readonly workspaceEditingService: IWorkspaceEditingService, @@ -234,7 +234,7 @@ export class ResourcesDropHandler { // Untitled: always ensure that we open a new untitled for each file we drop if (droppedDirtyEditor.resource.scheme === Schemas.untitled) { - droppedDirtyEditor.resource = this.untitledEditorService.createOrGet().getResource(); + droppedDirtyEditor.resource = this.untitledTextEditorService.createOrGet().getResource(); } // Return early if the resource is already dirty in target or opened already @@ -324,20 +324,14 @@ export function fillResourceDataTransfers(accessor: ServicesAccessor, resources: return obj; }); - const firstSource = sources[0]; - // Text: allows to paste into text-capable areas const lineDelimiter = isWindows ? '\r\n' : '\n'; event.dataTransfer.setData(DataTransfers.TEXT, sources.map(source => source.resource.scheme === Schemas.file ? normalize(normalizeDriveLetter(source.resource.fsPath)) : source.resource.toString()).join(lineDelimiter)); - const envService = accessor.get(IWorkbenchEnvironmentService); - const hasRemote = !!envService.configuration.remoteAuthority; - if ( - !(isLinux && hasRemote) && // Not supported on linux remote due to chrome limitation https://github.com/microsoft/vscode-remote-release/issues/849 - !isWeb // Does not seem to work anymore when running from web, the file ends up being empty (and PWA crashes) - ) { - // Download URL: enables support to drag a tab as file to desktop (only single file supported) - event.dataTransfer.setData(DataTransfers.DOWNLOAD_URL, [MIME_BINARY, basename(firstSource.resource), firstSource.resource.toString()].join(':')); + // Download URL: enables support to drag a tab as file to desktop (only single file supported) + // Disabled for PWA web due to: https://github.com/microsoft/vscode/issues/83441 + if (!sources[0].isDirectory && (!isWeb || !isStandalone)) { + event.dataTransfer.setData(DataTransfers.DOWNLOAD_URL, [MIME_BINARY, basename(sources[0].resource), asDomUri(sources[0].resource).toString()].join(':')); } // Resource URLs: allows to drop multiple resources to a target in VS Code (not directories) @@ -482,3 +476,23 @@ export class DragAndDropObserver extends Disposable { })); } } + +export function containsDragType(event: DragEvent, ...dragTypesToFind: string[]): boolean { + if (!event.dataTransfer) { + return false; + } + + const dragTypes = event.dataTransfer.types; + const lowercaseDragTypes: string[] = []; + for (let i = 0; i < dragTypes.length; i++) { + lowercaseDragTypes.push(dragTypes[i].toLowerCase()); // somehow the types are lowercase + } + + for (const dragType of dragTypesToFind) { + if (lowercaseDragTypes.indexOf(dragType.toLowerCase()) >= 0) { + return true; + } + } + + return false; +} diff --git a/src/vs/workbench/browser/editor.ts b/src/vs/workbench/browser/editor.ts index e636641866e7..1ebad388aa36 100644 --- a/src/vs/workbench/browser/editor.ts +++ b/src/vs/workbench/browser/editor.ts @@ -7,7 +7,7 @@ import { EditorInput } from 'vs/workbench/common/editor'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { Registry } from 'vs/platform/registry/common/platform'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; -import { IConstructorSignature0, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IConstructorSignature0, IInstantiationService, BrandedService } from 'vs/platform/instantiation/common/instantiation'; import { find } from 'vs/base/common/arrays'; export interface IEditorDescriptor { @@ -54,6 +54,14 @@ export interface IEditorRegistry { */ export class EditorDescriptor implements IEditorDescriptor { + public static create( + ctor: { new(...services: Services): BaseEditor }, + id: string, + name: string + ): EditorDescriptor { + return new EditorDescriptor(ctor as IConstructorSignature0, id, name); + } + constructor( private readonly ctor: IConstructorSignature0, private readonly id: string, diff --git a/src/vs/workbench/browser/labels.ts b/src/vs/workbench/browser/labels.ts index 0e186d874688..3f346c9251c5 100644 --- a/src/vs/workbench/browser/labels.ts +++ b/src/vs/workbench/browser/labels.ts @@ -13,7 +13,7 @@ import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; +import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { IDecorationsService, IResourceDecorationChangeEvent } from 'vs/workbench/services/decorations/browser/decorations'; import { Schemas } from 'vs/base/common/network'; import { FileKind, FILES_ASSOCIATIONS_CONFIG, IFileService } from 'vs/platform/files/common/files'; @@ -22,13 +22,13 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { Event, Emitter } from 'vs/base/common/event'; import { ILabelService } from 'vs/platform/label/common/label'; import { getIconClasses, detectModeId } from 'vs/editor/common/services/getIconClasses'; -import { Disposable, dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, dispose, IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { withNullAsUndefined } from 'vs/base/common/types'; export interface IResourceLabelProps { resource?: URI; - name?: string; + name?: string | string[]; description?: string; } @@ -41,6 +41,7 @@ export interface IResourceLabelOptions extends IIconLabelValueOptions { export interface IFileLabelOptions extends IResourceLabelOptions { hideLabel?: boolean; hidePath?: boolean; + readonly parentCount?: number; } export interface IResourceLabel extends IDisposable { @@ -164,7 +165,7 @@ export class ResourceLabels extends Disposable { const label: IResourceLabel = { element: widget.element, onDidRender: widget.onDidRender, - setLabel: (label?: string, description?: string, options?: IIconLabelValueOptions) => widget.setLabel(label, description, options), + setLabel: (label: string, description?: string, options?: IIconLabelValueOptions) => widget.setLabel(label, description, options), setResource: (label: IResourceLabelProps, options?: IResourceLabelOptions) => widget.setResource(label, options), setEditor: (editor: IEditorInput, options?: IResourceLabelOptions) => widget.setEditor(editor, options), setFile: (resource: URI, options?: IFileLabelOptions) => widget.setFile(resource, options), @@ -241,6 +242,8 @@ class ResourceLabelWidget extends IconLabel { private _onDidRender = this._register(new Emitter()); readonly onDidRender: Event = this._onDidRender.event; + private readonly renderDisposables = this._register(new DisposableStore()); + private label?: IResourceLabelProps; private options?: IResourceLabelOptions; private computedIconClasses?: string[]; @@ -257,7 +260,7 @@ class ResourceLabelWidget extends IconLabel { @IModelService private readonly modelService: IModelService, @IDecorationsService private readonly decorationsService: IDecorationsService, @ILabelService private readonly labelService: ILabelService, - @IUntitledEditorService private readonly untitledEditorService: IUntitledEditorService, + @IUntitledTextEditorService private readonly untitledTextEditorService: IUntitledTextEditorService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService ) { super(container, options); @@ -365,7 +368,7 @@ class ResourceLabelWidget extends IconLabel { setEditor(editor: IEditorInput, options?: IResourceLabelOptions): void { this.setResource({ resource: toResource(editor, { supportSideBySide: SideBySideEditor.MASTER }), - name: withNullAsUndefined(editor.getName()), + name: editor.getName(), description: editor.getDescription(options ? options.descriptionVerbosity : undefined) }, options); } @@ -387,7 +390,7 @@ class ResourceLabelWidget extends IconLabel { } let description: string | undefined; - const hidePath = (options && options.hidePath) || (resource.scheme === Schemas.untitled && !this.untitledEditorService.hasAssociatedFilePath(resource)); + const hidePath = (options && options.hidePath) || (resource.scheme === Schemas.untitled && !this.untitledTextEditorService.hasAssociatedFilePath(resource)); if (!hidePath) { description = this.labelService.getUriLabel(resources.dirname(resource), { relative: true }); } @@ -402,7 +405,7 @@ class ResourceLabelWidget extends IconLabel { this.computedIconClasses = undefined; this.computedPathLabel = undefined; - this.setLabel(); + this.setLabel(''); } private render(clearIconCache: boolean): void { @@ -434,11 +437,15 @@ class ResourceLabelWidget extends IconLabel { return; } + this.renderDisposables.clear(); + const iconLabelOptions: IIconLabelValueOptions & { extraClasses: string[] } = { title: '', italic: this.options && this.options.italic, matches: this.options && this.options.matches, - extraClasses: [] + extraClasses: [], + separator: this.options?.separator, + domId: this.options?.domId }; const resource = this.label.resource; @@ -472,6 +479,9 @@ class ResourceLabelWidget extends IconLabel { ); if (deco) { + + this.renderDisposables.add(deco); + if (deco.tooltip) { iconLabelOptions.title = `${iconLabelOptions.title} • ${deco.tooltip}`; } @@ -486,7 +496,7 @@ class ResourceLabelWidget extends IconLabel { } } - this.setLabel(label, this.label.description, iconLabelOptions); + this.setLabel(label || '', this.label.description, iconLabelOptions); this._onDidRender.fire(); } diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 7bc5480bbe67..7f34fec989dd 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -5,7 +5,7 @@ import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; -import { EventType, addDisposableListener, addClass, removeClass, isAncestor, getClientArea, position, size, Dimension } from 'vs/base/browser/dom'; +import { EventType, addDisposableListener, addClass, removeClass, isAncestor, getClientArea, Dimension, toggleClass, position, size } from 'vs/base/browser/dom'; import { onDidChangeFullscreen, isFullscreen } from 'vs/base/browser/browser'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -38,6 +38,8 @@ import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { coalesce } from 'vs/base/common/arrays'; import { assertIsDefined } from 'vs/base/common/types'; import { INotificationService, NotificationsFilter } from 'vs/platform/notification/common/notification'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { WINDOW_ACTIVE_BORDER, WINDOW_INACTIVE_BORDER } from 'vs/workbench/common/theme'; enum Settings { ACTIVITYBAR_VISIBLE = 'workbench.activityBar.visible', @@ -74,7 +76,8 @@ enum Classes { EDITOR_HIDDEN = 'noeditorarea', PANEL_HIDDEN = 'nopanel', STATUSBAR_HIDDEN = 'nostatusbar', - FULLSCREEN = 'fullscreen' + FULLSCREEN = 'fullscreen', + WINDOW_BORDER = 'border' } export abstract class Layout extends Disposable implements IWorkbenchLayoutService { @@ -92,6 +95,9 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi private readonly _onCenteredLayoutChange: Emitter = this._register(new Emitter()); readonly onCenteredLayoutChange: Event = this._onCenteredLayoutChange.event; + private readonly _onMaximizeChange: Emitter = this._register(new Emitter()); + readonly onMaximizeChange: Event = this._onMaximizeChange.event; + private readonly _onPanelPositionChange: Emitter = this._register(new Emitter()); readonly onPanelPositionChange: Event = this._onPanelPositionChange.event; @@ -135,9 +141,13 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi private contextService!: IWorkspaceContextService; private backupFileService!: IBackupFileService; private notificationService!: INotificationService; + private themeService!: IThemeService; protected readonly state = { fullscreen: false, + maximized: false, + hasFocus: false, + windowBorder: false, menuBar: { visibility: 'default' as MenuBarVisibility, @@ -204,6 +214,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.contextService = accessor.get(IWorkspaceContextService); this.storageService = accessor.get(IStorageService); this.backupFileService = accessor.get(IBackupFileService); + this.themeService = accessor.get(IThemeService); // Parts this.editorService = accessor.get(IEditorService); @@ -257,6 +268,12 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi if ((isWindows || isLinux || isWeb) && getTitleBarStyle(this.configurationService, this.environmentService) === 'custom') { this._register(this.titleService.onMenubarVisibilityChange(visible => this.onMenubarToggled(visible))); } + + // Theme changes + this._register(this.themeService.onThemeChange(theme => this.updateStyles())); + + // Window focus changes + this._register(this.hostService.onDidChangeFocus(e => this.onWindowFocusChanged(e))); } private onMenubarToggled(visible: boolean) { @@ -291,12 +308,23 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Propagate to grid this.workbenchGrid.setViewVisible(this.titleBarPartView, this.isVisible(Parts.TITLEBAR_PART)); + this.updateWindowBorder(true); + this.layout(); // handle title bar when fullscreen changes } this._onFullscreenChange.fire(this.state.fullscreen); } + private onWindowFocusChanged(hasFocus: boolean): void { + if (this.state.hasFocus === hasFocus) { + return; + } + + this.state.hasFocus = hasFocus; + this.updateWindowBorder(); + } + private doUpdateLayoutConfiguration(skipLayout?: boolean): void { // Sidebar position @@ -366,6 +394,44 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.layout(); } + private updateWindowBorder(skipLayout: boolean = false) { + if (isWeb || getTitleBarStyle(this.configurationService, this.environmentService) !== 'custom') { + return; + } + + const theme = this.themeService.getTheme(); + + const activeBorder = theme.getColor(WINDOW_ACTIVE_BORDER); + const inactiveBorder = theme.getColor(WINDOW_INACTIVE_BORDER); + + let windowBorder = false; + if (!this.state.fullscreen && !this.state.maximized && (activeBorder || inactiveBorder)) { + windowBorder = true; + + // If one color is missing, just fallback to the other one + const borderColor = this.state.hasFocus + ? activeBorder ?? inactiveBorder + : inactiveBorder ?? activeBorder; + this.container.style.setProperty('--window-border-color', borderColor ? borderColor.toString() : 'transparent'); + } + + if (windowBorder === this.state.windowBorder) { + return; + } + + this.state.windowBorder = windowBorder; + + toggleClass(this.container, Classes.WINDOW_BORDER, windowBorder); + + if (!skipLayout) { + this.layout(); + } + } + + private updateStyles() { + this.updateWindowBorder(); + } + private initLayoutState(lifecycleService: ILifecycleService, fileService: IFileService): void { // Fullscreen @@ -442,6 +508,11 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Zen mode enablement this.state.zenMode.restore = this.storageService.getBoolean(Storage.ZEN_MODE_ENABLED, StorageScope.WORKSPACE, false) && this.configurationService.getValue(Settings.ZEN_MODE_RESTORE); + this.state.hasFocus = this.hostService.hasFocus; + + // Window border + this.updateWindowBorder(true); + } private resolveEditorsToOpen(fileService: IFileService): Promise | IResourceEditor[] { @@ -682,6 +753,13 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi if (config.silentNotifications) { this.notificationService.setFilter(NotificationsFilter.ERROR); } + this.state.zenMode.transitionDisposables.add(this.configurationService.onDidChangeConfiguration(c => { + const silentNotificationsKey = 'zenMode.silentNotifications'; + if (c.affectsConfiguration(silentNotificationsKey)) { + const filter = this.configurationService.getValue(silentNotificationsKey) ? NotificationsFilter.ERROR : NotificationsFilter.OFF; + this.notificationService.setFilter(filter); + } + })); if (config.centerLayout) { this.centerEditorLayout(true, true); @@ -826,9 +904,13 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi })); } + getClientArea(): Dimension { + return getClientArea(this.parent); + } + layout(): void { if (!this.disposed) { - this._dimension = getClientArea(this.parent); + this._dimension = this.getClientArea(); position(this.container, 0, 0, 0, 0, 'relative'); size(this.container, this._dimension.width, this._dimension.height); @@ -1079,6 +1161,14 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } } + hasWindowBorder(): boolean { + return this.state.windowBorder; + } + + getWindowBorderRadius(): string | undefined { + return this.state.windowBorder && isMacintosh ? '5px' : undefined; + } + isPanelMaximized(): boolean { if (!this.workbenchGrid) { return false; @@ -1168,8 +1258,23 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this._onPanelPositionChange.fire(positionToString(this.state.panel.position)); } + isWindowMaximized() { + return this.state.maximized; + } + + updateWindowMaximizedState(maximized: boolean) { + if (this.state.maximized === maximized) { + return; + } + + this.state.maximized = maximized; + + this.updateWindowBorder(); + this._onMaximizeChange.fire(maximized); + } + private createGridDescriptor(): ISerializedGrid { - const workbenchDimensions = getClientArea(this.parent); + const workbenchDimensions = this.getClientArea(); const width = this.storageService.getNumber(Storage.GRID_WIDTH, StorageScope.GLOBAL, workbenchDimensions.width); const height = this.storageService.getNumber(Storage.GRID_HEIGHT, StorageScope.GLOBAL, workbenchDimensions.height); // At some point, we will not fall back to old keys from legacy layout, but for now, let's migrate the keys diff --git a/src/vs/workbench/browser/media/part.css b/src/vs/workbench/browser/media/part.css index a1fbb92ece6a..c26d2ec77e95 100644 --- a/src/vs/workbench/browser/media/part.css +++ b/src/vs/workbench/browser/media/part.css @@ -39,8 +39,7 @@ font-size: 11px; cursor: default; font-weight: normal; - -webkit-margin-before: 0; - -webkit-margin-after: 0; + margin: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; @@ -72,6 +71,10 @@ display: none; } +.monaco-workbench .part > .title > .title-actions .action-label.codicon { + color: inherit; +} + .monaco-workbench .part > .content { /* {{SQL CARBON EDIT}} */ font-size: 12px; diff --git a/src/vs/workbench/browser/media/style.css b/src/vs/workbench/browser/media/style.css index b7bb971a6263..724cac1d0704 100644 --- a/src/vs/workbench/browser/media/style.css +++ b/src/vs/workbench/browser/media/style.css @@ -37,12 +37,17 @@ body { overflow: hidden; font-size: 11px; user-select: none; + -webkit-user-select: none; } body.web { position: fixed; /* prevent bounce effect */ } +.monaco-workbench.web { + touch-action: initial; /* reenable touch events on workbench */ +} + @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } .monaco-workbench { @@ -53,6 +58,15 @@ body.web { overflow: hidden; } +.monaco-workbench.border:not(.fullscreen) { + box-sizing: border-box; + border: 1px solid var(--window-border-color); +} + +.monaco-workbench.border.mac { + border-radius: 5px; +} + .monaco-workbench img { border: 0; } @@ -97,15 +111,18 @@ body.web { .monaco-workbench.monaco-font-aliasing-antialiased { -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; } .monaco-workbench.monaco-font-aliasing-none { -webkit-font-smoothing: none; + -moz-osx-font-smoothing: unset; } @media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) { .monaco-workbench.monaco-font-aliasing-auto { -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; } } @@ -150,14 +167,40 @@ body.web { .monaco-workbench.linux .monaco-menu .monaco-action-bar.vertical .submenu-indicator { height: 100%; - -webkit-mask-size: 10px 10px; mask-size: 10px 10px; + -webkit-mask-size: 10px 10px; } .monaco-workbench .monaco-menu .action-item { cursor: default; } +/* Custom Dropdown (select) Arrows */ + +.monaco-workbench select { + -webkit-appearance: none; + -moz-appearance: none; +} + +.monaco-workbench .select-container { + position: relative; +} + +.monaco-workbench .select-container:after { + content: "\eab4"; + font-family: codicon; + font-size: 14px; + width: 14px; + height: 14px; + line-height: 14px; + position: absolute; + top: 0; + bottom: 0; + right: 6px; + margin: auto; + pointer-events: none; +} + /* START Keyboard Focus Indication Styles */ .monaco-workbench [tabindex="0"]:focus, diff --git a/src/vs/workbench/browser/panel.ts b/src/vs/workbench/browser/panel.ts index 5a21c9ade6be..1e232bc19494 100644 --- a/src/vs/workbench/browser/panel.ts +++ b/src/vs/workbench/browser/panel.ts @@ -9,7 +9,7 @@ import { Composite, CompositeDescriptor, CompositeRegistry } from 'vs/workbench/ import { Action } from 'vs/base/common/actions'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; -import { IConstructorSignature0 } from 'vs/platform/instantiation/common/instantiation'; +import { IConstructorSignature0, BrandedService } from 'vs/platform/instantiation/common/instantiation'; import { isAncestor } from 'vs/base/browser/dom'; import { assertIsDefined } from 'vs/base/common/types'; @@ -20,7 +20,11 @@ export abstract class Panel extends Composite implements IPanel { } */ export class PanelDescriptor extends CompositeDescriptor { - constructor(ctor: IConstructorSignature0, id: string, name: string, cssClass?: string, order?: number, _commandId?: string) { + public static create(ctor: { new(...services: Services): Panel }, id: string, name: string, cssClass?: string, order?: number, _commandId?: string): PanelDescriptor { + return new PanelDescriptor(ctor as IConstructorSignature0, id, name, cssClass, order, _commandId); + } + + private constructor(ctor: IConstructorSignature0, id: string, name: string, cssClass?: string, order?: number, _commandId?: string) { super(ctor, id, name, cssClass, order, _commandId); } } diff --git a/src/vs/workbench/browser/part.ts b/src/vs/workbench/browser/part.ts index 420c08269478..fd648557bc4e 100644 --- a/src/vs/workbench/browser/part.ts +++ b/src/vs/workbench/browser/part.ts @@ -46,7 +46,7 @@ export abstract class Part extends Component implements ISerializableView { private options: IPartOptions, themeService: IThemeService, storageService: IStorageService, - layoutService: IWorkbenchLayoutService + protected readonly layoutService: IWorkbenchLayoutService ) { super(id, themeService, storageService); diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts index a5b34d7d7825..8d6d2136ad9b 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts @@ -22,7 +22,7 @@ import { ActivityAction, ActivityActionViewItem, ICompositeBar, ICompositeBarCol import { ViewletDescriptor } from 'vs/workbench/browser/viewlet'; import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions'; import { IActivity } from 'vs/workbench/common/activity'; -import { ACTIVITY_BAR_FOREGROUND, ACTIVITY_BAR_ACTIVE_BORDER, ACTIVITY_BAR_ACTIVE_BACKGROUND } from 'vs/workbench/common/theme'; +import { ACTIVITY_BAR_FOREGROUND, ACTIVITY_BAR_ACTIVE_BORDER, ACTIVITY_BAR_ACTIVE_FOCUS_BORDER, ACTIVITY_BAR_ACTIVE_BACKGROUND } from 'vs/workbench/common/theme'; import { IActivityBarService } from 'vs/workbench/services/activityBar/browser/activityBarService'; import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; @@ -33,15 +33,45 @@ export class ViewletActivityAction extends ActivityAction { private static readonly preventDoubleClickDelay = 300; - private lastRun: number = 0; + private readonly viewletService: IViewletService; + private readonly layoutService: IWorkbenchLayoutService; + private readonly telemetryService: ITelemetryService; + + private lastRun: number; constructor( activity: IActivity, - @IViewletService private readonly viewletService: IViewletService, - @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, - @ITelemetryService private readonly telemetryService: ITelemetryService + @IViewletService viewletService: IViewletService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, + @ITelemetryService telemetryService: ITelemetryService ) { + ViewletActivityAction.generateIconCSS(activity); super(activity); + + this.lastRun = 0; + this.viewletService = viewletService; + this.layoutService = layoutService; + this.telemetryService = telemetryService; + } + + private static generateIconCSS(activity: IActivity): void { + if (activity.iconUrl) { + activity.cssClass = activity.cssClass || `activity-${activity.id.replace(/\./g, '-')}`; + const iconClass = `.monaco-workbench .activitybar .monaco-action-bar .action-label.${activity.cssClass}`; + DOM.createCSSRule(iconClass, ` + mask: ${DOM.asCSSUrl(activity.iconUrl)} no-repeat 50% 50%; + mask-size: 24px; + -webkit-mask: ${DOM.asCSSUrl(activity.iconUrl)} no-repeat 50% 50%; + -webkit-mask-size: 24px; + `); + } + } + + setActivity(activity: IActivity): void { + if (activity.iconUrl && this.activity.cssClass !== activity.cssClass) { + ViewletActivityAction.generateIconCSS(activity); + } + this.activity = activity; } async run(event: any): Promise { @@ -170,16 +200,7 @@ export class PlaceHolderViewletActivityAction extends ViewletActivityAction { @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @ITelemetryService telemetryService: ITelemetryService ) { - super({ id, name: id, cssClass: `extensionViewlet-placeholder-${id.replace(/\./g, '-')}` }, viewletService, layoutService, telemetryService); - - if (iconUrl) { - const iconClass = `.monaco-workbench .activitybar .monaco-action-bar .action-label.${this.class}`; // Generate Placeholder CSS to show the icon in the activity bar - DOM.createCSSRule(iconClass, `-webkit-mask: ${DOM.asCSSUrl(iconUrl)} no-repeat 50% 50%; -webkit-mask-size: 24px;`); - } - } - - setActivity(activity: IActivity): void { - this.activity = activity; + super({ id, name: id, iconUrl }, viewletService, layoutService, telemetryService); } } @@ -262,19 +283,24 @@ export class NextSideBarViewAction extends SwitchSideBarViewAction { } const registry = Registry.as(ActionExtensions.WorkbenchActions); -registry.registerWorkbenchAction(new SyncActionDescriptor(PreviousSideBarViewAction, PreviousSideBarViewAction.ID, PreviousSideBarViewAction.LABEL), 'View: Previous Side Bar View', nls.localize('view', "View")); -registry.registerWorkbenchAction(new SyncActionDescriptor(NextSideBarViewAction, NextSideBarViewAction.ID, NextSideBarViewAction.LABEL), 'View: Next Side Bar View', nls.localize('view', "View")); +registry.registerWorkbenchAction(SyncActionDescriptor.create(PreviousSideBarViewAction, PreviousSideBarViewAction.ID, PreviousSideBarViewAction.LABEL), 'View: Previous Side Bar View', nls.localize('view', "View")); +registry.registerWorkbenchAction(SyncActionDescriptor.create(NextSideBarViewAction, NextSideBarViewAction.ID, NextSideBarViewAction.LABEL), 'View: Next Side Bar View', nls.localize('view', "View")); registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { const activeForegroundColor = theme.getColor(ACTIVITY_BAR_FOREGROUND); if (activeForegroundColor) { collector.addRule(` - .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.active .action-label, - .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item:focus .action-label, - .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item:hover .action-label { + .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.active .action-label:not(.codicon), + .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item:focus .action-label:not(.codicon), + .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item:hover .action-label:not(.codicon) { background-color: ${activeForegroundColor} !important; } + .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.active .action-label.codicon, + .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item:focus .action-label.codicon, + .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item:hover .action-label.codicon { + color: ${activeForegroundColor} !important; + } `); } @@ -287,6 +313,20 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { `); } + const activeFocusBorderColor = theme.getColor(ACTIVITY_BAR_ACTIVE_FOCUS_BORDER); + if (activeFocusBorderColor) { + collector.addRule(` + .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked:focus::before { + visibility: hidden; + } + + .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked:focus .active-item-indicator:before { + visibility: visible; + border-left-color: ${activeFocusBorderColor}; + } + `); + } + const activeBackgroundColor = theme.getColor(ACTIVITY_BAR_ACTIVE_BACKGROUND); if (activeBackgroundColor) { collector.addRule(` diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index e85e7028666b..7b47da9c84d7 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -11,7 +11,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { Part } from 'vs/workbench/browser/part'; import { GlobalActivityActionViewItem, ViewletActivityAction, ToggleViewletAction, PlaceHolderToggleCompositePinnedAction, PlaceHolderViewletActivityAction } from 'vs/workbench/browser/parts/activitybar/activitybarActions'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; -import { IBadge } from 'vs/workbench/services/activity/common/activity'; +import { IBadge, NumberBadge } from 'vs/workbench/services/activity/common/activity'; import { IWorkbenchLayoutService, Parts, Position as SideBarPosition } from 'vs/workbench/services/layout/browser/layoutService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IDisposable, toDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle'; @@ -24,7 +24,7 @@ import { Dimension, addClass, removeNode } from 'vs/base/browser/dom'; import { IStorageService, StorageScope, IWorkspaceStorageChangeEvent } from 'vs/platform/storage/common/storage'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { ToggleCompositePinnedAction, ICompositeBarColors, ActivityAction } from 'vs/workbench/browser/parts/compositeBarActions'; +import { ToggleCompositePinnedAction, ICompositeBarColors, ActivityAction, ICompositeActivity } from 'vs/workbench/browser/parts/compositeBarActions'; import { ViewletDescriptor } from 'vs/workbench/browser/viewlet'; import { IViewsService, IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainer, TEST_VIEW_CONTAINER_ID, IViewDescriptorCollection } from 'vs/workbench/common/views'; import { IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -68,6 +68,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { private globalActivityAction: ActivityAction | undefined; private globalActivityActionBar: ActionBar | undefined; + private globalActivity: ICompositeActivity[] = []; private customMenubar: CustomMenubarControl | undefined; private menubar: HTMLElement | undefined; @@ -83,7 +84,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { constructor( @IViewletService private readonly viewletService: IViewletService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @IThemeService themeService: IThemeService, @IStorageService private readonly storageService: IStorageService, @IExtensionService private readonly extensionService: IExtensionService, @@ -202,18 +203,68 @@ export class ActivitybarPart extends Part implements IActivityBarService { } if (viewletOrActionId === GLOBAL_ACTIVITY_ID) { - return this.showGlobalActivity(badge, clazz); + return this.showGlobalActivity(badge, clazz, priority); } return Disposable.None; } - private showGlobalActivity(badge: IBadge, clazz?: string): IDisposable { - const globalActivityAction = assertIsDefined(this.globalActivityAction); + private showGlobalActivity(badge: IBadge, clazz?: string, priority?: number): IDisposable { + if (typeof priority !== 'number') { + priority = 0; + } + const activity: ICompositeActivity = { badge, clazz, priority }; + + for (let i = 0; i <= this.globalActivity.length; i++) { + if (i === this.globalActivity.length) { + this.globalActivity.push(activity); + break; + } else if (this.globalActivity[i].priority <= priority) { + this.globalActivity.splice(i, 0, activity); + break; + } + } + this.updateGlobalActivity(); - globalActivityAction.setBadge(badge, clazz); + return toDisposable(() => this.removeGlobalActivity(activity)); + } - return toDisposable(() => globalActivityAction.setBadge(undefined)); + private removeGlobalActivity(activity: ICompositeActivity): void { + const index = this.globalActivity.indexOf(activity); + if (index !== -1) { + this.globalActivity.splice(index, 1); + this.updateGlobalActivity(); + } + } + + private updateGlobalActivity(): void { + const globalActivityAction = assertIsDefined(this.globalActivityAction); + if (this.globalActivity.length) { + const [{ badge, clazz, priority }] = this.globalActivity; + if (badge instanceof NumberBadge && this.globalActivity.length > 1) { + const cumulativeNumberBadge = this.getCumulativeNumberBadge(priority); + globalActivityAction.setBadge(cumulativeNumberBadge); + } else { + globalActivityAction.setBadge(badge, clazz); + } + } else { + globalActivityAction.setBadge(undefined); + } + } + + private getCumulativeNumberBadge(priority: number): NumberBadge { + const numberActivities = this.globalActivity.filter(activity => activity.badge instanceof NumberBadge && activity.priority === priority); + let number = numberActivities.reduce((result, activity) => { return result + (activity.badge).number; }, 0); + let descriptorFn = (): string => { + return numberActivities.reduce((result, activity, index) => { + result = result + (activity.badge).getDescription(); + if (index < numberActivities.length - 1) { + result = result + '\n'; + } + return result; + }, ''); + }; + return new NumberBadge(number, descriptorFn); } private uninstallMenubar() { @@ -306,7 +357,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { this.globalActivityAction = new ActivityAction({ id: 'workbench.actions.manage', name: nls.localize('manage', "Manage"), - cssClass: 'update-activity' + cssClass: 'codicon-settings-gear' }); this.globalActivityActionBar.push(this.globalActivityAction); @@ -450,6 +501,9 @@ export class ActivitybarPart extends Part implements IActivityBarService { if (this.globalActivityActionBar) { availableHeight -= (this.globalActivityActionBar.viewItems.length * ActivitybarPart.ACTION_HEIGHT); // adjust height for global actions showing } + if (this.menubar) { + availableHeight -= this.menubar.clientHeight; + } this.compositeBar.layout(new Dimension(width, availableHeight)); } diff --git a/src/vs/workbench/browser/parts/activitybar/media/activityaction.css b/src/vs/workbench/browser/parts/activitybar/media/activityaction.css index 91c9d27e920f..96ffa37af694 100644 --- a/src/vs/workbench/browser/parts/activitybar/media/activityaction.css +++ b/src/vs/workbench/browser/parts/activitybar/media/activityaction.css @@ -6,7 +6,7 @@ .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item { display: block; position: relative; - padding: 5px 0; + margin-bottom: 4px; } .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-label { @@ -14,23 +14,32 @@ z-index: 1; display: flex; overflow: hidden; - height: 40px; - line-height: 40px; + height: 48px; margin-right: 0; - padding: 0 0 0 48px; box-sizing: border-box; + +} + +.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-label:not(.codicon) { font-size: 15px; + line-height: 40px; + padding: 0 0 0 48px; +} + +.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-label.codicon { + font-size: 24px; + align-items: center; + justify-content: center; } .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked .active-item-indicator:before, .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item:focus:before { content: ""; position: absolute; - top: 9px; - height: 32px; + top: 0; z-index: 1; - top: 5px; - height: 40px; + top: 0; + height: 100%; width: 0; border-left: 2px solid; } @@ -41,7 +50,7 @@ } .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked:focus .active-item-indicator:before { - border-left: none; /* don't show active border + focus at the same time, focus takes priority */ + visibility: hidden; /* don't show active border + focus at the same time, focus takes priority */ } /* Hides active elements in high contrast mode */ @@ -53,20 +62,14 @@ border-left: none !important; /* no focus feedback when using mouse */ } +.monaco-workbench .activitybar.left > .content :not(.monaco-menu) > .monaco-action-bar .action-item:focus:before, .monaco-workbench .activitybar.left > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked .active-item-indicator:before{ left: 0; } -.monaco-workbench .activitybar.left > .content :not(.monaco-menu) > .monaco-action-bar .action-item:focus:before { - left: 1px; -} - +.monaco-workbench .activitybar.right > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked .active-item-indicator:before, .monaco-workbench .activitybar.right > .content :not(.monaco-menu) > .monaco-action-bar .action-item:focus:before { - right: 1px; -} - -.monaco-workbench .activitybar.right > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked .active-item-indicator:before { - right: 2px; + right: 0; } /* Hides outline on HC as focus is handled by border */ @@ -79,16 +82,22 @@ .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .badge { position: absolute; z-index: 1; - top: 5px; + top: 0; + bottom: 0; + margin: auto; left: 0; overflow: hidden; - width: 50px; - height: 40px; + width: 100%; + height: 100%; +} + +.monaco-workbench.border .activitybar.right > .content :not(.monaco-menu) > .monaco-action-bar .active-item-indicator { + left: -2px; } .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .badge .badge-content { position: absolute; - top: 20px; + top: 24px; right: 8px; font-size: 9px; font-weight: 600; @@ -102,9 +111,9 @@ /* Right aligned */ -.monaco-workbench .activitybar.right > .content :not(.monaco-menu) > .monaco-action-bar .action-label { +.monaco-workbench .activitybar.right > .content :not(.monaco-menu) > .monaco-action-bar .action-label:not(.codicon) { margin-left: 0; - padding: 0 50px 0 0; + padding: 0 48px 0 0; background-position: calc(100% - 9px) center; } diff --git a/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css b/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css index 388dbf3390e2..3c2705d257b0 100644 --- a/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css +++ b/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css @@ -7,6 +7,21 @@ width: 48px; } +.monaco-workbench.windows.chromium .part.activitybar, +.monaco-workbench.linux.chromium .part.activitybar { + /* + * Explicitly put the part onto its own layer to help Chrome to + * render the content with LCD-anti-aliasing. By partioning the + * workbench into multiple layers, we can ensure that a bad + * behaving part is not making another part fallback to greyscale + * rendering. + * + * macOS: does not render LCD-anti-aliased. + */ + transform: translate3d(0px, 0px, 0px); + overflow: visible; /* when a new layer is created, we need to set overflow visible to avoid clipping the menubar */ +} + .monaco-workbench .activitybar > .content { height: 100%; display: flex; @@ -23,14 +38,6 @@ outline: 0 !important; /* activity bar indicates focus custom */ } -.monaco-workbench .activitybar > .content > .composite-bar > .monaco-action-bar .action-label.toggle-more { - -webkit-mask: url('ellipsis-activity-bar.svg') no-repeat 50% 50%; -} - -.monaco-workbench .activitybar .global-activity .monaco-action-bar .action-label.update-activity { - -webkit-mask: url('settings-activity-bar.svg') no-repeat 50% 50%; -} - .monaco-workbench .activitybar > .content > .composite-bar { margin-bottom: auto; } @@ -39,3 +46,8 @@ width: 100%; height: 35px; } + +.monaco-workbench .activitybar .menubar.compact .toolbar-toggle-more { + width: 100%; + height: 35px; +} diff --git a/src/vs/workbench/browser/parts/activitybar/media/ellipsis-activity-bar.svg b/src/vs/workbench/browser/parts/activitybar/media/ellipsis-activity-bar.svg deleted file mode 100644 index 6729ca3c90d1..000000000000 --- a/src/vs/workbench/browser/parts/activitybar/media/ellipsis-activity-bar.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/vs/workbench/browser/parts/activitybar/media/settings-activity-bar.svg b/src/vs/workbench/browser/parts/activitybar/media/settings-activity-bar.svg deleted file mode 100644 index 5d5fbb8ea5e2..000000000000 --- a/src/vs/workbench/browser/parts/activitybar/media/settings-activity-bar.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/compositeBarActions.ts b/src/vs/workbench/browser/parts/compositeBarActions.ts index fec6fa3bdd0b..e50012203039 100644 --- a/src/vs/workbench/browser/parts/compositeBarActions.ts +++ b/src/vs/workbench/browser/parts/compositeBarActions.ts @@ -158,7 +158,13 @@ export class ActivityActionViewItem extends BaseActionViewItem { if (this.label) { if (this.options.icon) { const foreground = this._action.checked ? colors.activeBackgroundColor || colors.activeForegroundColor : colors.inactiveBackgroundColor || colors.inactiveForegroundColor; - this.label.style.backgroundColor = foreground ? foreground.toString() : ''; + if (this.activity.iconUrl) { + // Apply background color to activity bar item provided with iconUrls + this.label.style.backgroundColor = foreground ? foreground.toString() : ''; + } else { + // Apply foreground color to activity bar items provided with codicons + this.label.style.color = foreground ? foreground.toString() : ''; + } } else { const foreground = this._action.checked ? colors.activeForegroundColor : colors.inactiveForegroundColor; const borderBottomColor = this._action.checked ? colors.activeBorderBottomColor : null; @@ -233,6 +239,7 @@ export class ActivityActionViewItem extends BaseActionViewItem { this.updateLabel(); this.updateTitle(this.activity.name); this.updateBadge(); + this.updateStyles(); } protected updateBadge(): void { @@ -313,6 +320,11 @@ export class ActivityActionViewItem extends BaseActionViewItem { dom.addClass(this.label, this.activity.cssClass); } + if (this.options.icon && !this.activity.iconUrl) { + // Only apply codicon class to activity bar icon items without iconUrl + dom.addClass(this.label, 'codicon'); + } + if (!this.options.icon) { this.label.textContent = this.getAction().label; } @@ -346,7 +358,7 @@ export class CompositeOverflowActivityAction extends ActivityAction { super({ id: 'additionalComposites.action', name: nls.localize('additionalViews', "Additional Views"), - cssClass: 'toggle-more' + cssClass: 'codicon-more' }); } @@ -481,11 +493,7 @@ export class CompositeActionViewItem extends ActivityActionViewItem { activityName = this.compositeActivityAction.activity.name; } - this.compositeActivity = { - id: this.compositeActivityAction.activity.id, - cssClass: this.compositeActivityAction.activity.cssClass, - name: activityName - }; + this.compositeActivity = { ...this.compositeActivityAction.activity, ... { name: activityName } }; } return this.compositeActivity; diff --git a/src/vs/workbench/browser/parts/compositePart.ts b/src/vs/workbench/browser/parts/compositePart.ts index 3884885d319c..f63db5ab3372 100644 --- a/src/vs/workbench/browser/parts/compositePart.ts +++ b/src/vs/workbench/browser/parts/compositePart.ts @@ -168,7 +168,7 @@ export abstract class CompositePart extends Part { // Instantiate composite from registry otherwise const compositeDescriptor = this.registry.getComposite(id); if (compositeDescriptor) { - const compositeProgressIndicator = this.instantiationService.createInstance(CompositeProgressIndicator, this.progressBar, compositeDescriptor.id, !!isActive); + const compositeProgressIndicator = this.instantiationService.createInstance(CompositeProgressIndicator, assertIsDefined(this.progressBar), compositeDescriptor.id, !!isActive); const compositeInstantiationService = this.instantiationService.createChild(new ServiceCollection( [IEditorProgressService, compositeProgressIndicator] // provide the editor progress service for any editors instantiated within the composite )); @@ -285,7 +285,7 @@ export abstract class CompositePart extends Part { if (this.activeComposite && this.activeComposite.getId() === compositeId) { // Title - this.updateTitle(this.activeComposite.getId(), this.activeComposite.getTitle() || undefined); + this.updateTitle(this.activeComposite.getId(), this.activeComposite.getTitle()); // Actions const actionsBinding = this.collectCompositeActions(this.activeComposite); diff --git a/src/vs/workbench/browser/parts/editor/baseEditor.ts b/src/vs/workbench/browser/parts/editor/baseEditor.ts index f722d1f4d8d8..74defd79e026 100644 --- a/src/vs/workbench/browser/parts/editor/baseEditor.ts +++ b/src/vs/workbench/browser/parts/editor/baseEditor.ts @@ -78,11 +78,9 @@ export abstract class BaseEditor extends Panel implements IEditor { * The provided cancellation token should be used to test if the operation * was cancelled. */ - setInput(input: EditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise { + async setInput(input: EditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise { this._input = input; this._options = options; - - return Promise.resolve(); } /** @@ -112,6 +110,8 @@ export abstract class BaseEditor extends Panel implements IEditor { this.createEditor(parent); } + onHide() { } + /** * Called to create the editor in the parent HTMLElement. */ diff --git a/src/vs/workbench/browser/parts/editor/binaryEditor.ts b/src/vs/workbench/browser/parts/editor/binaryEditor.ts index e98381960981..c59a36eba87c 100644 --- a/src/vs/workbench/browser/parts/editor/binaryEditor.ts +++ b/src/vs/workbench/browser/parts/editor/binaryEditor.ts @@ -56,7 +56,7 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor { this.callbacks = callbacks; } - getTitle() { + getTitle(): string { return this.input ? this.input.getName() : nls.localize('binaryEditor', "Binary Viewer"); } @@ -93,7 +93,7 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor { } const [binaryContainer, scrollbar] = assertAllDefined(this.binaryContainer, this.scrollbar); - this.resourceViewerContext = ResourceViewer.show({ name: model.getName(), resource: model.getResource(), size: model.getSize(), etag: model.getETag(), mime: model.getMime() }, binaryContainer, scrollbar, { + this.resourceViewerContext = ResourceViewer.show({ name: model.getName(), resource: model.resource, size: model.getSize(), etag: model.getETag(), mime: model.getMime() }, binaryContainer, scrollbar, { openInternalClb: () => this.handleOpenInternalCallback(input, options), openExternalClb: this.environmentService.configuration.remoteAuthority ? undefined : resource => this.callbacks.openExternal(resource), metadataClb: meta => this.handleMetadataChanged(meta) diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbs.ts b/src/vs/workbench/browser/parts/editor/breadcrumbs.ts index 6b2d52784c13..923b0e0a385e 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbs.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbs.ts @@ -167,135 +167,161 @@ Registry.as(Extensions.Configuration).registerConfigurat type: 'boolean', default: true }, - 'breadcrumbs.filteredTypes.file': { + 'breadcrumbs.showFiles': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.file', "When set to `false` breadcrumbs never show `file`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.file', "When enabled breadcrumbs show `file`-symbols.") }, - 'breadcrumbs.filteredTypes.module': { + 'breadcrumbs.showModules': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.module', "When set to `false` breadcrumbs never show `module`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.module', "When enabled breadcrumbs show `module`-symbols.") }, - 'breadcrumbs.filteredTypes.namespace': { + 'breadcrumbs.showNamespaces': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.namespace', "When set to `false` breadcrumbs never show `namespace`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.namespace', "When enabled breadcrumbs show `namespace`-symbols.") }, - 'breadcrumbs.filteredTypes.package': { + 'breadcrumbs.showPackages': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.package', "When set to `false` breadcrumbs never show `package`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.package', "When enabled breadcrumbs show `package`-symbols.") }, - 'breadcrumbs.filteredTypes.class': { + 'breadcrumbs.showClasses': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.class', "When set to `false` breadcrumbs never show `class`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.class', "When enabled breadcrumbs show `class`-symbols.") }, - 'breadcrumbs.filteredTypes.method': { + 'breadcrumbs.showMethods': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.method', "When set to `false` breadcrumbs never show `method`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.method', "When enabled breadcrumbs show `method`-symbols.") }, - 'breadcrumbs.filteredTypes.property': { + 'breadcrumbs.showProperties': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.property', "When set to `false` breadcrumbs never show `property`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.property', "When enabled breadcrumbs show `property`-symbols.") }, - 'breadcrumbs.filteredTypes.field': { + 'breadcrumbs.showFields': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.field', "When set to `false` breadcrumbs never show `field`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.field', "When enabled breadcrumbs show `field`-symbols.") }, - 'breadcrumbs.filteredTypes.constructor': { + 'breadcrumbs.showConstructors': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.constructor', "When set to `false` breadcrumbs never show `constructor`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.constructor', "When enabled breadcrumbs show `constructor`-symbols.") }, - 'breadcrumbs.filteredTypes.enum': { + 'breadcrumbs.showEnums': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.enum', "When set to `false` breadcrumbs never show `enum`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.enum', "When enabled breadcrumbs show `enum`-symbols.") }, - 'breadcrumbs.filteredTypes.interface': { + 'breadcrumbs.showInterfaces': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.interface', "When set to `false` breadcrumbs never show `interface`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.interface', "When enabled breadcrumbs show `interface`-symbols.") }, - 'breadcrumbs.filteredTypes.function': { + 'breadcrumbs.showFunctions': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.function', "When set to `false` breadcrumbs never show `function`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.function', "When enabled breadcrumbs show `function`-symbols.") }, - 'breadcrumbs.filteredTypes.variable': { + 'breadcrumbs.showVariables': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.variable', "When set to `false` breadcrumbs never show `variable`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.variable', "When enabled breadcrumbs show `variable`-symbols.") }, - 'breadcrumbs.filteredTypes.constant': { + 'breadcrumbs.showConstants': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.constant', "When set to `false` breadcrumbs never show `constant`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.constant', "When enabled breadcrumbs show `constant`-symbols.") }, - 'breadcrumbs.filteredTypes.string': { + 'breadcrumbs.showStrings': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.string', "When set to `false` breadcrumbs never show `string`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.string', "When enabled breadcrumbs show `string`-symbols.") }, - 'breadcrumbs.filteredTypes.number': { + 'breadcrumbs.showNumbers': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.number', "When set to `false` breadcrumbs never show `number`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.number', "When enabled breadcrumbs show `number`-symbols.") }, - 'breadcrumbs.filteredTypes.boolean': { + 'breadcrumbs.showBooleans': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.boolean', "When set to `false` breadcrumbs never show `boolean`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.boolean', "When enabled breadcrumbs show `boolean`-symbols.") }, - 'breadcrumbs.filteredTypes.array': { + 'breadcrumbs.showArrays': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.array', "When set to `false` breadcrumbs never show `array`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.array', "When enabled breadcrumbs show `array`-symbols.") }, - 'breadcrumbs.filteredTypes.object': { + 'breadcrumbs.showObjects': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.object', "When set to `false` breadcrumbs never show `object`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.object', "When enabled breadcrumbs show `object`-symbols.") }, - 'breadcrumbs.filteredTypes.key': { + 'breadcrumbs.showKeys': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.key', "When set to `false` breadcrumbs never show `key`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.key', "When enabled breadcrumbs show `key`-symbols.") }, - 'breadcrumbs.filteredTypes.null': { + 'breadcrumbs.showNull': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.null', "When set to `false` breadcrumbs never show `null`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.null', "When enabled breadcrumbs show `null`-symbols.") }, - 'breadcrumbs.filteredTypes.enumMember': { + 'breadcrumbs.showEnumMembers': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.enumMember', "When set to `false` breadcrumbs never show `enumMember`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.enumMember', "When enabled breadcrumbs show `enumMember`-symbols.") }, - 'breadcrumbs.filteredTypes.struct': { + 'breadcrumbs.showStructs': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.struct', "When set to `false` breadcrumbs never show `struct`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.struct', "When enabled breadcrumbs show `struct`-symbols.") }, - 'breadcrumbs.filteredTypes.event': { + 'breadcrumbs.showEvents': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.event', "When set to `false` breadcrumbs never show `event`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.event', "When enabled breadcrumbs show `event`-symbols.") }, - 'breadcrumbs.filteredTypes.operator': { + 'breadcrumbs.showOperators': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.operator', "When set to `false` breadcrumbs never show `operator`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.operator', "When enabled breadcrumbs show `operator`-symbols.") }, - 'breadcrumbs.filteredTypes.typeParameter': { + 'breadcrumbs.showTypeParameters': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.typeParameter', "When set to `false` breadcrumbs never show `typeParameter`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.typeParameter', "When enabled breadcrumbs show `typeParameter`-symbols.") } } }); diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index 8051d8445bda..8dec032b0314 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts @@ -47,6 +47,7 @@ import { IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor'; import { onDidChangeZoomLevel } from 'vs/base/browser/browser'; import { withNullAsUndefined, withUndefinedAsNull } from 'vs/base/common/types'; import { ILabelService } from 'vs/platform/label/common/label'; +import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; class Item extends BreadcrumbsItem { @@ -102,7 +103,7 @@ class Item extends BreadcrumbsItem { } else if (this.element instanceof OutlineGroup) { // provider let label = new IconLabel(container); - label.setLabel(this.element.provider.displayName); + label.setLabel(this.element.provider.displayName || ''); this._disposables.add(label); } else if (this.element instanceof OutlineElement) { @@ -168,6 +169,7 @@ export class BreadcrumbsControl { @IThemeService private readonly _themeService: IThemeService, @IQuickOpenService private readonly _quickOpenService: IQuickOpenService, @IConfigurationService private readonly _configurationService: IConfigurationService, + @ITextResourceConfigurationService private readonly _textResourceConfigurationService: ITextResourceConfigurationService, @IFileService private readonly _fileService: IFileService, @ITelemetryService private readonly _telemetryService: ITelemetryService, @ILabelService private readonly _labelService: ILabelService, @@ -246,7 +248,12 @@ export class BreadcrumbsControl { const uri = input.getResource()!; const editor = this._getActiveCodeEditor(); - const model = new EditorBreadcrumbsModel(uri, editor, this._configurationService, this._workspaceService); + const model = new EditorBreadcrumbsModel( + uri, editor, + this._configurationService, + this._textResourceConfigurationService, + this._workspaceService + ); dom.toggleClass(this.domNode, 'relative-path', model.isRelative()); dom.toggleClass(this.domNode, 'backslash-path', this._labelService.getSeparator(uri.scheme, uri.authority) === '\\'); @@ -375,7 +382,7 @@ export class BreadcrumbsControl { editorViewState = withNullAsUndefined(editor.saveViewState()); } const { symbol } = data.target; - editor.revealRangeInCenter(symbol.range, ScrollType.Smooth); + editor.revealRangeInCenterIfOutsideViewport(symbol.range, ScrollType.Smooth); editorDecorations = editor.deltaDecorations(editorDecorations, [{ range: symbol.range, options: { diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts index 0b76064787f3..165dd96fd48a 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts @@ -6,7 +6,7 @@ import { equals } from 'vs/base/common/arrays'; import { TimeoutTimer } from 'vs/base/common/async'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; -import { size } from 'vs/base/common/collections'; +import { size, values } from 'vs/base/common/collections'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore } from 'vs/base/common/lifecycle'; @@ -14,7 +14,7 @@ import { isEqual, dirname } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IPosition } from 'vs/editor/common/core/position'; -import { DocumentSymbolProviderRegistry, SymbolKinds } from 'vs/editor/common/modes'; +import { DocumentSymbolProviderRegistry } from 'vs/editor/common/modes'; import { OutlineElement, OutlineGroup, OutlineModel, TreeElement } from 'vs/editor/contrib/documentSymbols/outlineModel'; import { IWorkspaceContextService, IWorkspaceFolder, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { Schemas } from 'vs/base/common/network'; @@ -22,6 +22,9 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { BreadcrumbsConfig } from 'vs/workbench/browser/parts/editor/breadcrumbs'; import { FileKind } from 'vs/platform/files/common/files'; import { withNullAsUndefined } from 'vs/base/common/types'; +import { OutlineFilter } from 'vs/editor/contrib/documentSymbols/outlineTree'; +import { ITextModel } from 'vs/editor/common/model'; +import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; export class FileElement { constructor( @@ -52,9 +55,9 @@ export class EditorBreadcrumbsModel { private readonly _uri: URI, private readonly _editor: ICodeEditor | undefined, @IConfigurationService private readonly _configurationService: IConfigurationService, + @ITextResourceConfigurationService private readonly _textResourceConfigurationService: ITextResourceConfigurationService, @IWorkspaceContextService workspaceService: IWorkspaceContextService, ) { - this._cfgFilePath = BreadcrumbsConfig.FilePath.bindTo(_configurationService); this._cfgSymbolPath = BreadcrumbsConfig.SymbolPath.bindTo(_configurationService); @@ -139,8 +142,19 @@ export class EditorBreadcrumbsModel { // update when config changes (re-render) this._disposables.add(this._configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration('breadcrumbs.filteredTypes')) { + if (e.affectsConfiguration('breadcrumbs')) { this._updateOutline(true); + return; + } + if (this._editor && this._editor.getModel()) { + const editorModel = this._editor.getModel() as ITextModel; + const languageName = editorModel.getLanguageIdentifier().language; + + // Checking for changes in the current language override config. + // We can't be more specific than this because the ConfigurationChangeEvent(e) only includes the first part of the root path + if (e.affectsConfiguration(`[${languageName}]`)) { + this._updateOutline(true); + } } })); @@ -214,7 +228,7 @@ export class EditorBreadcrumbsModel { } let item: OutlineGroup | OutlineElement | undefined = model.getItemEnclosingPosition(position); if (!item) { - return [model]; + return this._getOutlineElementsRoot(model); } let chain: Array = []; while (item) { @@ -231,17 +245,34 @@ export class EditorBreadcrumbsModel { let result: Array = []; for (let i = chain.length - 1; i >= 0; i--) { let element = chain[i]; - if ( - element instanceof OutlineElement - && !this._configurationService.getValue(`breadcrumbs.filteredTypes.${SymbolKinds.toString(element.symbol.kind)}`) - ) { + if (this._isFiltered(element)) { break; } result.push(element); } + if (result.length === 0) { + return this._getOutlineElementsRoot(model); + } return result; } + private _getOutlineElementsRoot(model: OutlineModel): (OutlineModel | OutlineGroup | OutlineElement)[] { + return values(model.children).every(e => this._isFiltered(e)) ? [] : [model]; + } + + private _isFiltered(element: TreeElement): boolean { + if (element instanceof OutlineElement) { + const key = `breadcrumbs.${OutlineFilter.kindToConfigName[element.symbol.kind]}`; + let uri: URI | undefined; + if (this._editor && this._editor.getModel()) { + const model = this._editor.getModel() as ITextModel; + uri = model.uri; + } + return !this._textResourceConfigurationService.getValue(uri, key); + } + return false; + } + private _updateOutlineElements(elements: Array): void { if (!equals(elements, this._outlineElements, EditorBreadcrumbsModel._outlineElementEquals)) { this._outlineElements = elements; diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts index 34614e1ae8e9..89ed66d0a4fa 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts @@ -374,12 +374,15 @@ export class BreadcrumbsFilePicker extends BreadcrumbsPicker { const labels = this._instantiationService.createInstance(ResourceLabels, DEFAULT_LABELS_CONTAINER /* TODO@Jo visibility propagation */); this._disposables.add(labels); - return this._instantiationService.createInstance(WorkbenchAsyncDataTree, 'BreadcrumbsFilePicker', container, new FileVirtualDelegate(), [this._instantiationService.createInstance(FileRenderer, labels)], this._instantiationService.createInstance(FileDataSource), { + return this._instantiationService.createInstance>(WorkbenchAsyncDataTree, 'BreadcrumbsFilePicker', container, new FileVirtualDelegate(), [this._instantiationService.createInstance(FileRenderer, labels)], this._instantiationService.createInstance(FileDataSource), { multipleSelectionSupport: false, sorter: new FileSorter(), filter: this._instantiationService.createInstance(FileFilter), identityProvider: new FileIdentityProvider(), - keyboardNavigationLabelProvider: new FileNavigationLabelProvider() + keyboardNavigationLabelProvider: new FileNavigationLabelProvider(), + overrideStyles: { + listBackground: breadcrumbsPickerBackground + } }); } @@ -438,7 +441,7 @@ export class BreadcrumbsOutlinePicker extends BreadcrumbsPicker { } protected _createTree(container: HTMLElement) { - return this._instantiationService.createInstance( + return this._instantiationService.createInstance>( WorkbenchDataTree, 'BreadcrumbsOutlinePicker', container, @@ -452,7 +455,7 @@ export class BreadcrumbsOutlinePicker extends BreadcrumbsPicker { sorter: new OutlineItemComparator(this._getOutlineItemCompareType()), identityProvider: new OutlineIdentityProvider(), keyboardNavigationLabelProvider: new OutlineNavigationLabelProvider(), - filter: this._instantiationService.createInstance(OutlineFilter, 'breadcrumbs.filteredTypes') + filter: this._instantiationService.createInstance(OutlineFilter, 'breadcrumbs') } ); } @@ -468,14 +471,10 @@ export class BreadcrumbsOutlinePicker extends BreadcrumbsPicker { const tree = this._tree as WorkbenchDataTree; tree.setInput(model); - let focusElement: TreeElement; - if (element === model) { - focusElement = tree.navigate().first(); - } else { - focusElement = element; + if (element !== model) { + tree.reveal(element, 0.5); + tree.setFocus([element], this._fakeEvent); } - tree.reveal(focusElement, 0.5); - tree.setFocus([focusElement], this._fakeEvent); tree.domFocus(); return Promise.resolve(); diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index 24ee3f3ca30d..8be11b433418 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -13,11 +13,11 @@ import { EditorInput, IEditorInputFactory, SideBySideEditorInput, IEditorInputFa import { TextResourceEditor } from 'vs/workbench/browser/parts/editor/textResourceEditor'; import { SideBySideEditor } from 'vs/workbench/browser/parts/editor/sideBySideEditor'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; -import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput'; +import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput'; import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { TextDiffEditor } from 'vs/workbench/browser/parts/editor/textDiffEditor'; -import { ITextFileService, SUPPORTED_ENCODINGS } from 'vs/workbench/services/textfile/common/textfiles'; +import { SUPPORTED_ENCODINGS } from 'vs/workbench/services/textfile/common/textfiles'; import { BinaryResourceDiffEditor } from 'vs/workbench/browser/parts/editor/binaryDiffEditor'; import { ChangeEncodingAction, ChangeEOLAction, ChangeModeAction, EditorStatus } from 'vs/workbench/browser/parts/editor/editorStatus'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; @@ -52,24 +52,26 @@ import { toLocalResource } from 'vs/base/common/resources'; import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { withNullAsUndefined } from 'vs/base/common/types'; -import { registerAndGetAmdImageURL } from 'vs/base/common/amd'; +import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; +import { EditorAutoSave } from 'vs/workbench/browser/parts/editor/editorAutoSave'; +import { ThemeIcon } from 'vs/platform/theme/common/themeService'; // Register String Editor Registry.as(EditorExtensions.Editors).registerEditor( - new EditorDescriptor( + EditorDescriptor.create( TextResourceEditor, TextResourceEditor.ID, nls.localize('textEditor', "Text Editor"), ), [ - new SyncDescriptor(UntitledEditorInput), + new SyncDescriptor(UntitledTextEditorInput), new SyncDescriptor(ResourceEditorInput) ] ); // Register Text Diff Editor Registry.as(EditorExtensions.Editors).registerEditor( - new EditorDescriptor( + EditorDescriptor.create( TextDiffEditor, TextDiffEditor.ID, nls.localize('textDiffEditor', "Text Diff Editor") @@ -81,7 +83,7 @@ Registry.as(EditorExtensions.Editors).registerEditor( // Register Binary Resource Diff Editor Registry.as(EditorExtensions.Editors).registerEditor( - new EditorDescriptor( + EditorDescriptor.create( BinaryResourceDiffEditor, BinaryResourceDiffEditor.ID, nls.localize('binaryDiffEditor', "Binary Diff Editor") @@ -92,7 +94,7 @@ Registry.as(EditorExtensions.Editors).registerEditor( ); Registry.as(EditorExtensions.Editors).registerEditor( - new EditorDescriptor( + EditorDescriptor.create( SideBySideEditor, SideBySideEditor.ID, nls.localize('sideBySideEditor', "Side by Side Editor") @@ -102,7 +104,7 @@ Registry.as(EditorExtensions.Editors).registerEditor( ] ); -interface ISerializedUntitledEditorInput { +interface ISerializedUntitledTextEditorInput { resource: string; resourceJSON: object; modeId: string | undefined; @@ -110,48 +112,48 @@ interface ISerializedUntitledEditorInput { } // Register Editor Input Factory -class UntitledEditorInputFactory implements IEditorInputFactory { +class UntitledTextEditorInputFactory implements IEditorInputFactory { constructor( - @ITextFileService private readonly textFileService: ITextFileService, + @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService ) { } serialize(editorInput: EditorInput): string | undefined { - if (!this.textFileService.isHotExitEnabled) { + if (!this.filesConfigurationService.isHotExitEnabled) { return undefined; // never restore untitled unless hot exit is enabled } - const untitledEditorInput = editorInput; + const untitledTextEditorInput = editorInput; - let resource = untitledEditorInput.getResource(); - if (untitledEditorInput.hasAssociatedFilePath) { + let resource = untitledTextEditorInput.getResource(); + if (untitledTextEditorInput.hasAssociatedFilePath) { resource = toLocalResource(resource, this.environmentService.configuration.remoteAuthority); // untitled with associated file path use the local schema } - const serialized: ISerializedUntitledEditorInput = { + const serialized: ISerializedUntitledTextEditorInput = { resource: resource.toString(), // Keep for backwards compatibility resourceJSON: resource.toJSON(), - modeId: untitledEditorInput.getMode(), - encoding: untitledEditorInput.getEncoding() + modeId: untitledTextEditorInput.getMode(), + encoding: untitledTextEditorInput.getEncoding() }; return JSON.stringify(serialized); } - deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): UntitledEditorInput { - return instantiationService.invokeFunction(accessor => { - const deserialized: ISerializedUntitledEditorInput = JSON.parse(serializedEditorInput); + deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): UntitledTextEditorInput { + return instantiationService.invokeFunction(accessor => { + const deserialized: ISerializedUntitledTextEditorInput = JSON.parse(serializedEditorInput); const resource = !!deserialized.resourceJSON ? URI.revive(deserialized.resourceJSON) : URI.parse(deserialized.resource); const mode = deserialized.modeId; const encoding = deserialized.encoding; - return accessor.get(IEditorService).createInput({ resource, mode, encoding, forceUntitled: true }) as UntitledEditorInput; + return accessor.get(IEditorService).createInput({ resource, mode, encoding, forceUntitled: true }) as UntitledTextEditorInput; }); } } -Registry.as(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory(UntitledEditorInput.ID, UntitledEditorInputFactory); +Registry.as(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory(UntitledTextEditorInput.ID, UntitledTextEditorInputFactory); interface ISerializedSideBySideEditorInput { name: string; @@ -223,13 +225,16 @@ registerEditorContribution(OpenWorkspaceButtonContribution.ID, OpenWorkspaceButt // Register Editor Status Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(EditorStatus, LifecyclePhase.Ready); +// Register Editor Auto Save +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(EditorAutoSave, LifecyclePhase.Ready); + // Register Status Actions const registry = Registry.as(ActionExtensions.WorkbenchActions); -registry.registerWorkbenchAction(new SyncActionDescriptor(ChangeModeAction, ChangeModeAction.ID, ChangeModeAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_M) }), 'Change Language Mode'); -registry.registerWorkbenchAction(new SyncActionDescriptor(ChangeEOLAction, ChangeEOLAction.ID, ChangeEOLAction.LABEL), 'Change End of Line Sequence'); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ChangeModeAction, ChangeModeAction.ID, ChangeModeAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_M) }), 'Change Language Mode'); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ChangeEOLAction, ChangeEOLAction.ID, ChangeEOLAction.LABEL), 'Change End of Line Sequence'); if (Object.keys(SUPPORTED_ENCODINGS).length > 1) { - registry.registerWorkbenchAction(new SyncActionDescriptor(ChangeEncodingAction, ChangeEncodingAction.ID, ChangeEncodingAction.LABEL), 'Change File Encoding'); + registry.registerWorkbenchAction(SyncActionDescriptor.create(ChangeEncodingAction, ChangeEncodingAction.ID, ChangeEncodingAction.LABEL), 'Change File Encoding'); } export class QuickOpenActionContributor extends ActionBarContributor { @@ -278,7 +283,7 @@ const editorPickerContextKey = 'inEditorsPicker'; const editorPickerContext = ContextKeyExpr.and(inQuickOpenContext, ContextKeyExpr.has(editorPickerContextKey)); Registry.as(QuickOpenExtensions.Quickopen).registerQuickOpenHandler( - new QuickOpenHandlerDescriptor( + QuickOpenHandlerDescriptor.create( ActiveEditorGroupPicker, ActiveEditorGroupPicker.ID, editorCommands.NAVIGATE_IN_ACTIVE_GROUP_PREFIX, @@ -294,7 +299,7 @@ Registry.as(QuickOpenExtensions.Quickopen).registerQuickOpen ); Registry.as(QuickOpenExtensions.Quickopen).registerQuickOpenHandler( - new QuickOpenHandlerDescriptor( + QuickOpenHandlerDescriptor.create( AllEditorsPicker, AllEditorsPicker.ID, editorCommands.NAVIGATE_ALL_EDITORS_GROUP_PREFIX, @@ -311,84 +316,84 @@ Registry.as(QuickOpenExtensions.Quickopen).registerQuickOpen // Register Editor Actions const category = nls.localize('view', "View"); -registry.registerWorkbenchAction(new SyncActionDescriptor(OpenNextEditorInGroup, OpenNextEditorInGroup.ID, OpenNextEditorInGroup.LABEL), 'View: Open Next Editor in Group', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(OpenPreviousEditorInGroup, OpenPreviousEditorInGroup.ID, OpenPreviousEditorInGroup.LABEL), 'View: Open Previous Editor in Group', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(OpenLastEditorInGroup, OpenLastEditorInGroup.ID, OpenLastEditorInGroup.LABEL, { primary: KeyMod.Alt | KeyCode.KEY_0, secondary: [KeyMod.CtrlCmd | KeyCode.KEY_9], mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_0, secondary: [KeyMod.CtrlCmd | KeyCode.KEY_9] } }), 'View: Open Last Editor in Group', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(OpenFirstEditorInGroup, OpenFirstEditorInGroup.ID, OpenFirstEditorInGroup.LABEL), 'View: Open First Editor in Group', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(OpenNextRecentlyUsedEditorAction, OpenNextRecentlyUsedEditorAction.ID, OpenNextRecentlyUsedEditorAction.LABEL), 'View: Open Next Recently Used Editor', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(OpenPreviousRecentlyUsedEditorAction, OpenPreviousRecentlyUsedEditorAction.ID, OpenPreviousRecentlyUsedEditorAction.LABEL), 'View: Open Previous Recently Used Editor', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(ShowAllEditorsAction, ShowAllEditorsAction.ID, ShowAllEditorsAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_P), mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Tab } }), 'View: Show All Editors', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(ShowEditorsInActiveGroupAction, ShowEditorsInActiveGroupAction.ID, ShowEditorsInActiveGroupAction.LABEL), 'View: Show Editors in Active Group', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(OpenNextEditor, OpenNextEditor.ID, OpenNextEditor.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.PageDown, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.RightArrow, secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_CLOSE_SQUARE_BRACKET] } }), 'View: Open Next Editor', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(OpenPreviousEditor, OpenPreviousEditor.ID, OpenPreviousEditor.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.PageUp, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.LeftArrow, secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_OPEN_SQUARE_BRACKET] } }), 'View: Open Previous Editor', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(ReopenClosedEditorAction, ReopenClosedEditorAction.ID, ReopenClosedEditorAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_T }), 'View: Reopen Closed Editor', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(ClearRecentFilesAction, ClearRecentFilesAction.ID, ClearRecentFilesAction.LABEL), 'File: Clear Recently Opened', nls.localize('file', "File")); -registry.registerWorkbenchAction(new SyncActionDescriptor(CloseAllEditorsAction, CloseAllEditorsAction.ID, CloseAllEditorsAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_W) }), 'View: Close All Editors', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(CloseAllEditorGroupsAction, CloseAllEditorGroupsAction.ID, CloseAllEditorGroupsAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_W) }), 'View: Close All Editor Groups', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(CloseLeftEditorsInGroupAction, CloseLeftEditorsInGroupAction.ID, CloseLeftEditorsInGroupAction.LABEL), 'View: Close Editors to the Left in Group', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(CloseEditorsInOtherGroupsAction, CloseEditorsInOtherGroupsAction.ID, CloseEditorsInOtherGroupsAction.LABEL), 'View: Close Editors in Other Groups', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(CloseEditorInAllGroupsAction, CloseEditorInAllGroupsAction.ID, CloseEditorInAllGroupsAction.LABEL), 'View: Close Editor in All Groups', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(SplitEditorAction, SplitEditorAction.ID, SplitEditorAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.US_BACKSLASH }), 'View: Split Editor', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(SplitEditorOrthogonalAction, SplitEditorOrthogonalAction.ID, SplitEditorOrthogonalAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.US_BACKSLASH) }), 'View: Split Editor Orthogonal', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(SplitEditorLeftAction, SplitEditorLeftAction.ID, SplitEditorLeftAction.LABEL), 'View: Split Editor Left', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(SplitEditorRightAction, SplitEditorRightAction.ID, SplitEditorRightAction.LABEL), 'View: Split Editor Right', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(SplitEditorUpAction, SplitEditorUpAction.ID, SplitEditorUpAction.LABEL), 'Split Editor Up', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(SplitEditorDownAction, SplitEditorDownAction.ID, SplitEditorDownAction.LABEL), 'View: Split Editor Down', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(JoinTwoGroupsAction, JoinTwoGroupsAction.ID, JoinTwoGroupsAction.LABEL), 'View: Join Editor Group with Next Group', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(JoinAllGroupsAction, JoinAllGroupsAction.ID, JoinAllGroupsAction.LABEL), 'View: Join All Editor Groups', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(NavigateBetweenGroupsAction, NavigateBetweenGroupsAction.ID, NavigateBetweenGroupsAction.LABEL), 'View: Navigate Between Editor Groups', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(ResetGroupSizesAction, ResetGroupSizesAction.ID, ResetGroupSizesAction.LABEL), 'View: Reset Editor Group Sizes', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleGroupSizesAction, ToggleGroupSizesAction.ID, ToggleGroupSizesAction.LABEL), 'View: Toggle Editor Group Sizes', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(MaximizeGroupAction, MaximizeGroupAction.ID, MaximizeGroupAction.LABEL), 'View: Maximize Editor Group and Hide Side Bar', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(MinimizeOtherGroupsAction, MinimizeOtherGroupsAction.ID, MinimizeOtherGroupsAction.LABEL), 'View: Maximize Editor Group', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorLeftInGroupAction, MoveEditorLeftInGroupAction.ID, MoveEditorLeftInGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.PageUp, mac: { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.LeftArrow) } }), 'View: Move Editor Left', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorRightInGroupAction, MoveEditorRightInGroupAction.ID, MoveEditorRightInGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.PageDown, mac: { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.RightArrow) } }), 'View: Move Editor Right', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(MoveGroupLeftAction, MoveGroupLeftAction.ID, MoveGroupLeftAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.LeftArrow) }), 'View: Move Editor Group Left', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(MoveGroupRightAction, MoveGroupRightAction.ID, MoveGroupRightAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.RightArrow) }), 'View: Move Editor Group Right', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(MoveGroupUpAction, MoveGroupUpAction.ID, MoveGroupUpAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.UpArrow) }), 'View: Move Editor Group Up', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(MoveGroupDownAction, MoveGroupDownAction.ID, MoveGroupDownAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.DownArrow) }), 'View: Move Editor Group Down', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorToPreviousGroupAction, MoveEditorToPreviousGroupAction.ID, MoveEditorToPreviousGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.LeftArrow, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.LeftArrow } }), 'View: Move Editor into Previous Group', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorToNextGroupAction, MoveEditorToNextGroupAction.ID, MoveEditorToNextGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.RightArrow, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.RightArrow } }), 'View: Move Editor into Next Group', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorToFirstGroupAction, MoveEditorToFirstGroupAction.ID, MoveEditorToFirstGroupAction.LABEL, { primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_1, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KEY_1 } }), 'View: Move Editor into First Group', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorToLastGroupAction, MoveEditorToLastGroupAction.ID, MoveEditorToLastGroupAction.LABEL, { primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_9, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KEY_9 } }), 'View: Move Editor into Last Group', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorToLeftGroupAction, MoveEditorToLeftGroupAction.ID, MoveEditorToLeftGroupAction.LABEL), 'View: Move Editor into Left Group', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorToRightGroupAction, MoveEditorToRightGroupAction.ID, MoveEditorToRightGroupAction.LABEL), 'View: Move Editor into Right Group', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorToAboveGroupAction, MoveEditorToAboveGroupAction.ID, MoveEditorToAboveGroupAction.LABEL), 'View: Move Editor into Above Group', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorToBelowGroupAction, MoveEditorToBelowGroupAction.ID, MoveEditorToBelowGroupAction.LABEL), 'View: Move Editor into Below Group', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(FocusActiveGroupAction, FocusActiveGroupAction.ID, FocusActiveGroupAction.LABEL), 'View: Focus Active Editor Group', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(FocusFirstGroupAction, FocusFirstGroupAction.ID, FocusFirstGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_1 }), 'View: Focus First Editor Group', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(FocusLastGroupAction, FocusLastGroupAction.ID, FocusLastGroupAction.LABEL), 'View: Focus Last Editor Group', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(FocusPreviousGroup, FocusPreviousGroup.ID, FocusPreviousGroup.LABEL), 'View: Focus Previous Editor Group', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(FocusNextGroup, FocusNextGroup.ID, FocusNextGroup.LABEL), 'View: Focus Next Editor Group', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(FocusLeftGroup, FocusLeftGroup.ID, FocusLeftGroup.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.LeftArrow) }), 'View: Focus Left Editor Group', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(FocusRightGroup, FocusRightGroup.ID, FocusRightGroup.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.RightArrow) }), 'View: Focus Right Editor Group', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(FocusAboveGroup, FocusAboveGroup.ID, FocusAboveGroup.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.UpArrow) }), 'View: Focus Above Editor Group', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(FocusBelowGroup, FocusBelowGroup.ID, FocusBelowGroup.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.DownArrow) }), 'View: Focus Below Editor Group', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(NewEditorGroupLeftAction, NewEditorGroupLeftAction.ID, NewEditorGroupLeftAction.LABEL), 'View: New Editor Group to the Left', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(NewEditorGroupRightAction, NewEditorGroupRightAction.ID, NewEditorGroupRightAction.LABEL), 'View: New Editor Group to the Right', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(NewEditorGroupAboveAction, NewEditorGroupAboveAction.ID, NewEditorGroupAboveAction.LABEL), 'View: New Editor Group Above', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(NewEditorGroupBelowAction, NewEditorGroupBelowAction.ID, NewEditorGroupBelowAction.LABEL), 'View: New Editor Group Below', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(NavigateForwardAction, NavigateForwardAction.ID, NavigateForwardAction.LABEL, { primary: 0, win: { primary: KeyMod.Alt | KeyCode.RightArrow }, mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.US_MINUS }, linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_MINUS } }), 'Go Forward'); -registry.registerWorkbenchAction(new SyncActionDescriptor(NavigateBackwardsAction, NavigateBackwardsAction.ID, NavigateBackwardsAction.LABEL, { primary: 0, win: { primary: KeyMod.Alt | KeyCode.LeftArrow }, mac: { primary: KeyMod.WinCtrl | KeyCode.US_MINUS }, linux: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.US_MINUS } }), 'Go Back'); -registry.registerWorkbenchAction(new SyncActionDescriptor(NavigateToLastEditLocationAction, NavigateToLastEditLocationAction.ID, NavigateToLastEditLocationAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_Q) }), 'Go to Last Edit Location'); -registry.registerWorkbenchAction(new SyncActionDescriptor(NavigateLastAction, NavigateLastAction.ID, NavigateLastAction.LABEL), 'Go Last'); -registry.registerWorkbenchAction(new SyncActionDescriptor(OpenPreviousEditorFromHistoryAction, OpenPreviousEditorFromHistoryAction.ID, OpenPreviousEditorFromHistoryAction.LABEL), 'Open Previous Editor from History'); -registry.registerWorkbenchAction(new SyncActionDescriptor(ClearEditorHistoryAction, ClearEditorHistoryAction.ID, ClearEditorHistoryAction.LABEL), 'Clear Editor History'); -registry.registerWorkbenchAction(new SyncActionDescriptor(RevertAndCloseEditorAction, RevertAndCloseEditorAction.ID, RevertAndCloseEditorAction.LABEL), 'View: Revert and Close Editor', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(EditorLayoutSingleAction, EditorLayoutSingleAction.ID, EditorLayoutSingleAction.LABEL), 'View: Single Column Editor Layout', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(EditorLayoutTwoColumnsAction, EditorLayoutTwoColumnsAction.ID, EditorLayoutTwoColumnsAction.LABEL), 'View: Two Columns Editor Layout', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(EditorLayoutThreeColumnsAction, EditorLayoutThreeColumnsAction.ID, EditorLayoutThreeColumnsAction.LABEL), 'View: Three Columns Editor Layout', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(EditorLayoutTwoRowsAction, EditorLayoutTwoRowsAction.ID, EditorLayoutTwoRowsAction.LABEL), 'View: Two Rows Editor Layout', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(EditorLayoutThreeRowsAction, EditorLayoutThreeRowsAction.ID, EditorLayoutThreeRowsAction.LABEL), 'View: Three Rows Editor Layout', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(EditorLayoutTwoByTwoGridAction, EditorLayoutTwoByTwoGridAction.ID, EditorLayoutTwoByTwoGridAction.LABEL), 'View: Grid Editor Layout (2x2)', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(EditorLayoutTwoRowsRightAction, EditorLayoutTwoRowsRightAction.ID, EditorLayoutTwoRowsRightAction.LABEL), 'View: Two Rows Right Editor Layout', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(EditorLayoutTwoColumnsBottomAction, EditorLayoutTwoColumnsBottomAction.ID, EditorLayoutTwoColumnsBottomAction.LABEL), 'View: Two Columns Bottom Editor Layout', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenNextEditorInGroup, OpenNextEditorInGroup.ID, OpenNextEditorInGroup.LABEL), 'View: Open Next Editor in Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenPreviousEditorInGroup, OpenPreviousEditorInGroup.ID, OpenPreviousEditorInGroup.LABEL), 'View: Open Previous Editor in Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenLastEditorInGroup, OpenLastEditorInGroup.ID, OpenLastEditorInGroup.LABEL, { primary: KeyMod.Alt | KeyCode.KEY_0, secondary: [KeyMod.CtrlCmd | KeyCode.KEY_9], mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_0, secondary: [KeyMod.CtrlCmd | KeyCode.KEY_9] } }), 'View: Open Last Editor in Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenFirstEditorInGroup, OpenFirstEditorInGroup.ID, OpenFirstEditorInGroup.LABEL), 'View: Open First Editor in Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenNextRecentlyUsedEditorAction, OpenNextRecentlyUsedEditorAction.ID, OpenNextRecentlyUsedEditorAction.LABEL), 'View: Open Next Recently Used Editor', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenPreviousRecentlyUsedEditorAction, OpenPreviousRecentlyUsedEditorAction.ID, OpenPreviousRecentlyUsedEditorAction.LABEL), 'View: Open Previous Recently Used Editor', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ShowAllEditorsAction, ShowAllEditorsAction.ID, ShowAllEditorsAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_P), mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Tab } }), 'View: Show All Editors', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ShowEditorsInActiveGroupAction, ShowEditorsInActiveGroupAction.ID, ShowEditorsInActiveGroupAction.LABEL), 'View: Show Editors in Active Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenNextEditor, OpenNextEditor.ID, OpenNextEditor.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.PageDown, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.RightArrow, secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_CLOSE_SQUARE_BRACKET] } }), 'View: Open Next Editor', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenPreviousEditor, OpenPreviousEditor.ID, OpenPreviousEditor.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.PageUp, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.LeftArrow, secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_OPEN_SQUARE_BRACKET] } }), 'View: Open Previous Editor', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ReopenClosedEditorAction, ReopenClosedEditorAction.ID, ReopenClosedEditorAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_T }), 'View: Reopen Closed Editor', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ClearRecentFilesAction, ClearRecentFilesAction.ID, ClearRecentFilesAction.LABEL), 'File: Clear Recently Opened', nls.localize('file', "File")); +registry.registerWorkbenchAction(SyncActionDescriptor.create(CloseAllEditorsAction, CloseAllEditorsAction.ID, CloseAllEditorsAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_W) }), 'View: Close All Editors', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(CloseAllEditorGroupsAction, CloseAllEditorGroupsAction.ID, CloseAllEditorGroupsAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_W) }), 'View: Close All Editor Groups', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(CloseLeftEditorsInGroupAction, CloseLeftEditorsInGroupAction.ID, CloseLeftEditorsInGroupAction.LABEL), 'View: Close Editors to the Left in Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(CloseEditorsInOtherGroupsAction, CloseEditorsInOtherGroupsAction.ID, CloseEditorsInOtherGroupsAction.LABEL), 'View: Close Editors in Other Groups', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(CloseEditorInAllGroupsAction, CloseEditorInAllGroupsAction.ID, CloseEditorInAllGroupsAction.LABEL), 'View: Close Editor in All Groups', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(SplitEditorAction, SplitEditorAction.ID, SplitEditorAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.US_BACKSLASH }), 'View: Split Editor', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(SplitEditorOrthogonalAction, SplitEditorOrthogonalAction.ID, SplitEditorOrthogonalAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.US_BACKSLASH) }), 'View: Split Editor Orthogonal', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(SplitEditorLeftAction, SplitEditorLeftAction.ID, SplitEditorLeftAction.LABEL), 'View: Split Editor Left', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(SplitEditorRightAction, SplitEditorRightAction.ID, SplitEditorRightAction.LABEL), 'View: Split Editor Right', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(SplitEditorUpAction, SplitEditorUpAction.ID, SplitEditorUpAction.LABEL), 'Split Editor Up', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(SplitEditorDownAction, SplitEditorDownAction.ID, SplitEditorDownAction.LABEL), 'View: Split Editor Down', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(JoinTwoGroupsAction, JoinTwoGroupsAction.ID, JoinTwoGroupsAction.LABEL), 'View: Join Editor Group with Next Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(JoinAllGroupsAction, JoinAllGroupsAction.ID, JoinAllGroupsAction.LABEL), 'View: Join All Editor Groups', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(NavigateBetweenGroupsAction, NavigateBetweenGroupsAction.ID, NavigateBetweenGroupsAction.LABEL), 'View: Navigate Between Editor Groups', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ResetGroupSizesAction, ResetGroupSizesAction.ID, ResetGroupSizesAction.LABEL), 'View: Reset Editor Group Sizes', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleGroupSizesAction, ToggleGroupSizesAction.ID, ToggleGroupSizesAction.LABEL), 'View: Toggle Editor Group Sizes', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(MaximizeGroupAction, MaximizeGroupAction.ID, MaximizeGroupAction.LABEL), 'View: Maximize Editor Group and Hide Side Bar', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(MinimizeOtherGroupsAction, MinimizeOtherGroupsAction.ID, MinimizeOtherGroupsAction.LABEL), 'View: Maximize Editor Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(MoveEditorLeftInGroupAction, MoveEditorLeftInGroupAction.ID, MoveEditorLeftInGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.PageUp, mac: { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.LeftArrow) } }), 'View: Move Editor Left', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(MoveEditorRightInGroupAction, MoveEditorRightInGroupAction.ID, MoveEditorRightInGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.PageDown, mac: { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.RightArrow) } }), 'View: Move Editor Right', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(MoveGroupLeftAction, MoveGroupLeftAction.ID, MoveGroupLeftAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.LeftArrow) }), 'View: Move Editor Group Left', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(MoveGroupRightAction, MoveGroupRightAction.ID, MoveGroupRightAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.RightArrow) }), 'View: Move Editor Group Right', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(MoveGroupUpAction, MoveGroupUpAction.ID, MoveGroupUpAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.UpArrow) }), 'View: Move Editor Group Up', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(MoveGroupDownAction, MoveGroupDownAction.ID, MoveGroupDownAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.DownArrow) }), 'View: Move Editor Group Down', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(MoveEditorToPreviousGroupAction, MoveEditorToPreviousGroupAction.ID, MoveEditorToPreviousGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.LeftArrow, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.LeftArrow } }), 'View: Move Editor into Previous Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(MoveEditorToNextGroupAction, MoveEditorToNextGroupAction.ID, MoveEditorToNextGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.RightArrow, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.RightArrow } }), 'View: Move Editor into Next Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(MoveEditorToFirstGroupAction, MoveEditorToFirstGroupAction.ID, MoveEditorToFirstGroupAction.LABEL, { primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_1, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KEY_1 } }), 'View: Move Editor into First Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(MoveEditorToLastGroupAction, MoveEditorToLastGroupAction.ID, MoveEditorToLastGroupAction.LABEL, { primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_9, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KEY_9 } }), 'View: Move Editor into Last Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(MoveEditorToLeftGroupAction, MoveEditorToLeftGroupAction.ID, MoveEditorToLeftGroupAction.LABEL), 'View: Move Editor into Left Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(MoveEditorToRightGroupAction, MoveEditorToRightGroupAction.ID, MoveEditorToRightGroupAction.LABEL), 'View: Move Editor into Right Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(MoveEditorToAboveGroupAction, MoveEditorToAboveGroupAction.ID, MoveEditorToAboveGroupAction.LABEL), 'View: Move Editor into Above Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(MoveEditorToBelowGroupAction, MoveEditorToBelowGroupAction.ID, MoveEditorToBelowGroupAction.LABEL), 'View: Move Editor into Below Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(FocusActiveGroupAction, FocusActiveGroupAction.ID, FocusActiveGroupAction.LABEL), 'View: Focus Active Editor Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(FocusFirstGroupAction, FocusFirstGroupAction.ID, FocusFirstGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_1 }), 'View: Focus First Editor Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(FocusLastGroupAction, FocusLastGroupAction.ID, FocusLastGroupAction.LABEL), 'View: Focus Last Editor Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(FocusPreviousGroup, FocusPreviousGroup.ID, FocusPreviousGroup.LABEL), 'View: Focus Previous Editor Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(FocusNextGroup, FocusNextGroup.ID, FocusNextGroup.LABEL), 'View: Focus Next Editor Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(FocusLeftGroup, FocusLeftGroup.ID, FocusLeftGroup.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.LeftArrow) }), 'View: Focus Left Editor Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(FocusRightGroup, FocusRightGroup.ID, FocusRightGroup.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.RightArrow) }), 'View: Focus Right Editor Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(FocusAboveGroup, FocusAboveGroup.ID, FocusAboveGroup.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.UpArrow) }), 'View: Focus Above Editor Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(FocusBelowGroup, FocusBelowGroup.ID, FocusBelowGroup.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.DownArrow) }), 'View: Focus Below Editor Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(NewEditorGroupLeftAction, NewEditorGroupLeftAction.ID, NewEditorGroupLeftAction.LABEL), 'View: New Editor Group to the Left', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(NewEditorGroupRightAction, NewEditorGroupRightAction.ID, NewEditorGroupRightAction.LABEL), 'View: New Editor Group to the Right', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(NewEditorGroupAboveAction, NewEditorGroupAboveAction.ID, NewEditorGroupAboveAction.LABEL), 'View: New Editor Group Above', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(NewEditorGroupBelowAction, NewEditorGroupBelowAction.ID, NewEditorGroupBelowAction.LABEL), 'View: New Editor Group Below', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(NavigateForwardAction, NavigateForwardAction.ID, NavigateForwardAction.LABEL, { primary: 0, win: { primary: KeyMod.Alt | KeyCode.RightArrow }, mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.US_MINUS }, linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_MINUS } }), 'Go Forward'); +registry.registerWorkbenchAction(SyncActionDescriptor.create(NavigateBackwardsAction, NavigateBackwardsAction.ID, NavigateBackwardsAction.LABEL, { primary: 0, win: { primary: KeyMod.Alt | KeyCode.LeftArrow }, mac: { primary: KeyMod.WinCtrl | KeyCode.US_MINUS }, linux: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.US_MINUS } }), 'Go Back'); +registry.registerWorkbenchAction(SyncActionDescriptor.create(NavigateToLastEditLocationAction, NavigateToLastEditLocationAction.ID, NavigateToLastEditLocationAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_Q) }), 'Go to Last Edit Location'); +registry.registerWorkbenchAction(SyncActionDescriptor.create(NavigateLastAction, NavigateLastAction.ID, NavigateLastAction.LABEL), 'Go Last'); +registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenPreviousEditorFromHistoryAction, OpenPreviousEditorFromHistoryAction.ID, OpenPreviousEditorFromHistoryAction.LABEL), 'Open Previous Editor from History'); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ClearEditorHistoryAction, ClearEditorHistoryAction.ID, ClearEditorHistoryAction.LABEL), 'Clear Editor History'); +registry.registerWorkbenchAction(SyncActionDescriptor.create(RevertAndCloseEditorAction, RevertAndCloseEditorAction.ID, RevertAndCloseEditorAction.LABEL), 'View: Revert and Close Editor', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(EditorLayoutSingleAction, EditorLayoutSingleAction.ID, EditorLayoutSingleAction.LABEL), 'View: Single Column Editor Layout', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(EditorLayoutTwoColumnsAction, EditorLayoutTwoColumnsAction.ID, EditorLayoutTwoColumnsAction.LABEL), 'View: Two Columns Editor Layout', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(EditorLayoutThreeColumnsAction, EditorLayoutThreeColumnsAction.ID, EditorLayoutThreeColumnsAction.LABEL), 'View: Three Columns Editor Layout', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(EditorLayoutTwoRowsAction, EditorLayoutTwoRowsAction.ID, EditorLayoutTwoRowsAction.LABEL), 'View: Two Rows Editor Layout', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(EditorLayoutThreeRowsAction, EditorLayoutThreeRowsAction.ID, EditorLayoutThreeRowsAction.LABEL), 'View: Three Rows Editor Layout', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(EditorLayoutTwoByTwoGridAction, EditorLayoutTwoByTwoGridAction.ID, EditorLayoutTwoByTwoGridAction.LABEL), 'View: Grid Editor Layout (2x2)', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(EditorLayoutTwoRowsRightAction, EditorLayoutTwoRowsRightAction.ID, EditorLayoutTwoRowsRightAction.LABEL), 'View: Two Rows Right Editor Layout', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(EditorLayoutTwoColumnsBottomAction, EditorLayoutTwoColumnsBottomAction.ID, EditorLayoutTwoColumnsBottomAction.LABEL), 'View: Two Columns Bottom Editor Layout', category); // Register Editor Picker Actions including quick navigate support const openNextEditorKeybinding = { primary: KeyMod.CtrlCmd | KeyCode.Tab, mac: { primary: KeyMod.WinCtrl | KeyCode.Tab } }; const openPreviousEditorKeybinding = { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Tab, mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.Tab } }; -registry.registerWorkbenchAction(new SyncActionDescriptor(OpenNextRecentlyUsedEditorInGroupAction, OpenNextRecentlyUsedEditorInGroupAction.ID, OpenNextRecentlyUsedEditorInGroupAction.LABEL, openNextEditorKeybinding), 'View: Open Next Recently Used Editor in Group', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(OpenPreviousRecentlyUsedEditorInGroupAction, OpenPreviousRecentlyUsedEditorInGroupAction.ID, OpenPreviousRecentlyUsedEditorInGroupAction.LABEL, openPreviousEditorKeybinding), 'View: Open Previous Recently Used Editor in Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenNextRecentlyUsedEditorInGroupAction, OpenNextRecentlyUsedEditorInGroupAction.ID, OpenNextRecentlyUsedEditorInGroupAction.LABEL, openNextEditorKeybinding), 'View: Open Next Recently Used Editor in Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenPreviousRecentlyUsedEditorInGroupAction, OpenPreviousRecentlyUsedEditorInGroupAction.ID, OpenPreviousRecentlyUsedEditorInGroupAction.LABEL, openPreviousEditorKeybinding), 'View: Open Previous Recently Used Editor in Group', category); const quickOpenNavigateNextInEditorPickerId = 'workbench.action.quickOpenNavigateNextInEditorPicker'; KeybindingsRegistry.registerCommandAndKeybindingRule({ @@ -416,12 +421,12 @@ editorCommands.setup(); // Touch Bar if (isMacintosh) { MenuRegistry.appendMenuItem(MenuId.TouchBarContext, { - command: { id: NavigateBackwardsAction.ID, title: NavigateBackwardsAction.LABEL, iconLocation: { dark: URI.parse(require.toUrl('vs/workbench/browser/parts/editor/media/back-tb.png')) } }, + command: { id: NavigateBackwardsAction.ID, title: NavigateBackwardsAction.LABEL, icon: { dark: URI.parse(require.toUrl('vs/workbench/browser/parts/editor/media/back-tb.png')) } }, group: 'navigation' }); MenuRegistry.appendMenuItem(MenuId.TouchBarContext, { - command: { id: NavigateForwardAction.ID, title: NavigateForwardAction.LABEL, iconLocation: { dark: URI.parse(require.toUrl('vs/workbench/browser/parts/editor/media/forward-tb.png')) } }, + command: { id: NavigateForwardAction.ID, title: NavigateForwardAction.LABEL, icon: { dark: URI.parse(require.toUrl('vs/workbench/browser/parts/editor/media/forward-tb.png')) } }, group: 'navigation' }); } @@ -451,17 +456,15 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: editorCommands. MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: editorCommands.CLOSE_EDITORS_IN_GROUP_COMMAND_ID, title: nls.localize('closeAll', "Close All") }, group: '5_close', order: 10, when: ContextKeyExpr.has('config.workbench.editor.showTabs') }); MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: editorCommands.CLOSE_SAVED_EDITORS_COMMAND_ID, title: nls.localize('closeAllSaved', "Close Saved") }, group: '5_close', order: 20, when: ContextKeyExpr.has('config.workbench.editor.showTabs') }); -interface IEditorToolItem { id: string; title: string; iconDark: URI; iconLight: URI; } +interface IEditorToolItem { id: string; title: string; icon?: { dark?: URI; light?: URI; } | ThemeIcon; } -function appendEditorToolItem(primary: IEditorToolItem, when: ContextKeyExpr | undefined, order: number, alternative?: IEditorToolItem): void { +function appendEditorToolItem(primary: IEditorToolItem, when: ContextKeyExpr | undefined, order: number, alternative?: IEditorToolItem, precondition?: ContextKeyExpr | undefined): void { const item: IMenuItem = { command: { id: primary.id, title: primary.title, - iconLocation: { - dark: primary.iconDark, - light: primary.iconLight - } + icon: primary.icon, + precondition }, group: 'navigation', when, @@ -472,36 +475,26 @@ function appendEditorToolItem(primary: IEditorToolItem, when: ContextKeyExpr | u item.alt = { id: alternative.id, title: alternative.title, - iconLocation: { - dark: alternative.iconDark, - light: alternative.iconLight - } + icon: alternative.icon }; } MenuRegistry.appendMenuItem(MenuId.EditorTitle, item); } -const SPLIT_EDITOR_HORIZONTAL_DARK_ICON = URI.parse(registerAndGetAmdImageURL('vs/workbench/browser/parts/editor/media/split-editor-horizontal-dark.svg')); -const SPLIT_EDITOR_HORIZONTAL_LIGHT_ICON = URI.parse(registerAndGetAmdImageURL('vs/workbench/browser/parts/editor/media/split-editor-horizontal-light.svg')); -const SPLIT_EDITOR_VERTICAL_DARK_ICON = URI.parse(registerAndGetAmdImageURL('vs/workbench/browser/parts/editor/media/split-editor-vertical-dark.svg')); -const SPLIT_EDITOR_VERTICAL_LIGHT_ICON = URI.parse(registerAndGetAmdImageURL('vs/workbench/browser/parts/editor/media/split-editor-vertical-light.svg')); - // Editor Title Menu: Split Editor appendEditorToolItem( { id: SplitEditorAction.ID, title: nls.localize('splitEditorRight', "Split Editor Right"), - iconDark: SPLIT_EDITOR_HORIZONTAL_DARK_ICON, - iconLight: SPLIT_EDITOR_HORIZONTAL_LIGHT_ICON + icon: { id: 'codicon/split-horizontal' } }, ContextKeyExpr.not('splitEditorsVertically'), 100000, // towards the end { id: editorCommands.SPLIT_EDITOR_DOWN, title: nls.localize('splitEditorDown', "Split Editor Down"), - iconDark: SPLIT_EDITOR_VERTICAL_DARK_ICON, - iconLight: SPLIT_EDITOR_VERTICAL_LIGHT_ICON + icon: { id: 'codicon/split-vertical' } } ); @@ -509,37 +502,30 @@ appendEditorToolItem( { id: SplitEditorAction.ID, title: nls.localize('splitEditorDown', "Split Editor Down"), - iconDark: SPLIT_EDITOR_VERTICAL_DARK_ICON, - iconLight: SPLIT_EDITOR_VERTICAL_LIGHT_ICON + icon: { id: 'codicon/split-vertical' } }, ContextKeyExpr.has('splitEditorsVertically'), 100000, // towards the end { id: editorCommands.SPLIT_EDITOR_RIGHT, title: nls.localize('splitEditorRight', "Split Editor Right"), - iconDark: SPLIT_EDITOR_HORIZONTAL_DARK_ICON, - iconLight: SPLIT_EDITOR_HORIZONTAL_LIGHT_ICON + icon: { id: 'codicon/split-horizontal' } } ); -const CLOSE_ALL_DARK_ICON = URI.parse(registerAndGetAmdImageURL('vs/workbench/browser/parts/editor/media/close-all-dark.svg')); -const CLOSE_ALL_LIGHT_ICON = URI.parse(registerAndGetAmdImageURL('vs/workbench/browser/parts/editor/media/close-all-light.svg')); - // Editor Title Menu: Close Group (tabs disabled) appendEditorToolItem( { id: editorCommands.CLOSE_EDITOR_COMMAND_ID, title: nls.localize('close', "Close"), - iconDark: URI.parse(registerAndGetAmdImageURL('vs/workbench/browser/parts/editor/media/close-dark-alt.svg')), - iconLight: URI.parse(registerAndGetAmdImageURL('vs/workbench/browser/parts/editor/media/close-light-alt.svg')) + icon: { id: 'codicon/close' } }, ContextKeyExpr.and(ContextKeyExpr.not('config.workbench.editor.showTabs'), ContextKeyExpr.not('groupActiveEditorDirty')), 1000000, // towards the far end { id: editorCommands.CLOSE_EDITORS_IN_GROUP_COMMAND_ID, title: nls.localize('closeAll', "Close All"), - iconDark: CLOSE_ALL_DARK_ICON, - iconLight: CLOSE_ALL_LIGHT_ICON + icon: { id: 'codicon/close-all' } } ); @@ -547,16 +533,14 @@ appendEditorToolItem( { id: editorCommands.CLOSE_EDITOR_COMMAND_ID, title: nls.localize('close', "Close"), - iconDark: URI.parse(registerAndGetAmdImageURL('vs/workbench/browser/parts/editor/media/close-dirty-dark-alt.svg')), - iconLight: URI.parse(registerAndGetAmdImageURL('vs/workbench/browser/parts/editor/media/close-dirty-light-alt.svg')) + icon: { id: 'codicon/close-dirty' } }, ContextKeyExpr.and(ContextKeyExpr.not('config.workbench.editor.showTabs'), ContextKeyExpr.has('groupActiveEditorDirty')), 1000000, // towards the far end { id: editorCommands.CLOSE_EDITORS_IN_GROUP_COMMAND_ID, title: nls.localize('closeAll', "Close All"), - iconDark: CLOSE_ALL_DARK_ICON, - iconLight: CLOSE_ALL_LIGHT_ICON + icon: { id: 'codicon/close-all' } } ); @@ -565,8 +549,7 @@ appendEditorToolItem( { id: editorCommands.GOTO_PREVIOUS_CHANGE, title: nls.localize('navigate.prev.label', "Previous Change"), - iconDark: URI.parse(registerAndGetAmdImageURL('vs/workbench/browser/parts/editor/media/previous-diff-dark.svg')), - iconLight: URI.parse(registerAndGetAmdImageURL('vs/workbench/browser/parts/editor/media/previous-diff-light.svg')) + icon: { id: 'codicon/arrow-up' } }, TextCompareEditorActiveContext, 10 @@ -577,8 +560,7 @@ appendEditorToolItem( { id: editorCommands.GOTO_NEXT_CHANGE, title: nls.localize('navigate.next.label', "Next Change"), - iconDark: URI.parse(registerAndGetAmdImageURL('vs/workbench/browser/parts/editor/media/next-diff-dark.svg')), - iconLight: URI.parse(registerAndGetAmdImageURL('vs/workbench/browser/parts/editor/media/next-diff-light.svg')) + icon: { id: 'codicon/arrow-down' } }, TextCompareEditorActiveContext, 11 @@ -589,8 +571,7 @@ appendEditorToolItem( { id: editorCommands.TOGGLE_DIFF_IGNORE_TRIM_WHITESPACE, title: nls.localize('ignoreTrimWhitespace.label', "Ignore Leading/Trailing Whitespace Differences"), - iconDark: URI.parse(registerAndGetAmdImageURL('vs/workbench/browser/parts/editor/media/paragraph-dark.svg')), - iconLight: URI.parse(registerAndGetAmdImageURL('vs/workbench/browser/parts/editor/media/paragraph-light.svg')) + icon: { id: 'codicon/whitespace' } }, ContextKeyExpr.and(TextCompareEditorActiveContext, ContextKeyExpr.notEquals('config.diffEditor.ignoreTrimWhitespace', true)), 20 @@ -601,8 +582,7 @@ appendEditorToolItem( { id: editorCommands.TOGGLE_DIFF_IGNORE_TRIM_WHITESPACE, title: nls.localize('showTrimWhitespace.label', "Show Leading/Trailing Whitespace Differences"), - iconDark: URI.parse(registerAndGetAmdImageURL('vs/workbench/browser/parts/editor/media/paragraph-disabled-dark.svg')), - iconLight: URI.parse(registerAndGetAmdImageURL('vs/workbench/browser/parts/editor/media/paragraph-disabled-light.svg')) + icon: { id: 'codicon/whitespace~disabled' } }, ContextKeyExpr.and(TextCompareEditorActiveContext, ContextKeyExpr.notEquals('config.diffEditor.ignoreTrimWhitespace', false)), 20 diff --git a/src/vs/workbench/browser/parts/editor/editor.ts b/src/vs/workbench/browser/parts/editor/editor.ts index 6bd5a275e271..9823fada244b 100644 --- a/src/vs/workbench/browser/parts/editor/editor.ts +++ b/src/vs/workbench/browser/parts/editor/editor.ts @@ -5,7 +5,7 @@ import { GroupIdentifier, IWorkbenchEditorConfiguration, EditorOptions, TextEditorOptions, IEditorInput, IEditorIdentifier, IEditorCloseEvent, IEditor, IEditorPartOptions } from 'vs/workbench/common/editor'; import { EditorGroup } from 'vs/workbench/common/editor/editorGroup'; -import { IEditorGroup, GroupDirection, IAddGroupOptions, IMergeGroupOptions, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorGroup, GroupDirection, IAddGroupOptions, IMergeGroupOptions, GroupsOrder, GroupsArrangement } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IDisposable } from 'vs/base/common/lifecycle'; import { Dimension } from 'vs/base/browser/dom'; import { Event } from 'vs/base/common/event'; @@ -104,6 +104,8 @@ export interface IEditorGroupsAccessor { copyGroup(group: IEditorGroupView | GroupIdentifier, location: IEditorGroupView | GroupIdentifier, direction: GroupDirection): IEditorGroupView; removeGroup(group: IEditorGroupView | GroupIdentifier): void; + + arrangeGroups(arrangement: GroupsArrangement, target?: IEditorGroupView | GroupIdentifier): void; } export interface IEditorGroupView extends IDisposable, ISerializableView, IEditorGroup { diff --git a/src/vs/workbench/browser/parts/editor/editorActions.ts b/src/vs/workbench/browser/parts/editor/editorActions.ts index 7d3f4e0f6d95..200e87d96967 100644 --- a/src/vs/workbench/browser/parts/editor/editorActions.ts +++ b/src/vs/workbench/browser/parts/editor/editorActions.ts @@ -6,7 +6,7 @@ import * as nls from 'vs/nls'; import { Action } from 'vs/base/common/actions'; import { mixin } from 'vs/base/common/objects'; -import { IEditorInput, EditorInput, IEditorIdentifier, ConfirmResult, IEditorCommandsContext, CloseDirection } from 'vs/workbench/common/editor'; +import { IEditorInput, EditorInput, IEditorIdentifier, IEditorCommandsContext, CloseDirection, SaveReason } from 'vs/workbench/common/editor'; import { QuickOpenEntryGroup } from 'vs/base/parts/quickopen/browser/quickOpenModel'; import { EditorQuickOpenEntry, EditorQuickOpenEntryGroup, IEditorQuickOpenEntry, QuickOpenAction } from 'vs/workbench/browser/quickopen'; import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; @@ -15,13 +15,15 @@ import { IResourceInput } from 'vs/platform/editor/common/editor'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { CLOSE_EDITOR_COMMAND_ID, NAVIGATE_ALL_EDITORS_GROUP_PREFIX, MOVE_ACTIVE_EDITOR_COMMAND_ID, NAVIGATE_IN_ACTIVE_GROUP_PREFIX, ActiveEditorMoveArguments, SPLIT_EDITOR_LEFT, SPLIT_EDITOR_RIGHT, SPLIT_EDITOR_UP, SPLIT_EDITOR_DOWN, splitEditor, LAYOUT_EDITOR_GROUPS_COMMAND_ID, mergeAllGroups } from 'vs/workbench/browser/parts/editor/editorCommands'; import { IEditorGroupsService, IEditorGroup, GroupsArrangement, EditorsOrder, GroupLocation, GroupDirection, preferredSideBySideGroupDirection, IFindGroupScope, GroupOrientation, EditorGroupLayout, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; +import { IFileDialogService, ConfirmResult } from 'vs/platform/dialogs/common/dialogs'; +import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { ResourceMap, values } from 'vs/base/common/map'; export class ExecuteCommandAction extends Action { @@ -416,7 +418,7 @@ export class OpenToSideFromQuickOpenAction extends Action { updateClass(): void { const preferredDirection = preferredSideBySideGroupDirection(this.configurationService); - this.class = (preferredDirection === GroupDirection.RIGHT) ? 'quick-open-sidebyside-vertical' : 'quick-open-sidebyside-horizontal'; + this.class = (preferredDirection === GroupDirection.RIGHT) ? 'codicon-split-horizontal' : 'codicon-split-vertical'; } run(context: any): Promise { @@ -425,7 +427,7 @@ export class OpenToSideFromQuickOpenAction extends Action { const input = entry.getInput(); if (input) { if (input instanceof EditorInput) { - return this.editorService.openEditor(input, entry.getOptions() || undefined, SIDE_GROUP); + return this.editorService.openEditor(input, entry.getOptions(), SIDE_GROUP); } const resourceInput = input as IResourceInput; @@ -505,7 +507,7 @@ export class CloseOneEditorAction extends Action { // Close specific editor in group if (typeof editorIndex === 'number') { - const editorAtIndex = group.getEditor(editorIndex); + const editorAtIndex = group.getEditorByIndex(editorIndex); if (editorAtIndex) { return group.closeEditor(editorAtIndex); } @@ -596,8 +598,10 @@ export abstract class BaseCloseAllAction extends Action { id: string, label: string, clazz: string | undefined, - private textFileService: ITextFileService, - protected editorGroupService: IEditorGroupsService + private workingCopyService: IWorkingCopyService, + private fileDialogService: IFileDialogService, + protected editorGroupService: IEditorGroupsService, + private editorService: IEditorService ) { super(id, label, clazz); } @@ -619,7 +623,7 @@ export abstract class BaseCloseAllAction extends Action { async run(): Promise { // Just close all if there are no dirty editors - if (!this.textFileService.isDirty()) { + if (!this.workingCopyService.hasDirty) { return this.doCloseAll(); } @@ -636,18 +640,32 @@ export abstract class BaseCloseAllAction extends Action { return undefined; })); - const confirm = await this.textFileService.confirmSave(); + const dirtyEditorsToConfirmByName = new Set(); + const dirtyEditorsToConfirmByResource = new ResourceMap(); + + for (const editor of this.editorService.editors) { + if (!editor.isDirty()) { + continue; // only interested in dirty editors + } + + const resource = editor.getResource(); + if (resource) { + dirtyEditorsToConfirmByResource.set(resource, true); + } else { + dirtyEditorsToConfirmByName.add(editor.getName()); + } + } + + const confirm = await this.fileDialogService.showSaveConfirm([...dirtyEditorsToConfirmByResource.keys(), ...values(dirtyEditorsToConfirmByName)]); if (confirm === ConfirmResult.CANCEL) { return; } let saveOrRevert: boolean; if (confirm === ConfirmResult.DONT_SAVE) { - await this.textFileService.revertAll(undefined, { soft: true }); - saveOrRevert = true; + saveOrRevert = await this.editorService.revertAll({ soft: true, includeUntitled: true }); } else { - const res = await this.textFileService.saveAll(true); - saveOrRevert = res.results.every(r => !!r.success); + saveOrRevert = await this.editorService.saveAll({ reason: SaveReason.EXPLICIT, includeUntitled: true }); } if (saveOrRevert) { @@ -666,10 +684,12 @@ export class CloseAllEditorsAction extends BaseCloseAllAction { constructor( id: string, label: string, - @ITextFileService textFileService: ITextFileService, - @IEditorGroupsService editorGroupService: IEditorGroupsService + @IWorkingCopyService workingCopyService: IWorkingCopyService, + @IFileDialogService fileDialogService: IFileDialogService, + @IEditorGroupsService editorGroupService: IEditorGroupsService, + @IEditorService editorService: IEditorService ) { - super(id, label, 'codicon-close-all', textFileService, editorGroupService); + super(id, label, 'codicon-close-all', workingCopyService, fileDialogService, editorGroupService, editorService); } protected doCloseAll(): Promise { @@ -685,10 +705,12 @@ export class CloseAllEditorGroupsAction extends BaseCloseAllAction { constructor( id: string, label: string, - @ITextFileService textFileService: ITextFileService, - @IEditorGroupsService editorGroupService: IEditorGroupsService + @IWorkingCopyService workingCopyService: IWorkingCopyService, + @IFileDialogService fileDialogService: IFileDialogService, + @IEditorGroupsService editorGroupService: IEditorGroupsService, + @IEditorService editorService: IEditorService ) { - super(id, label, undefined, textFileService, editorGroupService); + super(id, label, undefined, workingCopyService, fileDialogService, editorGroupService, editorService); } protected async doCloseAll(): Promise { @@ -1274,8 +1296,6 @@ export class BaseQuickOpenEditorInGroupAction extends Action { run(): Promise { const keys = this.keybindingService.lookupKeybindings(this.id); - - this.quickOpenService.show(NAVIGATE_IN_ACTIVE_GROUP_PREFIX, { quickNavigateConfiguration: { keybindings: keys } }); return Promise.resolve(true); diff --git a/src/vs/workbench/browser/parts/editor/editorAutoSave.ts b/src/vs/workbench/browser/parts/editor/editorAutoSave.ts new file mode 100644 index 000000000000..65ba4b9979e1 --- /dev/null +++ b/src/vs/workbench/browser/parts/editor/editorAutoSave.ts @@ -0,0 +1,86 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { IFilesConfigurationService, AutoSaveMode } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; +import { SaveReason, IEditorIdentifier, IEditorInput, GroupIdentifier } from 'vs/workbench/common/editor'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { withNullAsUndefined } from 'vs/base/common/types'; + +export class EditorAutoSave extends Disposable implements IWorkbenchContribution { + + private lastActiveEditor: IEditorInput | undefined = undefined; + private lastActiveGroupId: GroupIdentifier | undefined = undefined; + private lastActiveEditorControlDisposable = this._register(new DisposableStore()); + + constructor( + @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService, + @IHostService private readonly hostService: IHostService, + @IEditorService private readonly editorService: IEditorService, + @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService + ) { + super(); + + this.registerListeners(); + } + + private registerListeners(): void { + this._register(this.hostService.onDidChangeFocus(focused => this.onWindowFocusChange(focused))); + this._register(this.editorService.onDidActiveEditorChange(() => this.onDidActiveEditorChange())); + } + + private onWindowFocusChange(focused: boolean): void { + if (!focused) { + this.maybeTriggerAutoSave(SaveReason.WINDOW_CHANGE); + } + } + + private onDidActiveEditorChange(): void { + + // Treat editor change like a focus change for our last active editor if any + if (this.lastActiveEditor && typeof this.lastActiveGroupId === 'number') { + this.maybeTriggerAutoSave(SaveReason.FOCUS_CHANGE, { groupId: this.lastActiveGroupId, editor: this.lastActiveEditor }); + } + + // Remember as last active + const activeGroup = this.editorGroupService.activeGroup; + const activeEditor = this.lastActiveEditor = withNullAsUndefined(activeGroup.activeEditor); + this.lastActiveGroupId = activeGroup.id; + + // Dispose previous active control listeners + this.lastActiveEditorControlDisposable.clear(); + + // Listen to focus changes on control for auto save + const activeEditorControl = this.editorService.activeControl; + if (activeEditor && activeEditorControl) { + this.lastActiveEditorControlDisposable.add(activeEditorControl.onDidBlur(() => { + this.maybeTriggerAutoSave(SaveReason.FOCUS_CHANGE, { groupId: activeGroup.id, editor: activeEditor }); + })); + } + } + + private maybeTriggerAutoSave(reason: SaveReason, editorIdentifier?: IEditorIdentifier): void { + if (editorIdentifier && (editorIdentifier.editor.isReadonly() || editorIdentifier.editor.isUntitled())) { + return; // no auto save for readonly or untitled editors + } + + // Determine if we need to save all. In case of a window focus change we also save if  + // auto save mode is configured to be ON_FOCUS_CHANGE (editor focus change) + const mode = this.filesConfigurationService.getAutoSaveMode(); + if ( + (reason === SaveReason.WINDOW_CHANGE && (mode === AutoSaveMode.ON_FOCUS_CHANGE || mode === AutoSaveMode.ON_WINDOW_CHANGE)) || + (reason === SaveReason.FOCUS_CHANGE && mode === AutoSaveMode.ON_FOCUS_CHANGE) + ) { + if (editorIdentifier) { + this.editorService.save(editorIdentifier, { reason }); + } else { + this.editorService.saveAll({ reason }); + } + } + } +} diff --git a/src/vs/workbench/browser/parts/editor/editorCommands.ts b/src/vs/workbench/browser/parts/editor/editorCommands.ts index 70736c4ceec4..7378ebe74df8 100644 --- a/src/vs/workbench/browser/parts/editor/editorCommands.ts +++ b/src/vs/workbench/browser/parts/editor/editorCommands.ts @@ -318,7 +318,7 @@ function registerOpenEditorAtIndexCommands(): void { const editorService = accessor.get(IEditorService); const activeControl = editorService.activeControl; if (activeControl) { - const editor = activeControl.group.getEditor(editorIndex); + const editor = activeControl.group.getEditorByIndex(editorIndex); if (editor) { editorService.openEditor(editor); } @@ -448,7 +448,7 @@ export function splitEditor(editorGroupService: IEditorGroupsService, direction: // Split editor (if it can be split) let editorToCopy: IEditorInput | undefined; if (context && typeof context.editorIndex === 'number') { - editorToCopy = sourceGroup.getEditor(context.editorIndex); + editorToCopy = sourceGroup.getEditorByIndex(context.editorIndex); } else { editorToCopy = types.withNullAsUndefined(sourceGroup.activeEditor); } @@ -548,7 +548,7 @@ function registerCloseEditorCommands() { if (group) { const editors = coalesce(contexts .filter(context => context.groupId === groupId) - .map(context => typeof context.editorIndex === 'number' ? group.getEditor(context.editorIndex) : group.activeEditor)); + .map(context => typeof context.editorIndex === 'number' ? group.getEditorByIndex(context.editorIndex) : group.activeEditor)); return group.closeEditors(editors); } @@ -603,7 +603,7 @@ function registerCloseEditorCommands() { if (group) { const editors = contexts .filter(context => context.groupId === groupId) - .map(context => typeof context.editorIndex === 'number' ? group.getEditor(context.editorIndex) : group.activeEditor); + .map(context => typeof context.editorIndex === 'number' ? group.getEditorByIndex(context.editorIndex) : group.activeEditor); const editorsToClose = group.editors.filter(e => editors.indexOf(e) === -1); if (group.activeEditor) { @@ -715,7 +715,7 @@ function resolveCommandsContext(editorGroupService: IEditorGroupsService, contex // Resolve from context let group = context && typeof context.groupId === 'number' ? editorGroupService.getGroup(context.groupId) : undefined; - let editor = group && context && typeof context.editorIndex === 'number' ? types.withNullAsUndefined(group.getEditor(context.editorIndex)) : undefined; + let editor = group && context && typeof context.editorIndex === 'number' ? types.withNullAsUndefined(group.getEditorByIndex(context.editorIndex)) : undefined; let control = group ? group.activeControl : undefined; // Fallback to active group as needed diff --git a/src/vs/workbench/browser/parts/editor/editorControl.ts b/src/vs/workbench/browser/parts/editor/editorControl.ts index ce8fd544360e..daa1290452c4 100644 --- a/src/vs/workbench/browser/parts/editor/editorControl.ts +++ b/src/vs/workbench/browser/parts/editor/editorControl.ts @@ -208,6 +208,7 @@ export class EditorControl extends Disposable { if (controlInstanceContainer) { this.parent.removeChild(controlInstanceContainer); hide(controlInstanceContainer); + this._activeControl.onHide(); } // Indicate to editor control diff --git a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts index 183f21d82218..5e8782e9bbdc 100644 --- a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts +++ b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts @@ -4,19 +4,20 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/editordroptarget'; -import { LocalSelectionTransfer, DraggedEditorIdentifier, ResourcesDropHandler, DraggedEditorGroupIdentifier, DragAndDropObserver } from 'vs/workbench/browser/dnd'; +import { LocalSelectionTransfer, DraggedEditorIdentifier, ResourcesDropHandler, DraggedEditorGroupIdentifier, DragAndDropObserver, containsDragType } from 'vs/workbench/browser/dnd'; import { addDisposableListener, EventType, EventHelper, isAncestor, toggleClass, addClass, removeClass } from 'vs/base/browser/dom'; import { IEditorGroupsAccessor, EDITOR_TITLE_HEIGHT, IEditorGroupView, getActiveTextEditorOptions } from 'vs/workbench/browser/parts/editor/editor'; import { EDITOR_DRAG_AND_DROP_BACKGROUND, Themable } from 'vs/workbench/common/theme'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { activeContrastBorder } from 'vs/platform/theme/common/colorRegistry'; import { IEditorIdentifier, EditorInput, EditorOptions } from 'vs/workbench/common/editor'; -import { isMacintosh } from 'vs/base/common/platform'; +import { isMacintosh, isWeb } from 'vs/base/common/platform'; import { GroupDirection, MergeGroupMode } from 'vs/workbench/services/editor/common/editorGroupsService'; import { toDisposable } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { RunOnceScheduler } from 'vs/base/common/async'; import { find } from 'vs/base/common/arrays'; +import { DataTransfers } from 'vs/base/browser/dnd'; interface IDropOperation { splitDirection?: GroupDirection; @@ -99,6 +100,10 @@ class DropOverlay extends Themable { this._register(new DragAndDropObserver(this.container, { onDragEnter: e => undefined, onDragOver: e => { + if (isWeb && containsDragType(e, DataTransfers.FILES)) { + return; // dropping files into editor is unsupported on web + } + const isDraggingGroup = this.groupTransfer.hasData(DraggedEditorGroupIdentifier.prototype); const isDraggingEditor = this.editorTransfer.hasData(DraggedEditorIdentifier.prototype); diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index 0ca8166512a9..f6f62a016ba4 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -6,7 +6,7 @@ import 'vs/css!./media/editorgroupview'; import { EditorGroup, IEditorOpenOptions, EditorCloseEvent, ISerializedEditorGroup, isSerializedEditorGroup } from 'vs/workbench/common/editor/editorGroup'; -import { EditorInput, EditorOptions, GroupIdentifier, ConfirmResult, SideBySideEditorInput, CloseDirection, IEditorCloseEvent, EditorGroupActiveEditorDirtyContext, IEditor, EditorGroupEditorsCountContext } from 'vs/workbench/common/editor'; +import { EditorInput, EditorOptions, GroupIdentifier, SideBySideEditorInput, CloseDirection, IEditorCloseEvent, EditorGroupActiveEditorDirtyContext, IEditor, EditorGroupEditorsCountContext, toResource, SideBySideEditor, SaveReason } from 'vs/workbench/common/editor'; import { Event, Emitter, Relay } from 'vs/base/common/event'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { addClass, addClasses, Dimension, trackFocus, toggleClass, removeClass, addDisposableListener, EventType, EventHelper, findParentWithClass, clearNode, isAncestor } from 'vs/base/browser/dom'; @@ -21,7 +21,7 @@ import { IMoveEditorOptions, ICopyEditorOptions, ICloseEditorsFilter, IGroupChan import { TabsTitleControl } from 'vs/workbench/browser/parts/editor/tabsTitleControl'; import { EditorControl } from 'vs/workbench/browser/parts/editor/editorControl'; import { IEditorProgressService } from 'vs/platform/progress/common/progress'; -import { EditorProgressService } from 'vs/workbench/services/progress/browser/editorProgressService'; +import { EditorProgressIndicator } from 'vs/workbench/services/progress/browser/progressIndicator'; import { localize } from 'vs/nls'; import { isPromiseCanceledError } from 'vs/base/common/errors'; import { dispose, MutableDisposable } from 'vs/base/common/lifecycle'; @@ -32,7 +32,7 @@ import { RunOnceWorker } from 'vs/base/common/async'; import { EventType as TouchEventType, GestureEvent } from 'vs/base/browser/touch'; import { TitleControl } from 'vs/workbench/browser/parts/editor/titleControl'; import { IEditorGroupsAccessor, IEditorGroupView, IEditorPartOptionsChangeEvent, getActiveTextEditorOptions, IEditorOpeningEvent } from 'vs/workbench/browser/parts/editor/editor'; -import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; +import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ActionRunner, IAction, Action } from 'vs/base/common/actions'; @@ -50,7 +50,8 @@ import { guessMimeTypes } from 'vs/base/common/mime'; import { extname } from 'vs/base/common/resources'; import { Schemas } from 'vs/base/common/network'; import { EditorActivation, EditorOpenContext } from 'vs/platform/editor/common/editor'; -import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IDialogService, IFileDialogService, ConfirmResult } from 'vs/platform/dialogs/common/dialogs'; +import { ILogService } from 'vs/platform/log/common/log'; export class EditorGroupView extends Themable implements IEditorGroupView { @@ -128,10 +129,12 @@ export class EditorGroupView extends Themable implements IEditorGroupView { @INotificationService private readonly notificationService: INotificationService, @IDialogService private readonly dialogService: IDialogService, @ITelemetryService private readonly telemetryService: ITelemetryService, - @IUntitledEditorService private readonly untitledEditorService: IUntitledEditorService, + @IUntitledTextEditorService private readonly untitledTextEditorService: IUntitledTextEditorService, @IKeybindingService private readonly keybindingService: IKeybindingService, @IMenuService private readonly menuService: IMenuService, - @IContextMenuService private readonly contextMenuService: IContextMenuService + @IContextMenuService private readonly contextMenuService: IContextMenuService, + @IFileDialogService private readonly fileDialogService: IFileDialogService, + @ILogService private readonly logService: ILogService ) { super(themeService); @@ -173,7 +176,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { const scopedContextKeyService = this._register(this.contextKeyService.createScoped(this.element)); this.scopedInstantiationService = this.instantiationService.createChild(new ServiceCollection( [IContextKeyService, scopedContextKeyService], - [IEditorProgressService, new EditorProgressService(this.progressBar)] + [IEditorProgressService, this._register(new EditorProgressIndicator(this.progressBar, this))] )); // Context keys @@ -255,7 +258,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { if (this.isEmpty) { EventHelper.stop(e); - this.openEditor(this.untitledEditorService.createOrGet(), EditorOptions.create({ pinned: true })); + this.openEditor(this.untitledTextEditorService.createOrGet(), EditorOptions.create({ pinned: true })); } })); @@ -517,11 +520,10 @@ export class EditorGroupView extends Themable implements IEditorGroupView { editorsToClose.push(editor.master, editor.details); } - // Close the editor when it is no longer open in any group including diff editors + // Dispose the editor when it is no longer open in any group including diff editors editorsToClose.forEach(editorToClose => { - const resource = editorToClose ? editorToClose.getResource() : undefined; // prefer resource to not close right-hand side editors of a diff editor - if (!this.accessor.groups.some(groupView => groupView.group.contains(resource || editorToClose))) { - editorToClose.close(); + if (!this.accessor.groups.some(groupView => groupView.group.contains(editorToClose, true /* include side by side editor master & details */))) { + editorToClose.dispose(); } }); @@ -760,8 +762,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView { return this.editors; } - getEditor(index: number): EditorInput | undefined { - return this._group.getEditor(index); + getEditorByIndex(index: number): EditorInput | undefined { + return this._group.getEditorByIndex(index); } getIndexOfEditor(editor: EditorInput): number { @@ -922,64 +924,74 @@ export class EditorGroupView extends Themable implements IEditorGroupView { private async doHandleOpenEditorError(error: Error, editor: EditorInput, options?: EditorOptions): Promise { - // Report error only if this was not us restoring previous error state or - // we are told to ignore errors that occur from opening an editor - if (this.isRestored && !isPromiseCanceledError(error) && (!options || !options.ignoreError)) { + // Report error only if we are not told to ignore errors that occur from opening an editor + if (!isPromiseCanceledError(error) && (!options || !options.ignoreError)) { - // Extract possible error actions from the error - let errorActions: ReadonlyArray | undefined = undefined; - if (isErrorWithActions(error)) { - errorActions = (error as IErrorWithActions).actions; - } + // Since it is more likely that errors fail to open when restoring them e.g. + // because files got deleted or moved meanwhile, we do not show any notifications + // if we are still restoring editors. + if (this.isRestored) { - // If the context is USER, we try to show a modal dialog instead of a background notification - if (options?.context === EditorOpenContext.USER) { - const buttons: string[] = []; - if (Array.isArray(errorActions) && errorActions.length > 0) { - errorActions.forEach(action => buttons.push(action.label)); - } else { - buttons.push(localize('ok', 'OK')); + // Extract possible error actions from the error + let errorActions: ReadonlyArray | undefined = undefined; + if (isErrorWithActions(error)) { + errorActions = (error as IErrorWithActions).actions; } - let cancelId: number | undefined = undefined; - if (buttons.length === 1) { - buttons.push(localize('cancel', "Cancel")); - cancelId = 1; - } + // If the context is USER, we try to show a modal dialog instead of a background notification + if (options?.context === EditorOpenContext.USER) { + const buttons: string[] = []; + if (Array.isArray(errorActions) && errorActions.length > 0) { + errorActions.forEach(action => buttons.push(action.label)); + } else { + buttons.push(localize('ok', 'OK')); + } - const result = await this.dialogService.show( - Severity.Error, - localize('editorOpenErrorDialog', "Unable to open '{0}'", editor.getName()), - buttons, - { - detail: toErrorMessage(error), - cancelId + let cancelId: number | undefined = undefined; + if (buttons.length === 1) { + buttons.push(localize('cancel', "Cancel")); + cancelId = 1; } - ); - // Make sure to run any error action if present - if (result.choice !== cancelId && Array.isArray(errorActions)) { - const errorAction = errorActions[result.choice]; - if (errorAction) { - errorAction.run(); + const result = await this.dialogService.show( + Severity.Error, + localize('editorOpenErrorDialog', "Unable to open '{0}'", editor.getName()), + buttons, + { + detail: toErrorMessage(error), + cancelId + } + ); + + // Make sure to run any error action if present + if (result.choice !== cancelId && Array.isArray(errorActions)) { + const errorAction = errorActions[result.choice]; + if (errorAction) { + errorAction.run(); + } } } - } - // Otherwise, show a background notification. - else { - const actions: INotificationActions = { primary: [] }; - if (Array.isArray(errorActions)) { - actions.primary = errorActions; - } + // Otherwise, show a background notification. + else { + const actions: INotificationActions = { primary: [] }; + if (Array.isArray(errorActions)) { + actions.primary = errorActions; + } + + const handle = this.notificationService.notify({ + severity: Severity.Error, + message: localize('editorOpenError', "Unable to open '{0}': {1}.", editor.getName(), toErrorMessage(error)), + actions + }); - const handle = this.notificationService.notify({ - severity: Severity.Error, - message: localize('editorOpenError', "Unable to open '{0}': {1}.", editor.getName(), toErrorMessage(error)), - actions - }); + Event.once(handle.onDidClose)(() => actions.primary && dispose(actions.primary)); + } + } - Event.once(handle.onDidClose)(() => actions.primary && dispose(actions.primary)); + // Restoring: just log errors to console + else { + this.logService.error(error); } } @@ -1110,7 +1122,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { } // Check for dirty and veto - const veto = await this.handleDirty([editor]); + const veto = await this.handleDirtyClosing([editor]); if (veto) { return; } @@ -1221,7 +1233,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this._group.closeEditor(editor); } - private async handleDirty(editors: EditorInput[]): Promise { + private async handleDirtyClosing(editors: EditorInput[]): Promise { if (!editors.length) { return false; // no veto } @@ -1230,13 +1242,13 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // To prevent multiple confirmation dialogs from showing up one after the other // we check if a pending confirmation is currently showing and if so, join that - let handleDirtyPromise = this.mapEditorToPendingConfirmation.get(editor); - if (!handleDirtyPromise) { - handleDirtyPromise = this.doHandleDirty(editor); - this.mapEditorToPendingConfirmation.set(editor, handleDirtyPromise); + let handleDirtyClosingPromise = this.mapEditorToPendingConfirmation.get(editor); + if (!handleDirtyClosingPromise) { + handleDirtyClosingPromise = this.doHandleDirtyClosing(editor); + this.mapEditorToPendingConfirmation.set(editor, handleDirtyClosingPromise); } - const veto = await handleDirtyPromise; + const veto = await handleDirtyClosingPromise; // Make sure to remove from our map of cached pending confirmations this.mapEditorToPendingConfirmation.delete(editor); @@ -1247,22 +1259,47 @@ export class EditorGroupView extends Themable implements IEditorGroupView { } // Otherwise continue with the remainders - return this.handleDirty(editors); + return this.handleDirtyClosing(editors); } - private async doHandleDirty(editor: EditorInput): Promise { - if ( - !editor.isDirty() || // editor must be dirty - this.accessor.groups.some(groupView => groupView !== this && groupView.group.contains(editor, true /* support side by side */)) || // editor is opened in other group - editor instanceof SideBySideEditorInput && this.isOpened(editor.master) // side by side editor master is still opened - ) { + private async doHandleDirtyClosing(editor: EditorInput): Promise { + if (!editor.isDirty()) { + return false; // editor must be dirty + } + + if (editor instanceof SideBySideEditorInput && this.isOpened(editor.master)) { + return false; // master-side of editor is still opened somewhere else + } + + // Note: we explicitly decide to ask for confirm if closing a normal editor even + // if it is opened in a side-by-side editor in the group. This decision is made + // because it may be less obvious that one side of a side by side editor is dirty + // and can still be changed. + + if (this.accessor.groups.some(groupView => { + if (groupView === this) { + return false; // skip this group to avoid false assumptions about the editor being opened still + } + + const otherGroup = groupView.group; + if (otherGroup.contains(editor)) { + return true; // exact editor still opened + } + + if (editor instanceof SideBySideEditorInput && otherGroup.contains(editor.master)) { + return true; // master side of side by side editor still opened + } + return false; + })) { + return false; // editor is still editable somewhere else } // Switch to editor that we want to handle and confirm to save/revert await this.openEditor(editor); - const res = await editor.confirmSave(); + const editorResource = toResource(editor, { supportSideBySide: SideBySideEditor.MASTER }); + const res = await this.fileDialogService.showSaveConfirm(editorResource ? [editorResource] : [editor.getName()]); // It could be that the editor saved meanwhile, so we check again // to see if anything needs to happen before closing for good. @@ -1275,7 +1312,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Otherwise, handle accordingly switch (res) { case ConfirmResult.SAVE: - const result = await editor.save(); + const result = await editor.save(this._group.id, { reason: SaveReason.EXPLICIT }); return !result; case ConfirmResult.DONT_SAVE: @@ -1312,7 +1349,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { const editors = this.getEditorsToClose(args); // Check for dirty and veto - const veto = await this.handleDirty(editors.slice(0)); + const veto = await this.handleDirtyClosing(editors.slice(0)); if (veto) { return; } @@ -1391,7 +1428,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Check for dirty and veto const editors = this._group.getEditors(true); - const veto = await this.handleDirty(editors.slice(0)); + const veto = await this.handleDirtyClosing(editors.slice(0)); if (veto) { return; } diff --git a/src/vs/workbench/browser/parts/editor/editorPart.ts b/src/vs/workbench/browser/parts/editor/editorPart.ts index 920f4ba7e598..986000f6bfbd 100644 --- a/src/vs/workbench/browser/parts/editor/editorPart.ts +++ b/src/vs/workbench/browser/parts/editor/editorPart.ts @@ -89,26 +89,26 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro //#region Events - private readonly _onDidLayout: Emitter = this._register(new Emitter()); - readonly onDidLayout: Event = this._onDidLayout.event; + private readonly _onDidLayout = this._register(new Emitter()); + readonly onDidLayout = this._onDidLayout.event; - private readonly _onDidActiveGroupChange: Emitter = this._register(new Emitter()); - readonly onDidActiveGroupChange: Event = this._onDidActiveGroupChange.event; + private readonly _onDidActiveGroupChange = this._register(new Emitter()); + readonly onDidActiveGroupChange = this._onDidActiveGroupChange.event; - private readonly _onDidGroupIndexChange: Emitter = this._register(new Emitter()); - readonly onDidGroupIndexChange: Event = this._onDidGroupIndexChange.event; + private readonly _onDidGroupIndexChange = this._register(new Emitter()); + readonly onDidGroupIndexChange = this._onDidGroupIndexChange.event; - private readonly _onDidActivateGroup: Emitter = this._register(new Emitter()); - readonly onDidActivateGroup: Event = this._onDidActivateGroup.event; + private readonly _onDidActivateGroup = this._register(new Emitter()); + readonly onDidActivateGroup = this._onDidActivateGroup.event; - private readonly _onDidAddGroup: Emitter = this._register(new Emitter()); - readonly onDidAddGroup: Event = this._onDidAddGroup.event; + private readonly _onDidAddGroup = this._register(new Emitter()); + readonly onDidAddGroup = this._onDidAddGroup.event; - private readonly _onDidRemoveGroup: Emitter = this._register(new Emitter()); - readonly onDidRemoveGroup: Event = this._onDidRemoveGroup.event; + private readonly _onDidRemoveGroup = this._register(new Emitter()); + readonly onDidRemoveGroup = this._onDidRemoveGroup.event; - private readonly _onDidMoveGroup: Emitter = this._register(new Emitter()); - readonly onDidMoveGroup: Event = this._onDidMoveGroup.event; + private readonly _onDidMoveGroup = this._register(new Emitter()); + readonly onDidMoveGroup = this._onDidMoveGroup.event; private onDidSetGridWidget = this._register(new Emitter<{ width: number; height: number; } | undefined>()); private _onDidSizeConstraintsChange = this._register(new Relay<{ width: number; height: number; } | undefined>()); @@ -139,7 +139,7 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro @IThemeService themeService: IThemeService, @IConfigurationService private readonly configurationService: IConfigurationService, @IStorageService storageService: IStorageService, - @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService ) { super(Parts.EDITOR_PART, { hasTitle: false }, themeService, storageService, layoutService); diff --git a/src/vs/workbench/browser/parts/editor/editorPicker.ts b/src/vs/workbench/browser/parts/editor/editorPicker.ts index b50c12c65ff9..8afb0f734386 100644 --- a/src/vs/workbench/browser/parts/editor/editorPicker.ts +++ b/src/vs/workbench/browser/parts/editor/editorPicker.ts @@ -18,7 +18,6 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { toResource, SideBySideEditor, IEditorInput } from 'vs/workbench/common/editor'; import { compareItemsByScore, scoreItem, ScorerCache, prepareQuery } from 'vs/base/parts/quickopen/common/quickOpenScorer'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { withNullAsUndefined } from 'vs/base/common/types'; export class EditorPickerEntry extends QuickOpenEntryGroup { @@ -38,8 +37,8 @@ export class EditorPickerEntry extends QuickOpenEntryGroup { }; } - getLabel() { - return withNullAsUndefined(this.editor.getName()); + getLabel(): string { + return this.editor.getName(); } getIcon(): string { diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index 6eae0cad1177..0e4130d1e14b 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -6,16 +6,16 @@ import 'vs/css!./media/editorstatus'; import * as nls from 'vs/nls'; import { runAtThisOrScheduleAtNextAnimationFrame } from 'vs/base/browser/dom'; -import { format } from 'vs/base/common/strings'; +import { format, compare } from 'vs/base/common/strings'; import { extname, basename, isEqual } from 'vs/base/common/resources'; import { areFunctions, withNullAsUndefined, withUndefinedAsNull } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { Action } from 'vs/base/common/actions'; import { Language } from 'vs/base/common/platform'; -import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput'; +import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput'; import { IFileEditorInput, EncodingMode, IEncodingSupport, toResource, SideBySideEditorInput, IEditor as IBaseEditor, IEditorInput, SideBySideEditor, IModeSupport } from 'vs/workbench/common/editor'; import { Disposable, MutableDisposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; +import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { IEditorAction } from 'vs/editor/common/editorCommon'; import { EndOfLineSequence } from 'vs/editor/common/model'; import { IModelLanguageChangedEvent, IModelOptionsChangedEvent } from 'vs/editor/common/model/textModelEvents'; @@ -50,6 +50,10 @@ import { Event } from 'vs/base/common/event'; import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment, IStatusbarEntry } from 'vs/workbench/services/statusbar/common/statusbar'; +import { IMarker, IMarkerService, MarkerSeverity, IMarkerData } from 'vs/platform/markers/common/markers'; +import { find } from 'vs/base/common/arrays'; + +// {{SQL CARBON EDIT}} import { setMode } from 'sql/workbench/browser/parts/editor/editorStatusModeSelect'; // {{SQL CARBON EDIT}} class SideBySideEditorEncodingSupport implements IEncodingSupport { @@ -74,8 +78,8 @@ class SideBySideEditorModeSupport implements IModeSupport { function toEditorWithEncodingSupport(input: IEditorInput): IEncodingSupport | null { - // Untitled Editor - if (input instanceof UntitledEditorInput) { + // Untitled Text Editor + if (input instanceof UntitledTextEditorInput) { return input; } @@ -103,8 +107,8 @@ function toEditorWithEncodingSupport(input: IEditorInput): IEncodingSupport | nu function toEditorWithModeSupport(input: IEditorInput): IModeSupport | null { - // Untitled Editor - if (input instanceof UntitledEditorInput) { + // Untitled Text Editor + if (input instanceof UntitledTextEditorInput) { return input; } @@ -283,6 +287,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { private readonly eolElement = this._register(new MutableDisposable()); private readonly modeElement = this._register(new MutableDisposable()); private readonly metadataElement = this._register(new MutableDisposable()); + private readonly currentProblemStatus: ShowCurrentMarkerInStatusbarContribution = this._register(this.instantiationService.createInstance(ShowCurrentMarkerInStatusbarContribution)); private readonly state = new State(); private readonly activeEditorListeners = this._register(new DisposableStore()); @@ -294,13 +299,14 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { constructor( @IEditorService private readonly editorService: IEditorService, @IQuickInputService private readonly quickInputService: IQuickInputService, - @IUntitledEditorService private readonly untitledEditorService: IUntitledEditorService, + @IUntitledTextEditorService private readonly untitledTextEditorService: IUntitledTextEditorService, @IModeService private readonly modeService: IModeService, @ITextFileService private readonly textFileService: ITextFileService, @IConfigurationService private readonly configurationService: IConfigurationService, @INotificationService private readonly notificationService: INotificationService, @IAccessibilityService private readonly accessibilityService: IAccessibilityService, - @IStatusbarService private readonly statusbarService: IStatusbarService + @IStatusbarService private readonly statusbarService: IStatusbarService, + @IInstantiationService private readonly instantiationService: IInstantiationService, ) { super(); @@ -310,7 +316,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { private registerListeners(): void { this._register(this.editorService.onDidActiveEditorChange(() => this.updateStatusBar())); - this._register(this.untitledEditorService.onDidChangeEncoding(r => this.onResourceEncodingChange(r))); + this._register(this.untitledTextEditorService.onDidChangeEncoding(r => this.onResourceEncodingChange(r))); this._register(this.textFileService.models.onModelEncodingChanged(e => this.onResourceEncodingChange((e.resource)))); this._register(TabFocus.onDidChangeTabFocus(e => this.onTabFocusModeChange())); } @@ -578,6 +584,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { this.onEncodingChange(activeControl, activeCodeEditor); this.onIndentationChange(activeCodeEditor); this.onMetadataChange(activeControl); + this.currentProblemStatus.update(activeCodeEditor); // Dispose old active editor listeners this.activeEditorListeners.clear(); @@ -595,6 +602,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { // Hook Listener for Selection changes this.activeEditorListeners.add(activeCodeEditor.onDidChangeCursorPosition((event: ICursorPositionChangedEvent) => { this.onSelectionChange(activeCodeEditor); + this.currentProblemStatus.update(activeCodeEditor); })); // Hook Listener for mode changes @@ -605,6 +613,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { // Hook Listener for content changes this.activeEditorListeners.add(activeCodeEditor.onDidChangeModelContent((e) => { this.onEOLChange(activeCodeEditor); + this.currentProblemStatus.update(activeCodeEditor); const selections = activeCodeEditor.getSelections(); if (selections) { @@ -660,7 +669,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { const textModel = editorWidget.getModel(); if (textModel) { const modeId = textModel.getLanguageIdentifier().language; - info = { mode: this.modeService.getLanguageName(modeId) || undefined }; + info = { mode: withNullAsUndefined(this.modeService.getLanguageName(modeId)) }; } } @@ -825,6 +834,130 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { } } +class ShowCurrentMarkerInStatusbarContribution extends Disposable { + + private readonly statusBarEntryAccessor: MutableDisposable; + private editor: ICodeEditor | undefined = undefined; + private markers: IMarker[] = []; + private currentMarker: IMarker | null = null; + + constructor( + @IStatusbarService private readonly statusbarService: IStatusbarService, + @IMarkerService private readonly markerService: IMarkerService, + @IConfigurationService private readonly configurationService: IConfigurationService, + ) { + super(); + this.statusBarEntryAccessor = this._register(new MutableDisposable()); + this._register(markerService.onMarkerChanged(changedResources => this.onMarkerChanged(changedResources))); + this._register(Event.filter(configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('problems.showCurrentInStatus'))(() => this.updateStatus())); + } + + update(editor: ICodeEditor | undefined): void { + this.editor = editor; + this.updateStatus(); + } + + private updateStatus(): void { + const previousMarker = this.currentMarker; + this.currentMarker = this.getMarker(); + if (this.hasToUpdateStatus(previousMarker, this.currentMarker)) { + if (this.currentMarker) { + const line = this.currentMarker.message.split(/\r\n|\r|\n/g)[0]; + const text = `${this.getType(this.currentMarker)} ${line}`; + if (!this.statusBarEntryAccessor.value) { + this.statusBarEntryAccessor.value = this.statusbarService.addEntry({ text: '' }, 'statusbar.currentProblem', nls.localize('currentProblem', "Current Problem"), StatusbarAlignment.LEFT); + } + this.statusBarEntryAccessor.value.update({ text }); + } else { + this.statusBarEntryAccessor.clear(); + } + } + } + + private hasToUpdateStatus(previousMarker: IMarker | null, currentMarker: IMarker | null): boolean { + if (!currentMarker) { + return true; + } + if (!previousMarker) { + return true; + } + return IMarkerData.makeKey(previousMarker) !== IMarkerData.makeKey(currentMarker); + } + + private getType(marker: IMarker): string { + switch (marker.severity) { + case MarkerSeverity.Error: return '$(error)'; + case MarkerSeverity.Warning: return '$(warning)'; + case MarkerSeverity.Info: return '$(info)'; + } + return ''; + } + + private getMarker(): IMarker | null { + if (!this.configurationService.getValue('problems.showCurrentInStatus')) { + return null; + } + if (!this.editor) { + return null; + } + const model = this.editor.getModel(); + if (!model) { + return null; + } + const position = this.editor.getPosition(); + if (!position) { + return null; + } + return find(this.markers, marker => Range.containsPosition(marker, position)) || null; + } + + private onMarkerChanged(changedResources: ReadonlyArray): void { + if (!this.editor) { + return; + } + const model = this.editor.getModel(); + if (!model) { + return; + } + if (model && !changedResources.some(r => isEqual(model.uri, r))) { + return; + } + this.updateMarkers(); + } + + private updateMarkers(): void { + if (!this.editor) { + return; + } + const model = this.editor.getModel(); + if (!model) { + return; + } + if (model) { + this.markers = this.markerService.read({ + resource: model.uri, + severities: MarkerSeverity.Error | MarkerSeverity.Warning | MarkerSeverity.Info + }); + this.markers.sort(compareMarker); + } else { + this.markers = []; + } + this.updateStatus(); + } +} + +function compareMarker(a: IMarker, b: IMarker): number { + let res = compare(a.resource.toString(), b.resource.toString()); + if (res === 0) { + res = MarkerSeverity.compare(a.severity, b.severity); + } + if (res === 0) { + res = Range.compareRangesUsingStarts(a, b); + } + return res; +} + + function isWritableCodeEditor(codeEditor: ICodeEditor | undefined): boolean { if (!codeEditor) { return false; @@ -833,7 +966,7 @@ function isWritableCodeEditor(codeEditor: ICodeEditor | undefined): boolean { } function isWritableBaseEditor(e: IBaseEditor): boolean { - return e && isWritableCodeEditor(getCodeEditor(e.getControl()) || undefined); + return e && isWritableCodeEditor(withNullAsUndefined(getCodeEditor(e.getControl()))); } export class ShowLanguageExtensionsAction extends Action { @@ -870,7 +1003,7 @@ export class ChangeModeAction extends Action { @IQuickInputService private readonly quickInputService: IQuickInputService, @IPreferencesService private readonly preferencesService: IPreferencesService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IUntitledEditorService private readonly untitledEditorService: IUntitledEditorService + @IUntitledTextEditorService private readonly untitledTextEditorService: IUntitledTextEditorService ) { super(actionId, actionLabel); } @@ -885,7 +1018,7 @@ export class ChangeModeAction extends Action { const resource = this.editorService.activeEditor ? toResource(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER }) : null; let hasLanguageSupport = !!resource; - if (resource?.scheme === Schemas.untitled && !this.untitledEditorService.hasAssociatedFilePath(resource)) { + if (resource?.scheme === Schemas.untitled && !this.untitledTextEditorService.hasAssociatedFilePath(resource)) { hasLanguageSupport = false; // no configuration for untitled resources (e.g. "Untitled-1") } @@ -894,7 +1027,7 @@ export class ChangeModeAction extends Action { let modeId: string | undefined; if (textModel) { modeId = textModel.getLanguageIdentifier().language; - currentModeId = this.modeService.getLanguageName(modeId) || undefined; + currentModeId = withNullAsUndefined(this.modeService.getLanguageName(modeId)); } // All languages are valid picks @@ -1153,7 +1286,7 @@ export class ChangeEncodingAction extends Action { } let action: IQuickPickItem; - if (encodingSupport instanceof UntitledEditorInput) { + if (encodingSupport instanceof UntitledTextEditorInput) { action = saveWithEncodingPick; } else if (!isWritableBaseEditor(activeControl)) { action = reopenWithEncodingPick; diff --git a/src/vs/workbench/browser/parts/editor/media/close-all-dark.svg b/src/vs/workbench/browser/parts/editor/media/close-all-dark.svg deleted file mode 100644 index d69cc9c8351a..000000000000 --- a/src/vs/workbench/browser/parts/editor/media/close-all-dark.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/browser/parts/editor/media/close-all-light.svg b/src/vs/workbench/browser/parts/editor/media/close-all-light.svg deleted file mode 100644 index 9fcf77fe72e2..000000000000 --- a/src/vs/workbench/browser/parts/editor/media/close-all-light.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/browser/parts/editor/media/close-dark-alt.svg b/src/vs/workbench/browser/parts/editor/media/close-dark-alt.svg deleted file mode 100644 index 44ece771f456..000000000000 --- a/src/vs/workbench/browser/parts/editor/media/close-dark-alt.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/editor/media/close-dirty-dark-alt.svg b/src/vs/workbench/browser/parts/editor/media/close-dirty-dark-alt.svg deleted file mode 100644 index 51946be5bb7a..000000000000 --- a/src/vs/workbench/browser/parts/editor/media/close-dirty-dark-alt.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/editor/media/close-dirty-light-alt.svg b/src/vs/workbench/browser/parts/editor/media/close-dirty-light-alt.svg deleted file mode 100644 index fb91225b9682..000000000000 --- a/src/vs/workbench/browser/parts/editor/media/close-dirty-light-alt.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/editor/media/close-light-alt.svg b/src/vs/workbench/browser/parts/editor/media/close-light-alt.svg deleted file mode 100644 index 742fcae4ae7d..000000000000 --- a/src/vs/workbench/browser/parts/editor/media/close-light-alt.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/editor/media/editorgroupview.css b/src/vs/workbench/browser/parts/editor/media/editorgroupview.css index 59677ad2f299..ce023f628a20 100644 --- a/src/vs/workbench/browser/parts/editor/media/editorgroupview.css +++ b/src/vs/workbench/browser/parts/editor/media/editorgroupview.css @@ -48,6 +48,20 @@ overflow: hidden; } +.monaco-workbench.windows.chromium .part.editor > .content .editor-group-container > .title, +.monaco-workbench.linux.chromium .part.editor > .content .editor-group-container > .title { + /* + * Explicitly put the part onto its own layer to help Chrome to + * render the content with LCD-anti-aliasing. By partioning the + * workbench into multiple layers, we can ensure that a bad + * behaving part is not making another part fallback to greyscale + * rendering. + * + * macOS: does not render LCD-anti-aliased. + */ + transform: translate3d(0px, 0px, 0px); +} + .monaco-workbench .part.editor > .content .editor-group-container > .title:not(.tabs) { display: flex; /* when tabs are not shown, use flex layout */ flex-wrap: nowrap; diff --git a/src/vs/workbench/browser/parts/editor/media/editorstatus.css b/src/vs/workbench/browser/parts/editor/media/editorstatus.css index e792c81da1b2..05eef2cf1bc7 100644 --- a/src/vs/workbench/browser/parts/editor/media/editorstatus.css +++ b/src/vs/workbench/browser/parts/editor/media/editorstatus.css @@ -48,4 +48,5 @@ padding-right: 12px; margin-right: 5px; max-width: fit-content; + max-width: -moz-fit-content; } diff --git a/src/vs/workbench/browser/parts/editor/media/next-diff-dark.svg b/src/vs/workbench/browser/parts/editor/media/next-diff-dark.svg deleted file mode 100644 index 455532ddb7af..000000000000 --- a/src/vs/workbench/browser/parts/editor/media/next-diff-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/editor/media/next-diff-light.svg b/src/vs/workbench/browser/parts/editor/media/next-diff-light.svg deleted file mode 100644 index a443086f358f..000000000000 --- a/src/vs/workbench/browser/parts/editor/media/next-diff-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/editor/media/notabstitlecontrol.css b/src/vs/workbench/browser/parts/editor/media/notabstitlecontrol.css index f95c8de3c9ea..457578a68244 100644 --- a/src/vs/workbench/browser/parts/editor/media/notabstitlecontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/notabstitlecontrol.css @@ -76,6 +76,15 @@ padding-right: 4px; /* does not have trailing separator*/ } +.monaco-workbench .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item .codicon[class*='codicon-symbol-'] { + padding: 0 1px; +} + +.monaco-workbench .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item .codicon:last-child, +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item:last-child .codicon:last-child { + display: none; /* hides chevrons when no tabs visible and when last items */ +} + /* Title Actions */ .monaco-workbench .part.editor > .content .editor-group-container > .title .title-actions { display: flex; diff --git a/src/vs/workbench/browser/parts/editor/media/paragraph-dark.svg b/src/vs/workbench/browser/parts/editor/media/paragraph-dark.svg deleted file mode 100644 index 24708ab31f90..000000000000 --- a/src/vs/workbench/browser/parts/editor/media/paragraph-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/editor/media/paragraph-disabled-dark.svg b/src/vs/workbench/browser/parts/editor/media/paragraph-disabled-dark.svg deleted file mode 100644 index 3c174668fdcc..000000000000 --- a/src/vs/workbench/browser/parts/editor/media/paragraph-disabled-dark.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/vs/workbench/browser/parts/editor/media/paragraph-disabled-light.svg b/src/vs/workbench/browser/parts/editor/media/paragraph-disabled-light.svg deleted file mode 100644 index 5edb9e63eb7f..000000000000 --- a/src/vs/workbench/browser/parts/editor/media/paragraph-disabled-light.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/vs/workbench/browser/parts/editor/media/paragraph-light.svg b/src/vs/workbench/browser/parts/editor/media/paragraph-light.svg deleted file mode 100644 index 734fe6192086..000000000000 --- a/src/vs/workbench/browser/parts/editor/media/paragraph-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/editor/media/previous-diff-dark.svg b/src/vs/workbench/browser/parts/editor/media/previous-diff-dark.svg deleted file mode 100644 index 5ca3526019f0..000000000000 --- a/src/vs/workbench/browser/parts/editor/media/previous-diff-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/editor/media/previous-diff-light.svg b/src/vs/workbench/browser/parts/editor/media/previous-diff-light.svg deleted file mode 100644 index 87e179a7f3c9..000000000000 --- a/src/vs/workbench/browser/parts/editor/media/previous-diff-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/editor/media/split-editor-horizontal-dark.svg b/src/vs/workbench/browser/parts/editor/media/split-editor-horizontal-dark.svg deleted file mode 100644 index 8c22a7c5bfed..000000000000 --- a/src/vs/workbench/browser/parts/editor/media/split-editor-horizontal-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/editor/media/split-editor-horizontal-hc.svg b/src/vs/workbench/browser/parts/editor/media/split-editor-horizontal-hc.svg deleted file mode 100644 index 82c19d0c8fc2..000000000000 --- a/src/vs/workbench/browser/parts/editor/media/split-editor-horizontal-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/editor/media/split-editor-horizontal-light.svg b/src/vs/workbench/browser/parts/editor/media/split-editor-horizontal-light.svg deleted file mode 100644 index 400952cdda8d..000000000000 --- a/src/vs/workbench/browser/parts/editor/media/split-editor-horizontal-light.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/vs/workbench/browser/parts/editor/media/split-editor-vertical-dark.svg b/src/vs/workbench/browser/parts/editor/media/split-editor-vertical-dark.svg deleted file mode 100644 index 419c21be4f62..000000000000 --- a/src/vs/workbench/browser/parts/editor/media/split-editor-vertical-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/editor/media/split-editor-vertical-hc.svg b/src/vs/workbench/browser/parts/editor/media/split-editor-vertical-hc.svg deleted file mode 100644 index 7565fd3c1681..000000000000 --- a/src/vs/workbench/browser/parts/editor/media/split-editor-vertical-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/editor/media/split-editor-vertical-light.svg b/src/vs/workbench/browser/parts/editor/media/split-editor-vertical-light.svg deleted file mode 100644 index 405291c14dd4..000000000000 --- a/src/vs/workbench/browser/parts/editor/media/split-editor-vertical-light.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css b/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css index 75424d5f8814..875c19f5dcd0 100644 --- a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css @@ -23,6 +23,7 @@ .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container { display: flex; height: 35px; + scrollbar-width: none; /* Firefox: hide scrollbar */ } .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container.scroll { @@ -30,7 +31,7 @@ } .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container::-webkit-scrollbar { - display: none; + display: none; /* Chrome + Safari: hide scrollbar */ } /* Tab */ @@ -53,6 +54,8 @@ .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-fit { width: 120px; min-width: fit-content; + min-width: -moz-fit-content; + flex-shrink: 0; } .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink { @@ -60,6 +63,11 @@ flex-basis: 0; /* all tabs are even */ flex-grow: 1; /* all tabs grow even */ max-width: fit-content; + max-width: -moz-fit-content; +} + +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-left .action-label { + margin-right: 4px !important; } .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.close-button-left::after, @@ -76,7 +84,7 @@ } .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dragged { - will-change: transform; /* forces tab to be drawn on a separate layer (fixes https://github.com/Microsoft/vscode/issues/18733) */ + transform: translate3d(0px, 0px, 0px); /* forces tab to be drawn on a separate layer (fixes https://github.com/Microsoft/vscode/issues/18733) */ } .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dragged-over div { @@ -198,6 +206,11 @@ opacity: 1; } +.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab > .tab-close .action-label.codicon { + color: inherit; + font-size: 16px; +} + /* change close icon to dirty state icon */ .monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab.dirty > .tab-close .action-label:not(:hover)::before, .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty > .tab-close .action-label:not(:hover)::before { @@ -232,6 +245,7 @@ padding-right: 5px; /* we need less room when sizing is shrink */ } +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-off.dirty-border-top > .tab-close, .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-off.dirty-border-top > .tab-close { display: none; /* hide dirty state when highlightModifiedTabs is enabled and when running without close button */ } diff --git a/src/vs/workbench/browser/parts/editor/media/titlecontrol.css b/src/vs/workbench/browser/parts/editor/media/titlecontrol.css index aa3bca6d2527..4bfd74fd743f 100644 --- a/src/vs/workbench/browser/parts/editor/media/titlecontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/titlecontrol.css @@ -30,17 +30,18 @@ .monaco-workbench .part.editor > .content .editor-group-container > .title .title-actions .action-label, .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label { - display: block; + display: flex; height: 35px; - line-height: 35px; min-width: 28px; + align-items: center; + justify-content: center; background-size: 16px; background-position: center center; background-repeat: no-repeat; } .hc-black .monaco-workbench .part.editor > .content .editor-group-container > .title .title-actions .action-label, -.hc-black .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label { +.hc-black .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label:not(.codicon) { line-height: initial; } @@ -49,6 +50,15 @@ display: none; } +.monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label.codicon { + color: inherit; +} + +.monaco-workbench .part.editor > .content .editor-group-container > .title .title-actions .action-label.disabled, +.monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label.disabled { + opacity: 0.4; +} + /* Drag Cursor */ .monaco-workbench .part.editor > .content .editor-group-container > .title { cursor: grab; diff --git a/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts index 9737270cbb18..7711ae4df2d4 100644 --- a/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts @@ -100,13 +100,21 @@ export class NoTabsTitleControl extends TitleControl { private onTitleClick(e: MouseEvent | GestureEvent): void { - // Close editor on middle mouse click - if (e instanceof MouseEvent && e.button === 1 /* Middle Button */) { - EventHelper.stop(e, true /* for https://github.com/Microsoft/vscode/issues/56715 */); - - if (this.group.activeEditor) { - this.group.closeEditor(this.group.activeEditor); + if (e instanceof MouseEvent) { + // Close editor on middle mouse click + if (e.button === 1 /* Middle Button */) { + EventHelper.stop(e, true /* for https://github.com/Microsoft/vscode/issues/56715 */); + + if (this.group.activeEditor) { + this.group.closeEditor(this.group.activeEditor); + } } + } else { + // @rebornix + // gesture tap should open the quick open + // editorGroupView will focus on the editor again when there are mouse/pointer/touch down events + // we need to wait a bit as `GesureEvent.Tap` is generated from `touchstart` and then `touchend` evnets, which are not an atom event. + setTimeout(() => this.quickOpenService.show(), 50); } } @@ -248,7 +256,7 @@ export class NoTabsTitleControl extends TitleControl { // Editor Label const resource = toResource(editor, { supportSideBySide: SideBySideEditor.MASTER }); - const name = editor.getName() || ''; + const name = editor.getName(); const { labelFormat } = this.accessor.partOptions; let description: string; @@ -265,7 +273,7 @@ export class NoTabsTitleControl extends TitleControl { title = ''; // dont repeat what is already shown } - editorLabel.setResource({ name, description, resource: resource || undefined }, { title: typeof title === 'string' ? title : undefined, italic: !isEditorPinned, extraClasses: ['no-tabs', 'title-label'] }); + editorLabel.setResource({ name, description, resource }, { title: typeof title === 'string' ? title : undefined, italic: !isEditorPinned, extraClasses: ['no-tabs', 'title-label'] }); if (isGroupActive) { editorLabel.element.style.color = this.getColor(TAB_ACTIVE_FOREGROUND); } else { diff --git a/src/vs/workbench/browser/parts/editor/resourceViewer.ts b/src/vs/workbench/browser/parts/editor/resourceViewer.ts index fc65d4ac65ec..1fa96e6f9ab7 100644 --- a/src/vs/workbench/browser/parts/editor/resourceViewer.ts +++ b/src/vs/workbench/browser/parts/editor/resourceViewer.ts @@ -6,7 +6,6 @@ import * as DOM from 'vs/base/browser/dom'; import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; -import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; import 'vs/css!./media/resourceviewer'; import * as nls from 'vs/nls'; @@ -132,13 +131,11 @@ class FileSeemsBinaryFileView { label.textContent = nls.localize('nativeBinaryError', "The file is not displayed in the editor because it is either binary or uses an unsupported text encoding."); container.appendChild(label); - if (descriptor.resource.scheme !== Schemas.data) { - const link = DOM.append(label, DOM.$('a.embedded-link')); - link.setAttribute('role', 'button'); - link.textContent = nls.localize('openAsText', "Do you want to open it anyway?"); + const link = DOM.append(label, DOM.$('a.embedded-link')); + link.setAttribute('role', 'button'); + link.textContent = nls.localize('openAsText', "Do you want to open it anyway?"); - disposables.add(DOM.addDisposableListener(link, DOM.EventType.CLICK, () => delegate.openInternalClb(descriptor.resource))); - } + disposables.add(DOM.addDisposableListener(link, DOM.EventType.CLICK, () => delegate.openInternalClb(descriptor.resource))); scrollbar.scanDomNode(); diff --git a/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts b/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts index c4442dc1e691..cc76f02d4a0c 100644 --- a/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts +++ b/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts @@ -56,7 +56,7 @@ export class SideBySideEditor extends BaseEditor { private onDidCreateEditors = this._register(new Emitter<{ width: number; height: number; } | undefined>()); private _onDidSizeConstraintsChange = this._register(new Relay<{ width: number; height: number; } | undefined>()); - readonly onDidSizeConstraintsChange: Event<{ width: number; height: number; } | undefined> = Event.any(this.onDidCreateEditors.event, this._onDidSizeConstraintsChange.event); + readonly onDidSizeConstraintsChange = Event.any(this.onDidCreateEditors.event, this._onDidSizeConstraintsChange.event); constructor( @ITelemetryService telemetryService: ITelemetryService, diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index c04eb4006c52..ed953d31755b 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -31,8 +31,8 @@ import { ResourcesDropHandler, fillResourceDataTransfers, DraggedEditorIdentifie import { Color } from 'vs/base/common/color'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { MergeGroupMode, IMergeGroupOptions } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; +import { MergeGroupMode, IMergeGroupOptions, GroupsArrangement } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { addClass, addDisposableListener, hasClass, EventType, EventHelper, removeClass, Dimension, scheduleAtNextAnimationFrame, findParentWithClass, clearNode } from 'vs/base/browser/dom'; import { localize } from 'vs/nls'; import { IEditorGroupsAccessor, IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor'; @@ -79,7 +79,7 @@ export class TabsTitleControl extends TitleControl { group: IEditorGroupView, @IContextMenuService contextMenuService: IContextMenuService, @IInstantiationService instantiationService: IInstantiationService, - @IUntitledEditorService private readonly untitledEditorService: IUntitledEditorService, + @IUntitledTextEditorService private readonly untitledTextEditorService: IUntitledTextEditorService, @IContextKeyService contextKeyService: IContextKeyService, @IKeybindingService keybindingService: IKeybindingService, @ITelemetryService telemetryService: ITelemetryService, @@ -111,6 +111,7 @@ export class TabsTitleControl extends TitleControl { this.tabsContainer.setAttribute('role', 'tablist'); this.tabsContainer.draggable = true; addClass(this.tabsContainer, 'tabs-container'); + this._register(Gesture.addTarget(this.tabsContainer)); // Tabs Scrollbar this.tabsScrollbar = this._register(this.createTabsScrollbar(this.tabsContainer)); @@ -178,13 +179,27 @@ export class TabsTitleControl extends TitleControl { })); // New file when double clicking on tabs container (but not tabs) - this._register(addDisposableListener(tabsContainer, EventType.DBLCLICK, e => { - if (e.target === tabsContainer) { + [TouchEventType.Tap, EventType.DBLCLICK].forEach(eventType => { + this._register(addDisposableListener(tabsContainer, eventType, (e: MouseEvent | GestureEvent) => { + if (eventType === EventType.DBLCLICK) { + if (e.target !== tabsContainer) { + return; // ignore if target is not tabs container + } + } else { + if ((e).tapCount !== 2) { + return; // ignore single taps + } + + if ((e).initialTarget !== tabsContainer) { + return; // ignore if target is not tabs container + } + } + EventHelper.stop(e); - this.group.openEditor(this.untitledEditorService.createOrGet(), { pinned: true /* untitled is always pinned */, index: this.group.count /* always at the end */ }); - } - })); + this.group.openEditor(this.untitledTextEditorService.createOrGet(), { pinned: true /* untitled is always pinned */, index: this.group.count /* always at the end */ }); + })); + }); // Prevent auto-scrolling (https://github.com/Microsoft/vscode/issues/16690) this._register(addDisposableListener(tabsContainer, EventType.MOUSE_DOWN, (e: MouseEvent) => { @@ -316,7 +331,7 @@ export class TabsTitleControl extends TitleControl { (tabsContainer.lastChild as HTMLElement).remove(); // Remove associated tab label and widget - this.tabDisposables.pop()!.dispose(); + dispose(this.tabDisposables.pop()); } // A removal of a label requires to recompute all labels @@ -360,7 +375,7 @@ export class TabsTitleControl extends TitleControl { } pinEditor(editor: IEditorInput): void { - this.withTab(editor, (tabContainer, tabLabelWidget, tabLabel) => this.redrawLabel(editor, tabContainer, tabLabelWidget, tabLabel)); + this.withTab(editor, (editor, index, tabContainer, tabLabelWidget, tabLabel) => this.redrawLabel(editor, tabContainer, tabLabelWidget, tabLabel)); } setActive(isGroupActive: boolean): void { @@ -396,7 +411,7 @@ export class TabsTitleControl extends TitleControl { } updateEditorDirty(editor: IEditorInput): void { - this.withTab(editor, (tabContainer, tabLabelWidget) => this.redrawEditorActiveAndDirty(this.accessor.activeGroup === this.group, editor, tabContainer, tabLabelWidget)); + this.withTab(editor, (editor, index, tabContainer, tabLabelWidget) => this.redrawEditorActiveAndDirty(this.accessor.activeGroup === this.group, editor, tabContainer, tabLabelWidget)); } updateOptions(oldOptions: IEditorPartOptions, newOptions: IEditorPartOptions): void { @@ -423,13 +438,23 @@ export class TabsTitleControl extends TitleControl { this.redraw(); } - private withTab(editor: IEditorInput, fn: (tabContainer: HTMLElement, tabLabelWidget: IResourceLabel, tabLabel: IEditorInputLabel) => void): void { - const editorIndex = this.group.getIndexOfEditor(editor); + private forEachTab(fn: (editor: IEditorInput, index: number, tabContainer: HTMLElement, tabLabelWidget: IResourceLabel, tabLabel: IEditorInputLabel) => void): void { + this.group.editors.forEach((editor, index) => { + this.doWithTab(index, editor, fn); + }); + } + private withTab(editor: IEditorInput, fn: (editor: IEditorInput, index: number, tabContainer: HTMLElement, tabLabelWidget: IResourceLabel, tabLabel: IEditorInputLabel) => void): void { + this.doWithTab(this.group.getIndexOfEditor(editor), editor, fn); + } + + private doWithTab(index: number, editor: IEditorInput, fn: (editor: IEditorInput, index: number, tabContainer: HTMLElement, tabLabelWidget: IResourceLabel, tabLabel: IEditorInputLabel) => void): void { const tabsContainer = assertIsDefined(this.tabsContainer); - const tabContainer = tabsContainer.children[editorIndex] as HTMLElement; - if (tabContainer) { - fn(tabContainer, this.tabResourceLabels.get(editorIndex), this.tabLabels[editorIndex]); + const tabContainer = tabsContainer.children[index] as HTMLElement; + const tabResourceLabel = this.tabResourceLabels.get(index); + const tabLabel = this.tabLabels[index]; + if (tabContainer && tabResourceLabel && tabLabel) { + fn(editor, index, tabContainer, tabResourceLabel, tabLabel); } } @@ -496,7 +521,7 @@ export class TabsTitleControl extends TitleControl { } // Open tabs editor - const input = this.group.getEditor(index); + const input = this.group.getEditorByIndex(index); if (input) { this.group.openEditor(input); } @@ -507,7 +532,7 @@ export class TabsTitleControl extends TitleControl { const showContextMenu = (e: Event) => { EventHelper.stop(e); - const input = this.group.getEditor(index); + const input = this.group.getEditorByIndex(index); if (input) { this.onContextMenu(input, e, tab); } @@ -557,7 +582,7 @@ export class TabsTitleControl extends TitleControl { // Run action on Enter/Space if (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) { handled = true; - const input = this.group.getEditor(index); + const input = this.group.getEditorByIndex(index); if (input) { this.group.openEditor(input); } @@ -576,7 +601,7 @@ export class TabsTitleControl extends TitleControl { targetIndex = this.group.count - 1; } - const target = this.group.getEditor(targetIndex); + const target = this.group.getEditorByIndex(targetIndex); if (target) { handled = true; this.group.openEditor(target, { preserveFocus: true }); @@ -594,18 +619,29 @@ export class TabsTitleControl extends TitleControl { }); })); - // Pin on double click - disposables.add(addDisposableListener(tab, EventType.DBLCLICK, (e: MouseEvent) => { - EventHelper.stop(e); + // Double click: either pin or toggle maximized + [TouchEventType.Tap, EventType.DBLCLICK].forEach(eventType => { + disposables.add(addDisposableListener(tab, eventType, (e: MouseEvent | GestureEvent) => { + if (eventType === EventType.DBLCLICK) { + EventHelper.stop(e); + } else if ((e).tapCount !== 2) { + return; // ignore single taps + } - this.group.pinEditor(this.group.getEditor(index) || undefined); - })); + const editor = this.group.getEditorByIndex(index); + if (editor && this.group.isPinned(editor)) { + this.accessor.arrangeGroups(GroupsArrangement.TOGGLE, this.group); + } else { + this.group.pinEditor(editor); + } + })); + }); // Context menu disposables.add(addDisposableListener(tab, EventType.CONTEXT_MENU, (e: Event) => { EventHelper.stop(e, true); - const input = this.group.getEditor(index); + const input = this.group.getEditorByIndex(index); if (input) { this.onContextMenu(input, e, tab); } @@ -613,7 +649,7 @@ export class TabsTitleControl extends TitleControl { // Drag support disposables.add(addDisposableListener(tab, EventType.DRAG_START, (e: DragEvent) => { - const editor = this.group.getEditor(index); + const editor = this.group.getEditorByIndex(index); if (!editor) { return; } @@ -659,7 +695,7 @@ export class TabsTitleControl extends TitleControl { const data = this.editorTransfer.getData(DraggedEditorIdentifier.prototype); if (Array.isArray(data)) { const localDraggedEditor = data[0].identifier; - if (localDraggedEditor.editor === this.group.getEditor(index) && localDraggedEditor.groupId === this.group.id) { + if (localDraggedEditor.editor === this.group.getEditorByIndex(index) && localDraggedEditor.groupId === this.group.id) { if (e.dataTransfer) { e.dataTransfer.dropEffect = 'none'; } @@ -729,7 +765,7 @@ export class TabsTitleControl extends TitleControl { private updateDropFeedback(element: HTMLElement, isDND: boolean, index?: number): void { const isTab = (typeof index === 'number'); - const editor = typeof index === 'number' ? this.group.getEditor(index) : undefined; + const editor = typeof index === 'number' ? this.group.getEditorByIndex(index) : undefined; const isActiveTab = isTab && !!editor && this.group.isActive(editor); // Background @@ -754,9 +790,9 @@ export class TabsTitleControl extends TitleControl { if (isTab) { const tabContainer = this.tabsContainer.children[index]; if (tabContainer instanceof HTMLElement) { - let editor = this.group.getEditor(index); - if (editor) { - this.setEditorTabColor(editor, tabContainer, isActiveTab); + let editor = this.group.getEditors(index); + if (editor.length > 0) { + this.setEditorTabColor(editor[0], tabContainer, isActiveTab); } } } @@ -878,16 +914,6 @@ export class TabsTitleControl extends TitleControl { this.layout(this.dimension); } - private forEachTab(fn: (editor: IEditorInput, index: number, tabContainer: HTMLElement, tabLabelWidget: IResourceLabel, tabLabel: IEditorInputLabel) => void): void { - this.group.editors.forEach((editor, index) => { - const tabsContainer = assertIsDefined(this.tabsContainer); - const tabContainer = tabsContainer.children[index] as HTMLElement; - if (tabContainer) { - fn(editor, index, tabContainer, this.tabResourceLabels.get(index), this.tabLabels[index]); - } - }); - } - private redrawTab(editor: IEditorInput, index: number, tabContainer: HTMLElement, tabLabelWidget: IResourceLabel, tabLabel: IEditorInputLabel): void { // Label @@ -931,7 +957,7 @@ export class TabsTitleControl extends TitleControl { tabContainer.title = title; // Label - tabLabelWidget.setResource({ name, description, resource: toResource(editor, { supportSideBySide: SideBySideEditor.MASTER }) || undefined }, { title, extraClasses: ['tab-label'], italic: !this.group.isPinned(editor) }); + tabLabelWidget.setResource({ name, description, resource: toResource(editor, { supportSideBySide: SideBySideEditor.MASTER }) }, { title, extraClasses: ['tab-label'], italic: !this.group.isPinned(editor) }); // {{SQL CARBON EDIT}} -- Display the editor's tab color const isTabActive = this.group.isActive(editor); @@ -1276,7 +1302,7 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { // High Contrast Border Color for Editor Actions const contrastBorderColor = theme.getColor(contrastBorder); - if (contrastBorder) { + if (contrastBorderColor) { collector.addRule(` .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions { outline: 1px solid ${contrastBorderColor} diff --git a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts index 4a9a80598a57..2312cab6ff08 100644 --- a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts @@ -10,7 +10,6 @@ import { IDiffEditor } from 'vs/editor/browser/editorBrowser'; import { IDiffEditorOptions, IEditorOptions as ICodeEditorOptions } from 'vs/editor/common/config/editorOptions'; import { BaseTextEditor, IEditorConfiguration } from 'vs/workbench/browser/parts/editor/textEditor'; import { TextEditorOptions, EditorInput, EditorOptions, TEXT_DIFF_EDITOR_ID, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions, ITextDiffEditor, IEditorMemento } from 'vs/workbench/common/editor'; -import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { DiffNavigator } from 'vs/editor/browser/widget/diffNavigator'; import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditorWidget'; @@ -20,7 +19,7 @@ import { IStorageService } from 'vs/platform/storage/common/storage'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { ITextFileService, TextFileOperationError, TextFileOperationResult } from 'vs/workbench/services/textfile/common/textfiles'; +import { TextFileOperationError, TextFileOperationResult } from 'vs/workbench/services/textfile/common/textfiles'; import { ScrollType, IDiffEditorViewState, IDiffEditorModel } from 'vs/editor/common/editorCommon'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -30,7 +29,6 @@ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editor import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { CancellationToken } from 'vs/base/common/cancellation'; import { EditorMemento } from 'vs/workbench/browser/parts/editor/baseEditor'; -import { IHostService } from 'vs/workbench/services/host/browser/host'; import { EditorActivation, IEditorOptions } from 'vs/platform/editor/common/editor'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; @@ -54,18 +52,16 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor { @IEditorService editorService: IEditorService, @IThemeService themeService: IThemeService, @IEditorGroupsService editorGroupService: IEditorGroupsService, - @ITextFileService textFileService: ITextFileService, - @IHostService hostService: IHostService, - @IClipboardService private _clipboardService: IClipboardService, + @IClipboardService private clipboardService: IClipboardService ) { - super(TextDiffEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, textFileService, editorService, editorGroupService, hostService); + super(TextDiffEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, editorService, editorGroupService); } protected getEditorMemento(editorGroupService: IEditorGroupsService, key: string, limit: number = 10): IEditorMemento { return new EditorMemento(this.getId(), key, Object.create(null), limit, editorGroupService); // do not persist in storage as diff editors are never persisted } - getTitle(): string | undefined { + getTitle(): string { if (this.input) { return this.input.getName(); } @@ -82,7 +78,7 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor { if (this.reverseColor) { // {{SQL CARBON EDIT}} (configuration as IDiffEditorOptions).reverse = true; } - return this.instantiationService.createInstance(DiffEditorWidget, parent, configuration, this._clipboardService); + return this.instantiationService.createInstance(DiffEditorWidget as any, parent, configuration, this.clipboardService); // {{SQL CARBON EDIT}} strict-null-check...i guess? } async setInput(input: EditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise { @@ -132,8 +128,15 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor { }); this.diffNavigatorDisposables.add(this.diffNavigator); - // Readonly flag - diffEditor.updateOptions({ readOnly: resolvedDiffEditorModel.isReadonly() }); + // Since the resolved model provides information about being readonly + // or not, we apply it here to the editor even though the editor input + // was already asked for being readonly or not. The rationale is that + // a resolved model might have more specific information about being + // readonly or not that the input did not have. + diffEditor.updateOptions({ + readOnly: resolvedDiffEditorModel.modifiedModel?.isReadonly(), + originalEditable: !resolvedDiffEditorModel.originalModel?.isReadonly() + }); } catch (error) { // In case we tried to open a file and the response indicates that this is not a text file, fallback to binary diff. @@ -145,14 +148,6 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor { } } - setOptions(options: EditorOptions | undefined): void { - const textOptions = options; - if (textOptions && isFunction(textOptions.apply)) { - const diffEditor = assertIsDefined(this.getControl()); - textOptions.apply(diffEditor, ScrollType.Smooth); - } - } - private restoreTextDiffEditorViewState(editor: EditorInput, control: IDiffEditor): boolean { if (editor instanceof DiffEditorInput) { const resource = this.toDiffEditorViewStateResource(editor); @@ -219,7 +214,8 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor { protected getConfigurationOverrides(): ICodeEditorOptions { const options: IDiffEditorOptions = super.getConfigurationOverrides(); - options.readOnly = this.isReadOnly(); + options.readOnly = this.input instanceof DiffEditorInput && this.input.modifiedInput.isReadonly(); + options.originalEditable = this.input instanceof DiffEditorInput && !this.input.originalInput.isReadonly(); options.lineDecorationsWidth = '2ch'; return options; @@ -227,8 +223,9 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor { protected getAriaLabel(): string { let ariaLabel: string; - const inputName = this.input && this.input.getName(); - if (this.isReadOnly()) { + + const inputName = this.input?.getName(); + if (this.input?.isReadonly()) { ariaLabel = inputName ? nls.localize('readonlyEditorWithInputAriaLabel', "{0}. Readonly text compare editor.", inputName) : nls.localize('readonlyEditorAriaLabel', "Readonly text compare editor."); } else { ariaLabel = inputName ? nls.localize('editableEditorWithInputAriaLabel', "{0}. Text file compare editor.", inputName) : nls.localize('editableEditorAriaLabel', "Text file compare editor."); @@ -237,17 +234,6 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor { return ariaLabel; } - private isReadOnly(): boolean { - const input = this.input; - if (input instanceof DiffEditorInput) { - const modifiedInput = input.modifiedInput; - - return modifiedInput instanceof ResourceEditorInput; - } - - return false; - } - private isFileBinaryError(error: Error[]): boolean; private isFileBinaryError(error: Error): boolean; private isFileBinaryError(error: Error | Error[]): boolean { diff --git a/src/vs/workbench/browser/parts/editor/textEditor.ts b/src/vs/workbench/browser/parts/editor/textEditor.ts index 240e97a981c4..0c9678e2a94c 100644 --- a/src/vs/workbench/browser/parts/editor/textEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textEditor.ts @@ -6,24 +6,22 @@ import { localize } from 'vs/nls'; import { URI } from 'vs/base/common/uri'; import { distinct, deepClone, assign } from 'vs/base/common/objects'; -import { isObject, assertIsDefined } from 'vs/base/common/types'; +import { isObject, assertIsDefined, withNullAsUndefined, isFunction } from 'vs/base/common/types'; import { Dimension } from 'vs/base/browser/dom'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; -import { EditorInput, EditorOptions, IEditorMemento, ITextEditor } from 'vs/workbench/common/editor'; +import { EditorInput, EditorOptions, IEditorMemento, ITextEditor, TextEditorOptions } from 'vs/workbench/common/editor'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; -import { IEditorViewState, IEditor } from 'vs/editor/common/editorCommon'; +import { IEditorViewState, IEditor, ScrollType } from 'vs/editor/common/editorCommon'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { ITextFileService, SaveReason, AutoSaveMode } from 'vs/workbench/services/textfile/common/textfiles'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; -import { isDiffEditor, isCodeEditor, getCodeEditor } from 'vs/editor/browser/editorBrowser'; +import { isCodeEditor, getCodeEditor } from 'vs/editor/browser/editorBrowser'; import { IEditorGroupsService, IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IHostService } from 'vs/workbench/services/host/browser/host'; const TEXT_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'textEditorViewState'; @@ -38,7 +36,7 @@ export interface IEditorConfiguration { */ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor { private editorControl: IEditor | undefined; - private _editorContainer: HTMLElement | undefined; + private editorContainer: HTMLElement | undefined; private hasPendingConfigurationChange: boolean | undefined; private lastAppliedEditorOptions?: IEditorOptions; private editorMemento: IEditorMemento; @@ -50,10 +48,8 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor { @IStorageService storageService: IStorageService, @ITextResourceConfigurationService private readonly _configurationService: ITextResourceConfigurationService, @IThemeService protected themeService: IThemeService, - @ITextFileService private readonly _textFileService: ITextFileService, @IEditorService protected editorService: IEditorService, - @IEditorGroupsService protected editorGroupService: IEditorGroupsService, - @IHostService private readonly hostService: IHostService + @IEditorGroupsService protected editorGroupService: IEditorGroupsService ) { super(id, telemetryService, themeService, storageService); @@ -74,10 +70,6 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor { return this._configurationService; } - protected get textFileService(): ITextFileService { - return this._textFileService; - } - protected handleConfigurationChangeEvent(configuration?: IEditorConfiguration): void { if (this.isVisible()) { this.updateEditorConfiguration(configuration); @@ -119,63 +111,25 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor { } protected getConfigurationOverrides(): IEditorOptions { - const overrides = {}; - assign(overrides, { + return { overviewRulerLanes: 3, lineNumbersMinChars: 3, - fixedOverflowWidgets: true - }); - - return overrides; + fixedOverflowWidgets: true, + readOnly: this.input?.isReadonly() + }; } protected createEditor(parent: HTMLElement): void { // Editor for Text - this._editorContainer = parent; + this.editorContainer = parent; this.editorControl = this._register(this.createEditorControl(parent, this.computeConfiguration(this.configurationService.getValue(this.getResource())))); // Model & Language changes const codeEditor = getCodeEditor(this.editorControl); if (codeEditor) { - this._register(codeEditor.onDidChangeModelLanguage(e => this.updateEditorConfiguration())); - this._register(codeEditor.onDidChangeModel(e => this.updateEditorConfiguration())); - } - - // Application & Editor focus change to respect auto save settings - if (isCodeEditor(this.editorControl)) { - this._register(this.editorControl.onDidBlurEditorWidget(() => this.onEditorFocusLost())); - } else if (isDiffEditor(this.editorControl)) { - this._register(this.editorControl.getOriginalEditor().onDidBlurEditorWidget(() => this.onEditorFocusLost())); - this._register(this.editorControl.getModifiedEditor().onDidBlurEditorWidget(() => this.onEditorFocusLost())); - } - - this._register(this.editorService.onDidActiveEditorChange(() => this.onEditorFocusLost())); - this._register(this.hostService.onDidChangeFocus(focused => this.onWindowFocusChange(focused))); - } - - private onEditorFocusLost(): void { - this.maybeTriggerSaveAll(SaveReason.FOCUS_CHANGE); - } - - private onWindowFocusChange(focused: boolean): void { - if (!focused) { - this.maybeTriggerSaveAll(SaveReason.WINDOW_CHANGE); - } - } - - private maybeTriggerSaveAll(reason: SaveReason): void { - const mode = this.textFileService.getAutoSaveMode(); - - // Determine if we need to save all. In case of a window focus change we also save if auto save mode - // is configured to be ON_FOCUS_CHANGE (editor focus change) - if ( - (reason === SaveReason.WINDOW_CHANGE && (mode === AutoSaveMode.ON_FOCUS_CHANGE || mode === AutoSaveMode.ON_WINDOW_CHANGE)) || - (reason === SaveReason.FOCUS_CHANGE && mode === AutoSaveMode.ON_FOCUS_CHANGE) - ) { - if (this.textFileService.isDirty()) { - this.textFileService.saveAll(undefined, { reason }); - } + this._register(codeEditor.onDidChangeModelLanguage(() => this.updateEditorConfiguration())); + this._register(codeEditor.onDidChangeModel(() => this.updateEditorConfiguration())); } } @@ -198,10 +152,18 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor { // editor input specific options (e.g. an ARIA label depending on the input showing) this.updateEditorConfiguration(); - const editorContainer = assertIsDefined(this._editorContainer); + const editorContainer = assertIsDefined(this.editorContainer); editorContainer.setAttribute('aria-label', this.computeAriaLabel()); } + setOptions(options: EditorOptions | undefined): void { + const textOptions = options as TextEditorOptions; + if (textOptions && isFunction(textOptions.apply)) { + const textEditor = assertIsDefined(this.getControl()); + textOptions.apply(textEditor, ScrollType.Smooth); + } + } + protected setEditorVisible(visible: boolean, group: IEditorGroup | undefined): void { // Pass on to Editor @@ -246,6 +208,15 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor { this.editorMemento.saveEditorState(this.group, resource, editorViewState); } + getViewState(): IEditorViewState | undefined { + const resource = this.input?.getResource(); + if (resource) { + return withNullAsUndefined(this.retrieveTextEditorViewState(resource)); + } + + return undefined; + } + protected retrieveTextEditorViewState(resource: URI): IEditorViewState | null { const control = this.getControl(); if (!isCodeEditor(control)) { @@ -292,6 +263,7 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor { configuration = this.configurationService.getValue(resource); } } + if (!this.editorControl || !configuration) { return; } diff --git a/src/vs/workbench/browser/parts/editor/textResourceEditor.ts b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts index 19f392d3545c..fc973f686fe5 100644 --- a/src/vs/workbench/browser/parts/editor/textResourceEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts @@ -6,24 +6,21 @@ import * as nls from 'vs/nls'; import { assertIsDefined, isFunction } from 'vs/base/common/types'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { TextEditorOptions, EditorInput, EditorOptions } from 'vs/workbench/common/editor'; import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel'; -import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput'; +import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput'; import { BaseTextEditor } from 'vs/workbench/browser/parts/editor/textEditor'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { Event } from 'vs/base/common/event'; import { ScrollType, IEditor } from 'vs/editor/common/editorCommon'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IHostService } from 'vs/workbench/services/host/browser/host'; /** * An editor implementation that is capable of showing the contents of resource inputs. Uses @@ -39,11 +36,9 @@ export class AbstractTextResourceEditor extends BaseTextEditor { @ITextResourceConfigurationService configurationService: ITextResourceConfigurationService, @IThemeService themeService: IThemeService, @IEditorGroupsService editorGroupService: IEditorGroupsService, - @ITextFileService textFileService: ITextFileService, - @IEditorService editorService: IEditorService, - @IHostService hostService: IHostService + @IEditorService editorService: IEditorService ) { - super(id, telemetryService, instantiationService, storageService, configurationService, themeService, textFileService, editorService, editorGroupService, hostService); + super(id, telemetryService, instantiationService, storageService, configurationService, themeService, editorService, editorGroupService); } getTitle(): string | undefined { @@ -89,10 +84,17 @@ export class AbstractTextResourceEditor extends BaseTextEditor { if (!optionsGotApplied) { this.restoreTextResourceEditorViewState(input, textEditor); } + + // Since the resolved model provides information about being readonly + // or not, we apply it here to the editor even though the editor input + // was already asked for being readonly or not. The rationale is that + // a resolved model might have more specific information about being + // readonly or not that the input did not have. + textEditor.updateOptions({ readOnly: resolvedModel.isReadonly() }); } private restoreTextResourceEditorViewState(editor: EditorInput, control: IEditor) { - if (editor instanceof UntitledEditorInput || editor instanceof ResourceEditorInput) { + if (editor instanceof UntitledTextEditorInput || editor instanceof ResourceEditorInput) { const viewState = this.loadTextEditorViewState(editor.getResource()); if (viewState) { control.restoreViewState(viewState); @@ -100,29 +102,11 @@ export class AbstractTextResourceEditor extends BaseTextEditor { } } - setOptions(options: EditorOptions | undefined): void { - const textOptions = options; - if (textOptions && isFunction(textOptions.apply)) { - const textEditor = assertIsDefined(this.getControl()); - textOptions.apply(textEditor, ScrollType.Smooth); - } - } - - protected getConfigurationOverrides(): IEditorOptions { - const options = super.getConfigurationOverrides(); - - options.readOnly = !(this.input instanceof UntitledEditorInput); // all resource editors are readonly except for the untitled one; - - return options; - } - protected getAriaLabel(): string { - const input = this.input; - const isReadonly = !(this.input instanceof UntitledEditorInput); - let ariaLabel: string; - const inputName = input?.getName(); - if (isReadonly) { + + const inputName = this.input?.getName(); + if (this.input?.isReadonly()) { ariaLabel = inputName ? nls.localize('readonlyEditorWithInputAriaLabel', "{0}. Readonly text editor.", inputName) : nls.localize('readonlyEditorAriaLabel', "Readonly text editor."); } else { ariaLabel = inputName ? nls.localize('untitledFileEditorWithInputAriaLabel', "{0}. Untitled file text editor.", inputName) : nls.localize('untitledFileEditorAriaLabel', "Untitled file text editor."); @@ -161,7 +145,7 @@ export class AbstractTextResourceEditor extends BaseTextEditor { protected saveState(): void { // Save View State (only for untitled) - if (this.input instanceof UntitledEditorInput) { + if (this.input instanceof UntitledTextEditorInput) { this.saveTextResourceEditorViewState(this.input); } @@ -169,7 +153,7 @@ export class AbstractTextResourceEditor extends BaseTextEditor { } private saveTextResourceEditorViewState(input: EditorInput | undefined): void { - if (!(input instanceof UntitledEditorInput) && !(input instanceof ResourceEditorInput)) { + if (!(input instanceof UntitledTextEditorInput) && !(input instanceof ResourceEditorInput)) { return; // only enabled for untitled and resource inputs } @@ -202,11 +186,9 @@ export class TextResourceEditor extends AbstractTextResourceEditor { @IStorageService storageService: IStorageService, @ITextResourceConfigurationService configurationService: ITextResourceConfigurationService, @IThemeService themeService: IThemeService, - @ITextFileService textFileService: ITextFileService, @IEditorService editorService: IEditorService, - @IEditorGroupsService editorGroupService: IEditorGroupsService, - @IHostService hostService: IHostService + @IEditorGroupsService editorGroupService: IEditorGroupsService ) { - super(TextResourceEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, editorGroupService, textFileService, editorService, hostService); + super(TextResourceEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, editorGroupService, editorService); } } diff --git a/src/vs/workbench/browser/parts/editor/titleControl.ts b/src/vs/workbench/browser/parts/editor/titleControl.ts index d980297e5ff6..1efb60744e54 100644 --- a/src/vs/workbench/browser/parts/editor/titleControl.ts +++ b/src/vs/workbench/browser/parts/editor/titleControl.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { applyDragImage } from 'vs/base/browser/dnd'; +import { applyDragImage, DataTransfers } from 'vs/base/browser/dnd'; import { addDisposableListener, Dimension, EventType } from 'vs/base/browser/dom'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { ActionsOrientation, IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; @@ -41,6 +41,7 @@ import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; import { IFileService } from 'vs/platform/files/common/files'; import { withNullAsUndefined, withUndefinedAsNull, assertIsDefined } from 'vs/base/common/types'; import { ILabelService } from 'vs/platform/label/common/label'; +import { isFirefox } from 'vs/base/browser/browser'; export interface IToolbarActions { primary: IAction[]; @@ -266,13 +267,20 @@ export abstract class TitleControl extends Themable { } // If tabs are disabled, treat dragging as if an editor tab was dragged + let hasDataTransfer = false; if (!this.accessor.partOptions.showTabs) { const resource = this.group.activeEditor ? toResource(this.group.activeEditor, { supportSideBySide: SideBySideEditor.MASTER }) : null; if (resource) { this.instantiationService.invokeFunction(fillResourceDataTransfers, [resource], e); + hasDataTransfer = true; } } + // Firefox: requires to set a text data transfer to get going + if (!hasDataTransfer && isFirefox) { + e.dataTransfer?.setData(DataTransfers.TEXT, String(this.group.label)); + } + // Drag Image if (this.group.activeEditor) { let label = this.group.activeEditor.getName(); @@ -280,7 +288,7 @@ export abstract class TitleControl extends Themable { label = localize('draggedEditorGroup', "{0} (+{1})", label, this.group.count - 1); } - applyDragImage(e, withUndefinedAsNull(label), 'monaco-editor-group-drag-image'); + applyDragImage(e, label, 'monaco-editor-group-drag-image'); } })); diff --git a/src/vs/workbench/browser/parts/notifications/media/notificationsActions.css b/src/vs/workbench/browser/parts/notifications/media/notificationsActions.css index 276048344749..5fcd3fe36279 100644 --- a/src/vs/workbench/browser/parts/notifications/media/notificationsActions.css +++ b/src/vs/workbench/browser/parts/notifications/media/notificationsActions.css @@ -11,6 +11,7 @@ height: 22px; margin-right: 4px; margin-left: 4px; + color: inherit; background-position: center; background-repeat: no-repeat; } diff --git a/src/vs/workbench/browser/parts/notifications/media/notificationsList.css b/src/vs/workbench/browser/parts/notifications/media/notificationsList.css index ca8807406fc7..d25fc80a9333 100644 --- a/src/vs/workbench/browser/parts/notifications/media/notificationsList.css +++ b/src/vs/workbench/browser/parts/notifications/media/notificationsList.css @@ -51,6 +51,7 @@ text-overflow: ellipsis; flex: 1; /* let the message always grow */ user-select: text; + -webkit-user-select: text; } .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-message a:focus { @@ -71,7 +72,8 @@ } .monaco-workbench .notifications-list-container .notification-list-item:hover .notification-list-item-toolbar-container, -.monaco-workbench .notifications-list-container .monaco-list-row.focused .notification-list-item .notification-list-item-toolbar-container { +.monaco-workbench .notifications-list-container .monaco-list-row.focused .notification-list-item .notification-list-item-toolbar-container, +.monaco-workbench .notifications-list-container .notification-list-item.expanded .notification-list-item-toolbar-container { display: block; } @@ -105,7 +107,8 @@ } .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-buttons-container .monaco-button { - max-width: fit-content; + width: fit-content; + width: -moz-fit-content; padding: 5px 10px; margin: 4px 5px; /* allows button focus outline to be visible */ font-size: 12px; diff --git a/src/vs/workbench/browser/parts/notifications/media/notificationsToasts.css b/src/vs/workbench/browser/parts/notifications/media/notificationsToasts.css index f6d6a712a482..44540d592fe9 100644 --- a/src/vs/workbench/browser/parts/notifications/media/notificationsToasts.css +++ b/src/vs/workbench/browser/parts/notifications/media/notificationsToasts.css @@ -27,10 +27,9 @@ .monaco-workbench > .notifications-toasts .notification-toast-container > .notification-toast { margin: 5px; /* enables separation and drop shadows around toasts */ - transform: translateY(100%); /* move the notification 50px to the bottom (to prevent bleed through) */ + transform: translate3d(0px, 100%, 0px); /* move the notification 50px to the bottom (to prevent bleed through) */ opacity: 0; /* fade the toast in */ transition: transform 300ms ease-out, opacity 300ms ease-out; - will-change: transform, opacity; /* force a separate layer for the toast to speed things up */ } .monaco-workbench > .notifications-toasts .notification-toast-container > .notification-toast.notification-fade-in { @@ -42,4 +41,4 @@ opacity: 1; transform: none; transition: none; -} \ No newline at end of file +} diff --git a/src/vs/workbench/browser/parts/notifications/notificationsList.ts b/src/vs/workbench/browser/parts/notifications/notificationsList.ts index c3c2b0934464..cec8ad6cb649 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsList.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsList.ts @@ -73,7 +73,7 @@ export class NotificationsList extends Themable { const renderer = this.instantiationService.createInstance(NotificationRenderer, actionRunner); // List - const list = this.list = this._register(this.instantiationService.createInstance( + const list = this.list = this._register(this.instantiationService.createInstance>( WorkbenchList, 'NotificationsList', this.listContainer, @@ -82,7 +82,10 @@ export class NotificationsList extends Themable { { ...this.options, setRowLineHeight: false, - horizontalScrolling: false + horizontalScrolling: false, + overrideStyles: { + listBackground: NOTIFICATIONS_BACKGROUND + } } )); @@ -218,13 +221,13 @@ export class NotificationsList extends Themable { protected updateStyles(): void { if (this.listContainer) { const foreground = this.getColor(NOTIFICATIONS_FOREGROUND); - this.listContainer.style.color = foreground ? foreground.toString() : null; + this.listContainer.style.color = foreground ? foreground : null; const background = this.getColor(NOTIFICATIONS_BACKGROUND); - this.listContainer.style.background = background ? background.toString() : ''; + this.listContainer.style.background = background ? background : ''; const outlineColor = this.getColor(contrastBorder); - this.listContainer.style.outlineColor = outlineColor ? outlineColor.toString() : ''; + this.listContainer.style.outlineColor = outlineColor ? outlineColor : ''; } } diff --git a/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts b/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts index f43e42ddfb63..9e05721438b5 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts @@ -11,7 +11,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { NotificationsList } from 'vs/workbench/browser/parts/notifications/notificationsList'; import { Event } from 'vs/base/common/event'; import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; -import { Themable, NOTIFICATIONS_TOAST_BORDER } from 'vs/workbench/common/theme'; +import { Themable, NOTIFICATIONS_TOAST_BORDER, NOTIFICATIONS_BACKGROUND } from 'vs/workbench/common/theme'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { widgetShadow } from 'vs/platform/theme/common/colorRegistry'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -421,6 +421,9 @@ export class NotificationsToasts extends Themable { protected updateStyles(): void { this.mapNotificationToToast.forEach(t => { + const backgroundColor = this.getColor(NOTIFICATIONS_BACKGROUND); + t.toast.style.background = backgroundColor ? backgroundColor : ''; + const widgetShadowColor = this.getColor(widgetShadow); t.toast.style.boxShadow = widgetShadowColor ? `0 0px 8px ${widgetShadowColor}` : ''; diff --git a/src/vs/workbench/browser/parts/panel/media/ellipsis-dark.svg b/src/vs/workbench/browser/parts/panel/media/ellipsis-dark.svg deleted file mode 100644 index 2c52e359f610..000000000000 --- a/src/vs/workbench/browser/parts/panel/media/ellipsis-dark.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/vs/workbench/browser/parts/panel/media/ellipsis-hc.svg b/src/vs/workbench/browser/parts/panel/media/ellipsis-hc.svg deleted file mode 100644 index 3d7068f6b4cd..000000000000 --- a/src/vs/workbench/browser/parts/panel/media/ellipsis-hc.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/vs/workbench/browser/parts/panel/media/ellipsis-light.svg b/src/vs/workbench/browser/parts/panel/media/ellipsis-light.svg deleted file mode 100644 index 883d2722ce30..000000000000 --- a/src/vs/workbench/browser/parts/panel/media/ellipsis-light.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/vs/workbench/browser/parts/panel/media/panelpart.css b/src/vs/workbench/browser/parts/panel/media/panelpart.css index 111a3a4d5897..fab8443ce62b 100644 --- a/src/vs/workbench/browser/parts/panel/media/panelpart.css +++ b/src/vs/workbench/browser/parts/panel/media/panelpart.css @@ -12,6 +12,20 @@ z-index: initial; } +.monaco-workbench.windows.chromium .part.panel, +.monaco-workbench.linux.chromium .part.panel { + /* + * Explicitly put the part onto its own layer to help Chrome to + * render the content with LCD-anti-aliasing. By partioning the + * workbench into multiple layers, we can ensure that a bad + * behaving part is not making another part fallback to greyscale + * rendering. + * + * macOS: does not render LCD-anti-aliased. + */ + transform: translate3d(0px, 0px, 0px); +} + .monaco-workbench .part.panel .title { height: 35px; display: flex; @@ -43,24 +57,14 @@ /** Panel Switcher */ -.monaco-workbench .part.panel > .title > .panel-switcher-container.composite-bar > .monaco-action-bar .action-label.toggle-more { - background-image: url('ellipsis-light.svg'); - display: block; - height: 28px; - min-width: 28px; +.monaco-workbench .part.panel > .title > .panel-switcher-container.composite-bar > .monaco-action-bar .action-label.codicon-more { + display: flex; + align-items: center; + justify-content: center; margin-left: 0px; margin-right: 0px; - background-size: 16px; - background-repeat: no-repeat; - background-position: center center; -} + color: inherit !important; -.vs-dark .monaco-workbench .part.panel > .title > .panel-switcher-container.composite-bar > .monaco-action-bar .action-label.toggle-more { - background-image: url('ellipsis-dark.svg'); -} - -.hc-black .monaco-workbench .part.panel > .title > .panel-switcher-container.composite-bar > .monaco-action-bar .action-label.toggle-more { - background-image: url('ellipsis-hc.svg'); } .monaco-workbench .part.panel > .title > .panel-switcher-container > .monaco-action-bar { @@ -110,7 +114,7 @@ text-align: center; display: inline-block; box-sizing: border-box; - } +} /** Actions */ @@ -121,7 +125,8 @@ .monaco-workbench .panel .monaco-action-bar .action-item .monaco-select-box { cursor: pointer; min-width: 110px; - margin-right: 10px; + min-height: 18px; + padding: 2px 23px 2px 8px; } /* Rotate icons when panel is on right */ diff --git a/src/vs/workbench/browser/parts/panel/panelActions.ts b/src/vs/workbench/browser/parts/panel/panelActions.ts index ecc291fd5734..7200d3b4b51f 100644 --- a/src/vs/workbench/browser/parts/panel/panelActions.ts +++ b/src/vs/workbench/browser/parts/panel/panelActions.ts @@ -243,14 +243,14 @@ export class NextPanelViewAction extends SwitchPanelViewAction { } const actionRegistry = Registry.as(WorkbenchExtensions.WorkbenchActions); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(TogglePanelAction, TogglePanelAction.ID, TogglePanelAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_J }), 'View: Toggle Panel', nls.localize('view', "View")); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FocusPanelAction, FocusPanelAction.ID, FocusPanelAction.LABEL), 'View: Focus into Panel', nls.localize('view', "View")); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ToggleMaximizedPanelAction, ToggleMaximizedPanelAction.ID, ToggleMaximizedPanelAction.LABEL), 'View: Toggle Maximized Panel', nls.localize('view', "View")); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ClosePanelAction, ClosePanelAction.ID, ClosePanelAction.LABEL), 'View: Close Panel', nls.localize('view', "View")); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(TogglePanelPositionAction, TogglePanelPositionAction.ID, TogglePanelPositionAction.LABEL), 'View: Toggle Panel Position', nls.localize('view', "View")); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ToggleMaximizedPanelAction, ToggleMaximizedPanelAction.ID, undefined), 'View: Toggle Panel Position', nls.localize('view', "View")); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(PreviousPanelViewAction, PreviousPanelViewAction.ID, PreviousPanelViewAction.LABEL), 'View: Previous Panel View', nls.localize('view', "View")); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(NextPanelViewAction, NextPanelViewAction.ID, NextPanelViewAction.LABEL), 'View: Next Panel View', nls.localize('view', "View")); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(TogglePanelAction, TogglePanelAction.ID, TogglePanelAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_J }), 'View: Toggle Panel', nls.localize('view', "View")); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(FocusPanelAction, FocusPanelAction.ID, FocusPanelAction.LABEL), 'View: Focus into Panel', nls.localize('view', "View")); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleMaximizedPanelAction, ToggleMaximizedPanelAction.ID, ToggleMaximizedPanelAction.LABEL), 'View: Toggle Maximized Panel', nls.localize('view', "View")); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ClosePanelAction, ClosePanelAction.ID, ClosePanelAction.LABEL), 'View: Close Panel', nls.localize('view', "View")); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(TogglePanelPositionAction, TogglePanelPositionAction.ID, TogglePanelPositionAction.LABEL), 'View: Toggle Panel Position', nls.localize('view', "View")); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleMaximizedPanelAction, ToggleMaximizedPanelAction.ID, undefined), 'View: Toggle Panel Position', nls.localize('view', "View")); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(PreviousPanelViewAction, PreviousPanelViewAction.ID, PreviousPanelViewAction.LABEL), 'View: Previous Panel View', nls.localize('view', "View")); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(NextPanelViewAction, NextPanelViewAction.ID, NextPanelViewAction.LABEL), 'View: Next Panel View', nls.localize('view', "View")); MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { group: '2_workbench_layout', diff --git a/src/vs/workbench/browser/parts/quickinput/media/quickInput.css b/src/vs/workbench/browser/parts/quickinput/media/quickInput.css index ee494b1d8bcc..bc07da5c6859 100644 --- a/src/vs/workbench/browser/parts/quickinput/media/quickInput.css +++ b/src/vs/workbench/browser/parts/quickinput/media/quickInput.css @@ -182,6 +182,10 @@ align-items: center; } +.quick-input-list .quick-input-list-rows > .quick-input-list-row .codicon { + vertical-align: sub; +} + .quick-input-list .quick-input-list-rows .monaco-highlighted-label span { opacity: 1; } @@ -216,8 +220,7 @@ margin: 0; width: 19px; height: 100%; - background-position: center; - background-repeat: no-repeat; + vertical-align: middle; } .quick-input-list .quick-input-list-entry-action-bar { diff --git a/src/vs/workbench/browser/parts/quickinput/quickInput.ts b/src/vs/workbench/browser/parts/quickinput/quickInput.ts index 61fe89a09fc2..b32cdd2b9930 100644 --- a/src/vs/workbench/browser/parts/quickinput/quickInput.ts +++ b/src/vs/workbench/browser/parts/quickinput/quickInput.ts @@ -62,13 +62,20 @@ const backButton = { interface QuickInputUI { container: HTMLElement; leftActionBar: ActionBar; + titleBar: HTMLElement; title: HTMLElement; rightActionBar: ActionBar; checkAll: HTMLInputElement; + filterContainer: HTMLElement; inputBox: QuickInputBox; + visibleCountContainer: HTMLElement; visibleCount: CountBadge; + countContainer: HTMLElement; count: CountBadge; + okContainer: HTMLElement; + ok: Button; message: HTMLElement; + customButtonContainer: HTMLElement; customButton: Button; progressBar: ProgressBar; list: QuickInputList; @@ -100,12 +107,12 @@ type Visibilities = { class QuickInput extends Disposable implements IQuickInput { - private _title: string; - private _steps: number; - private _totalSteps: number; + private _title: string | undefined; + private _steps: number | undefined; + private _totalSteps: number | undefined; protected visible = false; private _enabled = true; - private _contextKey: string; + private _contextKey: string | undefined; private _busy = false; private _ignoreFocusOut = false; private _buttons: IQuickInputButton[] = []; @@ -115,7 +122,7 @@ class QuickInput extends Disposable implements IQuickInput { protected readonly visibleDisposables = this._register(new DisposableStore()); - private busyDelay: TimeoutTimer | null; + private busyDelay: TimeoutTimer | undefined; constructor( protected ui: QuickInputUI @@ -127,7 +134,7 @@ class QuickInput extends Disposable implements IQuickInput { return this._title; } - set title(title: string) { + set title(title: string | undefined) { this._title = title; this.update(); } @@ -136,7 +143,7 @@ class QuickInput extends Disposable implements IQuickInput { return this._steps; } - set step(step: number) { + set step(step: number | undefined) { this._steps = step; this.update(); } @@ -145,7 +152,7 @@ class QuickInput extends Disposable implements IQuickInput { return this._totalSteps; } - set totalSteps(totalSteps: number) { + set totalSteps(totalSteps: number | undefined) { this._totalSteps = totalSteps; this.update(); } @@ -163,7 +170,7 @@ class QuickInput extends Disposable implements IQuickInput { return this._contextKey; } - set contextKey(contextKey: string) { + set contextKey(contextKey: string | undefined) { this._contextKey = contextKey; this.update(); } @@ -248,7 +255,7 @@ class QuickInput extends Disposable implements IQuickInput { if (!this.busy && this.busyDelay) { this.ui.progressBar.stop(); this.busyDelay.cancel(); - this.busyDelay = null; + this.busyDelay = undefined; } if (this.buttonsUpdated) { this.buttonsUpdated = false; @@ -326,7 +333,7 @@ class QuickPick extends QuickInput implements IQuickPi private static readonly INPUT_BOX_ARIA_LABEL = localize('quickInputBox.ariaLabel', "Type to narrow down results."); private _value = ''; - private _placeholder: string; + private _placeholder: string | undefined; private readonly onDidChangeValueEmitter = this._register(new Emitter()); private readonly onDidAcceptEmitter = this._register(new Emitter()); private readonly onDidCustomEmitter = this._register(new Emitter()); @@ -336,6 +343,7 @@ class QuickPick extends QuickInput implements IQuickPi private _matchOnDescription = false; private _matchOnDetail = false; private _matchOnLabel = true; + private _sortByLabel = true; private _autoFocusOnList = true; private _activeItems: T[] = []; private activeItemsUpdated = false; @@ -346,15 +354,15 @@ class QuickPick extends QuickInput implements IQuickPi private selectedItemsToConfirm: T[] | null = []; private readonly onDidChangeSelectionEmitter = this._register(new Emitter()); private readonly onDidTriggerItemButtonEmitter = this._register(new Emitter>()); - private _valueSelection: Readonly<[number, number]>; + private _valueSelection: Readonly<[number, number]> | undefined; private valueSelectionUpdated = true; - private _validationMessage: string; - private _ok: boolean; - private _customButton: boolean; - private _customButtonLabel: string; - private _customButtonHover: string; + private _validationMessage: string | undefined; + private _ok = false; + private _customButton = false; + private _customButtonLabel: string | undefined; + private _customButtonHover: string | undefined; - quickNavigate: IQuickNavigateConfiguration; + quickNavigate: IQuickNavigateConfiguration | undefined; get value() { @@ -370,7 +378,7 @@ class QuickPick extends QuickInput implements IQuickPi return this._placeholder; } - set placeholder(placeholder: string) { + set placeholder(placeholder: string | undefined) { this._placeholder = placeholder; this.update(); } @@ -427,6 +435,16 @@ class QuickPick extends QuickInput implements IQuickPi this.update(); } + get sortByLabel() { + return this._sortByLabel; + } + + set sortByLabel(sortByLabel: boolean) { + this._sortByLabel = sortByLabel; + this.update(); + } + + get autoFocusOnList() { return this._autoFocusOnList; } @@ -472,7 +490,7 @@ class QuickPick extends QuickInput implements IQuickPi return this._validationMessage; } - set validationMessage(validationMessage: string) { + set validationMessage(validationMessage: string | undefined) { this._validationMessage = validationMessage; this.update(); } @@ -490,7 +508,7 @@ class QuickPick extends QuickInput implements IQuickPi return this._customButtonLabel; } - set customLabel(label: string) { + set customLabel(label: string | undefined) { this._customButtonLabel = label; this.update(); } @@ -499,7 +517,7 @@ class QuickPick extends QuickInput implements IQuickPi return this._customButtonHover; } - set customHover(hover: string) { + set customHover(hover: string | undefined) { this._customButtonHover = hover; this.update(); } @@ -654,7 +672,7 @@ class QuickPick extends QuickInput implements IQuickPi // Select element when keys are pressed that signal it const quickNavKeys = this.quickNavigate.keybindings; - const wasTriggerKeyPressed = keyCode === KeyCode.Enter || quickNavKeys.some(k => { + const wasTriggerKeyPressed = quickNavKeys.some(k => { const [firstPart, chordPart] = k.getParts(); if (chordPart) { return false; @@ -692,10 +710,11 @@ class QuickPick extends QuickInput implements IQuickPi } protected update() { - super.update(); if (!this.visible) { return; } + this.ui.setVisibilities(this.canSelectMany ? { title: !!this.title || !!this.step, checkAll: true, inputBox: true, visibleCount: true, count: true, ok: true, list: true, message: !!this.validationMessage, customButton: this.customButton } : { title: !!this.title || !!this.step, inputBox: true, visibleCount: true, list: true, message: !!this.validationMessage, customButton: this.customButton, ok: this.ok }); + super.update(); if (this.ui.inputBox.value !== this.value) { this.ui.inputBox.value = this.value; } @@ -749,14 +768,14 @@ class QuickPick extends QuickInput implements IQuickPi this.ui.message.textContent = null; this.showMessageDecoration(Severity.Ignore); } - this.ui.customButton.label = this.customLabel; - this.ui.customButton.element.title = this.customHover; + this.ui.customButton.label = this.customLabel || ''; + this.ui.customButton.element.title = this.customHover || ''; this.ui.list.matchOnDescription = this.matchOnDescription; this.ui.list.matchOnDetail = this.matchOnDetail; this.ui.list.matchOnLabel = this.matchOnLabel; + this.ui.list.sortByLabel = this.sortByLabel; this.ui.setComboboxAccessibility(true); this.ui.inputBox.setAttribute('aria-label', QuickPick.INPUT_BOX_ARIA_LABEL); - this.ui.setVisibilities(this.canSelectMany ? { title: !!this.title || !!this.step, checkAll: true, inputBox: true, visibleCount: true, count: true, ok: true, list: true, message: !!this.validationMessage } : { title: !!this.title || !!this.step, inputBox: true, visibleCount: true, list: true, message: !!this.validationMessage, customButton: this.customButton, ok: this.ok }); } } @@ -765,13 +784,13 @@ class InputBox extends QuickInput implements IInputBox { private static readonly noPromptMessage = localize('inputModeEntry', "Press 'Enter' to confirm your input or 'Escape' to cancel"); private _value = ''; - private _valueSelection: Readonly<[number, number]>; + private _valueSelection: Readonly<[number, number]> | undefined; private valueSelectionUpdated = true; - private _placeholder: string; + private _placeholder: string | undefined; private _password = false; - private _prompt: string; + private _prompt: string | undefined; private noValidationMessage = InputBox.noPromptMessage; - private _validationMessage: string; + private _validationMessage: string | undefined; private readonly onDidValueChangeEmitter = this._register(new Emitter()); private readonly onDidAcceptEmitter = this._register(new Emitter()); @@ -794,7 +813,7 @@ class InputBox extends QuickInput implements IInputBox { return this._placeholder; } - set placeholder(placeholder: string) { + set placeholder(placeholder: string | undefined) { this._placeholder = placeholder; this.update(); } @@ -812,7 +831,7 @@ class InputBox extends QuickInput implements IInputBox { return this._prompt; } - set prompt(prompt: string) { + set prompt(prompt: string | undefined) { this._prompt = prompt; this.noValidationMessage = prompt ? localize('inputModeEntryDescription', "{0} (Press 'Enter' to confirm or 'Escape' to cancel)", prompt) @@ -824,7 +843,7 @@ class InputBox extends QuickInput implements IInputBox { return this._validationMessage; } - set validationMessage(validationMessage: string) { + set validationMessage(validationMessage: string | undefined) { this._validationMessage = validationMessage; this.update(); } @@ -850,10 +869,11 @@ class InputBox extends QuickInput implements IInputBox { } protected update() { - super.update(); if (!this.visible) { return; } + this.ui.setVisibilities({ title: !!this.title || !!this.step, inputBox: true, message: true }); + super.update(); if (this.ui.inputBox.value !== this.value) { this.ui.inputBox.value = this.value; } @@ -875,7 +895,6 @@ class InputBox extends QuickInput implements IInputBox { this.ui.message.textContent = this.validationMessage; this.showMessageDecoration(Severity.Error); } - this.ui.setVisibilities({ title: !!this.title || !!this.step, inputBox: true, message: true }); } } @@ -887,14 +906,8 @@ export class QuickInputService extends Component implements IQuickInputService { private static readonly MAX_WIDTH = 600; // Max total width of quick open widget private idPrefix = 'quickInput_'; // Constant since there is still only one. - private titleBar: HTMLElement; - private filterContainer: HTMLElement; - private visibleCountContainer: HTMLElement; - private countContainer: HTMLElement; - private okContainer: HTMLElement; - private ok: Button; - private customButtonContainer: HTMLElement; - private ui: QuickInputUI; + private ui: QuickInputUI | undefined; + private dimension?: dom.Dimension; private comboboxAccessibility = false; private enabled = true; private inQuickOpenWidgets: Record = {}; @@ -925,6 +938,7 @@ export class QuickInputService extends Component implements IQuickInputService { this._register(this.quickOpenService.onShow(() => this.inQuickOpen('quickOpen', true))); this._register(this.quickOpenService.onHide(() => this.inQuickOpen('quickOpen', false))); this._register(this.layoutService.onLayout(dimension => this.layout(dimension))); + this.layout(this.layoutService.dimension); this.registerKeyModsListeners(); } @@ -1003,9 +1017,9 @@ export class QuickInputService extends Component implements IQuickInputService { })); } - private create() { + private getUI() { if (this.ui) { - return; + return this.ui; } const workbench = this.layoutService.getWorkbenchElement(); @@ -1013,14 +1027,14 @@ export class QuickInputService extends Component implements IQuickInputService { container.tabIndex = -1; container.style.display = 'none'; - this.titleBar = dom.append(container, $('.quick-input-titlebar')); + const titleBar = dom.append(container, $('.quick-input-titlebar')); - const leftActionBar = this._register(new ActionBar(this.titleBar)); + const leftActionBar = this._register(new ActionBar(titleBar)); leftActionBar.domNode.classList.add('quick-input-left-action-bar'); - const title = dom.append(this.titleBar, $('.quick-input-title')); + const title = dom.append(titleBar, $('.quick-input-title')); - const rightActionBar = this._register(new ActionBar(this.titleBar)); + const rightActionBar = this._register(new ActionBar(titleBar)); rightActionBar.domNode.classList.add('quick-input-right-action-bar'); const headerContainer = dom.append(container, $('.quick-input-header')); @@ -1038,31 +1052,31 @@ export class QuickInputService extends Component implements IQuickInputService { })); const extraContainer = dom.append(headerContainer, $('.quick-input-and-message')); - this.filterContainer = dom.append(extraContainer, $('.quick-input-filter')); + const filterContainer = dom.append(extraContainer, $('.quick-input-filter')); - const inputBox = this._register(new QuickInputBox(this.filterContainer)); + const inputBox = this._register(new QuickInputBox(filterContainer)); inputBox.setAttribute('aria-describedby', `${this.idPrefix}message`); - this.visibleCountContainer = dom.append(this.filterContainer, $('.quick-input-visible-count')); - this.visibleCountContainer.setAttribute('aria-live', 'polite'); - this.visibleCountContainer.setAttribute('aria-atomic', 'true'); - const visibleCount = new CountBadge(this.visibleCountContainer, { countFormat: localize({ key: 'quickInput.visibleCount', comment: ['This tells the user how many items are shown in a list of items to select from. The items can be anything. Currently not visible, but read by screen readers.'] }, "{0} Results") }); + const visibleCountContainer = dom.append(filterContainer, $('.quick-input-visible-count')); + visibleCountContainer.setAttribute('aria-live', 'polite'); + visibleCountContainer.setAttribute('aria-atomic', 'true'); + const visibleCount = new CountBadge(visibleCountContainer, { countFormat: localize({ key: 'quickInput.visibleCount', comment: ['This tells the user how many items are shown in a list of items to select from. The items can be anything. Currently not visible, but read by screen readers.'] }, "{0} Results") }); - this.countContainer = dom.append(this.filterContainer, $('.quick-input-count')); - this.countContainer.setAttribute('aria-live', 'polite'); - const count = new CountBadge(this.countContainer, { countFormat: localize({ key: 'quickInput.countSelected', comment: ['This tells the user how many items are selected in a list of items to select from. The items can be anything.'] }, "{0} Selected") }); + const countContainer = dom.append(filterContainer, $('.quick-input-count')); + countContainer.setAttribute('aria-live', 'polite'); + const count = new CountBadge(countContainer, { countFormat: localize({ key: 'quickInput.countSelected', comment: ['This tells the user how many items are selected in a list of items to select from. The items can be anything.'] }, "{0} Selected") }); this._register(attachBadgeStyler(count, this.themeService)); - this.okContainer = dom.append(headerContainer, $('.quick-input-action')); - this.ok = new Button(this.okContainer); - attachButtonStyler(this.ok, this.themeService); - this.ok.label = localize('ok', "OK"); - this._register(this.ok.onDidClick(e => { + const okContainer = dom.append(headerContainer, $('.quick-input-action')); + const ok = new Button(okContainer); + attachButtonStyler(ok, this.themeService); + ok.label = localize('ok', "OK"); + this._register(ok.onDidClick(e => { this.onDidAcceptEmitter.fire(); })); - this.customButtonContainer = dom.append(headerContainer, $('.quick-input-action')); - const customButton = new Button(this.customButtonContainer); + const customButtonContainer = dom.append(headerContainer, $('.quick-input-action')); + const customButton = new Button(customButtonContainer); attachButtonStyler(customButton, this.themeService); customButton.label = localize('custom', "Custom"); this._register(customButton.onDidClick(e => { @@ -1096,14 +1110,14 @@ export class QuickInputService extends Component implements IQuickInputService { })); this._register(list.onDidChangeFocus(() => { if (this.comboboxAccessibility) { - this.ui.inputBox.setAttribute('aria-activedescendant', this.ui.list.getActiveDescendant() || ''); + this.getUI().inputBox.setAttribute('aria-activedescendant', this.getUI().list.getActiveDescendant() || ''); } })); const focusTracker = dom.trackFocus(container); this._register(focusTracker); this._register(focusTracker.onDidBlur(() => { - if (!this.ui.ignoreFocusOut && !this.environmentService.args['sticky-quickopen'] && this.configurationService.getValue(CLOSE_ON_FOCUS_LOST_CONFIG)) { + if (!this.getUI().ignoreFocusOut && !this.environmentService.args['sticky-quickopen'] && this.configurationService.getValue(CLOSE_ON_FOCUS_LOST_CONFIG)) { this.hide(true); } })); @@ -1129,7 +1143,7 @@ export class QuickInputService extends Component implements IQuickInputService { } else { selectors.push('input[type=text]'); } - if (this.ui.list.isDisplayed()) { + if (this.getUI().list.isDisplayed()) { selectors.push('.monaco-list'); } const stops = container.querySelectorAll(selectors.join(', ')); @@ -1150,13 +1164,20 @@ export class QuickInputService extends Component implements IQuickInputService { this.ui = { container, leftActionBar, + titleBar, title, rightActionBar, checkAll, + filterContainer, inputBox, + visibleCountContainer, visibleCount, + countContainer, count, + okContainer, + ok, message, + customButtonContainer, customButton, progressBar, list, @@ -1174,6 +1195,7 @@ export class QuickInputService extends Component implements IQuickInputService { setContextKey: contextKey => this.setContextKey(contextKey), }; this.updateStyles(); + return this.ui; } pick>(picks: Promise[]> | QuickPickInput[], options: O = {}, token: CancellationToken = CancellationToken.None): Promise { @@ -1334,17 +1356,17 @@ export class QuickInputService extends Component implements IQuickInputService { backButton = backButton; createQuickPick(): IQuickPick { - this.create(); - return new QuickPick(this.ui); + const ui = this.getUI(); + return new QuickPick(ui); } createInputBox(): IInputBox { - this.create(); - return new InputBox(this.ui); + const ui = this.getUI(); + return new InputBox(ui); } private show(controller: QuickInput) { - this.create(); + const ui = this.getUI(); this.quickOpenService.close(); const oldController = this.controller; this.controller = controller; @@ -1353,25 +1375,26 @@ export class QuickInputService extends Component implements IQuickInputService { } this.setEnabled(true); - this.ui.leftActionBar.clear(); - this.ui.title.textContent = ''; - this.ui.rightActionBar.clear(); - this.ui.checkAll.checked = false; - // this.ui.inputBox.value = ''; Avoid triggering an event. - this.ui.inputBox.placeholder = ''; - this.ui.inputBox.password = false; - this.ui.inputBox.showDecoration(Severity.Ignore); - this.ui.visibleCount.setCount(0); - this.ui.count.setCount(0); - this.ui.message.textContent = ''; - this.ui.progressBar.stop(); - this.ui.list.setElements([]); - this.ui.list.matchOnDescription = false; - this.ui.list.matchOnDetail = false; - this.ui.list.matchOnLabel = true; - this.ui.ignoreFocusOut = false; + ui.leftActionBar.clear(); + ui.title.textContent = ''; + ui.rightActionBar.clear(); + ui.checkAll.checked = false; + // ui.inputBox.value = ''; Avoid triggering an event. + ui.inputBox.placeholder = ''; + ui.inputBox.password = false; + ui.inputBox.showDecoration(Severity.Ignore); + ui.visibleCount.setCount(0); + ui.count.setCount(0); + ui.message.textContent = ''; + ui.progressBar.stop(); + ui.list.setElements([]); + ui.list.matchOnDescription = false; + ui.list.matchOnDetail = false; + ui.list.matchOnLabel = true; + ui.list.sortByLabel = true; + ui.ignoreFocusOut = false; this.setComboboxAccessibility(false); - this.ui.inputBox.removeAttribute('aria-label'); + ui.inputBox.removeAttribute('aria-label'); const keybinding = this.keybindingService.lookupKeybinding(BackAction.ID); backButton.tooltip = keybinding ? localize('quickInput.backWithKeybinding', "Back ({0})", keybinding.getLabel()) : localize('quickInput.back', "Back"); @@ -1379,38 +1402,40 @@ export class QuickInputService extends Component implements IQuickInputService { this.inQuickOpen('quickInput', true); this.resetContextKeys(); - this.ui.container.style.display = ''; + ui.container.style.display = ''; this.updateLayout(); - this.ui.inputBox.setFocus(); + ui.inputBox.setFocus(); } private setVisibilities(visibilities: Visibilities) { - this.ui.title.style.display = visibilities.title ? '' : 'none'; - this.ui.checkAll.style.display = visibilities.checkAll ? '' : 'none'; - this.filterContainer.style.display = visibilities.inputBox ? '' : 'none'; - this.visibleCountContainer.style.display = visibilities.visibleCount ? '' : 'none'; - this.countContainer.style.display = visibilities.count ? '' : 'none'; - this.okContainer.style.display = visibilities.ok ? '' : 'none'; - this.customButtonContainer.style.display = visibilities.customButton ? '' : 'none'; - this.ui.message.style.display = visibilities.message ? '' : 'none'; - this.ui.list.display(!!visibilities.list); - this.ui.container.classList[visibilities.checkAll ? 'add' : 'remove']('show-checkboxes'); + const ui = this.getUI(); + ui.title.style.display = visibilities.title ? '' : 'none'; + ui.checkAll.style.display = visibilities.checkAll ? '' : 'none'; + ui.filterContainer.style.display = visibilities.inputBox ? '' : 'none'; + ui.visibleCountContainer.style.display = visibilities.visibleCount ? '' : 'none'; + ui.countContainer.style.display = visibilities.count ? '' : 'none'; + ui.okContainer.style.display = visibilities.ok ? '' : 'none'; + ui.customButtonContainer.style.display = visibilities.customButton ? '' : 'none'; + ui.message.style.display = visibilities.message ? '' : 'none'; + ui.list.display(!!visibilities.list); + ui.container.classList[visibilities.checkAll ? 'add' : 'remove']('show-checkboxes'); this.updateLayout(); // TODO } private setComboboxAccessibility(enabled: boolean) { if (enabled !== this.comboboxAccessibility) { + const ui = this.getUI(); this.comboboxAccessibility = enabled; if (this.comboboxAccessibility) { - this.ui.inputBox.setAttribute('role', 'combobox'); - this.ui.inputBox.setAttribute('aria-haspopup', 'true'); - this.ui.inputBox.setAttribute('aria-autocomplete', 'list'); - this.ui.inputBox.setAttribute('aria-activedescendant', this.ui.list.getActiveDescendant() || ''); + ui.inputBox.setAttribute('role', 'combobox'); + ui.inputBox.setAttribute('aria-haspopup', 'true'); + ui.inputBox.setAttribute('aria-autocomplete', 'list'); + ui.inputBox.setAttribute('aria-activedescendant', ui.list.getActiveDescendant() || ''); } else { - this.ui.inputBox.removeAttribute('role'); - this.ui.inputBox.removeAttribute('aria-haspopup'); - this.ui.inputBox.removeAttribute('aria-autocomplete'); - this.ui.inputBox.removeAttribute('aria-activedescendant'); + ui.inputBox.removeAttribute('role'); + ui.inputBox.removeAttribute('aria-haspopup'); + ui.inputBox.removeAttribute('aria-autocomplete'); + ui.inputBox.removeAttribute('aria-activedescendant'); } } } @@ -1424,16 +1449,16 @@ export class QuickInputService extends Component implements IQuickInputService { private setEnabled(enabled: boolean) { if (enabled !== this.enabled) { this.enabled = enabled; - for (const item of this.ui.leftActionBar.viewItems) { + for (const item of this.getUI().leftActionBar.viewItems) { (item as ActionViewItem).getAction().enabled = enabled; } - for (const item of this.ui.rightActionBar.viewItems) { + for (const item of this.getUI().rightActionBar.viewItems) { (item as ActionViewItem).getAction().enabled = enabled; } - this.ui.checkAll.disabled = !enabled; - // this.ui.inputBox.enabled = enabled; Avoid loosing focus. - this.ok.enabled = enabled; - this.ui.list.enabled = enabled; + this.getUI().checkAll.disabled = !enabled; + // this.getUI().inputBox.enabled = enabled; Avoid loosing focus. + this.getUI().ok.enabled = enabled; + this.getUI().list.enabled = enabled; } } @@ -1443,7 +1468,7 @@ export class QuickInputService extends Component implements IQuickInputService { this.controller = null; this.inQuickOpen('quickInput', false); this.resetContextKeys(); - this.ui.container.style.display = 'none'; + this.getUI().container.style.display = 'none'; if (!focusLost) { this.editorGroupService.activeGroup.focus(); } @@ -1453,19 +1478,19 @@ export class QuickInputService extends Component implements IQuickInputService { focus() { if (this.isDisplayed()) { - this.ui.inputBox.setFocus(); + this.getUI().inputBox.setFocus(); } } toggle() { if (this.isDisplayed() && this.controller instanceof QuickPick && this.controller.canSelectMany) { - this.ui.list.toggleCheckbox(); + this.getUI().list.toggleCheckbox(); } } navigate(next: boolean, quickNavigate?: IQuickNavigateConfiguration) { - if (this.isDisplayed() && this.ui.list.isDisplayed()) { - this.ui.list.focus(next ? 'Next' : 'Previous'); + if (this.isDisplayed() && this.getUI().list.isDisplayed()) { + this.getUI().list.focus(next ? 'Next' : 'Previous'); if (quickNavigate && this.controller instanceof QuickPick) { this.controller.quickNavigate = quickNavigate; } @@ -1488,6 +1513,7 @@ export class QuickInputService extends Component implements IQuickInputService { } layout(dimension: dom.Dimension): void { + this.dimension = dimension; this.updateLayout(); } @@ -1502,7 +1528,7 @@ export class QuickInputService extends Component implements IQuickInputService { style.marginLeft = '-' + (width / 2) + 'px'; this.ui.inputBox.layout(); - this.ui.list.layout(); + this.ui.list.layout(this.dimension && this.dimension.height * 0.4); } } @@ -1511,7 +1537,7 @@ export class QuickInputService extends Component implements IQuickInputService { if (this.ui) { // TODO const titleColor = { dark: 'rgba(255, 255, 255, 0.105)', light: 'rgba(0,0,0,.06)', hc: 'black' }[theme.type]; - this.titleBar.style.backgroundColor = titleColor ? titleColor.toString() : ''; + this.ui.titleBar.style.backgroundColor = titleColor ? titleColor.toString() : ''; this.ui.inputBox.style(theme); const quickInputBackground = theme.getColor(QUICK_INPUT_BACKGROUND); this.ui.container.style.backgroundColor = quickInputBackground ? quickInputBackground.toString() : ''; diff --git a/src/vs/workbench/browser/parts/quickinput/quickInputActions.ts b/src/vs/workbench/browser/parts/quickinput/quickInputActions.ts index 92383601f60f..f071259c669f 100644 --- a/src/vs/workbench/browser/parts/quickinput/quickInputActions.ts +++ b/src/vs/workbench/browser/parts/quickinput/quickInputActions.ts @@ -14,4 +14,4 @@ import { inQuickOpenContext } from 'vs/workbench/browser/parts/quickopen/quickop KeybindingsRegistry.registerCommandAndKeybindingRule(QuickPickManyToggle); const registry = Registry.as(ActionExtensions.WorkbenchActions); -registry.registerWorkbenchAction(new SyncActionDescriptor(BackAction, BackAction.ID, BackAction.LABEL, { primary: 0, win: { primary: KeyMod.Alt | KeyCode.LeftArrow }, mac: { primary: KeyMod.WinCtrl | KeyCode.US_MINUS }, linux: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.US_MINUS } }, inQuickOpenContext, KeybindingWeight.WorkbenchContrib + 50), 'Back'); +registry.registerWorkbenchAction(SyncActionDescriptor.create(BackAction, BackAction.ID, BackAction.LABEL, { primary: 0, win: { primary: KeyMod.Alt | KeyCode.LeftArrow }, mac: { primary: KeyMod.WinCtrl | KeyCode.US_MINUS }, linux: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.US_MINUS } }, inQuickOpenContext, KeybindingWeight.WorkbenchContrib + 50), 'Back'); diff --git a/src/vs/workbench/browser/parts/quickinput/quickInputList.ts b/src/vs/workbench/browser/parts/quickinput/quickInputList.ts index e8a679391acd..3cef2131a537 100644 --- a/src/vs/workbench/browser/parts/quickinput/quickInputList.ts +++ b/src/vs/workbench/browser/parts/quickinput/quickInputList.ts @@ -7,7 +7,7 @@ import 'vs/css!./media/quickInput'; import { IListVirtualDelegate, IListRenderer } from 'vs/base/browser/ui/list/list'; import * as dom from 'vs/base/browser/dom'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; -import { WorkbenchList } from 'vs/platform/list/browser/listService'; +import { WorkbenchList, IWorkbenchListOptions } from 'vs/platform/list/browser/listService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IQuickPickItem, IQuickPickItemButtonEvent, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { IMatch } from 'vs/base/common/filters'; @@ -22,13 +22,13 @@ import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlighte import { memoize } from 'vs/base/common/decorators'; import { range } from 'vs/base/common/arrays'; import * as platform from 'vs/base/common/platform'; -import { listFocusBackground, pickerGroupBorder, pickerGroupForeground, activeContrastBorder } from 'vs/platform/theme/common/colorRegistry'; +import { listFocusBackground, pickerGroupBorder, pickerGroupForeground, activeContrastBorder, listFocusForeground } from 'vs/platform/theme/common/colorRegistry'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { Action } from 'vs/base/common/actions'; import { getIconClass } from 'vs/workbench/browser/parts/quickinput/quickInputUtils'; -import { IListOptions } from 'vs/base/browser/ui/list/listWidget'; import { withNullAsUndefined } from 'vs/base/common/types'; +import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; const $ = dom.$; @@ -44,9 +44,9 @@ interface IListElement { } class ListElement implements IListElement { - index: number; - item: IQuickPickItem; - saneLabel: string; + index!: number; + item!: IQuickPickItem; + saneLabel!: string; saneDescription?: string; saneDetail?: string; hidden = false; @@ -66,7 +66,7 @@ class ListElement implements IListElement { labelHighlights?: IMatch[]; descriptionHighlights?: IMatch[]; detailHighlights?: IMatch[]; - fireButtonTriggered: (event: IQuickPickItemButtonEvent) => void; + fireButtonTriggered!: (event: IQuickPickItemButtonEvent) => void; constructor(init: IListElement) { assign(this, init); @@ -216,12 +216,13 @@ export class QuickInputList { readonly id: string; private container: HTMLElement; private list: WorkbenchList; - private inputElements: Array; + private inputElements: Array = []; private elements: ListElement[] = []; private elementsToIndexes = new Map(); matchOnDescription = false; matchOnDetail = false; matchOnLabel = true; + sortByLabel = true; private readonly _onChangedAllVisibleChecked = new Emitter(); onChangedAllVisibleChecked: Event = this._onChangedAllVisibleChecked.event; private readonly _onChangedCheckedCount = new Emitter(); @@ -251,8 +252,11 @@ export class QuickInputList { openController: { shouldOpen: () => false }, // Workaround #58124 setRowLineHeight: false, multipleSelectionSupport: false, - horizontalScrolling: false - } as IListOptions); + horizontalScrolling: false, + overrideStyles: { + listBackground: SIDE_BAR_BACKGROUND + } + } as IWorkbenchListOptions); this.list.getHTMLElement().id = id; this.disposables.push(this.list); this.disposables.push(this.list.onKeyDown(e => { @@ -468,18 +472,19 @@ export class QuickInputList { this.list.domFocus(); } - layout(): void { + layout(maxHeight?: number): void { + this.list.getHTMLElement().style.maxHeight = maxHeight ? `calc(${Math.floor(maxHeight / 44) * 44}px)` : ''; this.list.layout(); } filter(query: string) { - if (!(this.matchOnLabel || this.matchOnDescription || this.matchOnDetail)) { + if (!(this.sortByLabel || this.matchOnLabel || this.matchOnDescription || this.matchOnDetail)) { return; } query = query.trim(); // Reset filtering - if (!query) { + if (!query || !(this.matchOnLabel || this.matchOnDescription || this.matchOnDetail)) { this.elements.forEach(element => { element.labelHighlights = undefined; element.descriptionHighlights = undefined; @@ -515,7 +520,7 @@ export class QuickInputList { const shownElements = this.elements.filter(element => !element.hidden); // Sort by value - if (query) { + if (this.sortByLabel && query) { const normalizedSearchValue = query.toLowerCase(); shownElements.sort((a, b) => { return compareEntries(a, b, normalizedSearchValue); @@ -590,18 +595,21 @@ function compareEntries(elementA: ListElement, elementB: ListElement, lookFor: s } registerThemingParticipant((theme, collector) => { + // Override inactive focus foreground with active focus foreground for single-pick case. + const listInactiveFocusForeground = theme.getColor(listFocusForeground); + if (listInactiveFocusForeground) { + collector.addRule(`.quick-input-list .monaco-list .monaco-list-row.focused { color: ${listInactiveFocusForeground}; }`); + } // Override inactive focus background with active focus background for single-pick case. const listInactiveFocusBackground = theme.getColor(listFocusBackground); if (listInactiveFocusBackground) { collector.addRule(`.quick-input-list .monaco-list .monaco-list-row.focused { background-color: ${listInactiveFocusBackground}; }`); collector.addRule(`.quick-input-list .monaco-list .monaco-list-row.focused:hover { background-color: ${listInactiveFocusBackground}; }`); } + // dotted instead of solid (as in listWidget.ts) to match QuickOpen const activeContrast = theme.getColor(activeContrastBorder); if (activeContrast) { - collector.addRule(`.quick-input-list .monaco-list .monaco-list-row.focused { border: 1px dotted ${activeContrast}; }`); - collector.addRule(`.quick-input-list .monaco-list .monaco-list-row { border: 1px solid transparent; }`); - collector.addRule(`.quick-input-list .monaco-list .quick-input-list-entry { padding: 0 5px; height: 18px; align-items: center; }`); - collector.addRule(`.quick-input-list .monaco-list .quick-input-list-entry-action-bar { margin-top: 0; }`); + collector.addRule(`.quick-input-list .monaco-list .monaco-list-row.focused { outline: 1px dotted ${activeContrast}; outline-offset: -1px; }`); } const pickerGroupBorderColor = theme.getColor(pickerGroupBorder); if (pickerGroupBorderColor) { diff --git a/src/vs/workbench/browser/parts/quickopen/quickOpenActions.ts b/src/vs/workbench/browser/parts/quickopen/quickOpenActions.ts index 58bda0bffc27..e0da651de76f 100644 --- a/src/vs/workbench/browser/parts/quickopen/quickOpenActions.ts +++ b/src/vs/workbench/browser/parts/quickopen/quickOpenActions.ts @@ -69,11 +69,11 @@ MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: QUICKOPEN_ACTION_ID, title: { value: QUICKOPEN_ACION_LABEL, original: 'Go to File...' } } }); -registry.registerWorkbenchAction(new SyncActionDescriptor(QuickOpenSelectNextAction, QuickOpenSelectNextAction.ID, QuickOpenSelectNextAction.LABEL, { primary: 0, mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_N } }, inQuickOpenContext, KeybindingWeight.WorkbenchContrib + 50), 'Select Next in Quick Open'); -registry.registerWorkbenchAction(new SyncActionDescriptor(QuickOpenSelectPreviousAction, QuickOpenSelectPreviousAction.ID, QuickOpenSelectPreviousAction.LABEL, { primary: 0, mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_P } }, inQuickOpenContext, KeybindingWeight.WorkbenchContrib + 50), 'Select Previous in Quick Open'); -registry.registerWorkbenchAction(new SyncActionDescriptor(QuickOpenNavigateNextAction, QuickOpenNavigateNextAction.ID, QuickOpenNavigateNextAction.LABEL), 'Navigate Next in Quick Open'); -registry.registerWorkbenchAction(new SyncActionDescriptor(QuickOpenNavigatePreviousAction, QuickOpenNavigatePreviousAction.ID, QuickOpenNavigatePreviousAction.LABEL), 'Navigate Previous in Quick Open'); -registry.registerWorkbenchAction(new SyncActionDescriptor(RemoveFromEditorHistoryAction, RemoveFromEditorHistoryAction.ID, RemoveFromEditorHistoryAction.LABEL), 'Remove From History'); +registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickOpenSelectNextAction, QuickOpenSelectNextAction.ID, QuickOpenSelectNextAction.LABEL, { primary: 0, mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_N } }, inQuickOpenContext, KeybindingWeight.WorkbenchContrib + 50), 'Select Next in Quick Open'); +registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickOpenSelectPreviousAction, QuickOpenSelectPreviousAction.ID, QuickOpenSelectPreviousAction.LABEL, { primary: 0, mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_P } }, inQuickOpenContext, KeybindingWeight.WorkbenchContrib + 50), 'Select Previous in Quick Open'); +registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickOpenNavigateNextAction, QuickOpenNavigateNextAction.ID, QuickOpenNavigateNextAction.LABEL), 'Navigate Next in Quick Open'); +registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickOpenNavigatePreviousAction, QuickOpenNavigatePreviousAction.ID, QuickOpenNavigatePreviousAction.LABEL), 'Navigate Previous in Quick Open'); +registry.registerWorkbenchAction(SyncActionDescriptor.create(RemoveFromEditorHistoryAction, RemoveFromEditorHistoryAction.ID, RemoveFromEditorHistoryAction.LABEL), 'Remove From History'); const quickOpenNavigateNextInFilePickerId = 'workbench.action.quickOpenNavigateNextInFilePicker'; KeybindingsRegistry.registerCommandAndKeybindingRule({ @@ -98,4 +98,4 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ primary: globalQuickOpenKeybinding.mac.primary | KeyMod.Shift, secondary: undefined } -}); \ No newline at end of file +}); diff --git a/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts b/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts index 26fd0012cb41..c8cc3ddfc704 100644 --- a/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts +++ b/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts @@ -16,7 +16,7 @@ import { Mode, IEntryRunContext, IAutoFocus, IQuickNavigateConfiguration, IModel import { QuickOpenEntry, QuickOpenModel, QuickOpenEntryGroup, QuickOpenItemAccessorClass } from 'vs/base/parts/quickopen/browser/quickOpenModel'; import { QuickOpenWidget, HideReason } from 'vs/base/parts/quickopen/browser/quickOpenWidget'; import { ContributableActionProvider } from 'vs/workbench/browser/actions'; -import { ITextFileService, AutoSaveMode } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { Registry } from 'vs/platform/registry/common/platform'; import { IResourceInput } from 'vs/platform/editor/common/editor'; import { IModeService } from 'vs/editor/common/services/modeService'; @@ -51,6 +51,7 @@ import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/commo import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cancellation'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IFilesConfigurationService, AutoSaveMode } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; const HELP_PREFIX = '?'; @@ -723,7 +724,7 @@ export class EditorHistoryEntryGroup extends QuickOpenEntryGroup { export class EditorHistoryEntry extends EditorQuickOpenEntry { private input: IEditorInput | IResourceInput; private resource: URI | undefined; - private label: string | undefined; + private label: string; private description?: string; private dirty: boolean; @@ -735,7 +736,8 @@ export class EditorHistoryEntry extends EditorQuickOpenEntry { @ITextFileService private readonly textFileService: ITextFileService, @IConfigurationService private readonly configurationService: IConfigurationService, @ILabelService labelService: ILabelService, - @IFileService fileService: IFileService + @IFileService fileService: IFileService, + @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService ) { super(editorService); @@ -743,7 +745,7 @@ export class EditorHistoryEntry extends EditorQuickOpenEntry { if (input instanceof EditorInput) { this.resource = resourceForEditorHistory(input, fileService); - this.label = types.withNullAsUndefined(input.getName()); + this.label = input.getName(); this.description = input.getDescription(); this.dirty = input.isDirty(); } else { @@ -753,7 +755,7 @@ export class EditorHistoryEntry extends EditorQuickOpenEntry { this.description = labelService.getUriLabel(resources.dirname(this.resource), { relative: true }); this.dirty = this.resource && this.textFileService.isDirty(this.resource); - if (this.dirty && this.textFileService.getAutoSaveMode() === AutoSaveMode.AFTER_SHORT_DELAY) { + if (this.dirty && this.filesConfigurationService.getAutoSaveMode() === AutoSaveMode.AFTER_SHORT_DELAY) { this.dirty = false; // no dirty decoration if auto save is on with a short timeout } } @@ -763,7 +765,7 @@ export class EditorHistoryEntry extends EditorQuickOpenEntry { return this.dirty ? 'dirty' : ''; } - getLabel(): string | undefined { + getLabel(): string { return this.label; } diff --git a/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css b/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css index 457943041c15..1ed0387de31b 100644 --- a/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css +++ b/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css @@ -3,16 +3,30 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.monaco-workbench .sidebar > .content { +.monaco-workbench.windows.chromium .part.sidebar, +.monaco-workbench.linux.chromium .part.sidebar { + /* + * Explicitly put the part onto its own layer to help Chrome to + * render the content with LCD-anti-aliasing. By partioning the + * workbench into multiple layers, we can ensure that a bad + * behaving part is not making another part fallback to greyscale + * rendering. + * + * macOS: does not render LCD-anti-aliased. + */ + transform: translate3d(0px, 0px, 0px); +} + +.monaco-workbench .part.sidebar > .content { overflow: hidden; } -.monaco-workbench.nosidebar > .sidebar { +.monaco-workbench.nosidebar > .part.sidebar { display: none !important; visibility: hidden !important; } -.monaco-workbench .sidebar > .title > .title-label h2 { +.monaco-workbench .part.sidebar > .title > .title-label h2 { text-transform: uppercase; } @@ -54,4 +68,4 @@ background-repeat: no-repeat; width: 16px; height: 16px; -} \ No newline at end of file +} diff --git a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts index 6c7524f61c09..af7a7106d477 100644 --- a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts +++ b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts @@ -320,7 +320,7 @@ class FocusSideBarAction extends Action { } const registry = Registry.as(ActionExtensions.WorkbenchActions); -registry.registerWorkbenchAction(new SyncActionDescriptor(FocusSideBarAction, FocusSideBarAction.ID, FocusSideBarAction.LABEL, { +registry.registerWorkbenchAction(SyncActionDescriptor.create(FocusSideBarAction, FocusSideBarAction.ID, FocusSideBarAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_0 }), 'View: Focus into Side Bar', nls.localize('viewCategory', "View")); diff --git a/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css b/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css index 035487680fd6..d27bc7a9f8cd 100644 --- a/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css +++ b/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css @@ -13,6 +13,20 @@ overflow: visible; } +.monaco-workbench.windows.chromium .part.statusbar, +.monaco-workbench.linux.chromium .part.statusbar { + /* + * Explicitly put the part onto its own layer to help Chrome to + * render the content with LCD-anti-aliasing. By partioning the + * workbench into multiple layers, we can ensure that a bad + * behaving part is not making another part fallback to greyscale + * rendering. + * + * macOS: does not render LCD-anti-aliased. + */ + transform: translate3d(0px, 0px, 0px); +} + .monaco-workbench .part.statusbar.status-border-top::after { content: ''; position: absolute; @@ -28,15 +42,16 @@ .monaco-workbench .part.statusbar > .left-items, .monaco-workbench .part.statusbar > .right-items { display: flex; - flex-wrap: wrap; /* individual entries should not shrink since we do not control their content */ } .monaco-workbench .part.statusbar > .right-items { - flex-direction: row-reverse; /* ensures that the most right elements wrap last when space is little */ + flex-direction: row-reverse; + flex-wrap: wrap /* ensures that the most right elements wrap last when space is little */; } .monaco-workbench .part.statusbar > .left-items { flex-grow: 1; /* left items push right items to the far right end */ + overflow: hidden; /* Hide the overflowing entries */ } .monaco-workbench .part.statusbar > .items-container > .statusbar-item { @@ -90,10 +105,11 @@ .monaco-workbench .part.statusbar > .items-container > .statusbar-item > a { cursor: pointer; - display: inline-block; + display: flex; height: 100%; padding: 0 5px 0 5px; white-space: pre; /* gives some degree of styling */ + align-items: center } .monaco-workbench .part.statusbar > .items-container > .statusbar-item > a:hover { diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts index 5be6b7b2e018..9e16b57eaca8 100644 --- a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts +++ b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts @@ -33,6 +33,7 @@ import { ToggleStatusbarVisibilityAction } from 'vs/workbench/browser/actions/la import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { values } from 'vs/base/common/map'; import { assertIsDefined } from 'vs/base/common/types'; +import { Emitter, Event } from 'vs/base/common/event'; interface IPendingStatusbarEntry { id: string; @@ -60,6 +61,9 @@ class StatusbarViewModel extends Disposable { private hidden!: Set; + private readonly _onDidChangeEntryVisibility = this._register(new Emitter<{ id: string, visible: boolean }>()); + readonly onDidChangeEntryVisibility = this._onDidChangeEntryVisibility.event; + constructor(private storageService: IStorageService) { super(); @@ -117,7 +121,7 @@ class StatusbarViewModel extends Disposable { if (changed.size > 0) { this._entries.forEach(entry => { if (changed.has(entry.id)) { - this.updateVisibility(entry.id); + this.updateVisibility(entry.id, true); changed.delete(entry.id); } @@ -130,7 +134,7 @@ class StatusbarViewModel extends Disposable { this._entries.push(entry); // intentionally not using a map here since multiple entries can have the same ID! // Update visibility directly - this.updateVisibility(entry); + this.updateVisibility(entry, false); // Sort according to priority this.sort(); @@ -159,7 +163,7 @@ class StatusbarViewModel extends Disposable { if (!this.hidden.has(id)) { this.hidden.add(id); - this.updateVisibility(id); + this.updateVisibility(id, true); this.saveState(); } @@ -169,7 +173,7 @@ class StatusbarViewModel extends Disposable { if (this.hidden.has(id)) { this.hidden.delete(id); - this.updateVisibility(id); + this.updateVisibility(id, true); this.saveState(); } @@ -183,9 +187,9 @@ class StatusbarViewModel extends Disposable { return this._entries.filter(entry => entry.alignment === alignment); } - private updateVisibility(id: string): void; - private updateVisibility(entry: IStatusbarViewModelEntry): void; - private updateVisibility(arg1: string | IStatusbarViewModelEntry): void { + private updateVisibility(id: string, trigger: boolean): void; + private updateVisibility(entry: IStatusbarViewModelEntry, trigger: boolean): void; + private updateVisibility(arg1: string | IStatusbarViewModelEntry, trigger: boolean): void { // By identifier if (typeof arg1 === 'string') { @@ -193,7 +197,7 @@ class StatusbarViewModel extends Disposable { for (const entry of this._entries) { if (entry.id === id) { - this.updateVisibility(entry); + this.updateVisibility(entry, trigger); } } } @@ -210,6 +214,10 @@ class StatusbarViewModel extends Disposable { show(entry.container); } + if (trigger) { + this._onDidChangeEntryVisibility.fire({ id: entry.id, visible: !isHidden }); + } + // Mark first/last visible entry this.markFirstLastVisibleEntry(); } @@ -341,6 +349,8 @@ export class StatusbarPart extends Part implements IStatusbarService { private leftItemsContainer: HTMLElement | undefined; private rightItemsContainer: HTMLElement | undefined; + readonly onDidChangeEntryVisibility: Event<{ id: string, visible: boolean }>; + constructor( @IInstantiationService private readonly instantiationService: IInstantiationService, @IThemeService themeService: IThemeService, @@ -352,6 +362,7 @@ export class StatusbarPart extends Part implements IStatusbarService { super(Parts.STATUSBAR_PART, { hasTitle: false }, themeService, storageService, layoutService); this.viewModel = this._register(new StatusbarViewModel(storageService)); + this.onDidChangeEntryVisibility = this.viewModel.onDidChangeEntryVisibility; this.registerListeners(); } @@ -422,6 +433,10 @@ export class StatusbarPart extends Part implements IStatusbarService { }; } + isEntryVisible(id: string): boolean { + return !this.viewModel.isHidden(id); + } + updateEntryVisibility(id: string, visible: boolean): void { if (visible) { this.viewModel.show(id); diff --git a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css index 3a2504b0d070..efc5f85d4d5f 100644 --- a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css +++ b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css @@ -12,12 +12,29 @@ align-items: center; justify-content: center; user-select: none; + -webkit-user-select: none; zoom: 1; /* prevent zooming */ line-height: 22px; height: 22px; display: flex; } +.monaco-workbench.windows .part.titlebar, +.monaco-workbench.linux .part.titlebar { + /* + * Explicitly put the part onto its own layer to help Chrome to + * render the content with LCD-anti-aliasing. By partioning the + * workbench into multiple layers, we can ensure that a bad + * behaving part is not making another part fallback to greyscale + * rendering. + * + * macOS: does not render LCD-anti-aliased. + */ + transform: translate3d(0px, 0px, 0px); + position: relative; + z-index: 1000; /* move the entire titlebar above the workbench, except modals/dialogs */ +} + .monaco-workbench .part.titlebar > .titlebar-drag-region { top: 0; left: 0; @@ -25,10 +42,14 @@ position: absolute; width: 100%; height: 100%; - z-index: -1; -webkit-app-region: drag; } +.monaco-workbench .part.titlebar > .menubar { + /* Move above drag region since negative z-index on that element causes AA issues */ + z-index: 1; +} + .monaco-workbench .part.titlebar > .window-title { flex: 0 1 auto; font-size: 12px; @@ -67,7 +88,7 @@ position: absolute; top: 0; width: 100%; - height: 20%; + height: 4px; } .monaco-workbench.windows.fullscreen .part.titlebar > .resizer, @@ -79,7 +100,7 @@ width: 35px; height: 100%; position: relative; - z-index: 3000; + z-index: 2; /* highest level of titlebar */ background-image: url('code-icon.svg'); background-repeat: no-repeat; background-position: center center; @@ -97,11 +118,12 @@ flex-shrink: 0; text-align: center; position: relative; - z-index: 3000; + z-index: 2; /* highest level of titlebar */ -webkit-app-region: no-drag; height: 100%; width: 138px; margin-left: auto; + transform: translate3d(0px, 0px, 0px); } .monaco-workbench.fullscreen .part.titlebar > .window-controls-container { diff --git a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts index 2529e54f9567..70a7bb13c3b1 100644 --- a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts @@ -12,7 +12,7 @@ import { IAction, Action } from 'vs/base/common/actions'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import * as DOM from 'vs/base/browser/dom'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { isMacintosh, isWeb } from 'vs/base/common/platform'; +import { isMacintosh, isWeb, isIOS } from 'vs/base/common/platform'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; import { Event, Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; @@ -42,6 +42,7 @@ import { IElectronService } from 'vs/platform/electron/node/electron'; import { optional } from 'vs/platform/instantiation/common/instantiation'; // tslint:disable-next-line: import-patterns layering TODO@sbatten import { IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService'; +import { BrowserFeatures } from 'vs/base/browser/canIUse'; export abstract class MenubarControl extends Disposable { @@ -294,6 +295,7 @@ export class CustomMenubarControl extends MenubarControl { @IThemeService private readonly themeService: IThemeService, @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @IHostService protected readonly hostService: IHostService, + @IWorkbenchEnvironmentService private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService, @optional(IElectronService) private readonly electronService: IElectronService, @optional(IElectronEnvironmentService) private readonly electronEnvironmentService: IElectronEnvironmentService ) { @@ -445,9 +447,8 @@ export class CustomMenubarControl extends MenubarControl { return null; case StateType.Idle: - const context = `window:${this.electronEnvironmentService ? this.electronEnvironmentService.windowId : 'any'}`; return new Action('update.check', nls.localize({ key: 'checkForUpdates', comment: ['&& denotes a mnemonic'] }, "Check for &&Updates..."), undefined, true, () => - this.updateService.checkForUpdates(context)); + this.updateService.checkForUpdates(this.workbenchEnvironmentService.configuration.sessionId)); case StateType.CheckingForUpdates: return new Action('update.checking', nls.localize('checkingForUpdates', "Checking for Updates..."), undefined, false); @@ -674,7 +675,7 @@ export class CustomMenubarControl extends MenubarControl { } this._register(DOM.addDisposableListener(window, DOM.EventType.RESIZE, () => { - if (this.menubar) { + if (this.menubar && !(isIOS && BrowserFeatures.pointerEvents)) { this.menubar.blur(); } })); diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index 3b01b0f8379c..97fac3d7f778 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -20,7 +20,7 @@ import { EditorInput, toResource, Verbosity, SideBySideEditor } from 'vs/workben import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; -import { TITLE_BAR_ACTIVE_BACKGROUND, TITLE_BAR_ACTIVE_FOREGROUND, TITLE_BAR_INACTIVE_FOREGROUND, TITLE_BAR_INACTIVE_BACKGROUND, TITLE_BAR_BORDER } from 'vs/workbench/common/theme'; +import { TITLE_BAR_ACTIVE_BACKGROUND, TITLE_BAR_ACTIVE_FOREGROUND, TITLE_BAR_INACTIVE_FOREGROUND, TITLE_BAR_INACTIVE_BACKGROUND, TITLE_BAR_BORDER, WORKBENCH_BACKGROUND } from 'vs/workbench/common/theme'; import { isMacintosh, isWindows, isLinux, isWeb } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { Color } from 'vs/base/common/color'; @@ -39,12 +39,12 @@ import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/m import { IMenuService, IMenu, MenuId } from 'vs/platform/actions/common/actions'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IHostService } from 'vs/workbench/services/host/browser/host'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; // TODO@sbatten https://github.com/microsoft/vscode/issues/81360 // tslint:disable-next-line: import-patterns layering import { IElectronService } from 'vs/platform/electron/node/electron'; -// tslint:disable-next-line: import-patterns layering -import { IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService'; export class TitlebarPart extends Part implements ITitleService { @@ -104,8 +104,8 @@ export class TitlebarPart extends Part implements ITitleService { @IMenuService menuService: IMenuService, @IContextKeyService contextKeyService: IContextKeyService, @IHostService private readonly hostService: IHostService, - @optional(IElectronService) private electronService: IElectronService, - @optional(IElectronEnvironmentService) private readonly electronEnvironmentService: IElectronEnvironmentService + @IProductService private readonly productService: IProductService, + @optional(IElectronService) private electronService: IElectronService ) { super(Parts.TITLEBAR_PART, { hasTitle: false }, themeService, storageService, layoutService); @@ -176,7 +176,7 @@ export class TitlebarPart extends Part implements ITitleService { } private onMenubarFocusChanged(focused: boolean) { - if (!isWeb && (isWindows || isLinux) && this.currentMenubarVisibility === 'compact' && this.dragRegion) { + if (!isWeb && (isWindows || isLinux) && this.currentMenubarVisibility !== 'compact' && this.dragRegion) { if (focused) { hide(this.dragRegion); } else { @@ -207,7 +207,7 @@ export class TitlebarPart extends Part implements ITitleService { // Always set the native window title to identify us properly to the OS let nativeTitle = title; if (!trim(nativeTitle)) { - nativeTitle = this.environmentService.appNameLong; + nativeTitle = this.productService.nameLong; } window.document.title = nativeTitle; @@ -229,15 +229,15 @@ export class TitlebarPart extends Part implements ITitleService { let title = this.doGetWindowTitle(); if (this.properties.isAdmin) { - title = `${title || this.environmentService.appNameLong} ${TitlebarPart.NLS_USER_IS_ADMIN}`; + title = `${title || this.productService.nameLong} ${TitlebarPart.NLS_USER_IS_ADMIN}`; } if (!this.properties.isPure) { - title = `${title || this.environmentService.appNameLong} ${TitlebarPart.NLS_UNSUPPORTED}`; + title = `${title || this.productService.nameLong} ${TitlebarPart.NLS_UNSUPPORTED}`; } if (this.environmentService.isExtensionDevelopment) { - title = `${TitlebarPart.NLS_EXTENSION_HOST} - ${title || this.environmentService.appNameLong}`; + title = `${TitlebarPart.NLS_EXTENSION_HOST} - ${title || this.productService.nameLong}`; } return title; @@ -317,8 +317,8 @@ export class TitlebarPart extends Part implements ITitleService { const folderName = folder ? folder.name : ''; const folderPath = folder ? this.labelService.getUriLabel(folder.uri) : ''; const dirty = editor?.isDirty() ? TitlebarPart.TITLE_DIRTY : ''; - const appName = this.environmentService.appNameLong; - const remoteName = this.environmentService.configuration.remoteAuthority; + const appName = this.productService.nameLong; + const remoteName = this.labelService.getHostLabel(REMOTE_HOST_SCHEME, this.environmentService.configuration.remoteAuthority); const separator = TitlebarPart.TITLE_SEPARATOR; const titleTemplate = this.configurationService.getValue('window.title'); @@ -445,13 +445,8 @@ export class TitlebarPart extends Part implements ITitleService { // Resizer this.resizer = append(this.element, $('div.resizer')); - const isMaximized = this.environmentService.configuration.maximized ? true : false; - this.onDidChangeMaximized(isMaximized); - - this._register(Event.any( - Event.map(Event.filter(this.electronService.onWindowMaximize, id => id === this.electronEnvironmentService.windowId), _ => true), - Event.map(Event.filter(this.electronService.onWindowUnmaximize, id => id === this.electronEnvironmentService.windowId), _ => false) - )(e => this.onDidChangeMaximized(e))); + this._register(this.layoutService.onMaximizeChange(maximized => this.onDidChangeMaximized(maximized))); + this.onDidChangeMaximized(this.layoutService.isWindowMaximized()); } // Since the title area is used to drag the window, we do not want to steal focus from the @@ -507,7 +502,13 @@ export class TitlebarPart extends Part implements ITitleService { removeClass(this.element, 'inactive'); } - const titleBackground = this.getColor(this.isInactive ? TITLE_BAR_INACTIVE_BACKGROUND : TITLE_BAR_ACTIVE_BACKGROUND) || ''; + const titleBackground = this.getColor(this.isInactive ? TITLE_BAR_INACTIVE_BACKGROUND : TITLE_BAR_ACTIVE_BACKGROUND, (color, theme) => { + // LCD Rendering Support: the title bar part is a defining its own GPU layer. + // To benefit from LCD font rendering, we must ensure that we always set an + // opaque background color. As such, we compute an opaque color given we know + // the background color is the workbench background. + return color.isOpaque() ? color : color.makeOpaque(WORKBENCH_BACKGROUND(theme)); + }) || ''; this.element.style.backgroundColor = titleBackground; if (titleBackground && Color.fromHex(titleBackground).isLighter()) { addClass(this.element, 'light'); diff --git a/src/vs/workbench/browser/parts/views/customView.ts b/src/vs/workbench/browser/parts/views/customView.ts index b2616e10a66c..4a0c521ebd54 100644 --- a/src/vs/workbench/browser/parts/views/customView.ts +++ b/src/vs/workbench/browser/parts/views/customView.ts @@ -26,10 +26,10 @@ import { ResourceLabels, IResourceLabel } from 'vs/workbench/browser/labels'; import { ActionBar, IActionViewItemProvider, ActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { URI } from 'vs/base/common/uri'; import { dirname, basename } from 'vs/base/common/resources'; -import { LIGHT, FileThemeIcon, FolderThemeIcon, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { LIGHT, FileThemeIcon, FolderThemeIcon, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { FileKind } from 'vs/platform/files/common/files'; import { WorkbenchAsyncDataTree, TreeResourceNavigator2 } from 'vs/platform/list/browser/listService'; -import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet'; +import { ViewletPane, IViewletPaneOptions } from 'vs/workbench/browser/parts/views/paneViewlet'; import { localize } from 'vs/nls'; import { timeout } from 'vs/base/common/async'; import { textLinkForeground, textCodeBlockBackground, focusBorder, listFilterMatchHighlight, listFilterMatchHighlightBorder } from 'vs/platform/theme/common/colorRegistry'; @@ -41,8 +41,9 @@ import { ITreeRenderer, ITreeNode, IAsyncDataSource, ITreeContextMenuEvent } fro import { FuzzyScore, createMatches } from 'vs/base/common/filters'; import { CollapseAllAction } from 'vs/base/browser/ui/tree/treeDefaults'; import { isFalsyOrWhitespace } from 'vs/base/common/strings'; +import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; -export class CustomTreeViewPanel extends ViewletPanel { +export class CustomTreeViewPane extends ViewletPane { private treeView: ITreeView; @@ -54,7 +55,7 @@ export class CustomTreeViewPanel extends ViewletPanel { @IConfigurationService configurationService: IConfigurationService, @IContextKeyService contextKeyService: IContextKeyService, ) { - super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: options.title }, keybindingService, contextMenuService, configurationService, contextKeyService); + super({ ...(options as IViewletPaneOptions), ariaHeaderLabel: options.title }, keybindingService, contextMenuService, configurationService, contextKeyService); const { treeView } = (Registry.as(Extensions.ViewsRegistry).getView(options.id)); this.treeView = treeView; this._register(this.treeView.onDidChangeActions(() => this.updateActions(), this)); @@ -403,6 +404,9 @@ export class CustomTreeView extends Disposable implements ITreeView { return e.collapsibleState !== TreeItemCollapsibleState.Expanded; }, multipleSelectionSupport: this.canSelectMany, + overrideStyles: { + listBackground: SIDE_BAR_BACKGROUND + } }) as WorkbenchAsyncDataTree); aligner.tree = this.tree; const actionRunner = new MultipleSelectionActionRunner(this.notificationService, () => this.tree!.getSelection()); @@ -610,8 +614,7 @@ export class CustomTreeView extends Disposable implements ITreeView { const tree = this.tree; if (tree && this.visible) { this.refreshing = true; - await Promise.all(elements.map(element => tree.updateChildren(element, true))); - elements.map(element => tree.rerender(element)); + await Promise.all(elements.map(element => tree.updateChildren(element, true, true))); this.refreshing = false; this.updateContentAreas(); if (this.focused) { @@ -777,16 +780,27 @@ class TreeRenderer extends Disposable implements ITreeRenderer('explorer.decorations'); templateData.resourceLabel.setResource({ name: label, description, resource: resource ? resource : URI.parse('missing:_icon_resource') }, { fileKind: this.getFileKind(node), title, hideIcon: !!iconUrl, fileDecorations, extraClasses: ['custom-view-tree-node-item-resourceLabel'], matches: matches ? matches : createMatches(element.filterData) }); } else { templateData.resourceLabel.setResource({ name: label, description }, { title, hideIcon: true, extraClasses: ['custom-view-tree-node-item-resourceLabel'], matches: matches ? matches : createMatches(element.filterData) }); } - templateData.icon.style.backgroundImage = iconUrl ? DOM.asCSSUrl(iconUrl) : ''; templateData.icon.title = title ? title : ''; - DOM.toggleClass(templateData.icon, 'custom-view-tree-node-item-icon', !!iconUrl); + + if (iconUrl) { + templateData.icon.className = 'custom-view-tree-node-item-icon'; + templateData.icon.style.backgroundImage = DOM.asCSSUrl(iconUrl); + + } else { + let iconClass: string | undefined; + if (node.themeIcon && !this.isFileKindThemeIcon(node.themeIcon)) { + iconClass = ThemeIcon.asClassName(node.themeIcon); + } + templateData.icon.className = iconClass ? `custom-view-tree-node-item-icon ${iconClass}` : ''; + } + templateData.actionBar.context = { $treeViewId: this.treeViewId, $treeItemHandle: node.handle }; templateData.actionBar.push(this.menus.getResourceActions(node), { icon: true, label: false }); if (this._actionRunner) { @@ -800,6 +814,14 @@ class TreeRenderer extends Disposable implements ITreeRenderer .panel > .panel-header { - border-top: none !important; /* less clutter: do not show any border for first views in a panel */ +.monaco-pane-view .split-view-view:first-of-type > .pane > .pane-header { + border-top: none !important; /* less clutter: do not show any border for first views in a pane */ } -.monaco-panel-view .panel > .panel-header > .actions.show { +.monaco-pane-view .pane > .pane-header > .actions.show { display: initial; } -.monaco-panel-view .panel > .panel-header h3.title { +.monaco-pane-view .pane > .pane-header h3.title { white-space: nowrap; text-overflow: ellipsis; overflow: hidden; diff --git a/src/vs/workbench/browser/parts/views/media/views.css b/src/vs/workbench/browser/parts/views/media/views.css index fd1b45ad7899..9e1d51f2afc0 100644 --- a/src/vs/workbench/browser/parts/views/media/views.css +++ b/src/vs/workbench/browser/parts/views/media/views.css @@ -32,7 +32,7 @@ .file-icon-themable-tree.hide-arrows .monaco-tl-twistie:not(.force-twistie) { background-image: none !important; width: 0 !important; - margin-right: 0 !important; + padding-right: 0 !important; visibility: hidden; } @@ -44,8 +44,9 @@ .monaco-workbench .tree-explorer-viewlet-tree-view .message { display: flex; - padding: 4px 12px 0px 18px; - user-select: text + padding: 4px 12px 4px 18px; + user-select: text; + -webkit-user-select: text; } .monaco-workbench .tree-explorer-viewlet-tree-view .message p { @@ -94,6 +95,11 @@ padding-left: 3px; } +.customview-tree .monaco-list .monaco-list-row .custom-view-tree-node-item .monaco-inputbox { + line-height: normal; + flex: 1; +} + .customview-tree .monaco-list .monaco-list-row .custom-view-tree-node-item .custom-view-tree-node-item-resourceLabel { flex: 1; text-overflow: ellipsis; @@ -108,6 +114,11 @@ width: 16px; height: 22px; -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.customview-tree .monaco-list .monaco-list-row .custom-view-tree-node-item > .custom-view-tree-node-item-icon.codicon { + margin-top: 3px; } .customview-tree .monaco-list .monaco-list-row .custom-view-tree-node-item .custom-view-tree-node-item-resourceLabel .monaco-icon-label-description-container { diff --git a/src/vs/workbench/browser/parts/views/panelViewlet.ts b/src/vs/workbench/browser/parts/views/paneViewlet.ts similarity index 71% rename from src/vs/workbench/browser/parts/views/panelViewlet.ts rename to src/vs/workbench/browser/parts/views/paneViewlet.ts index 1d486b812cc5..1a445057dfe1 100644 --- a/src/vs/workbench/browser/parts/views/panelViewlet.ts +++ b/src/vs/workbench/browser/parts/views/paneViewlet.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!./media/panelviewlet'; +import 'vs/css!./media/paneviewlet'; import * as nls from 'vs/nls'; import { Event, Emitter } from 'vs/base/common/event'; import { ColorIdentifier } from 'vs/platform/theme/common/colorRegistry'; @@ -22,7 +22,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { PanelView, IPanelViewOptions, IPanelOptions, Panel } from 'vs/base/browser/ui/splitview/panelview'; +import { PaneView, IPaneViewOptions, IPaneOptions, Pane } from 'vs/base/browser/ui/splitview/paneview'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; @@ -31,21 +31,21 @@ import { IStorageService } from 'vs/platform/storage/common/storage'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { assertIsDefined } from 'vs/base/common/types'; -export interface IPanelColors extends IColorMapping { +export interface IPaneColors extends IColorMapping { dropBackground?: ColorIdentifier; headerForeground?: ColorIdentifier; headerBackground?: ColorIdentifier; headerBorder?: ColorIdentifier; } -export interface IViewletPanelOptions extends IPanelOptions { +export interface IViewletPaneOptions extends IPaneOptions { actionRunner?: IActionRunner; id: string; title: string; showActionsAlways?: boolean; } -export abstract class ViewletPanel extends Panel implements IView { +export abstract class ViewletPane extends Pane implements IView { private static readonly AlwaysShowActionsConfig = 'workbench.view.alwaysShowHeaderActions'; @@ -65,7 +65,7 @@ export abstract class ViewletPanel extends Panel implements IView { private _isVisible: boolean = false; readonly id: string; - readonly title: string; + title: string; protected actionRunner?: IActionRunner; protected toolbar?: ToolBar; @@ -75,7 +75,7 @@ export abstract class ViewletPanel extends Panel implements IView { protected twistiesContainer?: HTMLElement; constructor( - options: IViewletPanelOptions, + options: IViewletPaneOptions, @IKeybindingService protected keybindingService: IKeybindingService, @IContextMenuService protected contextMenuService: IContextMenuService, @IConfigurationService protected readonly configurationService: IConfigurationService, @@ -152,7 +152,7 @@ export abstract class ViewletPanel extends Panel implements IView { this._register(this.toolbar); this.setActions(); - const onDidRelevantConfigurationChange = Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration(ViewletPanel.AlwaysShowActionsConfig)); + const onDidRelevantConfigurationChange = Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration(ViewletPane.AlwaysShowActionsConfig)); this._register(onDidRelevantConfigurationChange(this.updateActionsVisibility, this)); this.updateActionsVisibility(); } @@ -169,6 +169,7 @@ export abstract class ViewletPanel extends Panel implements IView { if (this.titleContainer) { this.titleContainer.textContent = title; } + this.title = title; this._onDidChangeTitleArea.fire(); } @@ -224,31 +225,31 @@ export abstract class ViewletPanel extends Panel implements IView { } } -export interface IViewsViewletOptions extends IPanelViewOptions { +export interface IViewsViewletOptions extends IPaneViewOptions { showHeaderInTitleWhenSingleView: boolean; } -interface IViewletPanelItem { - panel: ViewletPanel; +interface IViewletPaneItem { + pane: ViewletPane; disposable: IDisposable; } -export class PanelViewlet extends Viewlet { +export class PaneViewlet extends Viewlet { - private lastFocusedPanel: ViewletPanel | undefined; - private panelItems: IViewletPanelItem[] = []; - private panelview?: PanelView; + private lastFocusedPane: ViewletPane | undefined; + private paneItems: IViewletPaneItem[] = []; + private paneview?: PaneView; get onDidSashChange(): Event { - return assertIsDefined(this.panelview).onDidSashChange; + return assertIsDefined(this.paneview).onDidSashChange; } - protected get panels(): ViewletPanel[] { - return this.panelItems.map(i => i.panel); + protected get panes(): ViewletPane[] { + return this.paneItems.map(i => i.pane); } protected get length(): number { - return this.panelItems.length; + return this.paneItems.length; } constructor( @@ -266,15 +267,15 @@ export class PanelViewlet extends Viewlet { create(parent: HTMLElement): void { super.create(parent); - this.panelview = this._register(new PanelView(parent, this.options)); - this._register(this.panelview.onDidDrop(({ from, to }) => this.movePanel(from as ViewletPanel, to as ViewletPanel))); + this.paneview = this._register(new PaneView(parent, this.options)); + this._register(this.paneview.onDidDrop(({ from, to }) => this.movePane(from as ViewletPane, to as ViewletPane))); this._register(addDisposableListener(parent, EventType.CONTEXT_MENU, (e: MouseEvent) => this.showContextMenu(new StandardMouseEvent(e)))); } private showContextMenu(event: StandardMouseEvent): void { - for (const panelItem of this.panelItems) { - // Do not show context menu if target is coming from inside panel views - if (isAncestor(event.target, panelItem.panel.element)) { + for (const paneItem of this.paneItems) { + // Do not show context menu if target is coming from inside pane views + if (isAncestor(event.target, paneItem.pane.element)) { return; } } @@ -293,8 +294,8 @@ export class PanelViewlet extends Viewlet { let title = Registry.as(Extensions.Viewlets).getViewlet(this.getId()).name; if (this.isSingleView()) { - const panelItemTitle = this.panelItems[0].panel.title; - title = panelItemTitle ? `${title}: ${panelItemTitle}` : title; + const paneItemTitle = this.paneItems[0].pane.title; + title = paneItemTitle ? `${title}: ${paneItemTitle}` : title; } return title; @@ -302,7 +303,7 @@ export class PanelViewlet extends Viewlet { getActions(): IAction[] { if (this.isSingleView()) { - return this.panelItems[0].panel.getActions(); + return this.paneItems[0].pane.getActions(); } return []; @@ -310,7 +311,7 @@ export class PanelViewlet extends Viewlet { getSecondaryActions(): IAction[] { if (this.isSingleView()) { - return this.panelItems[0].panel.getSecondaryActions(); + return this.paneItems[0].pane.getSecondaryActions(); } return []; @@ -318,7 +319,7 @@ export class PanelViewlet extends Viewlet { getActionViewItem(action: IAction): IActionViewItem | undefined { if (this.isSingleView()) { - return this.panelItems[0].panel.getActionViewItem(action); + return this.paneItems[0].pane.getActionViewItem(action); } return super.getActionViewItem(action); @@ -327,12 +328,12 @@ export class PanelViewlet extends Viewlet { focus(): void { super.focus(); - if (this.lastFocusedPanel) { - this.lastFocusedPanel.focus(); - } else if (this.panelItems.length > 0) { - for (const { panel } of this.panelItems) { - if (panel.isExpanded()) { - panel.focus(); + if (this.lastFocusedPane) { + this.lastFocusedPane.focus(); + } else if (this.paneItems.length > 0) { + for (const { pane: pane } of this.paneItems) { + if (pane.isExpanded()) { + pane.focus(); return; } } @@ -340,23 +341,23 @@ export class PanelViewlet extends Viewlet { } layout(dimension: Dimension): void { - if (this.panelview) { - this.panelview.layout(dimension.height, dimension.width); + if (this.paneview) { + this.paneview.layout(dimension.height, dimension.width); } } getOptimalWidth(): number { - const sizes = this.panelItems - .map(panelItem => panelItem.panel.getOptimalWidth() || 0); + const sizes = this.paneItems + .map(paneItem => paneItem.pane.getOptimalWidth() || 0); return Math.max(...sizes); } - addPanels(panels: { panel: ViewletPanel, size: number, index?: number; }[]): void { + addPanes(panes: { pane: ViewletPane, size: number, index?: number; }[]): void { const wasSingleView = this.isSingleView(); - for (const { panel, size, index } of panels) { - this.addPanel(panel, size, index); + for (const { pane: pane, size, index } of panes) { + this.addPane(pane, size, index); } this.updateViewHeaders(); @@ -365,36 +366,36 @@ export class PanelViewlet extends Viewlet { } } - private addPanel(panel: ViewletPanel, size: number, index = this.panelItems.length - 1): void { - const onDidFocus = panel.onDidFocus(() => this.lastFocusedPanel = panel); - const onDidChangeTitleArea = panel.onDidChangeTitleArea(() => { + private addPane(pane: ViewletPane, size: number, index = this.paneItems.length - 1): void { + const onDidFocus = pane.onDidFocus(() => this.lastFocusedPane = pane); + const onDidChangeTitleArea = pane.onDidChangeTitleArea(() => { if (this.isSingleView()) { this.updateTitleArea(); } }); - const onDidChange = panel.onDidChange(() => { - if (panel === this.lastFocusedPanel && !panel.isExpanded()) { - this.lastFocusedPanel = undefined; + const onDidChange = pane.onDidChange(() => { + if (pane === this.lastFocusedPane && !pane.isExpanded()) { + this.lastFocusedPane = undefined; } }); - const panelStyler = attachStyler(this.themeService, { + const paneStyler = attachStyler(this.themeService, { headerForeground: SIDE_BAR_SECTION_HEADER_FOREGROUND, headerBackground: SIDE_BAR_SECTION_HEADER_BACKGROUND, headerBorder: SIDE_BAR_SECTION_HEADER_BORDER, dropBackground: SIDE_BAR_DRAG_AND_DROP_BACKGROUND - }, panel); - const disposable = combinedDisposable(onDidFocus, onDidChangeTitleArea, panelStyler, onDidChange); - const panelItem: IViewletPanelItem = { panel, disposable }; + }, pane); + const disposable = combinedDisposable(onDidFocus, onDidChangeTitleArea, paneStyler, onDidChange); + const paneItem: IViewletPaneItem = { pane: pane, disposable }; - this.panelItems.splice(index, 0, panelItem); - assertIsDefined(this.panelview).addPanel(panel, size, index); + this.paneItems.splice(index, 0, paneItem); + assertIsDefined(this.paneview).addPane(pane, size, index); } - removePanels(panels: ViewletPanel[]): void { + removePanes(panes: ViewletPane[]): void { const wasSingleView = this.isSingleView(); - panels.forEach(panel => this.removePanel(panel)); + panes.forEach(pane => this.removePane(pane)); this.updateViewHeaders(); if (wasSingleView !== this.isSingleView()) { @@ -402,67 +403,67 @@ export class PanelViewlet extends Viewlet { } } - private removePanel(panel: ViewletPanel): void { - const index = firstIndex(this.panelItems, i => i.panel === panel); + private removePane(pane: ViewletPane): void { + const index = firstIndex(this.paneItems, i => i.pane === pane); if (index === -1) { return; } - if (this.lastFocusedPanel === panel) { - this.lastFocusedPanel = undefined; + if (this.lastFocusedPane === pane) { + this.lastFocusedPane = undefined; } - assertIsDefined(this.panelview).removePanel(panel); - const [panelItem] = this.panelItems.splice(index, 1); - panelItem.disposable.dispose(); + assertIsDefined(this.paneview).removePane(pane); + const [paneItem] = this.paneItems.splice(index, 1); + paneItem.disposable.dispose(); } - movePanel(from: ViewletPanel, to: ViewletPanel): void { - const fromIndex = firstIndex(this.panelItems, item => item.panel === from); - const toIndex = firstIndex(this.panelItems, item => item.panel === to); + movePane(from: ViewletPane, to: ViewletPane): void { + const fromIndex = firstIndex(this.paneItems, item => item.pane === from); + const toIndex = firstIndex(this.paneItems, item => item.pane === to); - if (fromIndex < 0 || fromIndex >= this.panelItems.length) { + if (fromIndex < 0 || fromIndex >= this.paneItems.length) { return; } - if (toIndex < 0 || toIndex >= this.panelItems.length) { + if (toIndex < 0 || toIndex >= this.paneItems.length) { return; } - const [panelItem] = this.panelItems.splice(fromIndex, 1); - this.panelItems.splice(toIndex, 0, panelItem); + const [paneItem] = this.paneItems.splice(fromIndex, 1); + this.paneItems.splice(toIndex, 0, paneItem); - assertIsDefined(this.panelview).movePanel(from, to); + assertIsDefined(this.paneview).movePane(from, to); } - resizePanel(panel: ViewletPanel, size: number): void { - assertIsDefined(this.panelview).resizePanel(panel, size); + resizePane(pane: ViewletPane, size: number): void { + assertIsDefined(this.paneview).resizePane(pane, size); } - getPanelSize(panel: ViewletPanel): number { - return assertIsDefined(this.panelview).getPanelSize(panel); + getPaneSize(pane: ViewletPane): number { + return assertIsDefined(this.paneview).getPaneSize(pane); } protected updateViewHeaders(): void { if (this.isSingleView()) { - this.panelItems[0].panel.setExpanded(true); - this.panelItems[0].panel.headerVisible = false; + this.paneItems[0].pane.setExpanded(true); + this.paneItems[0].pane.headerVisible = false; } else { - this.panelItems.forEach(i => i.panel.headerVisible = true); + this.paneItems.forEach(i => i.pane.headerVisible = true); } } protected isSingleView(): boolean { - return this.options.showHeaderInTitleWhenSingleView && this.panelItems.length === 1; + return this.options.showHeaderInTitleWhenSingleView && this.paneItems.length === 1; } dispose(): void { super.dispose(); - this.panelItems.forEach(i => i.disposable.dispose()); - if (this.panelview) { - this.panelview.dispose(); + this.paneItems.forEach(i => i.disposable.dispose()); + if (this.paneview) { + this.paneview.dispose(); } } } diff --git a/src/vs/workbench/browser/parts/views/views.ts b/src/vs/workbench/browser/parts/views/views.ts index 00ac603e4134..ddb3220e6b9a 100644 --- a/src/vs/workbench/browser/parts/views/views.ts +++ b/src/vs/workbench/browser/parts/views/views.ts @@ -11,7 +11,7 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IContextKeyService, IContextKeyChangeEvent, IReadableSet, IContextKey, RawContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { Event, Emitter } from 'vs/base/common/event'; -import { sortedDiff, firstIndex, move, isNonEmptyArray } from 'vs/base/common/arrays'; +import { firstIndex, move, isNonEmptyArray } from 'vs/base/common/arrays'; import { isUndefinedOrNull, isUndefined } from 'vs/base/common/types'; import { MenuId, MenuRegistry, ICommandAction } from 'vs/platform/actions/common/actions'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; @@ -223,7 +223,11 @@ export interface IAddedViewDescriptorRef extends IViewDescriptorRef { export class ContributableViewsModel extends Disposable { - readonly viewDescriptors: IViewDescriptor[] = []; + private _viewDescriptors: IViewDescriptor[] = []; + get viewDescriptors(): ReadonlyArray { + return this._viewDescriptors; + } + get visibleViewDescriptors(): IViewDescriptor[] { return this.viewDescriptors.filter(v => this.isViewDescriptorVisible(v)); } @@ -240,6 +244,9 @@ export class ContributableViewsModel extends Disposable { private _onDidChangeViewState = this._register(new Emitter()); protected readonly onDidChangeViewState: Event = this._onDidChangeViewState.event; + private _onDidChangeActiveViews = this._register(new Emitter>()); + readonly onDidChangeActiveViews: Event> = this._onDidChangeActiveViews.event; + constructor( container: ViewContainer, viewsService: IViewsService, @@ -335,7 +342,7 @@ export class ContributableViewsModel extends Disposable { const fromViewDescriptor = this.viewDescriptors[fromIndex]; const toViewDescriptor = this.viewDescriptors[toIndex]; - move(this.viewDescriptors, fromIndex, toIndex); + move(this._viewDescriptors, fromIndex, toIndex); for (let index = 0; index < this.viewDescriptors.length; index++) { const state = this.viewStates.get(this.viewDescriptors[index].id)!; @@ -403,14 +410,6 @@ export class ContributableViewsModel extends Disposable { } private onDidChangeViewDescriptors(viewDescriptors: IViewDescriptor[]): void { - const ids = new Set(); - - for (const viewDescriptor of this.viewDescriptors) { - ids.add(viewDescriptor.id); - } - - viewDescriptors = viewDescriptors.sort(this.compareViewDescriptors.bind(this)); - for (const viewDescriptor of viewDescriptors) { const viewState = this.viewStates.get(viewDescriptor.id); if (viewState) { @@ -430,38 +429,29 @@ export class ContributableViewsModel extends Disposable { } } - const splices = sortedDiff( - this.viewDescriptors, - viewDescriptors, - (a, b) => a.id === b.id ? 0 : a.id < b.id ? -1 : 1 - ).reverse(); + viewDescriptors = viewDescriptors.sort(this.compareViewDescriptors.bind(this)); const toRemove: { index: number, viewDescriptor: IViewDescriptor; }[] = []; - const toAdd: { index: number, viewDescriptor: IViewDescriptor, size?: number, collapsed: boolean; }[] = []; - - for (const splice of splices) { - const startViewDescriptor = this.viewDescriptors[splice.start]; - let startIndex = startViewDescriptor ? this.find(startViewDescriptor.id).visibleIndex : this.viewDescriptors.length; - - for (let i = 0; i < splice.deleteCount; i++) { - const viewDescriptor = this.viewDescriptors[splice.start + i]; - - if (this.isViewDescriptorVisible(viewDescriptor)) { - toRemove.push({ index: startIndex++, viewDescriptor }); - } + for (let index = 0; index < this._viewDescriptors.length; index++) { + const previousViewDescriptor = this._viewDescriptors[index]; + if (this.isViewDescriptorVisible(previousViewDescriptor) && viewDescriptors.every(viewDescriptor => viewDescriptor.id !== previousViewDescriptor.id)) { + const { visibleIndex } = this.find(previousViewDescriptor.id); + toRemove.push({ index: visibleIndex, viewDescriptor: previousViewDescriptor }); } + } - for (const viewDescriptor of splice.toInsert) { - const state = this.viewStates.get(viewDescriptor.id)!; + const previous = this._viewDescriptors; + this._viewDescriptors = viewDescriptors.slice(0); - if (this.isViewDescriptorVisible(viewDescriptor)) { - toAdd.push({ index: startIndex++, viewDescriptor, size: state.size, collapsed: !!state.collapsed }); - } + const toAdd: { index: number, viewDescriptor: IViewDescriptor, size?: number, collapsed: boolean; }[] = []; + for (let i = 0; i < this._viewDescriptors.length; i++) { + const viewDescriptor = this._viewDescriptors[i]; + if (this.isViewDescriptorVisible(viewDescriptor) && previous.every(previousViewDescriptor => previousViewDescriptor.id !== viewDescriptor.id)) { + const { visibleIndex, state } = this.find(viewDescriptor.id); + toAdd.push({ index: visibleIndex, viewDescriptor, size: state.size, collapsed: !!state.collapsed }); } } - this.viewDescriptors.splice(0, this.viewDescriptors.length, ...viewDescriptors); - if (toRemove.length) { this._onDidRemove.fire(toRemove); } @@ -469,6 +459,8 @@ export class ContributableViewsModel extends Disposable { if (toAdd.length) { this._onDidAdd.fire(toAdd); } + + this._onDidChangeActiveViews.fire(this.viewDescriptors); } } diff --git a/src/vs/workbench/browser/parts/views/viewsViewlet.ts b/src/vs/workbench/browser/parts/views/viewsViewlet.ts index 71f94b18b5b8..f477ee5796da 100644 --- a/src/vs/workbench/browser/parts/views/viewsViewlet.ts +++ b/src/vs/workbench/browser/parts/views/viewsViewlet.ts @@ -18,8 +18,8 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; -import { PanelViewlet, ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet'; -import { DefaultPanelDndController } from 'vs/base/browser/ui/splitview/panelview'; +import { PaneViewlet, ViewletPane, IViewletPaneOptions } from 'vs/workbench/browser/parts/views/paneViewlet'; +import { DefaultPaneDndController } from 'vs/base/browser/ui/splitview/paneview'; import { WorkbenchTree, IListService } from 'vs/platform/list/browser/listService'; import { IWorkbenchThemeService, IFileIconTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { ITreeConfiguration, ITreeOptions } from 'vs/base/parts/tree/browser/tree'; @@ -31,11 +31,11 @@ import { IAddedViewDescriptorRef, IViewDescriptorRef, PersistentContributableVie import { Registry } from 'vs/platform/registry/common/platform'; import { MementoObject } from 'vs/workbench/common/memento'; -export interface IViewletViewOptions extends IViewletPanelOptions { +export interface IViewletViewOptions extends IViewletPaneOptions { viewletState: MementoObject; } -export abstract class ViewContainerViewlet extends PanelViewlet implements IViewsViewlet { +export abstract class ViewContainerViewlet extends PaneViewlet implements IViewsViewlet { private readonly viewletState: MementoObject; private didLayout = false; @@ -61,7 +61,7 @@ export abstract class ViewContainerViewlet extends PanelViewlet implements IView @IExtensionService protected extensionService: IExtensionService, @IWorkspaceContextService protected contextService: IWorkspaceContextService ) { - super(id, { showHeaderInTitleWhenSingleView, dnd: new DefaultPanelDndController() }, configurationService, layoutService, contextMenuService, telemetryService, themeService, storageService); + super(id, { showHeaderInTitleWhenSingleView, dnd: new DefaultPaneDndController() }, configurationService, layoutService, contextMenuService, telemetryService, themeService, storageService); const container = Registry.as(ViewContainerExtensions.ViewContainersRegistry).get(id); if (!container) { @@ -92,7 +92,7 @@ export abstract class ViewContainerViewlet extends PanelViewlet implements IView // Update headers after and title contributed views after available, since we read from cache in the beginning to know if the viewlet has single view or not. Ref #29609 this.extensionService.whenInstalledExtensionsRegistered().then(() => { this.areExtensionsReady = true; - if (this.panels.length) { + if (this.panes.length) { this.updateTitleArea(); this.updateViewHeaders(); } @@ -112,7 +112,7 @@ export abstract class ViewContainerViewlet extends PanelViewlet implements IView })); result.push(...viewToggleActions); - const parentActions = super.getContextMenuActions(); + const parentActions = this.getViewletContextMenuActions(); if (viewToggleActions.length && parentActions.length) { result.push(new Separator()); } @@ -121,9 +121,13 @@ export abstract class ViewContainerViewlet extends PanelViewlet implements IView return result; } + protected getViewletContextMenuActions() { + return super.getContextMenuActions(); + } + setVisible(visible: boolean): void { super.setVisible(visible); - this.panels.filter(view => view.isVisible() !== visible) + this.panes.filter(view => view.isVisible() !== visible) .map((view) => view.setVisible(visible)); } @@ -143,13 +147,13 @@ export abstract class ViewContainerViewlet extends PanelViewlet implements IView return view; } - movePanel(from: ViewletPanel, to: ViewletPanel): void { - const fromIndex = firstIndex(this.panels, panel => panel === from); - const toIndex = firstIndex(this.panels, panel => panel === to); + movePane(from: ViewletPane, to: ViewletPane): void { + const fromIndex = firstIndex(this.panes, pane => pane === from); + const toIndex = firstIndex(this.panes, pane => pane === to); const fromViewDescriptor = this.viewsModel.visibleViewDescriptors[fromIndex]; const toViewDescriptor = this.viewsModel.visibleViewDescriptors[toIndex]; - super.movePanel(from, to); + super.movePane(from, to); this.viewsModel.move(fromViewDescriptor.id, toViewDescriptor.id); } @@ -166,7 +170,7 @@ export abstract class ViewContainerViewlet extends PanelViewlet implements IView getOptimalWidth(): number { const additionalMargin = 16; - const optimalWidth = Math.max(...this.panels.map(view => view.getOptimalWidth() || 0)); + const optimalWidth = Math.max(...this.panes.map(view => view.getOptimalWidth() || 0)); return optimalWidth + additionalMargin; } @@ -184,18 +188,18 @@ export abstract class ViewContainerViewlet extends PanelViewlet implements IView return true; } - protected createView(viewDescriptor: IViewDescriptor, options: IViewletViewOptions): ViewletPanel { - return (this.instantiationService as any).createInstance(viewDescriptor.ctorDescriptor.ctor, ...(viewDescriptor.ctorDescriptor.arguments || []), options) as ViewletPanel; + protected createView(viewDescriptor: IViewDescriptor, options: IViewletViewOptions): ViewletPane { + return (this.instantiationService as any).createInstance(viewDescriptor.ctorDescriptor.ctor, ...(viewDescriptor.ctorDescriptor.arguments || []), options) as ViewletPane; } - protected getView(id: string): ViewletPanel | undefined { - return this.panels.filter(view => view.id === id)[0]; + protected getView(id: string): ViewletPane | undefined { + return this.panes.filter(view => view.id === id)[0]; } - protected onDidAddViews(added: IAddedViewDescriptorRef[]): ViewletPanel[] { - const panelsToAdd: { panel: ViewletPanel, size: number, index: number }[] = []; + protected onDidAddViews(added: IAddedViewDescriptorRef[]): ViewletPane[] { + const panesToAdd: { pane: ViewletPane, size: number, index: number }[] = []; for (const { viewDescriptor, collapsed, index, size } of added) { - const panel = this.createView(viewDescriptor, + const pane = this.createView(viewDescriptor, { id: viewDescriptor.id, title: viewDescriptor.name, @@ -203,42 +207,42 @@ export abstract class ViewContainerViewlet extends PanelViewlet implements IView expanded: !collapsed, viewletState: this.viewletState }); - panel.render(); - const contextMenuDisposable = DOM.addDisposableListener(panel.draggableElement, 'contextmenu', e => { + pane.render(); + const contextMenuDisposable = DOM.addDisposableListener(pane.draggableElement, 'contextmenu', e => { e.stopPropagation(); e.preventDefault(); this.onContextMenu(new StandardMouseEvent(e), viewDescriptor); }); - const collapseDisposable = Event.latch(Event.map(panel.onDidChange, () => !panel.isExpanded()))(collapsed => { + const collapseDisposable = Event.latch(Event.map(pane.onDidChange, () => !pane.isExpanded()))(collapsed => { this.viewsModel.setCollapsed(viewDescriptor.id, collapsed); }); this.viewDisposables.splice(index, 0, combinedDisposable(contextMenuDisposable, collapseDisposable)); - panelsToAdd.push({ panel, size: size || panel.minimumSize, index }); + panesToAdd.push({ pane, size: size || pane.minimumSize, index }); } - this.addPanels(panelsToAdd); + this.addPanes(panesToAdd); this.restoreViewSizes(); - const panels: ViewletPanel[] = []; - for (const { panel } of panelsToAdd) { - panel.setVisible(this.isVisible()); - panels.push(panel); + const panes: ViewletPane[] = []; + for (const { pane } of panesToAdd) { + pane.setVisible(this.isVisible()); + panes.push(pane); } - return panels; + return panes; } private onDidRemoveViews(removed: IViewDescriptorRef[]): void { removed = removed.sort((a, b) => b.index - a.index); - const panelsToRemove: ViewletPanel[] = []; + const panesToRemove: ViewletPane[] = []; for (const { index } of removed) { const [disposable] = this.viewDisposables.splice(index, 1); disposable.dispose(); - panelsToRemove.push(this.panels[index]); + panesToRemove.push(this.panes[index]); } - this.removePanels(panelsToRemove); - dispose(panelsToRemove); + this.removePanes(panesToRemove); + dispose(panesToRemove); } private onContextMenu(event: StandardMouseEvent, viewDescriptor: IViewDescriptor): void { @@ -264,7 +268,7 @@ export abstract class ViewContainerViewlet extends PanelViewlet implements IView }); } - private toggleViewVisibility(viewId: string): void { + protected toggleViewVisibility(viewId: string): void { const visible = !this.viewsModel.isVisible(viewId); type ViewsToggleVisibilityClassification = { viewId: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; @@ -277,8 +281,8 @@ export abstract class ViewContainerViewlet extends PanelViewlet implements IView private saveViewSizes(): void { // Save size only when the layout has happened if (this.didLayout) { - for (const view of this.panels) { - this.viewsModel.setSize(view.id, this.getPanelSize(view)); + for (const view of this.panes) { + this.viewsModel.setSize(view.id, this.getPaneSize(view)); } } } @@ -288,15 +292,15 @@ export abstract class ViewContainerViewlet extends PanelViewlet implements IView if (this.didLayout) { let initialSizes; for (let i = 0; i < this.viewsModel.visibleViewDescriptors.length; i++) { - const panel = this.panels[i]; + const pane = this.panes[i]; const viewDescriptor = this.viewsModel.visibleViewDescriptors[i]; const size = this.viewsModel.getSize(viewDescriptor.id); if (typeof size === 'number') { - this.resizePanel(panel, size); + this.resizePane(pane, size); } else { initialSizes = initialSizes ? initialSizes : this.computeInitialSizes(); - this.resizePanel(panel, initialSizes.get(panel.id) || 200); + this.resizePane(pane, initialSizes.get(pane.id) || 200); } } } @@ -314,13 +318,123 @@ export abstract class ViewContainerViewlet extends PanelViewlet implements IView } protected saveState(): void { - this.panels.forEach((view) => view.saveState()); + this.panes.forEach((view) => view.saveState()); this.storageService.store(this.visibleViewsStorageId, this.length, StorageScope.WORKSPACE); super.saveState(); } } +export abstract class FilterViewContainerViewlet extends ViewContainerViewlet { + private constantViewDescriptors: Map = new Map(); + private allViews: Map> = new Map(); + private filterValue: string | undefined; + + constructor( + viewletId: string, + onDidChangeFilterValue: Event, + @IConfigurationService configurationService: IConfigurationService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, + @ITelemetryService telemetryService: ITelemetryService, + @IStorageService storageService: IStorageService, + @IInstantiationService instantiationService: IInstantiationService, + @IThemeService themeService: IThemeService, + @IContextMenuService contextMenuService: IContextMenuService, + @IExtensionService extensionService: IExtensionService, + @IWorkspaceContextService contextService: IWorkspaceContextService + ) { + super(viewletId, `${viewletId}.state`, false, configurationService, layoutService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService); + this._register(onDidChangeFilterValue(newFilterValue => { + this.filterValue = newFilterValue; + this.onFilterChanged(newFilterValue); + })); + + this._register(this.viewsModel.onDidChangeActiveViews((viewDescriptors) => { + this.updateAllViews(viewDescriptors); + })); + } + + private updateAllViews(viewDescriptors: ReadonlyArray) { + viewDescriptors.forEach(descriptor => { + let filterOnValue = this.getFilterOn(descriptor); + if (!filterOnValue) { + return; + } + if (!this.allViews.has(filterOnValue)) { + this.allViews.set(filterOnValue, new Map()); + } + this.allViews.get(filterOnValue)!.set(descriptor.id, descriptor); + if (filterOnValue !== this.filterValue) { + this.viewsModel.setVisible(descriptor.id, false); + } + }); + } + + protected addConstantViewDescriptors(constantViewDescriptors: IViewDescriptor[]) { + constantViewDescriptors.forEach(viewDescriptor => this.constantViewDescriptors.set(viewDescriptor.id, viewDescriptor)); + } + + protected abstract getFilterOn(viewDescriptor: IViewDescriptor): string | undefined; + + private onFilterChanged(newFilterValue: string) { + this.getViewsNotForTarget(newFilterValue).forEach(item => this.viewsModel.setVisible(item.id, false)); + this.getViewsForTarget(newFilterValue).forEach(item => this.viewsModel.setVisible(item.id, true)); + } + + getContextMenuActions(): IAction[] { + const result: IAction[] = []; + let viewToggleActions: IAction[] = Array.from(this.constantViewDescriptors.values()).map(viewDescriptor => ({ + id: `${viewDescriptor.id}.toggleVisibility`, + label: viewDescriptor.name, + checked: this.viewsModel.isVisible(viewDescriptor.id), + enabled: viewDescriptor.canToggleVisibility, + run: () => this.toggleViewVisibility(viewDescriptor.id) + })); + + result.push(...viewToggleActions); + const parentActions = this.getViewletContextMenuActions(); + if (viewToggleActions.length && parentActions.length) { + result.push(new Separator()); + } + + result.push(...parentActions); + return result; + } + + private getViewsForTarget(target: string): IViewDescriptor[] { + return this.allViews.has(target) ? Array.from(this.allViews.get(target)!.values()) : []; + } + + private getViewsNotForTarget(target: string): IViewDescriptor[] { + const iterable = this.allViews.keys(); + let key = iterable.next(); + let views: IViewDescriptor[] = []; + while (!key.done) { + if (key.value !== target) { + views = views.concat(this.getViewsForTarget(key.value)); + } + key = iterable.next(); + } + return views; + } + + onDidAddViews(added: IAddedViewDescriptorRef[]): ViewletPane[] { + const panes: ViewletPane[] = super.onDidAddViews(added); + for (let i = 0; i < added.length; i++) { + if (this.constantViewDescriptors.has(added[i].viewDescriptor.id)) { + panes[i].setExpanded(false); + } + } + // Check that allViews is ready + if (this.allViews.size === 0) { + this.updateAllViews(this.viewsModel.viewDescriptors); + } + return panes; + } + + abstract getTitle(): string; +} + export class FileIconThemableWorkbenchTree extends WorkbenchTree { constructor( diff --git a/src/vs/workbench/browser/quickopen.ts b/src/vs/workbench/browser/quickopen.ts index fc88a4a70fce..d4240c3e7a05 100644 --- a/src/vs/workbench/browser/quickopen.ts +++ b/src/vs/workbench/browser/quickopen.ts @@ -15,7 +15,7 @@ import { QuickOpenEntry, QuickOpenEntryGroup } from 'vs/base/parts/quickopen/bro import { EditorOptions, EditorInput, IEditorInput } from 'vs/workbench/common/editor'; import { IResourceInput, IEditorOptions } from 'vs/platform/editor/common/editor'; import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; -import { IConstructorSignature0, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IConstructorSignature0, IInstantiationService, BrandedService } from 'vs/platform/instantiation/common/instantiation'; import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -136,9 +136,13 @@ export class QuickOpenHandlerDescriptor { private id: string; private ctor: IConstructorSignature0; - constructor(ctor: IConstructorSignature0, id: string, prefix: string, contextKey: string | undefined, description: string, instantProgress?: boolean); - constructor(ctor: IConstructorSignature0, id: string, prefix: string, contextKey: string | undefined, helpEntries: QuickOpenHandlerHelpEntry[], instantProgress?: boolean); - constructor(ctor: IConstructorSignature0, id: string, prefix: string, contextKey: string | undefined, param: string | QuickOpenHandlerHelpEntry[], instantProgress: boolean = false) { + public static create(ctor: { new(...services: Services): QuickOpenHandler }, id: string, prefix: string, contextKey: string | undefined, description: string, instantProgress?: boolean): QuickOpenHandlerDescriptor; + public static create(ctor: { new(...services: Services): QuickOpenHandler }, id: string, prefix: string, contextKey: string | undefined, helpEntries: QuickOpenHandlerHelpEntry[], instantProgress?: boolean): QuickOpenHandlerDescriptor; + public static create(ctor: { new(...services: Services): QuickOpenHandler }, id: string, prefix: string, contextKey: string | undefined, param: string | QuickOpenHandlerHelpEntry[], instantProgress: boolean = false): QuickOpenHandlerDescriptor { + return new QuickOpenHandlerDescriptor(ctor as IConstructorSignature0, id, prefix, contextKey, param, instantProgress); + } + + private constructor(ctor: IConstructorSignature0, id: string, prefix: string, contextKey: string | undefined, param: string | QuickOpenHandlerHelpEntry[], instantProgress: boolean = false) { this.ctor = ctor; this.id = id; this.prefix = prefix; diff --git a/src/vs/workbench/browser/style.ts b/src/vs/workbench/browser/style.ts index b19321d391cf..fd858c907131 100644 --- a/src/vs/workbench/browser/style.ts +++ b/src/vs/workbench/browser/style.ts @@ -8,8 +8,9 @@ import 'vs/css!./media/style'; import { registerThemingParticipant, ITheme, ICssStyleCollector, HIGH_CONTRAST } from 'vs/platform/theme/common/themeService'; import { iconForeground, foreground, selectionBackground, focusBorder, scrollbarShadow, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, listHighlightForeground, inputPlaceholderForeground } from 'vs/platform/theme/common/colorRegistry'; import { WORKBENCH_BACKGROUND, TITLE_BAR_ACTIVE_BACKGROUND } from 'vs/workbench/common/theme'; -import { isWeb } from 'vs/base/common/platform'; +import { isWeb, isIOS } from 'vs/base/common/platform'; import { createMetaElement } from 'vs/base/browser/dom'; +import { isSafari, isStandalone } from 'vs/base/browser/browser'; registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { @@ -34,8 +35,16 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { // Input placeholder const placeholderForeground = theme.getColor(inputPlaceholderForeground); if (placeholderForeground) { - collector.addRule(`.monaco-workbench input::-webkit-input-placeholder { color: ${placeholderForeground}; }`); - collector.addRule(`.monaco-workbench textarea::-webkit-input-placeholder { color: ${placeholderForeground}; }`); + collector.addRule(` + .monaco-workbench input::placeholder { color: ${placeholderForeground}; } + .monaco-workbench input::-webkit-input-placeholder { color: ${placeholderForeground}; } + .monaco-workbench input::-moz-placeholder { color: ${placeholderForeground}; } + `); + collector.addRule(` + .monaco-workbench textarea::placeholder { color: ${placeholderForeground}; } + .monaco-workbench textarea::-webkit-input-placeholder { color: ${placeholderForeground}; } + .monaco-workbench textarea::-moz-placeholder { color: ${placeholderForeground}; } + `); } // List highlight @@ -160,4 +169,24 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { metaElement.content = titleBackground.toString(); } } + + // We disable user select on the root element, however on Safari this seems + // to prevent any text selection in the monaco editor. As a workaround we + // allow to select text in monaco editor instances. + if (isSafari) { + collector.addRule(` + body.web { + touch-action: none; + } + .monaco-workbench .monaco-editor .view-lines { + user-select: text; + -webkit-user-select: text; + } + `); + } + + // Update body background color to ensure the home indicator area looks similar to the workbench + if (isIOS && isStandalone) { + collector.addRule(`body { background-color: ${workbenchBackground}; }`); + } }); diff --git a/src/vs/workbench/browser/viewlet.ts b/src/vs/workbench/browser/viewlet.ts index a8d1cd59719d..bd04fcd72920 100644 --- a/src/vs/workbench/browser/viewlet.ts +++ b/src/vs/workbench/browser/viewlet.ts @@ -10,7 +10,7 @@ import { Action, IAction } from 'vs/base/common/actions'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IViewlet } from 'vs/workbench/common/viewlet'; import { Composite, CompositeDescriptor, CompositeRegistry } from 'vs/workbench/browser/composite'; -import { IConstructorSignature0 } from 'vs/platform/instantiation/common/instantiation'; +import { IConstructorSignature0, BrandedService } from 'vs/platform/instantiation/common/instantiation'; import { ToggleSidebarVisibilityAction, ToggleSidebarPositionAction } from 'vs/workbench/browser/actions/layoutActions'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; @@ -56,20 +56,27 @@ export abstract class Viewlet extends Composite implements IViewlet { */ export class ViewletDescriptor extends CompositeDescriptor { - constructor( + public static create( + ctor: { new(...services: Services): Viewlet }, + id: string, + name: string, + cssClass?: string, + order?: number, + iconUrl?: URI + ): ViewletDescriptor { + return new ViewletDescriptor(ctor as IConstructorSignature0, id, name, cssClass, order, iconUrl); + } + + private constructor( ctor: IConstructorSignature0, id: string, name: string, cssClass?: string, order?: number, - private _iconUrl?: URI + readonly iconUrl?: URI ) { super(ctor, id, name, cssClass, order, id); } - - get iconUrl(): URI | undefined { - return this._iconUrl; - } } export const Extensions = { diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts index 290440d64bd7..fb98813a75db 100644 --- a/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts @@ -11,7 +11,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { BrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; import { Workbench } from 'vs/workbench/browser/workbench'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; -import { REMOTE_FILE_SYSTEM_CHANNEL_NAME, RemoteExtensionsFileSystemProvider } from 'vs/platform/remote/common/remoteAgentFileSystemChannel'; +import { REMOTE_FILE_SYSTEM_CHANNEL_NAME, RemoteFileSystemProvider } from 'vs/platform/remote/common/remoteAgentFileSystemChannel'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IProductService } from 'vs/platform/product/common/productService'; import product from 'vs/platform/product/common/product'; @@ -26,6 +26,7 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { onUnexpectedError } from 'vs/base/common/errors'; import * as browser from 'vs/base/browser/browser'; +import * as platform from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { IWorkspaceInitializationPayload } from 'vs/platform/workspaces/common/workspaces'; import { WorkspaceService } from 'vs/workbench/services/configuration/browser/configurationService'; @@ -90,7 +91,10 @@ class BrowserMain extends Disposable { private registerListeners(workbench: Workbench, storageService: BrowserStorageService): void { // Layout - this._register(addDisposableListener(window, EventType.RESIZE, () => workbench.layout())); + const viewport = platform.isIOS && (window).visualViewport ? (window).visualViewport /** Visual viewport */ : window /** Layout viewport */; + this._register(addDisposableListener(viewport, EventType.RESIZE, () => { + workbench.layout(); + })); // Prevent the back/forward gestures in macOS this._register(addDisposableListener(this.domElement, EventType.WHEEL, (e) => { @@ -100,6 +104,9 @@ class BrowserMain extends Disposable { // Prevent native context menus in web this._register(addDisposableListener(this.domElement, EventType.CONTEXT_MENU, (e) => EventHelper.stop(e, true))); + // Prevent default navigation on drop + this._register(addDisposableListener(this.domElement, EventType.DROP, (e) => EventHelper.stop(e, true))); + // Workbench Lifecycle this._register(workbench.onBeforeShutdown(event => { if (storageService.hasPendingUpdate) { @@ -126,7 +133,7 @@ class BrowserMain extends Disposable { } private restoreBaseTheme(): void { - addClass(this.domElement, window.localStorage.getItem('baseTheme') || getThemeTypeSelector(DARK)); + addClass(this.domElement, window.localStorage.getItem('vscode.baseTheme') || getThemeTypeSelector(DARK)); } private saveBaseTheme(): void { @@ -134,7 +141,7 @@ class BrowserMain extends Disposable { const baseThemes = [DARK, LIGHT, HIGH_CONTRAST].map(baseTheme => getThemeTypeSelector(baseTheme)); for (const baseTheme of baseThemes) { if (classes.indexOf(baseTheme) >= 0) { - window.localStorage.setItem('baseTheme', baseTheme); + window.localStorage.setItem('vscode.baseTheme', baseTheme); break; } } @@ -162,10 +169,7 @@ class BrowserMain extends Disposable { // Product const productService = { _serviceBrand: undefined, - ...{ - ...product, // dev or built time config - ...{ urlProtocol: '' } // web related overrides from us - } + ...product }; serviceCollection.set(IProductService, productService); @@ -214,17 +218,21 @@ class BrowserMain extends Disposable { private registerFileSystemProviders(environmentService: IWorkbenchEnvironmentService, fileService: IFileService, remoteAgentService: IRemoteAgentService, logService: BufferLogService, logsPath: URI): void { // Logger - const indexedDBLogProvider = new IndexedDBLogProvider(logsPath.scheme); (async () => { - try { - await indexedDBLogProvider.database; + if (browser.isEdge) { + fileService.registerProvider(logsPath.scheme, new InMemoryLogProvider(logsPath.scheme)); + } else { + try { + const indexedDBLogProvider = new IndexedDBLogProvider(logsPath.scheme); + await indexedDBLogProvider.database; - fileService.registerProvider(logsPath.scheme, indexedDBLogProvider); - } catch (error) { - logService.info('Error while creating indexedDB log provider. Falling back to in-memory log provider.'); - logService.error(error); + fileService.registerProvider(logsPath.scheme, indexedDBLogProvider); + } catch (error) { + logService.info('Error while creating indexedDB log provider. Falling back to in-memory log provider.'); + logService.error(error); - fileService.registerProvider(logsPath.scheme, new InMemoryLogProvider(logsPath.scheme)); + fileService.registerProvider(logsPath.scheme, new InMemoryLogProvider(logsPath.scheme)); + } } const consoleLogService = new ConsoleLogService(logService.getLevel()); @@ -237,7 +245,7 @@ class BrowserMain extends Disposable { // Remote file system const channel = connection.getChannel(REMOTE_FILE_SYSTEM_CHANNEL_NAME); - const remoteFileSystemProvider = this._register(new RemoteExtensionsFileSystemProvider(channel, remoteAgentService.getEnvironment())); + const remoteFileSystemProvider = this._register(new RemoteFileSystemProvider(channel, remoteAgentService.getEnvironment())); fileService.registerProvider(Schemas.vscodeRemote, remoteFileSystemProvider); if (!this.configuration.userDataProvider) { diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index fadcfa072572..1ce62d9c645b 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -7,6 +7,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import * as nls from 'vs/nls'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { isMacintosh, isWindows, isLinux, isWeb, isNative } from 'vs/base/common/platform'; +import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration'; // Configuration (function registerConfiguration(): void { @@ -14,10 +15,7 @@ import { isMacintosh, isWindows, isLinux, isWeb, isNative } from 'vs/base/common // Workbench registry.registerConfiguration({ - 'id': 'workbench', - 'order': 7, - 'title': nls.localize('workbenchConfigurationTitle', "Workbench"), - 'type': 'object', + ...workbenchConfigurationNodeBase, 'properties': { 'workbench.editor.showTabs': { 'type': 'boolean', @@ -87,7 +85,7 @@ import { isMacintosh, isWindows, isLinux, isWeb, isNative } from 'vs/base/common }, 'workbench.editor.enablePreviewFromQuickOpen': { 'type': 'boolean', - 'description': nls.localize('enablePreviewFromQuickOpen', "Controls whether opened editors from Quick Open show as preview. Preview editors are reused until they are pinned (e.g. via double click or editing)."), + 'description': nls.localize('enablePreviewFromQuickOpen', "Controls whether editors opened from Quick Open show as preview. Preview editors are reused until they are pinned (e.g. via double click or editing)."), 'default': true }, 'workbench.editor.closeOnFileDelete': { @@ -297,7 +295,7 @@ import { isMacintosh, isWindows, isLinux, isWeb, isNative } from 'vs/base/common nls.localize('window.menuBarVisibility.visible', "Menu is always visible even in full screen mode."), nls.localize('window.menuBarVisibility.toggle', "Menu is hidden but can be displayed via Alt key."), nls.localize('window.menuBarVisibility.hidden', "Menu is always hidden."), - nls.localize('window.menuBarVisibility.compact', "Menu is displayed as a compact button in the sidebar.") + nls.localize('window.menuBarVisibility.compact', "Menu is displayed as a compact button in the sidebar. This value is ignored when 'window.titleBarStyle' is 'native'.") ], 'default': isWeb ? 'compact' : 'default', 'scope': ConfigurationScope.APPLICATION, diff --git a/src/vs/workbench/browser/workbench.ts b/src/vs/workbench/browser/workbench.ts index 524f6b6ea17e..ef1be41590a6 100644 --- a/src/vs/workbench/browser/workbench.ts +++ b/src/vs/workbench/browser/workbench.ts @@ -9,7 +9,7 @@ import { localize } from 'vs/nls'; import { Event, Emitter, setGlobalLeakWarningThreshold } from 'vs/base/common/event'; import { addClasses, addClass, removeClasses } from 'vs/base/browser/dom'; import { runWhenIdle } from 'vs/base/common/async'; -import { getZoomLevel } from 'vs/base/browser/browser'; +import { getZoomLevel, isFirefox, isSafari, isChrome } from 'vs/base/browser/browser'; import { mark } from 'vs/base/common/performance'; import { onUnexpectedError, setUnexpectedErrorHandler } from 'vs/base/common/errors'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -272,7 +272,7 @@ export class Workbench extends Layout { private restoreFontInfo(storageService: IStorageService, configurationService: IConfigurationService): void { // Restore (native: use storage service, web: use browser specific local storage) - const storedFontInfoRaw = isNative ? storageService.get('editorFontInfo', StorageScope.GLOBAL) : window.localStorage.getItem('editorFontInfo'); + const storedFontInfoRaw = isNative ? storageService.get('editorFontInfo', StorageScope.GLOBAL) : window.localStorage.getItem('vscode.editorFontInfo'); if (storedFontInfoRaw) { try { const storedFontInfo = JSON.parse(storedFontInfoRaw); @@ -299,7 +299,7 @@ export class Workbench extends Layout { if (isNative) { storageService.store('editorFontInfo', serializedFontInfoRaw, StorageScope.GLOBAL); } else { - window.localStorage.setItem('editorFontInfo', serializedFontInfoRaw); + window.localStorage.setItem('vscode.editorFontInfo', serializedFontInfoRaw); } } } @@ -312,6 +312,7 @@ export class Workbench extends Layout { 'monaco-workbench', platformClass, isWeb ? 'web' : undefined, + isChrome ? 'chromium' : isFirefox ? 'firefox' : isSafari ? 'safari' : undefined, ...this.getLayoutClasses() ]); diff --git a/src/vs/workbench/common/activity.ts b/src/vs/workbench/common/activity.ts index ffce2de6a185..666bed860f09 100644 --- a/src/vs/workbench/common/activity.ts +++ b/src/vs/workbench/common/activity.ts @@ -3,11 +3,14 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { URI } from 'vs/base/common/uri'; + export interface IActivity { id: string; name: string; keybindingId?: string; cssClass?: string; + iconUrl?: URI; } -export const GLOBAL_ACTIVITY_ID = 'workbench.action.globalActivity'; \ No newline at end of file +export const GLOBAL_ACTIVITY_ID = 'workbench.action.globalActivity'; diff --git a/src/vs/workbench/common/composite.ts b/src/vs/workbench/common/composite.ts index 12300b3977fe..a900178bdaee 100644 --- a/src/vs/workbench/common/composite.ts +++ b/src/vs/workbench/common/composite.ts @@ -4,9 +4,20 @@ *--------------------------------------------------------------------------------------------*/ import { IAction, IActionViewItem } from 'vs/base/common/actions'; +import { Event } from 'vs/base/common/event'; export interface IComposite { + /** + * An event when the composite gained focus. + */ + readonly onDidFocus: Event; + + /** + * An event when the composite lost focus. + */ + readonly onDidBlur: Event; + /** * Returns the unique identifier of this composite. */ diff --git a/src/typings/require-monaco.d.ts b/src/vs/workbench/common/configuration.ts similarity index 51% rename from src/typings/require-monaco.d.ts rename to src/vs/workbench/common/configuration.ts index 81890ff24fcb..ca52580d80b0 100644 --- a/src/typings/require-monaco.d.ts +++ b/src/vs/workbench/common/configuration.ts @@ -3,10 +3,12 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -interface NodeRequire { - toUrl(path: string): string; - (dependencies: string[], callback: (...args: any[]) => any, errorback?: (err: any) => void): any; - config(data: any): any; -} +import { localize } from 'vs/nls'; +import { IConfigurationNode } from 'vs/platform/configuration/common/configurationRegistry'; -declare var require: NodeRequire; \ No newline at end of file +export const workbenchConfigurationNodeBase = Object.freeze({ + 'id': 'workbench', + 'order': 7, + 'title': localize('workbenchConfigurationTitle', "Workbench"), + 'type': 'object', +}); diff --git a/src/vs/workbench/common/contributions.ts b/src/vs/workbench/common/contributions.ts index db1197674faf..33fd16461251 100644 --- a/src/vs/workbench/common/contributions.ts +++ b/src/vs/workbench/common/contributions.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IInstantiationService, IConstructorSignature0, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, IConstructorSignature0, ServicesAccessor, BrandedService } from 'vs/platform/instantiation/common/instantiation'; import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; import { runWhenIdle, IdleDeadline } from 'vs/base/common/async'; @@ -19,7 +19,7 @@ export namespace Extensions { export const Workbench = 'workbench.contributions.kind'; } -export type IWorkbenchContributionSignature = IConstructorSignature0; +type IWorkbenchContributionSignature = new (...services: Service) => IWorkbenchContribution; export interface IWorkbenchContributionsRegistry { @@ -29,7 +29,7 @@ export interface IWorkbenchContributionsRegistry { * * @param phase the lifecycle phase when to instantiate the contribution. */ - registerWorkbenchContribution(contribution: IWorkbenchContributionSignature, phase: LifecyclePhase): void; + registerWorkbenchContribution(contribution: IWorkbenchContributionSignature, phase: LifecyclePhase): void; /** * Starts the registry by providing the required services. @@ -43,8 +43,7 @@ class WorkbenchContributionsRegistry implements IWorkbenchContributionsRegistry private readonly toBeInstantiated: Map[]> = new Map[]>(); - registerWorkbenchContribution(ctor: IWorkbenchContributionSignature, phase: LifecyclePhase = LifecyclePhase.Starting): void { - + registerWorkbenchContribution(ctor: { new(...services: Services): IWorkbenchContribution }, phase: LifecyclePhase = LifecyclePhase.Starting): void { // Instantiate directly if we are already matching the provided phase if (this.instantiationService && this.lifecycleService && this.lifecycleService.phase >= phase) { this.instantiationService.createInstance(ctor); diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 67c927afc5ea..7d1a8e5d330d 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -10,19 +10,24 @@ import { URI } from 'vs/base/common/uri'; import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { IEditor as ICodeEditor, IEditorViewState, ScrollType, IDiffEditor } from 'vs/editor/common/editorCommon'; import { IEditorModel, IEditorOptions, ITextEditorOptions, IBaseResourceInput, IResourceInput, EditorActivation, EditorOpenContext } from 'vs/platform/editor/common/editor'; -import { IInstantiationService, IConstructorSignature0, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, IConstructorSignature0, ServicesAccessor, BrandedService } from 'vs/platform/instantiation/common/instantiation'; import { RawContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { Registry } from 'vs/platform/registry/common/platform'; import { ITextModel } from 'vs/editor/common/model'; -import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ICompositeControl } from 'vs/workbench/common/composite'; import { ActionRunner, IAction } from 'vs/base/common/actions'; import { IFileService } from 'vs/platform/files/common/files'; import { IPathData } from 'vs/platform/windows/common/windows'; import { coalesce, firstOrDefault } from 'vs/base/common/arrays'; +import { ITextFileSaveOptions, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { isEqual } from 'vs/base/common/resources'; +import { IPanel } from 'vs/workbench/common/panel'; +export const DirtyWorkingCopiesContext = new RawContextKey('dirtyWorkingCopies', false); export const ActiveEditorContext = new RawContextKey('activeEditor', null); -export const ActiveEditorIsSaveableContext = new RawContextKey('activeEditorIsSaveable', false); +export const ActiveEditorIsReadonlyContext = new RawContextKey('activeEditorIsReadonly', false); export const EditorsVisibleContext = new RawContextKey('editorIsOpen', false); export const EditorPinnedContext = new RawContextKey('editorPinned', false); export const EditorGroupActiveEditorDirtyContext = new RawContextKey('groupActiveEditorDirty', false); @@ -50,7 +55,7 @@ export const TEXT_DIFF_EDITOR_ID = 'workbench.editors.textDiffEditor'; */ export const BINARY_DIFF_EDITOR_ID = 'workbench.editors.binaryResourceDiffEditor'; -export interface IEditor { +export interface IEditor extends IPanel { /** * The assigned input of this editor. @@ -92,21 +97,11 @@ export interface IEditor { */ readonly onDidSizeConstraintsChange: Event<{ width: number; height: number; } | undefined>; - /** - * Returns the unique identifier of this editor. - */ - getId(): string; - /** * Returns the underlying control of this editor. */ getControl(): IEditorControl | undefined; - /** - * Asks the underlying control to focus. - */ - focus(): void; - /** * Finds out if this editor is visible or not. */ @@ -119,6 +114,17 @@ export interface ITextEditor extends IEditor { * Returns the underlying text editor widget of this editor. */ getControl(): ICodeEditor | undefined; + + /** + * Returns the current view state of the text editor if any. + */ + getViewState(): IEditorViewState | undefined; +} + +export function isTextEditor(thing: IEditor | undefined): thing is ITextEditor { + const candidate = thing as ITextEditor | undefined; + + return typeof candidate?.getViewState === 'function'; } export interface ITextDiffEditor extends IEditor { @@ -175,7 +181,7 @@ export interface IEditorInputFactoryRegistry { * @param editorInputId the identifier of the editor input * @param factory the editor input factory for serialization/deserialization */ - registerEditorInputFactory(editorInputId: string, ctor: IConstructorSignature0): void; + registerEditorInputFactory(editorInputId: string, ctor: { new(...Services: Services): IEditorInputFactory }): void; /** * Returns the editor input factory for the given editor input. @@ -205,11 +211,11 @@ export interface IEditorInputFactory { deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): EditorInput | undefined; } -export interface IUntitledResourceInput extends IBaseResourceInput { +export interface IUntitledTextResourceInput extends IBaseResourceInput { /** * Optional resource. If the resource is not provided a new untitled file is created (e.g. Untitled-1). - * Otherwise the untitled editor will have an associated path and use that when saving. + * Otherwise the untitled text editor will have an associated path and use that when saving. */ resource?: URI; @@ -261,15 +267,67 @@ export const enum Verbosity { LONG } +export const enum SaveReason { + + /** + * Explicit user gesture. + */ + EXPLICIT = 1, + + /** + * Auto save after a timeout. + */ + AUTO = 2, + + /** + * Auto save after editor focus change. + */ + FOCUS_CHANGE = 3, + + /** + * Auto save after window change. + */ + WINDOW_CHANGE = 4 +} + +export interface ISaveOptions { + + /** + * An indicator how the save operation was triggered. + */ + reason?: SaveReason; + + /** + * Forces to load the contents of the working copy + * again even if the working copy is not dirty. + */ + force?: boolean; + + /** + * Instructs the save operation to skip any save participants. + */ + skipSaveParticipants?: boolean; + + /** + * A hint as to which file systems should be available for saving. + */ + availableFileSystems?: string[]; +} + export interface IRevertOptions { /** - * Forces to load the contents of the editor again even if the editor is not dirty. + * Forces to load the contents of the working copy + * again even if the working copy is not dirty. */ force?: boolean; /** - * A soft revert will clear dirty state of an editor but will not attempt to load it. + * A soft revert will clear dirty state of a working copy + * but will not attempt to load it from its persisted state. + * + * This option may be used in scenarios where an editor is + * closed and where we do not require to load the contents. */ soft?: boolean; } @@ -279,7 +337,7 @@ export interface IEditorInput extends IDisposable { /** * Triggered when this input is disposed. */ - onDispose: Event; + readonly onDispose: Event; /** * Returns the associated resource of this input. @@ -294,7 +352,7 @@ export interface IEditorInput extends IDisposable { /** * Returns the display name of this input. */ - getName(): string | undefined; + getName(): string; /** * Returns the display description of this input. @@ -311,11 +369,40 @@ export interface IEditorInput extends IDisposable { */ resolve(): Promise; + /** + * Returns if this input is readonly or not. + */ + isReadonly(): boolean; + + /** + * Returns if the input is an untitled editor or not. + */ + isUntitled(): boolean; + /** * Returns if this input is dirty or not. */ isDirty(): boolean; + /** + * Saves the editor. The provided groupId helps + * implementors to e.g. preserve view state of the editor + * and re-open it in the correct group after saving. + */ + save(groupId: GroupIdentifier, options?: ISaveOptions): Promise; + + /** + * Saves the editor to a different location. The provided groupId + * helps implementors to e.g. preserve view state of the editor + * and re-open it in the correct group after saving. + */ + saveAs(groupId: GroupIdentifier, options?: ISaveOptions): Promise; + + /** + * Handles when the input is replaced, such as by renaming its backing resource. + */ + handleMove?(groupId: GroupIdentifier, uri: URI, options?: ITextEditorOptions): IEditorInput | undefined; + /** * Reverts this input. */ @@ -325,6 +412,11 @@ export interface IEditorInput extends IDisposable { * Returns if the other object matches this input. */ matches(other: unknown): boolean; + + /** + * Returns if this editor is disposed. + */ + isDisposed(): boolean; } /** @@ -333,50 +425,32 @@ export interface IEditorInput extends IDisposable { */ export abstract class EditorInput extends Disposable implements IEditorInput { - protected readonly _onDidChangeDirty: Emitter = this._register(new Emitter()); - readonly onDidChangeDirty: Event = this._onDidChangeDirty.event; + protected readonly _onDidChangeDirty = this._register(new Emitter()); + readonly onDidChangeDirty = this._onDidChangeDirty.event; - protected readonly _onDidChangeLabel: Emitter = this._register(new Emitter()); - readonly onDidChangeLabel: Event = this._onDidChangeLabel.event; + protected readonly _onDidChangeLabel = this._register(new Emitter()); + readonly onDidChangeLabel = this._onDidChangeLabel.event; - private readonly _onDispose: Emitter = this._register(new Emitter()); - readonly onDispose: Event = this._onDispose.event; + private readonly _onDispose = this._register(new Emitter()); + readonly onDispose = this._onDispose.event; private disposed: boolean = false; - /** - * Returns the unique type identifier of this input. - */ abstract getTypeId(): string; - /** - * Returns the associated resource of this input if any. - */ getResource(): URI | undefined { return undefined; } - /** - * Returns the name of this input that can be shown to the user. Examples include showing the name of the input - * above the editor area when the input is shown. - */ - getName(): string | undefined { - return undefined; + getName(): string { + return `Editor ${this.getTypeId()}`; } - /** - * Returns the description of this input that can be shown to the user. Examples include showing the description of - * the input above the editor area to the side of the name of the input. - */ getDescription(verbosity?: Verbosity): string | undefined { return undefined; } - /** - * Returns the title of this input that can be shown to the user. Examples include showing the title of - * the input above the editor area as hover over the input label. - */ - getTitle(verbosity?: Verbosity): string | undefined { + getTitle(verbosity?: Verbosity): string { return this.getName(); } @@ -389,10 +463,10 @@ export abstract class EditorInput extends Disposable implements IEditorInput { } /** - * Returns a descriptor suitable for telemetry events. - * - * Subclasses should extend if they can contribute. - */ + * Returns a descriptor suitable for telemetry events. + * + * Subclasses should extend if they can contribute. + */ getTelemetryDescriptor(): { [key: string]: unknown } { /* __GDPR__FRAGMENT__ "EditorTelemetryDescriptor" : { @@ -408,39 +482,28 @@ export abstract class EditorInput extends Disposable implements IEditorInput { */ abstract resolve(): Promise; - /** - * An editor that is dirty will be asked to be saved once it closes. - */ - isDirty(): boolean { + isReadonly(): boolean { + return true; + } + + isUntitled(): boolean { return false; } - /** - * Subclasses should bring up a proper dialog for the user if the editor is dirty and return the result. - */ - confirmSave(): Promise { - return Promise.resolve(ConfirmResult.DONT_SAVE); + isDirty(): boolean { + return false; } - /** - * Saves the editor if it is dirty. Subclasses return a promise with a boolean indicating the success of the operation. - */ - save(): Promise { + save(groupId: GroupIdentifier, options?: ISaveOptions): Promise { return Promise.resolve(true); } - /** - * Reverts the editor if it is dirty. Subclasses return a promise with a boolean indicating the success of the operation. - */ - revert(options?: IRevertOptions): Promise { + saveAs(groupId: GroupIdentifier, options?: ISaveOptions): Promise { return Promise.resolve(true); } - /** - * Called when this input is no longer opened in any editor. Subclasses can free resources as needed. - */ - close(): void { - this.dispose(); + revert(options?: IRevertOptions): Promise { + return Promise.resolve(true); } /** @@ -450,24 +513,14 @@ export abstract class EditorInput extends Disposable implements IEditorInput { return true; } - /** - * Returns true if this input is identical to the otherInput. - */ matches(otherInput: unknown): boolean { return this === otherInput; } - /** - * Returns whether this input was disposed or not. - */ isDisposed(): boolean { return this.disposed; } - /** - * Called when an editor input is no longer needed. Allows to free up any resources taken by - * resolving the editor input. - */ dispose(): void { this.disposed = true; this._onDispose.fire(); @@ -476,10 +529,61 @@ export abstract class EditorInput extends Disposable implements IEditorInput { } } -export const enum ConfirmResult { - SAVE, - DONT_SAVE, - CANCEL +export abstract class TextEditorInput extends EditorInput { + + constructor( + protected readonly resource: URI, + @IEditorService protected readonly editorService: IEditorService, + @IEditorGroupsService protected readonly editorGroupService: IEditorGroupsService, + @ITextFileService protected readonly textFileService: ITextFileService + ) { + super(); + } + + getResource(): URI { + return this.resource; + } + + async save(groupId: GroupIdentifier, options?: ITextFileSaveOptions): Promise { + if (this.isReadonly()) { + return false; // return early if editor is readonly + } + + return this.textFileService.save(this.resource, options); + } + + saveAs(group: GroupIdentifier, options?: ITextFileSaveOptions): Promise { + return this.doSaveAs(group, () => this.textFileService.saveAs(this.resource, undefined, options)); + } + + protected async doSaveAs(group: GroupIdentifier, saveRunnable: () => Promise, replaceAllEditors?: boolean): Promise { + + // Preserve view state by opening the editor first. In addition + // this allows the user to review the contents of the editor. + let viewState: IEditorViewState | undefined = undefined; + const editor = await this.editorService.openEditor(this, undefined, group); + if (isTextEditor(editor)) { + viewState = editor.getViewState(); + } + + // Save as + const target = await saveRunnable(); + if (!target) { + return false; // save cancelled + } + + // Replace editor preserving viewstate (either across all groups or + // only selected group) if the target is different from the current resource + if (!isEqual(target, this.resource)) { + const replacement = this.editorService.createInput({ resource: target }); + const targetGroups = replaceAllEditors ? this.editorGroupService.groups.map(group => group.id) : [group]; + for (const group of targetGroups) { + await this.editorService.replaceEditors([{ editor: this, replacement, options: { pinned: true, viewState } }], group); + } + } + + return true; + } } export const enum EncodingMode { @@ -569,20 +673,28 @@ export class SideBySideEditorInput extends EditorInput { return this._details; } + isReadonly(): boolean { + return this.master.isReadonly(); + } + + isUntitled(): boolean { + return this.master.isUntitled(); + } + isDirty(): boolean { return this.master.isDirty(); } - confirmSave(): Promise { - return this.master.confirmSave(); + save(groupId: GroupIdentifier, options?: ISaveOptions): Promise { + return this.master.save(groupId, options); } - save(): Promise { - return this.master.save(); + saveAs(groupId: GroupIdentifier, options?: ISaveOptions): Promise { + return this.master.saveAs(groupId, options); } - revert(): Promise { - return this.master.revert(); + revert(options?: IRevertOptions): Promise { + return this.master.revert(options); } getTelemetryDescriptor(): { [key: string]: unknown } { @@ -657,8 +769,8 @@ export interface ITextEditorModel extends IEditorModel { */ export class EditorModel extends Disposable implements IEditorModel { - private readonly _onDispose: Emitter = this._register(new Emitter()); - readonly onDispose: Event = this._onDispose.event; + private readonly _onDispose = this._register(new Emitter()); + readonly onDispose = this._onDispose.event; /** * Causes this model to load returning a promise when loading is completed. @@ -1149,7 +1261,7 @@ export const Extensions = { Registry.add(Extensions.EditorInputFactories, new EditorInputFactoryRegistry()); -export async function pathsToEditors(paths: IPathData[] | undefined, fileService: IFileService): Promise<(IResourceInput | IUntitledResourceInput)[]> { +export async function pathsToEditors(paths: IPathData[] | undefined, fileService: IFileService): Promise<(IResourceInput | IUntitledTextResourceInput)[]> { if (!paths || !paths.length) { return []; } @@ -1170,7 +1282,7 @@ export async function pathsToEditors(paths: IPathData[] | undefined, fileService }; } - let input: IResourceInput | IUntitledResourceInput; + let input: IResourceInput | IUntitledTextResourceInput; if (!exists) { input = { resource, options, forceUntitled: true }; } else { diff --git a/src/vs/workbench/common/editor/binaryEditorModel.ts b/src/vs/workbench/common/editor/binaryEditorModel.ts index 2f55727b35e3..603b0e6c04b5 100644 --- a/src/vs/workbench/common/editor/binaryEditorModel.ts +++ b/src/vs/workbench/common/editor/binaryEditorModel.ts @@ -6,8 +6,6 @@ import { EditorModel } from 'vs/workbench/common/editor'; import { URI } from 'vs/base/common/uri'; import { IFileService } from 'vs/platform/files/common/files'; -import { Schemas } from 'vs/base/common/network'; -import { DataUri, basename } from 'vs/base/common/resources'; import { MIME_BINARY } from 'vs/base/common/mime'; /** @@ -19,8 +17,8 @@ export class BinaryEditorModel extends EditorModel { private readonly mime: string; constructor( - private readonly resource: URI, - private readonly name: string | undefined, + public readonly resource: URI, + private readonly name: string, @IFileService private readonly fileService: IFileService ) { super(); @@ -28,32 +26,13 @@ export class BinaryEditorModel extends EditorModel { this.resource = resource; this.name = name; this.mime = MIME_BINARY; - - if (resource.scheme === Schemas.data) { - const metadata = DataUri.parseMetaData(resource); - if (metadata.has(DataUri.META_DATA_SIZE)) { - this.size = Number(metadata.get(DataUri.META_DATA_SIZE)); - } - - const metadataMime = metadata.get(DataUri.META_DATA_MIME); - if (metadataMime) { - this.mime = metadataMime; - } - } } /** * The name of the binary resource. */ getName(): string { - return this.name || basename(this.resource); - } - - /** - * The resource of the binary resource. - */ - getResource(): URI { - return this.resource; + return this.name; } /** diff --git a/src/vs/workbench/common/editor/dataUriEditorInput.ts b/src/vs/workbench/common/editor/dataUriEditorInput.ts deleted file mode 100644 index 35195c30743b..000000000000 --- a/src/vs/workbench/common/editor/dataUriEditorInput.ts +++ /dev/null @@ -1,73 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { EditorInput } from 'vs/workbench/common/editor'; -import { URI } from 'vs/base/common/uri'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { BinaryEditorModel } from 'vs/workbench/common/editor/binaryEditorModel'; -import { DataUri } from 'vs/base/common/resources'; - -/** - * An editor input to present data URIs in a binary editor. Data URIs have the form of: - * data:[mime type];[meta data ;...];base64,[base64 encoded value] - */ -export class DataUriEditorInput extends EditorInput { - - static readonly ID: string = 'workbench.editors.dataUriEditorInput'; - - constructor( - private readonly name: string | undefined, - private readonly description: string | undefined, - private readonly resource: URI, - @IInstantiationService private readonly instantiationService: IInstantiationService - ) { - super(); - - if (!this.name || !this.description) { - const metadata = DataUri.parseMetaData(this.resource); - - if (!this.name) { - this.name = metadata.get(DataUri.META_DATA_LABEL); - } - - if (!this.description) { - this.description = metadata.get(DataUri.META_DATA_DESCRIPTION); - } - } - } - - getResource(): URI { - return this.resource; - } - - getTypeId(): string { - return DataUriEditorInput.ID; - } - - getName(): string | undefined { - return this.name; - } - - getDescription(): string | undefined { - return this.description; - } - - resolve(): Promise { - return this.instantiationService.createInstance(BinaryEditorModel, this.resource, this.getName()).load(); - } - - matches(otherInput: unknown): boolean { - if (super.matches(otherInput) === true) { - return true; - } - - // Compare by resource - if (otherInput instanceof DataUriEditorInput) { - return otherInput.resource.toString() === this.resource.toString(); - } - - return false; - } -} diff --git a/src/vs/workbench/common/editor/diffEditorInput.ts b/src/vs/workbench/common/editor/diffEditorInput.ts index 29d71bfd3f4c..f3600a257dce 100644 --- a/src/vs/workbench/common/editor/diffEditorInput.ts +++ b/src/vs/workbench/common/editor/diffEditorInput.ts @@ -18,7 +18,13 @@ export class DiffEditorInput extends SideBySideEditorInput { private cachedModel: DiffEditorModel | null = null; - constructor(name: string, description: string | undefined, original: EditorInput, modified: EditorInput, private readonly forceOpenAsBinary?: boolean) { + constructor( + name: string, + description: string | undefined, + original: EditorInput, + modified: EditorInput, + private readonly forceOpenAsBinary?: boolean + ) { super(name, description, original, modified); } diff --git a/src/vs/workbench/common/editor/editorGroup.ts b/src/vs/workbench/common/editor/editorGroup.ts index 07815ffaaa25..4690a6c3648d 100644 --- a/src/vs/workbench/common/editor/editorGroup.ts +++ b/src/vs/workbench/common/editor/editorGroup.ts @@ -4,19 +4,17 @@ *--------------------------------------------------------------------------------------------*/ import { Event, Emitter } from 'vs/base/common/event'; -import { Extensions, IEditorInputFactoryRegistry, EditorInput, toResource, IEditorIdentifier, IEditorCloseEvent, GroupIdentifier, SideBySideEditorInput, CloseDirection, IEditorInput, SideBySideEditor } from 'vs/workbench/common/editor'; -import { URI } from 'vs/base/common/uri'; +import { Extensions, IEditorInputFactoryRegistry, EditorInput, IEditorIdentifier, IEditorCloseEvent, GroupIdentifier, CloseDirection, IEditorInput, SideBySideEditorInput } from 'vs/workbench/common/editor'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; import { dispose, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; -import { ResourceMap } from 'vs/base/common/map'; import { coalesce, firstIndex } from 'vs/base/common/arrays'; // {{SQL CARBON EDIT}} -import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; import { QueryEditorInput } from 'sql/workbench/contrib/query/common/queryEditorInput'; +import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput'; import { doHandleUpgrade } from 'sql/workbench/common/languageAssociation'; const EditorOpenPositioning = { @@ -66,31 +64,31 @@ export class EditorGroup extends Disposable { //#region events private readonly _onDidEditorActivate = this._register(new Emitter()); - readonly onDidEditorActivate: Event = this._onDidEditorActivate.event; + readonly onDidEditorActivate = this._onDidEditorActivate.event; private readonly _onDidEditorOpen = this._register(new Emitter()); - readonly onDidEditorOpen: Event = this._onDidEditorOpen.event; + readonly onDidEditorOpen = this._onDidEditorOpen.event; private readonly _onDidEditorClose = this._register(new Emitter()); - readonly onDidEditorClose: Event = this._onDidEditorClose.event; + readonly onDidEditorClose = this._onDidEditorClose.event; private readonly _onDidEditorDispose = this._register(new Emitter()); - readonly onDidEditorDispose: Event = this._onDidEditorDispose.event; + readonly onDidEditorDispose = this._onDidEditorDispose.event; private readonly _onDidEditorBecomeDirty = this._register(new Emitter()); - readonly onDidEditorBecomeDirty: Event = this._onDidEditorBecomeDirty.event; + readonly onDidEditorBecomeDirty = this._onDidEditorBecomeDirty.event; private readonly _onDidEditorLabelChange = this._register(new Emitter()); - readonly onDidEditorLabelChange: Event = this._onDidEditorLabelChange.event; + readonly onDidEditorLabelChange = this._onDidEditorLabelChange.event; private readonly _onDidEditorMove = this._register(new Emitter()); - readonly onDidEditorMove: Event = this._onDidEditorMove.event; + readonly onDidEditorMove = this._onDidEditorMove.event; private readonly _onDidEditorPin = this._register(new Emitter()); - readonly onDidEditorPin: Event = this._onDidEditorPin.event; + readonly onDidEditorPin = this._onDidEditorPin.event; private readonly _onDidEditorUnpin = this._register(new Emitter()); - readonly onDidEditorUnpin: Event = this._onDidEditorUnpin.event; + readonly onDidEditorUnpin = this._onDidEditorUnpin.event; //#endregion @@ -99,7 +97,6 @@ export class EditorGroup extends Disposable { private editors: EditorInput[] = []; private mru: EditorInput[] = []; - private mapResourceToEditorCount: ResourceMap = new ResourceMap(); private preview: EditorInput | null = null; // editor in preview state private active: EditorInput | null = null; // editor in active state @@ -141,26 +138,8 @@ export class EditorGroup extends Disposable { return mru ? this.mru.slice(0) : this.editors.slice(0); } - getEditor(index: number): EditorInput | undefined; - getEditor(resource: URI): EditorInput | undefined; - getEditor(arg1: number | URI): EditorInput | undefined { - if (typeof arg1 === 'number') { - return this.editors[arg1]; - } - - const resource: URI = arg1; - if (!this.contains(resource)) { - return undefined; // fast check for resource opened or not - } - - for (const editor of this.editors) { - const editorResource = toResource(editor, { supportSideBySide: SideBySideEditor.MASTER }); - if (editorResource?.toString() === resource.toString()) { - return editor; - } - } - - return undefined; + getEditorByIndex(index: number): EditorInput | undefined { + return this.editors[index]; } get activeEditor(): EditorInput | null { @@ -515,7 +494,6 @@ export class EditorGroup extends Disposable { // Add if (!del && editor) { this.mru.push(editor); // make it LRU editor - this.updateResourceMap(editor, false /* add */); // add new to resource map } // Remove / Replace @@ -525,41 +503,11 @@ export class EditorGroup extends Disposable { // Remove if (del && !editor) { this.mru.splice(indexInMRU, 1); // remove from MRU - this.updateResourceMap(editorToDeleteOrReplace, true /* delete */); // remove from resource map } // Replace else if (del && editor) { this.mru.splice(indexInMRU, 1, editor); // replace MRU at location - this.updateResourceMap(editor, false /* add */); // add new to resource map - this.updateResourceMap(editorToDeleteOrReplace, true /* delete */); // remove replaced from resource map - } - } - } - - private updateResourceMap(editor: EditorInput, remove: boolean): void { - const resource = toResource(editor, { supportSideBySide: SideBySideEditor.MASTER }); - if (resource) { - - // It is possible to have the same resource opened twice (once as normal input and once as diff input) - // So we need to do ref counting on the resource to provide the correct picture - const counter = this.mapResourceToEditorCount.get(resource) || 0; - - // Add - let newCounter: number; - if (!remove) { - newCounter = counter + 1; - } - - // Delete - else { - newCounter = counter - 1; - } - - if (newCounter > 0) { - this.mapResourceToEditorCount.set(resource, newCounter); - } else { - this.mapResourceToEditorCount.delete(resource); } } } @@ -578,28 +526,20 @@ export class EditorGroup extends Disposable { return -1; } - contains(editorOrResource: EditorInput | URI): boolean; - contains(editor: EditorInput, supportSideBySide?: boolean): boolean; - contains(editorOrResource: EditorInput | URI, supportSideBySide?: boolean): boolean { - if (editorOrResource instanceof EditorInput) { - const index = this.indexOf(editorOrResource); - if (index >= 0) { + contains(candidate: EditorInput, searchInSideBySideEditors?: boolean): boolean { + for (const editor of this.editors) { + if (this.matches(editor, candidate)) { return true; } - if (supportSideBySide && editorOrResource instanceof SideBySideEditorInput) { - const index = this.indexOf(editorOrResource.master); - if (index >= 0) { + if (searchInSideBySideEditors && editor instanceof SideBySideEditorInput) { + if (this.matches(editor.master, candidate) || this.matches(editor.details, candidate)) { return true; } } - - return false; } - const counter = this.mapResourceToEditorCount.get(editorOrResource); - - return typeof counter === 'number' && counter > 0; + return false; } private setMostRecentlyUsed(editor: EditorInput): void { @@ -625,7 +565,6 @@ export class EditorGroup extends Disposable { const group = this.instantiationService.createInstance(EditorGroup, undefined); group.editors = this.editors.slice(0); group.mru = this.mru.slice(0); - group.mapResourceToEditorCount = this.mapResourceToEditorCount.clone(); group.preview = this.preview; group.active = this.active; group.editorOpenPositioning = this.editorOpenPositioning; @@ -647,7 +586,7 @@ export class EditorGroup extends Disposable { if (factory) { // {{SQL CARBON EDIT}} // don't serialize unmodified unitited files - if (e instanceof UntitledEditorInput && !e.isDirty() + if (e instanceof UntitledTextEditorInput && !e.isDirty() && !this.configurationService.getValue('sql.promptToSaveGeneratedFiles')) { return; } @@ -692,7 +631,6 @@ export class EditorGroup extends Disposable { const editor = this.instantiationService.invokeFunction(doHandleUpgrade, factory.deserialize(this.instantiationService, e.value)); // {{SQL CARBON EDIT}} handle upgrade path to new serialization if (editor) { this.registerEditorListeners(editor); - this.updateResourceMap(editor, false /* add */); } return editor; @@ -725,7 +663,7 @@ export class EditorGroup extends Disposable { // remove from MRU list otherwise later if we try to close them it leaves a sticky active editor with no data this.mru.splice(index, 1); this.active = this.isActive(editor) ? this.editors[0] : this.active; - editor.close(); + editor.dispose(); } else { n++; diff --git a/src/vs/workbench/common/editor/resourceEditorModel.ts b/src/vs/workbench/common/editor/resourceEditorModel.ts index 32bec24ef6bf..e1caa2daf71b 100644 --- a/src/vs/workbench/common/editor/resourceEditorModel.ts +++ b/src/vs/workbench/common/editor/resourceEditorModel.ts @@ -21,10 +21,6 @@ export class ResourceEditorModel extends BaseTextEditorModel { super(modelService, modeService, resource); } - isReadonly(): boolean { - return true; - } - dispose(): void { // TODO@Joao: force this class to dispose the underlying model diff --git a/src/vs/workbench/common/editor/textEditorModel.ts b/src/vs/workbench/common/editor/textEditorModel.ts index e07dcda8574f..f5479fee230f 100644 --- a/src/vs/workbench/common/editor/textEditorModel.ts +++ b/src/vs/workbench/common/editor/textEditorModel.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ITextModel, ITextBufferFactory, ITextSnapshot } from 'vs/editor/common/model'; +import { ITextModel, ITextBufferFactory, ITextSnapshot, ModelConstants } from 'vs/editor/common/model'; import { EditorModel, IModeSupport } from 'vs/workbench/common/editor'; import { URI } from 'vs/base/common/uri'; import { ITextEditorModel, IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService'; @@ -16,8 +16,10 @@ import { withUndefinedAsNull } from 'vs/base/common/types'; /** * The base text editor model leverages the code editor model. This class is only intended to be subclassed and not instantiated. */ -export abstract class BaseTextEditorModel extends EditorModel implements ITextEditorModel, IModeSupport { +export class BaseTextEditorModel extends EditorModel implements ITextEditorModel, IModeSupport { + protected textEditorModelHandle: URI | null = null; + private createdEditorModel: boolean | undefined; private readonly modelDisposeListener = this._register(new MutableDisposable()); @@ -59,7 +61,9 @@ export abstract class BaseTextEditorModel extends EditorModel implements ITextEd return this.textEditorModelHandle ? this.modelService.getModel(this.textEditorModelHandle) : null; } - abstract isReadonly(): boolean; + isReadonly(): boolean { + return true; + } setMode(mode: string): void { if (!this.isResolved()) { @@ -106,12 +110,12 @@ export abstract class BaseTextEditorModel extends EditorModel implements ITextEd // text buffer factory const textBufferFactory = value as ITextBufferFactory; if (typeof textBufferFactory.getFirstLineText === 'function') { - return textBufferFactory.getFirstLineText(100); + return textBufferFactory.getFirstLineText(ModelConstants.FIRST_LINE_DETECTION_LENGTH_LIMIT); } // text model const textSnapshot = value as ITextModel; - return textSnapshot.getLineContent(1).substr(0, 100); + return textSnapshot.getLineContent(1).substr(0, ModelConstants.FIRST_LINE_DETECTION_LENGTH_LIMIT); } /** diff --git a/src/vs/workbench/common/editor/untitledEditorInput.ts b/src/vs/workbench/common/editor/untitledTextEditorInput.ts similarity index 56% rename from src/vs/workbench/common/editor/untitledEditorInput.ts rename to src/vs/workbench/common/editor/untitledTextEditorInput.ts index a9da560f872d..2e00a3d55428 100644 --- a/src/vs/workbench/common/editor/untitledEditorInput.ts +++ b/src/vs/workbench/common/editor/untitledTextEditorInput.ts @@ -5,44 +5,58 @@ import { URI } from 'vs/base/common/uri'; import { suggestFilename } from 'vs/base/common/mime'; -import { memoize } from 'vs/base/common/decorators'; +import { createMemoizer } from 'vs/base/common/decorators'; import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; -import { basenameOrAuthority, dirname } from 'vs/base/common/resources'; -import { EditorInput, IEncodingSupport, EncodingMode, ConfirmResult, Verbosity, IModeSupport } from 'vs/workbench/common/editor'; -import { UntitledEditorModel } from 'vs/workbench/common/editor/untitledEditorModel'; +import { basenameOrAuthority, dirname, toLocalResource } from 'vs/base/common/resources'; +import { IEncodingSupport, EncodingMode, Verbosity, IModeSupport, TextEditorInput, GroupIdentifier, IRevertOptions } from 'vs/workbench/common/editor'; +import { UntitledTextEditorModel } from 'vs/workbench/common/editor/untitledTextEditorModel'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { Event, Emitter } from 'vs/base/common/event'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { Emitter } from 'vs/base/common/event'; +import { ITextFileService, ITextFileSaveOptions } from 'vs/workbench/services/textfile/common/textfiles'; import { ILabelService } from 'vs/platform/label/common/label'; import { IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; /** * An editor input to be used for untitled text buffers. */ -export class UntitledEditorInput extends EditorInput implements IEncodingSupport, IModeSupport { +export class UntitledTextEditorInput extends TextEditorInput implements IEncodingSupport, IModeSupport { static readonly ID: string = 'workbench.editors.untitledEditorInput'; - private cachedModel: UntitledEditorModel | null = null; - private modelResolve: Promise | null = null; + private static readonly MEMOIZER = createMemoizer(); - private readonly _onDidModelChangeContent: Emitter = this._register(new Emitter()); - readonly onDidModelChangeContent: Event = this._onDidModelChangeContent.event; + private cachedModel: UntitledTextEditorModel | null = null; + private modelResolve: Promise | null = null; - private readonly _onDidModelChangeEncoding: Emitter = this._register(new Emitter()); - readonly onDidModelChangeEncoding: Event = this._onDidModelChangeEncoding.event; + private readonly _onDidModelChangeContent = this._register(new Emitter()); + readonly onDidModelChangeContent = this._onDidModelChangeContent.event; + + private readonly _onDidModelChangeEncoding = this._register(new Emitter()); + readonly onDidModelChangeEncoding = this._onDidModelChangeEncoding.event; constructor( - private readonly resource: URI, + resource: URI, private readonly _hasAssociatedFilePath: boolean, private preferredMode: string | undefined, private readonly initialValue: string | undefined, private preferredEncoding: string | undefined, @IInstantiationService private readonly instantiationService: IInstantiationService, - @ITextFileService private readonly textFileService: ITextFileService, - @ILabelService private readonly labelService: ILabelService + @ITextFileService textFileService: ITextFileService, + @ILabelService private readonly labelService: ILabelService, + @IEditorService editorService: IEditorService, + @IEditorGroupsService editorGroupService: IEditorGroupsService, + @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService ) { - super(); + super(resource, editorService, editorGroupService, textFileService); + + this.registerListeners(); + } + + private registerListeners(): void { + this._register(this.labelService.onDidChangeFormatters(() => UntitledTextEditorInput.MEMOIZER.clear())); } get hasAssociatedFilePath(): boolean { @@ -50,28 +64,24 @@ export class UntitledEditorInput extends EditorInput implements IEncodingSupport } getTypeId(): string { - return UntitledEditorInput.ID; - } - - getResource(): URI { - return this.resource; + return UntitledTextEditorInput.ID; } getName(): string { return this.hasAssociatedFilePath ? basenameOrAuthority(this.resource) : this.resource.path; } - @memoize + @UntitledTextEditorInput.MEMOIZER private get shortDescription(): string { return this.labelService.getUriBasenameLabel(dirname(this.resource)); } - @memoize + @UntitledTextEditorInput.MEMOIZER private get mediumDescription(): string { return this.labelService.getUriLabel(dirname(this.resource), { relative: true }); } - @memoize + @UntitledTextEditorInput.MEMOIZER private get longDescription(): string { return this.labelService.getUriLabel(dirname(this.resource)); } @@ -92,22 +102,22 @@ export class UntitledEditorInput extends EditorInput implements IEncodingSupport } } - @memoize + @UntitledTextEditorInput.MEMOIZER private get shortTitle(): string { return this.getName(); } - @memoize + @UntitledTextEditorInput.MEMOIZER private get mediumTitle(): string { return this.labelService.getUriLabel(this.resource, { relative: true }); } - @memoize + @UntitledTextEditorInput.MEMOIZER private get longTitle(): string { return this.labelService.getUriLabel(this.resource); } - getTitle(verbosity: Verbosity): string | undefined { + getTitle(verbosity: Verbosity): string { if (!this.hasAssociatedFilePath) { return this.getName(); } @@ -120,8 +130,14 @@ export class UntitledEditorInput extends EditorInput implements IEncodingSupport case Verbosity.LONG: return this.longTitle; } + } - return undefined; + isReadonly(): boolean { + return false; + } + + isUntitled(): boolean { + return true; } isDirty(): boolean { @@ -146,22 +162,37 @@ export class UntitledEditorInput extends EditorInput implements IEncodingSupport return false; } - confirmSave(): Promise { - return this.textFileService.confirmSave([this.resource]); + save(group: GroupIdentifier, options?: ITextFileSaveOptions): Promise { + return this.doSaveAs(group, async () => { + + // With associated file path, save to the path that is + // associated. Make sure to convert the result using + // remote authority properly. + if (this.hasAssociatedFilePath) { + if (await this.textFileService.save(this.resource, options)) { + return toLocalResource(this.resource, this.environmentService.configuration.remoteAuthority); + } + + return undefined; // {{SQL CARBON EDIT}} strict-null-checks + } + + // Without associated file path, do a normal "Save As" + return this.textFileService.saveAs(this.resource, undefined, options); + }, true /* replace editor across all groups */); } - save(): Promise { - return this.textFileService.save(this.resource); + saveAs(group: GroupIdentifier, options?: ITextFileSaveOptions): Promise { + return this.doSaveAs(group, () => this.textFileService.saveAs(this.resource, undefined, options), true /* replace editor across all groups */); } - revert(): Promise { + async revert(options?: IRevertOptions): Promise { if (this.cachedModel) { this.cachedModel.revert(); } - this.dispose(); // a reverted untitled editor is no longer valid, so we dispose it + this.dispose(); // a reverted untitled text editor is no longer valid, so we dispose it - return Promise.resolve(true); + return true; } suggestFileName(): string { @@ -209,7 +240,7 @@ export class UntitledEditorInput extends EditorInput implements IEncodingSupport return this.preferredMode; } - resolve(): Promise { + resolve(): Promise { // Join a model resolve if we have had one before if (this.modelResolve) { @@ -223,8 +254,8 @@ export class UntitledEditorInput extends EditorInput implements IEncodingSupport return this.modelResolve; } - private createModel(): UntitledEditorModel { - const model = this._register(this.instantiationService.createInstance(UntitledEditorModel, this.preferredMode, this.resource, this.hasAssociatedFilePath, this.initialValue, this.preferredEncoding)); + private createModel(): UntitledTextEditorModel { + const model = this._register(this.instantiationService.createInstance(UntitledTextEditorModel, this.preferredMode, this.resource, this.hasAssociatedFilePath, this.initialValue, this.preferredEncoding)); // re-emit some events from the model this._register(model.onDidChangeContent(() => this._onDidModelChangeContent.fire())); @@ -240,7 +271,7 @@ export class UntitledEditorInput extends EditorInput implements IEncodingSupport } // Otherwise compare by properties - if (otherInput instanceof UntitledEditorInput) { + if (otherInput instanceof UntitledTextEditorInput) { return otherInput.resource.toString() === this.resource.toString(); } @@ -253,4 +284,4 @@ export class UntitledEditorInput extends EditorInput implements IEncodingSupport super.dispose(); } -} \ No newline at end of file +} diff --git a/src/vs/workbench/common/editor/untitledEditorModel.ts b/src/vs/workbench/common/editor/untitledTextEditorModel.ts similarity index 78% rename from src/vs/workbench/common/editor/untitledEditorModel.ts rename to src/vs/workbench/common/editor/untitledTextEditorModel.ts index 886b32a3ebdf..e1990c1c4159 100644 --- a/src/vs/workbench/common/editor/untitledEditorModel.ts +++ b/src/vs/workbench/common/editor/untitledTextEditorModel.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IEncodingSupport } from 'vs/workbench/common/editor'; +import { IEncodingSupport, ISaveOptions } from 'vs/workbench/common/editor'; import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel'; import { URI } from 'vs/base/common/uri'; import { CONTENT_CHANGE_EVENT_BUFFER_DELAY } from 'vs/platform/files/common/files'; @@ -16,8 +16,10 @@ import { ITextResourceConfigurationService } from 'vs/editor/common/services/res import { ITextBufferFactory } from 'vs/editor/common/model'; import { createTextBufferFactory } from 'vs/editor/common/model/textModel'; import { IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService'; +import { IWorkingCopyService, IWorkingCopy } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -export class UntitledEditorModel extends BaseTextEditorModel implements IEncodingSupport { +export class UntitledTextEditorModel extends BaseTextEditorModel implements IEncodingSupport, IWorkingCopy { static DEFAULT_CONTENT_CHANGE_BUFFER_DELAY = CONTENT_CHANGE_EVENT_BUFFER_DELAY; @@ -30,33 +32,34 @@ export class UntitledEditorModel extends BaseTextEditorModel implements IEncodin private readonly _onDidChangeEncoding: Emitter = this._register(new Emitter()); readonly onDidChangeEncoding: Event = this._onDidChangeEncoding.event; - private dirty: boolean = false; - private versionId: number = 0; - private readonly contentChangeEventScheduler: RunOnceScheduler; + readonly capabilities = 0; + + private dirty = false; + private versionId = 0; + private readonly contentChangeEventScheduler = this._register(new RunOnceScheduler(() => this._onDidChangeContent.fire(), UntitledTextEditorModel.DEFAULT_CONTENT_CHANGE_BUFFER_DELAY)); private configuredEncoding?: string; constructor( private readonly preferredMode: string | undefined, - private readonly resource: URI, - private _hasAssociatedFilePath: boolean, + public readonly resource: URI, + public readonly hasAssociatedFilePath: boolean, private readonly initialValue: string | undefined, private preferredEncoding: string | undefined, @IModeService modeService: IModeService, @IModelService modelService: IModelService, @IBackupFileService private readonly backupFileService: IBackupFileService, - @ITextResourceConfigurationService private readonly configurationService: ITextResourceConfigurationService + @ITextResourceConfigurationService private readonly configurationService: ITextResourceConfigurationService, + @IWorkingCopyService private readonly workingCopyService: IWorkingCopyService, + @ITextFileService private readonly textFileService: ITextFileService ) { super(modelService, modeService); - this.contentChangeEventScheduler = this._register(new RunOnceScheduler(() => this._onDidChangeContent.fire(), UntitledEditorModel.DEFAULT_CONTENT_CHANGE_BUFFER_DELAY)); + // Make known to working copy service + this._register(this.workingCopyService.registerWorkingCopy(this)); this.registerListeners(); } - get hasAssociatedFilePath(): boolean { - return this._hasAssociatedFilePath; - } - private registerListeners(): void { // Config Changes @@ -116,15 +119,17 @@ export class UntitledEditorModel extends BaseTextEditorModel implements IEncodin this._onDidChangeDirty.fire(); } - getResource(): URI { - return this.resource; + save(options?: ISaveOptions): Promise { + return this.textFileService.save(this.resource, options); } - revert(): void { + async revert(): Promise { this.setDirty(false); // Handle content change event buffered this.contentChangeEventScheduler.schedule(); + + return true; } backup(): Promise { @@ -139,7 +144,7 @@ export class UntitledEditorModel extends BaseTextEditorModel implements IEncodin return this.backupFileService.hasBackupSync(this.resource, this.versionId); } - async load(): Promise { + async load(): Promise { // Check for backups first let backup: IResolvedBackup | undefined = undefined; @@ -149,7 +154,7 @@ export class UntitledEditorModel extends BaseTextEditorModel implements IEncodin } // untitled associated to file path are dirty right away as well as untitled with content - this.setDirty(this._hasAssociatedFilePath || !!backup || !!this.initialValue); + this.setDirty(this.hasAssociatedFilePath || !!backup || !!this.initialValue); let untitledContents: ITextBufferFactory; if (backup) { @@ -180,7 +185,7 @@ export class UntitledEditorModel extends BaseTextEditorModel implements IEncodin // Listen to mode changes this._register(textEditorModel.onDidChangeLanguage(() => this.onConfigurationChange())); // mode change can have impact on config - return this as UntitledEditorModel & IResolvedTextEditorModel; + return this as UntitledTextEditorModel & IResolvedTextEditorModel; } private onModelContentChanged(): void { @@ -190,9 +195,9 @@ export class UntitledEditorModel extends BaseTextEditorModel implements IEncodin this.versionId++; - // mark the untitled editor as non-dirty once its content becomes empty and we do + // mark the untitled text editor as non-dirty once its content becomes empty and we do // not have an associated path set. we never want dirty indicator in that case. - if (!this._hasAssociatedFilePath && this.textEditorModel.getLineCount() === 1 && this.textEditorModel.getLineContent(1) === '') { + if (!this.hasAssociatedFilePath && this.textEditorModel.getLineCount() === 1 && this.textEditorModel.getLineContent(1) === '') { this.setDirty(false); } diff --git a/src/vs/workbench/common/theme.ts b/src/vs/workbench/common/theme.ts index 3e0871eb4a46..7ea68a9a899e 100644 --- a/src/vs/workbench/common/theme.ts +++ b/src/vs/workbench/common/theme.ts @@ -352,6 +352,12 @@ export const ACTIVITY_BAR_ACTIVE_BORDER = registerColor('activityBar.activeBorde hc: null }, nls.localize('activityBarActiveBorder', "Activity bar border color for the active item. The activity bar is showing on the far left or right and allows to switch between views of the side bar.")); +export const ACTIVITY_BAR_ACTIVE_FOCUS_BORDER = registerColor('activityBar.activeFocusBorder', { + dark: null, + light: null, + hc: null +}, nls.localize('activityBarActiveFocusBorder', "Activity bar focus border color for the active item. The activity bar is showing on the far left or right and allows to switch between views of the side bar.")); + export const ACTIVITY_BAR_ACTIVE_BACKGROUND = registerColor('activityBar.activeBackground', { dark: null, light: null, @@ -575,19 +581,31 @@ export const NOTIFICATIONS_ERROR_ICON_FOREGROUND = registerColor('notificationsE dark: editorErrorForeground, light: editorErrorForeground, hc: editorErrorForeground -}, nls.localize('notificationsErrorIconForeground', "The color used for the notification error icon.")); +}, nls.localize('notificationsErrorIconForeground', "The color used for the icon of error notifications. Notifications slide in from the bottom right of the window.")); export const NOTIFICATIONS_WARNING_ICON_FOREGROUND = registerColor('notificationsWarningIcon.foreground', { dark: editorWarningForeground, light: editorWarningForeground, hc: editorWarningForeground -}, nls.localize('notificationsWarningIconForeground', "The color used for the notification warning icon.")); +}, nls.localize('notificationsWarningIconForeground', "The color used for the icon of warning notifications. Notifications slide in from the bottom right of the window.")); export const NOTIFICATIONS_INFO_ICON_FOREGROUND = registerColor('notificationsInfoIcon.foreground', { dark: editorInfoForeground, light: editorInfoForeground, hc: editorInfoForeground -}, nls.localize('notificationsInfoIconForeground', "The color used for the notification info icon.")); +}, nls.localize('notificationsInfoIconForeground', "The color used for the icon of info notifications. Notifications slide in from the bottom right of the window.")); + +export const WINDOW_ACTIVE_BORDER = registerColor('window.activeBorder', { + dark: null, + light: null, + hc: contrastBorder +}, nls.localize('windowActiveBorder', "The color used for the border of the window when it is active. Only supported in the desktop client when using the custom title bar.")); + +export const WINDOW_INACTIVE_BORDER = registerColor('window.inactiveBorder', { + dark: null, + light: null, + hc: contrastBorder +}, nls.localize('windowInactiveBorder', "The color used for the border of the window when it is inactive. Only supported in the desktop client when using the custom title bar.")); /** * Base class for all themable workbench components. diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index 0a743adf6f9e..2eb24d53d2c5 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -424,3 +424,10 @@ export interface ITreeViewDataProvider { getChildren(element?: ITreeItem): Promise; } + +export interface IEditableData { + validationMessage: (value: string) => string | null; + placeholder?: string | null; + startingValue?: string | null; + onFinish: (value: string, success: boolean) => void; +} diff --git a/src/vs/workbench/contrib/backup/common/backupModelTracker.ts b/src/vs/workbench/contrib/backup/common/backupModelTracker.ts index e2883d5a5eec..59b2e5ec075c 100644 --- a/src/vs/workbench/contrib/backup/common/backupModelTracker.ts +++ b/src/vs/workbench/contrib/backup/common/backupModelTracker.ts @@ -7,10 +7,10 @@ import { URI as Uri } from 'vs/base/common/uri'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; import { Disposable } from 'vs/base/common/lifecycle'; import { ITextFileService, TextFileModelChangeEvent, StateChange } from 'vs/workbench/services/textfile/common/textfiles'; -import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; +import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IFilesConfiguration, AutoSaveConfiguration, CONTENT_CHANGE_EVENT_BUFFER_DELAY } from 'vs/platform/files/common/files'; +import { CONTENT_CHANGE_EVENT_BUFFER_DELAY } from 'vs/platform/files/common/files'; +import { IFilesConfigurationService, IAutoSaveConfiguration } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; const AUTO_SAVE_AFTER_DELAY_DISABLED_TIME = CONTENT_CHANGE_EVENT_BUFFER_DELAY + 500; @@ -21,8 +21,8 @@ export class BackupModelTracker extends Disposable implements IWorkbenchContribu constructor( @IBackupFileService private readonly backupFileService: IBackupFileService, @ITextFileService private readonly textFileService: ITextFileService, - @IUntitledEditorService private readonly untitledEditorService: IUntitledEditorService, - @IConfigurationService private readonly configurationService: IConfigurationService + @IUntitledTextEditorService private readonly untitledTextEditorService: IUntitledTextEditorService, + @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService ) { super(); @@ -32,26 +32,20 @@ export class BackupModelTracker extends Disposable implements IWorkbenchContribu private registerListeners() { // Listen for text file model changes - this._register(this.textFileService.models.onModelContentChanged((e) => this.onTextFileModelChanged(e))); - this._register(this.textFileService.models.onModelSaved((e) => this.discardBackup(e.resource))); - this._register(this.textFileService.models.onModelDisposed((e) => this.discardBackup(e))); + this._register(this.textFileService.models.onModelContentChanged(e => this.onTextFileModelChanged(e))); + this._register(this.textFileService.models.onModelSaved(e => this.discardBackup(e.resource))); + this._register(this.textFileService.models.onModelDisposed(e => this.discardBackup(e))); // Listen for untitled model changes - this._register(this.untitledEditorService.onDidChangeContent((e) => this.onUntitledModelChanged(e))); - this._register(this.untitledEditorService.onDidDisposeModel((e) => this.discardBackup(e))); + this._register(this.untitledTextEditorService.onDidChangeContent(e => this.onUntitledModelChanged(e))); + this._register(this.untitledTextEditorService.onDidDisposeModel(e => this.discardBackup(e))); - // Listen to config changes - this._register(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationChange(this.configurationService.getValue()))); + // Listen to auto save config changes + this._register(this.filesConfigurationService.onAutoSaveConfigurationChange(c => this.onAutoSaveConfigurationChange(c))); } - private onConfigurationChange(configuration: IFilesConfiguration): void { - if (!configuration || !configuration.files) { - this.configuredAutoSaveAfterDelay = false; - - return; - } - - this.configuredAutoSaveAfterDelay = (configuration.files.autoSave === AutoSaveConfiguration.AFTER_DELAY && configuration.files.autoSaveDelay <= AUTO_SAVE_AFTER_DELAY_DISABLED_TIME); + private onAutoSaveConfigurationChange(configuration: IAutoSaveConfiguration): void { + this.configuredAutoSaveAfterDelay = typeof configuration.autoSaveDelay === 'number' && configuration.autoSaveDelay < AUTO_SAVE_AFTER_DELAY_DISABLED_TIME; } private onTextFileModelChanged(event: TextFileModelChangeEvent): void { @@ -71,8 +65,8 @@ export class BackupModelTracker extends Disposable implements IWorkbenchContribu } private onUntitledModelChanged(resource: Uri): void { - if (this.untitledEditorService.isDirty(resource)) { - this.untitledEditorService.loadOrCreate({ resource }).then(model => model.backup()); + if (this.untitledTextEditorService.isDirty(resource)) { + this.untitledTextEditorService.loadOrCreate({ resource }).then(model => model.backup()); } else { this.discardBackup(resource); } diff --git a/src/vs/workbench/contrib/backup/common/backupRestorer.ts b/src/vs/workbench/contrib/backup/common/backupRestorer.ts index 40158dc9e36c..091ec7265bcc 100644 --- a/src/vs/workbench/contrib/backup/common/backupRestorer.ts +++ b/src/vs/workbench/contrib/backup/common/backupRestorer.ts @@ -10,7 +10,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { IResourceInput } from 'vs/platform/editor/common/editor'; import { Schemas } from 'vs/base/common/network'; import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; -import { IUntitledResourceInput } from 'vs/workbench/common/editor'; +import { IUntitledTextResourceInput } from 'vs/workbench/common/editor'; import { toLocalResource } from 'vs/base/common/resources'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -76,7 +76,7 @@ export class BackupRestorer implements IWorkbenchContribution { await this.editorService.openEditors(inputs); } - private resolveInput(resource: URI, index: number, hasOpenedEditors: boolean): IResourceInput | IUntitledResourceInput { + private resolveInput(resource: URI, index: number, hasOpenedEditors: boolean): IResourceInput | IUntitledTextResourceInput { const options = { pinned: true, preserveFocus: true, inactive: index > 0 || hasOpenedEditors }; // this is a (weak) strategy to find out if the untitled input had diff --git a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts index ac84a41c81a5..392c14b30354 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts +++ b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import { CallHierarchyProviderRegistry, CallHierarchyDirection } from 'vs/workbench/contrib/callHierarchy/browser/callHierarchy'; +import { CallHierarchyProviderRegistry, CallHierarchyDirection, CallHierarchyModel } from 'vs/workbench/contrib/callHierarchy/browser/callHierarchy'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { CallHierarchyTreePeekWidget } from 'vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek'; @@ -17,9 +17,12 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { PeekContext } from 'vs/editor/contrib/referenceSearch/peekViewWidget'; -import { CallHierarchyRoot } from 'vs/workbench/contrib/callHierarchy/browser/callHierarchyTree'; +import { PeekContext } from 'vs/editor/contrib/peekView/peekView'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import { Range } from 'vs/editor/common/core/range'; +import { IPosition } from 'vs/editor/common/core/position'; +import { MenuId } from 'vs/platform/actions/common/actions'; const _ctxHasCompletionItemProvider = new RawContextKey('editorHasCallHierarchyProvider', false); const _ctxCallHierarchyVisible = new RawContextKey('callHierarchyVisible', false); @@ -39,10 +42,13 @@ class CallHierarchyController implements IEditorContribution { private readonly _dispoables = new DisposableStore(); private readonly _sessionDisposables = new DisposableStore(); + private _widget?: CallHierarchyTreePeekWidget; + constructor( private readonly _editor: ICodeEditor, @IContextKeyService private readonly _contextKeyService: IContextKeyService, @IStorageService private readonly _storageService: IStorageService, + @ICodeEditorService private readonly _editorService: ICodeEditorService, @IInstantiationService private readonly _instantiationService: IInstantiationService, ) { this._ctxIsVisible = _ctxCallHierarchyVisible.bindTo(this._contextKeyService); @@ -59,48 +65,83 @@ class CallHierarchyController implements IEditorContribution { this._dispoables.dispose(); } - async startCallHierarchy(): Promise { + async startCallHierarchyFromEditor(): Promise { this._sessionDisposables.clear(); if (!this._editor.hasModel()) { return; } - const model = this._editor.getModel(); + const document = this._editor.getModel(); const position = this._editor.getPosition(); - const [provider] = CallHierarchyProviderRegistry.ordered(model); - if (!provider) { + if (!CallHierarchyProviderRegistry.has(document)) { return; } + const cts = new CancellationTokenSource(); + const model = CallHierarchyModel.create(document, position, cts.token); const direction = this._storageService.getNumber(CallHierarchyController._StorageDirection, StorageScope.GLOBAL, CallHierarchyDirection.CallsFrom); - Event.any(this._editor.onDidChangeModel, this._editor.onDidChangeModelLanguage)(this.endCallHierarchy, this, this._sessionDisposables); - const widget = this._instantiationService.createInstance( - CallHierarchyTreePeekWidget, - this._editor, - position, - provider, - direction - ); + this._showCallHierarchyWidget(position, direction, model, cts); + } - widget.showLoading(); - this._ctxIsVisible.set(true); + async startCallHierarchyFromCallHierarchy(): Promise { + if (!this._widget) { + return; + } + const model = this._widget.getModel(); + const call = this._widget.getFocused(); + if (!call || !model) { + return; + } + const newEditor = await this._editorService.openCodeEditor({ resource: call.item.uri }, this._editor); + if (!newEditor) { + return; + } + const newModel = model.fork(call.item); + this._sessionDisposables.clear(); - const cancel = new CancellationTokenSource(); + CallHierarchyController.get(newEditor)._showCallHierarchyWidget( + Range.lift(newModel.root.selectionRange).getStartPosition(), + this._widget.direction, + Promise.resolve(newModel), + new CancellationTokenSource() + ); + } + + private _showCallHierarchyWidget(position: IPosition, direction: number, model: Promise, cts: CancellationTokenSource) { - this._sessionDisposables.add(widget.onDidClose(() => { + Event.any(this._editor.onDidChangeModel, this._editor.onDidChangeModelLanguage)(this.endCallHierarchy, this, this._sessionDisposables); + this._widget = this._instantiationService.createInstance(CallHierarchyTreePeekWidget, this._editor, position, direction); + this._widget.showLoading(); + this._ctxIsVisible.set(true); + this._sessionDisposables.add(this._widget.onDidClose(() => { this.endCallHierarchy(); - this._storageService.store(CallHierarchyController._StorageDirection, widget.direction, StorageScope.GLOBAL); + this._storageService.store(CallHierarchyController._StorageDirection, this._widget!.direction, StorageScope.GLOBAL); })); - this._sessionDisposables.add({ dispose() { cancel.cancel(); } }); - this._sessionDisposables.add(widget); - - const root = CallHierarchyRoot.fromEditor(this._editor); - if (root) { - widget.showItem(root); - } else { - widget.showMessage(localize('no.item', "No results")); + this._sessionDisposables.add({ dispose() { cts.dispose(true); } }); + this._sessionDisposables.add(this._widget); + + model.then(model => { + if (cts.token.isCancellationRequested) { + return; // nothing + } + if (model) { + this._sessionDisposables.add(model); + this._widget!.showModel(model); + } + else { + this._widget!.showMessage(localize('no.item', "No results")); + } + }).catch(e => { + this._widget!.showMessage(localize('error', "Failed to show call hierarchy")); + console.error(e); + }); + } + + toggleCallHierarchyDirection(): void { + if (this._widget) { + this._widget.toggleDirection(); } } @@ -120,9 +161,10 @@ registerEditorAction(class extends EditorAction { id: 'editor.showCallHierarchy', label: localize('title', "Peek Call Hierarchy"), alias: 'Peek Call Hierarchy', - menuOpts: { + contextMenuOpts: { + menuId: MenuId.EditorContextPeek, group: 'navigation', - order: 1.48 + order: 1000 }, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, @@ -130,14 +172,55 @@ registerEditorAction(class extends EditorAction { primary: KeyMod.Shift + KeyMod.Alt + KeyCode.KEY_H }, precondition: ContextKeyExpr.and( + _ctxCallHierarchyVisible.negate(), _ctxHasCompletionItemProvider, PeekContext.notInPeekEditor ) }); } - async run(_accessor: ServicesAccessor, editor: ICodeEditor, args: any): Promise { - return CallHierarchyController.get(editor).startCallHierarchy(); + async run(_accessor: ServicesAccessor, editor: ICodeEditor): Promise { + return CallHierarchyController.get(editor).startCallHierarchyFromEditor(); + } +}); + +registerEditorAction(class extends EditorAction { + + constructor() { + super({ + id: 'editor.toggleCallHierarchy', + label: localize('title.toggle', "Toggle Call Hierarchy"), + alias: 'Toggle Call Hierarchy', + kbOpts: { + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyMod.Shift + KeyMod.Alt + KeyCode.KEY_H + }, + precondition: _ctxCallHierarchyVisible + }); + } + + async run(_accessor: ServicesAccessor, editor: ICodeEditor): Promise { + return CallHierarchyController.get(editor).toggleCallHierarchyDirection(); + } +}); + +registerEditorAction(class extends EditorAction { + + constructor() { + super({ + id: 'editor.refocusCallHierarchy', + label: localize('title.refocus', "Refocus Call Hierarchy"), + alias: 'Refocus Call Hierarchy', + kbOpts: { + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyMod.Shift + KeyCode.Enter + }, + precondition: _ctxCallHierarchyVisible + }); + } + + async run(_accessor: ServicesAccessor, editor: ICodeEditor): Promise { + return CallHierarchyController.get(editor).startCallHierarchyFromCallHierarchy(); } }); diff --git a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.ts b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.ts index 8d109470f64e..c784fbe82cfa 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.ts +++ b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.ts @@ -9,10 +9,14 @@ import { ITextModel } from 'vs/editor/common/model'; import { CancellationToken } from 'vs/base/common/cancellation'; import { LanguageFeatureRegistry } from 'vs/editor/common/modes/languageFeatureRegistry'; import { URI } from 'vs/base/common/uri'; -import { IPosition } from 'vs/editor/common/core/position'; -import { registerDefaultLanguageCommand } from 'vs/editor/browser/editorExtensions'; +import { IPosition, Position } from 'vs/editor/common/core/position'; import { isNonEmptyArray } from 'vs/base/common/arrays'; import { onUnexpectedExternalError } from 'vs/base/common/errors'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { assertType } from 'vs/base/common/types'; +import { IModelService } from 'vs/editor/common/services/modelService'; +import { ITextModelService } from 'vs/editor/common/services/resolverService'; export const enum CallHierarchyDirection { CallsTo = 1, @@ -20,6 +24,8 @@ export const enum CallHierarchyDirection { } export interface CallHierarchyItem { + _sessionId: string; + _itemId: string; kind: SymbolKind; name: string; detail?: string; @@ -38,47 +44,167 @@ export interface OutgoingCall { to: CallHierarchyItem; } +export interface CallHierarchySession { + root: CallHierarchyItem; + dispose(): void; +} + export interface CallHierarchyProvider { - provideIncomingCalls(document: ITextModel, postion: IPosition, token: CancellationToken): ProviderResult; + prepareCallHierarchy(document: ITextModel, position: IPosition, token: CancellationToken): ProviderResult; + + provideIncomingCalls(item: CallHierarchyItem, token: CancellationToken): ProviderResult; - provideOutgoingCalls(document: ITextModel, postion: IPosition, token: CancellationToken): ProviderResult; + provideOutgoingCalls(item: CallHierarchyItem, token: CancellationToken): ProviderResult; } export const CallHierarchyProviderRegistry = new LanguageFeatureRegistry(); -export async function provideIncomingCalls(model: ITextModel, position: IPosition, token: CancellationToken): Promise { - const [provider] = CallHierarchyProviderRegistry.ordered(model); - if (!provider) { - return []; +class RefCountedDisposabled { + + constructor( + private readonly _disposable: IDisposable, + private _counter = 1 + ) { } + + acquire() { + this._counter++; + return this; } - try { - const result = await provider.provideIncomingCalls(model, position, token); - if (isNonEmptyArray(result)) { - return result; + + release() { + if (--this._counter === 0) { + this._disposable.dispose(); } - } catch (e) { - onUnexpectedExternalError(e); + return this; } - return []; } -export async function provideOutgoingCalls(model: ITextModel, position: IPosition, token: CancellationToken): Promise { - const [provider] = CallHierarchyProviderRegistry.ordered(model); - if (!provider) { +export class CallHierarchyModel { + + static async create(model: ITextModel, position: IPosition, token: CancellationToken): Promise { + const [provider] = CallHierarchyProviderRegistry.ordered(model); + if (!provider) { + return undefined; + } + const session = await provider.prepareCallHierarchy(model, position, token); + if (!session) { + return undefined; + } + return new CallHierarchyModel(session.root._sessionId, provider, session.root, new RefCountedDisposabled(session)); + } + + private constructor( + readonly id: string, + readonly provider: CallHierarchyProvider, + readonly root: CallHierarchyItem, + readonly ref: RefCountedDisposabled, + ) { } + + dispose(): void { + this.ref.release(); + } + + fork(item: CallHierarchyItem): CallHierarchyModel { + const that = this; + return new class extends CallHierarchyModel { + constructor() { + super(that.id, that.provider, item, that.ref.acquire()); + } + }; + } + + async resolveIncomingCalls(item: CallHierarchyItem, token: CancellationToken): Promise { + try { + const result = await this.provider.provideIncomingCalls(item, token); + if (isNonEmptyArray(result)) { + return result; + } + } catch (e) { + onUnexpectedExternalError(e); + } return []; } + + async resolveOutgoingCalls(item: CallHierarchyItem, token: CancellationToken): Promise { + try { + const result = await this.provider.provideOutgoingCalls(item, token); + if (isNonEmptyArray(result)) { + return result; + } + } catch (e) { + onUnexpectedExternalError(e); + } + return []; + } +} + +// --- API command support + +const _models = new Map(); + +CommandsRegistry.registerCommand('_executePrepareCallHierarchy', async (accessor, ...args) => { + const [resource, position] = args; + assertType(URI.isUri(resource)); + assertType(Position.isIPosition(position)); + + const modelService = accessor.get(IModelService); + let textModel = modelService.getModel(resource); + let textModelReference: IDisposable | undefined; + if (!textModel) { + const textModelService = accessor.get(ITextModelService); + const result = await textModelService.createModelReference(resource); + textModel = result.object.textEditorModel; + textModelReference = result; + } + try { - const result = await provider.provideOutgoingCalls(model, position, token); - if (isNonEmptyArray(result)) { - return result; + const model = await CallHierarchyModel.create(textModel, position, CancellationToken.None); + if (!model) { + return []; } - } catch (e) { - onUnexpectedExternalError(e); + // + _models.set(model.id, model); + _models.forEach((value, key, map) => { + if (map.size > 10) { + value.dispose(); + _models.delete(key); + } + }); + return [model.root]; + + } finally { + dispose(textModelReference); } - return []; +}); + +function isCallHierarchyItemDto(obj: any): obj is CallHierarchyItem { + return true; } -registerDefaultLanguageCommand('_executeCallHierarchyIncomingCalls', async (model, position) => provideIncomingCalls(model, position, CancellationToken.None)); -registerDefaultLanguageCommand('_executeCallHierarchyOutgoingCalls', async (model, position) => provideOutgoingCalls(model, position, CancellationToken.None)); +CommandsRegistry.registerCommand('_executeProvideIncomingCalls', async (_accessor, ...args) => { + const [item] = args; + assertType(isCallHierarchyItemDto(item)); + + // find model + const model = _models.get(item._sessionId); + if (!model) { + return undefined; + } + + return model.resolveIncomingCalls(item, CancellationToken.None); +}); + +CommandsRegistry.registerCommand('_executeProvideOutgoingCalls', async (_accessor, ...args) => { + const [item] = args; + assertType(isCallHierarchyItemDto(item)); + + // find model + const model = _models.get(item._sessionId); + if (!model) { + return undefined; + } + + return model.resolveOutgoingCalls(item, CancellationToken.None); +}); diff --git a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts index 0c0eba65d9e0..fbc8cc33fafb 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts +++ b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts @@ -4,14 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/callHierarchy'; -import { PeekViewWidget, IPeekViewService } from 'vs/editor/contrib/referenceSearch/peekViewWidget'; +import * as peekView from 'vs/editor/contrib/peekView/peekView'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { CallHierarchyProvider, CallHierarchyDirection } from 'vs/workbench/contrib/callHierarchy/browser/callHierarchy'; -import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; +import { CallHierarchyDirection, CallHierarchyModel } from 'vs/workbench/contrib/callHierarchy/browser/callHierarchy'; +import { WorkbenchAsyncDataTree, IWorkbenchAsyncDataTreeOptions } from 'vs/platform/list/browser/listService'; import { FuzzyScore } from 'vs/base/common/filters'; import * as callHTree from 'vs/workbench/contrib/callHierarchy/browser/callHierarchyTree'; -import { IAsyncDataTreeOptions, IAsyncDataTreeViewState } from 'vs/base/browser/ui/tree/asyncDataTree'; +import { IAsyncDataTreeViewState } from 'vs/base/browser/ui/tree/asyncDataTree'; import { localize } from 'vs/nls'; import { ScrollType } from 'vs/editor/common/editorCommon'; import { IRange, Range } from 'vs/editor/common/core/range'; @@ -25,13 +25,12 @@ import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { TrackedRangeStickiness, IModelDeltaDecoration, IModelDecorationOptions, OverviewRulerLane } from 'vs/editor/common/model'; import { registerThemingParticipant, themeColorFromId, IThemeService, ITheme } from 'vs/platform/theme/common/themeService'; -import * as referencesWidget from 'vs/editor/contrib/referenceSearch/referencesWidget'; import { IPosition } from 'vs/editor/common/core/position'; import { Action } from 'vs/base/common/actions'; import { IActionBarOptions, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { Color } from 'vs/base/common/color'; -import { TreeMouseEventTarget } from 'vs/base/browser/ui/tree/tree'; +import { TreeMouseEventTarget, ITreeNode } from 'vs/base/browser/ui/tree/tree'; import { URI } from 'vs/base/common/uri'; const enum State { @@ -42,19 +41,14 @@ const enum State { class ChangeHierarchyDirectionAction extends Action { - constructor(direction: CallHierarchyDirection, updateDirection: (direction: CallHierarchyDirection) => void) { + constructor(getDirection: () => CallHierarchyDirection, toggleDirection: () => void) { super('', undefined, '', true, () => { - if (direction === CallHierarchyDirection.CallsTo) { - direction = CallHierarchyDirection.CallsFrom; - } else { - direction = CallHierarchyDirection.CallsTo; - } - updateDirection(direction); + toggleDirection(); update(); return Promise.resolve(); }); const update = () => { - if (direction === CallHierarchyDirection.CallsFrom) { + if (getDirection() === CallHierarchyDirection.CallsFrom) { this.label = localize('toggle.from', "Showing Calls"); this.class = 'calls-from'; } else { @@ -88,25 +82,26 @@ class LayoutInfo { ) { } } -export class CallHierarchyTreePeekWidget extends PeekViewWidget { +export class CallHierarchyTreePeekWidget extends peekView.PeekViewWidget { private _changeDirectionAction?: ChangeHierarchyDirectionAction; private _parent!: HTMLElement; private _message!: HTMLElement; private _splitView!: SplitView; - private _tree!: WorkbenchAsyncDataTree; + private _tree!: WorkbenchAsyncDataTree; private _treeViewStates = new Map(); private _editor!: EmbeddedCodeEditorWidget; private _dim!: Dimension; private _layoutInfo!: LayoutInfo; + private readonly _previewDisposable = new DisposableStore(); + constructor( editor: ICodeEditor, private readonly _where: IPosition, - private readonly _provider: CallHierarchyProvider, private _direction: CallHierarchyDirection, @IThemeService themeService: IThemeService, - @IPeekViewService private readonly _peekViewService: IPeekViewService, + @peekView.IPeekViewService private readonly _peekViewService: peekView.IPeekViewService, @IEditorService private readonly _editorService: IEditorService, @ITextModelService private readonly _textModelService: ITextModelService, @IStorageService private readonly _storageService: IStorageService, @@ -117,6 +112,7 @@ export class CallHierarchyTreePeekWidget extends PeekViewWidget { this._peekViewService.addExclusiveWidget(editor, this); this._applyTheme(themeService.getTheme()); this._disposables.add(themeService.onThemeChange(this._applyTheme, this)); + this._disposables.add(this._previewDisposable); } dispose(): void { @@ -132,13 +128,13 @@ export class CallHierarchyTreePeekWidget extends PeekViewWidget { } private _applyTheme(theme: ITheme) { - const borderColor = theme.getColor(referencesWidget.peekViewBorder) || Color.transparent; + const borderColor = theme.getColor(peekView.peekViewBorder) || Color.transparent; this.style({ arrowColor: borderColor, frameColor: borderColor, - headerBackgroundColor: theme.getColor(referencesWidget.peekViewTitleBackground) || Color.transparent, - primaryHeadingColor: theme.getColor(referencesWidget.peekViewTitleForeground), - secondaryHeadingColor: theme.getColor(referencesWidget.peekViewTitleInfoForeground) + headerBackgroundColor: theme.getColor(peekView.peekViewTitleBackground) || Color.transparent, + primaryHeadingColor: theme.getColor(peekView.peekViewTitleForeground), + secondaryHeadingColor: theme.getColor(peekView.peekViewTitleInfoForeground) }); } @@ -198,18 +194,22 @@ export class CallHierarchyTreePeekWidget extends PeekViewWidget { const treeContainer = document.createElement('div'); addClass(treeContainer, 'tree'); container.appendChild(treeContainer); - const options: IAsyncDataTreeOptions = { + const options: IWorkbenchAsyncDataTreeOptions = { + sorter: new callHTree.Sorter(), identityProvider: new callHTree.IdentityProvider(() => this._direction), ariaLabel: localize('tree.aria', "Call Hierarchy"), expandOnlyOnTwistieClick: true, + overrideStyles: { + listBackground: peekView.peekViewResultsBackground + } }; - this._tree = this._instantiationService.createInstance( + this._tree = this._instantiationService.createInstance>( WorkbenchAsyncDataTree, 'CallHierarchyPeek', treeContainer, new callHTree.VirtualDelegate(), [this._instantiationService.createInstance(callHTree.CallRenderer)], - this._instantiationService.createInstance(callHTree.DataSource, this._provider, () => this._direction), + this._instantiationService.createInstance(callHTree.DataSource, () => this._direction), options ); @@ -244,63 +244,8 @@ export class CallHierarchyTreePeekWidget extends PeekViewWidget { } })); - // session state - const localDispose = new DisposableStore(); - this._disposables.add(localDispose); - // update editor - this._disposables.add(this._tree.onDidChangeFocus(async e => { - const [element] = e.elements; - if (!element) { - return; - } - - localDispose.clear(); - - // update: editor and editor highlights - const options: IModelDecorationOptions = { - stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, - className: 'call-decoration', - overviewRuler: { - color: themeColorFromId(referencesWidget.peekViewEditorMatchHighlight), - position: OverviewRulerLane.Center - }, - }; - - let previewUri: URI; - if (this._direction === CallHierarchyDirection.CallsFrom) { - // outgoing calls: show caller and highlight focused calls - previewUri = element.parent ? element.parent.item.uri : this._tree.getInput()!.model.uri; - } else { - // incoming calls: show caller and highlight focused calls - previewUri = element.item.uri; - } - - const value = await this._textModelService.createModelReference(previewUri); - this._editor.setModel(value.object.textEditorModel); - - // set decorations for caller ranges (if in the same file) - let decorations: IModelDeltaDecoration[] = []; - let fullRange: IRange | undefined; - for (const loc of element.locations) { - if (loc.uri.toString() === previewUri.toString()) { - decorations.push({ range: loc.range, options }); - fullRange = !fullRange ? loc.range : Range.plusRange(loc.range, fullRange); - } - } - if (fullRange) { - this._editor.revealRangeInCenter(fullRange, ScrollType.Immediate); - const ids = this._editor.deltaDecorations([], decorations); - localDispose.add(toDisposable(() => this._editor.deltaDecorations(ids, []))); - } - localDispose.add(value); - - // update: title - const title = this._direction === CallHierarchyDirection.CallsFrom - ? localize('callFrom', "Calls from '{0}'", this._tree.getInput()!.word) - : localize('callsTo', "Callers of '{0}'", this._tree.getInput()!.word); - this.setTitle(title); - })); + this._disposables.add(this._tree.onDidChangeFocus(this._updatePreview, this)); this._disposables.add(this._editor.onMouseDown(e => { const { event, target } = e; @@ -346,6 +291,60 @@ export class CallHierarchyTreePeekWidget extends PeekViewWidget { })); } + private async _updatePreview() { + const [element] = this._tree.getFocus(); + if (!element) { + return; + } + + this._previewDisposable.clear(); + + // update: editor and editor highlights + const options: IModelDecorationOptions = { + stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, + className: 'call-decoration', + overviewRuler: { + color: themeColorFromId(peekView.peekViewEditorMatchHighlight), + position: OverviewRulerLane.Center + }, + }; + + let previewUri: URI; + if (this._direction === CallHierarchyDirection.CallsFrom) { + // outgoing calls: show caller and highlight focused calls + previewUri = element.parent ? element.parent.item.uri : element.model.root.uri; + + } else { + // incoming calls: show caller and highlight focused calls + previewUri = element.item.uri; + } + + const value = await this._textModelService.createModelReference(previewUri); + this._editor.setModel(value.object.textEditorModel); + + // set decorations for caller ranges (if in the same file) + let decorations: IModelDeltaDecoration[] = []; + let fullRange: IRange | undefined; + for (const loc of element.locations) { + if (loc.uri.toString() === previewUri.toString()) { + decorations.push({ range: loc.range, options }); + fullRange = !fullRange ? loc.range : Range.plusRange(loc.range, fullRange); + } + } + if (fullRange) { + this._editor.revealRangeInCenter(fullRange, ScrollType.Immediate); + const ids = this._editor.deltaDecorations([], decorations); + this._previewDisposable.add(toDisposable(() => this._editor.deltaDecorations(ids, []))); + } + this._previewDisposable.add(value); + + // update: title + const title = this._direction === CallHierarchyDirection.CallsFrom + ? localize('callFrom', "Calls from '{0}'", element.model.root.name) + : localize('callsTo', "Callers of '{0}'", element.model.root.name); + this.setTitle(title); + } + showLoading(): void { this._parent.dataset['state'] = State.Loading; this.setTitle(localize('title.loading', "Loading...")); @@ -361,41 +360,56 @@ export class CallHierarchyTreePeekWidget extends PeekViewWidget { this._message.focus(); } - async showItem(item: callHTree.CallHierarchyRoot): Promise { + async showModel(model: CallHierarchyModel): Promise { this._show(); const viewState = this._treeViewStates.get(this._direction); - await this._tree.setInput(item, viewState); - if (this._tree.getNode(item).children.length === 0) { + await this._tree.setInput(model, viewState); + + const root = >this._tree.getNode(model).children[0]; // {{SQL CARBON EDIT}} strict-null-checks + await this._tree.expand(root.element); + + if (root.children.length === 0) { // this.showMessage(this._direction === CallHierarchyDirection.CallsFrom - ? localize('empt.callsFrom', "No calls from '{0}'", item.word) - : localize('empt.callsTo', "No callers of '{0}'", item.word)); + ? localize('empt.callsFrom', "No calls from '{0}'", model.root.name) + : localize('empt.callsTo', "No callers of '{0}'", model.root.name)); } else { this._parent.dataset['state'] = State.Data; - this._tree.domFocus(); if (!viewState) { - this._tree.focusFirst(); + this._tree.setFocus([root.children[0].element]); } + this._tree.domFocus(); + this._updatePreview(); } if (!this._changeDirectionAction) { - const changeDirection = (newDirection: CallHierarchyDirection) => { - if (this._direction !== newDirection) { - this._treeViewStates.set(this._direction, this._tree.getViewState()); - this._direction = newDirection; - this._tree.setFocus([]); - this.showItem(this._tree.getInput()!); - } - }; - this._changeDirectionAction = new ChangeHierarchyDirectionAction(this._direction, changeDirection); + this._changeDirectionAction = new ChangeHierarchyDirectionAction(() => this._direction, () => this.toggleDirection()); this._disposables.add(this._changeDirectionAction); this._actionbarWidget!.push(this._changeDirectionAction, { icon: true, label: false }); } } + getModel(): CallHierarchyModel | undefined { + return this._tree.getInput(); + } + + getFocused(): callHTree.Call | undefined { + return this._tree.getFocus()[0]; + } + + async toggleDirection(): Promise { + const model = this._tree.getInput(); + if (model) { + const newDirection = this._direction === CallHierarchyDirection.CallsTo ? CallHierarchyDirection.CallsFrom : CallHierarchyDirection.CallsTo; + this._treeViewStates.set(this._direction, this._tree.getViewState()); + this._direction = newDirection; + await this.showModel(model); + } + } + private _show() { if (!this._isShowing) { this.editor.revealLineInCenterIfOutsideViewport(this._where.lineNumber, ScrollType.Smooth); @@ -421,31 +435,31 @@ export class CallHierarchyTreePeekWidget extends PeekViewWidget { } registerThemingParticipant((theme, collector) => { - const referenceHighlightColor = theme.getColor(referencesWidget.peekViewEditorMatchHighlight); + const referenceHighlightColor = theme.getColor(peekView.peekViewEditorMatchHighlight); if (referenceHighlightColor) { collector.addRule(`.monaco-editor .call-hierarchy .call-decoration { background-color: ${referenceHighlightColor}; }`); } - const referenceHighlightBorder = theme.getColor(referencesWidget.peekViewEditorMatchHighlightBorder); + const referenceHighlightBorder = theme.getColor(peekView.peekViewEditorMatchHighlightBorder); if (referenceHighlightBorder) { collector.addRule(`.monaco-editor .call-hierarchy .call-decoration { border: 2px solid ${referenceHighlightBorder}; box-sizing: border-box; }`); } - const resultsBackground = theme.getColor(referencesWidget.peekViewResultsBackground); + const resultsBackground = theme.getColor(peekView.peekViewResultsBackground); if (resultsBackground) { collector.addRule(`.monaco-editor .call-hierarchy .tree { background-color: ${resultsBackground}; }`); } - const resultsMatchForeground = theme.getColor(referencesWidget.peekViewResultsFileForeground); + const resultsMatchForeground = theme.getColor(peekView.peekViewResultsFileForeground); if (resultsMatchForeground) { collector.addRule(`.monaco-editor .call-hierarchy .tree { color: ${resultsMatchForeground}; }`); } - const resultsSelectedBackground = theme.getColor(referencesWidget.peekViewResultsSelectionBackground); + const resultsSelectedBackground = theme.getColor(peekView.peekViewResultsSelectionBackground); if (resultsSelectedBackground) { collector.addRule(`.monaco-editor .call-hierarchy .tree .monaco-list:focus .monaco-list-rows > .monaco-list-row.selected:not(.highlighted) { background-color: ${resultsSelectedBackground}; }`); } - const resultsSelectedForeground = theme.getColor(referencesWidget.peekViewResultsSelectionForeground); + const resultsSelectedForeground = theme.getColor(peekView.peekViewResultsSelectionForeground); if (resultsSelectedForeground) { collector.addRule(`.monaco-editor .call-hierarchy .tree .monaco-list:focus .monaco-list-rows > .monaco-list-row.selected:not(.highlighted) { color: ${resultsSelectedForeground} !important; }`); } - const editorBackground = theme.getColor(referencesWidget.peekViewEditorBackground); + const editorBackground = theme.getColor(peekView.peekViewEditorBackground); if (editorBackground) { collector.addRule( `.monaco-editor .call-hierarchy .editor .monaco-editor .monaco-editor-background,` + @@ -454,7 +468,7 @@ registerThemingParticipant((theme, collector) => { `}` ); } - const editorGutterBackground = theme.getColor(referencesWidget.peekViewEditorGutterBackground); + const editorGutterBackground = theme.getColor(peekView.peekViewEditorGutterBackground); if (editorGutterBackground) { collector.addRule( `.monaco-editor .call-hierarchy .editor .monaco-editor .margin {` + diff --git a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyTree.ts b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyTree.ts index c4cc0deab8e9..e64b2453f447 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyTree.ts +++ b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyTree.ts @@ -3,104 +3,79 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IAsyncDataSource, ITreeRenderer, ITreeNode } from 'vs/base/browser/ui/tree/tree'; -import { CallHierarchyItem, CallHierarchyProvider, CallHierarchyDirection, provideOutgoingCalls, provideIncomingCalls } from 'vs/workbench/contrib/callHierarchy/browser/callHierarchy'; +import { IAsyncDataSource, ITreeRenderer, ITreeNode, ITreeSorter } from 'vs/base/browser/ui/tree/tree'; +import { CallHierarchyItem, CallHierarchyDirection, CallHierarchyModel, } from 'vs/workbench/contrib/callHierarchy/browser/callHierarchy'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IIdentityProvider, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { FuzzyScore, createMatches } from 'vs/base/common/filters'; import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; import { SymbolKinds, Location } from 'vs/editor/common/modes'; -import { hash } from 'vs/base/common/hash'; -import { ITextModelService } from 'vs/editor/common/services/resolverService'; +import * as dom from 'vs/base/browser/dom'; +import { compare } from 'vs/base/common/strings'; import { Range } from 'vs/editor/common/core/range'; -import { ITextModel } from 'vs/editor/common/model'; -import { IPosition } from 'vs/editor/common/core/position'; -import { IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; export class Call { constructor( readonly item: CallHierarchyItem, readonly locations: Location[], + readonly model: CallHierarchyModel, readonly parent: Call | undefined ) { } -} - -export class CallHierarchyRoot { - static fromEditor(editor: IActiveCodeEditor): CallHierarchyRoot | undefined { - const model = editor.getModel(); - const position = editor.getPosition(); - const wordInfo = model.getWordAtPosition(position); - return wordInfo - ? new CallHierarchyRoot(model, position, wordInfo.word) - : undefined; + static compare(a: Call, b: Call): number { + let res = compare(a.item.uri.toString(), b.item.uri.toString()); + if (res === 0) { + res = Range.compareRangesUsingStarts(a.item.range, b.item.range); + } + return res; } - - constructor( - readonly model: ITextModel, - readonly position: IPosition, - readonly word: string - ) { } } -export class DataSource implements IAsyncDataSource { +export class DataSource implements IAsyncDataSource { constructor( - public provider: CallHierarchyProvider, public getDirection: () => CallHierarchyDirection, - @ITextModelService private readonly _modelService: ITextModelService, ) { } hasChildren(): boolean { return true; } - async getChildren(element: CallHierarchyRoot | Call): Promise { + async getChildren(element: CallHierarchyModel | Call): Promise { + if (element instanceof CallHierarchyModel) { + return [new Call(element.root, [], element, undefined)]; + } + + const { model, item } = element; - const results: Call[] = []; + if (this.getDirection() === CallHierarchyDirection.CallsFrom) { + return (await model.resolveOutgoingCalls(item, CancellationToken.None)).map(call => { + return new Call( + call.to, + call.fromRanges.map(range => ({ range, uri: item.uri })), + model, + element + ); + }); - if (element instanceof CallHierarchyRoot) { - if (this.getDirection() === CallHierarchyDirection.CallsFrom) { - await this._getOutgoingCalls(element.model, element.position, results); - } else { - await this._getIncomingCalls(element.model, element.position, results); - } } else { - const reference = await this._modelService.createModelReference(element.item.uri); - const position = Range.lift(element.item.selectionRange).getStartPosition(); - if (this.getDirection() === CallHierarchyDirection.CallsFrom) { - await this._getOutgoingCalls(reference.object.textEditorModel, position, results, element); - } else { - await this._getIncomingCalls(reference.object.textEditorModel, position, results, element); - } - reference.dispose(); + return (await model.resolveIncomingCalls(item, CancellationToken.None)).map(call => { + return new Call( + call.from, + call.fromRanges.map(range => ({ range, uri: call.from.uri })), + model, + element + ); + }); } - - return results; } +} - private async _getOutgoingCalls(model: ITextModel, position: IPosition, bucket: Call[], parent?: Call): Promise { - const outgoingCalls = await provideOutgoingCalls(model, position, CancellationToken.None); - for (const call of outgoingCalls) { - bucket.push(new Call( - call.to, - call.fromRanges.map(range => ({ range, uri: model.uri })), - parent - )); - } - } +export class Sorter implements ITreeSorter { - private async _getIncomingCalls(model: ITextModel, position: IPosition, bucket: Call[], parent?: Call): Promise { - const incomingCalls = await provideIncomingCalls(model, position, CancellationToken.None); - for (const call of incomingCalls) { - bucket.push(new Call( - call.from, - call.fromRanges.map(range => ({ range, uri: call.from.uri })), - parent - )); - } + compare(element: Call, otherElement: Call): number { + return Call.compare(element, otherElement); } - } export class IdentityProvider implements IIdentityProvider { @@ -110,13 +85,18 @@ export class IdentityProvider implements IIdentityProvider { ) { } getId(element: Call): { toString(): string; } { - return this.getDirection() + hash(element.item.uri.toString(), hash(JSON.stringify(element.item.range))).toString() + (element.parent ? this.getId(element.parent) : ''); + let res = this.getDirection() + JSON.stringify(element.item.uri) + JSON.stringify(element.item.range); + if (element.parent) { + res += this.getId(element.parent); + } + return res; } } class CallRenderingTemplate { constructor( - readonly iconLabel: IconLabel + readonly icon: HTMLDivElement, + readonly label: IconLabel ) { } } @@ -127,24 +107,23 @@ export class CallRenderer implements ITreeRenderer, _index: number, template: CallRenderingTemplate): void { const { element, filterData } = node; - - template.iconLabel.setLabel( + template.icon.className = SymbolKinds.toCssClassName(element.item.kind, true); + template.label.setLabel( element.item.name, element.item.detail, - { - labelEscapeNewLines: true, - matches: createMatches(filterData), - extraClasses: [SymbolKinds.toCssClassName(element.item.kind, true)] - } + { labelEscapeNewLines: true, matches: createMatches(filterData) } ); } disposeTemplate(template: CallRenderingTemplate): void { - template.iconLabel.dispose(); + template.label.dispose(); } } diff --git a/src/vs/workbench/contrib/callHierarchy/browser/media/callHierarchy.css b/src/vs/workbench/contrib/callHierarchy/browser/media/callHierarchy.css index 2a1b344742b9..0f1345c235a3 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/media/callHierarchy.css +++ b/src/vs/workbench/contrib/callHierarchy/browser/media/callHierarchy.css @@ -38,3 +38,14 @@ .monaco-workbench .call-hierarchy .tree { height: 100%; } + +.monaco-workbench .call-hierarchy .tree .callhierarchy-element { + display: flex; + flex: 1; + flex-flow: row nowrap; + align-items: center; +} + +.monaco-workbench .call-hierarchy .tree .callhierarchy-element .monaco-icon-label { + padding-left: 4px; +} diff --git a/src/vs/workbench/contrib/cli/node/cli.contribution.ts b/src/vs/workbench/contrib/cli/node/cli.contribution.ts index 9431092973c4..ad3ea9ed4c82 100644 --- a/src/vs/workbench/contrib/cli/node/cli.contribution.ts +++ b/src/vs/workbench/contrib/cli/node/cli.contribution.ts @@ -108,7 +108,7 @@ class InstallAction extends Action { promisify(cp.exec)(command, {}) .then(undefined, _ => Promise.reject(new Error(nls.localize('cantCreateBinFolder', "Unable to create '/usr/local/bin'.")))) - .then(resolve, reject); + .then(() => resolve(), reject); break; case 1 /* Cancel */: reject(new Error(nls.localize('aborted', "Aborted"))); @@ -175,7 +175,7 @@ class UninstallAction extends Action { promisify(cp.exec)(command, {}) .then(undefined, _ => Promise.reject(new Error(nls.localize('cantUninstall', "Unable to uninstall the shell command '{0}'.", this.target)))) - .then(resolve, reject); + .then(() => resolve(), reject); break; case 1 /* Cancel */: reject(new Error(nls.localize('aborted', "Aborted"))); @@ -189,6 +189,6 @@ if (platform.isMacintosh) { const category = nls.localize('shellCommand', "Shell Command"); const workbenchActionsRegistry = Registry.as(ActionExtensions.WorkbenchActions); - workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(InstallAction, InstallAction.ID, InstallAction.LABEL), `Shell Command: Install \'${product.applicationName}\' command in PATH`, category); - workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(UninstallAction, UninstallAction.ID, UninstallAction.LABEL), `Shell Command: Uninstall \'${product.applicationName}\' command from PATH`, category); + workbenchActionsRegistry.registerWorkbenchAction(SyncActionDescriptor.create(InstallAction, InstallAction.ID, InstallAction.LABEL), `Shell Command: Install \'${product.applicationName}\' command in PATH`, category); + workbenchActionsRegistry.registerWorkbenchAction(SyncActionDescriptor.create(UninstallAction, UninstallAction.ID, UninstallAction.LABEL), `Shell Command: Uninstall \'${product.applicationName}\' command from PATH`, category); } diff --git a/src/vs/workbench/contrib/codeActions/common/codeActions.contribution.ts b/src/vs/workbench/contrib/codeActions/common/codeActions.contribution.ts new file mode 100644 index 000000000000..3ab3f93e969a --- /dev/null +++ b/src/vs/workbench/contrib/codeActions/common/codeActions.contribution.ts @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; +import { CodeActionWorkbenchContribution, editorConfiguration } from 'vs/workbench/contrib/codeActions/common/configuration'; +import { CodeActionsExtensionPoint, codeActionsExtensionPointDescriptor } from 'vs/workbench/contrib/codeActions/common/extensionPoint'; +import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; + +const codeActionsExtensionPoint = ExtensionsRegistry.registerExtensionPoint(codeActionsExtensionPointDescriptor); + +Registry.as(Extensions.Configuration) + .registerConfiguration(editorConfiguration); + +class WorkbenchContribution { + constructor( + @IKeybindingService keybindingsService: IKeybindingService, + ) { + // tslint:disable-next-line: no-unused-expression + new CodeActionWorkbenchContribution(codeActionsExtensionPoint, keybindingsService); + } +} + +Registry.as(WorkbenchExtensions.Workbench) + .registerWorkbenchContribution(WorkbenchContribution, LifecyclePhase.Eventually); diff --git a/src/vs/workbench/contrib/codeActions/common/configuration.ts b/src/vs/workbench/contrib/codeActions/common/configuration.ts new file mode 100644 index 000000000000..bf44f6606d0d --- /dev/null +++ b/src/vs/workbench/contrib/codeActions/common/configuration.ts @@ -0,0 +1,155 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { flatten } from 'vs/base/common/arrays'; +import { Emitter } from 'vs/base/common/event'; +import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { values } from 'vs/base/common/map'; +import { codeActionCommandId, refactorCommandId, sourceActionCommandId } from 'vs/editor/contrib/codeAction/codeAction'; +import { CodeActionKind } from 'vs/editor/contrib/codeAction/types'; +import * as nls from 'vs/nls'; +import { Extensions, IConfigurationNode, IConfigurationRegistry, ConfigurationScope, IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { CodeActionsExtensionPoint, ContributedCodeAction } from 'vs/workbench/contrib/codeActions/common/extensionPoint'; +import { IExtensionPoint } from 'vs/workbench/services/extensions/common/extensionsRegistry'; +import { editorConfigurationBaseNode } from 'vs/editor/common/config/commonEditorConfig'; + +const codeActionsOnSaveDefaultProperties = Object.freeze({ + 'source.fixAll': { + type: 'boolean', + description: nls.localize('codeActionsOnSave.fixAll', "Controls whether auto fix action should be run on file save.") + } +}); + +const codeActionsOnSaveSchema: IConfigurationPropertySchema = { + type: 'object', + properties: codeActionsOnSaveDefaultProperties, + 'additionalProperties': { + type: 'boolean' + }, + default: {}, + description: nls.localize('codeActionsOnSave', "Code action kinds to be run on save."), + scope: ConfigurationScope.RESOURCE +}; + +export const editorConfiguration = Object.freeze({ + ...editorConfigurationBaseNode, + properties: { + 'editor.codeActionsOnSave': codeActionsOnSaveSchema, + 'editor.codeActionsOnSaveTimeout': { + type: 'number', + default: 750, + description: nls.localize('codeActionsOnSaveTimeout', "Timeout in milliseconds after which the code actions that are run on save are cancelled."), + scope: ConfigurationScope.RESOURCE + }, + } +}); + +export class CodeActionWorkbenchContribution extends Disposable implements IWorkbenchContribution { + + private _contributedCodeActions: CodeActionsExtensionPoint[] = []; + + private readonly _onDidChangeContributions = this._register(new Emitter()); + + constructor( + codeActionsExtensionPoint: IExtensionPoint, + keybindingService: IKeybindingService, + ) { + super(); + + codeActionsExtensionPoint.setHandler(extensionPoints => { + this._contributedCodeActions = flatten(extensionPoints.map(x => x.value)); + this.updateConfigurationSchema(this._contributedCodeActions); + this._onDidChangeContributions.fire(); + }); + + keybindingService.registerSchemaContribution({ + getSchemaAdditions: () => this.getSchemaAdditions(), + onDidChange: this._onDidChangeContributions.event, + }); + } + + private updateConfigurationSchema(codeActionContributions: readonly CodeActionsExtensionPoint[]) { + const newProperties: IJSONSchemaMap = { ...codeActionsOnSaveDefaultProperties }; + for (const [sourceAction, props] of this.getSourceActions(codeActionContributions)) { + newProperties[sourceAction] = { + type: 'boolean', + description: nls.localize('codeActionsOnSave.generic', "Controls whether '{0}' actions should be run on file save.", props.title) + }; + } + codeActionsOnSaveSchema.properties = newProperties; + Registry.as(Extensions.Configuration) + .notifyConfigurationSchemaUpdated(editorConfiguration); + } + + private getSourceActions(contributions: readonly CodeActionsExtensionPoint[]) { + const defaultKinds = Object.keys(codeActionsOnSaveDefaultProperties).map(value => new CodeActionKind(value)); + const sourceActions = new Map(); + for (const contribution of contributions) { + for (const action of contribution.actions) { + const kind = new CodeActionKind(action.kind); + if (CodeActionKind.Source.contains(kind) + // Exclude any we already included by default + && !defaultKinds.some(defaultKind => defaultKind.contains(kind)) + ) { + sourceActions.set(kind.value, action); + } + } + } + return sourceActions; + } + + private getSchemaAdditions(): IJSONSchema[] { + const conditionalSchema = (command: string, actions: readonly ContributedCodeAction[]): IJSONSchema => { + return { + if: { + properties: { + 'command': { const: command } + } + }, + then: { + required: ['args'], + properties: { + 'args': { + required: ['kind'], + properties: { + 'kind': { + anyOf: [ + { + enum: actions.map(action => action.kind), + enumDescriptions: actions.map(action => action.description ?? action.title), + }, + { type: 'string' }, + ] + } + } + } + } + } + }; + }; + + const getActions = (ofKind: CodeActionKind): ContributedCodeAction[] => { + const allActions = flatten(this._contributedCodeActions.map(desc => desc.actions.slice())); + + const out = new Map(); + for (const action of allActions) { + if (!out.has(action.kind) && ofKind.contains(new CodeActionKind(action.kind))) { + out.set(action.kind, action); + } + } + return values(out); + }; + + return [ + conditionalSchema(codeActionCommandId, getActions(CodeActionKind.Empty)), + conditionalSchema(refactorCommandId, getActions(CodeActionKind.Refactor)), + conditionalSchema(sourceActionCommandId, getActions(CodeActionKind.Source)), + ]; + } +} diff --git a/src/vs/workbench/contrib/codeActions/common/extensionPoint.ts b/src/vs/workbench/contrib/codeActions/common/extensionPoint.ts new file mode 100644 index 000000000000..54461eff3dc2 --- /dev/null +++ b/src/vs/workbench/contrib/codeActions/common/extensionPoint.ts @@ -0,0 +1,67 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import { IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry'; +import { languagesExtPoint } from 'vs/workbench/services/mode/common/workbenchModeService'; + +export enum CodeActionExtensionPointFields { + languages = 'languages', + actions = 'actions', + kind = 'kind', + title = 'title', + description = 'description' +} + +export interface ContributedCodeAction { + readonly [CodeActionExtensionPointFields.kind]: string; + readonly [CodeActionExtensionPointFields.title]: string; + readonly [CodeActionExtensionPointFields.description]?: string; +} + +export interface CodeActionsExtensionPoint { + readonly [CodeActionExtensionPointFields.languages]: readonly string[]; + readonly [CodeActionExtensionPointFields.actions]: readonly ContributedCodeAction[]; +} + +const codeActionsExtensionPointSchema = Object.freeze({ + type: 'array', + markdownDescription: nls.localize('contributes.codeActions', "Configure which editor to use for a resource."), + items: { + type: 'object', + required: [CodeActionExtensionPointFields.languages, CodeActionExtensionPointFields.actions], + properties: { + [CodeActionExtensionPointFields.languages]: { + type: 'array', + description: nls.localize('contributes.codeActions.languages', "Language modes that the code actions are enabled for."), + items: { type: 'string' } + }, + [CodeActionExtensionPointFields.actions]: { + type: 'object', + required: [CodeActionExtensionPointFields.kind, CodeActionExtensionPointFields.title], + properties: { + [CodeActionExtensionPointFields.kind]: { + type: 'string', + markdownDescription: nls.localize('contributes.codeActions.kind', "`CodeActionKind` of the contributed code action."), + }, + [CodeActionExtensionPointFields.title]: { + type: 'string', + description: nls.localize('contributes.codeActions.title', "Label for the code action used in the UI."), + }, + [CodeActionExtensionPointFields.description]: { + type: 'string', + description: nls.localize('contributes.codeActions.description', "Description of what the code action does."), + }, + } + } + } + } +}); + +export const codeActionsExtensionPointDescriptor = { + extensionPoint: 'codeActions', + deps: [languagesExtPoint], + jsonSchema: codeActionsExtensionPointSchema +}; diff --git a/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.css b/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.css index 00a2e5295ba7..df132418c67c 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.css +++ b/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.css @@ -6,4 +6,5 @@ .monaco-editor .accessibilityHelpWidget { padding: 10px; vertical-align: middle; -} \ No newline at end of file + overflow: auto; +} diff --git a/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts b/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts index fabfdf24a012..53971e97f693 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts @@ -263,11 +263,13 @@ class AccessibilityHelpWidget extends Widget implements IOverlayWidget { private _layout(): void { let editorLayout = this._editor.getLayoutInfo(); - let top = Math.round((editorLayout.height - AccessibilityHelpWidget.HEIGHT) / 2); - this._domNode.setTop(top); + const width = Math.min(editorLayout.width - 40, AccessibilityHelpWidget.WIDTH); + const height = Math.min(editorLayout.height - 40, AccessibilityHelpWidget.HEIGHT); - let left = Math.round((editorLayout.width - AccessibilityHelpWidget.WIDTH) / 2); - this._domNode.setLeft(left); + this._domNode.setTop(Math.round((editorLayout.height - height) / 2)); + this._domNode.setLeft(Math.round((editorLayout.width - width) / 2)); + this._domNode.setWidth(width); + this._domNode.setHeight(height); } } diff --git a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts index c614e74e098b..f009f85495fe 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts @@ -15,7 +15,7 @@ import { IMessage as InputBoxMessage } from 'vs/base/browser/ui/inputbox/inputBo import { SimpleButton } from 'vs/editor/contrib/find/findWidget'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; -import { editorWidgetBackground, inputActiveOptionBorder, inputActiveOptionBackground, inputBackground, inputBorder, inputForeground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, inputValidationInfoBackground, inputValidationInfoBorder, inputValidationInfoForeground, inputValidationWarningBackground, inputValidationWarningBorder, inputValidationWarningForeground, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; +import { editorWidgetBackground, inputActiveOptionBorder, inputActiveOptionBackground, inputBackground, inputBorder, inputForeground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, inputValidationInfoBackground, inputValidationInfoBorder, inputValidationInfoForeground, inputValidationWarningBackground, inputValidationWarningBorder, inputValidationWarningForeground, widgetShadow, editorWidgetForeground } from 'vs/platform/theme/common/colorRegistry'; import { ITheme, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { ContextScopedFindInput } from 'vs/platform/browser/contextScopedHistoryWidget'; @@ -280,6 +280,11 @@ registerThemingParticipant((theme, collector) => { collector.addRule(`.monaco-workbench .simple-find-part { background-color: ${findWidgetBGColor} !important; }`); } + const widgetForeground = theme.getColor(editorWidgetForeground); + if (widgetForeground) { + collector.addRule(`.monaco-workbench .simple-find-part { color: ${widgetForeground}; }`); + } + const widgetShadowColor = theme.getColor(widgetShadow); if (widgetShadowColor) { collector.addRule(`.monaco-workbench .simple-find-part { box-shadow: 0 2px 8px ${widgetShadowColor}; }`); diff --git a/src/vs/workbench/contrib/codeEditor/browser/inspectKeybindings.ts b/src/vs/workbench/contrib/codeEditor/browser/inspectKeybindings.ts index bc943535f708..3be91346a829 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/inspectKeybindings.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/inspectKeybindings.ts @@ -7,7 +7,7 @@ import * as nls from 'vs/nls'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorAction, ServicesAccessor, registerEditorAction } from 'vs/editor/browser/editorExtensions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { IUntitledResourceInput } from 'vs/workbench/common/editor'; +import { IUntitledTextResourceInput } from 'vs/workbench/common/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { Registry } from 'vs/platform/registry/common/platform'; import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions'; @@ -29,7 +29,7 @@ class InspectKeyMap extends EditorAction { const keybindingService = accessor.get(IKeybindingService); const editorService = accessor.get(IEditorService); - editorService.openEditor({ contents: keybindingService._dumpDebugInfo(), options: { pinned: true } } as IUntitledResourceInput); + editorService.openEditor({ contents: keybindingService._dumpDebugInfo(), options: { pinned: true } } as IUntitledTextResourceInput); } } @@ -49,9 +49,9 @@ class InspectKeyMapJSON extends Action { } public run(): Promise { - return this._editorService.openEditor({ contents: this._keybindingService._dumpDebugInfoJSON(), options: { pinned: true } } as IUntitledResourceInput); + return this._editorService.openEditor({ contents: this._keybindingService._dumpDebugInfoJSON(), options: { pinned: true } } as IUntitledTextResourceInput); } } const registry = Registry.as(ActionExtensions.WorkbenchActions); -registry.registerWorkbenchAction(new SyncActionDescriptor(InspectKeyMapJSON, InspectKeyMapJSON.ID, InspectKeyMapJSON.LABEL), 'Developer: Inspect Key Mappings (JSON)', nls.localize('developer', "Developer")); +registry.registerWorkbenchAction(SyncActionDescriptor.create(InspectKeyMapJSON, InspectKeyMapJSON.ID, InspectKeyMapJSON.LABEL), 'Developer: Inspect Key Mappings (JSON)', nls.localize('developer', "Developer")); diff --git a/src/vs/workbench/contrib/codeEditor/browser/inspectTMScopes/inspectTMScopes.css b/src/vs/workbench/contrib/codeEditor/browser/inspectTMScopes/inspectTMScopes.css index 53865e7f9588..e2f08ab66909 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/inspectTMScopes/inspectTMScopes.css +++ b/src/vs/workbench/contrib/codeEditor/browser/inspectTMScopes/inspectTMScopes.css @@ -6,6 +6,7 @@ .tm-inspect-widget { z-index: 50; user-select: text; + -webkit-user-select: text; padding: 10px; } diff --git a/src/vs/workbench/contrib/codeEditor/browser/inspectTMScopes/inspectTMScopes.ts b/src/vs/workbench/contrib/codeEditor/browser/inspectTMScopes/inspectTMScopes.ts index 8dc4cbc91e39..38141efdcd6a 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/inspectTMScopes/inspectTMScopes.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/inspectTMScopes/inspectTMScopes.ts @@ -174,7 +174,7 @@ class InspectTMScopesWidget extends Disposable implements IContentWidget { private readonly _notificationService: INotificationService; private readonly _model: ITextModel; private readonly _domNode: HTMLElement; - private readonly _grammar: Promise; + private readonly _grammar: Promise; constructor( editor: IActiveCodeEditor, @@ -212,7 +212,12 @@ class InspectTMScopesWidget extends Disposable implements IContentWidget { dom.clearNode(this._domNode); this._domNode.appendChild(document.createTextNode(nls.localize('inspectTMScopesWidget.loading', "Loading..."))); this._grammar.then( - (grammar) => this._compute(grammar, position), + (grammar) => { + if (!grammar) { + throw new Error(`Could not find grammar for language!`); + } + this._compute(grammar, position); + }, (err) => { this._notificationService.warn(err); setTimeout(() => { diff --git a/src/vs/workbench/contrib/codeEditor/browser/languageConfigurationExtensionPoint.ts b/src/vs/workbench/contrib/codeEditor/browser/languageConfigurationExtensionPoint.ts index 9469bb577b9d..7559db5610b3 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/languageConfigurationExtensionPoint.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/languageConfigurationExtensionPoint.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import { ParseError, parse } from 'vs/base/common/json'; +import { ParseError, parse, getNodeType } from 'vs/base/common/json'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import * as types from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; @@ -12,12 +12,12 @@ import { LanguageIdentifier } from 'vs/editor/common/modes'; import { CharacterPair, CommentRule, FoldingRules, IAutoClosingPair, IAutoClosingPairConditional, IndentationRule, LanguageConfiguration } from 'vs/editor/common/modes/languageConfiguration'; import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { IModeService } from 'vs/editor/common/services/modeService'; -import { IFileService } from 'vs/platform/files/common/files'; import { Extensions, IJSONContributionRegistry } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { ITextMateService } from 'vs/workbench/services/textMate/common/textMateService'; import { getParseErrorMessage } from 'vs/base/common/jsonErrorMessages'; +import { IExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader'; interface IRegExp { pattern: string; @@ -43,7 +43,16 @@ interface ILanguageConfiguration { } function isStringArr(something: string[] | null): something is string[] { - return Array.isArray(something) && something.every(value => typeof value === 'string'); + if (!Array.isArray(something)) { + return false; + } + for (let i = 0, len = something.length; i < len; i++) { + if (typeof something[i] !== 'string') { + return false; + } + } + return true; + } function isCharacterPair(something: CharacterPair | null): boolean { @@ -60,7 +69,7 @@ export class LanguageConfigurationFileHandler { constructor( @ITextMateService textMateService: ITextMateService, @IModeService private readonly _modeService: IModeService, - @IFileService private readonly _fileService: IFileService, + @IExtensionResourceLoaderService private readonly _extensionResourceLoaderService: IExtensionResourceLoaderService, @IExtensionService private readonly _extensionService: IExtensionService ) { this._done = []; @@ -89,12 +98,16 @@ export class LanguageConfigurationFileHandler { } private _handleConfigFile(languageIdentifier: LanguageIdentifier, configFileLocation: URI): void { - this._fileService.readFile(configFileLocation).then((contents) => { + this._extensionResourceLoaderService.readExtensionResource(configFileLocation).then((contents) => { const errors: ParseError[] = []; - const configuration = parse(contents.value.toString(), errors); + let configuration = parse(contents, errors); if (errors.length) { console.error(nls.localize('parseErrors', "Errors parsing {0}: {1}", configFileLocation.toString(), errors.map(e => (`[${e.offset}, ${e.length}] ${getParseErrorMessage(e.error)}`)).join('\n'))); } + if (getNodeType(configuration) !== 'object') { + console.error(nls.localize('formatError', "{0}: Invalid format, JSON object expected.", configFileLocation.toString())); + configuration = {}; + } this._handleConfig(languageIdentifier, configuration); }, (err) => { console.error(err); diff --git a/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.css b/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.css index b0680b67ae2b..1eb0923eb9f9 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.css +++ b/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.css @@ -10,8 +10,6 @@ .suggest-input-container .monaco-editor-background, .suggest-input-container .monaco-editor, .suggest-input-container .mtk1 { - /* allow the embedded monaco to be styled from the outer context */ - background-color: transparent; color: inherit; } @@ -25,4 +23,3 @@ margin-top: 2px; margin-left: 1px; } - diff --git a/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts b/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts index f90f89d6ae98..37cdf82fb7a3 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts @@ -27,12 +27,13 @@ import { SuggestController } from 'vs/editor/contrib/suggest/suggestController'; import { IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ColorIdentifier, editorSelectionBackground, inputBackground, inputBorder, inputForeground, inputPlaceholderForeground, selectionBackground } from 'vs/platform/theme/common/colorRegistry'; -import { IStyleOverrides, IThemable, attachStyler } from 'vs/platform/theme/common/styler'; +import { IStyleOverrides, attachStyler } from 'vs/platform/theme/common/styler'; import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { MenuPreventer } from 'vs/workbench/contrib/codeEditor/browser/menuPreventer'; import { getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions'; import { SelectionClipboardContributionID } from 'vs/workbench/contrib/codeEditor/browser/selectionClipboard'; import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; +import { IThemable } from 'vs/base/common/styler'; interface SuggestResultsProvider { /** @@ -81,7 +82,7 @@ export interface ISuggestEnabledInputStyleOverrides extends IStyleOverrides { } type ISuggestEnabledInputStyles = { - [P in keyof ISuggestEnabledInputStyleOverrides]: Color; + [P in keyof ISuggestEnabledInputStyleOverrides]: Color | undefined; }; export function attachSuggestEnabledInputBoxStyler(widget: IThemable, themeService: IThemeService, style?: ISuggestEnabledInputStyleOverrides): IDisposable { @@ -223,7 +224,8 @@ export class SuggestEnabledInput extends Widget implements IThemable { public style(colors: ISuggestEnabledInputStyles): void { - this.stylingContainer.style.backgroundColor = colors.inputBackground ? colors.inputBackground.toString() : ''; + this.placeholderText.style.backgroundColor = + this.stylingContainer.style.backgroundColor = colors.inputBackground ? colors.inputBackground.toString() : ''; this.stylingContainer.style.color = colors.inputForeground ? colors.inputForeground.toString() : null; this.placeholderText.style.color = colors.inputPlaceholderForeground ? colors.inputPlaceholderForeground.toString() : null; @@ -247,9 +249,13 @@ export class SuggestEnabledInput extends Widget implements IThemable { } } + public onHide(): void { + this.inputWidget.onHide(); + } + public layout(dimension: Dimension): void { this.inputWidget.layout(dimension); - this.placeholderText.style.width = `${dimension.width}px`; + this.placeholderText.style.width = `${dimension.width - 2}px`; } private selectAll(): void { @@ -281,6 +287,12 @@ registerThemingParticipant((theme, collector) => { if (inputForegroundColor) { collector.addRule(`.suggest-input-container .monaco-editor .view-line span.inline-selected-text { color: ${inputForegroundColor}; }`); } + + const backgroundColor = theme.getColor(inputBackground); + if (backgroundColor) { + collector.addRule(`.suggest-input-container .monaco-editor-background { background-color: ${backgroundColor}; } `); + collector.addRule(`.suggest-input-container .monaco-editor { background-color: ${backgroundColor}; } `); + } }); diff --git a/src/vs/workbench/contrib/codeEditor/browser/toggleMinimap.ts b/src/vs/workbench/contrib/codeEditor/browser/toggleMinimap.ts index d38d36ddde99..3038f45628d2 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/toggleMinimap.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/toggleMinimap.ts @@ -30,7 +30,7 @@ export class ToggleMinimapAction extends Action { } const registry = Registry.as(ActionExtensions.WorkbenchActions); -registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleMinimapAction, ToggleMinimapAction.ID, ToggleMinimapAction.LABEL), 'View: Toggle Minimap', nls.localize('view', "View")); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleMinimapAction, ToggleMinimapAction.ID, ToggleMinimapAction.LABEL), 'View: Toggle Minimap', nls.localize('view', "View")); /* {{SQL CARBON EDIT}} - Disable unused menu item MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { diff --git a/src/vs/workbench/contrib/codeEditor/browser/toggleMultiCursorModifier.ts b/src/vs/workbench/contrib/codeEditor/browser/toggleMultiCursorModifier.ts index 5d8813b05468..b8675d897fb2 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/toggleMultiCursorModifier.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/toggleMultiCursorModifier.ts @@ -66,7 +66,7 @@ Registry.as(WorkbenchExtensions.Workbench).regi const registry = Registry.as(Extensions.WorkbenchActions); -registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleMultiCursorModifierAction, ToggleMultiCursorModifierAction.ID, ToggleMultiCursorModifierAction.LABEL), 'Toggle Multi-Cursor Modifier'); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleMultiCursorModifierAction, ToggleMultiCursorModifierAction.ID, ToggleMultiCursorModifierAction.LABEL), 'Toggle Multi-Cursor Modifier'); MenuRegistry.appendMenuItem(MenuId.MenubarSelectionMenu, { group: '3_multi', command: { @@ -88,4 +88,4 @@ MenuRegistry.appendMenuItem(MenuId.MenubarSelectionMenu, { }, when: multiCursorModifier.isEqualTo('altKey'), order: 1 -}); \ No newline at end of file +}); diff --git a/src/vs/workbench/contrib/codeEditor/browser/toggleRenderControlCharacter.ts b/src/vs/workbench/contrib/codeEditor/browser/toggleRenderControlCharacter.ts index 161fba0dc50a..5702c01c331e 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/toggleRenderControlCharacter.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/toggleRenderControlCharacter.ts @@ -31,7 +31,7 @@ export class ToggleRenderControlCharacterAction extends Action { } const registry = Registry.as(ActionExtensions.WorkbenchActions); -registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleRenderControlCharacterAction, ToggleRenderControlCharacterAction.ID, ToggleRenderControlCharacterAction.LABEL), 'View: Toggle Control Characters', nls.localize('view', "View")); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleRenderControlCharacterAction, ToggleRenderControlCharacterAction.ID, ToggleRenderControlCharacterAction.LABEL), 'View: Toggle Control Characters', nls.localize('view', "View")); /* {{SQL CARBON EDIT}} - Disable unused menu item MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { diff --git a/src/vs/workbench/contrib/codeEditor/browser/toggleRenderWhitespace.ts b/src/vs/workbench/contrib/codeEditor/browser/toggleRenderWhitespace.ts index dffc2f7c1ccb..39601690bee9 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/toggleRenderWhitespace.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/toggleRenderWhitespace.ts @@ -39,7 +39,7 @@ export class ToggleRenderWhitespaceAction extends Action { } const registry = Registry.as(ActionExtensions.WorkbenchActions); -registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleRenderWhitespaceAction, ToggleRenderWhitespaceAction.ID, ToggleRenderWhitespaceAction.LABEL), 'View: Toggle Render Whitespace', nls.localize('view', "View")); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleRenderWhitespaceAction, ToggleRenderWhitespaceAction.ID, ToggleRenderWhitespaceAction.LABEL), 'View: Toggle Render Whitespace', nls.localize('view', "View")); /* {{SQL CARBON EDIT}} - Disable unused menu item MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { diff --git a/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts b/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts index 04652e39e008..8387e3921b93 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts @@ -211,6 +211,10 @@ class ToggleWordWrapController extends Disposable implements IEditorContribution // in the settings editor... return; } + if (this.editor.isSimpleWidget) { + // in a simple widget... + return; + } // Ensure correct word wrap settings const newModel = this.editor.getModel(); if (!newModel) { @@ -275,7 +279,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: TOGGLE_WORD_WRAP_ID, title: nls.localize('unwrapMinified', "Disable wrapping for this file"), - iconLocation: { + icon: { dark: WORD_WRAP_DARK_ICON, light: WORD_WRAP_LIGHT_ICON } @@ -292,7 +296,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: TOGGLE_WORD_WRAP_ID, title: nls.localize('wrapMinified', "Enable wrapping for this file"), - iconLocation: { + icon: { dark: WORD_WRAP_DARK_ICON, light: WORD_WRAP_LIGHT_ICON } diff --git a/src/vs/workbench/contrib/codeEditor/browser/workbenchReferenceSearch.ts b/src/vs/workbench/contrib/codeEditor/browser/workbenchReferenceSearch.ts index ea8f40e964fe..82a043f67f45 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/workbenchReferenceSearch.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/workbenchReferenceSearch.ts @@ -6,7 +6,7 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { ReferencesController } from 'vs/editor/contrib/referenceSearch/referencesController'; +import { ReferencesController } from 'vs/editor/contrib/gotoSymbol/peek/referencesController'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; diff --git a/src/vs/workbench/contrib/codeEditor/electron-browser/codeEditor.contribution.ts b/src/vs/workbench/contrib/codeEditor/electron-browser/codeEditor.contribution.ts index 6000fabe0eff..0e26f72cc16f 100644 --- a/src/vs/workbench/contrib/codeEditor/electron-browser/codeEditor.contribution.ts +++ b/src/vs/workbench/contrib/codeEditor/electron-browser/codeEditor.contribution.ts @@ -3,5 +3,6 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import './inputClipboardActions'; import './sleepResumeRepaintMinimap'; import './selectionClipboard'; diff --git a/src/vs/workbench/contrib/codeEditor/electron-browser/inputClipboardActions.ts b/src/vs/workbench/contrib/codeEditor/electron-browser/inputClipboardActions.ts new file mode 100644 index 000000000000..822cd5ab5fbe --- /dev/null +++ b/src/vs/workbench/contrib/codeEditor/electron-browser/inputClipboardActions.ts @@ -0,0 +1,43 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import * as platform from 'vs/base/common/platform'; +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; + +if (platform.isMacintosh) { + + // On the mac, cmd+x, cmd+c and cmd+v do not result in cut / copy / paste + // We therefore add a basic keybinding rule that invokes document.execCommand + // This is to cover s... + + KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'execCut', + primary: KeyMod.CtrlCmd | KeyCode.KEY_X, + handler: bindExecuteCommand('cut'), + weight: 0, + when: undefined, + }); + KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'execCopy', + primary: KeyMod.CtrlCmd | KeyCode.KEY_C, + handler: bindExecuteCommand('copy'), + weight: 0, + when: undefined, + }); + KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'execPaste', + primary: KeyMod.CtrlCmd | KeyCode.KEY_V, + handler: bindExecuteCommand('paste'), + weight: 0, + when: undefined, + }); + + function bindExecuteCommand(command: 'cut' | 'copy' | 'paste') { + return () => { + document.execCommand(command); + }; + } +} diff --git a/src/vs/workbench/contrib/codeEditor/electron-browser/selectionClipboard.ts b/src/vs/workbench/contrib/codeEditor/electron-browser/selectionClipboard.ts index 51e88400c702..3b56e368b18f 100644 --- a/src/vs/workbench/contrib/codeEditor/electron-browser/selectionClipboard.ts +++ b/src/vs/workbench/contrib/codeEditor/electron-browser/selectionClipboard.ts @@ -6,7 +6,7 @@ import { RunOnceScheduler } from 'vs/base/common/async'; import { Disposable } from 'vs/base/common/lifecycle'; import * as platform from 'vs/base/common/platform'; -import { ICodeEditor, IEditorMouseEvent } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { ConfigurationChangedEvent, EditorOption } from 'vs/editor/common/config/editorOptions'; import { ICursorSelectionChangedEvent } from 'vs/editor/common/controller/cursorEvents'; @@ -15,6 +15,10 @@ import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { EndOfLinePreference } from 'vs/editor/common/model'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { SelectionClipboardContributionID } from 'vs/workbench/contrib/codeEditor/browser/selectionClipboard'; +import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; export class SelectionClipboard extends Disposable implements IEditorContribution { private static readonly SELECTION_LENGTH_LIMIT = 65536; @@ -31,15 +35,6 @@ export class SelectionClipboard extends Disposable implements IEditorContributio } })); - this._register(editor.onMouseUp((e: IEditorMouseEvent) => { - if (!isEnabled) { - if (e.event.middleButton) { - // try to stop the upcoming paste - e.event.preventDefault(); - } - } - })); - let setSelectionToClipboard = this._register(new RunOnceScheduler(() => { if (!editor.hasModel()) { return; @@ -92,4 +87,25 @@ export class SelectionClipboard extends Disposable implements IEditorContributio } } +class SelectionClipboardPastePreventer implements IWorkbenchContribution { + constructor( + @IConfigurationService configurationService: IConfigurationService + ) { + if (platform.isLinux) { + document.addEventListener('mouseup', (e) => { + if (e.button === 1) { + // middle button + const config = configurationService.getValue<{ selectionClipboard: boolean; }>('editor'); + if (!config.selectionClipboard) { + // selection clipboard is disabled + // try to stop the upcoming paste + e.preventDefault(); + } + } + }); + } + } +} + registerEditorContribution(SelectionClipboardContributionID, SelectionClipboard); +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(SelectionClipboardPastePreventer, LifecyclePhase.Ready); diff --git a/src/vs/workbench/contrib/comments/browser/commentNode.ts b/src/vs/workbench/contrib/comments/browser/commentNode.ts index b942a6a1b8d1..368c41ae2abd 100644 --- a/src/vs/workbench/contrib/comments/browser/commentNode.ts +++ b/src/vs/workbench/contrib/comments/browser/commentNode.ts @@ -57,7 +57,7 @@ export class CommentNode extends Disposable { protected toolbar: ToolBar | undefined; private _commentFormActions: CommentFormActions | null = null; - private readonly _onDidDelete = new Emitter(); + private readonly _onDidClick = new Emitter(); public get domNode(): HTMLElement { return this._domNode; @@ -89,7 +89,7 @@ export class CommentNode extends Disposable { this._contextKeyService = contextKeyService.createScoped(this._domNode); this._commentContextValue = this._contextKeyService.createKey('comment', comment.contextValue); - this._domNode.tabIndex = 0; + this._domNode.tabIndex = -1; const avatar = dom.append(this._domNode, dom.$('div.avatar-container')); if (comment.userIconPath) { const img = dom.append(avatar, dom.$('img.avatar')); @@ -111,10 +111,12 @@ export class CommentNode extends Disposable { this._domNode.setAttribute('aria-label', `${comment.userName}, ${comment.body.value}`); this._domNode.setAttribute('role', 'treeitem'); this._clearTimeout = null; + + this._register(dom.addDisposableListener(this._domNode, dom.EventType.CLICK, () => this.isEditing || this._onDidClick.fire(this))); } - public get onDidDelete(): Event { - return this._onDidDelete.event; + public get onDidClick(): Event { + return this._onDidClick.event; } private createHeader(commentDetailsContainer: HTMLElement): void { @@ -430,19 +432,31 @@ export class CommentNode extends Disposable { this._commentFormActions.setActions(menu); } + setFocus(focused: boolean, visible: boolean = false) { + if (focused) { + this._domNode.focus(); + this._actionsToolbarContainer.classList.remove('hidden'); + this._actionsToolbarContainer.classList.add('tabfocused'); + this._domNode.tabIndex = 0; + } else { + if (this._actionsToolbarContainer.classList.contains('tabfocused') && !this._actionsToolbarContainer.classList.contains('mouseover')) { + this._actionsToolbarContainer.classList.add('hidden'); + this._domNode.tabIndex = -1; + } + this._actionsToolbarContainer.classList.remove('tabfocused'); + } + } + private registerActionBarListeners(actionsContainer: HTMLElement): void { this._register(dom.addDisposableListener(this._domNode, 'mouseenter', () => { actionsContainer.classList.remove('hidden'); + actionsContainer.classList.add('mouseover'); })); - - this._register(dom.addDisposableListener(this._domNode, 'focus', () => { - actionsContainer.classList.remove('hidden'); - })); - this._register(dom.addDisposableListener(this._domNode, 'mouseleave', () => { - if (!this._domNode.contains(document.activeElement)) { + if (actionsContainer.classList.contains('mouseover') && !actionsContainer.classList.contains('tabfocused')) { actionsContainer.classList.add('hidden'); } + actionsContainer.classList.remove('mouseover'); })); } diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts index c7cb23d75395..16ae10cfa795 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts @@ -23,11 +23,11 @@ import * as modes from 'vs/editor/common/modes'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IModeService } from 'vs/editor/common/services/modeService'; import { MarkdownRenderer } from 'vs/editor/contrib/markdown/markdownRenderer'; -import { peekViewBorder } from 'vs/editor/contrib/referenceSearch/referencesWidget'; +import { peekViewBorder } from 'vs/editor/contrib/peekView/peekView'; import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/zoneWidget'; import * as nls from 'vs/nls'; import { ContextAwareMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; -import { IMenu, MenuItemAction } from 'vs/platform/actions/common/actions'; +import { IMenu, MenuItemAction, SubmenuItemAction } from 'vs/platform/actions/common/actions'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -46,6 +46,8 @@ import { ICommentThreadWidget } from 'vs/workbench/contrib/comments/common/comme import { SimpleCommentEditor } from './simpleCommentEditor'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; +import { KeyCode } from 'vs/base/common/keyCodes'; +import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; export const COMMENTEDITOR_DECORATION_KEY = 'commenteditordecoration'; const COLLAPSE_ACTION_CLASS = 'expand-review-action'; @@ -84,6 +86,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget private _commentEditorIsEmpty!: IContextKey; private _commentFormActions!: CommentFormActions; private _scopedInstatiationService: IInstantiationService; + private _focusedComment: number | undefined = undefined; public get owner(): string { return this._owner; @@ -257,7 +260,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget } private setActionBarActions(menu: IMenu): void { - const groups = menu.getActions({ shouldForwardArgs: true }).reduce((r, [, actions]) => [...r, ...actions], []); + const groups = menu.getActions({ shouldForwardArgs: true }).reduce((r, [, actions]) => [...r, ...actions], <(MenuItemAction | SubmenuItemAction)[]>[]); this._actionbarWidget.clear(); this._actionbarWidget.push([...groups, this._collapseAction], { label: false, icon: true }); } @@ -268,6 +271,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget } public collapse(): Promise { + this._commentThread.collapsibleState = modes.CommentThreadCollapsibleState.Collapsed; if (this._commentThread.comments && this._commentThread.comments.length === 0) { this.deleteCommentThread(); return Promise.resolve(); @@ -286,11 +290,13 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget toggleExpand(lineNumber: number) { if (this._isExpanded) { + this._commentThread.collapsibleState = modes.CommentThreadCollapsibleState.Collapsed; this.hide(); if (!this._commentThread.comments || !this._commentThread.comments.length) { this.deleteCommentThread(); } } else { + this._commentThread.collapsibleState = modes.CommentThreadCollapsibleState.Expanded; this.show({ lineNumber: lineNumber, column: 1 }, 2); } } @@ -379,6 +385,8 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget } else { this._commentThreadContextValue.reset(); } + + this.setFocusedComment(this._focusedComment); } protected _onWidth(widthInPixel: number): void { @@ -401,6 +409,21 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget this._commentsElement = dom.append(this._bodyElement, dom.$('div.comments-container')); this._commentsElement.setAttribute('role', 'presentation'); + this._commentsElement.tabIndex = 0; + + this._disposables.add(dom.addDisposableListener(this._commentsElement, dom.EventType.KEY_DOWN, (e) => { + let event = new StandardKeyboardEvent(e as KeyboardEvent); + if (event.equals(KeyCode.UpArrow) || event.equals(KeyCode.DownArrow)) { + const moveFocusWithinBounds = (change: number): number => { + if (this._focusedComment === undefined && change >= 0) { return 0; } + if (this._focusedComment === undefined && change < 0) { return this._commentElements.length - 1; } + let newIndex = this._focusedComment! + change; + return Math.min(Math.max(0, newIndex), this._commentElements.length - 1); + }; + + this.setFocusedComment(event.equals(KeyCode.UpArrow) ? moveFocusWithinBounds(-1) : moveFocusWithinBounds(1)); + } + })); this._commentElements = []; if (this._commentThread.comments) { @@ -565,6 +588,19 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget })); } + private setFocusedComment(value: number | undefined) { + if (this._focusedComment !== undefined) { + this._commentElements[this._focusedComment]?.setFocus(false); + } + + if (this._commentElements.length === 0 || value === undefined) { + this._focusedComment = undefined; + } else { + this._focusedComment = Math.min(value, this._commentElements.length - 1); + this._commentElements[this._focusedComment].setFocus(true); + } + } + private getActiveComment(): CommentNode | ReviewZoneWidget { return this._commentElements.filter(node => node.isEditing)[0] || this; } @@ -613,25 +649,9 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget this._markdownRenderer); this._disposables.add(newCommentNode); - this._disposables.add(newCommentNode.onDidDelete(deletedNode => { - const deletedNodeId = deletedNode.comment.uniqueIdInThread; - const deletedElementIndex = arrays.firstIndex(this._commentElements, commentNode => commentNode.comment.uniqueIdInThread === deletedNodeId); - if (deletedElementIndex > -1) { - this._commentElements.splice(deletedElementIndex, 1); - } - - const deletedCommentIndex = arrays.firstIndex(this._commentThread.comments!, comment => comment.uniqueIdInThread === deletedNodeId); - if (deletedCommentIndex > -1) { - this._commentThread.comments!.splice(deletedCommentIndex, 1); - } - - this._commentsElement.removeChild(deletedNode.domNode); - deletedNode.dispose(); - - if (this._commentThread.comments!.length === 0) { - this.dispose(); - } - })); + this._disposables.add(newCommentNode.onDidClick(clickedNode => + this.setFocusedComment(arrays.firstIndex(this._commentElements, commentNode => commentNode.comment.uniqueIdInThread === clickedNode.comment.uniqueIdInThread)) + )); return newCommentNode; } diff --git a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts index 164f062e288c..cfcc940800ce 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts @@ -21,7 +21,7 @@ import { IEditorContribution, IModelChangedEvent } from 'vs/editor/common/editor import { IModelDecorationOptions } from 'vs/editor/common/model'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import * as modes from 'vs/editor/common/modes'; -import { peekViewResultsBackground, peekViewResultsSelectionBackground, peekViewTitleBackground } from 'vs/editor/contrib/referenceSearch/referencesWidget'; +import { peekViewResultsBackground, peekViewResultsSelectionBackground, peekViewTitleBackground } from 'vs/editor/contrib/peekView/peekView'; import * as nls from 'vs/nls'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; diff --git a/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts b/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts index e718cb1b9f8a..ddebf8db939e 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts @@ -8,7 +8,6 @@ import * as nls from 'vs/nls'; import { renderMarkdown } from 'vs/base/browser/markdownRenderer'; import { onUnexpectedError } from 'vs/base/common/errors'; import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { URI } from 'vs/base/common/uri'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IResourceLabel, ResourceLabels } from 'vs/workbench/browser/labels'; import { CommentNode, CommentsModel, ResourceWithCommentThreads } from 'vs/workbench/contrib/comments/common/commentModel'; @@ -21,6 +20,7 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { WorkbenchAsyncDataTree, IListService } from 'vs/platform/list/browser/listService'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { PANEL_BACKGROUND } from 'vs/workbench/common/theme'; export const COMMENTS_PANEL_ID = 'workbench.panel.comments'; export const COMMENTS_PANEL_TITLE = 'Comments'; @@ -127,12 +127,7 @@ export class CommentNodeRenderer implements IListRenderer inline: true, actionHandler: { callback: (content) => { - try { - const uri = URI.parse(content); - this.openerService.open(uri).catch(onUnexpectedError); - } catch (err) { - // ignore - } + this.openerService.open(content).catch(onUnexpectedError); }, disposeables: disposables } @@ -206,6 +201,9 @@ export class CommentsList extends WorkbenchAsyncDataTree { }, collapseByDefault: () => { return false; + }, + overrideStyles: { + listBackground: PANEL_BACKGROUND } }, contextKeyService, diff --git a/src/vs/workbench/contrib/comments/browser/media/review.css b/src/vs/workbench/contrib/comments/browser/media/review.css index 41c1ad803984..5652125d0b1d 100644 --- a/src/vs/workbench/contrib/comments/browser/media/review.css +++ b/src/vs/workbench/contrib/comments/browser/media/review.css @@ -78,6 +78,7 @@ .monaco-editor .review-widget .body .review-comment .review-comment-contents { padding-left: 20px; user-select: text; + -webkit-user-select: text; width: 100%; overflow: hidden; } @@ -134,6 +135,7 @@ width: 16px; height: 12px; -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; display: inline-block; margin-top: 3px; margin-right: 4px; @@ -274,10 +276,6 @@ text-align: left; width: 100%; box-sizing: border-box; - -webkit-box-sizing: border-box; - -o-box-sizing: border-box; - -moz-box-sizing: border-box; - -ms-box-sizing: border-box; padding: 0.4em; font-size: 12px; line-height: 17px; @@ -362,10 +360,6 @@ } .monaco-editor .review-widget .head { - -webkit-box-sizing: border-box; - -o-box-sizing: border-box; - -moz-box-sizing: border-box; - -ms-box-sizing: border-box; box-sizing: border-box; display: flex; height: 100%; diff --git a/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts b/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts index d621320c75b2..d1a6643e6425 100644 --- a/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts +++ b/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts @@ -4,9 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; -import { EditorAction, EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; +import { EditorAction, EditorExtensionsRegistry, IEditorContributionDescription } from 'vs/editor/browser/editorExtensions'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; import { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ICommandService } from 'vs/platform/commands/common/commands'; @@ -46,8 +46,9 @@ export class SimpleCommentEditor extends CodeEditorWidget { @INotificationService notificationService: INotificationService, @IAccessibilityService accessibilityService: IAccessibilityService ) { - const codeEditorWidgetOptions = { - contributions: [ + const codeEditorWidgetOptions: ICodeEditorWidgetOptions = { + isSimpleWidget: true, + contributions: [ { id: MenuPreventer.ID, ctor: MenuPreventer }, { id: ContextMenuController.ID, ctor: ContextMenuController }, { id: SuggestController.ID, ctor: SuggestController }, diff --git a/src/vs/workbench/contrib/customEditor/browser/commands.ts b/src/vs/workbench/contrib/customEditor/browser/commands.ts index a6c40d7a3bb2..b6333d20cfe7 100644 --- a/src/vs/workbench/contrib/customEditor/browser/commands.ts +++ b/src/vs/workbench/contrib/customEditor/browser/commands.ts @@ -4,15 +4,19 @@ *--------------------------------------------------------------------------------------------*/ import { firstOrDefault } from 'vs/base/common/arrays'; +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { URI } from 'vs/base/common/uri'; +import { Command } from 'vs/editor/browser/editorExtensions'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import * as nls from 'vs/nls'; import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkeys'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { IListService } from 'vs/platform/list/browser/listService'; import { IEditorCommandsContext } from 'vs/workbench/common/editor'; -import { ICustomEditorService, CONTEXT_HAS_CUSTOM_EDITORS } from 'vs/workbench/contrib/customEditor/common/customEditor'; +import { CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE, CONTEXT_HAS_CUSTOM_EDITORS, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; import { getMultiSelectedResources } from 'vs/workbench/contrib/files/browser/files'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -98,3 +102,72 @@ MenuRegistry.appendMenuItem(MenuId.CommandPalette, { }); // #endregion + + +(new class UndoCustomEditorCommand extends Command { + public static readonly ID = 'editor.action.customEditor.undo'; + + constructor() { + super({ + id: UndoCustomEditorCommand.ID, + precondition: ContextKeyExpr.and( + CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE, + ContextKeyExpr.not(InputFocusedContextKey)), + kbOpts: { + primary: KeyMod.CtrlCmd | KeyCode.KEY_Z, + weight: KeybindingWeight.EditorContrib + } + }); + } + + public runCommand(accessor: ServicesAccessor): void { + const customEditorService = accessor.get(ICustomEditorService); + + const activeCustomEditor = customEditorService.activeCustomEditor; + if (!activeCustomEditor) { + return; + } + + const model = customEditorService.models.get(activeCustomEditor.resource, activeCustomEditor.viewType); + if (!model) { + return; + } + + model.undo(); + } +}).register(); + +(new class RedoWebviewEditorCommand extends Command { + public static readonly ID = 'editor.action.customEditor.redo'; + + constructor() { + super({ + id: RedoWebviewEditorCommand.ID, + precondition: ContextKeyExpr.and( + CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE, + ContextKeyExpr.not(InputFocusedContextKey)), + kbOpts: { + primary: KeyMod.CtrlCmd | KeyCode.KEY_Y, + secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_Z], + mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_Z }, + weight: KeybindingWeight.EditorContrib + } + }); + } + + public runCommand(accessor: ServicesAccessor): void { + const customEditorService = accessor.get(ICustomEditorService); + + const activeCustomEditor = customEditorService.activeCustomEditor; + if (!activeCustomEditor) { + return; + } + + const model = customEditorService.models.get(activeCustomEditor.resource, activeCustomEditor.viewType); + if (!model) { + return; + } + + model.redo(); + } +}).register(); diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts index 0caa17305b3a..336b87326387 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts @@ -4,38 +4,44 @@ *--------------------------------------------------------------------------------------------*/ import { memoize } from 'vs/base/common/decorators'; -import { Emitter } from 'vs/base/common/event'; import { Lazy } from 'vs/base/common/lazy'; -import { UnownedDisposable } from 'vs/base/common/lifecycle'; -import { Schemas } from 'vs/base/common/network'; import { basename } from 'vs/base/common/path'; -import { DataUri, isEqual } from 'vs/base/common/resources'; +import { isEqual } from 'vs/base/common/resources'; +import { assertIsDefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; -import { WebviewContentState } from 'vs/editor/common/modes'; -import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { generateUuid } from 'vs/base/common/uuid'; +import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IEditorModel, ITextEditorOptions } from 'vs/platform/editor/common/editor'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILabelService } from 'vs/platform/label/common/label'; -import { ConfirmResult, IEditorInput, Verbosity } from 'vs/workbench/common/editor'; +import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; +import { GroupIdentifier, IEditorInput, IRevertOptions, ISaveOptions, Verbosity } from 'vs/workbench/common/editor'; +import { ICustomEditorModel, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; import { WebviewEditorOverlay } from 'vs/workbench/contrib/webview/browser/webview'; import { IWebviewWorkbenchService, LazilyResolvedWebviewEditorInput } from 'vs/workbench/contrib/webview/browser/webviewWorkbenchService'; -import { promptSave } from 'vs/workbench/services/textfile/browser/textFileService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; export class CustomFileEditorInput extends LazilyResolvedWebviewEditorInput { public static typeId = 'workbench.editors.webviewEditor'; private readonly _editorResource: URI; - private _state = WebviewContentState.Readonly; + private _model?: ICustomEditorModel; constructor( resource: URI, viewType: string, id: string, - webview: Lazy>, + webview: Lazy, + @ILifecycleService lifecycleService: ILifecycleService, @IWebviewWorkbenchService webviewWorkbenchService: IWebviewWorkbenchService, - @IDialogService private readonly dialogService: IDialogService, + @IInstantiationService private readonly instantiationService: IInstantiationService, @ILabelService private readonly labelService: ILabelService, + @ICustomEditorService private readonly customEditorService: ICustomEditorService, + @IEditorService private readonly editorService: IEditorService, + @IFileDialogService private readonly fileDialogService: IFileDialogService, ) { - super(id, viewType, '', webview, webviewWorkbenchService); + super(id, viewType, '', webview, webviewWorkbenchService, lifecycleService); this._editorResource = resource; } @@ -47,27 +53,17 @@ export class CustomFileEditorInput extends LazilyResolvedWebviewEditorInput { return this._editorResource; } + public supportsSplitEditor() { + return true; + } + @memoize getName(): string { - if (this.getResource().scheme === Schemas.data) { - const metadata = DataUri.parseMetaData(this.getResource()); - const label = metadata.get(DataUri.META_DATA_LABEL); - if (typeof label === 'string') { - return label; - } - } return basename(this.labelService.getUriLabel(this.getResource())); } @memoize getDescription(): string | undefined { - if (this.getResource().scheme === Schemas.data) { - const metadata = DataUri.parseMetaData(this.getResource()); - const description = metadata.get(DataUri.META_DATA_DESCRIPTION); - if (typeof description === 'string') { - return description; - } - } return super.getDescription(); } @@ -84,17 +80,11 @@ export class CustomFileEditorInput extends LazilyResolvedWebviewEditorInput { @memoize private get mediumTitle(): string { - if (this.getResource().scheme === Schemas.data) { - return this.getName(); - } return this.labelService.getUriLabel(this.getResource(), { relative: true }); } @memoize private get longTitle(): string { - if (this.getResource().scheme === Schemas.data) { - return this.getName(); - } return this.labelService.getUriLabel(this.getResource()); } @@ -110,34 +100,71 @@ export class CustomFileEditorInput extends LazilyResolvedWebviewEditorInput { } } - public setState(newState: WebviewContentState): void { - this._state = newState; - this._onDidChangeDirty.fire(); + public isReadonly(): boolean { + return false; } - public isDirty() { - return this._state === WebviewContentState.Dirty; + public isDirty(): boolean { + return this._model ? this._model.isDirty() : false; } - public async confirmSave(): Promise { - if (!this.isDirty()) { - return ConfirmResult.DONT_SAVE; - } - return promptSave(this.dialogService, [this.getResource()]); + public save(groupId: GroupIdentifier, options?: ISaveOptions): Promise { + return this._model ? this._model.save(options) : Promise.resolve(false); } - public async save(): Promise { - if (!this.isDirty) { - return true; + public async saveAs(groupId: GroupIdentifier, options?: ISaveOptions): Promise { + if (!this._model) { + return false; } - const waitingOn: Promise[] = []; - this._onWillSave.fire({ - waitUntil: (thenable: Promise): void => { waitingOn.push(thenable); }, - }); - const result = await Promise.all(waitingOn); - return result.every(x => x); + + // Preserve view state by opening the editor first. In addition + // this allows the user to review the contents of the editor. + // let viewState: IEditorViewState | undefined = undefined; + // const editor = await this.editorService.openEditor(this, undefined, group); + // if (isTextEditor(editor)) { + // viewState = editor.getViewState(); + // } + + let dialogPath = this._editorResource; + // if (this._editorResource.scheme === Schemas.untitled) { + // dialogPath = this.suggestFileName(resource); + // } + + const target = await this.promptForPath(this._editorResource, dialogPath, options?.availableFileSystems); + if (!target) { + return false; // save cancelled + } + + await this._model.saveAs(this._editorResource, target, options); + + return true; + } + + public revert(options?: IRevertOptions): Promise { + return this._model ? this._model.revert(options) : Promise.resolve(false); + } + + public async resolve(): Promise { + this._model = await this.customEditorService.models.loadOrCreate(this.getResource(), this.viewType); + this._register(this._model.onDidChangeDirty(() => this._onDidChangeDirty.fire())); + this._onDidChangeDirty.fire(); + return await super.resolve(); } - private readonly _onWillSave = this._register(new Emitter<{ waitUntil: (thenable: Thenable) => void }>()); - public readonly onWillSave = this._onWillSave.event; + protected async promptForPath(resource: URI, defaultUri: URI, availableFileSystems?: readonly string[]): Promise { + + // Help user to find a name for the file by opening it first + await this.editorService.openEditor({ resource, options: { revealIfOpened: true, preserveFocus: true } }); + + return this.fileDialogService.pickFileToSave({});//this.getSaveDialogOptions(defaultUri, availableFileSystems)); + } + + public handleMove(groupId: GroupIdentifier, uri: URI, options?: ITextEditorOptions): IEditorInput | undefined { + const webview = assertIsDefined(this.takeOwnershipOfWebview()); + return this.instantiationService.createInstance(CustomFileEditorInput, + uri, + this.viewType, + generateUuid(), + new Lazy(() => webview)); + } } diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts index 9e8189fc3dd0..fa3d7219e6ac 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts @@ -3,7 +3,6 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { UnownedDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -12,7 +11,7 @@ import { WebviewEditorInputFactory } from 'vs/workbench/contrib/webview/browser/ import { IWebviewWorkbenchService } from 'vs/workbench/contrib/webview/browser/webviewWorkbenchService'; import { Lazy } from 'vs/base/common/lazy'; -export class CustomEditoInputFactory extends WebviewEditorInputFactory { +export class CustomEditorInputFactory extends WebviewEditorInputFactory { public static readonly ID = CustomFileEditorInput.typeId; @@ -48,7 +47,7 @@ export class CustomEditoInputFactory extends WebviewEditorInputFactory { location: data.extensionLocation, id: data.extensionId } : undefined, data.group); - return new UnownedDisposable(webviewInput.webview); + return webviewInput.webview; }); const customInput = this._instantiationService.createInstance(CustomFileEditorInput, URI.from((data as any).editorResource), data.viewType, id, webview); diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts index 7f80c34a2537..a7d38c795db5 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts @@ -3,15 +3,16 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { coalesce, distinct, mergeSort, find } from 'vs/base/common/arrays'; +import { coalesce, distinct, find, mergeSort } from 'vs/base/common/arrays'; import * as glob from 'vs/base/common/glob'; -import { UnownedDisposable, Disposable } from 'vs/base/common/lifecycle'; -import { Schemas } from 'vs/base/common/network'; -import { basename, DataUri, isEqual } from 'vs/base/common/resources'; +import { Lazy } from 'vs/base/common/lazy'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { basename, isEqual } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import * as nls from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; @@ -21,15 +22,14 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { EditorInput, EditorOptions, IEditor, IEditorInput } from 'vs/workbench/common/editor'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { webviewEditorsExtensionPoint } from 'vs/workbench/contrib/customEditor/browser/extensionPoint'; -import { CustomEditorPriority, CustomEditorInfo, CustomEditorSelector, ICustomEditorService, CONTEXT_HAS_CUSTOM_EDITORS } from 'vs/workbench/contrib/customEditor/common/customEditor'; +import { CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE, CONTEXT_HAS_CUSTOM_EDITORS, CustomEditorInfo, CustomEditorPriority, CustomEditorSelector, ICustomEditor, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; +import { CustomEditorModelManager } from 'vs/workbench/contrib/customEditor/common/customEditorModelManager'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; -import { IWebviewService } from 'vs/workbench/contrib/webview/browser/webview'; +import { IWebviewService, webviewHasOwnEditFunctionsContext } from 'vs/workbench/contrib/webview/browser/webview'; import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService, IOpenEditorOverride } from 'vs/workbench/services/editor/common/editorService'; +import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { CustomFileEditorInput } from './customEditorInput'; -import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { Lazy } from 'vs/base/common/lazy'; - const defaultEditorId = 'default'; const defaultEditorInfo: CustomEditorInfo = { @@ -41,7 +41,7 @@ const defaultEditorInfo: CustomEditorInfo = { priority: CustomEditorPriority.default, }; -export class CustomEditorStore { +export class CustomEditorInfoStore { private readonly contributedEditors = new Map(); public clear() { @@ -71,11 +71,17 @@ export class CustomEditorStore { export class CustomEditorService extends Disposable implements ICustomEditorService { _serviceBrand: any; - private readonly editors = new CustomEditorStore(); + private readonly _editorInfoStore = new CustomEditorInfoStore(); + + private readonly _models: CustomEditorModelManager; + private readonly _hasCustomEditor: IContextKey; + private readonly _focusedCustomEditorIsEditable: IContextKey; + private readonly _webviewHasOwnEditFunctions: IContextKey; constructor( @IContextKeyService contextKeyService: IContextKeyService, + @IWorkingCopyService workingCopyService: IWorkingCopyService, @IConfigurationService private readonly configurationService: IConfigurationService, @IEditorService private readonly editorService: IEditorService, @IInstantiationService private readonly instantiationService: IInstantiationService, @@ -84,12 +90,14 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ ) { super(); + this._models = new CustomEditorModelManager(workingCopyService); + webviewEditorsExtensionPoint.setHandler(extensions => { - this.editors.clear(); + this._editorInfoStore.clear(); for (const extension of extensions) { for (const webviewEditorContribution of extension.value) { - this.editors.add({ + this._editorInfoStore.add({ id: webviewEditorContribution.viewType, displayName: webviewEditorContribution.displayName, selector: webviewEditorContribution.selector || [], @@ -97,24 +105,37 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ }); } } - this.updateContext(); + this.updateContexts(); }); this._hasCustomEditor = CONTEXT_HAS_CUSTOM_EDITORS.bindTo(contextKeyService); + this._focusedCustomEditorIsEditable = CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE.bindTo(contextKeyService); + this._webviewHasOwnEditFunctions = webviewHasOwnEditFunctionsContext.bindTo(contextKeyService); - this._register(this.editorService.onDidActiveEditorChange(() => this.updateContext())); - this.updateContext(); + this._register(this.editorService.onDidActiveEditorChange(() => this.updateContexts())); + this.updateContexts(); + } + + public get models() { return this._models; } + + public get activeCustomEditor(): ICustomEditor | undefined { + const activeInput = this.editorService.activeControl?.input; + if (!(activeInput instanceof CustomFileEditorInput)) { + return undefined; + } + const resource = activeInput.getResource(); + return { resource, viewType: activeInput.viewType }; } public getContributedCustomEditors(resource: URI): readonly CustomEditorInfo[] { - return this.editors.getContributedEditors(resource); + return this._editorInfoStore.getContributedEditors(resource); } public getUserConfiguredCustomEditors(resource: URI): readonly CustomEditorInfo[] { const rawAssociations = this.configurationService.getValue(customEditorsAssociationsKey) || []; return coalesce(rawAssociations .filter(association => matches(association, resource)) - .map(association => this.editors.get(association.viewType))); + .map(association => this._editorInfoStore.get(association.viewType))); } public async promptOpenWith( @@ -164,7 +185,7 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ return this.openEditorForResource(resource, fileInput, { ...options, ignoreOverrides: true }, group); } - if (!this.editors.get(viewType)) { + if (!this._editorInfoStore.get(viewType)) { return this.promptOpenWith(resource, options, group); } @@ -180,7 +201,7 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ ): CustomFileEditorInput { const id = generateUuid(); const webview = new Lazy(() => { - return new UnownedDisposable(this.webviewService.createWebviewEditorOverlay(id, { customClasses: options ? options.customClasses : undefined }, {})); + return this.webviewService.createWebviewEditorOverlay(id, { customClasses: options?.customClasses }, {}); }); const input = this.instantiationService.createInstance(CustomFileEditorInput, resource, viewType, id, webview); if (group) { @@ -215,22 +236,23 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ return this.editorService.openEditor(input, options, group); } - private updateContext() { + private updateContexts() { const activeControl = this.editorService.activeControl; - if (!activeControl) { - this._hasCustomEditor.reset(); - return; - } - const resource = activeControl.input.getResource(); + const resource = activeControl?.input.getResource(); if (!resource) { this._hasCustomEditor.reset(); + this._focusedCustomEditorIsEditable.reset(); + this._webviewHasOwnEditFunctions.reset(); return; } + const possibleEditors = [ ...this.getContributedCustomEditors(resource), ...this.getUserConfiguredCustomEditors(resource), ]; this._hasCustomEditor.set(possibleEditors.length > 0); + this._focusedCustomEditorIsEditable.set(activeControl?.input instanceof CustomFileEditorInput); + this._webviewHasOwnEditFunctions.set(true); } } @@ -301,6 +323,11 @@ export class CustomEditorContribution implements IWorkbenchContribution { }; } + // If we have all optional editors, then open VS Code's standard editor + if (contributedEditors.every(editor => editor.priority === CustomEditorPriority.option)) { + return undefined; // {{SQL CARBON EDIT}} strict-null-check + } + // Open VS Code's standard editor but prompt user to see if they wish to use a custom one instead return { override: (async () => { @@ -337,7 +364,7 @@ export class CustomEditorContribution implements IWorkbenchContribution { const editors = mergeSort( distinct([ ...this.customEditorService.getUserConfiguredCustomEditors(resource), - ...this.customEditorService.getContributedCustomEditors(resource), + ...this.customEditorService.getContributedCustomEditors(resource).filter(x => x.priority !== CustomEditorPriority.option), ], editor => editor.id), (a, b) => { return priorityToRank(a.priority) - priorityToRank(b.priority); @@ -379,18 +406,6 @@ function priorityToRank(priority: CustomEditorPriority): number { } function matches(selector: CustomEditorSelector, resource: URI): boolean { - if (resource.scheme === Schemas.data) { - if (!selector.mime) { - return false; - } - const metadata = DataUri.parseMetaData(resource); - const mime = metadata.get(DataUri.META_DATA_MIME); - if (!mime) { - return false; - } - return glob.match(selector.mime, mime.toLowerCase()); - } - if (selector.filenamePattern) { if (glob.match(selector.filenamePattern.toLowerCase(), basename(resource).toLowerCase())) { return true; diff --git a/src/vs/workbench/contrib/customEditor/browser/extensionPoint.ts b/src/vs/workbench/contrib/customEditor/browser/extensionPoint.ts index 446876b942a3..6f23a3bede4a 100644 --- a/src/vs/workbench/contrib/customEditor/browser/extensionPoint.ts +++ b/src/vs/workbench/contrib/customEditor/browser/extensionPoint.ts @@ -53,10 +53,6 @@ const webviewEditorsContribution: IJSONSchema = { type: 'string', description: nls.localize('contributes.selector.filenamePattern', 'Glob that the custom editor is enabled for.'), }, - mime: { - type: 'string', - description: nls.localize('contributes.selector.mime', 'Glob that matches the mime type of a data uri resource.'), - } } } }, diff --git a/src/vs/workbench/contrib/customEditor/browser/webviewEditor.contribution.ts b/src/vs/workbench/contrib/customEditor/browser/webviewEditor.contribution.ts index d6fd28dc764d..be9c891c72a6 100644 --- a/src/vs/workbench/contrib/customEditor/browser/webviewEditor.contribution.ts +++ b/src/vs/workbench/contrib/customEditor/browser/webviewEditor.contribution.ts @@ -10,9 +10,10 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; import { EditorDescriptor, Extensions as EditorExtensions, IEditorRegistry } from 'vs/workbench/browser/editor'; +import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration'; import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; import { Extensions as EditorInputExtensions, IEditorInputFactoryRegistry } from 'vs/workbench/common/editor'; -import { CustomEditoInputFactory } from 'vs/workbench/contrib/customEditor/browser/customEditorInputFactory'; +import { CustomEditorInputFactory } from 'vs/workbench/contrib/customEditor/browser/customEditorInputFactory'; import { ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; import { WebviewEditor } from 'vs/workbench/contrib/webview/browser/webviewEditor'; import './commands'; @@ -25,7 +26,7 @@ Registry.as(WorkbenchExtensions.Workbench) .registerWorkbenchContribution(CustomEditorContribution, LifecyclePhase.Starting); Registry.as(EditorExtensions.Editors).registerEditor( - new EditorDescriptor( + EditorDescriptor.create( WebviewEditor, WebviewEditor.ID, 'Webview Editor', @@ -34,15 +35,12 @@ Registry.as(EditorExtensions.Editors).registerEditor( ]); Registry.as(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory( - CustomEditoInputFactory.ID, - CustomEditoInputFactory); + CustomEditorInputFactory.ID, + CustomEditorInputFactory); Registry.as(ConfigurationExtensions.Configuration) .registerConfiguration({ - 'id': 'workbench', - 'order': 7, - 'title': nls.localize('workbenchConfigurationTitle', "Workbench"), - 'type': 'object', + ...workbenchConfigurationNodeBase, 'properties': { [customEditorsAssociationsKey]: { type: 'array', @@ -60,7 +58,7 @@ Registry.as(ConfigurationExtensions.Configuration) }, 'filenamePattern': { type: 'string', - description: nls.localize('editor.editorAssociations.filenamePattern', "Glob pattern the the editor should be used for."), + description: nls.localize('editor.editorAssociations.filenamePattern', "Glob pattern the editor should be used for."), } } } diff --git a/src/vs/workbench/contrib/customEditor/common/customEditor.ts b/src/vs/workbench/contrib/customEditor/common/customEditor.ts index 402d01560ee2..39a9dfcd626a 100644 --- a/src/vs/workbench/contrib/customEditor/common/customEditor.ts +++ b/src/vs/workbench/contrib/customEditor/common/customEditor.ts @@ -3,20 +3,32 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { Event } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; +import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { EditorInput, IEditor } from 'vs/workbench/common/editor'; +import { EditorInput, IEditor, ISaveOptions, IRevertOptions } from 'vs/workbench/common/editor'; import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { IWorkingCopy } from 'vs/workbench/services/workingCopy/common/workingCopyService'; export const ICustomEditorService = createDecorator('customEditorService'); export const CONTEXT_HAS_CUSTOM_EDITORS = new RawContextKey('hasCustomEditors', false); +export const CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE = new RawContextKey('focusedCustomEditorIsEditable', false); + +export interface ICustomEditor { + readonly resource: URI; + readonly viewType: string; +} export interface ICustomEditorService { _serviceBrand: any; + readonly models: ICustomEditorModelManager; + + readonly activeCustomEditor: ICustomEditor | undefined; + getContributedCustomEditors(resource: URI): readonly CustomEditorInfo[]; getUserConfiguredCustomEditors(resource: URI): readonly CustomEditorInfo[]; @@ -26,6 +38,45 @@ export interface ICustomEditorService { promptOpenWith(resource: URI, options?: ITextEditorOptions, group?: IEditorGroup): Promise; } +export type CustomEditorEdit = { source?: any, data: any }; + +export interface ICustomEditorModelManager { + get(resource: URI, viewType: string): ICustomEditorModel | undefined; + + loadOrCreate(resource: URI, viewType: string): Promise; + + disposeModel(model: ICustomEditorModel): void; +} + +export interface CustomEditorSaveEvent { + readonly resource: URI; + readonly waitUntil: (until: Promise) => void; +} + +export interface CustomEditorSaveAsEvent { + readonly resource: URI; + readonly targetResource: URI; + readonly waitUntil: (until: Promise) => void; +} + +export interface ICustomEditorModel extends IWorkingCopy { + readonly onUndo: Event; + readonly onApplyEdit: Event; + readonly onWillSave: Event; + readonly onWillSaveAs: Event; + + readonly currentEdits: readonly CustomEditorEdit[]; + + undo(): void; + redo(): void; + revert(options?: IRevertOptions): Promise; + + save(options?: ISaveOptions): Promise; + saveAs(resource: URI, targetResource: URI, currentOptions?: ISaveOptions): Promise; + + makeEdit(edit: CustomEditorEdit): void; +} + export const enum CustomEditorPriority { default = 'default', builtin = 'builtin', @@ -34,7 +85,6 @@ export const enum CustomEditorPriority { export interface CustomEditorSelector { readonly filenamePattern?: string; - readonly mime?: string; } export interface CustomEditorInfo { diff --git a/src/vs/workbench/contrib/customEditor/common/customEditorModel.ts b/src/vs/workbench/contrib/customEditor/common/customEditorModel.ts new file mode 100644 index 000000000000..d4c2f2402c40 --- /dev/null +++ b/src/vs/workbench/contrib/customEditor/common/customEditorModel.ts @@ -0,0 +1,157 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Emitter, Event } from 'vs/base/common/event'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { URI } from 'vs/base/common/uri'; +import { ICustomEditorModel, CustomEditorEdit, CustomEditorSaveAsEvent, CustomEditorSaveEvent } from 'vs/workbench/contrib/customEditor/common/customEditor'; +import { WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { ISaveOptions, IRevertOptions } from 'vs/workbench/common/editor'; + +export class CustomEditorModel extends Disposable implements ICustomEditorModel { + + private _currentEditIndex: number = -1; + private _savePoint: number = -1; + private _edits: Array = []; + + constructor( + private readonly _resource: URI, + ) { + super(); + } + + //#region IWorkingCopy + + public get resource() { + return this._resource; + } + + public get capabilities(): WorkingCopyCapabilities { + return 0; + } + + public isDirty(): boolean { + return this._edits.length > 0 && this._savePoint !== this._currentEditIndex; + } + + protected readonly _onDidChangeDirty: Emitter = this._register(new Emitter()); + readonly onDidChangeDirty: Event = this._onDidChangeDirty.event; + + //#endregion + + protected readonly _onUndo = this._register(new Emitter()); + readonly onUndo = this._onUndo.event; + + protected readonly _onApplyEdit = this._register(new Emitter()); + readonly onApplyEdit = this._onApplyEdit.event; + + protected readonly _onWillSave = this._register(new Emitter()); + readonly onWillSave = this._onWillSave.event; + + protected readonly _onWillSaveAs = this._register(new Emitter()); + readonly onWillSaveAs = this._onWillSaveAs.event; + + get currentEdits(): readonly CustomEditorEdit[] { + return this._edits.slice(0, Math.max(0, this._currentEditIndex + 1)); + } + + public makeEdit(edit: CustomEditorEdit): void { + this._edits.splice(this._currentEditIndex + 1, this._edits.length - this._currentEditIndex, edit.data); + this._currentEditIndex = this._edits.length - 1; + this.updateDirty(); + this._onApplyEdit.fire([edit]); + } + + private updateDirty() { + this._onDidChangeDirty.fire(); + } + + public async save(_options?: ISaveOptions): Promise { + const untils: Promise[] = []; + const handler: CustomEditorSaveEvent = { + resource: this._resource, + waitUntil: (until: Promise) => untils.push(until) + }; + + try { + this._onWillSave.fire(handler); + await Promise.all(untils); + } catch { + return false; + } + + this._savePoint = this._currentEditIndex; + this.updateDirty(); + + return true; + } + + public async saveAs(resource: URI, targetResource: URI, _options?: ISaveOptions): Promise { + const untils: Promise[] = []; + const handler: CustomEditorSaveAsEvent = { + resource, + targetResource, + waitUntil: (until: Promise) => untils.push(until) + }; + + try { + this._onWillSaveAs.fire(handler); + await Promise.all(untils); + } catch { + return false; + } + + this._savePoint = this._currentEditIndex; + this.updateDirty(); + + return true; + } + + public async revert(_options?: IRevertOptions) { + if (this._currentEditIndex === this._savePoint) { + return true; + } + + if (this._currentEditIndex >= this._savePoint) { + const editsToUndo = this._edits.slice(this._savePoint, this._currentEditIndex); + this._onUndo.fire(editsToUndo.reverse()); + } else if (this._currentEditIndex < this._savePoint) { + const editsToRedo = this._edits.slice(this._currentEditIndex, this._savePoint); + this._onApplyEdit.fire(editsToRedo); + } + + this._currentEditIndex = this._savePoint; + this._edits.splice(this._currentEditIndex + 1, this._edits.length - this._currentEditIndex); + this.updateDirty(); + return true; + } + + public undo() { + if (this._currentEditIndex < 0) { + // nothing to undo + return; + } + + const undoneEdit = this._edits[this._currentEditIndex]; + --this._currentEditIndex; + this._onUndo.fire([{ data: undoneEdit }]); + + this.updateDirty(); + } + + public redo() { + if (this._currentEditIndex >= this._edits.length - 1) { + // nothing to redo + return; + } + + ++this._currentEditIndex; + const redoneEdit = this._edits[this._currentEditIndex]; + + this._onApplyEdit.fire([{ data: redoneEdit }]); + + this.updateDirty(); + } +} diff --git a/src/vs/workbench/contrib/customEditor/common/customEditorModelManager.ts b/src/vs/workbench/contrib/customEditor/common/customEditorModelManager.ts new file mode 100644 index 000000000000..eb9630f8c33b --- /dev/null +++ b/src/vs/workbench/contrib/customEditor/common/customEditorModelManager.ts @@ -0,0 +1,54 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { URI } from 'vs/base/common/uri'; +import { ICustomEditorModel, ICustomEditorModelManager } from 'vs/workbench/contrib/customEditor/common/customEditor'; +import { CustomEditorModel } from 'vs/workbench/contrib/customEditor/common/customEditorModel'; +import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; + +export class CustomEditorModelManager implements ICustomEditorModelManager { + private readonly _models = new Map(); + + constructor( + @IWorkingCopyService private readonly _workingCopyService: IWorkingCopyService, + ) { } + + + public get(resource: URI, viewType: string): ICustomEditorModel | undefined { + return this._models.get(this.key(resource, viewType))?.model; + } + + public async loadOrCreate(resource: URI, viewType: string): Promise { + const existing = this.get(resource, viewType); + if (existing) { + return existing; + } + + const model = new CustomEditorModel(resource); + const disposables = new DisposableStore(); + this._workingCopyService.registerWorkingCopy(model); + this._models.set(this.key(resource, viewType), { model, disposables }); + return model; + } + + public disposeModel(model: ICustomEditorModel): void { + let foundKey: string | undefined; + this._models.forEach((value, key) => { + if (model === value.model) { + value.disposables.dispose(); + foundKey = key; + } + }); + if (typeof foundKey === 'string') { + this._models.delete(foundKey); + } + return; + } + + private key(resource: URI, viewType: string): string { + return `${resource.toString()}@@@${viewType}`; + } +} diff --git a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts index e8c2dc800560..b83c3d11676b 100644 --- a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts +++ b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts @@ -17,6 +17,7 @@ import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { HighlightedLabel, IHighlight } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; import { FuzzyScore, createMatches } from 'vs/base/common/filters'; import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector'; +import { ReplEvaluationResult } from 'vs/workbench/contrib/debug/common/replModel'; export const MAX_VALUE_RENDER_LENGTH_IN_VIEWLET = 1024; export const twistiePixels = 20; @@ -58,7 +59,7 @@ export function renderExpressionValue(expressionOrValue: IExpressionContainer | // remove stale classes container.className = 'value'; // when resolving expressions we represent errors from the server as a variable with name === null. - if (value === null || ((expressionOrValue instanceof Expression || expressionOrValue instanceof Variable) && !expressionOrValue.available)) { + if (value === null || ((expressionOrValue instanceof Expression || expressionOrValue instanceof Variable || expressionOrValue instanceof ReplEvaluationResult) && !expressionOrValue.available)) { dom.addClass(container, 'unavailable'); if (value !== Expression.DEFAULT_VALUE) { dom.addClass(container, 'error'); diff --git a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts index ad2eb8d43de6..55a9eccb58a3 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts @@ -32,6 +32,8 @@ import { distinct } from 'vs/base/common/arrays'; import { RunOnceScheduler } from 'vs/base/common/async'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { BrowserFeatures } from 'vs/base/browser/canIUse'; +import { isSafari } from 'vs/base/browser/browser'; const $ = dom.$; @@ -43,7 +45,7 @@ interface IBreakpointDecoration { } const breakpointHelperDecoration: IModelDecorationOptions = { - glyphMarginClassName: 'debug-breakpoint-hint', + glyphMarginClassName: 'codicon-debug-hint', stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges }; @@ -91,7 +93,7 @@ function getBreakpointDecorationOptions(model: ITextModel, breakpoint: IBreakpoi } return { - glyphMarginClassName: className, + glyphMarginClassName: `${className}`, glyphMarginHoverMessage, stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, beforeContentClassName: breakpoint.column ? `debug-breakpoint-placeholder` : undefined, @@ -105,25 +107,29 @@ async function createCandidateDecorations(model: ITextModel, breakpointDecoratio const session = debugService.getViewModel().focusedSession; if (session && session.capabilities.supportsBreakpointLocationsRequest) { await Promise.all(lineNumbers.map(async lineNumber => { - const positions = await session.breakpointsLocations(model.uri, lineNumber); - if (positions.length > 1) { - // Do not render candidates if there is only one, since it is already covered by the line breakpoint - positions.forEach(p => { - const range = new Range(p.lineNumber, p.column, p.lineNumber, p.column + 1); - const breakpointAtPosition = breakpointDecorations.filter(bpd => bpd.range.equalsRange(range)).pop(); - if (breakpointAtPosition && breakpointAtPosition.inlineWidget) { - // Space already occupied, do not render candidate. - return; - } - result.push({ - range, - options: { - stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, - beforeContentClassName: `debug-breakpoint-placeholder` - }, - breakpoint: breakpointAtPosition ? breakpointAtPosition.breakpoint : undefined + try { + const positions = await session.breakpointsLocations(model.uri, lineNumber); + if (positions.length > 1) { + // Do not render candidates if there is only one, since it is already covered by the line breakpoint + positions.forEach(p => { + const range = new Range(p.lineNumber, p.column, p.lineNumber, p.column + 1); + const breakpointAtPosition = breakpointDecorations.filter(bpd => bpd.range.equalsRange(range)).pop(); + if (breakpointAtPosition && breakpointAtPosition.inlineWidget) { + // Space already occupied, do not render candidate. + return; + } + result.push({ + range, + options: { + stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, + beforeContentClassName: `debug-breakpoint-placeholder` + }, + breakpoint: breakpointAtPosition ? breakpointAtPosition.breakpoint : undefined + }); }); - }); + } + } catch (e) { + // If there is an error when fetching breakpoint locations just do not render them } })); } @@ -131,7 +137,6 @@ async function createCandidateDecorations(model: ITextModel, breakpointDecoratio return result; } - class BreakpointEditorContribution implements IBreakpointEditorContribution { private breakpointHintDecoration: string[] = []; @@ -227,34 +232,48 @@ class BreakpointEditorContribution implements IBreakpointEditorContribution { } })); - this.toDispose.push(this.editor.onMouseMove((e: IEditorMouseEvent) => { - let showBreakpointHintAtLineNumber = -1; - const model = this.editor.getModel(); - if (model && e.target.position && (e.target.type === MouseTargetType.GUTTER_GLYPH_MARGIN || e.target.type === MouseTargetType.GUTTER_LINE_NUMBERS) && this.debugService.getConfigurationManager().canSetBreakpointsIn(model) && - this.marginFreeFromNonDebugDecorations(e.target.position.lineNumber)) { - const data = e.target.detail as IMarginData; - if (!data.isAfterLines) { - showBreakpointHintAtLineNumber = e.target.position.lineNumber; + if (!(BrowserFeatures.pointerEvents && isSafari)) { + /** + * We disable the hover feature for Safari on iOS as + * 1. Browser hover events are handled specially by the system (it treats first click as hover if there is `:hover` css registered). Below hover behavior will confuse users with inconsistent expeirence. + * 2. When users click on line numbers, the breakpoint hint displays immediately, however it doesn't create the breakpoint unless users click on the left gutter. On a touch screen, it's hard to click on that small area. + */ + this.toDispose.push(this.editor.onMouseMove((e: IEditorMouseEvent) => { + let showBreakpointHintAtLineNumber = -1; + const model = this.editor.getModel(); + if (model && e.target.position && (e.target.type === MouseTargetType.GUTTER_GLYPH_MARGIN || e.target.type === MouseTargetType.GUTTER_LINE_NUMBERS) && this.debugService.getConfigurationManager().canSetBreakpointsIn(model) && + this.marginFreeFromNonDebugDecorations(e.target.position.lineNumber)) { + const data = e.target.detail as IMarginData; + if (!data.isAfterLines) { + showBreakpointHintAtLineNumber = e.target.position.lineNumber; + } } - } - this.ensureBreakpointHintDecoration(showBreakpointHintAtLineNumber); - })); - this.toDispose.push(this.editor.onMouseLeave((e: IEditorMouseEvent) => { - this.ensureBreakpointHintDecoration(-1); - })); + this.ensureBreakpointHintDecoration(showBreakpointHintAtLineNumber); + })); + this.toDispose.push(this.editor.onMouseLeave(() => { + this.ensureBreakpointHintDecoration(-1); + })); + } + this.toDispose.push(this.editor.onDidChangeModel(async () => { this.closeBreakpointWidget(); await this.setDecorations(); })); - this.toDispose.push(this.debugService.getModel().onDidChangeBreakpoints(async () => { + this.toDispose.push(this.debugService.getModel().onDidChangeBreakpoints(() => { if (!this.ignoreBreakpointsChangeEvent && !this.setDecorationsScheduler.isScheduled()) { this.setDecorationsScheduler.schedule(); } })); + this.toDispose.push(this.debugService.onDidChangeState(() => { + // We need to update breakpoint decorations when state changes since the top stack frame and breakpoint decoration might change + if (!this.setDecorationsScheduler.isScheduled()) { + this.setDecorationsScheduler.schedule(); + } + })); this.toDispose.push(this.editor.onDidChangeModelDecorations(() => this.onModelDecorationsChanged())); this.toDispose.push(this.configurationService.onDidChangeConfiguration(async (e) => { - if (e.affectsConfiguration('debug.showBreakpointsInOverviewRuler')) { + if (e.affectsConfiguration('debug.showBreakpointsInOverviewRuler') || e.affectsConfiguration('debug.showInlineBreakpointCandidates')) { await this.setDecorations(); } })); @@ -338,7 +357,7 @@ class BreakpointEditorContribution implements IBreakpointEditorContribution { const decorations = this.editor.getLineDecorations(line); if (decorations) { for (const { options } of decorations) { - if (options.glyphMarginClassName && options.glyphMarginClassName.indexOf('debug') === -1) { + if (options.glyphMarginClassName && options.glyphMarginClassName.indexOf('codicon-') === -1) { return false; } } @@ -406,7 +425,7 @@ class BreakpointEditorContribution implements IBreakpointEditorContribution { } // Set breakpoint candidate decorations - const desiredCandidateDecorations = await createCandidateDecorations(this.editor.getModel(), this.breakpointDecorations, this.debugService); + const desiredCandidateDecorations = debugSettings.showInlineBreakpointCandidates ? await createCandidateDecorations(this.editor.getModel(), this.breakpointDecorations, this.debugService) : []; const candidateDecorationIds = this.editor.deltaDecorations(this.candidateDecorations.map(c => c.decorationId), desiredCandidateDecorations); this.candidateDecorations.forEach(candidate => { candidate.inlineWidget.dispose(); @@ -416,7 +435,7 @@ class BreakpointEditorContribution implements IBreakpointEditorContribution { // Candidate decoration has a breakpoint attached when a breakpoint is already at that location and we did not yet set a decoration there // In practice this happens for the first breakpoint that was set on a line // We could have also rendered this first decoration as part of desiredBreakpointDecorations however at that moment we have no location information - const cssClass = candidate.breakpoint ? getBreakpointMessageAndClassName(this.debugService, candidate.breakpoint).className : 'debug-breakpoint-disabled'; + const cssClass = candidate.breakpoint ? getBreakpointMessageAndClassName(this.debugService, candidate.breakpoint).className : 'codicon-debug-breakpoint-disabled'; const contextMenuActions = () => this.getContextMenuActions(candidate.breakpoint ? [candidate.breakpoint] : [], activeCodeEditor.getModel().uri, candidate.range.startLineNumber, candidate.range.startColumn); const inlineWidget = new InlineBreakpointWidget(activeCodeEditor, decorationId, cssClass, candidate.breakpoint, this.debugService, this.contextMenuService, contextMenuActions); @@ -537,6 +556,7 @@ class InlineBreakpointWidget implements IContentWidget, IDisposable { private create(cssClass: string | null | undefined): void { this.domNode = $('.inline-breakpoint-widget'); + this.domNode.classList.add('codicon'); if (cssClass) { this.domNode.classList.add(cssClass); } diff --git a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts index ed7f67dab2aa..b55a4134c0af 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts @@ -35,6 +35,8 @@ import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegis import { getSimpleEditorOptions, getSimpleCodeEditorWidgetOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions'; import { IRange, Range } from 'vs/editor/common/core/range'; import { onUnexpectedError } from 'vs/base/common/errors'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IEditorOptions, EditorOption } from 'vs/editor/common/config/editorOptions'; const $ = dom.$; const IPrivateBreakpointWidgetService = createDecorator('privateBreakpointWidgetService'); @@ -48,6 +50,7 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi _serviceBrand: undefined; private selectContainer!: HTMLElement; + private inputContainer!: HTMLElement; private input!: IActiveCodeEditor; private toDispose: lifecycle.IDisposable[]; private conditionInput = ''; @@ -55,6 +58,7 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi private logMessageInput = ''; private breakpoint: IBreakpoint | undefined; private context: Context; + private heightInPx: number | undefined; constructor(editor: ICodeEditor, private lineNumber: number, private column: number | undefined, context: Context | undefined, @IContextViewService private readonly contextViewService: IContextViewService, @@ -64,6 +68,7 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi @IInstantiationService private readonly instantiationService: IInstantiationService, @IModelService private readonly modelService: IModelService, @ICodeEditorService private readonly codeEditorService: ICodeEditorService, + @IConfigurationService private readonly _configurationService: IConfigurationService ) { super(editor, { showFrame: true, showArrow: false, frameWidth: 1 }); @@ -158,7 +163,8 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi this.input.focus(); }); - this.createBreakpointInput(dom.append(container, $('.inputContainer'))); + this.inputContainer = $('.inputContainer'); + this.createBreakpointInput(dom.append(container, this.inputContainer)); this.input.getModel().setValue(this.getInputValue(this.breakpoint)); this.toDispose.push(this.input.getModel().onDidChangeContent(() => { @@ -170,7 +176,9 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi } protected _doLayout(heightInPixel: number, widthInPixel: number): void { + this.heightInPx = heightInPixel; this.input.layout({ height: heightInPixel, width: widthInPixel - 113 }); + this.centerInputVertically(); } private createBreakpointInput(container: HTMLElement): void { @@ -180,7 +188,7 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi const scopedInstatiationService = this.instantiationService.createChild(new ServiceCollection( [IContextKeyService, scopedContextKeyService], [IPrivateBreakpointWidgetService, this])); - const options = getSimpleEditorOptions(); + const options = this.createEditorOptions(); const codeEditorWidgetOptions = getSimpleCodeEditorWidgetOptions(); this.input = scopedInstatiationService.createInstance(CodeEditorWidget, container, options, codeEditorWidgetOptions); CONTEXT_IN_BREAKPOINT_WIDGET.bindTo(scopedContextKeyService).set(true); @@ -227,6 +235,29 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi return suggestionsPromise; } })); + + this.toDispose.push(this._configurationService.onDidChangeConfiguration((e) => { + if (e.affectsConfiguration('editor.fontSize') || e.affectsConfiguration('editor.lineHeight')) { + this.input.updateOptions(this.createEditorOptions()); + this.centerInputVertically(); + } + })); + } + + private createEditorOptions(): IEditorOptions { + const editorConfig = this._configurationService.getValue('editor'); + const options = getSimpleEditorOptions(); + options.fontSize = editorConfig.fontSize; + return options; + } + + private centerInputVertically() { + if (this.container && typeof this.heightInPx === 'number') { + const lineHeight = this.input.getOption(EditorOption.lineHeight); + const lineNum = this.input.getModel().getLineCount(); + const newTopMargin = (this.heightInPx - lineNum * lineHeight) / 2; + this.inputContainer.style.marginTop = newTopMargin + 'px'; + } } private createDecorations(): IDecorationOptions[] { diff --git a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts index dd408218ee5b..8deb08fcaf97 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts @@ -28,9 +28,11 @@ import { attachInputBoxStyler } from 'vs/platform/theme/common/styler'; import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; -import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet'; +import { ViewletPane, IViewletPaneOptions } from 'vs/workbench/browser/parts/views/paneViewlet'; import { ILabelService } from 'vs/platform/label/common/label'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; +import { Gesture } from 'vs/base/browser/touch'; const $ = dom.$; @@ -38,11 +40,12 @@ function createCheckbox(): HTMLInputElement { const checkbox = $('input'); checkbox.type = 'checkbox'; checkbox.tabIndex = -1; + Gesture.ignoreTarget(checkbox); return checkbox; } -export class BreakpointsView extends ViewletPanel { +export class BreakpointsView extends ViewletPane { private static readonly MAX_VISIBLE_FILES = 9; private list!: WorkbenchList; @@ -60,7 +63,7 @@ export class BreakpointsView extends ViewletPanel { @IConfigurationService configurationService: IConfigurationService, @IContextKeyService contextKeyService: IContextKeyService, ) { - super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: nls.localize('breakpointsSection', "Breakpoints Section") }, keybindingService, contextMenuService, configurationService, contextKeyService); + super({ ...(options as IViewletPaneOptions), ariaHeaderLabel: nls.localize('breakpointsSection', "Breakpoints Section") }, keybindingService, contextMenuService, configurationService, contextKeyService); this.minimumBodySize = this.maximumBodySize = this.getExpandedBodySize(); this._register(this.debugService.getModel().onDidChangeBreakpoints(() => this.onBreakpointsChange())); @@ -85,6 +88,9 @@ export class BreakpointsView extends ViewletPanel { getPosInSet: (_: IEnablement, index: number) => index, getRole: (breakpoint: IEnablement) => 'checkbox', isChecked: (breakpoint: IEnablement) => breakpoint.enabled + }, + overrideStyles: { + listBackground: SIDE_BAR_BACKGROUND } }); @@ -346,7 +352,7 @@ class BreakpointsRenderer implements IListRenderer>(WorkbenchAsyncDataTree, 'CallStackView', treeContainer, new CallStackDelegate(), [ new SessionsRenderer(this.instantiationService), new ThreadsRenderer(this.instantiationService), this.instantiationService.createInstance(StackFramesRenderer), @@ -162,10 +175,13 @@ export class CallStackView extends ViewletPanel { return nls.localize('showMoreStackFrames2', "Show More Stack Frames"); } }, - expandOnlyOnTwistieClick: true + expandOnlyOnTwistieClick: true, + overrideStyles: { + listBackground: SIDE_BAR_BACKGROUND + } }); - this.tree.setInput(this.debugService.getModel()).then(undefined, onUnexpectedError); + this.tree.setInput(this.debugService.getModel()); const callstackNavigator = new TreeResourceNavigator2(this.tree); this._register(callstackNavigator); @@ -326,15 +342,15 @@ export class CallStackView extends ViewletPanel { this.contextMenuService.showContextMenu({ getAnchor: () => e.anchor, getActions: () => actions, - getActionsContext: () => element && element instanceof StackFrame ? element.getId() : undefined, + getActionsContext: () => getContext(element), onHide: () => dispose(actionsDisposable) }); } - private getContextForContributedActions(element: CallStackItem | null): string | number | undefined { + private getContextForContributedActions(element: CallStackItem | null): string | number { if (element instanceof StackFrame) { if (element.source.inMemory) { - return element.source.raw.path || element.source.reference; + return element.source.raw.path || element.source.reference || ''; } return element.source.uri.toString(); @@ -346,7 +362,7 @@ export class CallStackView extends ViewletPanel { return element.getId(); } - return undefined; + return ''; } } @@ -382,6 +398,7 @@ interface IStackFrameTemplateData { fileName: HTMLElement; lineNumber: HTMLElement; label: HighlightedLabel; + actionBar: ActionBar; } class SessionsRenderer implements ITreeRenderer { @@ -478,8 +495,9 @@ class StackFramesRenderer implements ITreeRenderer, index: number, data: IStackFrameTemplateData): void { @@ -487,6 +505,8 @@ class StackFramesRenderer implements ITreeRenderer { + return stackFrame.restart(); + }); + data.actionBar.push(action, { icon: true, label: false }); + } } disposeTemplate(templateData: IStackFrameTemplateData): void { - // noop + templateData.actionBar.dispose(); } } @@ -644,7 +672,7 @@ class CallStackDataSource implements IAsyncDataSource 1) { + if (sessions.length > 1 || this.debugService.getViewModel().isMultiSessionView()) { return Promise.resolve(sessions.filter(s => !s.parentSession)); } @@ -786,11 +814,11 @@ class StopAction extends Action { private readonly session: IDebugSession, @ICommandService private readonly commandService: ICommandService ) { - super(`action.${STOP_ID}`, STOP_LABEL, 'debug-action stop'); + super(`action.${STOP_ID}`, STOP_LABEL, 'debug-action codicon-debug-stop'); } public run(): Promise { - return this.commandService.executeCommand(STOP_ID, this.session.getId(), this.session); + return this.commandService.executeCommand(STOP_ID, getContext(this.session)); } } @@ -800,11 +828,11 @@ class DisconnectAction extends Action { private readonly session: IDebugSession, @ICommandService private readonly commandService: ICommandService ) { - super(`action.${DISCONNECT_ID}`, DISCONNECT_LABEL, 'debug-action disconnect'); + super(`action.${DISCONNECT_ID}`, DISCONNECT_LABEL, 'debug-action codicon-debug-disconnect'); } public run(): Promise { - return this.commandService.executeCommand(DISCONNECT_ID, this.session.getId(), this.session); + return this.commandService.executeCommand(DISCONNECT_ID, getContext(this.session)); } } @@ -814,11 +842,11 @@ class RestartAction extends Action { private readonly session: IDebugSession, @ICommandService private readonly commandService: ICommandService ) { - super(`action.${RESTART_SESSION_ID}`, RESTART_LABEL, 'debug-action restart'); + super(`action.${RESTART_SESSION_ID}`, RESTART_LABEL, 'debug-action codicon-debug-restart'); } public run(): Promise { - return this.commandService.executeCommand(RESTART_SESSION_ID, this.session.getId(), this.session); + return this.commandService.executeCommand(RESTART_SESSION_ID, getContext(this.session)); } } @@ -828,11 +856,11 @@ class StepOverAction extends Action { private readonly thread: IThread, @ICommandService private readonly commandService: ICommandService ) { - super(`action.${STEP_OVER_ID}`, STEP_OVER_LABEL, 'debug-action step-over', thread.stopped); + super(`action.${STEP_OVER_ID}`, STEP_OVER_LABEL, 'debug-action codicon-debug-step-over', thread.stopped); } public run(): Promise { - return this.commandService.executeCommand(STEP_OVER_ID, this.thread.threadId, this.thread); + return this.commandService.executeCommand(STEP_OVER_ID, getContext(this.thread)); } } @@ -842,11 +870,11 @@ class StepIntoAction extends Action { private readonly thread: IThread, @ICommandService private readonly commandService: ICommandService ) { - super(`action.${STEP_INTO_ID}`, STEP_INTO_LABEL, 'debug-action step-into', thread.stopped); + super(`action.${STEP_INTO_ID}`, STEP_INTO_LABEL, 'debug-action codicon-debug-step-into', thread.stopped); } public run(): Promise { - return this.commandService.executeCommand(STEP_INTO_ID, this.thread.threadId, this.thread); + return this.commandService.executeCommand(STEP_INTO_ID, getContext(this.thread)); } } @@ -856,11 +884,11 @@ class StepOutAction extends Action { private readonly thread: IThread, @ICommandService private readonly commandService: ICommandService ) { - super(`action.${STEP_OUT_ID}`, STEP_OUT_LABEL, 'debug-action step-out', thread.stopped); + super(`action.${STEP_OUT_ID}`, STEP_OUT_LABEL, 'debug-action codicon-debug-step-out', thread.stopped); } public run(): Promise { - return this.commandService.executeCommand(STEP_OUT_ID, this.thread.threadId, this.thread); + return this.commandService.executeCommand(STEP_OUT_ID, getContext(this.thread)); } } @@ -870,11 +898,11 @@ class PauseAction extends Action { private readonly thread: IThread, @ICommandService private readonly commandService: ICommandService ) { - super(`action.${PAUSE_ID}`, PAUSE_LABEL, 'debug-action pause', !thread.stopped); + super(`action.${PAUSE_ID}`, PAUSE_LABEL, 'debug-action codicon-debug-pause', !thread.stopped); } public run(): Promise { - return this.commandService.executeCommand(PAUSE_ID, this.thread.threadId, this.thread); + return this.commandService.executeCommand(PAUSE_ID, getContext(this.thread)); } } @@ -884,10 +912,10 @@ class ContinueAction extends Action { private readonly thread: IThread, @ICommandService private readonly commandService: ICommandService ) { - super(`action.${CONTINUE_ID}`, CONTINUE_LABEL, 'debug-action continue', thread.stopped); + super(`action.${CONTINUE_ID}`, CONTINUE_LABEL, 'debug-action codicon-debug-continue', thread.stopped); } public run(): Promise { - return this.commandService.executeCommand(CONTINUE_ID, this.thread.threadId, this.thread); + return this.commandService.executeCommand(CONTINUE_ID, getContext(this.thread)); } } diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index ff9b3c05f3db..2248736905b0 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -20,7 +20,7 @@ import { CallStackView } from 'vs/workbench/contrib/debug/browser/callStackView' import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; import { IDebugService, VIEWLET_ID, REPL_ID, CONTEXT_IN_DEBUG_MODE, INTERNAL_CONSOLE_OPTIONS_SCHEMA, - CONTEXT_DEBUG_STATE, VARIABLES_VIEW_ID, CALLSTACK_VIEW_ID, WATCH_VIEW_ID, BREAKPOINTS_VIEW_ID, VIEW_CONTAINER, LOADED_SCRIPTS_VIEW_ID, CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_FOCUSED_SESSION_IS_ATTACH, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_CALLSTACK_ITEM_TYPE, CONTEXT_RESTART_FRAME_SUPPORTED, CONTEXT_JUMP_TO_CURSOR_SUPPORTED, + CONTEXT_DEBUG_STATE, VARIABLES_VIEW_ID, CALLSTACK_VIEW_ID, WATCH_VIEW_ID, BREAKPOINTS_VIEW_ID, VIEW_CONTAINER, LOADED_SCRIPTS_VIEW_ID, CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_FOCUSED_SESSION_IS_ATTACH, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_CALLSTACK_ITEM_TYPE, CONTEXT_RESTART_FRAME_SUPPORTED, CONTEXT_JUMP_TO_CURSOR_SUPPORTED, CONTEXT_DEBUG_UX, } from 'vs/workbench/contrib/debug/common/debug'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; @@ -47,8 +47,9 @@ import { WatchExpressionsView } from 'vs/workbench/contrib/debug/browser/watchEx import { VariablesView } from 'vs/workbench/contrib/debug/browser/variablesView'; import { ClearReplAction, Repl } from 'vs/workbench/contrib/debug/browser/repl'; import { DebugContentProvider } from 'vs/workbench/contrib/debug/common/debugContentProvider'; -import { registerAndGetAmdImageURL } from 'vs/base/common/amd'; import { DebugCallStackContribution } from 'vs/workbench/contrib/debug/browser/debugCallStackContribution'; +import { StartView } from 'vs/workbench/contrib/debug/browser/startView'; +import { ThemeIcon } from 'vs/platform/theme/common/themeService'; class OpenDebugViewletAction extends ShowViewletAction { public static readonly ID = VIEWLET_ID; @@ -80,13 +81,12 @@ class OpenDebugPanelAction extends TogglePanelAction { } // register viewlet -Registry.as(ViewletExtensions.Viewlets).registerViewlet(new ViewletDescriptor( +Registry.as(ViewletExtensions.Viewlets).registerViewlet(ViewletDescriptor.create( DebugViewlet, VIEWLET_ID, - nls.localize('debug', "Debug"), - 'debug', - // {{SQL CARBON EDIT}} - 13 + nls.localize('debugAndRun', "Debug And Run"), + 'codicon-debug', + 13 // {{SQL CARBON EDIT}} )); const openViewletKb: IKeybindings = { @@ -97,7 +97,7 @@ const openPanelKb: IKeybindings = { }; // register repl panel -Registry.as(PanelExtensions.Panels).registerPanel(new PanelDescriptor( +Registry.as(PanelExtensions.Panels).registerPanel(PanelDescriptor.create( Repl, REPL_ID, nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'debugPanel' }, 'Debug Console'), @@ -108,18 +108,19 @@ Registry.as(PanelExtensions.Panels).registerPanel(new PanelDescri // Register default debug views const viewsRegistry = Registry.as(ViewExtensions.ViewsRegistry); -viewsRegistry.registerViews([{ id: VARIABLES_VIEW_ID, name: nls.localize('variables', "Variables"), ctorDescriptor: { ctor: VariablesView }, order: 10, weight: 40, canToggleVisibility: true, focusCommand: { id: 'workbench.debug.action.focusVariablesView' } }], VIEW_CONTAINER); -viewsRegistry.registerViews([{ id: WATCH_VIEW_ID, name: nls.localize('watch', "Watch"), ctorDescriptor: { ctor: WatchExpressionsView }, order: 20, weight: 10, canToggleVisibility: true, focusCommand: { id: 'workbench.debug.action.focusWatchView' } }], VIEW_CONTAINER); -viewsRegistry.registerViews([{ id: CALLSTACK_VIEW_ID, name: nls.localize('callStack', "Call Stack"), ctorDescriptor: { ctor: CallStackView }, order: 30, weight: 30, canToggleVisibility: true, focusCommand: { id: 'workbench.debug.action.focusCallStackView' } }], VIEW_CONTAINER); -viewsRegistry.registerViews([{ id: BREAKPOINTS_VIEW_ID, name: nls.localize('breakpoints', "Breakpoints"), ctorDescriptor: { ctor: BreakpointsView }, order: 40, weight: 20, canToggleVisibility: true, focusCommand: { id: 'workbench.debug.action.focusBreakpointsView' } }], VIEW_CONTAINER); -viewsRegistry.registerViews([{ id: LOADED_SCRIPTS_VIEW_ID, name: nls.localize('loadedScripts', "Loaded Scripts"), ctorDescriptor: { ctor: LoadedScriptsView }, order: 35, weight: 5, canToggleVisibility: true, collapsed: true, when: CONTEXT_LOADED_SCRIPTS_SUPPORTED }], VIEW_CONTAINER); +viewsRegistry.registerViews([{ id: VARIABLES_VIEW_ID, name: nls.localize('variables', "Variables"), ctorDescriptor: { ctor: VariablesView }, order: 10, weight: 40, canToggleVisibility: true, focusCommand: { id: 'workbench.debug.action.focusVariablesView' }, when: CONTEXT_DEBUG_UX.isEqualTo('default') }], VIEW_CONTAINER); +viewsRegistry.registerViews([{ id: WATCH_VIEW_ID, name: nls.localize('watch', "Watch"), ctorDescriptor: { ctor: WatchExpressionsView }, order: 20, weight: 10, canToggleVisibility: true, focusCommand: { id: 'workbench.debug.action.focusWatchView' }, when: CONTEXT_DEBUG_UX.isEqualTo('default') }], VIEW_CONTAINER); +viewsRegistry.registerViews([{ id: CALLSTACK_VIEW_ID, name: nls.localize('callStack', "Call Stack"), ctorDescriptor: { ctor: CallStackView }, order: 30, weight: 30, canToggleVisibility: true, focusCommand: { id: 'workbench.debug.action.focusCallStackView' }, when: CONTEXT_DEBUG_UX.isEqualTo('default') }], VIEW_CONTAINER); +viewsRegistry.registerViews([{ id: BREAKPOINTS_VIEW_ID, name: nls.localize('breakpoints', "Breakpoints"), ctorDescriptor: { ctor: BreakpointsView }, order: 40, weight: 20, canToggleVisibility: true, focusCommand: { id: 'workbench.debug.action.focusBreakpointsView' }, when: CONTEXT_DEBUG_UX.isEqualTo('default') }], VIEW_CONTAINER); +viewsRegistry.registerViews([{ id: StartView.ID, name: StartView.LABEL, ctorDescriptor: { ctor: StartView }, order: 10, weight: 40, canToggleVisibility: true, when: CONTEXT_DEBUG_UX.isEqualTo('simple') }], VIEW_CONTAINER); +viewsRegistry.registerViews([{ id: LOADED_SCRIPTS_VIEW_ID, name: nls.localize('loadedScripts', "Loaded Scripts"), ctorDescriptor: { ctor: LoadedScriptsView }, order: 35, weight: 5, canToggleVisibility: true, collapsed: true, when: ContextKeyExpr.and(CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_DEBUG_UX.isEqualTo('default')) }], VIEW_CONTAINER); registerCommands(); // register action to open viewlet const registry = Registry.as(WorkbenchActionRegistryExtensions.WorkbenchActions); -registry.registerWorkbenchAction(new SyncActionDescriptor(OpenDebugPanelAction, OpenDebugPanelAction.ID, OpenDebugPanelAction.LABEL, openPanelKb), 'View: Debug Console', nls.localize('view', "View")); -registry.registerWorkbenchAction(new SyncActionDescriptor(OpenDebugViewletAction, OpenDebugViewletAction.ID, OpenDebugViewletAction.LABEL, openViewletKb), 'View: Show Debug', nls.localize('view', "View")); +registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenDebugPanelAction, OpenDebugPanelAction.ID, OpenDebugPanelAction.LABEL, openPanelKb), 'View: Debug Console', nls.localize('view', "View")); +registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenDebugViewletAction, OpenDebugViewletAction.ID, OpenDebugViewletAction.LABEL, openViewletKb), 'View: Show Debug', nls.localize('view', "View")); Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(DebugCallStackContribution, LifecyclePhase.Restored); Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(DebugToolBar, LifecyclePhase.Restored); @@ -128,16 +129,16 @@ Registry.as(WorkbenchExtensions.Workbench).regi const debugCategory = nls.localize('debugCategory', "Debug"); -registry.registerWorkbenchAction(new SyncActionDescriptor(StartAction, StartAction.ID, StartAction.LABEL, { primary: KeyCode.F5 }, CONTEXT_IN_DEBUG_MODE.toNegated()), 'Debug: Start Debugging', debugCategory); -registry.registerWorkbenchAction(new SyncActionDescriptor(ConfigureAction, ConfigureAction.ID, ConfigureAction.LABEL), 'Debug: Open launch.json', debugCategory); -registry.registerWorkbenchAction(new SyncActionDescriptor(AddFunctionBreakpointAction, AddFunctionBreakpointAction.ID, AddFunctionBreakpointAction.LABEL), 'Debug: Add Function Breakpoint', debugCategory); -registry.registerWorkbenchAction(new SyncActionDescriptor(ReapplyBreakpointsAction, ReapplyBreakpointsAction.ID, ReapplyBreakpointsAction.LABEL), 'Debug: Reapply All Breakpoints', debugCategory); -registry.registerWorkbenchAction(new SyncActionDescriptor(RunAction, RunAction.ID, RunAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.F5, mac: { primary: KeyMod.WinCtrl | KeyCode.F5 } }), 'Debug: Start Without Debugging', debugCategory); -registry.registerWorkbenchAction(new SyncActionDescriptor(RemoveAllBreakpointsAction, RemoveAllBreakpointsAction.ID, RemoveAllBreakpointsAction.LABEL), 'Debug: Remove All Breakpoints', debugCategory); -registry.registerWorkbenchAction(new SyncActionDescriptor(EnableAllBreakpointsAction, EnableAllBreakpointsAction.ID, EnableAllBreakpointsAction.LABEL), 'Debug: Enable All Breakpoints', debugCategory); -registry.registerWorkbenchAction(new SyncActionDescriptor(DisableAllBreakpointsAction, DisableAllBreakpointsAction.ID, DisableAllBreakpointsAction.LABEL), 'Debug: Disable All Breakpoints', debugCategory); -registry.registerWorkbenchAction(new SyncActionDescriptor(SelectAndStartAction, SelectAndStartAction.ID, SelectAndStartAction.LABEL), 'Debug: Select and Start Debugging', debugCategory); -registry.registerWorkbenchAction(new SyncActionDescriptor(ClearReplAction, ClearReplAction.ID, ClearReplAction.LABEL), 'Debug: Clear Console', debugCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(StartAction, StartAction.ID, StartAction.LABEL, { primary: KeyCode.F5 }, CONTEXT_IN_DEBUG_MODE.toNegated()), 'Debug: Start Debugging', debugCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ConfigureAction, ConfigureAction.ID, ConfigureAction.LABEL), 'Debug: Open launch.json', debugCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(AddFunctionBreakpointAction, AddFunctionBreakpointAction.ID, AddFunctionBreakpointAction.LABEL), 'Debug: Add Function Breakpoint', debugCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ReapplyBreakpointsAction, ReapplyBreakpointsAction.ID, ReapplyBreakpointsAction.LABEL), 'Debug: Reapply All Breakpoints', debugCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(RunAction, RunAction.ID, RunAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.F5, mac: { primary: KeyMod.WinCtrl | KeyCode.F5 } }), 'Debug: Start Without Debugging', debugCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(RemoveAllBreakpointsAction, RemoveAllBreakpointsAction.ID, RemoveAllBreakpointsAction.LABEL), 'Debug: Remove All Breakpoints', debugCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(EnableAllBreakpointsAction, EnableAllBreakpointsAction.ID, EnableAllBreakpointsAction.LABEL), 'Debug: Enable All Breakpoints', debugCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(DisableAllBreakpointsAction, DisableAllBreakpointsAction.ID, DisableAllBreakpointsAction.LABEL), 'Debug: Disable All Breakpoints', debugCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(SelectAndStartAction, SelectAndStartAction.ID, SelectAndStartAction.LABEL), 'Debug: Select and Start Debugging', debugCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ClearReplAction, ClearReplAction.ID, ClearReplAction.LABEL), 'Debug: Clear Console', debugCategory); const registerDebugCommandPaletteItem = (id: string, title: string, when?: ContextKeyExpr, precondition?: ContextKeyExpr) => { MenuRegistry.appendMenuItem(MenuId.CommandPalette, { @@ -167,7 +168,7 @@ registerDebugCommandPaletteItem(TOGGLE_INLINE_BREAKPOINT_ID, nls.localize('inlin // Register Quick Open (Registry.as(QuickOpenExtensions.Quickopen)).registerQuickOpenHandler( - new QuickOpenHandlerDescriptor( + QuickOpenHandlerDescriptor.create( DebugQuickOpenHandler, DebugQuickOpenHandler.ID, 'debug ', @@ -270,6 +271,11 @@ configurationRegistry.registerConfiguration({ type: 'boolean', description: nls.localize({ comment: ['This is the description for a setting'], key: 'showBreakpointsInOverviewRuler' }, "Controls whether breakpoints should be shown in the overview ruler."), default: false + }, + 'debug.showInlineBreakpointCandidates': { + type: 'boolean', + description: nls.localize({ comment: ['This is the description for a setting'], key: 'showInlineBreakpointCandidates' }, "Controls whether inline breakpoints candidate decorations should be shown in the editor while debugging."), + default: true } } }); @@ -279,7 +285,7 @@ Registry.as(WorkbenchExtensions.Workbench).regi // Debug toolbar -const registerDebugToolBarItem = (id: string, title: string, iconLightUri: URI, iconDarkUri: URI, order: number, when?: ContextKeyExpr, precondition?: ContextKeyExpr) => { +const registerDebugToolBarItem = (id: string, title: string, order: number, icon: { light?: URI, dark?: URI } | ThemeIcon, when?: ContextKeyExpr, precondition?: ContextKeyExpr) => { MenuRegistry.appendMenuItem(MenuId.DebugToolBar, { group: 'navigation', when, @@ -287,25 +293,22 @@ const registerDebugToolBarItem = (id: string, title: string, iconLightUri: URI, command: { id, title, - iconLocation: { - light: iconLightUri, - dark: iconDarkUri - }, + icon, precondition } }); }; -registerDebugToolBarItem(CONTINUE_ID, CONTINUE_LABEL, URI.parse(registerAndGetAmdImageURL('vs/workbench/contrib/debug/browser/media/continue-light.svg')), URI.parse(registerAndGetAmdImageURL('vs/workbench/contrib/debug/browser/media/continue-dark.svg')), 10, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); -registerDebugToolBarItem(PAUSE_ID, PAUSE_LABEL, URI.parse(registerAndGetAmdImageURL('vs/workbench/contrib/debug/browser/media/pause-light.svg')), URI.parse(registerAndGetAmdImageURL('vs/workbench/contrib/debug/browser/media/pause-dark.svg')), 10, CONTEXT_DEBUG_STATE.notEqualsTo('stopped')); -registerDebugToolBarItem(STOP_ID, STOP_LABEL, URI.parse(registerAndGetAmdImageURL('vs/workbench/contrib/debug/browser/media/stop-light.svg')), URI.parse(registerAndGetAmdImageURL('vs/workbench/contrib/debug/browser/media/stop-dark.svg')), 70, CONTEXT_FOCUSED_SESSION_IS_ATTACH.toNegated()); -registerDebugToolBarItem(DISCONNECT_ID, DISCONNECT_LABEL, URI.parse(registerAndGetAmdImageURL('vs/workbench/contrib/debug/browser/media/disconnect-light.svg')), URI.parse(registerAndGetAmdImageURL('vs/workbench/contrib/debug/browser/media/disconnect-dark.svg')), 70, CONTEXT_FOCUSED_SESSION_IS_ATTACH); -registerDebugToolBarItem(STEP_OVER_ID, STEP_OVER_LABEL, URI.parse(registerAndGetAmdImageURL('vs/workbench/contrib/debug/browser/media/step-over-light.svg')), URI.parse(registerAndGetAmdImageURL('vs/workbench/contrib/debug/browser/media/step-over-dark.svg')), 20, undefined, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); -registerDebugToolBarItem(STEP_INTO_ID, STEP_INTO_LABEL, URI.parse(registerAndGetAmdImageURL('vs/workbench/contrib/debug/browser/media/step-into-light.svg')), URI.parse(registerAndGetAmdImageURL('vs/workbench/contrib/debug/browser/media/step-into-dark.svg')), 30, undefined, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); -registerDebugToolBarItem(STEP_OUT_ID, STEP_OUT_LABEL, URI.parse(registerAndGetAmdImageURL('vs/workbench/contrib/debug/browser/media/step-out-light.svg')), URI.parse(registerAndGetAmdImageURL('vs/workbench/contrib/debug/browser/media/step-out-dark.svg')), 40, undefined, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); -registerDebugToolBarItem(RESTART_SESSION_ID, RESTART_LABEL, URI.parse(registerAndGetAmdImageURL('vs/workbench/contrib/debug/browser/media/restart-light.svg')), URI.parse(registerAndGetAmdImageURL('vs/workbench/contrib/debug/browser/media/restart-dark.svg')), 60); -registerDebugToolBarItem(STEP_BACK_ID, nls.localize('stepBackDebug', "Step Back"), URI.parse(registerAndGetAmdImageURL('vs/workbench/contrib/debug/browser/media/step-back-light.svg')), URI.parse(registerAndGetAmdImageURL('vs/workbench/contrib/debug/browser/media/step-back-dark.svg')), 50, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); -registerDebugToolBarItem(REVERSE_CONTINUE_ID, nls.localize('reverseContinue', "Reverse"), URI.parse(registerAndGetAmdImageURL('vs/workbench/contrib/debug/browser/media/reverse-continue-light.svg')), URI.parse(registerAndGetAmdImageURL('vs/workbench/contrib/debug/browser/media/reverse-continue-dark.svg')), 60, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); +registerDebugToolBarItem(CONTINUE_ID, CONTINUE_LABEL, 10, { id: 'codicon/debug-continue' }, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); +registerDebugToolBarItem(PAUSE_ID, PAUSE_LABEL, 10, { id: 'codicon/debug-pause' }, CONTEXT_DEBUG_STATE.notEqualsTo('stopped')); +registerDebugToolBarItem(STOP_ID, STOP_LABEL, 70, { id: 'codicon/debug-stop' }, CONTEXT_FOCUSED_SESSION_IS_ATTACH.toNegated()); +registerDebugToolBarItem(DISCONNECT_ID, DISCONNECT_LABEL, 70, { id: 'codicon/debug-disconnect' }, CONTEXT_FOCUSED_SESSION_IS_ATTACH); +registerDebugToolBarItem(STEP_OVER_ID, STEP_OVER_LABEL, 20, { id: 'codicon/debug-step-over' }, undefined, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); +registerDebugToolBarItem(STEP_INTO_ID, STEP_INTO_LABEL, 30, { id: 'codicon/debug-step-into' }, undefined, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); +registerDebugToolBarItem(STEP_OUT_ID, STEP_OUT_LABEL, 40, { id: 'codicon/debug-step-out' }, undefined, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); +registerDebugToolBarItem(RESTART_SESSION_ID, RESTART_LABEL, 60, { id: 'codicon/debug-restart' }); +registerDebugToolBarItem(STEP_BACK_ID, nls.localize('stepBackDebug', "Step Back"), 50, { id: 'codicon/debug-step-back' }, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); +registerDebugToolBarItem(REVERSE_CONTINUE_ID, nls.localize('reverseContinue', "Reverse"), 60, { id: 'codicon/debug-reverse-continue' }, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); // Debug callstack context menu const registerDebugCallstackItem = (id: string, title: string, order: number, when?: ContextKeyExpr, precondition?: ContextKeyExpr, group = 'navigation') => { @@ -370,7 +373,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, { group: '1_debug', command: { id: RunAction.ID, - title: nls.localize({ key: 'miStartWithoutDebugging', comment: ['&& denotes a mnemonic'] }, "Start &&Without Debugging") + title: nls.localize({ key: 'miRun', comment: ['&& denotes a mnemonic'] }, "Run &&Without Debugging") }, order: 2 }); @@ -554,7 +557,7 @@ if (isMacintosh) { command: { id, title, - iconLocation: { dark: iconUri } + icon: { dark: iconUri } }, when, group: '9_debug', diff --git a/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts b/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts index 61d3025175d9..15cf1e121876 100644 --- a/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts +++ b/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts @@ -69,7 +69,7 @@ export class StartDebugActionViewItem implements IActionViewItem { render(container: HTMLElement): void { this.container = container; dom.addClass(container, 'start-debug-action-item'); - this.start = dom.append(container, $('.icon')); + this.start = dom.append(container, $('.codicon.codicon-debug-start')); this.start.title = this.action.label; this.start.setAttribute('role', 'button'); this.start.tabIndex = 0; diff --git a/src/vs/workbench/contrib/debug/browser/debugActions.ts b/src/vs/workbench/contrib/debug/browser/debugActions.ts index 05fb2da0e61a..43b5db1b9cd9 100644 --- a/src/vs/workbench/contrib/debug/browser/debugActions.ts +++ b/src/vs/workbench/contrib/debug/browser/debugActions.ts @@ -62,7 +62,7 @@ export class ConfigureAction extends AbstractDebugAction { @INotificationService private readonly notificationService: INotificationService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService ) { - super(id, label, 'debug-action configure', debugService, keybindingService); + super(id, label, 'debug-action codicon codicon-gear', debugService, keybindingService); this._register(debugService.getConfigurationManager().onDidSelectConfiguration(() => this.updateClass())); this.updateClass(); } @@ -78,7 +78,7 @@ export class ConfigureAction extends AbstractDebugAction { private updateClass(): void { const configurationManager = this.debugService.getConfigurationManager(); const configurationCount = configurationManager.getLaunches().map(l => l.getConfigurationNames().length).reduce((sum, current) => sum + current); - this.class = configurationCount > 0 ? 'debug-action configure' : 'debug-action configure notification'; + this.class = configurationCount > 0 ? 'debug-action codicon codicon-gear' : 'debug-action codicon codicon-gear notification'; } async run(event?: any): Promise { @@ -143,7 +143,7 @@ export class StartAction extends AbstractDebugAction { export class RunAction extends StartAction { static readonly ID = 'workbench.action.debug.run'; - static LABEL = nls.localize('startWithoutDebugging', "Start Without Debugging"); + static LABEL = nls.localize('startWithoutDebugging', "Run (Start Without Debugging)"); protected isNoDebug(): boolean { return true; @@ -186,7 +186,7 @@ export class RemoveAllBreakpointsAction extends AbstractDebugAction { static readonly LABEL = nls.localize('removeAllBreakpoints', "Remove All Breakpoints"); constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { - super(id, label, 'debug-action remove-all', debugService, keybindingService); + super(id, label, 'debug-action codicon-close-all', debugService, keybindingService); this._register(this.debugService.getModel().onDidChangeBreakpoints(() => this.updateEnablement())); } @@ -244,7 +244,7 @@ export class ToggleBreakpointsActivatedAction extends AbstractDebugAction { static readonly DEACTIVATE_LABEL = nls.localize('deactivateBreakpoints', "Deactivate Breakpoints"); constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { - super(id, label, 'debug-action breakpoints-activate', debugService, keybindingService); + super(id, label, 'debug-action codicon-activate-breakpoints', debugService, keybindingService); this.updateLabel(this.debugService.getModel().areBreakpointsActivated() ? ToggleBreakpointsActivatedAction.DEACTIVATE_LABEL : ToggleBreakpointsActivatedAction.ACTIVATE_LABEL); this._register(this.debugService.getModel().onDidChangeBreakpoints(() => { @@ -287,7 +287,7 @@ export class AddFunctionBreakpointAction extends AbstractDebugAction { static readonly LABEL = nls.localize('addFunctionBreakpoint', "Add Function Breakpoint"); constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { - super(id, label, 'debug-action add-function-breakpoint', debugService, keybindingService); + super(id, label, 'debug-action codicon-add', debugService, keybindingService); this._register(this.debugService.getModel().onDidChangeBreakpoints(() => this.updateEnablement())); } @@ -306,7 +306,7 @@ export class AddWatchExpressionAction extends AbstractDebugAction { static readonly LABEL = nls.localize('addWatchExpression', "Add Expression"); constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { - super(id, label, 'debug-action add-watch-expression', debugService, keybindingService); + super(id, label, 'debug-action codicon-add', debugService, keybindingService); this._register(this.debugService.getModel().onDidChangeWatchExpressions(() => this.updateEnablement())); this._register(this.debugService.getViewModel().onDidSelectExpression(() => this.updateEnablement())); } @@ -326,7 +326,7 @@ export class RemoveAllWatchExpressionsAction extends AbstractDebugAction { static readonly LABEL = nls.localize('removeAllWatchExpressions', "Remove All Expressions"); constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { - super(id, label, 'debug-action remove-all', debugService, keybindingService); + super(id, label, 'debug-action codicon-close-all', debugService, keybindingService); this._register(this.debugService.getModel().onDidChangeWatchExpressions(() => this.updateEnablement())); } diff --git a/src/vs/workbench/contrib/debug/browser/debugCallStackContribution.ts b/src/vs/workbench/contrib/debug/browser/debugCallStackContribution.ts index c9d96da1c209..0126ee998dde 100644 --- a/src/vs/workbench/contrib/debug/browser/debugCallStackContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debugCallStackContribution.ts @@ -128,12 +128,12 @@ export class DebugCallStackContribution implements IWorkbenchContribution { static readonly STICKINESS = TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges; // we need a separate decoration for glyph margin, since we do not want it on each line of a multi line statement. private static TOP_STACK_FRAME_MARGIN: IModelDecorationOptions = { - glyphMarginClassName: 'debug-top-stack-frame', + glyphMarginClassName: 'codicon-debug-breakpoint-stackframe', stickiness }; private static FOCUSED_STACK_FRAME_MARGIN: IModelDecorationOptions = { - glyphMarginClassName: 'debug-focused-stack-frame', + glyphMarginClassName: 'codicon-debug-breakpoint-stackframe-focused', stickiness }; @@ -176,7 +176,68 @@ registerThemingParticipant((theme, collector) => { if (focusedStackFrame) { collector.addRule(`.monaco-editor .view-overlays .debug-focused-stack-frame-line { background: ${focusedStackFrame}; }`); } + + const debugIconBreakpointColor = theme.getColor(debugIconBreakpointForeground); + if (debugIconBreakpointColor) { + collector.addRule(` + .monaco-workbench .codicon-debug-breakpoint, + .monaco-workbench .codicon-debug-breakpoint-conditional, + .monaco-workbench .codicon-debug-breakpoint-log, + .monaco-workbench .codicon-debug-breakpoint-function, + .monaco-workbench .codicon-debug-breakpoint-data, + .monaco-workbench .codicon-debug-breakpoint-unsupported, + .monaco-workbench .codicon-debug-hint:not(*[class*='codicon-debug-breakpoint']) , + .monaco-workbench .codicon-debug-breakpoint-stackframe-dot, + .monaco-workbench .codicon-debug-breakpoint.codicon-debug-breakpoint-stackframe-focused::after { + color: ${debugIconBreakpointColor} !important; + } + `); + } + + const debugIconBreakpointDisabledColor = theme.getColor(debugIconBreakpointDisabledForeground); + if (debugIconBreakpointDisabledColor) { + collector.addRule(` + .monaco-workbench .codicon[class*='-disabled'] { + color: ${debugIconBreakpointDisabledColor} !important; + } + `); + } + + const debugIconBreakpointUnverifiedColor = theme.getColor(debugIconBreakpointUnverifiedForeground); + if (debugIconBreakpointUnverifiedColor) { + collector.addRule(` + .monaco-workbench .codicon[class*='-unverified'] { + color: ${debugIconBreakpointUnverifiedColor} !important; + } + `); + } + + const debugIconBreakpointStackframeColor = theme.getColor(debugIconBreakpointStackframeForeground); + if (debugIconBreakpointStackframeColor) { + collector.addRule(` + .monaco-workbench .codicon-debug-breakpoint-stackframe, + .monaco-workbench .codicon-debug-breakpoint-stackframe-dot::after { + color: ${debugIconBreakpointStackframeColor} !important; + } + `); + } + + const debugIconBreakpointStackframeFocusedColor = theme.getColor(debugIconBreakpointStackframeFocusedForeground); + if (debugIconBreakpointStackframeFocusedColor) { + collector.addRule(` + .monaco-workbench .codicon-debug-breakpoint-stackframe-focused { + color: ${debugIconBreakpointStackframeFocusedColor} !important; + } + `); + } + }); const topStackFrameColor = registerColor('editor.stackFrameHighlightBackground', { dark: '#ffff0033', light: '#ffff6673', hc: '#fff600' }, localize('topStackFrameLineHighlight', 'Background color for the highlight of line at the top stack frame position.')); const focusedStackFrameColor = registerColor('editor.focusedStackFrameHighlightBackground', { dark: '#7abd7a4d', light: '#cee7ce73', hc: '#cee7ce' }, localize('focusedStackFrameLineHighlight', 'Background color for the highlight of line at focused stack frame position.')); + +const debugIconBreakpointForeground = registerColor('debugIcon.breakpointForeground', { dark: '#E51400', light: '#E51400', hc: '#E51400' }, localize('debugIcon.breakpointForeground', 'Icon color for breakpoints.')); +const debugIconBreakpointDisabledForeground = registerColor('debugIcon.breakpointDisabledForeground', { dark: '#848484', light: '#848484', hc: '#848484' }, localize('debugIcon.breakpointDisabledForeground', 'Icon color for disabled breakpoints.')); +const debugIconBreakpointUnverifiedForeground = registerColor('debugIcon.breakpointUnverifiedForeground', { dark: '#848484', light: '#848484', hc: '#848484' }, localize('debugIcon.breakpointUnverifiedForeground', 'Icon color for unverified breakpoints.')); +const debugIconBreakpointStackframeForeground = registerColor('debugIcon.breakpointStackframeForeground', { dark: '#FFCC00', light: '#FFCC00', hc: '#FFCC00' }, localize('debugIcon.breakpointStackframeForeground', 'Icon color for breakpoints.')); +const debugIconBreakpointStackframeFocusedForeground = registerColor('debugIcon.breakpointStackframeFocusedForeground', { dark: '#89D185', light: '#89D185', hc: '#89D185' }, localize('debugIcon.breakpointStackframeFocusedForeground', 'Icon color for breakpoints.')); diff --git a/src/vs/workbench/contrib/debug/browser/debugCommands.ts b/src/vs/workbench/contrib/debug/browser/debugCommands.ts index f79ccb922dda..e4827854f6e1 100644 --- a/src/vs/workbench/contrib/debug/browser/debugCommands.ts +++ b/src/vs/workbench/contrib/debug/browser/debugCommands.ts @@ -58,15 +58,24 @@ export const DISCONNECT_LABEL = nls.localize('disconnect', "Disconnect"); export const STOP_LABEL = nls.localize('stop', "Stop"); export const CONTINUE_LABEL = nls.localize('continueDebug', "Continue"); -async function getThreadAndRun(accessor: ServicesAccessor, threadId: number | any, run: (thread: IThread) => Promise): Promise { +interface CallStackContext { + sessionId: string; + threadId: string; + frameId: string; +} + +function isThreadContext(obj: any): obj is CallStackContext { + return obj && typeof obj.sessionId === 'string' && typeof obj.threadId === 'string'; +} + +async function getThreadAndRun(accessor: ServicesAccessor, sessionAndThreadId: CallStackContext | unknown, run: (thread: IThread) => Promise): Promise { const debugService = accessor.get(IDebugService); let thread: IThread | undefined; - if (typeof threadId === 'number') { - debugService.getModel().getSessions().forEach(s => { - if (!thread) { - thread = s.getThread(threadId); - } - }); + if (isThreadContext(sessionAndThreadId)) { + const session = debugService.getModel().getSession(sessionAndThreadId.sessionId); + if (session) { + thread = session.getAllThreads().filter(t => t.getId() === sessionAndThreadId.threadId).pop(); + } } else { thread = debugService.getViewModel().focusedThread; if (!thread) { @@ -81,18 +90,17 @@ async function getThreadAndRun(accessor: ServicesAccessor, threadId: number | an } } -function getFrame(debugService: IDebugService, frameId: string | undefined): IStackFrame | undefined { - if (!frameId) { - return undefined; - } +function isStackFrameContext(obj: any): obj is CallStackContext { + return obj && typeof obj.sessionId === 'string' && typeof obj.threadId === 'string' && typeof obj.frameId === 'string'; +} - const sessions = debugService.getModel().getSessions(); - for (let s of sessions) { - for (let t of s.getAllThreads()) { - for (let sf of t.getCallStack()) { - if (sf.getId() === frameId) { - return sf; - } +function getFrame(debugService: IDebugService, context: CallStackContext | unknown): IStackFrame | undefined { + if (isStackFrameContext(context)) { + const session = debugService.getModel().getSession(context.sessionId); + if (session) { + const thread = session.getAllThreads().filter(t => t.getId() === context.threadId).pop(); + if (thread) { + return thread.getCallStack().filter(sf => sf.getId() === context.frameId).pop(); } } } @@ -100,6 +108,10 @@ function getFrame(debugService: IDebugService, frameId: string | undefined): ISt return undefined; } +function isSessionContext(obj: any): obj is CallStackContext { + return obj && typeof obj.sessionId === 'string'; +} + export function registerCommands(): void { // These commands are used in call stack context menu, call stack inline actions, command pallete, debug toolbar, mac native touch bar @@ -108,10 +120,10 @@ export function registerCommands(): void { // Same for stackFrame commands and session commands. CommandsRegistry.registerCommand({ id: COPY_STACK_TRACE_ID, - handler: async (accessor: ServicesAccessor, _: string, frameId: string | undefined) => { + handler: async (accessor: ServicesAccessor, context: CallStackContext | unknown) => { const textResourcePropertiesService = accessor.get(ITextResourcePropertiesService); const clipboardService = accessor.get(IClipboardService); - let frame = getFrame(accessor.get(IDebugService), frameId); + let frame = getFrame(accessor.get(IDebugService), context); if (frame) { const eol = textResourcePropertiesService.getEOL(frame.source.uri); await clipboardService.writeText(frame.thread.getCallStack().map(sf => sf.toString()).join(eol)); @@ -121,22 +133,22 @@ export function registerCommands(): void { CommandsRegistry.registerCommand({ id: REVERSE_CONTINUE_ID, - handler: (accessor: ServicesAccessor, threadId: number | any) => { - getThreadAndRun(accessor, threadId, thread => thread.reverseContinue()); + handler: (accessor: ServicesAccessor, context: CallStackContext | unknown) => { + getThreadAndRun(accessor, context, thread => thread.reverseContinue()); } }); CommandsRegistry.registerCommand({ id: STEP_BACK_ID, - handler: (accessor: ServicesAccessor, threadId: number | any) => { - getThreadAndRun(accessor, threadId, thread => thread.stepBack()); + handler: (accessor: ServicesAccessor, context: CallStackContext | unknown) => { + getThreadAndRun(accessor, context, thread => thread.stepBack()); } }); CommandsRegistry.registerCommand({ id: TERMINATE_THREAD_ID, - handler: (accessor: ServicesAccessor, threadId: number | any) => { - getThreadAndRun(accessor, threadId, thread => thread.terminate()); + handler: (accessor: ServicesAccessor, context: CallStackContext | unknown) => { + getThreadAndRun(accessor, context, thread => thread.terminate()); } }); @@ -194,9 +206,12 @@ export function registerCommands(): void { weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.Shift | KeyMod.CtrlCmd | KeyCode.F5, when: CONTEXT_IN_DEBUG_MODE, - handler: (accessor: ServicesAccessor, _: string, session: IDebugSession | undefined) => { + handler: (accessor: ServicesAccessor, context: CallStackContext | unknown) => { const debugService = accessor.get(IDebugService); - if (!session || !session.getId) { + let session: IDebugSession | undefined; + if (isSessionContext(context)) { + session = debugService.getModel().getSession(context.sessionId); + } else { session = debugService.getViewModel().focusedSession; } @@ -215,8 +230,8 @@ export function registerCommands(): void { weight: KeybindingWeight.WorkbenchContrib, primary: KeyCode.F10, when: CONTEXT_DEBUG_STATE.isEqualTo('stopped'), - handler: (accessor: ServicesAccessor, threadId: number | any) => { - getThreadAndRun(accessor, threadId, (thread: IThread) => thread.next()); + handler: (accessor: ServicesAccessor, context: CallStackContext | unknown) => { + getThreadAndRun(accessor, context, (thread: IThread) => thread.next()); } }); @@ -224,9 +239,9 @@ export function registerCommands(): void { id: STEP_INTO_ID, weight: KeybindingWeight.WorkbenchContrib + 10, // Have a stronger weight to have priority over full screen when debugging primary: KeyCode.F11, - when: CONTEXT_IN_DEBUG_MODE, - handler: (accessor: ServicesAccessor, threadId: number | any) => { - getThreadAndRun(accessor, threadId, (thread: IThread) => thread.stepIn()); + when: CONTEXT_DEBUG_STATE.isEqualTo('stopped'), + handler: (accessor: ServicesAccessor, context: CallStackContext | unknown) => { + getThreadAndRun(accessor, context, (thread: IThread) => thread.stepIn()); } }); @@ -235,8 +250,8 @@ export function registerCommands(): void { weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.Shift | KeyCode.F11, when: CONTEXT_DEBUG_STATE.isEqualTo('stopped'), - handler: (accessor: ServicesAccessor, threadId: number | any) => { - getThreadAndRun(accessor, threadId, (thread: IThread) => thread.stepOut()); + handler: (accessor: ServicesAccessor, context: CallStackContext | unknown) => { + getThreadAndRun(accessor, context, (thread: IThread) => thread.stepOut()); } }); @@ -245,8 +260,8 @@ export function registerCommands(): void { weight: KeybindingWeight.WorkbenchContrib, primary: KeyCode.F6, when: CONTEXT_DEBUG_STATE.isEqualTo('running'), - handler: (accessor: ServicesAccessor, threadId: number | any) => { - getThreadAndRun(accessor, threadId, thread => thread.pause()); + handler: (accessor: ServicesAccessor, context: CallStackContext | unknown) => { + getThreadAndRun(accessor, context, thread => thread.pause()); } }); @@ -264,9 +279,15 @@ export function registerCommands(): void { weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.Shift | KeyCode.F5, when: CONTEXT_IN_DEBUG_MODE, - handler: (accessor: ServicesAccessor, sessionId: string | undefined) => { + handler: (accessor: ServicesAccessor, context: CallStackContext | unknown) => { const debugService = accessor.get(IDebugService); - let session = debugService.getModel().getSession(sessionId) || debugService.getViewModel().focusedSession; + let session: IDebugSession | undefined; + if (isSessionContext(context)) { + session = debugService.getModel().getSession(context.sessionId); + } else { + session = debugService.getViewModel().focusedSession; + } + const configurationService = accessor.get(IConfigurationService); const showSubSessions = configurationService.getValue('debug').showSubSessionsInToolBar; // Stop should be sent to the root parent session @@ -280,9 +301,9 @@ export function registerCommands(): void { CommandsRegistry.registerCommand({ id: RESTART_FRAME_ID, - handler: async (accessor: ServicesAccessor, _: string, frameId: string | undefined) => { + handler: async (accessor: ServicesAccessor, context: CallStackContext | unknown) => { const debugService = accessor.get(IDebugService); - let frame = getFrame(debugService, frameId); + let frame = getFrame(debugService, context); if (frame) { await frame.restart(); } @@ -294,8 +315,8 @@ export function registerCommands(): void { weight: KeybindingWeight.WorkbenchContrib, primary: KeyCode.F5, when: CONTEXT_IN_DEBUG_MODE, - handler: (accessor: ServicesAccessor, threadId: number | any) => { - getThreadAndRun(accessor, threadId, thread => thread.continue()); + handler: (accessor: ServicesAccessor, context: CallStackContext | unknown) => { + getThreadAndRun(accessor, context, thread => thread.continue()); } }); diff --git a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts index 3b0c83a31f24..bc1c86b7e788 100644 --- a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts +++ b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts @@ -11,6 +11,7 @@ import * as objects from 'vs/base/common/objects'; import { URI as uri } from 'vs/base/common/uri'; import * as resources from 'vs/base/common/resources'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; +import * as editorCommon from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; import { IEditor } from 'vs/workbench/common/editor'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; @@ -56,6 +57,7 @@ export class ConfigurationManager implements IConfigurationManager { private adapterDescriptorFactories: IDebugAdapterDescriptorFactory[]; private debugAdapterFactories = new Map(); private debugConfigurationTypeContext: IContextKey; + private readonly _onDidRegisterDebugger = new Emitter(); constructor( @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @@ -166,6 +168,10 @@ export class ConfigurationManager implements IConfigurationManager { return Promise.resolve(undefined); } + get onDidRegisterDebugger(): Event { + return this._onDidRegisterDebugger.event; + } + // debug configurations registerDebugConfigurationProvider(debugConfigurationProvider: IDebugConfigurationProvider): IDisposable { @@ -196,11 +202,15 @@ export class ConfigurationManager implements IConfigurationManager { const providers = this.configProviders.filter(p => p.type === type && p.resolveDebugConfiguration) .concat(this.configProviders.filter(p => p.type === '*' && p.resolveDebugConfiguration)); + let result: IConfig | null | undefined = config; await sequence(providers.map(provider => async () => { - config = (await provider.resolveDebugConfiguration!(folderUri, config, token)) || config; + // If any provider returned undefined or null make sure to respect that and do not pass the result to more resolver + if (result) { + result = await provider.resolveDebugConfiguration!(folderUri, result, token); + } })); - return config; + return result; } async provideDebugConfigurations(folderUri: uri | undefined, type: string, token: CancellationToken): Promise { @@ -262,6 +272,7 @@ export class ConfigurationManager implements IConfigurationManager { }); this.setCompoundSchemaValues(); + this._onDidRegisterDebugger.fire(); }); breakpointsExtPoint.setHandler((extensions, delta) => { @@ -275,7 +286,8 @@ export class ConfigurationManager implements IConfigurationManager { this.toDispose.push(this.contextService.onDidChangeWorkspaceFolders(() => { this.initLaunches(); - this.selectConfiguration(this.selectedLaunch); + const toSelect = this.selectedLaunch || (this.launches.length > 0 ? this.launches[0] : undefined); + this.selectConfiguration(toSelect); this.setCompoundSchemaValues(); })); this.toDispose.push(this.configurationService.onDidChangeConfiguration(e => { @@ -384,6 +396,17 @@ export class ConfigurationManager implements IConfigurationManager { return this.debuggers.filter(dbg => strings.equalsIgnoreCase(dbg.type, type)).pop(); } + getDebuggerLabelsForEditor(editor: editorCommon.IEditor | undefined): string[] { + if (isCodeEditor(editor)) { + const model = editor.getModel(); + const language = model ? model.getLanguageIdentifier().language : undefined; + + return this.debuggers.filter(a => language && a.languages && a.languages.indexOf(language) >= 0).map(d => d.label); + } + + return []; + } + async guessDebugger(type?: string): Promise { if (type) { const adapter = this.getDebugger(type); @@ -411,16 +434,16 @@ export class ConfigurationManager implements IConfigurationManager { candidates.sort((first, second) => first.label.localeCompare(second.label)); const picks = candidates.map(c => ({ label: c.label, debugger: c })); - const picked = await this.quickInputService.pick<{ label: string, debugger: Debugger | undefined }>([...picks, { type: 'separator' }, { label: 'More...', debugger: undefined }], { placeHolder: nls.localize('selectDebug', "Select Environment") }); - - if (picked && picked.debugger) { - return picked.debugger; - } - if (picked) { - this.commandService.executeCommand('debug.installAdditionalDebuggers'); - } - - return undefined; + return this.quickInputService.pick<{ label: string, debugger: Debugger | undefined }>([...picks, { type: 'separator' }, { label: nls.localize('more', "More..."), debugger: undefined }], { placeHolder: nls.localize('selectDebug', "Select Environment") }) + .then(picked => { + if (picked && picked.debugger) { + return picked.debugger; + } + if (picked) { + this.commandService.executeCommand('debug.installAdditionalDebuggers'); + } + return undefined; + }); } async activateDebuggers(activationEvent: string, debugType?: string): Promise { diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts b/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts index b25e36fa238a..af56a6c7f209 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts @@ -111,7 +111,7 @@ export class RunToCursorAction extends EditorAction { label: RunToCursorAction.LABEL, alias: 'Debug: Run to Cursor', precondition: ContextKeyExpr.and(CONTEXT_IN_DEBUG_MODE, PanelFocusContext.toNegated(), CONTEXT_DEBUG_STATE.isEqualTo('stopped'), EditorContextKeys.editorTextFocus), - menuOpts: { + contextMenuOpts: { group: 'debug', order: 2 } @@ -160,7 +160,7 @@ class SelectionToReplAction extends EditorAction { label: nls.localize('debugEvaluate', "Debug: Evaluate"), alias: 'Debug: Evaluate', precondition: ContextKeyExpr.and(EditorContextKeys.hasNonEmptySelection, CONTEXT_IN_DEBUG_MODE, EditorContextKeys.editorTextFocus), - menuOpts: { + contextMenuOpts: { group: 'debug', order: 0 } @@ -190,7 +190,7 @@ class SelectionToWatchExpressionsAction extends EditorAction { label: nls.localize('debugAddToWatch', "Debug: Add to Watch"), alias: 'Debug: Add to Watch', precondition: ContextKeyExpr.and(EditorContextKeys.hasNonEmptySelection, CONTEXT_IN_DEBUG_MODE, EditorContextKeys.editorTextFocus), - menuOpts: { + contextMenuOpts: { group: 'debug', order: 1 } diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts index 00fe86c75f97..d2c27cd57d49 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts @@ -12,7 +12,7 @@ import { KeyCode } from 'vs/base/common/keyCodes'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { StandardTokenType } from 'vs/editor/common/modes'; import { DEFAULT_WORD_REGEXP } from 'vs/editor/common/model/wordHelper'; -import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditor, IEditorMouseEvent, MouseTargetType, IPartialEditorMouseEvent } from 'vs/editor/browser/editorBrowser'; import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { IDecorationOptions } from 'vs/editor/common/editorCommon'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; @@ -80,7 +80,7 @@ class DebugEditorContribution implements IDebugEditorContribution { this.toDispose.push(this.editor.onMouseDown((e: IEditorMouseEvent) => this.onEditorMouseDown(e))); this.toDispose.push(this.editor.onMouseUp(() => this.mouseDown = false)); this.toDispose.push(this.editor.onMouseMove((e: IEditorMouseEvent) => this.onEditorMouseMove(e))); - this.toDispose.push(this.editor.onMouseLeave((e: IEditorMouseEvent) => { + this.toDispose.push(this.editor.onMouseLeave((e: IPartialEditorMouseEvent) => { this.provideNonDebugHoverScheduler.cancel(); const hoverDomNode = this.hoverWidget.getDomNode(); if (!hoverDomNode) { diff --git a/src/vs/workbench/contrib/debug/browser/debugHover.ts b/src/vs/workbench/contrib/debug/browser/debugHover.ts index 8fe8000748f4..44bb809d0108 100644 --- a/src/vs/workbench/contrib/debug/browser/debugHover.ts +++ b/src/vs/workbench/contrib/debug/browser/debugHover.ts @@ -20,7 +20,7 @@ import { renderExpressionValue, replaceWhitespace } from 'vs/workbench/contrib/d import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { attachStylerCallback } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { editorHoverBackground, editorHoverBorder } from 'vs/platform/theme/common/colorRegistry'; +import { editorHoverBackground, editorHoverBorder, editorHoverForeground } from 'vs/platform/theme/common/colorRegistry'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { getExactExpressionStartAndEnd } from 'vs/workbench/contrib/debug/common/debugUtils'; import { AsyncDataTree } from 'vs/base/browser/ui/tree/asyncDataTree'; @@ -73,12 +73,15 @@ export class DebugHoverWidget implements IContentWidget { this.treeContainer.setAttribute('role', 'tree'); const dataSource = new DebugHoverDataSource(); - this.tree = this.instantiationService.createInstance(WorkbenchAsyncDataTree, 'DebugHover', this.treeContainer, new DebugHoverDelegate(), [this.instantiationService.createInstance(VariablesRenderer)], + this.tree = this.instantiationService.createInstance>(WorkbenchAsyncDataTree, 'DebugHover', this.treeContainer, new DebugHoverDelegate(), [this.instantiationService.createInstance(VariablesRenderer)], dataSource, { ariaLabel: nls.localize('treeAriaLabel', "Debug Hover"), accessibilityProvider: new DebugHoverAccessibilityProvider(), mouseSupport: false, - horizontalScrolling: true + horizontalScrolling: true, + overrideStyles: { + listBackground: editorHoverBackground + } }); this.valueContainer = $('.value'); @@ -90,7 +93,7 @@ export class DebugHoverWidget implements IContentWidget { this.editor.applyFontInfo(this.domNode); - this.toDispose.push(attachStylerCallback(this.themeService, { editorHoverBackground, editorHoverBorder }, colors => { + this.toDispose.push(attachStylerCallback(this.themeService, { editorHoverBackground, editorHoverBorder, editorHoverForeground }, colors => { if (colors.editorHoverBackground) { this.domNode.style.backgroundColor = colors.editorHoverBackground.toString(); } else { @@ -101,6 +104,11 @@ export class DebugHoverWidget implements IContentWidget { } else { this.domNode.style.border = ''; } + if (colors.editorHoverForeground) { + this.domNode.style.color = colors.editorHoverForeground.toString(); + } else { + this.domNode.style.color = null; + } })); this.toDispose.push(this.tree.onDidChangeContentHeight(() => this.layoutTreeAndContainer())); diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts index b193f88f5263..e94a2550ff0b 100644 --- a/src/vs/workbench/contrib/debug/browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugService.ts @@ -40,13 +40,14 @@ import { IAction } from 'vs/base/common/actions'; import { deepClone, equals } from 'vs/base/common/objects'; import { DebugSession } from 'vs/workbench/contrib/debug/browser/debugSession'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; -import { IDebugService, State, IDebugSession, CONTEXT_DEBUG_TYPE, CONTEXT_DEBUG_STATE, CONTEXT_IN_DEBUG_MODE, IThread, IDebugConfiguration, VIEWLET_ID, REPL_ID, IConfig, ILaunch, IViewModel, IConfigurationManager, IDebugModel, IEnablement, IBreakpoint, IBreakpointData, ICompound, IGlobalConfig, IStackFrame, AdapterEndEvent, getStateLabel, IDebugSessionOptions } from 'vs/workbench/contrib/debug/common/debug'; +import { IDebugService, State, IDebugSession, CONTEXT_DEBUG_TYPE, CONTEXT_DEBUG_STATE, CONTEXT_IN_DEBUG_MODE, IThread, IDebugConfiguration, VIEWLET_ID, REPL_ID, IConfig, ILaunch, IViewModel, IConfigurationManager, IDebugModel, IEnablement, IBreakpoint, IBreakpointData, ICompound, IGlobalConfig, IStackFrame, AdapterEndEvent, getStateLabel, IDebugSessionOptions, CONTEXT_DEBUG_UX } from 'vs/workbench/contrib/debug/common/debug'; import { isExtensionHostDebugging } from 'vs/workbench/contrib/debug/common/debugUtils'; import { isErrorWithActions, createErrorWithActions } from 'vs/base/common/errorsWithActions'; import { RunOnceScheduler } from 'vs/base/common/async'; import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug'; import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { withUndefinedAsNull } from 'vs/base/common/types'; const DEBUG_BREAKPOINTS_KEY = 'debug.breakpoint'; const DEBUG_FUNCTION_BREAKPOINTS_KEY = 'debug.functionbreakpoint'; @@ -85,6 +86,7 @@ export class DebugService implements IDebugService { private debugType: IContextKey; private debugState: IContextKey; private inDebugMode: IContextKey; + private debugUx: IContextKey; private breakpointsToSendOnResourceSaved: Set; private initializing = false; private previousState: State | undefined; @@ -126,6 +128,8 @@ export class DebugService implements IDebugService { this.debugType = CONTEXT_DEBUG_TYPE.bindTo(contextKeyService); this.debugState = CONTEXT_DEBUG_STATE.bindTo(contextKeyService); this.inDebugMode = CONTEXT_IN_DEBUG_MODE.bindTo(contextKeyService); + this.debugUx = CONTEXT_DEBUG_UX.bindTo(contextKeyService); + this.debugUx.set(!!this.configurationManager.selectedConfiguration.name ? 'default' : 'simple'); this.model = new DebugModel(this.loadBreakpoints(), this.loadFunctionBreakpoints(), this.loadExceptionBreakpoints(), this.loadDataBreakpoints(), this.loadWatchExpressions(), this.textFileService); @@ -169,6 +173,9 @@ export class DebugService implements IDebugService { this.toDispose.push(this.viewModel.onDidFocusSession(() => { this.onStateChange(); })); + this.toDispose.push(this.configurationManager.onDidSelectConfiguration(() => { + this.debugUx.set(!!(this.state !== State.Inactive || this.configurationManager.selectedConfiguration.name) ? 'default' : 'simple'); + })); } getModel(): IDebugModel { @@ -225,6 +232,7 @@ export class DebugService implements IDebugService { if (this.previousState !== state) { this.debugState.set(getStateLabel(state)); this.inDebugMode.set(state !== State.Inactive); + this.debugUx.set(!!(state !== State.Inactive || this.configurationManager.selectedConfiguration.name) ? 'default' : 'simple'); this.previousState = state; this._onDidChangeState.fire(state); } @@ -258,7 +266,7 @@ export class DebugService implements IDebugService { try { // make sure to save all files and that the configuration is up to date await this.extensionService.activateByEvent('onDebug'); - await this.textFileService.saveAll(); + await this.editorService.saveAll(); await this.configurationService.reloadConfiguration(launch ? launch.workspace : undefined); await this.extensionService.whenInstalledExtensionsRegistered(); @@ -488,8 +496,6 @@ export class DebugService implements IDebugService { } const errorMessage = error instanceof Error ? error.message : error; - this.telemetryDebugMisconfiguration(session.configuration ? session.configuration.type : undefined, errorMessage); - await this.showError(errorMessage, isErrorWithActions(error) ? error.actions : []); return false; } @@ -570,7 +576,7 @@ export class DebugService implements IDebugService { } async restartSession(session: IDebugSession, restartData?: any): Promise { - await this.textFileService.saveAll(); + await this.editorService.saveAll(); const isAutoRestart = !!restartData; const runTasks: () => Promise = async () => { @@ -583,19 +589,19 @@ export class DebugService implements IDebugService { return this.runTaskAndCheckErrors(session.root, session.configuration.preLaunchTask); }; - if (session.capabilities.supportsRestartRequest) { + if (isExtensionHostDebugging(session.configuration)) { const taskResult = await runTasks(); if (taskResult === TaskRunResult.Success) { - await session.restart(); + this.extensionHostDebugService.reload(session.getId()); } return; } - if (isExtensionHostDebugging(session.configuration)) { + if (session.capabilities.supportsRestartRequest) { const taskResult = await runTasks(); if (taskResult === TaskRunResult.Success) { - this.extensionHostDebugService.reload(session.getId()); + await session.restart(); } return; @@ -793,6 +799,7 @@ export class DebugService implements IDebugService { // Check that the task isn't busy and if it is, wait for it const busyTasks = await this.taskService.getBusyTasks(); if (busyTasks.filter(t => t._id === task._id).length) { + taskStarted = true; return inactivePromise; } // task is already running and isn't busy - nothing to do. @@ -808,7 +815,7 @@ export class DebugService implements IDebugService { return inactivePromise; } - return taskPromise; + return taskPromise.then(withUndefinedAsNull); }); return new Promise((c, e) => { @@ -1200,19 +1207,6 @@ export class DebugService implements IDebugService { }); } - private telemetryDebugMisconfiguration(debugType: string | undefined, message: string): Promise { - /* __GDPR__ - "debugMisconfiguration" : { - "type" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "error": { "classification": "CallstackOrException", "purpose": "FeatureInsight" } - } - */ - return this.telemetryService.publicLog('debugMisconfiguration', { - type: debugType, - error: message - }); - } - private telemetryDebugAddBreakpoint(breakpoint: IBreakpoint, context: string): Promise { /* __GDPR__ "debugAddBreakpoint" : { diff --git a/src/vs/workbench/contrib/debug/browser/debugSession.ts b/src/vs/workbench/contrib/debug/browser/debugSession.ts index 9a9e68b39609..321b3e542023 100644 --- a/src/vs/workbench/contrib/debug/browser/debugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/debugSession.ts @@ -720,8 +720,8 @@ export class DebugSession implements IDebugSession { await this.debugService.sendAllBreakpoints(this); } finally { await sendConfigurationDone(); + await this.fetchThreads(); } - await this.fetchThreads(); })); this.rawListeners.push(this.raw.onDidStop(async event => { diff --git a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts index 787cd1fff3e2..db8d0ec28a92 100644 --- a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts +++ b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts @@ -19,7 +19,7 @@ import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/co import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { Themable } from 'vs/workbench/common/theme'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { registerThemingParticipant, IThemeService } from 'vs/platform/theme/common/themeService'; import { registerColor, contrastBorder, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; import { localize } from 'vs/nls'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -41,12 +41,73 @@ export const debugToolBarBackground = registerColor('debugToolBar.background', { light: '#F3F3F3', hc: '#000000' }, localize('debugToolBarBackground', "Debug toolbar background color.")); + export const debugToolBarBorder = registerColor('debugToolBar.border', { dark: null, light: null, hc: null }, localize('debugToolBarBorder', "Debug toolbar border color.")); +export const debugIconStartForeground = registerColor('debugIcon.startForeground', { + dark: '#89D185', + light: '#388A34', + hc: '#89D185' +}, localize('debugIcon.startForeground', "Debug toolbar icon for start debugging.")); + +export const debugIconPauseForeground = registerColor('debugIcon.pauseForeground', { + dark: '#75BEFF', + light: '#007ACC', + hc: '#75BEFF' +}, localize('debugIcon.pauseForeground', "Debug toolbar icon for pause.")); + +export const debugIconStopForeground = registerColor('debugIcon.stopForeground', { + dark: '#F48771', + light: '#A1260D', + hc: '#F48771' +}, localize('debugIcon.stopForeground', "Debug toolbar icon for stop.")); + +export const debugIconDisconnectForeground = registerColor('debugIcon.disconnectForeground', { + dark: '#F48771', + light: '#A1260D', + hc: '#F48771' +}, localize('debugIcon.disconnectForeground', "Debug toolbar icon for disconnect.")); + +export const debugIconRestartForeground = registerColor('debugIcon.restartForeground', { + dark: '#89D185', + light: '#388A34', + hc: '#89D185' +}, localize('debugIcon.restartForeground', "Debug toolbar icon for restart.")); + +export const debugIconStepOverForeground = registerColor('debugIcon.stepOverForeground', { + dark: '#75BEFF', + light: '#007ACC', + hc: '#75BEFF' +}, localize('debugIcon.stepOverForeground', "Debug toolbar icon for step over.")); + +export const debugIconStepIntoForeground = registerColor('debugIcon.stepIntoForeground', { + dark: '#75BEFF', + light: '#007ACC', + hc: '#75BEFF' +}, localize('debugIcon.stepIntoForeground', "Debug toolbar icon for step into.")); + +export const debugIconStepOutForeground = registerColor('debugIcon.stepOutForeground', { + dark: '#75BEFF', + light: '#007ACC', + hc: '#75BEFF' +}, localize('debugIcon.stepOutForeground', "Debug toolbar icon for step over.")); + +export const debugIconContinueForeground = registerColor('debugIcon.continueForeground', { + dark: '#75BEFF', + light: '#007ACC', + hc: '#75BEFF' +}, localize('debugIcon.continueForeground', "Debug toolbar icon for continue.")); + +export const debugIconStepBackForeground = registerColor('debugIcon.stepBackForeground', { + dark: '#75BEFF', + light: '#007ACC', + hc: '#75BEFF' +}, localize('debugIcon.stepBackForeground', "Debug toolbar icon for step back.")); + export class DebugToolBar extends Themable implements IWorkbenchContribution { private $el: HTMLElement; @@ -56,6 +117,7 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { private updateScheduler: RunOnceScheduler; private debugToolBarMenu: IMenu; private disposeOnUpdate: IDisposable | undefined; + private yCoordinate = 0; private isVisible = false; private isBuilt = false; @@ -79,7 +141,7 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { this.$el = dom.$('div.debug-toolbar'); this.$el.style.top = `${layoutService.getTitleBarOffset()}px`; - this.dragArea = dom.append(this.$el, dom.$('div.drag-area')); + this.dragArea = dom.append(this.$el, dom.$('div.drag-area.codicon.codicon-gripper')); const actionBarContainer = dom.append(this.$el, dom.$('div.action-bar-container')); this.debugToolBarMenu = menuService.createMenu(MenuId.DebugToolBar, contextKeyService); @@ -146,7 +208,7 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { })); this._register(dom.addDisposableListener(window, dom.EventType.RESIZE, () => this.setCoordinates())); - this._register(dom.addDisposableListener(this.dragArea, dom.EventType.MOUSE_UP, (event: MouseEvent) => { + this._register(dom.addDisposableGenericMouseUpListner(this.dragArea, (event: MouseEvent) => { const mouseClickEvent = new StandardMouseEvent(event); if (mouseClickEvent.detail === 2) { // double click on debug bar centers it again #8250 @@ -156,10 +218,10 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { } })); - this._register(dom.addDisposableListener(this.dragArea, dom.EventType.MOUSE_DOWN, (event: MouseEvent) => { + this._register(dom.addDisposableGenericMouseDownListner(this.dragArea, (event: MouseEvent) => { dom.addClass(this.dragArea, 'dragged'); - const mouseMoveListener = dom.addDisposableListener(window, 'mousemove', (e: MouseEvent) => { + const mouseMoveListener = dom.addDisposableGenericMouseMoveListner(window, (e: MouseEvent) => { const mouseMoveEvent = new StandardMouseEvent(e); // Prevent default to stop editor selecting text #8524 mouseMoveEvent.preventDefault(); @@ -167,7 +229,7 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { this.setCoordinates(mouseMoveEvent.posx - 14, mouseMoveEvent.posy - this.layoutService.getTitleBarOffset()); }); - const mouseUpListener = dom.addDisposableListener(window, 'mouseup', (e: MouseEvent) => { + const mouseUpListener = dom.addDisposableGenericMouseUpListner(window, (e: MouseEvent) => { this.storePosition(); dom.removeClass(this.dragArea, 'dragged'); @@ -209,9 +271,10 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { } } - private setYCoordinate(y = 0): void { + private setYCoordinate(y = this.yCoordinate): void { const titlebarOffset = this.layoutService.getTitleBarOffset(); this.$el.style.top = `${titlebarOffset + y}px`; + this.yCoordinate = y; } private setCoordinates(x?: number, y?: number): void { @@ -289,3 +352,56 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { } } } + +registerThemingParticipant((theme, collector) => { + + const debugIconStartColor = theme.getColor(debugIconStartForeground); + if (debugIconStartColor) { + collector.addRule(`.monaco-workbench .codicon-debug-start { color: ${debugIconStartColor} !important; }`); + } + + const debugIconPauseColor = theme.getColor(debugIconPauseForeground); + if (debugIconPauseColor) { + collector.addRule(`.monaco-workbench .codicon-debug-pause { color: ${debugIconPauseColor} !important; }`); + } + + const debugIconStopColor = theme.getColor(debugIconStopForeground); + if (debugIconStopColor) { + collector.addRule(`.monaco-workbench .codicon-debug-stop { color: ${debugIconStopColor} !important; }`); + } + + const debugIconDisconnectColor = theme.getColor(debugIconDisconnectForeground); + if (debugIconDisconnectColor) { + collector.addRule(`.monaco-workbench .codicon-debug-disconnect { color: ${debugIconDisconnectColor} !important; }`); + } + + const debugIconRestartColor = theme.getColor(debugIconRestartForeground); + if (debugIconRestartColor) { + collector.addRule(`.monaco-workbench .codicon-debug-restart, .monaco-workbench .codicon-debug-restart-frame { color: ${debugIconRestartColor} !important; }`); + } + + const debugIconStepOverColor = theme.getColor(debugIconStepOverForeground); + if (debugIconStepOverColor) { + collector.addRule(`.monaco-workbench .codicon-debug-step-over { color: ${debugIconStepOverColor} !important; }`); + } + + const debugIconStepIntoColor = theme.getColor(debugIconStepIntoForeground); + if (debugIconStepIntoColor) { + collector.addRule(`.monaco-workbench .codicon-debug-step-into { color: ${debugIconStepIntoColor} !important; }`); + } + + const debugIconStepOutColor = theme.getColor(debugIconStepOutForeground); + if (debugIconStepOutColor) { + collector.addRule(`.monaco-workbench .codicon-debug-step-out { color: ${debugIconStepOutColor} !important; }`); + } + + const debugIconContinueColor = theme.getColor(debugIconContinueForeground); + if (debugIconContinueColor) { + collector.addRule(`.monaco-workbench .codicon-debug-continue,.monaco-workbench .codicon-debug-reverse-continue { color: ${debugIconContinueColor} !important; }`); + } + + const debugIconStepBackColor = theme.getColor(debugIconStepBackForeground); + if (debugIconStepBackColor) { + collector.addRule(`.monaco-workbench .codicon-debug-step-back { color: ${debugIconStepBackColor} !important; }`); + } +}); diff --git a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts index f9835f38df0a..9d89a1843275 100644 --- a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts +++ b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts @@ -9,7 +9,7 @@ import { IAction } from 'vs/base/common/actions'; import * as DOM from 'vs/base/browser/dom'; import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { ViewContainerViewlet } from 'vs/workbench/browser/parts/views/viewsViewlet'; -import { IDebugService, VIEWLET_ID, State, BREAKPOINTS_VIEW_ID, IDebugConfiguration, REPL_ID } from 'vs/workbench/contrib/debug/common/debug'; +import { IDebugService, VIEWLET_ID, State, BREAKPOINTS_VIEW_ID, IDebugConfiguration, REPL_ID, CONTEXT_DEBUG_UX, CONTEXT_DEBUG_UX_KEY } from 'vs/workbench/contrib/debug/common/debug'; import { StartAction, ConfigureAction, SelectAndStartAction, FocusSessionAction } from 'vs/workbench/contrib/debug/browser/debugActions'; import { StartDebugActionViewItem, FocusSessionActionViewItem } from 'vs/workbench/contrib/debug/browser/debugActionViewItems'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -26,20 +26,21 @@ import { memoize } from 'vs/base/common/decorators'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { DebugToolBar } from 'vs/workbench/contrib/debug/browser/debugToolBar'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { ViewletPanel } from 'vs/workbench/browser/parts/views/panelViewlet'; +import { ViewletPane } from 'vs/workbench/browser/parts/views/paneViewlet'; import { IMenu, MenuId, IMenuService, MenuItemAction } from 'vs/platform/actions/common/actions'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { TogglePanelAction } from 'vs/workbench/browser/panel'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; +import { StartView } from 'vs/workbench/contrib/debug/browser/startView'; export class DebugViewlet extends ViewContainerViewlet { private startDebugActionViewItem: StartDebugActionViewItem | undefined; private progressResolve: (() => void) | undefined; - private breakpointView: ViewletPanel | undefined; - private panelListeners = new Map(); + private breakpointView: ViewletPane | undefined; + private paneListeners = new Map(); private debugToolBarMenu: IMenu | undefined; private disposeOnTitleUpdate: IDisposable | undefined; @@ -61,10 +62,16 @@ export class DebugViewlet extends ViewContainerViewlet { @IContextKeyService private readonly contextKeyService: IContextKeyService, @INotificationService private readonly notificationService: INotificationService ) { - super(VIEWLET_ID, `${VIEWLET_ID}.state`, false, configurationService, layoutService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService); + super(VIEWLET_ID, `${VIEWLET_ID}.state`, true, configurationService, layoutService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService); this._register(this.debugService.onDidChangeState(state => this.onDebugServiceStateChange(state))); this._register(this.debugService.onDidNewSession(() => this.updateToolBar())); + this._register(this.contextKeyService.onDidChangeContext(e => { + if (e.affectsSome(new Set(CONTEXT_DEBUG_UX_KEY))) { + this.updateTitleArea(); + } + })); + this._register(this.contextService.onDidChangeWorkbenchState(() => this.updateTitleArea())); this._register(this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration('debug.toolBarLocation')) { @@ -83,6 +90,8 @@ export class DebugViewlet extends ViewContainerViewlet { if (this.startDebugActionViewItem) { this.startDebugActionViewItem.focus(); + } else { + this.focusView(StartView.ID); } } @@ -107,6 +116,9 @@ export class DebugViewlet extends ViewContainerViewlet { } getActions(): IAction[] { + if (CONTEXT_DEBUG_UX.getValue(this.contextKeyService) === 'simple') { + return []; + } if (this.showInitialDebugActions) { return [this.startAction, this.configureAction, this.toggleReplAction]; } @@ -181,32 +193,32 @@ export class DebugViewlet extends ViewContainerViewlet { } } - addPanels(panels: { panel: ViewletPanel, size: number, index?: number }[]): void { - super.addPanels(panels); + addPanes(panes: { pane: ViewletPane, size: number, index?: number }[]): void { + super.addPanes(panes); - for (const { panel } of panels) { + for (const { pane: pane } of panes) { // attach event listener to - if (panel.id === BREAKPOINTS_VIEW_ID) { - this.breakpointView = panel; + if (pane.id === BREAKPOINTS_VIEW_ID) { + this.breakpointView = pane; this.updateBreakpointsMaxSize(); } else { - this.panelListeners.set(panel.id, panel.onDidChange(() => this.updateBreakpointsMaxSize())); + this.paneListeners.set(pane.id, pane.onDidChange(() => this.updateBreakpointsMaxSize())); } } } - removePanels(panels: ViewletPanel[]): void { - super.removePanels(panels); - for (const panel of panels) { - dispose(this.panelListeners.get(panel.id)); - this.panelListeners.delete(panel.id); + removePanes(panes: ViewletPane[]): void { + super.removePanes(panes); + for (const pane of panes) { + dispose(this.paneListeners.get(pane.id)); + this.paneListeners.delete(pane.id); } } private updateBreakpointsMaxSize(): void { if (this.breakpointView) { // We need to update the breakpoints view since all other views are collapsed #25384 - const allOtherCollapsed = this.panels.every(view => !view.isExpanded() || view === this.breakpointView); + const allOtherCollapsed = this.panes.every(view => !view.isExpanded() || view === this.breakpointView); this.breakpointView.maximumBodySize = allOtherCollapsed ? Number.POSITIVE_INFINITY : this.breakpointView.minimumBodySize; } } @@ -220,6 +232,6 @@ class ToggleReplAction extends TogglePanelAction { @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @IPanelService panelService: IPanelService ) { - super(id, label, REPL_ID, panelService, layoutService, 'debug-action toggle-repl'); + super(id, label, REPL_ID, panelService, layoutService, 'debug-action codicon-terminal'); } } diff --git a/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts b/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts index fd539246deb3..19d3434cb897 100644 --- a/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts +++ b/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts @@ -16,6 +16,9 @@ import { URI } from 'vs/base/common/uri'; import { mapToSerializable } from 'vs/base/common/map'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IWorkspaceProvider, IWorkspace } from 'vs/workbench/services/host/browser/browserHostService'; +import { IProcessEnvironment } from 'vs/base/common/platform'; +import { hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces'; +import { ILogService } from 'vs/platform/log/common/log'; class BrowserExtensionHostDebugService extends ExtensionHostDebugChannelClient implements IExtensionHostDebugService { @@ -23,7 +26,8 @@ class BrowserExtensionHostDebugService extends ExtensionHostDebugChannelClient i constructor( @IRemoteAgentService remoteAgentService: IRemoteAgentService, - @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService + @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, + @ILogService logService: ILogService ) { const connection = remoteAgentService.getConnection(); let channel: IChannel; @@ -32,7 +36,7 @@ class BrowserExtensionHostDebugService extends ExtensionHostDebugChannelClient i } else { channel = { call: async () => undefined, listen: () => Event.None } as any; // TODO@weinand TODO@isidorn fallback? - console.warn('Extension Host Debugging not available due to missing connection.'); + logService.warn('Extension Host Debugging not available due to missing connection.'); } super(channel); @@ -41,14 +45,9 @@ class BrowserExtensionHostDebugService extends ExtensionHostDebugChannelClient i this.workspaceProvider = environmentService.options.workspaceProvider; } else { this.workspaceProvider = { open: async () => undefined, workspace: undefined }; - console.warn('Extension Host Debugging not available due to missing workspace provider.'); + logService.warn('Extension Host Debugging not available due to missing workspace provider.'); } - this.registerListeners(environmentService); - } - - private registerListeners(environmentService: IWorkbenchEnvironmentService): void { - // Reload window on reload request this._register(this.onReload(event => { if (environmentService.isExtensionDevelopment && environmentService.debugExtensionHost.debugId === event.sessionId) { @@ -64,7 +63,8 @@ class BrowserExtensionHostDebugService extends ExtensionHostDebugChannelClient i })); } - async openExtensionDevelopmentHostWindow(args: string[]): Promise { + openExtensionDevelopmentHostWindow(args: string[], env: IProcessEnvironment): Promise { + if (!this.workspaceProvider.payload) { // TODO@Ben remove me once environment is adopted return this.openExtensionDevelopmentHostWindowLegacy(args); @@ -75,16 +75,31 @@ class BrowserExtensionHostDebugService extends ExtensionHostDebugChannelClient i const folderUriArg = this.findArgument('folder-uri', args); if (folderUriArg) { debugWorkspace = { folderUri: URI.parse(folderUriArg) }; + } else { + const fileUriArg = this.findArgument('file-uri', args); + if (fileUriArg && hasWorkspaceFileExtension(fileUriArg)) { + debugWorkspace = { workspaceUri: URI.parse(fileUriArg) }; + } } // Add environment parameters required for debug to work const environment = new Map(); + const fileUriArg = this.findArgument('file-uri', args); + if (fileUriArg && !hasWorkspaceFileExtension(fileUriArg)) { + environment.set('openFile', fileUriArg); + } + const extensionDevelopmentPath = this.findArgument('extensionDevelopmentPath', args); if (extensionDevelopmentPath) { environment.set('extensionDevelopmentPath', extensionDevelopmentPath); } + const extensionTestsPath = this.findArgument('extensionTestsPath', args); + if (extensionTestsPath) { + environment.set('extensionTestsPath', extensionTestsPath); + } + const debugId = this.findArgument('debugId', args); if (debugId) { environment.set('debugId', debugId); @@ -96,13 +111,13 @@ class BrowserExtensionHostDebugService extends ExtensionHostDebugChannelClient i } // Open debug window as new window. Pass ParsedArgs over. - this.workspaceProvider.open(debugWorkspace, { + return this.workspaceProvider.open(debugWorkspace, { reuse: false, // debugging always requires a new window payload: mapToSerializable(environment) // mandatory properties to enable debugging }); } - private async openExtensionDevelopmentHostWindowLegacy(args: string[]): Promise { + private openExtensionDevelopmentHostWindowLegacy(args: string[]): Promise { // we pass the "args" as query parameters of the URL let newAddress = `${document.location.origin}${document.location.pathname}?`; @@ -142,6 +157,11 @@ class BrowserExtensionHostDebugService extends ExtensionHostDebugChannelClient i addQueryParameter('extensionDevelopmentPath', ep); } + const etp = findArgument('extensionTestsPath'); + if (etp) { + addQueryParameter('extensionTestsPath', etp); + } + const di = findArgument('debugId'); if (di) { addQueryParameter('debugId', di); diff --git a/src/vs/workbench/contrib/debug/browser/linkDetector.ts b/src/vs/workbench/contrib/debug/browser/linkDetector.ts index 7690c87a850c..3062b04218d7 100644 --- a/src/vs/workbench/contrib/debug/browser/linkDetector.ts +++ b/src/vs/workbench/contrib/debug/browser/linkDetector.ts @@ -12,6 +12,7 @@ import { IFileService } from 'vs/platform/files/common/files'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; const CONTROL_CODES = '\\u0000-\\u0020\\u007f-\\u009f'; const WEB_LINK_REGEX = new RegExp('(?:[a-zA-Z][a-zA-Z0-9+.-]{2,}:\\/\\/|data:|www\\.)[^\\s' + CONTROL_CODES + '"]{2,}[^\\s' + CONTROL_CODES + '"\')}\\],:;.!?]', 'ug'); @@ -37,7 +38,8 @@ export class LinkDetector { @IEditorService private readonly editorService: IEditorService, @IFileService private readonly fileService: IFileService, @IOpenerService private readonly openerService: IOpenerService, - @IEnvironmentService private readonly environmentService: IEnvironmentService + @IEnvironmentService private readonly environmentService: IEnvironmentService, + @IWorkbenchEnvironmentService private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService ) { // noop } @@ -96,7 +98,7 @@ export class LinkDetector { private createWebLink(url: string): Node { const link = this.createLink(url); const uri = URI.parse(url); - this.decorateLink(link, () => this.openerService.open(uri)); + this.decorateLink(link, () => this.openerService.open(uri, { allowTunneling: !!this.workbenchEnvironmentService.configuration.remoteAuthority })); return link; } @@ -142,7 +144,7 @@ export class LinkDetector { private decorateLink(link: HTMLElement, onclick: () => void) { link.classList.add('link'); link.title = platform.isMacintosh ? nls.localize('fileLinkMac', "Cmd + click to follow link") : nls.localize('fileLink', "Ctrl + click to follow link"); - link.onmousemove = (event) => link.classList.toggle('pointer', platform.isMacintosh ? event.metaKey : event.ctrlKey); + link.onmousemove = (event) => { link.classList.toggle('pointer', platform.isMacintosh ? event.metaKey : event.ctrlKey); }; link.onmouseleave = () => link.classList.remove('pointer'); link.onclick = (event) => { const selection = window.getSelection(); diff --git a/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts b/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts index 21941fdd9c57..d7a87ebdef27 100644 --- a/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts +++ b/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts @@ -7,7 +7,7 @@ import * as nls from 'vs/nls'; import * as dom from 'vs/base/browser/dom'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { normalize, isAbsolute, posix } from 'vs/base/common/path'; -import { IViewletPanelOptions, ViewletPanel } from 'vs/workbench/browser/parts/views/panelViewlet'; +import { IViewletPaneOptions, ViewletPane } from 'vs/workbench/browser/parts/views/paneViewlet'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -34,6 +34,7 @@ import { dispose } from 'vs/base/common/lifecycle'; import { createMatches, FuzzyScore } from 'vs/base/common/filters'; import { DebugContentProvider } from 'vs/workbench/contrib/debug/common/debugContentProvider'; import { ILabelService } from 'vs/platform/label/common/label'; +import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; const SMART = true; @@ -111,7 +112,11 @@ class BaseTreeItem { // a dynamic ID based on the parent chain; required for reparenting (see #55448) getId(): string { const parent = this.getParent(); - return parent ? `${parent.getId()}/${this._label}` : this._label; + return parent ? `${parent.getId()}/${this.getInternalId()}` : this.getInternalId(); + } + + getInternalId(): string { + return this._label; } // skips intermediate single-child nodes @@ -254,6 +259,10 @@ class SessionTreeItem extends BaseTreeItem { this._session = session; } + getInternalId(): string { + return this._session.getId(); + } + getSession(): IDebugSession { return this._session; } @@ -379,7 +388,7 @@ class SessionTreeItem extends BaseTreeItem { } } -export class LoadedScriptsView extends ViewletPanel { +export class LoadedScriptsView extends ViewletPane { private treeContainer!: HTMLElement; private loadedScriptsItemType: IContextKey; @@ -402,7 +411,7 @@ export class LoadedScriptsView extends ViewletPanel { @IDebugService private readonly debugService: IDebugService, @ILabelService private readonly labelService: ILabelService ) { - super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: nls.localize('loadedScriptsSection', "Loaded Scripts Section") }, keybindingService, contextMenuService, configurationService, contextKeyService); + super({ ...(options as IViewletPaneOptions), ariaHeaderLabel: nls.localize('loadedScriptsSection', "Loaded Scripts Section") }, keybindingService, contextMenuService, configurationService, contextKeyService); this.loadedScriptsItemType = CONTEXT_LOADED_SCRIPTS_ITEM_TYPE.bindTo(contextKeyService); } @@ -419,7 +428,7 @@ export class LoadedScriptsView extends ViewletPanel { this.treeLabels = this.instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: this.onDidChangeBodyVisibility }); this._register(this.treeLabels); - this.tree = this.instantiationService.createInstance(WorkbenchAsyncDataTree, 'LoadedScriptsView', this.treeContainer, new LoadedScriptsDelegate(), + this.tree = this.instantiationService.createInstance>(WorkbenchAsyncDataTree, 'LoadedScriptsView', this.treeContainer, new LoadedScriptsDelegate(), [new LoadedScriptsRenderer(this.treeLabels)], new LoadedScriptsDataSource(), { @@ -432,6 +441,9 @@ export class LoadedScriptsView extends ViewletPanel { filter: this.filter, accessibilityProvider: new LoadedSciptsAccessibilityProvider(), ariaLabel: nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'loadedScriptsAriaLabel' }, "Debug Loaded Scripts"), + overrideStyles: { + listBackground: SIDE_BAR_BACKGROUND + } } ); diff --git a/src/vs/workbench/contrib/debug/browser/media/add-dark.svg b/src/vs/workbench/contrib/debug/browser/media/add-dark.svg deleted file mode 100644 index 4d9389336b93..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/add-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/add-hc.svg b/src/vs/workbench/contrib/debug/browser/media/add-hc.svg deleted file mode 100644 index fb50c6c28499..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/add-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/add-light.svg b/src/vs/workbench/contrib/debug/browser/media/add-light.svg deleted file mode 100644 index 01a9de7d5abc..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/add-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/breakpoint-conditional.svg b/src/vs/workbench/contrib/debug/browser/media/breakpoint-conditional.svg deleted file mode 100644 index 382507ebcdf4..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/breakpoint-conditional.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/breakpoint-data-disabled.svg b/src/vs/workbench/contrib/debug/browser/media/breakpoint-data-disabled.svg deleted file mode 100644 index a2c8c3417e50..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/breakpoint-data-disabled.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/breakpoint-data-unverified.svg b/src/vs/workbench/contrib/debug/browser/media/breakpoint-data-unverified.svg deleted file mode 100644 index 96dda92ee382..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/breakpoint-data-unverified.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/breakpoint-data.svg b/src/vs/workbench/contrib/debug/browser/media/breakpoint-data.svg deleted file mode 100644 index 6752b060aeb6..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/breakpoint-data.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/breakpoint-disabled.svg b/src/vs/workbench/contrib/debug/browser/media/breakpoint-disabled.svg deleted file mode 100644 index 84588f8eac59..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/breakpoint-disabled.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/breakpoint-function-disabled.svg b/src/vs/workbench/contrib/debug/browser/media/breakpoint-function-disabled.svg deleted file mode 100644 index cd71f6e462e2..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/breakpoint-function-disabled.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/breakpoint-function-unverified.svg b/src/vs/workbench/contrib/debug/browser/media/breakpoint-function-unverified.svg deleted file mode 100644 index 9e2354d67bd9..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/breakpoint-function-unverified.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/breakpoint-function.svg b/src/vs/workbench/contrib/debug/browser/media/breakpoint-function.svg deleted file mode 100644 index f25e57ffde94..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/breakpoint-function.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/breakpoint-hint.svg b/src/vs/workbench/contrib/debug/browser/media/breakpoint-hint.svg deleted file mode 100644 index d622c6cf0c4b..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/breakpoint-hint.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/breakpoint-log-disabled.svg b/src/vs/workbench/contrib/debug/browser/media/breakpoint-log-disabled.svg deleted file mode 100644 index ea246058e0f6..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/breakpoint-log-disabled.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/breakpoint-log-unverified.svg b/src/vs/workbench/contrib/debug/browser/media/breakpoint-log-unverified.svg deleted file mode 100644 index ae8ed0ba7b69..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/breakpoint-log-unverified.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/breakpoint-log.svg b/src/vs/workbench/contrib/debug/browser/media/breakpoint-log.svg deleted file mode 100644 index fc72afc7e2b3..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/breakpoint-log.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/breakpoint-unsupported.svg b/src/vs/workbench/contrib/debug/browser/media/breakpoint-unsupported.svg deleted file mode 100644 index 624b9f60c80d..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/breakpoint-unsupported.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/breakpoint-unverified.svg b/src/vs/workbench/contrib/debug/browser/media/breakpoint-unverified.svg deleted file mode 100644 index 0f39b8b7c8f0..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/breakpoint-unverified.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/breakpoint.svg b/src/vs/workbench/contrib/debug/browser/media/breakpoint.svg deleted file mode 100644 index af02a8749503..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/breakpoint.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/breakpointWidget.css b/src/vs/workbench/contrib/debug/browser/media/breakpointWidget.css index b680b2806eef..73d62b3489bc 100644 --- a/src/vs/workbench/contrib/debug/browser/media/breakpointWidget.css +++ b/src/vs/workbench/contrib/debug/browser/media/breakpointWidget.css @@ -16,7 +16,16 @@ flex-shrink: 0; } +.monaco-editor .zone-widget .zone-widget-container.breakpoint-widget .breakpoint-select-container .monaco-select-box { + min-width: 100px; + min-height: 18px; + padding: 2px 20px 2px 8px; +} + +.monaco-editor .zone-widget .zone-widget-container.breakpoint-widget .breakpoint-select-container:after { + right: 14px; +} + .monaco-editor .zone-widget .zone-widget-container.breakpoint-widget .inputContainer { flex: 1; - margin-top: 8px; } diff --git a/src/vs/workbench/contrib/debug/browser/media/close-all-dark.svg b/src/vs/workbench/contrib/debug/browser/media/close-all-dark.svg deleted file mode 100644 index 35e5fb44226f..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/close-all-dark.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/close-all-hc.svg b/src/vs/workbench/contrib/debug/browser/media/close-all-hc.svg deleted file mode 100644 index def744d1ab9b..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/close-all-hc.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/close-all-light.svg b/src/vs/workbench/contrib/debug/browser/media/close-all-light.svg deleted file mode 100644 index 6c7cec7461c3..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/close-all-light.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/configure-dark.svg b/src/vs/workbench/contrib/debug/browser/media/configure-dark.svg deleted file mode 100644 index ace01a5ddf5a..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/configure-dark.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/configure-hc.svg b/src/vs/workbench/contrib/debug/browser/media/configure-hc.svg deleted file mode 100644 index bd59cb81f6d9..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/configure-hc.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/configure-light.svg b/src/vs/workbench/contrib/debug/browser/media/configure-light.svg deleted file mode 100644 index 4194780bbaad..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/configure-light.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/console-dark.svg b/src/vs/workbench/contrib/debug/browser/media/console-dark.svg deleted file mode 100644 index 1e2d3b4ee13d..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/console-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/console-hc.svg b/src/vs/workbench/contrib/debug/browser/media/console-hc.svg deleted file mode 100644 index 44b59552d8b1..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/console-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/console-light.svg b/src/vs/workbench/contrib/debug/browser/media/console-light.svg deleted file mode 100644 index 429cb22b71f5..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/console-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/continue-dark.svg b/src/vs/workbench/contrib/debug/browser/media/continue-dark.svg deleted file mode 100644 index 7c58386cccc6..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/continue-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/continue-light.svg b/src/vs/workbench/contrib/debug/browser/media/continue-light.svg deleted file mode 100644 index 7c58386cccc6..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/continue-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/continue-white.svg b/src/vs/workbench/contrib/debug/browser/media/continue-white.svg deleted file mode 100644 index 69d48e9984e8..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/continue-white.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/current-and-breakpoint.svg b/src/vs/workbench/contrib/debug/browser/media/current-and-breakpoint.svg deleted file mode 100644 index 17d71fddac9d..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/current-and-breakpoint.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/current-arrow.svg b/src/vs/workbench/contrib/debug/browser/media/current-arrow.svg deleted file mode 100644 index 85f288efcd3e..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/current-arrow.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/debug-activity-bar.svg b/src/vs/workbench/contrib/debug/browser/media/debug-activity-bar.svg deleted file mode 100644 index fcb9413c8c20..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/debug-activity-bar.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css b/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css index 48e363422fb3..b009497a755d 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css +++ b/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css @@ -3,49 +3,43 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -/* Activity Bar */ -.monaco-workbench .activitybar .monaco-action-bar .action-label.debug { - -webkit-mask: url('debug-activity-bar.svg') no-repeat 50% 50%; -} - -.monaco-editor .debug-top-stack-frame-column::before { - background: url('current-arrow.svg') center center no-repeat; -} - -.debug-breakpoint-hint { - background: url('breakpoint-hint.svg') center center no-repeat; +.codicon-debug-hint { cursor: pointer; } -.debug-breakpoint-disabled, -.monaco-editor .inline-breakpoint-widget.debug-breakpoint-disabled { - background: url('breakpoint-disabled.svg') center center no-repeat; +.codicon-debug-hint:not(*[class*='codicon-debug-breakpoint']) { + opacity: .4 !important; } -.monaco-editor .inline-breakpoint-widget.debug-breakpoint-disabled:hover { - background: url('breakpoint-hint.svg') center center no-repeat; +.inline-breakpoint-widget.codicon { + display: flex !important; + align-items: center; } -.monaco-editor .inline-breakpoint-widget.line-start { - left: -0.45em !important; +/* overlapped icons */ +.inline-breakpoint-widget.codicon-debug-breakpoint-stackframe-dot::after { + position: absolute; + top: 0; + left: 0; + bottom: 0; + margin: auto; + display: table; } -.debug-breakpoint-unverified, -.monaco-editor .inline-breakpoint-widget.debug-breakpoint-unverified { - background: url('breakpoint-unverified.svg') center center no-repeat; +.codicon-debug-breakpoint.codicon-debug-breakpoint-stackframe-focused::after { + position: absolute; } -.monaco-editor .debug-top-stack-frame { - background: url('current-arrow.svg') center center no-repeat; +.inline-breakpoint-widget.codicon-debug-breakpoint-stackframe-dot::after { + content: "\eb8b"; } -.monaco-editor .debug-focused-stack-frame { - background: url('stackframe-arrow.svg') center center no-repeat; +.codicon-debug-breakpoint.codicon-debug-breakpoint-stackframe-focused::after { + content: "\eb8a"; } -.debug-breakpoint, -.monaco-editor .inline-breakpoint-widget { - background: url('breakpoint.svg') center center no-repeat; +.monaco-editor .inline-breakpoint-widget.line-start { + left: -8px !important; } .monaco-editor .debug-breakpoint-placeholder::before, @@ -58,6 +52,14 @@ margin-left: 2px; } +/* Do not show call stack decoration when we plan to show breakpoint and top stack frame in one decoration */ +.monaco-editor .debug-breakpoint-placeholder ~ .debug-top-stack-frame-column::before { + width: 0em; + content: ""; + margin-right: 0px; + margin-left: 0px; +} + .monaco-editor .debug-top-stack-frame-column::before { height: 1.3em; } @@ -66,68 +68,6 @@ cursor: pointer; } -.debug-function-breakpoint { - background: url('breakpoint-function.svg') center center no-repeat; -} - -.debug-function-breakpoint-unverified { - background: url('breakpoint-function-unverified.svg') center center no-repeat; -} - -.debug-function-breakpoint-disabled { - background: url('breakpoint-function-disabled.svg') center center no-repeat; -} - -.debug-data-breakpoint { - background: url('breakpoint-data.svg') center center no-repeat; -} - -.debug-data-breakpoint-unverified { - background: url('breakpoint-data-unverified.svg') center center no-repeat; -} - -.debug-data-breakpoint-disabled { - background: url('breakpoint-data-disabled.svg') center center no-repeat; -} - -.debug-breakpoint-conditional, -.monaco-editor .inline-breakpoint-widget.debug-breakpoint-conditional { - background: url('breakpoint-conditional.svg') center center no-repeat; -} - -.debug-breakpoint-log, -.monaco-editor .inline-breakpoint-widget.debug-breakpoint-log { - background: url('breakpoint-log.svg') center center no-repeat; -} - -.debug-breakpoint-log-disabled, -.monaco-editor .inline-breakpoint-widget.debug-breakpoint-log-disabled { - background: url('breakpoint-log-disabled.svg') center center no-repeat; -} - -.debug-breakpoint-log-unverified, -.monaco-editor .inline-breakpoint-widget.debug-breakpoint-log-unverified { - background: url('breakpoint-log-unverified.svg') center center no-repeat; -} - -.debug-breakpoint-unsupported, -.monaco-editor .inline-breakpoint-widget.debug-breakpoint-unsupported { - background: url('breakpoint-unsupported.svg') center center no-repeat; -} - -.monaco-editor .debug-top-stack-frame.debug-breakpoint, -.monaco-editor .debug-top-stack-frame.debug-breakpoint-conditional, -.monaco-editor .debug-top-stack-frame.debug-breakpoint-log, -.monaco-editor .inline-breakpoint-widget.debug-top-stack-frame-column { - background: url('current-and-breakpoint.svg') center center no-repeat; -} - -.monaco-editor .debug-focused-stack-frame.debug-breakpoint, -.monaco-editor .debug-focused-stack-frame.debug-breakpoint-conditional, -.monaco-editor .debug-focused-stack-frame.debug-breakpoint-log { - background: url('stackframe-and-breakpoint.svg') center center no-repeat; -} - /* Error editor */ .debug-error-editor:focus { outline: none !important; diff --git a/src/vs/workbench/contrib/debug/browser/media/debugHover.css b/src/vs/workbench/contrib/debug/browser/media/debugHover.css index 79dd89207822..95ac46899e07 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debugHover.css +++ b/src/vs/workbench/contrib/debug/browser/media/debugHover.css @@ -10,6 +10,7 @@ animation-duration: 0.15s; animation-name: fadeIn; user-select: text; + -webkit-user-select: text; word-break: break-all; padding: 4px 5px; } @@ -38,6 +39,7 @@ .monaco-editor .debug-hover-widget .debug-hover-tree .monaco-list-row .monaco-tl-contents { user-select: text; + -webkit-user-select: text; } /* Disable tree highlight in debug hover tree. */ diff --git a/src/vs/workbench/contrib/debug/browser/media/debugToolBar.css b/src/vs/workbench/contrib/debug/browser/media/debugToolBar.css index f3a2c7cff767..357e8ab1c117 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debugToolBar.css +++ b/src/vs/workbench/contrib/debug/browser/media/debugToolBar.css @@ -19,12 +19,19 @@ margin-right: 7px; } +.monaco-workbench .debug-toolbar .monaco-action-bar .action-item.select-container .monaco-select-box, +.monaco-workbench .start-debug-action-item .select-container .monaco-select-box { + padding: 0 22px 0 6px; +} + .monaco-workbench .debug-toolbar .drag-area { cursor: grab; height: 32px; width: 16px; - background: url('drag.svg') center center no-repeat; - background-size: 16px 16px; + opacity: 0.5; + display: flex; + align-items: center; + justify-content: center; } .monaco-workbench .debug-toolbar .drag-area.dragged { @@ -38,4 +45,7 @@ background-size: 16px; background-position: center center; background-repeat: no-repeat; + display: flex; + align-items: center; + justify-content: center; } diff --git a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css index 23b491247a6a..4dd78b8c4d7d 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css +++ b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css @@ -13,40 +13,39 @@ height: 100%; } -/* Actionbar actions */ - -.monaco-workbench .debug-action.configure { - background: url('configure-light.svg') center center no-repeat; -} - -.vs-dark .monaco-workbench .debug-action.configure { - background: url('configure-dark.svg') center center no-repeat; +.debug-viewlet .debug-start-view { + padding: 0 20px 0 20px; } -.hc-black .monaco-workbench .debug-action.configure { - background: url('configure-hc.svg') center center no-repeat; +.debug-viewlet .debug-start-view .monaco-button, +.debug-viewlet .debug-start-view .section { + margin-top: 20px; } -.monaco-workbench .debug-action.toggle-repl { - background: url('console-light.svg') center center no-repeat; +.debug-viewlet .debug-start-view .top-section { + margin-top: 10px; } -.vs-dark .monaco-workbench .debug-action.toggle-repl { - background: url('console-dark.svg') center center no-repeat; +.debug-viewlet .debug-start-view .monaco-button { + max-width: 260px; + margin-left: auto; + margin-right: auto; + display: block; } -.hc-black .monaco-workbench .debug-action.toggle-repl { - background: url('console-hc.svg') center center no-repeat; +.debug-viewlet .debug-start-view .click { + cursor: pointer; + color: #007ACC; } -.monaco-workbench .debug-action.notification:before { +.monaco-workbench .debug-action.notification:after { content: ''; width: 6px; height: 6px; background-color: #CC6633; position: absolute; - top: 11px; - right: 5px; + top: 10px; + right: 6px; border-radius: 10px; border: 1px solid white; } @@ -65,24 +64,11 @@ border-radius: 4px; } -.monaco-workbench .part > .title > .title-actions .start-debug-action-item .icon { - height: 20px; - width: 20px; - background: url('start-light.svg') no-repeat; - background-size: 16px 16px; - background-position: center center; +.monaco-workbench .part > .title > .title-actions .start-debug-action-item .codicon { flex-shrink: 0; transition: transform 50ms ease; } -.vs-dark .monaco-workbench .part > .title > .title-actions .start-debug-action-item .icon { - background-image: url('start-dark.svg'); -} - -.hc-black .monaco-workbench .part > .title > .title-actions .start-debug-action-item .icon { - background-image: url('start-hc.svg'); -} - .monaco-workbench .monaco-action-bar .start-debug-action-item .configuration .monaco-select-box { border: none; margin-top: 0px; @@ -95,7 +81,7 @@ cursor: initial; } -.monaco-workbench .part > .title > .title-actions .start-debug-action-item .icon.active { +.monaco-workbench .part > .title > .title-actions .start-debug-action-item .codicon.active { transform: scale(1.272019649, 1.272019649); } @@ -117,6 +103,10 @@ color: #666; } +.debug-viewlet .monaco-list:focus .monaco-list-row.selected.focused .codicon { + color: inherit !important; +} + .debug-viewlet .disabled { opacity: 0.35; } @@ -179,9 +169,11 @@ text-transform: uppercase; } -.debug-viewlet .debug-call-stack .thread:hover > .state, -.debug-viewlet .debug-call-stack .session:hover > .state, -.debug-viewlet .debug-call-stack .monaco-list-row.focused .state { +.debug-viewlet .debug-call-stack .monaco-list-row:hover .state { + display: none; +} + +.debug-viewlet .debug-call-stack .monaco-list-row:hover .stack-frame.has-actions .file { display: none; } @@ -189,7 +181,6 @@ display: none; } -.debug-viewlet .debug-call-stack .monaco-list-row.focused .monaco-action-bar, .debug-viewlet .debug-call-stack .monaco-list-row:hover .monaco-action-bar { display: initial; } @@ -198,6 +189,7 @@ width: 16px; height: 100%; margin-right: 8px; + vertical-align: text-top; } .debug-viewlet .debug-call-stack .thread > .state > .label, @@ -224,6 +216,7 @@ flex: 1; flex-shrink: 0; min-width: fit-content; + min-width: -moz-fit-content; } .debug-viewlet .debug-call-stack .stack-frame.subtle { @@ -278,100 +271,8 @@ overflow: hidden; } -.debug-viewlet .debug-call-stack .debug-action.stop { - background: url('stop-light.svg') center center no-repeat; -} - -.debug-viewlet .debug-call-stack .monaco-list:focus .monaco-list-row.selected .debug-action.stop { - background: url('stop-white.svg') center center no-repeat; -} - -.vs-dark .debug-viewlet .debug-call-stack .debug-action.stop { - background: url('stop-dark.svg') center center no-repeat; -} - -.debug-viewlet .debug-call-stack .debug-action.disconnect { - background: url('disconnect-light.svg') center center no-repeat; -} - -.vs-dark .debug-viewlet .debug-call-stack .debug-action.disconnect { - background: url('disconnect-dark.svg') center center no-repeat; -} - -.debug-viewlet .debug-call-stack .monaco-list:focus .monaco-list-row.selected .debug-action.disconnect { - background: url('disconnect-white.svg') center center no-repeat; -} - -.debug-viewlet .debug-call-stack .debug-action.restart { - background: url('restart-light.svg') center center no-repeat; -} - -.vs-dark .debug-viewlet .debug-call-stack .debug-action.restart { - background: url('restart-dark.svg') center center no-repeat; -} - -.debug-viewlet .debug-call-stack .monaco-list:focus .monaco-list-row.selected .debug-action.restart { - background: url('restart-white.svg') center center no-repeat; -} - -.debug-viewlet .debug-call-stack .debug-action.step-over { - background: url('step-over-light.svg') center center no-repeat; -} - -.vs-dark .debug-viewlet .debug-call-stack .debug-action.step-over { - background: url('step-over-dark.svg') center center no-repeat; -} - -.debug-viewlet .debug-call-stack .monaco-list:focus .monaco-list-row.selected .debug-action.step-over { - background: url('step-over-white.svg') center center no-repeat; -} - -.debug-viewlet .debug-call-stack .debug-action.step-into { - background: url('step-into-light.svg') center center no-repeat; -} - -.vs-dark .debug-viewlet .debug-call-stack .debug-action.step-into { - background: url('step-into-dark.svg') center center no-repeat; -} - -.debug-viewlet .debug-call-stack .monaco-list:focus .monaco-list-row.selected .debug-action.step-into { - background: url('step-into-white.svg') center center no-repeat; -} - -.debug-viewlet .debug-call-stack .debug-action.step-out { - background: url('step-out-light.svg') center center no-repeat; -} - -.vs-dark .debug-viewlet .debug-call-stack .debug-action.step-out { - background: url('step-out-dark.svg') center center no-repeat; -} - -.debug-viewlet .debug-call-stack .monaco-list:focus .monaco-list-row.selected .debug-action.step-out { - background: url('step-out-white.svg') center center no-repeat; -} - -.debug-viewlet .debug-call-stack .debug-action.pause { - background: url('pause-light.svg') center center no-repeat; -} - -.vs-dark .debug-viewlet .debug-call-stack .debug-action.pause { - background: url('pause-dark.svg') center center no-repeat; -} - -.debug-viewlet .debug-call-stack .monaco-list:focus .monaco-list-row.selected .debug-action.pause { - background: url('pause-white.svg') center center no-repeat; -} - -.debug-viewlet .debug-call-stack .debug-action.continue { - background: url('continue-light.svg') center center no-repeat; -} - -.vs-dark .debug-viewlet .debug-call-stack .debug-action.continue { - background: url('continue-dark.svg') center center no-repeat; -} - -.debug-viewlet .debug-call-stack .monaco-list:focus .monaco-list-row.selected .debug-action.continue { - background: url('continue-white.svg') center center no-repeat; +.debug-viewlet .debug-call-stack .monaco-list:focus .monaco-list-row.selected .codicon { + color: inherit !important; } /* Variables & Expression view */ @@ -435,21 +336,6 @@ flex : 1; } -.debug-viewlet .debug-action.add-watch-expression, -.debug-viewlet .debug-action.add-function-breakpoint { - background: url('add-light.svg') center center no-repeat; -} - -.vs-dark .debug-viewlet .debug-action.add-watch-expression, -.vs-dark .debug-viewlet .debug-action.add-function-breakpoint { - background: url('add-dark.svg') center center no-repeat; -} - -.hc-black .debug-viewlet .debug-action.add-watch-expression, -.hc-black .debug-viewlet .debug-action.add-function-breakpoint { - background: url('add-hc.svg') center center no-repeat; -} - .vs-dark .debug-viewlet .monaco-list-row .expression .value.changed { animation-name: debugViewletValueChanged; } @@ -479,10 +365,17 @@ flex-shrink: 0; } -.debug-viewlet .debug-breakpoints .breakpoint > .icon { +.debug-viewlet .debug-breakpoints .breakpoint > .codicon { width: 19px; height: 19px; min-width: 19px; + display: flex; + align-items: center; + justify-content: center; +} + +.debug-viewlet .debug-breakpoints .breakpoint > .codicon-debug-breakpoint-stackframe-dot::before { + content: "\ea71"; } .debug-viewlet .debug-breakpoints .breakpoint > .file-path { @@ -499,30 +392,6 @@ text-overflow: ellipsis } -.debug-viewlet .debug-action.remove-all { - background: url('close-all-light.svg') center center no-repeat; -} - -.vs-dark .debug-viewlet .debug-action.remove-all { - background: url('close-all-dark.svg') center center no-repeat; -} - -.hc-black .debug-viewlet .debug-action.remove-all { - background: url('close-all-hc.svg') center center no-repeat; -} - -.debug-viewlet .debug-action.breakpoints-activate { - background: url('toggle-breakpoints-light.svg') center center no-repeat; -} - -.vs-dark .debug-viewlet .debug-action.breakpoints-activate { - background: url('toggle-breakpoints-dark.svg') center center no-repeat; -} - -.hc-black .debug-viewlet .debug-action.breakpoints-activate { - background: url('toggle-breakpoints-hc.svg') center center no-repeat; -} - /* No workspace view */ .debug-viewlet > .noworkspace-view { diff --git a/src/vs/workbench/contrib/debug/browser/media/disconnect-dark.svg b/src/vs/workbench/contrib/debug/browser/media/disconnect-dark.svg deleted file mode 100644 index 71aae0dd887f..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/disconnect-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/disconnect-light.svg b/src/vs/workbench/contrib/debug/browser/media/disconnect-light.svg deleted file mode 100644 index 06fc4c31553e..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/disconnect-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/disconnect-white.svg b/src/vs/workbench/contrib/debug/browser/media/disconnect-white.svg deleted file mode 100644 index 42e2e75e091b..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/disconnect-white.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/drag.svg b/src/vs/workbench/contrib/debug/browser/media/drag.svg deleted file mode 100644 index b6b93f31fdff..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/drag.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/exceptionWidget.css b/src/vs/workbench/contrib/debug/browser/media/exceptionWidget.css index d4ffe3099dab..9801b3f07ad9 100644 --- a/src/vs/workbench/contrib/debug/browser/media/exceptionWidget.css +++ b/src/vs/workbench/contrib/debug/browser/media/exceptionWidget.css @@ -11,6 +11,7 @@ padding: 6px 10px; white-space: pre-wrap; user-select: text; + -webkit-user-select: text; } .monaco-editor .zone-widget .zone-widget-container.exception-widget .title { diff --git a/src/vs/workbench/contrib/debug/browser/media/pause-dark.svg b/src/vs/workbench/contrib/debug/browser/media/pause-dark.svg deleted file mode 100644 index 9cd9f4661307..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/pause-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/pause-light.svg b/src/vs/workbench/contrib/debug/browser/media/pause-light.svg deleted file mode 100644 index 01d3cbc290ce..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/pause-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/pause-white.svg b/src/vs/workbench/contrib/debug/browser/media/pause-white.svg deleted file mode 100644 index bd634a35a5af..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/pause-white.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/repl.css b/src/vs/workbench/contrib/debug/browser/media/repl.css index cdb38f82bae0..5aee190454ed 100644 --- a/src/vs/workbench/contrib/debug/browser/media/repl.css +++ b/src/vs/workbench/contrib/debug/browser/media/repl.css @@ -13,6 +13,8 @@ .repl .repl-tree .monaco-tl-contents { user-select: text; + -webkit-user-select: text; + white-space: pre; } .repl .repl-tree.word-wrap .monaco-tl-contents { @@ -37,6 +39,16 @@ flex: 1; } +.repl .repl-tree .monaco-tl-contents .arrow { + position:absolute; + left: 2px; + opacity: 0.3; +} + +.vs-dark .repl .repl-tree .monaco-tl-contents .arrow { + opacity: 0.45; +} + .repl .repl-tree .output.expression.value-and-source .source { margin-left: 4px; margin-right: 8px; diff --git a/src/vs/workbench/contrib/debug/browser/media/restart-dark.svg b/src/vs/workbench/contrib/debug/browser/media/restart-dark.svg deleted file mode 100644 index fc48916d5a64..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/restart-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/restart-light.svg b/src/vs/workbench/contrib/debug/browser/media/restart-light.svg deleted file mode 100644 index 4964d5bfaf19..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/restart-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/restart-white.svg b/src/vs/workbench/contrib/debug/browser/media/restart-white.svg deleted file mode 100644 index cf498da0c7f6..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/restart-white.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/reverse-continue-dark.svg b/src/vs/workbench/contrib/debug/browser/media/reverse-continue-dark.svg deleted file mode 100644 index e0bbfb4202e3..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/reverse-continue-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/reverse-continue-light.svg b/src/vs/workbench/contrib/debug/browser/media/reverse-continue-light.svg deleted file mode 100644 index e0bbfb4202e3..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/reverse-continue-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/stackframe-and-breakpoint.svg b/src/vs/workbench/contrib/debug/browser/media/stackframe-and-breakpoint.svg deleted file mode 100644 index 3ce31c822c74..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/stackframe-and-breakpoint.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/stackframe-arrow.svg b/src/vs/workbench/contrib/debug/browser/media/stackframe-arrow.svg deleted file mode 100644 index 38b63a34c537..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/stackframe-arrow.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/start-dark.svg b/src/vs/workbench/contrib/debug/browser/media/start-dark.svg deleted file mode 100644 index 5d91244bff14..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/start-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/start-hc.svg b/src/vs/workbench/contrib/debug/browser/media/start-hc.svg deleted file mode 100644 index 5d91244bff14..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/start-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/start-light.svg b/src/vs/workbench/contrib/debug/browser/media/start-light.svg deleted file mode 100644 index b6effa36f71d..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/start-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/step-back-dark.svg b/src/vs/workbench/contrib/debug/browser/media/step-back-dark.svg deleted file mode 100644 index 5a6ada3e113f..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/step-back-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/step-back-light.svg b/src/vs/workbench/contrib/debug/browser/media/step-back-light.svg deleted file mode 100644 index b5a994d42525..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/step-back-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/step-into-dark.svg b/src/vs/workbench/contrib/debug/browser/media/step-into-dark.svg deleted file mode 100644 index 570ae02aafa4..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/step-into-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/step-into-light.svg b/src/vs/workbench/contrib/debug/browser/media/step-into-light.svg deleted file mode 100644 index 55c47062f5c4..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/step-into-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/step-into-white.svg b/src/vs/workbench/contrib/debug/browser/media/step-into-white.svg deleted file mode 100644 index 77ef1cbf34b3..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/step-into-white.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/step-out-dark.svg b/src/vs/workbench/contrib/debug/browser/media/step-out-dark.svg deleted file mode 100644 index 33a7a2fdb728..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/step-out-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/step-out-light.svg b/src/vs/workbench/contrib/debug/browser/media/step-out-light.svg deleted file mode 100644 index 6ac2139659d4..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/step-out-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/step-out-white.svg b/src/vs/workbench/contrib/debug/browser/media/step-out-white.svg deleted file mode 100644 index 906cd8d33caa..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/step-out-white.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/step-over-dark.svg b/src/vs/workbench/contrib/debug/browser/media/step-over-dark.svg deleted file mode 100644 index 5bf10674eecd..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/step-over-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/step-over-light.svg b/src/vs/workbench/contrib/debug/browser/media/step-over-light.svg deleted file mode 100644 index b874a2564b5e..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/step-over-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/step-over-white.svg b/src/vs/workbench/contrib/debug/browser/media/step-over-white.svg deleted file mode 100644 index bac3022c88fb..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/step-over-white.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/stop-dark.svg b/src/vs/workbench/contrib/debug/browser/media/stop-dark.svg deleted file mode 100644 index 9a28f77a9f97..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/stop-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/stop-light.svg b/src/vs/workbench/contrib/debug/browser/media/stop-light.svg deleted file mode 100644 index 9a28f77a9f97..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/stop-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/stop-white.svg b/src/vs/workbench/contrib/debug/browser/media/stop-white.svg deleted file mode 100644 index f33eb6181db4..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/stop-white.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/toggle-breakpoints-dark.svg b/src/vs/workbench/contrib/debug/browser/media/toggle-breakpoints-dark.svg deleted file mode 100644 index 2e8b9f34e5e3..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/toggle-breakpoints-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/toggle-breakpoints-hc.svg b/src/vs/workbench/contrib/debug/browser/media/toggle-breakpoints-hc.svg deleted file mode 100644 index ab1b9e54f980..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/toggle-breakpoints-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/toggle-breakpoints-light.svg b/src/vs/workbench/contrib/debug/browser/media/toggle-breakpoints-light.svg deleted file mode 100644 index 479dc5f81e59..000000000000 --- a/src/vs/workbench/contrib/debug/browser/media/toggle-breakpoints-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index 6f16f3b4d996..7204f2e38a13 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -63,6 +63,7 @@ import { RunOnceScheduler } from 'vs/base/common/async'; import { FuzzyScore, createMatches } from 'vs/base/common/filters'; import { HighlightedLabel, IHighlight } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { PANEL_BACKGROUND } from 'vs/workbench/common/theme'; const $ = dom.$; @@ -344,7 +345,7 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati } focus(): void { - this.replInput.focus(); + setTimeout(() => this.replInput.focus(), 0); } getActionViewItem(action: IAction): IActionViewItem | undefined { @@ -406,7 +407,7 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati const wordWrap = this.configurationService.getValue('debug').console.wordWrap; dom.toggleClass(treeContainer, 'word-wrap', wordWrap); const linkDetector = this.instantiationService.createInstance(LinkDetector); - this.tree = this.instantiationService.createInstance( + this.tree = this.instantiationService.createInstance>( WorkbenchAsyncDataTree, 'DebugRepl', treeContainer, @@ -428,7 +429,10 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati keyboardNavigationLabelProvider: { getKeyboardNavigationLabel: (e: IReplElement) => e }, horizontalScrolling: !wordWrap, setRowLineHeight: false, - supportDynamicHeights: wordWrap + supportDynamicHeights: wordWrap, + overrideStyles: { + listBackground: PANEL_BACKGROUND + } }); this._register(this.tree.onContextMenu(e => this.onContextMenu(e))); let lastSelectedString: string; @@ -606,6 +610,7 @@ class ReplEvaluationInputsRenderer implements ITreeRenderer { const config = this.configurationService.getValue('debug'); if (!config.console.wordWrap) { - return Math.ceil(1.4 * config.console.fontSize); + return this.estimateHeight(element, true); } return super.getHeight(element); } - protected estimateHeight(element: IReplElement): number { + protected estimateHeight(element: IReplElement, ignoreValueLength = false): number { const config = this.configurationService.getValue('debug'); const rowHeight = Math.ceil(1.4 * config.console.fontSize); const countNumberOfLines = (str: string) => Math.max(1, (str && str.match(/\r\n|\n/g) || []).length); @@ -821,7 +827,7 @@ class ReplDelegate extends CachedListVirtualDelegate { // For every 30 characters increase the number of lines needed if (hasValue(element)) { let value = element.value; - let valueRows = countNumberOfLines(value) + Math.floor(value.length / 30); + let valueRows = countNumberOfLines(value) + (ignoreValueLength ? 0 : Math.floor(value.length / 30)); return valueRows * rowHeight; } diff --git a/src/vs/workbench/contrib/debug/browser/startView.ts b/src/vs/workbench/contrib/debug/browser/startView.ts new file mode 100644 index 000000000000..88fa88dfaf40 --- /dev/null +++ b/src/vs/workbench/contrib/debug/browser/startView.ts @@ -0,0 +1,156 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as dom from 'vs/base/browser/dom'; +import { Button } from 'vs/base/browser/ui/button/button'; +import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; +import { attachButtonStyler } from 'vs/platform/theme/common/styler'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +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 { ViewletPane, IViewletPaneOptions } from 'vs/workbench/browser/parts/views/paneViewlet'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { localize } from 'vs/nls'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { StartAction, RunAction, ConfigureAction } from 'vs/workbench/contrib/debug/browser/debugActions'; +import { IDebugService } from 'vs/workbench/contrib/debug/common/debug'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; +import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { equals } from 'vs/base/common/arrays'; +const $ = dom.$; + +export class StartView extends ViewletPane { + + static ID = 'workbench.debug.startView'; + static LABEL = localize('start', "Start"); + + private debugButton!: Button; + private runButton!: Button; + private firstMessageContainer!: HTMLElement; + private secondMessageContainer!: HTMLElement; + private debuggerLabels: string[] | undefined = undefined; + + constructor( + options: IViewletViewOptions, + @IThemeService private readonly themeService: IThemeService, + @IKeybindingService keybindingService: IKeybindingService, + @IContextMenuService contextMenuService: IContextMenuService, + @IConfigurationService configurationService: IConfigurationService, + @IContextKeyService contextKeyService: IContextKeyService, + @ICommandService private readonly commandService: ICommandService, + @IDebugService private readonly debugService: IDebugService, + @IEditorService private readonly editorService: IEditorService, + @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, + @IFileDialogService private readonly dialogService: IFileDialogService + ) { + super({ ...(options as IViewletPaneOptions), ariaHeaderLabel: localize('debugStart', "Debug Start Section") }, keybindingService, contextMenuService, configurationService, contextKeyService); + this._register(editorService.onDidActiveEditorChange(() => this.updateView())); + this._register(this.debugService.getConfigurationManager().onDidRegisterDebugger(() => this.updateView())); + } + + private updateView(): void { + const activeEditor = this.editorService.activeTextEditorWidget; + const debuggerLabels = this.debugService.getConfigurationManager().getDebuggerLabelsForEditor(activeEditor); + if (!equals(this.debuggerLabels, debuggerLabels)) { + this.debuggerLabels = debuggerLabels; + const enabled = this.debuggerLabels.length > 0; + + this.debugButton.enabled = enabled; + this.runButton.enabled = enabled; + this.debugButton.label = this.debuggerLabels.length !== 1 ? localize('debug', "Debug") : localize('debugWith', "Debug with {0}", this.debuggerLabels[0]); + this.runButton.label = this.debuggerLabels.length !== 1 ? localize('run', "Run") : localize('runWith', "Run with {0}", this.debuggerLabels[0]); + + const emptyWorkbench = this.workspaceContextService.getWorkbenchState() === WorkbenchState.EMPTY; + this.firstMessageContainer.innerHTML = ''; + this.secondMessageContainer.innerHTML = ''; + const secondMessageElement = $('span'); + this.secondMessageContainer.appendChild(secondMessageElement); + + const setSecondMessage = () => { + secondMessageElement.textContent = localize('specifyHowToRun', "To futher configure Debug and Run"); + const clickElement = $('span.click'); + clickElement.textContent = localize('configure', " create a launch.json file."); + clickElement.onclick = () => this.commandService.executeCommand(ConfigureAction.ID); + this.secondMessageContainer.appendChild(clickElement); + }; + const setSecondMessageWithFolder = () => { + secondMessageElement.textContent = localize('noLaunchConfiguration', "To futher configure Debug and Run, "); + const clickElement = $('span.click'); + clickElement.textContent = localize('openFolder', " open a folder"); + clickElement.onclick = () => this.dialogService.pickFolderAndOpen({ forceNewWindow: false }); + this.secondMessageContainer.appendChild(clickElement); + + const moreText = $('span.moreText'); + moreText.textContent = localize('andconfigure', " and create a launch.json file."); + this.secondMessageContainer.appendChild(moreText); + }; + + if (enabled && !emptyWorkbench) { + setSecondMessage(); + } + + if (enabled && emptyWorkbench) { + setSecondMessageWithFolder(); + } + + if (!enabled && !emptyWorkbench) { + const firstMessageElement = $('span'); + this.firstMessageContainer.appendChild(firstMessageElement); + firstMessageElement.textContent = localize('simplyDebugAndRun', "Open a file which can be debugged or run."); + + setSecondMessage(); + } + + if (!enabled && emptyWorkbench) { + const clickElement = $('span.click'); + clickElement.textContent = localize('openFile', "Open a file"); + clickElement.onclick = () => this.dialogService.pickFileAndOpen({ forceNewWindow: false }); + + this.firstMessageContainer.appendChild(clickElement); + const firstMessageElement = $('span'); + this.firstMessageContainer.appendChild(firstMessageElement); + firstMessageElement.textContent = localize('canBeDebuggedOrRun', " which can be debugged or run."); + + + setSecondMessageWithFolder(); + } + } + } + + protected renderBody(container: HTMLElement): void { + this.firstMessageContainer = $('.top-section'); + container.appendChild(this.firstMessageContainer); + + this.debugButton = new Button(container); + this._register(this.debugButton.onDidClick(() => { + this.commandService.executeCommand(StartAction.ID); + })); + attachButtonStyler(this.debugButton, this.themeService); + + this.runButton = new Button(container); + this.runButton.label = localize('run', "Run"); + + dom.addClass(container, 'debug-start-view'); + this._register(this.runButton.onDidClick(() => { + this.commandService.executeCommand(RunAction.ID); + })); + attachButtonStyler(this.runButton, this.themeService); + + this.secondMessageContainer = $('.section'); + container.appendChild(this.secondMessageContainer); + + this.updateView(); + } + + protected layoutBody(_: number, __: number): void { + // no-op + } + + focus(): void { + this.runButton.focus(); + } +} diff --git a/src/vs/workbench/contrib/debug/browser/variablesView.ts b/src/vs/workbench/contrib/debug/browser/variablesView.ts index 98b9cf1c1638..99589e4c6ca5 100644 --- a/src/vs/workbench/contrib/debug/browser/variablesView.ts +++ b/src/vs/workbench/contrib/debug/browser/variablesView.ts @@ -17,7 +17,7 @@ import { IAction, Action } from 'vs/base/common/actions'; import { CopyValueAction } from 'vs/workbench/contrib/debug/browser/debugActions'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IViewletPanelOptions, ViewletPanel } from 'vs/workbench/browser/parts/views/panelViewlet'; +import { IViewletPaneOptions, ViewletPane } from 'vs/workbench/browser/parts/views/paneViewlet'; import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { ITreeRenderer, ITreeNode, ITreeMouseEvent, ITreeContextMenuEvent, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree'; @@ -30,13 +30,14 @@ import { HighlightedLabel, IHighlight } from 'vs/base/browser/ui/highlightedlabe import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { dispose } from 'vs/base/common/lifecycle'; +import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; const $ = dom.$; let forgetScopes = true; export const variableSetEmitter = new Emitter(); -export class VariablesView extends ViewletPanel { +export class VariablesView extends ViewletPane { private onFocusStackFrameScheduler: RunOnceScheduler; private needsRefresh = false; @@ -53,7 +54,7 @@ export class VariablesView extends ViewletPanel { @IClipboardService private readonly clipboardService: IClipboardService, @IContextKeyService contextKeyService: IContextKeyService ) { - super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: nls.localize('variablesSection', "Variables Section") }, keybindingService, contextMenuService, configurationService, contextKeyService); + super({ ...(options as IViewletPaneOptions), ariaHeaderLabel: nls.localize('variablesSection', "Variables Section") }, keybindingService, contextMenuService, configurationService, contextKeyService); // Use scheduler to prevent unnecessary flashing this.onFocusStackFrameScheduler = new RunOnceScheduler(async () => { @@ -85,13 +86,16 @@ export class VariablesView extends ViewletPanel { dom.addClass(container, 'debug-variables'); const treeContainer = renderViewTree(container); - this.tree = this.instantiationService.createInstance(WorkbenchAsyncDataTree, 'VariablesView', treeContainer, new VariablesDelegate(), + this.tree = this.instantiationService.createInstance>(WorkbenchAsyncDataTree, 'VariablesView', treeContainer, new VariablesDelegate(), [this.instantiationService.createInstance(VariablesRenderer), new ScopesRenderer()], new VariablesDataSource(), { ariaLabel: nls.localize('variablesAriaTreeLabel', "Debug Variables"), accessibilityProvider: new VariablesAccessibilityProvider(), identityProvider: { getId: (element: IExpression | IScope) => element.getId() }, - keyboardNavigationLabelProvider: { getKeyboardNavigationLabel: (e: IExpression | IScope) => e } + keyboardNavigationLabelProvider: { getKeyboardNavigationLabel: (e: IExpression | IScope) => e }, + overrideStyles: { + listBackground: SIDE_BAR_BACKGROUND + } }); this.tree.setInput(this.debugService.getViewModel()); @@ -99,7 +103,7 @@ export class VariablesView extends ViewletPanel { CONTEXT_VARIABLES_FOCUSED.bindTo(this.tree.contextKeyService); if (this.toolbar) { - const collapseAction = new CollapseAction(this.tree, true, 'explorer-action collapse-explorer'); + const collapseAction = new CollapseAction(this.tree, true, 'explorer-action codicon-collapse-all'); this.toolbar.setActions([collapseAction])(); } this.tree.updateChildren(); diff --git a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts index 0f5f58a83ccd..b2b34fd35d1e 100644 --- a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts +++ b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts @@ -18,7 +18,7 @@ import { IAction, Action } from 'vs/base/common/actions'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { renderExpressionValue, renderViewTree, IInputBoxOptions, AbstractExpressionsRenderer, IExpressionTemplateData } from 'vs/workbench/contrib/debug/browser/baseDebugView'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IViewletPanelOptions, ViewletPanel } from 'vs/workbench/browser/parts/views/panelViewlet'; +import { IViewletPaneOptions, ViewletPane } from 'vs/workbench/browser/parts/views/paneViewlet'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; @@ -30,10 +30,11 @@ import { IHighlight } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel import { variableSetEmitter, VariablesRenderer } from 'vs/workbench/contrib/debug/browser/variablesView'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { dispose } from 'vs/base/common/lifecycle'; +import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; const MAX_VALUE_RENDER_LENGTH_IN_VIEWLET = 1024; -export class WatchExpressionsView extends ViewletPanel { +export class WatchExpressionsView extends ViewletPane { private onWatchExpressionsUpdatedScheduler: RunOnceScheduler; private needsRefresh = false; @@ -48,7 +49,7 @@ export class WatchExpressionsView extends ViewletPanel { @IConfigurationService configurationService: IConfigurationService, @IContextKeyService contextKeyService: IContextKeyService, ) { - super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: nls.localize('watchExpressionsSection', "Watch Expressions Section") }, keybindingService, contextMenuService, configurationService, contextKeyService); + super({ ...(options as IViewletPaneOptions), ariaHeaderLabel: nls.localize('watchExpressionsSection', "Watch Expressions Section") }, keybindingService, contextMenuService, configurationService, contextKeyService); this.onWatchExpressionsUpdatedScheduler = new RunOnceScheduler(() => { this.needsRefresh = false; @@ -61,13 +62,16 @@ export class WatchExpressionsView extends ViewletPanel { const treeContainer = renderViewTree(container); const expressionsRenderer = this.instantiationService.createInstance(WatchExpressionsRenderer); - this.tree = this.instantiationService.createInstance(WorkbenchAsyncDataTree, 'WatchExpressions', treeContainer, new WatchExpressionsDelegate(), [expressionsRenderer, this.instantiationService.createInstance(VariablesRenderer)], + this.tree = this.instantiationService.createInstance>(WorkbenchAsyncDataTree, 'WatchExpressions', treeContainer, new WatchExpressionsDelegate(), [expressionsRenderer, this.instantiationService.createInstance(VariablesRenderer)], new WatchExpressionsDataSource(), { ariaLabel: nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'watchAriaTreeLabel' }, "Debug Watch Expressions"), accessibilityProvider: new WatchExpressionsAccessibilityProvider(), identityProvider: { getId: (element: IExpression) => element.getId() }, keyboardNavigationLabelProvider: { getKeyboardNavigationLabel: (e: IExpression) => e }, dnd: new WatchExpressionsDragAndDrop(this.debugService), + overrideStyles: { + listBackground: SIDE_BAR_BACKGROUND + } }); this.tree.setInput(this.debugService); @@ -75,18 +79,21 @@ export class WatchExpressionsView extends ViewletPanel { if (this.toolbar) { const addWatchExpressionAction = new AddWatchExpressionAction(AddWatchExpressionAction.ID, AddWatchExpressionAction.LABEL, this.debugService, this.keybindingService); - const collapseAction = new CollapseAction(this.tree, true, 'explorer-action collapse-explorer'); + const collapseAction = new CollapseAction(this.tree, true, 'explorer-action codicon-collapse-all'); const removeAllWatchExpressionsAction = new RemoveAllWatchExpressionsAction(RemoveAllWatchExpressionsAction.ID, RemoveAllWatchExpressionsAction.LABEL, this.debugService, this.keybindingService); this.toolbar.setActions([addWatchExpressionAction, collapseAction, removeAllWatchExpressionsAction])(); } this._register(this.tree.onContextMenu(e => this.onContextMenu(e))); this._register(this.tree.onMouseDblClick(e => this.onMouseDblClick(e))); - this._register(this.debugService.getModel().onDidChangeWatchExpressions(we => { + this._register(this.debugService.getModel().onDidChangeWatchExpressions(async we => { if (!this.isBodyVisible()) { this.needsRefresh = true; } else { - this.tree.updateChildren(); + await this.tree.updateChildren(); + if (we instanceof Expression) { + this.tree.reveal(we); + } } })); this._register(this.debugService.getViewModel().onDidFocusStackFrame(() => { diff --git a/src/vs/workbench/contrib/debug/common/abstractDebugAdapter.ts b/src/vs/workbench/contrib/debug/common/abstractDebugAdapter.ts index 5c0c4ed2d73c..be8cb3b8d2d5 100644 --- a/src/vs/workbench/contrib/debug/common/abstractDebugAdapter.ts +++ b/src/vs/workbench/contrib/debug/common/abstractDebugAdapter.ts @@ -18,13 +18,11 @@ export abstract class AbstractDebugAdapter implements IDebugAdapter { private requestCallback: ((request: DebugProtocol.Request) => void) | undefined; private eventCallback: ((request: DebugProtocol.Event) => void) | undefined; private messageCallback: ((message: DebugProtocol.ProtocolMessage) => void) | undefined; - protected readonly _onError: Emitter; - protected readonly _onExit: Emitter; + protected readonly _onError = new Emitter(); + protected readonly _onExit = new Emitter(); constructor() { this.sequence = 1; - this._onError = new Emitter(); - this._onExit = new Emitter(); } abstract startSession(): Promise; diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index e4f01e01cea0..87730324548c 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -9,7 +9,7 @@ import severity from 'vs/base/common/severity'; import { Event } from 'vs/base/common/event'; import { IJSONSchemaSnippet } from 'vs/base/common/jsonSchema'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IEditorContribution } from 'vs/editor/common/editorCommon'; +import * as editorCommon from 'vs/editor/common/editorCommon'; import { ITextModel as EditorIModel } from 'vs/editor/common/model'; import { IEditor, ITextEditor } from 'vs/workbench/common/editor'; import { Position, IPosition } from 'vs/editor/common/core/position'; @@ -40,6 +40,8 @@ export const DEBUG_SERVICE_ID = 'debugService'; export const CONTEXT_DEBUG_TYPE = new RawContextKey('debugType', undefined); export const CONTEXT_DEBUG_CONFIGURATION_TYPE = new RawContextKey('debugConfigurationType', undefined); export const CONTEXT_DEBUG_STATE = new RawContextKey('debugState', 'inactive'); +export const CONTEXT_DEBUG_UX_KEY = 'debugUx'; +export const CONTEXT_DEBUG_UX = new RawContextKey(CONTEXT_DEBUG_UX_KEY, 'default'); export const CONTEXT_IN_DEBUG_MODE = new RawContextKey('inDebugMode', false); export const CONTEXT_IN_DEBUG_REPL = new RawContextKey('inDebugRepl', false); export const CONTEXT_BREAKPOINT_WIDGET_VISIBLE = new RawContextKey('breakpointWidgetVisible', false); @@ -468,6 +470,7 @@ export interface IDebugConfiguration { focusWindowOnBreak: boolean; onTaskErrors: 'debugAnyway' | 'showErrors' | 'prompt'; showBreakpointsInOverviewRuler: boolean; + showInlineBreakpointCandidates: boolean; } export interface IGlobalConfig { @@ -544,12 +547,17 @@ export interface IDebugAdapterServer { readonly host?: string; } -export interface IDebugAdapterImplementation { +export interface IDebugAdapterInlineImpl extends IDisposable { + readonly onSendMessage: Event; + handleMessage(message: DebugProtocol.Message): void; +} + +export interface IDebugAdapterImpl { readonly type: 'implementation'; - readonly implementation: any; + readonly implementation: IDebugAdapterInlineImpl; } -export type IAdapterDescriptor = IDebugAdapterExecutable | IDebugAdapterServer | IDebugAdapterImplementation; +export type IAdapterDescriptor = IDebugAdapterExecutable | IDebugAdapterServer | IDebugAdapterImpl; export interface IPlatformSpecificAdapterContribution { program?: string; @@ -628,8 +636,11 @@ export interface IConfigurationManager { */ onDidSelectConfiguration: Event; + onDidRegisterDebugger: Event; + activateDebuggers(activationEvent: string, debugType?: string): Promise; + getDebuggerLabelsForEditor(editor: editorCommon.IEditor | undefined): string[]; hasDebugConfigurationProvider(debugType: string): boolean; registerDebugConfigurationProvider(debugConfigurationProvider: IDebugConfigurationProvider): IDisposable; @@ -861,12 +872,12 @@ export const enum BreakpointWidgetContext { LOG_MESSAGE = 2 } -export interface IDebugEditorContribution extends IEditorContribution { +export interface IDebugEditorContribution extends editorCommon.IEditorContribution { showHover(range: Range, focus: boolean): Promise; addLaunchConfiguration(): Promise; } -export interface IBreakpointEditorContribution extends IEditorContribution { +export interface IBreakpointEditorContribution extends editorCommon.IEditorContribution { showBreakpointWidget(lineNumber: number, column: number | undefined, context?: BreakpointWidgetContext): void; closeBreakpointWidget(): void; } diff --git a/src/vs/workbench/contrib/debug/common/debugModel.ts b/src/vs/workbench/contrib/debug/common/debugModel.ts index 6abab2308252..d1b6e39ffbd4 100644 --- a/src/vs/workbench/contrib/debug/common/debugModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugModel.ts @@ -157,6 +157,11 @@ export class ExpressionContainer implements IExpressionContainer { this.session = session; try { const response = await session.evaluate(expression, stackFrame ? stackFrame.frameId : undefined, context); + if (response && response.success === false) { + this.value = response.message || ''; + return false; + } + if (response && response.body) { this.value = response.body.result || ''; this.reference = response.body.variablesReference; @@ -810,9 +815,9 @@ export class DebugModel implements IDebugModel { private toDispose: lifecycle.IDisposable[]; private schedulers = new Map(); private breakpointsActivated = true; - private readonly _onDidChangeBreakpoints: Emitter; - private readonly _onDidChangeCallStack: Emitter; - private readonly _onDidChangeWatchExpressions: Emitter; + private readonly _onDidChangeBreakpoints = new Emitter(); + private readonly _onDidChangeCallStack = new Emitter(); + private readonly _onDidChangeWatchExpressions = new Emitter(); constructor( private breakpoints: Breakpoint[], @@ -824,9 +829,6 @@ export class DebugModel implements IDebugModel { ) { this.sessions = []; this.toDispose = []; - this._onDidChangeBreakpoints = new Emitter(); - this._onDidChangeCallStack = new Emitter(); - this._onDidChangeWatchExpressions = new Emitter(); } getId(): string { @@ -1077,7 +1079,7 @@ export class DebugModel implements IDebugModel { if (first.column && second.column) { return first.column - second.column; } - return -1; + return 1; } return first.lineNumber - second.lineNumber; @@ -1093,6 +1095,9 @@ export class DebugModel implements IDebugModel { } element.enabled = enable; + if (enable) { + this.breakpointsActivated = true; + } this._onDidChangeBreakpoints.fire({ changed: changed }); } @@ -1119,6 +1124,9 @@ export class DebugModel implements IDebugModel { } dbp.enabled = enable; }); + if (enable) { + this.breakpointsActivated = true; + } this._onDidChangeBreakpoints.fire({ changed: changed }); } diff --git a/src/vs/workbench/contrib/debug/common/debugUtils.ts b/src/vs/workbench/contrib/debug/common/debugUtils.ts index d6ca8b95b882..2cd10f460ed8 100644 --- a/src/vs/workbench/contrib/debug/common/debugUtils.ts +++ b/src/vs/workbench/contrib/debug/common/debugUtils.ts @@ -28,7 +28,15 @@ export function isSessionAttach(session: IDebugSession): boolean { } export function isExtensionHostDebugging(config: IConfig) { - return config.type && equalsIgnoreCase(config.type === 'vslsShare' ? (config).adapterProxy.configuration.type : config.type, 'extensionhost'); + if (!config.type) { + return false; + } + + const type = config.type === 'vslsShare' + ? (config).adapterProxy.configuration.type + : config.type; + + return equalsIgnoreCase(type, 'extensionhost') || equalsIgnoreCase(type, 'pwa-extensionhost'); } // only a debugger contributions with a label, program, or runtime attribute is considered a "defining" or "main" debugger contribution diff --git a/src/vs/workbench/contrib/debug/common/debugViewModel.ts b/src/vs/workbench/contrib/debug/common/debugViewModel.ts index cf696c3aebc8..71f1b10eb64d 100644 --- a/src/vs/workbench/contrib/debug/common/debugViewModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugViewModel.ts @@ -17,9 +17,9 @@ export class ViewModel implements IViewModel { private _focusedThread: IThread | undefined; private selectedExpression: IExpression | undefined; private selectedFunctionBreakpoint: IFunctionBreakpoint | undefined; - private readonly _onDidFocusSession: Emitter; - private readonly _onDidFocusStackFrame: Emitter<{ stackFrame: IStackFrame | undefined, explicit: boolean }>; - private readonly _onDidSelectExpression: Emitter; + private readonly _onDidFocusSession = new Emitter(); + private readonly _onDidFocusStackFrame = new Emitter<{ stackFrame: IStackFrame | undefined, explicit: boolean }>(); + private readonly _onDidSelectExpression = new Emitter(); private multiSessionView: boolean; private expressionSelectedContextKey: IContextKey; private breakpointSelectedContextKey: IContextKey; @@ -30,9 +30,6 @@ export class ViewModel implements IViewModel { private jumpToCursorSupported: IContextKey; constructor(contextKeyService: IContextKeyService) { - this._onDidFocusSession = new Emitter(); - this._onDidFocusStackFrame = new Emitter<{ stackFrame: IStackFrame, explicit: boolean }>(); - this._onDidSelectExpression = new Emitter(); this.multiSessionView = false; this.expressionSelectedContextKey = CONTEXT_EXPRESSION_SELECTED.bindTo(contextKeyService); this.breakpointSelectedContextKey = CONTEXT_BREAKPOINT_SELECTED.bindTo(contextKeyService); diff --git a/src/vs/workbench/contrib/debug/common/replModel.ts b/src/vs/workbench/contrib/debug/common/replModel.ts index 8f34880c6941..c50c3eb5b137 100644 --- a/src/vs/workbench/contrib/debug/common/replModel.ts +++ b/src/vs/workbench/contrib/debug/common/replModel.ts @@ -98,10 +98,23 @@ export class ReplEvaluationInput implements IReplElement { } export class ReplEvaluationResult extends ExpressionContainer implements IReplElement { + private _available = true; + + get available(): boolean { + return this._available; + } + constructor() { super(undefined, undefined, 0, generateUuid()); } + async evaluateExpression(expression: string, session: IDebugSession | undefined, stackFrame: IStackFrame | undefined, context: string): Promise { + const result = await super.evaluateExpression(expression, session, stackFrame, context); + this._available = result; + + return result; + } + toString(): string { return `${this.value}`; } @@ -136,6 +149,7 @@ export class ReplModel { const previousElement = this.replElements.length ? this.replElements[this.replElements.length - 1] : undefined; if (previousElement instanceof SimpleReplElement && previousElement.severity === sev && !endsWith(previousElement.value, '\n') && !endsWith(previousElement.value, '\r\n')) { previousElement.value += data; + this._onDidChangeElements.fire(); } else { const element = new SimpleReplElement(session, `topReplElement:${topReplElementCounter++}`, data, sev, source); this.addReplElement(element); diff --git a/src/vs/workbench/contrib/debug/electron-browser/extensionHostDebugService.ts b/src/vs/workbench/contrib/debug/electron-browser/extensionHostDebugService.ts index 2f4212365d59..44baf87d9574 100644 --- a/src/vs/workbench/contrib/debug/electron-browser/extensionHostDebugService.ts +++ b/src/vs/workbench/contrib/debug/electron-browser/extensionHostDebugService.ts @@ -7,22 +7,14 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug'; import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; import { ExtensionHostDebugChannelClient, ExtensionHostDebugBroadcastChannel } from 'vs/platform/debug/common/extensionHostDebugIpc'; -import { IProcessEnvironment } from 'vs/base/common/platform'; -import { IElectronService } from 'vs/platform/electron/node/electron'; export class ExtensionHostDebugService extends ExtensionHostDebugChannelClient { constructor( - @IMainProcessService readonly mainProcessService: IMainProcessService, - @IElectronService private readonly electronService: IElectronService + @IMainProcessService readonly mainProcessService: IMainProcessService ) { super(mainProcessService.getChannel(ExtensionHostDebugBroadcastChannel.ChannelName)); } - - openExtensionDevelopmentHostWindow(args: string[], env: IProcessEnvironment): Promise { - // TODO@Isidor move into debug IPC channel (https://github.com/microsoft/vscode/issues/81060) - return this.electronService.openExtensionDevelopmentHostWindow(args, env); - } } registerSingleton(IExtensionHostDebugService, ExtensionHostDebugService, true); diff --git a/src/vs/workbench/contrib/debug/node/debugAdapter.ts b/src/vs/workbench/contrib/debug/node/debugAdapter.ts index d9b26f582dce..c963625da102 100644 --- a/src/vs/workbench/contrib/debug/node/debugAdapter.ts +++ b/src/vs/workbench/contrib/debug/node/debugAdapter.ts @@ -214,14 +214,14 @@ export class ExecutableDebugAdapter extends StreamDebugAdapter { this._onExit.fire(code); }); - this.serverProcess.stdout.on('close', () => { + this.serverProcess.stdout!.on('close', () => { this._onError.fire(new Error('read error')); }); - this.serverProcess.stdout.on('error', error => { + this.serverProcess.stdout!.on('error', error => { this._onError.fire(error); }); - this.serverProcess.stdin.on('error', error => { + this.serverProcess.stdin!.on('error', error => { this._onError.fire(error); }); @@ -231,7 +231,7 @@ export class ExecutableDebugAdapter extends StreamDebugAdapter { // this.serverProcess.stdout.on('data', (data: string) => { // console.log('%c' + sanitize(data), 'background: #ddd; font-style: italic;'); // }); - this.serverProcess.stderr.on('data', (data: string) => { + this.serverProcess.stderr!.on('data', (data: string) => { const channel = outputService.getChannel(ExtensionsChannelId); if (channel) { channel.append(sanitize(data)); @@ -240,7 +240,7 @@ export class ExecutableDebugAdapter extends StreamDebugAdapter { } // finally connect to the DA - this.connect(this.serverProcess.stdout, this.serverProcess.stdin); + this.connect(this.serverProcess.stdout!, this.serverProcess.stdin!); } catch (err) { this._onError.fire(err); diff --git a/src/vs/workbench/contrib/debug/node/terminals.ts b/src/vs/workbench/contrib/debug/node/terminals.ts index 32f31799d5b8..224c45f6649f 100644 --- a/src/vs/workbench/contrib/debug/node/terminals.ts +++ b/src/vs/workbench/contrib/debug/node/terminals.ts @@ -29,34 +29,49 @@ export function runInExternalTerminal(args: DebugProtocol.RunInTerminalRequestAr } } -export function hasChildProcesses(processId: number): boolean { +function spawnAsPromised(command: string, args: string[]): Promise { + return new Promise((resolve, reject) => { + let stdout = ''; + const child = cp.spawn(command, args); + if (child.pid) { + child.stdout.on('data', (data: Buffer) => { + stdout += data.toString(); + }); + } + child.on('error', err => { + reject(err); + }); + child.on('close', code => { + resolve(stdout); + }); + }); +} + +export function hasChildProcesses(processId: number | undefined): Promise { if (processId) { - try { - // if shell has at least one child process, assume that shell is busy - if (env.isWindows) { - const result = cp.spawnSync('wmic', ['process', 'get', 'ParentProcessId']); - if (result.stdout) { - const pids = result.stdout.toString().split('\r\n'); - if (!pids.some(p => parseInt(p) === processId)) { - return false; - } - } - } else { - const result = cp.spawnSync('/usr/bin/pgrep', ['-lP', String(processId)]); - if (result.stdout) { - const r = result.stdout.toString().trim(); - if (r.length === 0 || r.indexOf(' tmux') >= 0) { // ignore 'tmux'; see #43683 - return false; - } + // if shell has at least one child process, assume that shell is busy + if (env.isWindows) { + return spawnAsPromised('wmic', ['process', 'get', 'ParentProcessId']).then(stdout => { + const pids = stdout.split('\r\n'); + return pids.some(p => parseInt(p) === processId); + }, error => { + return true; + }); + } else { + return spawnAsPromised('/usr/bin/pgrep', ['-lP', String(processId)]).then(stdout => { + const r = stdout.trim(); + if (r.length === 0 || r.indexOf(' tmux') >= 0) { // ignore 'tmux'; see #43683 + return false; + } else { + return true; } - } - } - catch (e) { - // silently ignore + }, error => { + return true; + }); } } // fall back to safe side - return true; + return Promise.resolve(true); } const enum ShellType { cmd, powershell, bash } @@ -90,8 +105,6 @@ export function prepareCommand(args: DebugProtocol.RunInTerminalRequestArguments shellType = ShellType.cmd; } else if (shell.indexOf('bash') >= 0) { shellType = ShellType.bash; - } else if (shell.indexOf('git\\bin\\bash.exe') >= 0) { - shellType = ShellType.bash; } let quote: (s: string) => string; diff --git a/src/vs/workbench/contrib/emmet/browser/actions/expandAbbreviation.ts b/src/vs/workbench/contrib/emmet/browser/actions/expandAbbreviation.ts index 4a502c0d0855..da5b886f2617 100644 --- a/src/vs/workbench/contrib/emmet/browser/actions/expandAbbreviation.ts +++ b/src/vs/workbench/contrib/emmet/browser/actions/expandAbbreviation.ts @@ -29,13 +29,12 @@ class ExpandAbbreviationAction extends EmmetEditorAction { ), weight: KeybindingWeight.EditorContrib }, - // {{SQL CARBON EDIT}} - Remove from menu - // menubarOpts: { - // menuId: MenuId.MenubarEditMenu, - // group: '5_insert', - // title: nls.localize({ key: 'miEmmetExpandAbbreviation', comment: ['&& denotes a mnemonic'] }, "Emmet: E&&xpand Abbreviation"), - // order: 3 - // } + /*menuOpts: { {{SQL CARBON EDIT}} - Remove from menu + menuId: MenuId.MenubarEditMenu, + group: '5_insert', + title: nls.localize({ key: 'miEmmetExpandAbbreviation', comment: ['&& denotes a mnemonic'] }, "Emmet: E&&xpand Abbreviation"), + order: 3 + }*/ }); } diff --git a/src/vs/workbench/contrib/emmet/browser/actions/showEmmetCommands.ts b/src/vs/workbench/contrib/emmet/browser/actions/showEmmetCommands.ts index 7e66a58198fb..4eb3eb766e32 100644 --- a/src/vs/workbench/contrib/emmet/browser/actions/showEmmetCommands.ts +++ b/src/vs/workbench/contrib/emmet/browser/actions/showEmmetCommands.ts @@ -21,13 +21,12 @@ class ShowEmmetCommandsAction extends EditorAction { label: nls.localize('showEmmetCommands', "Show Emmet Commands"), alias: 'Show Emmet Commands', precondition: EditorContextKeys.writable, - // {{SQL CARBON EDIT}} - Remove from menu - // menubarOpts: { - // menuId: MenuId.MenubarEditMenu, - // group: '5_insert', - // title: nls.localize({ key: 'miShowEmmetCommands', comment: ['&& denotes a mnemonic'] }, "E&&mmet..."), - // order: 4 - // } + /*menuOpts: { {{SQL CARBON EDIT}} - Remove from menu + menuId: MenuId.MenubarEditMenu, + group: '5_insert', + title: nls.localize({ key: 'miShowEmmetCommands', comment: ['&& denotes a mnemonic'] }, "E&&mmet..."), + order: 4 + }*/ }); } diff --git a/src/vs/workbench/contrib/experiments/common/experimentService.ts b/src/vs/workbench/contrib/experiments/common/experimentService.ts index 04c6afaac8e7..97d5a685d78d 100644 --- a/src/vs/workbench/contrib/experiments/common/experimentService.ts +++ b/src/vs/workbench/contrib/experiments/common/experimentService.ts @@ -6,7 +6,6 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { Emitter, Event } from 'vs/base/common/event'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { ITelemetryService, lastSessionDateStorageKey } from 'vs/platform/telemetry/common/telemetry'; import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -20,7 +19,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { distinct } from 'vs/base/common/arrays'; import { ExtensionType } from 'vs/platform/extensions/common/extensions'; import { IProductService } from 'vs/platform/product/common/productService'; -import { IWorkspaceStatsService } from 'vs/workbench/contrib/stats/common/workspaceStats'; +import { IWorkspaceTagsService } from 'vs/workbench/contrib/tags/common/workspaceTags'; export const enum ExperimentState { Evaluating, @@ -120,13 +119,12 @@ export class ExperimentService extends Disposable implements IExperimentService @IStorageService private readonly storageService: IStorageService, @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, @ITextFileService private readonly textFileService: ITextFileService, - @IEnvironmentService private readonly environmentService: IEnvironmentService, @ITelemetryService private readonly telemetryService: ITelemetryService, @ILifecycleService private readonly lifecycleService: ILifecycleService, @IRequestService private readonly requestService: IRequestService, @IConfigurationService private readonly configurationService: IConfigurationService, @IProductService private readonly productService: IProductService, - @IWorkspaceStatsService private readonly workspaceStatsService: IWorkspaceStatsService + @IWorkspaceTagsService private readonly workspaceTagsService: IWorkspaceTagsService ) { super(); @@ -169,18 +167,22 @@ export class ExperimentService extends Disposable implements IExperimentService this.storageService.store(storageKey, JSON.stringify(experimentState), StorageScope.GLOBAL); } - protected getExperiments(): Promise { + protected async getExperiments(): Promise { if (!this.productService.experimentsUrl || this.configurationService.getValue('workbench.enableExperiments') === false) { - return Promise.resolve([]); + return []; } - return this.requestService.request({ type: 'GET', url: this.productService.experimentsUrl }, CancellationToken.None).then(context => { + + try { + const context = await this.requestService.request({ type: 'GET', url: this.productService.experimentsUrl }, CancellationToken.None); if (context.res.statusCode !== 200) { - return Promise.resolve(null); + return null; } - return asJson(context).then((result: any) => { - return result && Array.isArray(result['experiments']) ? result['experiments'] : []; - }); - }, () => Promise.resolve(null)); + const result: any = await asJson(context); + return result && Array.isArray(result['experiments']) ? result['experiments'] : []; + } catch (_e) { + // Bad request or invalid JSON + return null; + } } private loadExperiments(): Promise { @@ -333,7 +335,7 @@ export class ExperimentService extends Disposable implements IExperimentService return Promise.resolve(ExperimentState.NoRun); } - if (this.environmentService.appQuality === 'stable' && condition.insidersOnly === true) { + if (this.productService.quality === 'stable' && condition.insidersOnly === true) { return Promise.resolve(ExperimentState.NoRun); } @@ -422,11 +424,11 @@ export class ExperimentService extends Disposable implements IExperimentService filePathCheck = match(fileEdits.filePathPattern, event.resource.fsPath); } if (Array.isArray(fileEdits.workspaceIncludes) && fileEdits.workspaceIncludes.length) { - const tags = await this.workspaceStatsService.getTags(); + const tags = await this.workspaceTagsService.getTags(); workspaceCheck = !!tags && fileEdits.workspaceIncludes.some(x => !!tags[x]); } if (workspaceCheck && Array.isArray(fileEdits.workspaceExcludes) && fileEdits.workspaceExcludes.length) { - const tags = await this.workspaceStatsService.getTags(); + const tags = await this.workspaceTagsService.getTags(); workspaceCheck = !!tags && !fileEdits.workspaceExcludes.some(x => !!tags[x]); } if (filePathCheck && workspaceCheck) { diff --git a/src/vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts b/src/vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts index d8e7eda424c9..3be4c05fe63b 100644 --- a/src/vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts +++ b/src/vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts @@ -6,7 +6,6 @@ import * as assert from 'assert'; import { ExperimentActionType, ExperimentState, IExperiment, ExperimentService } from 'vs/workbench/contrib/experiments/common/experimentService'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { TestLifecycleService } from 'vs/workbench/test/workbenchTestServices'; import { IExtensionManagementService, DidInstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionEvent, IExtensionIdentifier, ILocalExtension @@ -27,6 +26,7 @@ import { URI } from 'vs/base/common/uri'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { ExtensionType } from 'vs/platform/extensions/common/extensions'; +import { IProductService } from 'vs/platform/product/common/productService'; interface ExperimentSettings { enabled?: boolean; @@ -88,7 +88,7 @@ suite('Experiment Service', () => { instantiationService.stub(IStorageService, >{ get: (a: string, b: StorageScope, c?: string) => c, getBoolean: (a: string, b: StorageScope, c?: boolean) => c, store: () => { }, remove: () => { } }); setup(() => { - instantiationService.stub(IEnvironmentService, {}); + instantiationService.stub(IProductService, {}); instantiationService.stub(IStorageService, >{ get: (a: string, b: StorageScope, c?: string) => c, getBoolean: (a: string, b: StorageScope, c?: boolean) => c, store: () => { }, remove: () => { } }); }); @@ -174,7 +174,7 @@ suite('Experiment Service', () => { ] }; - instantiationService.stub(IEnvironmentService, { appQuality: 'stable' }); + instantiationService.stub(IProductService, { quality: 'stable' }); testObject = instantiationService.createInstance(TestExperimentService); return testObject.getExperimentById('experiment1').then(result => { assert.equal(result.enabled, true); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index 2f78e35b76c2..9e10aa8cc3b9 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -30,7 +30,7 @@ import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { CombinedInstallAction, UpdateAction, ExtensionEditorDropDownAction, ReloadAction, MaliciousStatusLabelAction, IgnoreExtensionRecommendationAction, UndoIgnoreExtensionRecommendationAction, EnableDropDownAction, DisableDropDownAction, StatusLabelAction, SetFileIconThemeAction, SetColorThemeAction, RemoteInstallAction, ExtensionToolTipAction, SystemDisabledWarningAction, LocalInstallAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; -import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IOpenerService, matchesScheme } from 'vs/platform/opener/common/opener'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { KeybindingLabel } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -60,6 +60,7 @@ import { renderMarkdownDocument } from 'vs/workbench/contrib/markdown/common/mar import { IModeService } from 'vs/editor/common/services/modeService'; import { TokenizationRegistry } from 'vs/editor/common/modes'; import { generateTokensCSSForColorMap } from 'vs/editor/common/modes/supports/tokenization'; +import { editorBackground } from 'vs/platform/theme/common/colorRegistry'; import { ExtensionsViewlet } from 'vs/workbench/contrib/extensions/browser/extensionsViewlet'; function removeEmbeddedSVGs(documentContent: string): string { @@ -471,10 +472,8 @@ export class ExtensionEditor extends BaseEditor { private setSubText(extension: IExtension, reloadAction: ReloadAction, template: IExtensionEditorTemplate): void { hide(template.subtextContainer); - const ignoreAction = this.instantiationService.createInstance(IgnoreExtensionRecommendationAction); - const undoIgnoreAction = this.instantiationService.createInstance(UndoIgnoreExtensionRecommendationAction); - ignoreAction.extension = extension; - undoIgnoreAction.extension = extension; + const ignoreAction = this.instantiationService.createInstance(IgnoreExtensionRecommendationAction, extension); + const undoIgnoreAction = this.instantiationService.createInstance(UndoIgnoreExtensionRecommendationAction, extension); ignoreAction.enabled = false; undoIgnoreAction.enabled = false; @@ -628,9 +627,10 @@ export class ExtensionEditor extends BaseEditor { if (!link) { return; } - // Whitelist supported schemes for links - if ([Schemas.http, Schemas.https, Schemas.mailto].indexOf(link.scheme) >= 0 || (link.scheme === 'command' && link.path === ShowCurrentReleaseNotesActionId)) { + if (matchesScheme(link, Schemas.http) || matchesScheme(link, Schemas.https) || matchesScheme(link, Schemas.mailto) + || (matchesScheme(link, Schemas.command) && URI.parse(link).path === ShowCurrentReleaseNotesActionId) + ) { this.openerService.open(link); } }, null, this.contentDisposables)); @@ -663,6 +663,8 @@ export class ExtensionEditor extends BaseEditor { body { padding: 10px 20px; line-height: 22px; + max-width: 780px; + margin: 0 auto; } img { @@ -865,6 +867,7 @@ export class ExtensionEditor extends BaseEditor { const renders = [ this.renderSettings(content, manifest, layout), this.renderCommands(content, manifest, layout), + this.renderCodeActions(content, manifest, layout), this.renderLanguages(content, manifest, layout), this.renderColorThemes(content, manifest, layout), this.renderIconThemes(content, manifest, layout), @@ -907,7 +910,11 @@ export class ExtensionEditor extends BaseEditor { append(template.content, scrollableContent.getDomNode()); this.contentDisposables.add(scrollableContent); - const dependenciesTree = this.instantiationService.createInstance(ExtensionsTree, new ExtensionData(extension, null, extension => extension.dependencies || [], this.extensionsWorkbenchService), content); + const dependenciesTree = this.instantiationService.createInstance(ExtensionsTree, + new ExtensionData(extension, null, extension => extension.dependencies || [], this.extensionsWorkbenchService), content, + { + listBackground: editorBackground + }); const layout = () => { scrollableContent.scanDomNode(); const scrollDimensions = scrollableContent.getScrollDimensions(); @@ -927,7 +934,11 @@ export class ExtensionEditor extends BaseEditor { append(template.content, scrollableContent.getDomNode()); this.contentDisposables.add(scrollableContent); - const extensionsPackTree = this.instantiationService.createInstance(ExtensionsTree, new ExtensionData(extension, null, extension => extension.extensionPack || [], this.extensionsWorkbenchService), content); + const extensionsPackTree = this.instantiationService.createInstance(ExtensionsTree, + new ExtensionData(extension, null, extension => extension.extensionPack || [], this.extensionsWorkbenchService), content, + { + listBackground: editorBackground + }); const layout = () => { scrollableContent.scanDomNode(); const scrollDimensions = scrollableContent.getScrollDimensions(); @@ -942,8 +953,7 @@ export class ExtensionEditor extends BaseEditor { } private renderSettings(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean { - const contributes = manifest.contributes; - const configuration = contributes && contributes.configuration; + const configuration = manifest.contributes?.configuration; let properties: any = {}; if (Array.isArray(configuration)) { configuration.forEach(config => { @@ -979,9 +989,7 @@ export class ExtensionEditor extends BaseEditor { } private renderDebuggers(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean { - const contributes = manifest.contributes; - const contrib = contributes && contributes.debuggers || []; - + const contrib = manifest.contributes?.debuggers || []; if (!contrib.length) { return false; } @@ -1004,10 +1012,9 @@ export class ExtensionEditor extends BaseEditor { } private renderViewContainers(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean { - const contributes = manifest.contributes; - const contrib = contributes && contributes.viewsContainers || {}; + const contrib = manifest.contributes?.viewsContainers || {}; - let viewContainers = Object.keys(contrib).reduce((result, location) => { + const viewContainers = Object.keys(contrib).reduce((result, location) => { let viewContainersForLocation: IViewContainer[] = contrib[location]; result.push(...viewContainersForLocation.map(viewContainer => ({ ...viewContainer, location }))); return result; @@ -1030,10 +1037,9 @@ export class ExtensionEditor extends BaseEditor { } private renderViews(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean { - const contributes = manifest.contributes; - const contrib = contributes && contributes.views || {}; + const contrib = manifest.contributes?.views || {}; - let views = Object.keys(contrib).reduce((result, location) => { + const views = Object.keys(contrib).reduce((result, location) => { let viewsForLocation: IView[] = contrib[location]; result.push(...viewsForLocation.map(view => ({ ...view, location }))); return result; @@ -1056,9 +1062,7 @@ export class ExtensionEditor extends BaseEditor { } private renderLocalizations(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean { - const contributes = manifest.contributes; - const localizations = contributes && contributes.localizations || []; - + const localizations = manifest.contributes?.localizations || []; if (!localizations.length) { return false; } @@ -1076,7 +1080,7 @@ export class ExtensionEditor extends BaseEditor { } private renderCustomEditors(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean { - const webviewEditors = (manifest.contributes && manifest.contributes.webviewEditors) || []; + const webviewEditors = manifest.contributes?.webviewEditors || []; if (!webviewEditors.length) { return false; } @@ -1100,10 +1104,39 @@ export class ExtensionEditor extends BaseEditor { return true; } - private renderColorThemes(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean { - const contributes = manifest.contributes; - const contrib = contributes && contributes.themes || []; + private renderCodeActions(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean { + const codeActions = manifest.contributes?.codeActions || []; + if (!codeActions.length) { + return false; + } + + const flatActions = arrays.flatten( + codeActions.map(contribution => + contribution.actions.map(action => ({ ...action, languages: contribution.languages })))); + + const details = $('details', { open: true, ontoggle: onDetailsToggle }, + $('summary', { tabindex: '0' }, localize('codeActions', "Code Actions ({0})", flatActions.length)), + $('table', undefined, + $('tr', undefined, + $('th', undefined, localize('codeActions.title', "Title")), + $('th', undefined, localize('codeActions.kind', "Kind")), + $('th', undefined, localize('codeActions.description', "Description")), + $('th', undefined, localize('codeActions.languages', "Languages"))), + ...flatActions.map(action => + $('tr', undefined, + $('td', undefined, action.title), + $('td', undefined, $('code', undefined, action.kind)), + $('td', undefined, action.description ?? ''), + $('td', undefined, ...action.languages.map(language => $('code', undefined, language))))) + ) + ); + append(container, details); + return true; + } + + private renderColorThemes(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean { + const contrib = manifest.contributes?.themes || []; if (!contrib.length) { return false; } @@ -1118,9 +1151,7 @@ export class ExtensionEditor extends BaseEditor { } private renderIconThemes(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean { - const contributes = manifest.contributes; - const contrib = contributes && contributes.iconThemes || []; - + const contrib = manifest.contributes?.iconThemes || []; if (!contrib.length) { return false; } @@ -1135,10 +1166,8 @@ export class ExtensionEditor extends BaseEditor { } private renderColors(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean { - const contributes = manifest.contributes; - const colors = contributes && contributes.colors; - - if (!(colors && colors.length)) { + const colors = manifest.contributes?.colors || []; + if (!colors.length) { return false; } @@ -1180,9 +1209,7 @@ export class ExtensionEditor extends BaseEditor { private renderJSONValidation(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean { - const contributes = manifest.contributes; - const contrib = contributes && contributes.jsonValidation || []; - + const contrib = manifest.contributes?.jsonValidation || []; if (!contrib.length) { return false; } @@ -1204,8 +1231,7 @@ export class ExtensionEditor extends BaseEditor { } private renderCommands(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean { - const contributes = manifest.contributes; - const rawCommands = contributes && contributes.commands || []; + const rawCommands = manifest.contributes?.commands || []; const commands = rawCommands.map(c => ({ id: c.command, title: c.title, @@ -1215,7 +1241,7 @@ export class ExtensionEditor extends BaseEditor { const byId = arrays.index(commands, c => c.id); - const menus = contributes && contributes.menus || {}; + const menus = manifest.contributes?.menus || {}; Object.keys(menus).forEach(context => { menus[context].forEach(menu => { @@ -1231,7 +1257,7 @@ export class ExtensionEditor extends BaseEditor { }); }); - const rawKeybindings = contributes && contributes.keybindings ? (Array.isArray(contributes.keybindings) ? contributes.keybindings : [contributes.keybindings]) : []; + const rawKeybindings = manifest.contributes?.keybindings ? (Array.isArray(manifest.contributes.keybindings) ? manifest.contributes.keybindings : [manifest.contributes.keybindings]) : []; rawKeybindings.forEach(rawKeybinding => { const keybinding = this.resolveKeybinding(rawKeybinding); @@ -1285,7 +1311,7 @@ export class ExtensionEditor extends BaseEditor { private renderLanguages(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean { const contributes = manifest.contributes; - const rawLanguages = contributes && contributes.languages || []; + const rawLanguages = contributes?.languages || []; const languages = rawLanguages.map(l => ({ id: l.id, name: (l.aliases || [])[0] || l.id, @@ -1296,8 +1322,7 @@ export class ExtensionEditor extends BaseEditor { const byId = arrays.index(languages, l => l.id); - const grammars = contributes && contributes.grammars || []; - + const grammars = contributes?.grammars || []; grammars.forEach(grammar => { let language = byId[grammar.language]; @@ -1310,8 +1335,7 @@ export class ExtensionEditor extends BaseEditor { } }); - const snippets = contributes && contributes.snippets || []; - + const snippets = contributes?.snippets || []; snippets.forEach(snippet => { let language = byId[snippet.language]; diff --git a/src/vs/workbench/contrib/extensions/browser/extensionTipsService.ts b/src/vs/workbench/contrib/extensions/browser/extensionTipsService.ts index b0f6945bf1c2..81e355586c8a 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionTipsService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionTipsService.ts @@ -43,12 +43,12 @@ import { ExtensionType, ExtensionsPolicy, ExtensionsPolicyKey } from 'vs/platfor // import { extname } from 'vs/base/common/resources'; import { IExeBasedExtensionTip, IProductService } from 'vs/platform/product/common/productService'; import { timeout } from 'vs/base/common/async'; -import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; // {{SQL CARBON EDIT}} -import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys'; // {{SQL CARBON EDIT}} -import { IWorkspaceStatsService } from 'vs/workbench/contrib/stats/common/workspaceStats'; +import { IWorkspaceTagsService } from 'vs/workbench/contrib/tags/common/workspaceTags'; import { /*setImmediate,*/ isWeb } from 'vs/base/common/platform'; import { platform, env as processEnv } from 'vs/base/common/process'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys'; // {{SQL CARBON EDIT}} +import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; const milliSecondsInADay = 1000 * 60 * 60 * 24; const choiceNever = localize('neverShowAgain', "Don't Show Again"); @@ -112,10 +112,10 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe // @IViewletService private readonly viewletService: IViewletService, {{SQL CARBON EDIT}} comment out for no unused @INotificationService private readonly notificationService: INotificationService, @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, + @IAdsTelemetryService private readonly adsTelemetryService: IAdsTelemetryService, // {{SQL CARBON EDIT}} // @IExtensionsWorkbenchService private readonly extensionWorkbenchService: IExtensionsWorkbenchService, {{SQL CARBON EDIT}} comment out for no unused // @IExperimentService private readonly experimentService: IExperimentService, {{SQL CARBON EDIT}} comment out for no unused - @IAdsTelemetryService private readonly adsTelemetryService: IAdsTelemetryService, // {{SQL CARBON EDIT}} - @IWorkspaceStatsService private readonly workspaceStatsService: IWorkspaceStatsService, + @IWorkspaceTagsService private readonly workspaceTagsService: IWorkspaceTagsService, @IProductService private readonly productService: IProductService ) { super(); @@ -518,15 +518,6 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe private async promptForImportantExeBasedExtension(): Promise { - const storageKey = 'extensionsAssistant/workspaceRecommendationsIgnore'; - const config = this.configurationService.getValue(ConfigurationKey); - - if (config.ignoreRecommendations - || config.showRecommendationsOnlyOnDemand - || this.storageService.getBoolean(storageKey, StorageScope.WORKSPACE, false)) { - return false; - } - let recommendationsToSuggest = Object.keys(this._importantExeBasedRecommendations); const installed = await this.extensionManagementService.getInstalled(ExtensionType.User); @@ -539,13 +530,23 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe "exeName": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" } } */ - this.telemetryService.publicLog('exeExtensionRecommendations:alreadyInstalled', { extensionId, exeName: tip.exeFriendlyName || basename(tip.windowsPath!) }); + this.telemetryService.publicLog('exeExtensionRecommendations:alreadyInstalled', { extensionId, exeName: basename(tip.windowsPath!) }); }); + if (recommendationsToSuggest.length === 0) { return false; } + const storageKey = 'extensionsAssistant/workspaceRecommendationsIgnore'; + const config = this.configurationService.getValue(ConfigurationKey); + + if (config.ignoreRecommendations + || config.showRecommendationsOnlyOnDemand + || this.storageService.getBoolean(storageKey, StorageScope.WORKSPACE, false)) { + return false; + } + recommendationsToSuggest = this.filterIgnoredOrNotAllowed(recommendationsToSuggest); if (recommendationsToSuggest.length === 0) { return false; @@ -1123,7 +1124,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe const storageKey = 'extensionsAssistant/dynamicWorkspaceRecommendations'; const workspaceUri = this.contextService.getWorkspace().folders[0].uri; - return Promise.all([this.workspaceStatsService.getHashedRemotesFromUri(workspaceUri, false), this.workspaceStatsService.getHashedRemotesFromUri(workspaceUri, true)]).then(([hashedRemotes1, hashedRemotes2]) => { + return Promise.all([this.workspaceTagsService.getHashedRemotesFromUri(workspaceUri, false), this.workspaceTagsService.getHashedRemotesFromUri(workspaceUri, true)]).then(([hashedRemotes1, hashedRemotes2]) => { const hashedRemotes = (hashedRemotes1 || []).concat(hashedRemotes2 || []); if (!hashedRemotes.length) { return undefined; @@ -1133,7 +1134,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe if (context.res.statusCode !== 200) { return Promise.resolve(undefined); } - return asJson(context).then((result: { [key: string]: any }) => { + return asJson(context).then((result: { [key: string]: any } | null) => { if (!result) { return; } diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index 28454dbce467..858c08582c78 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -3,7 +3,6 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!./media/extensions'; import { localize } from 'vs/nls'; import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -55,7 +54,7 @@ Registry.as(OutputExtensions.OutputChannels) // Quickopen Registry.as(Extensions.Quickopen).registerQuickOpenHandler( - new QuickOpenHandlerDescriptor( + QuickOpenHandlerDescriptor.create( ExtensionsHandler, ExtensionsHandler.ID, 'ext ', @@ -67,7 +66,7 @@ Registry.as(Extensions.Quickopen).registerQuickOpenHandler( // Editor Registry.as(EditorExtensions.Editors).registerEditor( - new EditorDescriptor( + EditorDescriptor.create( ExtensionEditor, ExtensionEditor.ID, localize('extension', "Extension") @@ -77,13 +76,12 @@ Registry.as(EditorExtensions.Editors).registerEditor( ]); // Viewlet -const viewletDescriptor = new ViewletDescriptor( +const viewletDescriptor = ViewletDescriptor.create( ExtensionsViewlet, VIEWLET_ID, localize('extensions', "Extensions"), - 'extensions', - // {{SQL CARBON EDIT}} - 14 + 'codicon-extensions', + 14 // {{SQL CARBON EDIT}} ); Registry.as(ViewletExtensions.Viewlets) @@ -92,67 +90,67 @@ Registry.as(ViewletExtensions.Viewlets) // Global actions const actionRegistry = Registry.as(WorkbenchActionExtensions.WorkbenchActions); -const openViewletActionDescriptor = new SyncActionDescriptor(OpenExtensionsViewletAction, OpenExtensionsViewletAction.ID, OpenExtensionsViewletAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_X }); +const openViewletActionDescriptor = SyncActionDescriptor.create(OpenExtensionsViewletAction, OpenExtensionsViewletAction.ID, OpenExtensionsViewletAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_X }); actionRegistry.registerWorkbenchAction(openViewletActionDescriptor, 'View: Show Extensions', localize('view', "View")); -const installActionDescriptor = new SyncActionDescriptor(InstallExtensionsAction, InstallExtensionsAction.ID, InstallExtensionsAction.LABEL); +const installActionDescriptor = SyncActionDescriptor.create(InstallExtensionsAction, InstallExtensionsAction.ID, InstallExtensionsAction.LABEL); actionRegistry.registerWorkbenchAction(installActionDescriptor, 'Extensions: Install Extensions', ExtensionsLabel); -const listOutdatedActionDescriptor = new SyncActionDescriptor(ShowOutdatedExtensionsAction, ShowOutdatedExtensionsAction.ID, ShowOutdatedExtensionsAction.LABEL); +const listOutdatedActionDescriptor = SyncActionDescriptor.create(ShowOutdatedExtensionsAction, ShowOutdatedExtensionsAction.ID, ShowOutdatedExtensionsAction.LABEL); actionRegistry.registerWorkbenchAction(listOutdatedActionDescriptor, 'Extensions: Show Outdated Extensions', ExtensionsLabel); -const recommendationsActionDescriptor = new SyncActionDescriptor(ShowRecommendedExtensionsAction, ShowRecommendedExtensionsAction.ID, ShowRecommendedExtensionsAction.LABEL); +const recommendationsActionDescriptor = SyncActionDescriptor.create(ShowRecommendedExtensionsAction, ShowRecommendedExtensionsAction.ID, ShowRecommendedExtensionsAction.LABEL); actionRegistry.registerWorkbenchAction(recommendationsActionDescriptor, 'Extensions: Show Recommended Extensions', ExtensionsLabel); -const keymapRecommendationsActionDescriptor = new SyncActionDescriptor(ShowRecommendedKeymapExtensionsAction, ShowRecommendedKeymapExtensionsAction.ID, ShowRecommendedKeymapExtensionsAction.SHORT_LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_M) }); +const keymapRecommendationsActionDescriptor = SyncActionDescriptor.create(ShowRecommendedKeymapExtensionsAction, ShowRecommendedKeymapExtensionsAction.ID, ShowRecommendedKeymapExtensionsAction.SHORT_LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_M) }); actionRegistry.registerWorkbenchAction(keymapRecommendationsActionDescriptor, 'Preferences: Keymaps', PreferencesLabel); -const languageExtensionsActionDescriptor = new SyncActionDescriptor(ShowLanguageExtensionsAction, ShowLanguageExtensionsAction.ID, ShowLanguageExtensionsAction.SHORT_LABEL); +const languageExtensionsActionDescriptor = SyncActionDescriptor.create(ShowLanguageExtensionsAction, ShowLanguageExtensionsAction.ID, ShowLanguageExtensionsAction.SHORT_LABEL); actionRegistry.registerWorkbenchAction(languageExtensionsActionDescriptor, 'Preferences: Language Extensions', PreferencesLabel); -const azureExtensionsActionDescriptor = new SyncActionDescriptor(ShowAzureExtensionsAction, ShowAzureExtensionsAction.ID, ShowAzureExtensionsAction.SHORT_LABEL); +const azureExtensionsActionDescriptor = SyncActionDescriptor.create(ShowAzureExtensionsAction, ShowAzureExtensionsAction.ID, ShowAzureExtensionsAction.SHORT_LABEL); actionRegistry.registerWorkbenchAction(azureExtensionsActionDescriptor, 'Preferences: Azure Extensions', PreferencesLabel); -const popularActionDescriptor = new SyncActionDescriptor(ShowPopularExtensionsAction, ShowPopularExtensionsAction.ID, ShowPopularExtensionsAction.LABEL); +const popularActionDescriptor = SyncActionDescriptor.create(ShowPopularExtensionsAction, ShowPopularExtensionsAction.ID, ShowPopularExtensionsAction.LABEL); actionRegistry.registerWorkbenchAction(popularActionDescriptor, 'Extensions: Show Popular Extensions', ExtensionsLabel); -const enabledActionDescriptor = new SyncActionDescriptor(ShowEnabledExtensionsAction, ShowEnabledExtensionsAction.ID, ShowEnabledExtensionsAction.LABEL); +const enabledActionDescriptor = SyncActionDescriptor.create(ShowEnabledExtensionsAction, ShowEnabledExtensionsAction.ID, ShowEnabledExtensionsAction.LABEL); actionRegistry.registerWorkbenchAction(enabledActionDescriptor, 'Extensions: Show Enabled Extensions', ExtensionsLabel); -const installedActionDescriptor = new SyncActionDescriptor(ShowInstalledExtensionsAction, ShowInstalledExtensionsAction.ID, ShowInstalledExtensionsAction.LABEL); +const installedActionDescriptor = SyncActionDescriptor.create(ShowInstalledExtensionsAction, ShowInstalledExtensionsAction.ID, ShowInstalledExtensionsAction.LABEL); actionRegistry.registerWorkbenchAction(installedActionDescriptor, 'Extensions: Show Installed Extensions', ExtensionsLabel); -const disabledActionDescriptor = new SyncActionDescriptor(ShowDisabledExtensionsAction, ShowDisabledExtensionsAction.ID, ShowDisabledExtensionsAction.LABEL); +const disabledActionDescriptor = SyncActionDescriptor.create(ShowDisabledExtensionsAction, ShowDisabledExtensionsAction.ID, ShowDisabledExtensionsAction.LABEL); actionRegistry.registerWorkbenchAction(disabledActionDescriptor, 'Extensions: Show Disabled Extensions', ExtensionsLabel); -const builtinActionDescriptor = new SyncActionDescriptor(ShowBuiltInExtensionsAction, ShowBuiltInExtensionsAction.ID, ShowBuiltInExtensionsAction.LABEL); +const builtinActionDescriptor = SyncActionDescriptor.create(ShowBuiltInExtensionsAction, ShowBuiltInExtensionsAction.ID, ShowBuiltInExtensionsAction.LABEL); actionRegistry.registerWorkbenchAction(builtinActionDescriptor, 'Extensions: Show Built-in Extensions', ExtensionsLabel); -const updateAllActionDescriptor = new SyncActionDescriptor(UpdateAllAction, UpdateAllAction.ID, UpdateAllAction.LABEL); +const updateAllActionDescriptor = SyncActionDescriptor.create(UpdateAllAction, UpdateAllAction.ID, UpdateAllAction.LABEL); actionRegistry.registerWorkbenchAction(updateAllActionDescriptor, 'Extensions: Update All Extensions', ExtensionsLabel); -const installVSIXActionDescriptor = new SyncActionDescriptor(InstallVSIXAction, InstallVSIXAction.ID, InstallVSIXAction.LABEL); +const installVSIXActionDescriptor = SyncActionDescriptor.create(InstallVSIXAction, InstallVSIXAction.ID, InstallVSIXAction.LABEL); actionRegistry.registerWorkbenchAction(installVSIXActionDescriptor, 'Extensions: Install from VSIX...', ExtensionsLabel); -const disableAllAction = new SyncActionDescriptor(DisableAllAction, DisableAllAction.ID, DisableAllAction.LABEL); +const disableAllAction = SyncActionDescriptor.create(DisableAllAction, DisableAllAction.ID, DisableAllAction.LABEL); actionRegistry.registerWorkbenchAction(disableAllAction, 'Extensions: Disable All Installed Extensions', ExtensionsLabel); -const disableAllWorkspaceAction = new SyncActionDescriptor(DisableAllWorkspaceAction, DisableAllWorkspaceAction.ID, DisableAllWorkspaceAction.LABEL); +const disableAllWorkspaceAction = SyncActionDescriptor.create(DisableAllWorkspaceAction, DisableAllWorkspaceAction.ID, DisableAllWorkspaceAction.LABEL); actionRegistry.registerWorkbenchAction(disableAllWorkspaceAction, 'Extensions: Disable All Installed Extensions for this Workspace', ExtensionsLabel); -const enableAllAction = new SyncActionDescriptor(EnableAllAction, EnableAllAction.ID, EnableAllAction.LABEL); +const enableAllAction = SyncActionDescriptor.create(EnableAllAction, EnableAllAction.ID, EnableAllAction.LABEL); actionRegistry.registerWorkbenchAction(enableAllAction, 'Extensions: Enable All Extensions', ExtensionsLabel); -const enableAllWorkspaceAction = new SyncActionDescriptor(EnableAllWorkspaceAction, EnableAllWorkspaceAction.ID, EnableAllWorkspaceAction.LABEL); +const enableAllWorkspaceAction = SyncActionDescriptor.create(EnableAllWorkspaceAction, EnableAllWorkspaceAction.ID, EnableAllWorkspaceAction.LABEL); actionRegistry.registerWorkbenchAction(enableAllWorkspaceAction, 'Extensions: Enable All Extensions for this Workspace', ExtensionsLabel); -const checkForUpdatesAction = new SyncActionDescriptor(CheckForUpdatesAction, CheckForUpdatesAction.ID, CheckForUpdatesAction.LABEL); +const checkForUpdatesAction = SyncActionDescriptor.create(CheckForUpdatesAction, CheckForUpdatesAction.ID, CheckForUpdatesAction.LABEL); actionRegistry.registerWorkbenchAction(checkForUpdatesAction, `Extensions: Check for Extension Updates`, ExtensionsLabel); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(EnableAutoUpdateAction, EnableAutoUpdateAction.ID, EnableAutoUpdateAction.LABEL), `Extensions: Enable Auto Updating Extensions`, ExtensionsLabel); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(DisableAutoUpdateAction, DisableAutoUpdateAction.ID, DisableAutoUpdateAction.LABEL), `Extensions: Disable Auto Updating Extensions`, ExtensionsLabel); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(InstallSpecificVersionOfExtensionAction, InstallSpecificVersionOfExtensionAction.ID, InstallSpecificVersionOfExtensionAction.LABEL), 'Install Specific Version of Extension...', ExtensionsLabel); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ReinstallAction, ReinstallAction.ID, ReinstallAction.LABEL), 'Reinstall Extension...', localize('developer', "Developer")); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(EnableAutoUpdateAction, EnableAutoUpdateAction.ID, EnableAutoUpdateAction.LABEL), `Extensions: Enable Auto Updating Extensions`, ExtensionsLabel); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(DisableAutoUpdateAction, DisableAutoUpdateAction.ID, DisableAutoUpdateAction.LABEL), `Extensions: Disable Auto Updating Extensions`, ExtensionsLabel); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(InstallSpecificVersionOfExtensionAction, InstallSpecificVersionOfExtensionAction.ID, InstallSpecificVersionOfExtensionAction.LABEL), 'Install Specific Version of Extension...', ExtensionsLabel); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ReinstallAction, ReinstallAction.ID, ReinstallAction.LABEL), 'Reinstall Extension...', localize('developer', "Developer")); Registry.as(ConfigurationExtensions.Configuration) .registerConfiguration({ @@ -281,12 +279,13 @@ CommandsRegistry.registerCommand({ throw new Error(localize('id required', "Extension id required.")); } const extensionManagementService = accessor.get(IExtensionManagementService); + const installed = await extensionManagementService.getInstalled(ExtensionType.User); + const [extensionToUninstall] = installed.filter(e => areSameExtensions(e.identifier, { id })); + if (!extensionToUninstall) { + throw new Error(localize('notInstalled', "Extension '{0}' is not installed. Make sure you use the full extension ID, including the publisher, e.g.: ms-vscode.csharp.", id)); + } + try { - const installed = await extensionManagementService.getInstalled(ExtensionType.User); - const [extensionToUninstall] = installed.filter(e => areSameExtensions(e.identifier, { id })); - if (!extensionToUninstall) { - return Promise.reject(new Error(localize('notInstalled', "Extension '{0}' is not installed. Make sure you use the full extension ID, including the publisher, e.g.: ms-vscode.csharp.", id))); - } await extensionManagementService.uninstall(extensionToUninstall, true); } catch (e) { onUnexpectedError(e); @@ -357,7 +356,7 @@ class ExtensionsContributions implements IWorkbenchContribution { if (canManageExtensions) { Registry.as(Extensions.Quickopen).registerQuickOpenHandler( - new QuickOpenHandlerDescriptor( + QuickOpenHandlerDescriptor.create( GalleryExtensionsHandler, GalleryExtensionsHandler.ID, 'ext install ', diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index 48a98f3e5282..bef9cdcedcf7 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -16,7 +16,7 @@ import { dispose, Disposable } from 'vs/base/common/lifecycle'; // {{SQL CARBON EDIT}} import { IExtension, ExtensionState, IExtensionsWorkbenchService, VIEWLET_ID, IExtensionsViewlet, AutoUpdateConfigurationKey, IExtensionContainer, EXTENSIONS_CONFIG } from 'vs/workbench/contrib/extensions/common/extensions'; import { ExtensionsConfigurationInitialContent } from 'vs/workbench/contrib/extensions/common/extensionsFileTemplate'; -import { ExtensionsLabel, IGalleryExtension, IExtensionGalleryService, INSTALL_ERROR_MALICIOUS, INSTALL_ERROR_INCOMPATIBLE, IGalleryExtensionVersion, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { ExtensionsLabel, IGalleryExtension, IExtensionGalleryService, INSTALL_ERROR_MALICIOUS, INSTALL_ERROR_INCOMPATIBLE, IGalleryExtensionVersion, ILocalExtension, INSTALL_ERROR_NOT_SUPPORTED } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionTipsService, IExtensionRecommendation, IExtensionsConfigContent, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { ExtensionType, ExtensionIdentifier, IExtensionDescription, IExtensionManifest, isLanguagePackExtension, ExtensionsPolicy, ExtensionsPolicyKey } from 'vs/platform/extensions/common/extensions'; // {{SQL CARBON EDIT}} @@ -55,12 +55,12 @@ import { alert } from 'vs/base/browser/ui/aria/aria'; import { coalesce } from 'vs/base/common/arrays'; import { IWorkbenchThemeService, COLOR_THEME_SETTING, ICON_THEME_SETTING, IFileIconTheme, IColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { ILabelService } from 'vs/platform/label/common/label'; -import { isUIExtension } from 'vs/workbench/services/extensions/common/extensionsUtil'; +import { prefersExecuteOnUI, prefersExecuteOnWorkspace } from 'vs/workbench/services/extensions/common/extensionsUtil'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IProductService } from 'vs/platform/product/common/productService'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; -import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IFileDialogService, IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; // {{SQL CARBON EDIT}} @@ -76,31 +76,37 @@ export function toExtensionDescription(local: ILocalExtension): IExtensionDescri }; } -const promptDownloadManually = (extension: IGalleryExtension | undefined, message: string, error: Error, - instantiationService: IInstantiationService, notificationService: INotificationService, openerService: IOpenerService, productService: IProductService) => { - if (!extension || error.name === INSTALL_ERROR_INCOMPATIBLE || error.name === INSTALL_ERROR_MALICIOUS || !productService.extensionsGallery) { - return Promise.reject(error); - } else { - const downloadUrl = (extension.assets.downloadPage && extension.assets.downloadPage.uri) || extension.assets.download.uri; // {{SQL CARBON EDIT}} Use the URI directly since we don't have a marketplace hosting the packages - notificationService.prompt(Severity.Error, message, [{ - label: localize('download', "Download Manually"), - run: () => openerService.open(URI.parse(downloadUrl)).then(() => { - notificationService.prompt( - Severity.Info, - localize('install vsix', 'Once downloaded, please manually install the downloaded VSIX of \'{0}\'.', extension.identifier.id), - [{ - label: InstallVSIXAction.LABEL, - run: () => { - const action = instantiationService.createInstance(InstallVSIXAction, InstallVSIXAction.ID, InstallVSIXAction.LABEL); - action.run(); - action.dispose(); - } - }] - ); - }) - }]); - return Promise.resolve(); - } +const promptDownloadManually = (extension: IGalleryExtension | undefined, message: string, error: Error, instantiationService: IInstantiationService): Promise => { + return instantiationService.invokeFunction(accessor => { + const productService = accessor.get(IProductService); + const openerService = accessor.get(IOpenerService); + const notificationService = accessor.get(INotificationService); + const dialogService = accessor.get(IDialogService); + const erorrsToShows = [INSTALL_ERROR_INCOMPATIBLE, INSTALL_ERROR_MALICIOUS, INSTALL_ERROR_NOT_SUPPORTED]; + if (!extension || erorrsToShows.indexOf(error.name) !== -1 || !productService.extensionsGallery) { + return dialogService.show(Severity.Error, error.message, []); + } else { + const downloadUrl = (extension.assets.downloadPage && extension.assets.downloadPage.uri) || extension.assets.download.uri; // {{SQL CARBON EDIT}} Use the URI directly since we don't have a marketplace hosting the packages + notificationService.prompt(Severity.Error, message, [{ + label: localize('download', "Download Manually"), + run: () => openerService.open(URI.parse(downloadUrl)).then(() => { + notificationService.prompt( + Severity.Info, + localize('install vsix', 'Once downloaded, please manually install the downloaded VSIX of \'{0}\'.', extension.identifier.id), + [{ + label: InstallVSIXAction.LABEL, + run: () => { + const action = instantiationService.createInstance(InstallVSIXAction, InstallVSIXAction.ID, InstallVSIXAction.LABEL); + action.run(); + action.dispose(); + } + }] + ); + }) + }]); + return Promise.resolve(); + } + }); }; function getRelativeDateLabel(date: Date): string { @@ -138,9 +144,9 @@ function getRelativeDateLabel(date: Date): string { } export abstract class ExtensionAction extends Action implements IExtensionContainer { - private _extension: IExtension; - get extension(): IExtension { return this._extension; } - set extension(extension: IExtension) { this._extension = extension; this.update(); } + private _extension: IExtension | null = null; + get extension(): IExtension | null { return this._extension; } + set extension(extension: IExtension | null) { this._extension = extension; this.update(); } abstract update(): void; } @@ -153,7 +159,7 @@ export class InstallAction extends ExtensionAction { private static readonly InstallingClass = 'extension-action install installing'; - private _manifest: IExtensionManifest | null; + private _manifest: IExtensionManifest | null = null; set manifest(manifest: IExtensionManifest) { this._manifest = manifest; this.updateLabel(); @@ -163,7 +169,6 @@ export class InstallAction extends ExtensionAction { @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, @IInstantiationService private readonly instantiationService: IInstantiationService, @INotificationService private readonly notificationService: INotificationService, - @IOpenerService private readonly openerService: IOpenerService, @IExtensionService private readonly runtimeExtensionService: IExtensionService, @IWorkbenchThemeService private readonly workbenchThemeService: IWorkbenchThemeService, @IConfigurationService private readonly configurationService: IConfigurationService, @@ -196,12 +201,15 @@ export class InstallAction extends ExtensionAction { } private updateLabel(): void { + if (!this.extension) { + return; + } if (this.extension.state === ExtensionState.Installing) { this.label = InstallAction.INSTALLING_LABEL; this.tooltip = InstallAction.INSTALLING_LABEL; } else { if (this._manifest && this.extensionManagementServerService.localExtensionManagementServer && this.extensionManagementServerService.remoteExtensionManagementServer) { - if (isUIExtension(this._manifest, this.productService, this.configurationService)) { + if (prefersExecuteOnUI(this._manifest, this.productService, this.configurationService)) { this.label = `${InstallAction.INSTALL_LABEL} ${localize('locally', "Locally")}`; this.tooltip = `${InstallAction.INSTALL_LABEL} ${localize('locally', "Locally")}`; } else { @@ -217,6 +225,9 @@ export class InstallAction extends ExtensionAction { } async run(): Promise { + if (!this.extension) { + return; + } this.extensionsWorkbenchService.open(this.extension); alert(localize('installExtensionStart', "Installing extension {0} started. An editor is now open with more details on this extension", this.extension.displayName)); @@ -263,7 +274,7 @@ export class InstallAction extends ExtensionAction { console.error(err); - return promptDownloadManually(extension.gallery, localize('failedToInstall', "Failed to install \'{0}\'.", extension.identifier.id), err, this.instantiationService, this.notificationService, this.openerService, this.productService); + return promptDownloadManually(extension.gallery, localize('failedToInstall', "Failed to install \'{0}\'.", extension.identifier.id), err, this.instantiationService); }); } @@ -315,7 +326,7 @@ export abstract class InstallInOtherServerAction extends ExtensionAction { // disabled by extension kind or it is a language pack extension && (this.extension.enablementState === EnablementState.DisabledByExtensionKind || isLanguagePackExtension(this.extension.local.manifest)) ) { - const extensionInOtherServer = this.extensionsWorkbenchService.installed.filter(e => areSameExtensions(e.identifier, this.extension.identifier) && e.server === this.server)[0]; + const extensionInOtherServer = this.extensionsWorkbenchService.installed.filter(e => areSameExtensions(e.identifier, this.extension!.identifier) && e.server === this.server)[0]; if (extensionInOtherServer) { // Getting installed in other server if (extensionInOtherServer.state === ExtensionState.Installing && !extensionInOtherServer.local) { @@ -332,6 +343,9 @@ export abstract class InstallInOtherServerAction extends ExtensionAction { } async run(): Promise { + if (!this.extension) { + return; + } if (this.server) { this.extensionsWorkbenchService.open(this.extension); alert(localize('installExtensionStart', "Installing extension {0} started. An editor is now open with more details on this extension", this.extension.displayName)); @@ -424,12 +438,14 @@ export class UninstallAction extends ExtensionAction { this.enabled = true; } - run(): Promise { + async run(): Promise { + if (!this.extension) { + return; + } alert(localize('uninstallExtensionStart', "Uninstalling extension {0} started.", this.extension.displayName)); return this.extensionsWorkbenchService.uninstall(this.extension).then(() => { - // {{SQL CARBON EDIT}} - replace Visual Studio Code with Azure Data Studio - alert(localize('uninstallExtensionComplete', "Please reload Azure Data Studio to complete the uninstallation of the extension {0}.", this.extension.displayName)); + alert(localize('uninstallExtensionComplete', "Please reload Azure Data Studio to complete the uninstallation of the extension {0}.", this.extension!.displayName)); // {{SQL CARBON EDIT}} - replace Visual Studio Code with Azure Data Studio }); } } @@ -510,8 +526,6 @@ export class UpdateAction extends ExtensionAction { @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, @IInstantiationService private readonly instantiationService: IInstantiationService, @INotificationService private readonly notificationService: INotificationService, - @IOpenerService private readonly openerService: IOpenerService, - @IProductService private readonly productService: IProductService ) { super(`extensions.update`, '', UpdateAction.DisabledClass, false); this.update(); @@ -540,14 +554,17 @@ export class UpdateAction extends ExtensionAction { this.label = this.extension.outdated ? this.getUpdateLabel(this.extension.latestVersion) : this.getUpdateLabel(); } - run(): Promise { + async run(): Promise { + if (!this.extension) { + return; + } alert(localize('updateExtensionStart', "Updating extension {0} to version {1} started.", this.extension.displayName, this.extension.latestVersion)); return this.install(this.extension); } private install(extension: IExtension): Promise { return this.extensionsWorkbenchService.install(extension).then(() => { - alert(localize('updateExtensionComplete', "Updating extension {0} to version {1} completed.", this.extension.displayName, this.extension.latestVersion)); + alert(localize('updateExtensionComplete', "Updating extension {0} to version {1} completed.", extension.displayName, extension.latestVersion)); }, err => { if (!extension.gallery) { return this.notificationService.error(err); @@ -562,7 +579,7 @@ export class UpdateAction extends ExtensionAction { console.error(err); - return promptDownloadManually(extension.gallery, localize('failedToUpdate', "Failed to update \'{0}\'.", extension.identifier.id), err, this.instantiationService, this.notificationService, this.openerService, this.productService); + return promptDownloadManually(extension.gallery, localize('failedToUpdate', "Failed to update \'{0}\'.", extension.identifier.id), err, this.instantiationService); }); } @@ -577,8 +594,6 @@ interface IExtensionActionViewItemOptions extends IActionViewItemOptions { export class ExtensionActionViewItem extends ActionViewItem { - protected options: IExtensionActionViewItemOptions; - constructor(context: any, action: IAction, options: IExtensionActionViewItemOptions = {}) { super(context, action, options); } @@ -586,14 +601,14 @@ export class ExtensionActionViewItem extends ActionViewItem { updateEnabled(): void { super.updateEnabled(); - if (this.label && this.options.tabOnlyOnFocus && this.getAction().enabled && !this._hasFocus) { + if (this.label && (this.options).tabOnlyOnFocus && this.getAction().enabled && !this._hasFocus) { DOM.removeTabIndexAndUpdateFocus(this.label); } } - private _hasFocus: boolean; + private _hasFocus: boolean = false; setFocus(value: boolean): void { - if (!this.options.tabOnlyOnFocus || this._hasFocus === value) { + if (!(this.options).tabOnlyOnFocus || this._hasFocus === value) { return; } this._hasFocus = value; @@ -620,7 +635,7 @@ export abstract class ExtensionDropDownAction extends ExtensionAction { super(id, label, cssClass, enabled); } - private _actionViewItem: DropDownMenuActionViewItem; + private _actionViewItem: DropDownMenuActionViewItem | null = null; createActionViewItem(): DropDownMenuActionViewItem { this._actionViewItem = this.instantiationService.createInstance(DropDownMenuActionViewItem, this, this.tabOnlyOnFocus); return this._actionViewItem; @@ -669,7 +684,7 @@ export class DropDownMenuActionViewItem extends ExtensionActionViewItem { export class ManageExtensionAction extends ExtensionDropDownAction { static readonly ID = 'extensions.manage'; - private static readonly Class = 'extension-action manage'; + private static readonly Class = 'extension-action manage codicon-gear'; private static readonly HideManageExtensionClass = `${ManageExtensionAction.Class} hide`; constructor( @@ -712,13 +727,14 @@ export class ManageExtensionAction extends ExtensionDropDownAction { groups.push([this.instantiationService.createInstance(UninstallAction)]); groups.push([this.instantiationService.createInstance(InstallAnotherVersionAction)]); - const extensionActions: ExtensionAction[] = [this.instantiationService.createInstance(ExtensionInfoAction)]; - if (this.extension.local && this.extension.local.manifest.contributes && this.extension.local.manifest.contributes.configuration) { - extensionActions.push(this.instantiationService.createInstance(ExtensionSettingsAction)); + if (this.extension) { + const extensionActions: ExtensionAction[] = [this.instantiationService.createInstance(ExtensionInfoAction)]; + if (this.extension.local && this.extension.local.manifest.contributes && this.extension.local.manifest.contributes.configuration) { + extensionActions.push(this.instantiationService.createInstance(ExtensionSettingsAction)); + } + groups.push(extensionActions); } - groups.push(extensionActions); - groups.forEach(group => group.forEach(extensionAction => extensionAction.extension = this.extension)); return groups; @@ -754,15 +770,13 @@ export class InstallAnotherVersionAction extends ExtensionAction { @IQuickInputService private readonly quickInputService: IQuickInputService, @IInstantiationService private readonly instantiationService: IInstantiationService, @INotificationService private readonly notificationService: INotificationService, - @IOpenerService private readonly openerService: IOpenerService, - @IProductService private readonly productService: IProductService ) { super(InstallAnotherVersionAction.ID, InstallAnotherVersionAction.LABEL); this.update(); } update(): void { - this.enabled = this.extension && !!this.extension.gallery; + this.enabled = !!this.extension && !!this.extension.gallery; } run(): Promise { @@ -772,19 +786,19 @@ export class InstallAnotherVersionAction extends ExtensionAction { return this.quickInputService.pick(this.getVersionEntries(), { placeHolder: localize('selectVersion', "Select Version to Install"), matchOnDetail: true }) .then(pick => { if (pick) { - if (this.extension.version === pick.id) { + if (this.extension!.version === pick.id) { return Promise.resolve(); } - const promise: Promise = pick.latest ? this.extensionsWorkbenchService.install(this.extension) : this.extensionsWorkbenchService.installVersion(this.extension, pick.id); + const promise: Promise = pick.latest ? this.extensionsWorkbenchService.install(this.extension!) : this.extensionsWorkbenchService.installVersion(this.extension!, pick.id); return promise .then(null, err => { - if (!this.extension.gallery) { + if (!this.extension!.gallery) { return this.notificationService.error(err); } console.error(err); - return promptDownloadManually(this.extension.gallery, localize('failedToInstall', "Failed to install \'{0}\'.", this.extension.identifier.id), err, this.instantiationService, this.notificationService, this.openerService, this.productService); + return promptDownloadManually(this.extension!.gallery, localize('failedToInstall', "Failed to install \'{0}\'.", this.extension!.identifier.id), err, this.instantiationService); }); } return null; @@ -792,8 +806,8 @@ export class InstallAnotherVersionAction extends ExtensionAction { } private getVersionEntries(): Promise<(IQuickPickItem & { latest: boolean, id: string })[]> { - return this.extensionGalleryService.getAllVersions(this.extension.gallery!, true) - .then(allVersions => allVersions.map((v, i) => ({ id: v.version, label: v.version, description: `${getRelativeDateLabel(new Date(Date.parse(v.date)))}${v.version === this.extension.version ? ` (${localize('current', "Current")})` : ''}`, latest: i === 0 }))); + return this.extensionGalleryService.getAllVersions(this.extension!.gallery!, true) + .then(allVersions => allVersions.map((v, i) => ({ id: v.version, label: v.version, description: `${getRelativeDateLabel(new Date(Date.parse(v.date)))}${v.version === this.extension!.version ? ` (${localize('current', "Current")})` : ''}`, latest: i === 0 }))); } } @@ -813,7 +827,10 @@ export class ExtensionInfoAction extends ExtensionAction { this.enabled = !!this.extension; } - run(): Promise { + async run(): Promise { + if (!this.extension) { + return; + } const name = localize('extensionInfoName', 'Name: {0}', this.extension.displayName); const id = localize('extensionInfoId', 'Id: {0}', this.extension.identifier.id); @@ -843,7 +860,11 @@ export class ExtensionSettingsAction extends ExtensionAction { update(): void { this.enabled = !!this.extension; } - run(): Promise { + + async run(): Promise { + if (!this.extension) { + return; + } this.preferencesService.openSettings(false, `@ext:${this.extension.identifier.id}`); return Promise.resolve(); } @@ -871,7 +892,10 @@ export class EnableForWorkspaceAction extends ExtensionAction { } } - run(): Promise { + async run(): Promise { + if (!this.extension) { + return; + } return this.extensionsWorkbenchService.setEnablement(this.extension, EnablementState.EnabledWorkspace); } } @@ -898,7 +922,10 @@ export class EnableGloballyAction extends ExtensionAction { } } - run(): Promise { + async run(): Promise { + if (!this.extension) { + return; + } return this.extensionsWorkbenchService.setEnablement(this.extension, EnablementState.EnabledGlobally); } } @@ -919,14 +946,17 @@ export class DisableForWorkspaceAction extends ExtensionAction { update(): void { this.enabled = false; - if (this.extension && this.extension.local && this.runningExtensions.some(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension.identifier) && this.workspaceContextService.getWorkbenchState() !== WorkbenchState.EMPTY)) { + if (this.extension && this.extension.local && this.runningExtensions.some(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension!.identifier) && this.workspaceContextService.getWorkbenchState() !== WorkbenchState.EMPTY)) { this.enabled = this.extension.state === ExtensionState.Installed && (this.extension.enablementState === EnablementState.EnabledGlobally || this.extension.enablementState === EnablementState.EnabledWorkspace) && this.extensionEnablementService.canChangeEnablement(this.extension.local); } } - run(): Promise { + async run(): Promise { + if (!this.extension) { + return; + } return this.extensionsWorkbenchService.setEnablement(this.extension, EnablementState.DisabledWorkspace); } } @@ -946,14 +976,17 @@ export class DisableGloballyAction extends ExtensionAction { update(): void { this.enabled = false; - if (this.extension && this.extension.local && this.runningExtensions.some(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension.identifier))) { + if (this.extension && this.extension.local && this.runningExtensions.some(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension!.identifier))) { this.enabled = this.extension.state === ExtensionState.Installed && (this.extension.enablementState === EnablementState.EnabledGlobally || this.extension.enablementState === EnablementState.EnabledWorkspace) && this.extensionEnablementService.canChangeEnablement(this.extension.local); } } - run(): Promise { + async run(): Promise { + if (!this.extension) { + return; + } return this.extensionsWorkbenchService.setEnablement(this.extension, EnablementState.DisabledGlobally); } } @@ -1038,6 +1071,7 @@ export class CheckForUpdatesAction extends Action { @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, @IExtensionEnablementService private readonly extensionEnablementService: IExtensionEnablementService, @IViewletService private readonly viewletService: IViewletService, + @IDialogService private readonly dialogService: IDialogService, @INotificationService private readonly notificationService: INotificationService ) { super(id, label, '', true); @@ -1046,7 +1080,7 @@ export class CheckForUpdatesAction extends Action { private checkUpdatesAndNotify(): void { const outdated = this.extensionsWorkbenchService.outdated; if (!outdated.length) { - this.notificationService.info(localize('noUpdatesAvailable', "All extensions are up to date.")); + this.dialogService.show(Severity.Info, localize('noUpdatesAvailable', "All extensions are up to date."), [localize('ok', "OK")]); return; } @@ -1138,8 +1172,6 @@ export class UpdateAllAction extends Action { @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, @INotificationService private readonly notificationService: INotificationService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IOpenerService private readonly openerService: IOpenerService, - @IProductService private readonly productService: IProductService ) { super(id, label, '', false); @@ -1163,7 +1195,7 @@ export class UpdateAllAction extends Action { console.error(err); - return promptDownloadManually(extension.gallery, localize('failedToUpdate', "Failed to update \'{0}\'.", extension.identifier.id), err, this.instantiationService, this.notificationService, this.openerService, this.productService); + return promptDownloadManually(extension.gallery, localize('failedToUpdate', "Failed to update \'{0}\'.", extension.identifier.id), err, this.instantiationService); }); } } @@ -1181,7 +1213,9 @@ export class ReloadAction extends ExtensionAction { @IHostService private readonly hostService: IHostService, @IExtensionService private readonly extensionService: IExtensionService, @IExtensionEnablementService private readonly extensionEnablementService: IExtensionEnablementService, - @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService + @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, + @IProductService private readonly productService: IProductService, + @IConfigurationService private readonly configurationService: IConfigurationService, ) { super('extensions.reload', localize('reloadAction', "Reload"), ReloadAction.DisabledClass, false); this._register(this.extensionService.onDidChangeExtensions(this.updateRunningExtensions, this)); @@ -1210,11 +1244,11 @@ export class ReloadAction extends ExtensionAction { } private computeReloadState(): void { - if (!this._runningExtensions) { + if (!this._runningExtensions || !this.extension) { return; } const isUninstalled = this.extension.state === ExtensionState.Uninstalled; - const runningExtension = this._runningExtensions.filter(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension.identifier))[0]; + const runningExtension = this._runningExtensions.filter(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension!.identifier))[0]; const isSameExtensionRunning = runningExtension && this.extension.server === this.extensionManagementServerService.getExtensionManagementServer(runningExtension.extensionLocation); if (isUninstalled) { @@ -1233,16 +1267,37 @@ export class ReloadAction extends ExtensionAction { // Extension is running if (runningExtension) { if (isEnabled) { - if (!this.extensionService.canAddExtension(toExtensionDescription(this.extension.local))) { - if (isSameExtensionRunning) { - if (this.extension.version !== runningExtension.version) { + // No Reload is required if extension can run without reload + if (this.extensionService.canAddExtension(toExtensionDescription(this.extension.local))) { + return; + } + if (isSameExtensionRunning) { + // Different version of same extension is running. Requires reload to run the current version + if (this.extension.version !== runningExtension.version) { + this.enabled = true; + this.label = localize('reloadRequired', "Reload Required"); + this.tooltip = localize('postUpdateTooltip', "Please reload Azure Data Studio to enable the updated extension."); // {{SQL CARBON EDIT}} - replace Visual Studio Code with Azure Data Studio + } + } else { + const runningExtensionServer = this.extensionManagementServerService.getExtensionManagementServer(runningExtension.extensionLocation); + if (this.extension.server === this.extensionManagementServerService.localExtensionManagementServer && runningExtensionServer === this.extensionManagementServerService.remoteExtensionManagementServer) { + // This extension prefers to run on UI/Local side but is running in remote + if (prefersExecuteOnUI(this.extension.local!.manifest, this.productService, this.configurationService)) { + this.enabled = true; + this.label = localize('reloadRequired', "Reload Required"); + this.tooltip = localize('postEnableTooltip', "Please reload Azure Data Studio to enable this extension."); // {{SQL CARBON EDIT}} - replace Visual Studio Code with Azure Data Studio + } + } + if (this.extension.server === this.extensionManagementServerService.remoteExtensionManagementServer && runningExtensionServer === this.extensionManagementServerService.localExtensionManagementServer) { + // This extension prefers to run on Workspace/Remote side but is running in local + if (prefersExecuteOnWorkspace(this.extension.local!.manifest, this.productService, this.configurationService)) { this.enabled = true; this.label = localize('reloadRequired', "Reload Required"); - // {{SQL CARBON EDIT}} - replace Visual Studio Code with Azure Data Studio - this.tooltip = localize('postUpdateTooltip', "Please reload Azure Data Studio to enable the updated extension."); + this.tooltip = localize('postEnableTooltip', "Please reload Azure Data Studio to enable this extension."); // {{SQL CARBON EDIT}} - replace Visual Studio Code with Azure Data Studio } } } + return; } else { if (isSameExtensionRunning) { this.enabled = true; @@ -1265,7 +1320,7 @@ export class ReloadAction extends ExtensionAction { const otherServer = this.extension.server ? this.extension.server === this.extensionManagementServerService.localExtensionManagementServer ? this.extensionManagementServerService.remoteExtensionManagementServer : this.extensionManagementServerService.localExtensionManagementServer : null; if (otherServer && this.extension.enablementState === EnablementState.DisabledByExtensionKind) { - const extensionInOtherServer = this.extensionsWorkbenchService.local.filter(e => areSameExtensions(e.identifier, this.extension.identifier) && e.server === otherServer)[0]; + const extensionInOtherServer = this.extensionsWorkbenchService.local.filter(e => areSameExtensions(e.identifier, this.extension!.identifier) && e.server === otherServer)[0]; // Same extension in other server exists and if (extensionInOtherServer && extensionInOtherServer.local && this.extensionEnablementService.isEnabled(extensionInOtherServer.local)) { this.enabled = true; @@ -1323,8 +1378,8 @@ export class SetColorThemeAction extends ExtensionAction { if (!this.enabled) { return; } - let extensionThemes = SetColorThemeAction.getColorThemes(this.colorThemes, this.extension); - const currentTheme = this.colorThemes.filter(t => t.settingsId === this.configurationService.getValue(COLOR_THEME_SETTING))[0]; + let extensionThemes = SetColorThemeAction.getColorThemes(this.colorThemes, this.extension!); + const currentTheme = this.colorThemes.filter(t => t.settingsId === this.configurationService.getValue(COLOR_THEME_SETTING))[0] || this.workbenchThemeService.getColorTheme(); showCurrentTheme = showCurrentTheme || extensionThemes.some(t => t.id === currentTheme.id); if (showCurrentTheme) { extensionThemes = extensionThemes.filter(t => t.id !== currentTheme.id); @@ -1389,7 +1444,7 @@ export class SetFileIconThemeAction extends ExtensionAction { if (!this.enabled) { return; } - let extensionThemes = SetFileIconThemeAction.getFileIconThemes(this.fileIconThemes, this.extension); + let extensionThemes = SetFileIconThemeAction.getFileIconThemes(this.fileIconThemes, this.extension!); const currentTheme = this.fileIconThemes.filter(t => t.settingsId === this.configurationService.getValue(ICON_THEME_SETTING))[0] || this.workbenchThemeService.getFileIconTheme(); showCurrentTheme = showCurrentTheme || extensionThemes.some(t => t.id === currentTheme.id); if (showCurrentTheme) { @@ -1518,7 +1573,7 @@ export class ClearExtensionsInputAction extends Action { value: string, @IViewletService private readonly viewletService: IViewletService ) { - super(id, label, 'clear-extensions', true); + super(id, label, 'codicon-clear-all', true); this.onSearchChange(value); this._register(onSearchChange(this.onSearchChange, this)); } @@ -1643,9 +1698,7 @@ export class InstallWorkspaceRecommendedExtensionsAction extends Action { label: string = InstallWorkspaceRecommendedExtensionsAction.LABEL, recommendations: IExtensionRecommendation[], @IViewletService private readonly viewletService: IViewletService, - @INotificationService private readonly notificationService: INotificationService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IOpenerService private readonly openerService: IOpenerService, @IExtensionsWorkbenchService private readonly extensionWorkbenchService: IExtensionsWorkbenchService, @IConfigurationService private readonly configurationService: IConfigurationService, @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, @@ -1676,7 +1729,7 @@ export class InstallWorkspaceRecommendedExtensionsAction extends Action { private async installExtension(extension: IExtension): Promise { try { if (extension.local && extension.gallery) { - if (isUIExtension(extension.local.manifest, this.productService, this.configurationService)) { + if (prefersExecuteOnUI(extension.local.manifest, this.productService, this.configurationService)) { if (this.extensionManagementServerService.localExtensionManagementServer) { await this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.installFromGallery(extension.gallery); return; @@ -1689,7 +1742,7 @@ export class InstallWorkspaceRecommendedExtensionsAction extends Action { await this.extensionWorkbenchService.install(extension); } catch (err) { console.error(err); - return promptDownloadManually(extension.gallery, localize('failedToInstall', "Failed to install \'{0}\'.", extension.identifier.id), err, this.instantiationService, this.notificationService, this.openerService, this.productService); + return promptDownloadManually(extension.gallery, localize('failedToInstall', "Failed to install \'{0}\'.", extension.identifier.id), err, this.instantiationService); } } } @@ -1704,11 +1757,8 @@ export class InstallRecommendedExtensionAction extends Action { constructor( extensionId: string, @IViewletService private readonly viewletService: IViewletService, - @INotificationService private readonly notificationService: INotificationService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IOpenerService private readonly openerService: IOpenerService, @IExtensionsWorkbenchService private readonly extensionWorkbenchService: IExtensionsWorkbenchService, - @IProductService private readonly productService: IProductService ) { super(InstallRecommendedExtensionAction.ID, InstallRecommendedExtensionAction.LABEL, undefined, false); this.extensionId = extensionId; @@ -1727,7 +1777,7 @@ export class InstallRecommendedExtensionAction extends Action { return this.extensionWorkbenchService.install(extension) .then(() => null, err => { console.error(err); - return promptDownloadManually(extension.gallery, localize('failedToInstall', "Failed to install \'{0}\'.", extension.identifier.id), err, this.instantiationService, this.notificationService, this.openerService, this.productService); + return promptDownloadManually(extension.gallery, localize('failedToInstall', "Failed to install \'{0}\'.", extension.identifier.id), err, this.instantiationService); }); } return null; @@ -1742,9 +1792,8 @@ export class IgnoreExtensionRecommendationAction extends Action { private static readonly Class = 'extension-action ignore'; - extension: IExtension; - constructor( + private readonly extension: IExtension, @IExtensionTipsService private readonly extensionsTipsService: IExtensionTipsService, ) { super(IgnoreExtensionRecommendationAction.ID, 'Ignore Recommendation'); @@ -1766,9 +1815,8 @@ export class UndoIgnoreExtensionRecommendationAction extends Action { private static readonly Class = 'extension-action undo-ignore'; - extension: IExtension; - constructor( + private readonly extension: IExtension, @IExtensionTipsService private readonly extensionsTipsService: IExtensionTipsService, ) { super(UndoIgnoreExtensionRecommendationAction.ID, 'Undo'); @@ -2124,7 +2172,7 @@ export abstract class AbstractConfigureRecommendedExtensionsAction extends Actio protected getWorkspaceFolderExtensionsConfigContent(extensionsFileResource: URI): Promise { return Promise.resolve(this.fileService.readFile(extensionsFileResource)) .then(content => { - return (json.parse(content.value.toString())); + return (json.parse(content.value.toString()) || {}) as IExtensionsConfigContent; // {{SQL CARBON EDIT}} strict-null-check }, err => ({ recommendations: [], unwantedRecommendations: [] })); } @@ -2412,9 +2460,9 @@ export class StatusLabelAction extends Action implements IExtensionContainer { private status: ExtensionState | null = null; private enablementState: EnablementState | null = null; - private _extension: IExtension; - get extension(): IExtension { return this._extension; } - set extension(extension: IExtension) { + private _extension: IExtension | null = null; + get extension(): IExtension | null { return this._extension; } + set extension(extension: IExtension | null) { if (!(this._extension && extension && areSameExtensions(this._extension.identifier, extension.identifier))) { // Different extension. Reset this.initialStatus = null; @@ -2455,21 +2503,21 @@ export class StatusLabelAction extends Action implements IExtensionContainer { const runningExtensions = await this.extensionService.getExtensions(); const canAddExtension = () => { - const runningExtension = runningExtensions.filter(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension.identifier))[0]; - if (this.extension.local) { - if (runningExtension && this.extension.version === runningExtension.version) { + const runningExtension = runningExtensions.filter(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension!.identifier))[0]; + if (this.extension!.local) { + if (runningExtension && this.extension!.version === runningExtension.version) { return true; } - return this.extensionService.canAddExtension(toExtensionDescription(this.extension.local)); + return this.extensionService.canAddExtension(toExtensionDescription(this.extension!.local)); } return false; }; const canRemoveExtension = () => { - if (this.extension.local) { - if (runningExtensions.every(e => !(areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension.identifier) && this.extension.server === this.extensionManagementServerService.getExtensionManagementServer(e.extensionLocation)))) { + if (this.extension!.local) { + if (runningExtensions.every(e => !(areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension!.identifier) && this.extension!.server === this.extensionManagementServerService.getExtensionManagementServer(e.extensionLocation)))) { return true; } - return this.extensionService.canRemoveExtension(toExtensionDescription(this.extension.local)); + return this.extensionService.canRemoveExtension(toExtensionDescription(this.extension!.local)); } return false; }; @@ -2572,7 +2620,7 @@ export class ExtensionToolTipAction extends ExtensionAction { return this.warningAction.tooltip; } if (this.extension && this.extension.local && this.extension.state === ExtensionState.Installed && this._runningExtensions) { - const isRunning = this._runningExtensions.some(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension.identifier)); + const isRunning = this._runningExtensions.some(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension!.identifier)); const isEnabled = this.extensionEnablementService.isEnabled(this.extension.local); if (isEnabled && isRunning) { @@ -2609,8 +2657,8 @@ export class ExtensionToolTipAction extends ExtensionAction { export class SystemDisabledWarningAction extends ExtensionAction { private static readonly CLASS = 'system-disable'; - private static readonly WARNING_CLASS = `${SystemDisabledWarningAction.CLASS} warning`; - private static readonly INFO_CLASS = `${SystemDisabledWarningAction.CLASS} info`; + private static readonly WARNING_CLASS = `${SystemDisabledWarningAction.CLASS} codicon-warning`; + private static readonly INFO_CLASS = `${SystemDisabledWarningAction.CLASS} codicon-info`; updateWhenCounterExtensionChanges: boolean = true; private _runningExtensions: IExtensionDescription[] | null = null; @@ -2620,7 +2668,8 @@ export class SystemDisabledWarningAction extends ExtensionAction { @ILabelService private readonly labelService: ILabelService, @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, @IExtensionService private readonly extensionService: IExtensionService, - @IExtensionEnablementService private readonly extensionEnablementService: IExtensionEnablementService, + @IProductService private readonly productService: IProductService, + @IConfigurationService private readonly configurationService: IConfigurationService, ) { super('extensions.install', '', `${SystemDisabledWarningAction.CLASS} hide`, false); this._register(this.labelService.onDidChangeFormatters(() => this.update(), this)); @@ -2641,37 +2690,48 @@ export class SystemDisabledWarningAction extends ExtensionAction { !this.extension.local || !this.extension.server || !this._runningExtensions || - !(this.extensionManagementServerService.localExtensionManagementServer && this.extensionManagementServerService.remoteExtensionManagementServer) || this.extension.state !== ExtensionState.Installed ) { return; } - if (isLanguagePackExtension(this.extension.local.manifest)) { - if (!this.extensionsWorkbenchService.installed.some(e => areSameExtensions(e.identifier, this.extension.identifier) && e.server !== this.extension.server)) { - this.class = `${SystemDisabledWarningAction.INFO_CLASS}`; - this.tooltip = this.extension.server === this.extensionManagementServerService.localExtensionManagementServer - ? localize('Install language pack also in remote server', "Install the language pack extension on '{0}' to enable it also there.", this.extensionManagementServerService.remoteExtensionManagementServer.label) - : localize('Install language pack also locally', "Install the language pack extension locally to enable it also there."); + if (this.extensionManagementServerService.localExtensionManagementServer && this.extensionManagementServerService.remoteExtensionManagementServer) { + if (isLanguagePackExtension(this.extension.local.manifest)) { + if (!this.extensionsWorkbenchService.installed.some(e => areSameExtensions(e.identifier, this.extension!.identifier) && e.server !== this.extension!.server)) { + this.class = `${SystemDisabledWarningAction.INFO_CLASS}`; + this.tooltip = this.extension.server === this.extensionManagementServerService.localExtensionManagementServer + ? localize('Install language pack also in remote server', "Install the language pack extension on '{0}' to enable it also there.", this.extensionManagementServerService.remoteExtensionManagementServer.label) + : localize('Install language pack also locally', "Install the language pack extension locally to enable it also there."); + } + return; } - return; } - const runningExtension = this._runningExtensions.filter(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension.identifier))[0]; - if (!runningExtension && this.extension.enablementState === EnablementState.DisabledByExtensionKind) { - this.class = `${SystemDisabledWarningAction.WARNING_CLASS}`; - const server = this.extensionManagementServerService.localExtensionManagementServer === this.extension.server ? this.extensionManagementServerService.remoteExtensionManagementServer : this.extensionManagementServerService.localExtensionManagementServer; - this.tooltip = localize('Install in other server to enable', "Install the extension on '{0}' to enable.", server.label); - return; + if (this.extension.enablementState === EnablementState.DisabledByExtensionKind) { + if (!this.extensionsWorkbenchService.installed.some(e => areSameExtensions(e.identifier, this.extension!.identifier) && e.server !== this.extension!.server)) { + const server = this.extensionManagementServerService.localExtensionManagementServer === this.extension.server ? this.extensionManagementServerService.remoteExtensionManagementServer : this.extensionManagementServerService.localExtensionManagementServer; + this.class = `${SystemDisabledWarningAction.WARNING_CLASS}`; + if (server) { + this.tooltip = localize('Install in other server to enable', "Install the extension on '{0}' to enable.", server.label); + } else { + this.tooltip = localize('disabled because of extension kind', "This extension cannot be enabled in the remote server."); + } + return; + } } - if (this.extensionEnablementService.isEnabled(this.extension.local)) { + if (this.extensionManagementServerService.localExtensionManagementServer && this.extensionManagementServerService.remoteExtensionManagementServer) { + const runningExtension = this._runningExtensions.filter(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension!.identifier))[0]; const runningExtensionServer = runningExtension ? this.extensionManagementServerService.getExtensionManagementServer(runningExtension.extensionLocation) : null; if (this.extension.server === this.extensionManagementServerService.localExtensionManagementServer && runningExtensionServer === this.extensionManagementServerService.remoteExtensionManagementServer) { - this.class = `${SystemDisabledWarningAction.INFO_CLASS}`; - this.tooltip = localize('disabled locally', "Extension is enabled on '{0}' and disabled locally.", this.extensionManagementServerService.remoteExtensionManagementServer.label); + if (prefersExecuteOnWorkspace(this.extension.local!.manifest, this.productService, this.configurationService)) { + this.class = `${SystemDisabledWarningAction.INFO_CLASS}`; + this.tooltip = localize('disabled locally', "Extension is enabled on '{0}' and disabled locally.", this.extensionManagementServerService.remoteExtensionManagementServer.label); + } return; } if (this.extension.server === this.extensionManagementServerService.remoteExtensionManagementServer && runningExtensionServer === this.extensionManagementServerService.localExtensionManagementServer) { - this.class = `${SystemDisabledWarningAction.INFO_CLASS}`; - this.tooltip = localize('disabled remotely', "Extension is enabled locally and disabled on '{0}'.", this.extensionManagementServerService.remoteExtensionManagementServer.label); + if (prefersExecuteOnUI(this.extension.local!.manifest, this.productService, this.configurationService)) { + this.class = `${SystemDisabledWarningAction.INFO_CLASS}`; + this.tooltip = localize('disabled remotely', "Extension is enabled locally and disabled on '{0}'.", this.extensionManagementServerService.remoteExtensionManagementServer.label); + } return; } } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsDependencyChecker.ts b/src/vs/workbench/contrib/extensions/browser/extensionsDependencyChecker.ts index 102eb8ee391d..ce24b377d3f2 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsDependencyChecker.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsDependencyChecker.ts @@ -26,10 +26,10 @@ export class ExtensionDependencyChecker extends Disposable implements IWorkbench @IHostService private readonly hostService: IHostService ) { super(); - CommandsRegistry.registerCommand('workbench.extensions.installMissingDepenencies', () => this.installMissingDependencies()); + CommandsRegistry.registerCommand('workbench.extensions.installMissingDependencies', () => this.installMissingDependencies()); MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { - id: 'workbench.extensions.installMissingDepenencies', + id: 'workbench.extensions.installMissingDependencies', category: localize('extensions', "Extensions"), title: localize('auto install missing deps', "Install Missing Dependencies") } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts index ec5dbe78f68b..1ed548c5cc4f 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts @@ -21,6 +21,7 @@ import { IListVirtualDelegate, IListRenderer } from 'vs/base/browser/ui/list/lis import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { CancellationToken } from 'vs/base/common/cancellation'; import { isNonEmptyArray } from 'vs/base/common/arrays'; +import { IColorMapping } from 'vs/platform/theme/common/styler'; export interface IExtensionTemplateData { icon: HTMLImageElement; @@ -179,6 +180,7 @@ export class ExtensionsTree extends WorkbenchAsyncDataTree + return this.progress(Promise.all(this.panes.map(view => (view).show(this.normalizedQuery()) .then(model => this.alertSearchResult(model.length, view.id)) ))).then(() => undefined); } - protected onDidAddViews(added: IAddedViewDescriptorRef[]): ViewletPanel[] { + protected onDidAddViews(added: IAddedViewDescriptorRef[]): ViewletPane[] { const addedViews = super.onDidAddViews(added); this.progress(Promise.all(addedViews.map(addedView => (addedView).show(this.normalizedQuery()) @@ -568,12 +568,12 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio } private count(): number { - return this.panels.reduce((count, view) => (view).count() + count, 0); + return this.panes.reduce((count, view) => (view).count() + count, 0); } private focusListView(): void { if (this.count() > 0) { - this.panels[0].focus(); + this.panes[0].focus(); } } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts index 2103ed95a91e..48b57f60ab73 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts @@ -32,7 +32,7 @@ import { InstallWorkspaceRecommendedExtensionsAction, ConfigureWorkspaceFolderRe import { WorkbenchPagedList } from 'vs/platform/list/browser/listService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; -import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet'; +import { ViewletPane, IViewletPaneOptions } from 'vs/workbench/browser/parts/views/paneViewlet'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { distinct, coalesce, firstIndex } from 'vs/base/common/arrays'; import { IExperimentService, IExperiment, ExperimentActionType } from 'vs/workbench/contrib/experiments/common/experimentService'; @@ -47,6 +47,7 @@ import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async import { IProductService } from 'vs/platform/product/common/productService'; import { SeverityIcon } from 'vs/platform/severityIcon/common/severityIcon'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; class ExtensionsViewState extends Disposable implements IExtensionsViewState { @@ -72,7 +73,7 @@ export interface ExtensionsListViewOptions extends IViewletViewOptions { class ExtensionListViewWarning extends Error { } -export class ExtensionsListView extends ViewletPanel { +export class ExtensionsListView extends ViewletPane { protected readonly server: IExtensionManagementServer | undefined; private bodyTemplate: { @@ -105,7 +106,7 @@ export class ExtensionsListView extends ViewletPanel { @IProductService protected readonly productService: IProductService, @IContextKeyService contextKeyService: IContextKeyService, ) { - super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: options.title, showActionsAlways: true }, keybindingService, contextMenuService, configurationService, contextKeyService); + super({ ...(options as IViewletPaneOptions), ariaHeaderLabel: options.title, showActionsAlways: true }, keybindingService, contextMenuService, configurationService, contextKeyService); this.server = options.server; } @@ -125,11 +126,14 @@ export class ExtensionsListView extends ViewletPanel { const delegate = new Delegate(); const extensionsViewState = new ExtensionsViewState(); const renderer = this.instantiationService.createInstance(Renderer, extensionsViewState); - this.list = this.instantiationService.createInstance(WorkbenchPagedList, 'Extensions', extensionsList, delegate, [renderer], { + this.list = this.instantiationService.createInstance>(WorkbenchPagedList, 'Extensions', extensionsList, delegate, [renderer], { ariaLabel: localize('extensions', "Extensions"), multipleSelectionSupport: false, setRowLineHeight: false, - horizontalScrolling: false + horizontalScrolling: false, + overrideStyles: { + listBackground: SIDE_BAR_BACKGROUND + } }); this._register(this.list.onContextMenu(e => this.onContextMenu(e), this)); this._register(this.list.onFocusChange(e => extensionsViewState.onFocusChange(coalesce(e.elements)), this)); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts index b90e7b77e66e..6da86154861c 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts @@ -121,18 +121,18 @@ export class RatingsWidget extends ExtensionWidget { const rating = Math.round(this.extension.rating * 2) / 2; if (this.small) { - append(this.container, $('span.full.star')); + append(this.container, $('span.codicon.codicon-star-full')); const count = append(this.container, $('span.count')); count.textContent = String(rating); } else { for (let i = 1; i <= 5; i++) { if (rating >= i) { - append(this.container, $('span.full.star')); + append(this.container, $('span.codicon.codicon-star-full')); } else if (rating >= i - 0.5) { - append(this.container, $('span.half.star')); + append(this.container, $('span.codicon.codicon-star-half')); } else { - append(this.container, $('span.empty.star')); + append(this.container, $('span.codicon.codicon-star-empty')); } } } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index 56f1da277fd4..156b2fb3ba85 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -525,12 +525,12 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension @IOpenerService private readonly openerService: IOpenerService // {{SQL CARBON EDIT}} ) { super(); - if (this.extensionManagementServerService.localExtensionManagementServer) { + if (extensionManagementServerService.localExtensionManagementServer) { this.localExtensions = this._register(instantiationService.createInstance(Extensions, extensionManagementServerService.localExtensionManagementServer, ext => this.getExtensionState(ext))); this._register(this.localExtensions.onChange(e => this._onChange.fire(e ? e.extension : undefined))); this._register(Event.filter(this.localExtensions.onChange, e => !!e && e.operation === InstallOperation.Install)(e => this.onDidInstallExtension(e!.extension))); } - if (this.extensionManagementServerService.remoteExtensionManagementServer) { + if (extensionManagementServerService.remoteExtensionManagementServer) { this.remoteExtensions = this._register(instantiationService.createInstance(Extensions, extensionManagementServerService.remoteExtensionManagementServer, ext => this.getExtensionState(ext))); this._register(this.remoteExtensions.onChange(e => this._onChange.fire(e ? e.extension : undefined))); this._register(Event.filter(this.remoteExtensions.onChange, e => !!e && e.operation === InstallOperation.Install)(e => this.onDidInstallExtension(e!.extension))); diff --git a/src/vs/workbench/contrib/extensions/browser/media/clear-dark.svg b/src/vs/workbench/contrib/extensions/browser/media/clear-dark.svg deleted file mode 100644 index 04d64ab41ca3..000000000000 --- a/src/vs/workbench/contrib/extensions/browser/media/clear-dark.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/vs/workbench/contrib/extensions/browser/media/clear-hc.svg b/src/vs/workbench/contrib/extensions/browser/media/clear-hc.svg deleted file mode 100644 index 44a41edd3b3f..000000000000 --- a/src/vs/workbench/contrib/extensions/browser/media/clear-hc.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/vs/workbench/contrib/extensions/browser/media/clear-light.svg b/src/vs/workbench/contrib/extensions/browser/media/clear-light.svg deleted file mode 100644 index f6a51c856f00..000000000000 --- a/src/vs/workbench/contrib/extensions/browser/media/clear-light.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/vs/workbench/contrib/extensions/browser/media/configure-dark.svg b/src/vs/workbench/contrib/extensions/browser/media/configure-dark.svg deleted file mode 100644 index ace01a5ddf5a..000000000000 --- a/src/vs/workbench/contrib/extensions/browser/media/configure-dark.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/vs/workbench/contrib/extensions/browser/media/configure-hc.svg b/src/vs/workbench/contrib/extensions/browser/media/configure-hc.svg deleted file mode 100644 index bd59cb81f6d9..000000000000 --- a/src/vs/workbench/contrib/extensions/browser/media/configure-hc.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/vs/workbench/contrib/extensions/browser/media/configure-light.svg b/src/vs/workbench/contrib/extensions/browser/media/configure-light.svg deleted file mode 100644 index 4194780bbaad..000000000000 --- a/src/vs/workbench/contrib/extensions/browser/media/configure-light.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionActions.css b/src/vs/workbench/contrib/extensions/browser/media/extensionActions.css index 585422ab3337..1c1dd0f47b0b 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionActions.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionActions.css @@ -12,18 +12,6 @@ text-overflow: ellipsis; } -.monaco-action-bar .action-item .action-label.clear-extensions { - background: url('clear-light.svg') center center no-repeat; -} - -.vs-dark .monaco-action-bar .action-item .action-label.clear-extensions { - background: url('clear-dark.svg') center center no-repeat; -} - -.hc-black .monaco-action-bar .action-item .action-label.clear-extensions { - background: url('clear-hc.svg') center center no-repeat; -} - .monaco-action-bar .action-item .action-label.extension-action.multiserver.install:after, .monaco-action-bar .action-item .action-label.extension-action.multiserver.update:after, .monaco-action-bar .action-item .action-label.extension-action.extension-editor-dropdown-action.dropdown:after { @@ -70,12 +58,18 @@ .extension-editor > .header > .details > .actions > .monaco-action-bar > .actions-container > .action-item > .action-label.disable-status { margin-left: 0; + margin-top: 6px; padding-left: 0; } -.extension-editor > .header > .details > .actions > .monaco-action-bar > .actions-container > .action-item > .action-label.system-disable, -.extensions-viewlet>.extensions .extension>.details>.footer>.monaco-action-bar .action-item .action-label.system-disable { - margin: 0.15em; +.extension-editor > .header > .details > .actions > .monaco-action-bar > .actions-container > .action-item > .action-label.system-disable { + margin-right: 0.15em; +} + +.extensions-viewlet>.extensions .extension>.details>.footer>.monaco-action-bar .action-item .action-label.system-disable.codicon-info, +.extensions-viewlet>.extensions .extension>.details>.footer>.monaco-action-bar .action-item .action-label.system-disable.codicon-warning { + margin-top: 0.25em; + margin-left: 0.1em; } .monaco-action-bar .action-item .action-label.system-disable.codicon { @@ -104,17 +98,10 @@ height: 18px; width: 10px; border: none; - background: url('configure-light.svg') center center no-repeat; + color: inherit; + background: none; outline-offset: 0px; - margin-top: 0.15em -} - -.vs-dark .extensions-viewlet>.extensions .extension>.details>.footer>.monaco-action-bar .action-item .action-label.extension-action.manage { - background: url('configure-dark.svg') center center no-repeat; -} - -.hc-black .extensions-viewlet>.extensions .extension>.details>.footer>.monaco-action-bar .action-item .action-label.extension-action.manage { - background: url('configure-hc.svg') center center no-repeat; + margin-top: 0.2em; } .extension-editor > .header.recommended > .details > .recommendation > .monaco-action-bar .actions-container { diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css index aed18ad134d0..3dbadc23a9ca 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css @@ -54,6 +54,7 @@ padding-left: 20px; overflow: hidden; user-select: text; + -webkit-user-select: text; } .extension-editor > .header > .details > .title { @@ -77,6 +78,7 @@ padding: 0px 4px; border-radius: 4px; user-select: text; + -webkit-user-select: text; white-space: nowrap; } @@ -98,6 +100,7 @@ padding: 0px 4px; border-radius: 4px; user-select: none; + -webkit-user-select: none; } .extension-editor > .header > .details > .subtitle { @@ -111,6 +114,13 @@ font-size: 18px; } +.extension-editor > .header > .details > .subtitle, +.extension-editor > .header > .details > .subtitle .install, +.extension-editor > .header > .details > .subtitle .rating { + display: flex; + align-items: center; +} + .extension-editor > .header > .details > .subtitle > .install > .count { margin-left: 6px; } @@ -219,6 +229,7 @@ position: relative; overflow: hidden; user-select: text; + -webkit-user-select: text; } .extension-editor > .body > .content.loading { diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensions-activity-bar.svg b/src/vs/workbench/contrib/extensions/browser/media/extensions-activity-bar.svg deleted file mode 100644 index 93e4b554dab3..000000000000 --- a/src/vs/workbench/contrib/extensions/browser/media/extensions-activity-bar.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensions.css b/src/vs/workbench/contrib/extensions/browser/media/extensions.css deleted file mode 100644 index d581b73aabc7..000000000000 --- a/src/vs/workbench/contrib/extensions/browser/media/extensions.css +++ /dev/null @@ -1,8 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -.monaco-workbench .activitybar > .content .monaco-action-bar .action-label.extensions { - -webkit-mask: url('extensions-activity-bar.svg') no-repeat 50% 50%; -} diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionsViewlet.css b/src/vs/workbench/contrib/extensions/browser/media/extensionsViewlet.css index 7e0a854e3cd7..c734f95fd283 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionsViewlet.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionsViewlet.css @@ -21,10 +21,11 @@ padding: 4px; border: 1px solid transparent; -webkit-appearance: textfield; + -moz-appearance: textfield; } .extensions-viewlet > .extensions { - height: calc(100% - 38px); + height: calc(100% - 41px); } .extensions-viewlet > .extensions .extension-view-header .monaco-action-bar { @@ -89,7 +90,7 @@ top: 1px; left: 1px; color: inherit; - font-size: 90%; + font-size: 80%; } .extensions-viewlet > .extensions .extension { @@ -190,20 +191,28 @@ font-size: 80%; padding-left: 6px; min-width: fit-content; + min-width: -moz-fit-content; } .extensions-viewlet:not(.narrow) > .extensions .extension > .details > .header-container > .header > .version { flex: 1; } +.extensions-viewlet > .extensions .extension > .details > .header-container > .header > .install-count, +.extensions-viewlet > .extensions .extension > .details > .header-container > .header > .ratings { + display: flex; + align-items: center; +} + .extensions-viewlet > .extensions .extension > .details > .header-container > .header > .install-count:not(:empty) { font-size: 80%; margin: 0 6px; } -.extensions-viewlet > .extensions .extension > .details > .header-container > .header > .install-count > .codicon { +.extensions-viewlet > .extensions .extension > .details > .header-container > .header .codicon { font-size: 120%; margin-right: 2px; + -webkit-mask: inherit; } .extensions-viewlet > .extensions .extension > .details > .header-container > .header > .ratings { diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionsWidgets.css b/src/vs/workbench/contrib/extensions/browser/media/extensionsWidgets.css index f267cc5de5a7..f2db18e4fa64 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionsWidgets.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionsWidgets.css @@ -11,40 +11,24 @@ font-size: 80%; } -.extension-ratings > .star { - display: inline-block; - width: 16px; - height: 16px; - background-repeat: no-repeat; - background-position: center center; -} - -.extension-ratings > .star:not(:first-child) { +.extension-ratings > .codicon[class*='codicon-star']:not(:first-child) { margin-left: 3px; } -.extension-ratings.small > .star { - width: 10px; - height: 10px; - background-image: url('star-small.svg'); -} - -.extension-ratings > .full { - background-image: url('star-full.svg'); +.extension-ratings > .count { + margin-left: 6px; } -.extension-ratings > .half { - background-image: url('star-half.svg'); +.extension-ratings.small > .count { + margin-left: 0; } -.extension-ratings > .empty { - background-image: url('star-empty.svg'); +/* TODO @misolori make this a color token */ +.extension-ratings .codicon-star-full, +.extension-ratings .codicon-star-half { + color: #FF8E00 !important; } -.extension-ratings > .count { - margin-left: 6px; +.extension-ratings .codicon-star-empty { + opacity: .4; } - -.extension-ratings.small > .count { - margin-left: 2px; -} \ No newline at end of file diff --git a/src/vs/workbench/contrib/extensions/browser/media/star-empty.svg b/src/vs/workbench/contrib/extensions/browser/media/star-empty.svg deleted file mode 100644 index 999de6818171..000000000000 --- a/src/vs/workbench/contrib/extensions/browser/media/star-empty.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/vs/workbench/contrib/extensions/browser/media/star-full.svg b/src/vs/workbench/contrib/extensions/browser/media/star-full.svg deleted file mode 100644 index 1603b080b270..000000000000 --- a/src/vs/workbench/contrib/extensions/browser/media/star-full.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/extensions/browser/media/star-half.svg b/src/vs/workbench/contrib/extensions/browser/media/star-half.svg deleted file mode 100644 index 7615f5816540..000000000000 --- a/src/vs/workbench/contrib/extensions/browser/media/star-half.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/extensions/browser/media/star-small.svg b/src/vs/workbench/contrib/extensions/browser/media/star-small.svg deleted file mode 100644 index 41c128627fe0..000000000000 --- a/src/vs/workbench/contrib/extensions/browser/media/star-small.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionProfileService.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionProfileService.ts index 39cfaeed3899..f85d69efb107 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionProfileService.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionProfileService.ts @@ -81,7 +81,7 @@ export class ExtensionHostProfileService extends Disposable implements IExtensio if (visible) { const indicator: IStatusbarEntry = { - text: nls.localize('profilingExtensionHost', "$(sync~spin) Profiling Extension Host"), + text: '$(sync~spin) ' + nls.localize('profilingExtensionHost', "Profiling Extension Host"), tooltip: nls.localize('selectAndStartDebug', "Click to stop profiling."), command: 'workbench.action.extensionHostProfilder.stop' }; @@ -89,7 +89,7 @@ export class ExtensionHostProfileService extends Disposable implements IExtensio const timeStarted = Date.now(); const handle = setInterval(() => { if (this.profilingStatusBarIndicator) { - this.profilingStatusBarIndicator.update({ ...indicator, text: nls.localize('profilingExtensionHostTime', "$(sync~spin) Profiling Extension Host ({0} sec)", Math.round((new Date().getTime() - timeStarted) / 1000)), }); + this.profilingStatusBarIndicator.update({ ...indicator, text: '$(sync~spin) ' + nls.localize('profilingExtensionHostTime', "Profiling Extension Host ({0} sec)", Math.round((new Date().getTime() - timeStarted) / 1000)), }); } }, 1000); this.profilingStatusBarIndicatorLabelUpdater.value = toDisposable(() => clearInterval(handle)); diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts index 88e87217416f..f6436341e1b9 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts @@ -34,7 +34,7 @@ workbenchRegistry.registerWorkbenchContribution(ExtensionsAutoProfiler, Lifecycl // Running Extensions Editor -const runtimeExtensionsEditorDescriptor = new EditorDescriptor( +const runtimeExtensionsEditorDescriptor = EditorDescriptor.create( RuntimeExtensionsEditor, RuntimeExtensionsEditor.ID, localize('runtimeExtension', "Running Extensions") @@ -58,7 +58,7 @@ Registry.as(EditorInputExtensions.EditorInputFactor // Global actions const actionRegistry = Registry.as(WorkbenchActionExtensions.WorkbenchActions); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ShowRuntimeExtensionsAction, ShowRuntimeExtensionsAction.ID, ShowRuntimeExtensionsAction.LABEL), 'Show Running Extensions', localize('developer', "Developer")); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ShowRuntimeExtensionsAction, ShowRuntimeExtensionsAction.ID, ShowRuntimeExtensionsAction.LABEL), 'Show Running Extensions', localize('developer', "Developer")); class ExtensionsContributions implements IWorkbenchContribution { @@ -66,7 +66,7 @@ class ExtensionsContributions implements IWorkbenchContribution { @IWorkbenchEnvironmentService workbenchEnvironmentService: IWorkbenchEnvironmentService ) { if (workbenchEnvironmentService.extensionsPath) { - const openExtensionsFolderActionDescriptor = new SyncActionDescriptor(OpenExtensionsFolderAction, OpenExtensionsFolderAction.ID, OpenExtensionsFolderAction.LABEL); + const openExtensionsFolderActionDescriptor = SyncActionDescriptor.create(OpenExtensionsFolderAction, OpenExtensionsFolderAction.ID, OpenExtensionsFolderAction.LABEL); actionRegistry.registerWorkbenchAction(openExtensionsFolderActionDescriptor, 'Extensions: Open Extensions Folder', ExtensionsLabel); } } @@ -102,7 +102,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: DebugExtensionHostAction.ID, title: DebugExtensionHostAction.LABEL, - iconLocation: { + icon: { dark: URI.parse(registerAndGetAmdImageURL(`vs/workbench/contrib/extensions/browser/media/start-dark.svg`)), light: URI.parse(registerAndGetAmdImageURL(`vs/workbench/contrib/extensions/browser/media/start-light.svg`)), } @@ -115,7 +115,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: StartExtensionHostProfileAction.ID, title: StartExtensionHostProfileAction.LABEL, - iconLocation: { + icon: { dark: URI.parse(registerAndGetAmdImageURL(`vs/workbench/contrib/extensions/browser/media/profile-start-dark.svg`)), light: URI.parse(registerAndGetAmdImageURL(`vs/workbench/contrib/extensions/browser/media/profile-start-light.svg`)), } @@ -128,7 +128,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: StopExtensionHostProfileAction.ID, title: StopExtensionHostProfileAction.LABEL, - iconLocation: { + icon: { dark: URI.parse(registerAndGetAmdImageURL(`vs/workbench/contrib/extensions/browser/media/profile-stop-dark.svg`)), light: URI.parse(registerAndGetAmdImageURL(`vs/workbench/contrib/extensions/browser/media/profile-stop-light.svg`)), } @@ -141,7 +141,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: SaveExtensionHostProfileAction.ID, title: SaveExtensionHostProfileAction.LABEL, - iconLocation: { + icon: { dark: URI.parse(registerAndGetAmdImageURL(`vs/workbench/contrib/extensions/browser/media/save-dark.svg`)), light: URI.parse(registerAndGetAmdImageURL(`vs/workbench/contrib/extensions/browser/media/save-light.svg`)), }, diff --git a/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts b/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts index 84d8459b0a05..237120bf100c 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts @@ -45,6 +45,7 @@ import { SlowExtensionAction } from 'vs/workbench/contrib/extensions/electron-br import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { URI } from 'vs/base/common/uri'; +import { editorBackground } from 'vs/platform/theme/common/colorRegistry'; export const IExtensionHostProfileService = createDecorator('extensionHostProfileService'); export const CONTEXT_PROFILE_SESSION_STATE = new RawContextKey('profileSessionState', 'none'); @@ -405,12 +406,15 @@ export class RuntimeExtensionsEditor extends BaseEditor { } }; - this._list = this._instantiationService.createInstance(WorkbenchList, + this._list = this._instantiationService.createInstance>(WorkbenchList, 'RuntimeExtensions', parent, delegate, [renderer], { multipleSelectionSupport: false, setRowLineHeight: false, - horizontalScrolling: false + horizontalScrolling: false, + overrideStyles: { + listBackground: editorBackground + } }); this._list.splice(0, this._list.length, this._elements || undefined); diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts index 1bba4ecdf890..0f84c9c4fbb3 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts @@ -497,7 +497,7 @@ suite('ExtensionsActions Test', () => { .then(extensions => { testObject.extension = extensions[0]; assert.ok(testObject.enabled); - assert.equal('extension-action manage', testObject.class); + assert.equal('extension-action manage codicon-gear', testObject.class); assert.equal('', testObject.tooltip); }); }); @@ -512,7 +512,7 @@ suite('ExtensionsActions Test', () => { .then(page => { testObject.extension = page.firstPage[0]; assert.ok(!testObject.enabled); - assert.equal('extension-action manage hide', testObject.class); + assert.equal('extension-action manage codicon-gear hide', testObject.class); assert.equal('', testObject.tooltip); }); }); @@ -529,7 +529,7 @@ suite('ExtensionsActions Test', () => { installEvent.fire({ identifier: gallery.identifier, gallery }); assert.ok(!testObject.enabled); - assert.equal('extension-action manage hide', testObject.class); + assert.equal('extension-action manage codicon-gear hide', testObject.class); assert.equal('', testObject.tooltip); }); }); @@ -547,7 +547,7 @@ suite('ExtensionsActions Test', () => { didInstallEvent.fire({ identifier: gallery.identifier, gallery, operation: InstallOperation.Install, local: aLocalExtension('a', gallery, gallery) }); assert.ok(testObject.enabled); - assert.equal('extension-action manage', testObject.class); + assert.equal('extension-action manage codicon-gear', testObject.class); assert.equal('', testObject.tooltip); }); }); @@ -562,7 +562,7 @@ suite('ExtensionsActions Test', () => { .then(extensions => { testObject.extension = extensions[0]; assert.ok(testObject.enabled); - assert.equal('extension-action manage', testObject.class); + assert.equal('extension-action manage codicon-gear', testObject.class); assert.equal('', testObject.tooltip); }); }); @@ -579,7 +579,7 @@ suite('ExtensionsActions Test', () => { uninstallEvent.fire(local.identifier); assert.ok(!testObject.enabled); - assert.equal('extension-action manage', testObject.class); + assert.equal('extension-action manage codicon-gear', testObject.class); assert.equal('Uninstalling', testObject.tooltip); }); }); @@ -1362,8 +1362,8 @@ suite('ExtensionsActions Test', () => { test('Test ReloadAction when extension is not installed but extension from different server is installed and running', async () => { // multi server setup const gallery = aGalleryExtension('a'); - const localExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file('pub.a') }); - const remoteExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file('pub.a').with({ scheme: Schemas.vscodeRemote }) }); + const localExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file('pub.a') }); + const remoteExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file('pub.a').with({ scheme: Schemas.vscodeRemote }) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), createExtensionManagementService([remoteExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); @@ -1391,8 +1391,8 @@ suite('ExtensionsActions Test', () => { test('Test ReloadAction when extension is uninstalled but extension from different server is installed and running', async () => { // multi server setup const gallery = aGalleryExtension('a'); - const localExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file('pub.a') }); - const remoteExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file('pub.a').with({ scheme: Schemas.vscodeRemote }) }); + const localExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file('pub.a') }); + const remoteExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file('pub.a').with({ scheme: Schemas.vscodeRemote }) }); const localExtensionManagementService = createExtensionManagementService([localExtension]); const uninstallEvent = new Emitter(); const onDidUninstallEvent = new Emitter<{ identifier: IExtensionIdentifier }>(); @@ -1433,7 +1433,7 @@ suite('ExtensionsActions Test', () => { const remoteExtensionManagementService = createExtensionManagementService([]); const onDidInstallEvent = new Emitter(); remoteExtensionManagementService.onDidInstallExtension = onDidInstallEvent.event; - const localExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file('pub.a') }); + const localExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file('pub.a') }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), remoteExtensionManagementService); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); @@ -1456,20 +1456,20 @@ suite('ExtensionsActions Test', () => { assert.ok(testObject.extension); assert.ok(!testObject.enabled); - const remoteExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file('pub.a').with({ scheme: Schemas.vscodeRemote }) }); + const remoteExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file('pub.a').with({ scheme: Schemas.vscodeRemote }) }); onDidInstallEvent.fire({ identifier: remoteExtension.identifier, local: remoteExtension, operation: InstallOperation.Install }); assert.ok(testObject.enabled); assert.equal(testObject.tooltip, 'Please reload Azure Data Studio to enable this extension.'); // {{SQL CARBON EDIT}} - replace Visual Studio Code with Azure Data Studio }); - test('Test ReloadAction is disabled when remote ui extension is installed in local server', async () => { + test('Test ReloadAction when ui extension is disabled on remote server and installed in local server', async () => { // multi server setup const gallery = aGalleryExtension('a'); const localExtensionManagementService = createExtensionManagementService([]); const onDidInstallEvent = new Emitter(); localExtensionManagementService.onDidInstallExtension = onDidInstallEvent.event; - const remoteExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file('pub.a').with({ scheme: Schemas.vscodeRemote }) }); + const remoteExtension = aLocalExtension('a', { extensionKind: ['ui'] }, { location: URI.file('pub.a').with({ scheme: Schemas.vscodeRemote }) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, localExtensionManagementService, createExtensionManagementService([remoteExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); @@ -1478,7 +1478,7 @@ suite('ExtensionsActions Test', () => { const onDidChangeExtensionsEmitter: Emitter = new Emitter(); instantiationService.stub(IExtensionService, >{ - getExtensions: () => Promise.resolve([ExtensionsActions.toExtensionDescription(remoteExtension)]), + getExtensions: () => Promise.resolve([]), onDidChangeExtensions: onDidChangeExtensionsEmitter.event, canAddExtension: (extension) => false }); @@ -1492,20 +1492,21 @@ suite('ExtensionsActions Test', () => { assert.ok(testObject.extension); assert.ok(!testObject.enabled); - const localExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file('pub.a') }); + const localExtension = aLocalExtension('a', { extensionKind: ['ui'] }, { location: URI.file('pub.a') }); onDidInstallEvent.fire({ identifier: localExtension.identifier, local: localExtension, operation: InstallOperation.Install }); - assert.ok(!testObject.enabled); + assert.ok(testObject.enabled); + assert.equal(testObject.tooltip, 'Please reload Azure Data Studio to enable this extension.'); // {{SQL CARBON EDIT}} - replace Visual Studio Code with Azure Data Studio }); test('Test ReloadAction for remote ui extension is disabled when it is installed and enabled in local server', async () => { // multi server setup const gallery = aGalleryExtension('a'); - const localExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file('pub.a') }); + const localExtension = aLocalExtension('a', { extensionKind: ['ui'] }, { location: URI.file('pub.a') }); const localExtensionManagementService = createExtensionManagementService([localExtension]); const onDidInstallEvent = new Emitter(); localExtensionManagementService.onDidInstallExtension = onDidInstallEvent.event; - const remoteExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file('pub.a').with({ scheme: Schemas.vscodeRemote }) }); + const remoteExtension = aLocalExtension('a', { extensionKind: ['ui'] }, { location: URI.file('pub.a').with({ scheme: Schemas.vscodeRemote }) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, localExtensionManagementService, createExtensionManagementService([remoteExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); @@ -1529,40 +1530,9 @@ suite('ExtensionsActions Test', () => { assert.ok(!testObject.enabled); }); - test('Test ReloadAction for local ui extension is disabled when it is installed and enabled in remote server', async () => { - // multi server setup - const gallery = aGalleryExtension('a'); - const localExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file('pub.a') }); - const localExtensionManagementService = createExtensionManagementService([localExtension]); - const onDidInstallEvent = new Emitter(); - localExtensionManagementService.onDidInstallExtension = onDidInstallEvent.event; - const remoteExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file('pub.a').with({ scheme: Schemas.vscodeRemote }) }); - const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, localExtensionManagementService, createExtensionManagementService([remoteExtension])); - instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); - const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); - instantiationService.set(IExtensionsWorkbenchService, workbenchService); - - const onDidChangeExtensionsEmitter: Emitter = new Emitter(); - instantiationService.stub(IExtensionService, >{ - getExtensions: () => Promise.resolve([ExtensionsActions.toExtensionDescription(remoteExtension)]), - onDidChangeExtensions: onDidChangeExtensionsEmitter.event, - canAddExtension: (extension) => false - }); - const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); - instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); - - await workbenchService.queryGallery(CancellationToken.None); - const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!); - testObject.extension = extensions[0]; - assert.ok(testObject.extension); - assert.ok(!testObject.enabled); - }); - test('Test remote install action is enabled for local workspace extension', async () => { // multi server setup - const localWorkspaceExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`) }); + const localWorkspaceExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localWorkspaceExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); @@ -1586,7 +1556,7 @@ suite('ExtensionsActions Test', () => { const remoteExtensionManagementService: IExtensionManagementService = createExtensionManagementService(); const onInstallExtension = new Emitter(); remoteExtensionManagementService.onInstallExtension = onInstallExtension.event; - const localWorkspaceExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`) }); + const localWorkspaceExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localWorkspaceExtension]), remoteExtensionManagementService); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); @@ -1619,7 +1589,7 @@ suite('ExtensionsActions Test', () => { remoteExtensionManagementService.onInstallExtension = onInstallExtension.event; const onDidInstallEvent = new Emitter(); remoteExtensionManagementService.onDidInstallExtension = onDidInstallEvent.event; - const localWorkspaceExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`) }); + const localWorkspaceExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localWorkspaceExtension]), remoteExtensionManagementService); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); @@ -1644,14 +1614,14 @@ suite('ExtensionsActions Test', () => { assert.equal('Installing', testObject.label); assert.equal('extension-action install installing', testObject.class); - const installedExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + const installedExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); onDidInstallEvent.fire({ identifier: installedExtension.identifier, local: installedExtension, operation: InstallOperation.Install }); assert.ok(!testObject.enabled); }); test('Test remote install action is enabled for disabled local workspace extension', async () => { // multi server setup - const localWorkspaceExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`) }); + const localWorkspaceExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localWorkspaceExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); @@ -1673,7 +1643,7 @@ suite('ExtensionsActions Test', () => { test('Test remote install action is disabled when extension is not set', async () => { // multi server setup - const localWorkspaceExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`) }); + const localWorkspaceExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localWorkspaceExtension])); instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); @@ -1708,7 +1678,7 @@ suite('ExtensionsActions Test', () => { test('Test remote install action is disabled for local workspace extension which is disabled in env', async () => { // multi server setup - const localWorkspaceExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`) }); + const localWorkspaceExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localWorkspaceExtension])); instantiationService.stub(IWorkbenchEnvironmentService, { disableExtensions: true } as IWorkbenchEnvironmentService); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); @@ -1731,7 +1701,7 @@ suite('ExtensionsActions Test', () => { // single server setup const workbenchService = instantiationService.get(IExtensionsWorkbenchService); const extensionManagementServerService = instantiationService.get(IExtensionManagementServerService); - const localWorkspaceExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`) }); + const localWorkspaceExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`) }); instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [localWorkspaceExtension]); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: localWorkspaceExtension.identifier }))); const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction); @@ -1750,7 +1720,7 @@ suite('ExtensionsActions Test', () => { const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, extensionManagementService); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); - const localWorkspaceExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`) }); + const localWorkspaceExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`) }); instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [localWorkspaceExtension]); const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); instantiationService.set(IExtensionsWorkbenchService, workbenchService); @@ -1771,8 +1741,8 @@ suite('ExtensionsActions Test', () => { test('Test remote install action is disabled for local workspace extension if it is installed in remote', async () => { // multi server setup - const localWorkspaceExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`) }); - const remoteWorkspaceExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + const localWorkspaceExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`) }); + const remoteWorkspaceExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localWorkspaceExtension]), createExtensionManagementService([remoteWorkspaceExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); @@ -1792,7 +1762,7 @@ suite('ExtensionsActions Test', () => { test('Test remote install action is enabled for local workspace extension if it has not gallery', async () => { // multi server setup - const localWorkspaceExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`) }); + const localWorkspaceExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localWorkspaceExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); @@ -1811,7 +1781,7 @@ suite('ExtensionsActions Test', () => { test('Test remote install action is disabled for local workspace system extension', async () => { // multi server setup - const localWorkspaceSystemExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`), type: ExtensionType.System }); + const localWorkspaceSystemExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`), type: ExtensionType.System }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localWorkspaceSystemExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); @@ -1830,7 +1800,7 @@ suite('ExtensionsActions Test', () => { test('Test remote install action is disabled for local ui extension if it is not installed in remote', async () => { // multi server setup - const localUIExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`) }); + const localUIExtension = aLocalExtension('a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localUIExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); @@ -1849,8 +1819,8 @@ suite('ExtensionsActions Test', () => { test('Test remote install action is disabled for local ui extension if it is also installed in remote', async () => { // multi server setup - const localUIExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`) }); - const remoteUIExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + const localUIExtension = aLocalExtension('a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`) }); + const remoteUIExtension = aLocalExtension('a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localUIExtension]), createExtensionManagementService([remoteUIExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); @@ -1913,9 +1883,9 @@ suite('ExtensionsActions Test', () => { assert.ok(!testObject.enabled); }); - test('Test local install action is disabled for remote ui extension', async () => { + test('Test local install action is enabled for remote ui extension', async () => { // multi server setup - const remoteUIExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + const remoteUIExtension = aLocalExtension('a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService(), createExtensionManagementService([remoteUIExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); @@ -1929,7 +1899,9 @@ suite('ExtensionsActions Test', () => { const extensions = await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!); await workbenchService.queryGallery(CancellationToken.None); testObject.extension = extensions[0]; - assert.ok(!testObject.enabled); + assert.ok(testObject.enabled); + assert.equal('Install Locally', testObject.label); + assert.equal('extension-action prominent install', testObject.class); }); test('Test local install action when installing remote ui extension', async () => { @@ -1937,7 +1909,7 @@ suite('ExtensionsActions Test', () => { const localExtensionManagementService: IExtensionManagementService = createExtensionManagementService(); const onInstallExtension = new Emitter(); localExtensionManagementService.onInstallExtension = onInstallExtension.event; - const remoteUIExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + const remoteUIExtension = aLocalExtension('a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, localExtensionManagementService, createExtensionManagementService([remoteUIExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); @@ -1953,12 +1925,56 @@ suite('ExtensionsActions Test', () => { const extensions = await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!); await workbenchService.queryGallery(CancellationToken.None); testObject.extension = extensions[0]; + assert.ok(testObject.enabled); + assert.equal('Install Locally', testObject.label); + assert.equal('extension-action prominent install', testObject.class); + + onInstallExtension.fire({ identifier: remoteUIExtension.identifier, gallery }); + assert.ok(testObject.enabled); + assert.equal('Installing', testObject.label); + assert.equal('extension-action install installing', testObject.class); + }); + + test('Test local install action when installing remote ui extension is finished', async () => { + // multi server setup + const localExtensionManagementService: IExtensionManagementService = createExtensionManagementService(); + const onInstallExtension = new Emitter(); + localExtensionManagementService.onInstallExtension = onInstallExtension.event; + const onDidInstallEvent = new Emitter(); + localExtensionManagementService.onDidInstallExtension = onDidInstallEvent.event; + const remoteUIExtension = aLocalExtension('a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, localExtensionManagementService, createExtensionManagementService([remoteUIExtension])); + instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); + instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.stub(IExtensionsWorkbenchService, workbenchService, 'open', undefined); + instantiationService.set(IExtensionsWorkbenchService, workbenchService); + + const gallery = aGalleryExtension('a', { identifier: remoteUIExtension.identifier }); + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); + const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.LocalInstallAction); + instantiationService.createInstance(ExtensionContainers, [testObject]); + + const extensions = await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!); + await workbenchService.queryGallery(CancellationToken.None); + testObject.extension = extensions[0]; + assert.ok(testObject.enabled); + assert.equal('Install Locally', testObject.label); + assert.equal('extension-action prominent install', testObject.class); + + onInstallExtension.fire({ identifier: remoteUIExtension.identifier, gallery }); + assert.ok(testObject.enabled); + assert.equal('Installing', testObject.label); + assert.equal('extension-action install installing', testObject.class); + + const installedExtension = aLocalExtension('a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`) }); + onDidInstallEvent.fire({ identifier: installedExtension.identifier, local: installedExtension, operation: InstallOperation.Install }); assert.ok(!testObject.enabled); }); - test('Test local install action is disabled for disabled remote ui extension', async () => { + test('Test local install action is enabled for disabled remote ui extension', async () => { // multi server setup - const remoteUIExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + const remoteUIExtension = aLocalExtension('a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService(), createExtensionManagementService([remoteUIExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); @@ -1973,12 +1989,14 @@ suite('ExtensionsActions Test', () => { const extensions = await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!); await workbenchService.queryGallery(CancellationToken.None); testObject.extension = extensions[0]; - assert.ok(!testObject.enabled); + assert.ok(testObject.enabled); + assert.equal('Install Locally', testObject.label); + assert.equal('extension-action prominent install', testObject.class); }); test('Test local install action is disabled when extension is not set', async () => { // multi server setup - const remoteUIExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + const remoteUIExtension = aLocalExtension('a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService(), createExtensionManagementService([remoteUIExtension])); instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); @@ -2013,7 +2031,7 @@ suite('ExtensionsActions Test', () => { test('Test local install action is disabled for remote ui extension which is disabled in env', async () => { // multi server setup - const remoteUIExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + const remoteUIExtension = aLocalExtension('a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); instantiationService.stub(IWorkbenchEnvironmentService, { disableExtensions: true } as IWorkbenchEnvironmentService); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService(), createExtensionManagementService([remoteUIExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); @@ -2034,7 +2052,7 @@ suite('ExtensionsActions Test', () => { test('Test local install action is disabled when local server is not available', async () => { // single server setup - const remoteUIExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + const remoteUIExtension = aLocalExtension('a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); const extensionManagementServerService = aSingleRemoteExtensionManagementServerService(instantiationService, createExtensionManagementService([remoteUIExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); @@ -2054,8 +2072,8 @@ suite('ExtensionsActions Test', () => { test('Test local install action is disabled for remote ui extension if it is installed in local', async () => { // multi server setup - const localUIExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`) }); - const remoteUIExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + const localUIExtension = aLocalExtension('a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`) }); + const remoteUIExtension = aLocalExtension('a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localUIExtension]), createExtensionManagementService([remoteUIExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); @@ -2073,13 +2091,13 @@ suite('ExtensionsActions Test', () => { assert.ok(!testObject.enabled); }); - test('Test local install action is disabled for remote UI extension if it uninstalled locally', async () => { + test('Test local install action is disabled for remoteUI extension if it is uninstalled locally', async () => { // multi server setup const extensionManagementService = instantiationService.get(IExtensionManagementService); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService(), extensionManagementService); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); - const remoteUIExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + const remoteUIExtension = aLocalExtension('a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [remoteUIExtension]); const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); instantiationService.set(IExtensionsWorkbenchService, workbenchService); @@ -2091,15 +2109,16 @@ suite('ExtensionsActions Test', () => { const extensions = await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!); await workbenchService.queryGallery(CancellationToken.None); testObject.extension = extensions[0]; - assert.ok(!testObject.enabled); + assert.ok(testObject.enabled); + assert.equal('Install Locally', testObject.label); uninstallEvent.fire(remoteUIExtension.identifier); assert.ok(!testObject.enabled); }); - test('Test local install action is disabled for remote UI extension if it has gallery', async () => { + test('Test local install action is enabled for remote UI extension if it has gallery', async () => { // multi server setup - const remoteUIExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + const remoteUIExtension = aLocalExtension('a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService(), createExtensionManagementService([remoteUIExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); @@ -2113,12 +2132,12 @@ suite('ExtensionsActions Test', () => { const extensions = await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!); testObject.extension = extensions[0]; assert.ok(testObject.extension); - assert.ok(!testObject.enabled); + assert.ok(testObject.enabled); }); test('Test local install action is disabled for remote UI system extension', async () => { // multi server setup - const remoteUISystemExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }), type: ExtensionType.System }); + const remoteUISystemExtension = aLocalExtension('a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }), type: ExtensionType.System }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService(), createExtensionManagementService([remoteUISystemExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); @@ -2137,7 +2156,7 @@ suite('ExtensionsActions Test', () => { test('Test local install action is disabled for remote workspace extension if it is not installed in local', async () => { // multi server setup - const remoteWorkspaceExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + const remoteWorkspaceExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService(), createExtensionManagementService([remoteWorkspaceExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); @@ -2156,8 +2175,8 @@ suite('ExtensionsActions Test', () => { test('Test local install action is disabled for remote workspace extension if it is also installed in local', async () => { // multi server setup - const localWorkspaceExtension = aLocalExtension('a', { extensionKind: 'workspae' }, { location: URI.file(`pub.a`) }); - const remoteWorkspaceExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + const localWorkspaceExtension = aLocalExtension('a', { extensionKind: ['workspae'] }, { location: URI.file(`pub.a`) }); + const remoteWorkspaceExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localWorkspaceExtension]), createExtensionManagementService([remoteWorkspaceExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts index 965d9a1493ad..8c571f30d7a6 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts @@ -206,7 +206,7 @@ suite.skip('ExtensionsTipsService Test', () => { // {{SQL CARBON EDIT}} skip sui ...{ extensionTips: { 'ms-vscode.csharp': '{**/*.cs,**/project.json,**/global.json,**/*.csproj,**/*.sln,**/appsettings.json}', - 'msjsdiag.debugger-for-chrome': '{**/*.ts,**/*.tsx**/*.js,**/*.jsx,**/*.es6,**/.babelrc}', + 'msjsdiag.debugger-for-chrome': '{**/*.ts,**/*.tsx,**/*.js,**/*.jsx,**/*.es6,**/*.mjs,**/*.cjs,**/.babelrc}', 'lukehoban.Go': '**/*.go' }, extensionImportantTips: { @@ -501,6 +501,7 @@ suite.skip('ExtensionsTipsService Test', () => { // {{SQL CARBON EDIT}} skip sui const ignoredExtensionId = 'Some.Extension'; instantiationService.stub(IStorageService, { // {{SQL CARBON EDIT}} strict-null-checks? get: (a: string, b: StorageScope, c?: boolean) => a === 'extensionsAssistant/ignored_recommendations' ? '["ms-vscode.vscode"]' : c, + getBoolean: (a: string, b: StorageScope, c: boolean) => c, store: (...args: any[]) => { storageSetterTarget(...args); } @@ -519,7 +520,7 @@ suite.skip('ExtensionsTipsService Test', () => { // {{SQL CARBON EDIT}} skip sui test('ExtensionTipsService: Get file based recommendations from storage (old format)', () => { const storedRecommendations = '["ms-vscode.csharp", "ms-python.python", "ms-vscode.vscode-typescript-tslint-plugin"]'; - instantiationService.stub(IStorageService, >{ get: (a: string, b: StorageScope, c?: string) => a === 'extensionsAssistant/recommendations' ? storedRecommendations : c }); + instantiationService.stub(IStorageService, >{ get: (a: string, b: StorageScope, c?: string) => a === 'extensionsAssistant/recommendations' ? storedRecommendations : c, getBoolean: (a: string, b: StorageScope, c: boolean) => c }); return setUpFolderWorkspace('myFolder', []).then(() => { testObject = instantiationService.createInstance(ExtensionTipsService); @@ -538,7 +539,7 @@ suite.skip('ExtensionsTipsService Test', () => { // {{SQL CARBON EDIT}} skip sui const now = Date.now(); const tenDaysOld = 10 * milliSecondsInADay; const storedRecommendations = `{"ms-vscode.csharp": ${now}, "ms-python.python": ${now}, "ms-vscode.vscode-typescript-tslint-plugin": ${now}, "lukehoban.Go": ${tenDaysOld}}`; - instantiationService.stub(IStorageService, >{ get: (a: string, b: StorageScope, c?: string) => a === 'extensionsAssistant/recommendations' ? storedRecommendations : c }); + instantiationService.stub(IStorageService, >{ get: (a: string, b: StorageScope, c?: string) => a === 'extensionsAssistant/recommendations' ? storedRecommendations : c, getBoolean: (a: string, b: StorageScope, c: boolean) => c }); return setUpFolderWorkspace('myFolder', []).then(() => { testObject = instantiationService.createInstance(ExtensionTipsService); diff --git a/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts b/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts index f6b81551d443..3260eb613a2b 100644 --- a/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts +++ b/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts @@ -51,7 +51,7 @@ export class BinaryFileEditor extends BaseBinaryResourceEditor { } } - getTitle(): string | undefined { + getTitle(): string { return this.input ? this.input.getName() : nls.localize('binaryFileEditor', "Binary File Viewer"); } } diff --git a/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts b/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts index a76d73dfc7c7..d0326a0a1540 100644 --- a/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts +++ b/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts @@ -8,7 +8,7 @@ import { URI } from 'vs/base/common/uri'; import * as resources from 'vs/base/common/resources'; import { IEditorViewState } from 'vs/editor/common/editorCommon'; import { toResource, SideBySideEditorInput, IWorkbenchEditorConfiguration, SideBySideEditor as SideBySideEditorChoice } from 'vs/workbench/common/editor'; -import { ITextFileService, ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService, ITextFileEditorModel, TextFileModelChangeEvent, ModelState } from 'vs/workbench/services/textfile/common/textfiles'; import { FileOperationEvent, FileOperation, IFileService, FileChangeType, FileChangesEvent } from 'vs/platform/files/common/files'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; @@ -27,7 +27,7 @@ import { IEditorGroupsService, IEditorGroup } from 'vs/workbench/services/editor import { ResourceQueue, timeout } from 'vs/base/common/async'; import { onUnexpectedError } from 'vs/base/common/errors'; import { withNullAsUndefined } from 'vs/base/common/types'; -import { EditorActivation } from 'vs/platform/editor/common/editor'; +import { EditorActivation, ITextEditorOptions } from 'vs/platform/editor/common/editor'; // {{SQL CARBON EDIT}} import { QueryEditorInput } from 'sql/workbench/contrib/query/common/queryEditorInput'; @@ -64,6 +64,9 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut // Update editors from disk changes this._register(this.fileService.onFileChanges(e => this.onFileChanges(e))); + // Open editors from dirty text file models + this._register(this.textFileService.models.onModelsDirty(e => this.onTextFilesDirty(e))); + // Editor changing this._register(this.editorService.onDidVisibleEditorsChange(() => this.handleOutOfWorkspaceWatchers())); @@ -98,7 +101,7 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut return resource ? this.textFileService.models.get(resource) : undefined; })) .filter(model => !model.isDirty()), - m => m.getResource().toString() + m => m.resource.toString() ).forEach(model => this.queueModelLoad(model)); } } @@ -219,9 +222,8 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut private handleMovedFileInOpenedEditors(oldResource: URI, newResource: URI): void { this.editorGroupService.groups.forEach(group => { group.editors.forEach(editor => { - // {{SQL CARBON EDIT}} - Support FileEditorInput or QueryInput - if (editor instanceof FileEditorInput || editor instanceof QueryEditorInput) { - const resource = editor.getResource(); + const resource = editor.getResource(); + if (resource && (editor instanceof FileEditorInput || editor instanceof QueryEditorInput || editor.handleMove)) { // {{SQL CARBON EDIT}} #TODO we can remove this edit by just implementing handlemove // Update Editor if file (or any parent of the input) got renamed or moved if (resources.isEqualOrParent(resource, oldResource)) { @@ -233,15 +235,27 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut reopenFileResource = resources.joinPath(newResource, resource.path.substr(index + oldResource.path.length + 1)); // parent folder got moved } + const options: ITextEditorOptions = { + preserveFocus: true, + pinned: group.isPinned(editor), + index: group.getIndexOfEditor(editor), + inactive: !group.isActive(editor), + }; + + if (editor.handleMove) { + const replacement = editor.handleMove(group.id, reopenFileResource, options); + if (replacement) { + this.editorService.replaceEditors([{ editor, replacement }], group); + return; + } + } + this.editorService.replaceEditors([{ editor: { resource }, replacement: { resource: reopenFileResource, options: { - preserveFocus: true, - pinned: group.isPinned(editor), - index: group.getIndexOfEditor(editor), - inactive: !group.isActive(editor), + ...options, viewState: this.getViewStateFor(oldResource, group) } }, @@ -304,7 +318,7 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut // and updated right after. distinct(coalesce([...e.getUpdated(), ...e.getAdded()] .map(u => this.textFileService.models.get(u.resource))) - .filter(model => model && !model.isDirty()), m => m.getResource().toString()) + .filter(model => model && !model.isDirty()), m => m.resource.toString()) .forEach(model => this.queueModelLoad(model)); } @@ -313,9 +327,9 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut // Load model to update (use a queue to prevent accumulation of loads // when the load actually takes long. At most we only want the queue // to have a size of 2 (1 running load and 1 queued load). - const queue = this.modelLoadQueue.queueFor(model.getResource()); + const queue = this.modelLoadQueue.queueFor(model.resource); if (queue.size <= 1) { - queue.queue(() => model.load().then(undefined, onUnexpectedError)); + queue.queue(() => model.load().then(undefined, onUnexpectedError)); } } @@ -367,6 +381,31 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut }); } + private onTextFilesDirty(e: readonly TextFileModelChangeEvent[]): void { + + // If files become dirty but are not opened, we open it in the background unless there are pending to be saved + this.doOpenDirtyResources(distinct(e.filter(e => { + + // Only dirty models that are not PENDING_SAVE + const model = this.textFileService.models.get(e.resource); + const shouldOpen = model?.isDirty() && !model.hasState(ModelState.PENDING_SAVE); + + // Only if not open already + return shouldOpen && !this.editorService.isOpen({ resource: e.resource }); + }).map(e => e.resource), r => r.toString())); + } + + private doOpenDirtyResources(resources: URI[]): void { + + // Open + this.editorService.openEditors(resources.map(resource => { + return { + resource, + options: { inactive: true, pinned: true, preserveFocus: true } + }; + })); + } + dispose(): void { super.dispose(); diff --git a/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts b/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts index 84641c148bd6..811d7d9bcf7b 100644 --- a/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts +++ b/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts @@ -24,7 +24,6 @@ import { ITextResourceConfigurationService } from 'vs/editor/common/services/res import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ScrollType } from 'vs/editor/common/editorCommon'; -import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService, IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -54,11 +53,10 @@ export class TextFileEditor extends BaseTextEditor { @IEditorService editorService: IEditorService, @IThemeService themeService: IThemeService, @IEditorGroupsService editorGroupService: IEditorGroupsService, - @ITextFileService textFileService: ITextFileService, - @IHostService hostService: IHostService, + @ITextFileService private readonly textFileService: ITextFileService, @IExplorerService private readonly explorerService: IExplorerService ) { - super(TextFileEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, textFileService, editorService, editorGroupService, hostService); + super(TextFileEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, editorService, editorGroupService); this.updateRestoreViewStateConfiguration(); @@ -115,14 +113,6 @@ export class TextFileEditor extends BaseTextEditor { } } - setOptions(options: EditorOptions | undefined): void { - const textOptions = options as TextEditorOptions; - if (textOptions && isFunction(textOptions.apply)) { - const textEditor = assertIsDefined(this.getControl()); - textOptions.apply(textEditor, ScrollType.Smooth); - } - } - async setInput(input: FileEditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise { // Update/clear view settings if input changes @@ -162,7 +152,11 @@ export class TextFileEditor extends BaseTextEditor { (options).apply(textEditor, ScrollType.Immediate); } - // Readonly flag + // Since the resolved model provides information about being readonly + // or not, we apply it here to the editor even though the editor input + // was already asked for being readonly or not. The rationale is that + // a resolved model might have more specific information about being + // readonly or not that the input did not have. textEditor.updateOptions({ readOnly: textFileModel.isReadonly() }); } catch (error) { this.handleSetInputError(error, input, options); @@ -241,8 +235,7 @@ export class TextFileEditor extends BaseTextEditor { } protected getAriaLabel(): string { - const input = this.input; - const inputName = input?.getName(); + const inputName = this.input?.getName(); let ariaLabel: string; if (inputName) { diff --git a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts index 9621d38615f2..cd242f385dac 100644 --- a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts +++ b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts @@ -29,7 +29,7 @@ import { DelegatingEditorService } from 'vs/workbench/services/editor/browser/ed import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditor } from 'vs/workbench/common/editor'; -import { ViewletPanel } from 'vs/workbench/browser/parts/views/panelViewlet'; +import { ViewletPane } from 'vs/workbench/browser/parts/views/paneViewlet'; import { KeyChord, KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { Registry } from 'vs/platform/registry/common/platform'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; @@ -177,7 +177,7 @@ export class ExplorerViewlet extends ViewContainerViewlet { DOM.addClass(parent, 'explorer-viewlet'); } - protected createView(viewDescriptor: IViewDescriptor, options: IViewletViewOptions): ViewletPanel { + protected createView(viewDescriptor: IViewDescriptor, options: IViewletViewOptions): ViewletPane { if (viewDescriptor.id === ExplorerView.ID) { // Create a delegating editor service for the explorer to be able to delay the refresh in the opened // editors view above. This is a workaround for being able to double click on a file to make it pinned @@ -230,10 +230,6 @@ export class ExplorerViewlet extends ViewContainerViewlet { return this.getView(OpenEditorsView.ID); } - public getEmptyView(): EmptyView { - return this.getView(EmptyView.ID); - } - public setVisible(visible: boolean): void { this.viewletVisibleContextKey.set(visible); super.setVisible(visible); diff --git a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts index 3384ab1e250e..fdc0634308e6 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts @@ -5,12 +5,12 @@ import * as nls from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; -import { ToggleAutoSaveAction, GlobalNewUntitledFileAction, FocusFilesExplorer, GlobalCompareResourcesAction, SaveAllAction, ShowActiveFileInExplorer, CollapseExplorerView, RefreshExplorerView, CompareWithClipboardAction, NEW_FILE_COMMAND_ID, NEW_FILE_LABEL, NEW_FOLDER_COMMAND_ID, NEW_FOLDER_LABEL, TRIGGER_RENAME_LABEL, MOVE_FILE_TO_TRASH_LABEL, COPY_FILE_LABEL, PASTE_FILE_LABEL, FileCopiedContext, renameHandler, moveFileToTrashHandler, copyFileHandler, pasteFileHandler, deleteFileHandler, cutFileHandler, DOWNLOAD_COMMAND_ID, openFilePreserveFocusHandler, GlobalNewUntitledPlainFileAction } from 'vs/workbench/contrib/files/browser/fileActions'; // {{SQL CARBON EDIT}} -- Add 'New File' command for plain untitled files -import { revertLocalChangesCommand, acceptLocalChangesCommand, CONFLICT_RESOLUTION_CONTEXT } from 'vs/workbench/contrib/files/browser/saveErrorHandler'; +import { ToggleAutoSaveAction, GlobalNewUntitledFileAction, FocusFilesExplorer, GlobalCompareResourcesAction, SaveAllAction, ShowActiveFileInExplorer, CollapseExplorerView, RefreshExplorerView, CompareWithClipboardAction, NEW_FILE_COMMAND_ID, NEW_FILE_LABEL, NEW_FOLDER_COMMAND_ID, NEW_FOLDER_LABEL, TRIGGER_RENAME_LABEL, MOVE_FILE_TO_TRASH_LABEL, COPY_FILE_LABEL, PASTE_FILE_LABEL, FileCopiedContext, renameHandler, moveFileToTrashHandler, copyFileHandler, pasteFileHandler, deleteFileHandler, cutFileHandler, DOWNLOAD_COMMAND_ID, openFilePreserveFocusHandler, DOWNLOAD_LABEL, GlobalNewUntitledPlainFileAction } from 'vs/workbench/contrib/files/browser/fileActions'; +import { revertLocalChangesCommand, acceptLocalChangesCommand, CONFLICT_RESOLUTION_CONTEXT } from 'vs/workbench/contrib/files/browser/textFileSaveErrorHandler'; import { SyncActionDescriptor, MenuId, MenuRegistry, ILocalizedString } from 'vs/platform/actions/common/actions'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes'; -import { openWindowCommand, COPY_PATH_COMMAND_ID, REVEAL_IN_EXPLORER_COMMAND_ID, OPEN_TO_SIDE_COMMAND_ID, REVERT_FILE_COMMAND_ID, SAVE_FILE_COMMAND_ID, SAVE_FILE_LABEL, SAVE_FILE_AS_COMMAND_ID, SAVE_FILE_AS_LABEL, SAVE_ALL_IN_GROUP_COMMAND_ID, OpenEditorsGroupContext, COMPARE_WITH_SAVED_COMMAND_ID, COMPARE_RESOURCE_COMMAND_ID, SELECT_FOR_COMPARE_COMMAND_ID, ResourceSelectedForCompareContext, DirtyEditorContext, COMPARE_SELECTED_COMMAND_ID, REMOVE_ROOT_FOLDER_COMMAND_ID, REMOVE_ROOT_FOLDER_LABEL, SAVE_FILES_COMMAND_ID, COPY_RELATIVE_PATH_COMMAND_ID, SAVE_FILE_WITHOUT_FORMATTING_COMMAND_ID, SAVE_FILE_WITHOUT_FORMATTING_LABEL, newWindowCommand } from 'vs/workbench/contrib/files/browser/fileCommands'; +import { openWindowCommand, COPY_PATH_COMMAND_ID, REVEAL_IN_EXPLORER_COMMAND_ID, OPEN_TO_SIDE_COMMAND_ID, REVERT_FILE_COMMAND_ID, SAVE_FILE_COMMAND_ID, SAVE_FILE_LABEL, SAVE_FILE_AS_COMMAND_ID, SAVE_FILE_AS_LABEL, SAVE_ALL_IN_GROUP_COMMAND_ID, OpenEditorsGroupContext, COMPARE_WITH_SAVED_COMMAND_ID, COMPARE_RESOURCE_COMMAND_ID, SELECT_FOR_COMPARE_COMMAND_ID, ResourceSelectedForCompareContext, DirtyEditorContext, COMPARE_SELECTED_COMMAND_ID, REMOVE_ROOT_FOLDER_COMMAND_ID, REMOVE_ROOT_FOLDER_LABEL, SAVE_FILES_COMMAND_ID, COPY_RELATIVE_PATH_COMMAND_ID, SAVE_FILE_WITHOUT_FORMATTING_COMMAND_ID, SAVE_FILE_WITHOUT_FORMATTING_LABEL, newWindowCommand, ReadonlyEditorContext } from 'vs/workbench/contrib/files/browser/fileCommands'; import { CommandsRegistry, ICommandHandler } from 'vs/platform/commands/common/commands'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; @@ -18,42 +18,41 @@ import { isMacintosh } from 'vs/base/common/platform'; import { FilesExplorerFocusCondition, ExplorerRootContext, ExplorerFolderContext, ExplorerResourceNotReadonlyContext, ExplorerResourceCut, IExplorerService, ExplorerResourceMoveableToTrash, ExplorerViewletVisibleContext } from 'vs/workbench/contrib/files/common/files'; import { ADD_ROOT_FOLDER_COMMAND_ID, ADD_ROOT_FOLDER_LABEL } from 'vs/workbench/browser/actions/workspaceCommands'; import { CLOSE_SAVED_EDITORS_COMMAND_ID, CLOSE_EDITORS_IN_GROUP_COMMAND_ID, CLOSE_EDITOR_COMMAND_ID, CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; -import { AutoSaveContext } from 'vs/workbench/services/textfile/common/textfiles'; +import { AutoSaveAfterShortDelayContext } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { ResourceContextKey } from 'vs/workbench/common/resources'; import { WorkbenchListDoubleSelection } from 'vs/platform/list/browser/listService'; -import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; -import { IsWebContext, WorkspaceFolderCountContext } from 'vs/workbench/browser/contextkeys'; +import { WorkspaceFolderCountContext, IsWebContext } from 'vs/workbench/browser/contextkeys'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { OpenFileFolderAction, OpenFileAction, OpenFolderAction, OpenWorkspaceAction } from 'vs/workbench/browser/actions/workspaceActions'; -import { ActiveEditorIsSaveableContext } from 'vs/workbench/common/editor'; +import { ActiveEditorIsReadonlyContext, DirtyWorkingCopiesContext, ActiveEditorContext } from 'vs/workbench/common/editor'; import { SidebarFocusContext } from 'vs/workbench/common/viewlet'; -import { registerAndGetAmdImageURL } from 'vs/base/common/amd'; +import { ThemeIcon } from 'vs/platform/theme/common/themeService'; // Contribute Global Actions const category = { value: nls.localize('filesCategory', "File"), original: 'File' }; const registry = Registry.as(ActionExtensions.WorkbenchActions); -registry.registerWorkbenchAction(new SyncActionDescriptor(SaveAllAction, SaveAllAction.ID, SaveAllAction.LABEL, { primary: undefined, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_S }, win: { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_S) } }), 'File: Save All', category.value); -registry.registerWorkbenchAction(new SyncActionDescriptor(GlobalCompareResourcesAction, GlobalCompareResourcesAction.ID, GlobalCompareResourcesAction.LABEL), 'File: Compare Active File With...', category.value); -registry.registerWorkbenchAction(new SyncActionDescriptor(FocusFilesExplorer, FocusFilesExplorer.ID, FocusFilesExplorer.LABEL), 'File: Focus on Files Explorer', category.value); -registry.registerWorkbenchAction(new SyncActionDescriptor(ShowActiveFileInExplorer, ShowActiveFileInExplorer.ID, ShowActiveFileInExplorer.LABEL), 'File: Reveal Active File in Side Bar', category.value); -registry.registerWorkbenchAction(new SyncActionDescriptor(CollapseExplorerView, CollapseExplorerView.ID, CollapseExplorerView.LABEL), 'File: Collapse Folders in Explorer', category.value); -registry.registerWorkbenchAction(new SyncActionDescriptor(RefreshExplorerView, RefreshExplorerView.ID, RefreshExplorerView.LABEL), 'File: Refresh Explorer', category.value); -registry.registerWorkbenchAction(new SyncActionDescriptor(GlobalNewUntitledFileAction, GlobalNewUntitledFileAction.ID, GlobalNewUntitledFileAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_N }), 'File: New Untitled File', category.value); -registry.registerWorkbenchAction(new SyncActionDescriptor(CompareWithClipboardAction, CompareWithClipboardAction.ID, CompareWithClipboardAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_C) }), 'File: Compare Active File with Clipboard', category.value); -registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleAutoSaveAction, ToggleAutoSaveAction.ID, ToggleAutoSaveAction.LABEL), 'File: Toggle Auto Save', category.value); -registry.registerWorkbenchAction(new SyncActionDescriptor(GlobalNewUntitledPlainFileAction, GlobalNewUntitledPlainFileAction.ID, GlobalNewUntitledPlainFileAction.LABEL), 'File: New Plain Text File', category.value); // {{SQL CARBON EDIT}} -- Add 'New File' command for plain untitled files +registry.registerWorkbenchAction(SyncActionDescriptor.create(SaveAllAction, SaveAllAction.ID, SaveAllAction.LABEL, { primary: undefined, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_S }, win: { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_S) } }), 'File: Save All', category.value); +registry.registerWorkbenchAction(SyncActionDescriptor.create(GlobalCompareResourcesAction, GlobalCompareResourcesAction.ID, GlobalCompareResourcesAction.LABEL), 'File: Compare Active File With...', category.value); +registry.registerWorkbenchAction(SyncActionDescriptor.create(FocusFilesExplorer, FocusFilesExplorer.ID, FocusFilesExplorer.LABEL), 'File: Focus on Files Explorer', category.value); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ShowActiveFileInExplorer, ShowActiveFileInExplorer.ID, ShowActiveFileInExplorer.LABEL), 'File: Reveal Active File in Side Bar', category.value); +registry.registerWorkbenchAction(SyncActionDescriptor.create(CollapseExplorerView, CollapseExplorerView.ID, CollapseExplorerView.LABEL), 'File: Collapse Folders in Explorer', category.value); +registry.registerWorkbenchAction(SyncActionDescriptor.create(RefreshExplorerView, RefreshExplorerView.ID, RefreshExplorerView.LABEL), 'File: Refresh Explorer', category.value); +registry.registerWorkbenchAction(SyncActionDescriptor.create(GlobalNewUntitledFileAction, GlobalNewUntitledFileAction.ID, GlobalNewUntitledFileAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_N }), 'File: New Untitled File', category.value); +registry.registerWorkbenchAction(SyncActionDescriptor.create(CompareWithClipboardAction, CompareWithClipboardAction.ID, CompareWithClipboardAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_C) }), 'File: Compare Active File with Clipboard', category.value); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleAutoSaveAction, ToggleAutoSaveAction.ID, ToggleAutoSaveAction.LABEL), 'File: Toggle Auto Save', category.value); +registry.registerWorkbenchAction(SyncActionDescriptor.create(GlobalNewUntitledPlainFileAction, GlobalNewUntitledPlainFileAction.ID, GlobalNewUntitledPlainFileAction.LABEL), 'File: New Plain Text File', category.value); // {{SQL CARBON EDIT}} -- Add 'New File' command for plain untitled files const workspacesCategory = nls.localize('workspaces', "Workspaces"); -registry.registerWorkbenchAction(new SyncActionDescriptor(OpenWorkspaceAction, OpenWorkspaceAction.ID, OpenWorkspaceAction.LABEL), 'Workspaces: Open Workspace...', workspacesCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenWorkspaceAction, OpenWorkspaceAction.ID, OpenWorkspaceAction.LABEL), 'Workspaces: Open Workspace...', workspacesCategory); const fileCategory = nls.localize('file', "File"); if (isMacintosh) { - registry.registerWorkbenchAction(new SyncActionDescriptor(OpenFileFolderAction, OpenFileFolderAction.ID, OpenFileFolderAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_O }), 'File: Open...', fileCategory); + registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenFileFolderAction, OpenFileFolderAction.ID, OpenFileFolderAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_O }), 'File: Open...', fileCategory); } else { - registry.registerWorkbenchAction(new SyncActionDescriptor(OpenFileAction, OpenFileAction.ID, OpenFileAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_O }), 'File: Open File...', fileCategory); - registry.registerWorkbenchAction(new SyncActionDescriptor(OpenFolderAction, OpenFolderAction.ID, OpenFolderAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_O) }), 'File: Open Folder...', fileCategory); + registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenFileAction, OpenFileAction.ID, OpenFileAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_O }), 'File: Open File...', fileCategory); + registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenFolderAction, OpenFolderAction.ID, OpenFolderAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_O) }), 'File: Open Folder...', fileCategory); } // Commands @@ -78,7 +77,7 @@ const MOVE_FILE_TO_TRASH_ID = 'moveFileToTrash'; KeybindingsRegistry.registerCommandAndKeybindingRule({ id: MOVE_FILE_TO_TRASH_ID, weight: KeybindingWeight.WorkbenchContrib + explorerCommandsWeightBonus, - when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerRootContext.toNegated(), ExplorerResourceNotReadonlyContext, ExplorerResourceMoveableToTrash), + when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerResourceNotReadonlyContext, ExplorerResourceMoveableToTrash), primary: KeyCode.Delete, mac: { primary: KeyMod.CtrlCmd | KeyCode.Backspace @@ -90,7 +89,7 @@ const DELETE_FILE_ID = 'deleteFile'; KeybindingsRegistry.registerCommandAndKeybindingRule({ id: DELETE_FILE_ID, weight: KeybindingWeight.WorkbenchContrib + explorerCommandsWeightBonus, - when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerRootContext.toNegated(), ExplorerResourceNotReadonlyContext), + when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerResourceNotReadonlyContext), primary: KeyMod.Shift | KeyCode.Delete, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Backspace @@ -101,7 +100,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ KeybindingsRegistry.registerCommandAndKeybindingRule({ id: DELETE_FILE_ID, weight: KeybindingWeight.WorkbenchContrib + explorerCommandsWeightBonus, - when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerRootContext.toNegated(), ExplorerResourceNotReadonlyContext, ExplorerResourceMoveableToTrash.toNegated()), + when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerResourceNotReadonlyContext, ExplorerResourceMoveableToTrash.toNegated()), primary: KeyCode.Delete, mac: { primary: KeyMod.CtrlCmd | KeyCode.Backspace @@ -182,23 +181,17 @@ export function appendEditorTitleContextMenuItem(id: string, title: string, when } // Editor Title Menu for Conflict Resolution -appendSaveConflictEditorTitleAction('workbench.files.action.acceptLocalChanges', nls.localize('acceptLocalChanges', "Use your changes and overwrite file contents"), { - light: URI.parse(registerAndGetAmdImageURL(`vs/workbench/contrib/files/browser/media/check-light.svg`)), - dark: URI.parse(registerAndGetAmdImageURL(`vs/workbench/contrib/files/browser/media/check-dark.svg`)) -}, -10, acceptLocalChangesCommand); -appendSaveConflictEditorTitleAction('workbench.files.action.revertLocalChanges', nls.localize('revertLocalChanges', "Discard your changes and revert to file contents"), { - light: URI.parse(registerAndGetAmdImageURL(`vs/workbench/contrib/files/browser/media/undo-light.svg`)), - dark: URI.parse(registerAndGetAmdImageURL(`vs/workbench/contrib/files/browser/media/undo-dark.svg`)) -}, -9, revertLocalChangesCommand); +appendSaveConflictEditorTitleAction('workbench.files.action.acceptLocalChanges', nls.localize('acceptLocalChanges', "Use your changes and overwrite file contents"), { id: 'codicon/check' }, -10, acceptLocalChangesCommand); +appendSaveConflictEditorTitleAction('workbench.files.action.revertLocalChanges', nls.localize('revertLocalChanges', "Discard your changes and revert to file contents"), { id: 'codicon/discard' }, -9, revertLocalChangesCommand); -function appendSaveConflictEditorTitleAction(id: string, title: string, iconLocation: { dark: URI; light?: URI; }, order: number, command: ICommandHandler): void { +function appendSaveConflictEditorTitleAction(id: string, title: string, icon: ThemeIcon, order: number, command: ICommandHandler): void { // Command CommandsRegistry.registerCommand(id, command); // Action MenuRegistry.appendMenuItem(MenuId.EditorTitle, { - command: { id, title, iconLocation }, + command: { id, title, icon }, when: ContextKeyExpr.equals(CONFLICT_RESOLUTION_CONTEXT, true), group: 'navigation', order @@ -218,7 +211,6 @@ export function appendToCommandPalette(id: string, title: ILocalizedString, cate }); } -const downloadLabel = nls.localize('download', "Download"); appendToCommandPalette(COPY_PATH_COMMAND_ID, { value: nls.localize('copyPathOfActive', "Copy Path of Active File"), original: 'Copy Path of Active File' }, category); appendToCommandPalette(COPY_RELATIVE_PATH_COMMAND_ID, { value: nls.localize('copyRelativePathOfActive', "Copy Relative Path of Active File"), original: 'Copy Relative Path of Active File' }, category); appendToCommandPalette(SAVE_FILE_COMMAND_ID, { value: SAVE_FILE_LABEL, original: 'Save' }, category); @@ -231,7 +223,7 @@ appendToCommandPalette(SAVE_FILE_AS_COMMAND_ID, { value: SAVE_FILE_AS_LABEL, ori appendToCommandPalette(CLOSE_EDITOR_COMMAND_ID, { value: nls.localize('closeEditor', "Close Editor"), original: 'Close Editor' }, { value: nls.localize('view', "View"), original: 'View' }); appendToCommandPalette(NEW_FILE_COMMAND_ID, { value: NEW_FILE_LABEL, original: 'New File' }, category, WorkspaceFolderCountContext.notEqualsTo('0')); appendToCommandPalette(NEW_FOLDER_COMMAND_ID, { value: NEW_FOLDER_LABEL, original: 'New Folder' }, category, WorkspaceFolderCountContext.notEqualsTo('0')); -appendToCommandPalette(DOWNLOAD_COMMAND_ID, { value: downloadLabel, original: 'Download' }, category, ContextKeyExpr.and(IsWebContext.toNegated(), ResourceContextKey.Scheme.notEqualsTo(Schemas.file))); +appendToCommandPalette(DOWNLOAD_COMMAND_ID, { value: DOWNLOAD_LABEL, original: 'Download' }, category, ContextKeyExpr.and(ResourceContextKey.Scheme.notEqualsTo(Schemas.file))); // Menu registration - open editors @@ -243,7 +235,7 @@ MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { group: 'navigation', order: 10, command: openToSideCommand, - when: ResourceContextKey.IsFileSystemResource + when: ContextKeyExpr.or(ResourceContextKey.IsFileSystemResource, ResourceContextKey.Scheme.isEqualTo(Schemas.untitled)) }); MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { @@ -268,7 +260,19 @@ MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { title: SAVE_FILE_LABEL, precondition: DirtyEditorContext }, - when: ContextKeyExpr.and(ResourceContextKey.IsFileSystemResource, AutoSaveContext.notEqualsTo('afterDelay') && AutoSaveContext.notEqualsTo('')) + when: ContextKeyExpr.or( + // Untitled Editors + ResourceContextKey.Scheme.isEqualTo(Schemas.untitled), + // Or: + ContextKeyExpr.and( + // Not: editor groups + OpenEditorsGroupContext.toNegated(), + // Not: readonly editors + ReadonlyEditorContext.toNegated(), + // Not: auto save after short delay + AutoSaveAfterShortDelayContext.toNegated() + ) + ) }); MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { @@ -279,25 +283,28 @@ MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { title: nls.localize('revert', "Revert File"), precondition: DirtyEditorContext }, - when: ContextKeyExpr.and(ResourceContextKey.IsFileSystemResource, AutoSaveContext.notEqualsTo('afterDelay') && AutoSaveContext.notEqualsTo('')) -}); - -MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { - group: '2_save', - command: { - id: SAVE_FILE_AS_COMMAND_ID, - title: SAVE_FILE_AS_LABEL - }, - when: ResourceContextKey.Scheme.isEqualTo(Schemas.untitled) + when: ContextKeyExpr.and( + // Not: editor groups + OpenEditorsGroupContext.toNegated(), + // Not: readonly editors + ReadonlyEditorContext.toNegated(), + // Not: untitled editors (revert closes them) + ResourceContextKey.Scheme.notEqualsTo(Schemas.untitled), + // Not: auto save after short delay + AutoSaveAfterShortDelayContext.toNegated() + ) }); MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { group: '2_save', + order: 30, command: { id: SAVE_ALL_IN_GROUP_COMMAND_ID, - title: nls.localize('saveAll', "Save All") + title: nls.localize('saveAll', "Save All"), + precondition: DirtyWorkingCopiesContext }, - when: ContextKeyExpr.and(OpenEditorsGroupContext, AutoSaveContext.notEqualsTo('afterDelay') && AutoSaveContext.notEqualsTo('')) + // Editor Group + when: OpenEditorsGroupContext }); MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { @@ -308,7 +315,7 @@ MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { title: nls.localize('compareWithSaved', "Compare with Saved"), precondition: DirtyEditorContext }, - when: ContextKeyExpr.and(ResourceContextKey.IsFileSystemResource, AutoSaveContext.notEqualsTo('afterDelay') && AutoSaveContext.notEqualsTo(''), WorkbenchListDoubleSelection.toNegated()) + when: ContextKeyExpr.and(ResourceContextKey.IsFileSystemResource, AutoSaveAfterShortDelayContext.toNegated(), WorkbenchListDoubleSelection.toNegated()) }); const compareResourceCommand = { @@ -465,15 +472,15 @@ MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { when: ExplorerFolderContext }); -MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { +MenuRegistry.appendMenuItem(MenuId.ExplorerContext, ({ group: '5_cutcopypaste', order: 30, command: { id: DOWNLOAD_COMMAND_ID, - title: downloadLabel, + title: DOWNLOAD_LABEL, }, - when: ContextKeyExpr.and(IsWebContext.toNegated(), ResourceContextKey.Scheme.notEqualsTo(Schemas.file)) -}); + when: ContextKeyExpr.or(ContextKeyExpr.and(ResourceContextKey.Scheme.notEqualsTo(Schemas.file), IsWebContext.toNegated()), ContextKeyExpr.and(ResourceContextKey.Scheme.notEqualsTo(Schemas.file), ExplorerFolderContext.toNegated(), ExplorerRootContext.toNegated())) +})); MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { group: '6_copypath', @@ -496,7 +503,7 @@ MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { id: ADD_ROOT_FOLDER_COMMAND_ID, title: ADD_ROOT_FOLDER_LABEL }, - when: ContextKeyExpr.and(ExplorerRootContext) + when: ExplorerRootContext }); MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { @@ -591,7 +598,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { command: { id: SAVE_FILE_COMMAND_ID, title: nls.localize({ key: 'miSave', comment: ['&& denotes a mnemonic'] }, "&&Save"), - precondition: ContextKeyExpr.or(ActiveEditorIsSaveableContext, ContextKeyExpr.and(ExplorerViewletVisibleContext, SidebarFocusContext)) + precondition: ContextKeyExpr.or(ActiveEditorIsReadonlyContext.toNegated(), ContextKeyExpr.and(ExplorerViewletVisibleContext, SidebarFocusContext)) }, order: 1 }); @@ -601,7 +608,8 @@ MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { command: { id: SAVE_FILE_AS_COMMAND_ID, title: nls.localize({ key: 'miSaveAs', comment: ['&& denotes a mnemonic'] }, "Save &&As..."), - precondition: ContextKeyExpr.or(ActiveEditorIsSaveableContext, ContextKeyExpr.and(ExplorerViewletVisibleContext, SidebarFocusContext)) + // ActiveEditorContext is not 100% correct, but we lack a context for indicating "Save As..." support + precondition: ContextKeyExpr.or(ActiveEditorContext, ContextKeyExpr.and(ExplorerViewletVisibleContext, SidebarFocusContext)) }, order: 2 }); @@ -610,7 +618,8 @@ MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { group: '4_save', command: { id: SaveAllAction.ID, - title: nls.localize({ key: 'miSaveAll', comment: ['&& denotes a mnemonic'] }, "Save A&&ll") + title: nls.localize({ key: 'miSaveAll', comment: ['&& denotes a mnemonic'] }, "Save A&&ll"), + precondition: DirtyWorkingCopiesContext }, order: 3 }); @@ -667,7 +676,8 @@ MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { group: '6_close', command: { id: REVERT_FILE_COMMAND_ID, - title: nls.localize({ key: 'miRevert', comment: ['&& denotes a mnemonic'] }, "Re&&vert File") + title: nls.localize({ key: 'miRevert', comment: ['&& denotes a mnemonic'] }, "Re&&vert File"), + precondition: ContextKeyExpr.or(ActiveEditorIsReadonlyContext.toNegated(), ContextKeyExpr.and(ExplorerViewletVisibleContext, SidebarFocusContext)) }, order: 1 }); diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index dd8a33f37411..e2825fad6618 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -5,8 +5,7 @@ import 'vs/css!./media/fileactions'; import * as nls from 'vs/nls'; -import * as types from 'vs/base/common/types'; -import { isWindows } from 'vs/base/common/platform'; +import { isWindows, isWeb } from 'vs/base/common/platform'; import * as extpath from 'vs/base/common/extpath'; import { extname, basename } from 'vs/base/common/path'; import * as resources from 'vs/base/common/resources'; @@ -17,10 +16,9 @@ import { Action } from 'vs/base/common/actions'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; import { VIEWLET_ID, IExplorerService, IFilesConfiguration } from 'vs/workbench/contrib/files/common/files'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { IFileService, AutoSaveConfiguration } from 'vs/platform/files/common/files'; +import { IFileService } from 'vs/platform/files/common/files'; import { toResource, SideBySideEditor } from 'vs/workbench/common/editor'; import { ExplorerViewlet } from 'vs/workbench/contrib/files/browser/explorerViewlet'; -import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; @@ -33,18 +31,20 @@ import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService import { IModeService } from 'vs/editor/common/services/modeService'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands'; -import { IListService, ListWidget } from 'vs/platform/list/browser/listService'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { Schemas } from 'vs/base/common/network'; -import { IDialogService, IConfirmationResult, getConfirmMessage } from 'vs/platform/dialogs/common/dialogs'; +import { IDialogService, IConfirmationResult, getConfirmMessage, IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { Constants } from 'vs/base/common/uint'; import { CLOSE_EDITORS_AND_GROUP_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; import { coalesce } from 'vs/base/common/arrays'; -import { AsyncDataTree } from 'vs/base/browser/ui/tree/asyncDataTree'; import { ExplorerItem, NewExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel'; import { onUnexpectedError, getErrorMessage } from 'vs/base/common/errors'; +import { asDomUri, triggerDownload } from 'vs/base/browser/dom'; +import { mnemonicButtonLabel } from 'vs/base/common/labels'; +import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; +import { IWorkingCopyService, IWorkingCopy } from 'vs/workbench/services/workingCopy/common/workingCopyService'; export const NEW_FILE_COMMAND_ID = 'explorer.newFile'; export const NEW_FILE_LABEL = nls.localize('newFile', "New File"); @@ -62,6 +62,8 @@ export const PASTE_FILE_LABEL = nls.localize('pasteFile', "Paste"); export const FileCopiedContext = new RawContextKey('fileCopied', false); +export const DOWNLOAD_LABEL = nls.localize('download', "Download"); + const CONFIRM_DELETE_SETTING_KEY = 'explorer.confirmDelete'; function onError(notificationService: INotificationService, error: any): void { @@ -162,7 +164,7 @@ export class GlobalNewUntitledFileAction extends Action { } } -async function deleteFiles(textFileService: ITextFileService, dialogService: IDialogService, configurationService: IConfigurationService, fileService: IFileService, elements: ExplorerItem[], useTrash: boolean, skipConfirm = false): Promise { +async function deleteFiles(textFileService: ITextFileService, dialogService: IDialogService, configurationService: IConfigurationService, elements: ExplorerItem[], useTrash: boolean, skipConfirm = false): Promise { let primaryButton: string; if (useTrash) { primaryButton = isWindows ? nls.localize('deleteButtonLabelRecycleBin', "&&Move to Recycle Bin") : nls.localize({ key: 'deleteButtonLabelTrash', comment: ['&& denotes a mnemonic'] }, "&&Move to Trash"); @@ -258,7 +260,7 @@ async function deleteFiles(textFileService: ITextFileService, dialogService: IDi } // Call function - const servicePromise = Promise.all(distinctElements.map(e => fileService.del(e.resource, { useTrash: useTrash, recursive: true }))) + const servicePromise = Promise.all(distinctElements.map(e => textFileService.delete(e.resource, { useTrash: useTrash, recursive: true }))) .then(undefined, (error: any) => { // Handle error to delete file(s) from a modal confirmation dialog let errorMessage: string; @@ -287,14 +289,14 @@ async function deleteFiles(textFileService: ITextFileService, dialogService: IDi skipConfirm = true; - return deleteFiles(textFileService, dialogService, configurationService, fileService, elements, useTrash, skipConfirm); + return deleteFiles(textFileService, dialogService, configurationService, elements, useTrash, skipConfirm); } return Promise.resolve(); }); }); - return servicePromise; + return servicePromise.then(undefined); }); }); } @@ -513,26 +515,13 @@ export class ToggleAutoSaveAction extends Action { constructor( id: string, label: string, - @IConfigurationService private readonly configurationService: IConfigurationService + @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService ) { super(id, label); } run(): Promise { - const setting = this.configurationService.inspect('files.autoSave'); - let userAutoSaveConfig = setting.user; - if (types.isUndefinedOrNull(userAutoSaveConfig)) { - userAutoSaveConfig = setting.default; // use default if setting not defined - } - - let newAutoSaveValue: string; - if ([AutoSaveConfiguration.AFTER_DELAY, AutoSaveConfiguration.ON_FOCUS_CHANGE, AutoSaveConfiguration.ON_WINDOW_CHANGE].some(s => s === userAutoSaveConfig)) { - newAutoSaveValue = AutoSaveConfiguration.OFF; - } else { - newAutoSaveValue = AutoSaveConfiguration.AFTER_DELAY; - } - - return this.configurationService.updateValue('files.autoSave', newAutoSaveValue, ConfigurationTarget.USER); + return this.filesConfigurationService.toggleAutoSave(); } } @@ -542,38 +531,30 @@ export abstract class BaseSaveAllAction extends Action { constructor( id: string, label: string, - @ITextFileService private readonly textFileService: ITextFileService, - @IUntitledEditorService private readonly untitledEditorService: IUntitledEditorService, @ICommandService protected commandService: ICommandService, @INotificationService private notificationService: INotificationService, + @IWorkingCopyService private readonly workingCopyService: IWorkingCopyService ) { super(id, label); - this.lastIsDirty = this.textFileService.isDirty(); + this.lastIsDirty = this.workingCopyService.hasDirty; this.enabled = this.lastIsDirty; this.registerListeners(); } - protected abstract includeUntitled(): boolean; protected abstract doRun(context: any): Promise; private registerListeners(): void { - // listen to files being changed locally - this._register(this.textFileService.models.onModelsDirty(e => this.updateEnablement(true))); - this._register(this.textFileService.models.onModelsSaved(e => this.updateEnablement(false))); - this._register(this.textFileService.models.onModelsReverted(e => this.updateEnablement(false))); - this._register(this.textFileService.models.onModelsSaveError(e => this.updateEnablement(true))); - - if (this.includeUntitled()) { - this._register(this.untitledEditorService.onDidChangeDirty(resource => this.updateEnablement(this.untitledEditorService.isDirty(resource)))); - } + // update enablement based on working copy changes + this._register(this.workingCopyService.onDidChangeDirty(w => this.updateEnablement(w))); } - private updateEnablement(isDirty: boolean): void { - if (this.lastIsDirty !== isDirty) { - this.enabled = this.textFileService.isDirty(); + private updateEnablement(workingCopy: IWorkingCopy): void { + const hasDirty = workingCopy.isDirty() || this.workingCopyService.hasDirty; + if (this.lastIsDirty !== hasDirty) { + this.enabled = hasDirty; this.lastIsDirty = this.enabled; } } @@ -599,10 +580,6 @@ export class SaveAllAction extends BaseSaveAllAction { protected doRun(context: any): Promise { return this.commandService.executeCommand(SAVE_ALL_COMMAND_ID); } - - protected includeUntitled(): boolean { - return true; - } } export class SaveAllInGroupAction extends BaseSaveAllAction { @@ -617,10 +594,6 @@ export class SaveAllInGroupAction extends BaseSaveAllAction { protected doRun(context: any): Promise { return this.commandService.executeCommand(SAVE_ALL_IN_GROUP_COMMAND_ID, {}, context); } - - protected includeUntitled(): boolean { - return true; - } } export class CloseGroupAction extends Action { @@ -886,22 +859,6 @@ class ClipboardContentProvider implements ITextModelContentProvider { } } -interface IExplorerContext { - stat?: ExplorerItem; - selection: ExplorerItem[]; -} - -function getContext(listWidget: ListWidget): IExplorerContext { - // These commands can only be triggered when explorer viewlet is visible so get it using the active viewlet - const tree = >listWidget; - const focus = tree.getFocus(); - const stat = focus.length ? focus[0] : undefined; - const selection = tree.getSelection(); - - // Only respect the selection if user clicked inside it (focus belongs to it) - return { stat, selection: selection && typeof stat !== 'undefined' && selection.indexOf(stat) >= 0 ? selection : [] }; -} - function onErrorWithRetry(notificationService: INotificationService, error: any, retry: () => Promise): void { notificationService.prompt(Severity.Error, toErrorMessage(error, false), [{ @@ -912,7 +869,6 @@ function onErrorWithRetry(notificationService: INotificationService, error: any, } async function openExplorerAndCreate(accessor: ServicesAccessor, isFolder: boolean): Promise { - const listService = accessor.get(IListService); const explorerService = accessor.get(IExplorerService); const fileService = accessor.get(IFileService); const textFileService = accessor.get(ITextFileService); @@ -922,47 +878,45 @@ async function openExplorerAndCreate(accessor: ServicesAccessor, isFolder: boole await viewletService.openViewlet(VIEWLET_ID, true); - const list = listService.lastFocusedList; - if (list) { - const { stat } = getContext(list); - let folder: ExplorerItem; - if (stat) { - folder = stat.isDirectory ? stat : (stat.parent || explorerService.roots[0]); - } else { - folder = explorerService.roots[0]; - } + const stats = explorerService.getContext(false); + const stat = stats.length > 0 ? stats[0] : undefined; + let folder: ExplorerItem; + if (stat) { + folder = stat.isDirectory ? stat : (stat.parent || explorerService.roots[0]); + } else { + folder = explorerService.roots[0]; + } - if (folder.isReadonly) { - throw new Error('Parent folder is readonly.'); - } + if (folder.isReadonly) { + throw new Error('Parent folder is readonly.'); + } - const newStat = new NewExplorerItem(folder, isFolder); - await folder.fetchChildren(fileService, explorerService); + const newStat = new NewExplorerItem(folder, isFolder); + await folder.fetchChildren(fileService, explorerService); - folder.addChild(newStat); + folder.addChild(newStat); - const onSuccess = (value: string): Promise => { - const createPromise = isFolder ? fileService.createFolder(resources.joinPath(folder.resource, value)) : textFileService.create(resources.joinPath(folder.resource, value)); - return createPromise.then(created => { - refreshIfSeparator(value, explorerService); - return isFolder ? explorerService.select(created.resource, true) - : editorService.openEditor({ resource: created.resource, options: { pinned: true } }).then(() => undefined); - }, error => { - onErrorWithRetry(notificationService, error, () => onSuccess(value)); - }); - }; - - explorerService.setEditable(newStat, { - validationMessage: value => validateFileName(newStat, value), - onFinish: (value, success) => { - folder.removeChild(newStat); - explorerService.setEditable(newStat, null); - if (success) { - onSuccess(value); - } - } + const onSuccess = (value: string): Promise => { + const createPromise = isFolder ? fileService.createFolder(resources.joinPath(folder.resource, value)) : textFileService.create(resources.joinPath(folder.resource, value)); + return createPromise.then(created => { + refreshIfSeparator(value, explorerService); + return isFolder ? explorerService.select(created.resource, true) + : editorService.openEditor({ resource: created.resource, options: { pinned: true } }).then(() => undefined); + }, error => { + onErrorWithRetry(notificationService, error, () => onSuccess(value)); }); - } + }; + + explorerService.setEditable(newStat, { + validationMessage: value => validateFileName(newStat, value), + onFinish: (value, success) => { + folder.removeChild(newStat); + explorerService.setEditable(newStat, null); + if (success) { + onSuccess(value); + } + } + }); } CommandsRegistry.registerCommand({ @@ -980,14 +934,11 @@ CommandsRegistry.registerCommand({ }); export const renameHandler = (accessor: ServicesAccessor) => { - const listService = accessor.get(IListService); const explorerService = accessor.get(IExplorerService); const textFileService = accessor.get(ITextFileService); - if (!listService.lastFocusedList) { - return; - } - const { stat } = getContext(listService.lastFocusedList); + const stats = explorerService.getContext(false); + const stat = stats.length > 0 ? stats[0] : undefined; if (!stat) { return; } @@ -1007,52 +958,37 @@ export const renameHandler = (accessor: ServicesAccessor) => { }); }; -export const moveFileToTrashHandler = (accessor: ServicesAccessor) => { - const listService = accessor.get(IListService); - if (!listService.lastFocusedList) { - return Promise.resolve(); +export const moveFileToTrashHandler = async (accessor: ServicesAccessor) => { + const explorerService = accessor.get(IExplorerService); + const stats = explorerService.getContext(true).filter(s => !s.isRoot); + if (stats.length) { + await deleteFiles(accessor.get(ITextFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), stats, true); } - const explorerContext = getContext(listService.lastFocusedList); - const stats = explorerContext.selection.length > 1 ? explorerContext.selection : [explorerContext.stat!]; - - return deleteFiles(accessor.get(ITextFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), accessor.get(IFileService), stats, true); }; -export const deleteFileHandler = (accessor: ServicesAccessor) => { - const listService = accessor.get(IListService); - if (!listService.lastFocusedList) { - return Promise.resolve(); - } - const explorerContext = getContext(listService.lastFocusedList); - const stats = explorerContext.selection.length > 1 ? explorerContext.selection : [explorerContext.stat!]; +export const deleteFileHandler = async (accessor: ServicesAccessor) => { + const explorerService = accessor.get(IExplorerService); + const stats = explorerService.getContext(true).filter(s => !s.isRoot); - return deleteFiles(accessor.get(ITextFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), accessor.get(IFileService), stats, false); + if (stats.length) { + await deleteFiles(accessor.get(ITextFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), stats, false); + } }; let pasteShouldMove = false; export const copyFileHandler = (accessor: ServicesAccessor) => { - const listService = accessor.get(IListService); - if (!listService.lastFocusedList) { - return; - } - const explorerContext = getContext(listService.lastFocusedList); const explorerService = accessor.get(IExplorerService); - if (explorerContext.stat) { - const stats = explorerContext.selection.length > 1 ? explorerContext.selection : [explorerContext.stat]; + const stats = explorerService.getContext(true); + if (stats.length > 0) { explorerService.setToCopy(stats, false); pasteShouldMove = false; } }; export const cutFileHandler = (accessor: ServicesAccessor) => { - const listService = accessor.get(IListService); - if (!listService.lastFocusedList) { - return; - } - const explorerContext = getContext(listService.lastFocusedList); const explorerService = accessor.get(IExplorerService); - if (explorerContext.stat) { - const stats = explorerContext.selection.length > 1 ? explorerContext.selection : [explorerContext.stat]; + const stats = explorerService.getContext(true); + if (stats.length > 0) { explorerService.setToCopy(stats, true); pasteShouldMove = true; } @@ -1060,27 +996,41 @@ export const cutFileHandler = (accessor: ServicesAccessor) => { export const DOWNLOAD_COMMAND_ID = 'explorer.download'; const downloadFileHandler = (accessor: ServicesAccessor) => { - const listService = accessor.get(IListService); - if (!listService.lastFocusedList) { - return; - } - const explorerContext = getContext(listService.lastFocusedList); const textFileService = accessor.get(ITextFileService); + const fileDialogService = accessor.get(IFileDialogService); + const explorerService = accessor.get(IExplorerService); + const stats = explorerService.getContext(true); - if (explorerContext.stat) { - const stats = explorerContext.selection.length > 1 ? explorerContext.selection : [explorerContext.stat]; - stats.forEach(async s => { - await textFileService.saveAs(s.resource, undefined, { availableFileSystems: [Schemas.file] }); - }); - } + stats.forEach(async s => { + if (isWeb) { + if (!s.isDirectory) { + triggerDownload(asDomUri(s.resource), s.name); + } + } else { + let defaultUri = s.isDirectory ? fileDialogService.defaultFolderPath() : fileDialogService.defaultFilePath(); + if (defaultUri && !s.isDirectory) { + defaultUri = resources.joinPath(defaultUri, s.name); + } + + const destination = await fileDialogService.showSaveDialog({ + availableFileSystems: [Schemas.file], + saveLabel: mnemonicButtonLabel(nls.localize('download', "Download")), + title: s.isDirectory ? nls.localize('downloadFolder', "Download Folder") : nls.localize('downloadFile', "Download File"), + defaultUri + }); + if (destination) { + await textFileService.copy(s.resource, destination); + } + } + }); }; + CommandsRegistry.registerCommand({ id: DOWNLOAD_COMMAND_ID, handler: downloadFileHandler }); export const pasteFileHandler = async (accessor: ServicesAccessor) => { - const listService = accessor.get(IListService); const clipboardService = accessor.get(IClipboardService); const explorerService = accessor.get(IExplorerService); const fileService = accessor.get(IFileService); @@ -1089,72 +1039,65 @@ export const pasteFileHandler = async (accessor: ServicesAccessor) => { const editorService = accessor.get(IEditorService); const configurationService = accessor.get(IConfigurationService); - if (listService.lastFocusedList) { - const explorerContext = getContext(listService.lastFocusedList); - const toPaste = resources.distinctParents(clipboardService.readResources(), r => r); - const element = explorerContext.stat || explorerService.roots[0]; + const context = explorerService.getContext(true); + const toPaste = resources.distinctParents(clipboardService.readResources(), r => r); + const element = context.length ? context[0] : explorerService.roots[0]; - // Check if target is ancestor of pasted folder - const stats = await Promise.all(toPaste.map(async fileToPaste => { + // Check if target is ancestor of pasted folder + const stats = await Promise.all(toPaste.map(async fileToPaste => { - if (element.resource.toString() !== fileToPaste.toString() && resources.isEqualOrParent(element.resource, fileToPaste)) { - throw new Error(nls.localize('fileIsAncestor', "File to paste is an ancestor of the destination folder")); - } + if (element.resource.toString() !== fileToPaste.toString() && resources.isEqualOrParent(element.resource, fileToPaste)) { + throw new Error(nls.localize('fileIsAncestor', "File to paste is an ancestor of the destination folder")); + } - try { - const fileToPasteStat = await fileService.resolve(fileToPaste); + try { + const fileToPasteStat = await fileService.resolve(fileToPaste); - // Find target - let target: ExplorerItem; - if (element.resource.toString() === fileToPaste.toString()) { - target = element.parent!; - } else { - target = element.isDirectory ? element : element.parent!; - } + // Find target + let target: ExplorerItem; + if (element.resource.toString() === fileToPaste.toString()) { + target = element.parent!; + } else { + target = element.isDirectory ? element : element.parent!; + } - const incrementalNaming = configurationService.getValue().explorer.incrementalNaming; - const targetFile = findValidPasteFileTarget(target, { resource: fileToPaste, isDirectory: fileToPasteStat.isDirectory, allowOverwrite: pasteShouldMove }, incrementalNaming); + const incrementalNaming = configurationService.getValue().explorer.incrementalNaming; + const targetFile = findValidPasteFileTarget(target, { resource: fileToPaste, isDirectory: fileToPasteStat.isDirectory, allowOverwrite: pasteShouldMove }, incrementalNaming); - // Move/Copy File - if (pasteShouldMove) { - return await textFileService.move(fileToPaste, targetFile); - } else { - return await fileService.copy(fileToPaste, targetFile); - } - } catch (e) { - onError(notificationService, new Error(nls.localize('fileDeleted', "File to paste was deleted or moved meanwhile. {0}", getErrorMessage(e)))); - return undefined; + // Move/Copy File + if (pasteShouldMove) { + return await textFileService.move(fileToPaste, targetFile); + } else { + return await textFileService.copy(fileToPaste, targetFile); } - })); + } catch (e) { + onError(notificationService, new Error(nls.localize('fileDeleted', "The file to paste has been deleted or moved since you copied it. {0}", getErrorMessage(e)))); + return undefined; + } + })); - if (pasteShouldMove) { - // Cut is done. Make sure to clear cut state. - explorerService.setToCopy([], false); + if (pasteShouldMove) { + // Cut is done. Make sure to clear cut state. + explorerService.setToCopy([], false); + } + if (stats.length >= 1) { + const stat = stats[0]; + if (stat && !stat.isDirectory && stats.length === 1) { + await editorService.openEditor({ resource: stat.resource, options: { pinned: true, preserveFocus: true } }); } - if (stats.length >= 1) { - const stat = stats[0]; - if (stat && !stat.isDirectory && stats.length === 1) { - await editorService.openEditor({ resource: stat.resource, options: { pinned: true, preserveFocus: true } }); - } - if (stat) { - await explorerService.select(stat.resource); - } + if (stat) { + await explorerService.select(stat.resource); } } }; export const openFilePreserveFocusHandler = async (accessor: ServicesAccessor) => { - const listService = accessor.get(IListService); const editorService = accessor.get(IEditorService); + const explorerService = accessor.get(IExplorerService); + const stats = explorerService.getContext(true); - if (listService.lastFocusedList) { - const explorerContext = getContext(listService.lastFocusedList); - if (explorerContext.stat) { - const stats = explorerContext.selection.length > 1 ? explorerContext.selection : [explorerContext.stat]; - await editorService.openEditors(stats.filter(s => !s.isDirectory).map(s => ({ - resource: s.resource, - options: { preserveFocus: true } - }))); - } - } + await editorService.openEditors(stats.filter(s => !s.isDirectory).map(s => ({ + resource: s.resource, + options: { preserveFocus: true } + }))); }; diff --git a/src/vs/workbench/contrib/files/browser/fileCommands.ts b/src/vs/workbench/contrib/files/browser/fileCommands.ts index d9f683907ab1..7e46934f1be4 100644 --- a/src/vs/workbench/contrib/files/browser/fileCommands.ts +++ b/src/vs/workbench/contrib/files/browser/fileCommands.ts @@ -5,47 +5,41 @@ import * as nls from 'vs/nls'; import { URI } from 'vs/base/common/uri'; -import { toResource, IEditorCommandsContext, SideBySideEditor } from 'vs/workbench/common/editor'; +import { toResource, IEditorCommandsContext, SideBySideEditor, IEditorIdentifier, SaveReason, SideBySideEditorInput } from 'vs/workbench/common/editor'; import { IWindowOpenable, IOpenWindowOptions, isWorkspaceToOpen, IOpenEmptyWindowOptions } from 'vs/platform/windows/common/windows'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { ExplorerFocusCondition, TextFileContentProvider, VIEWLET_ID, IExplorerService } from 'vs/workbench/contrib/files/common/files'; +import { ExplorerFocusCondition, TextFileContentProvider, VIEWLET_ID, IExplorerService, ExplorerCompressedFocusContext, ExplorerCompressedFirstFocusContext, ExplorerCompressedLastFocusContext, FilesExplorerFocusCondition } from 'vs/workbench/contrib/files/common/files'; import { ExplorerViewlet } from 'vs/workbench/contrib/files/browser/explorerViewlet'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; -import { ITextFileService, ISaveOptions } from 'vs/workbench/services/textfile/common/textfiles'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { IListService } from 'vs/platform/list/browser/listService'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; -import { RawContextKey, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IResourceInput } from 'vs/platform/editor/common/editor'; +import { RawContextKey, IContextKey, IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IFileService } from 'vs/platform/files/common/files'; -import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; -import { IEditorViewState } from 'vs/editor/common/editorCommon'; -import { getCodeEditor } from 'vs/editor/browser/editorBrowser'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { KeyMod, KeyCode, KeyChord } from 'vs/base/common/keyCodes'; import { isWindows } from 'vs/base/common/platform'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; -import { getResourceForCommand, getMultiSelectedResources } from 'vs/workbench/contrib/files/browser/files'; +import { getResourceForCommand, getMultiSelectedResources, getOpenEditorsViewMultiSelection } from 'vs/workbench/contrib/files/browser/files'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing'; import { getMultiSelectedEditorContexts } from 'vs/workbench/browser/parts/editor/editorCommands'; import { Schemas } from 'vs/base/common/network'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { IEditorService, SIDE_GROUP, IResourceEditorReplacement } from 'vs/workbench/services/editor/common/editorService'; -import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorService, SIDE_GROUP, ISaveEditorsOptions } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorGroupsService, GroupsOrder, EditorsOrder, IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ILabelService } from 'vs/platform/label/common/label'; -import { basename, toLocalResource, joinPath, isEqual } from 'vs/base/common/resources'; +import { basename, joinPath, isEqual } from 'vs/base/common/resources'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { UNTITLED_WORKSPACE_NAME } from 'vs/platform/workspaces/common/workspaces'; -import { withUndefinedAsNull } from 'vs/base/common/types'; - -// {{SQL CARBON EDIT}} -import { IQueryEditorService } from 'sql/workbench/services/queryEditor/common/queryEditorService'; +import { coalesce } from 'vs/base/common/arrays'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; // Commands @@ -76,11 +70,17 @@ export const SAVE_FILES_COMMAND_ID = 'workbench.action.files.saveFiles'; export const OpenEditorsGroupContext = new RawContextKey('groupFocusedInOpenEditors', false); export const DirtyEditorContext = new RawContextKey('dirtyEditor', false); +export const ReadonlyEditorContext = new RawContextKey('readonlyEditor', false); export const ResourceSelectedForCompareContext = new RawContextKey('resourceSelectedForCompare', false); export const REMOVE_ROOT_FOLDER_COMMAND_ID = 'removeRootFolder'; export const REMOVE_ROOT_FOLDER_LABEL = nls.localize('removeFolderFromWorkspace', "Remove Folder from Workspace"); +export const PREVIOUS_COMPRESSED_FOLDER = 'previousCompressedFolder'; +export const NEXT_COMPRESSED_FOLDER = 'nextCompressedFolder'; +export const FIRST_COMPRESSED_FOLDER = 'firstCompressedFolder'; +export const LAST_COMPRESSED_FOLDER = 'lastCompressedFolder'; + export const openWindowCommand = (accessor: ServicesAccessor, toOpen: IWindowOpenable[], options?: IOpenWindowOptions) => { if (Array.isArray(toOpen)) { const hostService = accessor.get(IHostService); @@ -106,205 +106,8 @@ export const newWindowCommand = (accessor: ServicesAccessor, options?: IOpenEmpt hostService.openWindow(options); }; -// {{SQL CARBON EDIT}} -async function save( - resource: URI | null, - isSaveAs: boolean, - options: ISaveOptions | undefined, - editorService: IEditorService, - fileService: IFileService, - untitledEditorService: IUntitledEditorService, - textFileService: ITextFileService, - editorGroupService: IEditorGroupsService, - queryEditorService: IQueryEditorService, - environmentService: IWorkbenchEnvironmentService -): Promise { - if (!resource || (!fileService.canHandleResource(resource) && resource.scheme !== Schemas.untitled)) { - return; // save is not supported - } - - // Save As (or Save untitled with associated path) - if (isSaveAs || resource.scheme === Schemas.untitled) { - return doSaveAs(resource, isSaveAs, options, editorService, fileService, untitledEditorService, textFileService, editorGroupService, queryEditorService, environmentService); // {{SQL CARBON EDIT}} add paramater - } - - // Save - return doSave(resource, options, editorService, textFileService); -} - -async function doSaveAs( - resource: URI, - isSaveAs: boolean, - options: ISaveOptions | undefined, - editorService: IEditorService, - fileService: IFileService, - untitledEditorService: IUntitledEditorService, - textFileService: ITextFileService, - editorGroupService: IEditorGroupsService, - queryEditorService: IQueryEditorService, - environmentService: IWorkbenchEnvironmentService -): Promise { - let viewStateOfSource: IEditorViewState | null = null; - const activeTextEditorWidget = getCodeEditor(editorService.activeTextEditorWidget); - if (activeTextEditorWidget) { - const activeResource = toResource(editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER }); - if (activeResource && (fileService.canHandleResource(activeResource) || resource.scheme === Schemas.untitled) && isEqual(activeResource, resource)) { - viewStateOfSource = activeTextEditorWidget.saveViewState(); - } - } - - // Special case: an untitled file with associated path gets saved directly unless "saveAs" is true - let target: URI | undefined; - if (!isSaveAs && resource.scheme === Schemas.untitled && untitledEditorService.hasAssociatedFilePath(resource)) { - const result = await textFileService.save(resource, options); - if (result) { - target = toLocalResource(resource, environmentService.configuration.remoteAuthority); - } - } - - // Otherwise, really "Save As..." - else { - - // Force a change to the file to trigger external watchers if any - // fixes https://github.com/Microsoft/vscode/issues/59655 - options = ensureForcedSave(options); - - target = await textFileService.saveAs(resource, undefined, options); - } - - if (!target || isEqual(target, resource)) { - return false; // save canceled or same resource used - } - - const replacement: IResourceInput = { - resource: target, - options: { - pinned: true, - viewState: viewStateOfSource || undefined - } - }; - - await Promise.all(editorGroupService.groups.map(group => - editorService.replaceEditors([{ - editor: { resource }, - replacement - }], group))).then(() => { - // {{SQL CARBON EDIT}} - queryEditorService.onSaveAsCompleted(resource, target); - return true; - }); - - return true; -} - -async function doSave( - resource: URI, - options: ISaveOptions | undefined, - editorService: IEditorService, - textFileService: ITextFileService -): Promise { - - // Pin the active editor if we are saving it - const activeControl = editorService.activeControl; - const activeEditorResource = activeControl?.input?.getResource(); - if (activeControl && activeEditorResource && isEqual(activeEditorResource, resource)) { - activeControl.group.pinEditor(activeControl.input); - } - - // Just save (force a change to the file to trigger external watchers if any) - options = ensureForcedSave(options); - - return textFileService.save(resource, options); -} - -function ensureForcedSave(options?: ISaveOptions): ISaveOptions { - if (!options) { - options = { force: true }; - } else { - options.force = true; - } - - return options; -} - -async function saveAll(saveAllArguments: any, editorService: IEditorService, untitledEditorService: IUntitledEditorService, - textFileService: ITextFileService, editorGroupService: IEditorGroupsService): Promise { - - // Store some properties per untitled file to restore later after save is completed - const groupIdToUntitledResourceInput = new Map(); - - editorGroupService.groups.forEach(group => { - const activeEditorResource = group.activeEditor && group.activeEditor.getResource(); - group.editors.forEach(e => { - const resource = e.getResource(); - if (resource && untitledEditorService.isDirty(resource)) { - if (!groupIdToUntitledResourceInput.has(group.id)) { - groupIdToUntitledResourceInput.set(group.id, []); - } - - groupIdToUntitledResourceInput.get(group.id)!.push({ - encoding: untitledEditorService.getEncoding(resource), - resource, - options: { - inactive: activeEditorResource ? !isEqual(activeEditorResource, resource) : true, - pinned: true, - preserveFocus: true, - index: group.getIndexOfEditor(e) - } - }); - } - }); - }); - - // Save all - const result = await textFileService.saveAll(saveAllArguments); - - // Update untitled resources to the saved ones, so we open the proper files - groupIdToUntitledResourceInput.forEach((inputs, groupId) => { - // {{SQL CARBON EDIT}} Update untitled resources to the saved ones, so we open the proper files - const replacementPairs: IResourceEditorReplacement[] = []; - inputs.forEach(i => { - const targetResult = result.results.filter(r => r.success && isEqual(r.source, i.resource)).pop(); - if (targetResult?.target) { - // i.resource = targetResult.target;let editor = i; - const editor = i; - const replacement: IResourceInput = { - resource: targetResult.target, - encoding: i.encoding, - options: { - pinned: true, - viewState: undefined - } - }; - replacementPairs.push({ editor: editor, replacement: replacement }); - } - }); - - editorService.replaceEditors(replacementPairs, groupId); - }); -} - // Command registration -CommandsRegistry.registerCommand({ - id: REVERT_FILE_COMMAND_ID, - handler: async (accessor, resource: URI | object) => { - const editorService = accessor.get(IEditorService); - const textFileService = accessor.get(ITextFileService); - const notificationService = accessor.get(INotificationService); - const resources = getMultiSelectedResources(resource, accessor.get(IListService), editorService) - .filter(resource => resource.scheme !== Schemas.untitled); - - if (resources.length) { - try { - await textFileService.revertAll(resources, { force: true }); - } catch (error) { - notificationService.error(nls.localize('genericRevertError', "Failed to revert '{0}': {1}", resources.map(r => basename(r)).join(', '), toErrorMessage(error, false))); - } - } - } -}); - KeybindingsRegistry.registerCommandAndKeybindingRule({ weight: KeybindingWeight.WorkbenchContrib, when: ExplorerFocusCondition, @@ -320,10 +123,13 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ // Set side input if (resources.length) { - const resolved = await fileService.resolveAll(resources.map(resource => ({ resource }))); + const untitledResources = resources.filter(resource => resource.scheme === Schemas.untitled); + const fileResources = resources.filter(resource => resource.scheme !== Schemas.untitled); + + const resolved = await fileService.resolveAll(fileResources.map(resource => ({ resource }))); const editors = resolved.filter(r => r.stat && r.success && !r.stat.isDirectory).map(r => ({ resource: r.stat!.resource - })); + })).concat(...untitledResources.map(untitledResource => ({ resource: untitledResource }))); await editorService.openEditors(editors, SIDE_GROUP); } @@ -505,40 +311,90 @@ CommandsRegistry.registerCommand({ } }); -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: SAVE_FILE_AS_COMMAND_ID, - weight: KeybindingWeight.WorkbenchContrib, - when: undefined, - primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_S, - handler: (accessor, resourceOrObject: URI | object | { from: string }) => { - const editorService = accessor.get(IEditorService); - let resource: URI | null = null; - if (resourceOrObject && 'from' in resourceOrObject && resourceOrObject.from === 'menu') { - resource = withUndefinedAsNull(toResource(editorService.activeEditor)); - } else { - resource = withUndefinedAsNull(getResourceForCommand(resourceOrObject, accessor.get(IListService), editorService)); +// Save / Save As / Save All / Revert + +async function saveSelectedEditors(accessor: ServicesAccessor, options?: ISaveEditorsOptions): Promise { + const listService = accessor.get(IListService); + const editorGroupService = accessor.get(IEditorGroupsService); + const codeEditorService = accessor.get(ICodeEditorService); + const textFileService = accessor.get(ITextFileService); + + // Retrieve selected or active editor + let editors = getOpenEditorsViewMultiSelection(listService, editorGroupService); + if (!editors) { + const activeGroup = editorGroupService.activeGroup; + if (activeGroup.activeEditor) { + editors = []; + + // Special treatment for side by side editors: if the active editor + // has 2 sides, we consider both, to support saving both sides. + // We only allow this when saving, not for "Save As". + // See also https://github.com/microsoft/vscode/issues/4180 + if (activeGroup.activeEditor instanceof SideBySideEditorInput && !options?.saveAs) { + editors.push({ groupId: activeGroup.id, editor: activeGroup.activeEditor.master }); + editors.push({ groupId: activeGroup.id, editor: activeGroup.activeEditor.details }); + } else { + editors.push({ groupId: activeGroup.id, editor: activeGroup.activeEditor }); + } } + } - // {{SQL CARBON EDIT}} - return save(resource, true, undefined, editorService, accessor.get(IFileService), accessor.get(IUntitledEditorService), accessor.get(ITextFileService), accessor.get(IEditorGroupsService), accessor.get(IQueryEditorService), accessor.get(IWorkbenchEnvironmentService)); + if (!editors || editors.length === 0) { + return; // nothing to save } -}); + + // Save editors + await doSaveEditors(accessor, editors, options); + + // Special treatment for embedded editors: if we detect that focus is + // inside an embedded code editor, we save that model as well if we + // find it in our text file models. Currently, only textual editors + // support embedded editors. + const focusedCodeEditor = codeEditorService.getFocusedCodeEditor(); + if (focusedCodeEditor instanceof EmbeddedCodeEditorWidget) { + const resource = focusedCodeEditor.getModel()?.uri; + + // Check that the resource of the model was not saved already + if (resource && !editors.some(({ editor }) => isEqual(toResource(editor, { supportSideBySide: SideBySideEditor.MASTER }), resource))) { + const model = textFileService.models.get(resource); + if (!model?.isReadonly()) { + await textFileService.save(resource, options); + } + } + } +} + +function saveDirtyEditorsOfGroups(accessor: ServicesAccessor, groups: ReadonlyArray, options?: ISaveEditorsOptions): Promise { + const dirtyEditors: IEditorIdentifier[] = []; + for (const group of groups) { + for (const editor of group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)) { + if (editor.isDirty()) { + dirtyEditors.push({ groupId: group.id, editor }); + } + } + } + + return doSaveEditors(accessor, dirtyEditors, options); +} + +async function doSaveEditors(accessor: ServicesAccessor, editors: IEditorIdentifier[], options?: ISaveEditorsOptions): Promise { + const editorService = accessor.get(IEditorService); + const notificationService = accessor.get(INotificationService); + + try { + await editorService.save(editors, options); + } catch (error) { + notificationService.error(nls.localize('genericSaveError', "Failed to save '{0}': {1}", editors.map(({ editor }) => editor.getName()).join(', '), toErrorMessage(error, false))); + } +} KeybindingsRegistry.registerCommandAndKeybindingRule({ when: undefined, weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.CtrlCmd | KeyCode.KEY_S, id: SAVE_FILE_COMMAND_ID, - handler: (accessor, resource: URI | object) => { - const editorService = accessor.get(IEditorService); - const resources = getMultiSelectedResources(resource, accessor.get(IListService), editorService); - - if (resources.length === 1) { - // If only one resource is selected explictly call save since the behavior is a bit different than save all #41841 - // {{SQL CARBON EDIT}} - return save(resources[0], false, undefined, editorService, accessor.get(IFileService), accessor.get(IUntitledEditorService), accessor.get(ITextFileService), accessor.get(IEditorGroupsService), accessor.get(IQueryEditorService), accessor.get(IWorkbenchEnvironmentService)); - } - return saveAll(resources, editorService, accessor.get(IUntitledEditorService), accessor.get(ITextFileService), accessor.get(IEditorGroupsService)); + handler: accessor => { + return saveSelectedEditors(accessor, { reason: SaveReason.EXPLICIT, force: true /* force save even when non-dirty */ }); } }); @@ -549,57 +405,80 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ win: { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_S) }, id: SAVE_FILE_WITHOUT_FORMATTING_COMMAND_ID, handler: accessor => { - const editorService = accessor.get(IEditorService); - - const resource = toResource(editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER }); - if (resource) { - // {{SQL CARBON EDIT}} - return save(resource, false, { skipSaveParticipants: true }, editorService, accessor.get(IFileService), accessor.get(IUntitledEditorService), accessor.get(ITextFileService), accessor.get(IEditorGroupsService), accessor.get(IQueryEditorService), accessor.get(IWorkbenchEnvironmentService)); - } + return saveSelectedEditors(accessor, { reason: SaveReason.EXPLICIT, force: true /* force save even when non-dirty */, skipSaveParticipants: true }); + } +}); - return undefined; +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: SAVE_FILE_AS_COMMAND_ID, + weight: KeybindingWeight.WorkbenchContrib, + when: undefined, + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_S, + handler: accessor => { + return saveSelectedEditors(accessor, { reason: SaveReason.EXPLICIT, saveAs: true }); } }); CommandsRegistry.registerCommand({ id: SAVE_ALL_COMMAND_ID, handler: (accessor) => { - return saveAll(true, accessor.get(IEditorService), accessor.get(IUntitledEditorService), accessor.get(ITextFileService), accessor.get(IEditorGroupsService)); + return saveDirtyEditorsOfGroups(accessor, accessor.get(IEditorGroupsService).getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE), { reason: SaveReason.EXPLICIT }); } }); CommandsRegistry.registerCommand({ id: SAVE_ALL_IN_GROUP_COMMAND_ID, handler: (accessor, _: URI | object, editorContext: IEditorCommandsContext) => { - const contexts = getMultiSelectedEditorContexts(editorContext, accessor.get(IListService), accessor.get(IEditorGroupsService)); const editorGroupService = accessor.get(IEditorGroupsService); - let saveAllArg: any; + + const contexts = getMultiSelectedEditorContexts(editorContext, accessor.get(IListService), accessor.get(IEditorGroupsService)); + + let groups: ReadonlyArray | undefined = undefined; if (!contexts.length) { - saveAllArg = true; + groups = editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE); } else { - const fileService = accessor.get(IFileService); - saveAllArg = []; - contexts.forEach(context => { - const editorGroup = editorGroupService.getGroup(context.groupId); - if (editorGroup) { - editorGroup.editors.forEach(editor => { - const resource = toResource(editor, { supportSideBySide: SideBySideEditor.MASTER }); - if (resource && (resource.scheme === Schemas.untitled || fileService.canHandleResource(resource))) { - saveAllArg.push(resource); - } - }); - } - }); + groups = coalesce(contexts.map(context => editorGroupService.getGroup(context.groupId))); } - return saveAll(saveAllArg, accessor.get(IEditorService), accessor.get(IUntitledEditorService), accessor.get(ITextFileService), accessor.get(IEditorGroupsService)); + return saveDirtyEditorsOfGroups(accessor, groups, { reason: SaveReason.EXPLICIT }); } }); CommandsRegistry.registerCommand({ id: SAVE_FILES_COMMAND_ID, - handler: (accessor) => { - return saveAll(false, accessor.get(IEditorService), accessor.get(IUntitledEditorService), accessor.get(ITextFileService), accessor.get(IEditorGroupsService)); + handler: accessor => { + const editorService = accessor.get(IEditorService); + + return editorService.saveAll({ includeUntitled: false, reason: SaveReason.EXPLICIT }); + } +}); + +CommandsRegistry.registerCommand({ + id: REVERT_FILE_COMMAND_ID, + handler: async accessor => { + const notificationService = accessor.get(INotificationService); + const listService = accessor.get(IListService); + const editorGroupService = accessor.get(IEditorGroupsService); + const editorService = accessor.get(IEditorService); + + // Retrieve selected or active editor + let editors = getOpenEditorsViewMultiSelection(listService, editorGroupService); + if (!editors) { + const activeGroup = editorGroupService.activeGroup; + if (activeGroup.activeEditor) { + editors = [{ groupId: activeGroup.id, editor: activeGroup.activeEditor }]; + } + } + + if (!editors || editors.length === 0) { + return; // nothing to revert + } + + try { + await editorService.revert(editors.filter(({ editor }) => !editor.isUntitled() /* all except untitled */), { force: true }); + } catch (error) { + notificationService.error(nls.localize('genericRevertError', "Failed to revert '{0}': {1}", editors.map(({ editor }) => editor.getName()).join(', '), toErrorMessage(error, false))); + } } }); @@ -617,3 +496,81 @@ CommandsRegistry.registerCommand({ return workspaceEditingService.removeFolders(resources); } }); + +// Compressed item navigation + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + weight: KeybindingWeight.WorkbenchContrib + 10, + when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerCompressedFocusContext, ExplorerCompressedFirstFocusContext.negate()), + primary: KeyCode.LeftArrow, + id: PREVIOUS_COMPRESSED_FOLDER, + handler: (accessor) => { + const viewletService = accessor.get(IViewletService); + const viewlet = viewletService.getActiveViewlet(); + + if (viewlet?.getId() !== VIEWLET_ID) { + return; + } + + const explorer = viewlet as ExplorerViewlet; + const view = explorer.getExplorerView(); + view.previousCompressedStat(); + } +}); + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + weight: KeybindingWeight.WorkbenchContrib + 10, + when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerCompressedFocusContext, ExplorerCompressedLastFocusContext.negate()), + primary: KeyCode.RightArrow, + id: NEXT_COMPRESSED_FOLDER, + handler: (accessor) => { + const viewletService = accessor.get(IViewletService); + const viewlet = viewletService.getActiveViewlet(); + + if (viewlet?.getId() !== VIEWLET_ID) { + return; + } + + const explorer = viewlet as ExplorerViewlet; + const view = explorer.getExplorerView(); + view.nextCompressedStat(); + } +}); + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + weight: KeybindingWeight.WorkbenchContrib + 10, + when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerCompressedFocusContext, ExplorerCompressedFirstFocusContext.negate()), + primary: KeyCode.Home, + id: FIRST_COMPRESSED_FOLDER, + handler: (accessor) => { + const viewletService = accessor.get(IViewletService); + const viewlet = viewletService.getActiveViewlet(); + + if (viewlet?.getId() !== VIEWLET_ID) { + return; + } + + const explorer = viewlet as ExplorerViewlet; + const view = explorer.getExplorerView(); + view.firstCompressedStat(); + } +}); + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + weight: KeybindingWeight.WorkbenchContrib + 10, + when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerCompressedFocusContext, ExplorerCompressedLastFocusContext.negate()), + primary: KeyCode.End, + id: LAST_COMPRESSED_FOLDER, + handler: (accessor) => { + const viewletService = accessor.get(IViewletService); + const viewlet = viewletService.getActiveViewlet(); + + if (viewlet?.getId() !== VIEWLET_ID) { + return; + } + + const explorer = viewlet as ExplorerViewlet; + const view = explorer.getExplorerView(); + view.lastCompressedStat(); + } +}); diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts index 40b9527007d5..5cd844e60d1b 100644 --- a/src/vs/workbench/contrib/files/browser/files.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts @@ -9,14 +9,14 @@ import * as nls from 'vs/nls'; import { sep } from 'vs/base/common/path'; import { SyncActionDescriptor, MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; +import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope, IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IEditorInputFactory, EditorInput, IFileEditorInput, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions } from 'vs/workbench/common/editor'; import { AutoSaveConfiguration, HotExitConfiguration } from 'vs/platform/files/common/files'; import { VIEWLET_ID, SortOrderConfiguration, FILE_EDITOR_INPUT_ID, IExplorerService } from 'vs/workbench/contrib/files/common/files'; import { FileEditorTracker } from 'vs/workbench/contrib/files/browser/editors/fileEditorTracker'; -import { SaveErrorHandler } from 'vs/workbench/contrib/files/browser/saveErrorHandler'; +import { TextFileSaveErrorHandler } from 'vs/workbench/contrib/files/browser/textFileSaveErrorHandler'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; import { BinaryFileEditor } from 'vs/workbench/contrib/files/browser/editors/binaryFileEditor'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -27,7 +27,6 @@ import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import * as platform from 'vs/base/common/platform'; import { ExplorerViewlet, ExplorerViewletViewsContribution } from 'vs/workbench/contrib/files/browser/explorerViewlet'; import { IEditorRegistry, EditorDescriptor, Extensions as EditorExtensions } from 'vs/workbench/browser/editor'; -import { DataUriEditorInput } from 'vs/workbench/common/editor/dataUriEditorInput'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -38,6 +37,8 @@ import { ExplorerService } from 'vs/workbench/contrib/files/common/explorerServi import { SUPPORTED_ENCODINGS } from 'vs/workbench/services/textfile/common/textfiles'; import { Schemas } from 'vs/base/common/network'; import { WorkspaceWatcher } from 'vs/workbench/contrib/files/common/workspaceWatcher'; +import { editorConfigurationBaseNode } from 'vs/editor/common/config/commonEditorConfig'; +import { DirtyFilesIndicator } from 'vs/workbench/contrib/files/common/dirtyFilesIndicator'; // Viewlet Action export class OpenExplorerViewletAction extends ShowViewletAction { @@ -73,13 +74,12 @@ class FileUriLabelContribution implements IWorkbenchContribution { } // Register Viewlet -Registry.as(ViewletExtensions.Viewlets).registerViewlet(new ViewletDescriptor( +Registry.as(ViewletExtensions.Viewlets).registerViewlet(ViewletDescriptor.create( ExplorerViewlet, VIEWLET_ID, nls.localize('explore', "Explorer"), - 'explore', - // {{SQL CARBON EDIT}} - 10 + 'codicon-files', + 10 // {{SQL CARBON EDIT}} )); registerSingleton(IExplorerService, ExplorerService, true); @@ -94,21 +94,20 @@ const openViewletKb: IKeybindings = { // Register Action to Open Viewlet const registry = Registry.as(ActionExtensions.WorkbenchActions); registry.registerWorkbenchAction( - new SyncActionDescriptor(OpenExplorerViewletAction, OpenExplorerViewletAction.ID, OpenExplorerViewletAction.LABEL, openViewletKb), + SyncActionDescriptor.create(OpenExplorerViewletAction, OpenExplorerViewletAction.ID, OpenExplorerViewletAction.LABEL, openViewletKb), 'View: Show Explorer', nls.localize('view', "View") ); // Register file editors Registry.as(EditorExtensions.Editors).registerEditor( - new EditorDescriptor( + EditorDescriptor.create( BinaryFileEditor, BinaryFileEditor.ID, nls.localize('binaryFileEditor', "Binary File Editor") ), [ - new SyncDescriptor(FileEditorInput), - new SyncDescriptor(DataUriEditorInput) + new SyncDescriptor(FileEditorInput) ] ); @@ -166,19 +165,22 @@ Registry.as(WorkbenchExtensions.Workbench).regi // Register File Editor Tracker Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(FileEditorTracker, LifecyclePhase.Starting); -// Register Save Error Handler -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(SaveErrorHandler, LifecyclePhase.Starting); +// Register Text File Save Error Handler +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(TextFileSaveErrorHandler, LifecyclePhase.Starting); // Register uri display for file uris Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(FileUriLabelContribution, LifecyclePhase.Starting); -// Workspace Watcher +// Register Workspace Watcher Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(WorkspaceWatcher, LifecyclePhase.Restored); +// Register Dirty Files Indicator +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(DirtyFilesIndicator, LifecyclePhase.Starting); + // Configuration const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); -const hotExitConfiguration = platform.isNative ? +const hotExitConfiguration: IConfigurationPropertySchema = platform.isNative ? { 'type': 'string', 'scope': ConfigurationScope.APPLICATION, @@ -341,10 +343,7 @@ configurationRegistry.registerConfiguration({ }); configurationRegistry.registerConfiguration({ - id: 'editor', - order: 5, - title: nls.localize('editorConfigurationTitle', "Editor"), - type: 'object', + ...editorConfigurationBaseNode, properties: { 'editor.formatOnSave': { 'type': 'boolean', @@ -425,7 +424,12 @@ configurationRegistry.registerConfiguration({ ], description: nls.localize('explorer.incrementalNaming', "Controls what naming strategy to use when a giving a new name to a duplicated explorer item on paste."), default: 'simple' - } + }, + 'explorer.compactFolders': { + 'type': 'boolean', + 'description': nls.localize('compressSingleChildFolders', "Controls whether the explorer should render folders in a compact form. In such a form, single child folders will be compressed in a combined tree element. Useful for Java package structures, for example."), + 'default': true + }, } }); diff --git a/src/vs/workbench/contrib/files/browser/files.ts b/src/vs/workbench/contrib/files/browser/files.ts index 26c8e6ddaa08..cced7d25135b 100644 --- a/src/vs/workbench/contrib/files/browser/files.ts +++ b/src/vs/workbench/contrib/files/browser/files.ts @@ -4,21 +4,17 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from 'vs/base/common/uri'; -import { IListService, WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; +import { IListService } from 'vs/platform/list/browser/listService'; import { OpenEditor } from 'vs/workbench/contrib/files/common/files'; -import { toResource, SideBySideEditor } from 'vs/workbench/common/editor'; +import { toResource, SideBySideEditor, IEditorIdentifier } from 'vs/workbench/common/editor'; import { List } from 'vs/base/browser/ui/list/listWidget'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel'; import { coalesce } from 'vs/base/common/arrays'; +import { AsyncDataTree } from 'vs/base/browser/ui/tree/asyncDataTree'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -// Commands can get exeucted from a command pallete, from a context menu or from some list using a keybinding -// To cover all these cases we need to properly compute the resource on which the command is being executed -export function getResourceForCommand(resource: URI | object | undefined, listService: IListService, editorService: IEditorService): URI | undefined { - if (URI.isUri(resource)) { - return resource; - } - +function getFocus(listService: IListService): unknown | undefined { let list = listService.lastFocusedList; if (list?.getHTMLElement() === document.activeElement) { let focus: unknown; @@ -27,18 +23,31 @@ export function getResourceForCommand(resource: URI | object | undefined, listSe if (focused.length) { focus = focused[0]; } - } else if (list instanceof WorkbenchAsyncDataTree) { + } else if (list instanceof AsyncDataTree) { const focused = list.getFocus(); if (focused.length) { focus = focused[0]; } } - if (focus instanceof ExplorerItem) { - return focus.resource; - } else if (focus instanceof OpenEditor) { - return focus.getResource(); - } + return focus; + } + + return undefined; +} + +// Commands can get exeucted from a command pallete, from a context menu or from some list using a keybinding +// To cover all these cases we need to properly compute the resource on which the command is being executed +export function getResourceForCommand(resource: URI | object | undefined, listService: IListService, editorService: IEditorService): URI | undefined { + if (URI.isUri(resource)) { + return resource; + } + + const focus = getFocus(listService); + if (focus instanceof ExplorerItem) { + return focus.resource; + } else if (focus instanceof OpenEditor) { + return focus.getResource(); } return editorService.activeEditor ? toResource(editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER }) : undefined; @@ -48,7 +57,7 @@ export function getMultiSelectedResources(resource: URI | object | undefined, li const list = listService.lastFocusedList; if (list?.getHTMLElement() === document.activeElement) { // Explorer - if (list instanceof WorkbenchAsyncDataTree) { + if (list instanceof AsyncDataTree) { const selection = list.getSelection().map((fs: ExplorerItem) => fs.resource); const focusedElements = list.getFocus(); const focus = focusedElements.length ? focusedElements[0] : undefined; @@ -82,3 +91,25 @@ export function getMultiSelectedResources(resource: URI | object | undefined, li const result = getResourceForCommand(resource, listService, editorService); return !!result ? [result] : []; } + +export function getOpenEditorsViewMultiSelection(listService: IListService, editorGroupService: IEditorGroupsService): Array | undefined { + const list = listService.lastFocusedList; + if (list?.getHTMLElement() === document.activeElement) { + // Open editors view + if (list instanceof List) { + const selection = coalesce(list.getSelectedElements().filter(s => s instanceof OpenEditor)); + const focusedElements = list.getFocusedElements(); + const focus = focusedElements.length ? focusedElements[0] : undefined; + let mainEditor: IEditorIdentifier | undefined = undefined; + if (focus instanceof OpenEditor) { + mainEditor = focus; + } + // We only respect the selection if it contains the main element. + if (selection.some(s => s === mainEditor)) { + return selection; + } + } + } + + return undefined; +} diff --git a/src/vs/workbench/contrib/files/browser/files.web.contribution.ts b/src/vs/workbench/contrib/files/browser/files.web.contribution.ts index 4ae1f3dc989f..e7c162bf70aa 100644 --- a/src/vs/workbench/contrib/files/browser/files.web.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/files.web.contribution.ts @@ -10,13 +10,10 @@ import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileE import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IEditorRegistry, EditorDescriptor, Extensions as EditorExtensions } from 'vs/workbench/browser/editor'; import { TextFileEditor } from 'vs/workbench/contrib/files/browser/editors/textFileEditor'; -import { DirtyFilesTracker } from 'vs/workbench/contrib/files/common/dirtyFilesTracker'; -import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; -import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; // Register file editor Registry.as(EditorExtensions.Editors).registerEditor( - new EditorDescriptor( + EditorDescriptor.create( TextFileEditor, TextFileEditor.ID, nls.localize('textFileEditor', "Text File Editor") @@ -25,6 +22,3 @@ Registry.as(EditorExtensions.Editors).registerEditor( new SyncDescriptor(FileEditorInput) ] ); - -// Register Dirty Files Tracker -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(DirtyFilesTracker, LifecyclePhase.Starting); diff --git a/src/vs/workbench/contrib/files/browser/media/check-dark.svg b/src/vs/workbench/contrib/files/browser/media/check-dark.svg deleted file mode 100644 index 51674695e1fd..000000000000 --- a/src/vs/workbench/contrib/files/browser/media/check-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/files/browser/media/check-light.svg b/src/vs/workbench/contrib/files/browser/media/check-light.svg deleted file mode 100644 index 7b1da6d72086..000000000000 --- a/src/vs/workbench/contrib/files/browser/media/check-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/files/browser/media/collapse-all-dark.svg b/src/vs/workbench/contrib/files/browser/media/collapse-all-dark.svg deleted file mode 100644 index 4862c55dbeba..000000000000 --- a/src/vs/workbench/contrib/files/browser/media/collapse-all-dark.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/contrib/files/browser/media/collapse-all-hc.svg b/src/vs/workbench/contrib/files/browser/media/collapse-all-hc.svg deleted file mode 100644 index 05f920b29b68..000000000000 --- a/src/vs/workbench/contrib/files/browser/media/collapse-all-hc.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/contrib/files/browser/media/collapse-all-light.svg b/src/vs/workbench/contrib/files/browser/media/collapse-all-light.svg deleted file mode 100644 index 6359b42e623f..000000000000 --- a/src/vs/workbench/contrib/files/browser/media/collapse-all-light.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css b/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css index f45c99095e58..fc8387e3b930 100644 --- a/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css +++ b/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css @@ -3,11 +3,6 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -/* Activity Bar */ -.monaco-workbench .activitybar .monaco-action-bar .action-label.explore { - -webkit-mask: url('files-activity-bar.svg') no-repeat 50% 50%; -} - /* --- Explorer viewlet --- */ .explorer-viewlet, .explorer-folders-view { @@ -66,13 +61,14 @@ align-items: center; } -.explorer-viewlet .panel-header .count { +.explorer-viewlet .pane-header .count { min-width: fit-content; + min-width: -moz-fit-content; display: flex; align-items: center; } -.explorer-viewlet .panel-header .monaco-count-badge.hidden { +.explorer-viewlet .pane-header .monaco-count-badge.hidden { display: none; } @@ -123,6 +119,13 @@ padding: 0 20px 0 20px; } +.explorer-viewlet .explorer-empty-view .monaco-button { + max-width: 260px; + margin-left: auto; + margin-right: auto; + display: block; +} + .explorer-viewlet .explorer-item.nonexistent-root { opacity: 0.5; } @@ -132,6 +135,16 @@ line-height: normal; } +.explorer-viewlet .explorer-item .monaco-icon-name-container.multiple > .label-name > .monaco-highlighted-label { + padding: 1px; + border-radius: 3px; +} + +.explorer-viewlet .explorer-item .monaco-icon-name-container.multiple > .label-name:hover > .monaco-highlighted-label, +.explorer-viewlet .monaco-list .monaco-list-row.focused .explorer-item .monaco-icon-name-container.multiple > .label-name.active > .monaco-highlighted-label { + text-decoration: underline; +} + .monaco-workbench.linux .explorer-viewlet .explorer-item .monaco-inputbox, .monaco-workbench.mac .explorer-viewlet .explorer-item .monaco-inputbox { height: 22px; @@ -163,16 +176,3 @@ .hc-black .monaco-workbench .explorer-viewlet .editor-group { line-height: 20px; } - -/* TODO @misolori convert these to use icon font, for the debug viewlet */ -.monaco-workbench .explorer-action.collapse-explorer { - background: url("collapse-all-light.svg") 50% no-repeat; -} - -.vs-dark .monaco-workbench .explorer-action.collapse-explorer { - background: url("collapse-all-dark.svg") 50% no-repeat; -} - -.hc-black .monaco-workbench .explorer-action.collapse-explorer { - background: url("collapse-all-hc.svg") 50% no-repeat; -} diff --git a/src/vs/workbench/contrib/files/browser/media/fileactions.css b/src/vs/workbench/contrib/files/browser/media/fileactions.css index b97c0f5ac4e5..38b225f3299a 100644 --- a/src/vs/workbench/contrib/files/browser/media/fileactions.css +++ b/src/vs/workbench/contrib/files/browser/media/fileactions.css @@ -3,41 +3,6 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -/* Split editor vertical */ -.monaco-workbench .quick-open-sidebyside-vertical { - background-image: url("split-editor-vertical-light.svg"); -} - -.vs-dark .monaco-workbench .quick-open-sidebyside-vertical { - background-image: url("split-editor-vertical-dark.svg"); -} - -.hc-black .monaco-workbench .quick-open-sidebyside-vertical { - background-image: url("split-editor-vertical-hc.svg"); -} - -/* Split editor horizontal */ -.monaco-workbench .quick-open-sidebyside-horizontal { - background-image: url("split-editor-horizontal-light.svg"); -} - -.vs-dark .monaco-workbench .quick-open-sidebyside-horizontal { - background-image: url("split-editor-horizontal-dark.svg"); -} - -.hc-black .monaco-workbench .quick-open-sidebyside-horizontal { - background-image: url("split-editor-horizontal-hc.svg"); -} - -.monaco-workbench .file-editor-action.action-open-preview { - background: url("preview-light.svg") center center no-repeat; -} - -.vs-dark .monaco-workbench .file-editor-action.action-open-preview, -.hc-black .monaco-workbench .file-editor-action.action-open-preview { - background: url("preview-dark.svg") center center no-repeat; -} - .explorer-viewlet .explorer-open-editors .monaco-list .monaco-list-row.dirty:not(:hover) > .monaco-action-bar .codicon-close::before { content: "\ea71"; } diff --git a/src/vs/workbench/contrib/files/browser/media/files-activity-bar.svg b/src/vs/workbench/contrib/files/browser/media/files-activity-bar.svg deleted file mode 100644 index c109b13c3d24..000000000000 --- a/src/vs/workbench/contrib/files/browser/media/files-activity-bar.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/files/browser/media/preview-dark.svg b/src/vs/workbench/contrib/files/browser/media/preview-dark.svg deleted file mode 100644 index 1d59877196b0..000000000000 --- a/src/vs/workbench/contrib/files/browser/media/preview-dark.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/contrib/files/browser/media/preview-light.svg b/src/vs/workbench/contrib/files/browser/media/preview-light.svg deleted file mode 100644 index 3f1152fc3cdd..000000000000 --- a/src/vs/workbench/contrib/files/browser/media/preview-light.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/contrib/files/browser/media/split-editor-horizontal-dark.svg b/src/vs/workbench/contrib/files/browser/media/split-editor-horizontal-dark.svg deleted file mode 100644 index 419c21be4f62..000000000000 --- a/src/vs/workbench/contrib/files/browser/media/split-editor-horizontal-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/files/browser/media/split-editor-horizontal-hc.svg b/src/vs/workbench/contrib/files/browser/media/split-editor-horizontal-hc.svg deleted file mode 100644 index 7565fd3c1681..000000000000 --- a/src/vs/workbench/contrib/files/browser/media/split-editor-horizontal-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/files/browser/media/split-editor-horizontal-light.svg b/src/vs/workbench/contrib/files/browser/media/split-editor-horizontal-light.svg deleted file mode 100644 index 405291c14dd4..000000000000 --- a/src/vs/workbench/contrib/files/browser/media/split-editor-horizontal-light.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/vs/workbench/contrib/files/browser/media/split-editor-vertical-dark.svg b/src/vs/workbench/contrib/files/browser/media/split-editor-vertical-dark.svg deleted file mode 100644 index 8c22a7c5bfed..000000000000 --- a/src/vs/workbench/contrib/files/browser/media/split-editor-vertical-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/files/browser/media/split-editor-vertical-hc.svg b/src/vs/workbench/contrib/files/browser/media/split-editor-vertical-hc.svg deleted file mode 100644 index 82c19d0c8fc2..000000000000 --- a/src/vs/workbench/contrib/files/browser/media/split-editor-vertical-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/files/browser/media/split-editor-vertical-light.svg b/src/vs/workbench/contrib/files/browser/media/split-editor-vertical-light.svg deleted file mode 100644 index d5a2e9415b00..000000000000 --- a/src/vs/workbench/contrib/files/browser/media/split-editor-vertical-light.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/vs/workbench/contrib/files/browser/media/undo-dark.svg b/src/vs/workbench/contrib/files/browser/media/undo-dark.svg deleted file mode 100644 index de85d6ba679b..000000000000 --- a/src/vs/workbench/contrib/files/browser/media/undo-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/files/browser/media/undo-light.svg b/src/vs/workbench/contrib/files/browser/media/undo-light.svg deleted file mode 100644 index b70626957d05..000000000000 --- a/src/vs/workbench/contrib/files/browser/media/undo-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/files/browser/saveErrorHandler.ts b/src/vs/workbench/contrib/files/browser/textFileSaveErrorHandler.ts similarity index 95% rename from src/vs/workbench/contrib/files/browser/saveErrorHandler.ts rename to src/vs/workbench/contrib/files/browser/textFileSaveErrorHandler.ts index 30b3fb65b894..045cac2555cb 100644 --- a/src/vs/workbench/contrib/files/browser/saveErrorHandler.ts +++ b/src/vs/workbench/contrib/files/browser/textFileSaveErrorHandler.ts @@ -28,7 +28,7 @@ import { INotificationService, INotificationHandle, INotificationActions, Severi import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { ExecuteCommandAction } from 'vs/platform/actions/common/actions'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IProductService } from 'vs/platform/product/common/productService'; import { Event } from 'vs/base/common/event'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { isWindows } from 'vs/base/common/platform'; @@ -41,8 +41,8 @@ const LEARN_MORE_DIRTY_WRITE_IGNORE_KEY = 'learnMoreDirtyWriteError'; const conflictEditorHelp = nls.localize('userGuide', "Use the actions in the editor tool bar to either undo your changes or overwrite the content of the file with your changes."); -// A handler for save error happening with conflict resolution actions -export class SaveErrorHandler extends Disposable implements ISaveErrorHandler, IWorkbenchContribution { +// A handler for text file save error happening with conflict resolution actions +export class TextFileSaveErrorHandler extends Disposable implements ISaveErrorHandler, IWorkbenchContribution { private messages: ResourceMap; private conflictResolutionContext: IContextKey; private activeConflictResolutionResource?: URI; @@ -103,7 +103,7 @@ export class SaveErrorHandler extends Disposable implements ISaveErrorHandler, I onSaveError(error: any, model: ITextFileEditorModel): void { const fileOperationError = error as FileOperationError; - const resource = model.getResource(); + const resource = model.resource; let message: string; const primaryActions: IAction[] = []; @@ -113,7 +113,7 @@ export class SaveErrorHandler extends Disposable implements ISaveErrorHandler, I if (fileOperationError.fileOperationResult === FileOperationResult.FILE_MODIFIED_SINCE) { // If the user tried to save from the opened conflict editor, show its message again - if (this.activeConflictResolutionResource && this.activeConflictResolutionResource.toString() === model.getResource().toString()) { + if (this.activeConflictResolutionResource && this.activeConflictResolutionResource.toString() === model.resource.toString()) { if (this.storageService.getBoolean(LEARN_MORE_DIRTY_WRITE_IGNORE_KEY, StorageScope.GLOBAL)) { return; // return if this message is ignored } @@ -178,7 +178,7 @@ export class SaveErrorHandler extends Disposable implements ISaveErrorHandler, I const actions: INotificationActions = { primary: primaryActions, secondary: secondaryActions }; const handle = this.notificationService.notify({ severity: Severity.Error, message, actions }); Event.once(handle.onDidClose)(() => { dispose(primaryActions), dispose(secondaryActions); }); - this.messages.set(model.getResource(), handle); + this.messages.set(model.resource, handle); } dispose(): void { @@ -236,16 +236,16 @@ class ResolveSaveConflictAction extends Action { @IEditorService private readonly editorService: IEditorService, @INotificationService private readonly notificationService: INotificationService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IEnvironmentService private readonly environmentService: IEnvironmentService + @IProductService private readonly productService: IProductService ) { super('workbench.files.action.resolveConflict', nls.localize('compareChanges', "Compare")); } async run(): Promise { if (!this.model.isDisposed()) { - const resource = this.model.getResource(); + const resource = this.model.resource; const name = basename(resource); - const editorLabel = nls.localize('saveConflictDiffLabel', "{0} (in file) ↔ {1} (in {2}) - Resolve save conflict", name, name, this.environmentService.appNameLong); + const editorLabel = nls.localize('saveConflictDiffLabel', "{0} (in file) ↔ {1} (in {2}) - Resolve save conflict", name, name, this.productService.nameLong); await TextFileContentProvider.open(resource, CONFLICT_RESOLUTION_SCHEME, editorLabel, this.editorService, { pinned: true }); @@ -332,7 +332,7 @@ export const acceptLocalChangesCommand = async (accessor: ServicesAccessor, reso await model.save(); // Reopen file input - await editorService.openEditor({ resource: model.getResource() }, group); + await editorService.openEditor({ resource: model.resource }, group); // Clean up group.closeEditor(editor); @@ -361,7 +361,7 @@ export const revertLocalChangesCommand = async (accessor: ServicesAccessor, reso await model.revert(); // Reopen file input - await editorService.openEditor({ resource: model.getResource() }, group); + await editorService.openEditor({ resource: model.resource }, group); // Clean up group.closeEditor(editor); diff --git a/src/vs/workbench/contrib/files/browser/views/emptyView.ts b/src/vs/workbench/contrib/files/browser/views/emptyView.ts index 933355641e8c..3cea362ef59d 100644 --- a/src/vs/workbench/contrib/files/browser/views/emptyView.ts +++ b/src/vs/workbench/contrib/files/browser/views/emptyView.ts @@ -16,7 +16,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet'; +import { ViewletPane, IViewletPaneOptions } from 'vs/workbench/browser/parts/views/paneViewlet'; import { ResourcesDropHandler, DragAndDropObserver } from 'vs/workbench/browser/dnd'; import { listDropBackground } from 'vs/platform/theme/common/colorRegistry'; import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; @@ -26,7 +26,7 @@ import { Schemas } from 'vs/base/common/network'; import { isWeb } from 'vs/base/common/platform'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -export class EmptyView extends ViewletPanel { +export class EmptyView extends ViewletPane { static readonly ID: string = 'workbench.explorer.emptyView'; static readonly NAME = nls.localize('noWorkspace', "No Folder Opened"); @@ -46,7 +46,7 @@ export class EmptyView extends ViewletPanel { @ILabelService private labelService: ILabelService, @IContextKeyService contextKeyService: IContextKeyService ) { - super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: nls.localize('explorerSection', "Files Explorer Section") }, keybindingService, contextMenuService, configurationService, contextKeyService); + super({ ...(options as IViewletPaneOptions), ariaHeaderLabel: nls.localize('explorerSection', "Files Explorer Section") }, keybindingService, contextMenuService, configurationService, contextKeyService); this._register(this.contextService.onDidChangeWorkbenchState(() => this.setLabels())); this._register(this.labelService.onDidChangeFormatters(() => this.setLabels())); } @@ -134,9 +134,8 @@ export class EmptyView extends ViewletPanel { // no-op } - focusBody(): void { - if (this.button) { - this.button.element.focus(); - } + focus(): void { + this.button.element.focus(); } + } diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index 05bf1a45d04a..42474dd76f29 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -8,7 +8,7 @@ import { URI } from 'vs/base/common/uri'; import * as perf from 'vs/base/common/performance'; import { IAction, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions'; import { memoize } from 'vs/base/common/decorators'; -import { IFilesConfiguration, ExplorerFolderContext, FilesExplorerFocusedContext, ExplorerFocusedContext, ExplorerRootContext, ExplorerResourceReadonlyContext, IExplorerService, ExplorerResourceCut, ExplorerResourceMoveableToTrash } from 'vs/workbench/contrib/files/common/files'; +import { IFilesConfiguration, ExplorerFolderContext, FilesExplorerFocusedContext, ExplorerFocusedContext, ExplorerRootContext, ExplorerResourceReadonlyContext, IExplorerService, ExplorerResourceCut, ExplorerResourceMoveableToTrash, ExplorerCompressedFocusContext, ExplorerCompressedFirstFocusContext, ExplorerCompressedLastFocusContext } from 'vs/workbench/contrib/files/common/files'; import { NewFolderAction, NewFileAction, FileCopiedContext, RefreshExplorerView, CollapseExplorerView } from 'vs/workbench/contrib/files/browser/fileActions'; import { toResource, SideBySideEditor } from 'vs/workbench/common/editor'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; @@ -24,12 +24,12 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { ResourceContextKey } from 'vs/workbench/common/resources'; import { IDecorationsService } from 'vs/workbench/services/decorations/browser/decorations'; -import { WorkbenchAsyncDataTree, TreeResourceNavigator2 } from 'vs/platform/list/browser/listService'; +import { TreeResourceNavigator2, WorkbenchCompressibleAsyncDataTree } from 'vs/platform/list/browser/listService'; import { DelayedDragHandler } from 'vs/base/browser/dnd'; import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; -import { IViewletPanelOptions, ViewletPanel } from 'vs/workbench/browser/parts/views/panelViewlet'; +import { IViewletPaneOptions, ViewletPane } from 'vs/workbench/browser/parts/views/paneViewlet'; import { ILabelService } from 'vs/platform/label/common/label'; -import { ExplorerDelegate, ExplorerAccessibilityProvider, ExplorerDataSource, FilesRenderer, FilesFilter, FileSorter, FileDragAndDrop } from 'vs/workbench/contrib/files/browser/views/explorerViewer'; +import { ExplorerDelegate, ExplorerDataSource, FilesRenderer, ICompressedNavigationController, FilesFilter, FileSorter, FileDragAndDrop, ExplorerCompressionDelegate, isCompressedFolderName } from 'vs/workbench/contrib/files/browser/views/explorerViewer'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree'; @@ -49,13 +49,26 @@ import { values } from 'vs/base/common/map'; import { first } from 'vs/base/common/arrays'; import { withNullAsUndefined } from 'vs/base/common/types'; import { IFileService, FileSystemProviderCapabilities } from 'vs/platform/files/common/files'; -import { dispose } from 'vs/base/common/lifecycle'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { Event } from 'vs/base/common/event'; +import { attachStyler, IColorMapping } from 'vs/platform/theme/common/styler'; +import { ColorValue, listDropBackground } from 'vs/platform/theme/common/colorRegistry'; +import { Color } from 'vs/base/common/color'; +import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; + +interface IExplorerViewColors extends IColorMapping { + listDropBackground?: ColorValue | undefined; +} + +interface IExplorerViewStyles { + listDropBackground?: Color; +} -export class ExplorerView extends ViewletPanel { +export class ExplorerView extends ViewletPane { static readonly ID: string = 'workbench.explorer.fileView'; static readonly TREE_VIEW_STATE_STORAGE_KEY: string = 'workbench.explorer.treeViewState'; - private tree!: WorkbenchAsyncDataTree; + private tree!: WorkbenchCompressibleAsyncDataTree; private filter!: FilesFilter; private resourceContext: ResourceContextKey; @@ -64,6 +77,14 @@ export class ExplorerView extends ViewletPanel { private rootContext: IContextKey; private resourceMoveableToTrash: IContextKey; + private renderer!: FilesRenderer; + + private styleElement!: HTMLStyleElement; + private compressedFocusContext: IContextKey; + private compressedFocusFirstContext: IContextKey; + private compressedFocusLastContext: IContextKey; + private compressedNavigationController: ICompressedNavigationController | undefined; + // Refresh is needed on the initial explorer open private shouldRefresh = true; private dragHandler!: DelayedDragHandler; @@ -71,7 +92,7 @@ export class ExplorerView extends ViewletPanel { private actions: IAction[] | undefined; constructor( - options: IViewletPanelOptions, + options: IViewletPaneOptions, @IContextMenuService contextMenuService: IContextMenuService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @@ -91,19 +112,24 @@ export class ExplorerView extends ViewletPanel { @IClipboardService private clipboardService: IClipboardService, @IFileService private readonly fileService: IFileService ) { - super({ ...(options as IViewletPanelOptions), id: ExplorerView.ID, ariaHeaderLabel: nls.localize('explorerSection', "Files Explorer Section") }, keybindingService, contextMenuService, configurationService, contextKeyService); + super({ ...(options as IViewletPaneOptions), id: ExplorerView.ID, ariaHeaderLabel: nls.localize('explorerSection', "Files Explorer Section") }, keybindingService, contextMenuService, configurationService, contextKeyService); this.resourceContext = instantiationService.createInstance(ResourceContextKey); this._register(this.resourceContext); + this.folderContext = ExplorerFolderContext.bindTo(contextKeyService); this.readonlyContext = ExplorerResourceReadonlyContext.bindTo(contextKeyService); this.rootContext = ExplorerRootContext.bindTo(contextKeyService); this.resourceMoveableToTrash = ExplorerResourceMoveableToTrash.bindTo(contextKeyService); + this.compressedFocusContext = ExplorerCompressedFocusContext.bindTo(contextKeyService); + this.compressedFocusFirstContext = ExplorerCompressedFirstFocusContext.bindTo(contextKeyService); + this.compressedFocusLastContext = ExplorerCompressedLastFocusContext.bindTo(contextKeyService); + + this.explorerService.registerContextProvider(this); const decorationProvider = new ExplorerDecorationsProvider(this.explorerService, contextService); this._register(decorationService.registerDecorationsProvider(decorationProvider)); this._register(decorationProvider); - this._register(this.resourceContext); } get name(): string { @@ -160,6 +186,10 @@ export class ExplorerView extends ViewletPanel { renderBody(container: HTMLElement): void { const treeContainer = DOM.append(container, DOM.$('.explorer-folders-view')); + + this.styleElement = DOM.createStyleSheet(treeContainer); + attachStyler(this.themeService, { listDropBackground }, this.styleListDropBackground.bind(this)); + this.createTree(treeContainer); if (this.toolbar) { @@ -253,6 +283,42 @@ export class ExplorerView extends ViewletPanel { } } + getContext(respectMultiSelection: boolean): ExplorerItem[] { + let focusedStat: ExplorerItem | undefined; + + if (this.compressedNavigationController) { + focusedStat = this.compressedNavigationController.current; + } else { + const focus = this.tree.getFocus(); + focusedStat = focus.length ? focus[0] : undefined; + } + + const selectedStats: ExplorerItem[] = []; + + for (const stat of this.tree.getSelection()) { + const controller = this.renderer.getCompressedNavigationController(stat); + + if (controller) { + selectedStats.push(...controller.items); + } else { + selectedStats.push(stat); + } + } + if (!focusedStat) { + if (respectMultiSelection) { + return selectedStats; + } else { + return []; + } + } + + if (respectMultiSelection && selectedStats.indexOf(focusedStat) >= 0) { + return selectedStats; + } + + return [focusedStat]; + } + private selectActiveFile(deselect?: boolean, reveal = this.autoReveal): void { if (this.autoReveal) { const activeFile = this.getActiveFile(); @@ -277,14 +343,17 @@ export class ExplorerView extends ViewletPanel { this._register(explorerLabels); const updateWidth = (stat: ExplorerItem) => this.tree.updateWidth(stat); - const filesRenderer = this.instantiationService.createInstance(FilesRenderer, explorerLabels, updateWidth); - this._register(filesRenderer); + this.renderer = this.instantiationService.createInstance(FilesRenderer, explorerLabels, updateWidth); + this._register(this.renderer); this._register(createFileIconThemableTreeContainerScope(container, this.themeService)); - this.tree = this.instantiationService.createInstance(WorkbenchAsyncDataTree, 'FileExplorer', container, new ExplorerDelegate(), [filesRenderer], + const isCompressionEnabled = () => this.configurationService.getValue('explorer.compactFolders'); + + this.tree = this.instantiationService.createInstance>(WorkbenchCompressibleAsyncDataTree, 'FileExplorer', container, new ExplorerDelegate(), new ExplorerCompressionDelegate(), [this.renderer], this.instantiationService.createInstance(ExplorerDataSource), { - accessibilityProvider: new ExplorerAccessibilityProvider(), + compressionEnabled: isCompressionEnabled(), + accessibilityProvider: this.renderer, ariaLabel: nls.localize('treeAriaLabel', "Files Explorer"), identityProvider: { getId: (stat: ExplorerItem) => { @@ -302,6 +371,13 @@ export class ExplorerView extends ViewletPanel { } return stat.name; + }, + getCompressedNodeKeyboardNavigationLabel: (stats: ExplorerItem[]) => { + if (stats.some(stat => this.explorerService.isEditable(stat))) { + return undefined; + } + + return stats.map(stat => stat.name).join('/'); } }, multipleSelectionSupport: true, @@ -309,10 +385,17 @@ export class ExplorerView extends ViewletPanel { sorter: this.instantiationService.createInstance(FileSorter), dnd: this.instantiationService.createInstance(FileDragAndDrop), autoExpandSingleChildren: true, - additionalScrollHeight: ExplorerDelegate.ITEM_HEIGHT + additionalScrollHeight: ExplorerDelegate.ITEM_HEIGHT, + overrideStyles: { + listBackground: SIDE_BAR_BACKGROUND + } }); this._register(this.tree); + // Bind configuration + const onDidChangeCompressionConfiguration = Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('explorer.compactFolders')); + this._register(onDidChangeCompressionConfiguration(_ => this.tree.updateOptions({ compressionEnabled: isCompressionEnabled() }))); + // Bind context keys FilesExplorerFocusedContext.bindTo(this.tree.contextKeyService); ExplorerFocusedContext.bindTo(this.tree.contextKeyService); @@ -376,7 +459,7 @@ export class ExplorerView extends ViewletPanel { } } - private setContextKeys(stat: ExplorerItem | null): void { + private setContextKeys(stat: ExplorerItem | null | undefined): void { const isSingleFolder = this.contextService.getWorkbenchState() === WorkbenchState.FOLDER; const resource = stat ? stat.resource : isSingleFolder ? this.contextService.getWorkspace().folders[0].uri : null; this.resourceContext.set(resource); @@ -386,7 +469,22 @@ export class ExplorerView extends ViewletPanel { } private onContextMenu(e: ITreeContextMenuEvent): void { - const stat = e.element; + const disposables = new DisposableStore(); + let stat = e.element; + let anchor = e.anchor; + + // Compressed folders + if (stat) { + const controller = this.renderer.getCompressedNavigationController(stat); + + if (controller) { + if (isCompressedFolderName(e.browserEvent.target)) { + anchor = controller.labels[controller.index]; + } else { + controller.last(); + } + } + } // update dynamic contexts this.fileCopiedContextKey.set(this.clipboardService.hasResources()); @@ -397,17 +495,17 @@ export class ExplorerView extends ViewletPanel { const actions: IAction[] = []; const roots = this.explorerService.roots; // If the click is outside of the elements pass the root resource if there is only one root. If there are multiple roots pass empty object. const arg = stat instanceof ExplorerItem ? stat.resource : roots.length === 1 ? roots[0].resource : {}; - const actionsDisposable = createAndFillInContextMenuActions(this.contributedContextMenu, { arg, shouldForwardArgs: true }, actions, this.contextMenuService); + disposables.add(createAndFillInContextMenuActions(this.contributedContextMenu, { arg, shouldForwardArgs: true }, actions, this.contextMenuService)); this.contextMenuService.showContextMenu({ - getAnchor: () => e.anchor, + getAnchor: () => anchor, getActions: () => actions, onHide: (wasCancelled?: boolean) => { if (wasCancelled) { this.tree.domFocus(); } - dispose(actionsDisposable); + disposables.dispose(); }, getActionsContext: () => stat && selection && selection.indexOf(stat) >= 0 ? selection.map((fs: ExplorerItem) => fs.resource) @@ -416,7 +514,7 @@ export class ExplorerView extends ViewletPanel { } private onFocusChanged(elements: ExplorerItem[]): void { - const stat = elements && elements.length ? elements[0] : null; + const stat = elements && elements.length ? elements[0] : undefined; this.setContextKeys(stat); if (stat) { @@ -426,6 +524,17 @@ export class ExplorerView extends ViewletPanel { } else { this.resourceMoveableToTrash.reset(); } + + this.compressedNavigationController = stat && this.renderer.getCompressedNavigationController(stat); + + if (!this.compressedNavigationController) { + this.compressedFocusContext.set(false); + return; + } + + this.compressedFocusContext.set(true); + // this.compressedNavigationController.last(); + this.updateCompressedNavigationContextKeys(this.compressedNavigationController); } // General methods @@ -523,7 +632,12 @@ export class ExplorerView extends ViewletPanel { return withNullAsUndefined(toResource(input, { supportSideBySide: SideBySideEditor.MASTER })); } - private async onSelectResource(resource: URI | undefined, reveal = this.autoReveal): Promise { + private async onSelectResource(resource: URI | undefined, reveal = this.autoReveal, retry = 0): Promise { + // do no retry more than once to prevent inifinite loops in cases of inconsistent model + if (retry === 2) { + return; + } + if (!resource || !this.isBodyVisible()) { return; } @@ -534,12 +648,18 @@ export class ExplorerView extends ViewletPanel { .sort((first, second) => second.resource.path.length - first.resource.path.length)[0]; while (item && item.resource.toString() !== resource.toString()) { + if (item.isDisposed) { + return this.onSelectResource(resource, reveal, retry + 1); + } await this.tree.expand(item); item = first(values(item.children), i => isEqualOrParent(resource, i.resource)); } if (item && item.parent) { if (reveal) { + if (item.isDisposed) { + return this.onSelectResource(resource, reveal, retry + 1); + } this.tree.reveal(item, 0.5); } @@ -563,6 +683,60 @@ export class ExplorerView extends ViewletPanel { this.tree.collapseAll(); } + previousCompressedStat(): void { + if (!this.compressedNavigationController) { + return; + } + + this.compressedNavigationController.previous(); + this.updateCompressedNavigationContextKeys(this.compressedNavigationController); + } + + nextCompressedStat(): void { + if (!this.compressedNavigationController) { + return; + } + + this.compressedNavigationController.next(); + this.updateCompressedNavigationContextKeys(this.compressedNavigationController); + } + + firstCompressedStat(): void { + if (!this.compressedNavigationController) { + return; + } + + this.compressedNavigationController.first(); + this.updateCompressedNavigationContextKeys(this.compressedNavigationController); + } + + lastCompressedStat(): void { + if (!this.compressedNavigationController) { + return; + } + + this.compressedNavigationController.last(); + this.updateCompressedNavigationContextKeys(this.compressedNavigationController); + } + + private updateCompressedNavigationContextKeys(controller: ICompressedNavigationController): void { + this.compressedFocusFirstContext.set(controller.index === 0); + this.compressedFocusLastContext.set(controller.index === controller.count - 1); + } + + styleListDropBackground(styles: IExplorerViewStyles): void { + const content: string[] = []; + + if (styles.listDropBackground) { + content.push(`.explorer-viewlet .explorer-item .monaco-icon-name-container.multiple > .label-name.drop-target > .monaco-highlighted-label { background-color: ${styles.listDropBackground}; }`); + } + + const newStyles = content.join('\n'); + if (newStyles !== this.styleElement.innerHTML) { + this.styleElement.innerHTML = newStyles; + } + } + dispose(): void { if (this.dragHandler) { this.dragHandler.dispose(); diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index 4c876d23aba1..2186296fbd51 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -12,14 +12,14 @@ import { INotificationService, Severity } from 'vs/platform/notification/common/ import { IFileService, FileKind, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { IDisposable, Disposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, Disposable, dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { KeyCode } from 'vs/base/common/keyCodes'; import { IFileLabelOptions, IResourceLabel, ResourceLabels } from 'vs/workbench/browser/labels'; -import { ITreeRenderer, ITreeNode, ITreeFilter, TreeVisibility, TreeFilterResult, IAsyncDataSource, ITreeSorter, ITreeDragAndDrop, ITreeDragOverReaction, TreeDragOverBubble } from 'vs/base/browser/ui/tree/tree'; +import { ITreeNode, ITreeFilter, TreeVisibility, TreeFilterResult, IAsyncDataSource, ITreeSorter, ITreeDragAndDrop, ITreeDragOverReaction, TreeDragOverBubble } from 'vs/base/browser/ui/tree/tree'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; -import { IFilesConfiguration, IExplorerService, IEditableData } from 'vs/workbench/contrib/files/common/files'; +import { IFilesConfiguration, IExplorerService } from 'vs/workbench/contrib/files/common/files'; import { dirname, joinPath, isEqualOrParent, basename, hasToIgnoreCase, distinctParents } from 'vs/base/common/resources'; import { InputBox, MessageType } from 'vs/base/browser/ui/inputbox/inputBox'; import { localize } from 'vs/nls'; @@ -28,14 +28,14 @@ import { once } from 'vs/base/common/functional'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { equals, deepClone } from 'vs/base/common/objects'; import * as path from 'vs/base/common/path'; -import { ExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel'; +import { ExplorerItem, NewExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel'; import { compareFileExtensions, compareFileNames } from 'vs/base/common/comparers'; -import { fillResourceDataTransfers, CodeDataTransfers, extractResources } from 'vs/workbench/browser/dnd'; +import { fillResourceDataTransfers, CodeDataTransfers, extractResources, containsDragType } from 'vs/workbench/browser/dnd'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IDragAndDropData, DataTransfers } from 'vs/base/browser/dnd'; import { Schemas } from 'vs/base/common/network'; import { DesktopDragAndDropData, ExternalElementsDragAndDropData, ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView'; -import { isMacintosh } from 'vs/base/common/platform'; +import { isMacintosh, isWeb } from 'vs/base/common/platform'; import { IDialogService, IConfirmation, getConfirmMessage } from 'vs/platform/dialogs/common/dialogs'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IHostService } from 'vs/workbench/services/host/browser/host'; @@ -46,7 +46,15 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { IWorkspaceFolderCreationData } from 'vs/platform/workspaces/common/workspaces'; import { findValidPasteFileTarget } from 'vs/workbench/contrib/files/browser/fileActions'; import { FuzzyScore, createMatches } from 'vs/base/common/filters'; -import { Emitter } from 'vs/base/common/event'; +import { Emitter, Event, EventMultiplexer } from 'vs/base/common/event'; +import { ITreeCompressionDelegate } from 'vs/base/browser/ui/tree/asyncDataTree'; +import { ICompressibleTreeRenderer } from 'vs/base/browser/ui/tree/objectTree'; +import { ICompressedTreeNode } from 'vs/base/browser/ui/tree/compressedObjectTreeModel'; +import { VSBuffer } from 'vs/base/common/buffer'; +import { ILabelService } from 'vs/platform/label/common/label'; +import { isNumber } from 'vs/base/common/types'; +import { domEvent } from 'vs/base/browser/event'; +import { IEditableData } from 'vs/workbench/common/views'; export class ExplorerDelegate implements IListVirtualDelegate { @@ -110,17 +118,111 @@ export class ExplorerDataSource implements IAsyncDataSource; + previous(): void; + next(): void; + first(): void; + last(): void; + setIndex(index: number): void; +} + +export class CompressedNavigationController implements ICompressedNavigationController, IDisposable { + + static ID = 0; + + private _index: number; + readonly labels: HTMLElement[]; + + get index(): number { return this._index; } + get count(): number { return this.items.length; } + get current(): ExplorerItem { return this.items[this._index]!; } + get currentId(): string { return `${this.id}_${this.index}`; } + + private _onDidChange = new Emitter(); + readonly onDidChange = this._onDidChange.event; + + constructor(private id: string, readonly items: ExplorerItem[], templateData: IFileTemplateData) { + this._index = items.length - 1; + this.labels = Array.from(templateData.container.querySelectorAll('.label-name')) as HTMLElement[]; + + for (let i = 0; i < items.length; i++) { + this.labels[i].setAttribute('aria-label', items[i].name); + } + + DOM.addClass(this.labels[this._index], 'active'); + } + + previous(): void { + if (this._index <= 0) { + return; + } + + this.setIndex(this._index - 1); + } + + next(): void { + if (this._index >= this.items.length - 1) { + return; + } + + this.setIndex(this._index + 1); + } + + first(): void { + if (this._index === 0) { + return; + } + + this.setIndex(0); + } + + last(): void { + if (this._index === this.items.length - 1) { + return; + } + + this.setIndex(this.items.length - 1); + } + + setIndex(index: number): void { + if (index < 0 || index >= this.items.length) { + return; + } + + DOM.removeClass(this.labels[this._index], 'active'); + this._index = index; + DOM.addClass(this.labels[this._index], 'active'); + + this._onDidChange.fire(); + } + + dispose(): void { + this._onDidChange.dispose(); + } +} + export interface IFileTemplateData { elementDisposable: IDisposable; label: IResourceLabel; container: HTMLElement; } -export class FilesRenderer implements ITreeRenderer, IDisposable { +export class FilesRenderer implements ICompressibleTreeRenderer, IAccessibilityProvider, IDisposable { static readonly ID = 'file'; private config: IFilesConfiguration; private configListener: IDisposable; + private compressedNavigationControllers = new Map(); + + private _onDidChangeActiveDescendant = new EventMultiplexer(); + readonly onDidChangeActiveDescendant = this._onDidChangeActiveDescendant.event; constructor( private labels: ResourceLabels, @@ -128,7 +230,8 @@ export class FilesRenderer implements ITreeRenderer(); this.configListener = this.configurationService.onDidChangeConfiguration(e => { @@ -154,37 +257,92 @@ export class FilesRenderer implements ITreeRenderer, FuzzyScore>, index: number, templateData: IFileTemplateData, height: number | undefined): void { + templateData.elementDisposable.dispose(); + + const stat = node.element.elements[node.element.elements.length - 1]; + const editable = node.element.elements.filter(e => this.explorerService.isEditable(e)); + const editableData = editable.length === 0 ? undefined : this.explorerService.getEditableData(editable[0]); + + // File Label + if (!editableData) { + DOM.addClass(templateData.label.element, 'compressed'); + templateData.label.element.style.display = 'flex'; - templateData.elementDisposable = templateData.label.onDidRender(() => { - try { - this.updateWidth(stat); - } catch (e) { - // noop since the element might no longer be in the tree, no update of width necessery + const disposables = new DisposableStore(); + const id = `compressed-explorer_${CompressedNavigationController.ID++}`; + + const label = node.element.elements.map(e => e.name); + disposables.add(this.renderStat(stat, label, id, node.filterData, templateData)); + + const compressedNavigationController = new CompressedNavigationController(id, node.element.elements, templateData); + disposables.add(compressedNavigationController); + this.compressedNavigationControllers.set(stat, compressedNavigationController); + + // accessibility + disposables.add(this._onDidChangeActiveDescendant.add(compressedNavigationController.onDidChange)); + + domEvent(templateData.container, 'mousedown')(e => { + const result = getIconLabelNameFromHTMLElement(e.target); + + if (result) { + compressedNavigationController.setIndex(result.index); } - }); + }, undefined, disposables); + + disposables.add(toDisposable(() => this.compressedNavigationControllers.delete(stat))); + + templateData.elementDisposable = disposables; } // Input Box else { + DOM.removeClass(templateData.label.element, 'compressed'); templateData.label.element.style.display = 'none'; - templateData.elementDisposable = this.renderInputBox(templateData.container, stat, editableData); + templateData.elementDisposable = this.renderInputBox(templateData.container, editable[0], editableData); } } + private renderStat(stat: ExplorerItem, label: string | string[], domId: string | undefined, filterData: FuzzyScore | undefined, templateData: IFileTemplateData): IDisposable { + templateData.label.element.style.display = 'flex'; + const extraClasses = ['explorer-item']; + if (this.explorerService.isCut(stat)) { + extraClasses.push('cut'); + } + + templateData.label.setResource({ resource: stat.resource, name: label }, { + fileKind: stat.isRoot ? FileKind.ROOT_FOLDER : stat.isDirectory ? FileKind.FOLDER : FileKind.FILE, + extraClasses, + fileDecorations: this.config.explorer.decorations, + matches: createMatches(filterData), + separator: this.labelService.getSeparator(stat.resource.scheme, stat.resource.authority), + domId + }); + + return templateData.label.onDidRender(() => { + try { + this.updateWidth(stat); + } catch (e) { + // noop since the element might no longer be in the tree, no update of width necessery + } + }); + } + private renderInputBox(container: HTMLElement, stat: ExplorerItem, editableData: IEditableData): IDisposable { // Use a file label only for the icon next to the input box @@ -198,6 +356,9 @@ export class FilesRenderer implements ITreeRenderer, index: number, templateData: IFileTemplateData): void { + disposeElement(element: ITreeNode, index: number, templateData: IFileTemplateData): void { + templateData.elementDisposable.dispose(); + } + + disposeCompressedElements(node: ITreeNode, FuzzyScore>, index: number, templateData: IFileTemplateData): void { templateData.elementDisposable.dispose(); } @@ -270,15 +435,24 @@ export class FilesRenderer implements ITreeRenderer { + // IAccessibilityProvider + getAriaLabel(element: ExplorerItem): string { return element.name; } + + getActiveDescendantId(stat: ExplorerItem): string | undefined { + const compressedNavigationController = this.compressedNavigationControllers.get(stat); + return compressedNavigationController?.currentId; + } + + dispose(): void { + this.configListener.dispose(); + } } interface CachedParsedExpression { @@ -427,9 +601,21 @@ export class FileSorter implements ITreeSorter { } } +const fileOverwriteConfirm = (name: string) => { + return { + message: localize('confirmOverwrite', "A file or folder with the name '{0}' already exists in the destination folder. Do you want to replace it?", name), + detail: localize('irreversible', "This action is irreversible!"), + primaryButton: localize({ key: 'replaceButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Replace"), + type: 'warning' + }; +}; + export class FileDragAndDrop implements ITreeDragAndDrop { private static readonly CONFIRM_DND_SETTING_KEY = 'explorer.confirmDragAndDrop'; + private compressedDragOverElement: HTMLElement | undefined; + private compressedDropTargetDisposable: IDisposable = Disposable.None; + private toDispose: IDisposable[]; private dropEnabled = false; @@ -460,19 +646,49 @@ export class FileDragAndDrop implements ITreeDragAndDrop { return false; } + // Compressed folders + if (target) { + const compressedTarget = FileDragAndDrop.getCompressedStatFromDragEvent(target, originalEvent); + + if (compressedTarget) { + const iconLabelName = getIconLabelNameFromHTMLElement(originalEvent.target); + + if (iconLabelName && iconLabelName.index < iconLabelName.count - 1) { + const result = this._onDragOver(data, compressedTarget, targetIndex, originalEvent); + + if (result) { + if (iconLabelName.element !== this.compressedDragOverElement) { + this.compressedDragOverElement = iconLabelName.element; + this.compressedDropTargetDisposable.dispose(); + this.compressedDropTargetDisposable = toDisposable(() => { + DOM.removeClass(iconLabelName.element, 'drop-target'); + this.compressedDragOverElement = undefined; + }); + + DOM.addClass(iconLabelName.element, 'drop-target'); + } + + return typeof result === 'boolean' ? result : { ...result, feedback: [] }; + } + + this.compressedDropTargetDisposable.dispose(); + return false; + } + } + } + + this.compressedDropTargetDisposable.dispose(); + return this._onDragOver(data, target, targetIndex, originalEvent); + } + + private _onDragOver(data: IDragAndDropData, target: ExplorerItem | undefined, targetIndex: number | undefined, originalEvent: DragEvent): boolean | ITreeDragOverReaction { const isCopy = originalEvent && ((originalEvent.ctrlKey && !isMacintosh) || (originalEvent.altKey && isMacintosh)); const fromDesktop = data instanceof DesktopDragAndDropData; const effect = (fromDesktop || isCopy) ? ListDragOverEffect.Copy : ListDragOverEffect.Move; // Desktop DND - if (fromDesktop && originalEvent.dataTransfer) { - const types = originalEvent.dataTransfer.types; - const typesArray: string[] = []; - for (let i = 0; i < types.length; i++) { - typesArray.push(types[i].toLowerCase()); // somehow the types are lowercase - } - - if (typesArray.indexOf(DataTransfers.FILES.toLowerCase()) === -1 && typesArray.indexOf(CodeDataTransfers.FILES.toLowerCase()) === -1) { + if (fromDesktop) { + if (!containsDragType(originalEvent, DataTransfers.FILES, CodeDataTransfers.FILES)) { return false; } } @@ -484,7 +700,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop { // In-Explorer DND else { - const items = (data as ElementsDragAndDropData).elements; + const items = FileDragAndDrop.getStatsFromDragAndDropData(data as ElementsDragAndDropData); if (!target) { // Dropping onto the empty area. Do not accept if items dragged are already @@ -559,16 +775,17 @@ export class FileDragAndDrop implements ITreeDragAndDrop { return element.resource.toString(); } - getDragLabel(elements: ExplorerItem[]): string | undefined { - if (elements.length > 1) { - return String(elements.length); + getDragLabel(elements: ExplorerItem[], originalEvent: DragEvent): string | undefined { + if (elements.length === 1) { + const stat = FileDragAndDrop.getCompressedStatFromDragEvent(elements[0], originalEvent); + return stat.name; } - return elements[0].name; + return String(elements.length); } onDragStart(data: IDragAndDropData, originalEvent: DragEvent): void { - const items = (data as ElementsDragAndDropData).elements; + const items = FileDragAndDrop.getStatsFromDragAndDropData(data as ElementsDragAndDropData, originalEvent); if (items && items.length && originalEvent.dataTransfer) { // Apply some datatransfer types to allow for dragging the element outside of the application this.instantiationService.invokeFunction(fillResourceDataTransfers, items, originalEvent); @@ -583,6 +800,17 @@ export class FileDragAndDrop implements ITreeDragAndDrop { } drop(data: IDragAndDropData, target: ExplorerItem | undefined, targetIndex: number | undefined, originalEvent: DragEvent): void { + this.compressedDropTargetDisposable.dispose(); + + // Find compressed target + if (target) { + const compressedTarget = FileDragAndDrop.getCompressedStatFromDragEvent(target, originalEvent); + + if (compressedTarget) { + target = compressedTarget; + } + } + // Find parent to add to if (!target) { target = this.explorerService.roots[this.explorerService.roots.length - 1]; @@ -596,14 +824,42 @@ export class FileDragAndDrop implements ITreeDragAndDrop { // Desktop DND (Import file) if (data instanceof DesktopDragAndDropData) { - this.handleExternalDrop(data, target, originalEvent).then(undefined, e => this.notificationService.warn(e)); + if (isWeb) { + this.handleWebExternalDrop(data, target, originalEvent).then(undefined, e => this.notificationService.warn(e)); + } else { + this.handleExternalDrop(data, target, originalEvent).then(undefined, e => this.notificationService.warn(e)); + } } // In-Explorer DND (Move/Copy file) else { - this.handleExplorerDrop(data, target, originalEvent).then(undefined, e => this.notificationService.warn(e)); + this.handleExplorerDrop(data as ElementsDragAndDropData, target, originalEvent).then(undefined, e => this.notificationService.warn(e)); } } + private async handleWebExternalDrop(data: DesktopDragAndDropData, target: ExplorerItem, originalEvent: DragEvent): Promise { + data.files.forEach(file => { + const reader = new FileReader(); + reader.readAsArrayBuffer(file); + reader.onload = async (event) => { + const name = file.name; + if (typeof name === 'string' && event.target?.result instanceof ArrayBuffer) { + if (target.getChild(name)) { + const { confirmed } = await this.dialogService.confirm(fileOverwriteConfirm(name)); + if (!confirmed) { + return; + } + } + + const resource = joinPath(target.resource, name); + await this.fileService.writeFile(resource, VSBuffer.wrap(new Uint8Array(event.target?.result))); + if (data.files.length === 1) { + await this.editorService.openEditor({ resource, options: { pinned: true } }); + } + } + }; + }); + } + private async handleExternalDrop(data: DesktopDragAndDropData, target: ExplorerItem, originalEvent: DragEvent): Promise { const droppedResources = extractResources(originalEvent, true); // Check for dropped external files to be folders @@ -644,11 +900,9 @@ export class FileDragAndDrop implements ITreeDragAndDrop { else if (target instanceof ExplorerItem) { return this.addResources(target, droppedResources.map(res => res.resource)); } - - return undefined; } - private async addResources(target: ExplorerItem, resources: URI[]): Promise { + private async addResources(target: ExplorerItem, resources: URI[]): Promise { if (resources && resources.length > 0) { // Resolve target to check for name collisions and ask user @@ -659,22 +913,16 @@ export class FileDragAndDrop implements ITreeDragAndDrop { if (targetStat.children) { const ignoreCase = hasToIgnoreCase(target.resource); targetStat.children.forEach(child => { - targetNames.add(ignoreCase ? child.name : child.name.toLowerCase()); + targetNames.add(ignoreCase ? child.name.toLowerCase() : child.name); }); } - const resourceExists = resources.some(resource => targetNames.has(!hasToIgnoreCase(resource) ? basename(resource) : basename(resource).toLowerCase())); + const filtered = resources.filter(resource => targetNames.has(!hasToIgnoreCase(resource) ? basename(resource) : basename(resource).toLowerCase())); + const resourceExists = filtered.length >= 1; if (resourceExists) { - const confirm: IConfirmation = { - message: localize('confirmOverwrite', "A file or folder with the same name already exists in the destination folder. Do you want to replace it?"), - detail: localize('irreversible', "This action is irreversible!"), - primaryButton: localize({ key: 'replaceButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Replace"), - type: 'warning' - }; - - const confirmationResult = await this.dialogService.confirm(confirm); + const confirmationResult = await this.dialogService.confirm(fileOverwriteConfirm(basename(filtered[0]))); if (!confirmationResult.confirmed) { - return []; + return; } } @@ -693,7 +941,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop { } const copyTarget = joinPath(target.resource, basename(sourceFile)); - const stat = await this.fileService.copy(sourceFile, copyTarget, true); + const stat = await this.textFileService.copy(sourceFile, copyTarget, true); // if we only add one file, just open it directly if (resources.length === 1 && !stat.isDirectory) { this.editorService.openEditor({ resource: stat.resource, options: { pinned: true } }); @@ -705,8 +953,8 @@ export class FileDragAndDrop implements ITreeDragAndDrop { } } - private async handleExplorerDrop(data: IDragAndDropData, target: ExplorerItem, originalEvent: DragEvent): Promise { - const elementsData = (data as ElementsDragAndDropData).elements; + private async handleExplorerDrop(data: ElementsDragAndDropData, target: ExplorerItem, originalEvent: DragEvent): Promise { + const elementsData = FileDragAndDrop.getStatsFromDragAndDropData(data); const items = distinctParents(elementsData, s => s.resource); const isCopy = (originalEvent.ctrlKey && !isMacintosh) || (originalEvent.altKey && isMacintosh); @@ -715,9 +963,9 @@ export class FileDragAndDrop implements ITreeDragAndDrop { if (confirmDragAndDrop) { const confirmation = await this.dialogService.confirm({ message: items.length > 1 && items.every(s => s.isRoot) ? localize('confirmRootsMove', "Are you sure you want to change the order of multiple root folders in your workspace?") - : items.length > 1 ? getConfirmMessage(localize('confirmMultiMove', "Are you sure you want to move the following {0} files?", items.length), items.map(s => s.resource)) + : items.length > 1 ? getConfirmMessage(localize('confirmMultiMove', "Are you sure you want to move the following {0} files into '{1}'?", items.length, target.name), items.map(s => s.resource)) : items[0].isRoot ? localize('confirmRootMove', "Are you sure you want to change the order of root folder '{0}' in your workspace?", items[0].name) - : localize('confirmMove', "Are you sure you want to move '{0}'?", items[0].name), + : localize('confirmMove', "Are you sure you want to move '{0}' into '{1}'?", items[0].name, target.name), checkbox: { label: localize('doNotAskAgain', "Do not ask me again") }, @@ -776,7 +1024,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop { // Reuse duplicate action if user copies if (isCopy) { const incrementalNaming = this.configurationService.getValue().explorer.incrementalNaming; - const stat = await this.fileService.copy(source.resource, findValidPasteFileTarget(target, { resource: source.resource, isDirectory: source.isDirectory, allowOverwrite: false }, incrementalNaming)); + const stat = await this.textFileService.copy(source.resource, findValidPasteFileTarget(target, { resource: source.resource, isDirectory: source.isDirectory, allowOverwrite: false }, incrementalNaming)); if (!stat.isDirectory) { await this.editorService.openEditor({ resource: stat.resource, options: { pinned: true } }); } @@ -819,4 +1067,75 @@ export class FileDragAndDrop implements ITreeDragAndDrop { } } } + + private static getStatsFromDragAndDropData(data: ElementsDragAndDropData, dragStartEvent?: DragEvent): ExplorerItem[] { + if (data.context) { + return data.context; + } + + // Detect compressed folder dragging + if (dragStartEvent && data.elements.length === 1) { + data.context = [FileDragAndDrop.getCompressedStatFromDragEvent(data.elements[0], dragStartEvent)]; + return data.context; + } + + return data.elements; + } + + private static getCompressedStatFromDragEvent(stat: ExplorerItem, dragEvent: DragEvent): ExplorerItem { + const target = document.elementFromPoint(dragEvent.clientX, dragEvent.clientY); + const iconLabelName = getIconLabelNameFromHTMLElement(target); + + if (iconLabelName) { + const { count, index } = iconLabelName; + + let i = count - 1; + while (i > index && stat.parent) { + stat = stat.parent; + i--; + } + + return stat; + } + + return stat; + } + + onDragEnd(): void { + this.compressedDropTargetDisposable.dispose(); + } +} + +function getIconLabelNameFromHTMLElement(target: HTMLElement | EventTarget | Element | null): { element: HTMLElement, count: number, index: number } | null { + if (!(target instanceof HTMLElement)) { + return null; + } + + let element: HTMLElement | null = target; + + while (element && !DOM.hasClass(element, 'monaco-list-row')) { + if (DOM.hasClass(element, 'label-name') && element.hasAttribute('data-icon-label-count')) { + const count = Number(element.getAttribute('data-icon-label-count')); + const index = Number(element.getAttribute('data-icon-label-index')); + + if (isNumber(count) && isNumber(index)) { + return { element: element, count, index }; + } + } + + element = element.parentElement; + } + + return null; +} + +export function isCompressedFolderName(target: HTMLElement | EventTarget | Element | null): boolean { + return !!getIconLabelNameFromHTMLElement(target); +} + +export class ExplorerCompressionDelegate implements ITreeCompressionDelegate { + + isIncompressible(stat: ExplorerItem): boolean { + return stat.isRoot || !stat.isDirectory || stat instanceof NewExplorerItem; + } } diff --git a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts index 50bde81d733f..5146e1ed6eb7 100644 --- a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts +++ b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts @@ -15,8 +15,6 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IEditorInput, Verbosity } from 'vs/workbench/common/editor'; import { SaveAllAction, SaveAllInGroupAction, CloseGroupAction } from 'vs/workbench/contrib/files/browser/fileActions'; import { OpenEditorsFocusedContext, ExplorerFocusedContext, IFilesConfiguration, OpenEditor } from 'vs/workbench/contrib/files/common/files'; -import { ITextFileService, AutoSaveMode } from 'vs/workbench/services/textfile/common/textfiles'; -import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { CloseAllEditorsAction, CloseEditorAction } from 'vs/workbench/browser/parts/editor/editorActions'; import { ToggleEditorLayoutAction } from 'vs/workbench/browser/actions/layoutActions'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; @@ -32,20 +30,23 @@ import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IMenuService, MenuId, IMenu } from 'vs/platform/actions/common/actions'; -import { DirtyEditorContext, OpenEditorsGroupContext } from 'vs/workbench/contrib/files/browser/fileCommands'; +import { DirtyEditorContext, OpenEditorsGroupContext, ReadonlyEditorContext as ReadonlyEditorContext } from 'vs/workbench/contrib/files/browser/fileCommands'; import { ResourceContextKey } from 'vs/workbench/common/resources'; -import { ResourcesDropHandler, fillResourceDataTransfers, CodeDataTransfers } from 'vs/workbench/browser/dnd'; -import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet'; +import { ResourcesDropHandler, fillResourceDataTransfers, CodeDataTransfers, containsDragType } from 'vs/workbench/browser/dnd'; +import { ViewletPane, IViewletPaneOptions } from 'vs/workbench/browser/parts/views/paneViewlet'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { IDragAndDropData, DataTransfers } from 'vs/base/browser/dnd'; import { memoize } from 'vs/base/common/decorators'; import { ElementsDragAndDropData, DesktopDragAndDropData } from 'vs/base/browser/ui/list/listView'; import { URI } from 'vs/base/common/uri'; -import { withNullAsUndefined, withUndefinedAsNull } from 'vs/base/common/types'; +import { withUndefinedAsNull } from 'vs/base/common/types'; +import { isWeb } from 'vs/base/common/platform'; +import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; const $ = dom.$; -export class OpenEditorsView extends ViewletPanel { +export class OpenEditorsView extends ViewletPane { private static readonly DEFAULT_VISIBLE_OPEN_EDITORS = 9; static readonly ID = 'workbench.explorer.openEditorsView'; @@ -61,24 +62,24 @@ export class OpenEditorsView extends ViewletPanel { private resourceContext!: ResourceContextKey; private groupFocusedContext!: IContextKey; private dirtyEditorFocusedContext!: IContextKey; + private readonlyEditorFocusedContext!: IContextKey; constructor( options: IViewletViewOptions, @IInstantiationService private readonly instantiationService: IInstantiationService, @IContextMenuService contextMenuService: IContextMenuService, - @ITextFileService private readonly textFileService: ITextFileService, @IEditorService private readonly editorService: IEditorService, @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, @IConfigurationService configurationService: IConfigurationService, @IKeybindingService keybindingService: IKeybindingService, - @IUntitledEditorService private readonly untitledEditorService: IUntitledEditorService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IThemeService private readonly themeService: IThemeService, @ITelemetryService private readonly telemetryService: ITelemetryService, - @IMenuService private readonly menuService: IMenuService + @IMenuService private readonly menuService: IMenuService, + @IWorkingCopyService private readonly workingCopyService: IWorkingCopyService ) { super({ - ...(options as IViewletPanelOptions), + ...(options as IViewletPaneOptions), ariaHeaderLabel: nls.localize({ key: 'openEditosrSection', comment: ['Open is an adjective'] }, "Open Editors Section"), }, keybindingService, contextMenuService, configurationService, contextKeyService); @@ -99,11 +100,7 @@ export class OpenEditorsView extends ViewletPanel { this._register(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationChange(e))); // Handle dirty counter - this._register(this.untitledEditorService.onDidChangeDirty(() => this.updateDirtyIndicator())); - this._register(this.textFileService.models.onModelsDirty(() => this.updateDirtyIndicator())); - this._register(this.textFileService.models.onModelsSaved(() => this.updateDirtyIndicator())); - this._register(this.textFileService.models.onModelsSaveError(() => this.updateDirtyIndicator())); - this._register(this.textFileService.models.onModelsReverted(() => this.updateDirtyIndicator())); + this._register(this.workingCopyService.onDidChangeDirty(() => this.updateDirtyIndicator())); } private registerUpdateEvents(): void { @@ -218,7 +215,10 @@ export class OpenEditorsView extends ViewletPanel { new OpenEditorRenderer(this.listLabels, this.instantiationService, this.keybindingService, this.configurationService) ], { identityProvider: { getId: (element: OpenEditor | IEditorGroup) => element instanceof OpenEditor ? element.getId() : element.id.toString() }, - dnd: new OpenEditorsDragAndDrop(this.instantiationService, this.editorGroupService) + dnd: new OpenEditorsDragAndDrop(this.instantiationService, this.editorGroupService), + overrideStyles: { + listBackground: SIDE_BAR_BACKGROUND + } }); this._register(this.list); this._register(this.listLabels); @@ -236,16 +236,19 @@ export class OpenEditorsView extends ViewletPanel { this._register(this.resourceContext); this.groupFocusedContext = OpenEditorsGroupContext.bindTo(this.contextKeyService); this.dirtyEditorFocusedContext = DirtyEditorContext.bindTo(this.contextKeyService); + this.readonlyEditorFocusedContext = ReadonlyEditorContext.bindTo(this.contextKeyService); this._register(this.list.onContextMenu(e => this.onListContextMenu(e))); this.list.onFocusChange(e => { this.resourceContext.reset(); this.groupFocusedContext.reset(); this.dirtyEditorFocusedContext.reset(); + this.readonlyEditorFocusedContext.reset(); const element = e.elements.length ? e.elements[0] : undefined; if (element instanceof OpenEditor) { const resource = element.getResource(); - this.dirtyEditorFocusedContext.set(this.textFileService.isDirty(resource)); + this.dirtyEditorFocusedContext.set(element.editor.isDirty()); + this.readonlyEditorFocusedContext.set(element.editor.isReadonly()); this.resourceContext.set(withUndefinedAsNull(resource)); } else if (!!element) { this.groupFocusedContext.set(true); @@ -412,8 +415,7 @@ export class OpenEditorsView extends ViewletPanel { } private updateDirtyIndicator(): void { - let dirty = this.textFileService.getAutoSaveMode() !== AutoSaveMode.AFTER_SHORT_DELAY ? this.textFileService.getDirty().length - : this.untitledEditorService.getDirty().length; + let dirty = this.workingCopyService.dirtyCount; if (dirty === 0) { dom.addClass(this.dirtyCountElement, 'hidden'); } else { @@ -563,7 +565,6 @@ class OpenEditorRenderer implements IListRenderer 1) { return String(elements.length); } const element = elements[0]; - return element instanceof OpenEditor ? withNullAsUndefined(element.editor.getName()) : element.label; + return element instanceof OpenEditor ? element.editor.getName() : element.label; } onDragStart(data: IDragAndDropData, originalEvent: DragEvent): void { @@ -644,16 +645,12 @@ class OpenEditorsDragAndDrop implements IListDragAndDrop 0; + } + + constructor( + @ILifecycleService private readonly lifecycleService: ILifecycleService, + @IActivityService private readonly activityService: IActivityService, + @IWorkingCopyService private readonly workingCopyService: IWorkingCopyService, + @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService + ) { + super(); + + this.registerListeners(); + } + + private registerListeners(): void { + + // Working copy dirty indicator + this._register(this.workingCopyService.onDidChangeDirty(c => this.onWorkingCopyDidChangeDirty(c))); + + // Lifecycle + this.lifecycleService.onShutdown(this.dispose, this); + } + + private onWorkingCopyDidChangeDirty(workingCopy: IWorkingCopy): void { + const gotDirty = workingCopy.isDirty(); + if (gotDirty && !!(workingCopy.capabilities & WorkingCopyCapabilities.AutoSave) && this.filesConfigurationService.getAutoSaveMode() === AutoSaveMode.AFTER_SHORT_DELAY) { + return; // do not indicate dirty of working copies that are auto saved after short delay + } + + if (gotDirty || this.hasDirtyCount) { + this.updateActivityBadge(); + } + } + + private updateActivityBadge(): void { + const dirtyCount = this.workingCopyService.dirtyCount; + this.lastKnownDirtyCount = dirtyCount; + + // Indicate dirty count in badge if any + if (dirtyCount > 0) { + this.badgeHandle.value = this.activityService.showActivity( + VIEWLET_ID, + new NumberBadge(dirtyCount, num => num === 1 ? nls.localize('dirtyFile', "1 unsaved file") : nls.localize('dirtyFiles', "{0} unsaved files", dirtyCount)), + 'explorer-viewlet-label' + ); + } else { + this.badgeHandle.clear(); + } + } +} diff --git a/src/vs/workbench/contrib/files/common/dirtyFilesTracker.ts b/src/vs/workbench/contrib/files/common/dirtyFilesTracker.ts deleted file mode 100644 index 43bc61cbf2fe..000000000000 --- a/src/vs/workbench/contrib/files/common/dirtyFilesTracker.ts +++ /dev/null @@ -1,113 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as nls from 'vs/nls'; -import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { VIEWLET_ID } from 'vs/workbench/contrib/files/common/files'; -import { TextFileModelChangeEvent, ITextFileService, AutoSaveMode, ModelState } from 'vs/workbench/services/textfile/common/textfiles'; -import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; -import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; -import { URI } from 'vs/base/common/uri'; -import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity'; -import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; -import * as arrays from 'vs/base/common/arrays'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; - -export class DirtyFilesTracker extends Disposable implements IWorkbenchContribution { - private lastKnownDirtyCount: number | undefined; - private readonly badgeHandle = this._register(new MutableDisposable()); - - constructor( - @ITextFileService protected readonly textFileService: ITextFileService, - @ILifecycleService private readonly lifecycleService: ILifecycleService, - @IEditorService private readonly editorService: IEditorService, - @IActivityService private readonly activityService: IActivityService, - @IUntitledEditorService protected readonly untitledEditorService: IUntitledEditorService - ) { - super(); - - this.registerListeners(); - } - - private registerListeners(): void { - - // Local text file changes - this._register(this.untitledEditorService.onDidChangeDirty(e => this.onUntitledDidChangeDirty(e))); - this._register(this.textFileService.models.onModelsDirty(e => this.onTextFilesDirty(e))); - this._register(this.textFileService.models.onModelsSaved(e => this.onTextFilesSaved(e))); - this._register(this.textFileService.models.onModelsSaveError(e => this.onTextFilesSaveError(e))); - this._register(this.textFileService.models.onModelsReverted(e => this.onTextFilesReverted(e))); - - // Lifecycle - this.lifecycleService.onShutdown(this.dispose, this); - } - - private get hasDirtyCount(): boolean { - return typeof this.lastKnownDirtyCount === 'number' && this.lastKnownDirtyCount > 0; - } - - protected onUntitledDidChangeDirty(resource: URI): void { - const gotDirty = this.untitledEditorService.isDirty(resource); - - if (gotDirty || this.hasDirtyCount) { - this.updateActivityBadge(); - } - } - - protected onTextFilesDirty(e: readonly TextFileModelChangeEvent[]): void { - if (this.textFileService.getAutoSaveMode() !== AutoSaveMode.AFTER_SHORT_DELAY) { - this.updateActivityBadge(); // no indication needed when auto save is enabled for short delay - } - - // If files become dirty but are not opened, we open it in the background unless there are pending to be saved - this.doOpenDirtyResources(arrays.distinct(e.filter(e => { - - // Only dirty models that are not PENDING_SAVE - const model = this.textFileService.models.get(e.resource); - const shouldOpen = model?.isDirty() && !model.hasState(ModelState.PENDING_SAVE); - - // Only if not open already - return shouldOpen && !this.editorService.isOpen({ resource: e.resource }); - }).map(e => e.resource), r => r.toString())); - } - - private doOpenDirtyResources(resources: URI[]): void { - - // Open - this.editorService.openEditors(resources.map(resource => { - return { - resource, - options: { inactive: true, pinned: true, preserveFocus: true } - }; - })); - } - - protected onTextFilesSaved(e: readonly TextFileModelChangeEvent[]): void { - if (this.hasDirtyCount) { - this.updateActivityBadge(); - } - } - - protected onTextFilesSaveError(e: readonly TextFileModelChangeEvent[]): void { - this.updateActivityBadge(); - } - - protected onTextFilesReverted(e: readonly TextFileModelChangeEvent[]): void { - if (this.hasDirtyCount) { - this.updateActivityBadge(); - } - } - - private updateActivityBadge(): void { - const dirtyCount = this.textFileService.getDirty().length; - this.lastKnownDirtyCount = dirtyCount; - - this.badgeHandle.clear(); - - if (dirtyCount > 0) { - this.badgeHandle.value = this.activityService.showActivity(VIEWLET_ID, new NumberBadge(dirtyCount, num => num === 1 ? nls.localize('dirtyFile', "1 unsaved file") : nls.localize('dirtyFiles', "{0} unsaved files", dirtyCount)), 'explorer-viewlet-label'); - } - } -} diff --git a/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts b/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts index 915d5e586d0b..f1a0ea64462b 100644 --- a/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts +++ b/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts @@ -7,16 +7,19 @@ import { localize } from 'vs/nls'; import { createMemoizer } from 'vs/base/common/decorators'; import { dirname } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; -import { EncodingMode, ConfirmResult, EditorInput, IFileEditorInput, ITextEditorModel, Verbosity, IRevertOptions } from 'vs/workbench/common/editor'; +import { EncodingMode, IFileEditorInput, ITextEditorModel, Verbosity, TextEditorInput, IRevertOptions } from 'vs/workbench/common/editor'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; import { BinaryEditorModel } from 'vs/workbench/common/editor/binaryEditorModel'; -import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files'; -import { ITextFileService, AutoSaveMode, ModelState, TextFileModelChangeEvent, LoadReason, TextFileOperationError, TextFileOperationResult } from 'vs/workbench/services/textfile/common/textfiles'; +import { FileOperationError, FileOperationResult, IFileService, FileSystemProviderCapabilities } from 'vs/platform/files/common/files'; +import { ITextFileService, ModelState, TextFileModelChangeEvent, LoadReason, TextFileOperationError, TextFileOperationResult } from 'vs/workbench/services/textfile/common/textfiles'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IReference } from 'vs/base/common/lifecycle'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { FILE_EDITOR_INPUT_ID, TEXT_FILE_EDITOR_ID, BINARY_FILE_EDITOR_ID } from 'vs/workbench/contrib/files/common/files'; import { ILabelService } from 'vs/platform/label/common/label'; +import { IFilesConfigurationService, AutoSaveMode } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; const enum ForceOpenAs { None, @@ -27,7 +30,7 @@ const enum ForceOpenAs { /** * A file editor input is the input type for the file editor of file system resources. */ -export class FileEditorInput extends EditorInput implements IFileEditorInput { +export class FileEditorInput extends TextEditorInput implements IFileEditorInput { private static readonly MEMOIZER = createMemoizer(); @@ -38,20 +41,20 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput { private textModelReference: Promise> | null = null; - /** - * An editor input who's contents are retrieved from file services. - */ constructor( - private resource: URI, + resource: URI, preferredEncoding: string | undefined, preferredMode: string | undefined, @IInstantiationService private readonly instantiationService: IInstantiationService, - @ITextFileService private readonly textFileService: ITextFileService, + @ITextFileService textFileService: ITextFileService, @ITextModelService private readonly textModelResolverService: ITextModelService, @ILabelService private readonly labelService: ILabelService, - @IFileService private readonly fileService: IFileService + @IFileService private readonly fileService: IFileService, + @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService, + @IEditorService editorService: IEditorService, + @IEditorGroupsService editorGroupService: IEditorGroupsService ) { - super(); + super(resource, editorService, editorGroupService, textFileService); if (preferredEncoding) { this.setPreferredEncoding(preferredEncoding); @@ -89,10 +92,6 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput { } } - getResource(): URI { - return this.resource; - } - getEncoding(): string | undefined { const textModel = this.textFileService.models.get(this.resource); if (textModel) { @@ -217,13 +216,19 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput { return localize('orphanedFile', "{0} (deleted)", label); } - if (model?.isReadonly()) { + if (this.isReadonly()) { return localize('readonlyFile', "{0} (read-only)", label); } return label; } + isReadonly(): boolean { + const model = this.textFileService.models.get(this.resource); + + return model?.isReadonly() || this.fileService.hasCapability(this.resource, FileSystemProviderCapabilities.Readonly); + } + isDirty(): boolean { const model = this.textFileService.models.get(this.resource); if (!model) { @@ -234,21 +239,13 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput { return true; // always indicate dirty state if we are in conflict or error state } - if (this.textFileService.getAutoSaveMode() === AutoSaveMode.AFTER_SHORT_DELAY) { + if (this.filesConfigurationService.getAutoSaveMode() === AutoSaveMode.AFTER_SHORT_DELAY) { return false; // fast auto save enabled so we do not declare dirty } return model.isDirty(); } - confirmSave(): Promise { - return this.textFileService.confirmSave([this.resource]); - } - - save(): Promise { - return this.textFileService.save(this.resource); - } - revert(options?: IRevertOptions): Promise { return this.textFileService.revert(this.resource, options); } diff --git a/src/vs/workbench/contrib/files/common/explorerModel.ts b/src/vs/workbench/contrib/files/common/explorerModel.ts index d3b9d9834df1..d1a400c5bfff 100644 --- a/src/vs/workbench/contrib/files/common/explorerModel.ts +++ b/src/vs/workbench/contrib/files/common/explorerModel.ts @@ -8,7 +8,7 @@ import { isEqual } from 'vs/base/common/extpath'; import { posix } from 'vs/base/common/path'; import * as resources from 'vs/base/common/resources'; import { ResourceMap } from 'vs/base/common/map'; -import { IFileStat, IFileService } from 'vs/platform/files/common/files'; +import { IFileStat, IFileService, FileSystemProviderCapabilities } from 'vs/platform/files/common/files'; import { rtrim, startsWithIgnoreCase, startsWith, equalsIgnoreCase } from 'vs/base/common/strings'; import { coalesce } from 'vs/base/common/arrays'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; @@ -75,6 +75,7 @@ export class ExplorerModel implements IDisposable { export class ExplorerItem { private _isDirectoryResolved: boolean; + private _isDisposed: boolean; public isError = false; constructor( @@ -87,6 +88,11 @@ export class ExplorerItem { private _mtime?: number, ) { this._isDirectoryResolved = false; + this._isDisposed = false; + } + + get isDisposed(): boolean { + return this._isDisposed; } get isDirectoryResolved(): boolean { @@ -148,8 +154,8 @@ export class ExplorerItem { return this === this.root; } - static create(raw: IFileStat, parent: ExplorerItem | undefined, resolveTo?: readonly URI[]): ExplorerItem { - const stat = new ExplorerItem(raw.resource, parent, raw.isDirectory, raw.isSymbolicLink, raw.isReadonly, raw.name, raw.mtime); + static create(service: IFileService, raw: IFileStat, parent: ExplorerItem | undefined, resolveTo?: readonly URI[]): ExplorerItem { + const stat = new ExplorerItem(raw.resource, parent, raw.isDirectory, raw.isSymbolicLink, service.hasCapability(raw.resource, FileSystemProviderCapabilities.Readonly), raw.name, raw.mtime); // Recursively add children if present if (stat.isDirectory) { @@ -164,7 +170,7 @@ export class ExplorerItem { // Recurse into children if (raw.children) { for (let i = 0, len = raw.children.length; i < len; i++) { - const child = ExplorerItem.create(raw.children[i], stat, resolveTo); + const child = ExplorerItem.create(service, raw.children[i], stat, resolveTo); stat.addChild(child); } } @@ -218,6 +224,7 @@ export class ExplorerItem { if (formerLocalChild) { ExplorerItem.mergeLocalWithDisk(diskChild, formerLocalChild); local.addChild(formerLocalChild); + oldLocalChildren.delete(diskChild.resource); } // New child: add @@ -225,6 +232,10 @@ export class ExplorerItem { local.addChild(diskChild); } }); + + for (let child of oldLocalChildren.values()) { + child._dispose(); + } } } @@ -249,7 +260,7 @@ export class ExplorerItem { const resolveMetadata = explorerService.sortOrder === 'modified'; try { const stat = await fileService.resolve(this.resource, { resolveSingleChildDescendants: true, resolveMetadata }); - const resolved = ExplorerItem.create(stat, this); + const resolved = ExplorerItem.create(fileService, stat, this); ExplorerItem.mergeLocalWithDisk(resolved, this); } catch (e) { this.isError = true; @@ -274,10 +285,20 @@ export class ExplorerItem { } forgetChildren(): void { + for (let c of this.children.values()) { + c._dispose(); + } this.children.clear(); this._isDirectoryResolved = false; } + private _dispose() { + this._isDisposed = true; + for (let child of this.children.values()) { + child._dispose(); + } + } + private getPlatformAwareName(name: string): string { return (!name || !resources.hasToIgnoreCase(this.resource)) ? name : name.toLowerCase(); } diff --git a/src/vs/workbench/contrib/files/common/explorerService.ts b/src/vs/workbench/contrib/files/common/explorerService.ts index 09bf8a88c274..fe157c809e14 100644 --- a/src/vs/workbench/contrib/files/common/explorerService.ts +++ b/src/vs/workbench/contrib/files/common/explorerService.ts @@ -6,7 +6,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { IExplorerService, IEditableData, IFilesConfiguration, SortOrder, SortOrderConfiguration } from 'vs/workbench/contrib/files/common/files'; +import { IExplorerService, IFilesConfiguration, SortOrder, SortOrderConfiguration, IContextProvider } from 'vs/workbench/contrib/files/common/files'; import { ExplorerItem, ExplorerModel } from 'vs/workbench/contrib/files/common/explorerModel'; import { URI } from 'vs/base/common/uri'; import { FileOperationEvent, FileOperation, IFileStat, IFileService, FileChangesEvent, FILES_EXCLUDE_CONFIG, FileChangeType, IResolveFileOptions } from 'vs/platform/files/common/files'; @@ -18,6 +18,7 @@ import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/co import { IExpression } from 'vs/base/common/glob'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IEditableData } from 'vs/workbench/common/views'; function getFileEventsExcludes(configurationService: IConfigurationService, root?: URI): IExpression { const scope = root ? { resource: root } : undefined; @@ -41,6 +42,7 @@ export class ExplorerService implements IExplorerService { private _sortOrder: SortOrder; private cutItems: ExplorerItem[] | undefined; private fileSystemProviderSchemes = new Set(); + private contextProvider: IContextProvider | undefined; constructor( @IFileService private fileService: IFileService, @@ -48,7 +50,7 @@ export class ExplorerService implements IExplorerService { @IConfigurationService private configurationService: IConfigurationService, @IWorkspaceContextService private contextService: IWorkspaceContextService, @IClipboardService private clipboardService: IClipboardService, - @IEditorService private editorService: IEditorService + @IEditorService private editorService: IEditorService, ) { this._sortOrder = this.configurationService.getValue('explorer.sortOrder'); } @@ -81,6 +83,18 @@ export class ExplorerService implements IExplorerService { return this._sortOrder; } + registerContextProvider(contextProvider: IContextProvider): void { + this.contextProvider = contextProvider; + } + + getContext(respectMultiSelection: boolean): ExplorerItem[] { + if (!this.contextProvider) { + return []; + } + + return this.contextProvider.getContext(respectMultiSelection); + } + // Memoized locals @memoize private get fileEventsFilter(): ResourceGlobMatcher { const fileEventsFilter = this.instantiationService.createInstance( @@ -173,7 +187,7 @@ export class ExplorerService implements IExplorerService { const stat = await this.fileService.resolve(rootUri, options); // Convert to model - const modelStat = ExplorerItem.create(stat, undefined, options.resolveTo); + const modelStat = ExplorerItem.create(this.fileService, stat, undefined, options.resolveTo); // Update Input with disk Stat ExplorerItem.mergeLocalWithDisk(modelStat, root); const item = root.find(resource); @@ -217,11 +231,11 @@ export class ExplorerService implements IExplorerService { const thenable: Promise = p.isDirectoryResolved ? Promise.resolve(undefined) : this.fileService.resolve(p.resource, { resolveMetadata }); thenable.then(stat => { if (stat) { - const modelStat = ExplorerItem.create(stat, p.parent); + const modelStat = ExplorerItem.create(this.fileService, stat, p.parent); ExplorerItem.mergeLocalWithDisk(modelStat, p); } - const childElement = ExplorerItem.create(addedElement, p.parent); + const childElement = ExplorerItem.create(this.fileService, addedElement, p.parent); // Make sure to remove any previous version of the file if any p.removeChild(childElement); p.addChild(childElement); diff --git a/src/vs/workbench/contrib/files/common/files.ts b/src/vs/workbench/contrib/files/common/files.ts index 2db4997e4fae..71a53e381e03 100644 --- a/src/vs/workbench/contrib/files/common/files.ts +++ b/src/vs/workbench/contrib/files/common/files.ts @@ -17,8 +17,7 @@ import { IModeService, ILanguageSelection } from 'vs/editor/common/services/mode import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkeys'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainer } from 'vs/workbench/common/views'; -import { Schemas } from 'vs/base/common/network'; +import { IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainer, IEditableData } from 'vs/workbench/common/views'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel'; @@ -30,16 +29,12 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic * Explorer viewlet id. */ export const VIEWLET_ID = 'workbench.view.explorer'; + /** * Explorer viewlet container. */ export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(VIEWLET_ID); -export interface IEditableData { - validationMessage: (value: string) => string | null; - onFinish: (value: string, success: boolean) => void; -} - export interface IExplorerService { _serviceBrand: undefined; readonly roots: ExplorerItem[]; @@ -50,6 +45,7 @@ export interface IExplorerService { readonly onDidSelectResource: Event<{ resource?: URI, reveal?: boolean }>; readonly onDidCopyItems: Event<{ items: ExplorerItem[], cut: boolean, previouslyCutItems: ExplorerItem[] | undefined }>; + getContext(respectMultiSelection: boolean): ExplorerItem[]; setEditable(stat: ExplorerItem, data: IEditableData | null): void; getEditable(): { stat: ExplorerItem, data: IEditableData } | undefined; getEditableData(stat: ExplorerItem): IEditableData | undefined; @@ -65,7 +61,14 @@ export interface IExplorerService { * Will try to resolve the path in case the explorer is not yet expanded to the file yet. */ select(resource: URI, reveal?: boolean): Promise; + + registerContextProvider(contextProvider: IContextProvider): void; +} + +export interface IContextProvider { + getContext(respectMultiSelection: boolean): ExplorerItem[]; } + export const IExplorerService = createDecorator('explorerService'); /** @@ -83,6 +86,11 @@ export const OpenEditorsVisibleContext = new RawContextKey('openEditors export const OpenEditorsFocusedContext = new RawContextKey('openEditorsFocus', true); export const ExplorerFocusedContext = new RawContextKey('explorerViewletFocus', true); +// compressed nodes +export const ExplorerCompressedFocusContext = new RawContextKey('explorerViewletCompressedFocus', true); +export const ExplorerCompressedFirstFocusContext = new RawContextKey('explorerViewletCompressedFirstFocus', true); +export const ExplorerCompressedLastFocusContext = new RawContextKey('explorerViewletCompressedLastFocus', true); + export const FilesExplorerFocusCondition = ContextKeyExpr.and(ExplorerViewletVisibleContext, FilesExplorerFocusedContext, ContextKeyExpr.not(InputFocusedContextKey)); export const ExplorerFocusCondition = ContextKeyExpr.and(ExplorerViewletVisibleContext, ExplorerFocusedContext, ContextKeyExpr.not(InputFocusedContextKey)); @@ -101,7 +109,6 @@ export const FILE_EDITOR_INPUT_ID = 'workbench.editors.files.fileEditorInput'; */ export const BINARY_FILE_EDITOR_ID = 'workbench.editors.files.binaryFileEditor'; - export interface IFilesConfiguration extends PlatformIFilesConfiguration, IWorkbenchEditorConfiguration { explorer: { openEditors: { @@ -219,39 +226,35 @@ export class OpenEditor implements IEditorIdentifier { // noop } - public get editor() { + get editor() { return this._editor; } - public get editorIndex() { + get editorIndex() { return this._group.getIndexOfEditor(this.editor); } - public get group() { + get group() { return this._group; } - public get groupId() { + get groupId() { return this._group.id; } - public getId(): string { + getId(): string { return `openeditor:${this.groupId}:${this.editorIndex}:${this.editor.getName()}:${this.editor.getDescription()}`; } - public isPreview(): boolean { + isPreview(): boolean { return this._group.previewEditor === this.editor; } - public isUntitled(): boolean { - return !!toResource(this.editor, { supportSideBySide: SideBySideEditor.MASTER, filterByScheme: Schemas.untitled }); - } - - public isDirty(): boolean { + isDirty(): boolean { return this.editor.isDirty(); } - public getResource(): URI | undefined { + getResource(): URI | undefined { return toResource(this.editor, { supportSideBySide: SideBySideEditor.MASTER }); } } diff --git a/src/vs/workbench/contrib/files/electron-browser/dirtyFilesTracker.ts b/src/vs/workbench/contrib/files/electron-browser/dirtyFilesTracker.ts deleted file mode 100644 index ae3584084997..000000000000 --- a/src/vs/workbench/contrib/files/electron-browser/dirtyFilesTracker.ts +++ /dev/null @@ -1,81 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { TextFileModelChangeEvent, ITextFileService, AutoSaveMode } from 'vs/workbench/services/textfile/common/textfiles'; -import { platform, Platform } from 'vs/base/common/platform'; -import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; -import { URI } from 'vs/base/common/uri'; -import { IActivityService } from 'vs/workbench/services/activity/common/activity'; -import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { DirtyFilesTracker } from 'vs/workbench/contrib/files/common/dirtyFilesTracker'; -import { IElectronService } from 'vs/platform/electron/node/electron'; - -export class NativeDirtyFilesTracker extends DirtyFilesTracker { - private isDocumentedEdited: boolean; - - constructor( - @ITextFileService protected readonly textFileService: ITextFileService, - @ILifecycleService lifecycleService: ILifecycleService, - @IEditorService editorService: IEditorService, - @IActivityService activityService: IActivityService, - @IUntitledEditorService protected readonly untitledEditorService: IUntitledEditorService, - @IElectronService private readonly electronService: IElectronService - ) { - super(textFileService, lifecycleService, editorService, activityService, untitledEditorService); - - this.isDocumentedEdited = false; - } - - protected onUntitledDidChangeDirty(resource: URI): void { - const gotDirty = this.untitledEditorService.isDirty(resource); - if ((!this.isDocumentedEdited && gotDirty) || (this.isDocumentedEdited && !gotDirty)) { - this.updateDocumentEdited(); - } - - super.onUntitledDidChangeDirty(resource); - } - - protected onTextFilesDirty(e: readonly TextFileModelChangeEvent[]): void { - if ((this.textFileService.getAutoSaveMode() !== AutoSaveMode.AFTER_SHORT_DELAY) && !this.isDocumentedEdited) { - this.updateDocumentEdited(); // no indication needed when auto save is enabled for short delay - } - - super.onTextFilesDirty(e); - } - - protected onTextFilesSaved(e: readonly TextFileModelChangeEvent[]): void { - if (this.isDocumentedEdited) { - this.updateDocumentEdited(); - } - - super.onTextFilesSaved(e); - } - - protected onTextFilesSaveError(e: readonly TextFileModelChangeEvent[]): void { - if (!this.isDocumentedEdited) { - this.updateDocumentEdited(); - } - - super.onTextFilesSaveError(e); - } - - protected onTextFilesReverted(e: readonly TextFileModelChangeEvent[]): void { - if (this.isDocumentedEdited) { - this.updateDocumentEdited(); - } - - super.onTextFilesReverted(e); - } - - private updateDocumentEdited(): void { - if (platform === Platform.Mac) { - const hasDirtyFiles = this.textFileService.isDirty(); - this.isDocumentedEdited = hasDirtyFiles; - - this.electronService.setDocumentEdited(hasDirtyFiles); - } - } -} diff --git a/src/vs/workbench/contrib/files/electron-browser/fileActions.contribution.ts b/src/vs/workbench/contrib/files/electron-browser/fileActions.contribution.ts index 2008e6ad2ead..70f22266a0a2 100644 --- a/src/vs/workbench/contrib/files/electron-browser/fileActions.contribution.ts +++ b/src/vs/workbench/contrib/files/electron-browser/fileActions.contribution.ts @@ -86,4 +86,4 @@ const category = { value: nls.localize('filesCategory', "File"), original: 'File appendToCommandPalette(REVEAL_IN_OS_COMMAND_ID, { value: REVEAL_IN_OS_LABEL, original: isWindows ? 'Reveal in Explorer' : isMacintosh ? 'Reveal in Finder' : 'Open Containing Folder' }, category); const registry = Registry.as(ActionExtensions.WorkbenchActions); -registry.registerWorkbenchAction(new SyncActionDescriptor(ShowOpenedFileInNewWindow, ShowOpenedFileInNewWindow.ID, ShowOpenedFileInNewWindow.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_O) }), 'File: Open Active File in New Window', category.value); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ShowOpenedFileInNewWindow, ShowOpenedFileInNewWindow.ID, ShowOpenedFileInNewWindow.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_O) }), 'File: Open Active File in New Window', category.value); diff --git a/src/vs/workbench/contrib/files/electron-browser/files.contribution.ts b/src/vs/workbench/contrib/files/electron-browser/files.contribution.ts index 2772a704edf6..f00acc468f6b 100644 --- a/src/vs/workbench/contrib/files/electron-browser/files.contribution.ts +++ b/src/vs/workbench/contrib/files/electron-browser/files.contribution.ts @@ -10,13 +10,10 @@ import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileE import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IEditorRegistry, EditorDescriptor, Extensions as EditorExtensions } from 'vs/workbench/browser/editor'; import { NativeTextFileEditor } from 'vs/workbench/contrib/files/electron-browser/textFileEditor'; -import { NativeDirtyFilesTracker } from 'vs/workbench/contrib/files/electron-browser/dirtyFilesTracker'; -import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; -import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; // Register file editor Registry.as(EditorExtensions.Editors).registerEditor( - new EditorDescriptor( + EditorDescriptor.create( NativeTextFileEditor, NativeTextFileEditor.ID, nls.localize('textFileEditor', "Text File Editor") @@ -25,6 +22,3 @@ Registry.as(EditorExtensions.Editors).registerEditor( new SyncDescriptor(FileEditorInput) ] ); - -// Register Dirty Files Tracker -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(NativeDirtyFilesTracker, LifecyclePhase.Starting); diff --git a/src/vs/workbench/contrib/files/electron-browser/textFileEditor.ts b/src/vs/workbench/contrib/files/electron-browser/textFileEditor.ts index 7c19777ef1d5..ece48b108f52 100644 --- a/src/vs/workbench/contrib/files/electron-browser/textFileEditor.ts +++ b/src/vs/workbench/contrib/files/electron-browser/textFileEditor.ts @@ -10,7 +10,6 @@ import { EditorOptions } from 'vs/workbench/common/editor'; import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files'; import { MIN_MAX_MEMORY_SIZE_MB, FALLBACK_MAX_MEMORY_SIZE_MB } from 'vs/platform/files/node/files'; import { createErrorWithActions } from 'vs/base/common/errorsWithActions'; -import { toErrorMessage } from 'vs/base/common/errorMessage'; import { Action } from 'vs/base/common/actions'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; @@ -22,7 +21,6 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { IExplorerService } from 'vs/workbench/contrib/files/common/files'; import { IElectronService } from 'vs/platform/electron/node/electron'; @@ -46,19 +44,18 @@ export class NativeTextFileEditor extends TextFileEditor { @ITextFileService textFileService: ITextFileService, @IElectronService private readonly electronService: IElectronService, @IPreferencesService private readonly preferencesService: IPreferencesService, - @IHostService hostService: IHostService, @IExplorerService explorerService: IExplorerService ) { - super(telemetryService, fileService, viewletService, instantiationService, contextService, storageService, configurationService, editorService, themeService, editorGroupService, textFileService, hostService, explorerService); + super(telemetryService, fileService, viewletService, instantiationService, contextService, storageService, configurationService, editorService, themeService, editorGroupService, textFileService, explorerService); } protected handleSetInputError(error: Error, input: FileEditorInput, options: EditorOptions | undefined): void { // Allow to restart with higher memory limit if the file is too large - if ((error).fileOperationResult === FileOperationResult.FILE_EXCEED_MEMORY_LIMIT) { + if ((error).fileOperationResult === FileOperationResult.FILE_EXCEEDS_MEMORY_LIMIT) { const memoryLimit = Math.max(MIN_MAX_MEMORY_SIZE_MB, +this.configurationService.getValue(undefined, 'files.maxMemoryForLargeFilesMB') || FALLBACK_MAX_MEMORY_SIZE_MB); - throw createErrorWithActions(toErrorMessage(error), { + throw createErrorWithActions(nls.localize('fileTooLargeForHeapError', "To open a file of this size, you need to restart and allow it to use more memory"), { actions: [ new Action('workbench.window.action.relaunchWithIncreasedMemoryLimit', nls.localize('relaunchWithIncreasedMemoryLimit', "Restart with {0} MB", memoryLimit), undefined, true, () => { return this.electronService.relaunch({ 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 ea9ba2e522ec..a57d9e6d9aac 100644 --- a/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts @@ -141,7 +141,7 @@ suite('Files - FileEditorInput', () => { resolved.textEditorModel!.setValue('changed'); assert.ok(input.isDirty()); - await input.save(); + await input.save(0); assert.ok(!input.isDirty()); resolved.dispose(); }); @@ -153,8 +153,12 @@ suite('Files - FileEditorInput', () => { resolved.textEditorModel!.setValue('changed'); assert.ok(input.isDirty()); - await input.revert(); + assert.ok(await input.revert()); assert.ok(!input.isDirty()); + + input.dispose(); + assert.ok(input.isDisposed()); + resolved.dispose(); }); diff --git a/src/vs/workbench/contrib/format/browser/formatActionsMultiple.ts b/src/vs/workbench/contrib/format/browser/formatActionsMultiple.ts index eca779ecbf49..43ee61266b78 100644 --- a/src/vs/workbench/contrib/format/browser/formatActionsMultiple.ts +++ b/src/vs/workbench/contrib/format/browser/formatActionsMultiple.ts @@ -28,6 +28,7 @@ import { INotificationService, Severity } from 'vs/platform/notification/common/ import { IModeService } from 'vs/editor/common/services/modeService'; import { ILabelService } from 'vs/platform/label/common/label'; import { IExtensionEnablementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; +import { editorConfigurationBaseNode } from 'vs/editor/common/config/commonEditorConfig'; type FormattingEditProvider = DocumentFormattingEditProvider | DocumentRangeFormattingEditProvider; @@ -106,7 +107,7 @@ class DefaultFormatter extends Disposable implements IWorkbenchContribution { const langName = this._modeService.getLanguageName(document.getModeId()) || document.getModeId(); const silent = mode === FormattingMode.Silent; const message = !defaultFormatterId - ? nls.localize('config.needed', "There are multiple formatters for {0}-files. Select a default formatter to continue.", DefaultFormatter._maybeQuotes(langName)) + ? nls.localize('config.needed', "There are multiple formatters for '{0}' files. Select a default formatter to continue.", DefaultFormatter._maybeQuotes(langName)) : nls.localize('config.bad', "Extension '{0}' is configured as formatter but not available. Select a different default formatter to continue.", defaultFormatterId); return new Promise((resolve, reject) => { @@ -134,7 +135,7 @@ class DefaultFormatter extends Disposable implements IWorkbenchContribution { }; }); const langName = this._modeService.getLanguageName(document.getModeId()) || document.getModeId(); - const pick = await this._quickInputService.pick(picks, { placeHolder: nls.localize('select', "Select a default formatter for {0}-files", DefaultFormatter._maybeQuotes(langName)) }); + const pick = await this._quickInputService.pick(picks, { placeHolder: nls.localize('select', "Select a default formatter for '{0}' files", DefaultFormatter._maybeQuotes(langName)) }); if (!pick || !formatter[pick.index].extensionId) { return undefined; } @@ -152,10 +153,7 @@ Registry.as(WorkbenchExtensions.Workbench).regi ); Registry.as(ConfigurationExtensions.Configuration).registerConfiguration({ - id: 'editor', - order: 5, - type: 'object', - overridable: true, + ...editorConfigurationBaseNode, properties: { [DefaultFormatter.configName]: { description: nls.localize('formatter.default', "Defines a default formatter which takes precedence over all other formatter settings. Must be the identifier of an extension contributing a formatter."), @@ -234,7 +232,7 @@ async function showFormatterPick(accessor: ServicesAccessor, model: ITextModel, } else if (pick === configurePick) { // config default const langName = modeService.getLanguageName(model.getModeId()) || model.getModeId(); - const pick = await quickPickService.pick(picks, { placeHolder: nls.localize('select', "Select a default formatter for {0}-files", DefaultFormatter._maybeQuotes(langName)) }); + const pick = await quickPickService.pick(picks, { placeHolder: nls.localize('select', "Select a default formatter for '{0}' files", DefaultFormatter._maybeQuotes(langName)) }); if (pick && formatters[pick.index].extensionId) { configService.updateValue(DefaultFormatter.configName, formatters[pick.index].extensionId!.value, overrides); } @@ -255,7 +253,7 @@ registerEditorAction(class FormatDocumentMultipleAction extends EditorAction { label: nls.localize('formatDocument.label.multiple', "Format Document With..."), alias: 'Format Document...', precondition: ContextKeyExpr.and(EditorContextKeys.writable, EditorContextKeys.hasMultipleDocumentFormattingProvider), - menuOpts: { + contextMenuOpts: { group: '1_modification', order: 1.3 } @@ -286,7 +284,7 @@ registerEditorAction(class FormatSelectionMultipleAction extends EditorAction { label: nls.localize('formatSelection.label.multiple', "Format Selection With..."), alias: 'Format Code...', precondition: ContextKeyExpr.and(ContextKeyExpr.and(EditorContextKeys.writable), EditorContextKeys.hasMultipleDocumentSelectionFormattingProvider), - menuOpts: { + contextMenuOpts: { when: ContextKeyExpr.and(EditorContextKeys.hasNonEmptySelection), group: '1_modification', order: 1.31 diff --git a/src/vs/workbench/contrib/format/browser/formatActionsNone.ts b/src/vs/workbench/contrib/format/browser/formatActionsNone.ts index 0f31b7b564ef..440298e64a8e 100644 --- a/src/vs/workbench/contrib/format/browser/formatActionsNone.ts +++ b/src/vs/workbench/contrib/format/browser/formatActionsNone.ts @@ -50,7 +50,7 @@ registerEditorAction(class FormatDocumentMultipleAction extends EditorAction { return commandService.executeCommand('editor.action.formatDocument'); } else { const langName = model.getLanguageIdentifier().language; - const message = nls.localize('no.provider', "There is no formatter for '{0}'-files installed.", langName); + const message = nls.localize('no.provider', "There is no formatter for '{0}' files installed.", langName); const choice = { label: nls.localize('install.formatter', "Install Formatter..."), run: () => showExtensionQuery(viewletService, `category:formatters ${langName}`) diff --git a/src/vs/workbench/contrib/issue/browser/issue.contribution.ts b/src/vs/workbench/contrib/issue/browser/issue.contribution.ts new file mode 100644 index 000000000000..b422a8724939 --- /dev/null +++ b/src/vs/workbench/contrib/issue/browser/issue.contribution.ts @@ -0,0 +1,46 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import { ICommandAction, MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; +import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; +import { IWebIssueService, WebIssueService } from 'vs/workbench/contrib/issue/browser/issueService'; + +class RegisterIssueContribution implements IWorkbenchContribution { + + constructor(@IProductService readonly productService: IProductService) { + if (productService.reportIssueUrl) { + const helpCategory = { value: nls.localize('help', "Help"), original: 'Help' }; + const OpenIssueReporterActionId = 'workbench.action.openIssueReporter'; + const OpenIssueReporterActionLabel = nls.localize({ key: 'reportIssueInEnglish', comment: ['Translate this to "Report Issue in English" in all languages please!'] }, "Report Issue"); + + CommandsRegistry.registerCommand(OpenIssueReporterActionId, function (accessor, args?: [string]) { + let extensionId: string | undefined; + if (args && Array.isArray(args)) { + [extensionId] = args; + } + + return accessor.get(IWebIssueService).openReporter({ extensionId }); + }); + + const command: ICommandAction = { + id: OpenIssueReporterActionId, + title: { value: OpenIssueReporterActionLabel, original: 'Report Issue' }, + category: helpCategory + }; + + MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command }); + } + } +} + +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(RegisterIssueContribution, LifecyclePhase.Starting); + +registerSingleton(IWebIssueService, WebIssueService, true); diff --git a/src/vs/workbench/contrib/issue/browser/issueService.ts b/src/vs/workbench/contrib/issue/browser/issueService.ts new file mode 100644 index 000000000000..35edf62fcef5 --- /dev/null +++ b/src/vs/workbench/contrib/issue/browser/issueService.ts @@ -0,0 +1,67 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { URI } from 'vs/base/common/uri'; +import { normalizeGitHubUrl } from 'vs/code/common/issue/issueReporterUtil'; +import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { ExtensionType } from 'vs/platform/extensions/common/extensions'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IProductService } from 'vs/platform/product/common/productService'; + +export const IWebIssueService = createDecorator('webIssueService'); + +export interface IIssueReporterOptions { + extensionId?: string; +} + +export interface IWebIssueService { + _serviceBrand: undefined; + openReporter(options?: IIssueReporterOptions): Promise; +} + +export class WebIssueService implements IWebIssueService { + _serviceBrand: undefined; + + constructor( + @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, + @IOpenerService private readonly openerService: IOpenerService, + @IProductService private readonly productService: IProductService + ) { } + + async openReporter(options: IIssueReporterOptions): Promise { + let repositoryUrl = this.productService.reportIssueUrl; + if (options.extensionId) { + const extensionGitHubUrl = await this.getExtensionGitHubUrl(options.extensionId); + if (extensionGitHubUrl) { + repositoryUrl = extensionGitHubUrl + '/issues/new'; + } + } + + if (repositoryUrl) { + return this.openerService.open(URI.parse(repositoryUrl)).then(_ => { }); + } else { + throw new Error(`Unable to find issue reporting url for ${options.extensionId}`); + } + } + + private async getExtensionGitHubUrl(extensionId: string): Promise { + let repositoryUrl = ''; + + const extensions = await this.extensionManagementService.getInstalled(ExtensionType.User); + const selectedExtension = extensions.filter(ext => ext.identifier.id === extensionId)[0]; + const bugsUrl = selectedExtension?.manifest.bugs?.url; + const extensionUrl = selectedExtension?.manifest.repository?.url; + + // If given, try to match the extension's bug url + if (bugsUrl && bugsUrl.match(/^https?:\/\/github\.com\/(.*)/)) { + repositoryUrl = normalizeGitHubUrl(bugsUrl); + } else if (extensionUrl && extensionUrl.match(/^https?:\/\/github\.com\/(.*)/)) { + repositoryUrl = normalizeGitHubUrl(extensionUrl); + } + + return repositoryUrl; + } +} diff --git a/src/vs/workbench/contrib/issue/electron-browser/issue.contribution.ts b/src/vs/workbench/contrib/issue/electron-browser/issue.contribution.ts index 0dd6b7434bd0..d1f831f71ec8 100644 --- a/src/vs/workbench/contrib/issue/electron-browser/issue.contribution.ts +++ b/src/vs/workbench/contrib/issue/electron-browser/issue.contribution.ts @@ -19,7 +19,7 @@ const helpCategory = { value: nls.localize('help', "Help"), original: 'Help' }; const workbenchActionsRegistry = Registry.as(Extensions.WorkbenchActions); if (!!product.reportIssueUrl) { - workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(ReportPerformanceIssueUsingReporterAction, ReportPerformanceIssueUsingReporterAction.ID, ReportPerformanceIssueUsingReporterAction.LABEL), 'Help: Report Performance Issue', helpCategory.value); + workbenchActionsRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ReportPerformanceIssueUsingReporterAction, ReportPerformanceIssueUsingReporterAction.ID, ReportPerformanceIssueUsingReporterAction.LABEL), 'Help: Report Performance Issue', helpCategory.value); const OpenIssueReporterActionId = 'workbench.action.openIssueReporter'; const OpenIssueReporterActionLabel = nls.localize({ key: 'reportIssueInEnglish', comment: ['Translate this to "Report Issue in English" in all languages please!'] }, "Report Issue"); @@ -43,7 +43,7 @@ if (!!product.reportIssueUrl) { } const developerCategory = nls.localize('developer', "Developer"); -workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenProcessExplorer, OpenProcessExplorer.ID, OpenProcessExplorer.LABEL), 'Developer: Open Process Explorer', developerCategory); +workbenchActionsRegistry.registerWorkbenchAction(SyncActionDescriptor.create(OpenProcessExplorer, OpenProcessExplorer.ID, OpenProcessExplorer.LABEL), 'Developer: Open Process Explorer', developerCategory); registerSingleton(IWorkbenchIssueService, WorkbenchIssueService, true); diff --git a/src/vs/workbench/contrib/localizations/browser/localizations.contribution.ts b/src/vs/workbench/contrib/localizations/browser/localizations.contribution.ts index 36d1c66a3233..603250e515d1 100644 --- a/src/vs/workbench/contrib/localizations/browser/localizations.contribution.ts +++ b/src/vs/workbench/contrib/localizations/browser/localizations.contribution.ts @@ -29,7 +29,7 @@ import { ExtensionType } from 'vs/platform/extensions/common/extensions'; // Register action to configure locale and related settings const registry = Registry.as(Extensions.WorkbenchActions); -registry.registerWorkbenchAction(new SyncActionDescriptor(ConfigureLocaleAction, ConfigureLocaleAction.ID, ConfigureLocaleAction.LABEL), 'Configure Display Language'); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ConfigureLocaleAction, ConfigureLocaleAction.ID, ConfigureLocaleAction.LABEL), 'Configure Display Language'); export class LocalizationWorkbenchContribution extends Disposable implements IWorkbenchContribution { constructor( diff --git a/src/vs/workbench/contrib/localizations/browser/localizationsActions.ts b/src/vs/workbench/contrib/localizations/browser/localizationsActions.ts index effc89cf54ae..d88c6a5fefff 100644 --- a/src/vs/workbench/contrib/localizations/browser/localizationsActions.ts +++ b/src/vs/workbench/contrib/localizations/browser/localizationsActions.ts @@ -16,6 +16,7 @@ import { firstIndex } from 'vs/base/common/arrays'; import { IExtensionsViewlet, VIEWLET_ID as EXTENSIONS_VIEWLET_ID } from 'vs/workbench/contrib/extensions/common/extensions'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IProductService } from 'vs/platform/product/common/productService'; export class ConfigureLocaleAction extends Action { public static readonly ID = 'workbench.action.configureLocale'; @@ -29,7 +30,8 @@ export class ConfigureLocaleAction extends Action { @IHostService private readonly hostService: IHostService, @INotificationService private readonly notificationService: INotificationService, @IViewletService private readonly viewletService: IViewletService, - @IDialogService private readonly dialogService: IDialogService + @IDialogService private readonly dialogService: IDialogService, + @IProductService private readonly productService: IProductService ) { super(id, label); } @@ -69,7 +71,7 @@ export class ConfigureLocaleAction extends Action { const restart = await this.dialogService.confirm({ type: 'info', message: localize('relaunchDisplayLanguageMessage', "A restart is required for the change in display language to take effect."), - detail: localize('relaunchDisplayLanguageDetail', "Press the restart button to restart {0} and change the display language.", this.environmentService.appNameLong), + detail: localize('relaunchDisplayLanguageDetail', "Press the restart button to restart {0} and change the display language.", this.productService.nameLong), primaryButton: localize('restart', "&&Restart") }); diff --git a/src/vs/workbench/contrib/logs/common/logs.contribution.ts b/src/vs/workbench/contrib/logs/common/logs.contribution.ts index f0e8009fd9e2..416ce8088fed 100644 --- a/src/vs/workbench/contrib/logs/common/logs.contribution.ts +++ b/src/vs/workbench/contrib/logs/common/logs.contribution.ts @@ -22,11 +22,10 @@ import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { isWeb } from 'vs/base/common/platform'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { LogsDataCleaner } from 'vs/workbench/contrib/logs/common/logsDataCleaner'; -import { IProductService } from 'vs/platform/product/common/productService'; const workbenchActionsRegistry = Registry.as(WorkbenchActionExtensions.WorkbenchActions); const devCategory = nls.localize('developer', "Developer"); -workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(SetLogLevelAction, SetLogLevelAction.ID, SetLogLevelAction.LABEL), 'Developer: Set Log Level...', devCategory); +workbenchActionsRegistry.registerWorkbenchAction(SyncActionDescriptor.create(SetLogLevelAction, SetLogLevelAction.ID, SetLogLevelAction.LABEL), 'Developer: Set Log Level...', devCategory); class LogOutputChannels extends Disposable implements IWorkbenchContribution { @@ -35,7 +34,6 @@ class LogOutputChannels extends Disposable implements IWorkbenchContribution { @ILogService private readonly logService: ILogService, @IFileService private readonly fileService: IFileService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IProductService private readonly productService: IProductService ) { super(); this.registerCommonContributions(); @@ -47,9 +45,7 @@ class LogOutputChannels extends Disposable implements IWorkbenchContribution { } private registerCommonContributions(): void { - if (this.productService.settingsSyncStoreUrl) { - this.registerLogChannel(Constants.userDataSyncLogChannelId, nls.localize('userDataSyncLog', "Configuration Sync"), this.environmentService.userDataSyncLogResource); - } + this.registerLogChannel(Constants.userDataSyncLogChannelId, nls.localize('userDataSyncLog', "Sync"), this.environmentService.userDataSyncLogResource); this.registerLogChannel(Constants.rendererLogChannelId, nls.localize('rendererLog', "Window"), this.environmentService.logFile); } @@ -58,7 +54,7 @@ class LogOutputChannels extends Disposable implements IWorkbenchContribution { const workbenchActionsRegistry = Registry.as(WorkbenchActionExtensions.WorkbenchActions); const devCategory = nls.localize('developer', "Developer"); - workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenWindowSessionLogFileAction, OpenWindowSessionLogFileAction.ID, OpenWindowSessionLogFileAction.LABEL), 'Developer: Open Window Log File (Session)...', devCategory); + workbenchActionsRegistry.registerWorkbenchAction(SyncActionDescriptor.create(OpenWindowSessionLogFileAction, OpenWindowSessionLogFileAction.ID, OpenWindowSessionLogFileAction.LABEL), 'Developer: Open Window Log File (Session)...', devCategory); } private registerNativeContributions(): void { diff --git a/src/vs/workbench/contrib/logs/electron-browser/logs.contribution.ts b/src/vs/workbench/contrib/logs/electron-browser/logs.contribution.ts index ccd3051cd3cd..fc45f2312c90 100644 --- a/src/vs/workbench/contrib/logs/electron-browser/logs.contribution.ts +++ b/src/vs/workbench/contrib/logs/electron-browser/logs.contribution.ts @@ -11,4 +11,4 @@ import { OpenLogsFolderAction } from 'vs/workbench/contrib/logs/electron-browser const workbenchActionsRegistry = Registry.as(WorkbenchActionExtensions.WorkbenchActions); const devCategory = nls.localize('developer', "Developer"); -workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenLogsFolderAction, OpenLogsFolderAction.ID, OpenLogsFolderAction.LABEL), 'Developer: Open Logs Folder', devCategory); +workbenchActionsRegistry.registerWorkbenchAction(SyncActionDescriptor.create(OpenLogsFolderAction, OpenLogsFolderAction.ID, OpenLogsFolderAction.LABEL), 'Developer: Open Logs Folder', devCategory); diff --git a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts index f480e69e4639..ed52a509ee85 100644 --- a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts +++ b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts @@ -81,13 +81,18 @@ Registry.as(Extensions.Configuration).registerConfigurat 'description': Messages.PROBLEMS_PANEL_CONFIGURATION_AUTO_REVEAL, 'type': 'boolean', 'default': true + }, + 'problems.showCurrentInStatus': { + 'description': Messages.PROBLEMS_PANEL_CONFIGURATION_SHOW_CURRENT_STATUS, + 'type': 'boolean', + 'default': false } } }); // markers panel -Registry.as(PanelExtensions.Panels).registerPanel(new PanelDescriptor( +Registry.as(PanelExtensions.Panels).registerPanel(PanelDescriptor.create( MarkersPanel, Constants.MARKERS_PANEL_ID, Messages.MARKERS_PANEL_TITLE_PROBLEMS, @@ -102,10 +107,10 @@ workbenchRegistry.registerWorkbenchContribution(ActivityUpdater, LifecyclePhase. // actions const registry = Registry.as(ActionExtensions.WorkbenchActions); -registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleMarkersPanelAction, ToggleMarkersPanelAction.ID, ToggleMarkersPanelAction.LABEL, { +registry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleMarkersPanelAction, ToggleMarkersPanelAction.ID, ToggleMarkersPanelAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_M }), 'View: Toggle Problems (Errors, Warnings, Infos)', Messages.MARKERS_PANEL_VIEW_CATEGORY); -registry.registerWorkbenchAction(new SyncActionDescriptor(ShowProblemsPanelAction, ShowProblemsPanelAction.ID, ShowProblemsPanelAction.LABEL), 'View: Focus Problems (Errors, Warnings, Infos)', Messages.MARKERS_PANEL_VIEW_CATEGORY); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ShowProblemsPanelAction, ShowProblemsPanelAction.ID, ShowProblemsPanelAction.LABEL), 'View: Focus Problems (Errors, Warnings, Infos)', Messages.MARKERS_PANEL_VIEW_CATEGORY); registerAction({ id: Constants.MARKER_COPY_ACTION_ID, title: { value: localize('copyMarker', "Copy"), original: 'Copy' }, diff --git a/src/vs/workbench/contrib/markers/browser/markers.ts b/src/vs/workbench/contrib/markers/browser/markers.ts index f949e2a2de5c..3f39caafa66d 100644 --- a/src/vs/workbench/contrib/markers/browser/markers.ts +++ b/src/vs/workbench/contrib/markers/browser/markers.ts @@ -18,11 +18,6 @@ import { ResourceMap } from 'vs/base/common/map'; export const IMarkersWorkbenchService = createDecorator('markersWorkbenchService'); -export interface IFilter { - filterText: string; - useFilesExclude: boolean; -} - export interface IMarkersWorkbenchService { _serviceBrand: undefined; readonly markersModel: MarkersModel; @@ -38,7 +33,7 @@ export class MarkersWorkbenchService extends Disposable implements IMarkersWorkb @IInstantiationService instantiationService: IInstantiationService, ) { super(); - this.markersModel = this._register(instantiationService.createInstance(MarkersModel, this.readMarkers())); + this.markersModel = this._register(instantiationService.createInstance(MarkersModel)); this.markersModel.setResourceMarkers(groupBy(this.readMarkers(), compareMarkersByUri).map(group => [group[0].resource, group])); this._register(Event.debounce>(markerService.onMarkerChanged, (resourcesMap, resources) => { diff --git a/src/vs/workbench/contrib/markers/browser/markersFilterOptions.ts b/src/vs/workbench/contrib/markers/browser/markersFilterOptions.ts index 9418029d642a..eceb75095955 100644 --- a/src/vs/workbench/contrib/markers/browser/markersFilterOptions.ts +++ b/src/vs/workbench/contrib/markers/browser/markersFilterOptions.ts @@ -3,8 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import Messages from 'vs/workbench/contrib/markers/browser/messages'; -import { IFilter, matchesPrefix, matchesFuzzy, matchesFuzzy2 } from 'vs/base/common/filters'; +import { IFilter, matchesFuzzy, matchesFuzzy2 } from 'vs/base/common/filters'; import { IExpression, splitGlobAware, getEmptyExpression } from 'vs/base/common/glob'; import * as strings from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; @@ -15,15 +14,18 @@ export class FilterOptions { static readonly _filter: IFilter = matchesFuzzy2; static readonly _messageFilter: IFilter = matchesFuzzy; - readonly filterErrors: boolean = false; - readonly filterWarnings: boolean = false; - readonly filterInfos: boolean = false; + readonly showWarnings: boolean = false; + readonly showErrors: boolean = false; + readonly showInfos: boolean = false; readonly textFilter: string = ''; readonly excludesMatcher: ResourceGlobMatcher; readonly includesMatcher: ResourceGlobMatcher; - constructor(readonly filter: string = '', filesExclude: { root: URI, expression: IExpression }[] | IExpression = []) { + constructor(readonly filter: string = '', filesExclude: { root: URI, expression: IExpression }[] | IExpression = [], showWarnings: boolean = false, showErrors: boolean = false, showInfos: boolean = false) { filter = filter.trim(); + this.showWarnings = showWarnings; + this.showErrors = showErrors; + this.showInfos = showInfos; const filesExcludeByRoot = Array.isArray(filesExclude) ? filesExclude : []; const excludesExpression: IExpression = Array.isArray(filesExclude) ? getEmptyExpression() : filesExclude; @@ -32,9 +34,6 @@ export class FilterOptions { if (filter) { const filters = splitGlobAware(filter, ',').map(s => s.trim()).filter(s => !!s.length); for (const f of filters) { - this.filterErrors = this.filterErrors || this.matches(f, Messages.MARKERS_PANEL_FILTER_ERRORS); - this.filterWarnings = this.filterWarnings || this.matches(f, Messages.MARKERS_PANEL_FILTER_WARNINGS); - this.filterInfos = this.filterInfos || this.matches(f, Messages.MARKERS_PANEL_FILTER_INFOS); if (strings.startsWith(f, '!')) { this.setPattern(excludesExpression, strings.ltrim(f, '!')); } else { @@ -56,9 +55,4 @@ export class FilterOptions { expression[`**/${pattern}/**`] = true; expression[`**/${pattern}`] = true; } - - private matches(prefix: string, word: string): boolean { - const result = matchesPrefix(prefix, word); - return !!(result && result.length > 0); - } } diff --git a/src/vs/workbench/contrib/markers/browser/markersModel.ts b/src/vs/workbench/contrib/markers/browser/markersModel.ts index 115c48cf9ec0..105fd3a78321 100644 --- a/src/vs/workbench/contrib/markers/browser/markersModel.ts +++ b/src/vs/workbench/contrib/markers/browser/markersModel.ts @@ -117,6 +117,11 @@ export class MarkersModel { this.resourcesByUri = new Map(); } + private _total: number = 0; + get total(): number { + return this._total; + } + getResourceMarkers(resource: URI): ResourceMarkers | null { return withUndefinedAsNull(this.resourcesByUri.get(resource.toString())); } @@ -129,6 +134,7 @@ export class MarkersModel { if (resourceMarkers) { this.resourcesByUri.delete(resource.toString()); change.removed.push(resourceMarkers); + this._total -= resourceMarkers.markers.length; } } else { const resourceMarkersId = this.id(resource.toString()); @@ -149,12 +155,14 @@ export class MarkersModel { }), compareMarkers); if (resourceMarkers) { + this._total -= resourceMarkers.markers.length; resourceMarkers.markers = markers; change.updated.push(resourceMarkers); } else { resourceMarkers = new ResourceMarkers(resourceMarkersId, resource, markers); change.added.push(resourceMarkers); } + this._total += resourceMarkers.markers.length; this.resourcesByUri.set(resource.toString(), resourceMarkers); } } diff --git a/src/vs/workbench/contrib/markers/browser/markersPanel.ts b/src/vs/workbench/contrib/markers/browser/markersPanel.ts index b85b7b0ecf6e..40717bf147b3 100644 --- a/src/vs/workbench/contrib/markers/browser/markersPanel.ts +++ b/src/vs/workbench/contrib/markers/browser/markersPanel.ts @@ -12,7 +12,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { Panel } from 'vs/workbench/browser/panel'; import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import Constants from 'vs/workbench/contrib/markers/browser/constants'; -import { Marker, ResourceMarkers, RelatedInformation, MarkersModel, MarkerChangesEvent } from 'vs/workbench/contrib/markers/browser/markersModel'; +import { Marker, ResourceMarkers, RelatedInformation, MarkerChangesEvent } from 'vs/workbench/contrib/markers/browser/markersModel'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { MarkersFilterActionViewItem, MarkersFilterAction, IMarkersFilterActionChangeEvent, IMarkerFilterController } from 'vs/workbench/contrib/markers/browser/markersPanelActions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -27,32 +27,25 @@ import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/c import { Iterator } from 'vs/base/common/iterator'; import { ITreeElement, ITreeNode, ITreeContextMenuEvent, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; import { Relay, Event, Emitter } from 'vs/base/common/event'; -import { WorkbenchObjectTree, TreeResourceNavigator2, IListService } from 'vs/platform/list/browser/listService'; +import { WorkbenchObjectTree, TreeResourceNavigator2, IListService, IWorkbenchObjectTreeOptions } from 'vs/platform/list/browser/listService'; import { FilterOptions } from 'vs/workbench/contrib/markers/browser/markersFilterOptions'; import { IExpression } from 'vs/base/common/glob'; import { deepClone } from 'vs/base/common/objects'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { FilterData, Filter, VirtualDelegate, ResourceMarkersRenderer, MarkerRenderer, RelatedInformationRenderer, TreeElement, MarkersTreeAccessibilityProvider, MarkersViewModel, ResourceDragAndDrop } from 'vs/workbench/contrib/markers/browser/markersTreeViewer'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { Separator, ActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; +import { Separator, ActionViewItem, ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { IKeyboardEvent, StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import { KeyCode } from 'vs/base/common/keyCodes'; +import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { domEvent } from 'vs/base/browser/event'; import { ResourceLabels } from 'vs/workbench/browser/labels'; import { IMarker } from 'vs/platform/markers/common/markers'; import { withUndefinedAsNull } from 'vs/base/common/types'; import { MementoObject } from 'vs/workbench/common/memento'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; -import { IObjectTreeOptions } from 'vs/base/browser/ui/tree/objectTree'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; - -function createModelIterator(model: MarkersModel): Iterator> { - const resourcesIt = Iterator.fromArray(model.resourceMarkers); - - return Iterator.map(resourcesIt, m => ({ element: m, children: createResourceMarkersIterator(m) })); -} +import { PANEL_BACKGROUND } from 'vs/workbench/common/theme'; function createResourceMarkersIterator(resourceMarkers: ResourceMarkers): Iterator> { const markersIt = Iterator.fromArray(resourceMarkers.markers); @@ -75,12 +68,12 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { private readonly filter: Filter; private tree!: MarkersTree; + private filterActionBar!: ActionBar; private messageBoxContainer!: HTMLElement; private ariaLabelElement!: HTMLElement; private readonly collapseAllAction: IAction; private readonly filterAction: MarkersFilterAction; - private filterInputActionViewItem: MarkersFilterActionViewItem | null = null; private readonly panelState: MementoObject; private panelFoucusContextKey: IContextKey; @@ -91,6 +84,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { private currentResourceGotAddedToMarkersData: boolean = false; readonly markersViewModel: MarkersViewModel; + private isSmallLayout: boolean = false; constructor( @IInstantiationService private readonly instantiationService: IInstantiationService, @@ -118,7 +112,15 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { // actions this.collapseAllAction = this._register(new Action('vs.tree.collapse', localize('collapseAll', "Collapse All"), 'monaco-tree-action codicon-collapse-all', true, async () => this.collapseAll())); - this.filterAction = this._register(this.instantiationService.createInstance(MarkersFilterAction, { filterText: this.panelState['filter'] || '', filterHistory: this.panelState['filterHistory'] || [], useFilesExclude: !!this.panelState['useFilesExclude'] })); + this.filterAction = this._register(this.instantiationService.createInstance(MarkersFilterAction, { + filterText: this.panelState['filter'] || '', + filterHistory: this.panelState['filterHistory'] || [], + showErrors: this.panelState['showErrors'] !== false, + showWarnings: this.panelState['showWarnings'] !== false, + showInfos: this.panelState['showInfos'] !== false, + excludedFiles: !!this.panelState['useFilesExclude'], + activeFile: !!this.panelState['activeFile'] + })); } public create(parent: HTMLElement): void { @@ -129,6 +131,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { const container = dom.append(parent, dom.$('.markers-panel-container')); + this.createFilterActionBar(container); this.createArialLabelElement(container); this.createMessageBox(container); this.createTree(container); @@ -147,6 +150,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { } })); + this.filterActionBar.push(this.filterAction); this.render(); } @@ -155,10 +159,15 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { } public layout(dimension: dom.Dimension): void { - this.tree.layout(dimension.height, dimension.width); - if (this.filterInputActionViewItem) { - this.filterInputActionViewItem.toggleLayout(dimension.width < 1200); + const wasSmallLayout = this.isSmallLayout; + this.isSmallLayout = dimension.width < 600; + if (this.isSmallLayout !== wasSmallLayout) { + this.updateTitleArea(); + dom.toggleClass(this.filterActionBar.getContainer(), 'hide', !this.isSmallLayout); } + const treeHeight = this.isSmallLayout ? dimension.height - 44 : dimension.height; + this.tree.layout(treeHeight, dimension.width); + this.filterAction.layout(this.isSmallLayout ? dimension.width : dimension.width - 200); } public focus(): void { @@ -174,12 +183,13 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { } public focusFilter(): void { - if (this.filterInputActionViewItem) { - this.filterInputActionViewItem.focus(); - } + this.filterAction.focus(); } public getActions(): IAction[] { + if (this.isSmallLayout) { + return [this.collapseAllAction]; + } return [this.filterAction, this.collapseAllAction]; } @@ -239,7 +249,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { } else { if (markerOrChange.added.length || markerOrChange.removed.length) { // Reset complete tree - this.tree.setChildren(null, createModelIterator(this.markersWorkbenchService.markersModel)); + this.resetTree(); } else { // Update resource for (const updated of markerOrChange.updated) { @@ -249,7 +259,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { } } else { // Reset complete tree - this.tree.setChildren(null, createModelIterator(this.markersWorkbenchService.markersModel)); + this.resetTree(); } const { total, filtered } = this.getFilterStats(); @@ -263,9 +273,24 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { this.refreshPanel(marker); } + private resetTree(): void { + let resourceMarkers: ResourceMarkers[] = []; + if (this.filterAction.activeFile) { + if (this.currentActiveResource) { + const activeResourceMarkers = this.markersWorkbenchService.markersModel.getResourceMarkers(this.currentActiveResource); + if (activeResourceMarkers) { + resourceMarkers = [activeResourceMarkers]; + } + } + } else { + resourceMarkers = this.markersWorkbenchService.markersModel.resourceMarkers; + } + this.tree.setChildren(null, Iterator.map(Iterator.fromArray(resourceMarkers), m => ({ element: m, children: createResourceMarkersIterator(m) }))); + } + private updateFilter() { this.cachedFilterStats = undefined; - this.filter.options = new FilterOptions(this.filterAction.filterText, this.getFilesExcludeExpressions()); + this.filter.options = new FilterOptions(this.filterAction.filterText, this.getFilesExcludeExpressions(), this.filterAction.showWarnings, this.filterAction.showErrors, this.filterAction.showInfos); this.tree.refilter(); this._onDidFilter.fire(); @@ -275,7 +300,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { } private getFilesExcludeExpressions(): { root: URI, expression: IExpression }[] | IExpression { - if (!this.filterAction.useFilesExclude) { + if (!this.filterAction.excludedFiles) { return []; } @@ -289,6 +314,12 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { return deepClone(this.configurationService.getValue('files.exclude', { resource })) || {}; } + private createFilterActionBar(parent: HTMLElement): void { + this.filterActionBar = this._register(new ActionBar(parent, { actionViewItemProvider: action => this.getActionViewItem(action) })); + dom.addClass(this.filterActionBar.getContainer(), 'markers-panel-filter-container'); + dom.toggleClass(this.filterActionBar.getContainer(), 'hide', !this.isSmallLayout); + } + private createMessageBox(parent: HTMLElement): void { this.messageBoxContainer = dom.append(parent, dom.$('.message-box-container')); this.messageBoxContainer.setAttribute('aria-labelledby', 'markers-panel-arialabel'); @@ -329,8 +360,11 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { accessibilityProvider, identityProvider, dnd: new ResourceDragAndDrop(this.instantiationService), - expandOnlyOnTwistieClick: (e: TreeElement) => e instanceof Marker && e.relatedInformation.length > 0 - } + expandOnlyOnTwistieClick: (e: TreeElement) => e instanceof Marker && e.relatedInformation.length > 0, + overrideStyles: { + listBackground: PANEL_BACKGROUND + } + }, )); onDidChangeRenderNodeCount.input = this.tree.onDidChangeRenderNodeCount; @@ -362,15 +396,15 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { this._register(this.tree.onContextMenu(this.onContextMenu, this)); this._register(this.configurationService.onDidChangeConfiguration(e => { - if (this.filterAction.useFilesExclude && e.affectsConfiguration('files.exclude')) { + if (this.filterAction.excludedFiles && e.affectsConfiguration('files.exclude')) { this.updateFilter(); } })); // move focus to input, whenever a key is pressed in the panel container this._register(domEvent(parent, 'keydown')(e => { - if (this.filterInputActionViewItem && this.keybindingService.mightProducePrintableCharacter(new StandardKeyboardEvent(e))) { - this.filterInputActionViewItem.focus(); + if (this.keybindingService.mightProducePrintableCharacter(new StandardKeyboardEvent(e))) { + this.filterAction.focus(); } })); @@ -405,7 +439,9 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { })); this._register(this.tree.onDidChangeSelection(() => this.onSelected())); this._register(this.filterAction.onDidChange((event: IMarkersFilterActionChangeEvent) => { - if (event.filterText || event.useFilesExclude) { + if (event.activeFile) { + this.refreshPanel(); + } else if (event.filterText || event.excludedFiles || event.showWarnings || event.showErrors || event.showInfos) { this.updateFilter(); } })); @@ -447,6 +483,9 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { private onActiveEditorChanged(): void { this.setCurrentActiveEditor(); + if (this.filterAction.activeFile) { + this.refreshPanel(); + } this.autoReveal(); } @@ -469,7 +508,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { private render(): void { this.cachedFilterStats = undefined; - this.tree.setChildren(null, createModelIterator(this.markersWorkbenchService.markersModel)); + this.resetTree(); this.tree.toggleVisibility(this.isEmpty()); this.renderMessage(); } @@ -482,11 +521,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { this.messageBoxContainer.style.display = 'block'; this.messageBoxContainer.setAttribute('tabIndex', '0'); if (total > 0) { - if (this.filter.options.filter) { - this.renderFilteredByFilterMessage(this.messageBoxContainer); - } else { - this.renderFilteredByFilesExcludeMessage(this.messageBoxContainer); - } + this.renderFilteredByFilterMessage(this.messageBoxContainer); } else { this.renderNoProblemsMessage(this.messageBoxContainer); } @@ -501,37 +536,9 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { } } - private renderFilteredByFilesExcludeMessage(container: HTMLElement) { - const span1 = dom.append(container, dom.$('span')); - span1.textContent = Messages.MARKERS_PANEL_NO_PROBLEMS_FILE_EXCLUSIONS_FILTER; - const link = dom.append(container, dom.$('a.messageAction')); - link.textContent = localize('disableFilesExclude', "Disable Files Exclude Filter."); - link.setAttribute('tabIndex', '0'); - dom.addStandardDisposableListener(link, dom.EventType.CLICK, () => this.filterAction.useFilesExclude = false); - dom.addStandardDisposableListener(link, dom.EventType.KEY_DOWN, (e: IKeyboardEvent) => { - if (e.equals(KeyCode.Enter) || e.equals(KeyCode.Space)) { - this.filterAction.useFilesExclude = false; - e.stopPropagation(); - } - }); - this.ariaLabelElement.setAttribute('aria-label', Messages.MARKERS_PANEL_NO_PROBLEMS_FILE_EXCLUSIONS_FILTER); - } - private renderFilteredByFilterMessage(container: HTMLElement) { const span1 = dom.append(container, dom.$('span')); span1.textContent = Messages.MARKERS_PANEL_NO_PROBLEMS_FILTERS; - const link = dom.append(container, dom.$('a.messageAction')); - link.textContent = localize('clearFilter', "Clear Filter"); - link.setAttribute('tabIndex', '0'); - const span2 = dom.append(container, dom.$('span')); - span2.textContent = '.'; - dom.addStandardDisposableListener(link, dom.EventType.CLICK, () => this.filterAction.filterText = ''); - dom.addStandardDisposableListener(link, dom.EventType.KEY_DOWN, (e: IKeyboardEvent) => { - if (e.equals(KeyCode.Enter) || e.equals(KeyCode.Space)) { - this.filterAction.filterText = ''; - e.stopPropagation(); - } - }); this.ariaLabelElement.setAttribute('aria-label', Messages.MARKERS_PANEL_NO_PROBLEMS_FILTERS); } @@ -542,32 +549,32 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { } private autoReveal(focus: boolean = false): void { + // No need to auto reveal if active file filter is on + if (this.filterAction.activeFile) { + return; + } let autoReveal = this.configurationService.getValue('problems.autoReveal'); if (typeof autoReveal === 'boolean' && autoReveal) { - this.revealMarkersForCurrentActiveEditor(focus); - } - } - - private revealMarkersForCurrentActiveEditor(focus: boolean = false): void { - let currentActiveResource = this.getResourceForCurrentActiveResource(); - if (currentActiveResource) { - if (!this.tree.isCollapsed(currentActiveResource) && this.hasSelectedMarkerFor(currentActiveResource)) { - this.tree.reveal(this.tree.getSelection()[0], this.lastSelectedRelativeTop); - if (focus) { - this.tree.setFocus(this.tree.getSelection()); - } - } else { - this.tree.expand(currentActiveResource); - this.tree.reveal(currentActiveResource, 0); + let currentActiveResource = this.getResourceForCurrentActiveResource(); + if (currentActiveResource) { + if (!this.tree.isCollapsed(currentActiveResource) && this.hasSelectedMarkerFor(currentActiveResource)) { + this.tree.reveal(this.tree.getSelection()[0], this.lastSelectedRelativeTop); + if (focus) { + this.tree.setFocus(this.tree.getSelection()); + } + } else { + this.tree.expand(currentActiveResource); + this.tree.reveal(currentActiveResource, 0); - if (focus) { - this.tree.setFocus([currentActiveResource]); - this.tree.setSelection([currentActiveResource]); + if (focus) { + this.tree.setFocus([currentActiveResource]); + this.tree.setSelection([currentActiveResource]); + } } + } else if (focus) { + this.tree.setSelection([]); + this.tree.focusFirst(); } - } else if (focus) { - this.tree.setSelection([]); - this.tree.focusFirst(); } } @@ -671,8 +678,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { public getActionViewItem(action: IAction): IActionViewItem | undefined { if (action.id === MarkersFilterAction.ID) { - this.filterInputActionViewItem = this.instantiationService.createInstance(MarkersFilterActionViewItem, this.filterAction, this); - return this.filterInputActionViewItem; + return this.instantiationService.createInstance(MarkersFilterActionViewItem, this.filterAction, this); } return super.getActionViewItem(action); } @@ -691,20 +697,17 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { private computeFilterStats(): { total: number; filtered: number; } { const root = this.tree.getNode(); - let total = 0; let filtered = 0; for (const resourceMarkerNode of root.children) { for (const markerNode of resourceMarkerNode.children) { - total++; - if (resourceMarkerNode.visible && markerNode.visible) { filtered++; } } } - return { total, filtered }; + return { total: this.markersWorkbenchService.markersModel.total, filtered }; } private getTelemetryData({ source, code }: IMarker): any { @@ -714,7 +717,11 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { protected saveState(): void { this.panelState['filter'] = this.filterAction.filterText; this.panelState['filterHistory'] = this.filterAction.filterHistory; - this.panelState['useFilesExclude'] = this.filterAction.useFilesExclude; + this.panelState['showErrors'] = this.filterAction.showErrors; + this.panelState['showWarnings'] = this.filterAction.showWarnings; + this.panelState['showInfos'] = this.filterAction.showInfos; + this.panelState['useFilesExclude'] = this.filterAction.excludedFiles; + this.panelState['activeFile'] = this.filterAction.activeFile; this.panelState['multiline'] = this.markersViewModel.multiline; super.saveState(); @@ -729,7 +736,7 @@ class MarkersTree extends WorkbenchObjectTree { readonly container: HTMLElement, delegate: IListVirtualDelegate, renderers: ITreeRenderer[], - options: IObjectTreeOptions, + options: IWorkbenchObjectTreeOptions, @IContextKeyService contextKeyService: IContextKeyService, @IListService listService: IListService, @IThemeService themeService: IThemeService, diff --git a/src/vs/workbench/contrib/markers/browser/markersPanelActions.ts b/src/vs/workbench/contrib/markers/browser/markersPanelActions.ts index 746e5903e807..3910f09d18d7 100644 --- a/src/vs/workbench/contrib/markers/browser/markersPanelActions.ts +++ b/src/vs/workbench/contrib/markers/browser/markersPanelActions.ts @@ -5,7 +5,7 @@ import { Delayer } from 'vs/base/common/async'; import * as DOM from 'vs/base/browser/dom'; -import { Action, IActionChangeEvent, IAction } from 'vs/base/common/actions'; +import { Action, IActionChangeEvent, IAction, IActionRunner } from 'vs/base/common/actions'; import { HistoryInputBox } from 'vs/base/browser/ui/inputbox/inputBox'; import { KeyCode } from 'vs/base/common/keyCodes'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; @@ -15,14 +15,12 @@ import Messages from 'vs/workbench/contrib/markers/browser/messages'; import Constants from 'vs/workbench/contrib/markers/browser/constants'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { attachInputBoxStyler, attachStylerCallback, attachCheckboxStyler } from 'vs/platform/theme/common/styler'; -import { IMarkersWorkbenchService } from 'vs/workbench/contrib/markers/browser/markers'; +import { IThemeService, registerThemingParticipant, ICssStyleCollector, ITheme } from 'vs/platform/theme/common/themeService'; +import { attachInputBoxStyler, attachStylerCallback } from 'vs/platform/theme/common/styler'; import { toDisposable } from 'vs/base/common/lifecycle'; -import { BaseActionViewItem, ActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; -import { badgeBackground, badgeForeground, contrastBorder } from 'vs/platform/theme/common/colorRegistry'; +import { BaseActionViewItem, ActionViewItem, ActionBar, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; +import { badgeBackground, badgeForeground, contrastBorder, inputActiveOptionBorder, inputActiveOptionBackground } from 'vs/platform/theme/common/colorRegistry'; import { localize } from 'vs/nls'; -import { Checkbox } from 'vs/base/browser/ui/checkbox/checkbox'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ContextScopedHistoryInputBox } from 'vs/platform/browser/contextScopedHistoryWidget'; import { Marker } from 'vs/workbench/contrib/markers/browser/markersModel'; @@ -30,6 +28,8 @@ import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/c import { Event, Emitter } from 'vs/base/common/event'; import { FilterOptions } from 'vs/workbench/contrib/markers/browser/markersFilterOptions'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdown'; +import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; export class ToggleMarkersPanelAction extends TogglePanelAction { @@ -38,8 +38,7 @@ export class ToggleMarkersPanelAction extends TogglePanelAction { constructor(id: string, label: string, @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, - @IPanelService panelService: IPanelService, - @IMarkersWorkbenchService markersWorkbenchService: IMarkersWorkbenchService + @IPanelService panelService: IPanelService ) { super(id, label, Constants.MARKERS_PANEL_ID, panelService, layoutService); } @@ -64,23 +63,38 @@ export class ShowProblemsPanelAction extends Action { export interface IMarkersFilterActionChangeEvent extends IActionChangeEvent { filterText?: boolean; - useFilesExclude?: boolean; + excludedFiles?: boolean; + showWarnings?: boolean; + showErrors?: boolean; + showInfos?: boolean; + activeFile?: boolean; } export interface IMarkersFilterActionOptions { filterText: string; filterHistory: string[]; - useFilesExclude: boolean; + showErrors: boolean; + showWarnings: boolean; + showInfos: boolean; + excludedFiles: boolean; + activeFile: boolean; } export class MarkersFilterAction extends Action { public static readonly ID: string = 'workbench.actions.problems.filter'; + private readonly _onFocus: Emitter = this._register(new Emitter()); + readonly onFocus: Event = this._onFocus.event; + constructor(options: IMarkersFilterActionOptions) { super(MarkersFilterAction.ID, Messages.MARKERS_PANEL_ACTION_TOOLTIP_FILTER, 'markers-panel-action-filter', true); this._filterText = options.filterText; - this._useFilesExclude = options.useFilesExclude; + this._showErrors = options.showErrors; + this._showWarnings = options.showWarnings; + this._showInfos = options.showInfos; + this._excludedFiles = options.excludedFiles; + this._activeFile = options.activeFile; this.filterHistory = options.filterHistory; } @@ -97,14 +111,72 @@ export class MarkersFilterAction extends Action { filterHistory: string[]; - private _useFilesExclude: boolean; - get useFilesExclude(): boolean { - return this._useFilesExclude; + private _excludedFiles: boolean; + get excludedFiles(): boolean { + return this._excludedFiles; + } + set excludedFiles(filesExclude: boolean) { + if (this._excludedFiles !== filesExclude) { + this._excludedFiles = filesExclude; + this._onDidChange.fire({ excludedFiles: true }); + } + } + + private _activeFile: boolean; + get activeFile(): boolean { + return this._activeFile; + } + set activeFile(activeFile: boolean) { + if (this._activeFile !== activeFile) { + this._activeFile = activeFile; + this._onDidChange.fire({ activeFile: true }); + } + } + + private _showWarnings: boolean = true; + get showWarnings(): boolean { + return this._showWarnings; + } + set showWarnings(showWarnings: boolean) { + if (this._showWarnings !== showWarnings) { + this._showWarnings = showWarnings; + this._onDidChange.fire({ showWarnings: true }); + } + } + + private _showErrors: boolean = true; + get showErrors(): boolean { + return this._showErrors; } - set useFilesExclude(filesExclude: boolean) { - if (this._useFilesExclude !== filesExclude) { - this._useFilesExclude = filesExclude; - this._onDidChange.fire({ useFilesExclude: true }); + set showErrors(showErrors: boolean) { + if (this._showErrors !== showErrors) { + this._showErrors = showErrors; + this._onDidChange.fire({ showErrors: true }); + } + } + + private _showInfos: boolean = true; + get showInfos(): boolean { + return this._showInfos; + } + set showInfos(showInfos: boolean) { + if (this._showInfos !== showInfos) { + this._showInfos = showInfos; + this._onDidChange.fire({ showInfos: true }); + } + } + + focus(): void { + this._onFocus.fire(); + } + + layout(width: number): void { + if (width > 600) { + this.class = 'markers-panel-action-filter grow'; + } else if (width < 400) { + this.class = 'markers-panel-action-filter small'; + } else { + this.class = 'markers-panel-action-filter'; } } } @@ -115,6 +187,89 @@ export interface IMarkerFilterController { getFilterStats(): { total: number, filtered: number }; } +class FiltersDropdownMenuActionViewItem extends DropdownMenuActionViewItem { + + constructor( + action: IAction, private filterAction: MarkersFilterAction, actionRunner: IActionRunner, + @IContextMenuService contextMenuService: IContextMenuService + ) { + super(action, + { getActions: () => this.getActions() }, + contextMenuService, + action => undefined, + actionRunner!, + undefined, + action.class, + () => { return AnchorAlignment.RIGHT; }); + } + + render(container: HTMLElement): void { + super.render(container); + this.updateChecked(); + } + + private getActions(): IAction[] { + return [ + { + checked: this.filterAction.showErrors, + class: undefined, + enabled: true, + id: 'showErrors', + label: Messages.MARKERS_PANEL_FILTER_LABEL_SHOW_ERRORS, + run: async () => this.filterAction.showErrors = !this.filterAction.showErrors, + tooltip: '', + dispose: () => null + }, + { + checked: this.filterAction.showWarnings, + class: undefined, + enabled: true, + id: 'showWarnings', + label: Messages.MARKERS_PANEL_FILTER_LABEL_SHOW_WARNINGS, + run: async () => this.filterAction.showWarnings = !this.filterAction.showWarnings, + tooltip: '', + dispose: () => null + }, + { + checked: this.filterAction.showInfos, + class: undefined, + enabled: true, + id: 'showInfos', + label: Messages.MARKERS_PANEL_FILTER_LABEL_SHOW_INFOS, + run: async () => this.filterAction.showInfos = !this.filterAction.showInfos, + tooltip: '', + dispose: () => null + }, + new Separator(), + { + checked: this.filterAction.activeFile, + class: undefined, + enabled: true, + id: 'activeFile', + label: Messages.MARKERS_PANEL_FILTER_LABEL_ACTIVE_FILE, + run: async () => this.filterAction.activeFile = !this.filterAction.activeFile, + tooltip: '', + dispose: () => null + }, + { + checked: this.filterAction.excludedFiles, + class: undefined, + enabled: true, + id: 'useFilesExclude', + label: Messages.MARKERS_PANEL_FILTER_LABEL_EXCLUDED_FILES, + run: async () => this.filterAction.excludedFiles = !this.filterAction.excludedFiles, + tooltip: '', + dispose: () => null + }, + ]; + } + + updateChecked(): void { + DOM.toggleClass(this.element!, 'checked', this._action.checked); + } + +} + export class MarkersFilterActionViewItem extends BaseActionViewItem { private delayedFilterUpdate: Delayer; @@ -122,6 +277,7 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem { private filterInputBox: HistoryInputBox | null = null; private filterBadge: HTMLElement | null = null; private focusContextKey: IContextKey; + private readonly filtersAction: IAction; constructor( readonly action: MarkersFilterAction, @@ -136,15 +292,20 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem { this.focusContextKey = Constants.MarkerPanelFilterFocusContextKey.bindTo(contextKeyService); this.delayedFilterUpdate = new Delayer(200); this._register(toDisposable(() => this.delayedFilterUpdate.cancel())); + this._register(action.onFocus(() => this.focus())); + this.filtersAction = new Action('markersFiltersAction', Messages.MARKERS_PANEL_ACTION_TOOLTIP_MORE_FILTERS, 'markers-filters codicon-filter'); + this.filtersAction.checked = this.hasFiltersChanged(); + this._register(action.onDidChange(() => this.filtersAction.checked = this.hasFiltersChanged())); } render(container: HTMLElement): void { this.container = container; DOM.addClass(this.container, 'markers-panel-action-filter-container'); - const filterContainer = DOM.append(this.container, DOM.$('.markers-panel-action-filter')); - this.createInput(filterContainer); - this.createControls(filterContainer); + this.element = DOM.append(this.container, DOM.$('')); + this.element.className = this.action.class || ''; + this.createInput(this.element); + this.createControls(this.element); this.adjustInputBox(); } @@ -155,11 +316,8 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem { } } - toggleLayout(small: boolean) { - if (this.container) { - DOM.toggleClass(this.container, 'small', small); - this.adjustInputBox(); - } + private hasFiltersChanged(): boolean { + return !this.action.showErrors || !this.action.showWarnings || !this.action.showInfos || this.action.excludedFiles || this.action.activeFile; } private createInput(container: HTMLElement): void { @@ -190,7 +348,7 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem { private createControls(container: HTMLElement): void { const controlsContainer = DOM.append(container, DOM.$('.markers-panel-filter-controls')); this.createBadge(controlsContainer); - this.createFilesExcludeCheckbox(controlsContainer); + this.createFilters(controlsContainer); } private createBadge(container: HTMLElement): void { @@ -211,25 +369,16 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem { this._register(this.filterController.onDidFilter(() => this.updateBadge())); } - private createFilesExcludeCheckbox(container: HTMLElement): void { - const filesExcludeFilter = this._register(new Checkbox({ - actionClassName: 'codicon codicon-exclude', - title: this.action.useFilesExclude ? Messages.MARKERS_PANEL_ACTION_TOOLTIP_DO_NOT_USE_FILES_EXCLUDE : Messages.MARKERS_PANEL_ACTION_TOOLTIP_USE_FILES_EXCLUDE, - isChecked: this.action.useFilesExclude - })); - this._register(filesExcludeFilter.onChange(() => { - filesExcludeFilter.domNode.title = filesExcludeFilter.checked ? Messages.MARKERS_PANEL_ACTION_TOOLTIP_DO_NOT_USE_FILES_EXCLUDE : Messages.MARKERS_PANEL_ACTION_TOOLTIP_USE_FILES_EXCLUDE; - this.action.useFilesExclude = filesExcludeFilter.checked; - this.focus(); - })); - this._register(this.action.onDidChange((event: IMarkersFilterActionChangeEvent) => { - if (event.useFilesExclude) { - filesExcludeFilter.checked = this.action.useFilesExclude; + private createFilters(container: HTMLElement): void { + const actionbar = this._register(new ActionBar(container, { + actionViewItemProvider: action => { + if (action.id === this.filtersAction.id) { + return this.instantiationService.createInstance(FiltersDropdownMenuActionViewItem, action, this.action, this.actionRunner); + } + return undefined; } })); - - this._register(attachCheckboxStyler(filesExcludeFilter, this.themeService)); - container.appendChild(filesExcludeFilter.domNode); + actionbar.push(this.filtersAction, { icon: true, label: false }); } private onDidInputChange(inputbox: HistoryInputBox) { @@ -249,8 +398,8 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem { } private adjustInputBox(): void { - if (this.container && this.filterInputBox && this.filterBadge) { - this.filterInputBox.inputElement.style.paddingRight = DOM.hasClass(this.container, 'small') || DOM.hasClass(this.filterBadge, 'hidden') ? '25px' : '150px'; + if (this.element && this.filterInputBox && this.filterBadge) { + this.filterInputBox.inputElement.style.paddingRight = DOM.hasClass(this.element, 'small') || DOM.hasClass(this.filterBadge, 'hidden') ? '25px' : '150px'; } } @@ -280,9 +429,9 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem { private reportFilteringUsed(): void { const filterOptions = this.filterController.getFilterOptions(); const data = { - errors: filterOptions.filterErrors, - warnings: filterOptions.filterWarnings, - infos: filterOptions.filterInfos, + errors: filterOptions.showErrors, + warnings: filterOptions.showWarnings, + infos: filterOptions.showInfos, }; /* __GDPR__ "problems.filter" : { @@ -293,6 +442,14 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem { */ this.telemetryService.publicLog('problems.filter', data); } + + protected updateClass(): void { + if (this.element && this.container) { + this.element.className = this.action.class || ''; + DOM.toggleClass(this.container, 'grow', DOM.hasClass(this.element, 'grow')); + this.adjustInputBox(); + } + } } export class QuickFixAction extends Action { @@ -359,3 +516,14 @@ export class QuickFixActionViewItem extends ActionViewItem { } } } + +registerThemingParticipant((theme: ITheme, 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 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/markers/browser/markersTreeViewer.ts b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts index c518ff7cbbcc..ee3aac6c82b3 100644 --- a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts +++ b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts @@ -37,7 +37,7 @@ import { CancelablePromise, createCancelablePromise, Delayer } from 'vs/base/com import { IModelService } from 'vs/editor/common/services/modelService'; import { Range } from 'vs/editor/common/core/range'; import { getCodeActions, CodeActionSet } from 'vs/editor/contrib/codeAction/codeAction'; -import { CodeActionKind } from 'vs/editor/contrib/codeAction/codeActionTrigger'; +import { CodeActionKind } from 'vs/editor/contrib/codeAction/types'; import { ITextModel } from 'vs/editor/common/model'; import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; import { ICommandService } from 'vs/platform/commands/common/commands'; @@ -254,7 +254,7 @@ class MarkerWidget extends Disposable { ) { super(); this.actionBar = this._register(new ActionBar(dom.append(parent, dom.$('.actions')), { - actionViewItemProvider: (action) => action.id === QuickFixAction.ID ? instantiationService.createInstance(QuickFixActionViewItem, action) : undefined + actionViewItemProvider: (action: QuickFixAction) => action.id === QuickFixAction.ID ? instantiationService.createInstance(QuickFixActionViewItem, action) : undefined })); this.icon = dom.append(parent, dom.$('')); this.multilineActionbar = this._register(new ActionBar(dom.append(parent, dom.$('.multiline-actions')))); @@ -314,6 +314,7 @@ class MarkerWidget extends Disposable { const lineMatches = filterData && filterData.lineMatches || []; let lastLineElement: HTMLElement | undefined = undefined; + this.messageAndDetailsContainer.title = element.marker.message; for (let index = 0; index < (multiline ? lines.length : 1); index++) { lastLineElement = dom.append(this.messageAndDetailsContainer, dom.$('.marker-message-line')); const messageElement = dom.append(lastLineElement, dom.$('.marker-message')); @@ -425,16 +426,21 @@ export class Filter implements ITreeFilter { } private filterMarker(marker: Marker, parentVisibility: TreeVisibility): TreeFilterResult { - if (this.options.filterErrors && MarkerSeverity.Error === marker.marker.severity) { - return true; + let shouldAppear: boolean = false; + if (this.options.showErrors && MarkerSeverity.Error === marker.marker.severity) { + shouldAppear = true; } - if (this.options.filterWarnings && MarkerSeverity.Warning === marker.marker.severity) { - return true; + if (this.options.showWarnings && MarkerSeverity.Warning === marker.marker.severity) { + shouldAppear = true; } - if (this.options.filterInfos && MarkerSeverity.Info === marker.marker.severity) { - return true; + if (this.options.showInfos && MarkerSeverity.Info === marker.marker.severity) { + shouldAppear = true; + } + + if (!shouldAppear) { + return false; } if (!this.options.textFilter) { @@ -546,7 +552,7 @@ export class MarkerViewModel extends Disposable { if (model) { if (!this.codeActionsPromise) { this.codeActionsPromise = createCancelablePromise(cancellationToken => { - return getCodeActions(model, new Range(this.marker.range.startLineNumber, this.marker.range.startColumn, this.marker.range.endLineNumber, this.marker.range.endColumn), { type: 'manual', filter: { kind: CodeActionKind.QuickFix } }, cancellationToken).then(actions => { + return getCodeActions(model, new Range(this.marker.range.startLineNumber, this.marker.range.startColumn, this.marker.range.endLineNumber, this.marker.range.endColumn), { type: 'manual', filter: { include: CodeActionKind.QuickFix } }, cancellationToken).then(actions => { return this._register(actions); }); }); @@ -558,7 +564,7 @@ export class MarkerViewModel extends Disposable { } private toActions(codeActions: CodeActionSet): IAction[] { - return codeActions.actions.map(codeAction => new Action( + return codeActions.validActions.map(codeAction => new Action( codeAction.command ? codeAction.command.id : codeAction.title, codeAction.title, undefined, diff --git a/src/vs/workbench/contrib/markers/browser/media/markers.css b/src/vs/workbench/contrib/markers/browser/media/markers.css index 11a6c524b384..afdf80d7108e 100644 --- a/src/vs/workbench/contrib/markers/browser/media/markers.css +++ b/src/vs/workbench/contrib/markers/browser/media/markers.css @@ -5,20 +5,9 @@ .monaco-action-bar .action-item.markers-panel-action-filter-container { cursor: default; - margin-right: 10px; - min-width: 150px; - max-width: 500px; display: flex; } -.monaco-action-bar .markers-panel-action-filter-container { - flex: 0.7; -} - -.monaco-action-bar .markers-panel-action-filter-container.small { - flex: 0.5; -} - .monaco-action-bar .markers-panel-action-filter { display: flex; align-items: center; @@ -40,7 +29,7 @@ position: absolute; top: 0px; bottom: 0; - right: 4px; + right: 0px; display: flex; align-items: center; } @@ -52,14 +41,40 @@ } .markers-panel-action-filter > .markers-panel-filter-controls > .markers-panel-filter-badge.hidden, -.markers-panel-action-filter-container.small .markers-panel-action-filter > .markers-panel-filter-controls > .markers-panel-filter-badge { +.markers-panel-action-filter.small > .markers-panel-filter-controls > .markers-panel-filter-badge { display: none; } +.markers-panel-action-filter > .markers-panel-filter-controls > .monaco-action-bar .action-label.markers-filters { + line-height: 20px; + height: 20px; + min-width: 22px; + margin-left: 4px; +} + +.panel > .title .monaco-action-bar .action-item.markers-panel-action-filter-container { + max-width: 600px; + min-width: 300px; + margin-right: 10px; +} + +.markers-panel-container .monaco-action-bar.markers-panel-filter-container .action-item.markers-panel-action-filter-container, +.panel > .title .monaco-action-bar .action-item.markers-panel-action-filter-container.grow { + flex: 1; +} + .markers-panel .markers-panel-container { height: 100%; } +.markers-panel .hide { + display: none; +} + +.markers-panel-container .monaco-action-bar.markers-panel-filter-container { + margin: 10px 20px; +} + .markers-panel .markers-panel-container .message-box-container { line-height: 22px; padding-left: 20px; @@ -132,17 +147,13 @@ margin-left: 6px; } -.markers-panel .monaco-tl-contents .marker-icon { +.markers-panel .monaco-tl-contents .codicon { margin-right: 6px; display: flex; align-items: center; justify-content: center; } -.markers-panel .monaco-tl-contents .actions .action-item { - margin-right: 2px; -} - .markers-panel .markers-panel-container .tree-container .monaco-tl-contents .marker-source, .markers-panel .markers-panel-container .tree-container .monaco-tl-contents .related-info-resource, .markers-panel .markers-panel-container .tree-container .monaco-tl-contents .related-info-resource-separator, @@ -155,7 +166,7 @@ font-weight: bold; } -.markers-panel .monaco-tl-contents .marker-icon { +.markers-panel .monaco-tl-contents .codicon { height: 22px; } @@ -178,9 +189,6 @@ .markers-panel .monaco-tl-contents .multiline-actions .action-label, .markers-panel .monaco-tl-contents .actions .action-label { width: 16px; - height: 100%; - background-position: 50% 50%; - background-repeat: no-repeat; } .markers-panel .monaco-tl-contents .multiline-actions .action-label { diff --git a/src/vs/workbench/contrib/markers/browser/messages.ts b/src/vs/workbench/contrib/markers/browser/messages.ts index 4e7c54e36cad..baf1d1c2c220 100644 --- a/src/vs/workbench/contrib/markers/browser/messages.ts +++ b/src/vs/workbench/contrib/markers/browser/messages.ts @@ -16,15 +16,19 @@ export default class Messages { public static PROBLEMS_PANEL_CONFIGURATION_TITLE: string = nls.localize('problems.panel.configuration.title', "Problems View"); public static PROBLEMS_PANEL_CONFIGURATION_AUTO_REVEAL: string = nls.localize('problems.panel.configuration.autoreveal', "Controls whether Problems view should automatically reveal files when opening them."); + public static PROBLEMS_PANEL_CONFIGURATION_SHOW_CURRENT_STATUS: string = nls.localize('problems.panel.configuration.showCurrentInStatus', "When enabled shows the current problem in the status bar."); public static MARKERS_PANEL_TITLE_PROBLEMS: string = nls.localize('markers.panel.title.problems', "Problems"); public static MARKERS_PANEL_NO_PROBLEMS_BUILT: string = nls.localize('markers.panel.no.problems.build', "No problems have been detected in the workspace so far."); public static MARKERS_PANEL_NO_PROBLEMS_FILTERS: string = nls.localize('markers.panel.no.problems.filters', "No results found with provided filter criteria."); - public static MARKERS_PANEL_NO_PROBLEMS_FILE_EXCLUSIONS_FILTER: string = nls.localize('markers.panel.no.problems.file.exclusions', "All problems are hidden because files exclude filter is enabled."); - public static MARKERS_PANEL_ACTION_TOOLTIP_USE_FILES_EXCLUDE: string = nls.localize('markers.panel.action.useFilesExclude', "Filter using Files Exclude Setting"); - public static MARKERS_PANEL_ACTION_TOOLTIP_DO_NOT_USE_FILES_EXCLUDE: string = nls.localize('markers.panel.action.donotUseFilesExclude', "Do not use Files Exclude Setting"); + public static MARKERS_PANEL_ACTION_TOOLTIP_MORE_FILTERS: string = nls.localize('markers.panel.action.moreFilters', "More Filters..."); + public static MARKERS_PANEL_FILTER_LABEL_SHOW_ERRORS: string = nls.localize('markers.panel.filter.showErrors', "Show Errors"); + public static MARKERS_PANEL_FILTER_LABEL_SHOW_WARNINGS: string = nls.localize('markers.panel.filter.showWarnings', "Show Warnings"); + public static MARKERS_PANEL_FILTER_LABEL_SHOW_INFOS: string = nls.localize('markers.panel.filter.showInfos', "Show Infos"); + public static MARKERS_PANEL_FILTER_LABEL_EXCLUDED_FILES: string = nls.localize('markers.panel.filter.useFilesExclude', "Hide Excluded Files"); + public static MARKERS_PANEL_FILTER_LABEL_ACTIVE_FILE: string = nls.localize('markers.panel.filter.activeFile', "Show Active File Only"); public static MARKERS_PANEL_ACTION_TOOLTIP_FILTER: string = nls.localize('markers.panel.action.filter', "Filter Problems"); public static MARKERS_PANEL_ACTION_TOOLTIP_QUICKFIX: string = nls.localize('markers.panel.action.quickfix', "Show fixes"); public static MARKERS_PANEL_FILTER_ARIA_LABEL: string = nls.localize('markers.panel.filter.ariaLabel', "Filter Problems"); diff --git a/src/vs/workbench/contrib/outline/browser/outline.contribution.ts b/src/vs/workbench/contrib/outline/browser/outline.contribution.ts index 893dca149bf1..1c5c3db2bccd 100644 --- a/src/vs/workbench/contrib/outline/browser/outline.contribution.ts +++ b/src/vs/workbench/contrib/outline/browser/outline.contribution.ts @@ -5,16 +5,18 @@ import { localize } from 'vs/nls'; import { IViewsRegistry, IViewDescriptor, Extensions as ViewExtensions } from 'vs/workbench/common/views'; -import { OutlinePanel } from './outlinePanel'; +import { OutlinePane } from './outlinePane'; import { VIEW_CONTAINER } from 'vs/workbench/contrib/files/common/files'; import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; import { OutlineConfigKeys, OutlineViewId } from 'vs/editor/contrib/documentSymbols/outline'; +// import './outlineNavigation'; + const _outlineDesc = { id: OutlineViewId, name: localize('name', "Outline"), - ctorDescriptor: { ctor: OutlinePanel }, + ctorDescriptor: { ctor: OutlinePane }, canToggleVisibility: true, hideByDefault: false, collapsed: true, @@ -51,135 +53,153 @@ Registry.as(ConfigurationExtensions.Configuration).regis 'type': 'boolean', 'default': true }, - 'outline.filteredTypes.file': { + 'outline.showFiles': { type: 'boolean', + overridable: true, default: true, - markdownDescription: localize('filteredTypes.file', "When set to `false` outline never shows `file`-symbols.") + markdownDescription: localize('filteredTypes.file', "When enabled outline shows `file`-symbols.") }, - 'outline.filteredTypes.module': { + 'outline.showModules': { type: 'boolean', + overridable: true, default: true, - markdownDescription: localize('filteredTypes.module', "When set to `false` outline never shows `module`-symbols.") + markdownDescription: localize('filteredTypes.module', "When enabled outline shows `module`-symbols.") }, - 'outline.filteredTypes.namespace': { + 'outline.showNamespaces': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.namespace', "When set to `false` outline never shows `namespace`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.namespace', "When enabled outline shows `namespace`-symbols.") }, - 'outline.filteredTypes.package': { + 'outline.showPackages': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.package', "When set to `false` outline never shows `package`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.package', "When enabled outline shows `package`-symbols.") }, - 'outline.filteredTypes.class': { + 'outline.showClasses': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.class', "When set to `false` outline never shows `class`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.class', "When enabled outline shows `class`-symbols.") }, - 'outline.filteredTypes.method': { + 'outline.showMethods': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.method', "When set to `false` outline never shows `method`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.method', "When enabled outline shows `method`-symbols.") }, - 'outline.filteredTypes.property': { + 'outline.showProperties': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.property', "When set to `false` outline never shows `property`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.property', "When enabled outline shows `property`-symbols.") }, - 'outline.filteredTypes.field': { + 'outline.showFields': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.field', "When set to `false` outline never shows `field`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.field', "When enabled outline shows `field`-symbols.") }, - 'outline.filteredTypes.constructor': { + 'outline.showConstructors': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.constructor', "When set to `false` outline never shows `constructor`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.constructor', "When enabled outline shows `constructor`-symbols.") }, - 'outline.filteredTypes.enum': { + 'outline.showEnums': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.enum', "When set to `false` outline never shows `enum`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.enum', "When enabled outline shows `enum`-symbols.") }, - 'outline.filteredTypes.interface': { + 'outline.showInterfaces': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.interface', "When set to `false` outline never shows `interface`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.interface', "When enabled outline shows `interface`-symbols.") }, - 'outline.filteredTypes.function': { + 'outline.showFunctions': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.function', "When set to `false` outline never shows `function`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.function', "When enabled outline shows `function`-symbols.") }, - 'outline.filteredTypes.variable': { + 'outline.showVariables': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.variable', "When set to `false` outline never shows `variable`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.variable', "When enabled outline shows `variable`-symbols.") }, - 'outline.filteredTypes.constant': { + 'outline.showConstants': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.constant', "When set to `false` outline never shows `constant`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.constant', "When enabled outline shows `constant`-symbols.") }, - 'outline.filteredTypes.string': { + 'outline.showStrings': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.string', "When set to `false` outline never shows `string`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.string', "When enabled outline shows `string`-symbols.") }, - 'outline.filteredTypes.number': { + 'outline.showNumbers': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.number', "When set to `false` outline never shows `number`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.number', "When enabled outline shows `number`-symbols.") }, - 'outline.filteredTypes.boolean': { + 'outline.showBooleans': { type: 'boolean', + overridable: true, default: true, - markdownDescription: localize('filteredTypes.boolean', "When set to `false` outline never shows `boolean`-symbols.") + markdownDescription: localize('filteredTypes.boolean', "When enabled outline shows `boolean`-symbols.") }, - 'outline.filteredTypes.array': { + 'outline.showArrays': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.array', "When set to `false` outline never shows `array`-symbols.") + overridable: true, + markdownDescription: localize('filteredTypes.array', "When enabled outline shows `array`-symbols.") }, - 'outline.filteredTypes.object': { + 'outline.showObjects': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.object', "When set to `false` outline never shows `object`-symbols.") + markdownDescription: localize('filteredTypes.object', "When enabled outline shows `object`-symbols.") }, - 'outline.filteredTypes.key': { + 'outline.showKeys': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.key', "When set to `false` outline never shows `key`-symbols.") + markdownDescription: localize('filteredTypes.key', "When enabled outline shows `key`-symbols.") }, - 'outline.filteredTypes.null': { + 'outline.showNull': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.null', "When set to `false` outline never shows `null`-symbols.") + markdownDescription: localize('filteredTypes.null', "When enabled outline shows `null`-symbols.") }, - 'outline.filteredTypes.enumMember': { + 'outline.showEnumMembers': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.enumMember', "When set to `false` outline never shows `enumMember`-symbols.") + markdownDescription: localize('filteredTypes.enumMember', "When enabled outline shows `enumMember`-symbols.") }, - 'outline.filteredTypes.struct': { + 'outline.showStructs': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.struct', "When set to `false` outline never shows `struct`-symbols.") + markdownDescription: localize('filteredTypes.struct', "When enabled outline shows `struct`-symbols.") }, - 'outline.filteredTypes.event': { + 'outline.showEvents': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.event', "When set to `false` outline never shows `event`-symbols.") + markdownDescription: localize('filteredTypes.event', "When enabled outline shows `event`-symbols.") }, - 'outline.filteredTypes.operator': { + 'outline.showOperators': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.operator', "When set to `false` outline never shows `operator`-symbols.") + markdownDescription: localize('filteredTypes.operator', "When enabled outline shows `operator`-symbols.") }, - 'outline.filteredTypes.typeParameter': { + 'outline.showTypeParameters': { type: 'boolean', default: true, - markdownDescription: localize('filteredTypes.typeParameter', "When set to `false` outline never shows `typeParameter`-symbols.") + markdownDescription: localize('filteredTypes.typeParameter', "When enabled outline shows `typeParameter`-symbols.") } } }); diff --git a/src/vs/workbench/contrib/outline/browser/outlineNavigation.ts b/src/vs/workbench/contrib/outline/browser/outlineNavigation.ts new file mode 100644 index 000000000000..a0b2d8722d14 --- /dev/null +++ b/src/vs/workbench/contrib/outline/browser/outlineNavigation.ts @@ -0,0 +1,202 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Range } from 'vs/editor/common/core/range'; +import { IPosition, Position } from 'vs/editor/common/core/position'; +import { OutlineElement, OutlineModel, TreeElement } from 'vs/editor/contrib/documentSymbols/outlineModel'; +import { localize } from 'vs/nls'; +import { IEditorContribution, ScrollType } from 'vs/editor/common/editorCommon'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { EditorStateCancellationTokenSource, CodeEditorStateFlag } from 'vs/editor/browser/core/editorState'; +import { EditorAction, registerEditorAction, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { forEach } from 'vs/base/common/collections'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { OutlineFilter } from 'vs/editor/contrib/documentSymbols/outlineTree'; +import { binarySearch } from 'vs/base/common/arrays'; +import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; + +class FlatOutline { + + readonly elements: OutlineElement[] = []; + readonly _elementPositions: IPosition[]; + + constructor(model: OutlineModel, filter: OutlineFilter) { + + const walk = (element: TreeElement) => { + if (element instanceof OutlineElement && !filter.filter(element)) { + return; + } + if (element instanceof OutlineElement) { + this.elements.push(element); + } + forEach(element.children, entry => walk(entry.value)); + }; + + walk(model); + this.elements.sort(FlatOutline._compare); + this._elementPositions = this.elements.map(element => ({ + lineNumber: element.symbol.range.startLineNumber, + column: element.symbol.range.startColumn + })); + } + + private static _compare(a: TreeElement, b: TreeElement): number { + return (a instanceof OutlineElement && b instanceof OutlineElement) + ? Range.compareRangesUsingStarts(a.symbol.range, b.symbol.range) + : 0; + } + + find(position: IPosition, preferAfter: boolean): number { + const idx = binarySearch(this._elementPositions, position, Position.compare); + if (idx >= 0) { + return idx; + } else if (preferAfter) { + return ~idx; + } else { + return ~idx - 1; + } + } +} + +export class OutlineNavigation implements IEditorContribution { + + public static readonly ID = 'editor.contrib.OutlineNavigation'; + + public static get(editor: ICodeEditor): OutlineNavigation { + return editor.getContribution(OutlineNavigation.ID); + } + + private readonly _editor: ICodeEditor; + + private _cts?: CancellationTokenSource; + + constructor( + editor: ICodeEditor, + @ITextResourceConfigurationService private readonly _textResourceConfigService: ITextResourceConfigurationService, + ) { + this._editor = editor; + } + + dispose(): void { + if (this._cts) { + this._cts.dispose(true); + } + } + + async goto(up: boolean) { + + if (this._cts) { + this._cts.dispose(true); + } + + if (!this._editor.hasModel()) { + return; + } + + const textModel = this._editor.getModel(); + const position = this._editor.getPosition(); + + this._cts = new EditorStateCancellationTokenSource(this._editor, CodeEditorStateFlag.Position | CodeEditorStateFlag.Value | CodeEditorStateFlag.Scroll); + + const filter = new OutlineFilter('outline', this._textResourceConfigService); + const outlineModel = await OutlineModel.create(textModel, this._cts.token); + + if (this._cts.token.isCancellationRequested) { + return; + } + + const symbols = new FlatOutline(outlineModel, filter); + const idx = symbols.find(position, !up); + const element = symbols.elements[idx]; + + if (element) { + if (Range.containsPosition(element.symbol.selectionRange, position)) { + // at the "name" of a symbol -> move + const nextElement = symbols.elements[idx + (up ? -1 : +1)]; + this._revealElement(nextElement); + + } else { + // enclosing, lastBefore, or firstAfter element + this._revealElement(element); + } + } + } + + private _revealElement(element: OutlineElement | undefined): void { + if (!element) { + return; + } + const pos = Range.lift(element.symbol.selectionRange).getStartPosition(); + this._editor.setPosition(pos); + this._editor.revealPosition(pos, ScrollType.Smooth); + + const modelNow = this._editor.getModel(); + const ids = this._editor.deltaDecorations([], [{ + range: element.symbol.selectionRange, + options: { + className: 'symbolHighlight', + } + }]); + setTimeout(() => { + if (modelNow === this._editor.getModel()) { + this._editor.deltaDecorations(ids, []); + } + }, 350); + } +} + +registerEditorContribution(OutlineNavigation.ID, OutlineNavigation); + +registerEditorAction(class extends EditorAction { + + constructor() { + super({ + id: 'editor.action.gotoNextSymbol', + label: localize('label.next', "Go to Next Symbol"), + alias: 'Go to Next Symbol', + precondition: EditorContextKeys.hasDocumentSymbolProvider, + kbOpts: { + weight: KeybindingWeight.EditorContrib, + kbExpr: EditorContextKeys.focus, + primary: undefined, + mac: { + primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.DownArrow, + }, + } + }); + } + + run(_accessor: ServicesAccessor, editor: ICodeEditor): void | Promise { + OutlineNavigation.get(editor).goto(false); + } +}); + +registerEditorAction(class extends EditorAction { + + constructor() { + super({ + id: 'editor.action.gotoPrevSymbol', + label: localize('label.prev', "Go to Previous Symbol"), + alias: 'Go to Previous Symbol', + precondition: EditorContextKeys.hasDocumentSymbolProvider, + kbOpts: { + weight: KeybindingWeight.EditorContrib, + kbExpr: EditorContextKeys.focus, + primary: undefined, + mac: { + primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.UpArrow, + }, + } + }); + } + + run(_accessor: ServicesAccessor, editor: ICodeEditor): void | Promise { + OutlineNavigation.get(editor).goto(true); + } +}); diff --git a/src/vs/workbench/contrib/outline/browser/outlinePanel.css b/src/vs/workbench/contrib/outline/browser/outlinePane.css similarity index 67% rename from src/vs/workbench/contrib/outline/browser/outlinePanel.css rename to src/vs/workbench/contrib/outline/browser/outlinePane.css index 1bb11faee324..dfe10601a5f6 100644 --- a/src/vs/workbench/contrib/outline/browser/outlinePanel.css +++ b/src/vs/workbench/contrib/outline/browser/outlinePane.css @@ -3,45 +3,45 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.monaco-workbench .outline-panel { +.monaco-workbench .outline-pane { display: flex; flex-direction: column; } -.monaco-workbench .outline-panel .outline-progress { +.monaco-workbench .outline-pane .outline-progress { width: 100%; height: 2px; padding-bottom: 3px; position: absolute; } -.monaco-workbench .outline-panel .outline-progress .monaco-progress-container { +.monaco-workbench .outline-pane .outline-progress .monaco-progress-container { height: 2px; } -.monaco-workbench .outline-panel .outline-progress .monaco-progress-container .progress-bit { +.monaco-workbench .outline-pane .outline-progress .monaco-progress-container .progress-bit { height: 2px; } -.monaco-workbench .outline-panel .outline-tree { +.monaco-workbench .outline-pane .outline-tree { height: 100%; } -.monaco-workbench .outline-panel .outline-message { +.monaco-workbench .outline-pane .outline-message { display: none; padding: 10px 22px 0 22px; opacity: 0.5; } -.monaco-workbench .outline-panel.message .outline-message { +.monaco-workbench .outline-pane.message .outline-message { display: inherit; } -.monaco-workbench .outline-panel.message .outline-progress { +.monaco-workbench .outline-pane.message .outline-progress { display: none; } -.monaco-workbench .outline-panel.message .outline-tree { +.monaco-workbench .outline-pane.message .outline-tree { display: none; } diff --git a/src/vs/workbench/contrib/outline/browser/outlinePanel.ts b/src/vs/workbench/contrib/outline/browser/outlinePane.ts similarity index 95% rename from src/vs/workbench/contrib/outline/browser/outlinePanel.ts rename to src/vs/workbench/contrib/outline/browser/outlinePane.ts index 16792ed2c17a..066171f16355 100644 --- a/src/vs/workbench/contrib/outline/browser/outlinePanel.ts +++ b/src/vs/workbench/contrib/outline/browser/outlinePane.ts @@ -14,7 +14,7 @@ import { defaultGenerator } from 'vs/base/common/idGenerator'; import { dispose, IDisposable, toDisposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; import { LRUCache } from 'vs/base/common/map'; import { escape } from 'vs/base/common/strings'; -import 'vs/css!./outlinePanel'; +import 'vs/css!./outlinePane'; import { ICodeEditor, isCodeEditor, isDiffEditor } from 'vs/editor/browser/editorBrowser'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; @@ -34,7 +34,7 @@ import { WorkbenchDataTree } from 'vs/platform/list/browser/listService'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { attachProgressBarStyler } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { ViewletPanel } from 'vs/workbench/browser/parts/views/panelViewlet'; +import { ViewletPane } from 'vs/workbench/browser/parts/views/paneViewlet'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { CollapseAction } from 'vs/workbench/browser/viewlet'; import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; @@ -47,6 +47,7 @@ import { basename } from 'vs/base/common/resources'; import { IDataSource } from 'vs/base/browser/ui/tree/tree'; import { IMarkerDecorationsService } from 'vs/editor/common/services/markersDecorationService'; import { MarkerSeverity } from 'vs/platform/markers/common/markers'; +import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; class RequestState { @@ -232,7 +233,7 @@ class OutlineViewState { } } -export class OutlinePanel extends ViewletPanel { +export class OutlinePane extends ViewletPane { private _disposables = new Array(); @@ -296,7 +297,7 @@ export class OutlinePanel extends ViewletPanel { protected renderBody(container: HTMLElement): void { this._domNode = container; this._domNode.tabIndex = 0; - dom.addClass(container, 'outline-panel'); + dom.addClass(container, 'outline-pane'); let progressContainer = dom.$('.outline-progress'); this._message = dom.$('.outline-message'); @@ -314,10 +315,10 @@ export class OutlinePanel extends ViewletPanel { this._treeRenderer = this._instantiationService.createInstance(OutlineElementRenderer); this._treeDataSource = new OutlineDataSource(); this._treeComparator = new OutlineItemComparator(this._outlineViewState.sortBy); - this._treeFilter = this._instantiationService.createInstance(OutlineFilter, 'outline.filteredTypes'); + this._treeFilter = this._instantiationService.createInstance(OutlineFilter, 'outline'); this._tree = this._instantiationService.createInstance( WorkbenchDataTree, - 'OutlinePanel', + 'OutlinePane', treeContainer, new OutlineVirtualDelegate(), [new OutlineGroupRenderer(), this._treeRenderer], @@ -330,7 +331,11 @@ export class OutlinePanel extends ViewletPanel { sorter: this._treeComparator, filter: this._treeFilter, identityProvider: new OutlineIdentityProvider(), - keyboardNavigationLabelProvider: new OutlineNavigationLabelProvider() + keyboardNavigationLabelProvider: new OutlineNavigationLabelProvider(), + hideTwistiesOfChildlessElements: true, + overrideStyles: { + listBackground: SIDE_BAR_BACKGROUND + } } ); @@ -367,8 +372,9 @@ export class OutlinePanel extends ViewletPanel { if (e.affectsConfiguration(OutlineConfigKeys.icons)) { this._tree.updateChildren(); } - if (e.affectsConfiguration('outline.filteredTypes')) { - this._treeFilter.update(); + // This is a temporary solution to try and minimize refilters while + // ConfigurationChangeEvents only provide the first section of the config path. + if (e.affectedKeys.some(key => key.search(/(outline|\[\w+\])/) === 0)) { this._tree.refilter(); } })); @@ -400,7 +406,7 @@ export class OutlinePanel extends ViewletPanel { const group = this._register(new RadioGroup([ new SimpleToggleAction(this._outlineViewState, localize('sortByPosition', "Sort By: Position"), () => this._outlineViewState.sortBy === OutlineSortOrder.ByPosition, _ => this._outlineViewState.sortBy = OutlineSortOrder.ByPosition), new SimpleToggleAction(this._outlineViewState, localize('sortByName', "Sort By: Name"), () => this._outlineViewState.sortBy === OutlineSortOrder.ByName, _ => this._outlineViewState.sortBy = OutlineSortOrder.ByName), - new SimpleToggleAction(this._outlineViewState, localize('sortByKind', "Sort By: Type"), () => this._outlineViewState.sortBy === OutlineSortOrder.ByKind, _ => this._outlineViewState.sortBy = OutlineSortOrder.ByKind), + new SimpleToggleAction(this._outlineViewState, localize('sortByKind', "Sort By: Category"), () => this._outlineViewState.sortBy === OutlineSortOrder.ByKind, _ => this._outlineViewState.sortBy = OutlineSortOrder.ByKind), ])); const result = [ new SimpleToggleAction(this._outlineViewState, localize('followCur', "Follow Cursor"), () => this._outlineViewState.followCursor, action => this._outlineViewState.followCursor = action.checked), @@ -465,15 +471,19 @@ export class OutlinePanel extends ViewletPanel { } const textModel = editor.getModel(); - const loadingMessage = oldModel && new TimeoutTimer( - () => this._showMessage(localize('loading', "Loading document symbols for '{0}'...", basename(textModel.uri))), - 100 - ); + + let loadingMessage: IDisposable | undefined; + if (!oldModel) { + loadingMessage = new TimeoutTimer( + () => this._showMessage(localize('loading', "Loading document symbols for '{0}'...", basename(textModel.uri))), + 100 + ); + } const requestDelay = OutlineModel.getRequestDelay(textModel); this._progressBar.infinite().show(requestDelay); - const createdModel = await OutlinePanel._createOutlineModel(textModel, this._editorDisposables); + const createdModel = await OutlinePane._createOutlineModel(textModel, this._editorDisposables); dispose(loadingMessage); if (!createdModel) { return; diff --git a/src/vs/workbench/contrib/output/browser/logViewer.ts b/src/vs/workbench/contrib/output/browser/logViewer.ts index c0ce08988d64..d75c6574dbd8 100644 --- a/src/vs/workbench/contrib/output/browser/logViewer.ts +++ b/src/vs/workbench/contrib/output/browser/logViewer.ts @@ -11,21 +11,18 @@ import { ITextResourceConfigurationService } from 'vs/editor/common/services/res import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { AbstractTextResourceEditor } from 'vs/workbench/browser/parts/editor/textResourceEditor'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; import { URI } from 'vs/base/common/uri'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { LOG_SCHEME, IFileOutputChannelDescriptor } from 'vs/workbench/contrib/output/common/output'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IHostService } from 'vs/workbench/services/host/browser/host'; export class LogViewerInput extends ResourceEditorInput { public static readonly ID = 'workbench.editorinputs.output'; - constructor(private outputChannelDescriptor: IFileOutputChannelDescriptor, + constructor(private readonly outputChannelDescriptor: IFileOutputChannelDescriptor, @ITextModelService textModelResolverService: ITextModelService ) { super(basename(outputChannelDescriptor.file.path), dirname(outputChannelDescriptor.file.path), URI.from({ scheme: LOG_SCHEME, path: outputChannelDescriptor.id }), undefined, textModelResolverService); @@ -48,15 +45,12 @@ export class LogViewer extends AbstractTextResourceEditor { @ITelemetryService telemetryService: ITelemetryService, @IInstantiationService instantiationService: IInstantiationService, @IStorageService storageService: IStorageService, - @IConfigurationService baseConfigurationService: IConfigurationService, @ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService, @IThemeService themeService: IThemeService, @IEditorGroupsService editorGroupService: IEditorGroupsService, - @ITextFileService textFileService: ITextFileService, - @IEditorService editorService: IEditorService, - @IHostService hostService: IHostService + @IEditorService editorService: IEditorService ) { - super(LogViewer.LOG_VIEWER_EDITOR_ID, telemetryService, instantiationService, storageService, textResourceConfigurationService, themeService, editorGroupService, textFileService, editorService, hostService); + super(LogViewer.LOG_VIEWER_EDITOR_ID, telemetryService, instantiationService, storageService, textResourceConfigurationService, themeService, editorGroupService, editorService); } protected getConfigurationOverrides(): IEditorOptions { diff --git a/src/vs/workbench/contrib/output/browser/output.contribution.ts b/src/vs/workbench/contrib/output/browser/output.contribution.ts index e799362b6e1e..2102a266f2e8 100644 --- a/src/vs/workbench/contrib/output/browser/output.contribution.ts +++ b/src/vs/workbench/contrib/output/browser/output.contribution.ts @@ -41,7 +41,7 @@ ModesRegistry.registerLanguage({ }); // Register Output Panel -Registry.as(Extensions.Panels).registerPanel(new PanelDescriptor( +Registry.as(Extensions.Panels).registerPanel(PanelDescriptor.create( OutputPanel, OUTPUT_PANEL_ID, nls.localize('output', "Output"), @@ -51,7 +51,7 @@ Registry.as(Extensions.Panels).registerPanel(new PanelDescriptor( )); Registry.as(EditorExtensions.Editors).registerEditor( - new EditorDescriptor( + EditorDescriptor.create( LogViewer, LogViewer.LOG_VIEWER_EDITOR_ID, nls.localize('logViewer', "Log Viewer") @@ -74,19 +74,19 @@ Registry.as(WorkbenchExtensions.Workbench).regi // register toggle output action globally const actionRegistry = Registry.as(ActionExtensions.WorkbenchActions); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ToggleOutputAction, ToggleOutputAction.ID, ToggleOutputAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleOutputAction, ToggleOutputAction.ID, ToggleOutputAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_U, linux: { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_H) // On Ubuntu Ctrl+Shift+U is taken by some global OS command } }), 'View: Toggle Output', nls.localize('viewCategory', "View")); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ClearOutputAction, ClearOutputAction.ID, ClearOutputAction.LABEL), +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ClearOutputAction, ClearOutputAction.ID, ClearOutputAction.LABEL), 'View: Clear Output', nls.localize('viewCategory', "View")); const devCategory = nls.localize('developer', "Developer"); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ShowLogsOutputChannelAction, ShowLogsOutputChannelAction.ID, ShowLogsOutputChannelAction.LABEL), 'Developer: Show Logs...', devCategory); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenOutputLogFileAction, OpenOutputLogFileAction.ID, OpenOutputLogFileAction.LABEL), 'Developer: Open Log File...', devCategory); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ShowLogsOutputChannelAction, ShowLogsOutputChannelAction.ID, ShowLogsOutputChannelAction.LABEL), 'Developer: Show Logs...', devCategory); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(OpenOutputLogFileAction, OpenOutputLogFileAction.ID, OpenOutputLogFileAction.LABEL), 'Developer: Open Log File...', devCategory); // Define clear command, contribute to editor context menu registerAction({ diff --git a/src/vs/workbench/contrib/output/browser/outputActions.ts b/src/vs/workbench/contrib/output/browser/outputActions.ts index ca321d4bb374..6d09db645a59 100644 --- a/src/vs/workbench/contrib/output/browser/outputActions.ts +++ b/src/vs/workbench/contrib/output/browser/outputActions.ts @@ -21,6 +21,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { LogViewerInput } from 'vs/workbench/contrib/output/browser/logViewer'; import { ISelectOptionItem } from 'vs/base/browser/ui/selectBox/selectBox'; +import { assertIsDefined } from 'vs/base/common/types'; export class ToggleOutputAction extends TogglePanelAction { @@ -262,7 +263,8 @@ export class OpenOutputLogFileAction extends Action { return this.quickInputService.pick(entries, { placeHolder: nls.localize('selectlogFile', "Select Log file") }) .then(entry => { if (entry) { - return this.editorService.openEditor(this.instantiationService.createInstance(LogViewerInput, entry.channel)).then(() => undefined); + assertIsDefined(entry.channel.file); + return this.editorService.openEditor(this.instantiationService.createInstance(LogViewerInput, entry.channel as IFileOutputChannelDescriptor)).then(() => undefined); } return undefined; }); diff --git a/src/vs/workbench/contrib/output/browser/outputPanel.ts b/src/vs/workbench/contrib/output/browser/outputPanel.ts index 463307df0b89..840d46c3a107 100644 --- a/src/vs/workbench/contrib/output/browser/outputPanel.ts +++ b/src/vs/workbench/contrib/output/browser/outputPanel.ts @@ -19,12 +19,10 @@ import { AbstractTextResourceEditor } from 'vs/workbench/browser/parts/editor/te import { OUTPUT_PANEL_ID, IOutputService, CONTEXT_IN_OUTPUT } from 'vs/workbench/contrib/output/common/output'; import { SwitchOutputAction, SwitchOutputActionViewItem, ClearOutputAction, ToggleOrSetOutputScrollLockAction, OpenLogOutputFile } from 'vs/workbench/contrib/output/browser/outputActions'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IHostService } from 'vs/workbench/services/host/browser/host'; import { CursorChangeReason } from 'vs/editor/common/controller/cursorEvents'; export class OutputPanel extends AbstractTextResourceEditor { @@ -42,11 +40,9 @@ export class OutputPanel extends AbstractTextResourceEditor { @IOutputService private readonly outputService: IOutputService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IEditorGroupsService editorGroupService: IEditorGroupsService, - @ITextFileService textFileService: ITextFileService, - @IEditorService editorService: IEditorService, - @IHostService hostService: IHostService + @IEditorService editorService: IEditorService ) { - super(OUTPUT_PANEL_ID, telemetryService, instantiationService, storageService, textResourceConfigurationService, themeService, editorGroupService, textFileService, editorService, hostService); + super(OUTPUT_PANEL_ID, telemetryService, instantiationService, storageService, textResourceConfigurationService, themeService, editorGroupService, editorService); this.scopedInstantiationService = instantiationService; } diff --git a/src/vs/workbench/contrib/performance/electron-browser/startupProfiler.ts b/src/vs/workbench/contrib/performance/electron-browser/startupProfiler.ts index e63de97ca4ed..199997ee7c7e 100644 --- a/src/vs/workbench/contrib/performance/electron-browser/startupProfiler.ts +++ b/src/vs/workbench/contrib/performance/electron-browser/startupProfiler.ts @@ -18,6 +18,7 @@ import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService import { URI } from 'vs/base/common/uri'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IElectronService } from 'vs/platform/electron/node/electron'; +import { IProductService } from 'vs/platform/product/common/productService'; export class StartupProfiler implements IWorkbenchContribution { @@ -29,7 +30,8 @@ export class StartupProfiler implements IWorkbenchContribution { @ILifecycleService lifecycleService: ILifecycleService, @IExtensionService extensionService: IExtensionService, @IOpenerService private readonly _openerService: IOpenerService, - @IElectronService private readonly _electronService: IElectronService + @IElectronService private readonly _electronService: IElectronService, + @IProductService private readonly _productService: IProductService ) { // wait for everything to be ready Promise.all([ @@ -88,7 +90,7 @@ export class StartupProfiler implements IWorkbenchContribution { return this._dialogService.confirm({ type: 'info', message: localize('prof.thanks', "Thanks for helping us."), - detail: localize('prof.detail.restart', "A final restart is required to continue to use '{0}'. Again, thank you for your contribution.", this._environmentService.appNameLong), + detail: localize('prof.detail.restart', "A final restart is required to continue to use '{0}'. Again, thank you for your contribution.", this._productService.nameLong), primaryButton: localize('prof.restart', "Restart"), secondaryButton: undefined }).then(() => { diff --git a/src/vs/workbench/contrib/preferences/browser/keybindingWidgets.ts b/src/vs/workbench/contrib/preferences/browser/keybindingWidgets.ts index 9b466b2ac373..655ff9e19410 100644 --- a/src/vs/workbench/contrib/preferences/browser/keybindingWidgets.ts +++ b/src/vs/workbench/contrib/preferences/browser/keybindingWidgets.ts @@ -82,7 +82,7 @@ export class KeybindingsSearchWidget extends SearchWidget { stopRecordingKeys(): void { this._reset(); - this.recordDisposables.dispose(); + this.recordDisposables.clear(); } setInputValue(value: string): void { @@ -164,7 +164,7 @@ export class DefineKeybindingWidget extends Widget { readonly onShowExistingKeybidings: Event = this._onShowExistingKeybindings.event; constructor( - parent: HTMLElement, + parent: HTMLElement | null, @IInstantiationService private readonly instantiationService: IInstantiationService, @IThemeService private readonly themeService: IThemeService ) { diff --git a/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts b/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts index e05513d79d56..af18b1667d29 100644 --- a/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts +++ b/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts @@ -34,7 +34,7 @@ import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } import { IContextKeyService, IContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { StandardKeyboardEvent, IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode, ResolvedKeybinding } from 'vs/base/common/keyCodes'; -import { listHighlightForeground, badgeBackground, contrastBorder, badgeForeground, listActiveSelectionForeground, listInactiveSelectionForeground, listHoverForeground, listFocusForeground } from 'vs/platform/theme/common/colorRegistry'; +import { listHighlightForeground, badgeBackground, contrastBorder, badgeForeground, listActiveSelectionForeground, listInactiveSelectionForeground, listHoverForeground, listFocusForeground, editorBackground } from 'vs/platform/theme/common/colorRegistry'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; import { WorkbenchList } from 'vs/platform/list/browser/listService'; @@ -65,22 +65,22 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor private _onLayout: Emitter = this._register(new Emitter()); readonly onLayout: Event = this._onLayout.event; - private keybindingsEditorModel: KeybindingsEditorModel; + private keybindingsEditorModel: KeybindingsEditorModel | null = null; - private headerContainer: HTMLElement; - private actionsContainer: HTMLElement; - private searchWidget: KeybindingsSearchWidget; + private headerContainer!: HTMLElement; + private actionsContainer!: HTMLElement; + private searchWidget!: KeybindingsSearchWidget; - private overlayContainer: HTMLElement; - private defineKeybindingWidget: DefineKeybindingWidget; + private overlayContainer!: HTMLElement; + private defineKeybindingWidget!: DefineKeybindingWidget; private columnItems: ColumnItem[] = []; - private keybindingsListContainer: HTMLElement; - private unAssignedKeybindingItemToRevealAndFocus: IKeybindingItemEntry | null; - private listEntries: IListEntry[]; - private keybindingsList: WorkbenchList; + private keybindingsListContainer!: HTMLElement; + private unAssignedKeybindingItemToRevealAndFocus: IKeybindingItemEntry | null = null; + private listEntries: IListEntry[] = []; + private keybindingsList!: WorkbenchList; - private dimension: DOM.Dimension; + private dimension: DOM.Dimension | null = null; private delayedFiltering: Delayer; private latestEmptyFilters: string[] = []; private delayedFilterLogging: Delayer; @@ -88,11 +88,10 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor private keybindingFocusContextKey: IContextKey; private searchFocusContextKey: IContextKey; - private actionBar: ActionBar; - private sortByPrecedenceAction: Action; - private recordKeysAction: Action; + private readonly sortByPrecedenceAction: Action; + private readonly recordKeysAction: Action; - private ariaLabelElement: HTMLElement; + private ariaLabelElement!: HTMLElement; constructor( @ITelemetryService telemetryService: ITelemetryService, @@ -115,6 +114,16 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor this.searchFocusContextKey = CONTEXT_KEYBINDINGS_SEARCH_FOCUS.bindTo(this.contextKeyService); this.keybindingFocusContextKey = CONTEXT_KEYBINDING_FOCUS.bindTo(this.contextKeyService); this.delayedFilterLogging = new Delayer(1000); + + const recordKeysActionKeybinding = this.keybindingsService.lookupKeybinding(KEYBINDINGS_EDITOR_COMMAND_RECORD_SEARCH_KEYS); + const recordKeysActionLabel = localize('recordKeysLabel', "Record Keys"); + this.recordKeysAction = new Action(KEYBINDINGS_EDITOR_COMMAND_RECORD_SEARCH_KEYS, recordKeysActionKeybinding ? localize('recordKeysLabelWithKeybinding', "{0} ({1})", recordKeysActionLabel, recordKeysActionKeybinding.getLabel()) : recordKeysActionLabel, 'codicon-record-keys'); + this.recordKeysAction.checked = false; + + const sortByPrecedenceActionKeybinding = this.keybindingsService.lookupKeybinding(KEYBINDINGS_EDITOR_COMMAND_SORTBY_PRECEDENCE); + const sortByPrecedenceActionLabel = localize('sortByPrecedeneLabel', "Sort by Precedence"); + this.sortByPrecedenceAction = new Action('keybindings.editor.sortByPrecedence', sortByPrecedenceActionKeybinding ? localize('sortByPrecedeneLabelWithKeybinding', "{0} ({1})", sortByPrecedenceActionLabel, sortByPrecedenceActionKeybinding.getLabel()) : sortByPrecedenceActionLabel, 'codicon-sort-precedence'); + this.sortByPrecedenceAction.checked = false; } createEditor(parent: HTMLElement): void { @@ -298,7 +307,7 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor this.overlayContainer.style.position = 'absolute'; this.overlayContainer.style.zIndex = '10'; this.defineKeybindingWidget = this._register(this.instantiationService.createInstance(DefineKeybindingWidget, this.overlayContainer)); - this._register(this.defineKeybindingWidget.onDidChange(keybindingStr => this.defineKeybindingWidget.printExisting(this.keybindingsEditorModel.fetch(`"${keybindingStr}"`).length))); + this._register(this.defineKeybindingWidget.onDidChange(keybindingStr => this.defineKeybindingWidget.printExisting(this.keybindingsEditorModel!.fetch(`"${keybindingStr}"`).length))); this._register(this.defineKeybindingWidget.onShowExistingKeybidings(keybindingStr => this.searchWidget.setValue(`"${keybindingStr}"`))); this.hideOverlayContainer(); } @@ -337,10 +346,6 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor this.actionsContainer = DOM.append(searchContainer, DOM.$('.keybindings-search-actions-container')); const recordingBadge = this.createRecordingBadge(this.actionsContainer); - const sortByPrecedenceActionKeybinding = this.keybindingsService.lookupKeybinding(KEYBINDINGS_EDITOR_COMMAND_SORTBY_PRECEDENCE); - const sortByPrecedenceActionLabel = localize('sortByPrecedeneLabel', "Sort by Precedence"); - this.sortByPrecedenceAction = new Action('keybindings.editor.sortByPrecedence', sortByPrecedenceActionKeybinding ? localize('sortByPrecedeneLabelWithKeybinding', "{0} ({1})", sortByPrecedenceActionLabel, sortByPrecedenceActionKeybinding.getLabel()) : sortByPrecedenceActionLabel, 'codicon-sort-precedence'); - this.sortByPrecedenceAction.checked = false; this._register(this.sortByPrecedenceAction.onDidChange(e => { if (e.checked !== undefined) { this.renderKeybindingsEntries(false); @@ -348,10 +353,6 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor this.updateSearchOptions(); })); - const recordKeysActionKeybinding = this.keybindingsService.lookupKeybinding(KEYBINDINGS_EDITOR_COMMAND_RECORD_SEARCH_KEYS); - const recordKeysActionLabel = localize('recordKeysLabel', "Record Keys"); - this.recordKeysAction = new Action(KEYBINDINGS_EDITOR_COMMAND_RECORD_SEARCH_KEYS, recordKeysActionKeybinding ? localize('recordKeysLabelWithKeybinding', "{0} ({1})", recordKeysActionLabel, recordKeysActionKeybinding.getLabel()) : recordKeysActionLabel, 'codicon-record-keys'); - this.recordKeysAction.checked = false; this._register(this.recordKeysAction.onDidChange(e => { if (e.checked !== undefined) { DOM.toggleClass(recordingBadge, 'disabled', !e.checked); @@ -370,7 +371,7 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor } })); - this.actionBar = this._register(new ActionBar(this.actionsContainer, { + const actionBar = this._register(new ActionBar(this.actionsContainer, { animated: false, actionViewItemProvider: (action: Action) => { if (action.id === this.sortByPrecedenceAction.id) { @@ -383,7 +384,7 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor } })); - this.actionBar.push([this.recordKeysAction, this.sortByPrecedenceAction, clearInputAction], { label: false, icon: true }); + actionBar.push([this.recordKeysAction, this.sortByPrecedenceAction, clearInputAction], { label: false, icon: true }); } private updateSearchOptions(): void { @@ -456,7 +457,10 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor identityProvider: { getId: (e: IListEntry) => e.id }, ariaLabel: localize('keybindingsLabel', "Keybindings"), setRowLineHeight: false, - horizontalScrolling: false + horizontalScrolling: false, + overrideStyles: { + listBackground: editorBackground + } })); this._register(this.keybindingsList.onContextMenu(e => this.onContextMenu(e))); this._register(this.keybindingsList.onFocusChange(e => this.onFocusChange(e))); @@ -563,6 +567,9 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor } private layoutKeybindingsList(): void { + if (!this.dimension) { + return; + } let width = this.dimension.width - 27; for (const columnItem of this.columnItems) { if (columnItem.width && !columnItem.proportion) { @@ -855,7 +862,7 @@ abstract class Column extends Disposable { class ActionsColumn extends Column { - private actionBar: ActionBar; + private readonly actionBar: ActionBar; readonly element: HTMLElement; constructor( @@ -864,13 +871,8 @@ class ActionsColumn extends Column { @IKeybindingService private keybindingsService: IKeybindingService ) { super(keybindingsEditor); - this.element = this.create(parent); - } - - create(parent: HTMLElement): HTMLElement { - const actionsContainer = DOM.append(parent, $('.column.actions', { id: 'actions_' + ++Column.COUNTER })); - this.actionBar = new ActionBar(actionsContainer, { animated: false }); - return actionsContainer; + this.element = DOM.append(parent, $('.column.actions', { id: 'actions_' + ++Column.COUNTER })); + this.actionBar = new ActionBar(this.element, { animated: false }); } render(keybindingItemEntry: IKeybindingItemEntry): void { @@ -914,7 +916,7 @@ class ActionsColumn extends Column { class CommandColumn extends Column { - private commandColumn: HTMLElement; + private readonly commandColumn: HTMLElement; readonly element: HTMLElement; constructor( @@ -922,12 +924,7 @@ class CommandColumn extends Column { keybindingsEditor: IKeybindingsEditor, ) { super(keybindingsEditor); - this.element = this.create(parent); - } - - private create(parent: HTMLElement): HTMLElement { - this.commandColumn = DOM.append(parent, $('.column.command', { id: 'command_' + ++Column.COUNTER })); - return this.commandColumn; + this.element = this.commandColumn = DOM.append(parent, $('.column.command', { id: 'command_' + ++Column.COUNTER })); } render(keybindingItemEntry: IKeybindingItemEntry): void { @@ -962,7 +959,7 @@ class CommandColumn extends Column { class KeybindingColumn extends Column { - private keybindingLabel: HTMLElement; + private readonly keybindingLabel: HTMLElement; readonly element: HTMLElement; constructor( @@ -970,13 +967,9 @@ class KeybindingColumn extends Column { keybindingsEditor: IKeybindingsEditor, ) { super(keybindingsEditor); - this.element = this.create(parent); - } - private create(parent: HTMLElement): HTMLElement { - const column = DOM.append(parent, $('.column.keybinding', { id: 'keybinding_' + ++Column.COUNTER })); - this.keybindingLabel = DOM.append(column, $('div.keybinding-label')); - return column; + this.element = DOM.append(parent, $('.column.keybinding', { id: 'keybinding_' + ++Column.COUNTER })); + this.keybindingLabel = DOM.append(this.element, $('div.keybinding-label')); } render(keybindingItemEntry: IKeybindingItemEntry): void { @@ -994,7 +987,7 @@ class KeybindingColumn extends Column { class SourceColumn extends Column { - private sourceColumn: HTMLElement; + private readonly sourceColumn: HTMLElement; readonly element: HTMLElement; constructor( @@ -1002,12 +995,7 @@ class SourceColumn extends Column { keybindingsEditor: IKeybindingsEditor, ) { super(keybindingsEditor); - this.element = this.create(parent); - } - - create(parent: HTMLElement): HTMLElement { - this.sourceColumn = DOM.append(parent, $('.column.source', { id: 'source_' + ++Column.COUNTER })); - return this.sourceColumn; + this.element = this.sourceColumn = DOM.append(parent, $('.column.source', { id: 'source_' + ++Column.COUNTER })); } render(keybindingItemEntry: IKeybindingItemEntry): void { @@ -1024,8 +1012,8 @@ class SourceColumn extends Column { class WhenColumn extends Column { readonly element: HTMLElement; - private whenLabel: HTMLElement; - private whenInput: InputBox; + private readonly whenLabel: HTMLElement; + private readonly whenInput: InputBox; private readonly renderDisposables = this._register(new DisposableStore()); private _onDidAccept: Emitter = this._register(new Emitter()); @@ -1041,14 +1029,11 @@ class WhenColumn extends Column { @IThemeService private readonly themeService: IThemeService ) { super(keybindingsEditor); - this.element = this.create(parent); - } - private create(parent: HTMLElement): HTMLElement { - const column = DOM.append(parent, $('.column.when', { id: 'when_' + ++Column.COUNTER })); + this.element = DOM.append(parent, $('.column.when', { id: 'when_' + ++Column.COUNTER })); - this.whenLabel = DOM.append(column, $('div.when-label')); - this.whenInput = new InputBox(column, this.contextViewService, { + this.whenLabel = DOM.append(this.element, $('div.when-label')); + this.whenInput = new InputBox(this.element, this.contextViewService, { validationOptions: { validation: (value) => { try { @@ -1068,8 +1053,6 @@ class WhenColumn extends Column { this._register(attachInputBoxStyler(this.whenInput, this.themeService)); this._register(DOM.addStandardDisposableListener(this.whenInput.inputElement, DOM.EventType.KEY_DOWN, e => this.onInputKeyDown(e))); this._register(DOM.addDisposableListener(this.whenInput.inputElement, DOM.EventType.BLUR, () => this.cancelEditing())); - - return column; } private onInputKeyDown(e: IKeyboardEvent): void { @@ -1151,7 +1134,7 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { collector.addRule(`.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list:focus .monaco-list-row.selected > .column .monaco-keybinding-key { color: ${listActiveSelectionForegroundColor}; }`); } const listInactiveFocusAndSelectionForegroundColor = theme.getColor(listInactiveSelectionForeground); - if (listActiveSelectionForegroundColor) { + if (listInactiveFocusAndSelectionForegroundColor) { collector.addRule(`.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list .monaco-list-row.selected > .column .monaco-keybinding-key { color: ${listInactiveFocusAndSelectionForegroundColor}; }`); } const listHoverForegroundColor = theme.getColor(listHoverForeground); diff --git a/src/vs/workbench/contrib/preferences/browser/keybindingsEditorContribution.ts b/src/vs/workbench/contrib/preferences/browser/keybindingsEditorContribution.ts index e5bab5617bf2..2973c2fce798 100644 --- a/src/vs/workbench/contrib/preferences/browser/keybindingsEditorContribution.ts +++ b/src/vs/workbench/contrib/preferences/browser/keybindingsEditorContribution.ts @@ -14,7 +14,7 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { Range } from 'vs/editor/common/core/range'; import * as editorCommon from 'vs/editor/common/editorCommon'; import { registerEditorContribution, ServicesAccessor, registerEditorCommand, EditorCommand } from 'vs/editor/browser/editorExtensions'; -import { ICodeEditor, IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2'; import { SmartSnippetInserter } from 'vs/workbench/contrib/preferences/common/smartSnippetInserter'; import { DefineKeybindingOverlayWidget } from 'vs/workbench/contrib/preferences/browser/keybindingWidgets'; @@ -30,6 +30,7 @@ import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegis import { KeybindingParser } from 'vs/base/common/keybindingParser'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { equals } from 'vs/base/common/arrays'; +import { assertIsDefined } from 'vs/base/common/types'; const NLS_LAUNCH_MESSAGE = nls.localize('defineKeybinding.start', "Define Keybinding"); const NLS_KB_LAYOUT_ERROR_MESSAGE = nls.localize('defineKeybinding.kbLayoutErrorMessage', "You won't be able to produce this key combination under your current keyboard layout."); @@ -164,14 +165,14 @@ export class KeybindingEditorDecorationsRenderer extends Disposable { private _dec: string[] = []; constructor( - private _editor: IActiveCodeEditor, + private _editor: ICodeEditor, @IKeybindingService private readonly _keybindingService: IKeybindingService, ) { super(); this._updateDecorations = this._register(new RunOnceScheduler(() => this._updateDecorationsNow(), 500)); - const model = this._editor.getModel(); + const model = assertIsDefined(this._editor.getModel()); this._register(model.onDidChangeContent(() => this._updateDecorations.schedule())); this._register(this._keybindingService.onDidUpdateKeybindings((e) => this._updateDecorations.schedule())); this._register({ @@ -184,7 +185,7 @@ export class KeybindingEditorDecorationsRenderer extends Disposable { } private _updateDecorationsNow(): void { - const model = this._editor.getModel(); + const model = assertIsDefined(this._editor.getModel()); const newDecorations: IModelDeltaDecoration[] = []; diff --git a/src/vs/workbench/contrib/preferences/browser/keyboardLayoutPicker.ts b/src/vs/workbench/contrib/preferences/browser/keyboardLayoutPicker.ts index b3af1dcb0038..6e45800a7fc4 100644 --- a/src/vs/workbench/contrib/preferences/browser/keyboardLayoutPicker.ts +++ b/src/vs/workbench/contrib/preferences/browser/keyboardLayoutPicker.ts @@ -176,4 +176,4 @@ export class KeyboardLayoutPickerAction extends Action { } const registry = Registry.as(ActionExtensions.WorkbenchActions); -registry.registerWorkbenchAction(new SyncActionDescriptor(KeyboardLayoutPickerAction, KeyboardLayoutPickerAction.ID, KeyboardLayoutPickerAction.LABEL, {}), 'Preferences: Change Keyboard Layout', nls.localize('preferences', "Preferences")); +registry.registerWorkbenchAction(SyncActionDescriptor.create(KeyboardLayoutPickerAction, KeyboardLayoutPickerAction.ID, KeyboardLayoutPickerAction.LABEL, {}), 'Preferences: Change Keyboard Layout', nls.localize('preferences', "Preferences")); diff --git a/src/vs/workbench/contrib/preferences/browser/media/preferences.css b/src/vs/workbench/contrib/preferences/browser/media/preferences.css index 4dcf552240ce..bca0c4673371 100644 --- a/src/vs/workbench/contrib/preferences/browser/media/preferences.css +++ b/src/vs/workbench/contrib/preferences/browser/media/preferences.css @@ -139,6 +139,7 @@ .monaco-editor .settings-header-widget .title-container { display: flex; user-select: none; + -webkit-user-select: none; } .vs .monaco-editor .settings-header-widget .title-container { @@ -170,6 +171,7 @@ cursor: pointer; font-weight: bold; user-select: none; + -webkit-user-select: none; display: flex; } diff --git a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css index f44008484f13..f55f1a9ed1b8 100644 --- a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css +++ b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css @@ -216,14 +216,13 @@ overflow: hidden; text-overflow: ellipsis; line-height: 22px; - opacity: 0.9; flex-shrink: 1; } .settings-editor > .settings-body .settings-toc-container .monaco-list-row .settings-toc-count { display: none; line-height: 22px; - opacity: 0.7; + opacity: 0.8; margin-left: 3px; } @@ -231,17 +230,8 @@ display: block; } -.settings-editor > .settings-body .settings-toc-container .monaco-list-row .monaco-tl-twistie { - opacity: 0.9; -} - -.settings-editor > .settings-body .settings-toc-container .monaco-list-row.selected .monaco-tl-twistie { - opacity: 1; -} - .settings-editor > .settings-body .settings-toc-container .monaco-list-row.selected .settings-toc-entry { font-weight: bold; - opacity: 1; } .settings-editor > .settings-body .settings-tree-container { @@ -345,6 +335,7 @@ .settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-category { font-weight: 600; user-select: text; + -webkit-user-select: text; } .settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-category { @@ -354,15 +345,18 @@ .settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-deprecation-message { margin-top: 3px; user-select: text; + -webkit-user-select: text; + display: none; +} + +.settings-editor > .settings-body > .settings-tree-container .setting-item-contents.is-deprecated .setting-item-deprecation-message { + display: block; } .settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-description { margin-top: -1px; user-select: text; -} - -.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-deprecation-message { - position: absolute; + -webkit-user-select: text; } .settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-validation-message { @@ -395,6 +389,11 @@ -webkit-appearance: none !important; } +.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-number input[type=number] { + /* Hide arrow button that shows in type=number fields */ + -moz-appearance: textfield !important; +} + .settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-description-markdown * { margin: 0px; } @@ -480,6 +479,7 @@ width: initial; font: inherit; height: 26px; + padding: 2px 8px; } .settings-editor > .settings-body > .settings-tree-container .setting-item-new-extensions { diff --git a/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts b/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts index d26257ee933d..97832ccb3db0 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts @@ -39,10 +39,9 @@ import { ExplorerRootContext, ExplorerFolderContext } from 'vs/workbench/contrib import { ILabelService } from 'vs/platform/label/common/label'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; -import { registerAndGetAmdImageURL } from 'vs/base/common/amd'; Registry.as(EditorExtensions.Editors).registerEditor( - new EditorDescriptor( + EditorDescriptor.create( PreferencesEditor, PreferencesEditor.ID, nls.localize('defaultPreferencesEditor', "Default Preferences Editor") @@ -53,7 +52,7 @@ Registry.as(EditorExtensions.Editors).registerEditor( ); Registry.as(EditorExtensions.Editors).registerEditor( - new EditorDescriptor( + EditorDescriptor.create( SettingsEditor2, SettingsEditor2.ID, nls.localize('settingsEditor2', "Settings Editor 2") @@ -64,7 +63,7 @@ Registry.as(EditorExtensions.Editors).registerEditor( ); Registry.as(EditorExtensions.Editors).registerEditor( - new EditorDescriptor( + EditorDescriptor.create( KeybindingsEditor, KeybindingsEditor.ID, nls.localize('keybindingsEditor', "Keybindings Editor") @@ -199,15 +198,15 @@ Registry.as(EditorInputExtensions.EditorInputFactor // Contribute Global Actions const category = nls.localize('preferences', "Preferences"); const registry = Registry.as(Extensions.WorkbenchActions); -registry.registerWorkbenchAction(new SyncActionDescriptor(OpenRawDefaultSettingsAction, OpenRawDefaultSettingsAction.ID, OpenRawDefaultSettingsAction.LABEL), 'Preferences: Open Default Settings (JSON)', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(OpenSettingsJsonAction, OpenSettingsJsonAction.ID, OpenSettingsJsonAction.LABEL), 'Preferences: Open Settings (JSON)', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(OpenSettings2Action, OpenSettings2Action.ID, OpenSettings2Action.LABEL), 'Preferences: Open Settings (UI)', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(OpenGlobalSettingsAction, OpenGlobalSettingsAction.ID, OpenGlobalSettingsAction.LABEL), 'Preferences: Open User Settings', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenRawDefaultSettingsAction, OpenRawDefaultSettingsAction.ID, OpenRawDefaultSettingsAction.LABEL), 'Preferences: Open Default Settings (JSON)', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenSettingsJsonAction, OpenSettingsJsonAction.ID, OpenSettingsJsonAction.LABEL), 'Preferences: Open Settings (JSON)', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenSettings2Action, OpenSettings2Action.ID, OpenSettings2Action.LABEL), 'Preferences: Open Settings (UI)', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenGlobalSettingsAction, OpenGlobalSettingsAction.ID, OpenGlobalSettingsAction.LABEL), 'Preferences: Open User Settings', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(OpenGlobalKeybindingsAction, OpenGlobalKeybindingsAction.ID, OpenGlobalKeybindingsAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_S) }), 'Preferences: Open Keyboard Shortcuts', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(OpenDefaultKeybindingsFileAction, OpenDefaultKeybindingsFileAction.ID, OpenDefaultKeybindingsFileAction.LABEL), 'Preferences: Open Default Keyboard Shortcuts (JSON)', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(OpenGlobalKeybindingsFileAction, OpenGlobalKeybindingsFileAction.ID, OpenGlobalKeybindingsFileAction.LABEL, { primary: 0 }), 'Preferences: Open Keyboard Shortcuts (JSON)', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(ConfigureLanguageBasedSettingsAction, ConfigureLanguageBasedSettingsAction.ID, ConfigureLanguageBasedSettingsAction.LABEL), 'Preferences: Configure Language Specific Settings...', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenGlobalKeybindingsAction, OpenGlobalKeybindingsAction.ID, OpenGlobalKeybindingsAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_S) }), 'Preferences: Open Keyboard Shortcuts', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenDefaultKeybindingsFileAction, OpenDefaultKeybindingsFileAction.ID, OpenDefaultKeybindingsFileAction.LABEL), 'Preferences: Open Default Keyboard Shortcuts (JSON)', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenGlobalKeybindingsFileAction, OpenGlobalKeybindingsFileAction.ID, OpenGlobalKeybindingsFileAction.LABEL, { primary: 0 }), 'Preferences: Open Keyboard Shortcuts (JSON)', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ConfigureLanguageBasedSettingsAction, ConfigureLanguageBasedSettingsAction.ID, ConfigureLanguageBasedSettingsAction.LABEL), 'Preferences: Configure Language Specific Settings...', category); KeybindingsRegistry.registerCommandAndKeybindingRule({ id: SETTINGS_COMMAND_OPEN_SETTINGS, @@ -215,7 +214,8 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ when: null, primary: KeyMod.CtrlCmd | KeyCode.US_COMMA, handler: (accessor, args: string | undefined) => { - accessor.get(IPreferencesService).openSettings(undefined, typeof args === 'string' ? args : undefined); + const query = typeof args === 'string' ? args : undefined; + accessor.get(IPreferencesService).openSettings(query ? false : undefined, query); } }); @@ -367,8 +367,6 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ } }); -const PREFERENCES_EDITOR_LIGHT_ICON_URI = URI.parse(registerAndGetAmdImageURL(`vs/workbench/contrib/preferences/browser/media/preferences-editor-light.svg`)); -const PREFERENCES_EDITOR_DARK_ICON_URI = URI.parse(registerAndGetAmdImageURL(`vs/workbench/contrib/preferences/browser/media/preferences-editor-dark.svg`)); class PreferencesActionsContribution extends Disposable implements IWorkbenchContribution { constructor( @@ -383,10 +381,7 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon command: { id: OpenGlobalKeybindingsAction.ID, title: OpenGlobalKeybindingsAction.LABEL, - iconLocation: { - light: PREFERENCES_EDITOR_LIGHT_ICON_URI, - dark: PREFERENCES_EDITOR_DARK_ICON_URI - } + icon: { id: 'codicon/go-to-file' } }, when: ResourceContextKey.Resource.isEqualTo(environmentService.keybindingsResource.toString()), group: 'navigation', @@ -399,10 +394,7 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon command: { id: commandId, title: OpenSettings2Action.LABEL, - iconLocation: { - light: PREFERENCES_EDITOR_LIGHT_ICON_URI, - dark: PREFERENCES_EDITOR_DARK_ICON_URI - } + icon: { id: 'codicon/go-to-file' } }, when: ResourceContextKey.Resource.isEqualTo(environmentService.settingsResource.toString()), group: 'navigation', @@ -440,10 +432,7 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon command: { id: commandId, title: OpenSettings2Action.LABEL, - iconLocation: { - light: PREFERENCES_EDITOR_LIGHT_ICON_URI, - dark: PREFERENCES_EDITOR_DARK_ICON_URI - } + icon: { id: 'codicon/go-to-file' } }, when: ContextKeyExpr.and(ResourceContextKey.Resource.isEqualTo(this.preferencesService.workspaceSettingsResource!.toString()), WorkbenchStateContext.isEqualTo('workspace')), group: 'navigation', @@ -468,10 +457,7 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon command: { id: commandId, title: OpenSettings2Action.LABEL, - iconLocation: { - light: PREFERENCES_EDITOR_LIGHT_ICON_URI, - dark: PREFERENCES_EDITOR_DARK_ICON_URI - } + icon: { id: 'codicon/go-to-file' } }, when: ContextKeyExpr.and(ResourceContextKey.Resource.isEqualTo(this.preferencesService.getFolderSettingsResource(folder.uri)!.toString())), group: 'navigation', @@ -535,10 +521,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: OpenGlobalKeybindingsFileAction.ID, title: OpenGlobalKeybindingsFileAction.LABEL, - iconLocation: { - light: PREFERENCES_EDITOR_LIGHT_ICON_URI, - dark: PREFERENCES_EDITOR_DARK_ICON_URI - } + icon: { id: 'codicon/go-to-file' } }, when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR), group: 'navigation', @@ -819,10 +802,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: SETTINGS_EDITOR_COMMAND_SWITCH_TO_JSON, title: nls.localize('openSettingsJson', "Open Settings (JSON)"), - iconLocation: { - dark: PREFERENCES_EDITOR_DARK_ICON_URI, - light: PREFERENCES_EDITOR_LIGHT_ICON_URI - } + icon: { id: 'codicon/go-to-file' } }, group: 'navigation', order: 1, diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts b/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts index bbf8290bfa2b..d5caf5f72df8 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts @@ -52,8 +52,6 @@ import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor import { IFilterResult, IPreferencesService, ISetting, ISettingsEditorModel, ISettingsGroup, SettingsEditorOptions } from 'vs/workbench/services/preferences/common/preferences'; import { DefaultPreferencesEditorInput, PreferencesEditorInput } from 'vs/workbench/services/preferences/common/preferencesEditorInput'; import { DefaultSettingsEditorModel, SettingsEditorModel } from 'vs/workbench/services/preferences/common/preferencesModels'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { IHostService } from 'vs/workbench/services/host/browser/host'; import { withNullAsUndefined, withUndefinedAsNull, assertIsDefined } from 'vs/base/common/types'; export class PreferencesEditor extends BaseEditor { @@ -254,7 +252,7 @@ export class PreferencesEditor extends BaseEditor { if (this.editorService.activeControl !== this) { this.focus(); } - const promise: Promise = this.input && this.input.isDirty() ? this.input.save() : Promise.resolve(true); + const promise: Promise = this.input && this.input.isDirty() ? this.input.save(this.group!.id) : Promise.resolve(true); promise.then(() => { if (target === ConfigurationTarget.USER_LOCAL) { this.preferencesService.switchSettings(ConfigurationTarget.USER_LOCAL, this.preferencesService.userSettingsResource, true); @@ -634,7 +632,7 @@ class PreferencesRenderersController extends Disposable { if (filterResult) { filterResult.query = filter; - filterResult.exactMatch = searchResult && searchResult.exactMatch; + filterResult.exactMatch = !!searchResult && searchResult.exactMatch; } return filterResult; @@ -978,12 +976,10 @@ export class DefaultPreferencesEditor extends BaseTextEditor { @IStorageService storageService: IStorageService, @ITextResourceConfigurationService configurationService: ITextResourceConfigurationService, @IThemeService themeService: IThemeService, - @ITextFileService textFileService: ITextFileService, @IEditorGroupsService editorGroupService: IEditorGroupsService, - @IEditorService editorService: IEditorService, - @IHostService hostService: IHostService + @IEditorService editorService: IEditorService ) { - super(DefaultPreferencesEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, textFileService, editorService, editorGroupService, hostService); + super(DefaultPreferencesEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, editorService, editorGroupService); } private static _getContributions(): IEditorContributionDescription[] { diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts b/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts index e3b608b3240f..3d98f2f14c9f 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts @@ -1019,7 +1019,7 @@ class UnsupportedSettingsRenderer extends Disposable { severity: MarkerSeverity.Hint, tags: [MarkerTag.Unnecessary], ...setting.range, - message: nls.localize('unsupportedRemoteMachineSetting', "This setting cannot be applied now. It will be applied when you open local window.") + message: nls.localize('unsupportedRemoteMachineSetting', "This setting cannot be applied in this window. It will be applied when you open local window.") }); } } @@ -1054,7 +1054,7 @@ class UnsupportedSettingsRenderer extends Disposable { severity: MarkerSeverity.Hint, tags: [MarkerTag.Unnecessary], ...setting.range, - message: nls.localize('unsupportedWindowSetting', "This setting cannot be applied now. It will be applied when you open this folder directly.") + message: nls.localize('unsupportedWindowSetting', "This setting cannot be applied in this workspace. It will be applied when you open the containing workspace folder directly.") }); } } diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts index f641763207ba..93337e2b88df 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts @@ -4,13 +4,18 @@ *--------------------------------------------------------------------------------------------*/ 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 { ITreeElement } from 'vs/base/browser/ui/tree/tree'; +import { Action } from 'vs/base/common/actions'; import * as arrays from 'vs/base/common/arrays'; import { Delayer, ThrottledDelayer, timeout } from 'vs/base/common/async'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import * as collections from 'vs/base/common/collections'; import { getErrorMessage, isPromiseCanceledError } from 'vs/base/common/errors'; import { Iterator } from 'vs/base/common/iterator'; +import { KeyCode } from 'vs/base/common/keyCodes'; +import * as platform from 'vs/base/common/platform'; import * as strings from 'vs/base/common/strings'; import { isArray, withNullAsUndefined, withUndefinedAsNull } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; @@ -39,13 +44,11 @@ import { AbstractSettingRenderer, ISettingLinkClickEvent, ISettingOverrideClickE import { ISettingsEditorViewState, parseQuery, SearchResultIdx, SearchResultModel, SettingsTreeElement, SettingsTreeGroupChild, SettingsTreeGroupElement, SettingsTreeModel, SettingsTreeSettingElement } from 'vs/workbench/contrib/preferences/browser/settingsTreeModels'; import { settingsTextInputBorder } from 'vs/workbench/contrib/preferences/browser/settingsWidgets'; import { createTOCIterator, TOCTree, TOCTreeModel } from 'vs/workbench/contrib/preferences/browser/tocTree'; -import { CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_SEARCH_FOCUS, CONTEXT_TOC_ROW_FOCUS, EXTENSION_SETTING_TAG, IPreferencesSearchService, ISearchProvider, MODIFIED_SETTING_TAG, SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU, SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS } from 'vs/workbench/contrib/preferences/common/preferences'; +import { CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_SEARCH_FOCUS, CONTEXT_TOC_ROW_FOCUS, EXTENSION_SETTING_TAG, IPreferencesSearchService, ISearchProvider, MODIFIED_SETTING_TAG, SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU } from 'vs/workbench/contrib/preferences/common/preferences'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IPreferencesService, ISearchResult, ISettingsEditorModel, ISettingsEditorOptions, SettingsEditorOptions, SettingValueType } from 'vs/workbench/services/preferences/common/preferences'; import { SettingsEditor2Input } from 'vs/workbench/services/preferences/common/preferencesEditorInput'; import { Settings2EditorModel } from 'vs/workbench/services/preferences/common/preferencesModels'; -import { Action } from 'vs/base/common/actions'; -import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; function createGroupIterator(group: SettingsTreeGroupElement): Iterator> { const groupsIt = Iterator.fromArray(group.children); @@ -98,6 +101,7 @@ export class SettingsEditor2 extends BaseEditor { private headerContainer!: HTMLElement; private searchWidget!: SuggestEnabledInput; private countElement!: HTMLElement; + private controlsElement!: HTMLElement; private settingsTargetsWidget!: SettingsTargetsWidget; private settingsTreeContainer!: HTMLElement; @@ -134,9 +138,6 @@ export class SettingsEditor2 extends BaseEditor { private scheduledRefreshes: Map; private lastFocusedSettingElement: string | null = null; - private actionBar: ActionBar; - private actionsContainer: HTMLElement; - /** Don't spam warnings */ private hasWarnedMissingSettings = false; @@ -218,6 +219,7 @@ export class SettingsEditor2 extends BaseEditor { this.createHeader(this.rootElement); this.createBody(this.rootElement); + this.addCtrlAInterceptor(this.rootElement); this.updateStyles(); } @@ -308,7 +310,8 @@ export class SettingsEditor2 extends BaseEditor { this.layoutTrees(dimension); const innerWidth = Math.min(1000, dimension.width) - 24 * 2; // 24px padding on left and right; - const monacoWidth = innerWidth - 10 - this.countElement.clientWidth - 12; // minus padding inside inputbox, countElement width, extra padding before countElement + // minus padding inside inputbox, countElement width, controls width, extra padding before countElement + const monacoWidth = innerWidth - 10 - this.countElement.clientWidth - this.controlsElement.clientWidth - 12; this.searchWidget.layout({ height: 20, width: monacoWidth }); DOM.toggleClass(this.rootElement, 'mid-width', dimension.width < 1000 && dimension.width >= 600); @@ -330,6 +333,10 @@ export class SettingsEditor2 extends BaseEditor { this.focusSearch(); } + onHide(): void { + this.searchWidget.onHide(); + } + focusSettings(): void { // Update ARIA global labels const labelElement = this.settingsAriaExtraLabelsContainer.querySelector('#settings_aria_more_actions_shortcut_label'); @@ -378,6 +385,7 @@ export class SettingsEditor2 extends BaseEditor { clearSearchResults(): void { this.searchWidget.setValue(''); + this.focusSearch(); } clearSearchFilters(): void { @@ -390,17 +398,12 @@ export class SettingsEditor2 extends BaseEditor { this.searchWidget.setValue(query.trim()); } - clearSearch(): void { - this.clearSearchResults(); - this.focusSearch(); - } - private createHeader(parent: HTMLElement): void { this.headerContainer = DOM.append(parent, $('.settings-header')); const searchContainer = DOM.append(this.headerContainer, $('.search-container')); - const clearInputAction = new Action(SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, localize('clearInput', "Clear Settings Search Input"), 'codicon-clear-all', false, () => { this.clearSearch(); return Promise.resolve(null); }); + const clearInputAction = new Action(SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, localize('clearInput', "Clear Settings Search Input"), 'codicon-clear-all', false, () => { this.clearSearchResults(); return Promise.resolve(null); }); const searchBoxLabel = localize('SearchSettings.AriaLabel', "Search settings"); this.searchWidget = this._register(this.instantiationService.createInstance(SuggestEnabledInput, `${SettingsEditor2.ID}.searchbox`, searchContainer, { @@ -449,14 +452,14 @@ export class SettingsEditor2 extends BaseEditor { this.settingsTargetsWidget.settingsTarget = ConfigurationTarget.USER_LOCAL; this.settingsTargetsWidget.onDidTargetChange(target => this.onDidSettingsTargetChange(target)); - this.actionsContainer = DOM.append(searchContainer, DOM.$('.settings-clear-widget')); + this.controlsElement = DOM.append(searchContainer, DOM.$('.settings-clear-widget')); - this.actionBar = this._register(new ActionBar(this.actionsContainer, { + const actionBar = this._register(new ActionBar(this.controlsElement, { animated: false, actionViewItemProvider: (action: Action) => { return undefined; } })); - this.actionBar.push([clearInputAction], { label: false, icon: true }); + actionBar.push([clearInputAction], { label: false, icon: true }); } private onDidSettingsTargetChange(target: SettingsTarget): void { @@ -544,7 +547,6 @@ export class SettingsEditor2 extends BaseEditor { this._register(DOM.addDisposableListener(clearSearch, DOM.EventType.CLICK, (e: MouseEvent) => { DOM.EventHelper.stop(e, false); this.clearSearchResults(); - this.focusSearch(); })); DOM.append(this.noResultsMessage, clearSearchContainer); @@ -561,7 +563,11 @@ export class SettingsEditor2 extends BaseEditor { if (DOM.findParentWithClass(e.relatedTarget, 'settings-editor-tree')) { if (this.settingsTree.scrollTop > 0) { const firstElement = this.settingsTree.firstVisibleElement; - this.settingsTree.reveal(firstElement, 0.1); + + if (typeof firstElement !== 'undefined') { + this.settingsTree.reveal(firstElement, 0.1); + } + return true; } } else { @@ -594,6 +600,21 @@ export class SettingsEditor2 extends BaseEditor { ); } + private addCtrlAInterceptor(container: HTMLElement): void { + this._register(DOM.addStandardDisposableListener(container, DOM.EventType.KEY_DOWN, (e: StandardKeyboardEvent) => { + if ( + e.keyCode === KeyCode.KEY_A && + (platform.isMacintosh ? e.metaKey : e.ctrlKey) && + e.target.tagName !== 'TEXTAREA' && + e.target.tagName !== 'INPUT' + ) { + // Avoid browser ctrl+a + e.browserEvent.stopPropagation(); + e.browserEvent.preventDefault(); + } + })); + } + private createFocusSink(container: HTMLElement, callback: (e: any) => boolean, label: string): HTMLElement { const listFocusSink = DOM.append(container, $('.settings-tree-focus-sink')); listFocusSink.setAttribute('aria-label', label); @@ -1316,7 +1337,7 @@ export class SettingsEditor2 extends BaseEditor { private _filterOrSearchPreferencesModel(filter: string, model: ISettingsEditorModel, provider?: ISearchProvider, token?: CancellationToken): Promise { const searchP = provider ? provider.searchModel(model, token) : Promise.resolve(null); return searchP - .then(null, err => { + .then(undefined, err => { if (isPromiseCanceledError(err)) { return Promise.reject(err); } else { @@ -1332,7 +1353,7 @@ export class SettingsEditor2 extends BaseEditor { this.telemetryService.publicLog('settingsEditor.searchError', { message, filter }); this.logService.info('Setting search error: ' + message); } - return Promise.resolve(null); + return null; } }); } diff --git a/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts b/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts index a10b2cac5110..cf9b21d44257 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts @@ -147,7 +147,7 @@ export const tocData: ITOCEntry = { }, { id: 'features/extensions', - label: localize('extensionViewlet', "Extension Viewlet"), + label: localize('extensions', "Extensions"), settings: ['extensions.*'] }, { @@ -202,9 +202,9 @@ export const tocData: ITOCEntry = { settings: ['telemetry.*'] }, { - id: 'application/configurationSync', - label: localize('configuration sync', "Configuration Sync"), - settings: ['configurationSync.*'] + id: 'application/sync', + label: localize('sync', "Sync"), + settings: ['sync.*'] } ] } diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index cecbbf2ff35a..1410081828af 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -28,7 +28,6 @@ import { KeyCode } from 'vs/base/common/keyCodes'; import { dispose, IDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { ISpliceable } from 'vs/base/common/sequence'; import { escapeRegExpCharacters, startsWith } from 'vs/base/common/strings'; -import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { ICommandService } from 'vs/platform/commands/common/commands'; @@ -37,7 +36,7 @@ import { IContextMenuService, IContextViewService } from 'vs/platform/contextvie import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { errorForeground, focusBorder, foreground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, transparent } from 'vs/platform/theme/common/colorRegistry'; +import { errorForeground, focusBorder, foreground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, editorBackground } from 'vs/platform/theme/common/colorRegistry'; import { attachButtonStyler, attachInputBoxStyler, attachSelectBoxStyler, attachStyler } from 'vs/platform/theme/common/styler'; import { ICssStyleCollector, ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { ITOCEntry } from 'vs/workbench/contrib/preferences/browser/settingsLayout'; @@ -47,6 +46,8 @@ import { SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU } from 'vs/workbench/contrib/ import { ISetting, ISettingsGroup, SettingValueType } from 'vs/workbench/services/preferences/common/preferences'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { isArray } from 'vs/base/common/types'; +import { BrowserFeatures } from 'vs/base/browser/canIUse'; +import { isIOS } from 'vs/base/common/platform'; const $ = DOM.$; @@ -466,7 +467,10 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre } const onChange = (value: any) => this._onDidChangeSetting.fire({ key: element.setting.key, value, type: template.context!.valueType }); - template.deprecationWarningElement.innerText = element.setting.deprecationMessage || ''; + const deprecationText = element.setting.deprecationMessage || ''; + template.deprecationWarningElement.innerText = deprecationText; + DOM.toggleClass(template.containerElement, 'is-deprecated', !!deprecationText); + this.renderValue(element, template, onChange); } @@ -485,15 +489,7 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre }; this._onDidClickSettingLink.fire(e); } else { - let uri: URI | undefined; - try { - uri = URI.parse(content); - } catch (err) { - // ignore - } - if (uri) { - this._openerService.open(uri).catch(onUnexpectedError); - } + this._openerService.open(content).catch(onUnexpectedError); } }, disposeables @@ -918,7 +914,9 @@ export class SettingEnumRenderer extends AbstractSettingRenderer implements ITre renderTemplate(container: HTMLElement): ISettingEnumItemTemplate { const common = this.renderCommonTemplate(null, container, 'enum'); - const selectBox = new SelectBox([], 0, this._contextViewService, undefined, { useCustomDrawn: true }); + const selectBox = new SelectBox([], 0, this._contextViewService, undefined, { + useCustomDrawn: !(isIOS && BrowserFeatures.pointerEvents) + }); common.toDispose.push(selectBox); common.toDispose.push(attachSelectBoxStyler(selectBox, this._themeService, { @@ -1456,8 +1454,6 @@ export class SettingsTree extends ObjectTree { @IThemeService themeService: IThemeService, @IInstantiationService instantiationService: IInstantiationService, ) { - const treeClass = 'settings-editor-tree'; - super('SettingsTree', container, new SettingsTreeDelegate(), renderers, @@ -1470,7 +1466,7 @@ export class SettingsTree extends ObjectTree { return e.id; } }, - styleController: new DefaultStyleController(DOM.createStyleSheet(container), treeClass), + styleController: id => new DefaultStyleController(DOM.createStyleSheet(container), id), filter: instantiationService.createInstance(SettingsTreeFilter, viewState) }); @@ -1488,6 +1484,8 @@ export class SettingsTree extends ObjectTree { // applying an opacity to the link color. const fgWithOpacity = new Color(new RGBA(foregroundColor.rgba.r, foregroundColor.rgba.g, foregroundColor.rgba.b, 0.9)); collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-description { color: ${fgWithOpacity}; }`); + + collector.addRule(`.settings-editor > .settings-body .settings-toc-container .monaco-list-row:not(.selected) { color: ${fgWithOpacity}; }`); } const errorColor = theme.getColor(errorForeground); @@ -1523,23 +1521,24 @@ export class SettingsTree extends ObjectTree { } })); - this.getHTMLElement().classList.add(treeClass); + this.getHTMLElement().classList.add('settings-editor-tree'); this.disposables.add(attachStyler(themeService, { - listActiveSelectionBackground: transparent(Color.white, 0), + listBackground: editorBackground, + listActiveSelectionBackground: editorBackground, listActiveSelectionForeground: foreground, - listFocusAndSelectionBackground: transparent(Color.white, 0), + listFocusAndSelectionBackground: editorBackground, listFocusAndSelectionForeground: foreground, - listFocusBackground: transparent(Color.white, 0), + listFocusBackground: editorBackground, listFocusForeground: foreground, listHoverForeground: foreground, - listHoverBackground: transparent(Color.white, 0), - listHoverOutline: transparent(Color.white, 0), - listFocusOutline: transparent(Color.white, 0), - listInactiveSelectionBackground: transparent(Color.white, 0), + listHoverBackground: editorBackground, + listHoverOutline: editorBackground, + listFocusOutline: editorBackground, + listInactiveSelectionBackground: editorBackground, listInactiveSelectionForeground: foreground, - listInactiveFocusBackground: transparent(Color.white, 0), - listInactiveFocusOutline: transparent(Color.white, 0) + listInactiveFocusBackground: editorBackground, + listInactiveFocusOutline: editorBackground }, colors => { this.style(colors); })); diff --git a/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts b/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts index a92878548ba5..566133afd91e 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts @@ -23,33 +23,33 @@ import { disposableTimeout } from 'vs/base/common/async'; import { isUndefinedOrNull } from 'vs/base/common/types'; const $ = DOM.$; -export const settingsHeaderForeground = registerColor('settings.headerForeground', { light: '#444444', dark: '#e7e7e7', hc: '#ffffff' }, localize('headerForeground', "(For settings editor preview) The foreground color for a section header or active title.")); +export const settingsHeaderForeground = registerColor('settings.headerForeground', { light: '#444444', dark: '#e7e7e7', hc: '#ffffff' }, localize('headerForeground', "The foreground color for a section header or active title.")); export const modifiedItemIndicator = registerColor('settings.modifiedItemIndicator', { light: new Color(new RGBA(102, 175, 224)), dark: new Color(new RGBA(12, 125, 157)), hc: new Color(new RGBA(0, 73, 122)) -}, localize('modifiedItemForeground', "(For settings editor preview) The color of the modified setting indicator.")); +}, localize('modifiedItemForeground', "The color of the modified setting indicator.")); // Enum control colors -export const settingsSelectBackground = registerColor('settings.dropdownBackground', { dark: selectBackground, light: selectBackground, hc: selectBackground }, localize('settingsDropdownBackground', "(For settings editor preview) Settings editor dropdown background.")); -export const settingsSelectForeground = registerColor('settings.dropdownForeground', { dark: selectForeground, light: selectForeground, hc: selectForeground }, localize('settingsDropdownForeground', "(For settings editor preview) Settings editor dropdown foreground.")); -export const settingsSelectBorder = registerColor('settings.dropdownBorder', { dark: selectBorder, light: selectBorder, hc: selectBorder }, localize('settingsDropdownBorder', "(For settings editor preview) Settings editor dropdown border.")); -export const settingsSelectListBorder = registerColor('settings.dropdownListBorder', { dark: editorWidgetBorder, light: editorWidgetBorder, hc: editorWidgetBorder }, localize('settingsDropdownListBorder', "(For settings editor preview) Settings editor dropdown list border. This surrounds the options and separates the options from the description.")); +export const settingsSelectBackground = registerColor('settings.dropdownBackground', { dark: selectBackground, light: selectBackground, hc: selectBackground }, localize('settingsDropdownBackground', "Settings editor dropdown background.")); +export const settingsSelectForeground = registerColor('settings.dropdownForeground', { dark: selectForeground, light: selectForeground, hc: selectForeground }, localize('settingsDropdownForeground', "Settings editor dropdown foreground.")); +export const settingsSelectBorder = registerColor('settings.dropdownBorder', { dark: selectBorder, light: selectBorder, hc: selectBorder }, localize('settingsDropdownBorder', "Settings editor dropdown border.")); +export const settingsSelectListBorder = registerColor('settings.dropdownListBorder', { dark: editorWidgetBorder, light: editorWidgetBorder, hc: editorWidgetBorder }, localize('settingsDropdownListBorder', "Settings editor dropdown list border. This surrounds the options and separates the options from the description.")); // Bool control colors -export const settingsCheckboxBackground = registerColor('settings.checkboxBackground', { dark: simpleCheckboxBackground, light: simpleCheckboxBackground, hc: simpleCheckboxBackground }, localize('settingsCheckboxBackground', "(For settings editor preview) Settings editor checkbox background.")); -export const settingsCheckboxForeground = registerColor('settings.checkboxForeground', { dark: simpleCheckboxForeground, light: simpleCheckboxForeground, hc: simpleCheckboxForeground }, localize('settingsCheckboxForeground', "(For settings editor preview) Settings editor checkbox foreground.")); -export const settingsCheckboxBorder = registerColor('settings.checkboxBorder', { dark: simpleCheckboxBorder, light: simpleCheckboxBorder, hc: simpleCheckboxBorder }, localize('settingsCheckboxBorder', "(For settings editor preview) Settings editor checkbox border.")); +export const settingsCheckboxBackground = registerColor('settings.checkboxBackground', { dark: simpleCheckboxBackground, light: simpleCheckboxBackground, hc: simpleCheckboxBackground }, localize('settingsCheckboxBackground', "Settings editor checkbox background.")); +export const settingsCheckboxForeground = registerColor('settings.checkboxForeground', { dark: simpleCheckboxForeground, light: simpleCheckboxForeground, hc: simpleCheckboxForeground }, localize('settingsCheckboxForeground', "Settings editor checkbox foreground.")); +export const settingsCheckboxBorder = registerColor('settings.checkboxBorder', { dark: simpleCheckboxBorder, light: simpleCheckboxBorder, hc: simpleCheckboxBorder }, localize('settingsCheckboxBorder', "Settings editor checkbox border.")); // Text control colors -export const settingsTextInputBackground = registerColor('settings.textInputBackground', { dark: inputBackground, light: inputBackground, hc: inputBackground }, localize('textInputBoxBackground', "(For settings editor preview) Settings editor text input box background.")); -export const settingsTextInputForeground = registerColor('settings.textInputForeground', { dark: inputForeground, light: inputForeground, hc: inputForeground }, localize('textInputBoxForeground', "(For settings editor preview) Settings editor text input box foreground.")); -export const settingsTextInputBorder = registerColor('settings.textInputBorder', { dark: inputBorder, light: inputBorder, hc: inputBorder }, localize('textInputBoxBorder', "(For settings editor preview) Settings editor text input box border.")); +export const settingsTextInputBackground = registerColor('settings.textInputBackground', { dark: inputBackground, light: inputBackground, hc: inputBackground }, localize('textInputBoxBackground', "Settings editor text input box background.")); +export const settingsTextInputForeground = registerColor('settings.textInputForeground', { dark: inputForeground, light: inputForeground, hc: inputForeground }, localize('textInputBoxForeground', "Settings editor text input box foreground.")); +export const settingsTextInputBorder = registerColor('settings.textInputBorder', { dark: inputBorder, light: inputBorder, hc: inputBorder }, localize('textInputBoxBorder', "Settings editor text input box border.")); // Number control colors -export const settingsNumberInputBackground = registerColor('settings.numberInputBackground', { dark: inputBackground, light: inputBackground, hc: inputBackground }, localize('numberInputBoxBackground', "(For settings editor preview) Settings editor number input box background.")); -export const settingsNumberInputForeground = registerColor('settings.numberInputForeground', { dark: inputForeground, light: inputForeground, hc: inputForeground }, localize('numberInputBoxForeground', "(For settings editor preview) Settings editor number input box foreground.")); -export const settingsNumberInputBorder = registerColor('settings.numberInputBorder', { dark: inputBorder, light: inputBorder, hc: inputBorder }, localize('numberInputBoxBorder', "(For settings editor preview) Settings editor number input box border.")); +export const settingsNumberInputBackground = registerColor('settings.numberInputBackground', { dark: inputBackground, light: inputBackground, hc: inputBackground }, localize('numberInputBoxBackground', "Settings editor number input box background.")); +export const settingsNumberInputForeground = registerColor('settings.numberInputForeground', { dark: inputForeground, light: inputForeground, hc: inputForeground }, localize('numberInputBoxForeground', "Settings editor number input box foreground.")); +export const settingsNumberInputBorder = registerColor('settings.numberInputBorder', { dark: inputBorder, light: inputBorder, hc: inputBorder }, localize('numberInputBoxBorder', "Settings editor number input box border.")); registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { const checkboxBackgroundColor = theme.getColor(settingsCheckboxBackground); diff --git a/src/vs/workbench/contrib/preferences/browser/tocTree.ts b/src/vs/workbench/contrib/preferences/browser/tocTree.ts index 4f22c431e6eb..2b9feaf98c8d 100644 --- a/src/vs/workbench/contrib/preferences/browser/tocTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/tocTree.ts @@ -10,7 +10,7 @@ import { IObjectTreeOptions, ObjectTree } from 'vs/base/browser/ui/tree/objectTr import { ITreeElement, ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; import { Iterator } from 'vs/base/common/iterator'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { editorBackground } from 'vs/platform/theme/common/colorRegistry'; +import { editorBackground, transparent, foreground } from 'vs/platform/theme/common/colorRegistry'; import { attachStyler } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { SettingsTreeFilter } from 'vs/workbench/contrib/preferences/browser/settingsTree'; @@ -189,7 +189,6 @@ export class TOCTree extends ObjectTree { ) { // test open mode - const treeClass = 'settings-toc-tree'; const filter = instantiationService.createInstance(SettingsTreeFilter, viewState); const options: IObjectTreeOptions = { filter, @@ -199,7 +198,7 @@ export class TOCTree extends ObjectTree { return e.id; } }, - styleController: new DefaultStyleController(DOM.createStyleSheet(container), treeClass), + styleController: id => new DefaultStyleController(DOM.createStyleSheet(container), id), accessibilityProvider: instantiationService.createInstance(SettingsAccessibilityProvider), collapseByDefault: true }; @@ -209,16 +208,15 @@ export class TOCTree extends ObjectTree { [new TOCRenderer()], options); - this.getHTMLElement().classList.add(treeClass); - this.disposables.add(attachStyler(themeService, { + listBackground: editorBackground, listActiveSelectionBackground: editorBackground, listActiveSelectionForeground: settingsHeaderForeground, listFocusAndSelectionBackground: editorBackground, listFocusAndSelectionForeground: settingsHeaderForeground, listFocusBackground: editorBackground, - listFocusForeground: settingsHeaderForeground, - listHoverForeground: settingsHeaderForeground, + listFocusForeground: transparent(foreground, 0.9), + listHoverForeground: transparent(foreground, 0.9), listHoverBackground: editorBackground, listInactiveSelectionBackground: editorBackground, listInactiveSelectionForeground: settingsHeaderForeground, diff --git a/src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts b/src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts index 997f8f0fc2bc..a3c3fc70990e 100644 --- a/src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts +++ b/src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts @@ -32,6 +32,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { Disposable, DisposableStore, IDisposable, toDisposable, dispose } from 'vs/base/common/lifecycle'; import { timeout } from 'vs/base/common/async'; +import { isFirefox } from 'vs/base/browser/browser'; export const ALL_COMMANDS_PREFIX = '>'; @@ -213,7 +214,7 @@ class CommandPaletteEditorAction extends EditorAction { label: localize('showCommands.label', "Command Palette..."), alias: 'Command Palette', precondition: undefined, - menuOpts: { + contextMenuOpts: { group: 'z_commands', order: 1 } @@ -313,8 +314,7 @@ abstract class BaseCommandEntry extends QuickOpenEntryGroup { // Indicate onBeforeRun this.onBeforeRun(this.commandId); - // Use a timeout to give the quick open widget a chance to close itself first - setTimeout(async () => { + const commandRunner = (async () => { if (action && (!(action instanceof Action) || action.enabled)) { try { this.telemetryService.publicLog2('workbenchActionExecuted', { id: action.id, from: 'quick open' }); @@ -335,7 +335,16 @@ abstract class BaseCommandEntry extends QuickOpenEntryGroup { } else { this.notificationService.info(localize('actionNotEnabled', "Command '{0}' is not enabled in the current context.", this.getLabel())); } - }, 50); + }); + + // Use a timeout to give the quick open widget a chance to close itself first + // Firefox: since the browser is quite picky for certain commands, we do not + // use a timeout (https://github.com/microsoft/vscode/issues/83288) + if (!isFirefox) { + setTimeout(() => commandRunner(), 50); + } else { + commandRunner(); + } } private onError(error?: Error): void { diff --git a/src/vs/workbench/contrib/quickopen/browser/quickopen.contribution.ts b/src/vs/workbench/contrib/quickopen/browser/quickopen.contribution.ts index 29dd046e0c54..7cc96014e0bb 100644 --- a/src/vs/workbench/contrib/quickopen/browser/quickopen.contribution.ts +++ b/src/vs/workbench/contrib/quickopen/browser/quickopen.contribution.ts @@ -21,18 +21,18 @@ import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/co // Register Actions const registry = Registry.as(ActionExtensions.WorkbenchActions); -registry.registerWorkbenchAction(new SyncActionDescriptor(ClearCommandHistoryAction, ClearCommandHistoryAction.ID, ClearCommandHistoryAction.LABEL), 'Clear Command History'); -registry.registerWorkbenchAction(new SyncActionDescriptor(ShowAllCommandsAction, ShowAllCommandsAction.ID, ShowAllCommandsAction.LABEL, { +registry.registerWorkbenchAction(SyncActionDescriptor.create(ClearCommandHistoryAction, ClearCommandHistoryAction.ID, ClearCommandHistoryAction.LABEL), 'Clear Command History'); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ShowAllCommandsAction, ShowAllCommandsAction.ID, ShowAllCommandsAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_P, secondary: [KeyCode.F1] }), 'Show All Commands'); -registry.registerWorkbenchAction(new SyncActionDescriptor(GotoLineAction, GotoLineAction.ID, GotoLineAction.LABEL, { +registry.registerWorkbenchAction(SyncActionDescriptor.create(GotoLineAction, GotoLineAction.ID, GotoLineAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_G, mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_G } }), 'Go to Line...'); -registry.registerWorkbenchAction(new SyncActionDescriptor(GotoSymbolAction, GotoSymbolAction.ID, GotoSymbolAction.LABEL, { +registry.registerWorkbenchAction(SyncActionDescriptor.create(GotoSymbolAction, GotoSymbolAction.ID, GotoSymbolAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_O }), 'Go to Symbol in File...'); @@ -42,8 +42,8 @@ const inViewsPickerContext = ContextKeyExpr.and(inQuickOpenContext, ContextKeyEx const viewPickerKeybinding = { primary: KeyMod.CtrlCmd | KeyCode.KEY_Q, mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_Q }, linux: { primary: 0 } }; const viewCategory = nls.localize('view', "View"); -registry.registerWorkbenchAction(new SyncActionDescriptor(OpenViewPickerAction, OpenViewPickerAction.ID, OpenViewPickerAction.LABEL), 'View: Open View', viewCategory); -registry.registerWorkbenchAction(new SyncActionDescriptor(QuickOpenViewPickerAction, QuickOpenViewPickerAction.ID, QuickOpenViewPickerAction.LABEL, viewPickerKeybinding), 'View: Quick Open View', viewCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenViewPickerAction, OpenViewPickerAction.ID, OpenViewPickerAction.LABEL), 'View: Open View', viewCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickOpenViewPickerAction, QuickOpenViewPickerAction.ID, QuickOpenViewPickerAction.LABEL, viewPickerKeybinding), 'View: Quick Open View', viewCategory); const quickOpenNavigateNextInViewPickerId = 'workbench.action.quickOpenNavigateNextInViewPicker'; KeybindingsRegistry.registerCommandAndKeybindingRule({ @@ -72,7 +72,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ // Register Quick Open Handler Registry.as(QuickOpenExtensions.Quickopen).registerQuickOpenHandler( - new QuickOpenHandlerDescriptor( + QuickOpenHandlerDescriptor.create( CommandsHandler, CommandsHandler.ID, ALL_COMMANDS_PREFIX, @@ -82,7 +82,7 @@ Registry.as(QuickOpenExtensions.Quickopen).registerQuickOpen ); Registry.as(QuickOpenExtensions.Quickopen).registerQuickOpenHandler( - new QuickOpenHandlerDescriptor( + QuickOpenHandlerDescriptor.create( GotoLineHandler, GotoLineHandler.ID, GOTO_LINE_PREFIX, @@ -98,7 +98,7 @@ Registry.as(QuickOpenExtensions.Quickopen).registerQuickOpen ); Registry.as(QuickOpenExtensions.Quickopen).registerQuickOpenHandler( - new QuickOpenHandlerDescriptor( + QuickOpenHandlerDescriptor.create( GotoSymbolHandler, GotoSymbolHandler.ID, GOTO_SYMBOL_PREFIX, @@ -119,7 +119,7 @@ Registry.as(QuickOpenExtensions.Quickopen).registerQuickOpen ); Registry.as(QuickOpenExtensions.Quickopen).registerQuickOpenHandler( - new QuickOpenHandlerDescriptor( + QuickOpenHandlerDescriptor.create( HelpHandler, HelpHandler.ID, HELP_PREFIX, @@ -129,7 +129,7 @@ Registry.as(QuickOpenExtensions.Quickopen).registerQuickOpen ); Registry.as(QuickOpenExtensions.Quickopen).registerQuickOpenHandler( - new QuickOpenHandlerDescriptor( + QuickOpenHandlerDescriptor.create( ViewPickerHandler, ViewPickerHandler.ID, VIEW_PICKER_PREFIX, diff --git a/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts b/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts index aea45f60e9aa..1c24bae37cc6 100644 --- a/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts +++ b/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts @@ -10,7 +10,6 @@ import { IWindowsConfiguration } from 'vs/platform/windows/common/windows'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { localize } from 'vs/nls'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { RunOnceScheduler } from 'vs/base/common/async'; @@ -20,13 +19,13 @@ import { isMacintosh, isNative } from 'vs/base/common/platform'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { IProductService } from 'vs/platform/product/common/productService'; interface IConfiguration extends IWindowsConfiguration { update: { mode: string; }; telemetry: { enableCrashReporter: boolean }; workbench: { list: { horizontalScrolling: boolean } }; debug: { console: { wordWrap: boolean } }; - configurationSync: { enableAuth: boolean }; } export class SettingsChangeRelauncher extends Disposable implements IWorkbenchContribution { @@ -39,12 +38,11 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo private enableCrashReporter: boolean | undefined; private treeHorizontalScrolling: boolean | undefined; private debugConsoleWordWrap: boolean | undefined; - private enableConfigSyncAuth: boolean | undefined; constructor( @IHostService private readonly hostService: IHostService, @IConfigurationService private readonly configurationService: IConfigurationService, - @IEnvironmentService private readonly envService: IEnvironmentService, + @IProductService private readonly productService: IProductService, @IDialogService private readonly dialogService: IDialogService ) { super(); @@ -107,12 +105,6 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo } } - // Configuration Sync Auth - if (typeof config.configurationSync?.enableAuth === 'boolean' && config.configurationSync.enableAuth !== this.enableConfigSyncAuth) { - this.enableConfigSyncAuth = config.configurationSync.enableAuth; - changed = true; - } - // Notify only when changed and we are the focused window (avoids notification spam across windows) if (notify && changed) { this.doConfirm( @@ -120,8 +112,8 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo localize('relaunchSettingMessage', "A setting has changed that requires a restart to take effect.") : localize('relaunchSettingMessageWeb', "A setting has changed that requires a reload to take effect."), isNative ? - localize('relaunchSettingDetail', "Press the restart button to restart {0} and enable the setting.", this.envService.appNameLong) : - localize('relaunchSettingDetailWeb', "Press the reload button to reload {0} and enable the setting.", this.envService.appNameLong), + localize('relaunchSettingDetail', "Press the restart button to restart {0} and enable the setting.", this.productService.nameLong) : + localize('relaunchSettingDetailWeb', "Press the reload button to reload {0} and enable the setting.", this.productService.nameLong), isNative ? localize('restart', "&&Restart") : localize('restartWeb', "&&Reload"), diff --git a/src/vs/workbench/contrib/remote/browser/explorerViewItems.ts b/src/vs/workbench/contrib/remote/browser/explorerViewItems.ts new file mode 100644 index 000000000000..b7b4066a755b --- /dev/null +++ b/src/vs/workbench/contrib/remote/browser/explorerViewItems.ts @@ -0,0 +1,114 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import * as dom from 'vs/base/browser/dom'; + +import { IActionRunner, IAction, Action } from 'vs/base/common/actions'; +import { SelectActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { attachSelectBoxStyler, attachStylerCallback } from 'vs/platform/theme/common/styler'; +import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; +import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; +import { selectBorder } from 'vs/platform/theme/common/colorRegistry'; +import { IRemoteExplorerService, REMOTE_EXPLORER_TYPE_KEY } from 'vs/workbench/services/remote/common/remoteExplorerService'; +import { ISelectOptionItem } from 'vs/base/browser/ui/selectBox/selectBox'; +import { IViewDescriptor } from 'vs/workbench/common/views'; +import { startsWith } from 'vs/base/common/strings'; +import { isStringArray } from 'vs/base/common/types'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; + +export interface IRemoteSelectItem extends ISelectOptionItem { + authority: string[]; +} + +export class SwitchRemoteViewItem extends SelectActionViewItem { + + actionRunner!: IActionRunner; + + constructor( + action: IAction, + private readonly optionsItems: IRemoteSelectItem[], + @IThemeService private readonly themeService: IThemeService, + @IContextViewService contextViewService: IContextViewService, + @IRemoteExplorerService remoteExplorerService: IRemoteExplorerService, + @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, + @IStorageService private readonly storageService: IStorageService + ) { + super(null, action, optionsItems, 0, contextViewService, { ariaLabel: nls.localize('remotes', 'Switch Remote') }); + this._register(attachSelectBoxStyler(this.selectBox, themeService, { + selectBackground: SIDE_BAR_BACKGROUND + })); + + this.setSelectionForConnection(optionsItems, environmentService, remoteExplorerService); + } + + private setSelectionForConnection(optionsItems: IRemoteSelectItem[], environmentService: IWorkbenchEnvironmentService, remoteExplorerService: IRemoteExplorerService) { + if (this.optionsItems.length > 0) { + let index = 0; + const remoteAuthority = environmentService.configuration.remoteAuthority; + const explorerType: string | undefined = remoteAuthority ? remoteAuthority.split('+')[0] : + this.storageService.get(REMOTE_EXPLORER_TYPE_KEY, StorageScope.WORKSPACE) ?? this.storageService.get(REMOTE_EXPLORER_TYPE_KEY, StorageScope.GLOBAL); + if (explorerType) { + index = this.getOptionIndexForExplorerType(optionsItems, explorerType); + } + this.select(index); + remoteExplorerService.targetType = optionsItems[index].authority[0]; + } + } + + private getOptionIndexForExplorerType(optionsItems: IRemoteSelectItem[], explorerType: string): number { + let index = 0; + for (let optionIterator = 0; (optionIterator < this.optionsItems.length) && (index === 0); optionIterator++) { + for (let authorityIterator = 0; authorityIterator < optionsItems[optionIterator].authority.length; authorityIterator++) { + if (optionsItems[optionIterator].authority[authorityIterator] === explorerType) { + index = optionIterator; + break; + } + } + } + return index; + } + + render(container: HTMLElement) { + super.render(container); + dom.addClass(container, 'switch-remote'); + this._register(attachStylerCallback(this.themeService, { selectBorder }, colors => { + container.style.border = colors.selectBorder ? `1px solid ${colors.selectBorder}` : ''; + })); + } + + protected getActionContext(_: string, index: number): any { + return this.optionsItems[index]; + } + + static createOptionItems(views: IViewDescriptor[]): IRemoteSelectItem[] { + let options: IRemoteSelectItem[] = []; + views.forEach(view => { + if (view.group && startsWith(view.group, 'targets') && view.remoteAuthority) { + options.push({ text: view.name, authority: isStringArray(view.remoteAuthority) ? view.remoteAuthority : [view.remoteAuthority] }); + } + }); + return options; + } +} + +export class SwitchRemoteAction extends Action { + + public static readonly ID = 'remote.explorer.switch'; + public static readonly LABEL = nls.localize('remote.explorer.switch', "Switch Remote"); + + constructor( + id: string, label: string, + @IRemoteExplorerService private readonly remoteExplorerService: IRemoteExplorerService + ) { + super(id, label); + } + + public async run(item: IRemoteSelectItem): Promise { + this.remoteExplorerService.targetType = item.authority[0]; + } +} diff --git a/src/vs/workbench/contrib/remote/browser/help-documentation-dark.svg b/src/vs/workbench/contrib/remote/browser/help-documentation-dark.svg deleted file mode 100644 index 2673902c6840..000000000000 --- a/src/vs/workbench/contrib/remote/browser/help-documentation-dark.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - diff --git a/src/vs/workbench/contrib/remote/browser/help-documentation-hc.svg b/src/vs/workbench/contrib/remote/browser/help-documentation-hc.svg deleted file mode 100644 index e8dc8205bab3..000000000000 --- a/src/vs/workbench/contrib/remote/browser/help-documentation-hc.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - diff --git a/src/vs/workbench/contrib/remote/browser/help-documentation-light.svg b/src/vs/workbench/contrib/remote/browser/help-documentation-light.svg deleted file mode 100644 index 4a3009baeee4..000000000000 --- a/src/vs/workbench/contrib/remote/browser/help-documentation-light.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - diff --git a/src/vs/workbench/contrib/remote/browser/help-feedback-dark.svg b/src/vs/workbench/contrib/remote/browser/help-feedback-dark.svg deleted file mode 100644 index 5d99408934e8..000000000000 --- a/src/vs/workbench/contrib/remote/browser/help-feedback-dark.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/remote/browser/help-feedback-hc.svg b/src/vs/workbench/contrib/remote/browser/help-feedback-hc.svg deleted file mode 100644 index 941430e9dd6a..000000000000 --- a/src/vs/workbench/contrib/remote/browser/help-feedback-hc.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/remote/browser/help-feedback-light.svg b/src/vs/workbench/contrib/remote/browser/help-feedback-light.svg deleted file mode 100644 index 72437202b725..000000000000 --- a/src/vs/workbench/contrib/remote/browser/help-feedback-light.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/remote/browser/help-getting-started-dark.svg b/src/vs/workbench/contrib/remote/browser/help-getting-started-dark.svg deleted file mode 100644 index 0ea65d83198b..000000000000 --- a/src/vs/workbench/contrib/remote/browser/help-getting-started-dark.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/remote/browser/help-getting-started-hc.svg b/src/vs/workbench/contrib/remote/browser/help-getting-started-hc.svg deleted file mode 100644 index 5bb05d3d8c55..000000000000 --- a/src/vs/workbench/contrib/remote/browser/help-getting-started-hc.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/remote/browser/help-getting-started-light.svg b/src/vs/workbench/contrib/remote/browser/help-getting-started-light.svg deleted file mode 100644 index 46cde7f7cc05..000000000000 --- a/src/vs/workbench/contrib/remote/browser/help-getting-started-light.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/remote/browser/help-report-issue-dark.svg b/src/vs/workbench/contrib/remote/browser/help-report-issue-dark.svg deleted file mode 100644 index 0117ceb7ded9..000000000000 --- a/src/vs/workbench/contrib/remote/browser/help-report-issue-dark.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/remote/browser/help-report-issue-hc.svg b/src/vs/workbench/contrib/remote/browser/help-report-issue-hc.svg deleted file mode 100644 index b0c521b7dc6b..000000000000 --- a/src/vs/workbench/contrib/remote/browser/help-report-issue-hc.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/remote/browser/help-report-issue-light.svg b/src/vs/workbench/contrib/remote/browser/help-report-issue-light.svg deleted file mode 100644 index 5da9322b6a93..000000000000 --- a/src/vs/workbench/contrib/remote/browser/help-report-issue-light.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/remote/browser/help-review-issues-dark.svg b/src/vs/workbench/contrib/remote/browser/help-review-issues-dark.svg deleted file mode 100644 index 21eec9cbcb85..000000000000 --- a/src/vs/workbench/contrib/remote/browser/help-review-issues-dark.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/remote/browser/help-review-issues-hc.svg b/src/vs/workbench/contrib/remote/browser/help-review-issues-hc.svg deleted file mode 100644 index 94013ea52ae7..000000000000 --- a/src/vs/workbench/contrib/remote/browser/help-review-issues-hc.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/remote/browser/help-review-issues-light.svg b/src/vs/workbench/contrib/remote/browser/help-review-issues-light.svg deleted file mode 100644 index 826d0eefbf47..000000000000 --- a/src/vs/workbench/contrib/remote/browser/help-review-issues-light.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - diff --git a/src/vs/editor/browser/widget/media/tokens.css b/src/vs/workbench/contrib/remote/browser/media/tunnelView.css similarity index 73% rename from src/vs/editor/browser/widget/media/tokens.css rename to src/vs/workbench/contrib/remote/browser/media/tunnelView.css index 23d80dd9bebb..258a252ffb05 100644 --- a/src/vs/editor/browser/widget/media/tokens.css +++ b/src/vs/workbench/contrib/remote/browser/media/tunnelView.css @@ -3,7 +3,10 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.monaco-editor .vs-whitespace { - display:inline-block; +.customview-tree .tunnel-view-label { + flex: 1; } +.customview-tree .tunnel-view-label .action-label.codicon { + margin-top: 4px; +} diff --git a/src/vs/workbench/contrib/remote/browser/remote-activity-bar.svg b/src/vs/workbench/contrib/remote/browser/remote-activity-bar.svg deleted file mode 100644 index 029e6b051c28..000000000000 --- a/src/vs/workbench/contrib/remote/browser/remote-activity-bar.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - diff --git a/src/vs/workbench/contrib/remote/browser/remote.ts b/src/vs/workbench/contrib/remote/browser/remote.ts index f8df07e6924e..33b76f6db186 100644 --- a/src/vs/workbench/contrib/remote/browser/remote.ts +++ b/src/vs/workbench/contrib/remote/browser/remote.ts @@ -5,7 +5,6 @@ import 'vs/css!./remoteViewlet'; import * as nls from 'vs/nls'; -import * as dom from 'vs/base/browser/dom'; import { URI } from 'vs/base/common/uri'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -16,22 +15,14 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { ViewContainerViewlet } from 'vs/workbench/browser/parts/views/viewsViewlet'; +import { FilterViewContainerViewlet } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { VIEWLET_ID, VIEW_CONTAINER } from 'vs/workbench/contrib/remote/common/remote.contribution'; -import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet'; -import { IAddedViewDescriptorRef } from 'vs/workbench/browser/parts/views/views'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IViewDescriptor, IViewsRegistry, Extensions } from 'vs/workbench/common/views'; import { Registry } from 'vs/platform/registry/common/platform'; -import { ExtensionsRegistry, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -import { WorkbenchAsyncDataTree, TreeResourceNavigator2 } from 'vs/platform/list/browser/listService'; -import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; -import { ITreeRenderer, ITreeNode, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree'; -import { Event } from 'vs/base/common/event'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { ViewletRegistry, Extensions as ViewletExtensions, ViewletDescriptor, ShowViewletAction } from 'vs/workbench/browser/viewlet'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; @@ -47,245 +38,105 @@ import Severity from 'vs/base/common/severity'; import { ReloadWindowAction } from 'vs/workbench/browser/actions/windowActions'; import { IDisposable } from 'vs/base/common/lifecycle'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; +import { SwitchRemoteViewItem, SwitchRemoteAction } from 'vs/workbench/contrib/remote/browser/explorerViewItems'; +import { Action, IActionViewItem, IAction } from 'vs/base/common/actions'; +import { isStringArray } from 'vs/base/common/types'; +import { IRemoteExplorerService, HelpInformation } from 'vs/workbench/services/remote/common/remoteExplorerService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; - -interface HelpInformation { - extensionDescription: IExtensionDescription; - getStarted?: string; - documentation?: string; - feedback?: string; - issues?: string; -} - -const remoteHelpExtPoint = ExtensionsRegistry.registerExtensionPoint({ - extensionPoint: 'remoteHelp', - jsonSchema: { - description: nls.localize('RemoteHelpInformationExtPoint', 'Contributes help information for Remote'), - type: 'object', - properties: { - 'getStarted': { - description: nls.localize('RemoteHelpInformationExtPoint.getStarted', "The url to your project's Getting Started page"), - type: 'string' - }, - 'documentation': { - description: nls.localize('RemoteHelpInformationExtPoint.documentation', "The url to your project's documentation page"), - type: 'string' - }, - 'feedback': { - description: nls.localize('RemoteHelpInformationExtPoint.feedback', "The url to your project's feedback reporter"), - type: 'string' - }, - 'issues': { - description: nls.localize('RemoteHelpInformationExtPoint.issues', "The url to your project's issues list"), - type: 'string' - } - } - } -}); - -interface IViewModel { - helpInformations: HelpInformation[]; -} - -class HelpTreeVirtualDelegate implements IListVirtualDelegate { - getHeight(element: IHelpItem): number { - return 22; - } - - getTemplateId(element: IHelpItem): string { - return 'HelpItemTemplate'; - } -} - -interface IHelpItemTemplateData { - parent: HTMLElement; - icon: HTMLElement; -} - -class HelpTreeRenderer implements ITreeRenderer { - templateId: string = 'HelpItemTemplate'; - - renderTemplate(container: HTMLElement): IHelpItemTemplateData { - dom.addClass(container, 'remote-help-tree-node-item'); - - const icon = dom.append(container, dom.$('.remote-help-tree-node-item-icon')); - - const data = Object.create(null); - data.parent = container; - data.icon = icon; - - return data; - } - - renderElement(element: ITreeNode, index: number, templateData: IHelpItemTemplateData, height: number | undefined): void { - const container = templateData.parent; - dom.append(container, templateData.icon); - dom.addClass(templateData.icon, element.element.key); - const labelContainer = dom.append(container, dom.$('.help-item-label')); - labelContainer.innerText = element.element.label; - } - - disposeTemplate(templateData: IHelpItemTemplateData): void { - - } -} - -class HelpDataSource implements IAsyncDataSource { - hasChildren(element: any) { - return element instanceof HelpModel; - } - - getChildren(element: any) { - if (element instanceof HelpModel && element.items) { - return element.items; - } - - return []; - } -} - -interface IHelpItem { - key: string; - label: string; - handleClick(): Promise; -} - -class HelpItem implements IHelpItem { - constructor( - public key: string, - public label: string, - public values: { extensionDescription: IExtensionDescription; url: string }[], - private openerService: IOpenerService, - private quickInputService: IQuickInputService - ) { - } - - async handleClick() { - if (this.values.length > 1) { - let actions = this.values.map(value => { - return { - label: value.extensionDescription.displayName || value.extensionDescription.identifier.value, - description: value.url - }; - }); - - const action = await this.quickInputService.pick(actions, { placeHolder: nls.localize('pickRemoteExtension', "Select url to open") }); - - if (action) { - await this.openerService.open(URI.parse(action.label)); - } - } else { - await this.openerService.open(URI.parse(this.values[0].url)); - } - } -} - -class IssueReporterItem implements IHelpItem { - constructor( - public key: string, - public label: string, - public extensionDescriptions: IExtensionDescription[], - private quickInputService: IQuickInputService, - private commandService: ICommandService - ) { - } - - async handleClick() { - if (this.extensionDescriptions.length > 1) { - let actions = this.extensionDescriptions.map(extension => { - return { - label: extension.displayName || extension.identifier.value, - identifier: extension.identifier - }; - }); - - const action = await this.quickInputService.pick(actions, { placeHolder: nls.localize('pickRemoteExtensionToReportIssue', "Select an extension to report issue") }); - - if (action) { - await this.commandService.executeCommand('workbench.action.openIssueReporter', [action.identifier.value]); - } - } else { - await this.commandService.executeCommand('workbench.action.openIssueReporter', [this.extensionDescriptions[0].identifier.value]); - } - } -} +import { startsWith } from 'vs/base/common/strings'; +import { TunnelPanelDescriptor, TunnelViewModel } from 'vs/workbench/contrib/remote/browser/tunnelView'; +import { IAddedViewDescriptorRef } from 'vs/workbench/browser/parts/views/views'; +import { ViewletPane } from 'vs/workbench/browser/parts/views/paneViewlet'; class HelpModel { items: IHelpItem[] | undefined; constructor( - viewModel: IViewModel, openerService: IOpenerService, quickInputService: IQuickInputService, - commandService: ICommandService + commandService: ICommandService, + remoteExplorerService: IRemoteExplorerService, + environmentService: IWorkbenchEnvironmentService ) { let helpItems: IHelpItem[] = []; - const getStarted = viewModel.helpInformations.filter(info => info.getStarted); + const getStarted = remoteExplorerService.helpInformation.filter(info => info.getStarted); if (getStarted.length) { helpItems.push(new HelpItem( - 'getStarted', - nls.localize('remote.help.getStarted', "Get Started"), + ['getStarted'], + nls.localize('remote.help.getStarted', "$(star) Get Started"), getStarted.map((info: HelpInformation) => ({ extensionDescription: info.extensionDescription, - url: info.getStarted! + url: info.getStarted!, + remoteAuthority: (typeof info.remoteName === 'string') ? [info.remoteName] : info.remoteName })), - openerService, - quickInputService + quickInputService, + environmentService, + openerService )); } - const documentation = viewModel.helpInformations.filter(info => info.documentation); + const documentation = remoteExplorerService.helpInformation.filter(info => info.documentation); if (documentation.length) { helpItems.push(new HelpItem( - 'documentation', - nls.localize('remote.help.documentation', "Read Documentation"), + ['documentation'], + nls.localize('remote.help.documentation', "$(book) Read Documentation"), documentation.map((info: HelpInformation) => ({ extensionDescription: info.extensionDescription, - url: info.documentation! + url: info.documentation!, + remoteAuthority: (typeof info.remoteName === 'string') ? [info.remoteName] : info.remoteName })), - openerService, - quickInputService + quickInputService, + environmentService, + openerService )); } - const feedback = viewModel.helpInformations.filter(info => info.feedback); + const feedback = remoteExplorerService.helpInformation.filter(info => info.feedback); if (feedback.length) { helpItems.push(new HelpItem( - 'feedback', - nls.localize('remote.help.feedback', "Provide Feedback"), + ['feedback'], + nls.localize('remote.help.feedback', "$(twitter) Provide Feedback"), feedback.map((info: HelpInformation) => ({ extensionDescription: info.extensionDescription, - url: info.feedback! + url: info.feedback!, + remoteAuthority: (typeof info.remoteName === 'string') ? [info.remoteName] : info.remoteName })), - openerService, - quickInputService + quickInputService, + environmentService, + openerService )); } - const issues = viewModel.helpInformations.filter(info => info.issues); + const issues = remoteExplorerService.helpInformation.filter(info => info.issues); if (issues.length) { helpItems.push(new HelpItem( - 'issues', - nls.localize('remote.help.issues', "Review Issues"), + ['issues'], + nls.localize('remote.help.issues', "$(issues) Review Issues"), issues.map((info: HelpInformation) => ({ extensionDescription: info.extensionDescription, - url: info.issues! + url: info.issues!, + remoteAuthority: (typeof info.remoteName === 'string') ? [info.remoteName] : info.remoteName })), - openerService, - quickInputService + quickInputService, + environmentService, + openerService )); } if (helpItems.length) { helpItems.push(new IssueReporterItem( - 'issueReporter', - nls.localize('remote.help.report', "Report Issue"), - viewModel.helpInformations.map(info => info.extensionDescription), + ['issueReporter'], + nls.localize('remote.help.report', "$(comment) Report Issue"), + remoteExplorerService.helpInformation.map(info => ({ + extensionDescription: info.extensionDescription, + remoteAuthority: (typeof info.remoteName === 'string') ? [info.remoteName] : info.remoteName + })), quickInputService, + environmentService, commandService )); } @@ -296,79 +147,126 @@ class HelpModel { } } -class HelpPanel extends ViewletPanel { - static readonly ID = '~remote.helpPanel'; - static readonly TITLE = nls.localize('remote.help', "Help and feedback"); - private tree!: WorkbenchAsyncDataTree; +interface IHelpItem extends IQuickPickItem { + label: string; + handleClick(): Promise; +} +abstract class HelpItemBase implements IHelpItem { constructor( - protected viewModel: IViewModel, - options: IViewletPanelOptions, - @IKeybindingService protected keybindingService: IKeybindingService, - @IContextMenuService protected contextMenuService: IContextMenuService, - @IContextKeyService protected contextKeyService: IContextKeyService, - @IConfigurationService protected configurationService: IConfigurationService, - @IInstantiationService protected readonly instantiationService: IInstantiationService, - @IOpenerService protected openerService: IOpenerService, - @IQuickInputService protected quickInputService: IQuickInputService, - @ICommandService protected commandService: ICommandService - - + public iconClasses: string[], + public label: string, + public values: { extensionDescription: IExtensionDescription, url?: string, remoteAuthority: string[] | undefined }[], + private quickInputService: IQuickInputService, + private environmentService: IWorkbenchEnvironmentService ) { - super(options, keybindingService, contextMenuService, configurationService, contextKeyService); + iconClasses.push('remote-help-tree-node-item-icon'); } - protected renderBody(container: HTMLElement): void { - dom.addClass(container, 'remote-help'); - const treeContainer = document.createElement('div'); - dom.addClass(treeContainer, 'remote-help-content'); - container.appendChild(treeContainer); - - this.tree = this.instantiationService.createInstance(WorkbenchAsyncDataTree, - 'RemoteHelp', - treeContainer, - new HelpTreeVirtualDelegate(), - [new HelpTreeRenderer()], - new HelpDataSource(), - { - keyboardSupport: true, + async handleClick() { + const remoteAuthority = this.environmentService.configuration.remoteAuthority; + if (remoteAuthority) { + for (let value of this.values) { + if (value.remoteAuthority) { + for (let authority of value.remoteAuthority) { + if (startsWith(remoteAuthority, authority)) { + await this.takeAction(value.extensionDescription, value.url); + return; + } + } + } } - ); + } + + if (this.values.length > 1) { + let actions = this.values.map(value => { + return { + label: value.extensionDescription.displayName || value.extensionDescription.identifier.value, + description: value.url, + extensionDescription: value.extensionDescription + }; + }); - const model = new HelpModel(this.viewModel, this.openerService, this.quickInputService, this.commandService); + const action = await this.quickInputService.pick(actions, { placeHolder: nls.localize('pickRemoteExtension', "Select url to open") }); - this.tree.setInput(model); + if (action) { + await this.takeAction(action.extensionDescription, action.description); + } + } else { + await this.takeAction(this.values[0].extensionDescription, this.values[0].url); + } + } - const helpItemNavigator = this._register(new TreeResourceNavigator2(this.tree, { openOnFocus: false, openOnSelection: false })); + protected abstract takeAction(extensionDescription: IExtensionDescription, url?: string): Promise; +} - this._register(Event.debounce(helpItemNavigator.onDidOpenResource, (last, event) => event, 75, true)(e => { - e.element.handleClick(); - })); +class HelpItem extends HelpItemBase { + constructor( + iconClasses: string[], + label: string, + values: { extensionDescription: IExtensionDescription; url: string, remoteAuthority: string[] | undefined }[], + quickInputService: IQuickInputService, + environmentService: IWorkbenchEnvironmentService, + private openerService: IOpenerService + ) { + super(iconClasses, label, values, quickInputService, environmentService); } - protected layoutBody(height: number, width: number): void { - this.tree.layout(height, width); + protected async takeAction(extensionDescription: IExtensionDescription, url: string): Promise { + await this.openerService.open(URI.parse(url)); } } -class HelpPanelDescriptor implements IViewDescriptor { - readonly id = HelpPanel.ID; - readonly name = HelpPanel.TITLE; - readonly ctorDescriptor: { ctor: any, arguments?: any[] }; - readonly canToggleVisibility = true; - readonly hideByDefault = false; - readonly workspace = true; +class IssueReporterItem extends HelpItemBase { + constructor( + iconClasses: string[], + label: string, + values: { extensionDescription: IExtensionDescription; remoteAuthority: string[] | undefined }[], + quickInputService: IQuickInputService, + environmentService: IWorkbenchEnvironmentService, + private commandService: ICommandService, + ) { + super(iconClasses, label, values, quickInputService, environmentService); + } - constructor(viewModel: IViewModel) { - this.ctorDescriptor = { ctor: HelpPanel, arguments: [viewModel] }; + protected async takeAction(extensionDescription: IExtensionDescription): Promise { + await this.commandService.executeCommand('workbench.action.openIssueReporter', [extensionDescription.identifier.value]); } } +class HelpAction extends Action { + static readonly ID = 'remote.explorer.help'; + static readonly LABEL = nls.localize('remote.explorer.help', "Help and Feedback"); + private helpModel: HelpModel; + + constructor(id: string, + label: string, + @IOpenerService private readonly openerService: IOpenerService, + @IQuickInputService private readonly quickInputService: IQuickInputService, + @ICommandService private readonly commandService: ICommandService, + @IRemoteExplorerService private readonly remoteExplorerService: IRemoteExplorerService, + @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService + ) { + super(id, label, 'codicon codicon-question'); + this.helpModel = new HelpModel(openerService, quickInputService, commandService, remoteExplorerService, environmentService); + } -export class RemoteViewlet extends ViewContainerViewlet implements IViewModel { - private helpPanelDescriptor = new HelpPanelDescriptor(this); + async run(event?: any): Promise { + if (!this.helpModel.items) { + this.helpModel = new HelpModel(this.openerService, this.quickInputService, this.commandService, this.remoteExplorerService, this.environmentService); + } + if (this.helpModel.items) { + const selection = await this.quickInputService.pick(this.helpModel.items, { placeHolder: nls.localize('remote.explorer.helpPlaceholder', "Help and Feedback") }); + if (selection) { + return selection.handleClick(); + } + } + } +} - helpInformations: HelpInformation[] = []; +export class RemoteViewlet extends FilterViewContainerViewlet { + private actions: IAction[] | undefined; + private tunnelPanelDescriptor: TunnelPanelDescriptor | undefined; constructor( @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @@ -380,88 +278,59 @@ export class RemoteViewlet extends ViewContainerViewlet implements IViewModel { @IThemeService themeService: IThemeService, @IContextMenuService contextMenuService: IContextMenuService, @IExtensionService extensionService: IExtensionService, - @IWorkbenchEnvironmentService private environmentService: IWorkbenchEnvironmentService, + @IRemoteExplorerService private readonly remoteExplorerService: IRemoteExplorerService, + @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, ) { - super(VIEWLET_ID, `${VIEWLET_ID}.state`, true, configurationService, layoutService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService); - - remoteHelpExtPoint.setHandler((extensions) => { - let helpInformation: HelpInformation[] = []; - for (let extension of extensions) { - this._handleRemoteInfoExtensionPoint(extension, helpInformation); - } - - this.helpInformations = helpInformation; - - const viewsRegistry = Registry.as(Extensions.ViewsRegistry); - if (this.helpInformations.length) { - viewsRegistry.registerViews([this.helpPanelDescriptor], VIEW_CONTAINER); - } else { - viewsRegistry.deregisterViews([this.helpPanelDescriptor], VIEW_CONTAINER); - } - }); + super(VIEWLET_ID, remoteExplorerService.onDidChangeTargetType, configurationService, layoutService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService); } - private _handleRemoteInfoExtensionPoint(extension: IExtensionPointUser, helpInformation: HelpInformation[]) { - if (!extension.description.enableProposedApi) { - return; - } + protected getFilterOn(viewDescriptor: IViewDescriptor): string | undefined { + return isStringArray(viewDescriptor.remoteAuthority) ? viewDescriptor.remoteAuthority[0] : viewDescriptor.remoteAuthority; + } - if (!extension.value.documentation && !extension.value.feedback && !extension.value.getStarted && !extension.value.issues) { - return; + public getActionViewItem(action: Action): IActionViewItem | undefined { + if (action.id === SwitchRemoteAction.ID) { + return this.instantiationService.createInstance(SwitchRemoteViewItem, action, SwitchRemoteViewItem.createOptionItems(Registry.as(Extensions.ViewsRegistry).getViews(VIEW_CONTAINER))); } - helpInformation.push({ - extensionDescription: extension.description, - getStarted: extension.value.getStarted, - documentation: extension.value.documentation, - feedback: extension.value.feedback, - issues: extension.value.issues - }); + return super.getActionViewItem(action); } - onDidAddViews(added: IAddedViewDescriptorRef[]): ViewletPanel[] { - // too late, already added to the view model - const result = super.onDidAddViews(added); - - const remoteAuthority = this.environmentService.configuration.remoteAuthority; - if (remoteAuthority) { - const actualRemoteAuthority = remoteAuthority.split('+')[0]; - added.forEach((descriptor) => { - const panel = this.getView(descriptor.viewDescriptor.id); - if (!panel) { - return; - } - - const descriptorAuthority = descriptor.viewDescriptor.remoteAuthority; - if (typeof descriptorAuthority === 'undefined') { - panel.setExpanded(true); - } else if (descriptor.viewDescriptor.id === HelpPanel.ID) { - // Do nothing, keep the default behavior for Help - } else { - const descriptorAuthorityArr = Array.isArray(descriptorAuthority) ? descriptorAuthority : [descriptorAuthority]; - if (descriptorAuthorityArr.indexOf(actualRemoteAuthority) >= 0) { - panel.setExpanded(true); - } else { - panel.setExpanded(false); - } - } + public getActions(): IAction[] { + if (!this.actions) { + this.actions = [ + this.instantiationService.createInstance(SwitchRemoteAction, SwitchRemoteAction.ID, SwitchRemoteAction.LABEL), + this.instantiationService.createInstance(HelpAction, HelpAction.ID, HelpAction.LABEL) + ]; + this.actions.forEach(a => { + this._register(a); }); } - - return result; + return this.actions; } getTitle(): string { const title = nls.localize('remote.explorer', "Remote Explorer"); return title; } + + onDidAddViews(added: IAddedViewDescriptorRef[]): ViewletPane[] { + // Call to super MUST be first, since registering the additional view will cause this to be called again. + const panels: ViewletPane[] = super.onDidAddViews(added); + if (this.environmentService.configuration.remoteAuthority && !this.tunnelPanelDescriptor && this.configurationService.getValue('remote.forwardedPortsView.visible')) { + this.tunnelPanelDescriptor = new TunnelPanelDescriptor(new TunnelViewModel(this.remoteExplorerService), this.environmentService); + const viewsRegistry = Registry.as(Extensions.ViewsRegistry); + viewsRegistry.registerViews([this.tunnelPanelDescriptor!], VIEW_CONTAINER); + } + return panels; + } } -Registry.as(ViewletExtensions.Viewlets).registerViewlet(new ViewletDescriptor( +Registry.as(ViewletExtensions.Viewlets).registerViewlet(ViewletDescriptor.create( RemoteViewlet, VIEWLET_ID, nls.localize('remote.explorer', "Remote Explorer"), - 'remote', + 'codicon-remote-explorer', 4 )); @@ -477,7 +346,7 @@ class OpenRemoteViewletAction extends ShowViewletAction { // Register Action to Open Viewlet Registry.as(WorkbenchActionExtensions.WorkbenchActions).registerWorkbenchAction( - new SyncActionDescriptor(OpenRemoteViewletAction, VIEWLET_ID, nls.localize('toggleRemoteViewlet', "Show Remote Explorer"), { + SyncActionDescriptor.create(OpenRemoteViewletAction, VIEWLET_ID, nls.localize('toggleRemoteViewlet', "Show Remote Explorer"), { primary: 0 }), 'View: Show Remote Explorer', @@ -525,7 +394,7 @@ class RemoteAgentConnectionStatusListener implements IWorkbenchContribution { let reconnectWaitEvent: ReconnectionWaitEvent | null = null; let disposableListener: IDisposable | null = null; - function showProgress(location: ProgressLocation, buttons?: string[]) { + function showProgress(location: ProgressLocation, buttons: { label: string, callback: () => void }[]) { if (currentProgressPromiseResolve) { currentProgressPromiseResolve(); } @@ -536,12 +405,12 @@ class RemoteAgentConnectionStatusListener implements IWorkbenchContribution { if (location === ProgressLocation.Dialog) { // Show dialog progressService!.withProgress( - { location: ProgressLocation.Dialog, buttons }, + { location: ProgressLocation.Dialog, buttons: buttons.map(button => button.label) }, (progress) => { if (progressReporter) { progressReporter.currentProgress = progress; } return promise; }, (choice?) => { // Handle choice from dialog - if (choice === 0 && buttons && reconnectWaitEvent) { - reconnectWaitEvent.skipWait(); + if (buttons[choice]) { + buttons[choice].callback(); } else { showProgress(ProgressLocation.Notification, buttons); } @@ -551,12 +420,12 @@ class RemoteAgentConnectionStatusListener implements IWorkbenchContribution { } else { // Show notification progressService!.withProgress( - { location: ProgressLocation.Notification, buttons }, + { location: ProgressLocation.Notification, buttons: buttons.map(button => button.label) }, (progress) => { if (progressReporter) { progressReporter.currentProgress = progress; } return promise; }, (choice?) => { - // Handle choice from notification - if (choice === 0 && buttons && reconnectWaitEvent) { - reconnectWaitEvent.skipWait(); + // Handle choice from dialog + if (buttons[choice]) { + buttons[choice].callback(); } else { hideProgress(); } @@ -572,6 +441,22 @@ class RemoteAgentConnectionStatusListener implements IWorkbenchContribution { currentProgressPromiseResolve = null; } + const reconnectButton = { + label: nls.localize('reconnectNow', "Reconnect Now"), + callback: () => { + if (reconnectWaitEvent) { + reconnectWaitEvent.skipWait(); + } + } + }; + + const reloadButton = { + label: nls.localize('reloadWindow', "Reload Window"), + callback: () => { + commandService.executeCommand(ReloadWindowAction.ID); + } + }; + connection.onDidStateChange((e) => { if (currentTimer) { currentTimer.dispose(); @@ -586,7 +471,7 @@ class RemoteAgentConnectionStatusListener implements IWorkbenchContribution { case PersistentConnectionEventType.ConnectionLost: if (!currentProgressPromiseResolve) { progressReporter = new ProgressReporter(null); - showProgress(ProgressLocation.Dialog, [nls.localize('reconnectNow', "Reconnect Now")]); + showProgress(ProgressLocation.Dialog, [reconnectButton, reloadButton]); } progressReporter!.report(nls.localize('connectionLost', "Connection Lost")); @@ -594,12 +479,12 @@ class RemoteAgentConnectionStatusListener implements IWorkbenchContribution { case PersistentConnectionEventType.ReconnectionWait: hideProgress(); reconnectWaitEvent = e; - showProgress(lastLocation || ProgressLocation.Notification, [nls.localize('reconnectNow', "Reconnect Now")]); + showProgress(lastLocation || ProgressLocation.Notification, [reconnectButton, reloadButton]); currentTimer = new ReconnectionTimer(progressReporter!, Date.now() + 1000 * e.durationSeconds); break; case PersistentConnectionEventType.ReconnectionRunning: hideProgress(); - showProgress(lastLocation || ProgressLocation.Notification); + showProgress(lastLocation || ProgressLocation.Notification, [reloadButton]); progressReporter!.report(nls.localize('reconnectionRunning', "Attempting to reconnect...")); // Register to listen for quick input is opened @@ -609,7 +494,7 @@ class RemoteAgentConnectionStatusListener implements IWorkbenchContribution { // Need to move from dialog if being shown and user needs to type in a prompt if (lastLocation === ProgressLocation.Dialog && progressReporter !== null) { hideProgress(); - showProgress(ProgressLocation.Notification); + showProgress(ProgressLocation.Notification, [reloadButton]); progressReporter.report(); } } diff --git a/src/vs/workbench/contrib/remote/browser/remoteViewlet.css b/src/vs/workbench/contrib/remote/browser/remoteViewlet.css index a278b060ec0c..55384a1d20b2 100644 --- a/src/vs/workbench/contrib/remote/browser/remoteViewlet.css +++ b/src/vs/workbench/contrib/remote/browser/remoteViewlet.css @@ -3,90 +3,37 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.monaco-workbench .activitybar>.content .monaco-action-bar .action-label.remote { - -webkit-mask: url('remote-activity-bar.svg') no-repeat 50% 50%; -} - -.remote-help-content .monaco-list .monaco-list-row .remote-help-tree-node-item { - display: flex; - height: 22px; - line-height: 22px; - flex: 1; - text-overflow: ellipsis; - overflow: hidden; - flex-wrap: nowrap; -} - -.remote-help-content .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon { +.remote-help-tree-node-item-icon { background-size: 16px; background-position: left center; background-repeat: no-repeat; - padding-right: 6px; - width: 16px; - height: 22px; -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; } -.remote-help-content .monaco-list .monaco-list-row .monaco-tl-twistie { - width: 0px !important; -} - -.vs .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon.getStarted { - background-image: url('help-getting-started-light.svg') -} - -.vs .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon.documentation { - background-image: url('help-documentation-light.svg') -} - -.vs .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon.feedback { - background-image: url('help-feedback-light.svg') -} - -.vs .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon.issues { - background-image: url('help-review-issues-light.svg') -} - -.vs .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon.issueReporter { - background-image: url('help-report-issue-light.svg') -} - -.vs-dark .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon.getStarted { - background-image: url('help-getting-started-dark.svg') -} - -.vs-dark .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon.documentation { - background-image: url('help-documentation-dark.svg') -} - -.vs-dark .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon.feedback { - background-image: url('help-feedback-dark.svg') -} - -.vs-dark .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon.issues { - background-image: url('help-review-issues-dark.svg') -} - -.vs-dark .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon.issueReporter { - background-image: url('help-report-issue-dark.svg') -} - -.hc-black .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon.getStarted { - background-image: url('help-getting-started-hc.svg') +.remote-help-tree-node-item-icon .monaco-icon-label-description-container { + padding-left: 22px; } -.hc-black .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon.documentation { - background-image: url('help-documentation-hc.svg') +.remote-help-content .monaco-list .monaco-list-row .monaco-tl-twistie { + width: 0px !important; } -.hc-black .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon.feedback { - background-image: url('help-feedback-hc.svg') +.monaco-workbench .part > .title > .title-actions .switch-remote { + display: flex; + align-items: center; + font-size: 11px; + margin-right: 0.3em; + height: 20px; + flex-shrink: 1; + margin-top: 7px; } -.hc-black .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon.issues { - background-image: url('help-review-issues-hc.svg') +.switch-remote > .monaco-select-box { + border: none; + display: block; } -.hc-black .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon.issueReporter { - background-image: url('help-report-issue-hc.svg') +.monaco-workbench .part > .title > .title-actions .switch-remote > .monaco-select-box { + padding-left: 3px; } diff --git a/src/vs/workbench/contrib/remote/browser/tunnelView.ts b/src/vs/workbench/contrib/remote/browser/tunnelView.ts new file mode 100644 index 000000000000..92c2d7a3af8f --- /dev/null +++ b/src/vs/workbench/contrib/remote/browser/tunnelView.ts @@ -0,0 +1,757 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'vs/css!./media/tunnelView'; +import * as nls from 'vs/nls'; +import * as dom from 'vs/base/browser/dom'; +import { IViewDescriptor, IEditableData } from 'vs/workbench/common/views'; +import { WorkbenchAsyncDataTree, TreeResourceNavigator2 } from 'vs/platform/list/browser/listService'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; +import { IContextKeyService, IContextKey, RawContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { ICommandService, ICommandHandler, CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { Event, Emitter } from 'vs/base/common/event'; +import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; +import { ITreeRenderer, ITreeNode, IAsyncDataSource, ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { Disposable, IDisposable, toDisposable, MutableDisposable, dispose } from 'vs/base/common/lifecycle'; +import { ViewletPane, IViewletPaneOptions } from 'vs/workbench/browser/parts/views/paneViewlet'; +import { ActionBar, ActionViewItem, IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; +import { ActionRunner, IAction } from 'vs/base/common/actions'; +import { IMenuService, MenuId, IMenu, MenuRegistry, MenuItemAction } from 'vs/platform/actions/common/actions'; +import { createAndFillInContextMenuActions, createAndFillInActionBarActions, ContextAwareMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { IRemoteExplorerService, TunnelModel } from 'vs/workbench/services/remote/common/remoteExplorerService'; +import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { InputBox, MessageType } from 'vs/base/browser/ui/inputbox/inputBox'; +import { attachInputBoxStyler } from 'vs/platform/theme/common/styler'; +import { once } from 'vs/base/common/functional'; +import { KeyCode } from 'vs/base/common/keyCodes'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { URI } from 'vs/base/common/uri'; + +class TunnelTreeVirtualDelegate implements IListVirtualDelegate { + getHeight(element: ITunnelItem): number { + return 22; + } + + getTemplateId(element: ITunnelItem): string { + return 'tunnelItemTemplate'; + } +} + +export interface ITunnelViewModel { + onForwardedPortsChanged: Event; + readonly forwarded: TunnelItem[]; + readonly published: TunnelItem[]; + readonly candidates: TunnelItem[]; + readonly groups: ITunnelGroup[]; +} + +export class TunnelViewModel extends Disposable implements ITunnelViewModel { + private _onForwardedPortsChanged: Emitter = new Emitter(); + public onForwardedPortsChanged: Event = this._onForwardedPortsChanged.event; + private model: TunnelModel; + + constructor( + @IRemoteExplorerService remoteExplorerService: IRemoteExplorerService) { + super(); + this.model = remoteExplorerService.tunnelModel; + this._register(this.model.onForwardPort(() => this._onForwardedPortsChanged.fire())); + this._register(this.model.onClosePort(() => this._onForwardedPortsChanged.fire())); + this._register(this.model.onPortName(() => this._onForwardedPortsChanged.fire())); + } + + get groups(): ITunnelGroup[] { + const groups: ITunnelGroup[] = []; + if (this.model.forwarded.size > 0) { + groups.push({ + label: nls.localize('remote.tunnelsView.forwarded', "Forwarded"), + tunnelType: TunnelType.Forwarded, + items: this.forwarded + }); + } + if (this.model.published.size > 0) { + groups.push({ + label: nls.localize('remote.tunnelsView.published', "Published"), + tunnelType: TunnelType.Published, + items: this.published + }); + } + const candidates = this.candidates; + if (this.candidates.length > 0) { + groups.push({ + label: nls.localize('remote.tunnelsView.candidates', "Candidates"), + tunnelType: TunnelType.Candidate, + items: candidates + }); + } + groups.push({ + label: nls.localize('remote.tunnelsView.add', "Add a Port..."), + tunnelType: TunnelType.Add, + }); + return groups; + } + + get forwarded(): TunnelItem[] { + return Array.from(this.model.forwarded.values()).map(tunnel => { + return new TunnelItem(TunnelType.Forwarded, tunnel.remote, tunnel.localAddress, tunnel.closeable, tunnel.name, tunnel.description); + }); + } + + get published(): TunnelItem[] { + return Array.from(this.model.published.values()).map(tunnel => { + return new TunnelItem(TunnelType.Published, tunnel.remote, tunnel.localAddress, false, tunnel.name, tunnel.description); + }); + } + + get candidates(): TunnelItem[] { + const candidates: TunnelItem[] = []; + const values = this.model.candidates.values(); + let iterator = values.next(); + while (!iterator.done) { + if (!this.model.forwarded.has(iterator.value.remote) && !this.model.published.has(iterator.value.remote)) { + candidates.push(new TunnelItem(TunnelType.Candidate, iterator.value.remote, iterator.value.localAddress, false, undefined, iterator.value.description)); + } + iterator = values.next(); + } + return candidates; + } + + dispose() { + super.dispose(); + } +} + +interface ITunnelTemplateData { + elementDisposable: IDisposable; + container: HTMLElement; + iconLabel: IconLabel; + actionBar: ActionBar; +} + +class TunnelTreeRenderer extends Disposable implements ITreeRenderer { + static readonly ITEM_HEIGHT = 22; + static readonly TREE_TEMPLATE_ID = 'tunnelItemTemplate'; + + private _actionRunner: ActionRunner | undefined; + + constructor( + private readonly viewId: string, + @IMenuService private readonly menuService: IMenuService, + @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IContextViewService private readonly contextViewService: IContextViewService, + @IThemeService private readonly themeService: IThemeService, + @IRemoteExplorerService private readonly remoteExplorerService: IRemoteExplorerService + ) { + super(); + } + + set actionRunner(actionRunner: ActionRunner) { + this._actionRunner = actionRunner; + } + + get templateId(): string { + return TunnelTreeRenderer.TREE_TEMPLATE_ID; + } + + renderTemplate(container: HTMLElement): ITunnelTemplateData { + dom.addClass(container, 'custom-view-tree-node-item'); + const iconLabel = new IconLabel(container, { supportHighlights: true }); + // dom.addClass(iconLabel.element, 'tunnel-view-label'); + const actionsContainer = dom.append(iconLabel.element, dom.$('.actions')); + const actionBar = new ActionBar(actionsContainer, { + // actionViewItemProvider: undefined // this.actionViewItemProvider + actionViewItemProvider: (action: IAction) => { + if (action instanceof MenuItemAction) { + return this.instantiationService.createInstance(ContextAwareMenuEntryActionViewItem, action); + } + + return undefined; + } + }); + + return { iconLabel, actionBar, container, elementDisposable: Disposable.None }; + } + + private isTunnelItem(item: ITunnelGroup | ITunnelItem): item is ITunnelItem { + return !!((item).remote); + } + + renderElement(element: ITreeNode, index: number, templateData: ITunnelTemplateData): void { + templateData.elementDisposable.dispose(); + const node = element.element; + + // reset + templateData.actionBar.clear(); + let editableData: IEditableData | undefined; + if (this.isTunnelItem(node)) { + editableData = this.remoteExplorerService.getEditableData(node.remote); + if (editableData) { + templateData.iconLabel.element.style.display = 'none'; + this.renderInputBox(templateData.container, editableData); + } else { + templateData.iconLabel.element.style.display = 'flex'; + this.renderTunnel(node, templateData); + } + } else if ((node.tunnelType === TunnelType.Add) && (editableData = this.remoteExplorerService.getEditableData(undefined))) { + templateData.iconLabel.element.style.display = 'none'; + this.renderInputBox(templateData.container, editableData); + } else { + templateData.iconLabel.element.style.display = 'flex'; + templateData.iconLabel.setLabel(node.label); + } + } + + private renderTunnel(node: ITunnelItem, templateData: ITunnelTemplateData) { + templateData.iconLabel.setLabel(node.label, node.description, { title: node.label + ' - ' + node.description, extraClasses: ['tunnel-view-label'] }); + templateData.actionBar.context = node; + const contextKeyService = this.contextKeyService.createScoped(); + contextKeyService.createKey('view', this.viewId); + contextKeyService.createKey('tunnelType', node.tunnelType); + contextKeyService.createKey('tunnelCloseable', node.closeable); + const menu = this.menuService.createMenu(MenuId.TunnelInline, contextKeyService); + this._register(menu); + const actions: IAction[] = []; + this._register(createAndFillInActionBarActions(menu, { shouldForwardArgs: true }, actions)); + if (actions) { + templateData.actionBar.push(actions, { icon: true, label: false }); + if (this._actionRunner) { + templateData.actionBar.actionRunner = this._actionRunner; + } + } + } + + private renderInputBox(container: HTMLElement, editableData: IEditableData): IDisposable { + const value = editableData.startingValue || ''; + const inputBox = new InputBox(container, this.contextViewService, { + ariaLabel: nls.localize('remote.tunnelsView.input', "Press Enter to confirm or Escape to cancel."), + validationOptions: { + validation: (value) => { + const content = editableData.validationMessage(value); + if (!content) { + return null; + } + + return { + content, + formatContent: true, + type: MessageType.ERROR + }; + } + }, + placeholder: editableData.placeholder || '' + }); + const styler = attachInputBoxStyler(inputBox, this.themeService); + + inputBox.value = value; + inputBox.focus(); + + const done = once((success: boolean, finishEditing: boolean) => { + inputBox.element.style.display = 'none'; + const value = inputBox.value; + dispose(toDispose); + if (finishEditing) { + editableData.onFinish(value, success); + } + }); + + const toDispose = [ + inputBox, + dom.addStandardDisposableListener(inputBox.inputElement, dom.EventType.KEY_DOWN, (e: IKeyboardEvent) => { + if (e.equals(KeyCode.Enter)) { + if (inputBox.validate()) { + done(true, true); + } + } else if (e.equals(KeyCode.Escape)) { + done(false, true); + } + }), + dom.addDisposableListener(inputBox.inputElement, dom.EventType.BLUR, () => { + done(inputBox.isInputValid(), true); + }), + styler + ]; + + return toDisposable(() => { + done(false, false); + }); + } + + disposeElement(resource: ITreeNode, index: number, templateData: ITunnelTemplateData): void { + templateData.elementDisposable.dispose(); + } + + disposeTemplate(templateData: ITunnelTemplateData): void { + templateData.actionBar.dispose(); + templateData.elementDisposable.dispose(); + } +} + +class TunnelDataSource implements IAsyncDataSource { + hasChildren(element: ITunnelViewModel | ITunnelItem | ITunnelGroup) { + if (element instanceof TunnelViewModel) { + return true; + } else if (element instanceof TunnelItem) { + return false; + } else if ((element).items) { + return true; + } + return false; + } + + getChildren(element: ITunnelViewModel | ITunnelItem | ITunnelGroup) { + if (element instanceof TunnelViewModel) { + return element.groups; + } else if (element instanceof TunnelItem) { + return []; + } else if ((element).items) { + return (element).items!; + } + return []; + } +} + +enum TunnelType { + Candidate = 'Candidate', + Published = 'Published', + Forwarded = 'Forwarded', + Add = 'Add' +} + +interface ITunnelGroup { + tunnelType: TunnelType; + label: string; + items?: ITunnelItem[]; +} + +interface ITunnelItem { + tunnelType: TunnelType; + remote: number; + localAddress?: string; + name?: string; + closeable?: boolean; + readonly description?: string; + readonly label: string; +} + +class TunnelItem implements ITunnelItem { + constructor( + public tunnelType: TunnelType, + public remote: number, + public localAddress?: string, + public closeable?: boolean, + public name?: string, + private _description?: string, + ) { } + get label(): string { + if (this.name) { + return nls.localize('remote.tunnelsView.forwardedPortLabel0', "{0}", this.name); + } else if (this.localAddress) { + return nls.localize('remote.tunnelsView.forwardedPortLabel2', "{0} to {1}", this.remote, this.localAddress); + } else { + return nls.localize('remote.tunnelsView.forwardedPortLabel3', "{0} not forwarded", this.remote); + } + } + + get description(): string | undefined { + if (this._description) { + return this._description; + } else if (this.name) { + return nls.localize('remote.tunnelsView.forwardedPortDescription0', "{0} to {1}", this.remote, this.localAddress); + } + return undefined; + } +} + +export const TunnelTypeContextKey = new RawContextKey('tunnelType', TunnelType.Add); +export const TunnelCloseableContextKey = new RawContextKey('tunnelCloseable', false); + +export class TunnelPanel extends ViewletPane { + static readonly ID = '~remote.tunnelPanel'; + static readonly TITLE = nls.localize('remote.tunnel', "Tunnels"); + private tree!: WorkbenchAsyncDataTree; + private tunnelTypeContext: IContextKey; + private tunnelCloseableContext: IContextKey; + + private titleActions: IAction[] = []; + private readonly titleActionsDisposable = this._register(new MutableDisposable()); + + constructor( + protected viewModel: ITunnelViewModel, + options: IViewletPaneOptions, + @IKeybindingService protected keybindingService: IKeybindingService, + @IContextMenuService protected contextMenuService: IContextMenuService, + @IContextKeyService protected contextKeyService: IContextKeyService, + @IConfigurationService protected configurationService: IConfigurationService, + @IInstantiationService protected readonly instantiationService: IInstantiationService, + @IOpenerService protected openerService: IOpenerService, + @IQuickInputService protected quickInputService: IQuickInputService, + @ICommandService protected commandService: ICommandService, + @IMenuService private readonly menuService: IMenuService, + @INotificationService private readonly notificationService: INotificationService, + @IContextViewService private readonly contextViewService: IContextViewService, + @IThemeService private readonly themeService: IThemeService, + @IRemoteExplorerService private readonly remoteExplorerService: IRemoteExplorerService + ) { + super(options, keybindingService, contextMenuService, configurationService, contextKeyService); + this.tunnelTypeContext = TunnelTypeContextKey.bindTo(contextKeyService); + this.tunnelCloseableContext = TunnelCloseableContextKey.bindTo(contextKeyService); + + const scopedContextKeyService = this._register(this.contextKeyService.createScoped()); + scopedContextKeyService.createKey('view', TunnelPanel.ID); + + const titleMenu = this._register(this.menuService.createMenu(MenuId.TunnelTitle, scopedContextKeyService)); + const updateActions = () => { + this.titleActions = []; + this.titleActionsDisposable.value = createAndFillInActionBarActions(titleMenu, undefined, this.titleActions); + this.updateActions(); + }; + + this._register(titleMenu.onDidChange(updateActions)); + updateActions(); + + this._register(toDisposable(() => { + this.titleActions = []; + })); + + } + + protected renderBody(container: HTMLElement): void { + dom.addClass(container, '.tree-explorer-viewlet-tree-view'); + const treeContainer = document.createElement('div'); + dom.addClass(treeContainer, 'customview-tree'); + dom.addClass(treeContainer, 'file-icon-themable-tree'); + dom.addClass(treeContainer, 'show-file-icons'); + container.appendChild(treeContainer); + const renderer = new TunnelTreeRenderer(TunnelPanel.ID, this.menuService, this.contextKeyService, this.instantiationService, this.contextViewService, this.themeService, this.remoteExplorerService); + this.tree = this.instantiationService.createInstance(WorkbenchAsyncDataTree, + 'RemoteTunnels', + treeContainer, + new TunnelTreeVirtualDelegate(), + [renderer], + new TunnelDataSource(), + { + keyboardSupport: true, + collapseByDefault: (e: ITunnelItem | ITunnelGroup): boolean => { + return false; + }, + keyboardNavigationLabelProvider: { + getKeyboardNavigationLabel: (item: ITunnelItem | ITunnelGroup) => { + return item.label; + } + }, + } + ); + const actionRunner: ActionRunner = new ActionRunner(); + renderer.actionRunner = actionRunner; + + this._register(this.tree.onContextMenu(e => this.onContextMenu(e, actionRunner))); + + this.tree.setInput(this.viewModel); + this._register(this.viewModel.onForwardedPortsChanged(() => { + this.tree.updateChildren(undefined, true); + })); + + const navigator = this._register(new TreeResourceNavigator2(this.tree, { openOnFocus: false, openOnSelection: false })); + + this._register(Event.debounce(navigator.onDidOpenResource, (last, event) => event, 75, true)(e => { + if (e.element && (e.element.tunnelType === TunnelType.Add)) { + this.commandService.executeCommand(ForwardPortAction.ID); + } + })); + + this._register(this.remoteExplorerService.onDidChangeEditable(async e => { + const isEditing = !!this.remoteExplorerService.getEditableData(e); + + if (!isEditing) { + dom.removeClass(treeContainer, 'highlight'); + } + + await this.tree.updateChildren(undefined, false); + + if (isEditing) { + dom.addClass(treeContainer, 'highlight'); + } else { + this.tree.domFocus(); + } + })); + } + + private get contributedContextMenu(): IMenu { + const contributedContextMenu = this.menuService.createMenu(MenuId.TunnelContext, this.tree.contextKeyService); + this._register(contributedContextMenu); + return contributedContextMenu; + } + + getActions(): IAction[] { + return this.titleActions; + } + + private onContextMenu(treeEvent: ITreeContextMenuEvent, actionRunner: ActionRunner): void { + if (!(treeEvent.element instanceof TunnelItem)) { + return; + } + const node: ITunnelItem | null = treeEvent.element; + const event: UIEvent = treeEvent.browserEvent; + + event.preventDefault(); + event.stopPropagation(); + + this.tree!.setFocus([node]); + this.tunnelTypeContext.set(node.tunnelType); + this.tunnelCloseableContext.set(!!node.closeable); + + const actions: IAction[] = []; + this._register(createAndFillInContextMenuActions(this.contributedContextMenu, { shouldForwardArgs: true }, actions, this.contextMenuService)); + + this.contextMenuService.showContextMenu({ + getAnchor: () => treeEvent.anchor, + getActions: () => actions, + getActionViewItem: (action) => { + const keybinding = this.keybindingService.lookupKeybinding(action.id); + if (keybinding) { + return new ActionViewItem(action, action, { label: true, keybinding: keybinding.getLabel() }); + } + return undefined; + }, + onHide: (wasCancelled?: boolean) => { + if (wasCancelled) { + this.tree!.domFocus(); + } + }, + getActionsContext: () => node, + actionRunner + }); + } + + protected layoutBody(height: number, width: number): void { + this.tree.layout(height, width); + } + + getActionViewItem(action: IAction): IActionViewItem | undefined { + return action instanceof MenuItemAction ? new ContextAwareMenuEntryActionViewItem(action, this.keybindingService, this.notificationService, this.contextMenuService) : undefined; + } +} + +export class TunnelPanelDescriptor implements IViewDescriptor { + readonly id = TunnelPanel.ID; + readonly name = TunnelPanel.TITLE; + readonly ctorDescriptor: { ctor: any, arguments?: any[] }; + readonly canToggleVisibility = true; + readonly hideByDefault = false; + readonly workspace = true; + readonly group = 'details@0'; + readonly remoteAuthority?: string | string[]; + + constructor(viewModel: ITunnelViewModel, environmentService: IWorkbenchEnvironmentService) { + this.ctorDescriptor = { ctor: TunnelPanel, arguments: [viewModel] }; + this.remoteAuthority = environmentService.configuration.remoteAuthority ? environmentService.configuration.remoteAuthority.split('+')[0] : undefined; + } +} + +namespace NameTunnelAction { + export const ID = 'remote.tunnel.name'; + export const LABEL = nls.localize('remote.tunnel.name', "Rename"); + + export function handler(): ICommandHandler { + return async (accessor, arg) => { + if (arg instanceof TunnelItem) { + const remoteExplorerService = accessor.get(IRemoteExplorerService); + remoteExplorerService.setEditable(arg.remote, { + onFinish: (value, success) => { + if (success) { + remoteExplorerService.tunnelModel.name(arg.remote, value); + } + remoteExplorerService.setEditable(arg.remote, null); + }, + validationMessage: () => null, + placeholder: nls.localize('remote.tunnelsView.namePlaceholder', "Port name"), + startingValue: arg.name + }); + } + return; + }; + } +} + +namespace ForwardPortAction { + export const ID = 'remote.tunnel.forward'; + export const LABEL = nls.localize('remote.tunnel.forward', "Forward Port"); + + export function handler(): ICommandHandler { + return async (accessor, arg) => { + const remoteExplorerService = accessor.get(IRemoteExplorerService); + if (arg instanceof TunnelItem) { + remoteExplorerService.tunnelModel.forward(arg.remote); + } else { + remoteExplorerService.setEditable(undefined, { + onFinish: (value, success) => { + if (success) { + remoteExplorerService.tunnelModel.forward(Number(value)); + } + remoteExplorerService.setEditable(undefined, null); + }, + validationMessage: (value) => { + const asNumber = Number(value); + if (isNaN(asNumber) || (asNumber < 0) || (asNumber > 65535)) { + return nls.localize('remote.tunnelsView.portNumberValid', "Port number is invalid"); + } + return null; + }, + placeholder: nls.localize('remote.tunnelsView.forwardPortPlaceholder', "Port number") + }); + } + }; + } +} + +namespace ClosePortAction { + export const ID = 'remote.tunnel.close'; + export const LABEL = nls.localize('remote.tunnel.close', "Stop Forwarding Port"); + + export function handler(): ICommandHandler { + return async (accessor, arg) => { + if (arg instanceof TunnelItem) { + const remoteExplorerService = accessor.get(IRemoteExplorerService); + await remoteExplorerService.tunnelModel.close(arg.remote); + } + }; + } +} + +namespace OpenPortInBrowserAction { + export const ID = 'remote.tunnel.open'; + export const LABEL = nls.localize('remote.tunnel.open', "Open in Browser"); + + export function handler(): ICommandHandler { + return async (accessor, arg) => { + if (arg instanceof TunnelItem) { + const model = accessor.get(IRemoteExplorerService).tunnelModel; + const openerService = accessor.get(IOpenerService); + const tunnel = model.forwarded.has(arg.remote) ? model.forwarded.get(arg.remote) : model.published.get(arg.remote); + let address: string | undefined; + if (tunnel && tunnel.localAddress && (address = model.address(tunnel.remote))) { + return openerService.open(URI.parse('http://' + address)); + } + return Promise.resolve(); + } + }; + } +} + +namespace CopyAddressAction { + export const ID = 'remote.tunnel.copyAddress'; + export const LABEL = nls.localize('remote.tunnel.copyAddress', "Copy Address"); + + export function handler(): ICommandHandler { + return async (accessor, arg) => { + if (arg instanceof TunnelItem) { + const model = accessor.get(IRemoteExplorerService).tunnelModel; + const clipboard = accessor.get(IClipboardService); + const address = model.address(arg.remote); + if (address) { + await clipboard.writeText(address.toString()); + } + } + }; + } +} + +CommandsRegistry.registerCommand(NameTunnelAction.ID, NameTunnelAction.handler()); +CommandsRegistry.registerCommand(ForwardPortAction.ID, ForwardPortAction.handler()); +CommandsRegistry.registerCommand(ClosePortAction.ID, ClosePortAction.handler()); +CommandsRegistry.registerCommand(OpenPortInBrowserAction.ID, OpenPortInBrowserAction.handler()); +CommandsRegistry.registerCommand(CopyAddressAction.ID, CopyAddressAction.handler()); + +MenuRegistry.appendMenuItem(MenuId.TunnelTitle, ({ + group: 'navigation', + order: 0, + command: { + id: ForwardPortAction.ID, + title: ForwardPortAction.LABEL, + icon: { id: 'codicon/plus' } + } +})); +MenuRegistry.appendMenuItem(MenuId.TunnelContext, ({ + group: '0_manage', + order: 0, + command: { + id: CopyAddressAction.ID, + title: CopyAddressAction.LABEL, + }, + when: ContextKeyExpr.or(TunnelTypeContextKey.isEqualTo(TunnelType.Forwarded), TunnelTypeContextKey.isEqualTo(TunnelType.Published)) +})); +MenuRegistry.appendMenuItem(MenuId.TunnelContext, ({ + group: '0_manage', + order: 1, + command: { + id: OpenPortInBrowserAction.ID, + title: OpenPortInBrowserAction.LABEL, + }, + when: ContextKeyExpr.or(TunnelTypeContextKey.isEqualTo(TunnelType.Forwarded), TunnelTypeContextKey.isEqualTo(TunnelType.Published)) +})); +MenuRegistry.appendMenuItem(MenuId.TunnelContext, ({ + group: '0_manage', + order: 2, + command: { + id: NameTunnelAction.ID, + title: NameTunnelAction.LABEL, + }, + when: TunnelTypeContextKey.isEqualTo(TunnelType.Forwarded) +})); +MenuRegistry.appendMenuItem(MenuId.TunnelContext, ({ + group: '0_manage', + order: 1, + command: { + id: ForwardPortAction.ID, + title: ForwardPortAction.LABEL, + }, + when: ContextKeyExpr.or(TunnelTypeContextKey.isEqualTo(TunnelType.Candidate), TunnelTypeContextKey.isEqualTo(TunnelType.Published)) +})); +MenuRegistry.appendMenuItem(MenuId.TunnelContext, ({ + group: '0_manage', + order: 3, + command: { + id: ClosePortAction.ID, + title: ClosePortAction.LABEL, + }, + when: TunnelCloseableContextKey +})); + +MenuRegistry.appendMenuItem(MenuId.TunnelInline, ({ + order: 0, + command: { + id: OpenPortInBrowserAction.ID, + title: OpenPortInBrowserAction.LABEL, + icon: { id: 'codicon/globe' } + }, + when: ContextKeyExpr.or(TunnelTypeContextKey.isEqualTo(TunnelType.Forwarded), TunnelTypeContextKey.isEqualTo(TunnelType.Published)) +})); +MenuRegistry.appendMenuItem(MenuId.TunnelInline, ({ + order: 0, + command: { + id: ForwardPortAction.ID, + title: ForwardPortAction.LABEL, + icon: { id: 'codicon/plus' } + }, + when: ContextKeyExpr.or(TunnelTypeContextKey.isEqualTo(TunnelType.Candidate), TunnelTypeContextKey.isEqualTo(TunnelType.Published)) +})); +MenuRegistry.appendMenuItem(MenuId.TunnelInline, ({ + order: 2, + command: { + id: ClosePortAction.ID, + title: ClosePortAction.LABEL, + icon: { id: 'codicon/x' } + }, + when: TunnelCloseableContextKey +})); diff --git a/src/vs/workbench/contrib/remote/common/remote.contribution.ts b/src/vs/workbench/contrib/remote/common/remote.contribution.ts index 15873391af01..e69f252480d9 100644 --- a/src/vs/workbench/contrib/remote/common/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/common/remote.contribution.ts @@ -6,7 +6,7 @@ import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { Registry } from 'vs/platform/registry/common/platform'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; -import { ILabelService } from 'vs/platform/label/common/label'; +import { ILabelService, ResourceLabelFormatting } from 'vs/platform/label/common/label'; import { OperatingSystem, isWeb } from 'vs/base/common/platform'; import { Schemas } from 'vs/base/common/network'; import { IRemoteAgentService, RemoteExtensionLogFileName } from 'vs/workbench/services/remote/common/remoteAgentService'; @@ -34,7 +34,7 @@ export const VIEW_CONTAINER: ViewContainer = Registry.as { if (remoteEnvironment) { + const formatting: ResourceLabelFormatting = { + label: '${path}', + separator: remoteEnvironment.os === OperatingSystem.Windows ? '\\' : '/', + tildify: remoteEnvironment.os !== OperatingSystem.Windows, + normalizeDriveLetter: remoteEnvironment.os === OperatingSystem.Windows, + workspaceSuffix: isWeb ? undefined : Schemas.vscodeRemote + }; this.labelService.registerFormatter({ scheme: Schemas.vscodeRemote, - formatting: { - label: '${path}', - separator: remoteEnvironment.os === OperatingSystem.Windows ? '\\' : '/', - tildify: remoteEnvironment.os !== OperatingSystem.Windows, - normalizeDriveLetter: remoteEnvironment.os === OperatingSystem.Windows, - workspaceSuffix: isWeb ? undefined : Schemas.vscodeRemote - } + formatting + }); + this.labelService.registerFormatter({ + scheme: Schemas.userData, + formatting }); } }); diff --git a/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts b/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts index 1bb6ece1774c..6811573515a3 100644 --- a/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts @@ -38,6 +38,7 @@ import { IHostService } from 'vs/workbench/services/host/browser/host'; import { RemoteConnectionState, Deprecated_RemoteAuthorityContext, RemoteFileDialogContext } from 'vs/workbench/browser/contextkeys'; import { IDownloadService } from 'vs/platform/download/common/download'; import { OpenLocalFileFolderCommand, OpenLocalFileCommand, OpenLocalFolderCommand, SaveLocalFileCommand } from 'vs/workbench/services/dialogs/browser/simpleFileDialog'; +import { IJSONSchema } from 'vs/base/common/jsonSchema'; const WINDOW_ACTIONS_COMMAND_ID = 'workbench.action.remote.showMenu'; const CLOSE_REMOTE_COMMAND_ID = 'workbench.action.remote.close'; @@ -95,7 +96,7 @@ export class RemoteWindowActiveIndicator extends Disposable implements IWorkbenc }); // Pending entry until extensions are ready - this.renderWindowIndicator(nls.localize('host.open', "$(sync~spin) Opening Remote..."), undefined, WINDOW_ACTIONS_COMMAND_ID); + this.renderWindowIndicator('$(sync~spin) ' + nls.localize('host.open', "Opening Remote..."), undefined, WINDOW_ACTIONS_COMMAND_ID); this.connectionState = 'initializing'; RemoteConnectionState.bindTo(this.contextKeyService).set(this.connectionState); @@ -365,6 +366,18 @@ workbenchContributionsRegistry.registerWorkbenchContribution(RemoteWindowActiveI workbenchContributionsRegistry.registerWorkbenchContribution(RemoteTelemetryEnablementUpdater, LifecyclePhase.Ready); workbenchContributionsRegistry.registerWorkbenchContribution(RemoteEmptyWorkbenchPresentation, LifecyclePhase.Starting); +const extensionKindSchema: IJSONSchema = { + type: 'string', + enum: [ + 'ui', + 'workspace' + ], + enumDescriptions: [ + nls.localize('ui', "UI extension kind. In a remote window, such extensions are enabled only when available on the local machine."), + nls.localize('workspace', "Workspace extension kind. In a remote window, such extensions are enabled only when available on the remote.") + ], +}; + Registry.as(ConfigurationExtensions.Configuration) .registerConfiguration({ id: 'remote', @@ -376,21 +389,18 @@ Registry.as(ConfigurationExtensions.Configuration) markdownDescription: nls.localize('remote.extensionKind', "Override the kind of an extension. `ui` extensions are installed and run on the local machine while `workspace` extensions are run on the remote. By overriding an extension's default kind using this setting, you specify if that extension should be installed and enabled locally or remotely."), patternProperties: { '([a-z0-9A-Z][a-z0-9\-A-Z]*)\\.([a-z0-9A-Z][a-z0-9\-A-Z]*)$': { - type: 'string', - enum: [ - 'ui', - 'workspace' - ], - enumDescriptions: [ - nls.localize('ui', "UI extension kind. In a remote window, such extensions are enabled only when available on the local machine."), - nls.localize('workspace', "Workspace extension kind. In a remote window, such extensions are enabled only when available on the remote.") - ], - default: 'ui' + oneOf: [{ type: 'array', items: extensionKindSchema }, extensionKindSchema], + default: ['ui'], }, }, default: { - 'pub.name': 'ui' + 'pub.name': ['ui'] } + }, + 'remote.downloadExtensionsLocally': { + type: 'boolean', + markdownDescription: nls.localize('remote.downloadExtensionsLocally', "When enabled extensions are downloaded locally and installed on remote."), + default: false } } }); diff --git a/src/vs/workbench/contrib/scm/browser/activity.ts b/src/vs/workbench/contrib/scm/browser/activity.ts index 28d52e606769..4de5e4b9e77f 100644 --- a/src/vs/workbench/contrib/scm/browser/activity.ts +++ b/src/vs/workbench/contrib/scm/browser/activity.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import { basename } from 'vs/base/common/resources'; +import { basename, relativePath } from 'vs/base/common/resources'; import { IDisposable, dispose, Disposable, DisposableStore, combinedDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { Event } from 'vs/base/common/event'; import { VIEWLET_ID, ISCMService, ISCMRepository } from 'vs/workbench/contrib/scm/common/scm'; @@ -13,7 +13,6 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IStatusbarService, StatusbarAlignment as MainThreadStatusBarAlignment } from 'vs/workbench/services/statusbar/common/statusbar'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { commonPrefixLength } from 'vs/base/common/strings'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; function getCount(repository: ISCMRepository): number { @@ -51,23 +50,23 @@ export class SCMStatusController implements IWorkbenchContribution { this.onDidAddRepository(repository); } - editorService.onDidActiveEditorChange(this.onDidActiveEditorChange, this, this.disposables); + editorService.onDidActiveEditorChange(this.tryFocusRepositoryBasedOnActiveEditor, this, this.disposables); this.renderActivityCount(); } - private onDidActiveEditorChange(): void { + private tryFocusRepositoryBasedOnActiveEditor(): boolean { if (!this.editorService.activeEditor) { - return; + return false; } const resource = this.editorService.activeEditor.getResource(); - if (!resource || resource.scheme !== 'file') { - return; + if (!resource) { + return false; } let bestRepository: ISCMRepository | null = null; - let bestMatchLength = Number.NEGATIVE_INFINITY; + let bestMatchLength = Number.POSITIVE_INFINITY; for (const repository of this.scmService.repositories) { const root = repository.provider.rootUri; @@ -76,22 +75,24 @@ export class SCMStatusController implements IWorkbenchContribution { continue; } - const rootFSPath = root.fsPath; - const prefixLength = commonPrefixLength(rootFSPath, resource.fsPath); + const path = relativePath(root, resource); - if (prefixLength === rootFSPath.length && prefixLength > bestMatchLength) { + if (path && !/^\.\./.test(path) && path.length < bestMatchLength) { bestRepository = repository; - bestMatchLength = prefixLength; + bestMatchLength = path.length; } } - if (bestRepository) { - this.onDidFocusRepository(bestRepository); + if (!bestRepository) { + return false; } + + this.focusRepository(bestRepository); + return true; } private onDidAddRepository(repository: ISCMRepository): void { - const focusDisposable = repository.onDidFocus(() => this.onDidFocusRepository(repository)); + const focusDisposable = repository.onDidFocus(() => this.focusRepository(repository)); const onDidChange = Event.any(repository.provider.onDidChange, repository.provider.onDidChangeResources); const changeDisposable = onDidChange(() => this.renderActivityCount()); @@ -102,7 +103,7 @@ export class SCMStatusController implements IWorkbenchContribution { this.disposables = this.disposables.filter(d => d !== removeDisposable); if (this.scmService.repositories.length === 0) { - this.onDidFocusRepository(undefined); + this.focusRepository(undefined); } else if (this.focusedRepository === repository) { this.scmService.repositories[0].focus(); } @@ -113,12 +114,18 @@ export class SCMStatusController implements IWorkbenchContribution { const disposable = combinedDisposable(focusDisposable, changeDisposable, removeDisposable); this.disposables.push(disposable); - if (!this.focusedRepository) { - this.onDidFocusRepository(repository); + if (this.focusedRepository) { + return; + } + + if (this.tryFocusRepositoryBasedOnActiveEditor()) { + return; } + + this.focusRepository(repository); } - private onDidFocusRepository(repository: ISCMRepository | undefined): void { + private focusRepository(repository: ISCMRepository | undefined): void { if (this.focusedRepository === repository) { return; } diff --git a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts index 9a546f465191..c9d711927a22 100644 --- a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts +++ b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts @@ -24,14 +24,13 @@ import { registerColor } from 'vs/platform/theme/common/colorRegistry'; import { Color, RGBA } from 'vs/base/common/color'; import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; import { registerEditorAction, registerEditorContribution, ServicesAccessor, EditorAction } from 'vs/editor/browser/editorExtensions'; -import { PeekViewWidget, getOuterEditor } from 'vs/editor/contrib/referenceSearch/peekViewWidget'; +import { PeekViewWidget, getOuterEditor, peekViewBorder, peekViewTitleBackground, peekViewTitleForeground, peekViewTitleInfoForeground } from 'vs/editor/contrib/peekView/peekView'; import { IContextKeyService, IContextKey, ContextKeyExpr, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Position } from 'vs/editor/common/core/position'; import { rot } from 'vs/base/common/numbers'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { peekViewBorder, peekViewTitleBackground, peekViewTitleForeground, peekViewTitleInfoForeground } from 'vs/editor/contrib/referenceSearch/referencesWidget'; import { EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; import { IDiffEditorOptions, EditorOption } from 'vs/editor/common/config/editorOptions'; import { Action, IAction, ActionRunner } from 'vs/base/common/actions'; @@ -41,7 +40,7 @@ import { basename, isEqualOrParent } from 'vs/base/common/resources'; import { MenuId, IMenuService, IMenu, MenuItemAction, MenuRegistry } from 'vs/platform/actions/common/actions'; import { createAndFillInActionBarActions, ContextAwareMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IChange, IEditorModel, ScrollType, IEditorContribution, IDiffEditorModel } from 'vs/editor/common/editorCommon'; -import { OverviewRulerLane, ITextModel, IModelDecorationOptions } from 'vs/editor/common/model'; +import { OverviewRulerLane, ITextModel, IModelDecorationOptions, MinimapPosition } from 'vs/editor/common/model'; import { sortedDiff, firstIndex } from 'vs/base/common/arrays'; import { IMarginData } from 'vs/editor/browser/controller/mouseTarget'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; @@ -825,6 +824,24 @@ export const editorGutterDeletedBackground = registerColor('editorGutter.deleted hc: new Color(new RGBA(141, 14, 20)) }, nls.localize('editorGutterDeletedBackground', "Editor gutter background color for lines that are deleted.")); +export const minimapGutterModifiedBackground = registerColor('minimapGutter.modifiedBackground', { + dark: new Color(new RGBA(12, 125, 157)), + light: new Color(new RGBA(102, 175, 224)), + hc: new Color(new RGBA(0, 73, 122)) +}, nls.localize('minimapGutterModifiedBackground', "Minimap gutter background color for lines that are modified.")); + +export const minimapGutterAddedBackground = registerColor('minimapGutter.addedBackground', { + dark: new Color(new RGBA(88, 124, 12)), + light: new Color(new RGBA(129, 184, 139)), + hc: new Color(new RGBA(27, 82, 37)) +}, nls.localize('minimapGutterAddedBackground', "Minimap gutter background color for lines that are added.")); + +export const minimapGutterDeletedBackground = registerColor('minimapGutter.deletedBackground', { + dark: new Color(new RGBA(148, 21, 27)), + light: new Color(new RGBA(202, 75, 81)), + hc: new Color(new RGBA(141, 14, 20)) +}, nls.localize('minimapGutterDeletedBackground', "Minimap gutter background color for lines that are deleted.")); + const overviewRulerDefault = new Color(new RGBA(0, 122, 204, 0.6)); export const overviewRulerModifiedForeground = registerColor('editorOverviewRuler.modifiedForeground', { dark: overviewRulerDefault, light: overviewRulerDefault, hc: overviewRulerDefault }, nls.localize('overviewRulerModifiedForeground', 'Overview ruler marker color for modified content.')); export const overviewRulerAddedForeground = registerColor('editorOverviewRuler.addedForeground', { dark: overviewRulerDefault, light: overviewRulerDefault, hc: overviewRulerDefault }, nls.localize('overviewRulerAddedForeground', 'Overview ruler marker color for added content.')); @@ -832,7 +849,7 @@ export const overviewRulerDeletedForeground = registerColor('editorOverviewRuler class DirtyDiffDecorator extends Disposable { - static createDecoration(className: string, foregroundColor: string, options: { gutter: boolean, overview: boolean, isWholeLine: boolean }): ModelDecorationOptions { + static createDecoration(className: string, options: { gutter: boolean, overview: { active: boolean, color: string }, minimap: { active: boolean, color: string }, isWholeLine: boolean }): ModelDecorationOptions { const decorationOptions: IModelDecorationOptions = { isWholeLine: options.isWholeLine, }; @@ -841,13 +858,20 @@ class DirtyDiffDecorator extends Disposable { decorationOptions.linesDecorationsClassName = `dirty-diff-glyph ${className}`; } - if (options.overview) { + if (options.overview.active) { decorationOptions.overviewRuler = { - color: themeColorFromId(foregroundColor), + color: themeColorFromId(options.overview.color), position: OverviewRulerLane.Left }; } + if (options.minimap.active) { + decorationOptions.minimap = { + color: themeColorFromId(options.minimap.color), + position: MinimapPosition.Gutter + }; + } + return ModelDecorationOptions.createDynamic(decorationOptions); } @@ -867,11 +891,26 @@ class DirtyDiffDecorator extends Disposable { const decorations = configurationService.getValue('scm.diffDecorations'); const gutter = decorations === 'all' || decorations === 'gutter'; const overview = decorations === 'all' || decorations === 'overview'; - const options = { gutter, overview, isWholeLine: true }; + const minimap = decorations === 'all' || decorations === 'minimap'; - this.modifiedOptions = DirtyDiffDecorator.createDecoration('dirty-diff-modified', overviewRulerModifiedForeground, options); - this.addedOptions = DirtyDiffDecorator.createDecoration('dirty-diff-added', overviewRulerAddedForeground, options); - this.deletedOptions = DirtyDiffDecorator.createDecoration('dirty-diff-deleted', overviewRulerDeletedForeground, { ...options, isWholeLine: false }); + this.modifiedOptions = DirtyDiffDecorator.createDecoration('dirty-diff-modified', { + gutter, + overview: { active: overview, color: overviewRulerModifiedForeground }, + minimap: { active: minimap, color: minimapGutterModifiedBackground }, + isWholeLine: true + }); + this.addedOptions = DirtyDiffDecorator.createDecoration('dirty-diff-added', { + gutter, + overview: { active: overview, color: overviewRulerAddedForeground }, + minimap: { active: minimap, color: minimapGutterAddedBackground }, + isWholeLine: true + }); + this.deletedOptions = DirtyDiffDecorator.createDecoration('dirty-diff-deleted', { + gutter, + overview: { active: overview, color: overviewRulerDeletedForeground }, + minimap: { active: minimap, color: minimapGutterDeletedBackground }, + isWholeLine: false + }); this._register(model.onDidChange(this.onDidChange, this)); } @@ -1176,9 +1215,15 @@ class DirtyDiffItem { } } +interface IViewState { + readonly width: number; + readonly visibility: 'always' | 'hover'; +} + export class DirtyDiffWorkbenchController extends Disposable implements ext.IWorkbenchContribution, IModelRegistry { private enabled = false; + private viewState: IViewState = { width: 3, visibility: 'always' }; private models: ITextModel[] = []; private items: { [modelId: string]: DirtyDiffItem; } = Object.create(null); private readonly transientDisposables = this._register(new DisposableStore()); @@ -1223,15 +1268,20 @@ export class DirtyDiffWorkbenchController extends Disposable implements ext.IWor width = 3; } - this.stylesheet.innerHTML = `.monaco-editor .dirty-diff-modified,.monaco-editor .dirty-diff-added{border-left-width:${width}px;}`; + this.setViewState({ ...this.viewState, width }); } private onDidChangeDiffVisibiltiyConfiguration(): void { - const visibility = this.configurationService.getValue('scm.diffDecorationsGutterVisibility'); + const visibility = this.configurationService.getValue<'always' | 'hover'>('scm.diffDecorationsGutterVisibility'); + this.setViewState({ ...this.viewState, visibility }); + } + private setViewState(state: IViewState): void { + this.viewState = state; this.stylesheet.innerHTML = ` + .monaco-editor .dirty-diff-modified,.monaco-editor .dirty-diff-added{border-left-width:${state.width}px;} .monaco-editor .dirty-diff-modified, .monaco-editor .dirty-diff-added, .monaco-editor .dirty-diff-deleted { - opacity: ${visibility === 'always' ? 1 : 0}; + opacity: ${state.visibility === 'always' ? 1 : 0}; } `; } diff --git a/src/vs/workbench/contrib/scm/browser/mainPanel.ts b/src/vs/workbench/contrib/scm/browser/mainPane.ts similarity index 93% rename from src/vs/workbench/contrib/scm/browser/mainPanel.ts rename to src/vs/workbench/contrib/scm/browser/mainPane.ts index 4a3f174417df..128c0bd4cd90 100644 --- a/src/vs/workbench/contrib/scm/browser/mainPanel.ts +++ b/src/vs/workbench/contrib/scm/browser/mainPane.ts @@ -8,7 +8,7 @@ import { localize } from 'vs/nls'; import { Event, Emitter } from 'vs/base/common/event'; import { basename } from 'vs/base/common/resources'; import { IDisposable, dispose, Disposable, DisposableStore, combinedDisposable } from 'vs/base/common/lifecycle'; -import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet'; +import { ViewletPane, IViewletPaneOptions } from 'vs/workbench/browser/parts/views/paneViewlet'; import { append, $, toggleClass } from 'vs/base/browser/dom'; import { IListVirtualDelegate, IListRenderer, IListContextMenuEvent, IListEvent } from 'vs/base/browser/ui/list/list'; import { ISCMService, ISCMRepository } from 'vs/workbench/contrib/scm/common/scm'; @@ -29,6 +29,7 @@ import { renderCodicons } from 'vs/base/browser/ui/codiconLabel/codiconLabel'; import { WorkbenchList } from 'vs/platform/list/browser/listService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IViewDescriptor } from 'vs/workbench/common/views'; +import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; export interface ISpliceEvent { index: number; @@ -167,16 +168,16 @@ class ProviderRenderer implements IListRenderer; constructor( protected viewModel: IViewModel, - options: IViewletPanelOptions, + options: IViewletPaneOptions, @IKeybindingService protected keybindingService: IKeybindingService, @IContextMenuService protected contextMenuService: IContextMenuService, @ISCMService protected scmService: ISCMService, @@ -193,9 +194,12 @@ export class MainPanel extends ViewletPanel { const renderer = this.instantiationService.createInstance(ProviderRenderer); const identityProvider = { getId: (r: ISCMRepository) => r.provider.id }; - this.list = this.instantiationService.createInstance(WorkbenchList, `SCM Main`, container, delegate, [renderer], { + this.list = this.instantiationService.createInstance>(WorkbenchList, `SCM Main`, container, delegate, [renderer], { identityProvider, - horizontalScrolling: false + horizontalScrolling: false, + overrideStyles: { + listBackground: SIDE_BAR_BACKGROUND + } }); this._register(renderer.onDidRenderElement(e => this.list.updateWidth(this.viewModel.repositories.indexOf(e)), null)); @@ -311,10 +315,10 @@ export class MainPanel extends ViewletPanel { } } -export class MainPanelDescriptor implements IViewDescriptor { +export class MainPaneDescriptor implements IViewDescriptor { - readonly id = MainPanel.ID; - readonly name = MainPanel.TITLE; + readonly id = MainPane.ID; + readonly name = MainPane.TITLE; readonly ctorDescriptor: { ctor: any, arguments?: any[] }; readonly canToggleVisibility = true; readonly hideByDefault = false; @@ -323,6 +327,6 @@ export class MainPanelDescriptor implements IViewDescriptor { readonly when = ContextKeyExpr.or(ContextKeyExpr.equals('config.scm.alwaysShowProviders', true), ContextKeyExpr.and(ContextKeyExpr.notEquals('scm.providerCount', 0), ContextKeyExpr.notEquals('scm.providerCount', 1))); constructor(viewModel: IViewModel) { - this.ctorDescriptor = { ctor: MainPanel, arguments: [viewModel] }; + this.ctorDescriptor = { ctor: MainPane, arguments: [viewModel] }; } } diff --git a/src/vs/workbench/contrib/scm/browser/media/dirtydiffDecorator.css b/src/vs/workbench/contrib/scm/browser/media/dirtydiffDecorator.css index 28f22fa09e69..302281284c83 100644 --- a/src/vs/workbench/contrib/scm/browser/media/dirtydiffDecorator.css +++ b/src/vs/workbench/contrib/scm/browser/media/dirtydiffDecorator.css @@ -39,7 +39,7 @@ transition: height 80ms linear; } -.monaco-editor .margin-view-overlays > div:hover > .dirty-diff-glyph:before { +.monaco-editor .margin-view-overlays .dirty-diff-glyph:hover::before { position: absolute; content: ''; height: 100%; @@ -47,7 +47,7 @@ left: -6px; } -.monaco-editor .margin-view-overlays > div:hover > .dirty-diff-deleted:after { +.monaco-editor .margin-view-overlays .dirty-diff-deleted:hover::after { bottom: 0; border-top-width: 0; border-bottom-width: 0; @@ -56,4 +56,4 @@ /* Hide glyph decorations when inside the inline diff editor */ .monaco-editor.modified-in-monaco-diff-editor .margin-view-overlays > div > .dirty-diff-glyph { display: none; -} \ No newline at end of file +} diff --git a/src/vs/workbench/contrib/scm/browser/media/scm-activity-bar.svg b/src/vs/workbench/contrib/scm/browser/media/scm-activity-bar.svg deleted file mode 100644 index 5092b8573296..000000000000 --- a/src/vs/workbench/contrib/scm/browser/media/scm-activity-bar.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/vs/workbench/contrib/scm/browser/media/scmViewlet.css b/src/vs/workbench/contrib/scm/browser/media/scmViewlet.css index caa4fd6cf62d..79bd3156f3bc 100644 --- a/src/vs/workbench/contrib/scm/browser/media/scmViewlet.css +++ b/src/vs/workbench/contrib/scm/browser/media/scmViewlet.css @@ -3,10 +3,6 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.monaco-workbench .activitybar > .content .monaco-action-bar .action-label.scm { - -webkit-mask: url('scm-activity-bar.svg') no-repeat 50% 50%; -} - .monaco-workbench .viewlet.scm-viewlet .collapsible.header .actions { width: initial; flex: 1; @@ -19,7 +15,7 @@ } .scm-viewlet:not(.empty) .empty-message, -.scm-viewlet.empty .monaco-panel-view { +.scm-viewlet.empty .monaco-pane-view { display: none; } @@ -48,8 +44,15 @@ align-items: center; } +.scm-viewlet .monaco-list-row > .scm-provider > .monaco-action-bar .action-label { + text-overflow: ellipsis; + overflow: hidden; + min-width: 14px; /* minimum size of icons */ +} + .scm-viewlet .monaco-list-row > .scm-provider > .monaco-action-bar .action-label .codicon { font-size: 14px; + vertical-align: sub; } .scm-viewlet .monaco-list-row > .scm-provider > .monaco-action-bar .action-item:last-of-type { @@ -133,6 +136,7 @@ .scm-viewlet .monaco-list .monaco-list-row .resource-group > .actions, .scm-viewlet .monaco-list .monaco-list-row .resource > .name > .monaco-icon-label > .actions { display: none; + max-width: fit-content; } .scm-viewlet .monaco-list .monaco-list-row:hover .resource-group > .actions, diff --git a/src/vs/workbench/contrib/scm/browser/repositoryPanel.ts b/src/vs/workbench/contrib/scm/browser/repositoryPane.ts similarity index 95% rename from src/vs/workbench/contrib/scm/browser/repositoryPanel.ts rename to src/vs/workbench/contrib/scm/browser/repositoryPane.ts index 7aa60aff8837..c10f83f1c1d6 100644 --- a/src/vs/workbench/contrib/scm/browser/repositoryPanel.ts +++ b/src/vs/workbench/contrib/scm/browser/repositoryPane.ts @@ -6,9 +6,9 @@ import 'vs/css!./media/scmViewlet'; import { Event, Emitter } from 'vs/base/common/event'; import { domEvent } from 'vs/base/browser/event'; -import { basename } from 'vs/base/common/resources'; +import { basename, isEqual } from 'vs/base/common/resources'; import { IDisposable, Disposable, DisposableStore, combinedDisposable } from 'vs/base/common/lifecycle'; -import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet'; +import { ViewletPane, IViewletPaneOptions } from 'vs/workbench/browser/parts/views/paneViewlet'; import { append, $, addClass, toggleClass, trackFocus, removeClass } from 'vs/base/browser/dom'; import { IListVirtualDelegate, IIdentityProvider } from 'vs/base/browser/ui/list/list'; import { ISCMRepository, ISCMResourceGroup, ISCMResource, InputValidationType } from 'vs/workbench/contrib/scm/common/scm'; @@ -38,7 +38,7 @@ import * as platform from 'vs/base/common/platform'; import { ITreeNode, ITreeFilter, ITreeSorter, ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree'; import { ResourceTree, IResourceNode } from 'vs/base/common/resourceTree'; import { ISequence, ISplice } from 'vs/base/common/sequence'; -import { ObjectTree, ICompressibleTreeRenderer, ICompressibleKeyboardNavigationLabelProvider } from 'vs/base/browser/ui/tree/objectTree'; +import { ICompressibleTreeRenderer, ICompressibleKeyboardNavigationLabelProvider } from 'vs/base/browser/ui/tree/objectTree'; import { Iterator } from 'vs/base/common/iterator'; import { ICompressedTreeNode, ICompressedTreeElement } from 'vs/base/browser/ui/tree/compressedObjectTreeModel'; import { URI } from 'vs/base/common/uri'; @@ -47,11 +47,13 @@ import { compareFileNames } from 'vs/base/common/comparers'; import { FuzzyScore, createMatches } from 'vs/base/common/filters'; import { IViewDescriptor } from 'vs/workbench/common/views'; import { localize } from 'vs/nls'; -import { flatten } from 'vs/base/common/arrays'; +import { flatten, find } from 'vs/base/common/arrays'; import { memoize } from 'vs/base/common/decorators'; import { IWorkbenchThemeService, IFileIconTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { toResource, SideBySideEditor } from 'vs/workbench/common/editor'; +import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; +import { Hasher } from 'vs/base/common/hash'; type TreeElement = ISCMResourceGroup | IResourceNode | ISCMResource; @@ -428,7 +430,7 @@ class ViewModel { constructor( private groups: ISequence, - private tree: ObjectTree, + private tree: WorkbenchCompressibleObjectTree, private _mode: ViewModelMode, @IEditorService protected editorService: IEditorService, @IConfigurationService protected configurationService: IConfigurationService, @@ -466,18 +468,16 @@ class ViewModel { } private onDidSpliceGroup(item: IGroupItem, { start, deleteCount, toInsert }: ISplice): void { - if (this._mode === ViewModelMode.Tree) { - for (const resource of toInsert) { - item.tree.add(resource.sourceUri, resource); - } - } - const deleted = item.resources.splice(start, deleteCount, ...toInsert); if (this._mode === ViewModelMode.Tree) { for (const resource of deleted) { item.tree.delete(resource.sourceUri); } + + for (const resource of toInsert) { + item.tree.add(resource.sourceUri, resource); + } } this.refresh(item); @@ -536,12 +536,15 @@ class ViewModel { // go backwards from last group for (let i = this.items.length - 1; i >= 0; i--) { - const node = this.items[i].tree.getNode(uri); - - if (node && node.element) { - this.tree.reveal(node.element); - this.tree.setSelection([node.element]); - this.tree.setFocus([node.element]); + const item = this.items[i]; + const resource = this.mode === ViewModelMode.Tree + ? item.tree.getNode(uri)?.element + : find(item.resources, r => isEqual(r.sourceUri, uri)); + + if (resource) { + this.tree.reveal(resource); + this.tree.setSelection([resource]); + this.tree.setFocus([resource]); return; } } @@ -570,7 +573,7 @@ export class ToggleViewModeAction extends Action { } private onDidChangeMode(mode: ViewModelMode): void { - const iconClass = mode === ViewModelMode.List ? 'codicon-filter' : 'codicon-selection'; + const iconClass = mode === ViewModelMode.List ? 'codicon-list-tree' : 'codicon-list-flat'; this.class = `scm-action toggle-view-mode ${iconClass}`; } } @@ -583,14 +586,14 @@ function convertValidationType(type: InputValidationType): MessageType { } } -export class RepositoryPanel extends ViewletPanel { +export class RepositoryPane extends ViewletPane { private cachedHeight: number | undefined = undefined; private cachedWidth: number | undefined = undefined; private inputBoxContainer!: HTMLElement; private inputBox!: InputBox; private listContainer!: HTMLElement; - private tree!: ObjectTree; + private tree!: WorkbenchCompressibleObjectTree; private viewModel!: ViewModel; private listLabels!: ResourceLabels; private menus: SCMMenus; @@ -600,7 +603,7 @@ export class RepositoryPanel extends ViewletPanel { constructor( readonly repository: ISCMRepository, - options: IViewletPanelOptions, + options: IViewletPaneOptions, @IKeybindingService protected keybindingService: IKeybindingService, @IWorkbenchThemeService protected themeService: IWorkbenchThemeService, @IContextMenuService protected contextMenuService: IContextMenuService, @@ -732,7 +735,7 @@ export class RepositoryPanel extends ViewletPanel { const keyboardNavigationLabelProvider = new SCMTreeKeyboardNavigationLabelProvider(); const identityProvider = new SCMResourceIdentityProvider(); - this.tree = this.instantiationService.createInstance( + this.tree = this.instantiationService.createInstance>( WorkbenchCompressibleObjectTree, 'SCM Tree Repo', this.listContainer, @@ -743,7 +746,10 @@ export class RepositoryPanel extends ViewletPanel { horizontalScrolling: false, filter, sorter, - keyboardNavigationLabelProvider + keyboardNavigationLabelProvider, + overrideStyles: { + listBackground: SIDE_BAR_BACKGROUND + } }); this._register(Event.chain(this.tree.onDidOpen) @@ -958,9 +964,12 @@ export class RepositoryViewDescriptor implements IViewDescriptor { constructor(readonly repository: ISCMRepository, readonly hideByDefault: boolean) { const repoId = repository.provider.rootUri ? repository.provider.rootUri.toString() : `#${RepositoryViewDescriptor.counter++}`; - this.id = `scm:repository:${repository.provider.label}:${repoId}`; + const hasher = new Hasher(); + hasher.hash(repository.provider.label); + hasher.hash(repoId); + this.id = `scm:repository:${hasher.value}`; this.name = repository.provider.rootUri ? basename(repository.provider.rootUri) : repository.provider.label; - this.ctorDescriptor = { ctor: RepositoryPanel, arguments: [repository] }; + this.ctorDescriptor = { ctor: RepositoryPane, arguments: [repository] }; } } diff --git a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts index bce8fae3ff68..84b22a17f2ff 100644 --- a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts +++ b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts @@ -38,13 +38,12 @@ class OpenSCMViewletAction extends ShowViewletAction { Registry.as(WorkbenchExtensions.Workbench) .registerWorkbenchContribution(DirtyDiffWorkbenchController, LifecyclePhase.Restored); -Registry.as(ViewletExtensions.Viewlets).registerViewlet(new ViewletDescriptor( +Registry.as(ViewletExtensions.Viewlets).registerViewlet(ViewletDescriptor.create( SCMViewlet, VIEWLET_ID, localize('source control', "Source Control"), - 'scm', - // {{SQL CARBON EDIT}} - 12 + 'codicon-source-control', + 12 // {{SQL CARBON EDIT}} )); Registry.as(WorkbenchExtensions.Workbench) @@ -52,7 +51,7 @@ Registry.as(WorkbenchExtensions.Workbench) // Register Action to Open Viewlet Registry.as(WorkbenchActionExtensions.WorkbenchActions).registerWorkbenchAction( - new SyncActionDescriptor(OpenSCMViewletAction, VIEWLET_ID, localize('toggleSCMViewlet', "Show SCM"), { + SyncActionDescriptor.create(OpenSCMViewletAction, VIEWLET_ID, localize('toggleSCMViewlet', "Show SCM"), { primary: 0, win: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_G }, linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_G }, @@ -81,7 +80,7 @@ Registry.as(ConfigurationExtensions.Configuration).regis }, 'scm.diffDecorations': { type: 'string', - enum: ['all', 'gutter', 'overview', 'none'], + enum: ['all', 'gutter', 'overview', 'minimap', 'none'], default: 'all', description: localize('diffDecorations', "Controls diff decorations in the editor.") }, @@ -98,7 +97,7 @@ Registry.as(ConfigurationExtensions.Configuration).regis localize('scm.diffDecorationsGutterVisibility.always', "Show the diff decorator in the gutter at all times."), localize('scm.diffDecorationsGutterVisibility.hover', "Show the diff decorator in the gutter only on hover.") ], - description: localize('scm.diffDecorationsGutterVisibility', "Controls the visibilty of the Source Control diff decorator in the gutter."), + description: localize('scm.diffDecorationsGutterVisibility', "Controls the visibility of the Source Control diff decorator in the gutter."), default: 'always' }, 'scm.alwaysShowActions': { diff --git a/src/vs/workbench/contrib/scm/browser/scmViewlet.ts b/src/vs/workbench/contrib/scm/browser/scmViewlet.ts index 781c3ca350b4..38335eeb0203 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewlet.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewlet.ts @@ -29,8 +29,8 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace import { IViewsRegistry, Extensions } from 'vs/workbench/common/views'; import { Registry } from 'vs/platform/registry/common/platform'; import { nextTick } from 'vs/base/common/process'; -import { RepositoryPanel, RepositoryViewDescriptor } from 'vs/workbench/contrib/scm/browser/repositoryPanel'; -import { MainPanelDescriptor, MainPanel } from 'vs/workbench/contrib/scm/browser/mainPanel'; +import { RepositoryPane, RepositoryViewDescriptor } from 'vs/workbench/contrib/scm/browser/repositoryPane'; +import { MainPaneDescriptor, MainPane } from 'vs/workbench/contrib/scm/browser/mainPane'; export interface ISpliceEvent { index: number; @@ -73,8 +73,8 @@ export class SCMViewlet extends ViewContainerViewlet implements IViewModel { } get visibleRepositories(): ISCMRepository[] { - return this.panels.filter(panel => panel instanceof RepositoryPanel) - .map(panel => (panel as RepositoryPanel).repository); + return this.panes.filter(pane => pane instanceof RepositoryPane) + .map(pane => (pane as RepositoryPane).repository); } get onDidChangeVisibleRepositories(): Event { @@ -107,11 +107,11 @@ export class SCMViewlet extends ViewContainerViewlet implements IViewModel { this.message = $('.empty-message', { tabIndex: 0 }, localize('no open repo', "No source control providers registered.")); const viewsRegistry = Registry.as(Extensions.ViewsRegistry); - viewsRegistry.registerViews([new MainPanelDescriptor(this)], VIEW_CONTAINER); + viewsRegistry.registerViews([new MainPaneDescriptor(this)], VIEW_CONTAINER); this._register(configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration('scm.alwaysShowProviders') && configurationService.getValue('scm.alwaysShowProviders')) { - this.viewsModel.setVisible(MainPanel.ID, true); + this.viewsModel.setVisible(MainPane.ID, true); } })); @@ -184,11 +184,11 @@ export class SCMViewlet extends ViewContainerViewlet implements IViewModel { const repository = this.visibleRepositories[0]; if (repository) { - const panel = this.panels - .filter(panel => panel instanceof RepositoryPanel && panel.repository === repository)[0] as RepositoryPanel | undefined; + const pane = this.panes + .filter(pane => pane instanceof RepositoryPane && pane.repository === repository)[0] as RepositoryPane | undefined; - if (panel) { - panel.focus(); + if (pane) { + pane.focus(); } else { super.focus(); } @@ -257,10 +257,10 @@ export class SCMViewlet extends ViewContainerViewlet implements IViewModel { for (const viewDescriptor of toSetInvisible) { if (oneToOne) { - const panel = this.panels.filter(panel => panel.id === viewDescriptor.id)[0]; + const pane = this.panes.filter(pane => pane.id === viewDescriptor.id)[0]; - if (panel) { - size = this.getPanelSize(panel); + if (pane) { + size = this.getPaneSize(pane); } } diff --git a/src/vs/workbench/contrib/search/browser/media/search-activity-bar.svg b/src/vs/workbench/contrib/search/browser/media/search-activity-bar.svg deleted file mode 100644 index a7ea9ab29abe..000000000000 --- a/src/vs/workbench/contrib/search/browser/media/search-activity-bar.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/search/browser/media/search.contribution.css b/src/vs/workbench/contrib/search/browser/media/search.contribution.css deleted file mode 100644 index 9f80aa7cd292..000000000000 --- a/src/vs/workbench/contrib/search/browser/media/search.contribution.css +++ /dev/null @@ -1,9 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -/* Activity Bar */ -.monaco-workbench .activitybar .monaco-action-bar .action-label.search { - -webkit-mask: url('search-activity-bar.svg') no-repeat 50% 50%; -} \ No newline at end of file diff --git a/src/vs/workbench/contrib/search/browser/media/searchview.css b/src/vs/workbench/contrib/search/browser/media/searchview.css index bdd4ba27c648..f1d4fc141017 100644 --- a/src/vs/workbench/contrib/search/browser/media/searchview.css +++ b/src/vs/workbench/contrib/search/browser/media/searchview.css @@ -47,6 +47,10 @@ height: 24px; /* set initial height before measure */ } +.search-view .monaco-inputbox > .wrapper > textarea.input { + scrollbar-width: none; /* Firefox: hide scrollbar */ +} + .search-view .monaco-inputbox > .wrapper > textarea.input::-webkit-scrollbar { display: none; } @@ -66,7 +70,6 @@ .search-view .search-widget .replace-input { position: relative; display: flex; - display: -webkit-flex; vertical-align: middle; width: auto !important; } @@ -157,6 +160,7 @@ margin-bottom: 0px; padding-bottom: 4px; user-select: text; + -webkit-user-select: text; } .search-view .foldermatch, @@ -298,10 +302,6 @@ background-color: rgba(255, 255, 255, 0.1) !important; } -.vs-dark .search-view .message { - opacity: .5; -} - .vs-dark .search-view .foldermatch, .vs-dark .search-view .filematch { padding: 0; diff --git a/src/vs/workbench/contrib/search/browser/patternInputWidget.ts b/src/vs/workbench/contrib/search/browser/patternInputWidget.ts index 29fa19f8c5b7..6d278b13c119 100644 --- a/src/vs/workbench/contrib/search/browser/patternInputWidget.ts +++ b/src/vs/workbench/contrib/search/browser/patternInputWidget.ts @@ -16,6 +16,9 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { attachInputBoxStyler, attachCheckboxStyler } from 'vs/platform/theme/common/styler'; import { ContextScopedHistoryInputBox } from 'vs/platform/browser/contextScopedHistoryWidget'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ISearchConfigurationProperties } from 'vs/workbench/services/search/common/search'; +import { Delayer } from 'vs/base/common/async'; export interface IOptions { placeholder?: string; @@ -23,6 +26,8 @@ export interface IOptions { validation?: IInputValidator; ariaLabel?: string; history?: string[]; + submitOnType?: boolean; + submitOnTypeDelay?: number; } export class PatternInputWidget extends Widget { @@ -39,13 +44,16 @@ export class PatternInputWidget extends Widget { protected inputBox!: HistoryInputBox; private _onSubmit = this._register(new Emitter()); - onSubmit: CommonEvent = this._onSubmit.event; + onSubmit: CommonEvent = this._onSubmit.event; - private _onCancel = this._register(new Emitter()); - onCancel: CommonEvent = this._onCancel.event; + private _onCancel = this._register(new Emitter()); + onCancel: CommonEvent = this._onCancel.event; + + private searchOnTypeDelayer: Delayer; constructor(parent: HTMLElement, private contextViewProvider: IContextViewProvider, options: IOptions = Object.create(null), @IThemeService protected themeService: IThemeService, + @IConfigurationService private configurationService: IConfigurationService, @IContextKeyService private readonly contextKeyService: IContextKeyService ) { super(); @@ -53,6 +61,8 @@ export class PatternInputWidget extends Widget { this.placeholder = options.placeholder || ''; this.ariaLabel = options.ariaLabel || nls.localize('defaultLabel', "input"); + this._register(this.searchOnTypeDelayer = new Delayer(this.searchConfig.searchOnTypeDebouncePeriod)); + this.render(options); parent.appendChild(this.domNode); @@ -139,6 +149,12 @@ export class PatternInputWidget extends Widget { this._register(attachInputBoxStyler(this.inputBox, this.themeService)); this.inputFocusTracker = dom.trackFocus(this.inputBox.inputElement); this.onkeyup(this.inputBox.inputElement, (keyboardEvent) => this.onInputKeyUp(keyboardEvent)); + this._register(this.inputBox.onDidChange(() => { + if (this.searchConfig.searchOnType) { + this._onCancel.fire(); + this.searchOnTypeDelayer.trigger(() => this._onSubmit.fire(true), this.searchConfig.searchOnTypeDebouncePeriod); + } + })); const controls = document.createElement('div'); controls.className = 'controls'; @@ -154,24 +170,32 @@ export class PatternInputWidget extends Widget { private onInputKeyUp(keyboardEvent: IKeyboardEvent) { switch (keyboardEvent.keyCode) { case KeyCode.Enter: - this._onSubmit.fire(false); + this.searchOnTypeDelayer.trigger(() => this._onSubmit.fire(false), 0); return; case KeyCode.Escape: - this._onCancel.fire(false); + this._onCancel.fire(); return; default: return; } } + + private get searchConfig() { + return this.configurationService.getValue('search'); + } } 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, + @IConfigurationService configurationService: IConfigurationService, @IContextKeyService contextKeyService: IContextKeyService ) { - super(parent, contextViewProvider, options, themeService, contextKeyService); + super(parent, contextViewProvider, options, themeService, configurationService, contextKeyService); } private useExcludesAndIgnoreFilesBox!: Checkbox; @@ -200,6 +224,7 @@ export class ExcludePatternInputWidget extends PatternInputWidget { isChecked: true, })); this._register(this.useExcludesAndIgnoreFilesBox.onChange(viaKeyboard => { + this._onChangeIgnoreBoxEmitter.fire(); if (!viaKeyboard) { this.inputBox.focus(); } diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index 6845ececa360..4ae03845a496 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -11,7 +11,6 @@ import * as objects from 'vs/base/common/objects'; import * as platform from 'vs/base/common/platform'; import { dirname } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; -import 'vs/css!./media/search.contribution'; import { registerLanguageCommand } from 'vs/editor/browser/editorExtensions'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { getSelectionSearchString } from 'vs/editor/contrib/find/findController'; @@ -42,9 +41,9 @@ import { ExplorerFolderContext, ExplorerRootContext, FilesExplorerFocusCondition import { OpenAnythingHandler } from 'vs/workbench/contrib/search/browser/openAnythingHandler'; import { OpenSymbolHandler } from 'vs/workbench/contrib/search/browser/openSymbolHandler'; import { registerContributions as replaceContributions } from 'vs/workbench/contrib/search/browser/replaceContributions'; -import { clearHistoryCommand, ClearSearchResultsAction, CloseReplaceAction, CollapseDeepestExpandedLevelAction, copyAllCommand, copyMatchCommand, copyPathCommand, FocusNextInputAction, FocusNextSearchResultAction, FocusPreviousInputAction, FocusPreviousSearchResultAction, focusSearchListCommand, getSearchView, openSearchView, OpenSearchViewletAction, RefreshAction, RemoveAction, ReplaceAction, ReplaceAllAction, ReplaceAllInFolderAction, ReplaceInFilesAction, toggleCaseSensitiveCommand, toggleRegexCommand, toggleWholeWordCommand, FindInFilesCommand } from 'vs/workbench/contrib/search/browser/searchActions'; +import { clearHistoryCommand, ClearSearchResultsAction, CloseReplaceAction, CollapseDeepestExpandedLevelAction, copyAllCommand, copyMatchCommand, copyPathCommand, FocusNextInputAction, FocusNextSearchResultAction, FocusPreviousInputAction, FocusPreviousSearchResultAction, focusSearchListCommand, getSearchView, openSearchView, OpenSearchViewletAction, RefreshAction, RemoveAction, ReplaceAction, ReplaceAllAction, ReplaceAllInFolderAction, ReplaceInFilesAction, toggleCaseSensitiveCommand, toggleRegexCommand, toggleWholeWordCommand, FindInFilesCommand, ToggleSearchOnTypeAction, OpenResultsInEditorAction, RerunEditorSearchAction } from 'vs/workbench/contrib/search/browser/searchActions'; import { SearchPanel } from 'vs/workbench/contrib/search/browser/searchPanel'; -import { SearchView } from 'vs/workbench/contrib/search/browser/searchView'; +import { SearchView, SearchViewPosition } from 'vs/workbench/contrib/search/browser/searchView'; import { SearchViewlet } from 'vs/workbench/contrib/search/browser/searchViewlet'; import { registerContributions as searchWidgetContributions } from 'vs/workbench/contrib/search/browser/searchWidget'; import * as Constants from 'vs/workbench/contrib/search/common/constants'; @@ -57,6 +56,7 @@ import { ISearchConfiguration, ISearchConfigurationProperties, PANEL_ID, VIEWLET import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { ExplorerViewlet } from 'vs/workbench/contrib/files/browser/explorerViewlet'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; registerSingleton(ISearchWorkbenchService, SearchWorkbenchService, true); registerSingleton(ISearchHistoryService, SearchHistoryService, true); @@ -334,7 +334,7 @@ CommandsRegistry.registerCommand({ const RevealInSideBarForSearchResultsCommand: ICommandAction = { id: Constants.RevealInSideBarForSearchResults, - title: nls.localize('revealInSideBar', "Reveal in Explorer") + title: nls.localize('revealInSideBar', "Reveal in Side Bar") }; MenuRegistry.appendMenuItem(MenuId.SearchContext, { @@ -503,15 +503,15 @@ class ShowAllSymbolsAction extends Action { } } -Registry.as(ViewletExtensions.Viewlets).registerViewlet(new ViewletDescriptor( +Registry.as(ViewletExtensions.Viewlets).registerViewlet(ViewletDescriptor.create( SearchViewlet, VIEWLET_ID, nls.localize('name', "Search"), - 'search', + 'codicon-search', 1 )); -Registry.as(PanelExtensions.Panels).registerPanel(new PanelDescriptor( +Registry.as(PanelExtensions.Panels).registerPanel(PanelDescriptor.create( SearchPanel, PANEL_ID, nls.localize('name', "Search"), @@ -531,7 +531,7 @@ class RegisterSearchViewContribution implements IWorkbenchContribution { const config = configurationService.getValue(); if (config.search.location === 'panel') { viewsRegistry.deregisterViews(viewsRegistry.getViews(VIEW_CONTAINER), VIEW_CONTAINER); - Registry.as(PanelExtensions.Panels).registerPanel(new PanelDescriptor( + Registry.as(PanelExtensions.Panels).registerPanel(PanelDescriptor.create( SearchPanel, PANEL_ID, nls.localize('name', "Search"), @@ -543,7 +543,7 @@ class RegisterSearchViewContribution implements IWorkbenchContribution { } } else { Registry.as(PanelExtensions.Panels).deregisterPanel(PANEL_ID); - viewsRegistry.registerViews([{ id: VIEW_ID, name: nls.localize('search', "Search"), ctorDescriptor: { ctor: SearchView }, canToggleVisibility: false }], VIEW_CONTAINER); + viewsRegistry.registerViews([{ id: VIEW_ID, name: nls.localize('search', "Search"), ctorDescriptor: { ctor: SearchView, arguments: [SearchViewPosition.SideBar] }, canToggleVisibility: false }], VIEW_CONTAINER); if (open) { viewletService.openViewlet(VIEWLET_ID); } @@ -565,7 +565,7 @@ const registry = Registry.as(ActionExtensions.Workbenc // Show Search and Find in Files are redundant, but we can't break keybindings by removing one. So it's the same action, same keybinding, registered to different IDs. // Show Search 'when' is redundant but if the two conflict with exactly the same keybinding and 'when' clause, then they can show up as "unbound" - #51780 -registry.registerWorkbenchAction(new SyncActionDescriptor(OpenSearchViewletAction, VIEWLET_ID, OpenSearchViewletAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_F }, Constants.SearchViewVisibleKey.toNegated()), 'View: Show Search', nls.localize('view', "View")); +registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenSearchViewletAction, VIEWLET_ID, OpenSearchViewletAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_F }, Constants.SearchViewVisibleKey.toNegated()), 'View: Show Search', nls.localize('view', "View")); KeybindingsRegistry.registerCommandAndKeybindingRule({ id: Constants.FindInFilesActionId, weight: KeybindingWeight.WorkbenchContrib, @@ -583,10 +583,10 @@ MenuRegistry.appendMenuItem(MenuId.MenubarEditMenu, { order: 1 }); -registry.registerWorkbenchAction(new SyncActionDescriptor(FocusNextSearchResultAction, FocusNextSearchResultAction.ID, FocusNextSearchResultAction.LABEL, { primary: KeyCode.F4 }, ContextKeyExpr.and(Constants.HasSearchResults)), 'Focus Next Search Result', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(FocusPreviousSearchResultAction, FocusPreviousSearchResultAction.ID, FocusPreviousSearchResultAction.LABEL, { primary: KeyMod.Shift | KeyCode.F4 }, ContextKeyExpr.and(Constants.HasSearchResults)), 'Focus Previous Search Result', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(FocusNextSearchResultAction, FocusNextSearchResultAction.ID, FocusNextSearchResultAction.LABEL, { primary: KeyCode.F4 }, ContextKeyExpr.and(Constants.HasSearchResults)), 'Focus Next Search Result', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(FocusPreviousSearchResultAction, FocusPreviousSearchResultAction.ID, FocusPreviousSearchResultAction.LABEL, { primary: KeyMod.Shift | KeyCode.F4 }, ContextKeyExpr.and(Constants.HasSearchResults)), 'Focus Previous Search Result', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(ReplaceInFilesAction, ReplaceInFilesAction.ID, ReplaceInFilesAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_H }), 'Replace in Files', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ReplaceInFilesAction, ReplaceInFilesAction.ID, ReplaceInFilesAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_H }), 'Replace in Files', category); MenuRegistry.appendMenuItem(MenuId.MenubarEditMenu, { group: '4_find_global', command: { @@ -617,16 +617,44 @@ KeybindingsRegistry.registerCommandAndKeybindingRule(objects.assign({ handler: toggleRegexCommand }, ToggleRegexKeybinding)); -registry.registerWorkbenchAction(new SyncActionDescriptor(CollapseDeepestExpandedLevelAction, CollapseDeepestExpandedLevelAction.ID, CollapseDeepestExpandedLevelAction.LABEL), 'Search: Collapse All', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(ShowAllSymbolsAction, ShowAllSymbolsAction.ID, ShowAllSymbolsAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_T }), 'Go to Symbol in Workspace...'); +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: Constants.AddCursorsAtSearchResults, + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.FileMatchOrMatchFocusKey), + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_L, + handler: (accessor, args: any) => { + const searchView = getSearchView(accessor.get(IViewletService), accessor.get(IPanelService)); + if (searchView) { + const tree: WorkbenchObjectTree = searchView.getControl(); + searchView.openEditorWithMultiCursor(tree.getFocus()[0]); + } + } +}); -registry.registerWorkbenchAction(new SyncActionDescriptor(RefreshAction, RefreshAction.ID, RefreshAction.LABEL), 'Search: Refresh', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(ClearSearchResultsAction, ClearSearchResultsAction.ID, ClearSearchResultsAction.LABEL), 'Search: Clear Search Results', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(CollapseDeepestExpandedLevelAction, CollapseDeepestExpandedLevelAction.ID, CollapseDeepestExpandedLevelAction.LABEL), 'Search: Collapse All', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ShowAllSymbolsAction, ShowAllSymbolsAction.ID, ShowAllSymbolsAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_T }), 'Go to Symbol in Workspace...'); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleSearchOnTypeAction, ToggleSearchOnTypeAction.ID, ToggleSearchOnTypeAction.LABEL), 'Search: Toggle Search on Type', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(RefreshAction, RefreshAction.ID, RefreshAction.LABEL), 'Search: Refresh', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ClearSearchResultsAction, ClearSearchResultsAction.ID, ClearSearchResultsAction.LABEL), 'Search: Clear Search Results', category); + +registry.registerWorkbenchAction( + SyncActionDescriptor.create(OpenResultsInEditorAction, OpenResultsInEditorAction.ID, OpenResultsInEditorAction.LABEL, + { mac: { primary: KeyMod.CtrlCmd | KeyCode.Enter } }, + ContextKeyExpr.and(Constants.HasSearchResults, Constants.SearchViewFocusedKey, Constants.EnableSearchEditorPreview)), + 'Search: Open Results in Editor', category, + ContextKeyExpr.and(Constants.EnableSearchEditorPreview)); + +registry.registerWorkbenchAction( + SyncActionDescriptor.create(RerunEditorSearchAction, RerunEditorSearchAction.ID, RerunEditorSearchAction.LABEL, + { primary: KeyMod.Shift | KeyMod.CtrlCmd | KeyCode.KEY_R }, + ContextKeyExpr.and(EditorContextKeys.languageId.isEqualTo('search-result'))), + 'Search Editor: Search Again', category, + ContextKeyExpr.and(EditorContextKeys.languageId.isEqualTo('search-result'))); // Register Quick Open Handler Registry.as(QuickOpenExtensions.Quickopen).registerDefaultQuickOpenHandler( - new QuickOpenHandlerDescriptor( + QuickOpenHandlerDescriptor.create( OpenAnythingHandler, OpenAnythingHandler.ID, '', @@ -636,7 +664,7 @@ Registry.as(QuickOpenExtensions.Quickopen).registerDefaultQu ); Registry.as(QuickOpenExtensions.Quickopen).registerQuickOpenHandler( - new QuickOpenHandlerDescriptor( + QuickOpenHandlerDescriptor.create( OpenSymbolHandler, OpenSymbolHandler.ID, ShowAllSymbolsAction.ALL_SYMBOLS_PREFIX, @@ -747,7 +775,7 @@ configurationRegistry.registerConfiguration({ '', '' ], - default: 'auto', + default: 'alwaysExpand', description: nls.localize('search.collapseAllResults', "Controls whether the search results will be collapsed or expanded."), }, 'search.useReplacePreview': { @@ -775,6 +803,21 @@ configurationRegistry.registerConfiguration({ ], default: 'auto', description: nls.localize('search.actionsPosition', "Controls the positioning of the actionbar on rows in the search view.") + }, + 'search.searchOnType': { + type: 'boolean', + default: true, + description: nls.localize('search.searchOnType', "Search all files as you type.") + }, + 'search.searchOnTypeDebouncePeriod': { + type: 'number', + default: 300, + markdownDescription: nls.localize('search.searchOnTypeDebouncePeriod', "When `#search.searchOnType#` is enabled, controls the timeout in milliseconds between a character being typed and the search starting. Has no effect when `search.searchOnType` is disabled.") + }, + 'search.enableSearchEditorPreview': { + type: 'boolean', + default: false, + description: nls.localize('search.enableSearchEditorPreview', "Experimental: When enabled, allows opening workspace search results in an editor.") } } }); diff --git a/src/vs/workbench/contrib/search/browser/searchActions.ts b/src/vs/workbench/contrib/search/browser/searchActions.ts index 49343231d6b8..c625a2f167cc 100644 --- a/src/vs/workbench/contrib/search/browser/searchActions.ts +++ b/src/vs/workbench/contrib/search/browser/searchActions.ts @@ -13,7 +13,7 @@ import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService import { ILabelService } from 'vs/platform/label/common/label'; 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 { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { getSelectionKeyboardEvent, WorkbenchObjectTree } from 'vs/platform/list/browser/listService'; import { SearchView } from 'vs/workbench/contrib/search/browser/searchView'; @@ -23,12 +23,15 @@ import { FolderMatch, FileMatch, FileMatchOrMatch, FolderMatchWithResource, Matc import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; -import { ISearchConfiguration, VIEWLET_ID, PANEL_ID } from 'vs/workbench/services/search/common/search'; +import { ISearchConfiguration, VIEWLET_ID, PANEL_ID, ISearchConfigurationProperties } from 'vs/workbench/services/search/common/search'; import { ISearchHistoryService } from 'vs/workbench/contrib/search/common/searchHistoryService'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { SearchViewlet } from 'vs/workbench/contrib/search/browser/searchViewlet'; import { SearchPanel } from 'vs/workbench/contrib/search/browser/searchPanel'; import { ITreeNavigator } from 'vs/base/browser/ui/tree/tree'; +import { createEditorFromSearchResult, refreshActiveEditorSearch } from 'vs/workbench/contrib/search/browser/searchEditor'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; export function isSearchViewFocused(viewletService: IViewletService, panelService: IPanelService): boolean { const searchView = getSearchView(viewletService, panelService); @@ -251,6 +254,30 @@ export class CloseReplaceAction extends Action { } } +// --- Toggle Search On Type + +export class ToggleSearchOnTypeAction extends Action { + + static readonly ID = 'workbench.action.toggleSearchOnType'; + static readonly LABEL = nls.localize('toggleTabs', "Toggle Search on Type"); + + private static readonly searchOnTypeKey = 'search.searchOnType'; + + constructor( + id: string, + label: string, + @IConfigurationService private readonly configurationService: IConfigurationService + ) { + super(id, label); + } + + run(): Promise { + const searchOnType = this.configurationService.getValue(ToggleSearchOnTypeAction.searchOnTypeKey); + return this.configurationService.updateValue(ToggleSearchOnTypeAction.searchOnTypeKey, !searchOnType); + } +} + + export class RefreshAction extends Action { static readonly ID: string = 'search.action.refreshSearchResults'; @@ -265,7 +292,7 @@ export class RefreshAction extends Action { get enabled(): boolean { const searchView = getSearchView(this.viewletService, this.panelService); - return !!searchView && searchView.hasSearchResults(); + return !!searchView && searchView.hasSearchPattern(); } update(): void { @@ -275,7 +302,7 @@ export class RefreshAction extends Action { run(): Promise { const searchView = getSearchView(this.viewletService, this.panelService); if (searchView) { - searchView.onQueryChanged(); + searchView.onQueryChanged(false); } return Promise.resolve(); @@ -394,6 +421,63 @@ export class CancelSearchAction extends Action { } } +export class OpenResultsInEditorAction extends Action { + + static readonly ID: string = Constants.OpenInEditorCommandId; + static readonly LABEL = nls.localize('search.openResultsInEditor', "Open Results in Editor"); + + constructor(id: string, label: string, + @IViewletService private viewletService: IViewletService, + @IPanelService private panelService: IPanelService, + @ILabelService private labelService: ILabelService, + @IEditorService private editorService: IEditorService, + @IConfigurationService private configurationService: IConfigurationService + ) { + super(id, label, 'codicon-go-to-file'); + } + + get enabled(): boolean { + const searchView = getSearchView(this.viewletService, this.panelService); + return !!searchView && searchView.hasSearchResults(); + } + + update() { + this._setEnabled(this.enabled); + } + + async run() { + const searchView = getSearchView(this.viewletService, this.panelService); + if (searchView && this.configurationService.getValue('search').enableSearchEditorPreview) { + await createEditorFromSearchResult(searchView.searchResult, searchView.searchIncludePattern.getValue(), searchView.searchExcludePattern.getValue(), this.labelService, this.editorService); + } + } +} + +export class RerunEditorSearchAction extends Action { + + static readonly ID: string = Constants.RerunEditorSearchCommandId; + static readonly LABEL = nls.localize('search.rerunEditorSearch', "Search Again"); + + constructor(id: string, label: string, + @IInstantiationService private instantiationService: IInstantiationService, + @IEditorService private editorService: IEditorService, + @IConfigurationService private configurationService: IConfigurationService, + @IWorkspaceContextService private contextService: IWorkspaceContextService, + @ILabelService private labelService: ILabelService, + @IProgressService private progressService: IProgressService + ) { + super(id, label); + } + + async run() { + if (this.configurationService.getValue('search').enableSearchEditorPreview) { + await this.progressService.withProgress({ location: ProgressLocation.Window }, + () => refreshActiveEditorSearch(this.editorService, this.instantiationService, this.contextService, this.labelService, this.configurationService)); + } + } +} + + export class FocusNextSearchResultAction extends Action { static readonly ID = 'search.action.focusNextSearchResult'; static readonly LABEL = nls.localize('FocusNextSearchResult.label', "Focus Next Search Result"); diff --git a/src/vs/workbench/contrib/search/browser/searchEditor.ts b/src/vs/workbench/contrib/search/browser/searchEditor.ts new file mode 100644 index 000000000000..62969ab1c10a --- /dev/null +++ b/src/vs/workbench/contrib/search/browser/searchEditor.ts @@ -0,0 +1,266 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Match, searchMatchComparer, FileMatch, SearchResult, SearchModel } from 'vs/workbench/contrib/search/common/searchModel'; +import { repeat } from 'vs/base/common/strings'; +import { ILabelService } from 'vs/platform/label/common/label'; +import { coalesce, flatten } from 'vs/base/common/arrays'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { URI } from 'vs/base/common/uri'; +import { ITextQuery, IPatternInfo, ISearchConfigurationProperties } from 'vs/workbench/services/search/common/search'; +import * as network from 'vs/base/common/network'; +import { Range } from 'vs/editor/common/core/range'; +import { ITextModel, TrackedRangeStickiness } from 'vs/editor/common/model'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ITextQueryBuilderOptions, QueryBuilder } from 'vs/workbench/contrib/search/common/queryBuilder'; +import { getOutOfWorkspaceEditorResources } from 'vs/workbench/contrib/search/common/search'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { searchEditorFindMatch, searchEditorFindMatchBorder } from 'vs/platform/theme/common/colorRegistry'; + +// Using \r\n on Windows inserts an extra newline between results. +const lineDelimiter = '\n'; + +const translateRangeLines = + (n: number) => + (range: Range) => + new Range(range.startLineNumber + n, range.startColumn, range.endLineNumber + n, range.endColumn); + +const matchToSearchResultFormat = (match: Match): { line: string, ranges: Range[], lineNumber: string }[] => { + const getLinePrefix = (i: number) => `${match.range().startLineNumber + i}`; + + const fullMatchLines = match.fullPreviewLines(); + const largestPrefixSize = fullMatchLines.reduce((largest, _, i) => Math.max(getLinePrefix(i).length, largest), 0); + + + const results: { line: string, ranges: Range[], lineNumber: string }[] = []; + + fullMatchLines + .forEach((sourceLine, i) => { + const lineNumber = getLinePrefix(i); + const paddingStr = repeat(' ', largestPrefixSize - lineNumber.length); + const prefix = ` ${lineNumber}: ${paddingStr}`; + const prefixOffset = prefix.length; + + const line = (prefix + sourceLine); + + const rangeOnThisLine = ({ start, end }: { start?: number; end?: number; }) => new Range(1, (start ?? 1) + prefixOffset, 1, (end ?? sourceLine.length + 1) + prefixOffset); + + const matchRange = match.range(); + const matchIsSingleLine = matchRange.startLineNumber === matchRange.endLineNumber; + + let lineRange; + if (matchIsSingleLine) { lineRange = (rangeOnThisLine({ start: matchRange.startColumn, end: matchRange.endColumn })); } + else if (i === 0) { lineRange = (rangeOnThisLine({ start: matchRange.startColumn })); } + else if (i === fullMatchLines.length - 1) { lineRange = (rangeOnThisLine({ end: matchRange.endColumn })); } + else { lineRange = (rangeOnThisLine({})); } + + results.push({ lineNumber: lineNumber, line, ranges: [lineRange] }); + }); + + return results; +}; + +type SearchResultSerialization = { text: string[], matchRanges: Range[] }; +function fileMatchToSearchResultFormat(fileMatch: FileMatch, labelFormatter: (x: URI) => string): SearchResultSerialization { + const serializedMatches = flatten(fileMatch.matches() + .sort(searchMatchComparer) + .map(match => matchToSearchResultFormat(match))); + + const uriString = labelFormatter(fileMatch.resource); + let text: string[] = [`${uriString}:`]; + let matchRanges: Range[] = []; + + const targetLineNumberToOffset: Record = {}; + + const seenLines = new Set(); + serializedMatches.forEach(match => { + if (!seenLines.has(match.line)) { + targetLineNumberToOffset[match.lineNumber] = text.length; + seenLines.add(match.line); + text.push(match.line); + } + + matchRanges.push(...match.ranges.map(translateRangeLines(targetLineNumberToOffset[match.lineNumber]))); + }); + + + return { text, matchRanges }; +} + +const flattenSearchResultSerializations = (serializations: SearchResultSerialization[]): SearchResultSerialization => { + let text: string[] = []; + let matchRanges: Range[] = []; + + serializations.forEach(serialized => { + serialized.matchRanges.map(translateRangeLines(text.length)).forEach(range => matchRanges.push(range)); + serialized.text.forEach(line => text.push(line)); + text.push(''); // new line + }); + + return { text, matchRanges }; +}; + +const contentPatternToSearchResultHeader = (pattern: ITextQuery | null, includes: string, excludes: string): string[] => { + if (!pattern) { return []; } + + const removeNullFalseAndUndefined = (a: (T | null | false | undefined)[]) => a.filter(a => a !== false && a !== null && a !== undefined) as T[]; + + const escapeNewlines = (str: string) => str.replace(/\\/g, '\\\\').replace(/\n/g, '\\n'); + + return removeNullFalseAndUndefined([ + `# Query: ${escapeNewlines(pattern.contentPattern.pattern)}`, + + (pattern.contentPattern.isCaseSensitive || pattern.contentPattern.isWordMatch || pattern.contentPattern.isRegExp || pattern.userDisabledExcludesAndIgnoreFiles) + && `# Flags: ${coalesce([ + pattern.contentPattern.isCaseSensitive && 'CaseSensitive', + pattern.contentPattern.isWordMatch && 'WordMatch', + pattern.contentPattern.isRegExp && 'RegExp', + pattern.userDisabledExcludesAndIgnoreFiles && 'IgnoreExcludeSettings' + ]).join(' ')}`, + includes ? `# Including: ${includes}` : undefined, + excludes ? `# Excluding: ${excludes}` : undefined, + '' + ]); +}; + +const searchHeaderToContentPattern = (header: string[]): { pattern: string, flags: { regex: boolean, wholeWord: boolean, caseSensitive: boolean, ignoreExcludes: boolean }, includes: string, excludes: string } => { + const query = { + pattern: '', + flags: { regex: false, caseSensitive: false, ignoreExcludes: false, wholeWord: false }, + includes: '', + excludes: '' + }; + + const unescapeNewlines = (str: string) => str.replace(/\\\\/g, '\\').replace(/\\n/g, '\n'); + const parseYML = /^# ([^:]*): (.*)$/; + for (const line of header) { + const parsed = parseYML.exec(line); + if (!parsed) { continue; } + const [, key, value] = parsed; + switch (key) { + case 'Query': query.pattern = unescapeNewlines(value); break; + case 'Including': query.includes = value; break; + case 'Excluding': query.excludes = value; break; + case 'Flags': { + query.flags = { + regex: value.indexOf('RegExp') !== -1, + caseSensitive: value.indexOf('CaseSensitive') !== -1, + ignoreExcludes: value.indexOf('IgnoreExcludeSettings') !== -1, + wholeWord: value.indexOf('WordMatch') !== -1 + }; + } + } + } + + return query; +}; + +const serializeSearchResultForEditor = (searchResult: SearchResult, rawIncludePattern: string, rawExcludePattern: string, labelFormatter: (x: URI) => string): SearchResultSerialization => { + const header = contentPatternToSearchResultHeader(searchResult.query, rawIncludePattern, rawExcludePattern); + const allResults = + flattenSearchResultSerializations( + flatten(searchResult.folderMatches().sort(searchMatchComparer) + .map(folderMatch => folderMatch.matches().sort(searchMatchComparer) + .map(fileMatch => fileMatchToSearchResultFormat(fileMatch, labelFormatter))))); + + return { matchRanges: allResults.matchRanges.map(translateRangeLines(header.length)), text: header.concat(allResults.text) }; +}; + +export const refreshActiveEditorSearch = + async (editorService: IEditorService, instantiationService: IInstantiationService, contextService: IWorkspaceContextService, labelService: ILabelService, configurationService: IConfigurationService) => { + const model = editorService.activeTextEditorWidget?.getModel(); + if (!model) { return; } + + const textModel = model as ITextModel; + + const header = textModel.getValueInRange(new Range(1, 1, 5, 1)) + .split(lineDelimiter) + .filter(line => line.indexOf('# ') === 0); + + const contentPattern = searchHeaderToContentPattern(header); + + const content: IPatternInfo = { + pattern: contentPattern.pattern, + isRegExp: contentPattern.flags.regex, + isCaseSensitive: contentPattern.flags.caseSensitive, + isWordMatch: contentPattern.flags.wholeWord + }; + + const options: ITextQueryBuilderOptions = { + _reason: 'searchEditor', + extraFileResources: instantiationService.invokeFunction(getOutOfWorkspaceEditorResources), + maxResults: 10000, + disregardIgnoreFiles: contentPattern.flags.ignoreExcludes, + disregardExcludeSettings: contentPattern.flags.ignoreExcludes, + excludePattern: contentPattern.excludes, + includePattern: contentPattern.includes, + previewOptions: { + matchLines: 1, + charsPerLine: 1000 + }, + isSmartCase: configurationService.getValue('search').smartCase, + expandPatterns: true + }; + + const folderResources = contextService.getWorkspace().folders; + + let query: ITextQuery; + try { + const queryBuilder = instantiationService.createInstance(QueryBuilder); + query = queryBuilder.text(content, folderResources.map(folder => folder.uri), options); + } catch (err) { + return; + } + + const searchModel = instantiationService.createInstance(SearchModel); + await searchModel.search(query); + + const labelFormatter = (uri: URI): string => labelService.getUriLabel(uri, { relative: true }); + const results = serializeSearchResultForEditor(searchModel.searchResult, '', '', labelFormatter); + + textModel.setValue(results.text.join(lineDelimiter)); + textModel.deltaDecorations([], results.matchRanges.map(range => ({ range, options: { className: 'searchEditorFindMatch', stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges } }))); + }; + + +export const createEditorFromSearchResult = + async (searchResult: SearchResult, rawIncludePattern: string, rawExcludePattern: string, labelService: ILabelService, editorService: IEditorService) => { + const searchTerm = searchResult.query?.contentPattern.pattern.replace(/[^\w-_. ]/g, '') || 'Search'; + + const labelFormatter = (uri: URI): string => labelService.getUriLabel(uri, { relative: true }); + + const results = serializeSearchResultForEditor(searchResult, rawIncludePattern, rawExcludePattern, labelFormatter); + + let possible = { + contents: results.text.join(lineDelimiter), + mode: 'search-result', + resource: URI.from({ scheme: network.Schemas.untitled, path: searchTerm }) + }; + + let id = 0; + while (editorService.getOpened(possible)) { + possible.resource = possible.resource.with({ path: searchTerm + '-' + ++id }); + } + + const editor = await editorService.openEditor(possible); + const control = editor?.getControl()!; + control.updateOptions({ lineNumbers: 'off' }); + + const model = control.getModel() as ITextModel; + + model.deltaDecorations([], results.matchRanges.map(range => ({ range, options: { className: 'searchEditorFindMatch', stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges } }))); + }; + +// theming +registerThemingParticipant((theme, collector) => { + collector.addRule(`.monaco-editor .searchEditorFindMatch { background-color: ${theme.getColor(searchEditorFindMatch)}; }`); + + const findMatchHighlightBorder = theme.getColor(searchEditorFindMatchBorder); + if (findMatchHighlightBorder) { + collector.addRule(`.monaco-editor .searchEditorFindMatch { border: 1px ${theme.type === 'hc' ? 'dotted' : 'solid'} ${findMatchHighlightBorder}; box-sizing: border-box; }`); + } +}); diff --git a/src/vs/workbench/contrib/search/browser/searchPanel.ts b/src/vs/workbench/contrib/search/browser/searchPanel.ts index c40912b0ae1e..6960ab3096aa 100644 --- a/src/vs/workbench/contrib/search/browser/searchPanel.ts +++ b/src/vs/workbench/contrib/search/browser/searchPanel.ts @@ -7,7 +7,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { PANEL_ID } from 'vs/workbench/services/search/common/search'; -import { SearchView } from 'vs/workbench/contrib/search/browser/searchView'; +import { SearchView, SearchViewPosition } from 'vs/workbench/contrib/search/browser/searchView'; import { Panel } from 'vs/workbench/browser/panel'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { localize } from 'vs/nls'; @@ -25,13 +25,13 @@ export class SearchPanel extends Panel { @IInstantiationService instantiationService: IInstantiationService, ) { super(PANEL_ID, telemetryService, themeService, storageService); - this.searchView = this._register(instantiationService.createInstance(SearchView, { id: PANEL_ID, title: localize('search', "Search") })); + this.searchView = this._register(instantiationService.createInstance(SearchView, SearchViewPosition.Panel, { id: PANEL_ID, title: localize('search', "Search"), actionRunner: this.getActionRunner() })); this._register(this.searchView.onDidChangeTitleArea(() => this.updateTitleArea())); this._register(this.onDidChangeVisibility(visible => this.searchView.setVisible(visible))); } create(parent: HTMLElement): void { - dom.addClasses(parent, 'monaco-panel-view', 'search-panel'); + dom.addClasses(parent, 'monaco-pane-view', 'search-panel'); this.searchView.render(); dom.append(parent, this.searchView.element); this.searchView.setExpanded(true); diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index b445e1ba1201..4c63d2f5fb0f 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -20,7 +20,7 @@ import * as env from 'vs/base/common/platform'; import * as strings from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; import 'vs/css!./media/searchview'; -import { ICodeEditor, isCodeEditor, isDiffEditor } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditor, isCodeEditor, isDiffEditor, getCodeEditor } from 'vs/editor/browser/editorBrowser'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import * as nls from 'vs/nls'; import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; @@ -36,14 +36,14 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { IProgressService, IProgressStep, IProgress } from 'vs/platform/progress/common/progress'; import { IPatternInfo, ISearchComplete, ISearchConfiguration, ISearchConfigurationProperties, ITextQuery, VIEW_ID, VIEWLET_ID } from 'vs/workbench/services/search/common/search'; import { ISearchHistoryService, ISearchHistoryValues } from 'vs/workbench/contrib/search/common/searchHistoryService'; -import { diffInserted, diffInsertedOutline, diffRemoved, diffRemovedOutline, editorFindMatchHighlight, editorFindMatchHighlightBorder, listActiveSelectionForeground } from 'vs/platform/theme/common/colorRegistry'; +import { diffInserted, diffInsertedOutline, diffRemoved, diffRemovedOutline, editorFindMatchHighlight, editorFindMatchHighlightBorder, listActiveSelectionForeground, foreground } from 'vs/platform/theme/common/colorRegistry'; import { ICssStyleCollector, ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { OpenFileFolderAction, OpenFolderAction } from 'vs/workbench/browser/actions/workspaceActions'; import { ResourceLabels } from 'vs/workbench/browser/labels'; import { IEditor } from 'vs/workbench/common/editor'; import { ExcludePatternInputWidget, PatternInputWidget } from 'vs/workbench/contrib/search/browser/patternInputWidget'; -import { CancelSearchAction, ClearSearchResultsAction, CollapseDeepestExpandedLevelAction, RefreshAction, IFindInFilesArgs } from 'vs/workbench/contrib/search/browser/searchActions'; +import { CancelSearchAction, ClearSearchResultsAction, CollapseDeepestExpandedLevelAction, RefreshAction, IFindInFilesArgs, OpenResultsInEditorAction, appendKeyBindingLabel } from 'vs/workbench/contrib/search/browser/searchActions'; import { FileMatchRenderer, FolderMatchRenderer, MatchRenderer, SearchAccessibilityProvider, SearchDelegate, SearchDND } from 'vs/workbench/contrib/search/browser/searchResultsView'; import { ISearchWidgetOptions, SearchWidget } from 'vs/workbench/contrib/search/browser/searchWidget'; import * as Constants from 'vs/workbench/contrib/search/common/constants'; @@ -53,14 +53,20 @@ import { getOutOfWorkspaceEditorResources } from 'vs/workbench/contrib/search/co import { FileMatch, FileMatchOrMatch, IChangeEvent, ISearchWorkbenchService, Match, RenderableMatch, searchMatchComparer, SearchModel, SearchResult, FolderMatch, FolderMatchWithResource } from 'vs/workbench/contrib/search/common/searchModel'; import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { IPreferencesService, ISettingsEditorOptions } from 'vs/workbench/services/preferences/common/preferences'; -import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; +import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { relativePath } from 'vs/base/common/resources'; import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; -import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet'; +import { ViewletPane, IViewletPaneOptions } from 'vs/workbench/browser/parts/views/paneViewlet'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { Memento, MementoObject } from 'vs/workbench/common/memento'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { MultiCursorSelectionController } from 'vs/editor/contrib/multicursor/multicursor'; +import { Selection } from 'vs/editor/common/core/selection'; +import { SIDE_BAR_BACKGROUND, PANEL_BACKGROUND } from 'vs/workbench/common/theme'; +import { createEditorFromSearchResult } from 'vs/workbench/contrib/search/browser/searchEditor'; +import { ILabelService } from 'vs/platform/label/common/label'; +import { Color, RGBA } from 'vs/base/common/color'; const $ = dom.$; @@ -70,7 +76,13 @@ enum SearchUIState { SlowSearch } -export class SearchView extends ViewletPanel { +export enum SearchViewPosition { + SideBar, + Panel +} + +const SEARCH_CANCELLED_MESSAGE = nls.localize('searchCanceled', "Search was canceled before any results could be found - "); +export class SearchView extends ViewletPane { private static readonly MAX_TEXT_RESULTS = 10000; @@ -98,10 +110,11 @@ export class SearchView extends ViewletPanel { private folderMatchFocused: IContextKey; private matchFocused: IContextKey; private hasSearchResultsKey: IContextKey; + private enableSearchEditorPreview: IContextKey; private state: SearchUIState = SearchUIState.Idle; - private actions: Array = []; + private actions: Array = []; private cancelAction: CancelSearchAction; private refreshAction: RefreshAction; private contextMenu: IMenu | null = null; @@ -128,9 +141,11 @@ export class SearchView extends ViewletPanel { private searchWithoutFolderMessageElement: HTMLElement | undefined; private currentSearchQ = Promise.resolve(); + private addToSearchHistoryDelayer: Delayer; constructor( - options: IViewletPanelOptions, + private position: SearchViewPosition, + options: IViewletPaneOptions, @IFileService private readonly fileService: IFileService, @IEditorService private readonly editorService: IEditorService, @IProgressService private readonly progressService: IProgressService, @@ -143,7 +158,7 @@ export class SearchView extends ViewletPanel { @ISearchWorkbenchService private readonly searchWorkbenchService: ISearchWorkbenchService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IReplaceService private readonly replaceService: IReplaceService, - @IUntitledEditorService private readonly untitledEditorService: IUntitledEditorService, + @IUntitledTextEditorService private readonly untitledTextEditorService: IUntitledTextEditorService, @IPreferencesService private readonly preferencesService: IPreferencesService, @IThemeService protected themeService: IThemeService, @ISearchHistoryService private readonly searchHistoryService: ISearchHistoryService, @@ -152,9 +167,10 @@ export class SearchView extends ViewletPanel { @IAccessibilityService private readonly accessibilityService: IAccessibilityService, @IKeybindingService keybindingService: IKeybindingService, @IStorageService storageService: IStorageService, + @ILabelService private readonly labelService: ILabelService, @IOpenerService private readonly openerService: IOpenerService ) { - super({ ...(options as IViewletPanelOptions), id: VIEW_ID, ariaHeaderLabel: nls.localize('searchView', "Search") }, keybindingService, contextMenuService, configurationService, contextKeyService); + super({ ...options, id: VIEW_ID, ariaHeaderLabel: nls.localize('searchView', "Search") }, keybindingService, contextMenuService, configurationService, contextKeyService); this.viewletVisible = Constants.SearchViewVisibleKey.bindTo(contextKeyService); this.viewletFocused = Constants.SearchViewFocusedKey.bindTo(contextKeyService); @@ -169,6 +185,14 @@ export class SearchView extends ViewletPanel { this.folderMatchFocused = Constants.FolderFocusKey.bindTo(contextKeyService); this.matchFocused = Constants.MatchFocusKey.bindTo(this.contextKeyService); this.hasSearchResultsKey = Constants.HasSearchResults.bindTo(this.contextKeyService); + this.enableSearchEditorPreview = Constants.EnableSearchEditorPreview.bindTo(this.contextKeyService); + + this.enableSearchEditorPreview.set(this.searchConfig.enableSearchEditorPreview); + this.configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('search.previewSearchEditor')) { + this.enableSearchEditorPreview.set(this.searchConfig.enableSearchEditorPreview); + } + }); this.viewModel = this._register(this.searchWorkbenchService.searchModel); this.queryBuilder = this.instantiationService.createInstance(QueryBuilder); @@ -176,16 +200,25 @@ export class SearchView extends ViewletPanel { this.viewletState = this.memento.getMemento(StorageScope.WORKSPACE); this._register(this.fileService.onFileChanges(e => this.onFilesChanged(e))); - this._register(this.untitledEditorService.onDidChangeDirty(e => this.onUntitledDidChangeDirty(e))); + this._register(this.untitledTextEditorService.onDidDisposeModel(e => this.onUntitledDidDispose(e))); this._register(this.contextService.onDidChangeWorkbenchState(() => this.onDidChangeWorkbenchState())); this._register(this.searchHistoryService.onDidClearHistory(() => this.clearHistory())); this.delayedRefresh = this._register(new Delayer(250)); + this.addToSearchHistoryDelayer = this._register(new Delayer(500)); + this.actions = [ this._register(this.instantiationService.createInstance(ClearSearchResultsAction, ClearSearchResultsAction.ID, ClearSearchResultsAction.LABEL)), this._register(this.instantiationService.createInstance(CollapseDeepestExpandedLevelAction, CollapseDeepestExpandedLevelAction.ID, CollapseDeepestExpandedLevelAction.LABEL)) ]; + + if (this.searchConfig.enableSearchEditorPreview) { + this.actions.push( + this._register(this.instantiationService.createInstance(OpenResultsInEditorAction, OpenResultsInEditorAction.ID, OpenResultsInEditorAction.LABEL)) + ); + } + this.refreshAction = this._register(this.instantiationService.createInstance(RefreshAction, RefreshAction.ID, RefreshAction.LABEL)); this.cancelAction = this._register(this.instantiationService.createInstance(CancelSearchAction, CancelSearchAction.ID, CancelSearchAction.LABEL)); } @@ -264,8 +297,8 @@ export class SearchView extends ViewletPanel { this.inputPatternIncludes.setValue(patternIncludes); - this.inputPatternIncludes.onSubmit(() => this.onQueryChanged(true)); - this.inputPatternIncludes.onCancel(() => this.viewModel.cancelSearch()); // Cancel search without focusing the search widget + this.inputPatternIncludes.onSubmit(triggeredOnType => this.onQueryChanged(true, triggeredOnType)); + this.inputPatternIncludes.onCancel(() => this.cancelSearch(false)); this.trackInputBox(this.inputPatternIncludes.inputFocusTracker, this.inputPatternIncludesFocused); // excludes list @@ -280,8 +313,9 @@ export class SearchView extends ViewletPanel { this.inputPatternExcludes.setValue(patternExclusions); this.inputPatternExcludes.setUseExcludesAndIgnoreFiles(useExcludesAndIgnoreFiles); - this.inputPatternExcludes.onSubmit(() => this.onQueryChanged(true)); - this.inputPatternExcludes.onCancel(() => this.viewModel.cancelSearch()); // Cancel search without focusing the search widget + this.inputPatternExcludes.onSubmit(triggeredOnType => this.onQueryChanged(true, triggeredOnType)); + this.inputPatternExcludes.onCancel(() => this.cancelSearch(false)); + this.inputPatternExcludes.onChangeIgnoreBox(() => this.onQueryChanged(true)); this.trackInputBox(this.inputPatternExcludes.inputFocusTracker, this.inputPatternExclusionsFocused); this.messagesElement = dom.append(this.container, $('.messages')); @@ -381,8 +415,8 @@ export class SearchView extends ViewletPanel { this.searchWidget.toggleReplace(true); } - this._register(this.searchWidget.onSearchSubmit(() => this.onQueryChanged())); - this._register(this.searchWidget.onSearchCancel(() => this.cancelSearch())); + this._register(this.searchWidget.onSearchSubmit(triggeredOnType => this.onQueryChanged(false, triggeredOnType))); + this._register(this.searchWidget.onSearchCancel(({ focus }) => this.cancelSearch(focus))); this._register(this.searchWidget.searchInput.onDidOptionChange(() => this.onQueryChanged(true))); this._register(this.searchWidget.onDidHeightChange(() => this.reLayout())); @@ -446,7 +480,7 @@ export class SearchView extends ViewletPanel { } refreshTree(event?: IChangeEvent): void { - const collapseResults = this.configurationService.getValue('search').collapseResults; + const collapseResults = this.searchConfig.collapseResults; if (!event || event.added || event.removed) { // Refresh whole tree this.tree.setChildren(null, this.createResultIterator(collapseResults)); @@ -538,6 +572,7 @@ export class SearchView extends ViewletPanel { progressComplete(); const messageEl = this.clearMessage(); dom.append(messageEl, $('p', undefined, afterReplaceAllMessage)); + this.reLayout(); }, (error) => { progressComplete(); errors.isPromiseCanceledError(error); @@ -646,7 +681,10 @@ export class SearchView extends ViewletPanel { identityProvider, accessibilityProvider: this.instantiationService.createInstance(SearchAccessibilityProvider, this.viewModel), dnd: this.instantiationService.createInstance(SearchDND), - multipleSelectionSupport: false + multipleSelectionSupport: false, + overrideStyles: { + listBackground: this.position === SearchViewPosition.SideBar ? SIDE_BAR_BACKGROUND : PANEL_BACKGROUND + } })); this._register(this.tree.onContextMenu(e => this.onContextMenu(e))); @@ -793,9 +831,9 @@ export class SearchView extends ViewletPanel { if (this.searchWidget.searchInput.getRegex()) { selectedText = strings.escapeRegExpCharacters(selectedText); } - - this.searchWidget.searchInput.setValue(selectedText); + this.searchWidget.setValue(selectedText, true); updatedText = true; + this.onQueryChanged(false); } } @@ -877,7 +915,7 @@ export class SearchView extends ViewletPanel { return; } - const actionsPosition = this.configurationService.getValue('search').actionsPosition; + const actionsPosition = this.searchConfig.actionsPosition; dom.toggleClass(this.getContainer(), SearchView.ACTIONS_RIGHT_CLASS_NAME, actionsPosition === 'right'); dom.toggleClass(this.getContainer(), SearchView.WIDE_CLASS_NAME, this.size.width >= SearchView.WIDE_VIEW_SIZE); @@ -921,9 +959,13 @@ export class SearchView extends ViewletPanel { return !this.viewModel.searchResult.isEmpty(); } + hasSearchPattern(): boolean { + return this.searchWidget && this.searchWidget.searchInput.getValue().length > 0; + } + clearSearchResults(): void { this.viewModel.searchResult.clear(); - this.showEmptyStage(); + this.showEmptyStage(true); if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) { this.showSearchWithoutFolderMessage(); } @@ -934,9 +976,9 @@ export class SearchView extends ViewletPanel { aria.status(nls.localize('ariaSearchResultsClearStatus', "The search results have been cleared")); } - cancelSearch(): boolean { + cancelSearch(focus: boolean = true): boolean { if (this.viewModel.cancelSearch()) { - this.searchWidget.focus(); + if (focus) { this.searchWidget.focus(); } return true; } return false; @@ -1146,7 +1188,7 @@ export class SearchView extends ViewletPanel { this.searchWidget.focus(false); } - onQueryChanged(preserveFocus?: boolean): void { + onQueryChanged(preserveFocus: boolean, triggeredOnType = false): void { if (!this.searchWidget.searchInput.inputBox.isInputValid()) { return; } @@ -1160,6 +1202,8 @@ export class SearchView extends ViewletPanel { const useExcludesAndIgnoreFiles = this.inputPatternExcludes.useExcludesAndIgnoreFiles(); if (contentPattern.length === 0) { + this.clearSearchResults(); + this.clearMessage(); return; } @@ -1176,8 +1220,7 @@ export class SearchView extends ViewletPanel { // Need the full match line to correctly calculate replace text, if this is a search/replace with regex group references ($1, $2, ...). // 10000 chars is enough to avoid sending huge amounts of text around, if you do a replace with a longer match, it may or may not resolve the group refs correctly. // https://github.com/Microsoft/vscode/issues/58374 - const charsPerLine = content.isRegExp ? 10000 : - 250; + const charsPerLine = content.isRegExp ? 10000 : 1000; const options: ITextQueryBuilderOptions = { _reason: 'searchView', @@ -1191,7 +1234,7 @@ export class SearchView extends ViewletPanel { matchLines: 1, charsPerLine }, - isSmartCase: this.configurationService.getValue().search.smartCase, + isSmartCase: this.searchConfig.smartCase, expandPatterns: true }; const folderResources = this.contextService.getWorkspace().folders; @@ -1210,7 +1253,7 @@ export class SearchView extends ViewletPanel { } this.validateQuery(query).then(() => { - this.onQueryTriggered(query, options, excludePatternText, includePatternText); + this.onQueryTriggered(query, options, excludePatternText, includePatternText, triggeredOnType); if (!preserveFocus) { this.searchWidget.focus(false); // focus back to input field @@ -1240,21 +1283,21 @@ export class SearchView extends ViewletPanel { }); } - private onQueryTriggered(query: ITextQuery, options: ITextQueryBuilderOptions, excludePatternText: string, includePatternText: string): void { - this.searchWidget.searchInput.onSearchSubmit(); + private onQueryTriggered(query: ITextQuery, options: ITextQueryBuilderOptions, excludePatternText: string, includePatternText: string, triggeredOnType: boolean): void { + this.addToSearchHistoryDelayer.trigger(() => this.searchWidget.searchInput.onSearchSubmit()); this.inputPatternExcludes.onSearchSubmit(); this.inputPatternIncludes.onSearchSubmit(); this.viewModel.cancelSearch(); this.currentSearchQ = this.currentSearchQ - .then(() => this.doSearch(query, options, excludePatternText, includePatternText)) + .then(() => this.doSearch(query, options, excludePatternText, includePatternText, triggeredOnType)) .then(() => undefined, () => undefined); } - private doSearch(query: ITextQuery, options: ITextQueryBuilderOptions, excludePatternText: string, includePatternText: string): Thenable { + private doSearch(query: ITextQuery, options: ITextQueryBuilderOptions, excludePatternText: string, includePatternText: string, triggeredOnType: boolean): Thenable { let progressComplete: () => void; - this.progressService.withProgress({ location: VIEWLET_ID }, _progress => { + this.progressService.withProgress({ location: VIEWLET_ID, delay: triggeredOnType ? 300 : 0 }, _progress => { return new Promise(resolve => progressComplete = resolve); }); @@ -1277,7 +1320,7 @@ export class SearchView extends ViewletPanel { // Do final render, then expand if just 1 file with less than 50 matches this.onSearchResultsChanged(); - const collapseResults = this.configurationService.getValue('search').collapseResults; + const collapseResults = this.searchConfig.collapseResults; if (collapseResults !== 'alwaysCollapse' && this.viewModel.searchResult.matches().length === 1) { const onlyMatch = this.viewModel.searchResult.matches()[0]; if (onlyMatch.count() < 50) { @@ -1303,7 +1346,7 @@ export class SearchView extends ViewletPanel { let message: string; if (!completed) { - message = nls.localize('searchCanceled', "Search was canceled before any results could be found - "); + message = SEARCH_CANCELLED_MESSAGE; } else if (hasIncludes && hasExcludes) { message = nls.localize('noResultsIncludesExcludes', "No results found in '{0}' excluding '{1}' - ", includePatternText, excludePatternText); } else if (hasIncludes) { @@ -1324,7 +1367,7 @@ export class SearchView extends ViewletPanel { const searchAgainLink = dom.append(p, $('a.pointer.prominent', undefined, nls.localize('rerunSearch.message', "Search again"))); this.messageDisposables.push(dom.addDisposableListener(searchAgainLink, dom.EventType.CLICK, (e: MouseEvent) => { dom.EventHelper.stop(e, false); - this.onQueryChanged(); + this.onQueryChanged(false); })); } else if (hasIncludes || hasExcludes) { const searchAgainLink = dom.append(p, $('a.pointer.prominent', { tabindex: 0 }, nls.localize('rerunSearchInAll.message', "Search again in all files"))); @@ -1334,7 +1377,7 @@ export class SearchView extends ViewletPanel { this.inputPatternExcludes.setValue(''); this.inputPatternIncludes.setValue(''); - this.onQueryChanged(); + this.onQueryChanged(false); })); } else { const openSettingsLink = dom.append(p, $('a.pointer.prominent', { tabindex: 0 }, nls.localize('openSettings.message', "Open Settings"))); @@ -1455,7 +1498,23 @@ export class SearchView extends ViewletPanel { resultMsg += nls.localize('useIgnoresAndExcludesDisabled', " - exclude settings and ignore files are disabled"); } - dom.append(messageEl, $('p', undefined, resultMsg)); + if (this.searchConfig.enableSearchEditorPreview) { + dom.append(messageEl, $('span', undefined, resultMsg + ' - ')); + const span = dom.append(messageEl, $('span', undefined)); + const openInEditorLink = dom.append(span, $('a.pointer.prominent', undefined, nls.localize('openInEditor.message', "Open in editor"))); + + openInEditorLink.title = appendKeyBindingLabel( + nls.localize('openInEditor.tooltip', "Copy current search results to an editor"), + this.keybindingService.lookupKeybinding(Constants.OpenInEditorCommandId), this.keybindingService); + + this.messageDisposables.push(dom.addDisposableListener(openInEditorLink, dom.EventType.CLICK, (e: MouseEvent) => { + dom.EventHelper.stop(e, false); + createEditorFromSearchResult(this.searchResult, this.searchIncludePattern.getValue(), this.searchExcludePattern.getValue(), this.labelService, this.editorService); + })); + + } else { + dom.append(messageEl, $('p', undefined, resultMsg)); + } this.reLayout(); } else if (!msgWasHidden) { dom.hide(this.messagesElement); @@ -1499,13 +1558,19 @@ export class SearchView extends ViewletPanel { })); } - private showEmptyStage(): void { + private showEmptyStage(forceHideMessages = false): void { // disable 'result'-actions this.updateActions(); + const showingCancelled = (this.messagesElement.firstChild?.textContent?.indexOf(SEARCH_CANCELLED_MESSAGE) ?? -1) > -1; + // clean up ui // this.replaceService.disposeAllReplacePreviews(); - dom.hide(this.messagesElement); + if (showingCancelled || forceHideMessages || !this.configurationService.getValue().search.searchOnType) { + // when in search to type, don't preemptively hide, as it causes flickering and shifting of the live results + dom.hide(this.messagesElement); + } + dom.show(this.resultsElement); this.currentSelectedFileMatch = undefined; } @@ -1540,6 +1605,38 @@ export class SearchView extends ViewletPanel { }, errors.onUnexpectedError); } + openEditorWithMultiCursor(element: FileMatchOrMatch): Promise { + const resource = element instanceof Match ? element.parent().resource : (element).resource; + return this.editorService.openEditor({ + resource: resource, + options: { + preserveFocus: false, + pinned: true, + revealIfVisible: true + } + }).then(editor => { + if (editor) { + let fileMatch = null; + if (element instanceof FileMatch) { + fileMatch = element; + } + else if (element instanceof Match) { + fileMatch = element.parent(); + } + + if (fileMatch) { + const selections = fileMatch.matches().map(m => new Selection(m.range().startLineNumber, m.range().startColumn, m.range().endLineNumber, m.range().endColumn)); + const codeEditor = getCodeEditor(editor.getControl()); + if (codeEditor) { + let multiCursorController = MultiCursorSelectionController.get(codeEditor); + multiCursorController.selectAllUsingSelections(selections); + } + } + } + this.viewModel.searchResult.rangeHighlightDecorations.removeHighlightRange(); + }, errors.onUnexpectedError); + } + private getSelectionFrom(element: FileMatchOrMatch): any { let match: Match | null = null; if (element instanceof Match) { @@ -1564,18 +1661,16 @@ export class SearchView extends ViewletPanel { return undefined; } - private onUntitledDidChangeDirty(resource: URI): void { + private onUntitledDidDispose(resource: URI): void { if (!this.viewModel) { return; } // remove search results from this resource as it got disposed - if (!this.untitledEditorService.isDirty(resource)) { - const matches = this.viewModel.searchResult.matches(); - for (let i = 0, len = matches.length; i < len; i++) { - if (resource.toString() === matches[i].resource.toString()) { - this.viewModel.searchResult.remove(matches[i]); - } + const matches = this.viewModel.searchResult.matches(); + for (let i = 0, len = matches.length; i < len; i++) { + if (resource.toString() === matches[i].resource.toString()) { + this.viewModel.searchResult.remove(matches[i]); } } } @@ -1600,6 +1695,10 @@ export class SearchView extends ViewletPanel { ]; } + private get searchConfig(): ISearchConfigurationProperties { + return this.configurationService.getValue('search'); + } + private clearHistory(): void { this.searchWidget.clearHistory(); this.inputPatternExcludes.clearHistory(); @@ -1698,4 +1797,10 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { if (outlineSelectionColor) { collector.addRule(`.monaco-workbench .search-view .monaco-list.element-focused .monaco-list-row.focused.selected:not(.highlighted) .action-label:focus { outline-color: ${outlineSelectionColor} }`); } + + const foregroundColor = theme.getColor(foreground); + if (foregroundColor) { + const fgWithOpacity = new Color(new RGBA(foregroundColor.rgba.r, foregroundColor.rgba.g, foregroundColor.rgba.b, 0.5)); + collector.addRule(`.vs-dark .search-view .message { color: ${fgWithOpacity}; }`); + } }); diff --git a/src/vs/workbench/contrib/search/browser/searchWidget.ts b/src/vs/workbench/contrib/search/browser/searchWidget.ts index 6b40a1b55f87..4860263ea8fa 100644 --- a/src/vs/workbench/contrib/search/browser/searchWidget.ts +++ b/src/vs/workbench/contrib/search/browser/searchWidget.ts @@ -15,7 +15,6 @@ import { Action } from 'vs/base/common/actions'; import { Delayer } from 'vs/base/common/async'; import { Emitter, Event } from 'vs/base/common/event'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import * as strings from 'vs/base/common/strings'; import { CONTEXT_FIND_WIDGET_NOT_VISIBLE } from 'vs/editor/contrib/find/findModel'; import * as nls from 'vs/nls'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; @@ -118,15 +117,15 @@ export class SearchWidget extends Widget { private replaceActive: IContextKey; private replaceActionBar!: ActionBar; private _replaceHistoryDelayer: Delayer; - + private _searchDelayer: Delayer; private ignoreGlobalFindBufferOnNextFocus = false; private previousGlobalFindBufferValue: string | null = null; - private _onSearchSubmit = this._register(new Emitter()); - readonly onSearchSubmit: Event = this._onSearchSubmit.event; + private _onSearchSubmit = this._register(new Emitter()); + readonly onSearchSubmit: Event = this._onSearchSubmit.event; - private _onSearchCancel = this._register(new Emitter()); - readonly onSearchCancel: Event = this._onSearchCancel.event; + private _onSearchCancel = this._register(new Emitter<{ focus: boolean }>()); + readonly onSearchCancel: Event<{ focus: boolean }> = this._onSearchCancel.event; private _onReplaceToggled = this._register(new Emitter()); readonly onReplaceToggled: Event = this._onReplaceToggled.event; @@ -149,6 +148,8 @@ export class SearchWidget extends Widget { private _onDidHeightChange = this._register(new Emitter()); readonly onDidHeightChange: Event = this._onDidHeightChange.event; + private temporarilySkipSearchOnChange = false; + constructor( container: HTMLElement, options: ISearchWidgetOptions, @@ -165,6 +166,7 @@ export class SearchWidget extends Widget { this.searchInputBoxFocused = Constants.SearchInputBoxFocusedKey.bindTo(this.contextKeyService); this.replaceInputBoxFocused = Constants.ReplaceInputBoxFocusedKey.bindTo(this.contextKeyService); this._replaceHistoryDelayer = new Delayer(500); + this._searchDelayer = this._register(new Delayer(this.searchConfiguration.searchOnTypeDebouncePeriod)); this.render(container, options); this.configurationService.onDidChangeConfiguration(e => { @@ -323,9 +325,6 @@ export class SearchWidget extends Widget { this.searchInput.setRegex(!!options.isRegex); this.searchInput.setCaseSensitive(!!options.isCaseSensitive); this.searchInput.setWholeWords(!!options.isWholeWords); - this._register(this.onSearchSubmit(() => { - this.searchInput.inputBox.addToHistory(); - })); this._register(this.searchInput.onCaseSensitiveKeyDown((keyboardEvent: IKeyboardEvent) => this.onCaseSensitiveKeyDown(keyboardEvent))); this._register(this.searchInput.onRegexKeyDown((keyboardEvent: IKeyboardEvent) => this.onRegexKeyDown(keyboardEvent))); this._register(this.searchInput.inputBox.onDidChange(() => this.onSearchInputChanged())); @@ -406,6 +405,11 @@ export class SearchWidget extends Widget { this._onReplaceToggled.fire(); } + setValue(value: string, skipSearchOnChange: boolean) { + this.searchInput.setValue(value); + this.temporarilySkipSearchOnChange = skipSearchOnChange || this.temporarilySkipSearchOnChange; + } + setReplaceAllActionState(enabled: boolean): void { if (this.replaceAllAction.enabled !== enabled) { this.replaceAllAction.enabled = enabled; @@ -438,18 +442,21 @@ export class SearchWidget extends Widget { return { content: e.message }; } - if (strings.regExpContainsBackreference(value)) { - if (!this.searchConfiguration.usePCRE2) { - return { content: nls.localize('regexp.backreferenceValidationFailure', "Backreferences are not supported") }; - } - } - return null; } private onSearchInputChanged(): void { this.searchInput.clearMessage(); this.setReplaceAllActionState(false); + + if (this.searchConfiguration.searchOnType) { + if (this.temporarilySkipSearchOnChange) { + this.temporarilySkipSearchOnChange = false; + } else { + this._onSearchCancel.fire({ focus: false }); + this._searchDelayer.trigger((() => this.submitSearch(true)), this.searchConfiguration.searchOnTypeDebouncePeriod); + } + } } private onSearchInputKeyDown(keyboardEvent: IKeyboardEvent) { @@ -464,7 +471,7 @@ export class SearchWidget extends Widget { } else if (keyboardEvent.equals(KeyCode.Escape)) { - this._onSearchCancel.fire(); + this._onSearchCancel.fire({ focus: true }); keyboardEvent.preventDefault(); } @@ -556,7 +563,7 @@ export class SearchWidget extends Widget { } } - private submitSearch(): void { + private submitSearch(triggeredOnType = false): void { this.searchInput.validate(); if (!this.searchInput.inputBox.isInputValid()) { return; @@ -564,13 +571,10 @@ export class SearchWidget extends Widget { const value = this.searchInput.getValue(); const useGlobalFindBuffer = this.searchConfiguration.globalFindClipboard; - if (value) { - if (useGlobalFindBuffer) { - this.clipboardServce.writeFindText(value); - } - - this._onSearchSubmit.fire(); + if (value && useGlobalFindBuffer) { + this.clipboardServce.writeFindText(value); } + this._onSearchSubmit.fire(triggeredOnType); } dispose(): void { diff --git a/src/vs/workbench/contrib/search/common/constants.ts b/src/vs/workbench/contrib/search/common/constants.ts index cde331349ca0..9fa0b9d991b6 100644 --- a/src/vs/workbench/contrib/search/common/constants.ts +++ b/src/vs/workbench/contrib/search/common/constants.ts @@ -15,6 +15,8 @@ export const RemoveActionId = 'search.action.remove'; export const CopyPathCommandId = 'search.action.copyPath'; export const CopyMatchCommandId = 'search.action.copyMatch'; export const CopyAllCommandId = 'search.action.copyAll'; +export const OpenInEditorCommandId = 'search.action.openInEditor'; +export const RerunEditorSearchCommandId = 'search.action.rerunEditorSearch'; export const ClearSearchHistoryCommandId = 'search.action.clearHistory'; export const FocusSearchListCommandID = 'search.action.focusSearchList'; export const ReplaceActionId = 'search.action.replace'; @@ -24,6 +26,7 @@ export const CloseReplaceWidgetActionId = 'closeReplaceInFilesWidget'; export const ToggleCaseSensitiveCommandId = 'toggleSearchCaseSensitive'; export const ToggleWholeWordCommandId = 'toggleSearchWholeWord'; export const ToggleRegexCommandId = 'toggleSearchRegex'; +export const AddCursorsAtSearchResults = 'addCursorsAtSearchResults'; export const RevealInSideBarForSearchResults = 'search.action.revealInSideBar'; export const ToggleSearchViewPositionCommandId = 'search.action.toggleSearchViewPosition'; @@ -37,6 +40,7 @@ export const PatternIncludesFocusedKey = new RawContextKey('patternIncl export const PatternExcludesFocusedKey = new RawContextKey('patternExcludesInputBoxFocus', false); export const ReplaceActiveKey = new RawContextKey('replaceActive', false); export const HasSearchResults = new RawContextKey('hasSearchResult', false); +export const EnableSearchEditorPreview = new RawContextKey('previewSearchEditor', false); export const FirstMatchFocusKey = new RawContextKey('firstMatchFocus', false); export const FileMatchOrMatchFocusKey = new RawContextKey('fileMatchOrMatchFocus', false); // This is actually, Match or File or Folder diff --git a/src/vs/workbench/contrib/search/common/searchModel.ts b/src/vs/workbench/contrib/search/common/searchModel.ts index 45a121cabaf4..8e2507e4618e 100644 --- a/src/vs/workbench/contrib/search/common/searchModel.ts +++ b/src/vs/workbench/contrib/search/common/searchModel.ts @@ -20,7 +20,7 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IProgress, IProgressStep } from 'vs/platform/progress/common/progress'; import { ReplacePattern } from 'vs/workbench/services/search/common/replace'; -import { IFileMatch, IPatternInfo, ISearchComplete, ISearchProgressItem, ISearchService, ITextQuery, ITextSearchPreviewOptions, ITextSearchMatch, ITextSearchStats, resultIsMatch, ISearchRange, OneLineRange } from 'vs/workbench/services/search/common/search'; +import { IFileMatch, IPatternInfo, ISearchComplete, ISearchProgressItem, ISearchConfigurationProperties, ISearchService, ITextQuery, ITextSearchPreviewOptions, ITextSearchMatch, ITextSearchStats, resultIsMatch, ISearchRange, OneLineRange } from 'vs/workbench/services/search/common/search'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { overviewRulerFindMatchForeground, minimapFindMatch } from 'vs/platform/theme/common/colorRegistry'; import { themeColorFromId } from 'vs/platform/theme/common/themeService'; @@ -28,6 +28,7 @@ import { IReplaceService } from 'vs/workbench/contrib/search/common/replace'; import { editorMatchesToTextSearchResults } from 'vs/workbench/services/search/common/searchHelpers'; import { withNullAsUndefined } from 'vs/base/common/types'; import { memoize } from 'vs/base/common/decorators'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; export class Match { @@ -638,6 +639,7 @@ export class SearchResult extends Disposable { private _query: ITextQuery | null = null; private _rangeHighlightDecorations: RangeHighlightDecorations; + private disposePastResults: () => void = () => { }; constructor( private _searchModel: SearchModel, @@ -657,8 +659,15 @@ export class SearchResult extends Disposable { } set query(query: ITextQuery | null) { - // When updating the query we could change the roots, so ensure we clean up the old roots first. - this.clear(); + // When updating the query we could change the roots, so keep a reference to them to clean up when we trigger `disposePastResults` + const oldFolderMatches = this.folderMatches(); + new Promise(resolve => this.disposePastResults = resolve) + .then(() => oldFolderMatches.forEach(match => match.clear())) + .then(() => oldFolderMatches.forEach(match => match.dispose())); + + this._rangeHighlightDecorations.removeHighlightRange(); + this._folderMatchesMap = TernarySearchTree.forPaths(); + if (!query) { return; } @@ -714,7 +723,8 @@ export class SearchResult extends Disposable { } }); - this._otherFilesMatch!.add(other, silent); + this._otherFilesMatch?.add(other, silent); + this.disposePastResults(); } clear(): void { @@ -883,6 +893,7 @@ export class SearchResult extends Disposable { } dispose(): void { + this.disposePastResults(); this.disposeMatches(); this._rangeHighlightDecorations.dispose(); super.dispose(); @@ -897,6 +908,8 @@ export class SearchModel extends Disposable { private _replaceString: string | null = null; private _replacePattern: ReplacePattern | null = null; private _preserveCase: boolean = false; + private _startStreamDelay: Promise = Promise.resolve(); + private _resultQueue: IFileMatch[] = []; private readonly _onReplaceTermChanged: Emitter = this._register(new Emitter()); readonly onReplaceTermChanged: Event = this._onReplaceTermChanged.event; @@ -906,6 +919,7 @@ export class SearchModel extends Disposable { constructor( @ISearchService private readonly searchService: ISearchService, @ITelemetryService private readonly telemetryService: ITelemetryService, + @IConfigurationService private readonly configurationService: IConfigurationService, @IInstantiationService private readonly instantiationService: IInstantiationService ) { super(); @@ -951,13 +965,25 @@ export class SearchModel extends Disposable { search(query: ITextQuery, onProgress?: (result: ISearchProgressItem) => void): Promise { this.cancelSearch(); + // Exclude Search Editor results unless explicity included + const searchEditorFilenameGlob = `**/*.code-search`; + if (!query.includePattern || !query.includePattern[searchEditorFilenameGlob]) { + query.excludePattern = { ...(query.excludePattern ?? {}), [searchEditorFilenameGlob]: true }; + } + this._searchQuery = query; - this.searchResult.clear(); + if (!this.searchConfig.searchOnType) { + this.searchResult.clear(); + } + this._searchResult.query = this._searchQuery; const progressEmitter = new Emitter(); this._replacePattern = new ReplacePattern(this.replaceString, this._searchQuery.contentPattern); + // In search on type case, delay the streaming of results just a bit, so that we don't flash the only "local results" fast path + this._startStreamDelay = new Promise(resolve => setTimeout(resolve, this.searchConfig.searchOnType ? 100 : 0)); + const tokenSource = this.currentCancelTokenSource = new CancellationTokenSource(); const currentRequest = this.searchService.textSearch(this._searchQuery, this.currentCancelTokenSource.token, p => { progressEmitter.fire(); @@ -1001,6 +1027,9 @@ export class SearchModel extends Disposable { throw new Error('onSearchCompleted must be called after a search is started'); } + this._searchResult.add(this._resultQueue); + this._resultQueue = []; + const options: IPatternInfo = objects.assign({}, this._searchQuery.contentPattern); delete options.pattern; @@ -1019,7 +1048,8 @@ export class SearchModel extends Disposable { "options": { "${inline}": [ "${IPatternInfo}" ] }, "duration": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, "type" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, - "scheme" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" } + "scheme" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "searchOnTypeEnabled" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } } */ this.telemetryService.publicLog('searchResultsShown', { @@ -1028,7 +1058,8 @@ export class SearchModel extends Disposable { options, duration, type: stats && stats.type, - scheme + scheme, + searchOnTypeEnabled: this.searchConfig.searchOnType }); return completed; } @@ -1039,12 +1070,21 @@ export class SearchModel extends Disposable { } } - private onSearchProgress(p: ISearchProgressItem): void { + private async onSearchProgress(p: ISearchProgressItem) { if ((p).resource) { - this._searchResult.add([p], true); + this._resultQueue.push(p); + await this._startStreamDelay; + if (this._resultQueue.length) { + this._searchResult.add(this._resultQueue, true); + this._resultQueue = []; + } } } + private get searchConfig() { + return this.configurationService.getValue('search'); + } + cancelSearch(): boolean { if (this.currentCancelTokenSource) { this.currentCancelTokenSource.cancel(); diff --git a/src/vs/workbench/contrib/search/test/common/searchModel.test.ts b/src/vs/workbench/contrib/search/test/common/searchModel.test.ts index 0950afa2d780..ca8c505a7aa4 100644 --- a/src/vs/workbench/contrib/search/test/common/searchModel.test.ts +++ b/src/vs/workbench/contrib/search/test/common/searchModel.test.ts @@ -70,6 +70,10 @@ suite('SearchModel', () => { instantiationService.stub(IModelService, stubModelService(instantiationService)); instantiationService.stub(ISearchService, {}); instantiationService.stub(ISearchService, 'textSearch', Promise.resolve({ results: [] })); + + const config = new TestConfigurationService(); + config.setUserConfiguration('search', { searchOnType: true }); + instantiationService.stub(IConfigurationService, config); }); teardown(() => { diff --git a/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts b/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts index 285072f305ed..6115924776a3 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { MarkdownString } from 'vs/base/common/htmlContent'; -import { compare } from 'vs/base/common/strings'; +import { compare, startsWith } from 'vs/base/common/strings'; import { Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; import { ITextModel } from 'vs/editor/common/model'; @@ -22,14 +22,14 @@ export class SnippetCompletion implements CompletionItem { detail: string; insertText: string; documentation?: MarkdownString; - range: IRange; + range: IRange | { insert: IRange, replace: IRange }; sortText: string; kind: CompletionItemKind; insertTextRules: CompletionItemInsertTextRule; constructor( readonly snippet: Snippet, - range: IRange + range: IRange | { insert: IRange, replace: IRange } ) { this.label = snippet.prefix; this.detail = localize('detail.snippet', "{0} ({1})", snippet.description || snippet.name, snippet.source); @@ -80,7 +80,8 @@ export class SnippetCompletionProvider implements CompletionItemProvider { let suggestions: SnippetCompletion[]; let pos = { lineNumber: position.lineNumber, column: 1 }; let lineOffsets: number[] = []; - let linePrefixLow = model.getLineContent(position.lineNumber).substr(0, position.column - 1).toLowerCase(); + const lineContent = model.getLineContent(position.lineNumber); + const linePrefixLow = lineContent.substr(0, position.column - 1).toLowerCase(); let endsInWhitespace = linePrefixLow.match(/\s$/); while (pos.column < position.column) { @@ -104,13 +105,19 @@ export class SnippetCompletionProvider implements CompletionItemProvider { } } + const lineSuffixLow = lineContent.substr(position.column - 1).toLowerCase(); let availableSnippets = new Set(); snippets.forEach(availableSnippets.add, availableSnippets); suggestions = []; for (let start of lineOffsets) { availableSnippets.forEach(snippet => { if (isPatternInWord(linePrefixLow, start, linePrefixLow.length, snippet.prefixLow, 0, snippet.prefixLow.length)) { - suggestions.push(new SnippetCompletion(snippet, Range.fromPositions(position.delta(0, -(linePrefixLow.length - start)), position))); + const snippetPrefixSubstr = snippet.prefixLow.substr(linePrefixLow.length - start); + const endColumn = startsWith(lineSuffixLow, snippetPrefixSubstr) ? position.column + snippetPrefixSubstr.length : position.column; + const replace = Range.fromPositions(position.delta(0, -(linePrefixLow.length - start)), { lineNumber: position.lineNumber, column: endColumn }); + const insert = replace.setEndPosition(position.lineNumber, position.column); + + suggestions.push(new SnippetCompletion(snippet, { replace, insert })); availableSnippets.delete(snippet); } }); @@ -119,7 +126,8 @@ export class SnippetCompletionProvider implements CompletionItemProvider { // add remaing snippets when the current prefix ends in whitespace or when no // interesting positions have been found availableSnippets.forEach(snippet => { - suggestions.push(new SnippetCompletion(snippet, Range.fromPositions(position))); + const range = Range.fromPositions(position); + suggestions.push(new SnippetCompletion(snippet, { replace: range, insert: range })); }); } diff --git a/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts b/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts index 3eff3b3a0f65..098bb6cc1300 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { parse as jsonParse } from 'vs/base/common/json'; +import { parse as jsonParse, getNodeType } from 'vs/base/common/json'; import { forEach } from 'vs/base/common/collections'; import { localize } from 'vs/nls'; import { extname, basename } from 'vs/base/common/path'; @@ -14,6 +14,7 @@ import { URI } from 'vs/base/common/uri'; import { IFileService } from 'vs/platform/files/common/files'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { IdleValue } from 'vs/base/common/async'; +import { IExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader'; class SnippetBodyInsights { @@ -153,7 +154,8 @@ export class SnippetFile { readonly location: URI, public defaultScopes: string[] | undefined, private readonly _extension: IExtensionDescription | undefined, - private readonly _fileService: IFileService + private readonly _fileService: IFileService, + private readonly _extensionResourceLoaderService: IExtensionResourceLoaderService ) { this.isGlobalSnippets = extname(location.path) === '.code-snippets'; this.isUserSnippets = !this._extension; @@ -199,11 +201,20 @@ export class SnippetFile { } } + private async _load(): Promise { + if (this._extension) { + return this._extensionResourceLoaderService.readExtensionResource(this.location); + } else { + const content = await this._fileService.readFile(this.location); + return content.value.toString(); + } + } + load(): Promise { if (!this._loadPromise) { - this._loadPromise = Promise.resolve(this._fileService.readFile(this.location)).then(content => { - const data = jsonParse(content.value.toString()); - if (typeof data === 'object') { + this._loadPromise = Promise.resolve(this._load()).then(content => { + const data = jsonParse(content); + if (getNodeType(data) === 'object') { forEach(data, entry => { const { key: name, value: scopeOrTemplate } = entry; if (isJsonSerializedSnippet(scopeOrTemplate)) { diff --git a/src/vs/workbench/contrib/snippets/browser/snippetsService.ts b/src/vs/workbench/contrib/snippets/browser/snippetsService.ts index 316efe6b2bd6..768602e84465 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippetsService.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippetsService.ts @@ -25,6 +25,7 @@ import { Snippet, SnippetFile, SnippetSource } from 'vs/workbench/contrib/snippe import { ExtensionsRegistry, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { languagesExtPoint } from 'vs/workbench/services/mode/common/workbenchModeService'; import { SnippetCompletionProvider } from './snippetCompletionProvider'; +import { IExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader'; namespace snippetExt { @@ -139,6 +140,7 @@ class SnippetsService implements ISnippetsService { @IModeService private readonly _modeService: IModeService, @ILogService private readonly _logService: ILogService, @IFileService private readonly _fileService: IFileService, + @IExtensionResourceLoaderService private readonly _extensionResourceLoaderService: IExtensionResourceLoaderService, @ILifecycleService lifecycleService: ILifecycleService, ) { this._pendingWork.push(Promise.resolve(lifecycleService.when(LifecyclePhase.Restored).then(() => { @@ -225,7 +227,7 @@ class SnippetsService implements ISnippetsService { file.defaultScopes = []; } } else { - const file = new SnippetFile(SnippetSource.Extension, validContribution.location, validContribution.language ? [validContribution.language] : undefined, extension.description, this._fileService); + const file = new SnippetFile(SnippetSource.Extension, validContribution.location, validContribution.language ? [validContribution.language] : undefined, extension.description, this._fileService, this._extensionResourceLoaderService); this._files.set(file.location.toString(), file); if (this._environmentService.isExtensionDevelopment) { @@ -318,9 +320,9 @@ class SnippetsService implements ISnippetsService { const key = uri.toString(); if (source === SnippetSource.User && ext === '.json') { const langName = resources.basename(uri).replace(/\.json/, ''); - this._files.set(key, new SnippetFile(source, uri, [langName], undefined, this._fileService)); + this._files.set(key, new SnippetFile(source, uri, [langName], undefined, this._fileService, this._extensionResourceLoaderService)); } else if (ext === '.code-snippets') { - this._files.set(key, new SnippetFile(source, uri, undefined, undefined, this._fileService)); + this._files.set(key, new SnippetFile(source, uri, undefined, undefined, this._fileService, this._extensionResourceLoaderService)); } return { dispose: () => this._files.delete(key) diff --git a/src/vs/workbench/contrib/snippets/test/browser/snippetFile.test.ts b/src/vs/workbench/contrib/snippets/test/browser/snippetFile.test.ts index 3a00334a03d7..5085c9264bc9 100644 --- a/src/vs/workbench/contrib/snippets/test/browser/snippetFile.test.ts +++ b/src/vs/workbench/contrib/snippets/test/browser/snippetFile.test.ts @@ -11,7 +11,7 @@ suite('Snippets', function () { class TestSnippetFile extends SnippetFile { constructor(filepath: URI, snippets: Snippet[]) { - super(SnippetSource.Extension, filepath, undefined, undefined, undefined!); + super(SnippetSource.Extension, filepath, undefined, undefined, undefined!, undefined!); this.data.push(...snippets); } } diff --git a/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts b/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts index 96f577666818..970561955f6c 100644 --- a/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts +++ b/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts @@ -84,7 +84,7 @@ suite('SnippetsService', function () { assert.equal(result.incomplete, undefined); assert.equal(result.suggestions.length, 1); assert.equal(result.suggestions[0].label, 'bar'); - assert.equal(result.suggestions[0].range.startColumn, 1); + assert.equal((result.suggestions[0].range as any).insert.startColumn, 1); assert.equal(result.suggestions[0].insertText, 'barCodeSnippet'); }); }); @@ -117,10 +117,10 @@ suite('SnippetsService', function () { assert.equal(result.suggestions.length, 2); assert.equal(result.suggestions[0].label, 'bar'); assert.equal(result.suggestions[0].insertText, 's1'); - assert.equal(result.suggestions[0].range.startColumn, 1); + assert.equal((result.suggestions[0].range as any).insert.startColumn, 1); assert.equal(result.suggestions[1].label, 'bar-bar'); assert.equal(result.suggestions[1].insertText, 's2'); - assert.equal(result.suggestions[1].range.startColumn, 1); + assert.equal((result.suggestions[1].range as any).insert.startColumn, 1); }); await provider.provideCompletionItems(model, new Position(1, 5), context)!.then(result => { @@ -128,7 +128,7 @@ suite('SnippetsService', function () { assert.equal(result.suggestions.length, 1); assert.equal(result.suggestions[0].label, 'bar-bar'); assert.equal(result.suggestions[0].insertText, 's2'); - assert.equal(result.suggestions[0].range.startColumn, 1); + assert.equal((result.suggestions[0].range as any).insert.startColumn, 1); }); await provider.provideCompletionItems(model, new Position(1, 6), context)!.then(result => { @@ -136,10 +136,10 @@ suite('SnippetsService', function () { assert.equal(result.suggestions.length, 2); assert.equal(result.suggestions[0].label, 'bar'); assert.equal(result.suggestions[0].insertText, 's1'); - assert.equal(result.suggestions[0].range.startColumn, 5); + assert.equal((result.suggestions[0].range as any).insert.startColumn, 5); assert.equal(result.suggestions[1].label, 'bar-bar'); assert.equal(result.suggestions[1].insertText, 's2'); - assert.equal(result.suggestions[1].range.startColumn, 1); + assert.equal((result.suggestions[1].range as any).insert.startColumn, 1); }); }); @@ -165,14 +165,14 @@ suite('SnippetsService', function () { return provider.provideCompletionItems(model, new Position(1, 4), context)!; }).then(result => { assert.equal(result.suggestions.length, 1); - assert.equal(result.suggestions[0].range.startColumn, 2); + assert.equal((result.suggestions[0].range as any).insert.startColumn, 2); model.dispose(); model = TextModel.createFromString('a { assert.equal(result.suggestions.length, 1); - assert.equal(result.suggestions[0].range.startColumn, 2); + assert.equal((result.suggestions[0].range as any).insert.startColumn, 2); model.dispose(); }); }); @@ -400,13 +400,43 @@ suite('SnippetsService', function () { assert.equal(result.suggestions.length, 1); let [first] = result.suggestions; - assert.equal(first.range.startColumn, 2); + assert.equal((first.range as any).insert.startColumn, 2); model = TextModel.createFromString('1', undefined, modeService.getLanguageIdentifier('fooLang')); result = await provider.provideCompletionItems(model, new Position(1, 2), context)!; assert.equal(result.suggestions.length, 1); [first] = result.suggestions; - assert.equal(first.range.startColumn, 1); + assert.equal((first.range as any).insert.startColumn, 1); + }); + + test('Snippet replace range', async function () { + snippetService = new SimpleSnippetService([new Snippet( + ['fooLang'], + 'notWordTest', + 'not word', + '', + 'not word snippet', + '', + SnippetSource.User + )]); + + const provider = new SnippetCompletionProvider(modeService, snippetService); + + let model = TextModel.createFromString('not wordFoo bar', undefined, modeService.getLanguageIdentifier('fooLang')); + let result = await provider.provideCompletionItems(model, new Position(1, 3), context)!; + + assert.equal(result.suggestions.length, 1); + let [first] = result.suggestions; + assert.equal((first.range as any).insert.endColumn, 3); + assert.equal((first.range as any).replace.endColumn, 9); + + model = TextModel.createFromString('not woFoo bar', undefined, modeService.getLanguageIdentifier('fooLang')); + result = await provider.provideCompletionItems(model, new Position(1, 3), context)!; + + assert.equal(result.suggestions.length, 1); + [first] = result.suggestions; + assert.equal((first.range as any).insert.endColumn, 3); + assert.equal((first.range as any).replace.endColumn, 3); }); }); diff --git a/src/vs/workbench/contrib/splash/electron-browser/partsSplash.contribution.ts b/src/vs/workbench/contrib/splash/electron-browser/partsSplash.contribution.ts index 4c72713307ca..d7f076129853 100644 --- a/src/vs/workbench/contrib/splash/electron-browser/partsSplash.contribution.ts +++ b/src/vs/workbench/contrib/splash/electron-browser/partsSplash.contribution.ts @@ -60,10 +60,12 @@ class PartsSplash { if (e.affectsConfiguration('window.titleBarStyle')) { this._didChangeTitleBarStyle = true; this._savePartsSplash(); - } else if (e.affectsConfiguration('workbench.colorTheme') || e.affectsConfiguration('workbench.colorCustomizations')) { - this._savePartsSplash(); } }, this, this._disposables); + + _themeService.onThemeChange(_ => { + this._savePartsSplash(); + }, this, this._disposables); } dispose(): void { @@ -80,6 +82,7 @@ class PartsSplash { sideBarBackground: this._getThemeColor(themes.SIDE_BAR_BACKGROUND), statusBarBackground: this._getThemeColor(themes.STATUS_BAR_BACKGROUND), statusBarNoFolderBackground: this._getThemeColor(themes.STATUS_BAR_NO_FOLDER_BACKGROUND), + windowBorder: this._getThemeColor(themes.WINDOW_ACTIVE_BORDER) ?? this._getThemeColor(themes.WINDOW_INACTIVE_BORDER) }; const layoutInfo = !this._shouldSaveLayoutInfo() ? undefined : { sideBarSide: this._layoutService.getSideBarPosition() === Position.RIGHT ? 'right' : 'left', @@ -88,6 +91,8 @@ class PartsSplash { activityBarWidth: this._layoutService.isVisible(Parts.ACTIVITYBAR_PART) ? getTotalWidth(assertIsDefined(this._layoutService.getContainer(Parts.ACTIVITYBAR_PART))) : 0, sideBarWidth: this._layoutService.isVisible(Parts.SIDEBAR_PART) ? getTotalWidth(assertIsDefined(this._layoutService.getContainer(Parts.SIDEBAR_PART))) : 0, statusBarHeight: this._layoutService.isVisible(Parts.STATUSBAR_PART) ? getTotalHeight(assertIsDefined(this._layoutService.getContainer(Parts.STATUSBAR_PART))) : 0, + windowBorder: this._layoutService.hasWindowBorder(), + windowBorderRadius: this._layoutService.getWindowBorderRadius() }; this._textFileService.write( URI.file(join(this._envService.userDataPath, 'rapid_render.json')), diff --git a/src/vs/workbench/contrib/stats/browser/workspaceStatsService.ts b/src/vs/workbench/contrib/tags/browser/workspaceTagsService.ts similarity index 78% rename from src/vs/workbench/contrib/stats/browser/workspaceStatsService.ts rename to src/vs/workbench/contrib/tags/browser/workspaceTagsService.ts index c9683a14c038..3080b549e9d0 100644 --- a/src/vs/workbench/contrib/stats/browser/workspaceStatsService.ts +++ b/src/vs/workbench/contrib/tags/browser/workspaceTagsService.ts @@ -6,9 +6,9 @@ import { WorkbenchState, IWorkspace } from 'vs/platform/workspace/common/workspace'; import { URI } from 'vs/base/common/uri'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IWorkspaceStatsService, Tags } from 'vs/workbench/contrib/stats/common/workspaceStats'; +import { IWorkspaceTagsService, Tags } from 'vs/workbench/contrib/tags/common/workspaceTags'; -export class NoOpWorkspaceStatsService implements IWorkspaceStatsService { +export class NoOpWorkspaceTagsService implements IWorkspaceTagsService { _serviceBrand: undefined; @@ -25,4 +25,4 @@ export class NoOpWorkspaceStatsService implements IWorkspaceStatsService { } } -registerSingleton(IWorkspaceStatsService, NoOpWorkspaceStatsService, true); +registerSingleton(IWorkspaceTagsService, NoOpWorkspaceTagsService, true); diff --git a/src/vs/workbench/contrib/stats/common/workspaceStats.ts b/src/vs/workbench/contrib/tags/common/workspaceTags.ts similarity index 88% rename from src/vs/workbench/contrib/stats/common/workspaceStats.ts rename to src/vs/workbench/contrib/tags/common/workspaceTags.ts index d03a5895b2df..74749bdd0509 100644 --- a/src/vs/workbench/contrib/stats/common/workspaceStats.ts +++ b/src/vs/workbench/contrib/tags/common/workspaceTags.ts @@ -9,9 +9,9 @@ import { URI } from 'vs/base/common/uri'; export type Tags = { [index: string]: boolean | number | string | undefined }; -export const IWorkspaceStatsService = createDecorator('workspaceStatsService'); +export const IWorkspaceTagsService = createDecorator('workspaceTagsService'); -export interface IWorkspaceStatsService { +export interface IWorkspaceTagsService { _serviceBrand: undefined; getTags(): Promise; diff --git a/src/vs/workbench/contrib/stats/electron-browser/stats.contribution.ts b/src/vs/workbench/contrib/tags/electron-browser/tags.contribution.ts similarity index 75% rename from src/vs/workbench/contrib/stats/electron-browser/stats.contribution.ts rename to src/vs/workbench/contrib/tags/electron-browser/tags.contribution.ts index 94a27a0a335d..3da7192483b6 100644 --- a/src/vs/workbench/contrib/stats/electron-browser/stats.contribution.ts +++ b/src/vs/workbench/contrib/tags/electron-browser/tags.contribution.ts @@ -5,8 +5,8 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; -import { WorkspaceStats } from 'vs/workbench/contrib/stats/electron-browser/workspaceStats'; +import { WorkspaceTags } from 'vs/workbench/contrib/tags/electron-browser/workspaceTags'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; -// Register Workspace Stats Contribution -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(WorkspaceStats, LifecyclePhase.Eventually); \ No newline at end of file +// Register Workspace Tags Contribution +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(WorkspaceTags, LifecyclePhase.Eventually); diff --git a/src/vs/workbench/contrib/stats/electron-browser/workspaceStats.ts b/src/vs/workbench/contrib/tags/electron-browser/workspaceTags.ts similarity index 95% rename from src/vs/workbench/contrib/stats/electron-browser/workspaceStats.ts rename to src/vs/workbench/contrib/tags/electron-browser/workspaceTags.ts index f37960a8c8ea..999761f2c8ee 100644 --- a/src/vs/workbench/contrib/stats/electron-browser/workspaceStats.ts +++ b/src/vs/workbench/contrib/tags/electron-browser/workspaceTags.ts @@ -13,7 +13,7 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { endsWith } from 'vs/base/common/strings'; import { ITextFileService, } from 'vs/workbench/services/textfile/common/textfiles'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; -import { IWorkspaceStatsService, Tags } from 'vs/workbench/contrib/stats/common/workspaceStats'; +import { IWorkspaceTagsService, Tags } from 'vs/workbench/contrib/tags/common/workspaceTags'; import { IWorkspaceInformation } from 'vs/platform/diagnostics/common/diagnostics'; import { IRequestService } from 'vs/platform/request/common/request'; import { isWindows } from 'vs/base/common/platform'; @@ -137,7 +137,7 @@ export function getHashedRemotesFromConfig(text: string, stripEndingDotGit: bool }); } -export class WorkspaceStats implements IWorkbenchContribution { +export class WorkspaceTags implements IWorkbenchContribution { constructor( @IFileService private readonly fileService: IFileService, @@ -146,7 +146,7 @@ export class WorkspaceStats implements IWorkbenchContribution { @IRequestService private readonly requestService: IRequestService, @ITextFileService private readonly textFileService: ITextFileService, @ISharedProcessService private readonly sharedProcessService: ISharedProcessService, - @IWorkspaceStatsService private readonly workspaceStatsService: IWorkspaceStatsService + @IWorkspaceTagsService private readonly workspaceTagsService: IWorkspaceTagsService ) { if (this.telemetryService.isOptedIn) { this.report(); @@ -157,8 +157,8 @@ export class WorkspaceStats implements IWorkbenchContribution { // Windows-only Edition Event this.reportWindowsEdition(); - // Workspace Stats - this.workspaceStatsService.getTags() + // Workspace Tags + this.workspaceTagsService.getTags() .then(tags => this.reportWorkspaceTags(tags), error => onUnexpectedError(error)); // Cloud Stats @@ -192,7 +192,7 @@ export class WorkspaceStats implements IWorkbenchContribution { private async getWorkspaceInformation(): Promise { const workspace = this.contextService.getWorkspace(); const state = this.contextService.getWorkbenchState(); - const telemetryId = this.workspaceStatsService.getTelemetryWorkspaceId(workspace, state); + const telemetryId = this.workspaceTagsService.getTelemetryWorkspaceId(workspace, state); return this.telemetryService.getTelemetryInfo().then(info => { return { id: workspace.id, @@ -243,7 +243,7 @@ export class WorkspaceStats implements IWorkbenchContribution { private reportRemotes(workspaceUris: URI[]): void { Promise.all(workspaceUris.map(workspaceUri => { - return this.workspaceStatsService.getHashedRemotesFromUri(workspaceUri, true); + return this.workspaceTagsService.getHashedRemotesFromUri(workspaceUri, true); })).then(hashedRemotes => { /* __GDPR__ "workspace.hashedRemotes" : { @@ -268,7 +268,7 @@ export class WorkspaceStats implements IWorkbenchContribution { return this.fileService.resolveAll(uris.map(resource => ({ resource }))).then( results => { const names = ([]).concat(...results.map(result => result.success ? (result.stat!.children || []) : [])).map(c => c.name); - const referencesAzure = WorkspaceStats.searchArray(names, /azure/i); + const referencesAzure = WorkspaceTags.searchArray(names, /azure/i); if (referencesAzure) { tags['node'] = true; } diff --git a/src/vs/workbench/contrib/stats/electron-browser/workspaceStatsService.ts b/src/vs/workbench/contrib/tags/electron-browser/workspaceTagsService.ts similarity index 97% rename from src/vs/workbench/contrib/stats/electron-browser/workspaceStatsService.ts rename to src/vs/workbench/contrib/tags/electron-browser/workspaceTagsService.ts index 4f564eef8d5e..f08d92976f55 100644 --- a/src/vs/workbench/contrib/stats/electron-browser/workspaceStatsService.ts +++ b/src/vs/workbench/contrib/tags/electron-browser/workspaceTagsService.ts @@ -19,8 +19,9 @@ import { localize } from 'vs/nls'; import Severity from 'vs/base/common/severity'; import { joinPath } from 'vs/base/common/resources'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IWorkspaceStatsService, Tags } from 'vs/workbench/contrib/stats/common/workspaceStats'; -import { getHashedRemotesFromConfig } from 'vs/workbench/contrib/stats/electron-browser/workspaceStats'; +import { IWorkspaceTagsService, Tags } from 'vs/workbench/contrib/tags/common/workspaceTags'; +import { getHashedRemotesFromConfig } from 'vs/workbench/contrib/tags/electron-browser/workspaceTags'; +import { IProductService } from 'vs/platform/product/common/productService'; const ModulesToLookFor = [ // Packages that suggest a node server @@ -90,7 +91,7 @@ const PyModulesToLookFor = [ 'botframework-connector' ]; -export class WorkspaceStatsService implements IWorkspaceStatsService { +export class WorkspaceTagsService implements IWorkspaceTagsService { _serviceBrand: undefined; private _tags: Tags | undefined; @@ -98,13 +99,14 @@ export class WorkspaceStatsService implements IWorkspaceStatsService { @IFileService private readonly fileService: IFileService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, + @IProductService private readonly productService: IProductService, @IHostService private readonly hostService: IHostService, @INotificationService private readonly notificationService: INotificationService, @IQuickInputService private readonly quickInputService: IQuickInputService, @ITextFileService private readonly textFileService: ITextFileService ) { } - public async getTags(): Promise { + async getTags(): Promise { if (!this._tags) { this._tags = await this.resolveWorkspaceTags(this.environmentService.configuration, rootFiles => this.handleWorkspaceFiles(rootFiles)); } @@ -112,7 +114,7 @@ export class WorkspaceStatsService implements IWorkspaceStatsService { return this._tags; } - public getTelemetryWorkspaceId(workspace: IWorkspace, state: WorkbenchState): string | undefined { + getTelemetryWorkspaceId(workspace: IWorkspace, state: WorkbenchState): string | undefined { function createHash(uri: URI): string { return crypto.createHash('sha1').update(uri.scheme === Schemas.file ? uri.fsPath : uri.toString()).digest('hex'); } @@ -260,7 +262,7 @@ export class WorkspaceStatsService implements IWorkspaceStatsService { tags['workspace.roots'] = isEmpty ? 0 : workspace.folders.length; tags['workspace.empty'] = isEmpty; - const folders = !isEmpty ? workspace.folders.map(folder => folder.uri) : this.environmentService.appQuality !== 'stable' && this.findFolders(configuration); + const folders = !isEmpty ? workspace.folders.map(folder => folder.uri) : this.productService.quality !== 'stable' && this.findFolders(configuration); if (!folders || !folders.length || !this.fileService) { return Promise.resolve(tags); } @@ -503,4 +505,4 @@ export class WorkspaceStatsService implements IWorkspaceStatsService { } } -registerSingleton(IWorkspaceStatsService, WorkspaceStatsService, true); +registerSingleton(IWorkspaceTagsService, WorkspaceTagsService, true); diff --git a/src/vs/workbench/contrib/stats/test/workspaceStats.test.ts b/src/vs/workbench/contrib/tags/test/workspaceTags.test.ts similarity index 98% rename from src/vs/workbench/contrib/stats/test/workspaceStats.test.ts rename to src/vs/workbench/contrib/tags/test/workspaceTags.test.ts index b2960ffc77f1..202af2773f80 100644 --- a/src/vs/workbench/contrib/stats/test/workspaceStats.test.ts +++ b/src/vs/workbench/contrib/tags/test/workspaceTags.test.ts @@ -5,13 +5,13 @@ import * as assert from 'assert'; import * as crypto from 'crypto'; -import { getDomainsOfRemotes, getRemotes, getHashedRemotesFromConfig } from 'vs/workbench/contrib/stats/electron-browser/workspaceStats'; +import { getDomainsOfRemotes, getRemotes, getHashedRemotesFromConfig } from 'vs/workbench/contrib/tags/electron-browser/workspaceTags'; function hash(value: string): string { return crypto.createHash('sha1').update(value.toString()).digest('hex'); } -suite('Telemetry - WorkspaceStats', () => { +suite('Telemetry - WorkspaceTags', () => { const whitelist = [ 'github.com', @@ -163,4 +163,4 @@ suite('Telemetry - WorkspaceStats', () => { fetch = +refs/heads/*:refs/remotes/origin/* `; } -}); \ No newline at end of file +}); diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index 3e22c5022a9a..9ab398a0d0f5 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -82,12 +82,16 @@ import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cance const QUICKOPEN_HISTORY_LIMIT_CONFIG = 'task.quickOpen.history'; const QUICKOPEN_DETAIL_CONFIG = 'task.quickOpen.detail'; +const PROBLEM_MATCHER_NEVER_CONFIG = 'task.problemMatchers.neverPrompt'; +const QUICKOPEN_SKIP_CONFIG = 'task.quickOpen.skip'; export namespace ConfigureTaskAction { export const ID = 'workbench.action.tasks.configureTaskRunner'; export const TEXT = nls.localize('ConfigureTaskRunnerAction.label', "Configure Task"); } +type TaskQuickPickEntryType = (IQuickPickItem & { task: Task; }) | (IQuickPickItem & { folder: IWorkspaceFolder; }); + class ProblemReporter implements TaskConfig.IProblemReporter { private _validationStatus: ValidationStatus; @@ -184,6 +188,13 @@ interface TaskQuickPickEntry extends IQuickPickItem { task: Task | undefined | null; } +interface ProblemMatcherDisableMetrics { + type: string; +} +type ProblemMatcherDisableMetricsClassification = { + type: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; +}; + export abstract class AbstractTaskService extends Disposable implements ITaskService { // private static autoDetectTelemetryName: string = 'taskServer.autoDetect'; @@ -286,9 +297,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer this.updateSetup(folderSetup); this.updateWorkspaceTasks(); })); - this._register(Event.debounce(this.configurationService.onDidChangeConfiguration, () => { - return; - }, 1000)(() => { + this._register(this.configurationService.onDidChangeConfiguration(() => { if (!this._taskSystem && !this._workspaceTasksPromise) { return; } @@ -690,7 +699,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer }); } - public run(task: Task | undefined, options?: ProblemMatcherRunOptions, runSource: TaskRunSource = TaskRunSource.System): Promise { + public run(task: Task | undefined, options?: ProblemMatcherRunOptions, runSource: TaskRunSource = TaskRunSource.System): Promise { if (!task) { throw new TaskError(Severity.Info, nls.localize('TaskServer.noTask', 'Task to execute is undefined'), TaskErrors.TaskNotFound); } @@ -721,14 +730,35 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer private isProvideTasksEnabled(): boolean { const settingValue = this.configurationService.getValue('task.autoDetect'); - return settingValue === true; + return settingValue === 'on'; + } + + private isProblemMatcherPromptEnabled(type?: string): boolean { + const settingValue = this.configurationService.getValue(PROBLEM_MATCHER_NEVER_CONFIG); + if (Types.isBoolean(settingValue)) { + return !settingValue; + } + if (type === undefined) { + return true; + } + const settingValueMap: IStringDictionary = settingValue; + return !settingValueMap[type]; + } + + private getTypeForTask(task: Task): string { + let type: string; + if (CustomTask.is(task)) { + let configProperties: TaskConfig.ConfigurationProperties = task._source.config.element; + type = (configProperties).type; + } else { + type = task.getDefinition()!.type; + } + return type; } private shouldAttachProblemMatcher(task: Task): boolean { - const settingValue = this.configurationService.getValue('task.problemMatchers.neverPrompt'); - if (settingValue === true) { - return false; - } else if (task.type && Types.isStringArray(settingValue) && (settingValue.indexOf(task.type) >= 0)) { + const enabled = this.isProblemMatcherPromptEnabled(this.getTypeForTask(task)); + if (enabled === false) { return false; } if (!this.canCustomize(task)) { @@ -745,17 +775,33 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } if (CustomTask.is(task)) { let configProperties: TaskConfig.ConfigurationProperties = task._source.config.element; - const type: string = (configProperties).type; - return configProperties.problemMatcher === undefined && !task.hasDefinedMatchers && (Types.isStringArray(settingValue) && (settingValue.indexOf(type) < 0)); + return configProperties.problemMatcher === undefined && !task.hasDefinedMatchers; } return false; } + private async updateNeverProblemMatcherSetting(type: string): Promise { + this.telemetryService.publicLog2('problemMatcherDisabled', { type }); + const current = this.configurationService.getValue(PROBLEM_MATCHER_NEVER_CONFIG); + if (current === true) { + return; + } + let newValue: IStringDictionary; + if (current !== false) { + newValue = current; + } else { + newValue = Object.create(null); + } + newValue[type] = true; + return this.configurationService.updateValue(PROBLEM_MATCHER_NEVER_CONFIG, newValue, ConfigurationTarget.USER); + } + private attachProblemMatcher(task: ContributedTask | CustomTask): Promise { interface ProblemMatcherPickEntry extends IQuickPickItem { matcher: NamedProblemMatcher | undefined; never?: boolean; learnMore?: boolean; + setting?: string; } let entries: QuickPickInput[] = []; for (let key of ProblemMatcherRegistry.keys()) { @@ -782,14 +828,22 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } }); entries.unshift({ type: 'separator', label: nls.localize('TaskService.associate', 'associate') }); + let taskType: string; + if (CustomTask.is(task)) { + let configProperties: TaskConfig.ConfigurationProperties = task._source.config.element; + taskType = (configProperties).type; + } else { + taskType = task.getDefinition().type; + } entries.unshift( { label: nls.localize('TaskService.attachProblemMatcher.continueWithout', 'Continue without scanning the task output'), matcher: undefined }, - { label: nls.localize('TaskService.attachProblemMatcher.never', 'Never scan the task output'), matcher: undefined, never: true }, + { label: nls.localize('TaskService.attachProblemMatcher.never', 'Never scan the task output for this task'), matcher: undefined, never: true }, + { label: nls.localize('TaskService.attachProblemMatcher.neverType', 'Never scan the task output for {0} tasks', taskType), matcher: undefined, setting: taskType }, { label: nls.localize('TaskService.attachProblemMatcher.learnMoreAbout', 'Learn more about scanning the task output'), matcher: undefined, learnMore: true } ); return this.quickInputService.pick(entries, { placeHolder: nls.localize('selectProblemMatcher', 'Select for which kind of errors and warnings to scan the task output'), - }).then((selected) => { + }).then(async (selected) => { if (selected) { if (selected.learnMore) { this.openDocumentation(); @@ -809,6 +863,9 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } this.customize(task, properties, true); return newTask; + } else if (selected.setting) { + await this.updateNeverProblemMatcherSetting(selected.setting); + return task; } else { return task; } @@ -1177,7 +1234,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer private executeTask(task: Task, resolver: ITaskResolver): Promise { return ProblemMatcherRegistry.onReady().then(() => { - return this.textFileService.saveAll().then((value) => { // make sure all dirty files are saved + return this.editorService.saveAll().then((value) => { // make sure all dirty editors are saved let executeResult = this.getTaskSystem().run(task, resolver); return this.handleExecuteResult(executeResult); }); @@ -1286,7 +1343,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer setTimeout(() => { if (!isDone) { const settings: IPromptChoice = { label: nls.localize('TaskSystem.slowProvider.settings', "Settings"), run: () => this.preferencesService.openSettings(false, undefined) }; - const disableAll: IPromptChoice = { label: nls.localize('TaskSystem.slowProvider.disableAll', "Disable All"), run: () => this.configurationService.updateValue('task.autoDetect', false) }; + const disableAll: IPromptChoice = { label: nls.localize('TaskSystem.slowProvider.disableAll', "Disable All"), run: () => this.configurationService.updateValue('task.autoDetect', 'off') }; const dontShow: IPromptChoice = { label: nls.localize('TaskSystem.slowProvider.dontShow', "Don't warn again for {0} tasks", type), run: () => { if (!Types.isStringArray(settingValue)) { @@ -1299,13 +1356,13 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer this.notificationService.prompt(Severity.Warning, nls.localize('TaskSystem.slowProvider', "The {0} task provider is slow. The extension that provides {0} tasks may provide a setting to disable it, or you can disable all tasks providers", type), [settings, disableAll, dontShow]); } - }, 2000); + }, 4000); } }); } private getGroupedTasks(type?: string): Promise { - return Promise.all([this.extensionService.activateByEvent('onCommand:workbench.action.tasks.runTask'), TaskDefinitionRegistry.onReady()]).then(() => { + return Promise.all([this.extensionService.activateByEvent('onCommand:workbench.action.tasks.runTask'), this.extensionService.whenInstalledExtensionsRegistered()]).then(() => { let validTypes: IStringDictionary = Object.create(null); TaskDefinitionRegistry.all().forEach(definition => validTypes[definition.taskType] = true); validTypes['shell'] = true; @@ -1907,7 +1964,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } for (let task of tasks) { let entry: TaskQuickPickEntry = TaskQuickPickEntry(task); - entry.buttons = [{ iconClass: 'quick-open-task-configure', tooltip: nls.localize('configureTask', "Configure Task") }]; + entry.buttons = [{ iconClass: 'codicon-gear', tooltip: nls.localize('configureTask', "Configure Task") }]; if (selectedEntry && (task === selectedEntry.task)) { entries.unshift(selectedEntry); } else { @@ -1941,7 +1998,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer for (let task of tasks) { let key = task.getRecentlyUsedKey(); if (!key || !recentlyUsedTasks.has(key)) { - if (task._source.kind === TaskSourceKind.Workspace) { + if ((task._source.kind === TaskSourceKind.Workspace) || (task._source.kind === TaskSourceKind.User)) { configured.push(task); } else { detected.push(task); @@ -1965,23 +2022,43 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer return entries; } - private showQuickPick(tasks: Promise | Task[], placeHolder: string, defaultEntry?: TaskQuickPickEntry, group: boolean = false, sort: boolean = false, selectedEntry?: TaskQuickPickEntry, additionalEntries?: TaskQuickPickEntry[]): Promise { - let _createEntries = (): Promise[]> => { + private async showQuickPick(tasks: Promise | Task[], placeHolder: string, defaultEntry?: TaskQuickPickEntry, group: boolean = false, sort: boolean = false, selectedEntry?: TaskQuickPickEntry, additionalEntries?: TaskQuickPickEntry[]): Promise { + const tokenSource = new CancellationTokenSource(); + const cancellationToken: CancellationToken = tokenSource.token; + let _createEntries = new Promise[]>((resolve) => { if (Array.isArray(tasks)) { - return Promise.resolve(this.createTaskQuickPickEntries(tasks, group, sort, selectedEntry)); + resolve(this.createTaskQuickPickEntries(tasks, group, sort, selectedEntry)); } else { - return tasks.then((tasks) => this.createTaskQuickPickEntries(tasks, group, sort, selectedEntry)); + resolve(tasks.then((tasks) => this.createTaskQuickPickEntries(tasks, group, sort, selectedEntry))); } - }; - return this.quickInputService.pick(_createEntries().then((entries) => { - if ((entries.length === 0) && defaultEntry) { + }); + + const timeout: boolean = await Promise.race([new Promise(async (resolve) => { + await _createEntries; + resolve(false); + }), new Promise((resolve) => { + const timer = setTimeout(() => { + clearTimeout(timer); + resolve(true); + }, 200); + })]); + + if (!timeout && ((await _createEntries).length === 1) && this.configurationService.getValue(QUICKOPEN_SKIP_CONFIG)) { + return ((await _createEntries)[0]); + } + + const pickEntries = _createEntries.then((entries) => { + if ((entries.length === 1) && this.configurationService.getValue(QUICKOPEN_SKIP_CONFIG)) { + tokenSource.cancel(); + } else if ((entries.length === 0) && defaultEntry) { entries.push(defaultEntry); } else if (entries.length > 1 && additionalEntries && additionalEntries.length > 0) { entries.push({ type: 'separator', label: '' }); entries.push(additionalEntries[0]); } return entries; - }), { + }); + return this.quickInputService.pick(pickEntries, { placeHolder, matchOnDescription: true, onDidTriggerItemButton: context => { @@ -1993,6 +2070,18 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer this.openConfig(task); } } + }, cancellationToken).then(async (selection) => { + if (cancellationToken.isCancellationRequested) { + // canceled when there's only one task + const task = (await pickEntries)[0]; + if ((task).task) { + selection = task; + } + } + if (!selection) { + return undefined; //{{SQL CARBON EDIT}} strict-null-check + } + return selection; }); } @@ -2075,7 +2164,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } ProblemMatcherRegistry.onReady().then(() => { - return this.textFileService.saveAll().then((value) => { // make sure all dirty files are saved + return this.editorService.saveAll().then((value) => { // make sure all dirty editors are saved let executeResult = this.getTaskSystem().rerun(); if (executeResult) { return this.handleExecuteResult(executeResult); @@ -2374,8 +2463,33 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer }); } + private isTaskEntry(value: IQuickPickItem): value is IQuickPickItem & { task: Task } { + let candidate: IQuickPickItem & { task: Task } = value as any; + return candidate && !!candidate.task; + } + + private configureTask(task: Task) { + if (ContributedTask.is(task)) { + this.customize(task, undefined, true); + } else if (CustomTask.is(task)) { + this.openConfig(task); + } else if (ConfiguringTask.is(task)) { + // Do nothing. + } + } - private runConfigureTasks(): void { + private handleSelection(selection: TaskQuickPickEntryType) { + if (!selection) { + return; + } + if (this.isTaskEntry(selection)) { + this.configureTask(selection.task); + } else { + this.openTaskFile(selection.folder.toResource('.vscode/tasks.json')); + } + } + + private async runConfigureTasks(): Promise { if (!this.canRunCommand()) { return undefined; } @@ -2386,21 +2500,6 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer taskPromise = Promise.resolve(new TaskMap()); } - let configureTask = (task: Task): void => { - if (ContributedTask.is(task)) { - this.customize(task, undefined, true); - } else if (CustomTask.is(task)) { - this.openConfig(task); - } else if (ConfiguringTask.is(task)) { - // Do nothing. - } - }; - - function isTaskEntry(value: IQuickPickItem): value is IQuickPickItem & { task: Task } { - let candidate: IQuickPickItem & { task: Task } = value as any; - return candidate && !!candidate.task; - } - let stats = this.contextService.getWorkspace().folders.map>((folder) => { return this.fileService.resolve(folder.toResource('.vscode/tasks.json')).then(stat => stat, () => undefined); }); @@ -2409,13 +2508,12 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer let openLabel = nls.localize('TaskService.openJsonFile', 'Open tasks.json file'); const tokenSource = new CancellationTokenSource(); const cancellationToken: CancellationToken = tokenSource.token; - type EntryType = (IQuickPickItem & { task: Task; }) | (IQuickPickItem & { folder: IWorkspaceFolder; }); let entries = Promise.all(stats).then((stats) => { return taskPromise.then((taskMap) => { - let entries: QuickPickInput[] = []; + let entries: QuickPickInput[] = []; + let needsCreateOrOpen: boolean = true; if (this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY) { let tasks = taskMap.all(); - let needsCreateOrOpen: boolean = true; if (tasks.length > 0) { tasks = tasks.sort((a, b) => a._label.localeCompare(b._label)); for (let task of tasks) { @@ -2440,7 +2538,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer if (tasks.length > 0) { tasks = tasks.slice().sort((a, b) => a._label.localeCompare(b._label)); for (let i = 0; i < tasks.length; i++) { - let entry: EntryType = { label: tasks[i]._label, task: tasks[i], description: folder.name }; + let entry: TaskQuickPickEntryType = { label: tasks[i]._label, task: tasks[i], description: folder.name }; if (i === 0) { entries.push({ type: 'separator', label: folder.name }); } @@ -2448,20 +2546,38 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } } else { let label = stats[index] !== undefined ? openLabel : createLabel; - let entry: EntryType = { label, folder: folder }; + let entry: TaskQuickPickEntryType = { label, folder: folder }; entries.push({ type: 'separator', label: folder.name }); entries.push(entry); } index++; } } - if (entries.length === 1) { + if ((entries.length === 1) && !needsCreateOrOpen) { tokenSource.cancel(); } return entries; }); }); + const timeout: boolean = await Promise.race([new Promise(async (resolve) => { + await entries; + resolve(false); + }), new Promise((resolve) => { + const timer = setTimeout(() => { + clearTimeout(timer); + resolve(true); + }, 200); + })]); + + if (!timeout && ((await entries).length === 1) && this.configurationService.getValue(QUICKOPEN_SKIP_CONFIG)) { + const entry: any = ((await entries)[0]); + if (entry.task) { + this.handleSelection(entry); + return; + } + } + this.quickInputService.pick(entries, { placeHolder: nls.localize('TaskService.pickTask', 'Select a task to configure') }, cancellationToken). then(async (selection) => { @@ -2469,17 +2585,10 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer // canceled when there's only one task const task = (await entries)[0]; if ((task).task) { - selection = task; + selection = task; } } - if (!selection) { - return; - } - if (isTaskEntry(selection)) { - configureTask(selection.task); - } else { - this.openTaskFile(selection.folder.toResource('.vscode/tasks.json')); - } + this.handleSelection(selection); }); } diff --git a/src/vs/workbench/contrib/tasks/browser/quickOpen.ts b/src/vs/workbench/contrib/tasks/browser/quickOpen.ts index 00dd2a53dab2..075a84f5daa6 100644 --- a/src/vs/workbench/contrib/tasks/browser/quickOpen.ts +++ b/src/vs/workbench/contrib/tasks/browser/quickOpen.ts @@ -173,7 +173,7 @@ class CustomizeTaskAction extends Action { } public updateClass(): void { - this.class = 'quick-open-task-configure'; + this.class = 'codicon-gear'; } public run(element: any): Promise { @@ -235,4 +235,4 @@ export class QuickOpenActionContributor extends ActionBarContributor { } return undefined; } -} \ No newline at end of file +} diff --git a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts index 388a4f97dbd6..f74221c1d0ab 100644 --- a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts +++ b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts @@ -3,8 +3,6 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!../common/media/task.contribution'; - import * as nls from 'vs/nls'; import { QuickOpenHandler } from 'vs/workbench/contrib/tasks/browser/taskQuickOpen'; @@ -47,7 +45,7 @@ const workbenchRegistry = Registry.as(Workbench workbenchRegistry.registerWorkbenchContribution(RunAutomaticTasks, LifecyclePhase.Eventually); const actionRegistry = Registry.as(ActionExtensions.WorkbenchActions); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ManageAutomaticTaskRunning, ManageAutomaticTaskRunning.ID, ManageAutomaticTaskRunning.LABEL), 'Tasks: Manage Automatic Tasks in Folder', tasksCategory); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ManageAutomaticTaskRunning, ManageAutomaticTaskRunning.ID, ManageAutomaticTaskRunning.LABEL), 'Tasks: Manage Automatic Tasks in Folder', tasksCategory); export class TaskStatusBarContributions extends Disposable implements IWorkbenchContribution { private runningTasksStatusItem: IStatusbarEntryAccessor | undefined; @@ -256,7 +254,7 @@ const quickOpenRegistry = (Registry.as(QuickOpenExtensions.Q const tasksPickerContextKey = 'inTasksPicker'; quickOpenRegistry.registerQuickOpenHandler( - new QuickOpenHandlerDescriptor( + QuickOpenHandlerDescriptor.create( QuickOpenHandler, QuickOpenHandler.ID, 'task ', @@ -315,17 +313,22 @@ configurationRegistry.registerConfiguration({ type: 'object', properties: { 'task.problemMatchers.neverPrompt': { - markdownDescription: nls.localize('task.problemMatchers.neverPrompt', "Configures whether to show the problem matcher prompt when running a task. Set to `true` to never promp, or use an array of task types to turn off prompting only for specific task types."), + markdownDescription: nls.localize('task.problemMatchers.neverPrompt', "Configures whether to show the problem matcher prompt when running a task. Set to `true` to never prompt, or use a dictionary of task types to turn off prompting only for specific task types."), 'oneOf': [ { type: 'boolean', markdownDescription: nls.localize('task.problemMatchers.neverPrompt.boolean', 'Sets problem matcher prompting behavior for all tasks.') }, { - type: 'array', - items: { - type: 'string', - markdownDescription: nls.localize('task.problemMatchers.neverPrompt.array', 'An array of task types to never prompt for problem matchers on.') + type: 'object', + patternProperties: { + '.*': { + type: 'boolean' + } + }, + markdownDescription: nls.localize('task.problemMatchers.neverPrompt.array', 'An object containing task type-boolean pairs to never prompt for problem matchers on.'), + default: { + 'shell': true } } ], @@ -333,8 +336,9 @@ configurationRegistry.registerConfiguration({ }, 'task.autoDetect': { markdownDescription: nls.localize('task.autoDetect', "Controls enablement of `provideTasks` for all task provider extension. If the Tasks: Run Task command is slow, disabling auto detect for task providers may help. Individual extensions my provide settings to disabled auto detection."), - type: 'boolean', - default: true + type: 'string', + enum: ['on', 'off'], + default: 'on' }, 'task.slowProviderWarning': { markdownDescription: nls.localize('task.slowProviderWarning', "Configures whether a warning is shown when a provider is slow"), @@ -362,6 +366,11 @@ configurationRegistry.registerConfiguration({ markdownDescription: nls.localize('task.quickOpen.detail', "Controls whether to show the task detail for task that have a detail in the Run Task quick pick."), type: 'boolean', default: true + }, + 'task.quickOpen.skip': { + type: 'boolean', + description: nls.localize('task.quickOpen.skip', "Controls whether the task quick pick is skipped when there is only one task to pick from."), + default: false } } }); diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts index 39d9a2f94532..e59f9b6c644c 100644 --- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts @@ -343,7 +343,8 @@ export class TerminalTaskSystem implements ITaskSystem { return Promise.all(promises); } - private async executeTask(task: Task, resolver: ITaskResolver, trigger: string): Promise { + private async executeTask(task: Task, resolver: ITaskResolver, trigger: string, alreadyResolved?: Map): Promise { + alreadyResolved = alreadyResolved ?? new Map(); let promises: Promise[] = []; if (task.configurationProperties.dependsOn) { for (const dependency of task.configurationProperties.dependsOn) { @@ -353,7 +354,7 @@ export class TerminalTaskSystem implements ITaskSystem { let promise = this.activeTasks[key] ? this.activeTasks[key].promise : undefined; if (!promise) { this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.DependsOnStarted, task)); - promise = this.executeTask(dependencyTask, resolver, trigger); + promise = this.executeTask(dependencyTask, resolver, trigger, alreadyResolved); } if (task.configurationProperties.dependsOrder === DependsOrder.sequence) { promise = Promise.resolve(await promise); @@ -378,9 +379,9 @@ export class TerminalTaskSystem implements ITaskSystem { } } if (this.isRerun) { - return this.reexecuteCommand(task, trigger); + return this.reexecuteCommand(task, trigger, alreadyResolved!); } else { - return this.executeCommand(task, trigger); + return this.executeCommand(task, trigger, alreadyResolved!); } }); } else { @@ -395,7 +396,36 @@ export class TerminalTaskSystem implements ITaskSystem { } } - private resolveVariablesFromSet(taskSystemInfo: TaskSystemInfo | undefined, workspaceFolder: IWorkspaceFolder | undefined, task: CustomTask | ContributedTask, variables: Set): Promise { + private resolveAndFindExecutable(workspaceFolder: IWorkspaceFolder | undefined, task: CustomTask | ContributedTask, cwd: string | undefined, envPath: string | undefined): Promise { + return this.findExecutable( + this.configurationResolverService.resolve(workspaceFolder, CommandString.value(task.command.name!)), + cwd ? this.configurationResolverService.resolve(workspaceFolder, cwd) : undefined, + envPath ? envPath.split(path.delimiter).map(p => this.configurationResolverService.resolve(workspaceFolder, p)) : undefined + ); + } + + private findUnresolvedVariables(variables: Set, alreadyResolved: Map): Set { + if (alreadyResolved.size === 0) { + return variables; + } + const unresolved = new Set(); + for (const variable of variables) { + if (!alreadyResolved.has(variable.substring(2, variable.length - 1))) { + unresolved.add(variable); + } + } + return unresolved; + } + + private mergeMaps(mergeInto: Map, mergeFrom: Map) { + for (const entry of mergeFrom) { + if (!mergeInto.has(entry[0])) { + mergeInto.set(entry[0], entry[1]); + } + } + } + + private resolveVariablesFromSet(taskSystemInfo: TaskSystemInfo | undefined, workspaceFolder: IWorkspaceFolder | undefined, task: CustomTask | ContributedTask, variables: Set, alreadyResolved: Map): Promise { let isProcess = task.command && task.command.runtime === RuntimeType.Process; let options = task.command && task.command.options ? task.command.options : undefined; let cwd = options ? options.cwd : undefined; @@ -410,11 +440,11 @@ export class TerminalTaskSystem implements ITaskSystem { } } } - + const unresolved = this.findUnresolvedVariables(variables, alreadyResolved); let resolvedVariables: Promise; if (taskSystemInfo && workspaceFolder) { let resolveSet: ResolveSet = { - variables + variables: unresolved }; if (taskSystemInfo.platform === Platform.Platform.Windows && isProcess) { @@ -426,28 +456,32 @@ export class TerminalTaskSystem implements ITaskSystem { resolveSet.process.path = envPath; } } - resolvedVariables = taskSystemInfo.resolveVariables(workspaceFolder, resolveSet).then(resolved => { - if ((taskSystemInfo.platform !== Platform.Platform.Windows) && isProcess) { - resolved.variables.set(TerminalTaskSystem.ProcessVarName, CommandString.value(task.command.name!)); + resolvedVariables = taskSystemInfo.resolveVariables(workspaceFolder, resolveSet).then(async (resolved) => { + this.mergeMaps(alreadyResolved, resolved.variables); + resolved.variables = new Map(alreadyResolved); + if (isProcess) { + let process = CommandString.value(task.command.name!); + if (taskSystemInfo.platform === Platform.Platform.Windows) { + process = await this.resolveAndFindExecutable(workspaceFolder, task, cwd, envPath); + } + resolved.variables.set(TerminalTaskSystem.ProcessVarName, process); } return Promise.resolve(resolved); }); return resolvedVariables; } else { let variablesArray = new Array(); - variables.forEach(variable => variablesArray.push(variable)); + unresolved.forEach(variable => variablesArray.push(variable)); return new Promise((resolve, reject) => { - this.configurationResolverService.resolveWithInteraction(workspaceFolder, variablesArray, 'tasks').then(async resolvedVariablesMap => { + this.configurationResolverService.resolveWithInteraction(workspaceFolder, variablesArray, 'tasks').then(async (resolvedVariablesMap: Map | undefined) => { if (resolvedVariablesMap) { + this.mergeMaps(alreadyResolved, resolvedVariablesMap); + resolvedVariablesMap = new Map(alreadyResolved); if (isProcess) { let processVarValue: string; if (Platform.isWindows) { - processVarValue = await this.findExecutable( - this.configurationResolverService.resolve(workspaceFolder, CommandString.value(task.command.name!)), - cwd ? this.configurationResolverService.resolve(workspaceFolder, cwd) : undefined, - envPath ? envPath.split(path.delimiter).map(p => this.configurationResolverService.resolve(workspaceFolder, p)) : undefined - ); + processVarValue = await this.resolveAndFindExecutable(workspaceFolder, task, cwd, envPath); } else { processVarValue = this.configurationResolverService.resolve(workspaceFolder, CommandString.value(task.command.name!)); } @@ -467,7 +501,7 @@ export class TerminalTaskSystem implements ITaskSystem { } } - private executeCommand(task: CustomTask | ContributedTask, trigger: string): Promise { + private executeCommand(task: CustomTask | ContributedTask, trigger: string, alreadyResolved: Map): Promise { const taskWorkspaceFolder = task.getWorkspaceFolder(); let workspaceFolder: IWorkspaceFolder | undefined; if (taskWorkspaceFolder) { @@ -480,7 +514,7 @@ export class TerminalTaskSystem implements ITaskSystem { let variables = new Set(); this.collectTaskVariables(variables, task); - const resolvedVariables = this.resolveVariablesFromSet(systemInfo, workspaceFolder, task, variables); + const resolvedVariables = this.resolveVariablesFromSet(systemInfo, workspaceFolder, task, variables, alreadyResolved); return resolvedVariables.then((resolvedVariables) => { const isCustomExecution = (task.command.runtime === RuntimeType.CustomExecution); @@ -497,7 +531,7 @@ export class TerminalTaskSystem implements ITaskSystem { }); } - private reexecuteCommand(task: CustomTask | ContributedTask, trigger: string): Promise { + private reexecuteCommand(task: CustomTask | ContributedTask, trigger: string, alreadyResolved: Map): Promise { const lastTask = this.lastTask; if (!lastTask) { return Promise.reject(new Error('No task previously run')); @@ -515,7 +549,7 @@ export class TerminalTaskSystem implements ITaskSystem { }); if (!hasAllVariables) { - return this.resolveVariablesFromSet(lastTask.getVerifiedTask().systemInfo, lastTask.getVerifiedTask().workspaceFolder, task, variables).then((resolvedVariables) => { + return this.resolveVariablesFromSet(lastTask.getVerifiedTask().systemInfo, lastTask.getVerifiedTask().workspaceFolder, task, variables, alreadyResolved).then((resolvedVariables) => { this.currentTask.resolvedVariables = resolvedVariables; return this.executeInTerminal(task, trigger, new VariableResolver(lastTask.getVerifiedTask().workspaceFolder, lastTask.getVerifiedTask().systemInfo, resolvedVariables.variables, this.configurationResolverService), workspaceFolder!); }, reason => { @@ -1412,6 +1446,14 @@ export class TerminalTaskSystem implements ITaskSystem { } } + private async fileExists(path: string): Promise { + const uri: URI = resources.toLocalResource(URI.from({ scheme: Schemas.file, path: path }), this.environmentService.configuration.remoteAuthority); + if (await this.fileService.exists(uri)) { + return !((await this.fileService.resolve(uri)).isDirectory); + } + return false; + } + private async findExecutable(command: string, cwd?: string, paths?: string[]): Promise { // If we have an absolute path then we take it. if (path.isAbsolute(command)) { @@ -1444,15 +1486,15 @@ export class TerminalTaskSystem implements ITaskSystem { fullPath = path.join(cwd, pathEntry, command); } - if (await this.fileService.exists(resources.toLocalResource(URI.from({ scheme: Schemas.file, path: fullPath }), this.environmentService.configuration.remoteAuthority))) { + if (await this.fileExists(fullPath)) { return fullPath; } let withExtension = fullPath + '.com'; - if (await this.fileService.exists(resources.toLocalResource(URI.from({ scheme: Schemas.file, path: withExtension }), this.environmentService.configuration.remoteAuthority))) { + if (await this.fileExists(withExtension)) { return withExtension; } withExtension = fullPath + '.exe'; - if (await this.fileService.exists(resources.toLocalResource(URI.from({ scheme: Schemas.file, path: withExtension }), this.environmentService.configuration.remoteAuthority))) { + if (await this.fileExists(withExtension)) { return withExtension; } } diff --git a/src/vs/workbench/contrib/tasks/common/jsonSchemaCommon.ts b/src/vs/workbench/contrib/tasks/common/jsonSchemaCommon.ts index 1340ffd64d71..37afaaac666d 100644 --- a/src/vs/workbench/contrib/tasks/common/jsonSchemaCommon.ts +++ b/src/vs/workbench/contrib/tasks/common/jsonSchemaCommon.ts @@ -44,10 +44,10 @@ const schema: IJSONSchema = { type: 'array', items: { anyOf: [ - Schemas.LegacyProblemMatcher, { type: 'string', - } + }, + Schemas.LegacyProblemMatcher ] } } diff --git a/src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts b/src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts index 5af470eb9d0c..5e9931a3e065 100644 --- a/src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts +++ b/src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts @@ -569,7 +569,7 @@ export function updateProblemMatchers() { try { let matcherIds = ProblemMatcherRegistry.keys().map(key => '$' + key); definitions.problemMatcherType2.oneOf![0].enum = matcherIds; - (definitions.problemMatcherType2.oneOf![2].items as IJSONSchema).anyOf![1].enum = matcherIds; + (definitions.problemMatcherType2.oneOf![2].items as IJSONSchema).anyOf![0].enum = matcherIds; } catch (err) { console.log('Installing problem matcher ids failed'); } diff --git a/src/vs/workbench/contrib/tasks/common/media/configure-dark.svg b/src/vs/workbench/contrib/tasks/common/media/configure-dark.svg deleted file mode 100644 index ace01a5ddf5a..000000000000 --- a/src/vs/workbench/contrib/tasks/common/media/configure-dark.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/vs/workbench/contrib/tasks/common/media/configure-hc.svg b/src/vs/workbench/contrib/tasks/common/media/configure-hc.svg deleted file mode 100644 index bd59cb81f6d9..000000000000 --- a/src/vs/workbench/contrib/tasks/common/media/configure-hc.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/vs/workbench/contrib/tasks/common/media/configure-light.svg b/src/vs/workbench/contrib/tasks/common/media/configure-light.svg deleted file mode 100644 index 4194780bbaad..000000000000 --- a/src/vs/workbench/contrib/tasks/common/media/configure-light.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/vs/workbench/contrib/tasks/common/media/task.contribution.css b/src/vs/workbench/contrib/tasks/common/media/task.contribution.css deleted file mode 100644 index faa818475bbe..000000000000 --- a/src/vs/workbench/contrib/tasks/common/media/task.contribution.css +++ /dev/null @@ -1,16 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -.monaco-workbench .quick-open-task-configure { - background-image: url('configure-light.svg'); -} - -.vs-dark .monaco-workbench .quick-open-task-configure { - background-image: url('configure-dark.svg'); -} - -.hc-black .monaco-workbench .quick-open-task-configure { - background-image: url('configure-hc.svg'); -} diff --git a/src/vs/workbench/contrib/tasks/common/problemMatcher.ts b/src/vs/workbench/contrib/tasks/common/problemMatcher.ts index a3ced763379e..9c2c5953ec4f 100644 --- a/src/vs/workbench/contrib/tasks/common/problemMatcher.ts +++ b/src/vs/workbench/contrib/tasks/common/problemMatcher.ts @@ -790,6 +790,12 @@ export namespace Config { * the current working directory. This is the default. * - ["relative", "path value"]: the filename is always * treated relative to the given path value. + * - "autodetect": the filename is treated relative to + * the current workspace directory, and if the file + * does not exist, it is treated as absolute. + * - ["autodetect", "path value"]: the filename is treated + * relative to the given path value, and if it does not + * exist, it is treated as absolute. */ fileLocation?: string | string[]; diff --git a/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts b/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts index 52cb5e13643c..93e8a60853cc 100644 --- a/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts +++ b/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts @@ -552,7 +552,7 @@ interface MetaData { } -function _isEmpty(this: void, value: T | undefined, properties: MetaData[] | undefined): boolean { +function _isEmpty(this: void, value: T | undefined, properties: MetaData[] | undefined, allowEmptyArray: boolean = false): boolean { if (value === undefined || value === null || properties === undefined) { return true; } @@ -561,7 +561,7 @@ function _isEmpty(this: void, value: T | undefined, properties: MetaData 0) { + } else if (!Array.isArray(property) || (property.length > 0) || allowEmptyArray) { return false; } } @@ -591,11 +591,11 @@ function _assignProperties(this: void, target: T | undefined, source: T | und return target; } -function _fillProperties(this: void, target: T | undefined, source: T | undefined, properties: MetaData[] | undefined): T | undefined { +function _fillProperties(this: void, target: T | undefined, source: T | undefined, properties: MetaData[] | undefined, allowEmptyArray: boolean = false): T | undefined { if (!source || _isEmpty(source, properties)) { return target; } - if (!target || _isEmpty(target, properties)) { + if (!target || _isEmpty(target, properties, allowEmptyArray)) { return source; } for (let meta of properties!) { @@ -727,7 +727,7 @@ namespace ShellConfiguration { } export function isEmpty(this: void, value: Tasks.ShellConfiguration): boolean { - return _isEmpty(value, properties); + return _isEmpty(value, properties, true); } export function assignProperties(this: void, target: Tasks.ShellConfiguration | undefined, source: Tasks.ShellConfiguration | undefined): Tasks.ShellConfiguration | undefined { @@ -735,7 +735,7 @@ namespace ShellConfiguration { } export function fillProperties(this: void, target: Tasks.ShellConfiguration, source: Tasks.ShellConfiguration): Tasks.ShellConfiguration | undefined { - return _fillProperties(target, source, properties); + return _fillProperties(target, source, properties, true); } export function fillDefaults(this: void, value: Tasks.ShellConfiguration, context: ParseContext): Tasks.ShellConfiguration { diff --git a/src/vs/workbench/contrib/tasks/common/taskService.ts b/src/vs/workbench/contrib/tasks/common/taskService.ts index b58551703270..cca9420dd298 100644 --- a/src/vs/workbench/contrib/tasks/common/taskService.ts +++ b/src/vs/workbench/contrib/tasks/common/taskService.ts @@ -58,7 +58,7 @@ export interface ITaskService { configureAction(): Action; build(): Promise; runTest(): Promise; - run(task: Task | undefined, options?: ProblemMatcherRunOptions): Promise; + run(task: Task | undefined, options?: ProblemMatcherRunOptions): Promise; inTerminal(): boolean; isActive(): Promise; getActiveTasks(): Promise; diff --git a/src/vs/workbench/contrib/tasks/node/processTaskSystem.ts b/src/vs/workbench/contrib/tasks/node/processTaskSystem.ts index f47fb1fbdbce..8a9756c2ae75 100644 --- a/src/vs/workbench/contrib/tasks/node/processTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/node/processTaskSystem.ts @@ -91,7 +91,7 @@ export class ProcessTaskSystem implements ITaskSystem { } public getBusyTasks(): Task[] { - return this.getActiveTasks(); + return []; } public run(task: Task): ITaskExecuteResult { diff --git a/src/vs/workbench/contrib/terminal/browser/addons/commandTrackerAddon.ts b/src/vs/workbench/contrib/terminal/browser/addons/commandTrackerAddon.ts index 9a75ad3a021f..8caac7c43436 100644 --- a/src/vs/workbench/contrib/terminal/browser/addons/commandTrackerAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/addons/commandTrackerAddon.ts @@ -64,7 +64,15 @@ export class CommandTrackerAddon implements ICommandTracker, ITerminalAddon { } let markerIndex; - if (this._currentMarker === Boundary.Bottom) { + const currentLineY = Math.min(this._getLine(this._terminal, this._currentMarker), this._terminal.buffer.baseY); + const viewportY = this._terminal.buffer.viewportY; + if (!retainSelection && currentLineY !== viewportY) { + // The user has scrolled, find the line based on the current scroll position. This only + // works when not retaining selection + const markersBelowViewport = this._terminal.markers.filter(e => e.line >= viewportY).length; + // -1 will scroll to the top + markerIndex = this._terminal.markers.length - markersBelowViewport - 1; + } else if (this._currentMarker === Boundary.Bottom) { markerIndex = this._terminal.markers.length - 1; } else if (this._currentMarker === Boundary.Top) { markerIndex = -1; @@ -95,7 +103,15 @@ export class CommandTrackerAddon implements ICommandTracker, ITerminalAddon { } let markerIndex; - if (this._currentMarker === Boundary.Bottom) { + const currentLineY = Math.min(this._getLine(this._terminal, this._currentMarker), this._terminal.buffer.baseY); + const viewportY = this._terminal.buffer.viewportY; + if (!retainSelection && currentLineY !== viewportY) { + // The user has scrolled, find the line based on the current scroll position. This only + // works when not retaining selection + const markersAboveViewport = this._terminal.markers.filter(e => e.line <= viewportY).length; + // markers.length will scroll to the bottom + markerIndex = markersAboveViewport; + } else if (this._currentMarker === Boundary.Bottom) { markerIndex = this._terminal.markers.length; } else if (this._currentMarker === Boundary.Top) { markerIndex = 0; diff --git a/src/vs/workbench/contrib/terminal/browser/media/scrollbar.css b/src/vs/workbench/contrib/terminal/browser/media/scrollbar.css index dee3846c0b5a..b23ca300e21b 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/scrollbar.css +++ b/src/vs/workbench/contrib/terminal/browser/media/scrollbar.css @@ -38,4 +38,4 @@ .monaco-workbench .panel.integrated-terminal .xterm .xterm-viewport::-webkit-scrollbar-thumb:window-inactive { background-color: inherit; -} \ No newline at end of file +} diff --git a/src/vs/workbench/contrib/terminal/browser/media/terminal.css b/src/vs/workbench/contrib/terminal/browser/media/terminal.css index 5ea5fc9e9174..8bb86f41388a 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/terminal.css +++ b/src/vs/workbench/contrib/terminal/browser/media/terminal.css @@ -10,6 +10,7 @@ flex-direction: column; background-color: transparent!important; user-select: initial; + -webkit-user-select: initial; position: relative; } @@ -104,6 +105,7 @@ bottom: 0; left: 0; user-select: none; + -webkit-user-select: none; } .monaco-workbench .panel.integrated-terminal .monaco-split-view2.vertical .split-view-view:not(:last-child) .xterm { /* When vertical and NOT the bottom terminal, align to the top instead to prevent the output jumping around erratically */ @@ -149,6 +151,11 @@ cursor: -webkit-image-set(url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAAL0lEQVQoz2NgCD3x//9/BhBYBWdhgFVAiVW4JBFKGIa4AqD0//9D3pt4I4tAdAMAHTQ/j5Zom30AAAAASUVORK5CYII=') 1x, url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAAz0lEQVRIx2NgYGBY/R8I/vx5eelX3n82IJ9FxGf6tksvf/8FiTMQAcAGQMDvSwu09abffY8QYSAScNk45G198eX//yev73/4///701eh//kZSARckrNBRvz//+8+6ZohwCzjGNjdgQxkAg7B9WADeBjIBqtJCbhRA0YNoIkBSNmaPEMoNmA0FkYNoFKhapJ6FGyAH3nauaSmPfwI0v/3OukVi0CIZ+F25KrtYcx/CTIy0e+rC7R1Z4KMICVTQQ14feVXIbR695u14+Ir4gwAAD49E54wc1kWAAAAAElFTkSuQmCC') 2x) 5 8, text; } +/* Override default xterm style to make !important so it takes precedence over custom mac cursor */ +.xterm.xterm-cursor-pointer { + cursor: pointer!important; +} + .monaco-workbench .quick-open-terminal-configure { background-image: url('configure-light.svg'); } diff --git a/src/vs/workbench/contrib/terminal/browser/media/xterm.css b/src/vs/workbench/contrib/terminal/browser/media/xterm.css index 8e078be9bc9e..e3db5a7dfe75 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/xterm.css +++ b/src/vs/workbench/contrib/terminal/browser/media/xterm.css @@ -43,7 +43,6 @@ font-feature-settings: "liga" 0; position: relative; user-select: none; - -ms-user-select: none; -webkit-user-select: none; } diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts index 997ce22b3561..c8438e912fb1 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts @@ -20,7 +20,7 @@ import * as panel from 'vs/workbench/browser/panel'; import { getQuickNavigateHandler } from 'vs/workbench/browser/parts/quickopen/quickopen'; import { Extensions as QuickOpenExtensions, IQuickOpenRegistry, QuickOpenHandlerDescriptor } from 'vs/workbench/browser/quickopen'; import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions'; -import { ClearSelectionTerminalAction, ClearTerminalAction, CopyTerminalSelectionAction, CreateNewInActiveWorkspaceTerminalAction, CreateNewTerminalAction, DeleteToLineStartTerminalAction, DeleteWordLeftTerminalAction, DeleteWordRightTerminalAction, FindNext, FindPrevious, FocusActiveTerminalAction, FocusNextPaneTerminalAction, FocusNextTerminalAction, FocusPreviousPaneTerminalAction, FocusPreviousTerminalAction, FocusTerminalFindWidgetAction, HideTerminalFindWidgetAction, KillTerminalAction, MoveToLineEndTerminalAction, MoveToLineStartTerminalAction, QuickOpenActionTermContributor, QuickOpenTermAction, RenameTerminalAction, ResizePaneDownTerminalAction, ResizePaneLeftTerminalAction, ResizePaneRightTerminalAction, ResizePaneUpTerminalAction, RunActiveFileInTerminalAction, RunSelectedTextInTerminalAction, ScrollDownPageTerminalAction, ScrollDownTerminalAction, ScrollToBottomTerminalAction, ScrollToNextCommandAction, ScrollToPreviousCommandAction, ScrollToTopTerminalAction, ScrollUpPageTerminalAction, ScrollUpTerminalAction, SelectAllTerminalAction, SelectDefaultShellWindowsTerminalAction, SelectToNextCommandAction, SelectToNextLineAction, SelectToPreviousCommandAction, SelectToPreviousLineAction, SendSequenceTerminalCommand, SplitInActiveWorkspaceTerminalAction, SplitTerminalAction, TerminalPasteAction, TERMINAL_PICKER_PREFIX, ToggleCaseSensitiveCommand, ToggleEscapeSequenceLoggingAction, ToggleRegexCommand, ToggleTerminalAction, ToggleWholeWordCommand, NavigationModeFocusPreviousTerminalAction, NavigationModeFocusNextTerminalAction, NavigationModeExitTerminalAction, ManageWorkspaceShellPermissionsTerminalCommand, CreateNewWithCwdTerminalCommand } from 'vs/workbench/contrib/terminal/browser/terminalActions'; +import { ClearSelectionTerminalAction, ClearTerminalAction, CopyTerminalSelectionAction, CreateNewInActiveWorkspaceTerminalAction, CreateNewTerminalAction, DeleteToLineStartTerminalAction, DeleteWordLeftTerminalAction, DeleteWordRightTerminalAction, FindNext, FindPrevious, FocusActiveTerminalAction, FocusNextPaneTerminalAction, FocusNextTerminalAction, FocusPreviousPaneTerminalAction, FocusPreviousTerminalAction, FocusTerminalFindWidgetAction, HideTerminalFindWidgetAction, KillTerminalAction, MoveToLineEndTerminalAction, MoveToLineStartTerminalAction, QuickOpenActionTermContributor, QuickOpenTermAction, RenameTerminalAction, ResizePaneDownTerminalAction, ResizePaneLeftTerminalAction, ResizePaneRightTerminalAction, ResizePaneUpTerminalAction, RunActiveFileInTerminalAction, RunSelectedTextInTerminalAction, ScrollDownPageTerminalAction, ScrollDownTerminalAction, ScrollToBottomTerminalAction, ScrollToNextCommandAction, ScrollToPreviousCommandAction, ScrollToTopTerminalAction, ScrollUpPageTerminalAction, ScrollUpTerminalAction, SelectAllTerminalAction, SelectDefaultShellWindowsTerminalAction, SelectToNextCommandAction, SelectToNextLineAction, SelectToPreviousCommandAction, SelectToPreviousLineAction, SendSequenceTerminalCommand, SplitInActiveWorkspaceTerminalAction, SplitTerminalAction, TerminalPasteAction, TERMINAL_PICKER_PREFIX, ToggleCaseSensitiveCommand, ToggleEscapeSequenceLoggingAction, ToggleRegexCommand, ToggleTerminalAction, ToggleWholeWordCommand, NavigationModeFocusPreviousTerminalAction, NavigationModeFocusNextTerminalAction, NavigationModeExitTerminalAction, ManageWorkspaceShellPermissionsTerminalCommand, CreateNewWithCwdTerminalCommand, RenameWithArgTerminalCommand } from 'vs/workbench/contrib/terminal/browser/terminalActions'; import { TerminalPanel } from 'vs/workbench/contrib/terminal/browser/terminalPanel'; import { TerminalPickerHandler } from 'vs/workbench/contrib/terminal/browser/terminalQuickOpen'; import { KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_NOT_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, TERMINAL_PANEL_ID, DEFAULT_LETTER_SPACING, DEFAULT_LINE_HEIGHT, TerminalCursorStyle, TERMINAL_ACTION_CATEGORY, KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, TERMINAL_COMMAND_ID } from 'vs/workbench/contrib/terminal/common/terminal'; @@ -35,6 +35,7 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { registerShellConfiguration } from 'vs/workbench/contrib/terminal/common/terminalShellConfig'; import { CONTEXT_ACCESSIBILITY_MODE_ENABLED } from 'vs/platform/accessibility/common/accessibility'; import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { BrowserFeatures } from 'vs/base/browser/canIUse'; registerSingleton(ITerminalService, TerminalService, true); @@ -47,7 +48,7 @@ const quickOpenRegistry = (Registry.as(QuickOpenExtensions.Q const inTerminalsPicker = 'inTerminalPicker'; quickOpenRegistry.registerQuickOpenHandler( - new QuickOpenHandlerDescriptor( + QuickOpenHandlerDescriptor.create( TerminalPickerHandler, TerminalPickerHandler.ID, TERMINAL_PICKER_PREFIX, @@ -168,6 +169,11 @@ configurationRegistry.registerConfiguration({ type: 'number', default: DEFAULT_LINE_HEIGHT }, + 'terminal.integrated.minimumContrastRatio': { + description: nls.localize('terminal.integrated.minimumContrastRatio', "When set the foreground color of each cell will change to try meet the contrast ratio specified. Example values:\n\n- 1: The default, do nothing.\n- 4.5: Minimum for WCAG AA compliance.\n- 7: Minimum for WCAG AAA compliance.\n- 21: White on black or black on white."), + type: 'number', + default: 1 + }, 'terminal.integrated.fontWeight': { type: 'string', enum: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'], @@ -208,11 +214,12 @@ configurationRegistry.registerConfiguration({ }, 'terminal.integrated.rendererType': { type: 'string', - enum: ['auto', 'canvas', 'dom'], + enum: ['auto', 'canvas', 'dom', 'experimentalWebgl'], enumDescriptions: [ - nls.localize('terminal.integrated.rendererType.auto', "Let Azure Data Studio guess which renderer to use."),// {{SQL CARBON EDIT}} Change product name to ADS - nls.localize('terminal.integrated.rendererType.canvas', "Use the standard GPU/canvas-based renderer"), - nls.localize('terminal.integrated.rendererType.dom', "Use the fallback DOM-based renderer.") + nls.localize('terminal.integrated.rendererType.auto', "Let Azure Data Studio which renderer to use."), // {{SQL CARBON EDIT}} Change product name to ADS + nls.localize('terminal.integrated.rendererType.canvas', "Use the standard GPU/canvas-based renderer."), + nls.localize('terminal.integrated.rendererType.dom', "Use the fallback DOM-based renderer."), + nls.localize('terminal.integrated.rendererType.experimentalWebgl', "Use the experimental webgl-based renderer. Note that this has some [known issues](https://github.com/xtermjs/xterm.js/issues?q=is%3Aopen+is%3Aissue+label%3Aarea%2Faddon%2Fwebgl) and this will only be enabled for new terminals (not hot swappable like the other renderers).") ], default: 'auto', description: nls.localize('terminal.integrated.rendererType', "Controls how the terminal is rendered.") @@ -326,11 +333,11 @@ configurationRegistry.registerConfiguration({ }); const registry = Registry.as(ActionExtensions.WorkbenchActions); -registry.registerWorkbenchAction(new SyncActionDescriptor(QuickOpenTermAction, QuickOpenTermAction.ID, QuickOpenTermAction.LABEL), 'Terminal: Switch Active Terminal', nls.localize('terminal', "Terminal")); +registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickOpenTermAction, QuickOpenTermAction.ID, QuickOpenTermAction.LABEL), 'Terminal: Switch Active Terminal', nls.localize('terminal', "Terminal")); const actionBarRegistry = Registry.as(ActionBarExtensions.Actionbar); actionBarRegistry.registerActionBarContributor(Scope.VIEWER, QuickOpenActionTermContributor); -(Registry.as(panel.Extensions.Panels)).registerPanel(new panel.PanelDescriptor( +(Registry.as(panel.Extensions.Panels)).registerPanel(panel.PanelDescriptor.create( TerminalPanel, TERMINAL_PANEL_ID, nls.localize('terminal', "Terminal"), @@ -343,28 +350,20 @@ Registry.as(panel.Extensions.Panels).setDefaultPanelId(TERM // On mac cmd+` is reserved to cycle between windows, that's why the keybindings use WinCtrl const category = TERMINAL_ACTION_CATEGORY; const actionRegistry = Registry.as(ActionExtensions.WorkbenchActions); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(KillTerminalAction, KillTerminalAction.ID, KillTerminalAction.LABEL), 'Terminal: Kill the Active Terminal Instance', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(CopyTerminalSelectionAction, CopyTerminalSelectionAction.ID, CopyTerminalSelectionAction.LABEL, { - primary: KeyMod.CtrlCmd | KeyCode.KEY_C, - linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_C } -}, ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, KEYBINDING_CONTEXT_TERMINAL_FOCUS)), 'Terminal: Copy Selection', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(CreateNewTerminalAction, CreateNewTerminalAction.ID, CreateNewTerminalAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(KillTerminalAction, KillTerminalAction.ID, KillTerminalAction.LABEL), 'Terminal: Kill the Active Terminal Instance', category); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(CreateNewTerminalAction, CreateNewTerminalAction.ID, CreateNewTerminalAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_BACKTICK, mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.US_BACKTICK } }), 'Terminal: Create New Integrated Terminal', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ClearSelectionTerminalAction, ClearSelectionTerminalAction.ID, ClearSelectionTerminalAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ClearSelectionTerminalAction, ClearSelectionTerminalAction.ID, ClearSelectionTerminalAction.LABEL, { primary: KeyCode.Escape, linux: { primary: KeyCode.Escape } }, ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_NOT_VISIBLE)), 'Terminal: Clear Selection', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(CreateNewInActiveWorkspaceTerminalAction, CreateNewInActiveWorkspaceTerminalAction.ID, CreateNewInActiveWorkspaceTerminalAction.LABEL), 'Terminal: Create New Integrated Terminal (In Active Workspace)', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FocusActiveTerminalAction, FocusActiveTerminalAction.ID, FocusActiveTerminalAction.LABEL), 'Terminal: Focus Terminal', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FocusNextTerminalAction, FocusNextTerminalAction.ID, FocusNextTerminalAction.LABEL), 'Terminal: Focus Next Terminal', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FocusPreviousTerminalAction, FocusPreviousTerminalAction.ID, FocusPreviousTerminalAction.LABEL), 'Terminal: Focus Previous Terminal', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(TerminalPasteAction, TerminalPasteAction.ID, TerminalPasteAction.LABEL, { - primary: KeyMod.CtrlCmd | KeyCode.KEY_V, - linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_V } -}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Paste into Active Terminal', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(SelectAllTerminalAction, SelectAllTerminalAction.ID, SelectAllTerminalAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(CreateNewInActiveWorkspaceTerminalAction, CreateNewInActiveWorkspaceTerminalAction.ID, CreateNewInActiveWorkspaceTerminalAction.LABEL), 'Terminal: Create New Integrated Terminal (In Active Workspace)', category); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(FocusActiveTerminalAction, FocusActiveTerminalAction.ID, FocusActiveTerminalAction.LABEL), 'Terminal: Focus Terminal', category); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(FocusNextTerminalAction, FocusNextTerminalAction.ID, FocusNextTerminalAction.LABEL), 'Terminal: Focus Next Terminal', category); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(FocusPreviousTerminalAction, FocusPreviousTerminalAction.ID, FocusPreviousTerminalAction.LABEL), 'Terminal: Focus Previous Terminal', category); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(SelectAllTerminalAction, SelectAllTerminalAction.ID, SelectAllTerminalAction.LABEL, { // Don't use ctrl+a by default as that would override the common go to start // of prompt shell binding primary: 0, @@ -373,82 +372,82 @@ actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(SelectAllTermina // makes it easier for users to see how it works though. mac: { primary: KeyMod.CtrlCmd | KeyCode.KEY_A } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Select All', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(RunSelectedTextInTerminalAction, RunSelectedTextInTerminalAction.ID, RunSelectedTextInTerminalAction.LABEL), 'Terminal: Run Selected Text In Active Terminal', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(RunActiveFileInTerminalAction, RunActiveFileInTerminalAction.ID, RunActiveFileInTerminalAction.LABEL), 'Terminal: Run Active File In Active Terminal', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ToggleTerminalAction, ToggleTerminalAction.ID, ToggleTerminalAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(RunSelectedTextInTerminalAction, RunSelectedTextInTerminalAction.ID, RunSelectedTextInTerminalAction.LABEL), 'Terminal: Run Selected Text In Active Terminal', category); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(RunActiveFileInTerminalAction, RunActiveFileInTerminalAction.ID, RunActiveFileInTerminalAction.LABEL), 'Terminal: Run Active File In Active Terminal', category); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleTerminalAction, ToggleTerminalAction.ID, ToggleTerminalAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.US_BACKTICK, mac: { primary: KeyMod.WinCtrl | KeyCode.US_BACKTICK } }), 'View: Toggle Integrated Terminal', nls.localize('viewCategory', "View")); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ScrollDownTerminalAction, ScrollDownTerminalAction.ID, ScrollDownTerminalAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ScrollDownTerminalAction, ScrollDownTerminalAction.ID, ScrollDownTerminalAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.PageDown, linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.DownArrow } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Scroll Down (Line)', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ScrollDownPageTerminalAction, ScrollDownPageTerminalAction.ID, ScrollDownPageTerminalAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ScrollDownPageTerminalAction, ScrollDownPageTerminalAction.ID, ScrollDownPageTerminalAction.LABEL, { primary: KeyMod.Shift | KeyCode.PageDown, mac: { primary: KeyCode.PageDown } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Scroll Down (Page)', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ScrollToBottomTerminalAction, ScrollToBottomTerminalAction.ID, ScrollToBottomTerminalAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ScrollToBottomTerminalAction, ScrollToBottomTerminalAction.ID, ScrollToBottomTerminalAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.End, linux: { primary: KeyMod.Shift | KeyCode.End } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Scroll to Bottom', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ScrollUpTerminalAction, ScrollUpTerminalAction.ID, ScrollUpTerminalAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ScrollUpTerminalAction, ScrollUpTerminalAction.ID, ScrollUpTerminalAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.PageUp, linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.UpArrow }, }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Scroll Up (Line)', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ScrollUpPageTerminalAction, ScrollUpPageTerminalAction.ID, ScrollUpPageTerminalAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ScrollUpPageTerminalAction, ScrollUpPageTerminalAction.ID, ScrollUpPageTerminalAction.LABEL, { primary: KeyMod.Shift | KeyCode.PageUp, mac: { primary: KeyCode.PageUp } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Scroll Up (Page)', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ScrollToTopTerminalAction, ScrollToTopTerminalAction.ID, ScrollToTopTerminalAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ScrollToTopTerminalAction, ScrollToTopTerminalAction.ID, ScrollToTopTerminalAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.Home, linux: { primary: KeyMod.Shift | KeyCode.Home } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Scroll to Top', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ClearTerminalAction, ClearTerminalAction.ID, ClearTerminalAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ClearTerminalAction, ClearTerminalAction.ID, ClearTerminalAction.LABEL, { primary: 0, mac: { primary: KeyMod.CtrlCmd | KeyCode.KEY_K } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KeybindingWeight.WorkbenchContrib + 1), 'Terminal: Clear', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(SelectDefaultShellWindowsTerminalAction, SelectDefaultShellWindowsTerminalAction.ID, SelectDefaultShellWindowsTerminalAction.LABEL), 'Terminal: Select Default Shell', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ManageWorkspaceShellPermissionsTerminalCommand, ManageWorkspaceShellPermissionsTerminalCommand.ID, ManageWorkspaceShellPermissionsTerminalCommand.LABEL), 'Terminal: Manage Workspace Shell Permissions', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(RenameTerminalAction, RenameTerminalAction.ID, RenameTerminalAction.LABEL), 'Terminal: Rename', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FocusTerminalFindWidgetAction, FocusTerminalFindWidgetAction.ID, FocusTerminalFindWidgetAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(SelectDefaultShellWindowsTerminalAction, SelectDefaultShellWindowsTerminalAction.ID, SelectDefaultShellWindowsTerminalAction.LABEL), 'Terminal: Select Default Shell', category); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ManageWorkspaceShellPermissionsTerminalCommand, ManageWorkspaceShellPermissionsTerminalCommand.ID, ManageWorkspaceShellPermissionsTerminalCommand.LABEL), 'Terminal: Manage Workspace Shell Permissions', category); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(RenameTerminalAction, RenameTerminalAction.ID, RenameTerminalAction.LABEL), 'Terminal: Rename', category); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(FocusTerminalFindWidgetAction, FocusTerminalFindWidgetAction.ID, FocusTerminalFindWidgetAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_F }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Focus Find Widget', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FocusTerminalFindWidgetAction, FocusTerminalFindWidgetAction.ID, FocusTerminalFindWidgetAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(FocusTerminalFindWidgetAction, FocusTerminalFindWidgetAction.ID, FocusTerminalFindWidgetAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_F }, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED), 'Terminal: Focus Find Widget', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(HideTerminalFindWidgetAction, HideTerminalFindWidgetAction.ID, HideTerminalFindWidgetAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(HideTerminalFindWidgetAction, HideTerminalFindWidgetAction.ID, HideTerminalFindWidgetAction.LABEL, { primary: KeyCode.Escape, secondary: [KeyMod.Shift | KeyCode.Escape] }, ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_VISIBLE)), 'Terminal: Hide Find Widget', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(DeleteWordLeftTerminalAction, DeleteWordLeftTerminalAction.ID, DeleteWordLeftTerminalAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(DeleteWordLeftTerminalAction, DeleteWordLeftTerminalAction.ID, DeleteWordLeftTerminalAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.Backspace, mac: { primary: KeyMod.Alt | KeyCode.Backspace } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Delete Word Left', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(DeleteWordRightTerminalAction, DeleteWordRightTerminalAction.ID, DeleteWordRightTerminalAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(DeleteWordRightTerminalAction, DeleteWordRightTerminalAction.ID, DeleteWordRightTerminalAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.Delete, mac: { primary: KeyMod.Alt | KeyCode.Delete } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Delete Word Right', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(DeleteToLineStartTerminalAction, DeleteToLineStartTerminalAction.ID, DeleteToLineStartTerminalAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(DeleteToLineStartTerminalAction, DeleteToLineStartTerminalAction.ID, DeleteToLineStartTerminalAction.LABEL, { primary: 0, mac: { primary: KeyMod.CtrlCmd | KeyCode.Backspace } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Delete To Line Start', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(MoveToLineStartTerminalAction, MoveToLineStartTerminalAction.ID, MoveToLineStartTerminalAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(MoveToLineStartTerminalAction, MoveToLineStartTerminalAction.ID, MoveToLineStartTerminalAction.LABEL, { primary: 0, mac: { primary: KeyMod.CtrlCmd | KeyCode.LeftArrow } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Move To Line Start', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(MoveToLineEndTerminalAction, MoveToLineEndTerminalAction.ID, MoveToLineEndTerminalAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(MoveToLineEndTerminalAction, MoveToLineEndTerminalAction.ID, MoveToLineEndTerminalAction.LABEL, { primary: 0, mac: { primary: KeyMod.CtrlCmd | KeyCode.RightArrow } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Move To Line End', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(SplitTerminalAction, SplitTerminalAction.ID, SplitTerminalAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(SplitTerminalAction, SplitTerminalAction.ID, SplitTerminalAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_5, mac: { primary: KeyMod.CtrlCmd | KeyCode.US_BACKSLASH, secondary: [KeyMod.WinCtrl | KeyMod.Shift | KeyCode.KEY_5] } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Split Terminal', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(SplitInActiveWorkspaceTerminalAction, SplitInActiveWorkspaceTerminalAction.ID, SplitInActiveWorkspaceTerminalAction.LABEL), 'Terminal: Split Terminal (In Active Workspace)', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FocusPreviousPaneTerminalAction, FocusPreviousPaneTerminalAction.ID, FocusPreviousPaneTerminalAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(SplitInActiveWorkspaceTerminalAction, SplitInActiveWorkspaceTerminalAction.ID, SplitInActiveWorkspaceTerminalAction.LABEL), 'Terminal: Split Terminal (In Active Workspace)', category); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(FocusPreviousPaneTerminalAction, FocusPreviousPaneTerminalAction.ID, FocusPreviousPaneTerminalAction.LABEL, { primary: KeyMod.Alt | KeyCode.LeftArrow, secondary: [KeyMod.Alt | KeyCode.UpArrow], mac: { @@ -456,7 +455,7 @@ actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FocusPreviousPan secondary: [KeyMod.Alt | KeyMod.CtrlCmd | KeyCode.UpArrow] } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Focus Previous Pane', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FocusNextPaneTerminalAction, FocusNextPaneTerminalAction.ID, FocusNextPaneTerminalAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(FocusNextPaneTerminalAction, FocusNextPaneTerminalAction.ID, FocusNextPaneTerminalAction.LABEL, { primary: KeyMod.Alt | KeyCode.RightArrow, secondary: [KeyMod.Alt | KeyCode.DownArrow], mac: { @@ -464,101 +463,116 @@ actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FocusNextPaneTer secondary: [KeyMod.Alt | KeyMod.CtrlCmd | KeyCode.DownArrow] } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Focus Next Pane', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ResizePaneLeftTerminalAction, ResizePaneLeftTerminalAction.ID, ResizePaneLeftTerminalAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ResizePaneLeftTerminalAction, ResizePaneLeftTerminalAction.ID, ResizePaneLeftTerminalAction.LABEL, { primary: 0, linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.LeftArrow }, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.LeftArrow } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Resize Pane Left', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ResizePaneRightTerminalAction, ResizePaneRightTerminalAction.ID, ResizePaneRightTerminalAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ResizePaneRightTerminalAction, ResizePaneRightTerminalAction.ID, ResizePaneRightTerminalAction.LABEL, { primary: 0, linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.RightArrow }, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.RightArrow } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Resize Pane Right', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ResizePaneUpTerminalAction, ResizePaneUpTerminalAction.ID, ResizePaneUpTerminalAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ResizePaneUpTerminalAction, ResizePaneUpTerminalAction.ID, ResizePaneUpTerminalAction.LABEL, { primary: 0, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.UpArrow } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Resize Pane Up', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ResizePaneDownTerminalAction, ResizePaneDownTerminalAction.ID, ResizePaneDownTerminalAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ResizePaneDownTerminalAction, ResizePaneDownTerminalAction.ID, ResizePaneDownTerminalAction.LABEL, { primary: 0, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.DownArrow } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Resize Pane Down', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ScrollToPreviousCommandAction, ScrollToPreviousCommandAction.ID, ScrollToPreviousCommandAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ScrollToPreviousCommandAction, ScrollToPreviousCommandAction.ID, ScrollToPreviousCommandAction.LABEL, { primary: 0, mac: { primary: KeyMod.CtrlCmd | KeyCode.UpArrow } }, ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_FOCUS, CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate())), 'Terminal: Scroll To Previous Command', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ScrollToNextCommandAction, ScrollToNextCommandAction.ID, ScrollToNextCommandAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ScrollToNextCommandAction, ScrollToNextCommandAction.ID, ScrollToNextCommandAction.LABEL, { primary: 0, mac: { primary: KeyMod.CtrlCmd | KeyCode.DownArrow } }, ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_FOCUS, CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate())), 'Terminal: Scroll To Next Command', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(SelectToPreviousCommandAction, SelectToPreviousCommandAction.ID, SelectToPreviousCommandAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(SelectToPreviousCommandAction, SelectToPreviousCommandAction.ID, SelectToPreviousCommandAction.LABEL, { primary: 0, mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.UpArrow } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Select To Previous Command', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(SelectToNextCommandAction, SelectToNextCommandAction.ID, SelectToNextCommandAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(SelectToNextCommandAction, SelectToNextCommandAction.ID, SelectToNextCommandAction.LABEL, { primary: 0, mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.DownArrow } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Select To Next Command', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(NavigationModeExitTerminalAction, NavigationModeExitTerminalAction.ID, NavigationModeExitTerminalAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(NavigationModeExitTerminalAction, NavigationModeExitTerminalAction.ID, NavigationModeExitTerminalAction.LABEL, { primary: KeyCode.Escape }, ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, CONTEXT_ACCESSIBILITY_MODE_ENABLED)), 'Terminal: Exit Navigation Mode', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(NavigationModeFocusPreviousTerminalAction, NavigationModeFocusPreviousTerminalAction.ID, NavigationModeFocusPreviousTerminalAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(NavigationModeFocusPreviousTerminalAction, NavigationModeFocusPreviousTerminalAction.ID, NavigationModeFocusPreviousTerminalAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.UpArrow }, ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_FOCUS, CONTEXT_ACCESSIBILITY_MODE_ENABLED)), 'Terminal: Focus Previous Line (Navigation Mode)', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(NavigationModeFocusPreviousTerminalAction, NavigationModeFocusPreviousTerminalAction.ID, NavigationModeFocusPreviousTerminalAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(NavigationModeFocusPreviousTerminalAction, NavigationModeFocusPreviousTerminalAction.ID, NavigationModeFocusPreviousTerminalAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.UpArrow }, ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, CONTEXT_ACCESSIBILITY_MODE_ENABLED)), 'Terminal: Focus Previous Line (Navigation Mode)', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(NavigationModeFocusNextTerminalAction, NavigationModeFocusNextTerminalAction.ID, NavigationModeFocusNextTerminalAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(NavigationModeFocusNextTerminalAction, NavigationModeFocusNextTerminalAction.ID, NavigationModeFocusNextTerminalAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.DownArrow }, ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_FOCUS, CONTEXT_ACCESSIBILITY_MODE_ENABLED)), 'Terminal: Focus Next Line (Navigation Mode)', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(NavigationModeFocusNextTerminalAction, NavigationModeFocusNextTerminalAction.ID, NavigationModeFocusNextTerminalAction.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(NavigationModeFocusNextTerminalAction, NavigationModeFocusNextTerminalAction.ID, NavigationModeFocusNextTerminalAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.DownArrow }, ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, CONTEXT_ACCESSIBILITY_MODE_ENABLED)), 'Terminal: Focus Next Line (Navigation Mode)', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(SelectToPreviousLineAction, SelectToPreviousLineAction.ID, SelectToPreviousLineAction.LABEL), 'Terminal: Select To Previous Line', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(SelectToNextLineAction, SelectToNextLineAction.ID, SelectToNextLineAction.LABEL), 'Terminal: Select To Next Line', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ToggleEscapeSequenceLoggingAction, ToggleEscapeSequenceLoggingAction.ID, ToggleEscapeSequenceLoggingAction.LABEL), 'Terminal: Toggle Escape Sequence Logging', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ToggleRegexCommand, ToggleRegexCommand.ID, ToggleRegexCommand.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(SelectToPreviousLineAction, SelectToPreviousLineAction.ID, SelectToPreviousLineAction.LABEL), 'Terminal: Select To Previous Line', category); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(SelectToNextLineAction, SelectToNextLineAction.ID, SelectToNextLineAction.LABEL), 'Terminal: Select To Next Line', category); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleEscapeSequenceLoggingAction, ToggleEscapeSequenceLoggingAction.ID, ToggleEscapeSequenceLoggingAction.LABEL), 'Terminal: Toggle Escape Sequence Logging', category); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleRegexCommand, ToggleRegexCommand.ID, ToggleRegexCommand.LABEL, { primary: KeyMod.Alt | KeyCode.KEY_R, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_R } -}, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED), 'Terminal: Toggle find using regex'); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ToggleRegexCommand, ToggleRegexCommand.ID_TERMINAL_FOCUS, ToggleRegexCommand.LABEL, { +}, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED), 'Terminal: Toggle find using regex', category); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleRegexCommand, ToggleRegexCommand.ID, ToggleRegexCommand.LABEL, { primary: KeyMod.Alt | KeyCode.KEY_R, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_R } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Toggle find using regex', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ToggleWholeWordCommand, ToggleWholeWordCommand.ID, ToggleWholeWordCommand.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleWholeWordCommand, ToggleWholeWordCommand.ID, ToggleWholeWordCommand.LABEL, { primary: KeyMod.Alt | KeyCode.KEY_W, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_W } -}, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED), 'Terminal: Toggle find using whole word'); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ToggleWholeWordCommand, ToggleWholeWordCommand.ID_TERMINAL_FOCUS, ToggleWholeWordCommand.LABEL, { +}, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED), 'Terminal: Toggle find using whole word', category); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleWholeWordCommand, ToggleWholeWordCommand.ID, ToggleWholeWordCommand.LABEL, { primary: KeyMod.Alt | KeyCode.KEY_W, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_W } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Toggle find using whole word', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ToggleCaseSensitiveCommand, ToggleCaseSensitiveCommand.ID, ToggleCaseSensitiveCommand.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleCaseSensitiveCommand, ToggleCaseSensitiveCommand.ID, ToggleCaseSensitiveCommand.LABEL, { primary: KeyMod.Alt | KeyCode.KEY_C, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_C } -}, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED), 'Terminal: Toggle find using case sensitive'); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ToggleCaseSensitiveCommand, ToggleCaseSensitiveCommand.ID_TERMINAL_FOCUS, ToggleCaseSensitiveCommand.LABEL, { +}, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED), 'Terminal: Toggle find using case sensitive', category); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleCaseSensitiveCommand, ToggleCaseSensitiveCommand.ID, ToggleCaseSensitiveCommand.LABEL, { primary: KeyMod.Alt | KeyCode.KEY_C, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_C } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Toggle find using case sensitive', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FindNext, FindNext.ID_TERMINAL_FOCUS, FindNext.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(FindNext, FindNext.ID, FindNext.LABEL, { primary: KeyCode.F3, mac: { primary: KeyMod.CtrlCmd | KeyCode.KEY_G, secondary: [KeyCode.F3] } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Find next', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FindNext, FindNext.ID, FindNext.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(FindNext, FindNext.ID, FindNext.LABEL, { primary: KeyCode.F3, secondary: [KeyMod.Shift | KeyCode.Enter], mac: { primary: KeyMod.CtrlCmd | KeyCode.KEY_G, secondary: [KeyCode.F3, KeyMod.Shift | KeyCode.Enter] } -}, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED), 'Terminal: Find next'); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FindPrevious, FindPrevious.ID_TERMINAL_FOCUS, FindPrevious.LABEL, { +}, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED), 'Terminal: Find next', category); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(FindPrevious, FindPrevious.ID, FindPrevious.LABEL, { primary: KeyMod.Shift | KeyCode.F3, mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_G, secondary: [KeyMod.Shift | KeyCode.F3] }, }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Find previous', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FindPrevious, FindPrevious.ID, FindPrevious.LABEL, { +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(FindPrevious, FindPrevious.ID, FindPrevious.LABEL, { primary: KeyMod.Shift | KeyCode.F3, secondary: [KeyCode.Enter], mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_G, secondary: [KeyMod.Shift | KeyCode.F3, KeyCode.Enter] }, -}, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED), 'Terminal: Find previous'); +}, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED), 'Terminal: Find previous', category); +// Commands might be affected by Web restrictons +if (BrowserFeatures.clipboard.writeText) { + actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(CopyTerminalSelectionAction, CopyTerminalSelectionAction.ID, CopyTerminalSelectionAction.LABEL, { + primary: KeyMod.CtrlCmd | KeyCode.KEY_C, + win: { primary: KeyMod.CtrlCmd | KeyCode.KEY_C, secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_C] }, + linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_C } + }, ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, KEYBINDING_CONTEXT_TERMINAL_FOCUS)), 'Terminal: Copy Selection', category); +} +if (BrowserFeatures.clipboard.readText) { + actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(TerminalPasteAction, TerminalPasteAction.ID, TerminalPasteAction.LABEL, { + primary: KeyMod.CtrlCmd | KeyCode.KEY_V, + win: { primary: KeyMod.CtrlCmd | KeyCode.KEY_V, secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_V] }, + linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_V } + }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Paste into Active Terminal', category); +} (new SendSequenceTerminalCommand({ id: SendSequenceTerminalCommand.ID, precondition: undefined, @@ -576,6 +590,7 @@ actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FindPrevious, Fi }] } })).register(); + (new CreateNewWithCwdTerminalCommand({ id: CreateNewWithCwdTerminalCommand.ID, precondition: undefined, @@ -597,6 +612,28 @@ actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FindPrevious, Fi } })).register(); +(new RenameWithArgTerminalCommand({ + id: RenameWithArgTerminalCommand.ID, + precondition: undefined, + description: { + description: RenameWithArgTerminalCommand.LABEL, + args: [{ + name: 'args', + schema: { + type: 'object', + required: ['name'], + properties: { + name: { + description: RenameWithArgTerminalCommand.NAME_ARG_LABEL, + type: 'string', + minLength: 1 + } + } + } + }] + } +})).register(); + setupTerminalCommands(); setupTerminalMenu(); diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index 1530c7f0b8ee..02b13b1e9670 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -6,6 +6,7 @@ import { Terminal as XTermTerminal } from 'xterm'; import { WebLinksAddon as XTermWebLinksAddon } from 'xterm-addon-web-links'; import { SearchAddon as XTermSearchAddon } from 'xterm-addon-search'; +import { WebglAddon as XTermWebglAddon } from 'xterm-addon-webgl'; import { IWindowsShellHelper, ITerminalConfigHelper, ITerminalChildProcess, IShellLaunchConfig, IDefaultShellAndArgsRequest, ISpawnExtHostProcessRequest, IStartExtensionTerminalRequest, IAvailableShellsRequest, ITerminalProcessExtHostProxy, ICommandTracker, INavigationMode, TitleEventSource, ITerminalDimensions } from 'vs/workbench/contrib/terminal/common/terminal'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IProcessEnvironment, Platform } from 'vs/base/common/platform'; @@ -31,6 +32,7 @@ export interface ITerminalInstanceService { getXtermConstructor(): Promise; getXtermWebLinksConstructor(): Promise; getXtermSearchConstructor(): Promise; + getXtermWebglConstructor(): Promise; createWindowsShellHelper(shellProcessId: number, instance: ITerminalInstance, xterm: XTermTerminal): IWindowsShellHelper; createTerminalProcess(shellLaunchConfig: IShellLaunchConfig, cwd: string, cols: number, rows: number, env: IProcessEnvironment, windowsEnableConpty: boolean): ITerminalChildProcess; @@ -141,7 +143,7 @@ export interface ITerminalService { * @param path The path to be escaped and formatted. * @returns An escaped version of the path to be execuded in the terminal. */ - preparePathForTerminalAsync(path: string, executable: string | undefined, title: string): Promise; + preparePathForTerminalAsync(path: string, executable: string | undefined, title: string, shellType: TerminalShellType): Promise; extHostReady(remoteAuthority: string): void; requestSpawnExtHostProcess(proxy: ITerminalProcessExtHostProxy, shellLaunchConfig: IShellLaunchConfig, activeWorkspaceRootUri: URI | undefined, cols: number, rows: number, isWorkspaceShellAllowed: boolean): void; @@ -167,6 +169,14 @@ export interface ISearchOptions { incremental?: boolean; } +export enum WindowsShellType { + CommandPrompt, + PowerShell, + Wsl, + GitBash +} +export type TerminalShellType = WindowsShellType | undefined; + export interface ITerminalInstance { /** * The ID of the terminal instance, this is an arbitrary number only used to identify the @@ -228,6 +238,8 @@ export interface ITerminalInstance { */ onExit: Event; + readonly exitCode: number | undefined; + processReady: Promise; /** @@ -236,6 +248,11 @@ export interface ITerminalInstance { */ readonly title: string; + /** + * The shell type of the terminal. + */ + readonly shellType: TerminalShellType; + /** * The focus state of the terminal before exiting. */ @@ -431,6 +448,11 @@ export interface ITerminalInstance { */ setTitle(title: string, eventSource: TitleEventSource): void; + /** + * Sets the shell type of the terminal instance. + */ + setShellType(shellType: TerminalShellType): void; + waitForTitle(): Promise; setDimensions(dimensions: ITerminalDimensions): void; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 7a71e2c2c54f..8b6044bedbc5 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -305,17 +305,7 @@ export class CreateNewWithCwdTerminalCommand extends Command { public runCommand(accessor: ServicesAccessor, args: { cwd: string } | undefined): Promise { const terminalService = accessor.get(ITerminalService); - const configurationResolverService = accessor.get(IConfigurationResolverService); - const workspaceContextService = accessor.get(IWorkspaceContextService); - const historyService = accessor.get(IHistoryService); - const activeWorkspaceRootUri = historyService.getLastActiveWorkspaceRoot(Schemas.file); - const lastActiveWorkspaceRoot = activeWorkspaceRootUri ? withNullAsUndefined(workspaceContextService.getWorkspaceFolder(activeWorkspaceRootUri)) : undefined; - - let cwd: string | undefined; - if (args && args.cwd) { - cwd = configurationResolverService.resolve(lastActiveWorkspaceRoot, args.cwd); - } - const instance = terminalService.createTerminal({ cwd }); + const instance = terminalService.createTerminal({ cwd: args?.cwd }); if (!instance) { return Promise.resolve(undefined); } @@ -722,7 +712,7 @@ export class RunActiveFileInTerminalAction extends Action { return Promise.resolve(undefined); } - return this.terminalService.preparePathForTerminalAsync(uri.fsPath, instance.shellLaunchConfig.executable, instance.title).then(path => { + return this.terminalService.preparePathForTerminalAsync(uri.fsPath, instance.shellLaunchConfig.executable, instance.title, instance.shellType).then(path => { instance.sendText(path, true); return this.terminalService.showPanel(); }); @@ -1065,6 +1055,31 @@ export class RenameTerminalAction extends Action { }); } } +export class RenameWithArgTerminalCommand extends Command { + public static readonly ID = TERMINAL_COMMAND_ID.RENAME_WITH_ARG; + public static readonly LABEL = nls.localize('workbench.action.terminal.renameWithArg', "Rename the Currently Active Terminal"); + public static readonly NAME_ARG_LABEL = nls.localize('workbench.action.terminal.renameWithArg.name', "The new name for the terminal"); + + public runCommand( + accessor: ServicesAccessor, + args?: { name?: string } + ): void { + const notificationService = accessor.get(INotificationService); + const terminalInstance = accessor.get(ITerminalService).getActiveInstance(); + + if (!terminalInstance) { + notificationService.warn(nls.localize('workbench.action.terminal.renameWithArg.noTerminal', "No active terminal to rename")); + return; + } + + if (!args || !args.name) { + notificationService.warn(nls.localize('workbench.action.terminal.renameWithArg.noName', "No name argument provided")); + return; + } + + terminalInstance.setTitle(args.name, TitleEventSource.Api); + } +} export class FocusTerminalFindWidgetAction extends Action { @@ -1328,7 +1343,6 @@ abstract class ToggleFindOptionCommand extends Action { export class ToggleRegexCommand extends ToggleFindOptionCommand { public static readonly ID = TERMINAL_COMMAND_ID.TOGGLE_FIND_REGEX; - public static readonly ID_TERMINAL_FOCUS = TERMINAL_COMMAND_ID.TOGGLE_FIND_REGEX_TERMINAL_FOCUS; public static readonly LABEL = nls.localize('workbench.action.terminal.toggleFindRegex', "Toggle find using regex"); protected runInner(state: FindReplaceState): void { @@ -1338,7 +1352,6 @@ export class ToggleRegexCommand extends ToggleFindOptionCommand { export class ToggleWholeWordCommand extends ToggleFindOptionCommand { public static readonly ID = TERMINAL_COMMAND_ID.TOGGLE_FIND_WHOLE_WORD; - public static readonly ID_TERMINAL_FOCUS = TERMINAL_COMMAND_ID.TOGGLE_FIND_WHOLE_WORD_TERMINAL_FOCUS; public static readonly LABEL = nls.localize('workbench.action.terminal.toggleFindWholeWord', "Toggle find using whole word"); protected runInner(state: FindReplaceState): void { @@ -1348,7 +1361,6 @@ export class ToggleWholeWordCommand extends ToggleFindOptionCommand { export class ToggleCaseSensitiveCommand extends ToggleFindOptionCommand { public static readonly ID = TERMINAL_COMMAND_ID.TOGGLE_FIND_CASE_SENSITIVE; - public static readonly ID_TERMINAL_FOCUS = TERMINAL_COMMAND_ID.TOGGLE_FIND_CASE_SENSITIVE_TERMINAL_FOCUS; public static readonly LABEL = nls.localize('workbench.action.terminal.toggleFindCaseSensitive', "Toggle find using case sensitive"); protected runInner(state: FindReplaceState): void { @@ -1358,7 +1370,6 @@ export class ToggleCaseSensitiveCommand extends ToggleFindOptionCommand { export class FindNext extends Action { public static readonly ID = TERMINAL_COMMAND_ID.FIND_NEXT; - public static readonly ID_TERMINAL_FOCUS = TERMINAL_COMMAND_ID.FIND_NEXT_TERMINAL_FOCUS; public static readonly LABEL = nls.localize('workbench.action.terminal.findNext', "Find next"); constructor( @@ -1376,7 +1387,6 @@ export class FindNext extends Action { export class FindPrevious extends Action { public static readonly ID = TERMINAL_COMMAND_ID.FIND_PREVIOUS; - public static readonly ID_TERMINAL_FOCUS = TERMINAL_COMMAND_ID.FIND_PREVIOUS_TERMINAL_FOCUS; public static readonly LABEL = nls.localize('workbench.action.terminal.findPrevious', "Find previous"); constructor( diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index a2d7d5d52e73..d0634ac8e0ee 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -31,7 +31,7 @@ import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/term import { TerminalLinkHandler } from 'vs/workbench/contrib/terminal/browser/terminalLinkHandler'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; -import { ITerminalInstanceService, ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { ITerminalInstanceService, ITerminalInstance, TerminalShellType } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalProcessManager } from 'vs/workbench/contrib/terminal/browser/terminalProcessManager'; import { Terminal as XTermTerminal, IBuffer, ITerminalAddon } from 'xterm'; import { SearchAddon, ISearchOptions } from 'xterm-addon-search'; @@ -54,11 +54,11 @@ export const DEFAULT_COMMANDS_TO_SKIP_SHELL: string[] = [ TERMINAL_COMMAND_ID.DELETE_WORD_RIGHT, TERMINAL_COMMAND_ID.FIND_WIDGET_FOCUS, TERMINAL_COMMAND_ID.FIND_WIDGET_HIDE, - TERMINAL_COMMAND_ID.FIND_NEXT_TERMINAL_FOCUS, - TERMINAL_COMMAND_ID.FIND_PREVIOUS_TERMINAL_FOCUS, - TERMINAL_COMMAND_ID.TOGGLE_FIND_REGEX_TERMINAL_FOCUS, - TERMINAL_COMMAND_ID.TOGGLE_FIND_WHOLE_WORD_TERMINAL_FOCUS, - TERMINAL_COMMAND_ID.TOGGLE_FIND_CASE_SENSITIVE_TERMINAL_FOCUS, + TERMINAL_COMMAND_ID.FIND_NEXT, + TERMINAL_COMMAND_ID.FIND_PREVIOUS, + TERMINAL_COMMAND_ID.TOGGLE_FIND_REGEX, + TERMINAL_COMMAND_ID.TOGGLE_FIND_WHOLE_WORD, + TERMINAL_COMMAND_ID.TOGGLE_FIND_CASE_SENSITIVE, TERMINAL_COMMAND_ID.FOCUS_NEXT_PANE, TERMINAL_COMMAND_ID.FOCUS_NEXT, TERMINAL_COMMAND_ID.FOCUS_PREVIOUS_PANE, @@ -181,7 +181,9 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { private _hadFocusOnExit: boolean; private _isVisible: boolean; private _isDisposed: boolean; + private _exitCode: number | undefined; private _skipTerminalCommands: string[]; + private _shellType: TerminalShellType; private _title: string = ''; private _wrapperElement: (HTMLElement & { xterm?: XTermTerminal }) | undefined; private _xterm: XTermTerminal | undefined; @@ -226,10 +228,12 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { // TODO: How does this work with detached processes? // TODO: Should this be an event as it can fire twice? public get processReady(): Promise { return this._processManager.ptyProcessReady; } + public get exitCode(): number | undefined { return this._exitCode; } public get title(): string { return this._title; } public get hadFocusOnExit(): boolean { return this._hadFocusOnExit; } public get isTitleSetByProcess(): boolean { return !!this._messageTitleDisposable; } public get shellLaunchConfig(): IShellLaunchConfig { return this._shellLaunchConfig; } + public get shellType(): TerminalShellType { return this._shellType; } public get commandTracker(): CommandTrackerAddon | undefined { return this._commandTrackerAddon; } public get navigationMode(): INavigationMode | undefined { return this._navigationModeAddon; } @@ -305,7 +309,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { }); this.addDisposable(this._configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration('terminal.integrated')) { + if (e.affectsConfiguration('terminal.integrated') || e.affectsConfiguration('editor.fastScrollSensitivity') || e.affectsConfiguration('editor.mouseWheelScrollSensitivity')) { this.updateConfig(); // HACK: Trigger another async layout to ensure xterm's CharMeasure is ready to use, // this hack can be removed when https://github.com/xtermjs/xterm.js/issues/702 is @@ -365,12 +369,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { // when window.devicePixelRatio changes. const scaledWidthAvailable = dimension.width * window.devicePixelRatio; - let scaledCharWidth: number; - if (this._configHelper.config.rendererType === 'dom') { - scaledCharWidth = font.charWidth * window.devicePixelRatio; - } else { - scaledCharWidth = Math.floor(font.charWidth * window.devicePixelRatio) + font.letterSpacing; - } + const scaledCharWidth = font.charWidth * window.devicePixelRatio + font.letterSpacing; const newCols = Math.max(Math.floor(scaledWidthAvailable / scaledCharWidth), 1); const scaledHeightAvailable = dimension.height * window.devicePixelRatio; @@ -421,7 +420,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { const bottom = parseInt(wrapperElementStyle.bottom!.split('px')[0], 10); const innerWidth = width - marginLeft - marginRight; - const innerHeight = height - bottom; + const innerHeight = height - bottom - 1; TerminalInstance._lastKnownCanvasDimensions = new dom.Dimension(innerWidth, innerHeight); return TerminalInstance._lastKnownCanvasDimensions; @@ -448,7 +447,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { const Terminal = await this._getXtermConstructor(); const font = this._configHelper.getFont(undefined, true); const config = this._configHelper.config; - const fastScrollSensitivity = this._configurationService.getValue('editor.fastScrollSensitivity').fastScrollSensitivity; + const editorOptions = this._configurationService.getValue('editor'); + const xterm = new Terminal({ scrollback: config.scrollback, theme: this._getXtermTheme(), @@ -459,14 +459,16 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { fontSize: font.fontSize, letterSpacing: font.letterSpacing, lineHeight: font.lineHeight, + minimumContrastRatio: config.minimumContrastRatio, bellStyle: config.enableBell ? 'sound' : 'none', macOptionIsMeta: config.macOptionIsMeta, macOptionClickForcesSelection: config.macOptionClickForcesSelection, rightClickSelectsWord: config.rightClickBehavior === 'selectWord', fastScrollModifier: 'alt', - fastScrollSensitivity, - // TODO: Guess whether to use canvas or dom better - rendererType: config.rendererType === 'auto' ? 'canvas' : config.rendererType + fastScrollSensitivity: editorOptions.fastScrollSensitivity, + scrollSensitivity: editorOptions.mouseWheelScrollSensitivity, + rendererType: config.rendererType === 'auto' || config.rendererType === 'experimentalWebgl' ? 'canvas' : config.rendererType, + wordSeparator: ' ()[]{}\',:;"`' }); this._xterm = xterm; this._xtermCore = (xterm as any)._core as XTermCore; @@ -484,7 +486,6 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._processManager.onProcessData(data => this._onProcessData(data)); this._xterm.onData(data => this._processManager.write(data)); - // TODO: How does the cwd work on detached processes? this.processReady.then(async () => { if (this._linkHandler) { this._linkHandler.processCwd = await this._processManager.getInitialCwd(); @@ -524,9 +525,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { throw new Error('The terminal instance has not been attached to a container yet'); } - if (this._wrapperElement.parentNode) { - this._wrapperElement.parentNode.removeChild(this._wrapperElement); - } + this._wrapperElement.parentNode?.removeChild(this._wrapperElement); this._container = container; this._container.appendChild(this._wrapperElement); } @@ -544,9 +543,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } // The container changed, reattach - if (this._container) { - this._container.removeChild(this._wrapperElement); - } + this._container?.removeChild(this._wrapperElement); this._container = container; this._container.appendChild(this._wrapperElement); } @@ -568,6 +565,11 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._wrapperElement.appendChild(this._xtermElement); this._container.appendChild(this._wrapperElement); xterm.open(this._xtermElement); + if (this._configHelper.config.rendererType === 'experimentalWebgl') { + this._terminalInstanceService.getXtermWebglConstructor().then(Addon => { + xterm.loadAddon(new Addon()); + }); + } if (!xterm.element || !xterm.textarea) { throw new Error('xterm elements not set after open'); @@ -584,7 +586,10 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { // within commandsToSkipShell const standardKeyboardEvent = new StandardKeyboardEvent(event); const resolveResult = this._keybindingService.softDispatch(standardKeyboardEvent, standardKeyboardEvent.target); - const allowChords = resolveResult && resolveResult.enterChord && this._configHelper.config.allowChords; + // Respect chords if the allowChords setting is set and it's not Escape. Escape is + // handled specially for Zen Mode's Escape, Escape chord, plus it's important in + // terminals generally + const allowChords = resolveResult && resolveResult.enterChord && this._configHelper.config.allowChords && event.key !== 'Escape'; if (allowChords || resolveResult && this._skipTerminalCommands.some(k => k === resolveResult.commandId)) { event.preventDefault(); return false; @@ -594,6 +599,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { if (TabFocus.getTabFocusMode() && event.keyCode === 9) { return false; } + // Always have alt+F4 skip the terminal on Windows and allow it to be handled by the // system if (platform.isWindows && event.altKey && event.key === 'F4' && !event.ctrlKey) { @@ -652,11 +658,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { const widgetManager = new TerminalWidgetManager(this._wrapperElement); this._widgetManager = widgetManager; - this._processManager.onProcessReady(() => { - if (this._linkHandler) { - this._linkHandler.setWidgetManager(widgetManager); - } - }); + this._processManager.onProcessReady(() => this._linkHandler?.setWidgetManager(widgetManager)); const computedStyle = window.getComputedStyle(this._container); const width = parseInt(computedStyle.getPropertyValue('width').replace('px', ''), 10); @@ -751,19 +753,13 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } public clearSelection(): void { - if (!this._xterm) { - return; - } - this._xterm.clearSelection(); + this._xterm?.clearSelection(); } public selectAll(): void { - if (!this._xterm) { - return; - } // Focus here to ensure the terminal context key is set - this._xterm.focus(); - this._xterm.selectAll(); + this._xterm?.focus(); + this._xterm?.selectAll(); } public findNext(term: string, searchOptions: ISearchOptions): boolean { @@ -924,45 +920,31 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } public scrollDownLine(): void { - if (this._xterm) { - this._xterm.scrollLines(1); - } + this._xterm?.scrollLines(1); } public scrollDownPage(): void { - if (this._xterm) { - this._xterm.scrollPages(1); - } + this._xterm?.scrollPages(1); } public scrollToBottom(): void { - if (this._xterm) { - this._xterm.scrollToBottom(); - } + this._xterm?.scrollToBottom(); } public scrollUpLine(): void { - if (this._xterm) { - this._xterm.scrollLines(-1); - } + this._xterm?.scrollLines(-1); } public scrollUpPage(): void { - if (this._xterm) { - this._xterm.scrollPages(-1); - } + this._xterm?.scrollPages(-1); } public scrollToTop(): void { - if (this._xterm) { - this._xterm.scrollToTop(); - } + this._xterm?.scrollToTop(); } public clear(): void { - if (this._xterm) { - this._xterm.clear(); - } + this._xterm?.clear(); } private _refreshSelectionContextKey() { @@ -1019,12 +1001,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } private _onProcessData(data: string): void { - if (this._widgetManager) { - this._widgetManager.closeMessage(); - } - if (this._xterm) { - this._xterm.write(data); - } + this._widgetManager?.closeMessage(); + this._xterm?.write(data); } /** @@ -1041,6 +1019,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._logService.debug(`Terminal process exit (id: ${this.id}) with code ${exitCode}`); + this._exitCode = exitCode; this._isExiting = true; let exitCodeMessage: string | undefined; @@ -1130,10 +1109,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { public reuseTerminal(shell: IShellLaunchConfig): void { // Unsubscribe any key listener we may have. - if (this._pressAnyKeyToCloseListener) { - this._pressAnyKeyToCloseListener.dispose(); - this._pressAnyKeyToCloseListener = undefined; - } + this._pressAnyKeyToCloseListener?.dispose(); + this._pressAnyKeyToCloseListener = undefined; // Kill and clear up the process, making the process manager ready for a new process this._processManager.dispose(); @@ -1243,11 +1220,18 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._setCommandsToSkipShell(config.commandsToSkipShell); this._setEnableBell(config.enableBell); this._safeSetOption('scrollback', config.scrollback); + this._safeSetOption('minimumContrastRatio', config.minimumContrastRatio); this._safeSetOption('macOptionIsMeta', config.macOptionIsMeta); this._safeSetOption('macOptionClickForcesSelection', config.macOptionClickForcesSelection); this._safeSetOption('rightClickSelectsWord', config.rightClickBehavior === 'selectWord'); - this._safeSetOption('rendererType', config.rendererType === 'auto' ? 'canvas' : config.rendererType); - this._safeSetOption('fastScrollSensitivity', this._configurationService.getValue('editor.fastScrollSensitivity').fastScrollSensitivity); + if (config.rendererType !== 'experimentalWebgl') { + // Never set webgl as it's an addon not a rendererType + this._safeSetOption('rendererType', config.rendererType === 'auto' ? 'canvas' : config.rendererType); + } + + const editorOptions = this._configurationService.getValue('editor'); + this._safeSetOption('fastScrollSensitivity', editorOptions.fastScrollSensitivity); + this._safeSetOption('scrollSensitivity', editorOptions.mouseWheelScrollSensitivity); } public updateAccessibilitySupport(): void { @@ -1256,10 +1240,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._navigationModeAddon = new NavigationModeAddon(this._terminalA11yTreeFocusContextKey); this._xterm!.loadAddon(this._navigationModeAddon); } else { - if (this._navigationModeAddon) { - this._navigationModeAddon.dispose(); - this._navigationModeAddon = undefined; - } + this._navigationModeAddon?.dispose(); + this._navigationModeAddon = undefined; } this._xterm!.setOption('screenReaderMode', isEnabled); } @@ -1375,6 +1357,10 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._processManager.ptyProcessReady.then(() => this._processManager.setDimensions(cols, rows)); } + public setShellType(shellType: TerminalShellType) { + this._shellType = shellType; + } + public setTitle(title: string | undefined, eventSource: TitleEventSource): void { if (!title) { return; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts index 0ba736bd578d..1f4b706ba671 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts @@ -8,6 +8,7 @@ import { IWindowsShellHelper, ITerminalChildProcess, IDefaultShellAndArgsRequest import { Terminal as XTermTerminal } from 'xterm'; import { WebLinksAddon as XTermWebLinksAddon } from 'xterm-addon-web-links'; import { SearchAddon as XTermSearchAddon } from 'xterm-addon-search'; +import { WebglAddon as XTermWebglAddon } from 'xterm-addon-webgl'; import { IProcessEnvironment } from 'vs/base/common/platform'; import { Emitter, Event } from 'vs/base/common/event'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -15,6 +16,7 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; let Terminal: typeof XTermTerminal; let WebLinksAddon: typeof XTermWebLinksAddon; let SearchAddon: typeof XTermSearchAddon; +let WebglAddon: typeof XTermWebglAddon; export class TerminalInstanceService implements ITerminalInstanceService { public _serviceBrand: undefined; @@ -43,6 +45,13 @@ export class TerminalInstanceService implements ITerminalInstanceService { return SearchAddon; } + public async getXtermWebglConstructor(): Promise { + if (!WebglAddon) { + WebglAddon = (await import('xterm-addon-webgl')).WebglAddon; + } + return WebglAddon; + } + public createWindowsShellHelper(): IWindowsShellHelper { throw new Error('Not implemented'); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts b/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts index 752cb6c4a095..7dd1d015710e 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts @@ -7,13 +7,13 @@ import * as nls from 'vs/nls'; import { URI } from 'vs/base/common/uri'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/terminalWidgetManager'; +import { TerminalWidgetManager, WidgetVerticalAlignment } from 'vs/workbench/contrib/terminal/browser/terminalWidgetManager'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ITerminalProcessManager, ITerminalConfigHelper } from 'vs/workbench/contrib/terminal/common/terminal'; import { ITextEditorSelection } from 'vs/platform/editor/common/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IFileService } from 'vs/platform/files/common/files'; -import { Terminal, ILinkMatcherOptions } from 'xterm'; +import { Terminal, ILinkMatcherOptions, IViewportRange } from 'xterm'; import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; import { posix, win32 } from 'vs/base/common/path'; import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal'; @@ -71,7 +71,7 @@ export class TerminalLinkHandler { private _processCwd: string | undefined; private _gitDiffPreImagePattern: RegExp; private _gitDiffPostImagePattern: RegExp; - private readonly _tooltipCallback: (event: MouseEvent, uri: string) => boolean | void; + private readonly _tooltipCallback: (event: MouseEvent, uri: string, location: IViewportRange) => boolean | void; private readonly _leaveCallback: () => void; constructor( @@ -89,15 +89,42 @@ export class TerminalLinkHandler { // Matches '+++ b/src/file1', capturing 'src/file1' in group 1 this._gitDiffPostImagePattern = /^\+\+\+ b\/(\S*)/; - this._tooltipCallback = (e: MouseEvent) => { + this._tooltipCallback = (e: MouseEvent, uri: string, location: IViewportRange) => { if (!this._widgetManager) { return; } + + // Get the row bottom up + let offsetRow = this._xterm.rows - location.start.y; + let verticalAlignment = WidgetVerticalAlignment.Bottom; + + // Show the tooltip on the top of the next row to avoid obscuring the first row + if (location.start.y <= 0) { + offsetRow = this._xterm.rows - 1; + verticalAlignment = WidgetVerticalAlignment.Top; + // The start of the wrapped line is above the viewport, move to start of the line + if (location.start.y < 0) { + location.start.x = 0; + } + } + if (this._configHelper.config.rendererType === 'dom') { - const target = (e.target as HTMLElement); - this._widgetManager.showMessage(target.offsetLeft, target.offsetTop, this._getLinkHoverString()); + const font = this._configHelper.getFont(); + const charWidth = font.charWidth; + const charHeight = font.charHeight; + + const leftPosition = location.start.x * (charWidth! + (font.letterSpacing / window.devicePixelRatio)); + const bottomPosition = offsetRow * (Math.ceil(charHeight! * window.devicePixelRatio) * font.lineHeight) / window.devicePixelRatio; + + this._widgetManager.showMessage(leftPosition, bottomPosition, this._getLinkHoverString(), verticalAlignment); } else { - this._widgetManager.showMessage(e.offsetX, e.offsetY, this._getLinkHoverString()); + const target = (e.target as HTMLElement); + const colWidth = target.offsetWidth / this._xterm.cols; + const rowHeight = target.offsetHeight / this._xterm.rows; + + const leftPosition = location.start.x * colWidth; + const bottomPosition = offsetRow * rowHeight; + this._widgetManager.showMessage(leftPosition, bottomPosition, this._getLinkHoverString(), verticalAlignment); } }; this._leaveCallback = () => { @@ -239,8 +266,7 @@ export class TerminalLinkHandler { } private _handleHypertextLink(url: string): void { - const uri = URI.parse(url); - this._openerService.open(uri, { allowTunneling: !!(this._processManager && this._processManager.remoteAuthority) }); + this._openerService.open(url, { allowTunneling: !!(this._processManager && this._processManager.remoteAuthority) }); } private _isLinkActivationModifierDown(event: MouseEvent): boolean { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalPanel.ts b/src/vs/workbench/contrib/terminal/browser/terminalPanel.ts index 6576483fd734..f4046ada5afc 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalPanel.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalPanel.ts @@ -15,7 +15,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { TERMINAL_PANEL_ID } from 'vs/workbench/contrib/terminal/common/terminal'; import { IThemeService, ITheme, registerThemingParticipant, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; import { TerminalFindWidget } from 'vs/workbench/contrib/terminal/browser/terminalFindWidget'; -import { editorHoverBackground, editorHoverBorder, editorForeground } from 'vs/platform/theme/common/colorRegistry'; +import { editorHoverBackground, editorHoverBorder, editorHoverForeground } from 'vs/platform/theme/common/colorRegistry'; import { KillTerminalAction, SwitchTerminalAction, SwitchTerminalActionViewItem, CopyTerminalSelectionAction, TerminalPasteAction, ClearTerminalAction, SelectAllTerminalAction, CreateNewTerminalAction, SplitTerminalAction } from 'vs/workbench/contrib/terminal/browser/terminalActions'; import { Panel } from 'vs/workbench/browser/panel'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; @@ -26,6 +26,7 @@ import { INotificationService, IPromptChoice, Severity } from 'vs/platform/notif import { IStorageService } from 'vs/platform/storage/common/storage'; import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { assertIsDefined } from 'vs/base/common/types'; +import { BrowserFeatures } from 'vs/base/browser/canIUse'; const FIND_FOCUS_CLASS = 'find-focused'; @@ -141,13 +142,22 @@ export class TerminalPanel extends Panel { private _getContextMenuActions(): IAction[] { if (!this._contextMenuActions || !this._copyContextMenuAction) { this._copyContextMenuAction = this._instantiationService.createInstance(CopyTerminalSelectionAction, CopyTerminalSelectionAction.ID, CopyTerminalSelectionAction.SHORT_LABEL); + + const clipboardActions = []; + if (BrowserFeatures.clipboard.writeText) { + clipboardActions.push(this._copyContextMenuAction); + } + if (BrowserFeatures.clipboard.readText) { + clipboardActions.push(this._instantiationService.createInstance(TerminalPasteAction, TerminalPasteAction.ID, TerminalPasteAction.SHORT_LABEL)); + } + + clipboardActions.push(this._instantiationService.createInstance(SelectAllTerminalAction, SelectAllTerminalAction.ID, SelectAllTerminalAction.LABEL)); + this._contextMenuActions = [ this._instantiationService.createInstance(CreateNewTerminalAction, CreateNewTerminalAction.ID, CreateNewTerminalAction.SHORT_LABEL), this._instantiationService.createInstance(SplitTerminalAction, SplitTerminalAction.ID, SplitTerminalAction.SHORT_LABEL), new Separator(), - this._copyContextMenuAction, - this._instantiationService.createInstance(TerminalPasteAction, TerminalPasteAction.ID, TerminalPasteAction.SHORT_LABEL), - this._instantiationService.createInstance(SelectAllTerminalAction, SelectAllTerminalAction.ID, SelectAllTerminalAction.LABEL), + ...clipboardActions, new Separator(), this._instantiationService.createInstance(ClearTerminalAction, ClearTerminalAction.ID, ClearTerminalAction.LABEL), new Separator(), @@ -252,9 +262,9 @@ export class TerminalPanel extends Panel { getActions: () => this._getContextMenuActions(), getActionsContext: () => this._parentDomElement }); - } else { - event.stopImmediatePropagation(); } + event.preventDefault(); + event.stopImmediatePropagation(); this._cancelContextMenu = false; })); this._register(dom.addDisposableListener(document, 'keydown', (event: KeyboardEvent) => { @@ -291,7 +301,7 @@ export class TerminalPanel extends Panel { const terminal = this._terminalService.getActiveInstance(); if (terminal) { - return this._terminalService.preparePathForTerminalAsync(path, terminal.shellLaunchConfig.executable, terminal.title).then(preparedPath => { + return this._terminalService.preparePathForTerminalAsync(path, terminal.shellLaunchConfig.executable, terminal.title, terminal.shellType).then(preparedPath => { terminal.sendText(preparedPath, false); }); } @@ -337,7 +347,7 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { if (hoverBorder) { collector.addRule(`.monaco-workbench .panel.integrated-terminal .terminal-message-widget { border: 1px solid ${hoverBorder}; }`); } - const hoverForeground = theme.getColor(editorForeground); + const hoverForeground = theme.getColor(editorHoverForeground); if (hoverForeground) { collector.addRule(`.monaco-workbench .panel.integrated-terminal .terminal-message-widget { color: ${hoverForeground}; }`); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProcessExtHostProxy.ts b/src/vs/workbench/contrib/terminal/browser/terminalProcessExtHostProxy.ts index 36be0aa641b2..2d18b3c91c45 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProcessExtHostProxy.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProcessExtHostProxy.ts @@ -17,8 +17,8 @@ export class TerminalProcessExtHostProxy extends Disposable implements ITerminal private readonly _onProcessData = this._register(new Emitter()); public readonly onProcessData: Event = this._onProcessData.event; - private readonly _onProcessExit = this._register(new Emitter()); - public readonly onProcessExit: Event = this._onProcessExit.event; + private readonly _onProcessExit = this._register(new Emitter()); + public readonly onProcessExit: Event = this._onProcessExit.event; private readonly _onProcessReady = this._register(new Emitter<{ pid: number, cwd: string }>()); public get onProcessReady(): Event<{ pid: number, cwd: string }> { return this._onProcessReady.event; } private readonly _onProcessTitleChanged = this._register(new Emitter()); @@ -87,7 +87,7 @@ export class TerminalProcessExtHostProxy extends Disposable implements ITerminal this._onProcessReady.fire({ pid, cwd }); } - public emitExit(exitCode: number): void { + public emitExit(exitCode: number | undefined): void { this._onProcessExit.fire(exitCode); this.dispose(); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts index 8660142f7072..21cd00663fa2 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts @@ -67,8 +67,8 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce public get onProcessData(): Event { return this._onProcessData.event; } private readonly _onProcessTitle = this._register(new Emitter()); public get onProcessTitle(): Event { return this._onProcessTitle.event; } - private readonly _onProcessExit = this._register(new Emitter()); - public get onProcessExit(): Event { return this._onProcessExit.event; } + private readonly _onProcessExit = this._register(new Emitter()); + public get onProcessExit(): Event { return this._onProcessExit.event; } private readonly _onProcessOverrideDimensions = this._register(new Emitter()); public get onProcessOverrideDimensions(): Event { return this._onProcessOverrideDimensions.event; } private readonly _onProcessOverrideShellLaunchConfig = this._register(new Emitter()); @@ -285,7 +285,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce return Promise.resolve(this._latency); } - private _onExit(exitCode: number): void { + private _onExit(exitCode: number | undefined): void { this._process = null; // If the process is marked as launching then mark the process as killed diff --git a/src/vs/workbench/contrib/terminal/browser/terminalQuickOpen.ts b/src/vs/workbench/contrib/terminal/browser/terminalQuickOpen.ts index 1fc468034baa..1a95d03d642e 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalQuickOpen.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalQuickOpen.ts @@ -88,7 +88,7 @@ export class TerminalPickerHandler extends QuickOpenHandler { const normalizedSearchValueLowercase = stripWildcards(searchValue).toLowerCase(); const terminalEntries: QuickOpenEntry[] = this.getTerminals(); - terminalEntries.push(new CreateTerminal(nls.localize("workbench.action.terminal.newplus", "$(plus) Create New Integrated Terminal"), this.commandService)); + terminalEntries.push(new CreateTerminal('$(plus) ' + nls.localize("workbench.action.terminal.newplus", "Create New Integrated Terminal"), this.commandService)); const entries = terminalEntries.filter(e => { if (!searchValue) { @@ -113,7 +113,7 @@ export class TerminalPickerHandler extends QuickOpenHandler { } private getTerminals(): TerminalEntry[] { - return this.terminalService.terminalTabs.reduce((terminals, tab, tabIndex) => { + return this.terminalService.terminalTabs.reduce((terminals: TerminalEntry[], tab, tabIndex) => { const terminalsInTab = tab.terminalInstances.map((terminal, terminalIndex) => { const label = `${tabIndex + 1}.${terminalIndex + 1}: ${terminal.title}`; return new TerminalEntry(terminal, label, this.terminalService); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index 5b2f93e16672..87942fa4c13f 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -16,7 +16,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IFileService } from 'vs/platform/files/common/files'; import { TerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminalInstance'; -import { ITerminalService, ITerminalInstance, ITerminalTab } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { ITerminalService, ITerminalInstance, ITerminalTab, TerminalShellType, WindowsShellType } from 'vs/workbench/contrib/terminal/browser/terminal'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper'; import { IQuickInputService, IQuickPickItem, IPickOptions } from 'vs/platform/quickinput/common/quickInput'; @@ -504,7 +504,7 @@ export class TerminalService implements ITerminalService { }); } - public preparePathForTerminalAsync(originalPath: string, executable: string, title: string): Promise { + public preparePathForTerminalAsync(originalPath: string, executable: string, title: string, shellType: TerminalShellType): Promise { return new Promise(c => { if (!executable) { c(originalPath); @@ -527,18 +527,41 @@ export class TerminalService implements ITerminalService { if (isWindows) { // 17063 is the build number where wsl path was introduced. // Update Windows uriPath to be executed in WSL. - const lowerExecutable = executable.toLowerCase(); - if (this._terminalNativeService.getWindowsBuildNumber() >= 17063 && - (lowerExecutable.indexOf('wsl') !== -1 || (lowerExecutable.indexOf('bash.exe') !== -1 && lowerExecutable.toLowerCase().indexOf('git') === -1))) { - c(this._terminalNativeService.getWslPath(originalPath)); - return; - } else if (hasSpace) { - c('"' + originalPath + '"'); + if (shellType !== undefined) { + if (shellType === WindowsShellType.GitBash) { + c(originalPath.replace(/\\/g, '/')); + return; + } + else if (shellType === WindowsShellType.Wsl) { + if (this._terminalNativeService.getWindowsBuildNumber() >= 17063) { + c(this._terminalNativeService.getWslPath(originalPath)); + } else { + c(originalPath.replace(/\\/g, '/')); + } + return; + } + + if (hasSpace) { + c('"' + originalPath + '"'); + } else { + c(originalPath); + } } else { - c(originalPath); + const lowerExecutable = executable.toLowerCase(); + if (this._terminalNativeService.getWindowsBuildNumber() >= 17063 && + (lowerExecutable.indexOf('wsl') !== -1 || (lowerExecutable.indexOf('bash.exe') !== -1 && lowerExecutable.toLowerCase().indexOf('git') === -1))) { + c(this._terminalNativeService.getWslPath(originalPath)); + return; + } else if (hasSpace) { + c('"' + originalPath + '"'); + } else { + c(originalPath); + } } + return; } + c(escapeNonWindowsPath(originalPath)); }); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalWidgetManager.ts b/src/vs/workbench/contrib/terminal/browser/terminalWidgetManager.ts index 87a7162ed6f5..e31a851c3422 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalWidgetManager.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalWidgetManager.ts @@ -5,6 +5,11 @@ import { IDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle'; +export enum WidgetVerticalAlignment { + Bottom, + Top +} + const WIDGET_HEIGHT = 29; export class TerminalWidgetManager implements IDisposable { @@ -43,13 +48,13 @@ export class TerminalWidgetManager implements IDisposable { mutationObserver.observe(this._xtermViewport, { attributes: true, attributeFilter: ['style'] }); } - public showMessage(left: number, top: number, text: string): void { + public showMessage(left: number, y: number, text: string, verticalAlignment: WidgetVerticalAlignment = WidgetVerticalAlignment.Bottom): void { if (!this._container) { return; } dispose(this._messageWidget); this._messageListeners.clear(); - this._messageWidget = new MessageWidget(this._container, left, top, text); + this._messageWidget = new MessageWidget(this._container, left, y, text, verticalAlignment); } public closeMessage(): void { @@ -71,9 +76,10 @@ class MessageWidget { private _domNode: HTMLDivElement; public get left(): number { return this._left; } - public get top(): number { return this._top; } + public get y(): number { return this._y; } public get text(): string { return this._text; } public get domNode(): HTMLElement { return this._domNode; } + public get verticalAlignment(): WidgetVerticalAlignment { return this._verticalAlignment; } public static fadeOut(messageWidget: MessageWidget): IDisposable { let handle: any; @@ -91,13 +97,22 @@ class MessageWidget { constructor( private _container: HTMLElement, private _left: number, - private _top: number, - private _text: string + private _y: number, + private _text: string, + private _verticalAlignment: WidgetVerticalAlignment ) { this._domNode = document.createElement('div'); this._domNode.style.position = 'absolute'; this._domNode.style.left = `${_left}px`; - this._domNode.style.bottom = `${_container.offsetHeight - Math.max(_top, WIDGET_HEIGHT)}px`; + + if (this.verticalAlignment === WidgetVerticalAlignment.Top) { + // Y position is to the top of the widget + this._domNode.style.bottom = `${Math.max(_y, WIDGET_HEIGHT) - WIDGET_HEIGHT}px`; + } else { + // Y position is to the bottom of the widget + this._domNode.style.bottom = `${Math.min(_y, _container.offsetHeight - WIDGET_HEIGHT)}px`; + } + this._domNode.classList.add('terminal-message-widget', 'fadeIn'); this._domNode.textContent = _text; this._container.appendChild(this._domNode); diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index b8138fba753d..35a4d55afc84 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -87,7 +87,7 @@ export interface ITerminalConfiguration { }; macOptionIsMeta: boolean; macOptionClickForcesSelection: boolean; - rendererType: 'auto' | 'canvas' | 'dom'; + rendererType: 'auto' | 'canvas' | 'dom' | 'experimentalWebgl'; rightClickBehavior: 'default' | 'copyPaste' | 'paste' | 'selectWord'; cursorBlinking: boolean; cursorStyle: string; @@ -95,6 +95,7 @@ export interface ITerminalConfiguration { fontFamily: string; fontWeight: FontWeight; fontWeightBold: FontWeight; + minimumContrastRatio: number; // fontLigatures: boolean; fontSize: number; letterSpacing: number; @@ -113,7 +114,6 @@ export interface ITerminalConfiguration { windows: { [key: string]: string }; }; showExitAlert: boolean; - experimentalBufferImpl: 'JsArray' | 'TypedArray'; splitCwd: 'workspaceRoot' | 'initial' | 'inherited'; windowsEnableConpty: boolean; experimentalRefreshOnResume: boolean; @@ -286,7 +286,7 @@ export interface ITerminalProcessManager extends IDisposable { readonly onBeforeProcessData: Event; readonly onProcessData: Event; readonly onProcessTitle: Event; - readonly onProcessExit: Event; + readonly onProcessExit: Event; readonly onProcessOverrideDimensions: Event; readonly onProcessResolvedShellLaunchConfig: Event; @@ -325,7 +325,7 @@ export interface ITerminalProcessExtHostProxy extends IDisposable { emitData(data: string): void; emitTitle(title: string): void; emitReady(pid: number, cwd: string): void; - emitExit(exitCode: number): void; + emitExit(exitCode: number | undefined): void; emitOverrideDimensions(dimensions: ITerminalDimensions | undefined): void; emitResolvedShellLaunchConfig(shellLaunchConfig: IShellLaunchConfig): void; emitInitialCwd(initialCwd: string): void; @@ -389,7 +389,7 @@ export interface IWindowsShellHelper extends IDisposable { */ export interface ITerminalChildProcess { onProcessData: Event; - onProcessExit: Event; + onProcessExit: Event; onProcessReady: Event<{ pid: number, cwd: string }>; onProcessTitleChanged: Event; onProcessOverrideDimensions?: Event; @@ -412,9 +412,7 @@ export interface ITerminalChildProcess { export const enum TERMINAL_COMMAND_ID { FIND_NEXT = 'workbench.action.terminal.findNext', - FIND_NEXT_TERMINAL_FOCUS = 'workbench.action.terminal.findNextTerminalFocus', FIND_PREVIOUS = 'workbench.action.terminal.findPrevious', - FIND_PREVIOUS_TERMINAL_FOCUS = 'workbench.action.terminal.findPreviousTerminalFocus', TOGGLE = 'workbench.action.terminal.toggleTerminal', KILL = 'workbench.action.terminal.kill', QUICK_KILL = 'workbench.action.terminal.quickKill', @@ -455,6 +453,7 @@ export const enum TERMINAL_COMMAND_ID { CLEAR_SELECTION = 'workbench.action.terminal.clearSelection', MANAGE_WORKSPACE_SHELL_PERMISSIONS = 'workbench.action.terminal.manageWorkspaceShellPermissions', RENAME = 'workbench.action.terminal.rename', + RENAME_WITH_ARG = 'workbench.action.terminal.renameWithArg', FIND_WIDGET_FOCUS = 'workbench.action.terminal.focusFindWidget', FIND_WIDGET_HIDE = 'workbench.action.terminal.hideFindWidget', QUICK_OPEN_TERM = 'workbench.action.quickOpenTerm', @@ -469,9 +468,6 @@ export const enum TERMINAL_COMMAND_ID { TOGGLE_FIND_REGEX = 'workbench.action.terminal.toggleFindRegex', TOGGLE_FIND_WHOLE_WORD = 'workbench.action.terminal.toggleFindWholeWord', TOGGLE_FIND_CASE_SENSITIVE = 'workbench.action.terminal.toggleFindCaseSensitive', - TOGGLE_FIND_REGEX_TERMINAL_FOCUS = 'workbench.action.terminal.toggleFindRegexTerminalFocus', - TOGGLE_FIND_WHOLE_WORD_TERMINAL_FOCUS = 'workbench.action.terminal.toggleFindWholeWordTerminalFocus', - TOGGLE_FIND_CASE_SENSITIVE_TERMINAL_FOCUS = 'workbench.action.terminal.toggleFindCaseSensitiveTerminalFocus', NAVIGATION_MODE_EXIT = 'workbench.action.terminal.navigationModeExit', NAVIGATION_MODE_FOCUS_NEXT = 'workbench.action.terminal.navigationModeFocusNext', NAVIGATION_MODE_FOCUS_PREVIOUS = 'workbench.action.terminal.navigationModeFocusPrevious' diff --git a/src/vs/workbench/contrib/terminal/common/terminalDataBuffering.ts b/src/vs/workbench/contrib/terminal/common/terminalDataBuffering.ts new file mode 100644 index 000000000000..612c56407e45 --- /dev/null +++ b/src/vs/workbench/contrib/terminal/common/terminalDataBuffering.ts @@ -0,0 +1,57 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Event } from 'vs/base/common/event'; +import { IDisposable } from 'vs/base/common/lifecycle'; + +interface TerminalDataBuffer extends IDisposable { + data: string[]; + timeoutId: any; +} + +export class TerminalDataBufferer implements IDisposable { + private readonly _terminalBufferMap = new Map(); + + dispose() { + for (const buffer of this._terminalBufferMap.values()) { + buffer.dispose(); + } + } + + startBuffering(id: number, event: Event, callback: (id: number, data: string) => void, throttleBy: number = 5): IDisposable { + let disposable: IDisposable; + disposable = event((e: string) => { + let buffer = this._terminalBufferMap.get(id); + if (buffer) { + buffer.data.push(e); + + return; + } + + const timeoutId = setTimeout(() => { + this._terminalBufferMap.delete(id); + callback(id, buffer!.data.join('')); + }, throttleBy); + buffer = { + data: [e], + timeoutId: timeoutId, + dispose: () => { + clearTimeout(timeoutId); + this._terminalBufferMap.delete(id); + disposable.dispose(); + } + }; + this._terminalBufferMap.set(id, buffer); + }); + return disposable; + } + + stopBuffering(id: number) { + const buffer = this._terminalBufferMap.get(id); + if (buffer) { + buffer.dispose(); + } + } +} diff --git a/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts b/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts index b18b8fd33449..70509ef973ad 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts @@ -184,23 +184,16 @@ export function getCwd( logService?: ILogService ): string { if (shell.cwd) { - return (typeof shell.cwd === 'object') ? shell.cwd.fsPath : shell.cwd; + const unresolved = (typeof shell.cwd === 'object') ? shell.cwd.fsPath : shell.cwd; + const resolved = _resolveCwd(unresolved, lastActiveWorkspace, configurationResolverService); + return resolved || unresolved; } let cwd: string | undefined; if (!shell.ignoreConfigurationCwd && customCwd) { if (configurationResolverService) { - try { - customCwd = configurationResolverService.resolve(lastActiveWorkspace, customCwd); - } catch (e) { - // There was an issue resolving a variable, log the error in the console and - // fallback to the default. - if (logService) { - logService.error('Could not resolve terminal.integrated.cwd', e); - } - customCwd = undefined; - } + customCwd = _resolveCwd(customCwd, lastActiveWorkspace, configurationResolverService, logService); } if (customCwd) { if (path.isAbsolute(customCwd)) { @@ -219,6 +212,18 @@ export function getCwd( return _sanitizeCwd(cwd); } +function _resolveCwd(cwd: string, lastActiveWorkspace: IWorkspaceFolder | undefined, configurationResolverService: IConfigurationResolverService | undefined, logService?: ILogService): string | undefined { + if (configurationResolverService) { + try { + return configurationResolverService.resolve(lastActiveWorkspace, cwd); + } catch (e) { + logService?.error('Could not resolve terminal cwd', e); + return undefined; + } + } + return cwd; +} + function _sanitizeCwd(cwd: string): string { // Make the drive letter uppercase on Windows (see #9448) if (platform.platform === platform.Platform.Windows && cwd && cwd[1] === ':') { diff --git a/src/vs/workbench/contrib/terminal/electron-browser/terminalInstanceService.ts b/src/vs/workbench/contrib/terminal/electron-browser/terminalInstanceService.ts index 48b70035d411..16a6353ecc72 100644 --- a/src/vs/workbench/contrib/terminal/electron-browser/terminalInstanceService.ts +++ b/src/vs/workbench/contrib/terminal/electron-browser/terminalInstanceService.ts @@ -13,6 +13,7 @@ import { getSystemShell } from 'vs/workbench/contrib/terminal/node/terminal'; import { Terminal as XTermTerminal } from 'xterm'; import { WebLinksAddon as XTermWebLinksAddon } from 'xterm-addon-web-links'; import { SearchAddon as XTermSearchAddon } from 'xterm-addon-search'; +import { WebglAddon as XTermWebglAddon } from 'xterm-addon-webgl'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { getDefaultShell, getDefaultShellArgs } from 'vs/workbench/contrib/terminal/common/terminalEnvironment'; import { StorageScope, IStorageService } from 'vs/platform/storage/common/storage'; @@ -25,6 +26,7 @@ import { ILogService } from 'vs/platform/log/common/log'; let Terminal: typeof XTermTerminal; let WebLinksAddon: typeof XTermWebLinksAddon; let SearchAddon: typeof XTermSearchAddon; +let WebglAddon: typeof XTermWebglAddon; export class TerminalInstanceService implements ITerminalInstanceService { public _serviceBrand: undefined; @@ -61,6 +63,13 @@ export class TerminalInstanceService implements ITerminalInstanceService { return SearchAddon; } + public async getXtermWebglConstructor(): Promise { + if (!WebglAddon) { + WebglAddon = (await import('xterm-addon-webgl')).WebglAddon; + } + return WebglAddon; + } + public createWindowsShellHelper(shellProcessId: number, instance: ITerminalInstance, xterm: XTermTerminal): IWindowsShellHelper { return new WindowsShellHelper(shellProcessId, instance, xterm); } diff --git a/src/vs/workbench/contrib/terminal/electron-browser/terminalNativeService.ts b/src/vs/workbench/contrib/terminal/electron-browser/terminalNativeService.ts index 5fa271ddcc95..a0dd76f7256c 100644 --- a/src/vs/workbench/contrib/terminal/electron-browser/terminalNativeService.ts +++ b/src/vs/workbench/contrib/terminal/electron-browser/terminalNativeService.ts @@ -69,9 +69,10 @@ export class TerminalNativeService implements ITerminalNativeService { throw new Error('wslpath does not exist on Windows build < 17063'); } return new Promise(c => { - execFile('bash.exe', ['-c', 'echo $(wslpath ' + escapeNonWindowsPath(path) + ')'], {}, (error, stdout, stderr) => { + const proc = execFile('bash.exe', ['-c', `wslpath ${escapeNonWindowsPath(path)}`], {}, (error, stdout, stderr) => { c(escapeNonWindowsPath(stdout.trim())); }); + proc.stdin!.end(); }); } diff --git a/src/vs/workbench/contrib/terminal/electron-browser/terminalRemote.ts b/src/vs/workbench/contrib/terminal/electron-browser/terminalRemote.ts index fab36b0428df..2af06aed7b66 100644 --- a/src/vs/workbench/contrib/terminal/electron-browser/terminalRemote.ts +++ b/src/vs/workbench/contrib/terminal/electron-browser/terminalRemote.ts @@ -15,7 +15,7 @@ import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal export function registerRemoteContributions() { const actionRegistry = Registry.as(ActionExtensions.WorkbenchActions); - actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(CreateNewLocalTerminalAction, CreateNewLocalTerminalAction.ID, CreateNewLocalTerminalAction.LABEL), 'Terminal: Create New Integrated Terminal (Local)', TERMINAL_ACTION_CATEGORY); + actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(CreateNewLocalTerminalAction, CreateNewLocalTerminalAction.ID, CreateNewLocalTerminalAction.LABEL), 'Terminal: Create New Integrated Terminal (Local)', TERMINAL_ACTION_CATEGORY); } export class CreateNewLocalTerminalAction extends Action { diff --git a/src/vs/workbench/contrib/terminal/electron-browser/windowsShellHelper.ts b/src/vs/workbench/contrib/terminal/electron-browser/windowsShellHelper.ts index df8fc2942a42..d0f038a7fa3d 100644 --- a/src/vs/workbench/contrib/terminal/electron-browser/windowsShellHelper.ts +++ b/src/vs/workbench/contrib/terminal/electron-browser/windowsShellHelper.ts @@ -9,11 +9,12 @@ import { IWindowsShellHelper, TitleEventSource } from 'vs/workbench/contrib/term import { Terminal as XTermTerminal } from 'xterm'; import * as WindowsProcessTreeType from 'windows-process-tree'; import { Disposable } from 'vs/base/common/lifecycle'; -import { ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { ITerminalInstance, TerminalShellType, WindowsShellType } from 'vs/workbench/contrib/terminal/browser/terminal'; const SHELL_EXECUTABLES = [ 'cmd.exe', 'powershell.exe', + 'pwsh.exe', 'bash.exe', 'wsl.exe', 'ubuntu.exe', @@ -81,6 +82,7 @@ export class WindowsShellHelper extends Disposable implements IWindowsShellHelpe if (platform.isWindows && this._terminalInstance.isTitleSetByProcess) { this.getShellName().then(title => { if (!this._isDisposed) { + this._terminalInstance.setShellType(this.getShellType(title)); this._terminalInstance.setTitle(title, TitleEventSource.Process); } }); @@ -138,4 +140,26 @@ export class WindowsShellHelper extends Disposable implements IWindowsShellHelpe }); return this._currentRequest; } + + public getShellType(executable: string): TerminalShellType { + switch (executable.toLowerCase()) { + case 'cmd.exe': + return WindowsShellType.CommandPrompt; + case 'powershell.exe': + case 'pwsh.exe': + return WindowsShellType.PowerShell; + case 'bash.exe': + return WindowsShellType.GitBash; + case 'wsl.exe': + case 'ubuntu.exe': + case 'ubuntu1804.exe': + case 'kali.exe': + case 'debian.exe': + case 'opensuse-42.exe': + case 'sles-12.exe': + return WindowsShellType.Wsl; + default: + return undefined; + } + } } diff --git a/src/vs/workbench/contrib/terminal/node/terminalProcess.ts b/src/vs/workbench/contrib/terminal/node/terminalProcess.ts index 3eae6170617c..7af8e2cae0d4 100644 --- a/src/vs/workbench/contrib/terminal/node/terminalProcess.ts +++ b/src/vs/workbench/contrib/terminal/node/terminalProcess.ts @@ -65,7 +65,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess env, cols, rows, - experimentalUseConpty: useConpty, + useConpty, // This option will force conpty to not redraw the whole viewport on launch conptyInheritCursor: useConpty && !!shellLaunchConfig.initialText }; @@ -74,18 +74,21 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess if (!stat.isDirectory()) { return Promise.reject(SHELL_CWD_INVALID_EXIT_CODE); } + return undefined; }, async err => { if (err && err.code === 'ENOENT') { // So we can include in the error message the specified CWD shellLaunchConfig.cwd = cwd; return Promise.reject(SHELL_CWD_INVALID_EXIT_CODE); } + return undefined; }); const executableVerification = stat(shellLaunchConfig.executable!).then(async stat => { if (!stat.isFile() && !stat.isSymbolicLink()) { return Promise.reject(stat.isDirectory() ? SHELL_PATH_DIRECTORY_EXIT_CODE : SHELL_PATH_INVALID_EXIT_CODE); } + return undefined; }, async (err) => { if (err && err.code === 'ENOENT') { let cwd = shellLaunchConfig.cwd instanceof URI ? shellLaunchConfig.cwd.path : shellLaunchConfig.cwd!; @@ -96,6 +99,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess return Promise.reject(SHELL_PATH_INVALID_EXIT_CODE); } } + return undefined; }); Promise.all([cwdVerification, executableVerification]).then(() => { @@ -260,7 +264,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess return; } this._logService.trace('IPty#pid'); - exec('lsof -p ' + this._ptyProcess.pid + ' | grep cwd', (error, stdout, stderr) => { + exec('lsof -OPl -p ' + this._ptyProcess.pid + ' | grep cwd', (error, stdout, stderr) => { if (stdout !== '') { resolve(stdout.substring(stdout.indexOf('/'), stdout.length - 1)); } diff --git a/src/vs/workbench/contrib/terminal/test/common/terminalDataBuffering.test.ts b/src/vs/workbench/contrib/terminal/test/common/terminalDataBuffering.test.ts new file mode 100644 index 000000000000..7b12303c9a2c --- /dev/null +++ b/src/vs/workbench/contrib/terminal/test/common/terminalDataBuffering.test.ts @@ -0,0 +1,191 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { Emitter } from 'vs/base/common/event'; +import { TerminalDataBufferer } from 'vs/workbench/contrib/terminal/common/terminalDataBuffering'; + +const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); + +suite('Workbench - TerminalDataBufferer', () => { + let bufferer: TerminalDataBufferer; + + setup(async () => { + bufferer = new TerminalDataBufferer(); + }); + + test('start', async () => { + let terminalOnData = new Emitter(); + let counter = 0; + let data: string | undefined; + + bufferer.startBuffering(1, terminalOnData.event, (id, e) => { + counter++; + data = e; + }, 0); + + terminalOnData.fire('1'); + terminalOnData.fire('2'); + terminalOnData.fire('3'); + + await wait(0); + + terminalOnData.fire('4'); + + assert.equal(counter, 1); + assert.equal(data, '123'); + + await wait(0); + + assert.equal(counter, 2); + assert.equal(data, '4'); + }); + + test('start 2', async () => { + let terminal1OnData = new Emitter(); + let terminal1Counter = 0; + let terminal1Data: string | undefined; + + bufferer.startBuffering(1, terminal1OnData.event, (id, e) => { + terminal1Counter++; + terminal1Data = e; + }, 0); + + let terminal2OnData = new Emitter(); + let terminal2Counter = 0; + let terminal2Data: string | undefined; + + bufferer.startBuffering(2, terminal2OnData.event, (id, e) => { + terminal2Counter++; + terminal2Data = e; + }, 0); + + terminal1OnData.fire('1'); + terminal2OnData.fire('4'); + terminal1OnData.fire('2'); + terminal2OnData.fire('5'); + terminal1OnData.fire('3'); + terminal2OnData.fire('6'); + terminal2OnData.fire('7'); + + assert.equal(terminal1Counter, 0); + assert.equal(terminal1Data, undefined); + assert.equal(terminal2Counter, 0); + assert.equal(terminal2Data, undefined); + + await wait(0); + + assert.equal(terminal1Counter, 1); + assert.equal(terminal1Data, '123'); + assert.equal(terminal2Counter, 1); + assert.equal(terminal2Data, '4567'); + }); + + test('stop', async () => { + let terminalOnData = new Emitter(); + let counter = 0; + let data: string | undefined; + + bufferer.startBuffering(1, terminalOnData.event, (id, e) => { + counter++; + data = e; + }, 0); + + terminalOnData.fire('1'); + terminalOnData.fire('2'); + terminalOnData.fire('3'); + + bufferer.stopBuffering(1); + + await wait(0); + + assert.equal(counter, 0); + assert.equal(data, undefined); + }); + + test('start 2 stop 1', async () => { + let terminal1OnData = new Emitter(); + let terminal1Counter = 0; + let terminal1Data: string | undefined; + + bufferer.startBuffering(1, terminal1OnData.event, (id, e) => { + terminal1Counter++; + terminal1Data = e; + }, 0); + + let terminal2OnData = new Emitter(); + let terminal2Counter = 0; + let terminal2Data: string | undefined; + + bufferer.startBuffering(2, terminal2OnData.event, (id, e) => { + terminal2Counter++; + terminal2Data = e; + }, 0); + + + terminal1OnData.fire('1'); + terminal2OnData.fire('4'); + terminal1OnData.fire('2'); + terminal2OnData.fire('5'); + terminal1OnData.fire('3'); + terminal2OnData.fire('6'); + terminal2OnData.fire('7'); + + assert.equal(terminal1Counter, 0); + assert.equal(terminal1Data, undefined); + assert.equal(terminal2Counter, 0); + assert.equal(terminal2Data, undefined); + + bufferer.stopBuffering(1); + await wait(0); + + assert.equal(terminal1Counter, 0); + assert.equal(terminal1Data, undefined); + assert.equal(terminal2Counter, 1); + assert.equal(terminal2Data, '4567'); + }); + + test('dispose', async () => { + let terminal1OnData = new Emitter(); + let terminal1Counter = 0; + let terminal1Data: string | undefined; + + bufferer.startBuffering(1, terminal1OnData.event, (id, e) => { + terminal1Counter++; + terminal1Data = e; + }, 0); + + let terminal2OnData = new Emitter(); + let terminal2Counter = 0; + let terminal2Data: string | undefined; + + bufferer.startBuffering(2, terminal2OnData.event, (id, e) => { + terminal2Counter++; + terminal2Data = e; + }, 0); + + + terminal1OnData.fire('1'); + terminal2OnData.fire('4'); + terminal1OnData.fire('2'); + terminal2OnData.fire('5'); + terminal1OnData.fire('3'); + terminal2OnData.fire('6'); + terminal2OnData.fire('7'); + + assert.equal(terminal1Counter, 0); + assert.equal(terminal1Data, undefined); + assert.equal(terminal2Counter, 0); + assert.equal(terminal2Data, undefined); + + bufferer.dispose(); + await wait(0); + + assert.equal(terminal1Counter, 0); + assert.equal(terminal1Data, undefined); + assert.equal(terminal2Counter, 0); + assert.equal(terminal2Data, undefined); + }); +}); diff --git a/src/vs/workbench/contrib/terminal/test/electron-browser/terminalColorRegistry.test.ts b/src/vs/workbench/contrib/terminal/test/electron-browser/terminalColorRegistry.test.ts index 9ce8be5f7094..5a95842d9b9d 100644 --- a/src/vs/workbench/contrib/terminal/test/electron-browser/terminalColorRegistry.test.ts +++ b/src/vs/workbench/contrib/terminal/test/electron-browser/terminalColorRegistry.test.ts @@ -19,7 +19,10 @@ function getMockTheme(type: ThemeType): ITheme { label: '', type: type, getColor: (colorId: ColorIdentifier): Color | undefined => themingRegistry.resolveDefaultColor(colorId, theme), - defines: () => true + defines: () => true, + getTokenStyleMetadata: () => undefined, + tokenColorMap: [] + }; return theme; } @@ -99,4 +102,4 @@ suite('Workbench - TerminalColorRegistry', () => { '#e5e5e5' ], 'The dark terminal colors should be used when a dark theme is active'); }); -}); \ No newline at end of file +}); diff --git a/src/vs/workbench/contrib/terminal/test/electron-browser/terminalLinkHandler.test.ts b/src/vs/workbench/contrib/terminal/test/electron-browser/terminalLinkHandler.test.ts index 01ddc8a8bfc8..b48c72617d0d 100644 --- a/src/vs/workbench/contrib/terminal/test/electron-browser/terminalLinkHandler.test.ts +++ b/src/vs/workbench/contrib/terminal/test/electron-browser/terminalLinkHandler.test.ts @@ -46,6 +46,9 @@ class MockTerminalInstanceService implements ITerminalInstanceService { getXtermSearchConstructor(): Promise { throw new Error('Method not implemented.'); } + getXtermWebglConstructor(): Promise { + throw new Error('Method not implemented.'); + } createWindowsShellHelper(): any { throw new Error('Method not implemented.'); } diff --git a/src/vs/workbench/contrib/testCustomEditors/browser/testCustomEditors.ts b/src/vs/workbench/contrib/testCustomEditors/browser/testCustomEditors.ts new file mode 100644 index 000000000000..0430a52789ec --- /dev/null +++ b/src/vs/workbench/contrib/testCustomEditors/browser/testCustomEditors.ts @@ -0,0 +1,252 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import { Action } from 'vs/base/common/actions'; +import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; +import { IEditorInputFactory, EditorInput, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions, EditorModel, EditorOptions, GroupIdentifier, ISaveOptions, IRevertOptions } from 'vs/workbench/common/editor'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorModel } from 'vs/platform/editor/common/editor'; +import { Dimension, addDisposableListener, EventType } from 'vs/base/browser/dom'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IEditorRegistry, EditorDescriptor, Extensions as EditorExtensions } from 'vs/workbench/browser/editor'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; +import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { URI } from 'vs/base/common/uri'; +import { isEqual } from 'vs/base/common/resources'; +import { generateUuid } from 'vs/base/common/uuid'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { editorBackground, editorForeground } from 'vs/platform/theme/common/colorRegistry'; +import { IWorkingCopy, IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { env } from 'vs/base/common/process'; + +const CUSTOM_SCHEME = 'testCustomEditor'; +const ENABLE = !!env['VSCODE_DEV']; + +class TestCustomEditorsAction extends Action { + + static readonly ID = 'workbench.action.openCustomEditor'; + static readonly LABEL = nls.localize('openCustomEditor', "Test Open Custom Editor"); + + constructor( + id: string, + label: string, + @IEditorService private readonly editorService: IEditorService, + @IInstantiationService private readonly instantiationService: IInstantiationService + ) { + super(id, label); + } + + async run(): Promise { + const input = this.instantiationService.createInstance(TestCustomEditorInput, URI.parse(`${CUSTOM_SCHEME}:/${generateUuid()}`)); + await this.editorService.openEditor(input); + + return true; + } +} + +class TestCustomEditor extends BaseEditor { + + static ID = 'testCustomEditor'; + + private textArea: HTMLTextAreaElement | undefined = undefined; + + constructor( + @ITelemetryService telemetryService: ITelemetryService, + @IThemeService themeService: IThemeService, + @IStorageService storageService: IStorageService + ) { + super(TestCustomEditor.ID, telemetryService, themeService, storageService); + } + + updateStyles(): void { + super.updateStyles(); + + if (this.textArea) { + this.textArea.style.backgroundColor = this.getColor(editorBackground)!.toString(); + this.textArea.style.color = this.getColor(editorForeground)!.toString(); + } + } + + protected createEditor(parent: HTMLElement): void { + this.textArea = document.createElement('textarea'); + this.textArea.style.width = '100%'; + this.textArea.style.height = '100%'; + + parent.appendChild(this.textArea); + + addDisposableListener(this.textArea, EventType.CHANGE, e => this.onDidType()); + addDisposableListener(this.textArea, EventType.KEY_UP, e => this.onDidType()); + + this.updateStyles(); + } + + private onDidType(): void { + if (this._input instanceof TestCustomEditorInput) { + this._input.setValue(this.textArea!.value); + } + } + + async setInput(input: EditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise { + await super.setInput(input, options, token); + + const model = await input.resolve(); + if (model instanceof TestCustomEditorModel) { + this.textArea!.value = model.value; + } + } + + clearInput() { + super.clearInput(); + + this.textArea!.value = ''; + } + + focus(): void { + this.textArea!.focus(); + } + + layout(dimension: Dimension): void { } +} + +class TestCustomEditorInput extends EditorInput implements IWorkingCopy { + private model: TestCustomEditorModel | undefined = undefined; + + private dirty = false; + + readonly capabilities = 0; + + constructor(public readonly resource: URI, @IWorkingCopyService workingCopyService: IWorkingCopyService) { + super(); + + this._register(workingCopyService.registerWorkingCopy(this)); + } + + getResource(): URI { + return this.resource; + } + + getTypeId(): string { + return TestCustomEditor.ID; + } + + getName(): string { + return this.resource.toString(); + } + + setValue(value: string) { + if (this.model) { + if (this.model.value === value) { + return; + } + + this.model.value = value; + } + + this.setDirty(value.length > 0); + } + + private setDirty(dirty: boolean) { + if (this.dirty !== dirty) { + this.dirty = dirty; + this._onDidChangeDirty.fire(); + } + } + + isReadonly(): boolean { + return false; + } + + isDirty(): boolean { + return this.dirty; + } + + async save(groupId: GroupIdentifier, options?: ISaveOptions): Promise { + this.setDirty(false); + + return true; + } + + async saveAs(groupId: GroupIdentifier, options?: ISaveOptions): Promise { + this.setDirty(false); + + return true; + } + + async revert(options?: IRevertOptions): Promise { + this.setDirty(false); + + return true; + } + + async resolve(): Promise { + if (!this.model) { + this.model = new TestCustomEditorModel(this.resource); + } + + return this.model; + } + + matches(other: EditorInput) { + return other instanceof TestCustomEditorInput && isEqual(other.resource, this.resource); + } + + dispose(): void { + this.setDirty(false); + + if (this.model) { + this.model.dispose(); + this.model = undefined; + } + + super.dispose(); + } +} + +class TestCustomEditorModel extends EditorModel { + + public value: string = ''; + + constructor(public readonly resource: URI) { + super(); + } +} + +if (ENABLE) { + Registry.as(EditorExtensions.Editors).registerEditor( + EditorDescriptor.create( + TestCustomEditor, + TestCustomEditor.ID, + nls.localize('testCustomEditor', "Test Custom Editor") + ), + [ + new SyncDescriptor(TestCustomEditorInput), + ] + ); + + const registry = Registry.as(Extensions.WorkbenchActions); + + registry.registerWorkbenchAction(SyncActionDescriptor.create(TestCustomEditorsAction, TestCustomEditorsAction.ID, TestCustomEditorsAction.LABEL), 'Test Open Custom Editor'); + + class TestCustomEditorInputFactory implements IEditorInputFactory { + + serialize(editorInput: TestCustomEditorInput): string { + return JSON.stringify({ + resource: editorInput.resource.toString() + }); + } + + deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): TestCustomEditorInput { + return instantiationService.createInstance(TestCustomEditorInput, URI.parse(JSON.parse(serializedEditorInput).resource)); + } + } + + Registry.as(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory(TestCustomEditor.ID, TestCustomEditorInputFactory); +} diff --git a/src/vs/workbench/contrib/themes/browser/themes.contribution.ts b/src/vs/workbench/contrib/themes/browser/themes.contribution.ts index 5c1835fa95b1..8f2f38257c46 100644 --- a/src/vs/workbench/contrib/themes/browser/themes.contribution.ts +++ b/src/vs/workbench/contrib/themes/browser/themes.contribution.ts @@ -60,17 +60,10 @@ export class SelectColorThemeAction extends Action { } selectThemeTimeout = window.setTimeout(() => { selectThemeTimeout = undefined; - - let themeId = theme.id; - if (typeof theme.id === 'undefined') { // 'pick in marketplace' entry - if (applyTheme) { - openExtensionViewlet(this.viewletService, 'category:themes '); - } - themeId = currentTheme.id; - } + const themeId = theme && theme.id !== undefined ? theme.id : currentTheme.id; let target: ConfigurationTarget | undefined = undefined; if (applyTheme) { - let confValue = this.configurationService.inspect(COLOR_THEME_SETTING); + const confValue = this.configurationService.inspect(COLOR_THEME_SETTING); target = typeof confValue.workspace !== 'undefined' ? ConfigurationTarget.WORKSPACE : ConfigurationTarget.USER; } @@ -83,15 +76,35 @@ export class SelectColorThemeAction extends Action { }, applyTheme ? 0 : 200); }; - const placeHolder = localize('themes.selectTheme', "Select Color Theme (Up/Down Keys to Preview)"); - const autoFocusIndex = firstIndex(picks, p => isItem(p) && p.id === currentTheme.id); - const activeItem: ThemeItem = picks[autoFocusIndex] as ThemeItem; - - const chooseTheme = (theme: ThemeItem) => selectTheme(theme || currentTheme, true); - const tryTheme = (theme: ThemeItem) => selectTheme(theme, false); - - return this.quickInputService.pick(picks, { placeHolder, activeItem, onDidFocus: tryTheme }) - .then(chooseTheme); + return new Promise((s, _) => { + let isCompleted = false; + + const autoFocusIndex = firstIndex(picks, p => isItem(p) && p.id === currentTheme.id); + const quickpick = this.quickInputService.createQuickPick(); + quickpick.items = picks; + quickpick.placeholder = localize('themes.selectTheme', "Select Color Theme (Up/Down Keys to Preview)"); + quickpick.activeItems = [picks[autoFocusIndex] as ThemeItem]; + quickpick.canSelectMany = false; + quickpick.onDidAccept(_ => { + const theme = quickpick.activeItems[0]; + if (!theme || typeof theme.id === 'undefined') { // 'pick in marketplace' entry + openExtensionViewlet(this.viewletService, `category:themes ${quickpick.value}`); + } else { + selectTheme(theme, true); + } + isCompleted = true; + quickpick.hide(); + s(); + }); + quickpick.onDidChangeActive(themes => selectTheme(themes[0], false)); + quickpick.onDidHide(() => { + if (!isCompleted) { + selectTheme(currentTheme, true); + s(); + } + }); + quickpick.show(); + }); }); } } @@ -133,16 +146,10 @@ class SelectIconThemeAction extends Action { } selectThemeTimeout = window.setTimeout(() => { selectThemeTimeout = undefined; - let themeId = theme.id; - if (typeof theme.id === 'undefined') { // 'pick in marketplace' entry - if (applyTheme) { - openExtensionViewlet(this.viewletService, 'tag:icon-theme '); - } - themeId = currentTheme.id; - } + const themeId = theme && theme.id !== undefined ? theme.id : currentTheme.id; let target: ConfigurationTarget | undefined = undefined; if (applyTheme) { - let confValue = this.configurationService.inspect(ICON_THEME_SETTING); + const confValue = this.configurationService.inspect(ICON_THEME_SETTING); target = typeof confValue.workspace !== 'undefined' ? ConfigurationTarget.WORKSPACE : ConfigurationTarget.USER; } this.themeService.setFileIconTheme(themeId, target).then(undefined, @@ -154,14 +161,35 @@ class SelectIconThemeAction extends Action { }, applyTheme ? 0 : 200); }; - const placeHolder = localize('themes.selectIconTheme', "Select File Icon Theme"); - const autoFocusIndex = firstIndex(picks, p => isItem(p) && p.id === currentTheme.id); - const activeItem: ThemeItem = picks[autoFocusIndex] as ThemeItem; - const chooseTheme = (theme: ThemeItem) => selectTheme(theme || currentTheme, true); - const tryTheme = (theme: ThemeItem) => selectTheme(theme, false); - - return this.quickInputService.pick(picks, { placeHolder, activeItem, onDidFocus: tryTheme }) - .then(chooseTheme); + return new Promise((s, _) => { + let isCompleted = false; + + const autoFocusIndex = firstIndex(picks, p => isItem(p) && p.id === currentTheme.id); + const quickpick = this.quickInputService.createQuickPick(); + quickpick.items = picks; + quickpick.placeholder = localize('themes.selectIconTheme', "Select File Icon Theme"); + quickpick.activeItems = [picks[autoFocusIndex] as ThemeItem]; + quickpick.canSelectMany = false; + quickpick.onDidAccept(_ => { + const theme = quickpick.activeItems[0]; + if (!theme || typeof theme.id === 'undefined') { // 'pick in marketplace' entry + openExtensionViewlet(this.viewletService, `tag:icon-theme ${quickpick.value}`); + } else { + selectTheme(theme, true); + } + isCompleted = true; + quickpick.hide(); + s(); + }); + quickpick.onDidChangeActive(themes => selectTheme(themes[0], false)); + quickpick.onDidHide(() => { + if (!isCompleted) { + selectTheme(currentTheme, true); + s(); + } + }); + quickpick.show(); + }); }); } } @@ -259,16 +287,16 @@ class GenerateColorThemeAction extends Action { const category = localize('preferences', "Preferences"); -const colorThemeDescriptor = new SyncActionDescriptor(SelectColorThemeAction, SelectColorThemeAction.ID, SelectColorThemeAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_T) }); +const colorThemeDescriptor = SyncActionDescriptor.create(SelectColorThemeAction, SelectColorThemeAction.ID, SelectColorThemeAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_T) }); Registry.as(Extensions.WorkbenchActions).registerWorkbenchAction(colorThemeDescriptor, 'Preferences: Color Theme', category); -const iconThemeDescriptor = new SyncActionDescriptor(SelectIconThemeAction, SelectIconThemeAction.ID, SelectIconThemeAction.LABEL); +const iconThemeDescriptor = SyncActionDescriptor.create(SelectIconThemeAction, SelectIconThemeAction.ID, SelectIconThemeAction.LABEL); Registry.as(Extensions.WorkbenchActions).registerWorkbenchAction(iconThemeDescriptor, 'Preferences: File Icon Theme', category); const developerCategory = localize('developer', "Developer"); -const generateColorThemeDescriptor = new SyncActionDescriptor(GenerateColorThemeAction, GenerateColorThemeAction.ID, GenerateColorThemeAction.LABEL); +const generateColorThemeDescriptor = SyncActionDescriptor.create(GenerateColorThemeAction, GenerateColorThemeAction.ID, GenerateColorThemeAction.LABEL); Registry.as(Extensions.WorkbenchActions).registerWorkbenchAction(generateColorThemeDescriptor, 'Developer: Generate Color Theme From Current Settings', developerCategory); MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { diff --git a/src/vs/workbench/contrib/themes/test/electron-browser/themes.test.contribution.ts b/src/vs/workbench/contrib/themes/test/electron-browser/themes.test.contribution.ts index 335d062c851f..3ec872a9ebc0 100644 --- a/src/vs/workbench/contrib/themes/test/electron-browser/themes.test.contribution.ts +++ b/src/vs/workbench/contrib/themes/test/electron-browser/themes.test.contribution.ts @@ -216,6 +216,9 @@ class Snapper { public captureSyntaxTokens(fileName: string, content: string): Promise { const modeId = this.modeService.getModeIdByFilepathOrFirstLine(URI.file(fileName)); return this.textMateService.createGrammar(modeId!).then((grammar) => { + if (!grammar) { + return []; + } let lines = content.split(/\r\n|\r|\n/); let result = this._tokenize(grammar, lines); diff --git a/src/vs/workbench/contrib/update/browser/media/markdown.css b/src/vs/workbench/contrib/update/browser/media/markdown.css deleted file mode 100644 index 5fbef60ceb0c..000000000000 --- a/src/vs/workbench/contrib/update/browser/media/markdown.css +++ /dev/null @@ -1,130 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -body { - padding: 10px 20px; - line-height: 22px; -} - -img { - max-width: 100%; - max-height: 100%; -} - -a { - text-decoration: none; -} - -a:hover { - text-decoration: underline; -} - -a:focus, -input:focus, -select:focus, -textarea:focus { - outline: 1px solid -webkit-focus-ring-color; - outline-offset: -1px; -} - -hr { - border: 0; - height: 2px; - border-bottom: 2px solid; -} - -h1 { - padding-bottom: 0.3em; - line-height: 1.2; - border-bottom-width: 1px; - border-bottom-style: solid; -} - -h1, h2, h3 { - font-weight: normal; -} - - -table { - border-collapse: collapse; -} - -table > thead > tr > th { - text-align: left; - border-bottom: 1px solid; -} - -table > thead > tr > th, -table > thead > tr > td, -table > tbody > tr > th, -table > tbody > tr > td { - padding: 5px 10px; -} - -table > tbody > tr + tr > td { - border-top-width: 1px; - border-top-style: solid; -} - -blockquote { - margin: 0 7px 0 5px; - padding: 0 16px 0 10px; - border-left-width: 5px; - border-left-style: solid; -} - -code { - font-family: Menlo, Monaco, Consolas, "Droid Sans Mono", "Courier New", monospace, "Droid Sans Fallback"; - font-size: 14px; - line-height: 19px; -} - -code > div { - padding: 16px; - border-radius: 3px; - overflow: auto; -} - -.monaco-tokenized-source { - white-space: pre; -} - -/** Theming */ - -.vscode-light code > div { - background-color: rgba(220, 220, 220, 0.4); -} - -.vscode-dark code > div { - background-color: rgba(10, 10, 10, 0.4); -} - -.vscode-high-contrast code > div { - background-color: rgb(0, 0, 0); -} - -.vscode-high-contrast h1 { - border-color: rgb(0, 0, 0); -} - -.vscode-light table > thead > tr > th { - border-color: rgba(0, 0, 0, 0.69); -} - -.vscode-dark table > thead > tr > th { - border-color: rgba(255, 255, 255, 0.69); -} - -.vscode-light h1, -.vscode-light hr, -.vscode-light table > tbody > tr + tr > td { - border-color: rgba(0, 0, 0, 0.18); -} - -.vscode-dark h1, -.vscode-dark hr, -.vscode-dark table > tbody > tr + tr > td { - border-color: rgba(255, 255, 255, 0.18); -} diff --git a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts index 2b7ab73aa4cc..c5756e55964f 100644 --- a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts +++ b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts @@ -81,13 +81,11 @@ export class ReleaseNotesManager { { tryRestoreScrollPosition: true, enableFindWidget: true, - localResourceRoots: [ - URI.parse(require.toUrl('./media')) - ] + localResourceRoots: [] }, undefined); - this._currentReleaseNotes.webview.onDidClickLink(uri => this.onDidClickLink(uri)); + this._currentReleaseNotes.webview.onDidClickLink(uri => this.onDidClickLink(URI.parse(uri))); this._currentReleaseNotes.onDispose(() => { this._currentReleaseNotes = undefined; }); const iconPath = URI.parse(require.toUrl('./media/code-icon.svg')); @@ -178,18 +176,146 @@ export class ReleaseNotesManager { } private async renderBody(text: string) { + const nonce = generateUuid(); const content = await renderMarkdownDocument(text, this._extensionService, this._modeService); const colorMap = TokenizationRegistry.getColorMap(); const css = colorMap ? generateTokensCSSForColorMap(colorMap) : ''; - const styleSheetPath = require.toUrl('./media/markdown.css').replace('file://', 'vscode-resource://'); return ` - - - + + ${content} `; diff --git a/src/vs/workbench/contrib/update/browser/update.contribution.ts b/src/vs/workbench/contrib/update/browser/update.contribution.ts index c44d7a68d648..abfec72a5226 100644 --- a/src/vs/workbench/contrib/update/browser/update.contribution.ts +++ b/src/vs/workbench/contrib/update/browser/update.contribution.ts @@ -4,18 +4,38 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/platform/update/common/update.config.contribution'; +import { localize } from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; -import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; -import { ShowCurrentReleaseNotesAction, ProductContribution, UpdateContribution } from 'vs/workbench/contrib/update/browser/update'; +import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; +import { ShowCurrentReleaseNotesAction, ProductContribution, UpdateContribution, CheckForVSCodeUpdateAction, CONTEXT_UPDATE_STATE } from 'vs/workbench/contrib/update/browser/update'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; +import product from 'vs/platform/product/common/product'; +import { StateType } from 'vs/platform/update/common/update'; const workbench = Registry.as(WorkbenchExtensions.Workbench); workbench.registerWorkbenchContribution(ProductContribution, LifecyclePhase.Restored); workbench.registerWorkbenchContribution(UpdateContribution, LifecyclePhase.Restored); +const actionRegistry = Registry.as(ActionExtensions.WorkbenchActions); + // Editor -Registry.as(ActionExtensions.WorkbenchActions) - .registerWorkbenchAction(new SyncActionDescriptor(ShowCurrentReleaseNotesAction, ShowCurrentReleaseNotesAction.ID, ShowCurrentReleaseNotesAction.LABEL), 'Show Release Notes'); +actionRegistry + .registerWorkbenchAction(SyncActionDescriptor.create(ShowCurrentReleaseNotesAction, ShowCurrentReleaseNotesAction.ID, ShowCurrentReleaseNotesAction.LABEL), `${product.nameShort}: Show Release Notes`, product.nameShort); + +actionRegistry + .registerWorkbenchAction(SyncActionDescriptor.create(CheckForVSCodeUpdateAction, CheckForVSCodeUpdateAction.ID, CheckForVSCodeUpdateAction.LABEL), `${product.nameShort}: Check for Update`, product.nameShort, CONTEXT_UPDATE_STATE.isEqualTo(StateType.Idle)); + +// Menu +if (ShowCurrentReleaseNotesAction.AVAILABE) { + MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { + group: '1_welcome', + command: { + id: ShowCurrentReleaseNotesAction.ID, + title: localize({ key: 'miReleaseNotes', comment: ['&& denotes a mnemonic'] }, "&&Release Notes") + }, + order: 4 + }); +} diff --git a/src/vs/workbench/contrib/update/browser/update.ts b/src/vs/workbench/contrib/update/browser/update.ts index 7b364f3999dc..3445523f9a41 100644 --- a/src/vs/workbench/contrib/update/browser/update.ts +++ b/src/vs/workbench/contrib/update/browser/update.ts @@ -9,7 +9,7 @@ import { Action } from 'vs/base/common/actions'; import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { IActivityService, NumberBadge, IBadge, ProgressBadge } from 'vs/workbench/services/activity/common/activity'; -import { IInstantiationService, optional } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { GLOBAL_ACTIVITY_ID } from 'vs/workbench/common/activity'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; @@ -26,15 +26,12 @@ import { RawContextKey, IContextKey, IContextKeyService } from 'vs/platform/cont import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { FalseContext } from 'vs/platform/contextkey/common/contextkeys'; -import { ShowCurrentReleaseNotesActionId } from 'vs/workbench/contrib/update/common/update'; +import { ShowCurrentReleaseNotesActionId, CheckForVSCodeUpdateActionId } from 'vs/workbench/contrib/update/common/update'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IProductService } from 'vs/platform/product/common/productService'; +import product from 'vs/platform/product/common/product'; -// TODO@Joao layer breaker -// tslint:disable-next-line: layering -import { IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService'; - -const CONTEXT_UPDATE_STATE = new RawContextKey('updateState', StateType.Uninitialized); +export const CONTEXT_UPDATE_STATE = new RawContextKey('updateState', StateType.Idle); /*let releaseNotesManager: ReleaseNotesManager | undefined = undefined; {{SQL CARBON EDIT}} comment out for no unused @@ -104,6 +101,7 @@ export class ShowCurrentReleaseNotesAction extends AbstractShowReleaseNotesActio static readonly ID = ShowCurrentReleaseNotesActionId; static readonly LABEL = nls.localize('showReleaseNotes', "Show Release Notes"); + static readonly AVAILABE = !!product.releaseNotesUrl; constructor( id = ShowCurrentReleaseNotesAction.ID, @@ -138,7 +136,7 @@ export class ProductContribution implements IWorkbenchContribution { // was there an update? if so, open release notes const releaseNotesUrl = productService.releaseNotesUrl; - if (shouldShowReleaseNotes && !environmentService.skipReleaseNotes && releaseNotesUrl && lastVersion && productService.version !== lastVersion && productService.quality === 'stable') { // {{SQL CARBON EDIT}} Only show release notes for stable build + if (shouldShowReleaseNotes && !environmentService.args['skip-release-notes'] && releaseNotesUrl && lastVersion && productService.version !== lastVersion && productService.quality === 'stable') { // {{SQL CARBON EDIT}} Only show release notes for stable build /* // {{SQL CARBON EDIT}} Prompt user to open release notes in browser until we can get ADS release notes from the web showReleaseNotes(instantiationService, productService.version) .then(undefined, () => { @@ -173,8 +171,6 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu private readonly badgeDisposable = this._register(new MutableDisposable()); private updateStateContextKey: IContextKey; - private context = `window:${this.electronEnvironmentService ? this.electronEnvironmentService.windowId : 'any'}`; - constructor( @IStorageService private readonly storageService: IStorageService, @IInstantiationService private readonly instantiationService: IInstantiationService, @@ -184,7 +180,7 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu @IActivityService private readonly activityService: IActivityService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IProductService private readonly productService: IProductService, - @optional(IElectronEnvironmentService) private readonly electronEnvironmentService: IElectronEnvironmentService + @IWorkbenchEnvironmentService private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService ) { super(); this.state = updateService.state; @@ -220,7 +216,7 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu case StateType.Idle: if (state.error) { this.onError(state.error); - } else if (this.state.type === StateType.CheckingForUpdates && this.state.context === this.context) { + } else if (this.state.type === StateType.CheckingForUpdates && this.state.context === this.workbenchEnvironmentService.configuration.sessionId) { this.onUpdateNotAvailable(); } break; @@ -244,18 +240,20 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu let badge: IBadge | undefined = undefined; let clazz: string | undefined; + let priority: number | undefined = undefined; if (state.type === StateType.AvailableForDownload || state.type === StateType.Downloaded || state.type === StateType.Ready) { badge = new NumberBadge(1, () => nls.localize('updateIsReady', "New {0} update available.", this.productService.nameLong)); // {{SQL CARBON EDIT}} change to namelong } else if (state.type === StateType.CheckingForUpdates || state.type === StateType.Downloading || state.type === StateType.Updating) { badge = new ProgressBadge(() => nls.localize('updateIsReady', "New {0} update available.", this.productService.nameLong)); // {{SQL CARBON EDIT}} change to namelong clazz = 'progress-badge'; + priority = 1; } this.badgeDisposable.clear(); if (badge) { - this.badgeDisposable.value = this.activityService.showActivity(GLOBAL_ACTIVITY_ID, badge, clazz); + this.badgeDisposable.value = this.activityService.showActivity(GLOBAL_ACTIVITY_ID, badge, clazz, priority); } this.state = state; @@ -403,7 +401,7 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu } private registerGlobalActivityActions(): void { - CommandsRegistry.registerCommand('update.check', () => this.updateService.checkForUpdates(this.context)); + CommandsRegistry.registerCommand('update.check', () => this.updateService.checkForUpdates(this.workbenchEnvironmentService.configuration.sessionId)); MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { group: '6_update', command: { @@ -477,3 +475,23 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu }); } } + +export class CheckForVSCodeUpdateAction extends Action { + + static readonly ID = CheckForVSCodeUpdateActionId; + static LABEL = nls.localize('checkForUpdates', "Check for Updates..."); + + constructor( + id: string, + label: string, + @IWorkbenchEnvironmentService private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService, + @IUpdateService private readonly updateService: IUpdateService, + ) { + super(id, label, undefined, true); + } + + run(): Promise { + return this.updateService.checkForUpdates(this.workbenchEnvironmentService.configuration.sessionId); + } +} + diff --git a/src/vs/workbench/contrib/update/common/update.ts b/src/vs/workbench/contrib/update/common/update.ts index 898424cfa921..9a06a7707291 100644 --- a/src/vs/workbench/contrib/update/common/update.ts +++ b/src/vs/workbench/contrib/update/common/update.ts @@ -3,4 +3,5 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -export const ShowCurrentReleaseNotesActionId = 'update.showCurrentReleaseNotes'; \ No newline at end of file +export const ShowCurrentReleaseNotesActionId = 'update.showCurrentReleaseNotes'; +export const CheckForVSCodeUpdateActionId = 'update.checkForVSCodeUpdate'; \ No newline at end of file diff --git a/src/vs/workbench/contrib/url/common/trustedDomains.ts b/src/vs/workbench/contrib/url/common/trustedDomains.ts index 1cb1b7d4286b..687308346485 100644 --- a/src/vs/workbench/contrib/url/common/trustedDomains.ts +++ b/src/vs/workbench/contrib/url/common/trustedDomains.ts @@ -76,6 +76,7 @@ export async function configureOpenerTrustedDomainsHandler( return trustedDomains; } if (pickedResult.id && trustedDomains.indexOf(pickedResult.id) === -1) { + storageService.remove('http.linkProtectionTrustedDomainsContent', StorageScope.GLOBAL); storageService.store( 'http.linkProtectionTrustedDomains', JSON.stringify([...trustedDomains, pickedResult.id]), diff --git a/src/vs/workbench/contrib/url/common/trustedDomainsFileSystemProvider.ts b/src/vs/workbench/contrib/url/common/trustedDomainsFileSystemProvider.ts index 56e39a613739..3d619239d62c 100644 --- a/src/vs/workbench/contrib/url/common/trustedDomainsFileSystemProvider.ts +++ b/src/vs/workbench/contrib/url/common/trustedDomainsFileSystemProvider.ts @@ -7,17 +7,7 @@ import { Event } from 'vs/base/common/event'; import { parse } from 'vs/base/common/json'; import { IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; -import { - FileDeleteOptions, - FileOverwriteOptions, - FileSystemProviderCapabilities, - FileType, - FileWriteOptions, - IFileService, - IFileSystemProvider, - IStat, - IWatchOptions -} from 'vs/platform/files/common/files'; +import { FileDeleteOptions, FileOverwriteOptions, FileSystemProviderCapabilities, FileType, FileWriteOptions, IFileService, IStat, IWatchOptions, IFileSystemProviderWithFileReadWriteCapability } from 'vs/platform/files/common/files'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { VSBuffer } from 'vs/base/common/buffer'; @@ -36,6 +26,8 @@ const TRUSTED_DOMAINS_STAT: IStat = { const CONFIG_HELP_TEXT_PRE = `// Links matching one or more entries in the list below can be opened without link protection. // The following examples show what entries can look like: // - "https://microsoft.com": Matches this specific domain using https +// - "https://microsoft.com/foo": Matches https://microsoft.com/foo and https://microsoft.com/foo/bar, +// but not https://microsoft.com/foobar or https://microsoft.com/bar // - "https://*.microsoft.com": Match all domains ending in "microsoft.com" using https // - "microsoft.com": Match this specific domain using either http or https // - "*.microsoft.com": Match all domains ending in "microsoft.com" using either http or https @@ -75,7 +67,7 @@ function computeTrustedDomainContent(defaultTrustedDomains: string[], trustedDom return content; } -export class TrustedDomainsFileSystemProvider implements IFileSystemProvider, IWorkbenchContribution { +export class TrustedDomainsFileSystemProvider implements IFileSystemProviderWithFileReadWriteCapability, IWorkbenchContribution { readonly capabilities = FileSystemProviderCapabilities.FileReadWrite; readonly onDidChangeCapabilities = Event.None; @@ -115,13 +107,13 @@ export class TrustedDomainsFileSystemProvider implements IFileSystemProvider, IW writeFile(resource: URI, content: Uint8Array, opts: FileWriteOptions): Promise { try { - const trustedDomainsContent = content.toString(); + const trustedDomainsContent = VSBuffer.wrap(content).toString(); const trustedDomains = parse(trustedDomainsContent); this.storageService.store('http.linkProtectionTrustedDomainsContent', trustedDomainsContent, StorageScope.GLOBAL); this.storageService.store( 'http.linkProtectionTrustedDomains', - JSON.stringify(trustedDomains), + JSON.stringify(trustedDomains) || '', StorageScope.GLOBAL ); } catch (err) { } diff --git a/src/vs/workbench/contrib/url/common/trustedDomainsValidator.ts b/src/vs/workbench/contrib/url/common/trustedDomainsValidator.ts index f774c2a940da..eb5f25f99b3a 100644 --- a/src/vs/workbench/contrib/url/common/trustedDomainsValidator.ts +++ b/src/vs/workbench/contrib/url/common/trustedDomainsValidator.ts @@ -5,17 +5,21 @@ import { Schemas } from 'vs/base/common/network'; import Severity from 'vs/base/common/severity'; -import { equalsIgnoreCase } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IOpenerService, matchesScheme } from 'vs/platform/opener/common/opener'; import { IProductService } from 'vs/platform/product/common/productService'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { configureOpenerTrustedDomainsHandler, readTrustedDomains } from 'vs/workbench/contrib/url/common/trustedDomains'; +import { + configureOpenerTrustedDomainsHandler, + readTrustedDomains +} from 'vs/workbench/contrib/url/common/trustedDomains'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; + export class OpenerValidatorContributions implements IWorkbenchContribution { constructor( @@ -24,18 +28,22 @@ export class OpenerValidatorContributions implements IWorkbenchContribution { @IDialogService private readonly _dialogService: IDialogService, @IProductService private readonly _productService: IProductService, @IQuickInputService private readonly _quickInputService: IQuickInputService, - @IEditorService private readonly _editorService: IEditorService + @IEditorService private readonly _editorService: IEditorService, + @IClipboardService private readonly _clipboardService: IClipboardService ) { this._openerService.registerValidator({ shouldOpen: r => this.validateLink(r) }); } - async validateLink(resource: URI): Promise { - const { scheme, authority } = resource; - - if (!equalsIgnoreCase(scheme, Schemas.http) && !equalsIgnoreCase(scheme, Schemas.https)) { + async validateLink(resource: URI | string): Promise { + if (!matchesScheme(resource, Schemas.http) && !matchesScheme(resource, Schemas.https)) { return true; } + if (typeof resource === 'string') { + resource = URI.parse(resource); + } + const { scheme, authority, path, query, fragment } = resource; + const domainToOpen = `${scheme}://${authority}`; const { defaultTrustedDomains, trustedDomains } = readTrustedDomains(this._storageService, this._productService); const allTrustedDomains = [...defaultTrustedDomains, ...trustedDomains]; @@ -43,21 +51,38 @@ export class OpenerValidatorContributions implements IWorkbenchContribution { if (isURLDomainTrusted(resource, allTrustedDomains)) { return true; } else { + let formattedLink = `${scheme}://${authority}${path}`; + + const linkTail = `${query ? '?' + query : ''}${fragment ? '#' + fragment : ''}`; + + + const remainingLength = Math.max(0, 60 - formattedLink.length); + const linkTailLengthToKeep = Math.min(Math.max(5, remainingLength), linkTail.length); + + if (linkTailLengthToKeep === linkTail.length) { + formattedLink += linkTail; + } else { + // keep the first char ? or # + // add ... and keep the tail end as much as possible + formattedLink += linkTail.charAt(0) + '...' + linkTail.substring(linkTail.length - linkTailLengthToKeep + 1); + } + const { choice } = await this._dialogService.show( Severity.Info, localize( 'openExternalLinkAt', - 'Do you want {0} to open the external website?\n{1}', - this._productService.nameShort, - resource.toString(true) + 'Do you want {0} to open the external website?', + this._productService.nameShort ), [ - localize('openLink', 'Open Link'), + localize('open', 'Open'), + localize('copy', 'Copy'), localize('cancel', 'Cancel'), localize('configureTrustedDomains', 'Configure Trusted Domains') ], { - cancelId: 1 + detail: formattedLink, + cancelId: 2 } ); @@ -65,8 +90,12 @@ export class OpenerValidatorContributions implements IWorkbenchContribution { if (choice === 0) { return true; } + // Copy Link + else if (choice === 1) { + this._clipboardService.writeText(resource.toString(true)); + } // Configure Trusted Domains - else if (choice === 2) { + else if (choice === 3) { const pickedDomains = await configureOpenerTrustedDomainsHandler( trustedDomains, domainToOpen, @@ -132,10 +161,15 @@ export function isURLDomainTrusted(url: URI, trustedDomains: string[]) { } if (url.authority === parsedTrustedDomain.authority) { - return true; + if (pathMatches(url.path, parsedTrustedDomain.path)) { + return true; + } else { + continue; + } } if (trustedDomains[i].indexOf('*') !== -1) { + let reversedAuthoritySegments = url.authority.split('.').reverse(); const reversedTrustedDomainAuthoritySegments = parsedTrustedDomain.authority.split('.').reverse(); @@ -146,11 +180,11 @@ export function isURLDomainTrusted(url: URI, trustedDomains: string[]) { reversedAuthoritySegments = reversedAuthoritySegments.slice(0, reversedTrustedDomainAuthoritySegments.length); } - if ( - reversedAuthoritySegments.every((val, i) => { - return reversedTrustedDomainAuthoritySegments[i] === '*' || val === reversedTrustedDomainAuthoritySegments[i]; - }) - ) { + const authorityMatches = reversedAuthoritySegments.every((val, i) => { + return reversedTrustedDomainAuthoritySegments[i] === '*' || val === reversedTrustedDomainAuthoritySegments[i]; + }); + + if (authorityMatches && pathMatches(url.path, parsedTrustedDomain.path)) { return true; } } @@ -158,3 +192,19 @@ export function isURLDomainTrusted(url: URI, trustedDomains: string[]) { return false; } + +function pathMatches(open: string, rule: string) { + if (rule === '/') { + return true; + } + + const openSegments = open.split('/'); + const ruleSegments = rule.split('/'); + for (let i = 0; i < ruleSegments.length; i++) { + if (ruleSegments[i] !== openSegments[i]) { + return false; + } + } + + return true; +} diff --git a/src/vs/workbench/contrib/url/common/url.contribution.ts b/src/vs/workbench/contrib/url/common/url.contribution.ts index 550feb03957b..ef0bea94c56b 100644 --- a/src/vs/workbench/contrib/url/common/url.contribution.ts +++ b/src/vs/workbench/contrib/url/common/url.contribution.ts @@ -41,7 +41,7 @@ export class OpenUrlAction extends Action { } Registry.as(ActionExtensions.WorkbenchActions).registerWorkbenchAction( - new SyncActionDescriptor(OpenUrlAction, OpenUrlAction.ID, OpenUrlAction.LABEL), + SyncActionDescriptor.create(OpenUrlAction, OpenUrlAction.ID, OpenUrlAction.LABEL), 'Open URL', localize('developer', 'Developer') ); @@ -54,7 +54,10 @@ CommandsRegistry.registerCommand(manageTrustedDomainSettingsCommand); MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: manageTrustedDomainSettingsCommand.id, - title: manageTrustedDomainSettingsCommand.description.description + title: { + value: manageTrustedDomainSettingsCommand.description.description, + original: 'Manage Trusted Domains' + } } }); diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataAutoSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataAutoSync.ts new file mode 100644 index 000000000000..2b7c856d777d --- /dev/null +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataAutoSync.ts @@ -0,0 +1,35 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IUserDataSyncService, IUserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IAuthTokenService } from 'vs/platform/auth/common/auth'; +import { Event } from 'vs/base/common/event'; +import { UserDataAutoSync as BaseUserDataAutoSync } from 'vs/platform/userDataSync/common/userDataAutoSync'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { UserDataSyncTrigger } from 'vs/workbench/contrib/userDataSync/browser/userDataSyncTrigger'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; + +export class UserDataAutoSync extends BaseUserDataAutoSync { + + constructor( + @IUserDataSyncService userDataSyncService: IUserDataSyncService, + @IConfigurationService configurationService: IConfigurationService, + @IUserDataSyncLogService logService: IUserDataSyncLogService, + @IAuthTokenService authTokenService: IAuthTokenService, + @IInstantiationService instantiationService: IInstantiationService, + @IHostService hostService: IHostService, + ) { + super(configurationService, userDataSyncService, logService, authTokenService); + + // Sync immediately if there is a local change. + this._register(Event.debounce(Event.any( + userDataSyncService.onDidChangeLocal, + instantiationService.createInstance(UserDataSyncTrigger).onDidTriggerSync, + hostService.onDidChangeFocus + ), () => undefined, 500)(() => this.sync(false))); + } + +} diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.contribution.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.contribution.ts index a5e1edba2142..2c9482fb8c2d 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.contribution.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.contribution.ts @@ -3,42 +3,10 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { registerConfiguration } from 'vs/platform/userDataSync/common/userDataSync'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { Registry } from 'vs/platform/registry/common/platform'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { isWeb } from 'vs/base/common/platform'; -import { UserDataAutoSync } from 'vs/platform/userDataSync/common/userDataSyncService'; -import { IProductService } from 'vs/platform/product/common/productService'; import { UserDataSyncWorkbenchContribution } from 'vs/workbench/contrib/userDataSync/browser/userDataSync'; -class UserDataSyncConfigurationContribution implements IWorkbenchContribution { - - constructor( - @IProductService productService: IProductService - ) { - if (productService.settingsSyncStoreUrl) { - registerConfiguration(); - } - } -} - -class UserDataAutoSyncContribution extends Disposable implements IWorkbenchContribution { - - constructor( - @IInstantiationService instantiationService: IInstantiationService - ) { - super(); - if (isWeb) { - instantiationService.createInstance(UserDataAutoSync); - } - } -} - - const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); -workbenchRegistry.registerWorkbenchContribution(UserDataSyncConfigurationContribution, LifecyclePhase.Starting); -workbenchRegistry.registerWorkbenchContribution(UserDataSyncWorkbenchContribution, LifecyclePhase.Restored); -workbenchRegistry.registerWorkbenchContribution(UserDataAutoSyncContribution, LifecyclePhase.Restored); +workbenchRegistry.registerWorkbenchContribution(UserDataSyncWorkbenchContribution, LifecyclePhase.Ready); diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 0a77ca5b0e49..9fef4396e2a8 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -4,9 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { IUserDataSyncService, SyncStatus, SyncSource, CONTEXT_SYNC_STATE } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncService, SyncStatus, SyncSource, CONTEXT_SYNC_STATE, IUserDataSyncStore, registerConfiguration, getUserDataSyncStore } from 'vs/platform/userDataSync/common/userDataSync'; import { localize } from 'vs/nls'; -import { Disposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, MutableDisposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { MenuRegistry, MenuId, IMenuItem } from 'vs/platform/actions/common/actions'; @@ -25,15 +25,24 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ import { isEqual } from 'vs/base/common/resources'; import { IEditorInput } from 'vs/workbench/common/editor'; import { IAuthTokenService, AuthTokenStatus } from 'vs/platform/auth/common/auth'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { FalseContext } from 'vs/platform/contextkey/common/contextkeys'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { isWeb } from 'vs/base/common/platform'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { UserDataAutoSync } from 'vs/workbench/contrib/userDataSync/browser/userDataAutoSync'; +import { UserDataSyncTrigger } from 'vs/workbench/contrib/userDataSync/browser/userDataSyncTrigger'; import { timeout } from 'vs/base/common/async'; -const CONTEXT_AUTH_TOKEN_STATE = new RawContextKey('authTokenStatus', AuthTokenStatus.Inactive); +const CONTEXT_AUTH_TOKEN_STATE = new RawContextKey('authTokenStatus', AuthTokenStatus.Initializing); const SYNC_PUSH_LIGHT_ICON_URI = URI.parse(registerAndGetAmdImageURL(`vs/workbench/contrib/userDataSync/browser/media/check-light.svg`)); const SYNC_PUSH_DARK_ICON_URI = URI.parse(registerAndGetAmdImageURL(`vs/workbench/contrib/userDataSync/browser/media/check-dark.svg`)); export class UserDataSyncWorkbenchContribution extends Disposable implements IWorkbenchContribution { + private static readonly ENABLEMENT_SETTING = 'sync.enable'; + + private readonly userDataSyncStore: IUserDataSyncStore | undefined; private readonly syncStatusContext: IContextKey; private readonly authTokenContext: IContextKey; private readonly badgeDisposable = this._register(new MutableDisposable()); @@ -51,29 +60,43 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo @ITextFileService private readonly textFileService: ITextFileService, @IHistoryService private readonly historyService: IHistoryService, @IWorkbenchEnvironmentService private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService, + @IDialogService private readonly dialogService: IDialogService, @IQuickInputService private readonly quickInputService: IQuickInputService, + @IInstantiationService instantiationService: IInstantiationService, ) { super(); + this.userDataSyncStore = getUserDataSyncStore(configurationService); this.syncStatusContext = CONTEXT_SYNC_STATE.bindTo(contextKeyService); this.authTokenContext = CONTEXT_AUTH_TOKEN_STATE.bindTo(contextKeyService); - this.onDidChangeAuthTokenStatus(this.authTokenService.status); - this.onDidChangeSyncStatus(this.userDataSyncService.status); - this._register(Event.debounce(authTokenService.onDidChangeStatus, () => undefined, 500)(() => this.onDidChangeAuthTokenStatus(this.authTokenService.status))); - this._register(Event.debounce(userDataSyncService.onDidChangeStatus, () => undefined, 500)(() => this.onDidChangeSyncStatus(this.userDataSyncService.status))); - this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('configurationSync.enable'))(() => this.updateBadge())); - this.registerActions(); + if (this.userDataSyncStore) { + registerConfiguration(); + this.onDidChangeAuthTokenStatus(this.authTokenService.status); + this.onDidChangeSyncStatus(this.userDataSyncService.status); + this._register(Event.debounce(authTokenService.onDidChangeStatus, () => undefined, 500)(() => this.onDidChangeAuthTokenStatus(this.authTokenService.status))); + this._register(Event.debounce(userDataSyncService.onDidChangeStatus, () => undefined, 500)(() => this.onDidChangeSyncStatus(this.userDataSyncService.status))); + this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration(UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING))(() => this.onDidChangeEnablement())); + this.registerActions(); - timeout(2000).then(() => { - if (this.authTokenService.status === AuthTokenStatus.Inactive && configurationService.getValue('configurationSync.enable')) { - this.showSignInNotification(); + if (isWeb) { + this._register(instantiationService.createInstance(UserDataAutoSync)); + } else { + this._register(instantiationService.createInstance(UserDataSyncTrigger).onDidTriggerSync(() => this.triggerSync())); } - }); + } + } + + private triggerSync(): void { + if (this.configurationService.getValue('sync.enable') + && this.userDataSyncService.status !== SyncStatus.Uninitialized + && this.authTokenService.status === AuthTokenStatus.SignedIn) { + this.userDataSyncService.sync(); + } } private onDidChangeAuthTokenStatus(status: AuthTokenStatus) { this.authTokenContext.set(status); - if (status === AuthTokenStatus.Active) { + if (status === AuthTokenStatus.SignedIn) { this.signInNotificationDisposable.clear(); } this.updateBadge(); @@ -82,7 +105,12 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo private onDidChangeSyncStatus(status: SyncStatus) { this.syncStatusContext.set(status); - this.updateBadge(); + if (status === SyncStatus.Syncing) { + // Show syncing progress if takes more than 1s. + timeout(1000).then(() => this.updateBadge()); + } else { + this.updateBadge(); + } if (this.userDataSyncService.status === SyncStatus.HasConflicts) { if (!this.conflictsWarningDisposable.value) { @@ -105,47 +133,124 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } } + private onDidChangeEnablement() { + this.updateBadge(); + const enabled = this.configurationService.getValue(UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING); + if (enabled) { + if (this.authTokenService.status === AuthTokenStatus.SignedOut) { + const handle = this.notificationService.prompt(Severity.Info, localize('ask to sign in', "Please sign in with your {0} account to sync configuration across all your machines", this.userDataSyncStore!.account), + [ + { + label: localize('Sign in', "Sign in"), + run: () => this.signIn() + } + ]); + this.signInNotificationDisposable.value = toDisposable(() => handle.close()); + handle.onDidClose(() => this.signInNotificationDisposable.clear()); + } + } else { + this.signInNotificationDisposable.clear(); + } + } + private updateBadge(): void { this.badgeDisposable.clear(); let badge: IBadge | undefined = undefined; let clazz: string | undefined; + let priority: number | undefined = undefined; - if (this.authTokenService.status === AuthTokenStatus.Inactive && this.configurationService.getValue('configurationSync.enable')) { - badge = new NumberBadge(1, () => localize('sign in', "Sign in...")); + if (this.userDataSyncService.status !== SyncStatus.Uninitialized && this.configurationService.getValue(UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING) && this.authTokenService.status === AuthTokenStatus.SignedOut) { + badge = new NumberBadge(1, () => localize('sign in', "Sync: Sign in...")); + } else if (this.authTokenService.status === AuthTokenStatus.SigningIn) { + badge = new ProgressBadge(() => localize('signing in', "Signin in...")); + clazz = 'progress-badge'; + priority = 1; } else if (this.userDataSyncService.status === SyncStatus.HasConflicts) { badge = new NumberBadge(1, () => localize('resolve conflicts', "Resolve Conflicts")); } else if (this.userDataSyncService.status === SyncStatus.Syncing) { badge = new ProgressBadge(() => localize('syncing', "Synchronizing User Configuration...")); clazz = 'progress-badge'; + priority = 1; } if (badge) { - this.badgeDisposable.value = this.activityService.showActivity(GLOBAL_ACTIVITY_ID, badge, clazz); + this.badgeDisposable.value = this.activityService.showActivity(GLOBAL_ACTIVITY_ID, badge, clazz, priority); + } + } + + private async turnOn(): Promise { + if (this.authTokenService.status === AuthTokenStatus.SignedOut) { + const result = await this.dialogService.confirm({ + type: 'info', + message: localize('sign in to account', "Sign in to {0}", this.userDataSyncStore!.name), + detail: localize('ask to sign in', "Please sign in with your {0} account to sync configuration across all your machines", this.userDataSyncStore!.account), + primaryButton: localize('Sign in', "Sign in") + }); + if (!result.confirmed) { + return; + } + await this.signIn(); } + await this.configureSyncOptions(); + await this.configurationService.updateValue(UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING, true); + this.notificationService.info(localize('Sync Started', "Sync Started.")); } - private showSignInNotification(): void { - const handle = this.notificationService.prompt(Severity.Info, localize('show sign in', "Please sign in to Settings Sync service to start syncing configuration."), - [ - { - label: localize('sign in', "Sign in..."), - run: () => this.signIn() + private async configureSyncOptions(): Promise { + return new Promise((c, e) => { + const disposables: DisposableStore = new DisposableStore(); + const quickPick = this.quickInputService.createQuickPick(); + disposables.add(quickPick); + quickPick.title = localize('configure sync title', "Sync: Configure"); + quickPick.placeholder = localize('select configurations to sync', "Choose what to sync"); + quickPick.canSelectMany = true; + quickPick.ignoreFocusOut = true; + const items = [{ + id: 'sync.enableSettings', + label: localize('user settings', "User Settings") + }, { + id: 'sync.enableKeybindings', + label: localize('user keybindings', "User Keybindings") + }, { + id: 'sync.enableExtensions', + label: localize('extensions', "Extensions") + }]; + quickPick.items = items; + quickPick.selectedItems = items.filter(item => this.configurationService.getValue(item.id)); + disposables.add(quickPick.onDidAccept(() => { + for (const item of items) { + const wasEnabled = this.configurationService.getValue(item.id); + const isEnabled = !!quickPick.selectedItems.filter(selected => selected.id === item.id)[0]; + if (wasEnabled !== isEnabled) { + this.configurationService.updateValue(item.id!, isEnabled); + } } - ]); - this.signInNotificationDisposable.value = toDisposable(() => handle.close()); - handle.onDidClose(() => this.signInNotificationDisposable.clear()); + quickPick.hide(); + })); + disposables.add(quickPick.onDidHide(() => { + disposables.dispose(); + c(); + })); + quickPick.show(); + }); + } + + private async turnOff(): Promise { + await this.configurationService.updateValue(UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING, false); } private async signIn(): Promise { - const token = await this.quickInputService.input({ placeHolder: localize('enter token', "Please provide the auth bearer token"), ignoreFocusLost: true, }); - if (token) { - await this.authTokenService.updateToken(token); + try { + await this.authTokenService.login(); + } catch (e) { + this.notificationService.error(e); + throw e; } } private async signOut(): Promise { - await this.authTokenService.deleteToken(); + await this.authTokenService.logout(); } private async continueSync(): Promise { @@ -169,13 +274,14 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } private getPreviewEditorInput(): IEditorInput | undefined { - return this.editorService.editors.filter(input => isEqual(input.getResource(), this.workbenchEnvironmentService.settingsSyncPreviewResource))[0]; + return this.editorService.editors.filter(input => isEqual(input.getResource(), this.workbenchEnvironmentService.settingsSyncPreviewResource) || isEqual(input.getResource(), this.workbenchEnvironmentService.keybindingsSyncPreviewResource))[0]; } private async handleConflicts(): Promise { - if (this.userDataSyncService.conflictsSource === SyncSource.Settings) { + const conflictsResource = this.getConflictsResource(); + if (conflictsResource) { const resourceInput = { - resource: this.workbenchEnvironmentService.settingsSyncPreviewResource, + resource: conflictsResource, options: { preserveFocus: false, pinned: false, @@ -197,80 +303,109 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } } + private getConflictsResource(): URI | null { + if (this.userDataSyncService.conflictsSource === SyncSource.Settings) { + return this.workbenchEnvironmentService.settingsSyncPreviewResource; + } + if (this.userDataSyncService.conflictsSource === SyncSource.Keybindings) { + return this.workbenchEnvironmentService.keybindingsSyncPreviewResource; + } + return null; + } + private registerActions(): void { - const signInMenuItem: IMenuItem = { + const startSyncMenuItem: IMenuItem = { group: '5_sync', command: { - id: 'workbench.userData.actions.login', - title: localize('sign in', "Sign in...") + id: 'workbench.userData.actions.syncStart', + title: localize('start sync', "Sync: Turn On") }, - when: ContextKeyExpr.and(CONTEXT_AUTH_TOKEN_STATE.isEqualTo(AuthTokenStatus.Inactive), ContextKeyExpr.has('config.configurationSync.enable')), + when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), ContextKeyExpr.not(`config.${UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING}`), CONTEXT_AUTH_TOKEN_STATE.notEqualsTo(AuthTokenStatus.SigningIn)), }; - CommandsRegistry.registerCommand(signInMenuItem.command.id, () => this.signIn()); - MenuRegistry.appendMenuItem(MenuId.GlobalActivity, signInMenuItem); - MenuRegistry.appendMenuItem(MenuId.CommandPalette, signInMenuItem); + CommandsRegistry.registerCommand(startSyncMenuItem.command.id, () => this.turnOn()); + MenuRegistry.appendMenuItem(MenuId.GlobalActivity, startSyncMenuItem); + MenuRegistry.appendMenuItem(MenuId.CommandPalette, startSyncMenuItem); - const signOutMenuItem: IMenuItem = { + const signInCommandId = 'workbench.userData.actions.signin'; + const signInWhenContext = ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), ContextKeyExpr.has(`config.${UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING}`), CONTEXT_AUTH_TOKEN_STATE.isEqualTo(AuthTokenStatus.SignedOut)); + CommandsRegistry.registerCommand(signInCommandId, () => this.signIn()); + MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { + group: '5_sync', command: { - id: 'workbench.userData.actions.logout', - title: localize('sign out', "Sign Out") + id: signInCommandId, + title: localize('global activity sign in', "Sync: Sign in... (1)") }, - when: ContextKeyExpr.and(CONTEXT_AUTH_TOKEN_STATE.isEqualTo(AuthTokenStatus.Active)), - }; - CommandsRegistry.registerCommand(signOutMenuItem.command.id, () => this.signOut()); - MenuRegistry.appendMenuItem(MenuId.CommandPalette, signOutMenuItem); - - const startSyncMenuItem: IMenuItem = { - group: '5_sync', + when: signInWhenContext, + }); + MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { - id: 'workbench.userData.actions.syncStart', - title: localize('start sync', "Configuration Sync: Turn On") + id: signInCommandId, + title: localize('sign in', "Sync: Sign in...") }, - when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), ContextKeyExpr.not('config.configurationSync.enable')), - }; - CommandsRegistry.registerCommand(startSyncMenuItem.command.id, () => this.configurationService.updateValue('configurationSync.enable', true)); - MenuRegistry.appendMenuItem(MenuId.GlobalActivity, startSyncMenuItem); - MenuRegistry.appendMenuItem(MenuId.CommandPalette, startSyncMenuItem); + when: signInWhenContext, + }); - const stopSyncMenuItem: IMenuItem = { + const signingInCommandId = 'workbench.userData.actions.signingin'; + CommandsRegistry.registerCommand(signingInCommandId, () => null); + MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { group: '5_sync', command: { - id: 'workbench.userData.actions.stopSync', - title: localize('stop sync', "Configuration Sync: Turn Off") + id: signingInCommandId, + title: localize('signinig in', "Sync: Signing in..."), + precondition: FalseContext }, - when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), ContextKeyExpr.has('config.configurationSync.enable')), + when: CONTEXT_AUTH_TOKEN_STATE.isEqualTo(AuthTokenStatus.SigningIn) + }); + + const stopSyncCommand = { + id: 'workbench.userData.actions.stopSync', + title: localize('stop sync', "Sync: Turn Off") }; - CommandsRegistry.registerCommand(stopSyncMenuItem.command.id, () => this.configurationService.updateValue('configurationSync.enable', false)); - MenuRegistry.appendMenuItem(MenuId.GlobalActivity, stopSyncMenuItem); - MenuRegistry.appendMenuItem(MenuId.CommandPalette, stopSyncMenuItem); + CommandsRegistry.registerCommand(stopSyncCommand.id, () => this.turnOff()); + MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { + group: '5_sync', + command: stopSyncCommand, + when: ContextKeyExpr.and(ContextKeyExpr.has(`config.${UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING}`), CONTEXT_AUTH_TOKEN_STATE.isEqualTo(AuthTokenStatus.SignedIn), CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.HasConflicts)) + }); + MenuRegistry.appendMenuItem(MenuId.CommandPalette, { + command: stopSyncCommand, + when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), ContextKeyExpr.has(`config.${UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING}`)), + }); - const resolveConflictsMenuItem: IMenuItem = { + const resolveConflictsCommandId = 'workbench.userData.actions.resolveConflicts'; + const resolveConflictsWhenContext = CONTEXT_SYNC_STATE.isEqualTo(SyncStatus.HasConflicts); + CommandsRegistry.registerCommand(resolveConflictsCommandId, () => this.handleConflicts()); + MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { group: '5_sync', command: { - id: 'sync.resolveConflicts', - title: localize('resolveConflicts', "Configuration Sync: Resolve Conflicts"), + id: resolveConflictsCommandId, + title: localize('resolveConflicts_global', "Sync: Resolve Conflicts (1)"), }, - when: CONTEXT_SYNC_STATE.isEqualTo(SyncStatus.HasConflicts), - }; - CommandsRegistry.registerCommand(resolveConflictsMenuItem.command.id, () => this.handleConflicts()); - MenuRegistry.appendMenuItem(MenuId.GlobalActivity, resolveConflictsMenuItem); - MenuRegistry.appendMenuItem(MenuId.CommandPalette, resolveConflictsMenuItem); + when: resolveConflictsWhenContext, + }); + MenuRegistry.appendMenuItem(MenuId.CommandPalette, { + command: { + id: resolveConflictsCommandId, + title: localize('resolveConflicts', "Sync: Resolve Conflicts"), + }, + when: resolveConflictsWhenContext, + }); const continueSyncCommandId = 'workbench.userData.actions.continueSync'; CommandsRegistry.registerCommand(continueSyncCommandId, () => this.continueSync()); MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: continueSyncCommandId, - title: localize('continue sync', "Configuration Sync: Continue") + title: localize('continue sync', "Sync: Continue") }, when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.isEqualTo(SyncStatus.HasConflicts)), }); MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: continueSyncCommandId, - title: localize('continue sync', "Configuration Sync: Continue"), - iconLocation: { + title: localize('continue sync', "Sync: Continue"), + icon: { light: SYNC_PUSH_LIGHT_ICON_URI, dark: SYNC_PUSH_DARK_ICON_URI } @@ -279,5 +414,29 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo order: 1, when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.isEqualTo(SyncStatus.HasConflicts), ResourceContextKey.Resource.isEqualTo(this.workbenchEnvironmentService.settingsSyncPreviewResource.toString())), }); + MenuRegistry.appendMenuItem(MenuId.EditorTitle, { + command: { + id: continueSyncCommandId, + title: localize('continue sync', "Sync: Continue"), + icon: { + light: SYNC_PUSH_LIGHT_ICON_URI, + dark: SYNC_PUSH_DARK_ICON_URI + } + }, + group: 'navigation', + order: 1, + when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.isEqualTo(SyncStatus.HasConflicts), ResourceContextKey.Resource.isEqualTo(this.workbenchEnvironmentService.keybindingsSyncPreviewResource.toString())), + }); + + const signOutMenuItem: IMenuItem = { + group: '5_sync', + command: { + id: 'workbench.userData.actions.signout', + title: localize('sign out', "Sign Out") + }, + when: ContextKeyExpr.and(CONTEXT_AUTH_TOKEN_STATE.isEqualTo(AuthTokenStatus.SignedIn)), + }; + CommandsRegistry.registerCommand(signOutMenuItem.command.id, () => this.signOut()); + MenuRegistry.appendMenuItem(MenuId.CommandPalette, signOutMenuItem); } } diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncTrigger.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncTrigger.ts new file mode 100644 index 000000000000..84c358ae7eb9 --- /dev/null +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncTrigger.ts @@ -0,0 +1,61 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. 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 { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { SettingsEditor2Input, KeybindingsEditorInput, PreferencesEditorInput } from 'vs/workbench/services/preferences/common/preferencesEditorInput'; +import { isEqual } from 'vs/base/common/resources'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; +import { VIEWLET_ID } from 'vs/workbench/contrib/extensions/common/extensions'; +import { IEditorInput } from 'vs/workbench/common/editor'; +import { IViewlet } from 'vs/workbench/common/viewlet'; + +export class UserDataSyncTrigger extends Disposable { + + private readonly _onDidTriggerSync: Emitter = this._register(new Emitter()); + readonly onDidTriggerSync: Event = this._onDidTriggerSync.event; + + constructor( + @IEditorService editorService: IEditorService, + @IWorkbenchEnvironmentService private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService, + @IViewletService viewletService: IViewletService, + ) { + super(); + this._register(Event.debounce(Event.any( + Event.filter(editorService.onDidActiveEditorChange, () => this.isUserDataEditorInput(editorService.activeEditor)), + Event.filter(viewletService.onDidViewletOpen, viewlet => this.isUserDataViewlet(viewlet)) + ), () => undefined, 500)(() => this._onDidTriggerSync.fire())); + } + + private isUserDataViewlet(viewlet: IViewlet): boolean { + return viewlet.getId() === VIEWLET_ID; + } + + private isUserDataEditorInput(editorInput: IEditorInput | undefined): boolean { + if (!editorInput) { + return false; + } + if (editorInput instanceof SettingsEditor2Input) { + return true; + } + if (editorInput instanceof PreferencesEditorInput) { + return true; + } + if (editorInput instanceof KeybindingsEditorInput) { + return true; + } + const resource = editorInput.getResource(); + if (isEqual(resource, this.workbenchEnvironmentService.settingsResource)) { + return true; + } + if (isEqual(resource, this.workbenchEnvironmentService.keybindingsResource)) { + return true; + } + return false; + } +} + diff --git a/src/vs/workbench/contrib/userDataSync/electron-browser/userDataSync.contribution.ts b/src/vs/workbench/contrib/userDataSync/electron-browser/userDataSync.contribution.ts index 6ffa857b3055..752167375062 100644 --- a/src/vs/workbench/contrib/userDataSync/electron-browser/userDataSync.contribution.ts +++ b/src/vs/workbench/contrib/userDataSync/electron-browser/userDataSync.contribution.ts @@ -4,19 +4,22 @@ *--------------------------------------------------------------------------------------------*/ import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { ISettingsMergeService } from 'vs/platform/userDataSync/common/userDataSync'; +import { ISettingsMergeService, IUserDataSyncUtilService } from 'vs/platform/userDataSync/common/userDataSync'; import { Registry } from 'vs/platform/registry/common/platform'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; import { SettingsMergeChannel } from 'vs/platform/userDataSync/common/settingsSyncIpc'; +import { UserDataSycnUtilServiceChannel } from 'vs/platform/userDataSync/common/keybindingsSyncIpc'; class UserDataSyncServicesContribution implements IWorkbenchContribution { constructor( @ISettingsMergeService settingsMergeService: ISettingsMergeService, + @IUserDataSyncUtilService userDataSyncUtilService: IUserDataSyncUtilService, @ISharedProcessService sharedProcessService: ISharedProcessService, ) { sharedProcessService.registerChannel('settingsMerge', new SettingsMergeChannel(settingsMergeService)); + sharedProcessService.registerChannel('userDataSyncUtil', new UserDataSycnUtilServiceChannel(userDataSyncUtilService)); } } diff --git a/src/vs/workbench/contrib/watermark/browser/watermark.ts b/src/vs/workbench/contrib/watermark/browser/watermark.ts index 5f00c6693ad5..d1755926caba 100644 --- a/src/vs/workbench/contrib/watermark/browser/watermark.ts +++ b/src/vs/workbench/contrib/watermark/browser/watermark.ts @@ -29,6 +29,7 @@ import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IDimension } from 'vs/platform/layout/browser/layoutService'; // import { TERMINAL_COMMAND_ID } from 'vs/workbench/contrib/terminal/common/terminal'; import { assertIsDefined } from 'vs/base/common/types'; +import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration'; // {{SQL CARBON EDIT}} import { NewNotebookAction } from 'sql/workbench/contrib/notebook/browser/notebookActions'; @@ -196,9 +197,7 @@ Registry.as(WorkbenchExtensions.Workbench) Registry.as(ConfigurationExtensions.Configuration) .registerConfiguration({ - 'id': 'workbench', - 'order': 7, - 'title': nls.localize('workbenchConfigurationTitle', "Workbench"), + ...workbenchConfigurationNodeBase, 'properties': { 'workbench.tips.enabled': { 'type': 'boolean', diff --git a/src/vs/workbench/contrib/webview/browser/baseWebviewElement.ts b/src/vs/workbench/contrib/webview/browser/baseWebviewElement.ts index 45e296b7a1a4..82d33c601138 100644 --- a/src/vs/workbench/contrib/webview/browser/baseWebviewElement.ts +++ b/src/vs/workbench/contrib/webview/browser/baseWebviewElement.ts @@ -10,7 +10,6 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { WebviewExtensionDescription, WebviewOptions, WebviewContentOptions } from 'vs/workbench/contrib/webview/browser/webview'; -import { URI } from 'vs/base/common/uri'; import { areWebviewInputOptionsEqual } from 'vs/workbench/contrib/webview/browser/webviewWorkbenchService'; import { WebviewThemeDataProvider } from 'vs/workbench/contrib/webview/common/themeing'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -50,8 +49,8 @@ export abstract class BaseWebview extends Disposable { private _element: T | undefined; protected get element(): T | undefined { return this._element; } - private _focused: boolean; - protected get focused(): boolean { return this._focused; } + private _focused: boolean | undefined; + protected get focused(): boolean { return !!this._focused; } private readonly _ready: Promise; @@ -94,7 +93,7 @@ export abstract class BaseWebview extends Disposable { })); this._register(this.on(WebviewMessageChannels.didClickLink, (uri: string) => { - this._onDidClickLink.fire(URI.parse(uri)); + this._onDidClickLink.fire(uri); })); this._register(this.on(WebviewMessageChannels.onmessage, (data: any) => { @@ -145,7 +144,7 @@ export abstract class BaseWebview extends Disposable { private readonly _onMissingCsp = this._register(new Emitter()); public readonly onMissingCsp = this._onMissingCsp.event; - private readonly _onDidClickLink = this._register(new Emitter()); + private readonly _onDidClickLink = this._register(new Emitter()); public readonly onDidClickLink = this._onDidClickLink.event; private readonly _onMessage = this._register(new Emitter()); diff --git a/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts b/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts index 260b800e1305..a4edd1ac7aa8 100644 --- a/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts +++ b/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts @@ -6,7 +6,6 @@ import { memoize } from 'vs/base/common/decorators'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { URI } from 'vs/base/common/uri'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { IWebviewService, Webview, WebviewContentOptions, WebviewEditorOverlay, WebviewElement, WebviewOptions, WebviewExtensionDescription } from 'vs/workbench/contrib/webview/browser/webview'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; @@ -99,7 +98,7 @@ export class DynamicWebviewEditorOverlay extends Disposable implements WebviewEd if (this._options.tryRestoreScrollPosition) { webview.initialScrollProgress = this._initialScrollProgress; } - this._webview.value.mountTo(this.container); + webview.mountTo(this.container); // Forward events from inner webview to outer listeners this._webviewEvents.clear(); @@ -143,7 +142,7 @@ export class DynamicWebviewEditorOverlay extends Disposable implements WebviewEd } public get options(): WebviewOptions { return this._options; } - public set options(value: WebviewOptions) { this._options = value; } + public set options(value: WebviewOptions) { this._options = { customClasses: this._options.customClasses, ...value }; } public get contentOptions(): WebviewContentOptions { return this._contentOptions; } public set contentOptions(value: WebviewContentOptions) { @@ -160,8 +159,8 @@ export class DynamicWebviewEditorOverlay extends Disposable implements WebviewEd private readonly _onDidFocus = this._register(new Emitter()); public readonly onDidFocus: Event = this._onDidFocus.event; - private readonly _onDidClickLink = this._register(new Emitter()); - public readonly onDidClickLink: Event = this._onDidClickLink.event; + private readonly _onDidClickLink = this._register(new Emitter()); + public readonly onDidClickLink: Event = this._onDidClickLink.event; private readonly _onDidScroll = this._register(new Emitter<{ scrollYPercentage: number; }>()); public readonly onDidScroll: Event<{ scrollYPercentage: number; }> = this._onDidScroll.event; diff --git a/src/vs/workbench/contrib/webview/browser/pre/fake.html b/src/vs/workbench/contrib/webview/browser/pre/fake.html index e69de29bb2d1..726a69397f84 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/fake.html +++ b/src/vs/workbench/contrib/webview/browser/pre/fake.html @@ -0,0 +1,11 @@ + + + + + + + Fake + + + + diff --git a/src/vs/workbench/contrib/webview/browser/pre/index.html b/src/vs/workbench/contrib/webview/browser/pre/index.html index e301f5ea90d7..d142be649a6e 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/index.html +++ b/src/vs/workbench/contrib/webview/browser/pre/index.html @@ -3,7 +3,11 @@ - + + + + Virtual Document diff --git a/src/vs/workbench/contrib/webview/browser/pre/main.js b/src/vs/workbench/contrib/webview/browser/pre/main.js index 63541521d7d7..97d54a7614b0 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/main.js +++ b/src/vs/workbench/contrib/webview/browser/pre/main.js @@ -91,6 +91,24 @@ border-color: var(--vscode-textBlockQuote-border); } + kbd { + color: var(--vscode-editor-foreground); + border-radius: 3px; + vertical-align: middle; + padding: 1px 3px; + + background-color: hsla(0,0%,50%,.17); + border: 1px solid rgba(71,71,71,.4); + border-bottom-color: rgba(88,88,88,.4); + box-shadow: inset 0 -1px 0 rgba(88,88,88,.4); + } + .vscode-light kbd { + background-color: hsla(0,0%,87%,.5); + border: 1px solid hsla(0,0%,80%,.7); + border-bottom-color: hsla(0,0%,73%,.7); + box-shadow: inset 0 -1px 0 hsla(0,0%,73%,.7); + } + ::-webkit-scrollbar { width: 10px; height: 10px; @@ -213,7 +231,8 @@ /** * @param {MouseEvent} event */ - const handleAuxClick = (event) => { + const handleAuxClick = + (event) => { // Prevent middle clicks opening a broken link in the browser if (!event.view || !event.view.document) { return; @@ -308,7 +327,12 @@ } else { // Rewrite vscode-resource in csp if (data.endpoint) { - csp.setAttribute('content', csp.getAttribute('content').replace(/vscode-resource:/g, data.endpoint)); + try { + const endpointUrl = new URL(data.endpoint); + csp.setAttribute('content', csp.getAttribute('content').replace(/vscode-resource:(?=(\s|;|$))/g, endpointUrl.origin)); + } catch (e) { + console.error('Could not rewrite csp'); + } } } diff --git a/src/vs/workbench/contrib/webview/browser/webview.contribution.ts b/src/vs/workbench/contrib/webview/browser/webview.contribution.ts index e1e810fbe5da..41305eb73842 100644 --- a/src/vs/workbench/contrib/webview/browser/webview.contribution.ts +++ b/src/vs/workbench/contrib/webview/browser/webview.contribution.ts @@ -21,7 +21,7 @@ import { WebviewEditor } from '../browser/webviewEditor'; import { WebviewInput } from '../browser/webviewEditorInput'; import { IWebviewWorkbenchService, WebviewEditorService } from './webviewWorkbenchService'; -(Registry.as(EditorExtensions.Editors)).registerEditor(new EditorDescriptor( +(Registry.as(EditorExtensions.Editors)).registerEditor(EditorDescriptor.create( WebviewEditor, WebviewEditor.ID, localize('webview.editor.label', "webview editor")), @@ -85,6 +85,6 @@ function registerWebViewCommands(editorId: string): void { registerWebViewCommands(WebviewEditor.ID); actionRegistry.registerWorkbenchAction( - new SyncActionDescriptor(ReloadWebviewAction, ReloadWebviewAction.ID, ReloadWebviewAction.LABEL), + SyncActionDescriptor.create(ReloadWebviewAction, ReloadWebviewAction.ID, ReloadWebviewAction.LABEL), 'Reload Webviews', webviewDeveloperCategory); diff --git a/src/vs/workbench/contrib/webview/browser/webview.ts b/src/vs/workbench/contrib/webview/browser/webview.ts index 10db2b70acfb..4af73dd16314 100644 --- a/src/vs/workbench/contrib/webview/browser/webview.ts +++ b/src/vs/workbench/contrib/webview/browser/webview.ts @@ -19,6 +19,9 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' export const KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE = new RawContextKey('webviewFindWidgetVisible', false); export const KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_FOCUSED = new RawContextKey('webviewFindWidgetFocused', false); +export const webviewHasOwnEditFunctionsContextKey = 'webviewHasOwnEditFunctions'; +export const webviewHasOwnEditFunctionsContext = new RawContextKey(webviewHasOwnEditFunctionsContextKey, false); + export const IWebviewService = createDecorator('webviewService'); /** @@ -68,7 +71,7 @@ export interface Webview extends IDisposable { state: string | undefined; readonly onDidFocus: Event; - readonly onDidClickLink: Event; + readonly onDidClickLink: Event; readonly onDidScroll: Event<{ scrollYPercentage: number }>; readonly onDidUpdateState: Event; readonly onMessage: Event; diff --git a/src/vs/workbench/contrib/webview/browser/webviewCommands.ts b/src/vs/workbench/contrib/webview/browser/webviewCommands.ts index 1c7f26efa742..2018c13aab11 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewCommands.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewCommands.ts @@ -13,44 +13,32 @@ import { WebviewEditor } from 'vs/workbench/contrib/webview/browser/webviewEdito export class ShowWebViewEditorFindWidgetCommand extends Command { public static readonly ID = 'editor.action.webvieweditor.showFind'; - public runCommand(accessor: ServicesAccessor, args: any): void { - const webViewEditor = getActiveWebviewEditor(accessor); - if (webViewEditor) { - webViewEditor.showFind(); - } + public runCommand(accessor: ServicesAccessor): void { + getActiveWebviewEditor(accessor)?.showFind(); } } export class HideWebViewEditorFindCommand extends Command { public static readonly ID = 'editor.action.webvieweditor.hideFind'; - public runCommand(accessor: ServicesAccessor, args: any): void { - const webViewEditor = getActiveWebviewEditor(accessor); - if (webViewEditor) { - webViewEditor.hideFind(); - } + public runCommand(accessor: ServicesAccessor): void { + getActiveWebviewEditor(accessor)?.hideFind(); } } export class WebViewEditorFindNextCommand extends Command { public static readonly ID = 'editor.action.webvieweditor.findNext'; - public runCommand(accessor: ServicesAccessor, args: any): void { - const webViewEditor = getActiveWebviewEditor(accessor); - if (webViewEditor) { - webViewEditor.find(false); - } + public runCommand(accessor: ServicesAccessor): void { + getActiveWebviewEditor(accessor)?.find(false); } } export class WebViewEditorFindPreviousCommand extends Command { public static readonly ID = 'editor.action.webvieweditor.findPrevious'; - public runCommand(accessor: ServicesAccessor, args: any): void { - const webViewEditor = getActiveWebviewEditor(accessor); - if (webViewEditor) { - webViewEditor.find(true); - } + public runCommand(accessor: ServicesAccessor): void { + getActiveWebviewEditor(accessor)?.find(true); } } export class ReloadWebviewAction extends Action { @@ -79,8 +67,8 @@ export class ReloadWebviewAction extends Action { } } -function getActiveWebviewEditor(accessor: ServicesAccessor): WebviewEditor | null { +export function getActiveWebviewEditor(accessor: ServicesAccessor): WebviewEditor | undefined { const editorService = accessor.get(IEditorService); - const activeControl = editorService.activeControl as WebviewEditor; - return activeControl.isWebviewEditor ? activeControl : null; + const activeControl = editorService.activeControl as WebviewEditor | undefined; + return activeControl?.isWebviewEditor ? activeControl : undefined; } diff --git a/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts b/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts index 515a3474df42..52c2e4d21411 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts @@ -6,17 +6,19 @@ import * as dom from 'vs/base/browser/dom'; import { memoize } from 'vs/base/common/decorators'; import { Lazy } from 'vs/base/common/lazy'; -import { UnownedDisposable as Unowned } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { IEditorModel } from 'vs/platform/editor/common/editor'; +import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { EditorInput, EditorModel, GroupIdentifier, IEditorInput, Verbosity } from 'vs/workbench/common/editor'; import { WebviewEditorOverlay } from 'vs/workbench/contrib/webview/browser/webview'; +import { Emitter } from 'vs/base/common/event'; const WebviewPanelResourceScheme = 'webview-panel'; class WebviewIconsManager { private readonly _icons = new Map(); + @memoize private get _styleElement(): HTMLStyleElement { const element = dom.createStyleSheet(); @@ -26,7 +28,8 @@ class WebviewIconsManager { public setIcons( webviewId: string, - iconPath: { light: URI, dark: URI } | undefined + iconPath: { light: URI, dark: URI } | undefined, + lifecycleService: ILifecycleService, ) { if (iconPath) { this._icons.set(webviewId, iconPath); @@ -34,21 +37,27 @@ class WebviewIconsManager { this._icons.delete(webviewId); } - this.updateStyleSheet(); - } - - private updateStyleSheet() { - const cssRules: string[] = []; - this._icons.forEach((value, key) => { - const webviewSelector = `.show-file-icons .webview-${key}-name-file-icon::before`; - if (URI.isUri(value)) { - cssRules.push(`${webviewSelector} { content: ""; background-image: ${dom.asCSSUrl(value)}; }`); - } else { - cssRules.push(`.vs ${webviewSelector} { content: ""; background-image: ${dom.asCSSUrl(value.light)}; }`); - cssRules.push(`.vs-dark ${webviewSelector} { content: ""; background-image: ${dom.asCSSUrl(value.dark)}; }`); - } - }); - this._styleElement.innerHTML = cssRules.join('\n'); + this.updateStyleSheet(lifecycleService); + } + + private async updateStyleSheet(lifecycleService: ILifecycleService, ) { + await lifecycleService.when(LifecyclePhase.Starting); + + try { + const cssRules: string[] = []; + this._icons.forEach((value, key) => { + const webviewSelector = `.show-file-icons .webview-${key}-name-file-icon::before`; + if (URI.isUri(value)) { + cssRules.push(`${webviewSelector} { content: ""; background-image: ${dom.asCSSUrl(value)}; }`); + } else { + cssRules.push(`.vs ${webviewSelector} { content: ""; background-image: ${dom.asCSSUrl(value.light)}; }`); + cssRules.push(`.vs-dark ${webviewSelector} { content: ""; background-image: ${dom.asCSSUrl(value.dark)}; }`); + } + }); + this._styleElement.innerHTML = cssRules.join('\n'); + } catch { + // noop + } } } @@ -61,19 +70,31 @@ export class WebviewInput extends EditorInput { private _name: string; private _iconPath?: { light: URI, dark: URI }; private _group?: GroupIdentifier; + private readonly _webview: Lazy; + private _didSomeoneTakeMyWebview = false; + + private readonly _onDisposeWebview = this._register(new Emitter()); + readonly onDisposeWebview = this._onDisposeWebview.event; constructor( public readonly id: string, public readonly viewType: string, name: string, - webview: Lazy> + webview: Lazy, + @ILifecycleService private readonly lifecycleService: ILifecycleService, ) { super(); - this._name = name; + this._webview = webview; + } - this._webview = webview.map(value => this._register(value.acquire())); // The input owns this webview + dispose() { + if (!this._didSomeoneTakeMyWebview) { + this._webview?.rawValue?.dispose(); + this._onDisposeWebview.fire(); + } + super.dispose(); } public getTypeId(): string { @@ -91,7 +112,7 @@ export class WebviewInput extends EditorInput { return this._name; } - public getTitle(_verbosity?: Verbosity) { + public getTitle(_verbosity?: Verbosity): string { return this.getName(); } @@ -109,7 +130,7 @@ export class WebviewInput extends EditorInput { } public get extension() { - return this._webview.getValue().extension; + return this.webview.extension; } public get iconPath() { @@ -118,7 +139,7 @@ export class WebviewInput extends EditorInput { public set iconPath(value: { light: URI, dark: URI } | undefined) { this._iconPath = value; - WebviewInput.iconsManager.setIcons(this.id, value); + WebviewInput.iconsManager.setIcons(this.id, value, this.lifecycleService); } public matches(other: IEditorInput): boolean { @@ -140,4 +161,12 @@ export class WebviewInput extends EditorInput { public supportsSplitEditor() { return false; } + + protected takeOwnershipOfWebview(): WebviewEditorOverlay | undefined { + if (this._didSomeoneTakeMyWebview) { + return undefined; + } + this._didSomeoneTakeMyWebview = true; + return this.webview; + } } diff --git a/src/vs/workbench/contrib/webview/browser/webviewElement.ts b/src/vs/workbench/contrib/webview/browser/webviewElement.ts index 155c3fb23d5e..32e0db6012a5 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewElement.ts @@ -12,7 +12,7 @@ import { IFileService } from 'vs/platform/files/common/files'; import { ITunnelService } from 'vs/platform/remote/common/tunnel'; import { Webview, WebviewContentOptions, WebviewOptions } from 'vs/workbench/contrib/webview/browser/webview'; import { WebviewPortMappingManager } from 'vs/workbench/contrib/webview/common/portMapping'; -import { loadLocalResource } from 'vs/workbench/contrib/webview/common/resourceLoader'; +import { loadLocalResource, WebviewResourceResponse } from 'vs/workbench/contrib/webview/common/resourceLoader'; import { WebviewThemeDataProvider } from 'vs/workbench/contrib/webview/common/themeing'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { BaseWebview, WebviewMessageChannels } from 'vs/workbench/contrib/webview/browser/baseWebviewElement'; @@ -60,7 +60,7 @@ export class IFrameWebview extends BaseWebview implements Web protected createElement(options: WebviewOptions) { const element = document.createElement('iframe'); - element.className = `webview ${options.customClasses}`; + element.className = `webview ${options.customClasses || ''}`; element.sandbox.add('allow-scripts', 'allow-same-origin'); element.setAttribute('src', `${this.externalEndpoint}/index.html?id=${this.id}`); element.style.border = 'none'; @@ -108,7 +108,6 @@ export class IFrameWebview extends BaseWebview implements Web } focus(): void { - console.log('focus'); if (this.element) { this._send('focus'); } @@ -131,7 +130,7 @@ export class IFrameWebview extends BaseWebview implements Web const result = await loadLocalResource(uri, this.fileService, this.extension ? this.extension.location : undefined, () => (this.content.options.localResourceRoots || [])); - if (result.type === 'success') { + if (result.type === WebviewResourceResponse.Type.Success) { return this._send('did-load-resource', { status: 200, path: requestPath, diff --git a/src/vs/workbench/contrib/webview/browser/webviewWorkbenchService.ts b/src/vs/workbench/contrib/webview/browser/webviewWorkbenchService.ts index 5bfae2a7123a..b76646bf40cb 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewWorkbenchService.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewWorkbenchService.ts @@ -5,19 +5,20 @@ import { equals } from 'vs/base/common/arrays'; import { memoize } from 'vs/base/common/decorators'; -import { IDisposable, toDisposable, UnownedDisposable } from 'vs/base/common/lifecycle'; +import { Lazy } from 'vs/base/common/lazy'; +import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { values } from 'vs/base/common/map'; import { isEqual } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { EditorActivation, IEditorModel } from 'vs/platform/editor/common/editor'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { GroupIdentifier } from 'vs/workbench/common/editor'; -import { IWebviewService, WebviewContentOptions, WebviewEditorOverlay, WebviewOptions, WebviewExtensionDescription } from 'vs/workbench/contrib/webview/browser/webview'; +import { IWebviewService, WebviewContentOptions, WebviewEditorOverlay, WebviewExtensionDescription, WebviewOptions } from 'vs/workbench/contrib/webview/browser/webview'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ACTIVE_GROUP_TYPE, IEditorService, SIDE_GROUP_TYPE } from 'vs/workbench/services/editor/common/editorService'; import { WebviewInput } from './webviewEditorInput'; -import { Lazy } from 'vs/base/common/lazy'; export const IWebviewWorkbenchService = createDecorator('webviewEditorService'); @@ -107,10 +108,11 @@ export class LazilyResolvedWebviewEditorInput extends WebviewInput { id: string, viewType: string, name: string, - webview: Lazy>, + webview: Lazy, @IWebviewWorkbenchService private readonly _webviewWorkbenchService: IWebviewWorkbenchService, + @ILifecycleService lifeCycleService: ILifecycleService, ) { - super(id, viewType, name, webview); + super(id, viewType, name, webview, lifeCycleService); } @memoize @@ -145,8 +147,9 @@ export class WebviewEditorService implements IWebviewWorkbenchService { private readonly _revivalPool = new RevivalPool(); constructor( - @IEditorService private readonly _editorService: IEditorService, @IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService, + @IEditorService private readonly _editorService: IEditorService, + @IInstantiationService private readonly _instantiationService: IInstantiationService, @IWebviewService private readonly _webviewService: IWebviewService, ) { } @@ -158,8 +161,8 @@ export class WebviewEditorService implements IWebviewWorkbenchService { options: WebviewInputOptions, extension: WebviewExtensionDescription | undefined, ): WebviewInput { - const webview = new Lazy(() => new UnownedDisposable(this.createWebiew(id, extension, options))); - const webviewInput = new WebviewInput(id, viewType, title, webview); + const webview = new Lazy(() => this.createWebiew(id, extension, options)); + const webviewInput = this._instantiationService.createInstance(WebviewInput, id, viewType, title, webview); this._editorService.openEditor(webviewInput, { pinned: true, preserveFocus: showOptions.preserveFocus, @@ -203,10 +206,10 @@ export class WebviewEditorService implements IWebviewWorkbenchService { const webview = new Lazy(() => { const webview = this.createWebiew(id, extension, options); webview.state = state; - return new UnownedDisposable(webview); + return webview; }); - const webviewInput = new LazilyResolvedWebviewEditorInput(id, viewType, title, webview, this); + const webviewInput = this._instantiationService.createInstance(LazilyResolvedWebviewEditorInput, id, viewType, title, webview); webviewInput.iconPath = iconPath; if (typeof group === 'number') { diff --git a/src/vs/workbench/contrib/webview/common/resourceLoader.ts b/src/vs/workbench/contrib/webview/common/resourceLoader.ts index cb607a3f1349..a7a7a28ef273 100644 --- a/src/vs/workbench/contrib/webview/common/resourceLoader.ts +++ b/src/vs/workbench/contrib/webview/common/resourceLoader.ts @@ -10,34 +10,39 @@ import { URI } from 'vs/base/common/uri'; import { IFileService } from 'vs/platform/files/common/files'; import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; import { getWebviewContentMimeType } from 'vs/workbench/contrib/webview/common/mimeTypes'; +import { isUNC } from 'vs/base/common/extpath'; export const WebviewResourceScheme = 'vscode-resource'; -class Success { - readonly type = 'success'; +export namespace WebviewResourceResponse { + export enum Type { Success, Failed, AccessDenied } - constructor( - public readonly data: VSBuffer, - public readonly mimeType: string - ) { } -} + export class Success { + readonly type = Type.Success; + + constructor( + public readonly data: VSBuffer, + public readonly mimeType: string + ) { } + } -const Failed = new class { readonly type = 'failed'; }; -const AccessDenied = new class { readonly type = 'access-denied'; }; + export const Failed = { type: Type.Failed } as const; + export const AccessDenied = { type: Type.AccessDenied } as const; -type LocalResourceResponse = Success | typeof Failed | typeof AccessDenied; + export type Response = Success | typeof Failed | typeof AccessDenied; +} async function resolveContent( fileService: IFileService, resource: URI, mime: string -): Promise { +): Promise { try { const contents = await fileService.readFile(resource); - return new Success(contents.value, mime); + return new WebviewResourceResponse.Success(contents.value, mime); } catch (err) { console.log(err); - return Failed; + return WebviewResourceResponse.Failed; } } @@ -46,7 +51,7 @@ export async function loadLocalResource( fileService: IFileService, extensionLocation: URI | undefined, getRoots: () => ReadonlyArray -): Promise { +): Promise { const normalizedPath = normalizeRequestPath(requestUri); for (const root of getRoots()) { @@ -69,7 +74,7 @@ export async function loadLocalResource( } } - return AccessDenied; + return WebviewResourceResponse.AccessDenied; } function normalizeRequestPath(requestUri: URI) { @@ -79,7 +84,10 @@ function normalizeRequestPath(requestUri: URI) { // Modern vscode-resources uris put the scheme of the requested resource as the authority if (requestUri.authority) { - return URI.parse(requestUri.authority + ':' + requestUri.path); + return URI.parse(`${requestUri.authority}:${encodeURIComponent(requestUri.path).replace(/%2F/g, '/')}`).with({ + query: requestUri.query, + fragment: requestUri.fragment + }); } // Old style vscode-resource uris lose the scheme of the resource which means they are unable to @@ -88,6 +96,13 @@ function normalizeRequestPath(requestUri: URI) { } function containsResource(root: URI, resource: URI): boolean { - const rootPath = root.fsPath + (endsWith(root.fsPath, sep) ? '' : sep); + let rootPath = root.fsPath + (endsWith(root.fsPath, sep) ? '' : sep); + let resourceFsPath = resource.fsPath; + + if (isUNC(root.fsPath) && isUNC(resource.fsPath)) { + rootPath = rootPath.toLowerCase(); + resourceFsPath = resourceFsPath.toLowerCase(); + } + return startsWith(resource.fsPath, rootPath); } diff --git a/src/vs/workbench/contrib/webview/electron-browser/webview.contribution.ts b/src/vs/workbench/contrib/webview/electron-browser/webview.contribution.ts index f726efbd0048..e53e97b0d7ea 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webview.contribution.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webview.contribution.ts @@ -3,17 +3,14 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { isMacintosh } from 'vs/base/common/platform'; import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkeys'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions'; -import { WebviewEditor } from 'vs/workbench/contrib/webview/browser/webviewEditor'; import { IWebviewService, webviewDeveloperCategory } from 'vs/workbench/contrib/webview/browser/webview'; +import { WebviewEditor } from 'vs/workbench/contrib/webview/browser/webviewEditor'; import * as webviewCommands from 'vs/workbench/contrib/webview/electron-browser/webviewCommands'; import { ElectronWebviewService } from 'vs/workbench/contrib/webview/electron-browser/webviewService'; @@ -22,70 +19,22 @@ registerSingleton(IWebviewService, ElectronWebviewService, true); const actionRegistry = Registry.as(ActionExtensions.WorkbenchActions); actionRegistry.registerWorkbenchAction( - new SyncActionDescriptor(webviewCommands.OpenWebviewDeveloperToolsAction, webviewCommands.OpenWebviewDeveloperToolsAction.ID, webviewCommands.OpenWebviewDeveloperToolsAction.LABEL), + SyncActionDescriptor.create(webviewCommands.OpenWebviewDeveloperToolsAction, webviewCommands.OpenWebviewDeveloperToolsAction.ID, webviewCommands.OpenWebviewDeveloperToolsAction.LABEL), webviewCommands.OpenWebviewDeveloperToolsAction.ALIAS, webviewDeveloperCategory); function registerWebViewCommands(editorId: string): void { - const contextKeyExpr = ContextKeyExpr.and(ContextKeyExpr.equals('activeEditor', editorId), ContextKeyExpr.not('editorFocus') /* https://github.com/Microsoft/vscode/issues/58668 */); + const contextKeyExpr = ContextKeyExpr.and(ContextKeyExpr.equals('activeEditor', editorId), ContextKeyExpr.not('editorFocus') /* https://github.com/Microsoft/vscode/issues/58668 */)!; - (new webviewCommands.SelectAllWebviewEditorCommand({ - id: webviewCommands.SelectAllWebviewEditorCommand.ID, - precondition: ContextKeyExpr.and(contextKeyExpr, ContextKeyExpr.not(InputFocusedContextKey)), - kbOpts: { - primary: KeyMod.CtrlCmd | KeyCode.KEY_A, - weight: KeybindingWeight.EditorContrib - } - })).register(); + new webviewCommands.SelectAllWebviewEditorCommand(contextKeyExpr).register(); // These commands are only needed on MacOS where we have to disable the menu bar commands if (isMacintosh) { - (new webviewCommands.CopyWebviewEditorCommand({ - id: webviewCommands.CopyWebviewEditorCommand.ID, - precondition: ContextKeyExpr.and(contextKeyExpr, ContextKeyExpr.not(InputFocusedContextKey)), - kbOpts: { - primary: KeyMod.CtrlCmd | KeyCode.KEY_C, - weight: KeybindingWeight.EditorContrib - } - })).register(); - - (new webviewCommands.PasteWebviewEditorCommand({ - id: webviewCommands.PasteWebviewEditorCommand.ID, - precondition: ContextKeyExpr.and(contextKeyExpr, ContextKeyExpr.not(InputFocusedContextKey)), - kbOpts: { - primary: KeyMod.CtrlCmd | KeyCode.KEY_V, - weight: KeybindingWeight.EditorContrib - } - })).register(); - - (new webviewCommands.CutWebviewEditorCommand({ - id: webviewCommands.CutWebviewEditorCommand.ID, - precondition: ContextKeyExpr.and(contextKeyExpr, ContextKeyExpr.not(InputFocusedContextKey)), - kbOpts: { - primary: KeyMod.CtrlCmd | KeyCode.KEY_X, - weight: KeybindingWeight.EditorContrib - } - })).register(); - - (new webviewCommands.UndoWebviewEditorCommand({ - id: webviewCommands.UndoWebviewEditorCommand.ID, - precondition: ContextKeyExpr.and(contextKeyExpr, ContextKeyExpr.not(InputFocusedContextKey)), - kbOpts: { - primary: KeyMod.CtrlCmd | KeyCode.KEY_Z, - weight: KeybindingWeight.EditorContrib - } - })).register(); - - (new webviewCommands.RedoWebviewEditorCommand({ - id: webviewCommands.RedoWebviewEditorCommand.ID, - precondition: ContextKeyExpr.and(contextKeyExpr, ContextKeyExpr.not(InputFocusedContextKey)), - kbOpts: { - primary: KeyMod.CtrlCmd | KeyCode.KEY_Y, - secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_Z], - mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_Z }, - weight: KeybindingWeight.EditorContrib - } - })).register(); + new webviewCommands.CopyWebviewEditorCommand(contextKeyExpr).register(); + new webviewCommands.PasteWebviewEditorCommand(contextKeyExpr).register(); + new webviewCommands.CutWebviewEditorCommand(contextKeyExpr).register(); + new webviewCommands.UndoWebviewEditorCommand(contextKeyExpr).register(); + new webviewCommands.RedoWebviewEditorCommand(contextKeyExpr).register(); } } diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewCommands.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewCommands.ts index 7cfb3cd277aa..950c94e99ffb 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewCommands.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewCommands.ts @@ -3,14 +3,17 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { WebviewTag } from 'electron'; import { Action } from 'vs/base/common/actions'; -import * as nls from 'vs/nls'; +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Command, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; -import { WebviewEditor } from 'vs/workbench/contrib/webview/browser/webviewEditor'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import * as nls from 'vs/nls'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkeys'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { WebviewEditorOverlay, webviewHasOwnEditFunctionsContextKey } from 'vs/workbench/contrib/webview/browser/webview'; +import { getActiveWebviewEditor } from 'vs/workbench/contrib/webview/browser/webviewCommands'; import { ElectronWebviewBasedWebview } from 'vs/workbench/contrib/webview/electron-browser/webviewElement'; -import { WebviewEditorOverlay } from 'vs/workbench/contrib/webview/browser/webview'; -import { WebviewTag } from 'electron'; export class OpenWebviewDeveloperToolsAction extends Action { static readonly ID = 'workbench.action.webview.openDeveloperTools'; @@ -37,6 +40,17 @@ export class OpenWebviewDeveloperToolsAction extends Action { export class SelectAllWebviewEditorCommand extends Command { public static readonly ID = 'editor.action.webvieweditor.selectAll'; + constructor(contextKeyExpr: ContextKeyExpr) { + super({ + id: SelectAllWebviewEditorCommand.ID, + precondition: ContextKeyExpr.and(contextKeyExpr, ContextKeyExpr.not(InputFocusedContextKey)), + kbOpts: { + primary: KeyMod.CtrlCmd | KeyCode.KEY_A, + weight: KeybindingWeight.EditorContrib + } + }); + } + public runCommand(accessor: ServicesAccessor, args: any): void { withActiveWebviewBasedWebview(accessor, webview => webview.selectAll()); } @@ -45,6 +59,17 @@ export class SelectAllWebviewEditorCommand extends Command { export class CopyWebviewEditorCommand extends Command { public static readonly ID = 'editor.action.webvieweditor.copy'; + constructor(contextKeyExpr: ContextKeyExpr) { + super({ + id: CopyWebviewEditorCommand.ID, + precondition: ContextKeyExpr.and(contextKeyExpr, ContextKeyExpr.not(InputFocusedContextKey)), + kbOpts: { + primary: KeyMod.CtrlCmd | KeyCode.KEY_C, + weight: KeybindingWeight.EditorContrib + } + }); + } + public runCommand(accessor: ServicesAccessor, _args: any): void { withActiveWebviewBasedWebview(accessor, webview => webview.copy()); } @@ -53,6 +78,17 @@ export class CopyWebviewEditorCommand extends Command { export class PasteWebviewEditorCommand extends Command { public static readonly ID = 'editor.action.webvieweditor.paste'; + constructor(contextKeyExpr: ContextKeyExpr) { + super({ + id: PasteWebviewEditorCommand.ID, + precondition: ContextKeyExpr.and(contextKeyExpr, ContextKeyExpr.not(InputFocusedContextKey)), + kbOpts: { + primary: KeyMod.CtrlCmd | KeyCode.KEY_V, + weight: KeybindingWeight.EditorContrib + } + }); + } + public runCommand(accessor: ServicesAccessor, _args: any): void { withActiveWebviewBasedWebview(accessor, webview => webview.paste()); } @@ -61,6 +97,17 @@ export class PasteWebviewEditorCommand extends Command { export class CutWebviewEditorCommand extends Command { public static readonly ID = 'editor.action.webvieweditor.cut'; + constructor(contextKeyExpr: ContextKeyExpr) { + super({ + id: CutWebviewEditorCommand.ID, + precondition: ContextKeyExpr.and(contextKeyExpr, ContextKeyExpr.not(InputFocusedContextKey)), + kbOpts: { + primary: KeyMod.CtrlCmd | KeyCode.KEY_X, + weight: KeybindingWeight.EditorContrib + } + }); + } + public runCommand(accessor: ServicesAccessor, _args: any): void { withActiveWebviewBasedWebview(accessor, webview => webview.cut()); } @@ -69,6 +116,17 @@ export class CutWebviewEditorCommand extends Command { export class UndoWebviewEditorCommand extends Command { public static readonly ID = 'editor.action.webvieweditor.undo'; + constructor(contextKeyExpr: ContextKeyExpr) { + super({ + id: UndoWebviewEditorCommand.ID, + precondition: ContextKeyExpr.and(contextKeyExpr, ContextKeyExpr.not(InputFocusedContextKey), ContextKeyExpr.not(webviewHasOwnEditFunctionsContextKey)), + kbOpts: { + primary: KeyMod.CtrlCmd | KeyCode.KEY_Z, + weight: KeybindingWeight.EditorContrib + } + }); + } + public runCommand(accessor: ServicesAccessor, args: any): void { withActiveWebviewBasedWebview(accessor, webview => webview.undo()); } @@ -77,17 +135,24 @@ export class UndoWebviewEditorCommand extends Command { export class RedoWebviewEditorCommand extends Command { public static readonly ID = 'editor.action.webvieweditor.redo'; + constructor(contextKeyExpr: ContextKeyExpr) { + super({ + id: RedoWebviewEditorCommand.ID, + precondition: ContextKeyExpr.and(contextKeyExpr, ContextKeyExpr.not(InputFocusedContextKey), ContextKeyExpr.not(webviewHasOwnEditFunctionsContextKey)), + kbOpts: { + primary: KeyMod.CtrlCmd | KeyCode.KEY_Y, + secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_Z], + mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_Z }, + weight: KeybindingWeight.EditorContrib + } + }); + } + public runCommand(accessor: ServicesAccessor, args: any): void { withActiveWebviewBasedWebview(accessor, webview => webview.redo()); } } -function getActiveWebviewEditor(accessor: ServicesAccessor): WebviewEditor | undefined { - const editorService = accessor.get(IEditorService); - const activeControl = editorService.activeControl as WebviewEditor; - return activeControl.isWebviewEditor ? activeControl : undefined; -} - function withActiveWebviewBasedWebview(accessor: ServicesAccessor, f: (webview: ElectronWebviewBasedWebview) => void): void { const webViewEditor = getActiveWebviewEditor(accessor); if (webViewEditor) { diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts index b3963111b9a1..6d8ae3892bbe 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts @@ -287,6 +287,8 @@ export class ElectronWebviewBasedWebview extends BaseWebview impleme this._register(addDisposableListener(this.element!, 'found-in-page', e => { this._hasFindResult.fire(e.result.matches > 0); })); + + this.styledFindWidget(); } } @@ -294,7 +296,7 @@ export class ElectronWebviewBasedWebview extends BaseWebview impleme const element = document.createElement('webview'); element.setAttribute('partition', `webview${Date.now()}`); element.setAttribute('webpreferences', 'contextIsolation=yes'); - element.className = `webview ${options.customClasses}`; + element.className = `webview ${options.customClasses || ''}`; element.style.flex = '0 1'; element.style.width = '0'; @@ -341,10 +343,11 @@ export class ElectronWebviewBasedWebview extends BaseWebview impleme protected style(): void { super.style(); + this.styledFindWidget(); + } - if (this._webviewFindWidget) { - this._webviewFindWidget.updateTheme(this._webviewThemeDataProvider.getTheme()); - } + private styledFindWidget() { + this._webviewFindWidget?.updateTheme(this._webviewThemeDataProvider.getTheme()); } private readonly _hasFindResult = this._register(new Emitter()); diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewProtocols.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewProtocols.ts index 810c3da9d4e0..fa8d568edcbf 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewProtocols.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewProtocols.ts @@ -5,7 +5,7 @@ import * as electron from 'electron'; import { URI } from 'vs/base/common/uri'; import { IFileService } from 'vs/platform/files/common/files'; -import { loadLocalResource } from 'vs/workbench/contrib/webview/common/resourceLoader'; +import { loadLocalResource, WebviewResourceResponse } from 'vs/workbench/contrib/webview/common/resourceLoader'; export function registerFileProtocol( contents: electron.WebContents, @@ -17,13 +17,13 @@ export function registerFileProtocol( contents.session.protocol.registerBufferProtocol(protocol, async (request, callback: any) => { try { const result = await loadLocalResource(URI.parse(request.url), fileService, extensionLocation, getRoots); - if (result.type === 'success') { + if (result.type === WebviewResourceResponse.Type.Success) { return callback({ data: Buffer.from(result.data.buffer), mimeType: result.mimeType }); } - if (result.type === 'access-denied') { + if (result.type === WebviewResourceResponse.Type.AccessDenied) { console.error('Webview: Cannot load resource outside of protocol root'); return callback({ error: -10 /* ACCESS_DENIED: https://cs.chromium.org/chromium/src/net/base/net_error_list.h */ }); } diff --git a/src/vs/workbench/contrib/welcome/overlay/browser/welcomeOverlay.ts b/src/vs/workbench/contrib/welcome/overlay/browser/welcomeOverlay.ts index 16c9634e083c..c12eb963d41e 100644 --- a/src/vs/workbench/contrib/welcome/overlay/browser/welcomeOverlay.ts +++ b/src/vs/workbench/contrib/welcome/overlay/browser/welcomeOverlay.ts @@ -207,6 +207,7 @@ class WelcomeOverlay extends Disposable { dom.addClass(workbench, 'blur-background'); this._overlayVisible.set(true); this.updateProblemsKey(); + this.updateActivityBarKeys(); this._overlay.focus(); } } @@ -227,6 +228,25 @@ class WelcomeOverlay extends Disposable { } } + private updateActivityBarKeys() { + const ids = ['explorer', 'search', 'git', 'debug', 'extensions']; + const activityBar = document.querySelector('.activitybar .composite-bar'); + if (activityBar instanceof HTMLElement) { + const target = activityBar.getBoundingClientRect(); + const bounds = this._overlay.getBoundingClientRect(); + for (let i = 0; i < ids.length; i++) { + const key = this._overlay.querySelector(`.key.${ids[i]}`) as HTMLElement; + const top = target.top - bounds.top + 50 * i + 13; + key.style.top = top + 'px'; + } + } else { + for (let i = 0; i < ids.length; i++) { + const key = this._overlay.querySelector(`.key.${ids[i]}`) as HTMLElement; + key.style.top = ''; + } + } + } + public hide() { if (this._overlay.style.display !== 'none') { this._overlay.style.display = 'none'; @@ -237,8 +257,11 @@ class WelcomeOverlay extends Disposable { } } -// {SQL CARBON EDIT} -// remove Interface Overview command registrations +/*Registry.as(Extensions.WorkbenchActions) + .registerWorkbenchAction(SyncActionDescriptor.create(WelcomeOverlayAction, WelcomeOverlayAction.ID, WelcomeOverlayAction.LABEL), 'Help: User Interface Overview', localize('help', "Help")); {{SQL CARBON EDIT}} remove interface overview + +Registry.as(Extensions.WorkbenchActions) + .registerWorkbenchAction(SyncActionDescriptor.create(HideWelcomeOverlayAction, HideWelcomeOverlayAction.ID, HideWelcomeOverlayAction.LABEL, { primary: KeyCode.Escape }, OVERLAY_VISIBLE), 'Help: Hide Interface Overview', localize('help', "Help"));*/ // theming diff --git a/src/vs/workbench/contrib/welcome/page/browser/welcomePage.contribution.ts b/src/vs/workbench/contrib/welcome/page/browser/welcomePage.contribution.ts index a0b7f4ee6dac..10d152bfdd1f 100644 --- a/src/vs/workbench/contrib/welcome/page/browser/welcomePage.contribution.ts +++ b/src/vs/workbench/contrib/welcome/page/browser/welcomePage.contribution.ts @@ -12,12 +12,11 @@ import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/ import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { IEditorInputFactoryRegistry, Extensions as EditorExtensions } from 'vs/workbench/common/editor'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; +import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration'; Registry.as(ConfigurationExtensions.Configuration) .registerConfiguration({ - 'id': 'workbench', - 'order': 7, - 'title': localize('workbenchConfigurationTitle', "Workbench"), + ...workbenchConfigurationNodeBase, 'properties': { 'workbench.startupEditor': { 'scope': ConfigurationScope.APPLICATION, // Make sure repositories cannot trigger opening a README for tracking. @@ -40,7 +39,7 @@ Registry.as(WorkbenchExtensions.Workbench) .registerWorkbenchContribution(WelcomePageContribution, LifecyclePhase.Restored); Registry.as(ActionExtensions.WorkbenchActions) - .registerWorkbenchAction(new SyncActionDescriptor(WelcomePageAction, WelcomePageAction.ID, WelcomePageAction.LABEL), 'Help: Welcome', localize('help', "Help")); + .registerWorkbenchAction(SyncActionDescriptor.create(WelcomePageAction, WelcomePageAction.ID, WelcomePageAction.LABEL), 'Help: Welcome', localize('help', "Help")); Registry.as(EditorExtensions.EditorInputFactories).registerEditorInputFactory(WelcomeInputFactory.ID, WelcomeInputFactory); diff --git a/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts b/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts index 3efd68677876..760195d8def9 100644 --- a/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts +++ b/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts @@ -44,6 +44,7 @@ import { IRecentlyOpened, isRecentWorkspace, IRecentWorkspace, IRecentFolder, is import { CancellationToken } from 'vs/base/common/cancellation'; import 'sql/workbench/contrib/welcome/page/browser/az_data_welcome_page'; // {{SQL CARBON EDIT}} import { IHostService } from 'vs/workbench/services/host/browser/host'; +import { IProductService } from 'vs/platform/product/common/productService'; const configurationKey = 'workbench.startupEditor'; const oldConfigurationKey = 'workbench.welcome.enabled'; @@ -264,7 +265,9 @@ class WelcomePage extends Disposable { @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, @ILifecycleService lifecycleService: ILifecycleService, @ITelemetryService private readonly telemetryService: ITelemetryService, - @IHostService private readonly hostService: IHostService + @IHostService private readonly hostService: IHostService, + @IProductService private readonly productService: IProductService, + ) { super(); this._register(lifecycleService.onShutdown(() => this.dispose())); @@ -300,6 +303,11 @@ class WelcomePage extends Disposable { this.configurationService.updateValue(configurationKey, showOnStartup.checked ? 'welcomePage' : 'newUntitledFile', ConfigurationTarget.USER); }); + const prodName = container.querySelector('.welcomePage .title .caption') as HTMLElement; + if (prodName) { + prodName.innerHTML = this.productService.nameLong; + } + recentlyOpened.then(({ workspaces }) => { // Filter out the current workspace workspaces = workspaces.filter(recent => !this.contextService.isCurrentWorkspace(isRecentWorkspace(recent) ? recent.workspace : recent.folderUri)); diff --git a/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThrough.contribution.ts b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThrough.contribution.ts index 7000c73ef2af..8036730058c3 100644 --- a/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThrough.contribution.ts +++ b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThrough.contribution.ts @@ -20,7 +20,7 @@ import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; Registry.as(EditorExtensions.Editors) - .registerEditor(new EditorDescriptor( + .registerEditor(EditorDescriptor.create( WalkThroughPart, WalkThroughPart.ID, localize('walkThrough.editor.label', "Interactive Playground"), @@ -29,7 +29,7 @@ Registry.as(EditorExtensions.Editors) Registry.as(Extensions.WorkbenchActions) .registerWorkbenchAction( - new SyncActionDescriptor(EditorWalkThroughAction, EditorWalkThroughAction.ID, EditorWalkThroughAction.LABEL), + SyncActionDescriptor.create(EditorWalkThroughAction, EditorWalkThroughAction.ID, EditorWalkThroughAction.LABEL), 'Help: Interactive Playground', localize('help', "Help")); Registry.as(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory(EditorWalkThroughInputFactory.ID, EditorWalkThroughInputFactory); diff --git a/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.css b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.css index 967e230cddbf..cfaa3188a4d1 100644 --- a/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.css +++ b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.css @@ -8,6 +8,7 @@ padding: 10px 20px; line-height: 22px; user-select: initial; + -webkit-user-select: initial; } .monaco-workbench .part.editor > .content .walkThroughContent img { diff --git a/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts index b1e212795636..fc5887602393 100644 --- a/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts +++ b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts @@ -5,6 +5,7 @@ import 'vs/css!./walkThroughPart'; import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; +import { EventType as TouchEventType, GestureEvent, Gesture } from 'vs/base/browser/touch'; import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import * as strings from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; @@ -37,6 +38,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { Dimension, size } from 'vs/base/browser/dom'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { domEvent } from 'vs/base/browser/event'; export const WALK_THROUGH_FOCUS = new RawContextKey('interactivePlaygroundFocus', false); @@ -58,11 +60,11 @@ export class WalkThroughPart extends BaseEditor { private readonly disposables = new DisposableStore(); private contentDisposables: IDisposable[] = []; - private content: HTMLDivElement; - private scrollbar: DomScrollableElement; + private content!: HTMLDivElement; + private scrollbar!: DomScrollableElement; private editorFocus: IContextKey; - private lastFocus: HTMLElement; - private size: Dimension; + private lastFocus: HTMLElement | undefined; + private size: Dimension | undefined; private editorMemento: IEditorMemento; constructor( @@ -112,6 +114,14 @@ export class WalkThroughPart extends BaseEditor { } } + private onTouchChange(event: GestureEvent) { + event.preventDefault(); + event.stopPropagation(); + + const scrollPosition = this.scrollbar.getScrollPosition(); + this.scrollbar.setScrollPosition({ scrollTop: scrollPosition.scrollTop - event.translationY }); + } + private addEventListener(element: E, type: K, listener: (this: E, ev: HTMLElementEventMap[K]) => any, useCapture?: boolean): IDisposable; private addEventListener(element: E, type: string, listener: EventListenerOrEventListenerObject, useCapture?: boolean): IDisposable; private addEventListener(element: E, type: string, listener: EventListenerOrEventListenerObject, useCapture?: boolean): IDisposable { @@ -396,6 +406,8 @@ export class WalkThroughPart extends BaseEditor { this.scrollbar.scanDomNode(); this.loadTextEditorViewState(input); this.updatedScrollPosition(); + this.contentDisposables.push(Gesture.addTarget(innerContent)); + this.contentDisposables.push(domEvent(innerContent, TouchEventType.Change)(this.onTouchChange, this, this.disposables)); }); } diff --git a/src/vs/workbench/electron-browser/desktop.contribution.ts b/src/vs/workbench/electron-browser/desktop.contribution.ts index 6c4199cba047..4b913b299b87 100644 --- a/src/vs/workbench/electron-browser/desktop.contribution.ts +++ b/src/vs/workbench/electron-browser/desktop.contribution.ts @@ -21,6 +21,7 @@ import { IsMacContext, HasMacNativeTabsContext } from 'vs/workbench/browser/cont import { NoEditorsVisibleContext, SingleEditorGroupsContext } from 'vs/workbench/common/editor'; import { IElectronService } from 'vs/platform/electron/node/electron'; import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; +import product from 'vs/platform/product/common/product'; import { InstallVSIXAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; // {{SQL CARBON EDIT}} add import @@ -28,20 +29,20 @@ import { InstallVSIXAction } from 'vs/workbench/contrib/extensions/browser/exten (function registerActions(): void { const registry = Registry.as(Extensions.WorkbenchActions); - // Actions: View - (function registerViewActions(): void { + // Actions: Zoom + (function registerZoomActions(): void { const viewCategory = nls.localize('view', "View"); - registry.registerWorkbenchAction(new SyncActionDescriptor(ZoomInAction, ZoomInAction.ID, ZoomInAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.US_EQUAL, secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_EQUAL, KeyMod.CtrlCmd | KeyCode.NUMPAD_ADD] }), 'View: Zoom In', viewCategory); - registry.registerWorkbenchAction(new SyncActionDescriptor(ZoomOutAction, ZoomOutAction.ID, ZoomOutAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.US_MINUS, secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_MINUS, KeyMod.CtrlCmd | KeyCode.NUMPAD_SUBTRACT], linux: { primary: KeyMod.CtrlCmd | KeyCode.US_MINUS, secondary: [KeyMod.CtrlCmd | KeyCode.NUMPAD_SUBTRACT] } }), 'View: Zoom Out', viewCategory); - registry.registerWorkbenchAction(new SyncActionDescriptor(ZoomResetAction, ZoomResetAction.ID, ZoomResetAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.NUMPAD_0 }), 'View: Reset Zoom', viewCategory); + registry.registerWorkbenchAction(SyncActionDescriptor.create(ZoomInAction, ZoomInAction.ID, ZoomInAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.US_EQUAL, secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_EQUAL, KeyMod.CtrlCmd | KeyCode.NUMPAD_ADD] }), 'View: Zoom In', viewCategory); + registry.registerWorkbenchAction(SyncActionDescriptor.create(ZoomOutAction, ZoomOutAction.ID, ZoomOutAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.US_MINUS, secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_MINUS, KeyMod.CtrlCmd | KeyCode.NUMPAD_SUBTRACT], linux: { primary: KeyMod.CtrlCmd | KeyCode.US_MINUS, secondary: [KeyMod.CtrlCmd | KeyCode.NUMPAD_SUBTRACT] } }), 'View: Zoom Out', viewCategory); + registry.registerWorkbenchAction(SyncActionDescriptor.create(ZoomResetAction, ZoomResetAction.ID, ZoomResetAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.NUMPAD_0 }), 'View: Reset Zoom', viewCategory); })(); // Actions: Window (function registerWindowActions(): void { - registry.registerWorkbenchAction(new SyncActionDescriptor(CloseCurrentWindowAction, CloseCurrentWindowAction.ID, CloseCurrentWindowAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_W }), 'Close Window'); - registry.registerWorkbenchAction(new SyncActionDescriptor(SwitchWindow, SwitchWindow.ID, SwitchWindow.LABEL, { primary: 0, mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_W } }), 'Switch Window...'); - registry.registerWorkbenchAction(new SyncActionDescriptor(QuickSwitchWindow, QuickSwitchWindow.ID, QuickSwitchWindow.LABEL), 'Quick Switch Window...'); + registry.registerWorkbenchAction(SyncActionDescriptor.create(CloseCurrentWindowAction, CloseCurrentWindowAction.ID, CloseCurrentWindowAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_W }), 'Close Window'); + registry.registerWorkbenchAction(SyncActionDescriptor.create(SwitchWindow, SwitchWindow.ID, SwitchWindow.LABEL, { primary: 0, mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_W } }), 'Switch Window...'); + registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickSwitchWindow, QuickSwitchWindow.ID, QuickSwitchWindow.LABEL), 'Quick Switch Window...'); KeybindingsRegistry.registerCommandAndKeybindingRule({ id: CloseCurrentWindowAction.ID, // close the window when the last editor is closed by reusing the same keybinding @@ -91,10 +92,9 @@ import { InstallVSIXAction } from 'vs/workbench/contrib/extensions/browser/exten // Actions: Developer (function registerDeveloperActions(): void { const developerCategory = nls.localize('developer', "Developer"); - registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleSharedProcessAction, ToggleSharedProcessAction.ID, ToggleSharedProcessAction.LABEL), 'Developer: Toggle Shared Process', developerCategory); - registry.registerWorkbenchAction(new SyncActionDescriptor(ConfigureRuntimeArgumentsAction, ConfigureRuntimeArgumentsAction.ID, ConfigureRuntimeArgumentsAction.LABEL), 'Developer: Configure Runtime Arguments', developerCategory); - registry.registerWorkbenchAction(new SyncActionDescriptor(ReloadWindowWithExtensionsDisabledAction, ReloadWindowWithExtensionsDisabledAction.ID, ReloadWindowWithExtensionsDisabledAction.LABEL), 'Developer: Reload With Extensions Disabled', developerCategory); - registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleDevToolsAction, ToggleDevToolsAction.ID, ToggleDevToolsAction.LABEL), 'Developer: Toggle Developer Tools', developerCategory); + registry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleSharedProcessAction, ToggleSharedProcessAction.ID, ToggleSharedProcessAction.LABEL), 'Developer: Toggle Shared Process', developerCategory); + registry.registerWorkbenchAction(SyncActionDescriptor.create(ReloadWindowWithExtensionsDisabledAction, ReloadWindowWithExtensionsDisabledAction.ID, ReloadWindowWithExtensionsDisabledAction.LABEL), 'Developer: Reload With Extensions Disabled', developerCategory); + registry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleDevToolsAction, ToggleDevToolsAction.ID, ToggleDevToolsAction.LABEL), 'Developer: Toggle Developer Tools', developerCategory); KeybindingsRegistry.registerKeybindingRule({ id: ToggleDevToolsAction.ID, @@ -104,6 +104,12 @@ import { InstallVSIXAction } from 'vs/workbench/contrib/extensions/browser/exten mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_I } }); })(); + + // Actions: Runtime Arguments + (function registerRuntimeArgumentsAction(): void { + const preferencesCategory = nls.localize('preferences', "Preferences"); + registry.registerWorkbenchAction(SyncActionDescriptor.create(ConfigureRuntimeArgumentsAction, ConfigureRuntimeArgumentsAction.ID, ConfigureRuntimeArgumentsAction.LABEL), 'Preferences: Configure Runtime Arguments', preferencesCategory); + })(); })(); // Menu @@ -164,14 +170,16 @@ import { InstallVSIXAction } from 'vs/workbench/contrib/extensions/browser/exten order: 3 }); - MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { - group: '3_feedback', - command: { - id: 'workbench.action.openIssueReporter', - title: nls.localize({ key: 'miReportIssue', comment: ['&& denotes a mnemonic', 'Translate this to "Report Issue in English" in all languages please!'] }, "Report &&Issue") - }, - order: 3 - }); + if (!!product.reportIssueUrl) { + MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { + group: '3_feedback', + command: { + id: 'workbench.action.openIssueReporter', + title: nls.localize({ key: 'miReportIssue', comment: ['&& denotes a mnemonic', 'Translate this to "Report Issue in English" in all languages please!'] }, "Report &&Issue") + }, + order: 3 + }); + } // Tools MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { diff --git a/src/vs/workbench/electron-browser/desktop.main.ts b/src/vs/workbench/electron-browser/desktop.main.ts index e03cb33c74ae..cdd5c24de977 100644 --- a/src/vs/workbench/electron-browser/desktop.main.ts +++ b/src/vs/workbench/electron-browser/desktop.main.ts @@ -15,7 +15,7 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { WorkspaceService } from 'vs/workbench/services/configuration/browser/configurationService'; -import { WorkbenchEnvironmentService } from 'vs/workbench/services/environment/node/environmentService'; +import { NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { stat } from 'vs/base/node/pfs'; @@ -43,7 +43,7 @@ import { FileService } from 'vs/platform/files/common/fileService'; import { IFileService } from 'vs/platform/files/common/files'; import { DiskFileSystemProvider } from 'vs/platform/files/electron-browser/diskFileSystemProvider'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; -import { REMOTE_FILE_SYSTEM_CHANNEL_NAME, RemoteExtensionsFileSystemProvider } from 'vs/platform/remote/common/remoteAgentFileSystemChannel'; +import { REMOTE_FILE_SYSTEM_CHANNEL_NAME, RemoteFileSystemProvider } from 'vs/platform/remote/common/remoteAgentFileSystemChannel'; import { ConfigurationCache } from 'vs/workbench/services/configuration/node/configurationCache'; import { SpdLogService } from 'vs/platform/log/node/spdlogService'; import { SignService } from 'vs/platform/sign/node/signService'; @@ -56,12 +56,12 @@ import { ElectronEnvironmentService, IElectronEnvironmentService } from 'vs/work class DesktopMain extends Disposable { - private readonly environmentService: WorkbenchEnvironmentService; + private readonly environmentService: NativeWorkbenchEnvironmentService; constructor(private configuration: IWindowConfiguration) { super(); - this.environmentService = new WorkbenchEnvironmentService(configuration, configuration.execPath, configuration.windowId); + this.environmentService = new NativeWorkbenchEnvironmentService(configuration, configuration.execPath, configuration.windowId); this.init(); } @@ -217,7 +217,7 @@ class DesktopMain extends Disposable { const connection = remoteAgentService.getConnection(); if (connection) { const channel = connection.getChannel(REMOTE_FILE_SYSTEM_CHANNEL_NAME); - const remoteFileSystemProvider = this._register(new RemoteExtensionsFileSystemProvider(channel, remoteAgentService.getEnvironment())); + const remoteFileSystemProvider = this._register(new RemoteFileSystemProvider(channel, remoteAgentService.getEnvironment())); fileService.registerProvider(Schemas.vscodeRemote, remoteFileSystemProvider); } diff --git a/src/vs/workbench/electron-browser/window.ts b/src/vs/workbench/electron-browser/window.ts index d72cf86bc6e7..0ee0e07b4461 100644 --- a/src/vs/workbench/electron-browser/window.ts +++ b/src/vs/workbench/electron-browser/window.ts @@ -11,7 +11,7 @@ import * as DOM from 'vs/base/browser/dom'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { IAction } from 'vs/base/common/actions'; import { IFileService } from 'vs/platform/files/common/files'; -import { toResource, IUntitledResourceInput, SideBySideEditor, pathsToEditors } from 'vs/workbench/common/editor'; +import { toResource, IUntitledTextResourceInput, SideBySideEditor, pathsToEditors } from 'vs/workbench/common/editor'; import { IEditorService, IResourceEditor } from 'vs/workbench/services/editor/common/editorService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWindowSettings, IOpenFileRequest, IWindowsConfiguration, IAddFoldersRequest, IRunActionInWindowRequest, IRunKeybindingInWindowRequest, getTitleBarStyle } from 'vs/platform/windows/common/windows'; @@ -21,7 +21,7 @@ import * as browser from 'vs/base/browser/browser'; import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IResourceInput } from 'vs/platform/editor/common/editor'; import { KeyboardMapperFactory } from 'vs/workbench/services/keybinding/electron-browser/nativeKeymapService'; -import { ipcRenderer as ipc, webFrame, crashReporter, Event } from 'electron'; +import { ipcRenderer as ipc, webFrame, crashReporter, Event as IpcEvent } from 'electron'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing'; import { IMenuService, MenuId, IMenu, MenuItemAction, ICommandAction, SubmenuItemAction, MenuRegistry } from 'vs/platform/actions/common/actions'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -60,6 +60,9 @@ import { ITunnelService, extractLocalHostUriMetaDataForPortMapping } from 'vs/pl import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService'; +import { IWorkingCopyService, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { AutoSaveMode, IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; +import { Event } from 'vs/base/common/event'; export class ElectronWindow extends Disposable { @@ -67,14 +70,16 @@ export class ElectronWindow extends Disposable { private readonly touchBarDisposables = this._register(new DisposableStore()); private lastInstalledTouchedBar: ICommandAction[][] | undefined; - private customTitleContextMenuDisposable = this._register(new DisposableStore()); + private readonly customTitleContextMenuDisposable = this._register(new DisposableStore()); private previousConfiguredZoomLevel: number | undefined; - private addFoldersScheduler: RunOnceScheduler; - private pendingFoldersToAdd: URI[]; + private readonly addFoldersScheduler = this._register(new RunOnceScheduler(() => this.doAddFolders(), 100)); + private pendingFoldersToAdd: URI[] = []; - private closeEmptyWindowScheduler: RunOnceScheduler = this._register(new RunOnceScheduler(() => this.onAllEditorsClosed(), 50)); + private readonly closeEmptyWindowScheduler: RunOnceScheduler = this._register(new RunOnceScheduler(() => this.onAllEditorsClosed(), 50)); + + private isDocumentedEdited = false; constructor( @IEditorService private readonly editorService: EditorServiceImpl, @@ -99,13 +104,12 @@ export class ElectronWindow extends Disposable { @IElectronService private readonly electronService: IElectronService, @ITunnelService private readonly tunnelService: ITunnelService, @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, - @IElectronEnvironmentService private readonly electronEnvironmentService: IElectronEnvironmentService + @IElectronEnvironmentService private readonly electronEnvironmentService: IElectronEnvironmentService, + @IWorkingCopyService private readonly workingCopyService: IWorkingCopyService, + @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService ) { super(); - this.pendingFoldersToAdd = []; - this.addFoldersScheduler = this._register(new RunOnceScheduler(() => this.doAddFolders(), 100)); - this.registerListeners(); this.create(); } @@ -123,7 +127,7 @@ export class ElectronWindow extends Disposable { }); // Support runAction event - ipc.on('vscode:runAction', async (event: Event, request: IRunActionInWindowRequest) => { + ipc.on('vscode:runAction', async (event: IpcEvent, request: IRunActionInWindowRequest) => { const args: unknown[] = request.args || []; // If we run an action from the touchbar, we fill in the currently active resource @@ -154,27 +158,27 @@ export class ElectronWindow extends Disposable { }); // Support runKeybinding event - ipc.on('vscode:runKeybinding', (event: Event, request: IRunKeybindingInWindowRequest) => { + ipc.on('vscode:runKeybinding', (event: IpcEvent, request: IRunKeybindingInWindowRequest) => { if (document.activeElement) { this.keybindingService.dispatchByUserSettingsLabel(request.userSettingsLabel, document.activeElement); } }); // Error reporting from main - ipc.on('vscode:reportError', (event: Event, error: string) => { + ipc.on('vscode:reportError', (event: IpcEvent, error: string) => { if (error) { errors.onUnexpectedError(JSON.parse(error)); } }); // Support openFiles event for existing and new files - ipc.on('vscode:openFiles', (event: Event, request: IOpenFileRequest) => this.onOpenFiles(request)); + ipc.on('vscode:openFiles', (event: IpcEvent, request: IOpenFileRequest) => this.onOpenFiles(request)); // Support addFolders event if we have a workspace opened - ipc.on('vscode:addFolders', (event: Event, request: IAddFoldersRequest) => this.onAddFoldersRequest(request)); + ipc.on('vscode:addFolders', (event: IpcEvent, request: IAddFoldersRequest) => this.onAddFoldersRequest(request)); // Message support - ipc.on('vscode:showInfoMessage', (event: Event, message: string) => { + ipc.on('vscode:showInfoMessage', (event: IpcEvent, message: string) => { this.notificationService.info(message); }); @@ -212,7 +216,7 @@ export class ElectronWindow extends Disposable { }); // keyboard layout changed event - ipc.on('vscode:accessibilitySupportChanged', (event: Event, accessibilitySupportEnabled: boolean) => { + ipc.on('vscode:accessibilitySupportChanged', (event: IpcEvent, accessibilitySupportEnabled: boolean) => { this.accessibilityService.setAccessibilitySupport(accessibilitySupportEnabled ? AccessibilitySupport.Enabled : AccessibilitySupport.Disabled); }); @@ -261,6 +265,34 @@ export class ElectronWindow extends Disposable { this.electronService.handleTitleDoubleClick(); })); } + + // Document edited (macOS only): indicate for dirty working copies + if (isMacintosh) { + this._register(this.workingCopyService.onDidChangeDirty(workingCopy => { + const gotDirty = workingCopy.isDirty(); + if (gotDirty && !!(workingCopy.capabilities & WorkingCopyCapabilities.AutoSave) && this.filesConfigurationService.getAutoSaveMode() === AutoSaveMode.AFTER_SHORT_DELAY) { + return; // do not indicate dirty of working copies that are auto saved after short delay + } + + if ((!this.isDocumentedEdited && gotDirty) || (this.isDocumentedEdited && !gotDirty)) { + const hasDirtyFiles = this.workingCopyService.hasDirty; + this.isDocumentedEdited = hasDirtyFiles; + + this.electronService.setDocumentEdited(hasDirtyFiles); + } + })); + } + + this._register(Event.any( + Event.map(Event.filter(this.electronService.onWindowMaximize, id => id === this.electronEnvironmentService.windowId), () => true), + Event.map(Event.filter(this.electronService.onWindowUnmaximize, id => id === this.electronEnvironmentService.windowId), () => false) + )(e => this.onDidChangeMaximized(e))); + + this.onDidChangeMaximized(this.environmentService.configuration.maximized ?? false); + } + + private onDidChangeMaximized(maximized: boolean): void { + this.layoutService.updateWindowMaximizedState(maximized); } private onDidVisibleEditorsChange(): void { @@ -398,29 +430,23 @@ export class ElectronWindow extends Disposable { throw new Error('Prevented call to window.open(). Use IOpenerService instead!'); }; - // Handle internal open() calls - this.openerService.registerOpener({ - open: async (resource: URI, options?: OpenOptions): Promise => { - - // If either the caller wants to open externally or the - // scheme is one where we prefer to open externally - // we handle this resource by delegating the opening to - // the main process to prevent window focus issues. - if (this.shouldOpenExternal(resource, options)) { - const { resolved } = await this.openerService.resolveExternalUri(resource, options); - const success = await this.electronService.openExternal(encodeURI(resolved.toString(true))); - if (!success && resolved.scheme === Schemas.file) { + // Handle external open() calls + this.openerService.setExternalOpener({ + openExternal: async (href: string) => { + const success = await this.electronService.openExternal(href); + if (!success) { + const fileCandidate = URI.parse(href); + if (fileCandidate.scheme === Schemas.file) { // if opening failed, and this is a file, we can still try to reveal it - await this.electronService.showItemInFolder(resolved.fsPath); + await this.electronService.showItemInFolder(fileCandidate.fsPath); } - - return true; } - return false; // not handled by us + return true; } }); + // Register external URI resolver this.openerService.registerExternalUriResolver({ resolveExternalUri: async (uri: URI, options?: OpenOptions) => { if (options?.allowTunneling) { @@ -440,12 +466,6 @@ export class ElectronWindow extends Disposable { }); } - private shouldOpenExternal(resource: URI, options?: OpenOptions) { - const scheme = resource.scheme.toLowerCase(); - const preferOpenExternal = (scheme === Schemas.mailto || scheme === Schemas.http || scheme === Schemas.https); - return options?.openExternal || preferOpenExternal; - } - private updateTouchbarMenu(): void { if (!isMacintosh) { return; // macOS only @@ -628,7 +648,7 @@ export class ElectronWindow extends Disposable { }); } - private async openResources(resources: Array, diffMode: boolean): Promise { + private async openResources(resources: Array, diffMode: boolean): Promise { await this.lifecycleService.when(LifecyclePhase.Ready); // In diffMode we open 2 resources as diff diff --git a/src/vs/workbench/services/accessibility/node/accessibilityService.ts b/src/vs/workbench/services/accessibility/node/accessibilityService.ts index 2d3b3fba1e30..8f449ecb16d9 100644 --- a/src/vs/workbench/services/accessibility/node/accessibilityService.ts +++ b/src/vs/workbench/services/accessibility/node/accessibilityService.ts @@ -24,14 +24,16 @@ export class AccessibilityService extends AbstractAccessibilityService implement _serviceBrand: undefined; private _accessibilitySupport = AccessibilitySupport.Unknown; + private didSendTelemetry = false; constructor( - @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, - @IContextKeyService readonly contextKeyService: IContextKeyService, - @IConfigurationService readonly configurationService: IConfigurationService, + @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, + @IContextKeyService contextKeyService: IContextKeyService, + @IConfigurationService configurationService: IConfigurationService, @ITelemetryService private readonly _telemetryService: ITelemetryService ) { super(contextKeyService, configurationService); + this.setAccessibilitySupport(environmentService.configuration.accessibilitySupport ? AccessibilitySupport.Enabled : AccessibilitySupport.Disabled); } alwaysUnderlineAccessKeys(): Promise { @@ -61,17 +63,13 @@ export class AccessibilityService extends AbstractAccessibilityService implement this._accessibilitySupport = accessibilitySupport; this._onDidChangeAccessibilitySupport.fire(); - if (accessibilitySupport === AccessibilitySupport.Enabled) { + if (!this.didSendTelemetry && accessibilitySupport === AccessibilitySupport.Enabled) { this._telemetryService.publicLog2('accessibility', { enabled: true }); + this.didSendTelemetry = true; } } getAccessibilitySupport(): AccessibilitySupport { - if (this._accessibilitySupport === AccessibilitySupport.Unknown) { - const config = this.environmentService.configuration; - this._accessibilitySupport = config?.accessibilitySupport ? AccessibilitySupport.Enabled : AccessibilitySupport.Disabled; - } - return this._accessibilitySupport; } } diff --git a/src/vs/platform/auth/common/authTokenService.ts b/src/vs/workbench/services/authToken/browser/authTokenService.ts similarity index 51% rename from src/vs/platform/auth/common/authTokenService.ts rename to src/vs/workbench/services/authToken/browser/authTokenService.ts index 23c2c3565f6a..3622da7a0d63 100644 --- a/src/vs/platform/auth/common/authTokenService.ts +++ b/src/vs/workbench/services/authToken/browser/authTokenService.ts @@ -3,12 +3,13 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { localize } from 'vs/nls'; import { Event, Emitter } from 'vs/base/common/event'; import { IAuthTokenService, AuthTokenStatus } from 'vs/platform/auth/common/auth'; import { ICredentialsService } from 'vs/platform/credentials/common/credentials'; import { Disposable } from 'vs/base/common/lifecycle'; -import { IProductService } from 'vs/platform/product/common/productService'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { URI } from 'vs/base/common/uri'; const SERVICE_NAME = 'VS Code'; const ACCOUNT = 'MyAccount'; @@ -16,55 +17,51 @@ const ACCOUNT = 'MyAccount'; export class AuthTokenService extends Disposable implements IAuthTokenService { _serviceBrand: undefined; - private _status: AuthTokenStatus = AuthTokenStatus.Disabled; + private _status: AuthTokenStatus = AuthTokenStatus.Initializing; get status(): AuthTokenStatus { return this._status; } private _onDidChangeStatus: Emitter = this._register(new Emitter()); readonly onDidChangeStatus: Event = this._onDidChangeStatus.event; + readonly _onDidGetCallback: Emitter = this._register(new Emitter()); + constructor( @ICredentialsService private readonly credentialsService: ICredentialsService, - @IProductService productService: IProductService, - @IConfigurationService configurationService: IConfigurationService, + @IQuickInputService private readonly quickInputService: IQuickInputService ) { super(); - if (productService.settingsSyncStoreUrl && configurationService.getValue('configurationSync.enableAuth')) { - this._status = AuthTokenStatus.Inactive; - this.getToken().then(token => { - if (token) { - this.setStatus(AuthTokenStatus.Active); - } - }); - } + this.getToken().then(token => { + if (token) { + this.setStatus(AuthTokenStatus.SignedIn); + } else { + this.setStatus(AuthTokenStatus.SignedOut); + } + }); } - getToken(): Promise { - if (this.status === AuthTokenStatus.Disabled) { - throw new Error('Not enabled'); + async getToken(): Promise { + const token = await this.credentialsService.getPassword(SERVICE_NAME, ACCOUNT); + if (token) { + return token; } - return this.credentialsService.getPassword(SERVICE_NAME, ACCOUNT); + + return undefined; // {{SQL CARBON EDIT}} strict-null-check } - async updateToken(token: string): Promise { - if (this.status === AuthTokenStatus.Disabled) { - throw new Error('Not enabled'); + async login(): Promise { + const token = await this.quickInputService.input({ placeHolder: localize('enter token', "Please provide the auth bearer token"), ignoreFocusLost: true, }); + if (token) { + await this.credentialsService.setPassword(SERVICE_NAME, ACCOUNT, token); + this.setStatus(AuthTokenStatus.SignedIn); } - await this.credentialsService.setPassword(SERVICE_NAME, ACCOUNT, token); - this.setStatus(AuthTokenStatus.Active); } async refreshToken(): Promise { - if (this.status === AuthTokenStatus.Disabled) { - throw new Error('Not enabled'); - } - await this.deleteToken(); + await this.logout(); } - async deleteToken(): Promise { - if (this.status === AuthTokenStatus.Disabled) { - throw new Error('Not enabled'); - } + async logout(): Promise { await this.credentialsService.deletePassword(SERVICE_NAME, ACCOUNT); - this.setStatus(AuthTokenStatus.Inactive); + this.setStatus(AuthTokenStatus.SignedOut); } private setStatus(status: AuthTokenStatus): void { @@ -75,4 +72,3 @@ export class AuthTokenService extends Disposable implements IAuthTokenService { } } - diff --git a/src/vs/workbench/services/authToken/electron-browser/authTokenService.ts b/src/vs/workbench/services/authToken/electron-browser/authTokenService.ts index c9537db9348e..4719b29741d2 100644 --- a/src/vs/workbench/services/authToken/electron-browser/authTokenService.ts +++ b/src/vs/workbench/services/authToken/electron-browser/authTokenService.ts @@ -9,6 +9,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IAuthTokenService, AuthTokenStatus } from 'vs/platform/auth/common/auth'; +import { URI } from 'vs/base/common/uri'; export class AuthTokenService extends Disposable implements IAuthTokenService { @@ -16,41 +17,43 @@ export class AuthTokenService extends Disposable implements IAuthTokenService { private readonly channel: IChannel; - private _status: AuthTokenStatus = AuthTokenStatus.Disabled; + private _status: AuthTokenStatus = AuthTokenStatus.Initializing; get status(): AuthTokenStatus { return this._status; } private _onDidChangeStatus: Emitter = this._register(new Emitter()); readonly onDidChangeStatus: Event = this._onDidChangeStatus.event; + readonly _onDidGetCallback: Emitter = this._register(new Emitter()); + constructor( - @ISharedProcessService sharedProcessService: ISharedProcessService + @ISharedProcessService sharedProcessService: ISharedProcessService, ) { super(); this.channel = sharedProcessService.getChannel('authToken'); - this.channel.call('_getInitialStatus').then(status => { - this.updateStatus(status); - this._register(this.channel.listen('onDidChangeStatus')(status => this.updateStatus(status))); - }); + this._register(this.channel.listen('onDidChangeStatus')(status => this.updateStatus(status))); + this.channel.call('_getInitialStatus').then(status => this.updateStatus(status)); } getToken(): Promise { return this.channel.call('getToken'); } - updateToken(token: string): Promise { - return this.channel.call('updateToken', [token]); + login(): Promise { + return this.channel.call('login'); } refreshToken(): Promise { return this.channel.call('getToken'); } - deleteToken(): Promise { - return this.channel.call('deleteToken'); + logout(): Promise { + return this.channel.call('logout'); } private async updateStatus(status: AuthTokenStatus): Promise { - this._status = status; - this._onDidChangeStatus.fire(status); + if (status !== AuthTokenStatus.Initializing) { + this._status = status; + this._onDidChangeStatus.fire(status); + } } } diff --git a/src/vs/workbench/services/backup/common/backup.ts b/src/vs/workbench/services/backup/common/backup.ts index e07605e486f9..9a8948149ce2 100644 --- a/src/vs/workbench/services/backup/common/backup.ts +++ b/src/vs/workbench/services/backup/common/backup.ts @@ -6,8 +6,6 @@ import { URI } from 'vs/base/common/uri'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ITextBufferFactory, ITextSnapshot } from 'vs/editor/common/model'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { joinPath, relativePath } from 'vs/base/common/resources'; export const IBackupFileService = createDecorator('backupFileService'); @@ -88,7 +86,3 @@ export interface IBackupFileService { */ discardAllWorkspaceBackups(): Promise; } - -export function toBackupWorkspaceResource(backupWorkspacePath: string, environmentService: IEnvironmentService): URI { - return joinPath(environmentService.userRoamingDataHome, relativePath(URI.file(environmentService.userDataPath), URI.file(backupWorkspacePath))!); -} diff --git a/src/vs/workbench/services/backup/common/backupFileService.ts b/src/vs/workbench/services/backup/common/backupFileService.ts index 26a46a7e4dfb..c33c52589c4b 100644 --- a/src/vs/workbench/services/backup/common/backupFileService.ts +++ b/src/vs/workbench/services/backup/common/backupFileService.ts @@ -429,13 +429,13 @@ export class InMemoryBackupFileService implements IBackupFileService { return Promise.resolve(); } - resolveBackupContent(backupResource: URI): Promise> { + async resolveBackupContent(backupResource: URI): Promise> { const snapshot = this.backups.get(backupResource.toString()); if (snapshot) { - return Promise.resolve({ value: createTextBufferFactoryFromSnapshot(snapshot) }); + return { value: createTextBufferFactoryFromSnapshot(snapshot) }; } - return Promise.reject('Unexpected backup resource to resolve'); + throw new Error('Unexpected backup resource to resolve'); } getWorkspaceFileBackups(): Promise { diff --git a/src/vs/workbench/services/backup/electron-browser/backup.ts b/src/vs/workbench/services/backup/electron-browser/backup.ts new file mode 100644 index 000000000000..3c3b9887eea0 --- /dev/null +++ b/src/vs/workbench/services/backup/electron-browser/backup.ts @@ -0,0 +1,12 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { URI } from 'vs/base/common/uri'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { joinPath, relativePath } from 'vs/base/common/resources'; + +export function toBackupWorkspaceResource(backupWorkspacePath: string, environmentService: IEnvironmentService): URI { + return joinPath(environmentService.userRoamingDataHome, relativePath(URI.file(environmentService.userDataPath), URI.file(backupWorkspacePath))!); +} diff --git a/src/vs/workbench/services/backup/test/node/backupFileService.test.ts b/src/vs/workbench/services/backup/test/electron-browser/backupFileService.test.ts similarity index 99% rename from src/vs/workbench/services/backup/test/node/backupFileService.test.ts rename to src/vs/workbench/services/backup/test/electron-browser/backupFileService.test.ts index 294945d5d3ca..b52720a9e51a 100644 --- a/src/vs/workbench/services/backup/test/node/backupFileService.test.ts +++ b/src/vs/workbench/services/backup/test/electron-browser/backupFileService.test.ts @@ -20,7 +20,7 @@ import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; import { FileService } from 'vs/platform/files/common/fileService'; import { NullLogService } from 'vs/platform/log/common/log'; import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider'; -import { WorkbenchEnvironmentService } from 'vs/workbench/services/environment/node/environmentService'; +import { NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv'; import { snapshotToString } from 'vs/workbench/services/textfile/common/textfiles'; import { IFileService } from 'vs/platform/files/common/files'; @@ -46,7 +46,7 @@ const fooBackupPath = path.join(workspaceBackupPath, 'file', hashPath(fooFile)); const barBackupPath = path.join(workspaceBackupPath, 'file', hashPath(barFile)); const untitledBackupPath = path.join(workspaceBackupPath, 'untitled', hashPath(untitledFile)); -class TestBackupEnvironmentService extends WorkbenchEnvironmentService { +class TestBackupEnvironmentService extends NativeWorkbenchEnvironmentService { constructor(backupPath: string) { super({ ...parseArgs(process.argv, OPTIONS), ...{ backupPath, 'user-data-dir': userdataDir } } as IWindowConfiguration, process.execPath, 0); diff --git a/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts b/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts index c132e63e7a6d..c7b6adeeecf2 100644 --- a/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts +++ b/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts @@ -226,17 +226,18 @@ class BulkEditModel implements IDisposable { } } -export type Edit = ResourceFileEdit | ResourceTextEdit; +type Edit = ResourceFileEdit | ResourceTextEdit; -export class BulkEdit { +class BulkEdit { - private _edits: Edit[] = []; - private _editor: ICodeEditor | undefined; - private _progress: IProgress; + private readonly _edits: Edit[] = []; + private readonly _editor: ICodeEditor | undefined; + private readonly _progress: IProgress; constructor( editor: ICodeEditor | undefined, progress: IProgress | undefined, + edits: Edit[], @ILogService private readonly _logService: ILogService, @ITextModelService private readonly _textModelService: ITextModelService, @IFileService private readonly _fileService: IFileService, @@ -246,14 +247,7 @@ export class BulkEdit { ) { this._editor = editor; this._progress = progress || emptyProgress; - } - - add(edits: Edit[] | Edit): void { - if (Array.isArray(edits)) { - this._edits.push(...edits); - } else { - this._edits.push(edits); - } + this._edits = edits; } ariaMessage(): string { @@ -419,8 +413,11 @@ export class BulkEditService implements IBulkEditService { // If the code editor is readonly still allow bulk edits to be applied #68549 codeEditor = undefined; } - const bulkEdit = new BulkEdit(codeEditor, options.progress, this._logService, this._textModelService, this._fileService, this._textFileService, this._labelService, this._configurationService); - bulkEdit.add(edits); + const bulkEdit = new BulkEdit( + codeEditor, options.progress, edits, + this._logService, this._textModelService, this._fileService, this._textFileService, this._labelService, this._configurationService + ); + return bulkEdit.perform().then(() => { return { ariaSummary: bulkEdit.ariaMessage() }; diff --git a/src/vs/workbench/services/clipboard/browser/clipboardService.ts b/src/vs/workbench/services/clipboard/browser/clipboardService.ts index b30f50f6ba5e..9b2fd1460666 100644 --- a/src/vs/workbench/services/clipboard/browser/clipboardService.ts +++ b/src/vs/workbench/services/clipboard/browser/clipboardService.ts @@ -18,7 +18,28 @@ export class BrowserClipboardService implements IClipboardService { return; // TODO@sbatten } - return navigator.clipboard.writeText(text); + if (navigator.clipboard && navigator.clipboard.writeText) { + return navigator.clipboard.writeText(text); + } else { + const activeElement = document.activeElement; + const newTextarea = document.createElement('textarea'); + newTextarea.className = 'clipboard-copy'; + newTextarea.style.visibility = 'false'; + newTextarea.style.height = '1px'; + newTextarea.style.width = '1px'; + newTextarea.setAttribute('aria-hidden', 'true'); + newTextarea.style.position = 'absolute'; + newTextarea.style.top = '-1000'; + newTextarea.style.left = '-1000'; + document.body.appendChild(newTextarea); + newTextarea.value = text; + newTextarea.focus(); + newTextarea.select(); + document.execCommand('copy'); + activeElement.focus(); + document.body.removeChild(newTextarea); + } + return; } async readText(type?: string): Promise { diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index e133b052dbe6..136ab47c7226 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -33,7 +33,7 @@ export class WorkspaceService extends Disposable implements IConfigurationServic public _serviceBrand: undefined; - private workspace: Workspace; + private workspace!: Workspace; private completeWorkspaceBarrier: Barrier; private readonly configurationCache: IConfigurationCache; private _configuration: Configuration; diff --git a/src/vs/workbench/services/configuration/common/configurationEditingService.ts b/src/vs/workbench/services/configuration/common/configurationEditingService.ts index 41281c251363..54a4b398605a 100644 --- a/src/vs/workbench/services/configuration/common/configurationEditingService.ts +++ b/src/vs/workbench/services/configuration/common/configurationEditingService.ts @@ -158,7 +158,7 @@ export class ConfigurationEditingService { writeConfiguration(target: EditableConfigurationTarget, value: IConfigurationValue, options: IConfigurationEditingOptions = {}): Promise { const operation = this.getConfigurationEditOperation(target, value, options.scopes || {}); return Promise.resolve(this.queue.queue(() => this.doWriteConfiguration(operation, options) // queue up writes to prevent race conditions - .then(() => null, + .then(() => { }, error => { if (!options.donotNotifyError) { this.onError(error, operation, options.scopes); @@ -409,7 +409,7 @@ export class ConfigurationEditingService { return false; } const parseErrors: json.ParseError[] = []; - json.parse(model.getValue(), parseErrors); + json.parse(model.getValue(), parseErrors, { allowTrailingComma: true, allowEmptyContent: true }); return parseErrors.length > 0; } @@ -436,7 +436,7 @@ export class ConfigurationEditingService { } if (target === EditableConfigurationTarget.WORKSPACE) { - if (!operation.workspaceStandAloneConfigurationKey) { + if (!operation.workspaceStandAloneConfigurationKey && !OVERRIDE_PROPERTY_PATTERN.test(operation.key)) { const configurationProperties = Registry.as(ConfigurationExtensions.Configuration).getConfigurationProperties(); if (configurationProperties[operation.key].scope === ConfigurationScope.APPLICATION) { return this.reject(ConfigurationEditingErrorCode.ERROR_INVALID_WORKSPACE_CONFIGURATION_APPLICATION, target, operation); @@ -452,7 +452,7 @@ export class ConfigurationEditingService { return this.reject(ConfigurationEditingErrorCode.ERROR_INVALID_FOLDER_TARGET, target, operation); } - if (!operation.workspaceStandAloneConfigurationKey) { + if (!operation.workspaceStandAloneConfigurationKey && !OVERRIDE_PROPERTY_PATTERN.test(operation.key)) { const configurationProperties = Registry.as(ConfigurationExtensions.Configuration).getConfigurationProperties(); if (configurationProperties[operation.key].scope !== ConfigurationScope.RESOURCE) { return this.reject(ConfigurationEditingErrorCode.ERROR_INVALID_FOLDER_CONFIGURATION, target, operation); diff --git a/src/vs/workbench/services/configuration/common/jsonEditingService.ts b/src/vs/workbench/services/configuration/common/jsonEditingService.ts index ad91a72bb72b..08a52db25448 100644 --- a/src/vs/workbench/services/configuration/common/jsonEditingService.ts +++ b/src/vs/workbench/services/configuration/common/jsonEditingService.ts @@ -98,7 +98,7 @@ export class JSONEditingService implements IJSONEditingService { private hasParseErrors(model: ITextModel): boolean { const parseErrors: json.ParseError[] = []; - json.parse(model.getValue(), parseErrors); + json.parse(model.getValue(), parseErrors, { allowTrailingComma: true, allowEmptyContent: true }); return parseErrors.length > 0; } diff --git a/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts b/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts index 3cf078487cab..211c2b95bbab 100644 --- a/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts +++ b/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts @@ -18,7 +18,7 @@ import * as uuid from 'vs/base/common/uuid'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; import { WorkspaceService } from 'vs/workbench/services/configuration/browser/configurationService'; import { ConfigurationEditingService, ConfigurationEditingError, ConfigurationEditingErrorCode, EditableConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditingService'; -import { WORKSPACE_STANDALONE_CONFIGURATIONS } from 'vs/workbench/services/configuration/common/configuration'; +import { WORKSPACE_STANDALONE_CONFIGURATIONS, FOLDER_SETTINGS_PATH } from 'vs/workbench/services/configuration/common/configuration'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; @@ -39,11 +39,11 @@ import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemPro import { IFileService } from 'vs/platform/files/common/files'; import { ConfigurationCache } from 'vs/workbench/services/configuration/node/configurationCache'; import { KeybindingsEditingService, IKeybindingEditingService } from 'vs/workbench/services/keybinding/common/keybindingEditing'; -import { WorkbenchEnvironmentService } from 'vs/workbench/services/environment/node/environmentService'; +import { NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; import { FileUserDataProvider } from 'vs/workbench/services/userData/common/fileUserDataProvider'; -class TestEnvironmentService extends WorkbenchEnvironmentService { +class TestEnvironmentService extends NativeWorkbenchEnvironmentService { constructor(private _appSettingsHome: URI) { super(parseArgs(process.argv, OPTIONS) as IWindowConfiguration, process.execPath, 0); @@ -236,6 +236,41 @@ suite('ConfigurationEditingService', () => { }); }); + test('write overridable settings to user settings', () => { + const key = '[language]'; + const value = { 'configurationEditing.service.testSetting': 'overridden value' }; + return testObject.writeConfiguration(EditableConfigurationTarget.USER_LOCAL, { key, value }) + .then(() => { + const contents = fs.readFileSync(globalSettingsFile).toString('utf8'); + const parsed = json.parse(contents); + assert.deepEqual(parsed[key], value); + }); + }); + + test('write overridable settings to workspace settings', () => { + const key = '[language]'; + const value = { 'configurationEditing.service.testSetting': 'overridden value' }; + return testObject.writeConfiguration(EditableConfigurationTarget.WORKSPACE, { key, value }) + .then(() => { + const target = path.join(workspaceDir, FOLDER_SETTINGS_PATH); + const contents = fs.readFileSync(target).toString('utf8'); + const parsed = json.parse(contents); + assert.deepEqual(parsed[key], value); + }); + }); + + test('write overridable settings to workspace folder settings', () => { + const key = '[language]'; + const value = { 'configurationEditing.service.testSetting': 'overridden value' }; + const folderSettingsFile = path.join(workspaceDir, FOLDER_SETTINGS_PATH); + return testObject.writeConfiguration(EditableConfigurationTarget.WORKSPACE_FOLDER, { key, value }, { scopes: { resource: URI.file(folderSettingsFile) } }) + .then(() => { + const contents = fs.readFileSync(folderSettingsFile).toString('utf8'); + const parsed = json.parse(contents); + assert.deepEqual(parsed[key], value); + }); + }); + test('write workspace standalone setting - empty file', () => { return testObject.writeConfiguration(EditableConfigurationTarget.WORKSPACE, { key: 'tasks.service.testSetting', value: 'value' }) .then(() => { diff --git a/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts b/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts index 7528c24e9ba5..cb428f846f4e 100644 --- a/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts @@ -45,10 +45,10 @@ import { IConfigurationCache } from 'vs/workbench/services/configuration/common/ import { SignService } from 'vs/platform/sign/browser/signService'; import { FileUserDataProvider } from 'vs/workbench/services/userData/common/fileUserDataProvider'; import { IKeybindingEditingService, KeybindingsEditingService } from 'vs/workbench/services/keybinding/common/keybindingEditing'; -import { WorkbenchEnvironmentService } from 'vs/workbench/services/environment/node/environmentService'; +import { NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -class TestEnvironmentService extends WorkbenchEnvironmentService { +class TestEnvironmentService extends NativeWorkbenchEnvironmentService { constructor(private _appSettingsHome: URI) { super(parseArgs(process.argv, OPTIONS) as IWindowConfiguration, process.execPath, 0); diff --git a/src/vs/workbench/services/configurationResolver/common/variableResolver.ts b/src/vs/workbench/services/configurationResolver/common/variableResolver.ts index 0fef61e5c9fb..a54f09b52ae0 100644 --- a/src/vs/workbench/services/configurationResolver/common/variableResolver.ts +++ b/src/vs/workbench/services/configurationResolver/common/variableResolver.ts @@ -32,17 +32,24 @@ export class AbstractVariableResolverService implements IConfigurationResolverSe _serviceBrand: undefined; + private _context: IVariableResolveContext; + private _envVariables?: IProcessEnvironment; protected _contributedVariables: Map Promise> = new Map(); - constructor( - private _context: IVariableResolveContext, - private _envVariables: IProcessEnvironment - ) { - if (isWindows && _envVariables) { - this._envVariables = Object.create(null); - Object.keys(_envVariables).forEach(key => { - this._envVariables[key.toLowerCase()] = _envVariables[key]; - }); + + constructor(_context: IVariableResolveContext, _envVariables?: IProcessEnvironment) { + this._context = _context; + if (_envVariables) { + if (isWindows) { + // windows env variables are case insensitive + const ev: IProcessEnvironment = Object.create(null); + this._envVariables = ev; + Object.keys(_envVariables).forEach(key => { + ev[key.toLowerCase()] = _envVariables[key]; + }); + } else { + this._envVariables = _envVariables; + } } } @@ -180,14 +187,13 @@ export class AbstractVariableResolverService implements IConfigurationResolverSe case 'env': if (argument) { - if (isWindows) { - argument = argument.toLowerCase(); - } - const env = this._envVariables[argument]; - if (types.isString(env)) { - return env; + if (this._envVariables) { + const env = this._envVariables[isWindows ? argument.toLowerCase() : argument]; + if (types.isString(env)) { + return env; + } } - // For `env` we should do the same as a normal shell does - evaluates missing envs to an empty string #46436 + // For `env` we should do the same as a normal shell does - evaluates undefined envs to an empty string #46436 return ''; } throw new Error(localize('missingEnvVarName', "'{0}' can not be resolved because no environment variable name is given.", match)); diff --git a/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts b/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts index 86347e2a106c..5193ff073a80 100644 --- a/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts +++ b/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts @@ -19,7 +19,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import * as Types from 'vs/base/common/types'; import { EditorType } from 'vs/editor/common/editorCommon'; import { Selection } from 'vs/editor/common/core/selection'; -import { WorkbenchEnvironmentService } from 'vs/workbench/services/environment/node/environmentService'; +import { NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -642,7 +642,7 @@ class MockInputsConfigurationService extends TestConfigurationService { } } -class MockWorkbenchEnvironmentService extends WorkbenchEnvironmentService { +class MockWorkbenchEnvironmentService extends NativeWorkbenchEnvironmentService { constructor(env: platform.IProcessEnvironment) { super({ userEnv: env } as IWindowConfiguration, process.execPath, 0); diff --git a/src/vs/workbench/services/contextmenu/electron-browser/contextmenuService.ts b/src/vs/workbench/services/contextmenu/electron-browser/contextmenuService.ts index 20cc3c44d14d..a3498fee9555 100644 --- a/src/vs/workbench/services/contextmenu/electron-browser/contextmenuService.ts +++ b/src/vs/workbench/services/contextmenu/electron-browser/contextmenuService.ts @@ -91,18 +91,25 @@ class NativeContextMenuService extends Disposable implements IContextMenuService const anchor = delegate.getAnchor(); let x: number, y: number; + let zoom = webFrame.getZoomFactor(); if (dom.isHTMLElement(anchor)) { let elementPosition = dom.getDomNodePagePosition(anchor); x = elementPosition.left; y = elementPosition.top + elementPosition.height; + + // Shift macOS menus by a few pixels below elements + // to account for extra padding on top of native menu + // https://github.com/microsoft/vscode/issues/84231 + if (isMacintosh) { + y += 4 / zoom; + } } else { const pos: { x: number; y: number; } = anchor; x = pos.x + 1; /* prevent first item from being selected automatically under mouse */ y = pos.y; } - let zoom = webFrame.getZoomFactor(); x *= zoom; y *= zoom; diff --git a/src/vs/workbench/services/decorations/browser/decorations.ts b/src/vs/workbench/services/decorations/browser/decorations.ts index c5fd3b8b1a48..b90138e73916 100644 --- a/src/vs/workbench/services/decorations/browser/decorations.ts +++ b/src/vs/workbench/services/decorations/browser/decorations.ts @@ -20,7 +20,7 @@ export interface IDecorationData { readonly bubble?: boolean; } -export interface IDecoration { +export interface IDecoration extends IDisposable { readonly tooltip: string; readonly labelClassName: string; readonly badgeClassName: string; diff --git a/src/vs/workbench/services/decorations/browser/decorationsService.ts b/src/vs/workbench/services/decorations/browser/decorationsService.ts index 7cf9130eb4c4..6f937d8209c0 100644 --- a/src/vs/workbench/services/decorations/browser/decorationsService.ts +++ b/src/vs/workbench/services/decorations/browser/decorationsService.ts @@ -12,14 +12,13 @@ import { isThenable } from 'vs/base/common/async'; import { LinkedList } from 'vs/base/common/linkedList'; import { createStyleSheet, createCSSRule, removeCSSRulesContainingSelector } from 'vs/base/browser/dom'; import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService'; -import { IdGenerator } from 'vs/base/common/idGenerator'; -import { Iterator } from 'vs/base/common/iterator'; import { isFalsyOrWhitespace } from 'vs/base/common/strings'; import { localize } from 'vs/nls'; import { isPromiseCanceledError } from 'vs/base/common/errors'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ILogService } from 'vs/platform/log/common/log'; +import { hash } from 'vs/base/common/hash'; class DecorationRule { @@ -32,18 +31,29 @@ class DecorationRule { } } - private static readonly _classNames = new IdGenerator('monaco-decorations-style-'); + private static readonly _classNamesPrefix = 'monaco-decoration'; readonly data: IDecorationData | IDecorationData[]; readonly itemColorClassName: string; readonly itemBadgeClassName: string; readonly bubbleBadgeClassName: string; - constructor(data: IDecorationData | IDecorationData[]) { + private _refCounter: number = 0; + + constructor(data: IDecorationData | IDecorationData[], key: string) { this.data = data; - this.itemColorClassName = DecorationRule._classNames.nextId(); - this.itemBadgeClassName = DecorationRule._classNames.nextId(); - this.bubbleBadgeClassName = DecorationRule._classNames.nextId(); + const suffix = hash(key).toString(36); + this.itemColorClassName = `${DecorationRule._classNamesPrefix}-itemColor-${suffix}`; + this.itemBadgeClassName = `${DecorationRule._classNamesPrefix}-itemBadge-${suffix}`; + this.bubbleBadgeClassName = `${DecorationRule._classNamesPrefix}-bubbleBadge-${suffix}`; + } + + acquire(): void { + this._refCounter += 1; + } + + release(): boolean { + return --this._refCounter === 0; } appendCSSRules(element: HTMLStyleElement, theme: ITheme): void { @@ -89,12 +99,6 @@ class DecorationRule { removeCSSRulesContainingSelector(this.itemBadgeClassName, element); removeCSSRulesContainingSelector(this.bubbleBadgeClassName, element); } - - isUnused(): boolean { - return !document.querySelector(`.${this.itemColorClassName}`) - && !document.querySelector(`.${this.itemBadgeClassName}`) - && !document.querySelector(`.${this.bubbleBadgeClassName}`); - } } class DecorationStyles { @@ -124,11 +128,13 @@ class DecorationStyles { if (!rule) { // new css rule - rule = new DecorationRule(data); + rule = new DecorationRule(data, key); this._decorationRules.set(key, rule); rule.appendCSSRules(this._styleElement, this._themeService.getTheme()); } + rule.acquire(); + let labelClassName = rule.itemColorClassName; let badgeClassName = rule.itemBadgeClassName; let tooltip = data.filter(d => !isFalsyOrWhitespace(d.tooltip)).map(d => d.tooltip).join(' • '); @@ -142,7 +148,14 @@ class DecorationStyles { return { labelClassName, badgeClassName, - tooltip + tooltip, + dispose: () => { + if (rule && rule.release()) { + this._decorationRules.delete(key); + rule.removeCSSRules(this._styleElement); + rule = undefined; + } + } }; } @@ -152,34 +165,6 @@ class DecorationStyles { rule.appendCSSRules(this._styleElement, this._themeService.getTheme()); }); } - - cleanUp(iter: Iterator): void { - // remove every rule for which no more - // decoration (data) is kept. this isn't cheap - let usedDecorations = new Set(); - for (let e = iter.next(); !e.done; e = iter.next()) { - e.value.data.forEach((value, key) => { - if (value && !(value instanceof DecorationDataRequest)) { - usedDecorations.add(DecorationRule.keyOf(value)); - } - }); - } - this._decorationRules.forEach((value, index) => { - const { data } = value; - if (value.isUnused()) { - let remove: boolean = false; - if (Array.isArray(data)) { - remove = data.every(data => !usedDecorations.has(DecorationRule.keyOf(data))); - } else if (!usedDecorations.has(DecorationRule.keyOf(data))) { - remove = true; - } - if (remove) { - value.removeCSSRules(this._styleElement); - this._decorationRules.delete(index); - } - } - }); - } } class FileDecorationChangeEvent implements IResourceDecorationChangeEvent { @@ -345,15 +330,6 @@ export class DecorationsService implements IDecorationsService { @ILogService private readonly _logService: ILogService, ) { this._decorationStyles = new DecorationStyles(themeService); - - // every so many events we check if there are - // css styles that we don't need anymore - let count = 0; - this.onDidChangeDecorations(() => { - if (++count % 17 === 0) { - this._decorationStyles.cleanUp(this._data.iterator()); - } - }); } dispose(): void { diff --git a/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts b/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts index c6fdb83548ec..1fc05900fea7 100644 --- a/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts +++ b/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts @@ -5,7 +5,7 @@ import * as nls from 'vs/nls'; import { IWindowOpenable } from 'vs/platform/windows/common/windows'; -import { IPickAndOpenOptions, ISaveDialogOptions, IOpenDialogOptions, FileFilter } from 'vs/platform/dialogs/common/dialogs'; +import { IPickAndOpenOptions, ISaveDialogOptions, IOpenDialogOptions, FileFilter, IFileDialogService, IDialogService, ConfirmResult, getConfirmMessage } from 'vs/platform/dialogs/common/dialogs'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -14,14 +14,15 @@ import { Schemas } from 'vs/base/common/network'; import * as resources from 'vs/base/common/resources'; import { IInstantiationService, } from 'vs/platform/instantiation/common/instantiation'; import { SimpleFileDialog } from 'vs/workbench/services/dialogs/browser/simpleFileDialog'; -import { WORKSPACE_EXTENSION } from 'vs/platform/workspaces/common/workspaces'; +import { WORKSPACE_EXTENSION, isUntitledWorkspace } from 'vs/platform/workspaces/common/workspaces'; import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IFileService } from 'vs/platform/files/common/files'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IHostService } from 'vs/workbench/services/host/browser/host'; +import Severity from 'vs/base/common/severity'; -export abstract class AbstractFileDialogService { +export abstract class AbstractFileDialogService implements IFileDialogService { _serviceBrand: undefined; @@ -34,6 +35,7 @@ export abstract class AbstractFileDialogService { @IConfigurationService protected readonly configurationService: IConfigurationService, @IFileService protected readonly fileService: IFileService, @IOpenerService protected readonly openerService: IOpenerService, + @IDialogService private readonly dialogService: IDialogService ) { } defaultFilePath(schemeFilter = this.getSchemeFilterForWindow()): URI | undefined { @@ -78,6 +80,40 @@ export abstract class AbstractFileDialogService { return this.defaultFilePath(schemeFilter); } + async showSaveConfirm(fileNamesOrResources: (string | URI)[]): Promise { + if (this.environmentService.isExtensionDevelopment) { + return ConfirmResult.DONT_SAVE; // no veto when we are in extension dev mode because we cannot assume we run interactive (e.g. tests) + } + + if (fileNamesOrResources.length === 0) { + return ConfirmResult.DONT_SAVE; + } + + let message: string; + if (fileNamesOrResources.length === 1) { + message = nls.localize('saveChangesMessage', "Do you want to save the changes you made to {0}?", typeof fileNamesOrResources[0] === 'string' ? fileNamesOrResources[0] : resources.basename(fileNamesOrResources[0])); + } else { + message = getConfirmMessage(nls.localize('saveChangesMessages', "Do you want to save the changes to the following {0} files?", fileNamesOrResources.length), fileNamesOrResources); + } + + const buttons: string[] = [ + fileNamesOrResources.length > 1 ? nls.localize({ key: 'saveAll', comment: ['&& denotes a mnemonic'] }, "&&Save All") : nls.localize({ key: 'save', comment: ['&& denotes a mnemonic'] }, "&&Save"), + nls.localize({ key: 'dontSave', comment: ['&& denotes a mnemonic'] }, "Do&&n't Save"), + nls.localize('cancel', "Cancel") + ]; + + const { choice } = await this.dialogService.show(Severity.Warning, message, buttons, { + cancelId: 2, + detail: nls.localize('saveChangesDetail', "Your changes will be lost if you don't save them.") + }); + + switch (choice) { + case 0: return ConfirmResult.SAVE; + case 1: return ConfirmResult.DONT_SAVE; + default: return ConfirmResult.CANCEL; + } + } + protected abstract addFileSchemaIfNeeded(schema: string): string[]; protected async pickFileFolderAndOpenSimplified(schema: string, options: IPickAndOpenOptions, preferNewWindow: boolean): Promise { @@ -179,8 +215,12 @@ export abstract class AbstractFileDialogService { protected getFileSystemSchema(options: { availableFileSystems?: readonly string[], defaultUri?: URI }): string { return options.availableFileSystems && options.availableFileSystems[0] || this.getSchemeFilterForWindow(); } -} -function isUntitledWorkspace(path: URI, environmentService: IWorkbenchEnvironmentService): boolean { - return resources.isEqualOrParent(path, environmentService.untitledWorkspacesHome); + abstract pickFileFolderAndOpen(options: IPickAndOpenOptions): Promise; + abstract pickFileAndOpen(options: IPickAndOpenOptions): Promise; + abstract pickFolderAndOpen(options: IPickAndOpenOptions): Promise; + abstract pickWorkspaceAndOpen(options: IPickAndOpenOptions): Promise; + abstract pickFileToSave(options: ISaveDialogOptions): Promise; + abstract showSaveDialog(options: ISaveDialogOptions): Promise; + abstract showOpenDialog(options: IOpenDialogOptions): Promise; } diff --git a/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts b/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts index efbc4ea0a543..6cb3d59bc954 100644 --- a/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts +++ b/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts @@ -32,10 +32,9 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { createCancelablePromise, CancelablePromise } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { ICommandHandler } from 'vs/platform/commands/common/commands'; -import { ITextFileService, ISaveOptions } from 'vs/workbench/services/textfile/common/textfiles'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { toResource } from 'vs/workbench/common/editor'; import { normalizeDriveLetter } from 'vs/base/common/labels'; +import { SaveReason } from 'vs/workbench/common/editor'; export namespace OpenLocalFileCommand { export const ID = 'workbench.action.files.openLocalFile'; @@ -53,13 +52,12 @@ export namespace SaveLocalFileCommand { export const LABEL = nls.localize('saveLocalFile', "Save Local File..."); export function handler(): ICommandHandler { return accessor => { - const textFileService = accessor.get(ITextFileService); const editorService = accessor.get(IEditorService); - let resource: URI | undefined = toResource(editorService.activeEditor); - const options: ISaveOptions = { force: true, availableFileSystems: [Schemas.file] }; - if (resource) { - return textFileService.saveAs(resource, undefined, options); + const activeControl = editorService.activeControl; + if (activeControl) { + return editorService.save({ groupId: activeControl.group.id, editor: activeControl.input }, { saveAs: true, availableFileSystems: [Schemas.file], reason: SaveReason.EXPLICIT }); } + return Promise.resolve(undefined); }; } @@ -262,6 +260,7 @@ export class SimpleFileDialog { this.filePickBox = this.quickInputService.createQuickPick(); this.busy = true; this.filePickBox.matchOnLabel = false; + this.filePickBox.sortByLabel = false; this.filePickBox.autoFocusOnList = false; this.filePickBox.ignoreFocusOut = true; this.filePickBox.ok = true; @@ -373,28 +372,7 @@ export class SimpleFileDialog { }); this.filePickBox.onDidChangeValue(async value => { - try { - // onDidChangeValue can also be triggered by the auto complete, so if it looks like the auto complete, don't do anything - if (this.isValueChangeFromUser()) { - // If the user has just entered more bad path, don't change anything - if (!equalsIgnoreCase(value, this.constructFullUserPath()) && !this.isBadSubpath(value)) { - this.filePickBox.validationMessage = undefined; - const filePickBoxUri = this.filePickBoxValue(); - let updated: UpdateResult = UpdateResult.NotUpdated; - if (!resources.isEqual(this.currentFolder, filePickBoxUri, true)) { - updated = await this.tryUpdateItems(value, filePickBoxUri); - } - if (updated === UpdateResult.NotUpdated) { - this.setActiveItems(value); - } - } else { - this.filePickBox.activeItems = []; - this.userEnteredPathSegment = ''; - } - } - } catch { - // Since any text can be entered in the input box, there is potential for error causing input. If this happens, do nothing. - } + return this.handleValueChange(value); }); this.filePickBox.onDidHide(() => { this.hidden = true; @@ -415,6 +393,31 @@ export class SimpleFileDialog { }); } + private async handleValueChange(value: string) { + try { + // onDidChangeValue can also be triggered by the auto complete, so if it looks like the auto complete, don't do anything + if (this.isValueChangeFromUser()) { + // If the user has just entered more bad path, don't change anything + if (!equalsIgnoreCase(value, this.constructFullUserPath()) && !this.isBadSubpath(value)) { + this.filePickBox.validationMessage = undefined; + const filePickBoxUri = this.filePickBoxValue(); + let updated: UpdateResult = UpdateResult.NotUpdated; + if (!resources.isEqual(this.currentFolder, filePickBoxUri, true)) { + updated = await this.tryUpdateItems(value, filePickBoxUri); + } + if (updated === UpdateResult.NotUpdated) { + this.setActiveItems(value); + } + } else { + this.filePickBox.activeItems = []; + this.userEnteredPathSegment = ''; + } + } + } catch { + // Since any text can be entered in the input box, there is potential for error causing input. If this happens, do nothing. + } + } + private isBadSubpath(value: string) { return this.badPath && (value.length > this.badPath.length) && equalsIgnoreCase(value.substring(0, this.badPath.length), this.badPath); } @@ -514,6 +517,16 @@ export class SimpleFileDialog { return undefined; } + private root(value: URI) { + let lastDir = value; + let dir = resources.dirname(value); + while (!resources.isEqual(lastDir, dir)) { + lastDir = dir; + dir = resources.dirname(dir); + } + return dir; + } + private async tryUpdateItems(value: string, valueUri: URI): Promise { if ((value.length > 0) && ((value[value.length - 1] === '~') || (value[0] === '~'))) { let newDir = this.userHome; @@ -522,6 +535,11 @@ export class SimpleFileDialog { } await this.updateItems(newDir, true); return UpdateResult.Updated; + } else if (value === '\\') { + valueUri = this.root(this.currentFolder); + value = this.pathFromUri(valueUri); + await this.updateItems(valueUri, true); + return UpdateResult.Updated; } else if (!resources.isEqual(this.currentFolder, valueUri, true) && (this.endsWithSlash(value) || (!resources.isEqual(this.currentFolder, resources.dirname(valueUri), true) && resources.isEqualOrParent(this.currentFolder, resources.dirname(valueUri), true)))) { let stat: IFileStat | undefined; try { @@ -535,7 +553,7 @@ export class SimpleFileDialog { } else if (this.endsWithSlash(value)) { // The input box contains a path that doesn't exist on the system. this.filePickBox.validationMessage = nls.localize('remoteFileDialog.badPath', 'The path does not exist.'); - // Save this bad path. It can take too long to to a stat on every user entered character, but once a user enters a bad path they are likely + // Save this bad path. It can take too long to a stat on every user entered character, but once a user enters a bad path they are likely // to keep typing more bad path. We can compare against this bad path and see if the user entered path starts with it. this.badPath = value; return UpdateResult.InvalidPath; @@ -602,7 +620,7 @@ export class SimpleFileDialog { this.activeItem = quickPickItem; if (force) { // clear any selected text - this.insertText(this.userEnteredPathSegment, ''); + document.execCommand('insertText', false, ''); } return false; } else if (!force && (itemBasename.length >= startingBasename.length) && equalsIgnoreCase(itemBasename.substr(0, startingBasename.length), startingBasename)) { @@ -631,14 +649,19 @@ export class SimpleFileDialog { private insertText(wholeValue: string, insertText: string) { if (this.filePickBox.inputHasFocus()) { document.execCommand('insertText', false, insertText); + if (this.filePickBox.value !== wholeValue) { + this.filePickBox.value = wholeValue; + this.handleValueChange(wholeValue); + } } else { this.filePickBox.value = wholeValue; + this.handleValueChange(wholeValue); } } private addPostfix(uri: URI): URI { let result = uri; - if (this.requiresTrailing && this.options.filters && this.options.filters.length > 0) { + if (this.requiresTrailing && this.options.filters && this.options.filters.length > 0 && !resources.hasTrailingPathSeparator(uri)) { // Make sure that the suffix is added. If the user deleted it, we automatically add it here let hasExt: boolean = false; const currentExt = resources.extname(uri).substr(1); diff --git a/src/vs/workbench/services/dialogs/electron-browser/fileDialogService.ts b/src/vs/workbench/services/dialogs/electron-browser/fileDialogService.ts index 3b07328a29f3..55cfe83ed40f 100644 --- a/src/vs/workbench/services/dialogs/electron-browser/fileDialogService.ts +++ b/src/vs/workbench/services/dialogs/electron-browser/fileDialogService.ts @@ -6,7 +6,7 @@ import { SaveDialogOptions, OpenDialogOptions } from 'electron'; import { INativeOpenDialogOptions } from 'vs/platform/dialogs/node/dialogs'; import { IHostService } from 'vs/workbench/services/host/browser/host'; -import { IPickAndOpenOptions, ISaveDialogOptions, IOpenDialogOptions, IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IPickAndOpenOptions, ISaveDialogOptions, IOpenDialogOptions, IFileDialogService, IDialogService, ConfirmResult } from 'vs/platform/dialogs/common/dialogs'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -33,8 +33,11 @@ export class FileDialogService extends AbstractFileDialogService implements IFil @IConfigurationService configurationService: IConfigurationService, @IFileService fileService: IFileService, @IOpenerService openerService: IOpenerService, - @IElectronService private readonly electronService: IElectronService - ) { super(hostService, contextService, historyService, environmentService, instantiationService, configurationService, fileService, openerService); } + @IElectronService private readonly electronService: IElectronService, + @IDialogService dialogService: IDialogService + ) { + super(hostService, contextService, historyService, environmentService, instantiationService, configurationService, fileService, openerService, dialogService); + } private toNativeOpenDialogOptions(options: IPickAndOpenOptions): INativeOpenDialogOptions { return { @@ -180,6 +183,16 @@ export class FileDialogService extends AbstractFileDialogService implements IFil // Don't allow untitled schema through. return schema === Schemas.untitled ? [Schemas.file] : (schema !== Schemas.file ? [schema, Schemas.file] : [schema]); } + + async showSaveConfirm(fileNamesOrResources: (string | URI)[]): Promise { + if (this.environmentService.isExtensionDevelopment) { + if (!this.environmentService.args['extension-development-confirm-save']) { + return ConfirmResult.DONT_SAVE; // no veto when we are in extension dev mode because we cannot assume we run interactive (e.g. tests) + } + } + + return super.showSaveConfirm(fileNamesOrResources); + } } registerSingleton(IFileDialogService, FileDialogService, true); diff --git a/src/vs/workbench/services/editor/browser/editorService.ts b/src/vs/workbench/services/editor/browser/editorService.ts index ccdc691cb417..13ec07e6936e 100644 --- a/src/vs/workbench/services/editor/browser/editorService.ts +++ b/src/vs/workbench/services/editor/browser/editorService.ts @@ -5,12 +5,11 @@ import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IResourceInput, ITextEditorOptions, IEditorOptions, EditorActivation } from 'vs/platform/editor/common/editor'; -import { IEditorInput, IEditor, GroupIdentifier, IFileEditorInput, IUntitledResourceInput, IResourceDiffInput, IResourceSideBySideInput, IEditorInputFactoryRegistry, Extensions as EditorExtensions, IFileInputFactory, EditorInput, SideBySideEditorInput, IEditorInputWithOptions, isEditorInputWithOptions, EditorOptions, TextEditorOptions, IEditorIdentifier, IEditorCloseEvent, ITextEditor, ITextDiffEditor, ITextSideBySideEditor, toResource, SideBySideEditor } from 'vs/workbench/common/editor'; +import { IEditorInput, IEditor, GroupIdentifier, IFileEditorInput, IUntitledTextResourceInput, IResourceDiffInput, IResourceSideBySideInput, IEditorInputFactoryRegistry, Extensions as EditorExtensions, IFileInputFactory, EditorInput, SideBySideEditorInput, IEditorInputWithOptions, isEditorInputWithOptions, EditorOptions, TextEditorOptions, IEditorIdentifier, IEditorCloseEvent, ITextEditor, ITextDiffEditor, ITextSideBySideEditor, toResource, SideBySideEditor, IRevertOptions } from 'vs/workbench/common/editor'; import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; -import { DataUriEditorInput } from 'vs/workbench/common/editor/dataUriEditorInput'; import { Registry } from 'vs/platform/registry/common/platform'; import { ResourceMap } from 'vs/base/common/map'; -import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; +import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { IFileService } from 'vs/platform/files/common/files'; import { Schemas } from 'vs/base/common/network'; import { Event, Emitter } from 'vs/base/common/event'; @@ -18,8 +17,8 @@ import { URI } from 'vs/base/common/uri'; import { basename, isEqual } from 'vs/base/common/resources'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { localize } from 'vs/nls'; -import { IEditorGroupsService, IEditorGroup, GroupsOrder, IEditorReplacement, GroupChangeKind, preferredSideBySideGroupDirection } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { IResourceEditor, SIDE_GROUP, IResourceEditorReplacement, IOpenEditorOverrideHandler, IVisibleEditor, IEditorService, SIDE_GROUP_TYPE, ACTIVE_GROUP_TYPE } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorGroupsService, IEditorGroup, GroupsOrder, IEditorReplacement, GroupChangeKind, preferredSideBySideGroupDirection, EditorsOrder } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IResourceEditor, SIDE_GROUP, IResourceEditorReplacement, IOpenEditorOverrideHandler, IVisibleEditor, IEditorService, SIDE_GROUP_TYPE, ACTIVE_GROUP_TYPE, ISaveEditorsOptions, ISaveAllEditorsOptions, IRevertAllEditorsOptions, IBaseSaveRevertAllEditorOptions } from 'vs/workbench/services/editor/common/editorService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { Disposable, IDisposable, dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { coalesce } from 'vs/base/common/arrays'; @@ -29,40 +28,40 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { withNullAsUndefined } from 'vs/base/common/types'; -type CachedEditorInput = ResourceEditorInput | IFileEditorInput | DataUriEditorInput; +type CachedEditorInput = ResourceEditorInput | IFileEditorInput; type OpenInEditorGroup = IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE; export class EditorService extends Disposable implements EditorServiceImpl { _serviceBrand: undefined; - private static CACHE: ResourceMap = new ResourceMap(); + private static CACHE = new ResourceMap(); //#region events - private readonly _onDidActiveEditorChange: Emitter = this._register(new Emitter()); - readonly onDidActiveEditorChange: Event = this._onDidActiveEditorChange.event; + private readonly _onDidActiveEditorChange = this._register(new Emitter()); + readonly onDidActiveEditorChange = this._onDidActiveEditorChange.event; - private readonly _onDidVisibleEditorsChange: Emitter = this._register(new Emitter()); - readonly onDidVisibleEditorsChange: Event = this._onDidVisibleEditorsChange.event; + private readonly _onDidVisibleEditorsChange = this._register(new Emitter()); + readonly onDidVisibleEditorsChange = this._onDidVisibleEditorsChange.event; - private readonly _onDidCloseEditor: Emitter = this._register(new Emitter()); - readonly onDidCloseEditor: Event = this._onDidCloseEditor.event; + private readonly _onDidCloseEditor = this._register(new Emitter()); + readonly onDidCloseEditor = this._onDidCloseEditor.event; - private readonly _onDidOpenEditorFail: Emitter = this._register(new Emitter()); - readonly onDidOpenEditorFail: Event = this._onDidOpenEditorFail.event; + private readonly _onDidOpenEditorFail = this._register(new Emitter()); + readonly onDidOpenEditorFail = this._onDidOpenEditorFail.event; //#endregion private fileInputFactory: IFileInputFactory; private openEditorHandlers: IOpenEditorOverrideHandler[] = []; - private lastActiveEditor: IEditorInput | null = null; - private lastActiveGroupId: GroupIdentifier | null = null; + private lastActiveEditor: IEditorInput | undefined = undefined; + private lastActiveGroupId: GroupIdentifier | undefined = undefined; constructor( @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, - @IUntitledEditorService private readonly untitledEditorService: IUntitledEditorService, + @IUntitledTextEditorService private readonly untitledTextEditorService: IUntitledTextEditorService, @IInstantiationService private readonly instantiationService: IInstantiationService, @ILabelService private readonly labelService: ILabelService, @IFileService private readonly fileService: IFileService, @@ -76,6 +75,8 @@ export class EditorService extends Disposable implements EditorServiceImpl { } private registerListeners(): void { + + // Editor & group changes this.editorGroupService.whenRestored.then(() => this.onEditorsRestored()); this.editorGroupService.onDidActiveGroupChange(group => this.handleActiveEditorChange(group)); this.editorGroupService.onDidAddGroup(group => this.registerGroupListeners(group as IEditorGroupView)); @@ -88,7 +89,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { // Fire initial set of editor events if there is an active editor if (this.activeEditor) { - this.doEmitActiveEditorChangeEvent(); + this.doHandleActiveEditorChangeEvent(); this._onDidVisibleEditorsChange.fire(); } } @@ -106,15 +107,17 @@ export class EditorService extends Disposable implements EditorServiceImpl { return; // ignore if the editor actually did not change } - this.doEmitActiveEditorChangeEvent(); + this.doHandleActiveEditorChangeEvent(); } - private doEmitActiveEditorChangeEvent(): void { - const activeGroup = this.editorGroupService.activeGroup; + private doHandleActiveEditorChangeEvent(): void { + // Remember as last active + const activeGroup = this.editorGroupService.activeGroup; this.lastActiveGroupId = activeGroup.id; - this.lastActiveEditor = activeGroup.activeEditor; + this.lastActiveEditor = withNullAsUndefined(activeGroup.activeEditor); + // Fire event to outside parties this._onDidActiveEditorChange.fire(); } @@ -221,7 +224,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { //#region openEditor() openEditor(editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions, group?: OpenInEditorGroup): Promise; - openEditor(editor: IResourceInput | IUntitledResourceInput, group?: OpenInEditorGroup): Promise; + openEditor(editor: IResourceInput | IUntitledTextResourceInput, group?: OpenInEditorGroup): Promise; openEditor(editor: IResourceDiffInput, group?: OpenInEditorGroup): Promise; openEditor(editor: IResourceSideBySideInput, group?: OpenInEditorGroup): Promise; async openEditor(editor: IEditorInput | IResourceEditor, optionsOrGroup?: IEditorOptions | ITextEditorOptions | OpenInEditorGroup, group?: OpenInEditorGroup): Promise { @@ -362,7 +365,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { return neighbourGroup; } - private toOptions(options?: IEditorOptions | EditorOptions): EditorOptions { + private toOptions(options?: IEditorOptions | ITextEditorOptions | EditorOptions): EditorOptions { if (!options || options instanceof EditorOptions) { return options as EditorOptions; } @@ -424,7 +427,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { //#region isOpen() - isOpen(editor: IEditorInput | IResourceInput | IUntitledResourceInput): boolean { + isOpen(editor: IEditorInput | IResourceInput | IUntitledTextResourceInput): boolean { return !!this.doGetOpened(editor); } @@ -432,13 +435,13 @@ export class EditorService extends Disposable implements EditorServiceImpl { //#region getOpend() - getOpened(editor: IResourceInput | IUntitledResourceInput): IEditorInput | undefined { + getOpened(editor: IResourceInput | IUntitledTextResourceInput): IEditorInput | undefined { return this.doGetOpened(editor); } - private doGetOpened(editor: IEditorInput | IResourceInput | IUntitledResourceInput): IEditorInput | undefined { + private doGetOpened(editor: IEditorInput | IResourceInput | IUntitledTextResourceInput): IEditorInput | undefined { if (!(editor instanceof EditorInput)) { - const resourceInput = editor as IResourceInput | IUntitledResourceInput; + const resourceInput = editor as IResourceInput | IUntitledTextResourceInput; if (!resourceInput.resource) { return undefined; // we need a resource at least } @@ -462,7 +465,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { continue; // need a resource to compare with } - const resourceInput = editor as IResourceInput | IUntitledResourceInput; + const resourceInput = editor as IResourceInput | IUntitledTextResourceInput; if (resourceInput.resource && isEqual(resource, resourceInput.resource)) { return editorInGroup; } @@ -484,17 +487,20 @@ export class EditorService extends Disposable implements EditorServiceImpl { editors.forEach(replaceEditorArg => { if (replaceEditorArg.editor instanceof EditorInput) { - typedEditors.push(replaceEditorArg as IEditorReplacement); + const replacementArg = replaceEditorArg as IEditorReplacement; + + typedEditors.push({ + editor: replacementArg.editor, + replacement: replacementArg.replacement, + options: this.toOptions(replacementArg.options) + }); } else { - const editor = replaceEditorArg.editor as IResourceEditor; - const replacement = replaceEditorArg.replacement as IResourceEditor; - const typedEditor = this.createInput(editor); - const typedReplacement = this.createInput(replacement); + const replacementArg = replaceEditorArg as IResourceEditorReplacement; typedEditors.push({ - editor: typedEditor, - replacement: typedReplacement, - options: this.toOptions(replacement.options) + editor: this.createInput(replacementArg.editor), + replacement: this.createInput(replacementArg.replacement), + options: this.toOptions(replacementArg.replacement.options) }); } }); @@ -568,17 +574,17 @@ export class EditorService extends Disposable implements EditorServiceImpl { } // Untitled file support - const untitledInput = input as IUntitledResourceInput; + const untitledInput = input as IUntitledTextResourceInput; if (untitledInput.forceUntitled || !untitledInput.resource || (untitledInput.resource && untitledInput.resource.scheme === Schemas.untitled)) { - return this.untitledEditorService.createOrGet(untitledInput.resource, untitledInput.mode, untitledInput.contents, untitledInput.encoding); + return this.untitledTextEditorService.createOrGet(untitledInput.resource, untitledInput.mode, untitledInput.contents, untitledInput.encoding); } // Resource Editor Support const resourceInput = input as IResourceInput; if (resourceInput.resource instanceof URI) { let label = resourceInput.label; - if (!label && resourceInput.resource.scheme !== Schemas.data) { - label = basename(resourceInput.resource); // derive the label from the path (but not for data URIs) + if (!label) { + label = basename(resourceInput.resource); // derive the label from the path } return this.createOrGet(resourceInput.resource, this.instantiationService, label, resourceInput.description, resourceInput.encoding, resourceInput.mode, resourceInput.forceFile) as EditorInput; @@ -602,7 +608,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { if (mode) { input.setPreferredMode(mode); } - } else if (!(input instanceof DataUriEditorInput)) { + } else { if (encoding) { input.setPreferredEncoding(encoding); } @@ -621,11 +627,6 @@ export class EditorService extends Disposable implements EditorServiceImpl { input = this.fileInputFactory.createFileInput(resource, encoding, mode, instantiationService); } - // Data URI - else if (resource.scheme === Schemas.data) { - input = instantiationService.createInstance(DataUriEditorInput, label, description, resource); - } - // Resource else { input = instantiationService.createInstance(ResourceEditorInput, label, description, resource, mode); @@ -644,8 +645,8 @@ export class EditorService extends Disposable implements EditorServiceImpl { return undefined; } - // Do not try to extract any paths from simple untitled editors - if (res.scheme === Schemas.untitled && !this.untitledEditorService.hasAssociatedFilePath(res)) { + // Do not try to extract any paths from simple untitled text editors + if (res.scheme === Schemas.untitled && !this.untitledTextEditorService.hasAssociatedFilePath(res)) { return input.getName(); } @@ -654,6 +655,101 @@ export class EditorService extends Disposable implements EditorServiceImpl { } //#endregion + + //#region save/revert + + async save(editors: IEditorIdentifier | IEditorIdentifier[], options?: ISaveEditorsOptions): Promise { + + // Convert to array + if (!Array.isArray(editors)) { + editors = [editors]; + } + + // Split editors up into a bucket that is saved in parallel + // and sequentially. Unless "Save As", all non-untitled editors + // can be saved in parallel to speed up the operation. Remaining + // editors are potentially bringing up some UI and thus run + // sequentially. + const editorsToSaveParallel: IEditorIdentifier[] = []; + const editorsToSaveAsSequentially: IEditorIdentifier[] = []; + if (options?.saveAs) { + editorsToSaveAsSequentially.push(...editors); + } else { + for (const { groupId, editor } of editors) { + if (editor.isUntitled()) { + editorsToSaveAsSequentially.push({ groupId, editor }); + } else { + editorsToSaveParallel.push({ groupId, editor }); + } + } + } + + // Editors to save in parallel + await Promise.all(editorsToSaveParallel.map(({ groupId, editor }) => { + + // Use save as a hint to pin the editor + this.editorGroupService.getGroup(groupId)?.pinEditor(editor); + + // Save + return editor.save(groupId, options); + })); + + // Editors to save sequentially + for (const { groupId, editor } of editorsToSaveAsSequentially) { + if (editor.isDisposed()) { + continue; // might have been disposed from from the save already + } + + const result = options?.saveAs ? await editor.saveAs(groupId, options) : await editor.save(groupId, options); + if (!result) { + return false; // failed or cancelled, abort + } + } + + return true; + } + + saveAll(options?: ISaveAllEditorsOptions): Promise { + return this.save(this.getAllDirtyEditors(options), options); + } + + async revert(editors: IEditorIdentifier | IEditorIdentifier[], options?: IRevertOptions): Promise { + + // Convert to array + if (!Array.isArray(editors)) { + editors = [editors]; + } + + const result = await Promise.all(editors.map(async ({ groupId, editor }) => { + + // Use revert as a hint to pin the editor + this.editorGroupService.getGroup(groupId)?.pinEditor(editor); + + return editor.revert(options); + })); + + return result.every(success => !!success); + } + + async revertAll(options?: IRevertAllEditorsOptions): Promise { + return this.revert(this.getAllDirtyEditors(options), options); + } + + private getAllDirtyEditors(options?: IBaseSaveRevertAllEditorOptions): IEditorIdentifier[] { + const editors: IEditorIdentifier[] = []; + + for (const group of this.editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE)) { + for (const editor of group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)) { + if (editor.isDirty() && (!editor.isUntitled() || !!options?.includeUntitled)) { + editors.push({ groupId: group.id, editor }); + } + } + } + + return editors; + } + + //#endregion } export interface IEditorOpenHandler { @@ -674,7 +770,7 @@ export class DelegatingEditorService extends EditorService { constructor( @IEditorGroupsService editorGroupService: IEditorGroupsService, - @IUntitledEditorService untitledEditorService: IUntitledEditorService, + @IUntitledTextEditorService untitledTextEditorService: IUntitledTextEditorService, @IInstantiationService instantiationService: IInstantiationService, @ILabelService labelService: ILabelService, @IFileService fileService: IFileService, @@ -682,7 +778,7 @@ export class DelegatingEditorService extends EditorService { ) { super( editorGroupService, - untitledEditorService, + untitledTextEditorService, instantiationService, labelService, fileService, diff --git a/src/vs/workbench/services/editor/common/editorGroupsService.ts b/src/vs/workbench/services/editor/common/editorGroupsService.ts index 0c7a55a61e52..f6434709388e 100644 --- a/src/vs/workbench/services/editor/common/editorGroupsService.ts +++ b/src/vs/workbench/services/editor/common/editorGroupsService.ts @@ -427,11 +427,6 @@ export interface IEditorGroup { */ readonly editors: ReadonlyArray; - /** - * Returns the editor at a specific index of the group. - */ - getEditor(index: number): IEditorInput | undefined; - /** * Get all editors that are currently opened in the group optionally * sorted by being most recent active. Will sort by sequential appearance @@ -439,6 +434,11 @@ export interface IEditorGroup { */ getEditors(order?: EditorsOrder): ReadonlyArray; + /** + * Returns the editor at a specific index of the group. + */ + getEditorByIndex(index: number): IEditorInput | undefined; + /** * Returns the index of the editor in the group or -1 if not opened. */ diff --git a/src/vs/workbench/services/editor/common/editorService.ts b/src/vs/workbench/services/editor/common/editorService.ts index 3d49efcd32a6..b0802af3b519 100644 --- a/src/vs/workbench/services/editor/common/editorService.ts +++ b/src/vs/workbench/services/editor/common/editorService.ts @@ -5,7 +5,7 @@ import { createDecorator, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IResourceInput, IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/editor'; -import { IEditorInput, IEditor, GroupIdentifier, IEditorInputWithOptions, IUntitledResourceInput, IResourceDiffInput, IResourceSideBySideInput, ITextEditor, ITextDiffEditor, ITextSideBySideEditor } from 'vs/workbench/common/editor'; +import { IEditorInput, IEditor, GroupIdentifier, IEditorInputWithOptions, IUntitledTextResourceInput, IResourceDiffInput, IResourceSideBySideInput, ITextEditor, ITextDiffEditor, ITextSideBySideEditor, IEditorIdentifier, ISaveOptions, IRevertOptions } from 'vs/workbench/common/editor'; import { Event } from 'vs/base/common/event'; import { IEditor as ICodeEditor } from 'vs/editor/common/editorCommon'; import { IEditorGroup, IEditorReplacement } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -13,7 +13,7 @@ import { IDisposable } from 'vs/base/common/lifecycle'; export const IEditorService = createDecorator('editorService'); -export type IResourceEditor = IResourceInput | IUntitledResourceInput | IResourceDiffInput | IResourceSideBySideInput; +export type IResourceEditor = IResourceInput | IUntitledTextResourceInput | IResourceDiffInput | IResourceSideBySideInput; export interface IResourceEditorReplacement { editor: IResourceEditor; @@ -44,6 +44,26 @@ export interface IVisibleEditor extends IEditor { group: IEditorGroup; } +export interface ISaveEditorsOptions extends ISaveOptions { + + /** + * If true, will ask for a location of the editor to save to. + */ + saveAs?: boolean; +} + +export interface IBaseSaveRevertAllEditorOptions { + + /** + * Wether to include untitled editors as well. + */ + includeUntitled?: boolean; +} + +export interface ISaveAllEditorsOptions extends ISaveEditorsOptions, IBaseSaveRevertAllEditorOptions { } + +export interface IRevertAllEditorsOptions extends IRevertOptions, IBaseSaveRevertAllEditorOptions { } + export interface IEditorService { _serviceBrand: undefined; @@ -121,7 +141,7 @@ export interface IEditorService { * opened to be active. */ openEditor(editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; - openEditor(editor: IResourceInput | IUntitledResourceInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; + openEditor(editor: IResourceInput | IUntitledTextResourceInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; openEditor(editor: IResourceDiffInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; openEditor(editor: IResourceSideBySideInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; @@ -158,7 +178,7 @@ export interface IEditorService { * * @param group optional to specify a group to check for the editor being opened */ - isOpen(editor: IEditorInput | IResourceInput | IUntitledResourceInput, group?: IEditorGroup | GroupIdentifier): boolean; + isOpen(editor: IEditorInput | IResourceInput | IUntitledTextResourceInput, group?: IEditorGroup | GroupIdentifier): boolean; /** * Get the actual opened editor input in any or a specific editor group based on the resource. @@ -167,7 +187,7 @@ export interface IEditorService { * * @param group optional to specify a group to check for the editor */ - getOpened(editor: IResourceInput | IUntitledResourceInput, group?: IEditorGroup | GroupIdentifier): IEditorInput | undefined; + getOpened(editor: IResourceInput | IUntitledTextResourceInput, group?: IEditorGroup | GroupIdentifier): IEditorInput | undefined; /** * Allows to override the opening of editors by installing a handler that will @@ -184,5 +204,25 @@ export interface IEditorService { /** * Converts a lightweight input to a workbench editor input. */ - createInput(input: IResourceEditor): IEditorInput | null; + createInput(input: IResourceEditor): IEditorInput; + + /** + * Save the provided list of editors. + */ + save(editors: IEditorIdentifier | IEditorIdentifier[], options?: ISaveEditorsOptions): Promise; + + /** + * Save all editors. + */ + saveAll(options?: ISaveAllEditorsOptions): Promise; + + /** + * Reverts the provided list of editors. + */ + revert(editors: IEditorIdentifier | IEditorIdentifier[], options?: IRevertOptions): Promise; + + /** + * Reverts all editors. + */ + revertAll(options?: IRevertAllEditorsOptions): Promise; } diff --git a/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts b/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts index b7deef6b4c31..990c642d2dac 100644 --- a/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts @@ -78,7 +78,7 @@ suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} skip suite } (Registry.as(EditorExtensions.EditorInputFactories)).registerEditorInputFactory('testEditorInputForGroupsService', TestEditorInputFactory); - (Registry.as(Extensions.Editors)).registerEditor(new EditorDescriptor(TestEditorControl, 'MyTestEditorForGroupsService', 'My Test File Editor'), [new SyncDescriptor(TestEditorInput)]); + (Registry.as(Extensions.Editors)).registerEditor(EditorDescriptor.create(TestEditorControl, 'MyTestEditorForGroupsService', 'My Test File Editor'), [new SyncDescriptor(TestEditorInput)]); } registerTestEditorInput(); @@ -440,8 +440,8 @@ suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} skip suite assert.equal(editorWillOpenCounter, 2); assert.equal(editorDidOpenCounter, 2); assert.equal(activeEditorChangeCounter, 1); - assert.equal(group.getEditor(0), input); - assert.equal(group.getEditor(1), inputInactive); + assert.equal(group.getEditorByIndex(0), input); + assert.equal(group.getEditorByIndex(1), inputInactive); assert.equal(group.getIndexOfEditor(input), 0); assert.equal(group.getIndexOfEditor(inputInactive), 1); @@ -491,8 +491,8 @@ suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} skip suite await group.openEditors([{ editor: input, options: { pinned: true } }, { editor: inputInactive }]); assert.equal(group.count, 2); - assert.equal(group.getEditor(0), input); - assert.equal(group.getEditor(1), inputInactive); + assert.equal(group.getEditorByIndex(0), input); + assert.equal(group.getEditorByIndex(1), inputInactive); await group.closeEditors([input, inputInactive]); assert.equal(group.isEmpty, true); @@ -510,13 +510,13 @@ suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} skip suite await group.openEditors([{ editor: input1, options: { pinned: true } }, { editor: input2, options: { pinned: true } }, { editor: input3 }]); assert.equal(group.count, 3); - assert.equal(group.getEditor(0), input1); - assert.equal(group.getEditor(1), input2); - assert.equal(group.getEditor(2), input3); + assert.equal(group.getEditorByIndex(0), input1); + assert.equal(group.getEditorByIndex(1), input2); + assert.equal(group.getEditorByIndex(2), input3); await group.closeEditors({ except: input2 }); assert.equal(group.count, 1); - assert.equal(group.getEditor(0), input2); + assert.equal(group.getEditorByIndex(0), input2); part.dispose(); }); @@ -531,9 +531,9 @@ suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} skip suite await group.openEditors([{ editor: input1, options: { pinned: true } }, { editor: input2, options: { pinned: true } }, { editor: input3 }]); assert.equal(group.count, 3); - assert.equal(group.getEditor(0), input1); - assert.equal(group.getEditor(1), input2); - assert.equal(group.getEditor(2), input3); + assert.equal(group.getEditorByIndex(0), input1); + assert.equal(group.getEditorByIndex(1), input2); + assert.equal(group.getEditorByIndex(2), input3); await group.closeEditors({ savedOnly: true }); assert.equal(group.count, 0); @@ -551,14 +551,14 @@ suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} skip suite await group.openEditors([{ editor: input1, options: { pinned: true } }, { editor: input2, options: { pinned: true } }, { editor: input3 }]); assert.equal(group.count, 3); - assert.equal(group.getEditor(0), input1); - assert.equal(group.getEditor(1), input2); - assert.equal(group.getEditor(2), input3); + assert.equal(group.getEditorByIndex(0), input1); + assert.equal(group.getEditorByIndex(1), input2); + assert.equal(group.getEditorByIndex(2), input3); await group.closeEditors({ direction: CloseDirection.RIGHT, except: input2 }); assert.equal(group.count, 2); - assert.equal(group.getEditor(0), input1); - assert.equal(group.getEditor(1), input2); + assert.equal(group.getEditorByIndex(0), input1); + assert.equal(group.getEditorByIndex(1), input2); part.dispose(); }); @@ -573,14 +573,14 @@ suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} skip suite await group.openEditors([{ editor: input1, options: { pinned: true } }, { editor: input2, options: { pinned: true } }, { editor: input3 }]); assert.equal(group.count, 3); - assert.equal(group.getEditor(0), input1); - assert.equal(group.getEditor(1), input2); - assert.equal(group.getEditor(2), input3); + assert.equal(group.getEditorByIndex(0), input1); + assert.equal(group.getEditorByIndex(1), input2); + assert.equal(group.getEditorByIndex(2), input3); await group.closeEditors({ direction: CloseDirection.LEFT, except: input2 }); assert.equal(group.count, 2); - assert.equal(group.getEditor(0), input2); - assert.equal(group.getEditor(1), input3); + assert.equal(group.getEditorByIndex(0), input2); + assert.equal(group.getEditorByIndex(1), input3); part.dispose(); }); @@ -594,8 +594,8 @@ suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} skip suite await group.openEditors([{ editor: input, options: { pinned: true } }, { editor: inputInactive }]); assert.equal(group.count, 2); - assert.equal(group.getEditor(0), input); - assert.equal(group.getEditor(1), inputInactive); + assert.equal(group.getEditorByIndex(0), input); + assert.equal(group.getEditorByIndex(1), inputInactive); await group.closeAllEditors(); assert.equal(group.isEmpty, true); @@ -620,12 +620,12 @@ suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} skip suite await group.openEditors([{ editor: input, options: { pinned: true } }, { editor: inputInactive }]); assert.equal(group.count, 2); - assert.equal(group.getEditor(0), input); - assert.equal(group.getEditor(1), inputInactive); + assert.equal(group.getEditorByIndex(0), input); + assert.equal(group.getEditorByIndex(1), inputInactive); group.moveEditor(inputInactive, group, { index: 0 }); assert.equal(editorMoveCounter, 1); - assert.equal(group.getEditor(0), inputInactive); - assert.equal(group.getEditor(1), input); + assert.equal(group.getEditorByIndex(0), inputInactive); + assert.equal(group.getEditorByIndex(1), input); editorGroupChangeListener.dispose(); part.dispose(); }); @@ -642,13 +642,13 @@ suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} skip suite await group.openEditors([{ editor: input, options: { pinned: true } }, { editor: inputInactive }]); assert.equal(group.count, 2); - assert.equal(group.getEditor(0), input); - assert.equal(group.getEditor(1), inputInactive); + assert.equal(group.getEditorByIndex(0), input); + assert.equal(group.getEditorByIndex(1), inputInactive); group.moveEditor(inputInactive, rightGroup, { index: 0 }); assert.equal(group.count, 1); - assert.equal(group.getEditor(0), input); + assert.equal(group.getEditorByIndex(0), input); assert.equal(rightGroup.count, 1); - assert.equal(rightGroup.getEditor(0), inputInactive); + assert.equal(rightGroup.getEditorByIndex(0), inputInactive); part.dispose(); }); @@ -664,14 +664,14 @@ suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} skip suite await group.openEditors([{ editor: input, options: { pinned: true } }, { editor: inputInactive }]); assert.equal(group.count, 2); - assert.equal(group.getEditor(0), input); - assert.equal(group.getEditor(1), inputInactive); + assert.equal(group.getEditorByIndex(0), input); + assert.equal(group.getEditorByIndex(1), inputInactive); group.copyEditor(inputInactive, rightGroup, { index: 0 }); assert.equal(group.count, 2); - assert.equal(group.getEditor(0), input); - assert.equal(group.getEditor(1), inputInactive); + assert.equal(group.getEditorByIndex(0), input); + assert.equal(group.getEditorByIndex(1), inputInactive); assert.equal(rightGroup.count, 1); - assert.equal(rightGroup.getEditor(0), inputInactive); + assert.equal(rightGroup.getEditorByIndex(0), inputInactive); part.dispose(); }); @@ -685,11 +685,11 @@ suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} skip suite await group.openEditor(input); assert.equal(group.count, 1); - assert.equal(group.getEditor(0), input); + assert.equal(group.getEditorByIndex(0), input); await group.replaceEditors([{ editor: input, replacement: inputInactive }]); assert.equal(group.count, 1); - assert.equal(group.getEditor(0), inputInactive); + assert.equal(group.getEditorByIndex(0), inputInactive); part.dispose(); }); diff --git a/src/vs/workbench/services/editor/test/browser/editorService.test.ts b/src/vs/workbench/services/editor/test/browser/editorService.test.ts index 44b3ed0d1d38..589184579957 100644 --- a/src/vs/workbench/services/editor/test/browser/editorService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorService.test.ts @@ -7,7 +7,7 @@ import * as assert from 'assert'; import { IEditorModel, EditorActivation } from 'vs/platform/editor/common/editor'; import { URI } from 'vs/base/common/uri'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; -import { EditorInput, EditorOptions, IFileEditorInput, IEditorInput } from 'vs/workbench/common/editor'; +import { EditorInput, EditorOptions, IFileEditorInput, IEditorInput, GroupIdentifier, ISaveOptions, IRevertOptions } from 'vs/workbench/common/editor'; import { workbenchInstantiationService, TestStorageService } from 'vs/workbench/test/workbenchTestServices'; import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; @@ -22,7 +22,7 @@ import { IEditorRegistry, EditorDescriptor, Extensions } from 'vs/workbench/brow import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { Registry } from 'vs/platform/registry/common/platform'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; -import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput'; +import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput'; import { EditorServiceImpl } from 'vs/workbench/browser/parts/editor/editor'; import { CancellationToken } from 'vs/base/common/cancellation'; import { timeout } from 'vs/base/common/async'; @@ -30,7 +30,7 @@ import { toResource } from 'vs/base/test/common/utils'; import { IFileService } from 'vs/platform/files/common/files'; import { Disposable } from 'vs/base/common/lifecycle'; import { ModesRegistry } from 'vs/editor/common/modes/modesRegistry'; -import { UntitledEditorModel } from 'vs/workbench/common/editor/untitledEditorModel'; +import { UntitledTextEditorModel } from 'vs/workbench/common/editor/untitledTextEditorModel'; import { NullFileSystemProvider } from 'vs/platform/files/test/common/nullFileSystemProvider'; export class TestEditorControl extends BaseEditor { @@ -50,11 +50,15 @@ export class TestEditorControl extends BaseEditor { export class TestEditorInput extends EditorInput implements IFileEditorInput { public gotDisposed = false; + public gotSaved = false; + public gotSavedAs = false; + public gotReverted = false; + public dirty = false; private fails = false; constructor(private resource: URI) { super(); } getTypeId() { return 'testEditorInputForEditorService'; } - resolve(): Promise { return !this.fails ? Promise.resolve(null) : Promise.reject(new Error('fails')); } + resolve(): Promise { return !this.fails ? Promise.resolve(null) : Promise.reject(new Error('fails')); } matches(other: TestEditorInput): boolean { return other && other.resource && this.resource.toString() === other.resource.toString() && other instanceof TestEditorInput; } setEncoding(encoding: string) { } getEncoding() { return undefined; } @@ -66,6 +70,26 @@ export class TestEditorInput extends EditorInput implements IFileEditorInput { setFailToOpen(): void { this.fails = true; } + save(groupId: GroupIdentifier, options?: ISaveOptions): Promise { + this.gotSaved = true; + return Promise.resolve(true); + } + saveAs(groupId: GroupIdentifier, options?: ISaveOptions): Promise { + this.gotSavedAs = true; + return Promise.resolve(true); + } + revert(options?: IRevertOptions): Promise { + this.gotReverted = true; + this.gotSaved = false; + this.gotSavedAs = false; + return Promise.resolve(true); + } + isDirty(): boolean { + return this.dirty; + } + isReadonly(): boolean { + return false; + } dispose(): void { super.dispose(); this.gotDisposed = true; @@ -83,7 +107,7 @@ class FileServiceProvider extends Disposable { suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} skip suite function registerTestEditorInput(): void { - Registry.as(Extensions.Editors).registerEditor(new EditorDescriptor(TestEditorControl, 'MyTestEditorForEditorService', 'My Test Editor For Next Editor Service'), [new SyncDescriptor(TestEditorInput)]); + Registry.as(Extensions.Editors).registerEditor(EditorDescriptor.create(TestEditorControl, 'MyTestEditorForEditorService', 'My Test Editor For Next Editor Service'), [new SyncDescriptor(TestEditorInput)]); } registerTestEditorInput(); @@ -270,36 +294,36 @@ suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} skip suite // Untyped Input (untitled) input = service.createInput({ options: { selection: { startLineNumber: 1, startColumn: 1 } } }); - assert(input instanceof UntitledEditorInput); + assert(input instanceof UntitledTextEditorInput); // Untyped Input (untitled with contents) input = service.createInput({ contents: 'Hello Untitled', options: { selection: { startLineNumber: 1, startColumn: 1 } } }); - assert(input instanceof UntitledEditorInput); - let model = await input.resolve() as UntitledEditorModel; + assert(input instanceof UntitledTextEditorInput); + let model = await input.resolve() as UntitledTextEditorModel; assert.equal(model.textEditorModel!.getValue(), 'Hello Untitled'); // Untyped Input (untitled with mode) input = service.createInput({ mode, options: { selection: { startLineNumber: 1, startColumn: 1 } } }); - assert(input instanceof UntitledEditorInput); - model = await input.resolve() as UntitledEditorModel; + assert(input instanceof UntitledTextEditorInput); + model = await input.resolve() as UntitledTextEditorModel; assert.equal(model.getMode(), mode); // Untyped Input (untitled with file path) input = service.createInput({ resource: URI.file('/some/path.txt'), forceUntitled: true, options: { selection: { startLineNumber: 1, startColumn: 1 } } }); - assert(input instanceof UntitledEditorInput); - assert.ok((input as UntitledEditorInput).hasAssociatedFilePath); + assert(input instanceof UntitledTextEditorInput); + assert.ok((input as UntitledTextEditorInput).hasAssociatedFilePath); // Untyped Input (untitled with untitled resource) input = service.createInput({ resource: URI.parse('untitled://Untitled-1'), forceUntitled: true, options: { selection: { startLineNumber: 1, startColumn: 1 } } }); - assert(input instanceof UntitledEditorInput); - assert.ok(!(input as UntitledEditorInput).hasAssociatedFilePath); + assert(input instanceof UntitledTextEditorInput); + assert.ok(!(input as UntitledTextEditorInput).hasAssociatedFilePath); // Untyped Input (untitled with custom resource) const provider = instantiationService.createInstance(FileServiceProvider, 'untitled-custom'); input = service.createInput({ resource: URI.parse('untitled-custom://some/path'), forceUntitled: true, options: { selection: { startLineNumber: 1, startColumn: 1 } } }); - assert(input instanceof UntitledEditorInput); - assert.ok((input as UntitledEditorInput).hasAssociatedFilePath); + assert(input instanceof UntitledTextEditorInput); + assert.ok((input as UntitledTextEditorInput).hasAssociatedFilePath); provider.dispose(); @@ -686,4 +710,45 @@ suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} skip suite let failingEditor = await service.openEditor(failingInput); assert.ok(!failingEditor); }); + + test('save, saveAll, revertAll', async function () { + const partInstantiator = workbenchInstantiationService(); + + const part = partInstantiator.createInstance(EditorPart); + part.create(document.createElement('div')); + part.layout(400, 300); + + const testInstantiationService = partInstantiator.createChild(new ServiceCollection([IEditorGroupsService, part])); + + const service: IEditorService = testInstantiationService.createInstance(EditorService); + + const input1 = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource1-openside')); + input1.dirty = true; + const input2 = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource2-openside')); + input2.dirty = true; + + const rootGroup = part.activeGroup; + + await part.whenRestored; + + await service.openEditor(input1, { pinned: true }); + await service.openEditor(input2, { pinned: true }); + + await service.save({ groupId: rootGroup.id, editor: input1 }); + assert.equal(input1.gotSaved, true); + + await service.save({ groupId: rootGroup.id, editor: input1 }, { saveAs: true }); + assert.equal(input1.gotSavedAs, true); + + await service.revertAll(); + assert.equal(input1.gotReverted, true); + + await service.saveAll(); + assert.equal(input1.gotSaved, true); + assert.equal(input2.gotSaved, true); + + await service.saveAll({ saveAs: true }); + assert.equal(input1.gotSavedAs, true); + assert.equal(input2.gotSavedAs, true); + }); }); diff --git a/src/vs/workbench/services/environment/browser/environmentService.ts b/src/vs/workbench/services/environment/browser/environmentService.ts index 25e3da30f38a..28edfcc91b99 100644 --- a/src/vs/workbench/services/environment/browser/environmentService.ts +++ b/src/vs/workbench/services/environment/browser/environmentService.ts @@ -9,7 +9,7 @@ import { IProcessEnvironment } from 'vs/base/common/platform'; import { joinPath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; -import { BACKUPS, IDebugParams, IExtensionHostDebugParams } from 'vs/platform/environment/common/environment'; +import { BACKUPS, IExtensionHostDebugParams } from 'vs/platform/environment/common/environment'; import { LogLevel } from 'vs/platform/log/common/log'; import { IPath, IPathsToWaitFor, IWindowConfiguration } from 'vs/platform/windows/common/windows'; import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; @@ -17,104 +17,294 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ import { IWorkbenchConstructionOptions } from 'vs/workbench/workbench.web.api'; import product from 'vs/platform/product/common/product'; import { serializableToMap } from 'vs/base/common/map'; +import { memoize } from 'vs/base/common/decorators'; +// TODO@ben remove properties that are node/electron only export class BrowserWindowConfiguration implements IWindowConfiguration { + constructor( + private readonly options: IBrowserWorkbenchEnvironmentConstructionOptions, + private readonly payload: Map | undefined, + private readonly environment: IWorkbenchEnvironmentService + ) { } + + //#region PROPERLY CONFIGURED IN DESKTOP + WEB + + @memoize + get sessionId(): string { return generateUuid(); } + + @memoize + get remoteAuthority(): string | undefined { return this.options.remoteAuthority; } + + @memoize + get connectionToken(): string | undefined { return this.options.connectionToken || this.getCookieValue('vscode-tkn'); } + + @memoize + get backupWorkspaceResource(): URI { return joinPath(this.environment.backupHome, this.options.workspaceId); } + + @memoize + get filesToOpenOrCreate(): IPath[] | undefined { + if (this.payload) { + const fileToOpen = this.payload.get('openFile'); + if (fileToOpen) { + return [{ fileUri: URI.parse(fileToOpen) }]; + } + } + + return undefined; + } + + // Currently unsupported in web + get filesToDiff(): IPath[] | undefined { return undefined; } + + //#endregion + + + //#region TODO MOVE TO NODE LAYER + _!: any[]; - machineId!: string; windowId!: number; - logLevel!: LogLevel; - mainPid!: number; + logLevel!: LogLevel; + appRoot!: string; execPath!: string; - isInitialStartup?: boolean; - - userEnv!: IProcessEnvironment; + backupPath?: string; nodeCachedDataDir?: string; - backupPath?: string; - backupWorkspaceResource?: URI; + userEnv!: IProcessEnvironment; workspace?: IWorkspaceIdentifier; folderUri?: ISingleFolderWorkspaceIdentifier; - remoteAuthority?: string; - connectionToken?: string; - zoomLevel?: number; fullscreen?: boolean; maximized?: boolean; highContrast?: boolean; - frameless?: boolean; accessibilitySupport?: boolean; partsSplashPath?: string; - perfStartTime?: number; - perfAppReady?: number; - perfWindowLoadTime?: number; + isInitialStartup?: boolean; perfEntries!: ExportData; - filesToOpenOrCreate?: IPath[]; - filesToDiff?: IPath[]; filesToWait?: IPathsToWaitFor; - termProgram?: string; + + //#endregion + + private getCookieValue(name: string): string | undefined { + const m = document.cookie.match('(^|[^;]+)\\s*' + name + '\\s*=\\s*([^;]+)'); // See https://stackoverflow.com/a/25490531 + + return m ? m.pop() : undefined; + } } -interface IBrowserWorkbenchEnvironemntConstructionOptions extends IWorkbenchConstructionOptions { +interface IBrowserWorkbenchEnvironmentConstructionOptions extends IWorkbenchConstructionOptions { workspaceId: string; logsPath: URI; } +interface IExtensionHostDebugEnvironment { + params: IExtensionHostDebugParams; + isExtensionDevelopment: boolean; + extensionDevelopmentLocationURI: URI[]; + extensionTestsLocationURI?: URI; +} + export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironmentService { _serviceBrand: undefined; - readonly configuration: IWindowConfiguration = new BrowserWindowConfiguration(); - - constructor(readonly options: IBrowserWorkbenchEnvironemntConstructionOptions) { - this.args = { _: [] }; - this.logsPath = options.logsPath.path; - this.logFile = joinPath(options.logsPath, 'window.log'); - this.appRoot = '/web/'; - this.appNameLong = 'Azure Data Studio - Web'; // {{SQL CARBON EDIT}} vscode to ads - - this.configuration.remoteAuthority = options.remoteAuthority; - this.configuration.machineId = generateUuid(); - this.userRoamingDataHome = URI.file('/User').with({ scheme: Schemas.userData }); - this.settingsResource = joinPath(this.userRoamingDataHome, 'settings.json'); - this.settingsSyncPreviewResource = joinPath(this.userRoamingDataHome, '.settings.json'); - this.userDataSyncLogResource = joinPath(options.logsPath, 'userDataSync.log'); - this.keybindingsResource = joinPath(this.userRoamingDataHome, 'keybindings.json'); - this.keyboardLayoutResource = joinPath(this.userRoamingDataHome, 'keyboardLayout.json'); - this.argvResource = joinPath(this.userRoamingDataHome, 'argv.json'); - this.backupHome = joinPath(this.userRoamingDataHome, BACKUPS); - this.untitledWorkspacesHome = joinPath(this.userRoamingDataHome, 'Workspaces'); - this.configuration.backupWorkspaceResource = joinPath(this.backupHome, options.workspaceId); - this.configuration.connectionToken = options.connectionToken || getCookieValue('vscode-tkn'); - - this.debugExtensionHost = { - port: null, - break: false + //#region PROPERLY CONFIGURED IN DESKTOP + WEB + + @memoize + get isBuilt(): boolean { return !!product.commit; } + + @memoize + get logsPath(): string { return this.options.logsPath.path; } + + @memoize + get logFile(): URI { return joinPath(this.options.logsPath, 'window.log'); } + + @memoize + get userRoamingDataHome(): URI { return URI.file('/User').with({ scheme: Schemas.userData }); } + + @memoize + get settingsResource(): URI { return joinPath(this.userRoamingDataHome, 'settings.json'); } + + @memoize + get settingsSyncPreviewResource(): URI { return joinPath(this.userRoamingDataHome, '.settings.json'); } + + @memoize + get keybindingsSyncPreviewResource(): URI { return joinPath(this.userRoamingDataHome, '.keybindings.json'); } + + @memoize + get userDataSyncLogResource(): URI { return joinPath(this.options.logsPath, 'userDataSync.log'); } + + @memoize + get keybindingsResource(): URI { return joinPath(this.userRoamingDataHome, 'keybindings.json'); } + + @memoize + get keyboardLayoutResource(): URI { return joinPath(this.userRoamingDataHome, 'keyboardLayout.json'); } + + @memoize + get backupHome(): URI { return joinPath(this.userRoamingDataHome, BACKUPS); } + + @memoize + get untitledWorkspacesHome(): URI { return joinPath(this.userRoamingDataHome, 'Workspaces'); } + + private _extensionHostDebugEnvironment: IExtensionHostDebugEnvironment | undefined = undefined; + get debugExtensionHost(): IExtensionHostDebugParams { + if (!this._extensionHostDebugEnvironment) { + this._extensionHostDebugEnvironment = this.resolveExtensionHostDebugEnvironment(); + } + + return this._extensionHostDebugEnvironment.params; + } + + get isExtensionDevelopment(): boolean { + if (!this._extensionHostDebugEnvironment) { + this._extensionHostDebugEnvironment = this.resolveExtensionHostDebugEnvironment(); + } + + return this._extensionHostDebugEnvironment.isExtensionDevelopment; + } + + get extensionDevelopmentLocationURI(): URI[] { + if (!this._extensionHostDebugEnvironment) { + this._extensionHostDebugEnvironment = this.resolveExtensionHostDebugEnvironment(); + } + + return this._extensionHostDebugEnvironment.extensionDevelopmentLocationURI; + } + + get extensionTestsLocationURI(): URI | undefined { + if (!this._extensionHostDebugEnvironment) { + this._extensionHostDebugEnvironment = this.resolveExtensionHostDebugEnvironment(); + } + + return this._extensionHostDebugEnvironment.extensionTestsLocationURI; + } + + @memoize + get webviewExternalEndpoint(): string { + // TODO: get fallback from product.json + return (this.options.webviewEndpoint || 'https://{{uuid}}.vscode-webview-test.com/{{commit}}').replace('{{commit}}', product.commit || '0d728c31ebdf03869d2687d9be0b017667c9ff37'); + } + + @memoize + get webviewResourceRoot(): string { + return `${this.webviewExternalEndpoint}/vscode-resource/{{resource}}`; + } + + @memoize + get webviewCspSource(): string { + return this.webviewExternalEndpoint.replace('{{uuid}}', '*'); + } + + // Currently not configurable in web + get disableExtensions() { return false; } + get extensionsPath(): string | undefined { return undefined; } + get verbose(): boolean { return false; } + get disableUpdates(): boolean { return false; } + get logExtensionHostCommunication(): boolean { return false; } + + //#endregion + + + //#region TODO MOVE TO NODE LAYER + + private _configuration: IWindowConfiguration | undefined = undefined; + get configuration(): IWindowConfiguration { + if (!this._configuration) { + this._configuration = new BrowserWindowConfiguration(this.options, this.payload, this); + } + + return this._configuration; + } + + args = { _: [] }; + + wait!: boolean; + status!: boolean; + log?: string; + + mainIPCHandle!: string; + sharedIPCHandle!: string; + + nodeCachedDataDir?: string; + + argvResource!: URI; + + disableCrashReporter!: boolean; + + driverHandle?: string; + driverVerbose!: boolean; + + installSourcePath!: string; + + builtinExtensionsPath!: string; + + globalStorageHome!: string; + workspaceStorageHome!: string; + + backupWorkspacesPath!: string; + + machineSettingsHome!: URI; + machineSettingsResource!: URI; + + userHome!: string; + userDataPath!: string; + appRoot!: string; + appSettingsHome!: URI; + execPath!: string; + cliPath!: string; + + //#endregion + + + //#region TODO ENABLE IN WEB + + galleryMachineIdResource?: URI; + + //#endregion + + private payload: Map | undefined; + + constructor(readonly options: IBrowserWorkbenchEnvironmentConstructionOptions) { + if (options.workspaceProvider && Array.isArray(options.workspaceProvider.payload)) { + this.payload = serializableToMap(options.workspaceProvider.payload); + } + } + + private resolveExtensionHostDebugEnvironment(): IExtensionHostDebugEnvironment { + const extensionHostDebugEnvironment: IExtensionHostDebugEnvironment = { + params: { + port: null, + break: false + }, + isExtensionDevelopment: false, + extensionDevelopmentLocationURI: [] }; // Fill in selected extra environmental properties - if (options.workspaceProvider && Array.isArray(options.workspaceProvider.payload)) { - const environment = serializableToMap(options.workspaceProvider.payload); - for (const [key, value] of environment) { + if (this.payload) { + for (const [key, value] of this.payload) { switch (key) { case 'extensionDevelopmentPath': - this.extensionDevelopmentLocationURI = [URI.parse(value)]; - this.isExtensionDevelopment = true; + extensionHostDebugEnvironment.extensionDevelopmentLocationURI = [URI.parse(value)]; + extensionHostDebugEnvironment.isExtensionDevelopment = true; + break; + case 'extensionTestsPath': + extensionHostDebugEnvironment.extensionTestsLocationURI = URI.parse(value); break; case 'debugId': - this.debugExtensionHost.debugId = value; + extensionHostDebugEnvironment.params.debugId = value; break; case 'inspect-brk-extensions': - this.debugExtensionHost.port = parseInt(value); - this.debugExtensionHost.break = false; + extensionHostDebugEnvironment.params.port = parseInt(value); + extensionHostDebugEnvironment.params.break = true; break; } } @@ -133,96 +323,23 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment const edp = map.get('extensionDevelopmentPath'); if (edp) { - this.extensionDevelopmentLocationURI = [URI.parse(edp)]; - this.isExtensionDevelopment = true; + extensionHostDebugEnvironment.extensionDevelopmentLocationURI = [URI.parse(edp)]; + extensionHostDebugEnvironment.isExtensionDevelopment = true; } const di = map.get('debugId'); if (di) { - this.debugExtensionHost.debugId = di; + extensionHostDebugEnvironment.params.debugId = di; } const ibe = map.get('inspect-brk-extensions'); if (ibe) { - this.debugExtensionHost.port = parseInt(ibe); - this.debugExtensionHost.break = false; + extensionHostDebugEnvironment.params.port = parseInt(ibe); + extensionHostDebugEnvironment.params.break = false; } } } - } - untitledWorkspacesHome: URI; - extensionTestsLocationURI?: URI; - args: any; - execPath!: string; - cliPath!: string; - appRoot: string; - userHome!: string; - userDataPath!: string; - appNameLong: string; - appQuality?: string; - appSettingsHome!: URI; - userRoamingDataHome: URI; - settingsResource: URI; - keybindingsResource: URI; - keyboardLayoutResource: URI; - argvResource: URI; - settingsSyncPreviewResource: URI; - userDataSyncLogResource: URI; - machineSettingsHome!: URI; - machineSettingsResource!: URI; - globalStorageHome!: string; - workspaceStorageHome!: string; - backupHome: URI; - backupWorkspacesPath!: string; - workspacesHome!: string; - isExtensionDevelopment!: boolean; - disableExtensions!: boolean | string[]; - builtinExtensionsPath!: string; - extensionsPath?: string; - extensionDevelopmentLocationURI?: URI[]; - extensionTestsPath?: string; - debugExtensionHost: IExtensionHostDebugParams; - debugSearch!: IDebugParams; - logExtensionHostCommunication!: boolean; - isBuilt!: boolean; - wait!: boolean; - status!: boolean; - log?: string; - logsPath: string; - verbose!: boolean; - skipReleaseNotes!: boolean; - mainIPCHandle!: string; - sharedIPCHandle!: string; - nodeCachedDataDir?: string; - installSourcePath!: string; - disableUpdates!: boolean; - disableCrashReporter!: boolean; - driverHandle?: string; - driverVerbose!: boolean; - galleryMachineIdResource?: URI; - readonly logFile: URI; - - get webviewExternalEndpoint(): string { - // TODO: get fallback from product.json - return (this.options.webviewEndpoint || 'https://{{uuid}}.vscode-webview-test.com/{{commit}}') - .replace('{{commit}}', product.commit || 'c58aaab8a1cc22a7139b761166a0d4f37d41e998'); - } - - get webviewResourceRoot(): string { - return `${this.webviewExternalEndpoint}/vscode-resource/{{resource}}`; + return extensionHostDebugEnvironment; } - - get webviewCspSource(): string { - return this.webviewExternalEndpoint - .replace('{{uuid}}', '*'); - } -} - -/** - * See https://stackoverflow.com/a/25490531 - */ -function getCookieValue(name: string): string | undefined { - const m = document.cookie.match('(^|[^;]+)\\s*' + name + '\\s*=\\s*([^;]+)'); - return m ? m.pop() : undefined; } diff --git a/src/vs/workbench/services/environment/common/environmentService.ts b/src/vs/workbench/services/environment/common/environmentService.ts index 47ea9e2f40da..651137883140 100644 --- a/src/vs/workbench/services/environment/common/environmentService.ts +++ b/src/vs/workbench/services/environment/common/environmentService.ts @@ -5,7 +5,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; -import { IEnvironmentService, IDebugParams } from 'vs/platform/environment/common/environment'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IWorkbenchConstructionOptions } from 'vs/workbench/workbench.web.api'; import { URI } from 'vs/base/common/uri'; @@ -20,14 +20,8 @@ export interface IWorkbenchEnvironmentService extends IEnvironmentService { readonly options?: IWorkbenchConstructionOptions; readonly logFile: URI; - readonly logExtensionHostCommunication: boolean; - - readonly debugSearch: IDebugParams; readonly webviewExternalEndpoint: string; readonly webviewResourceRoot: string; readonly webviewCspSource: string; - - readonly skipReleaseNotes: boolean | undefined; - } diff --git a/src/vs/workbench/services/environment/node/environmentService.ts b/src/vs/workbench/services/environment/electron-browser/environmentService.ts similarity index 65% rename from src/vs/workbench/services/environment/node/environmentService.ts rename to src/vs/workbench/services/environment/electron-browser/environmentService.ts index 334d9896a71d..022c35fd229f 100644 --- a/src/vs/workbench/services/environment/node/environmentService.ts +++ b/src/vs/workbench/services/environment/electron-browser/environmentService.ts @@ -3,28 +3,38 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { EnvironmentService, parseSearchPort } from 'vs/platform/environment/node/environmentService'; +import { EnvironmentService } from 'vs/platform/environment/node/environmentService'; import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { memoize } from 'vs/base/common/decorators'; import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; -import { toBackupWorkspaceResource } from 'vs/workbench/services/backup/common/backup'; +import { toBackupWorkspaceResource } from 'vs/workbench/services/backup/electron-browser/backup'; import { join } from 'vs/base/common/path'; -import { IDebugParams } from 'vs/platform/environment/common/environment'; import product from 'vs/platform/product/common/product'; -export class WorkbenchEnvironmentService extends EnvironmentService implements IWorkbenchEnvironmentService { +export class NativeWorkbenchEnvironmentService extends EnvironmentService implements IWorkbenchEnvironmentService { _serviceBrand: undefined; + @memoize get webviewExternalEndpoint(): string { const baseEndpoint = 'https://{{uuid}}.vscode-webview-test.com/{{commit}}'; - return baseEndpoint.replace('{{commit}}', product.commit || 'c58aaab8a1cc22a7139b761166a0d4f37d41e998'); + + return baseEndpoint.replace('{{commit}}', product.commit || '0d728c31ebdf03869d2687d9be0b017667c9ff37'); } - readonly webviewResourceRoot = 'vscode-resource://{{resource}}'; - readonly webviewCspSource = 'vscode-resource:'; + @memoize + get webviewResourceRoot(): string { return 'vscode-resource://{{resource}}'; } + + @memoize + get webviewCspSource(): string { return 'vscode-resource:'; } + + @memoize + get userRoamingDataHome(): URI { return this.appSettingsHome.with({ scheme: Schemas.userData }); } + + @memoize + get logFile(): URI { return URI.file(join(this.logsPath, `renderer${this.windowId}.log`)); } constructor( readonly configuration: IWindowConfiguration, @@ -35,17 +45,4 @@ export class WorkbenchEnvironmentService extends EnvironmentService implements I this.configuration.backupWorkspaceResource = this.configuration.backupPath ? toBackupWorkspaceResource(this.configuration.backupPath, this) : undefined; } - - get skipReleaseNotes(): boolean { return !!this.args['skip-release-notes']; } - - @memoize - get userRoamingDataHome(): URI { return this.appSettingsHome.with({ scheme: Schemas.userData }); } - - @memoize - get logFile(): URI { return URI.file(join(this.logsPath, `renderer${this.windowId}.log`)); } - - get logExtensionHostCommunication(): boolean { return !!this.args.logExtensionHostCommunication; } - - @memoize - get debugSearch(): IDebugParams { return parseSearchPort(this.args, this.isBuilt); } } diff --git a/src/vs/workbench/services/extensionManagement/common/extensionEnablementService.ts b/src/vs/workbench/services/extensionManagement/common/extensionEnablementService.ts index 12739940e73e..e60e8844fd4f 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionEnablementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionEnablementService.ts @@ -15,7 +15,7 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ import { isUndefinedOrNull } from 'vs/base/common/types'; import { ExtensionType, IExtension } from 'vs/platform/extensions/common/extensions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { isUIExtension } from 'vs/workbench/services/extensions/common/extensionsUtil'; +import { getExtensionKind } from 'vs/workbench/services/extensions/common/extensionsUtil'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IProductService } from 'vs/platform/product/common/productService'; @@ -146,12 +146,21 @@ export class ExtensionEnablementService extends Disposable implements IExtension } private _isDisabledByExtensionKind(extension: IExtension): boolean { - if (this.extensionManagementServerService.localExtensionManagementServer && this.extensionManagementServerService.remoteExtensionManagementServer) { - if (!isUIExtension(extension.manifest, this.productService, this.configurationService)) { - // workspace extensions must run on the remote, but UI extensions can run on either side - const server = this.extensionManagementServerService.remoteExtensionManagementServer; - return this.extensionManagementServerService.getExtensionManagementServer(extension.location) !== server; + if (this.extensionManagementServerService.remoteExtensionManagementServer) { + const server = this.extensionManagementServerService.getExtensionManagementServer(extension.location); + for (const extensionKind of getExtensionKind(extension.manifest, this.productService, this.configurationService)) { + if (extensionKind === 'ui') { + if (this.extensionManagementServerService.localExtensionManagementServer && this.extensionManagementServerService.localExtensionManagementServer === server) { + return false; + } + } + if (extensionKind === 'workspace') { + if (server === this.extensionManagementServerService.remoteExtensionManagementServer) { + return false; + } + } } + return true; } return false; } diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts index 6d231747112c..e877fb59c114 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts @@ -5,7 +5,7 @@ import { Event, EventMultiplexer } from 'vs/base/common/event'; import { - IExtensionManagementService, ILocalExtension, IGalleryExtension, InstallExtensionEvent, DidInstallExtensionEvent, IExtensionIdentifier, DidUninstallExtensionEvent, IReportedExtension, IGalleryMetadata, IExtensionGalleryService + IExtensionManagementService, ILocalExtension, IGalleryExtension, InstallExtensionEvent, DidInstallExtensionEvent, IExtensionIdentifier, DidUninstallExtensionEvent, IReportedExtension, IGalleryMetadata, IExtensionGalleryService, INSTALL_ERROR_NOT_SUPPORTED } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IExtensionManagementServer, IExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { ExtensionType, isLanguagePackExtension, IExtensionManifest } from 'vs/platform/extensions/common/extensions'; @@ -15,7 +15,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { CancellationToken } from 'vs/base/common/cancellation'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { localize } from 'vs/nls'; -import { isUIExtension } from 'vs/workbench/services/extensions/common/extensionsUtil'; +import { prefersExecuteOnUI, canExecuteOnWorkspace } from 'vs/workbench/services/extensions/common/extensionsUtil'; import { IProductService } from 'vs/platform/product/common/productService'; import { Schemas } from 'vs/base/common/network'; import { IDownloadService } from 'vs/platform/download/common/download'; @@ -93,7 +93,7 @@ export class ExtensionManagementService extends Disposable implements IExtension private async uninstallInServer(extension: ILocalExtension, server: IExtensionManagementServer, force?: boolean): Promise { if (server === this.extensionManagementServerService.localExtensionManagementServer) { const installedExtensions = await this.extensionManagementServerService.remoteExtensionManagementServer!.extensionManagementService.getInstalled(ExtensionType.User); - const dependentNonUIExtensions = installedExtensions.filter(i => !isUIExtension(i.manifest, this.productService, this.configurationService) + const dependentNonUIExtensions = installedExtensions.filter(i => !prefersExecuteOnUI(i.manifest, this.productService, this.configurationService) && i.manifest.extensionDependencies && i.manifest.extensionDependencies.some(id => areSameExtensions({ id }, extension.identifier))); if (dependentNonUIExtensions.length) { return Promise.reject(new Error(this.getDependentsErrorMessage(extension, dependentNonUIExtensions))); @@ -152,7 +152,7 @@ export class ExtensionManagementService extends Disposable implements IExtension const [local] = await Promise.all(this.servers.map(server => this.installVSIX(vsix, server))); return local; } - if (isUIExtension(manifest, this.productService, this.configurationService)) { + if (prefersExecuteOnUI(manifest, this.productService, this.configurationService)) { // Install only on local server return this.installVSIX(vsix, this.extensionManagementServerService.localExtensionManagementServer); } @@ -190,7 +190,7 @@ export class ExtensionManagementService extends Disposable implements IExtension // Install on both servers return Promise.all(this.servers.map(server => server.extensionManagementService.installFromGallery(gallery))).then(([local]) => local); } - if (isUIExtension(manifest, this.productService, this.configurationService)) { + if (prefersExecuteOnUI(manifest, this.productService, this.configurationService)) { // Install only on local server return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.installFromGallery(gallery); } @@ -204,6 +204,15 @@ export class ExtensionManagementService extends Disposable implements IExtension return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.installFromGallery(gallery); } if (this.extensionManagementServerService.remoteExtensionManagementServer) { + const manifest = await this.extensionGalleryService.getManifest(gallery, CancellationToken.None); + if (!manifest) { + return Promise.reject(localize('Manifest is not found', "Installing Extension {0} failed: Manifest is not found.", gallery.displayName || gallery.name)); + } + if (!isLanguagePackExtension(manifest) && !canExecuteOnWorkspace(manifest, this.productService, this.configurationService)) { + const error = new Error(localize('cannot be installed', "Cannot install '{0}' extension since it cannot be enabled in the remote server.", gallery.displayName || gallery.name)); + error.name = INSTALL_ERROR_NOT_SUPPORTED; + return Promise.reject(error); + } return this.extensionManagementServerService.remoteExtensionManagementServer.extensionManagementService.installFromGallery(gallery); } return Promise.reject('No Servers to Install'); diff --git a/src/vs/workbench/services/extensionManagement/test/electron-browser/extensionEnablementService.test.ts b/src/vs/workbench/services/extensionManagement/test/electron-browser/extensionEnablementService.test.ts index 2f79c9193ca9..fbdc4a3e81af 100644 --- a/src/vs/workbench/services/extensionManagement/test/electron-browser/extensionEnablementService.test.ts +++ b/src/vs/workbench/services/extensionManagement/test/electron-browser/extensionEnablementService.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import * as sinon from 'sinon'; import { IExtensionManagementService, DidUninstallExtensionEvent, ILocalExtension, DidInstallExtensionEvent } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { IExtensionEnablementService, EnablementState, IExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; +import { IExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { ExtensionEnablementService } from 'vs/workbench/services/extensionManagement/common/extensionEnablementService'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { Emitter } from 'vs/base/common/event'; @@ -39,13 +39,15 @@ function storageService(instantiationService: TestInstantiationService): IStorag export class TestExtensionEnablementService extends ExtensionEnablementService { constructor(instantiationService: TestInstantiationService) { + const extensionManagementService = instantiationService.get(IExtensionManagementService) || instantiationService.stub(IExtensionManagementService, { onDidInstallExtension: new Emitter().event, onDidUninstallExtension: new Emitter().event } as IExtensionManagementService); + const extensionManagementServerService = instantiationService.get(IExtensionManagementServerService) || instantiationService.stub(IExtensionManagementServerService, { localExtensionManagementServer: { extensionManagementService } }); super( storageService(instantiationService), instantiationService.get(IWorkspaceContextService), instantiationService.get(IWorkbenchEnvironmentService) || instantiationService.stub(IWorkbenchEnvironmentService, { configuration: Object.create(null) } as IWorkbenchEnvironmentService), - instantiationService.get(IExtensionManagementService) || instantiationService.stub(IExtensionManagementService, - { onDidInstallExtension: new Emitter().event, onDidUninstallExtension: new Emitter().event } as IExtensionManagementService), - instantiationService.get(IConfigurationService), instantiationService.get(IExtensionManagementServerService), + extensionManagementService, + instantiationService.get(IConfigurationService), + extensionManagementServerService, productService ); } @@ -403,15 +405,23 @@ suite('ExtensionEnablementService Test', () => { test('test local workspace extension is disabled by kind', async () => { instantiationService.stub(IExtensionManagementServerService, aMultiExtensionManagementServerService(instantiationService)); - const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`) }); + const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`) }); testObject = new TestExtensionEnablementService(instantiationService); assert.ok(!testObject.isEnabled(localWorkspaceExtension)); assert.deepEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.DisabledByExtensionKind); }); + test('test local workspace + ui extension is enabled by kind', async () => { + instantiationService.stub(IExtensionManagementServerService, aMultiExtensionManagementServerService(instantiationService)); + const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['workspace', 'ui'] }, { location: URI.file(`pub.a`) }); + testObject = new TestExtensionEnablementService(instantiationService); + assert.ok(testObject.isEnabled(localWorkspaceExtension)); + assert.deepEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.EnabledGlobally); + }); + test('test local ui extension is not disabled by kind', async () => { instantiationService.stub(IExtensionManagementServerService, aMultiExtensionManagementServerService(instantiationService)); - const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`) }); + const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`) }); testObject = new TestExtensionEnablementService(instantiationService); assert.ok(testObject.isEnabled(localWorkspaceExtension)); assert.deepEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.EnabledGlobally); @@ -419,29 +429,45 @@ suite('ExtensionEnablementService Test', () => { test('test canChangeEnablement return false when the local workspace extension is disabled by kind', () => { instantiationService.stub(IExtensionManagementServerService, aMultiExtensionManagementServerService(instantiationService)); - const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`) }); + const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`) }); testObject = new TestExtensionEnablementService(instantiationService); assert.equal(testObject.canChangeEnablement(localWorkspaceExtension), false); }); test('test canChangeEnablement return true for local ui extension', () => { instantiationService.stub(IExtensionManagementServerService, aMultiExtensionManagementServerService(instantiationService)); - const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`) }); + const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`) }); testObject = new TestExtensionEnablementService(instantiationService); assert.equal(testObject.canChangeEnablement(localWorkspaceExtension), true); }); test('test remote ui extension is disabled by kind', async () => { instantiationService.stub(IExtensionManagementServerService, aMultiExtensionManagementServerService(instantiationService)); - const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + testObject = new TestExtensionEnablementService(instantiationService); + assert.ok(!testObject.isEnabled(localWorkspaceExtension)); + assert.deepEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.DisabledByExtensionKind); + }); + + test('test remote ui+workspace extension is disabled by kind', async () => { + instantiationService.stub(IExtensionManagementServerService, aMultiExtensionManagementServerService(instantiationService)); + const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['ui', 'workspace'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); testObject = new TestExtensionEnablementService(instantiationService); assert.ok(testObject.isEnabled(localWorkspaceExtension)); assert.deepEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.EnabledGlobally); }); + test('test remote ui extension is disabled by kind when there is no local server', async () => { + instantiationService.stub(IExtensionManagementServerService, anExtensionManagementServerService(null, anExtensionManagementServer('vscode-remote', instantiationService))); + const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + testObject = new TestExtensionEnablementService(instantiationService); + assert.ok(!testObject.isEnabled(localWorkspaceExtension)); + assert.deepEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.DisabledByExtensionKind); + }); + test('test remote workspace extension is not disabled by kind', async () => { instantiationService.stub(IExtensionManagementServerService, aMultiExtensionManagementServerService(instantiationService)); - const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); testObject = new TestExtensionEnablementService(instantiationService); assert.ok(testObject.isEnabled(localWorkspaceExtension)); assert.deepEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.EnabledGlobally); @@ -449,31 +475,35 @@ suite('ExtensionEnablementService Test', () => { test('test canChangeEnablement return false when the remote ui extension is disabled by kind', () => { instantiationService.stub(IExtensionManagementServerService, aMultiExtensionManagementServerService(instantiationService)); - const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); testObject = new TestExtensionEnablementService(instantiationService); - assert.equal(testObject.canChangeEnablement(localWorkspaceExtension), true); + assert.equal(testObject.canChangeEnablement(localWorkspaceExtension), false); }); test('test canChangeEnablement return true for remote workspace extension', () => { instantiationService.stub(IExtensionManagementServerService, aMultiExtensionManagementServerService(instantiationService)); - const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); testObject = new TestExtensionEnablementService(instantiationService); assert.equal(testObject.canChangeEnablement(localWorkspaceExtension), true); }); }); -function aMultiExtensionManagementServerService(instantiationService: TestInstantiationService): IExtensionManagementServerService { - const localExtensionManagementServer = { - authority: 'vscode-local', - label: 'local', - extensionManagementService: instantiationService.get(IExtensionManagementService) - }; - const remoteExtensionManagementServer = { - authority: 'vscode-remote', - label: 'remote', +function anExtensionManagementServer(authority: string, instantiationService: TestInstantiationService): IExtensionManagementServer { + return { + authority, + label: authority, extensionManagementService: instantiationService.get(IExtensionManagementService) }; +} + +function aMultiExtensionManagementServerService(instantiationService: TestInstantiationService): IExtensionManagementServerService { + const localExtensionManagementServer = anExtensionManagementServer('vscode-local', instantiationService); + const remoteExtensionManagementServer = anExtensionManagementServer('vscode-remote', instantiationService); + return anExtensionManagementServerService(localExtensionManagementServer, remoteExtensionManagementServer); +} + +function anExtensionManagementServerService(localExtensionManagementServer: IExtensionManagementServer | null, remoteExtensionManagementServer: IExtensionManagementServer | null): IExtensionManagementServerService { return { _serviceBrand: undefined, localExtensionManagementServer, diff --git a/src/vs/workbench/services/extensionResourceLoader/browser/extensionResourceLoaderService.ts b/src/vs/workbench/services/extensionResourceLoader/browser/extensionResourceLoaderService.ts new file mode 100644 index 000000000000..770910dcbe61 --- /dev/null +++ b/src/vs/workbench/services/extensionResourceLoader/browser/extensionResourceLoaderService.ts @@ -0,0 +1,38 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { URI } from 'vs/base/common/uri'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IFileService } from 'vs/platform/files/common/files'; +import { IExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader'; +import * as dom from 'vs/base/browser/dom'; +import { Schemas } from 'vs/base/common/network'; + +class ExtensionResourceLoaderService implements IExtensionResourceLoaderService { + + _serviceBrand: undefined; + + constructor( + @IFileService private readonly _fileService: IFileService + ) { } + + async readExtensionResource(uri: URI): Promise { + uri = dom.asDomUri(uri); + + if (uri.scheme !== Schemas.http && uri.scheme !== Schemas.https) { + const result = await this._fileService.readFile(uri); + return result.value.toString(); + } + + const response = await fetch(uri.toString(true)); + if (response.status !== 200) { + throw new Error(response.statusText); + } + return response.text(); + + } +} + +registerSingleton(IExtensionResourceLoaderService, ExtensionResourceLoaderService); diff --git a/src/vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader.ts b/src/vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader.ts new file mode 100644 index 000000000000..a646f7b4470e --- /dev/null +++ b/src/vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader.ts @@ -0,0 +1,21 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { URI } from 'vs/base/common/uri'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; + +export const IExtensionResourceLoaderService = createDecorator('extensionResourceLoaderService'); + +/** + * A service useful for reading resources from within extensions. + */ +export interface IExtensionResourceLoaderService { + _serviceBrand: undefined; + + /** + * Read a certain resource within an extension. + */ + readExtensionResource(uri: URI): Promise; +} diff --git a/src/vs/workbench/services/extensionResourceLoader/electron-browser/extensionResourceLoaderService.ts b/src/vs/workbench/services/extensionResourceLoader/electron-browser/extensionResourceLoaderService.ts new file mode 100644 index 000000000000..9e52d65f57e5 --- /dev/null +++ b/src/vs/workbench/services/extensionResourceLoader/electron-browser/extensionResourceLoaderService.ts @@ -0,0 +1,25 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { URI } from 'vs/base/common/uri'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IFileService } from 'vs/platform/files/common/files'; +import { IExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader'; + +export class ExtensionResourceLoaderService implements IExtensionResourceLoaderService { + + _serviceBrand: undefined; + + constructor( + @IFileService private readonly _fileService: IFileService + ) { } + + async readExtensionResource(uri: URI): Promise { + const result = await this._fileService.readFile(uri); + return result.value.toString(); + } +} + +registerSingleton(IExtensionResourceLoaderService, ExtensionResourceLoaderService); diff --git a/src/vs/workbench/services/extensions/browser/extensionService.ts b/src/vs/workbench/services/extensions/browser/extensionService.ts index bc43981dbdb3..14b0a5c31aab 100644 --- a/src/vs/workbench/services/extensions/browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/browser/extensionService.ts @@ -20,7 +20,7 @@ import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEn import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { WebWorkerExtensionHostStarter } from 'vs/workbench/services/extensions/browser/webWorkerExtensionHostStarter'; import { URI } from 'vs/base/common/uri'; -import { isWebExtension } from 'vs/workbench/services/extensions/common/extensionsUtil'; +import { canExecuteOnWeb } from 'vs/workbench/services/extensions/common/extensionsUtil'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { FetchFileSystemProvider } from 'vs/workbench/services/extensions/browser/webWorkerFileSystemProvider'; @@ -85,14 +85,14 @@ export class ExtensionService extends AbstractExtensionService implements IExten protected _createExtensionHosts(_isInitialStart: boolean, initialActivationEvents: string[]): ExtensionHostProcessManager[] { const result: ExtensionHostProcessManager[] = []; - const webExtensions = this.getExtensions().then(extensions => extensions.filter(ext => isWebExtension(ext, this._configService))); + const webExtensions = this.getExtensions().then(extensions => extensions.filter(ext => canExecuteOnWeb(ext, this._productService, this._configService))); const webHostProcessWorker = this._instantiationService.createInstance(WebWorkerExtensionHostStarter, true, webExtensions, URI.file(this._environmentService.logsPath).with({ scheme: this._environmentService.logFile.scheme })); const webHostProcessManager = this._instantiationService.createInstance(ExtensionHostProcessManager, false, webHostProcessWorker, null, initialActivationEvents); result.push(webHostProcessManager); const remoteAgentConnection = this._remoteAgentService.getConnection(); if (remoteAgentConnection) { - const remoteExtensions = this.getExtensions().then(extensions => extensions.filter(ext => !isWebExtension(ext, this._configService))); + const remoteExtensions = this.getExtensions().then(extensions => extensions.filter(ext => !canExecuteOnWeb(ext, this._productService, this._configService))); const remoteExtHostProcessWorker = this._instantiationService.createInstance(RemoteExtensionHostClient, remoteExtensions, this._createProvider(remoteAgentConnection.remoteAuthority), this._remoteAgentService.socketFactory); const remoteExtHostProcessManager = this._instantiationService.createInstance(ExtensionHostProcessManager, false, remoteExtHostProcessWorker, remoteAgentConnection.remoteAuthority, initialActivationEvents); result.push(remoteExtHostProcessManager); @@ -111,7 +111,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten let result: DeltaExtensionsResult; // local: only enabled and web'ish extension - localExtensions = localExtensions.filter(ext => this._isEnabled(ext) && isWebExtension(ext, this._configService)); + localExtensions = localExtensions!.filter(ext => this._isEnabled(ext) && canExecuteOnWeb(ext, this._productService, this._configService)); this._checkEnableProposedApi(localExtensions); if (!remoteEnv) { @@ -119,7 +119,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten } else { // remote: only enabled and none-web'ish extension - remoteEnv.extensions = remoteEnv.extensions.filter(extension => this._isEnabled(extension) && !isWebExtension(extension, this._configService)); + remoteEnv.extensions = remoteEnv.extensions.filter(extension => this._isEnabled(extension) && !canExecuteOnWeb(extension, this._productService, this._configService)); this._checkEnableProposedApi(remoteEnv.extensions); // in case of overlap, the remote wins diff --git a/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts b/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts index 96d576849189..c6b6e842329b 100644 --- a/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts +++ b/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts @@ -428,4 +428,4 @@ export class ManageAuthorizedExtensionURIsAction extends Action { } const actionRegistry = Registry.as(WorkbenchActionExtensions.WorkbenchActions); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ManageAuthorizedExtensionURIsAction, ManageAuthorizedExtensionURIsAction.ID, ManageAuthorizedExtensionURIsAction.LABEL), `Extensions: Manage Authorized Extension URIs...`, ExtensionsLabel); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ManageAuthorizedExtensionURIsAction, ManageAuthorizedExtensionURIsAction.ID, ManageAuthorizedExtensionURIsAction.LABEL), `Extensions: Manage Authorized Extension URIs...`, ExtensionsLabel); diff --git a/src/vs/workbench/services/extensions/browser/webWorkerFileSystemProvider.ts b/src/vs/workbench/services/extensions/browser/webWorkerFileSystemProvider.ts index 80644cd17b85..735c38bb9f9f 100644 --- a/src/vs/workbench/services/extensions/browser/webWorkerFileSystemProvider.ts +++ b/src/vs/workbench/services/extensions/browser/webWorkerFileSystemProvider.ts @@ -3,14 +3,13 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IFileSystemProvider, FileSystemProviderCapabilities, IStat, FileType, FileDeleteOptions, FileOverwriteOptions, FileWriteOptions, FileSystemProviderError, FileSystemProviderErrorCode } from 'vs/platform/files/common/files'; - +import { FileSystemProviderCapabilities, IStat, FileType, FileDeleteOptions, FileOverwriteOptions, FileWriteOptions, FileSystemProviderError, FileSystemProviderErrorCode, IFileSystemProviderWithFileReadWriteCapability } from 'vs/platform/files/common/files'; import { Event } from 'vs/base/common/event'; import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { NotImplementedError } from 'vs/base/common/errors'; -export class FetchFileSystemProvider implements IFileSystemProvider { +export class FetchFileSystemProvider implements IFileSystemProviderWithFileReadWriteCapability { readonly capabilities = FileSystemProviderCapabilities.Readonly + FileSystemProviderCapabilities.FileReadWrite + FileSystemProviderCapabilities.PathCaseSensitive; readonly onDidChangeCapabilities = Event.None; diff --git a/src/vs/workbench/services/extensions/common/extensionDescriptionRegistry.ts b/src/vs/workbench/services/extensions/common/extensionDescriptionRegistry.ts index b0e8ade7c3ae..7d0e77ccad16 100644 --- a/src/vs/workbench/services/extensions/common/extensionDescriptionRegistry.ts +++ b/src/vs/workbench/services/extensions/common/extensionDescriptionRegistry.ts @@ -118,7 +118,12 @@ export class ExtensionDescriptionRegistry { hasOnlyGoodArcs(id: string, good: Set): boolean { const dependencies = G.getArcs(id); - return dependencies.every(dependency => good.has(dependency)); + for (let i = 0; i < dependencies.length; i++) { + if (!good.has(dependencies[i])) { + return false; + } + } + return true; } getNodes(): string[] { diff --git a/src/vs/workbench/services/extensions/common/extensionDevOptions.ts b/src/vs/workbench/services/extensions/common/extensionDevOptions.ts index b4902eb1f66f..acddff78c0f0 100644 --- a/src/vs/workbench/services/extensions/common/extensionDevOptions.ts +++ b/src/vs/workbench/services/extensions/common/extensionDevOptions.ts @@ -29,7 +29,7 @@ export function parseExtensionDevOptions(environmentService: IEnvironmentService let isExtensionDevDebug = debugOk && typeof environmentService.debugExtensionHost.port === 'number'; let isExtensionDevDebugBrk = debugOk && !!environmentService.debugExtensionHost.break; - let isExtensionDevTestFromCli = isExtensionDevHost && !!environmentService.extensionTestsLocationURI && !environmentService.debugExtensionHost.break; + let isExtensionDevTestFromCli = isExtensionDevHost && !!environmentService.extensionTestsLocationURI && !environmentService.debugExtensionHost.debugId; return { isExtensionDevHost, isExtensionDevDebug, diff --git a/src/vs/workbench/services/extensions/common/extensionHostProcessManager.ts b/src/vs/workbench/services/extensions/common/extensionHostProcessManager.ts index 82a901cdbf83..cdd2cf6d3828 100644 --- a/src/vs/workbench/services/extensions/common/extensionHostProcessManager.ts +++ b/src/vs/workbench/services/extensions/common/extensionHostProcessManager.ts @@ -22,7 +22,7 @@ import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { Registry } from 'vs/platform/registry/common/platform'; import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IUntitledResourceInput } from 'vs/workbench/common/editor'; +import { IUntitledTextResourceInput } from 'vs/workbench/common/editor'; import { StopWatch } from 'vs/base/common/stopwatch'; import { VSBuffer } from 'vs/base/common/buffer'; import { IExtensionHostStarter } from 'vs/workbench/services/extensions/common/extensions'; @@ -57,7 +57,7 @@ export class ExtensionHostProcessManager extends Disposable { constructor( public readonly isLocal: boolean, extensionHostProcessWorker: IExtensionHostStarter, - private readonly _remoteAuthority: string, + private readonly _remoteAuthority: string | null, initialActivationEvents: string[], @IInstantiationService private readonly _instantiationService: IInstantiationService, @IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService, @@ -185,7 +185,7 @@ export class ExtensionHostProcessManager extends Disposable { this._extensionHostProcessRPCProtocol = new RPCProtocol(protocol, logger); this._register(this._extensionHostProcessRPCProtocol.onDidChangeResponsiveState((responsiveState: ResponsiveState) => this._onDidChangeResponsiveState.fire(responsiveState))); const extHostContext: IExtHostContext = { - remoteAuthority: this._remoteAuthority, + remoteAuthority: this._remoteAuthority! /* TODO: alexdima, remove not-null assertion */, getProxy: (identifier: ProxyIdentifier): T => this._extensionHostProcessRPCProtocol!.getProxy(identifier), set: (identifier: ProxyIdentifier, instance: R): R => this._extensionHostProcessRPCProtocol!.set(identifier, instance), assertRegistered: (identifiers: ProxyIdentifier[]): void => this._extensionHostProcessRPCProtocol!.assertRegistered(identifiers), @@ -362,7 +362,7 @@ class RPCLogger implements IRPCProtocolLogger { } interface ExtHostLatencyResult { - remoteAuthority: string; + remoteAuthority: string | null; up: number; down: number; latency: number; @@ -405,10 +405,13 @@ export class MeasureExtHostLatencyAction extends Action { public async run(): Promise { const measurements = await Promise.all(getLatencyTestProviders().map(provider => provider.measure())); - this._editorService.openEditor({ contents: measurements.map(MeasureExtHostLatencyAction._print).join('\n\n'), options: { pinned: true } } as IUntitledResourceInput); + this._editorService.openEditor({ contents: measurements.map(MeasureExtHostLatencyAction._print).join('\n\n'), options: { pinned: true } } as IUntitledTextResourceInput); } - private static _print(m: ExtHostLatencyResult): string { + private static _print(m: ExtHostLatencyResult | null): string { + if (!m) { + return ''; + } return `${m.remoteAuthority ? `Authority: ${m.remoteAuthority}\n` : ``}Roundtrip latency: ${m.latency.toFixed(3)}ms\nUp: ${MeasureExtHostLatencyAction._printSpeed(m.up)}\nDown: ${MeasureExtHostLatencyAction._printSpeed(m.down)}\n`; } @@ -424,4 +427,4 @@ export class MeasureExtHostLatencyAction extends Action { } const registry = Registry.as(ActionExtensions.WorkbenchActions); -registry.registerWorkbenchAction(new SyncActionDescriptor(MeasureExtHostLatencyAction, MeasureExtHostLatencyAction.ID, MeasureExtHostLatencyAction.LABEL), 'Developer: Measure Extension Host Latency', nls.localize('developer', "Developer")); +registry.registerWorkbenchAction(SyncActionDescriptor.create(MeasureExtHostLatencyAction, MeasureExtHostLatencyAction.ID, MeasureExtHostLatencyAction.LABEL), 'Developer: Measure Extension Host Latency', nls.localize('developer', "Developer")); diff --git a/src/vs/workbench/services/extensions/common/extensionsRegistry.ts b/src/vs/workbench/services/extensions/common/extensionsRegistry.ts index 3aff7c62a1a2..cfcb6c029752 100644 --- a/src/vs/workbench/services/extensions/common/extensionsRegistry.ts +++ b/src/vs/workbench/services/extensions/common/extensionsRegistry.ts @@ -146,8 +146,20 @@ export class ExtensionPoint implements IExtensionPoint { } } +const extensionKindSchema: IJSONSchema = { + type: 'string', + enum: [ + 'ui', + 'workspace' + ], + enumDescriptions: [ + nls.localize('ui', "UI extension kind. In a remote window, such extensions are enabled only when available on the local machine."), + nls.localize('workspace', "Workspace extension kind. In a remote window, such extensions are enabled only when available on the remote.") + ], +}; + const schemaId = 'vscode://schemas/vscode-extensions'; -export const schema = { +export const schema: IJSONSchema = { properties: { engines: { type: 'object', @@ -345,17 +357,32 @@ export const schema = { } }, extensionKind: { - description: nls.localize('extensionKind', "Define the kind of an extension. `ui` extensions are installed and run on the local machine while `workspace` extensions are run on the remote."), - type: 'string', - enum: [ - 'ui', - 'workspace' - ], - enumDescriptions: [ - nls.localize('ui', "UI extension kind. In a remote window, such extensions are enabled only when available on the local machine."), - nls.localize('workspace', "Workspace extension kind. In a remote window, such extensions are enabled only when available on the remote.") - ], - default: 'workspace' + description: nls.localize('extensionKind', "Define the kind of an extension. `ui` extensions are installed and run on the local machine while `workspace` extensions run on the remote."), + type: 'array', + items: extensionKindSchema, + default: ['workspace'], + defaultSnippets: [ + { + body: ['ui'], + description: nls.localize('extensionKind.ui', "Define an extension which can run only on the local machine when connected to remote window.") + }, + { + body: ['workspace'], + description: nls.localize('extensionKind.workspace', "Define an extension which can run only on the remote machine when connected remote window.") + }, + { + body: ['ui', 'workspace'], + description: nls.localize('extensionKind.ui-workspace', "Define an extension which can run on either side, with a preference towards running on the local machine.") + }, + { + body: ['workspace', 'ui'], + description: nls.localize('extensionKind.workspace-ui', "Define an extension which can run on either side, with a preference towards running on the remote machine.") + }, + { + body: [], + description: nls.localize('extensionKind.empty', "Define an extension which cannot run in a remote context, neither on the local, nor on the remote machine.") + } + ] }, scripts: { type: 'object', @@ -395,7 +422,7 @@ export class ExtensionsRegistryImpl { const result = new ExtensionPoint(desc.extensionPoint, desc.defaultExtensionKind); this._extensionPoints.set(desc.extensionPoint, result); - schema.properties['contributes'].properties[desc.extensionPoint] = desc.jsonSchema; + schema.properties!['contributes'].properties![desc.extensionPoint] = desc.jsonSchema; schemaRegistry.registerSchema(schemaId, schema); return result; diff --git a/src/vs/workbench/services/extensions/common/extensionsUtil.ts b/src/vs/workbench/services/extensions/common/extensionsUtil.ts index 043f5e2837f0..907f84f0c528 100644 --- a/src/vs/workbench/services/extensions/common/extensionsUtil.ts +++ b/src/vs/workbench/services/extensions/common/extensionsUtil.ts @@ -4,55 +4,124 @@ *--------------------------------------------------------------------------------------------*/ import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IExtensionManifest } from 'vs/platform/extensions/common/extensions'; +import { IExtensionManifest, ExtensionKind, ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -import { getGalleryExtensionId, areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { isNonEmptyArray } from 'vs/base/common/arrays'; import { IProductService } from 'vs/platform/product/common/productService'; -export function isWebExtension(manifest: IExtensionManifest, configurationService: IConfigurationService): boolean { - const extensionKind = getExtensionKind(manifest, configurationService); - return extensionKind === 'web'; +export function prefersExecuteOnUI(manifest: IExtensionManifest, productService: IProductService, configurationService: IConfigurationService): boolean { + const extensionKind = getExtensionKind(manifest, productService, configurationService); + return (extensionKind.length > 0 && extensionKind[0] === 'ui'); } -export function isUIExtension(manifest: IExtensionManifest, productService: IProductService, configurationService: IConfigurationService): boolean { - const uiContributions = ExtensionsRegistry.getExtensionPoints().filter(e => e.defaultExtensionKind !== 'workspace').map(e => e.name); - const extensionId = getGalleryExtensionId(manifest.publisher, manifest.name); - const extensionKind = getExtensionKind(manifest, configurationService); - switch (extensionKind) { - case 'ui': return true; - case 'workspace': return false; - default: { - // Tagged as UI extension in product - if (isNonEmptyArray(productService.uiExtensions) && productService.uiExtensions.some(id => areSameExtensions({ id }, { id: extensionId }))) { - return true; - } - // Not an UI extension if it has main - if (manifest.main) { - return false; - } - // Not an UI extension if it has dependencies or an extension pack - if (isNonEmptyArray(manifest.extensionDependencies) || isNonEmptyArray(manifest.extensionPack)) { - return false; - } - if (manifest.contributes) { - // Not an UI extension if it has no ui contributions - if (!uiContributions.length || Object.keys(manifest.contributes).some(contribution => uiContributions.indexOf(contribution) === -1)) { - return false; - } +export function prefersExecuteOnWorkspace(manifest: IExtensionManifest, productService: IProductService, configurationService: IConfigurationService): boolean { + const extensionKind = getExtensionKind(manifest, productService, configurationService); + return (extensionKind.length > 0 && extensionKind[0] === 'workspace'); +} + +export function canExecuteOnUI(manifest: IExtensionManifest, productService: IProductService, configurationService: IConfigurationService): boolean { + const extensionKind = getExtensionKind(manifest, productService, configurationService); + return extensionKind.some(kind => kind === 'ui'); +} + +export function canExecuteOnWorkspace(manifest: IExtensionManifest, productService: IProductService, configurationService: IConfigurationService): boolean { + const extensionKind = getExtensionKind(manifest, productService, configurationService); + return extensionKind.some(kind => kind === 'workspace'); +} + +export function canExecuteOnWeb(manifest: IExtensionManifest, productService: IProductService, configurationService: IConfigurationService): boolean { + const extensionKind = getExtensionKind(manifest, productService, configurationService); + return extensionKind.some(kind => kind === 'web'); +} + +export function getExtensionKind(manifest: IExtensionManifest, productService: IProductService, configurationService: IConfigurationService): ExtensionKind[] { + // check in config + let result = getConfiguredExtensionKind(manifest, configurationService); + if (typeof result !== 'undefined') { + return toArray(result); + } + + // check product.json + result = getProductExtensionKind(manifest, productService); + if (typeof result !== 'undefined') { + return toArray(result); + } + + // check the manifest itself + result = manifest.extensionKind; + if (typeof result !== 'undefined') { + return toArray(result); + } + + // Not an UI extension if it has main + if (manifest.main) { + return ['workspace']; + } + + // Not an UI extension if it has dependencies or an extension pack + if (isNonEmptyArray(manifest.extensionDependencies) || isNonEmptyArray(manifest.extensionPack)) { + return ['workspace']; + } + + if (manifest.contributes) { + // Not an UI extension if it has no ui contributions + for (const contribution of Object.keys(manifest.contributes)) { + if (!isUIExtensionPoint(contribution)) { + return ['workspace']; } - return true; } } + + return ['ui', 'workspace']; } -function getExtensionKind(manifest: IExtensionManifest, configurationService: IConfigurationService): string | undefined { +let _uiExtensionPoints: Set | null = null; +function isUIExtensionPoint(extensionPoint: string): boolean { + if (_uiExtensionPoints === null) { + const uiExtensionPoints = new Set(); + ExtensionsRegistry.getExtensionPoints().filter(e => e.defaultExtensionKind !== 'workspace').forEach(e => { + uiExtensionPoints.add(e.name); + }); + _uiExtensionPoints = uiExtensionPoints; + } + return _uiExtensionPoints.has(extensionPoint); +} + +let _productExtensionKindsMap: Map | null = null; +function getProductExtensionKind(manifest: IExtensionManifest, productService: IProductService): ExtensionKind | ExtensionKind[] | undefined { + if (_productExtensionKindsMap === null) { + const productExtensionKindsMap = new Map(); + if (productService.extensionKind) { + for (const id of Object.keys(productService.extensionKind)) { + productExtensionKindsMap.set(ExtensionIdentifier.toKey(id), productService.extensionKind[id]); + } + } + _productExtensionKindsMap = productExtensionKindsMap; + } + const extensionId = getGalleryExtensionId(manifest.publisher, manifest.name); - const configuredExtensionKinds = configurationService.getValue<{ [key: string]: string }>('remote.extensionKind') || {}; - for (const id of Object.keys(configuredExtensionKinds)) { - if (areSameExtensions({ id: extensionId }, { id })) { - return configuredExtensionKinds[id]; + return _productExtensionKindsMap.get(ExtensionIdentifier.toKey(extensionId)); +} + +let _configuredExtensionKindsMap: Map | null = null; +function getConfiguredExtensionKind(manifest: IExtensionManifest, configurationService: IConfigurationService): ExtensionKind | ExtensionKind[] | undefined { + if (_configuredExtensionKindsMap === null) { + const configuredExtensionKindsMap = new Map(); + const configuredExtensionKinds = configurationService.getValue<{ [key: string]: ExtensionKind | ExtensionKind[] }>('remote.extensionKind') || {}; + for (const id of Object.keys(configuredExtensionKinds)) { + configuredExtensionKindsMap.set(ExtensionIdentifier.toKey(id), configuredExtensionKinds[id]); } + _configuredExtensionKindsMap = configuredExtensionKindsMap; + } + + const extensionId = getGalleryExtensionId(manifest.publisher, manifest.name); + return _configuredExtensionKindsMap.get(ExtensionIdentifier.toKey(extensionId)); +} + +function toArray(extensionKind: ExtensionKind | ExtensionKind[]): ExtensionKind[] { + if (Array.isArray(extensionKind)) { + return extensionKind; } - return manifest.extensionKind; + return extensionKind === 'ui' ? ['ui', 'workspace'] : [extensionKind]; } diff --git a/src/vs/workbench/services/extensions/common/rpcProtocol.ts b/src/vs/workbench/services/extensions/common/rpcProtocol.ts index 968c3e6bcd23..f08e8d8faf82 100644 --- a/src/vs/workbench/services/extensions/common/rpcProtocol.ts +++ b/src/vs/workbench/services/extensions/common/rpcProtocol.ts @@ -543,10 +543,16 @@ class MessageBuffer { const el = arr[i]; const elType = arrType[i]; size += 1; // arg type - if (elType === ArgType.String) { - size += this.sizeLongString(el); - } else { - size += this.sizeVSBuffer(el); + switch (elType) { + case ArgType.String: + size += this.sizeLongString(el); + break; + case ArgType.VSBuffer: + size += this.sizeVSBuffer(el); + break; + case ArgType.Undefined: + // empty... + break; } } return size; @@ -557,19 +563,25 @@ class MessageBuffer { for (let i = 0, len = arr.length; i < len; i++) { const el = arr[i]; const elType = arrType[i]; - if (elType === ArgType.String) { - this.writeUInt8(ArgType.String); - this.writeLongString(el); - } else { - this.writeUInt8(ArgType.VSBuffer); - this.writeVSBuffer(el); + switch (elType) { + case ArgType.String: + this.writeUInt8(ArgType.String); + this.writeLongString(el); + break; + case ArgType.VSBuffer: + this.writeUInt8(ArgType.VSBuffer); + this.writeVSBuffer(el); + break; + case ArgType.Undefined: + this.writeUInt8(ArgType.Undefined); + break; } } } - public readMixedArray(): Array { + public readMixedArray(): Array { const arrLen = this._buff.readUInt8(this._offset); this._offset += 1; - let arr: Array = new Array(arrLen); + let arr: Array = new Array(arrLen); for (let i = 0; i < arrLen; i++) { const argType = this.readUInt8(); switch (argType) { @@ -579,6 +591,9 @@ class MessageBuffer { case ArgType.VSBuffer: arr[i] = this.readVSBuffer(); break; + case ArgType.Undefined: + arr[i] = undefined; + break; } } return arr; @@ -587,12 +602,20 @@ class MessageBuffer { class MessageIO { - private static _arrayContainsBuffer(arr: any[]): boolean { - return arr.some(value => value instanceof VSBuffer); + private static _arrayContainsBufferOrUndefined(arr: any[]): boolean { + for (let i = 0, len = arr.length; i < len; i++) { + if (arr[i] instanceof VSBuffer) { + return true; + } + if (typeof arr[i] === 'undefined') { + return true; + } + } + return false; } public static serializeRequest(req: number, rpcId: number, method: string, args: any[], usesCancellationToken: boolean, replacer: JSONStringifyReplacer | null): VSBuffer { - if (this._arrayContainsBuffer(args)) { + if (this._arrayContainsBufferOrUndefined(args)) { let massagedArgs: VSBuffer[] = []; let massagedArgsType: ArgType[] = []; for (let i = 0, len = args.length; i < len; i++) { @@ -600,6 +623,9 @@ class MessageIO { if (arg instanceof VSBuffer) { massagedArgs[i] = arg; massagedArgsType[i] = ArgType.VSBuffer; + } else if (typeof arg === 'undefined') { + massagedArgs[i] = VSBuffer.alloc(0); + massagedArgsType[i] = ArgType.Undefined; } else { massagedArgs[i] = VSBuffer.fromString(safeStringify(arg, replacer)); massagedArgsType[i] = ArgType.String; @@ -767,5 +793,6 @@ const enum MessageType { const enum ArgType { String = 1, - VSBuffer = 2 + VSBuffer = 2, + Undefined = 3 } diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts index 9e2396588225..6b0fbf34b727 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts @@ -37,7 +37,7 @@ import { parseExtensionDevOptions } from '../common/extensionDevOptions'; import { VSBuffer } from 'vs/base/common/buffer'; import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug'; import { IExtensionHostStarter } from 'vs/workbench/services/extensions/common/extensions'; -import { isEqualOrParent } from 'vs/base/common/resources'; +import { isUntitledWorkspace } from 'vs/platform/workspaces/common/workspaces'; import { IHostService } from 'vs/workbench/services/host/browser/host'; export class ExtensionHostProcessWorker implements IExtensionHostStarter { @@ -158,6 +158,8 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { '--nolazy', (this._isExtensionDevDebugBrk ? '--inspect-brk=' : '--inspect=') + portNumber ]; + } else { + opts.execArgv = ['--inspect-port=0']; } const crashReporterOptions = undefined; // TODO@electron pass this in as options to the extension host after verifying this actually works @@ -170,10 +172,10 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { // Catch all output coming from the extension host process type Output = { data: string, format: string[] }; - this._extensionHostProcess.stdout.setEncoding('utf8'); - this._extensionHostProcess.stderr.setEncoding('utf8'); - const onStdout = Event.fromNodeEventEmitter(this._extensionHostProcess.stdout, 'data'); - const onStderr = Event.fromNodeEventEmitter(this._extensionHostProcess.stderr, 'data'); + this._extensionHostProcess.stdout!.setEncoding('utf8'); + this._extensionHostProcess.stderr!.setEncoding('utf8'); + const onStdout = Event.fromNodeEventEmitter(this._extensionHostProcess.stdout!, 'data'); + const onStderr = Event.fromNodeEventEmitter(this._extensionHostProcess.stderr!, 'data'); const onOutput = Event.any( Event.map(onStdout, o => ({ data: `%c${o}`, format: [''] })), Event.map(onStderr, o => ({ data: `%c${o}`, format: ['color: red'] })) @@ -411,7 +413,7 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { configuration: withNullAsUndefined(workspace.configuration), id: workspace.id, name: this._labelService.getWorkspaceLabel(workspace), - isUntitled: workspace.configuration ? isEqualOrParent(workspace.configuration, this._environmentService.untitledWorkspacesHome) : false + isUntitled: workspace.configuration ? isUntitledWorkspace(workspace.configuration, this._environmentService) : false }, remote: { authority: this._environmentService.configuration.remoteAuthority, @@ -432,19 +434,19 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { private _logExtensionHostMessage(entry: IRemoteConsoleLog) { - // Send to local console unless we run tests from cli - if (!this._isExtensionDevTestFromCli) { - log(entry, 'Extension Host'); - } - - // Log on main side if running tests from cli if (this._isExtensionDevTestFromCli) { + + // Log on main side if running tests from cli logRemoteEntry(this._logService, entry); - } + } else { + + // Send to local console + log(entry, 'Extension Host'); - // Broadcast to other windows if we are in development mode - else if (this._environmentService.debugExtensionHost.debugId && (!this._environmentService.isBuilt || this._isExtensionDevHost)) { - this._extensionHostDebugService.logToSession(this._environmentService.debugExtensionHost.debugId, entry); + // Broadcast to other windows if we are in development mode + if (this._environmentService.debugExtensionHost.debugId && (!this._environmentService.isBuilt || this._isExtensionDevHost)) { + this._extensionHostDebugService.logToSession(this._environmentService.debugExtensionHost.debugId, entry); + } } } diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts index e51432cf9200..5ad6cedc6b22 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts @@ -19,7 +19,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IInitDataProvider, RemoteExtensionHostClient } from 'vs/workbench/services/extensions/common/remoteExtensionHostClient'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { IRemoteAuthorityResolverService, RemoteAuthorityResolverError, ResolverResult } from 'vs/platform/remote/common/remoteAuthorityResolver'; -import { isUIExtension as isUIExtensionFunc } from 'vs/workbench/services/extensions/common/extensionsUtil'; +import { getExtensionKind } from 'vs/workbench/services/extensions/common/extensionsUtil'; import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; @@ -439,8 +439,6 @@ export class ExtensionService extends AbstractExtensionService implements IExten } protected async _scanAndHandleExtensions(): Promise { - const isUIExtension = (extension: IExtensionDescription) => isUIExtensionFunc(extension, this._productService, this._configurationService); - this._extensionScanner.startScanningExtensions(this.createLogger()); const remoteAuthority = this._environmentService.configuration.remoteAuthority; @@ -510,14 +508,38 @@ export class ExtensionService extends AbstractExtensionService implements IExten // remove disabled extensions remoteEnv.extensions = remove(remoteEnv.extensions, extension => this._isDisabled(extension)); + // Determine where each extension will execute, based on extensionKind + const isInstalledLocally = new Set(); + localExtensions.forEach(ext => isInstalledLocally.add(ExtensionIdentifier.toKey(ext.identifier))); + + const isInstalledRemotely = new Set(); + remoteEnv.extensions.forEach(ext => isInstalledRemotely.add(ExtensionIdentifier.toKey(ext.identifier))); + + const enum RunningLocation { None, Local, Remote } + const pickRunningLocation = (extension: IExtensionDescription): RunningLocation => { + for (const extensionKind of getExtensionKind(extension, this._productService, this._configurationService)) { + if (extensionKind === 'ui') { + if (isInstalledLocally.has(ExtensionIdentifier.toKey(extension.identifier))) { + return RunningLocation.Local; + } + } else if (extensionKind === 'workspace') { + if (isInstalledRemotely.has(ExtensionIdentifier.toKey(extension.identifier))) { + return RunningLocation.Remote; + } + } + } + return RunningLocation.None; + }; + + const runningLocation = new Map(); + localExtensions.forEach(ext => runningLocation.set(ExtensionIdentifier.toKey(ext.identifier), pickRunningLocation(ext))); + remoteEnv.extensions.forEach(ext => runningLocation.set(ExtensionIdentifier.toKey(ext.identifier), pickRunningLocation(ext))); + // remove non-UI extensions from the local extensions - localExtensions = remove(localExtensions, extension => !extension.isBuiltin && !isUIExtension(extension)); + localExtensions = localExtensions.filter(ext => runningLocation.get(ExtensionIdentifier.toKey(ext.identifier)) === RunningLocation.Local); // in case of UI extensions overlap, the local extension wins - remoteEnv.extensions = remove(remoteEnv.extensions, localExtensions.filter(extension => isUIExtension(extension))); - - // in case of other extensions overlap, the remote extension wins - localExtensions = remove(localExtensions, remoteEnv.extensions); + remoteEnv.extensions = remoteEnv.extensions.filter(ext => runningLocation.get(ExtensionIdentifier.toKey(ext.identifier)) === RunningLocation.Remote); // save for remote extension's init data this._remoteExtensionsEnvironmentData.set(remoteAuthority, remoteEnv); @@ -550,14 +572,12 @@ export class ExtensionService extends AbstractExtensionService implements IExten } public _onExtensionHostExit(code: number): void { - // Expected development extension termination: When the extension host goes down we also shutdown the window - if (!this._isExtensionDevTestFromCli) { - this._electronService.closeWindow(); - } - - // When CLI testing make sure to exit with proper exit code - else { + if (this._isExtensionDevTestFromCli) { + // When CLI testing make sure to exit with proper exit code ipc.send('vscode:exit', code); + } else { + // Expected development extension termination: When the extension host goes down we also shutdown the window + this._electronService.closeWindow(); } } } diff --git a/src/vs/workbench/services/extensions/electron-browser/remoteExtensionManagementIpc.ts b/src/vs/workbench/services/extensions/electron-browser/remoteExtensionManagementIpc.ts index 3e74d33c9cc8..846d4a96c780 100644 --- a/src/vs/workbench/services/extensions/electron-browser/remoteExtensionManagementIpc.ts +++ b/src/vs/workbench/services/extensions/electron-browser/remoteExtensionManagementIpc.ts @@ -11,7 +11,7 @@ import { ExtensionType, IExtensionManifest } from 'vs/platform/extensions/common import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { ILogService } from 'vs/platform/log/common/log'; import { toErrorMessage } from 'vs/base/common/errorMessage'; -import { isUIExtension } from 'vs/workbench/services/extensions/common/extensionsUtil'; +import { prefersExecuteOnUI } from 'vs/workbench/services/extensions/common/extensionsUtil'; import { isNonEmptyArray } from 'vs/base/common/arrays'; import { values } from 'vs/base/common/map'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -48,6 +48,10 @@ export class RemoteExtensionManagementChannelClient extends ExtensionManagementC } private async doInstallFromGallery(extension: IGalleryExtension): Promise { + if (this.configurationService.getValue('remote.downloadExtensionsLocally')) { + this.logService.trace(`Download '${extension.identifier.id}' extension locally and install`); + return this.downloadCompatibleAndInstall(extension); + } try { const local = await super.installFromGallery(extension); return local; @@ -116,7 +120,7 @@ export class RemoteExtensionManagementChannelClient extends ExtensionManagementC for (let idx = 0; idx < extensions.length; idx++) { const extension = extensions[idx]; const manifest = manifests[idx]; - if (manifest && isUIExtension(manifest, this.productService, this.configurationService) === uiExtension) { + if (manifest && prefersExecuteOnUI(manifest, this.productService, this.configurationService) === uiExtension) { result.set(extension.identifier.id.toLowerCase(), extension); extensionsManifests.push(manifest); } diff --git a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts index 4f8aed30ad65..91c65e44fc69 100644 --- a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts +++ b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts @@ -27,6 +27,17 @@ interface ParsedExtHostArgs { uriTransformerPath?: string; } +// workaround for https://github.com/microsoft/vscode/issues/85490 +// remove --inspect-port=0 after start so that it doesn't trigger LSP debugging +(function removeInspectPort() { + for (let i = 0; i < process.execArgv.length; i++) { + if (process.execArgv[i] === '--inspect-port=0') { + process.execArgv.splice(i, 1); + i--; + } + } +})(); + const args = minimist(process.argv.slice(2), { string: [ 'uriTransformerPath' diff --git a/src/vs/workbench/services/extensions/node/extensionPoints.ts b/src/vs/workbench/services/extensions/node/extensionPoints.ts index c93d977c30ca..30fbf23e7dc2 100644 --- a/src/vs/workbench/services/extensions/node/extensionPoints.ts +++ b/src/vs/workbench/services/extensions/node/extensionPoints.ts @@ -51,7 +51,7 @@ class ExtensionManifestParser extends ExtensionManifestHandler { return pfs.readFile(this._absoluteManifestPath).then((manifestContents) => { const errors: json.ParseError[] = []; const manifest = json.parse(manifestContents.toString(), errors); - if (!!manifest && errors.length === 0) { + if (errors.length === 0 && json.getNodeType(manifest) === 'object') { if (manifest.__metadata) { manifest.uuid = manifest.__metadata.id; } @@ -108,6 +108,9 @@ class ExtensionManifestNLSReplacer extends ExtensionManifestHandler { this._log.error(this._absoluteFolderPath, nls.localize('jsonsParseReportErrors', "Failed to parse {0}: {1}.", localized, getParseErrorMessage(error.error))); }); }; + const reportInvalidFormat = (localized: string | null): void => { + this._log.error(this._absoluteFolderPath, nls.localize('jsonInvalidFormat', "Invalid format {0}: JSON object expected.", localized)); + }; let extension = path.extname(this._absoluteManifestPath); let basename = this._absoluteManifestPath.substr(0, this._absoluteManifestPath.length - extension.length); @@ -122,6 +125,9 @@ class ExtensionManifestNLSReplacer extends ExtensionManifestHandler { if (errors.length > 0) { reportErrors(translationPath, errors); return { values: undefined, default: `${basename}.nls.json` }; + } else if (json.getNodeType(translationBundle) !== 'object') { + reportInvalidFormat(translationPath); + return { values: undefined, default: `${basename}.nls.json` }; } else { let values = translationBundle.contents ? translationBundle.contents.package : undefined; return { values: values, default: `${basename}.nls.json` }; @@ -144,6 +150,9 @@ class ExtensionManifestNLSReplacer extends ExtensionManifestHandler { if (errors.length > 0) { reportErrors(messageBundle.localized, errors); return { values: undefined, default: messageBundle.original }; + } else if (json.getNodeType(messages) !== 'object') { + reportInvalidFormat(messageBundle.localized); + return { values: undefined, default: messageBundle.original }; } return { values: messages, default: messageBundle.original }; }, (err) => { @@ -165,6 +174,9 @@ class ExtensionManifestNLSReplacer extends ExtensionManifestHandler { if (errors.length > 0) { reportErrors(localizedMessages.default, errors); return extensionDescription; + } else if (json.getNodeType(localizedMessages) !== 'object') { + reportInvalidFormat(localizedMessages.default); + return extensionDescription; } const localized = localizedMessages.values || Object.create(null); ExtensionManifestNLSReplacer._replaceNLStrings(this._nlsConfig, extensionDescription, localized, defaults, this._log, this._absoluteFolderPath); @@ -397,7 +409,15 @@ class ExtensionManifestValidator extends ExtensionManifestHandler { } private static _isStringArray(arr: string[]): boolean { - return Array.isArray(arr) && arr.every(value => typeof value === 'string'); + if (!Array.isArray(arr)) { + return false; + } + for (let i = 0, len = arr.length; i < len; i++) { + if (typeof arr[i] !== 'string') { + return false; + } + } + return true; } } diff --git a/src/vs/workbench/services/extensions/node/proxyResolver.ts b/src/vs/workbench/services/extensions/node/proxyResolver.ts index 81812dfc2948..83d23617687d 100644 --- a/src/vs/workbench/services/extensions/node/proxyResolver.ts +++ b/src/vs/workbench/services/extensions/node/proxyResolver.ts @@ -343,9 +343,13 @@ function patches(originals: typeof http | typeof https, resolveProxy: ReturnType return original.apply(null, arguments as unknown as any[]); } - const optionsPatched = options.agent instanceof ProxyAgent; + const originalAgent = options.agent; + if (originalAgent === true) { + throw new Error('Unexpected agent option: true'); + } + const optionsPatched = originalAgent instanceof ProxyAgent; const config = onRequest && ((options)._vscodeProxySupport || /* LS */ (options)._vscodeSystemProxy) || proxySetting.config; - const useProxySettings = !optionsPatched && (config === 'override' || config === 'on' && !options.agent); + const useProxySettings = !optionsPatched && (config === 'override' || config === 'on' && originalAgent === undefined); const useSystemCertificates = !optionsPatched && certSetting.config && originals === https && !(options as https.RequestOptions).ca; if (useProxySettings || useSystemCertificates) { @@ -367,7 +371,7 @@ function patches(originals: typeof http | typeof https, resolveProxy: ReturnType options.agent = new ProxyAgent({ resolveProxy: resolveProxy.bind(undefined, { useProxySettings, useSystemCertificates }), defaultPort: originals === https ? 443 : 80, - originalAgent: options.agent + originalAgent }); return original(options, callback); } @@ -469,7 +473,9 @@ async function readCaCertificates() { } async function readWindowsCaCertificates() { - const winCA = await import('vscode-windows-ca-certs'); + const winCA = await new Promise((resolve, reject) => { + require(['vscode-windows-ca-certs'], resolve, reject); + }); let ders: any[] = []; const store = winCA(); diff --git a/src/vs/workbench/services/extensions/test/node/rpcProtocol.test.ts b/src/vs/workbench/services/extensions/test/node/rpcProtocol.test.ts index d11c7ccb6e0a..94b7a355e498 100644 --- a/src/vs/workbench/services/extensions/test/node/rpcProtocol.test.ts +++ b/src/vs/workbench/services/extensions/test/node/rpcProtocol.test.ts @@ -200,4 +200,15 @@ suite('RPCProtocol', () => { done(null); }); }); + + test('undefined arguments arrive as null', function () { + delegate = (a1: any, a2: any) => { + assert.equal(typeof a1, 'undefined'); + assert.equal(a2, null); + return 7; + }; + return bProxy.$m(undefined, null).then((res) => { + assert.equal(res, 7); + }); + }); }); diff --git a/src/vs/workbench/services/extensions/worker/extHost.services.ts b/src/vs/workbench/services/extensions/worker/extHost.services.ts index aa0f90c7622f..2a33b13962ed 100644 --- a/src/vs/workbench/services/extensions/worker/extHost.services.ts +++ b/src/vs/workbench/services/extensions/worker/extHost.services.ts @@ -12,8 +12,8 @@ import { IExtHostCommands, ExtHostCommands } from 'vs/workbench/api/common/extHo import { IExtHostDocumentsAndEditors, ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import { IExtHostTerminalService, WorkerExtHostTerminalService } from 'vs/workbench/api/common/extHostTerminalService'; // import { IExtHostTask, WorkerExtHostTask } from 'vs/workbench/api/common/extHostTask'; -// import { IExtHostDebugService } from 'vs/workbench/api/common/extHostDebugService'; -import { IExtHostSearch } from 'vs/workbench/api/common/extHostSearch'; +// import { IExtHostDebugService, WorkerExtHostDebugService } from 'vs/workbench/api/common/extHostDebugService'; +import { IExtHostSearch, ExtHostSearch } from 'vs/workbench/api/common/extHostSearch'; import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths'; import { IExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService'; import { IExtHostStorage, ExtHostStorage } from 'vs/workbench/api/common/extHostStorage'; @@ -32,6 +32,7 @@ registerSingleton(IExtHostCommands, ExtHostCommands); registerSingleton(IExtHostDocumentsAndEditors, ExtHostDocumentsAndEditors); registerSingleton(IExtHostStorage, ExtHostStorage); registerSingleton(IExtHostExtensionService, ExtHostExtensionService); +registerSingleton(IExtHostSearch, ExtHostSearch); // register services that only throw errors function NotImplementedProxy(name: ServiceIdentifier): { new(): T } { @@ -49,9 +50,8 @@ function NotImplementedProxy(name: ServiceIdentifier): { new(): T } { }; } registerSingleton(IExtHostTerminalService, WorkerExtHostTerminalService); -// registerSingleton(IExtHostTask, WorkerExtHostTask); {{SQL CARBON EDIT}} disable tasks -// registerSingleton(IExtHostDebugService, class extends NotImplementedProxy(IExtHostDebugService) { }); {{SQL CARBON EDIT}} remove debug service -registerSingleton(IExtHostSearch, class extends NotImplementedProxy(IExtHostSearch) { }); +// registerSingleton(IExtHostTask, WorkerExtHostTask); {{SQL CARBON EDIT}} disable +// registerSingleton(IExtHostDebugService, WorkerExtHostDebugService); {{SQL CARBON EDIT}} disable registerSingleton(IExtensionStoragePaths, class extends NotImplementedProxy(IExtensionStoragePaths) { whenReady = Promise.resolve(); }); diff --git a/src/vs/workbench/services/filesConfiguration/common/filesConfigurationService.ts b/src/vs/workbench/services/filesConfiguration/common/filesConfigurationService.ts new file mode 100644 index 000000000000..35ff71ab0393 --- /dev/null +++ b/src/vs/workbench/services/filesConfiguration/common/filesConfigurationService.ts @@ -0,0 +1,208 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { Event, Emitter } from 'vs/base/common/event'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { RawContextKey, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; +import { IFilesConfiguration, AutoSaveConfiguration, HotExitConfiguration } from 'vs/platform/files/common/files'; +import { isUndefinedOrNull } from 'vs/base/common/types'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { equals } from 'vs/base/common/objects'; + +export const AutoSaveAfterShortDelayContext = new RawContextKey('autoSaveAfterShortDelayContext', false); + +export interface IAutoSaveConfiguration { + autoSaveDelay?: number; + autoSaveFocusChange: boolean; + autoSaveApplicationChange: boolean; +} + +export const enum AutoSaveMode { + OFF, + AFTER_SHORT_DELAY, + AFTER_LONG_DELAY, + ON_FOCUS_CHANGE, + ON_WINDOW_CHANGE +} + +export const IFilesConfigurationService = createDecorator('filesConfigurationService'); + +export interface IFilesConfigurationService { + + _serviceBrand: undefined; + + //#region Auto Save + + readonly onAutoSaveConfigurationChange: Event; + + getAutoSaveMode(): AutoSaveMode; + + getAutoSaveConfiguration(): IAutoSaveConfiguration; + + toggleAutoSave(): Promise; + + //#endregion + + readonly onFilesAssociationChange: Event; + + readonly isHotExitEnabled: boolean; + + readonly hotExitConfiguration: string | undefined; +} + +export class FilesConfigurationService extends Disposable implements IFilesConfigurationService { + + _serviceBrand: undefined; + + private readonly _onAutoSaveConfigurationChange = this._register(new Emitter()); + readonly onAutoSaveConfigurationChange = this._onAutoSaveConfigurationChange.event; + + private readonly _onFilesAssociationChange = this._register(new Emitter()); + readonly onFilesAssociationChange = this._onFilesAssociationChange.event; + + private configuredAutoSaveDelay?: number; + private configuredAutoSaveOnFocusChange: boolean | undefined; + private configuredAutoSaveOnWindowChange: boolean | undefined; + + private autoSaveAfterShortDelayContext: IContextKey; + + private currentFilesAssociationConfig: { [key: string]: string; }; + + private currentHotExitConfig: string; + + constructor( + @IContextKeyService contextKeyService: IContextKeyService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService + ) { + super(); + + this.autoSaveAfterShortDelayContext = AutoSaveAfterShortDelayContext.bindTo(contextKeyService); + + const configuration = configurationService.getValue(); + + this.currentFilesAssociationConfig = configuration?.files?.associations; + this.currentHotExitConfig = configuration?.files?.hotExit || HotExitConfiguration.ON_EXIT; + + this.onFilesConfigurationChange(configuration); + + this.registerListeners(); + } + + private registerListeners(): void { + + // Files configuration changes + this._register(this.configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('files')) { + this.onFilesConfigurationChange(this.configurationService.getValue()); + } + })); + } + + protected onFilesConfigurationChange(configuration: IFilesConfiguration): void { + + // Auto Save + const autoSaveMode = configuration?.files?.autoSave || AutoSaveConfiguration.OFF; + switch (autoSaveMode) { + case AutoSaveConfiguration.AFTER_DELAY: + this.configuredAutoSaveDelay = configuration?.files?.autoSaveDelay; + this.configuredAutoSaveOnFocusChange = false; + this.configuredAutoSaveOnWindowChange = false; + break; + + case AutoSaveConfiguration.ON_FOCUS_CHANGE: + this.configuredAutoSaveDelay = undefined; + this.configuredAutoSaveOnFocusChange = true; + this.configuredAutoSaveOnWindowChange = false; + break; + + case AutoSaveConfiguration.ON_WINDOW_CHANGE: + this.configuredAutoSaveDelay = undefined; + this.configuredAutoSaveOnFocusChange = false; + this.configuredAutoSaveOnWindowChange = true; + break; + + default: + this.configuredAutoSaveDelay = undefined; + this.configuredAutoSaveOnFocusChange = false; + this.configuredAutoSaveOnWindowChange = false; + break; + } + + this.autoSaveAfterShortDelayContext.set(this.getAutoSaveMode() === AutoSaveMode.AFTER_SHORT_DELAY); + + // Emit as event + this._onAutoSaveConfigurationChange.fire(this.getAutoSaveConfiguration()); + + // Check for change in files associations + const filesAssociation = configuration?.files?.associations; + if (!equals(this.currentFilesAssociationConfig, filesAssociation)) { + this.currentFilesAssociationConfig = filesAssociation; + this._onFilesAssociationChange.fire(); + } + + // Hot exit + const hotExitMode = configuration?.files?.hotExit; + if (hotExitMode === HotExitConfiguration.OFF || hotExitMode === HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) { + this.currentHotExitConfig = hotExitMode; + } else { + this.currentHotExitConfig = HotExitConfiguration.ON_EXIT; + } + } + + getAutoSaveMode(): AutoSaveMode { + if (this.configuredAutoSaveOnFocusChange) { + return AutoSaveMode.ON_FOCUS_CHANGE; + } + + if (this.configuredAutoSaveOnWindowChange) { + return AutoSaveMode.ON_WINDOW_CHANGE; + } + + if (this.configuredAutoSaveDelay && this.configuredAutoSaveDelay > 0) { + return this.configuredAutoSaveDelay <= 1000 ? AutoSaveMode.AFTER_SHORT_DELAY : AutoSaveMode.AFTER_LONG_DELAY; + } + + return AutoSaveMode.OFF; + } + + getAutoSaveConfiguration(): IAutoSaveConfiguration { + return { + autoSaveDelay: this.configuredAutoSaveDelay && this.configuredAutoSaveDelay > 0 ? this.configuredAutoSaveDelay : undefined, + autoSaveFocusChange: !!this.configuredAutoSaveOnFocusChange, + autoSaveApplicationChange: !!this.configuredAutoSaveOnWindowChange + }; + } + + async toggleAutoSave(): Promise { + const setting = this.configurationService.inspect('files.autoSave'); + let userAutoSaveConfig = setting.user; + if (isUndefinedOrNull(userAutoSaveConfig)) { + userAutoSaveConfig = setting.default; // use default if setting not defined + } + + let newAutoSaveValue: string; + if ([AutoSaveConfiguration.AFTER_DELAY, AutoSaveConfiguration.ON_FOCUS_CHANGE, AutoSaveConfiguration.ON_WINDOW_CHANGE].some(s => s === userAutoSaveConfig)) { + newAutoSaveValue = AutoSaveConfiguration.OFF; + } else { + newAutoSaveValue = AutoSaveConfiguration.AFTER_DELAY; + } + + return this.configurationService.updateValue('files.autoSave', newAutoSaveValue, ConfigurationTarget.USER); + } + + get isHotExitEnabled(): boolean { + return !this.environmentService.isExtensionDevelopment && this.currentHotExitConfig !== HotExitConfiguration.OFF; + } + + get hotExitConfiguration(): string { + return this.currentHotExitConfig; + } +} + +registerSingleton(IFilesConfigurationService, FilesConfigurationService); diff --git a/src/vs/workbench/services/history/browser/history.ts b/src/vs/workbench/services/history/browser/history.ts index 046f380d7e2f..85981bdaebb4 100644 --- a/src/vs/workbench/services/history/browser/history.ts +++ b/src/vs/workbench/services/history/browser/history.ts @@ -33,6 +33,7 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { withNullAsUndefined } from 'vs/base/common/types'; import { addDisposableListener, EventType, EventHelper } from 'vs/base/browser/dom'; import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; +import { Schemas } from 'vs/base/common/network'; /** * Stores the selection & view state of an editor and allows to compare it to other selection states. @@ -482,10 +483,10 @@ export class HistoryService extends Disposable implements IHistoryService { } private handleEditorEventInHistory(editor?: IBaseEditor): void { - const input = editor?.input; - // Ensure we have at least a name to show and not configured to exclude input - if (!input || !input.getName() || !this.include(input)) { + // Ensure we have not configured to exclude input and don't track invalid inputs + const input = editor?.input; + if (!input || input.isDisposed() || !this.include(input)) { return; } @@ -592,10 +593,10 @@ export class HistoryService extends Disposable implements IHistoryService { // stack but we need to keep our currentTextEditorState up to date with // the navigtion that occurs. if (this.navigatingInStack) { - if (codeEditor && control?.input) { + if (codeEditor && control?.input && !control.input.isDisposed()) { this.currentTextEditorState = new TextEditorState(control.input, codeEditor.getSelection()); } else { - this.currentTextEditorState = null; // we navigated to a non text editor + this.currentTextEditorState = null; // we navigated to a non text or disposed editor } } @@ -603,15 +604,15 @@ export class HistoryService extends Disposable implements IHistoryService { else { // navigation inside text editor - if (codeEditor && control?.input) { + if (codeEditor && control?.input && !control.input.isDisposed()) { this.handleTextEditorEvent(control, codeEditor, event); } - // navigation to non-text editor + // navigation to non-text disposed editor else { this.currentTextEditorState = null; // at this time we have no active text editor view state - if (control?.input) { + if (control?.input && !control.input.isDisposed()) { this.handleNonTextEditorEvent(control); } } @@ -735,8 +736,10 @@ export class HistoryService extends Disposable implements IHistoryService { private preferResourceInput(input: IEditorInput): IEditorInput | IResourceInput { const resource = input.getResource(); - if (resource && this.fileService.canHandleResource(resource)) { - return { resource: resource }; + if (resource && (resource.scheme === Schemas.file || resource.scheme === Schemas.vscodeRemote || resource.scheme === Schemas.userData)) { + // for now, only prefer well known schemes that we control to prevent + // issues such as https://github.com/microsoft/vscode/issues/85204 + return { resource }; } return input; diff --git a/src/vs/workbench/services/host/browser/browserHostService.ts b/src/vs/workbench/services/host/browser/browserHostService.ts index 8ad22183f270..ecdb00df0cdd 100644 --- a/src/vs/workbench/services/host/browser/browserHostService.ts +++ b/src/vs/workbench/services/host/browser/browserHostService.ts @@ -146,7 +146,7 @@ export class BrowserHostService extends Disposable implements IHostService { const windowConfig = this.configurationService.getValue('window'); const openFolderInNewWindowConfig = windowConfig?.openFoldersInNewWindow || 'default' /* default */; - let openFolderInNewWindow = !!options.forceNewWindow && !options.forceReuseWindow; + let openFolderInNewWindow = (options.preferNewWindow || !!options.forceNewWindow) && !options.forceReuseWindow; if (!options.forceNewWindow && !options.forceReuseWindow && (openFolderInNewWindowConfig === 'on' || openFolderInNewWindowConfig === 'off')) { openFolderInNewWindow = (openFolderInNewWindowConfig === 'on'); } diff --git a/src/vs/workbench/services/keybinding/browser/keybindingService.ts b/src/vs/workbench/services/keybinding/browser/keybindingService.ts index 8b1599740b24..741f113bc1c6 100644 --- a/src/vs/workbench/services/keybinding/browser/keybindingService.ts +++ b/src/vs/workbench/services/keybinding/browser/keybindingService.ts @@ -11,7 +11,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { Keybinding, ResolvedKeybinding, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { KeybindingParser } from 'vs/base/common/keybindingParser'; -import { OS, OperatingSystem, isWeb } from 'vs/base/common/platform'; +import { OS, OperatingSystem } from 'vs/base/common/platform'; import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { Extensions as ConfigExtensions, IConfigurationNode, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; @@ -19,7 +19,7 @@ import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/commo import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { Extensions, IJSONContributionRegistry } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { AbstractKeybindingService } from 'vs/platform/keybinding/common/abstractKeybindingService'; -import { IKeyboardEvent, IUserFriendlyKeybinding, KeybindingSource, IKeybindingService, IKeybindingEvent } from 'vs/platform/keybinding/common/keybinding'; +import { IKeyboardEvent, IUserFriendlyKeybinding, KeybindingSource, IKeybindingService, IKeybindingEvent, KeybindingsSchemaContribution } from 'vs/platform/keybinding/common/keybinding'; import { KeybindingResolver } from 'vs/platform/keybinding/common/keybindingResolver'; import { IKeybindingItem, IKeybindingRule2, KeybindingWeight, KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; @@ -43,8 +43,10 @@ import * as objects from 'vs/base/common/objects'; import { IKeymapService } from 'vs/workbench/services/keybinding/common/keymapInfo'; import { getDispatchConfig } from 'vs/workbench/services/keybinding/common/dispatchConfig'; import { isArray } from 'vs/base/common/types'; -import { INavigatorWithKeyboard } from 'vs/workbench/services/keybinding/browser/navigatorKeyboard'; +import { INavigatorWithKeyboard, IKeyboard } from 'vs/workbench/services/keybinding/browser/navigatorKeyboard'; import { ScanCodeUtils, IMMUTABLE_CODE_TO_KEY_CODE } from 'vs/base/common/scanCode'; +import { flatten } from 'vs/base/common/arrays'; +import { BrowserFeatures, KeyboardSupport } from 'vs/base/browser/canIUse'; interface ContributedKeyBinding { command: string; @@ -146,6 +148,7 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { private _keyboardMapper: IKeyboardMapper; private _cachedResolver: KeybindingResolver | null; private userKeybindings: UserKeybindings; + private readonly _contributions: KeybindingsSchemaContribution[] = []; constructor( @IContextKeyService contextKeyService: IContextKeyService, @@ -161,7 +164,7 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { ) { super(contextKeyService, commandService, telemetryService, notificationService); - updateSchema(); + this.updateSchema(); let dispatchConfig = getDispatchConfig(configurationService); configurationService.onDidChangeConfiguration((e) => { @@ -190,13 +193,6 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { } }); this._register(this.userKeybindings.onDidChange(() => { - type CustomKeybindingsChangedClassification = { - keyCount: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true } - }; - - this._telemetryService.publicLog2<{ keyCount: number }, CustomKeybindingsChangedClassification>('customKeybindingsChanged', { - keyCount: this.userKeybindings.keybindings.length - }); this.updateResolver({ source: KeybindingSource.User, keybindings: this.userKeybindings.keybindings @@ -214,8 +210,8 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { this.updateResolver({ source: KeybindingSource.Default }); }); - updateSchema(); - this._register(extensionService.onDidRegisterExtensions(() => updateSchema())); + this.updateSchema(); + this._register(extensionService.onDidRegisterExtensions(() => this.updateSchema())); this._register(dom.addDisposableListener(window, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => { let keyEvent = new StandardKeyboardEvent(e); @@ -226,6 +222,28 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { })); let data = this.keymapService.getCurrentKeyboardLayout(); + /* __GDPR__FRAGMENT__ + "IKeyboardLayoutInfo" : { + "name" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "id": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "text": { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + } + */ + /* __GDPR__FRAGMENT__ + "IKeyboardLayoutInfo" : { + "model" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "layout": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "variant": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "options": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "rules": { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + } + */ + /* __GDPR__FRAGMENT__ + "IKeyboardLayoutInfo" : { + "id" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "lang": { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + } + */ /* __GDPR__ "keyboardLayout" : { "currentKeyboardLayout": { "${inline}": [ "${IKeyboardLayoutInfo}" ] } @@ -236,16 +254,16 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { }); this._register(browser.onDidChangeFullscreen(() => { - const keyboard = (navigator).keyboard; + const keyboard: IKeyboard | null = (navigator).keyboard; - if (!keyboard) { + if (BrowserFeatures.keyboard === KeyboardSupport.None) { return; } if (browser.isFullscreen()) { - keyboard.lock(['Escape']); + keyboard?.lock(['Escape']); } else { - keyboard.unlock(); + keyboard?.unlock(); } // update resolver which will bring back all unbound keyboard shortcuts @@ -254,6 +272,18 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { })); } + public registerSchemaContribution(contribution: KeybindingsSchemaContribution): void { + this._contributions.push(contribution); + if (contribution.onDidChange) { + this._register(contribution.onDidChange(() => this.updateSchema())); + } + this.updateSchema(); + } + + private updateSchema() { + updateSchema(flatten(this._contributions.map(x => x.getSchemaAdditions()))); + } + public _dumpDebugInfo(): string { const layoutInfo = JSON.stringify(this.keymapService.getCurrentKeyboardLayout(), null, '\t'); const mapperInfo = this._keyboardMapper.dumpDebugInfo(); @@ -337,15 +367,11 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { } private _assertBrowserConflicts(kb: Keybinding, commandId: string): boolean { - if (!isWeb) { + if (BrowserFeatures.keyboard === KeyboardSupport.Always) { return false; } - if (browser.isStandalone) { - return false; - } - - if (browser.isFullscreen() && (navigator).keyboard) { + if (BrowserFeatures.keyboard === KeyboardSupport.FullScreen && browser.isFullscreen()) { return false; } @@ -503,7 +529,7 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { ); } - private static _getDefaultKeybindings(defaultKeybindings: ResolvedKeybindingItem[]): string { + private static _getDefaultKeybindings(defaultKeybindings: readonly ResolvedKeybindingItem[]): string { let out = new OutputBuilder(); out.writeLine('['); @@ -653,7 +679,7 @@ let schema: IJSONSchema = { let schemaRegistry = Registry.as(Extensions.JSONContribution); schemaRegistry.registerSchema(schemaId, schema); -function updateSchema() { +function updateSchema(additionalContributions: readonly IJSONSchema[]) { commandsSchemas.length = 0; commandsEnum.length = 0; commandsEnumDescriptions.length = 0; @@ -707,6 +733,9 @@ function updateSchema() { for (const commandId of menuCommands.keys()) { addKnownCommand(commandId); } + + commandsSchemas.push(...additionalContributions); + schemaRegistry.notifySchemaChanged(schemaId); } const configurationRegistry = Registry.as(ConfigExtensions.Configuration); diff --git a/src/vs/workbench/services/keybinding/browser/keymapService.ts b/src/vs/workbench/services/keybinding/browser/keymapService.ts index 2445acde0b02..fdc98efcc139 100644 --- a/src/vs/workbench/services/keybinding/browser/keymapService.ts +++ b/src/vs/workbench/services/keybinding/browser/keymapService.ts @@ -19,7 +19,7 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { URI } from 'vs/base/common/uri'; import { IFileService } from 'vs/platform/files/common/files'; import { RunOnceScheduler } from 'vs/base/common/async'; -import { parse } from 'vs/base/common/json'; +import { parse, getNodeType } from 'vs/base/common/json'; import * as objects from 'vs/base/common/objects'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -335,6 +335,10 @@ export class BrowserKeyboardMapperFactoryBase { return true; } + if (standardKeyboardEvent.browserEvent.key === 'Dead' || standardKeyboardEvent.browserEvent.isComposing) { + return true; + } + const mapping = currentKeymap.mapping[standardKeyboardEvent.code]; if (!mapping) { @@ -482,9 +486,13 @@ class UserKeyboardLayout extends Disposable { try { const content = await this.fileService.readFile(this.keyboardLayoutResource); const value = parse(content.value.toString()); - const layoutInfo = value.layout; - const mappings = value.rawMapping; - this._keyboardLayout = KeymapInfo.createKeyboardLayoutFromDebugInfo(layoutInfo, mappings, true); + if (getNodeType(value) === 'object') { + const layoutInfo = value.layout; + const mappings = value.rawMapping; + this._keyboardLayout = KeymapInfo.createKeyboardLayoutFromDebugInfo(layoutInfo, mappings, true); + } else { + this._keyboardLayout = null; + } } catch (e) { this._keyboardLayout = null; } diff --git a/src/vs/workbench/services/keybinding/common/keybindingEditing.ts b/src/vs/workbench/services/keybinding/common/keybindingEditing.ts index c91b2fd1704a..a690f63a17fc 100644 --- a/src/vs/workbench/services/keybinding/common/keybindingEditing.ts +++ b/src/vs/workbench/services/keybinding/common/keybindingEditing.ts @@ -250,7 +250,7 @@ export class KeybindingsEditingService extends Disposable implements IKeybinding private parse(model: ITextModel): { result: IUserFriendlyKeybinding[], parseErrors: json.ParseError[] } { const parseErrors: json.ParseError[] = []; - const result = json.parse(model.getValue(), parseErrors); + const result = json.parse(model.getValue(), parseErrors, { allowTrailingComma: true, allowEmptyContent: true }); return { result, parseErrors }; } diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts b/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts index 8288261017b1..5368a2a88fcf 100644 --- a/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts +++ b/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts @@ -38,19 +38,20 @@ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editor import { KeybindingsEditingService } from 'vs/workbench/services/keybinding/common/keybindingEditing'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { TextModelResolverService } from 'vs/workbench/services/textmodelResolver/common/textModelResolverService'; -import { IUntitledEditorService, UntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; -import { TestBackupFileService, TestContextService, TestEditorGroupsService, TestEditorService, TestLifecycleService, TestTextFileService, TestTextResourcePropertiesService } from 'vs/workbench/test/workbenchTestServices'; +import { IUntitledTextEditorService, UntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; +import { TestBackupFileService, TestContextService, TestEditorGroupsService, TestEditorService, TestLifecycleService, TestTextFileService, TestTextResourcePropertiesService, TestWorkingCopyService } from 'vs/workbench/test/workbenchTestServices'; import { FileService } from 'vs/platform/files/common/fileService'; import { Schemas } from 'vs/base/common/network'; import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider'; import { URI } from 'vs/base/common/uri'; import { FileUserDataProvider } from 'vs/workbench/services/userData/common/fileUserDataProvider'; import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv'; -import { WorkbenchEnvironmentService } from 'vs/workbench/services/environment/node/environmentService'; +import { NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; +import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; -class TestEnvironmentService extends WorkbenchEnvironmentService { +class TestEnvironmentService extends NativeWorkbenchEnvironmentService { constructor(private _appSettingsHome: URI) { super(parseArgs(process.argv, OPTIONS) as IWindowConfiguration, process.execPath, 0); @@ -67,7 +68,7 @@ interface Modifiers { shiftKey?: boolean; } -suite('KeybindingsEditing', () => { +suite.skip('KeybindingsEditing', () => { let instantiationService: TestInstantiationService; let testObject: KeybindingsEditingService; @@ -93,6 +94,7 @@ suite('KeybindingsEditing', () => { instantiationService.stub(IContextKeyService, instantiationService.createInstance(MockContextKeyService)); instantiationService.stub(IEditorGroupsService, new TestEditorGroupsService()); instantiationService.stub(IEditorService, new TestEditorService()); + instantiationService.stub(IWorkingCopyService, new TestWorkingCopyService()); instantiationService.stub(ITelemetryService, NullTelemetryService); instantiationService.stub(IModeService, ModeServiceImpl); instantiationService.stub(ILogService, new NullLogService()); @@ -103,7 +105,7 @@ suite('KeybindingsEditing', () => { fileService.registerProvider(Schemas.file, diskFileSystemProvider); fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, environmentService.backupHome, diskFileSystemProvider, environmentService)); instantiationService.stub(IFileService, fileService); - instantiationService.stub(IUntitledEditorService, instantiationService.createInstance(UntitledEditorService)); + instantiationService.stub(IUntitledTextEditorService, instantiationService.createInstance(UntitledTextEditorService)); instantiationService.stub(ITextFileService, instantiationService.createInstance(TestTextFileService)); instantiationService.stub(ITextModelService, instantiationService.createInstance(TextModelResolverService)); instantiationService.stub(IBackupFileService, new TestBackupFileService()); diff --git a/src/vs/workbench/services/label/common/labelService.ts b/src/vs/workbench/services/label/common/labelService.ts index 1d6cb967f0b4..0d1d0bc7763a 100644 --- a/src/vs/workbench/services/label/common/labelService.ts +++ b/src/vs/workbench/services/label/common/labelService.ts @@ -12,10 +12,10 @@ import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, IWo import { Registry } from 'vs/platform/registry/common/platform'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IWorkspaceContextService, IWorkspace } from 'vs/platform/workspace/common/workspace'; -import { isEqual, basenameOrAuthority, isEqualOrParent, basename, joinPath, dirname } from 'vs/base/common/resources'; +import { isEqual, basenameOrAuthority, basename, joinPath, dirname } from 'vs/base/common/resources'; import { tildify, getPathLabel } from 'vs/base/common/labels'; import { ltrim, endsWith } from 'vs/base/common/strings'; -import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, WORKSPACE_EXTENSION, toWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; +import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, WORKSPACE_EXTENSION, toWorkspaceIdentifier, isWorkspaceIdentifier, isUntitledWorkspace } from 'vs/platform/workspaces/common/workspaces'; import { ILabelService, ResourceLabelFormatter, ResourceLabelFormatting } from 'vs/platform/label/common/label'; import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { match } from 'vs/base/common/glob'; @@ -117,7 +117,7 @@ export class LabelService implements ILabelService { return; } - if (match(formatter.authority, resource.authority) && (!bestResult || !bestResult.authority || formatter.authority.length > bestResult.authority.length || ((formatter.authority.length === bestResult.authority.length) && formatter.priority))) { + if (match(formatter.authority.toLowerCase(), resource.authority.toLowerCase()) && (!bestResult || !bestResult.authority || formatter.authority.length > bestResult.authority.length || ((formatter.authority.length === bestResult.authority.length) && formatter.priority))) { bestResult = formatter; } } @@ -193,7 +193,7 @@ export class LabelService implements ILabelService { if (isWorkspaceIdentifier(workspace)) { // Workspace: Untitled - if (isEqualOrParent(workspace.configPath, this.environmentService.untitledWorkspacesHome)) { + if (isUntitledWorkspace(workspace.configPath, this.environmentService)) { return localize('untitledWorkspace', "Untitled (Workspace)"); } diff --git a/src/vs/workbench/services/layout/browser/layoutService.ts b/src/vs/workbench/services/layout/browser/layoutService.ts index 0f192a8d3978..39d37c14ef35 100644 --- a/src/vs/workbench/services/layout/browser/layoutService.ts +++ b/src/vs/workbench/services/layout/browser/layoutService.ts @@ -41,6 +41,11 @@ export interface IWorkbenchLayoutService extends ILayoutService { */ readonly onFullscreenChange: Event; + /** + * Emits when the window is maximized or unmaximized. + */ + readonly onMaximizeChange: Event; + /** * Emits when centered layout is enabled or disabled. */ @@ -114,6 +119,16 @@ export interface IWorkbenchLayoutService extends ILayoutService { */ toggleMaximizedPanel(): void; + /** + * Returns true if the window has a border. + */ + hasWindowBorder(): boolean; + + /** + * Returns the window border radius if any. + */ + getWindowBorderRadius(): string | undefined; + /** * Returns true if the panel is maximized. */ @@ -178,4 +193,15 @@ export interface IWorkbenchLayoutService extends ILayoutService { * Register a part to participate in the layout. */ registerPart(part: Part): void; + + + /** + * Returns whether the window is maximized. + */ + isWindowMaximized(): boolean; + + /** + * Updates the maximized state of the window. + */ + updateWindowMaximizedState(maximized: boolean): void; } diff --git a/src/vs/workbench/services/mode/common/workbenchModeService.ts b/src/vs/workbench/services/mode/common/workbenchModeService.ts index ade3a29230a7..f91f58da636b 100644 --- a/src/vs/workbench/services/mode/common/workbenchModeService.ts +++ b/src/vs/workbench/services/mode/common/workbenchModeService.ts @@ -105,7 +105,7 @@ export class WorkbenchModeServiceImpl extends ModeServiceImpl { this._configurationService = configurationService; this._extensionService = extensionService; - languagesExtPoint.setHandler((extensions: IExtensionPointUser[]) => { + languagesExtPoint.setHandler((extensions: readonly IExtensionPointUser[]) => { let allValidLanguages: ILanguageExtensionPoint[] = []; for (let i = 0, len = extensions.length; i < len; i++) { diff --git a/src/vs/workbench/services/preferences/common/preferencesEditorInput.ts b/src/vs/workbench/services/preferences/common/preferencesEditorInput.ts index fa2ec956f3ed..749ecc4d8d56 100644 --- a/src/vs/workbench/services/preferences/common/preferencesEditorInput.ts +++ b/src/vs/workbench/services/preferences/common/preferencesEditorInput.ts @@ -21,7 +21,7 @@ export class PreferencesEditorInput extends SideBySideEditorInput { return PreferencesEditorInput.ID; } - getTitle(verbosity: Verbosity): string | undefined { + getTitle(verbosity: Verbosity): string { return this.master.getTitle(verbosity); } } diff --git a/src/vs/workbench/services/preferences/common/preferencesModels.ts b/src/vs/workbench/services/preferences/common/preferencesModels.ts index 61a6e50628e9..0f53ec4bb984 100644 --- a/src/vs/workbench/services/preferences/common/preferencesModels.ts +++ b/src/vs/workbench/services/preferences/common/preferencesModels.ts @@ -1117,7 +1117,7 @@ export function createValidator(prop: IConfigurationPropertySchema): (value: any patternRegex = new RegExp(prop.pattern); } - const type = Array.isArray(prop.type) ? prop.type : [prop.type]; + const type: (string | undefined)[] = Array.isArray(prop.type) ? prop.type : [prop.type]; const canBeType = (t: string) => type.indexOf(t) > -1; const isNullable = canBeType('null'); diff --git a/src/vs/workbench/services/preferences/test/common/keybindingsEditorModel.test.ts b/src/vs/workbench/services/preferences/test/common/keybindingsEditorModel.test.ts index b7fc5de557b5..8031c77c24a0 100644 --- a/src/vs/workbench/services/preferences/test/common/keybindingsEditorModel.test.ts +++ b/src/vs/workbench/services/preferences/test/common/keybindingsEditorModel.test.ts @@ -577,7 +577,7 @@ suite('KeybindingsEditorModel test', () => { function registerCommandWithTitle(command: string, title: string): void { const registry = Registry.as(ActionExtensions.WorkbenchActions); - registry.registerWorkbenchAction(new SyncActionDescriptor(AnAction, command, title, { primary: 0 }), ''); + registry.registerWorkbenchAction(SyncActionDescriptor.create(AnAction, command, title, { primary: 0 }), ''); } function assertKeybindingItems(actual: ResolvedKeybindingItem[], expected: ResolvedKeybindingItem[]) { diff --git a/src/vs/workbench/services/progress/browser/editorProgressService.ts b/src/vs/workbench/services/progress/browser/editorProgressService.ts deleted file mode 100644 index 60392f50e44b..000000000000 --- a/src/vs/workbench/services/progress/browser/editorProgressService.ts +++ /dev/null @@ -1,11 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { ProgressBarIndicator } from 'vs/workbench/services/progress/browser/progressIndicator'; - -export class EditorProgressService extends ProgressBarIndicator { - - _serviceBrand: undefined; -} diff --git a/src/vs/workbench/services/progress/browser/media/progressService.css b/src/vs/workbench/services/progress/browser/media/progressService.css index cc899d47dd9c..d86980fa1e4c 100644 --- a/src/vs/workbench/services/progress/browser/media/progressService.css +++ b/src/vs/workbench/services/progress/browser/media/progressService.css @@ -12,6 +12,7 @@ } .monaco-workbench .progress-badge > .badge-content::before { + mask: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNCIgaGVpZ2h0PSIxNCIgdmlld0JveD0iMiAyIDE0IDE0IiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDIgMiAxNCAxNCI+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTkgMTZjLTMuODYgMC03LTMuMTQtNy03czMuMTQtNyA3LTdjMy44NTkgMCA3IDMuMTQxIDcgN3MtMy4xNDEgNy03IDd6bTAtMTIuNmMtMy4wODggMC01LjYgMi41MTMtNS42IDUuNnMyLjUxMiA1LjYgNS42IDUuNiA1LjYtMi41MTIgNS42LTUuNi0yLjUxMi01LjYtNS42LTUuNnptMy44NiA3LjFsLTMuMTYtMS44OTZ2LTMuODA0aC0xLjR2NC41OTZsMy44NCAyLjMwNS43Mi0xLjIwMXoiLz48L3N2Zz4="); -webkit-mask: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNCIgaGVpZ2h0PSIxNCIgdmlld0JveD0iMiAyIDE0IDE0IiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDIgMiAxNCAxNCI+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTkgMTZjLTMuODYgMC03LTMuMTQtNy03czMuMTQtNyA3LTdjMy44NTkgMCA3IDMuMTQxIDcgN3MtMy4xNDEgNy03IDd6bTAtMTIuNmMtMy4wODggMC01LjYgMi41MTMtNS42IDUuNnMyLjUxMiA1LjYgNS42IDUuNiA1LjYtMi41MTIgNS42LTUuNi0yLjUxMi01LjYtNS42LTUuNnptMy44NiA3LjFsLTMuMTYtMS44OTZ2LTMuODA0aC0xLjR2NC41OTZsMy44NCAyLjMwNS43Mi0xLjIwMXoiLz48L3N2Zz4="); width: 14px; height: 14px; diff --git a/src/vs/workbench/services/progress/browser/progressIndicator.ts b/src/vs/workbench/services/progress/browser/progressIndicator.ts index 6f2f66f51f24..67820473449c 100644 --- a/src/vs/workbench/services/progress/browser/progressIndicator.ts +++ b/src/vs/workbench/services/progress/browser/progressIndicator.ts @@ -8,11 +8,14 @@ import { isUndefinedOrNull } from 'vs/base/common/types'; import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; -import { IProgressRunner, IProgressIndicator } from 'vs/platform/progress/common/progress'; +import { IProgressRunner, IProgressIndicator, emptyProgressRunner } from 'vs/platform/progress/common/progress'; +import { IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor'; -export class ProgressBarIndicator implements IProgressIndicator { +export class ProgressBarIndicator extends Disposable implements IProgressIndicator { - constructor(private progressbar: ProgressBar) { } + constructor(protected progressbar: ProgressBar) { + super(); + } show(infinite: true, delay?: number): IProgressRunner; show(total: number, delay?: number): IProgressRunner; @@ -55,6 +58,55 @@ export class ProgressBarIndicator implements IProgressIndicator { } } +export class EditorProgressIndicator extends ProgressBarIndicator { + + _serviceBrand: undefined; + + constructor(progressBar: ProgressBar, private readonly group: IEditorGroupView) { + super(progressBar); + + this.registerListeners(); + } + + private registerListeners() { + this._register(this.group.onDidCloseEditor(e => { + if (this.group.isEmpty) { + this.progressbar.stop().hide(); + } + })); + } + + show(infinite: true, delay?: number): IProgressRunner; + show(total: number, delay?: number): IProgressRunner; + show(infiniteOrTotal: true | number, delay?: number): IProgressRunner { + + // No editor open: ignore any progress reporting + if (this.group.isEmpty) { + return emptyProgressRunner; + } + + if (infiniteOrTotal === true) { + return super.show(true, delay); + } + + return super.show(infiniteOrTotal, delay); + } + + async showWhile(promise: Promise, delay?: number): Promise { + + // No editor open: ignore any progress reporting + if (this.group.isEmpty) { + try { + await promise; + } catch (error) { + // ignore + } + } + + return super.showWhile(promise, delay); + } +} + namespace ProgressIndicatorState { export const enum Type { @@ -65,9 +117,9 @@ namespace ProgressIndicatorState { Work } - export const None = new class { readonly type = Type.None; }; - export const Done = new class { readonly type = Type.Done; }; - export const Infinite = new class { readonly type = Type.Infinite; }; + export const None = { type: Type.None } as const; + export const Done = { type: Type.Done } as const; + export const Infinite = { type: Type.Infinite } as const; export class While { readonly type = Type.While; diff --git a/src/vs/workbench/services/progress/browser/progressService.ts b/src/vs/workbench/services/progress/browser/progressService.ts index cc8f82eac47c..26cc44e55234 100644 --- a/src/vs/workbench/services/progress/browser/progressService.ts +++ b/src/vs/workbench/services/progress/browser/progressService.ts @@ -45,7 +45,7 @@ export class ProgressService extends Disposable implements IProgressService { super(); } - withProgress(options: IProgressOptions, task: (progress: IProgress) => Promise, onDidCancel?: (choice?: number) => void): Promise { + async withProgress(options: IProgressOptions, task: (progress: IProgress) => Promise, onDidCancel?: (choice?: number) => void): Promise { const { location } = options; if (typeof location === 'string') { if (this.viewletService.getProgressIndicator(location)) { @@ -56,7 +56,7 @@ export class ProgressService extends Disposable implements IProgressService { return this.withPanelProgress(location, task, { ...options, location }); } - return Promise.reject(new Error(`Bad progress location: ${location}`)); + throw new Error(`Bad progress location: ${location}`); } switch (location) { @@ -73,7 +73,7 @@ export class ProgressService extends Disposable implements IProgressService { case ProgressLocation.Dialog: return this.withDialogProgress(options, task, onDidCancel); default: - return Promise.reject(new Error(`Bad progress location: ${location}`)); + throw new Error(`Bad progress location: ${location}`); } } diff --git a/src/vs/workbench/services/progress/test/progressIndicator.test.ts b/src/vs/workbench/services/progress/test/progressIndicator.test.ts index d7add3b9a1b7..b85279c4f2e0 100644 --- a/src/vs/workbench/services/progress/test/progressIndicator.test.ts +++ b/src/vs/workbench/services/progress/test/progressIndicator.test.ts @@ -11,11 +11,15 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IViewlet } from 'vs/workbench/common/viewlet'; import { TestViewletService, TestPanelService } from 'vs/workbench/test/workbenchTestServices'; +import { Event } from 'vs/base/common/event'; class TestViewlet implements IViewlet { constructor(private id: string) { } + readonly onDidBlur = Event.None; + readonly onDidFocus = Event.None; + getId(): string { return this.id; } getTitle(): string { return this.id; } getActions(): IAction[] { return []; } diff --git a/src/vs/workbench/services/remote/common/remoteExplorerService.ts b/src/vs/workbench/services/remote/common/remoteExplorerService.ts new file mode 100644 index 000000000000..1d1c2ebf37f7 --- /dev/null +++ b/src/vs/workbench/services/remote/common/remoteExplorerService.ts @@ -0,0 +1,236 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import { Event, Emitter } from 'vs/base/common/event'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { ExtensionsRegistry, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; +import { ITunnelService } from 'vs/platform/remote/common/tunnel'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { IEditableData } from 'vs/workbench/common/views'; + +export const IRemoteExplorerService = createDecorator('remoteExplorerService'); +export const REMOTE_EXPLORER_TYPE_KEY: string = 'remote.explorerType'; + +export interface Tunnel { + remote: number; + localAddress: string; + local?: number; + name?: string; + description?: string; + closeable?: boolean; +} + +export class TunnelModel extends Disposable { + readonly forwarded: Map; + readonly published: Map; + readonly candidates: Map; + private _onForwardPort: Emitter = new Emitter(); + public onForwardPort: Event = this._onForwardPort.event; + private _onClosePort: Emitter = new Emitter(); + public onClosePort: Event = this._onClosePort.event; + private _onPortName: Emitter = new Emitter(); + public onPortName: Event = this._onPortName.event; + constructor( + @ITunnelService private readonly tunnelService: ITunnelService + ) { + super(); + this.forwarded = new Map(); + this.tunnelService.tunnels.then(tunnels => { + tunnels.forEach(tunnel => { + if (tunnel.localAddress) { + this.forwarded.set(tunnel.tunnelRemotePort, { + remote: tunnel.tunnelRemotePort, + localAddress: tunnel.localAddress, + local: tunnel.tunnelLocalPort + }); + } + }); + }); + + this.published = new Map(); + this.candidates = new Map(); + this._register(this.tunnelService.onTunnelOpened(tunnel => { + if (this.candidates.has(tunnel.tunnelRemotePort)) { + this.candidates.delete(tunnel.tunnelRemotePort); + } + if (!this.forwarded.has(tunnel.tunnelRemotePort) && tunnel.localAddress) { + this.forwarded.set(tunnel.tunnelRemotePort, { + remote: tunnel.tunnelRemotePort, + localAddress: tunnel.localAddress, + local: tunnel.tunnelLocalPort + }); + } + this._onForwardPort.fire(this.forwarded.get(tunnel.tunnelRemotePort)!); + })); + this._register(this.tunnelService.onTunnelClosed(remotePort => { + if (this.forwarded.has(remotePort)) { + this.forwarded.delete(remotePort); + this._onClosePort.fire(remotePort); + } + })); + } + + async forward(remote: number, local?: number, name?: string): Promise { + if (!this.forwarded.has(remote)) { + const tunnel = await this.tunnelService.openTunnel(remote, local); + if (tunnel && tunnel.localAddress) { + const newForward: Tunnel = { + remote: tunnel.tunnelRemotePort, + local: tunnel.tunnelLocalPort, + name: name, + closeable: true, + localAddress: tunnel.localAddress + }; + this.forwarded.set(remote, newForward); + this._onForwardPort.fire(newForward); + } + } + } + + name(remote: number, name: string) { + if (this.forwarded.has(remote)) { + this.forwarded.get(remote)!.name = name; + this._onPortName.fire(remote); + } + } + + async close(remote: number): Promise { + return this.tunnelService.closeTunnel(remote); + } + + address(remote: number): string | undefined { + return (this.forwarded.get(remote) || this.published.get(remote))?.localAddress; + } +} + +export interface IRemoteExplorerService { + _serviceBrand: undefined; + onDidChangeTargetType: Event; + targetType: string; + readonly helpInformation: HelpInformation[]; + readonly tunnelModel: TunnelModel; + onDidChangeEditable: Event; + setEditable(remote: number | undefined, data: IEditableData | null): void; + getEditableData(remote: number | undefined): IEditableData | undefined; +} + +export interface HelpInformation { + extensionDescription: IExtensionDescription; + getStarted?: string; + documentation?: string; + feedback?: string; + issues?: string; + remoteName?: string[] | string; +} + +const remoteHelpExtPoint = ExtensionsRegistry.registerExtensionPoint({ + extensionPoint: 'remoteHelp', + jsonSchema: { + description: nls.localize('RemoteHelpInformationExtPoint', 'Contributes help information for Remote'), + type: 'object', + properties: { + 'getStarted': { + description: nls.localize('RemoteHelpInformationExtPoint.getStarted', "The url to your project's Getting Started page"), + type: 'string' + }, + 'documentation': { + description: nls.localize('RemoteHelpInformationExtPoint.documentation', "The url to your project's documentation page"), + type: 'string' + }, + 'feedback': { + description: nls.localize('RemoteHelpInformationExtPoint.feedback', "The url to your project's feedback reporter"), + type: 'string' + }, + 'issues': { + description: nls.localize('RemoteHelpInformationExtPoint.issues', "The url to your project's issues list"), + type: 'string' + } + } + } +}); + +class RemoteExplorerService implements IRemoteExplorerService { + public _serviceBrand: undefined; + private _targetType: string = ''; + private readonly _onDidChangeTargetType: Emitter = new Emitter(); + public readonly onDidChangeTargetType: Event = this._onDidChangeTargetType.event; + private _helpInformation: HelpInformation[] = []; + private _tunnelModel: TunnelModel; + private editable: { remote: number | undefined, data: IEditableData } | undefined; + private readonly _onDidChangeEditable: Emitter = new Emitter(); + public readonly onDidChangeEditable: Event = this._onDidChangeEditable.event; + + constructor( + @IStorageService private readonly storageService: IStorageService, + @ITunnelService tunnelService: ITunnelService) { + this._tunnelModel = new TunnelModel(tunnelService); + remoteHelpExtPoint.setHandler((extensions) => { + let helpInformation: HelpInformation[] = []; + for (let extension of extensions) { + this._handleRemoteInfoExtensionPoint(extension, helpInformation); + } + + this._helpInformation = helpInformation; + }); + } + + set targetType(name: string) { + if (this._targetType !== name) { + this._targetType = name; + this.storageService.store(REMOTE_EXPLORER_TYPE_KEY, this._targetType, StorageScope.WORKSPACE); + this.storageService.store(REMOTE_EXPLORER_TYPE_KEY, this._targetType, StorageScope.GLOBAL); + this._onDidChangeTargetType.fire(this._targetType); + } + } + get targetType(): string { + return this._targetType; + } + + private _handleRemoteInfoExtensionPoint(extension: IExtensionPointUser, helpInformation: HelpInformation[]) { + if (!extension.description.enableProposedApi) { + return; + } + + if (!extension.value.documentation && !extension.value.feedback && !extension.value.getStarted && !extension.value.issues) { + return; + } + + helpInformation.push({ + extensionDescription: extension.description, + getStarted: extension.value.getStarted, + documentation: extension.value.documentation, + feedback: extension.value.feedback, + issues: extension.value.issues, + remoteName: extension.value.remoteName + }); + } + + get helpInformation(): HelpInformation[] { + return this._helpInformation; + } + + get tunnelModel(): TunnelModel { + return this._tunnelModel; + } + + setEditable(remote: number | undefined, data: IEditableData | null): void { + if (!data) { + this.editable = undefined; + } else { + this.editable = { remote, data }; + } + this._onDidChangeEditable.fire(remote); + } + + getEditableData(remote: number | undefined): IEditableData | undefined { + return this.editable && this.editable.remote === remote ? this.editable.data : undefined; + } +} + +registerSingleton(IRemoteExplorerService, RemoteExplorerService, true); diff --git a/src/vs/workbench/services/remote/node/tunnelService.ts b/src/vs/workbench/services/remote/node/tunnelService.ts index 6a905d07b486..28895626c52d 100644 --- a/src/vs/workbench/services/remote/node/tunnelService.ts +++ b/src/vs/workbench/services/remote/node/tunnelService.ts @@ -17,9 +17,10 @@ import { ISignService } from 'vs/platform/sign/common/sign'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ILogService } from 'vs/platform/log/common/log'; import { findFreePort } from 'vs/base/node/ports'; +import { Event, Emitter } from 'vs/base/common/event'; -export async function createRemoteTunnel(options: IConnectionOptions, tunnelRemotePort: number): Promise { - const tunnel = new NodeRemoteTunnel(options, tunnelRemotePort); +export async function createRemoteTunnel(options: IConnectionOptions, tunnelRemotePort: number, tunnelLocalPort?: number): Promise { + const tunnel = new NodeRemoteTunnel(options, tunnelRemotePort, tunnelLocalPort); return tunnel.waitForReady(); } @@ -27,6 +28,7 @@ class NodeRemoteTunnel extends Disposable implements RemoteTunnel { public readonly tunnelRemotePort: number; public tunnelLocalPort!: number; + public localAddress?: string; private readonly _options: IConnectionOptions; private readonly _server: net.Server; @@ -35,7 +37,7 @@ class NodeRemoteTunnel extends Disposable implements RemoteTunnel { private readonly _listeningListener: () => void; private readonly _connectionListener: (socket: net.Socket) => void; - constructor(options: IConnectionOptions, tunnelRemotePort: number) { + constructor(options: IConnectionOptions, tunnelRemotePort: number, private readonly suggestedLocalPort?: number) { super(); this._options = options; this._server = net.createServer(); @@ -61,12 +63,14 @@ class NodeRemoteTunnel extends Disposable implements RemoteTunnel { public async waitForReady(): Promise { // try to get the same port number as the remote port number... - const localPort = await findFreePort(this.tunnelRemotePort, 1, 1000); + const localPort = await findFreePort(this.suggestedLocalPort ?? this.tunnelRemotePort, 1, 1000); // if that fails, the method above returns 0, which works out fine below... - this.tunnelLocalPort = (this._server.listen(localPort).address()).port; + const address = (this._server.listen(localPort).address()); + this.tunnelLocalPort = address.port; await this._barrier.wait(); + this.localAddress = 'localhost:' + address.port; return this; } @@ -96,6 +100,10 @@ class NodeRemoteTunnel extends Disposable implements RemoteTunnel { export class TunnelService implements ITunnelService { _serviceBrand: undefined; + private _onTunnelOpened: Emitter = new Emitter(); + public onTunnelOpened: Event = this._onTunnelOpened.event; + private _onTunnelClosed: Emitter = new Emitter(); + public onTunnelClosed: Event = this._onTunnelClosed.event; private readonly _tunnels = new Map }>(); public constructor( @@ -116,33 +124,51 @@ export class TunnelService implements ITunnelService { this._tunnels.clear(); } - openTunnel(remotePort: number): Promise | undefined { + openTunnel(remotePort: number, localPort: number): Promise | undefined { const remoteAuthority = this.environmentService.configuration.remoteAuthority; if (!remoteAuthority) { return undefined; } - const resolvedTunnel = this.retainOrCreateTunnel(remoteAuthority, remotePort); + const resolvedTunnel = this.retainOrCreateTunnel(remoteAuthority, remotePort, localPort); if (!resolvedTunnel) { return resolvedTunnel; } - return resolvedTunnel.then(tunnel => ({ + return resolvedTunnel.then(tunnel => { + const newTunnel = this.makeTunnel(tunnel); + this._onTunnelOpened.fire(newTunnel); + return newTunnel; + }); + } + + private makeTunnel(tunnel: RemoteTunnel): RemoteTunnel { + return { tunnelRemotePort: tunnel.tunnelRemotePort, tunnelLocalPort: tunnel.tunnelLocalPort, + localAddress: tunnel.localAddress, dispose: () => { - const existing = this._tunnels.get(remotePort); + const existing = this._tunnels.get(tunnel.tunnelRemotePort); if (existing) { if (--existing.refcount <= 0) { existing.value.then(tunnel => tunnel.dispose()); - this._tunnels.delete(remotePort); + this._tunnels.delete(tunnel.tunnelRemotePort); + this._onTunnelClosed.fire(tunnel.tunnelRemotePort); } } } - })); + }; + } + + async closeTunnel(remotePort: number): Promise { + if (this._tunnels.has(remotePort)) { + const value = this._tunnels.get(remotePort)!; + (await value.value).dispose(); + value.refcount = 0; + } } - private retainOrCreateTunnel(remoteAuthority: string, remotePort: number): Promise | undefined { + private retainOrCreateTunnel(remoteAuthority: string, remotePort: number, localPort?: number): Promise | undefined { const existing = this._tunnels.get(remotePort); if (existing) { ++existing.refcount; @@ -162,8 +188,9 @@ export class TunnelService implements ITunnelService { logService: this.logService }; - const tunnel = createRemoteTunnel(options, remotePort); - this._tunnels.set(remotePort, { refcount: 1, value: tunnel }); + const tunnel = createRemoteTunnel(options, remotePort, localPort); + // Using makeTunnel here for the value does result in dispose getting called twice, but it also ensures that _onTunnelClosed will be fired when closeTunnel is called. + this._tunnels.set(remotePort, { refcount: 1, value: tunnel.then(value => this.makeTunnel(value)) }); return tunnel; } } diff --git a/src/vs/workbench/services/search/node/fileSearchManager.ts b/src/vs/workbench/services/search/common/fileSearchManager.ts similarity index 99% rename from src/vs/workbench/services/search/node/fileSearchManager.ts rename to src/vs/workbench/services/search/common/fileSearchManager.ts index 7f7d3da4ef62..7e161e4eaf42 100644 --- a/src/vs/workbench/services/search/node/fileSearchManager.ts +++ b/src/vs/workbench/services/search/common/fileSearchManager.ts @@ -12,6 +12,7 @@ import { StopWatch } from 'vs/base/common/stopwatch'; import { URI } from 'vs/base/common/uri'; import { IFileMatch, IFileSearchProviderStats, IFolderQuery, ISearchCompleteStats, IFileQuery, QueryGlobTester, resolvePatternsForProvider } from 'vs/workbench/services/search/common/search'; import { FileSearchProvider, FileSearchOptions } from 'vs/workbench/services/search/common/searchExtTypes'; +import { nextTick } from 'vs/base/common/process'; export interface IInternalFileMatch { base: URI; @@ -114,7 +115,7 @@ class FileSearchEngine { const noSiblingsClauses = !queryTester.hasSiblingExcludeClauses(); let providerSW: StopWatch; - new Promise(_resolve => process.nextTick(_resolve)) + new Promise(_resolve => nextTick(_resolve)) .then(() => { this.activeCancellationTokens.add(cancellation); diff --git a/src/vs/workbench/services/search/common/replace.ts b/src/vs/workbench/services/search/common/replace.ts index a180601f43b0..d29dc756d17b 100644 --- a/src/vs/workbench/services/search/common/replace.ts +++ b/src/vs/workbench/services/search/common/replace.ts @@ -61,9 +61,9 @@ export class ReplacePattern { if (match) { if (this.hasParameters) { if (match[0] === text) { - return text.replace(this._regExp, this.pattern); + return text.replace(this._regExp, this.buildReplaceString(match, preserveCase)); } - let replaceString = text.replace(this._regExp, this.pattern); + let replaceString = text.replace(this._regExp, this.buildReplaceString(match, preserveCase)); return replaceString.substr(match.index, match[0].length - (text.length - replaceString.length)); } return this.buildReplaceString(match, preserveCase); diff --git a/src/vs/workbench/services/search/common/search.ts b/src/vs/workbench/services/search/common/search.ts index 3bf1d4f0ec3e..b8a3b15d15aa 100644 --- a/src/vs/workbench/services/search/common/search.ts +++ b/src/vs/workbench/services/search/common/search.ts @@ -329,6 +329,10 @@ export interface ISearchConfigurationProperties { actionsPosition: 'auto' | 'right'; maintainFileSearchCache: boolean; collapseResults: 'auto' | 'alwaysCollapse' | 'alwaysExpand'; + searchOnType: boolean; + searchOnTypeDebouncePeriod: number; + enableSearchEditorPreview: boolean; + searchEditorPreviewForceAbsolutePaths: boolean; } export interface ISearchConfiguration extends IFilesConfiguration { diff --git a/src/vs/workbench/services/search/common/searchService.ts b/src/vs/workbench/services/search/common/searchService.ts index db5d6262964c..78c297c63531 100644 --- a/src/vs/workbench/services/search/common/searchService.ts +++ b/src/vs/workbench/services/search/common/searchService.ts @@ -19,7 +19,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { deserializeSearchError, FileMatch, ICachedSearchStats, IFileMatch, IFileQuery, IFileSearchStats, IFolderQuery, IProgressMessage, ISearchComplete, ISearchEngineStats, ISearchProgressItem, ISearchQuery, ISearchResultProvider, ISearchService, ITextQuery, pathIncludedInQuery, QueryType, SearchError, SearchErrorCode, SearchProviderType, isFileMatch, isProgressMessage } from 'vs/workbench/services/search/common/search'; import { addContextToEditorMatches, editorMatchesToTextSearchResults } from 'vs/workbench/services/search/common/searchHelpers'; -import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; +import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; export class SearchService extends Disposable implements ISearchService { @@ -32,7 +32,7 @@ export class SearchService extends Disposable implements ISearchService { constructor( private readonly modelService: IModelService, - private readonly untitledEditorService: IUntitledEditorService, + private readonly untitledTextEditorService: IUntitledTextEditorService, private readonly editorService: IEditorService, private readonly telemetryService: ITelemetryService, private readonly logService: ILogService, @@ -391,9 +391,15 @@ export class SearchService extends Disposable implements ISearchService { return; } + // Skip search results + if (model.getModeId() === 'search-result' && !(query.includePattern && query.includePattern['**/*.code-search'])) { + // TODO: untitled search editors will be excluded from search even when include *.code-search is specified + return; + } + // Support untitled files if (resource.scheme === Schemas.untitled) { - if (!this.untitledEditorService.exists(resource)) { + if (!this.untitledTextEditorService.exists(resource)) { return; } } @@ -442,14 +448,14 @@ export class SearchService extends Disposable implements ISearchService { export class RemoteSearchService extends SearchService { constructor( @IModelService modelService: IModelService, - @IUntitledEditorService untitledEditorService: IUntitledEditorService, + @IUntitledTextEditorService untitledTextEditorService: IUntitledTextEditorService, @IEditorService editorService: IEditorService, @ITelemetryService telemetryService: ITelemetryService, @ILogService logService: ILogService, @IExtensionService extensionService: IExtensionService, @IFileService fileService: IFileService ) { - super(modelService, untitledEditorService, editorService, telemetryService, logService, extensionService, fileService); + super(modelService, untitledTextEditorService, editorService, telemetryService, logService, extensionService, fileService); } } diff --git a/src/vs/workbench/services/search/common/textSearchManager.ts b/src/vs/workbench/services/search/common/textSearchManager.ts new file mode 100644 index 000000000000..a77a3938261f --- /dev/null +++ b/src/vs/workbench/services/search/common/textSearchManager.ts @@ -0,0 +1,355 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as path from 'vs/base/common/path'; +import { mapArrayOrNot } from 'vs/base/common/arrays'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; +import { toErrorMessage } from 'vs/base/common/errorMessage'; +import * as resources from 'vs/base/common/resources'; +import * as glob from 'vs/base/common/glob'; +import { URI } from 'vs/base/common/uri'; +import { IExtendedExtensionSearchOptions, IFileMatch, IFolderQuery, IPatternInfo, ISearchCompleteStats, ITextQuery, ITextSearchContext, ITextSearchMatch, ITextSearchResult, QueryGlobTester, resolvePatternsForProvider } from 'vs/workbench/services/search/common/search'; +import { TextSearchProvider, TextSearchResult, TextSearchMatch, TextSearchComplete, Range, TextSearchOptions, TextSearchQuery } from 'vs/workbench/services/search/common/searchExtTypes'; +import { nextTick } from 'vs/base/common/process'; + +export interface IFileUtils { + readdir: (resource: URI) => Promise; + toCanonicalName: (encoding: string) => string; +} + +export class TextSearchManager { + + private collector: TextSearchResultsCollector | null = null; + + private isLimitHit = false; + private resultCount = 0; + + constructor(private query: ITextQuery, private provider: TextSearchProvider, private fileUtils: IFileUtils) { } + + search(onProgress: (matches: IFileMatch[]) => void, token: CancellationToken): Promise { + const folderQueries = this.query.folderQueries || []; + const tokenSource = new CancellationTokenSource(); + token.onCancellationRequested(() => tokenSource.cancel()); + + return new Promise((resolve, reject) => { + this.collector = new TextSearchResultsCollector(onProgress); + + let isCanceled = false; + const onResult = (result: TextSearchResult, folderIdx: number) => { + if (isCanceled) { + return; + } + + if (!this.isLimitHit) { + const resultSize = this.resultSize(result); + if (extensionResultIsMatch(result) && typeof this.query.maxResults === 'number' && this.resultCount + resultSize > this.query.maxResults) { + this.isLimitHit = true; + isCanceled = true; + tokenSource.cancel(); + + result = this.trimResultToSize(result, this.query.maxResults - this.resultCount); + } + + const newResultSize = this.resultSize(result); + this.resultCount += newResultSize; + if (newResultSize > 0) { + this.collector!.add(result, folderIdx); + } + } + }; + + // For each root folder + Promise.all(folderQueries.map((fq, i) => { + return this.searchInFolder(fq, r => onResult(r, i), tokenSource.token); + })).then(results => { + tokenSource.dispose(); + this.collector!.flush(); + + const someFolderHitLImit = results.some(result => !!result && !!result.limitHit); + resolve({ + limitHit: this.isLimitHit || someFolderHitLImit, + stats: { + type: 'textSearchProvider' + } + }); + }, (err: Error) => { + tokenSource.dispose(); + const errMsg = toErrorMessage(err); + reject(new Error(errMsg)); + }); + }); + } + + private resultSize(result: TextSearchResult): number { + const match = result; + return Array.isArray(match.ranges) ? + match.ranges.length : + 1; + } + + private trimResultToSize(result: TextSearchMatch, size: number): TextSearchMatch { + const rangesArr = Array.isArray(result.ranges) ? result.ranges : [result.ranges]; + const matchesArr = Array.isArray(result.preview.matches) ? result.preview.matches : [result.preview.matches]; + + return { + ranges: rangesArr.slice(0, size), + preview: { + matches: matchesArr.slice(0, size), + text: result.preview.text + }, + uri: result.uri + }; + } + + private searchInFolder(folderQuery: IFolderQuery, onResult: (result: TextSearchResult) => void, token: CancellationToken): Promise { + const queryTester = new QueryGlobTester(this.query, folderQuery); + const testingPs: Promise[] = []; + const progress = { + report: (result: TextSearchResult) => { + if (!this.validateProviderResult(result)) { + return; + } + + const hasSibling = folderQuery.folder.scheme === 'file' ? + glob.hasSiblingPromiseFn(() => { + return this.fileUtils.readdir(resources.dirname(result.uri)); + }) : + undefined; + + const relativePath = resources.relativePath(folderQuery.folder, result.uri); + if (relativePath) { + testingPs.push( + queryTester.includedInQuery(relativePath, path.basename(relativePath), hasSibling) + .then(included => { + if (included) { + onResult(result); + } + })); + } + } + }; + + const searchOptions = this.getSearchOptionsForFolder(folderQuery); + return new Promise(resolve => nextTick(resolve)) + .then(() => this.provider.provideTextSearchResults(patternInfoToQuery(this.query.contentPattern), searchOptions, progress, token)) + .then(result => { + return Promise.all(testingPs) + .then(() => result); + }); + } + + private validateProviderResult(result: TextSearchResult): boolean { + if (extensionResultIsMatch(result)) { + if (Array.isArray(result.ranges)) { + if (!Array.isArray(result.preview.matches)) { + console.warn('INVALID - A text search provider match\'s`ranges` and`matches` properties must have the same type.'); + return false; + } + + if ((result.preview.matches).length !== result.ranges.length) { + console.warn('INVALID - A text search provider match\'s`ranges` and`matches` properties must have the same length.'); + return false; + } + } else { + if (Array.isArray(result.preview.matches)) { + console.warn('INVALID - A text search provider match\'s`ranges` and`matches` properties must have the same length.'); + return false; + } + } + } + + return true; + } + + private getSearchOptionsForFolder(fq: IFolderQuery): TextSearchOptions { + const includes = resolvePatternsForProvider(this.query.includePattern, fq.includePattern); + const excludes = resolvePatternsForProvider(this.query.excludePattern, fq.excludePattern); + + const options = { + folder: URI.from(fq.folder), + excludes, + includes, + useIgnoreFiles: !fq.disregardIgnoreFiles, + useGlobalIgnoreFiles: !fq.disregardGlobalIgnoreFiles, + followSymlinks: !fq.ignoreSymlinks, + encoding: fq.fileEncoding && this.fileUtils.toCanonicalName(fq.fileEncoding), + maxFileSize: this.query.maxFileSize, + maxResults: this.query.maxResults, + previewOptions: this.query.previewOptions, + afterContext: this.query.afterContext, + beforeContext: this.query.beforeContext + }; + (options).usePCRE2 = this.query.usePCRE2; + return options; + } +} + +function patternInfoToQuery(patternInfo: IPatternInfo): TextSearchQuery { + return { + isCaseSensitive: patternInfo.isCaseSensitive || false, + isRegExp: patternInfo.isRegExp || false, + isWordMatch: patternInfo.isWordMatch || false, + isMultiline: patternInfo.isMultiline || false, + pattern: patternInfo.pattern + }; +} + +export class TextSearchResultsCollector { + private _batchedCollector: BatchedCollector; + + private _currentFolderIdx: number = -1; + private _currentUri: URI | undefined; + private _currentFileMatch: IFileMatch | null = null; + + constructor(private _onResult: (result: IFileMatch[]) => void) { + this._batchedCollector = new BatchedCollector(512, items => this.sendItems(items)); + } + + add(data: TextSearchResult, folderIdx: number): void { + // Collects TextSearchResults into IInternalFileMatches and collates using BatchedCollector. + // This is efficient for ripgrep which sends results back one file at a time. It wouldn't be efficient for other search + // providers that send results in random order. We could do this step afterwards instead. + if (this._currentFileMatch && (this._currentFolderIdx !== folderIdx || !resources.isEqual(this._currentUri, data.uri))) { + this.pushToCollector(); + this._currentFileMatch = null; + } + + if (!this._currentFileMatch) { + this._currentFolderIdx = folderIdx; + this._currentFileMatch = { + resource: data.uri, + results: [] + }; + } + + this._currentFileMatch.results!.push(extensionResultToFrontendResult(data)); + } + + private pushToCollector(): void { + const size = this._currentFileMatch && this._currentFileMatch.results ? + this._currentFileMatch.results.length : + 0; + this._batchedCollector.addItem(this._currentFileMatch!, size); + } + + flush(): void { + this.pushToCollector(); + this._batchedCollector.flush(); + } + + private sendItems(items: IFileMatch[]): void { + this._onResult(items); + } +} + +function extensionResultToFrontendResult(data: TextSearchResult): ITextSearchResult { + // Warning: result from RipgrepTextSearchEH has fake Range. Don't depend on any other props beyond these... + if (extensionResultIsMatch(data)) { + return { + preview: { + matches: mapArrayOrNot(data.preview.matches, m => ({ + startLineNumber: m.start.line, + startColumn: m.start.character, + endLineNumber: m.end.line, + endColumn: m.end.character + })), + text: data.preview.text + }, + ranges: mapArrayOrNot(data.ranges, r => ({ + startLineNumber: r.start.line, + startColumn: r.start.character, + endLineNumber: r.end.line, + endColumn: r.end.character + })) + }; + } else { + return { + text: data.text, + lineNumber: data.lineNumber + }; + } +} + +export function extensionResultIsMatch(data: TextSearchResult): data is TextSearchMatch { + return !!(data).preview; +} + +/** + * Collects items that have a size - before the cumulative size of collected items reaches START_BATCH_AFTER_COUNT, the callback is called for every + * set of items collected. + * But after that point, the callback is called with batches of maxBatchSize. + * If the batch isn't filled within some time, the callback is also called. + */ +export class BatchedCollector { + private static readonly TIMEOUT = 4000; + + // After START_BATCH_AFTER_COUNT items have been collected, stop flushing on timeout + private static readonly START_BATCH_AFTER_COUNT = 50; + + private totalNumberCompleted = 0; + private batch: T[] = []; + private batchSize = 0; + private timeoutHandle: any; + + constructor(private maxBatchSize: number, private cb: (items: T[]) => void) { + } + + addItem(item: T, size: number): void { + if (!item) { + return; + } + + this.addItemToBatch(item, size); + } + + addItems(items: T[], size: number): void { + if (!items) { + return; + } + + this.addItemsToBatch(items, size); + } + + private addItemToBatch(item: T, size: number): void { + this.batch.push(item); + this.batchSize += size; + this.onUpdate(); + } + + private addItemsToBatch(item: T[], size: number): void { + this.batch = this.batch.concat(item); + this.batchSize += size; + this.onUpdate(); + } + + private onUpdate(): void { + if (this.totalNumberCompleted < BatchedCollector.START_BATCH_AFTER_COUNT) { + // Flush because we aren't batching yet + this.flush(); + } else if (this.batchSize >= this.maxBatchSize) { + // Flush because the batch is full + this.flush(); + } else if (!this.timeoutHandle) { + // No timeout running, start a timeout to flush + this.timeoutHandle = setTimeout(() => { + this.flush(); + }, BatchedCollector.TIMEOUT); + } + } + + flush(): void { + if (this.batchSize) { + this.totalNumberCompleted += this.batchSize; + this.cb(this.batch); + this.batch = []; + this.batchSize = 0; + + if (this.timeoutHandle) { + clearTimeout(this.timeoutHandle); + this.timeoutHandle = 0; + } + } + } +} diff --git a/src/vs/workbench/services/search/node/rawSearchService.ts b/src/vs/workbench/services/search/node/rawSearchService.ts index 4bc07a28c1f6..082e9a3add28 100644 --- a/src/vs/workbench/services/search/node/rawSearchService.ts +++ b/src/vs/workbench/services/search/node/rawSearchService.ts @@ -277,7 +277,7 @@ export class SearchService implements IRawSearchService { for (const previousSearch in cache.resultsToSearchCache) { // If we narrow down, we might be able to reuse the cached results if (strings.startsWith(searchValue, previousSearch)) { - if (hasPathSep && previousSearch.indexOf(sep) < 0) { + if (hasPathSep && previousSearch.indexOf(sep) < 0 && previousSearch !== '') { continue; // since a path character widens the search for potential more matches, require it in previous search too } @@ -383,7 +383,7 @@ export class SearchService implements IRawSearchService { cancel() { // Do nothing } - then(resolve: any, reject: any) { + then(resolve?: ((value: C) => TResult1 | Promise) | undefined | null, reject?: ((reason: any) => TResult2 | Promise) | undefined | null): Promise { return promise.then(resolve, reject); } catch(reject?: any) { diff --git a/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts b/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts index 2fa1aa7c2979..03d396fdaf75 100644 --- a/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts +++ b/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts @@ -79,15 +79,15 @@ export class RipgrepTextSearchEngine { cancel(); }); - rgProc.stdout.on('data', data => { + rgProc.stdout!.on('data', data => { ripgrepParser.handleData(data); }); let gotData = false; - rgProc.stdout.once('data', () => gotData = true); + rgProc.stdout!.once('data', () => gotData = true); let stderr = ''; - rgProc.stderr.on('data', data => { + rgProc.stderr!.on('data', data => { const message = data.toString(); this.outputChannel.appendLine(message); stderr += message; diff --git a/src/vs/workbench/services/search/node/searchService.ts b/src/vs/workbench/services/search/node/searchService.ts index 76c2d3bcc27f..5d43f9e2b863 100644 --- a/src/vs/workbench/services/search/node/searchService.ts +++ b/src/vs/workbench/services/search/node/searchService.ts @@ -21,16 +21,17 @@ import { SearchChannelClient } from './searchIpc'; import { SearchService } from 'vs/workbench/services/search/common/searchService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; +import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { parseSearchPort } from 'vs/platform/environment/node/environmentService'; export class LocalSearchService extends SearchService { constructor( @IModelService modelService: IModelService, - @IUntitledEditorService untitledEditorService: IUntitledEditorService, + @IUntitledTextEditorService untitledTextEditorService: IUntitledTextEditorService, @IEditorService editorService: IEditorService, @ITelemetryService telemetryService: ITelemetryService, @ILogService logService: ILogService, @@ -39,10 +40,10 @@ export class LocalSearchService extends SearchService { @IWorkbenchEnvironmentService readonly environmentService: IWorkbenchEnvironmentService, @IInstantiationService readonly instantiationService: IInstantiationService ) { - super(modelService, untitledEditorService, editorService, telemetryService, logService, extensionService, fileService); + super(modelService, untitledTextEditorService, editorService, telemetryService, logService, extensionService, fileService); - this.diskSearch = instantiationService.createInstance(DiskSearch, !environmentService.isBuilt || environmentService.verbose, environmentService.debugSearch); + this.diskSearch = instantiationService.createInstance(DiskSearch, !environmentService.isBuilt || environmentService.verbose, parseSearchPort(environmentService.args, environmentService.isBuilt)); } } diff --git a/src/vs/workbench/services/search/node/textSearchAdapter.ts b/src/vs/workbench/services/search/node/textSearchAdapter.ts index 0576ec265103..bb96d536f304 100644 --- a/src/vs/workbench/services/search/node/textSearchAdapter.ts +++ b/src/vs/workbench/services/search/node/textSearchAdapter.ts @@ -7,12 +7,11 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import * as pfs from 'vs/base/node/pfs'; import { IFileMatch, IProgressMessage, ITextQuery, ITextSearchStats, ITextSearchMatch, ISerializedFileMatch, ISerializedSearchSuccess } from 'vs/workbench/services/search/common/search'; import { RipgrepTextSearchEngine } from 'vs/workbench/services/search/node/ripgrepTextSearchEngine'; -import { TextSearchManager } from 'vs/workbench/services/search/node/textSearchManager'; +import { NativeTextSearchManager } from 'vs/workbench/services/search/node/textSearchManager'; export class TextSearchEngineAdapter { - constructor(private query: ITextQuery) { - } + constructor(private query: ITextQuery) { } search(token: CancellationToken, onResult: (matches: ISerializedFileMatch[]) => void, onMessage: (message: IProgressMessage) => void): Promise { if ((!this.query.folderQueries || !this.query.folderQueries.length) && (!this.query.extraFileResources || !this.query.extraFileResources.length)) { @@ -30,7 +29,7 @@ export class TextSearchEngineAdapter { onMessage({ message: msg }); } }; - const textSearchManager = new TextSearchManager(this.query, new RipgrepTextSearchEngine(pretendOutputChannel), pfs); + const textSearchManager = new NativeTextSearchManager(this.query, new RipgrepTextSearchEngine(pretendOutputChannel), pfs); return new Promise((resolve, reject) => { return textSearchManager .search( diff --git a/src/vs/workbench/services/search/node/textSearchManager.ts b/src/vs/workbench/services/search/node/textSearchManager.ts index 5e16591a94fd..355212f28146 100644 --- a/src/vs/workbench/services/search/node/textSearchManager.ts +++ b/src/vs/workbench/services/search/node/textSearchManager.ts @@ -3,352 +3,18 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as path from 'vs/base/common/path'; -import { mapArrayOrNot } from 'vs/base/common/arrays'; -import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; -import { toErrorMessage } from 'vs/base/common/errorMessage'; -import * as resources from 'vs/base/common/resources'; -import * as glob from 'vs/base/common/glob'; -import { URI } from 'vs/base/common/uri'; import { toCanonicalName } from 'vs/base/node/encoding'; import * as pfs from 'vs/base/node/pfs'; -import { IExtendedExtensionSearchOptions, IFileMatch, IFolderQuery, IPatternInfo, ISearchCompleteStats, ITextQuery, ITextSearchContext, ITextSearchMatch, ITextSearchResult, QueryGlobTester, resolvePatternsForProvider } from 'vs/workbench/services/search/common/search'; -import { TextSearchProvider, TextSearchResult, TextSearchMatch, TextSearchComplete, Range, TextSearchOptions, TextSearchQuery } from 'vs/workbench/services/search/common/searchExtTypes'; +import { ITextQuery } from 'vs/workbench/services/search/common/search'; +import { TextSearchProvider } from 'vs/workbench/services/search/common/searchExtTypes'; +import { TextSearchManager } from 'vs/workbench/services/search/common/textSearchManager'; -export class TextSearchManager { +export class NativeTextSearchManager extends TextSearchManager { - private collector: TextSearchResultsCollector | null = null; - - private isLimitHit = false; - private resultCount = 0; - - constructor(private query: ITextQuery, private provider: TextSearchProvider, private _pfs: typeof pfs = pfs) { - } - - search(onProgress: (matches: IFileMatch[]) => void, token: CancellationToken): Promise { - const folderQueries = this.query.folderQueries || []; - const tokenSource = new CancellationTokenSource(); - token.onCancellationRequested(() => tokenSource.cancel()); - - return new Promise((resolve, reject) => { - this.collector = new TextSearchResultsCollector(onProgress); - - let isCanceled = false; - const onResult = (result: TextSearchResult, folderIdx: number) => { - if (isCanceled) { - return; - } - - if (!this.isLimitHit) { - const resultSize = this.resultSize(result); - if (extensionResultIsMatch(result) && typeof this.query.maxResults === 'number' && this.resultCount + resultSize > this.query.maxResults) { - this.isLimitHit = true; - isCanceled = true; - tokenSource.cancel(); - - result = this.trimResultToSize(result, this.query.maxResults - this.resultCount); - } - - const newResultSize = this.resultSize(result); - this.resultCount += newResultSize; - if (newResultSize > 0) { - this.collector!.add(result, folderIdx); - } - } - }; - - // For each root folder - Promise.all(folderQueries.map((fq, i) => { - return this.searchInFolder(fq, r => onResult(r, i), tokenSource.token); - })).then(results => { - tokenSource.dispose(); - this.collector!.flush(); - - const someFolderHitLImit = results.some(result => !!result && !!result.limitHit); - resolve({ - limitHit: this.isLimitHit || someFolderHitLImit, - stats: { - type: 'textSearchProvider' - } - }); - }, (err: Error) => { - tokenSource.dispose(); - const errMsg = toErrorMessage(err); - reject(new Error(errMsg)); - }); + constructor(query: ITextQuery, provider: TextSearchProvider, _pfs: typeof pfs = pfs) { + super(query, provider, { + readdir: resource => _pfs.readdir(resource.fsPath), + toCanonicalName: name => toCanonicalName(name) }); } - - private resultSize(result: TextSearchResult): number { - const match = result; - return Array.isArray(match.ranges) ? - match.ranges.length : - 1; - } - - private trimResultToSize(result: TextSearchMatch, size: number): TextSearchMatch { - const rangesArr = Array.isArray(result.ranges) ? result.ranges : [result.ranges]; - const matchesArr = Array.isArray(result.preview.matches) ? result.preview.matches : [result.preview.matches]; - - return { - ranges: rangesArr.slice(0, size), - preview: { - matches: matchesArr.slice(0, size), - text: result.preview.text - }, - uri: result.uri - }; - } - - private searchInFolder(folderQuery: IFolderQuery, onResult: (result: TextSearchResult) => void, token: CancellationToken): Promise { - const queryTester = new QueryGlobTester(this.query, folderQuery); - const testingPs: Promise[] = []; - const progress = { - report: (result: TextSearchResult) => { - if (!this.validateProviderResult(result)) { - return; - } - - const hasSibling = folderQuery.folder.scheme === 'file' ? - glob.hasSiblingPromiseFn(() => { - return this.readdir(path.dirname(result.uri.fsPath)); - }) : - undefined; - - const relativePath = path.relative(folderQuery.folder.fsPath, result.uri.fsPath); - testingPs.push( - queryTester.includedInQuery(relativePath, path.basename(relativePath), hasSibling) - .then(included => { - if (included) { - onResult(result); - } - })); - } - }; - - const searchOptions = this.getSearchOptionsForFolder(folderQuery); - return new Promise(resolve => process.nextTick(resolve)) - .then(() => this.provider.provideTextSearchResults(patternInfoToQuery(this.query.contentPattern), searchOptions, progress, token)) - .then(result => { - return Promise.all(testingPs) - .then(() => result); - }); - } - - private validateProviderResult(result: TextSearchResult): boolean { - if (extensionResultIsMatch(result)) { - if (Array.isArray(result.ranges)) { - if (!Array.isArray(result.preview.matches)) { - console.warn('INVALID - A text search provider match\'s`ranges` and`matches` properties must have the same type.'); - return false; - } - - if ((result.preview.matches).length !== result.ranges.length) { - console.warn('INVALID - A text search provider match\'s`ranges` and`matches` properties must have the same length.'); - return false; - } - } else { - if (Array.isArray(result.preview.matches)) { - console.warn('INVALID - A text search provider match\'s`ranges` and`matches` properties must have the same length.'); - return false; - } - } - } - - return true; - } - - private readdir(dirname: string): Promise { - return this._pfs.readdir(dirname); - } - - private getSearchOptionsForFolder(fq: IFolderQuery): TextSearchOptions { - const includes = resolvePatternsForProvider(this.query.includePattern, fq.includePattern); - const excludes = resolvePatternsForProvider(this.query.excludePattern, fq.excludePattern); - - const options = { - folder: URI.from(fq.folder), - excludes, - includes, - useIgnoreFiles: !fq.disregardIgnoreFiles, - useGlobalIgnoreFiles: !fq.disregardGlobalIgnoreFiles, - followSymlinks: !fq.ignoreSymlinks, - encoding: fq.fileEncoding && toCanonicalName(fq.fileEncoding), - maxFileSize: this.query.maxFileSize, - maxResults: this.query.maxResults, - previewOptions: this.query.previewOptions, - afterContext: this.query.afterContext, - beforeContext: this.query.beforeContext - }; - (options).usePCRE2 = this.query.usePCRE2; - return options; - } -} - -function patternInfoToQuery(patternInfo: IPatternInfo): TextSearchQuery { - return { - isCaseSensitive: patternInfo.isCaseSensitive || false, - isRegExp: patternInfo.isRegExp || false, - isWordMatch: patternInfo.isWordMatch || false, - isMultiline: patternInfo.isMultiline || false, - pattern: patternInfo.pattern - }; -} - -export class TextSearchResultsCollector { - private _batchedCollector: BatchedCollector; - - private _currentFolderIdx: number = -1; - private _currentUri: URI | undefined; - private _currentFileMatch: IFileMatch | null = null; - - constructor(private _onResult: (result: IFileMatch[]) => void) { - this._batchedCollector = new BatchedCollector(512, items => this.sendItems(items)); - } - - add(data: TextSearchResult, folderIdx: number): void { - // Collects TextSearchResults into IInternalFileMatches and collates using BatchedCollector. - // This is efficient for ripgrep which sends results back one file at a time. It wouldn't be efficient for other search - // providers that send results in random order. We could do this step afterwards instead. - if (this._currentFileMatch && (this._currentFolderIdx !== folderIdx || !resources.isEqual(this._currentUri, data.uri))) { - this.pushToCollector(); - this._currentFileMatch = null; - } - - if (!this._currentFileMatch) { - this._currentFolderIdx = folderIdx; - this._currentFileMatch = { - resource: data.uri, - results: [] - }; - } - - this._currentFileMatch.results!.push(extensionResultToFrontendResult(data)); - } - - private pushToCollector(): void { - const size = this._currentFileMatch && this._currentFileMatch.results ? - this._currentFileMatch.results.length : - 0; - this._batchedCollector.addItem(this._currentFileMatch!, size); - } - - flush(): void { - this.pushToCollector(); - this._batchedCollector.flush(); - } - - private sendItems(items: IFileMatch[]): void { - this._onResult(items); - } -} - -function extensionResultToFrontendResult(data: TextSearchResult): ITextSearchResult { - // Warning: result from RipgrepTextSearchEH has fake Range. Don't depend on any other props beyond these... - if (extensionResultIsMatch(data)) { - return { - preview: { - matches: mapArrayOrNot(data.preview.matches, m => ({ - startLineNumber: m.start.line, - startColumn: m.start.character, - endLineNumber: m.end.line, - endColumn: m.end.character - })), - text: data.preview.text - }, - ranges: mapArrayOrNot(data.ranges, r => ({ - startLineNumber: r.start.line, - startColumn: r.start.character, - endLineNumber: r.end.line, - endColumn: r.end.character - })) - }; - } else { - return { - text: data.text, - lineNumber: data.lineNumber - }; - } -} - -export function extensionResultIsMatch(data: TextSearchResult): data is TextSearchMatch { - return !!(data).preview; -} - -/** - * Collects items that have a size - before the cumulative size of collected items reaches START_BATCH_AFTER_COUNT, the callback is called for every - * set of items collected. - * But after that point, the callback is called with batches of maxBatchSize. - * If the batch isn't filled within some time, the callback is also called. - */ -export class BatchedCollector { - private static readonly TIMEOUT = 4000; - - // After START_BATCH_AFTER_COUNT items have been collected, stop flushing on timeout - private static readonly START_BATCH_AFTER_COUNT = 50; - - private totalNumberCompleted = 0; - private batch: T[] = []; - private batchSize = 0; - private timeoutHandle: any; - - constructor(private maxBatchSize: number, private cb: (items: T[]) => void) { - } - - addItem(item: T, size: number): void { - if (!item) { - return; - } - - this.addItemToBatch(item, size); - } - - addItems(items: T[], size: number): void { - if (!items) { - return; - } - - this.addItemsToBatch(items, size); - } - - private addItemToBatch(item: T, size: number): void { - this.batch.push(item); - this.batchSize += size; - this.onUpdate(); - } - - private addItemsToBatch(item: T[], size: number): void { - this.batch = this.batch.concat(item); - this.batchSize += size; - this.onUpdate(); - } - - private onUpdate(): void { - if (this.totalNumberCompleted < BatchedCollector.START_BATCH_AFTER_COUNT) { - // Flush because we aren't batching yet - this.flush(); - } else if (this.batchSize >= this.maxBatchSize) { - // Flush because the batch is full - this.flush(); - } else if (!this.timeoutHandle) { - // No timeout running, start a timeout to flush - this.timeoutHandle = setTimeout(() => { - this.flush(); - }, BatchedCollector.TIMEOUT); - } - } - - flush(): void { - if (this.batchSize) { - this.totalNumberCompleted += this.batchSize; - this.cb(this.batch); - this.batch = []; - this.batchSize = 0; - - if (this.timeoutHandle) { - clearTimeout(this.timeoutHandle); - this.timeoutHandle = 0; - } - } - } } diff --git a/src/vs/workbench/services/search/test/common/replace.test.ts b/src/vs/workbench/services/search/test/common/replace.test.ts index a1dde74b952f..cc9ea1424bdf 100644 --- a/src/vs/workbench/services/search/test/common/replace.test.ts +++ b/src/vs/workbench/services/search/test/common/replace.test.ts @@ -214,5 +214,21 @@ suite('Replace Pattern test', () => { testObject = new ReplacePattern('$0ah', { pattern: 'b(la)(?=\\stext$)', isRegExp: true }); actual = testObject.getReplaceString('this is a bla text'); assert.equal('blaah', actual); + + testObject = new ReplacePattern('newrege$1', true, /Testrege(\w*)/); + actual = testObject.getReplaceString('Testregex', true); + assert.equal('Newregex', actual); + + testObject = new ReplacePattern('newrege$1', true, /TESTREGE(\w*)/); + actual = testObject.getReplaceString('TESTREGEX', true); + assert.equal('NEWREGEX', actual); + + testObject = new ReplacePattern('new_rege$1', true, /Test_Rege(\w*)/); + actual = testObject.getReplaceString('Test_Regex', true); + assert.equal('New_Regex', actual); + + testObject = new ReplacePattern('new-rege$1', true, /Test-Rege(\w*)/); + actual = testObject.getReplaceString('Test-Regex', true); + assert.equal('New-Regex', actual); }); -}); \ No newline at end of file +}); diff --git a/src/vs/workbench/services/search/test/node/textSearchManager.test.ts b/src/vs/workbench/services/search/test/node/textSearchManager.test.ts index c654d90a9283..e2da74999d88 100644 --- a/src/vs/workbench/services/search/test/node/textSearchManager.test.ts +++ b/src/vs/workbench/services/search/test/node/textSearchManager.test.ts @@ -9,9 +9,9 @@ import { URI } from 'vs/base/common/uri'; import { Progress } from 'vs/platform/progress/common/progress'; import { ITextQuery, QueryType } from 'vs/workbench/services/search/common/search'; import { ProviderResult, TextSearchComplete, TextSearchOptions, TextSearchProvider, TextSearchQuery, TextSearchResult } from 'vs/workbench/services/search/common/searchExtTypes'; -import { TextSearchManager } from 'vs/workbench/services/search/node/textSearchManager'; +import { NativeTextSearchManager } from 'vs/workbench/services/search/node/textSearchManager'; -suite('TextSearchManager', () => { +suite('NativeTextSearchManager', () => { test('fixes encoding', async () => { let correctEncoding = false; const provider: TextSearchProvider = { @@ -33,7 +33,7 @@ suite('TextSearchManager', () => { }] }; - const m = new TextSearchManager(query, provider); + const m = new NativeTextSearchManager(query, provider); await m.search(() => { }, new CancellationTokenSource().token); assert.ok(correctEncoding); diff --git a/src/vs/workbench/services/statusbar/common/statusbar.ts b/src/vs/workbench/services/statusbar/common/statusbar.ts index 120d2a8bdc7f..814a7c254146 100644 --- a/src/vs/workbench/services/statusbar/common/statusbar.ts +++ b/src/vs/workbench/services/statusbar/common/statusbar.ts @@ -6,6 +6,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IDisposable } from 'vs/base/common/lifecycle'; import { ThemeColor } from 'vs/platform/theme/common/themeService'; +import { Event } from 'vs/base/common/event'; export const IStatusbarService = createDecorator('statusbarService'); @@ -74,7 +75,17 @@ export interface IStatusbarService { addEntry(entry: IStatusbarEntry, id: string, name: string, alignment: StatusbarAlignment, priority?: number): IStatusbarEntryAccessor; /** - * Allows to update an entry's visibilty with the provided ID. + * An event that is triggered when an entry's visibility is changed. + */ + readonly onDidChangeEntryVisibility: Event<{ id: string, visible: boolean }>; + + /** + * Return if an entry is visible or not. + */ + isEntryVisible(id: string): boolean; + + /** + * Allows to update an entry's visibility with the provided ID. */ updateEntryVisibility(id: string, visible: boolean): void; } diff --git a/src/vs/workbench/services/telemetry/browser/telemetryService.ts b/src/vs/workbench/services/telemetry/browser/telemetryService.ts index cb1178a21feb..1e793ddc4d63 100644 --- a/src/vs/workbench/services/telemetry/browser/telemetryService.ts +++ b/src/vs/workbench/services/telemetry/browser/telemetryService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { ITelemetryService, ITelemetryInfo, ITelemetryData } from 'vs/platform/telemetry/common/telemetry'; -import { NullTelemetryService, combinedAppender, LogAppender, ITelemetryAppender, validateTelemetryData } from 'vs/platform/telemetry/common/telemetryUtils'; +import { NullTelemetryService, combinedAppender, LogAppender, ITelemetryAppender } from 'vs/platform/telemetry/common/telemetryUtils'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { Disposable } from 'vs/base/common/lifecycle'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -19,18 +19,11 @@ import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteA export class WebTelemetryAppender implements ITelemetryAppender { - constructor(private _logService: ILogService, private _appender: IRemoteAgentService, - @IWorkbenchEnvironmentService private _environmentService: IWorkbenchEnvironmentService) { } // {{ SQL CARBON EDIT }} + constructor(private _logService: ILogService, private _appender: IRemoteAgentService) { } log(eventName: string, data: any): void { - data = validateTelemetryData(data); this._logService.trace(`telemetry/${eventName}`, data); - - const eventPrefix = this._environmentService.appQuality !== 'stable' ? '/adsworkbench/' : '/monacoworkbench/'; // {{SQL CARBON EDIT}} - this._appender.logTelemetry(eventPrefix + eventName, { - properties: data.properties, - measurements: data.measurements - }); + this._appender.logTelemetry(eventName, data); } flush(): Promise { @@ -54,11 +47,10 @@ export class TelemetryService extends Disposable implements ITelemetryService { ) { super(); - if (!environmentService.args['disable-telemetry'] && !!productService.enableTelemetry) { + if (!!productService.enableTelemetry) { const config: ITelemetryServiceConfig = { - appender: combinedAppender(new WebTelemetryAppender(logService, remoteAgentService, environmentService), new LogAppender(logService)), // {{SQL CARBON EDIT}} - commonProperties: resolveWorkbenchCommonProperties(storageService, productService.commit, productService.version, environmentService.configuration.machineId, environmentService.configuration.remoteAuthority), - piiPaths: [environmentService.appRoot] + appender: combinedAppender(new WebTelemetryAppender(logService, remoteAgentService), new LogAppender(logService)), + commonProperties: resolveWorkbenchCommonProperties(storageService, productService.commit, productService.version, environmentService.configuration.remoteAuthority, environmentService.options && environmentService.options.resolveCommonTelemetryProperties) }; this.impl = this._register(new BaseTelemetryService(config, configurationService)); diff --git a/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts b/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts index 49781cbd4ebf..99f3eb0b60ee 100644 --- a/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts +++ b/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts @@ -38,7 +38,7 @@ export class TelemetryService extends Disposable implements ITelemetryService { const channel = sharedProcessService.getChannel('telemetryAppender'); const config: ITelemetryServiceConfig = { appender: combinedAppender(new TelemetryAppenderClient(channel), new LogAppender(logService)), - commonProperties: resolveWorkbenchCommonProperties(storageService, productService.commit, productService.version, environmentService.configuration.machineId, productService.msftInternalDomains, environmentService.installSourcePath, environmentService.configuration.remoteAuthority), + commonProperties: resolveWorkbenchCommonProperties(storageService, productService.commit, productService.version, environmentService.configuration.machineId!, productService.msftInternalDomains, environmentService.installSourcePath, environmentService.configuration.remoteAuthority), piiPaths: environmentService.extensionsPath ? [environmentService.appRoot, environmentService.extensionsPath] : [environmentService.appRoot] }; diff --git a/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts b/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts index e91cd7e53140..a48300e30644 100644 --- a/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts +++ b/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts @@ -10,25 +10,26 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import * as resources from 'vs/base/common/resources'; import * as types from 'vs/base/common/types'; +import { equals as equalArray } from 'vs/base/common/arrays'; import { URI } from 'vs/base/common/uri'; import { TokenizationResult, TokenizationResult2 } from 'vs/editor/common/core/token'; import { IState, ITokenizationSupport, LanguageId, TokenMetadata, TokenizationRegistry, StandardTokenType, LanguageIdentifier } from 'vs/editor/common/modes'; import { nullTokenize2 } from 'vs/editor/common/modes/nullMode'; import { generateTokensCSSForColorMap } from 'vs/editor/common/modes/supports/tokenization'; import { IModeService } from 'vs/editor/common/services/modeService'; -import { IFileService } from 'vs/platform/files/common/files'; import { ILogService } from 'vs/platform/log/common/log'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { ExtensionMessageCollector } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { ITMSyntaxExtensionPoint, grammarsExtPoint } from 'vs/workbench/services/textMate/common/TMGrammars'; import { ITextMateService } from 'vs/workbench/services/textMate/common/textMateService'; -import { ITokenColorizationRule, IWorkbenchThemeService, IColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { ITextMateThemingRule, IWorkbenchThemeService, IColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { IGrammar, StackElement, IOnigLib, IRawTheme } from 'vscode-textmate'; import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IValidGrammarDefinition, IValidEmbeddedLanguagesMap, IValidTokenTypeMap } from 'vs/workbench/services/textMate/common/TMScopeRegistry'; import { TMGrammarFactory } from 'vs/workbench/services/textMate/common/TMGrammarFactory'; +import { IExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader'; export abstract class AbstractTextMateService extends Disposable implements ITextMateService { public _serviceBrand: undefined; @@ -44,11 +45,12 @@ export abstract class AbstractTextMateService extends Disposable implements ITex private _grammarFactory: TMGrammarFactory | null; private _tokenizersRegistrations: IDisposable[]; protected _currentTheme: IRawTheme | null; + protected _currentTokenColorMap: string[] | null; constructor( @IModeService private readonly _modeService: IModeService, @IWorkbenchThemeService private readonly _themeService: IWorkbenchThemeService, - @IFileService protected readonly _fileService: IFileService, + @IExtensionResourceLoaderService protected readonly _extensionResourceLoaderService: IExtensionResourceLoaderService, @INotificationService private readonly _notificationService: INotificationService, @ILogService private readonly _logService: ILogService, @IConfigurationService private readonly _configurationService: IConfigurationService, @@ -65,6 +67,7 @@ export abstract class AbstractTextMateService extends Disposable implements ITex this._tokenizersRegistrations = []; this._currentTheme = null; + this._currentTokenColorMap = null; grammarsExtPoint.setHandler((extensions) => { this._grammarDefinitions = null; @@ -191,10 +194,7 @@ export abstract class AbstractTextMateService extends Disposable implements ITex this._grammarFactory = new TMGrammarFactory({ logTrace: (msg: string) => this._logService.trace(msg), logError: (msg: string, err: any) => this._logService.error(msg, err), - readFile: async (resource: URI) => { - const content = await this._fileService.readFile(resource); - return content.value.toString(); - } + readFile: (resource: URI) => this._extensionResourceLoaderService.readExtensionResource(resource) }, this._grammarDefinitions || [], vscodeTextmate, this._loadOnigLib()); this._onDidCreateGrammarFactory(this._grammarDefinitions || []); @@ -221,6 +221,9 @@ export abstract class AbstractTextMateService extends Disposable implements ITex return null; } const r = await grammarFactory.createGrammar(languageId); + if (!r.grammar) { + return null; + } const tokenization = new TMTokenization(r.grammar, r.initialState, r.containsEmbeddedLanguages); tokenization.onDidEncounterLanguage((languageId) => { if (!this._encounteredLanguages[languageId]) { @@ -245,22 +248,23 @@ export abstract class AbstractTextMateService extends Disposable implements ITex } private _updateTheme(grammarFactory: TMGrammarFactory, colorTheme: IColorTheme, forceUpdate: boolean): void { - if (!forceUpdate && this._currentTheme && AbstractTextMateService.equalsTokenRules(this._currentTheme.settings, colorTheme.tokenColors)) { + if (!forceUpdate && this._currentTheme && this._currentTokenColorMap && AbstractTextMateService.equalsTokenRules(this._currentTheme.settings, colorTheme.tokenColors) && equalArray(this._currentTokenColorMap, colorTheme.tokenColorMap)) { return; } this._currentTheme = { name: colorTheme.label, settings: colorTheme.tokenColors }; - this._doUpdateTheme(grammarFactory, this._currentTheme); + this._currentTokenColorMap = colorTheme.tokenColorMap; + this._doUpdateTheme(grammarFactory, this._currentTheme, this._currentTokenColorMap); } - protected _doUpdateTheme(grammarFactory: TMGrammarFactory, theme: IRawTheme): void { - grammarFactory.setTheme(theme); - let colorMap = AbstractTextMateService._toColorMap(grammarFactory.getColorMap()); + protected _doUpdateTheme(grammarFactory: TMGrammarFactory, theme: IRawTheme, tokenColorMap: string[]): void { + grammarFactory.setTheme(theme, tokenColorMap); + let colorMap = AbstractTextMateService._toColorMap(tokenColorMap); let cssRules = generateTokensCSSForColorMap(colorMap); this._styleElement.innerHTML = cssRules; TokenizationRegistry.setColorMap(colorMap); } - private static equalsTokenRules(a: ITokenColorizationRule[] | null, b: ITokenColorizationRule[] | null): boolean { + private static equalsTokenRules(a: ITextMateThemingRule[] | null, b: ITextMateThemingRule[] | null): boolean { if (!b || !a || b.length !== a.length) { return false; } @@ -317,7 +321,7 @@ export abstract class AbstractTextMateService extends Disposable implements ITex return true; } - public async createGrammar(modeId: string): Promise { + public async createGrammar(modeId: string): Promise { const grammarFactory = await this._getOrCreateGrammarFactory(); const { grammar } = await grammarFactory.createGrammar(this._modeService.getLanguageIdentifier(modeId)!.id); return grammar; diff --git a/src/vs/workbench/services/textMate/browser/textMateService.ts b/src/vs/workbench/services/textMate/browser/textMateService.ts index ebd30c827780..0d5a26f663fc 100644 --- a/src/vs/workbench/services/textMate/browser/textMateService.ts +++ b/src/vs/workbench/services/textMate/browser/textMateService.ts @@ -8,25 +8,25 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { AbstractTextMateService } from 'vs/workbench/services/textMate/browser/abstractTextMateService'; import { IOnigLib } from 'vscode-textmate'; import { IModeService } from 'vs/editor/common/services/modeService'; -import { IFileService } from 'vs/platform/files/common/files'; import { ILogService } from 'vs/platform/log/common/log'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IStorageService } from 'vs/platform/storage/common/storage'; +import { IExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader'; export class TextMateService extends AbstractTextMateService { constructor( @IModeService modeService: IModeService, @IWorkbenchThemeService themeService: IWorkbenchThemeService, - @IFileService fileService: IFileService, + @IExtensionResourceLoaderService extensionResourceLoaderService: IExtensionResourceLoaderService, @INotificationService notificationService: INotificationService, @ILogService logService: ILogService, @IConfigurationService configurationService: IConfigurationService, @IStorageService storageService: IStorageService ) { - super(modeService, themeService, fileService, notificationService, logService, configurationService, storageService); + super(modeService, themeService, extensionResourceLoaderService, notificationService, logService, configurationService, storageService); } protected _loadVSCodeTextmate(): Promise { diff --git a/src/vs/workbench/services/textMate/common/TMGrammarFactory.ts b/src/vs/workbench/services/textMate/common/TMGrammarFactory.ts index a51b45def6b3..d4f2ced0b327 100644 --- a/src/vs/workbench/services/textMate/common/TMGrammarFactory.ts +++ b/src/vs/workbench/services/textMate/common/TMGrammarFactory.ts @@ -18,7 +18,7 @@ interface ITMGrammarFactoryHost { export interface ICreateGrammarResult { languageId: LanguageId; - grammar: IGrammar; + grammar: IGrammar | null; initialState: StackElement; containsEmbeddedLanguages: boolean; } @@ -102,8 +102,8 @@ export class TMGrammarFactory extends Disposable { return this._languageToScope2[languageId] ? true : false; } - public setTheme(theme: IRawTheme): void { - this._grammarRegistry.setTheme(theme); + public setTheme(theme: IRawTheme, colorMap: string[]): void { + this._grammarRegistry.setTheme(theme, colorMap); } public getColorMap(): string[] { diff --git a/src/vs/workbench/services/textMate/common/textMateService.ts b/src/vs/workbench/services/textMate/common/textMateService.ts index fb15c5027294..207b2d07b8b6 100644 --- a/src/vs/workbench/services/textMate/common/textMateService.ts +++ b/src/vs/workbench/services/textMate/common/textMateService.ts @@ -14,7 +14,7 @@ export interface ITextMateService { onDidEncounterLanguage: Event; - createGrammar(modeId: string): Promise; + createGrammar(modeId: string): Promise; } // -------------- Types "liberated" from vscode-textmate due to usage in /common/ diff --git a/src/vs/workbench/services/textMate/electron-browser/textMateService.ts b/src/vs/workbench/services/textMate/electron-browser/textMateService.ts index 228ac18d01cf..7a15f4676afe 100644 --- a/src/vs/workbench/services/textMate/electron-browser/textMateService.ts +++ b/src/vs/workbench/services/textMate/electron-browser/textMateService.ts @@ -8,7 +8,6 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { AbstractTextMateService } from 'vs/workbench/services/textMate/browser/abstractTextMateService'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; -import { IFileService } from 'vs/platform/files/common/files'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { ILogService } from 'vs/platform/log/common/log'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -24,6 +23,7 @@ import { MultilineTokensBuilder } from 'vs/editor/common/model/tokensStore'; import { TMGrammarFactory } from 'vs/workbench/services/textMate/common/TMGrammarFactory'; import { IModelContentChangedEvent } from 'vs/editor/common/model/textModelEvents'; import { IStorageService } from 'vs/platform/storage/common/storage'; +import { IExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader'; const RUN_TEXTMATE_IN_WORKER = false; @@ -117,14 +117,13 @@ export class TextMateWorkerHost { constructor( private readonly textMateService: TextMateService, - @IFileService private readonly _fileService: IFileService + @IExtensionResourceLoaderService private readonly _extensionResourceLoaderService: IExtensionResourceLoaderService, ) { } async readFile(_resource: UriComponents): Promise { const resource = URI.revive(_resource); - const content = await this._fileService.readFile(resource); - return content.value.toString(); + return this._extensionResourceLoaderService.readExtensionResource(resource); } async setTokens(_resource: UriComponents, versionId: number, tokens: Uint8Array): Promise { @@ -142,14 +141,14 @@ export class TextMateService extends AbstractTextMateService { constructor( @IModeService modeService: IModeService, @IWorkbenchThemeService themeService: IWorkbenchThemeService, - @IFileService fileService: IFileService, + @IExtensionResourceLoaderService extensionResourceLoaderService: IExtensionResourceLoaderService, @INotificationService notificationService: INotificationService, @ILogService logService: ILogService, @IConfigurationService configurationService: IConfigurationService, @IStorageService storageService: IStorageService, @IModelService private readonly _modelService: IModelService, ) { - super(modeService, themeService, fileService, notificationService, logService, configurationService, storageService); + super(modeService, themeService, extensionResourceLoaderService, notificationService, logService, configurationService, storageService); this._worker = null; this._workerProxy = null; this._tokenizers = Object.create(null); @@ -190,7 +189,7 @@ export class TextMateService extends AbstractTextMateService { this._killWorker(); if (RUN_TEXTMATE_IN_WORKER) { - const workerHost = new TextMateWorkerHost(this, this._fileService); + const workerHost = new TextMateWorkerHost(this, this._extensionResourceLoaderService); const worker = createWebWorker(this._modelService, { createData: { grammarDefinitions @@ -207,18 +206,18 @@ export class TextMateService extends AbstractTextMateService { return; } this._workerProxy = proxy; - if (this._currentTheme) { - this._workerProxy.acceptTheme(this._currentTheme); + if (this._currentTheme && this._currentTokenColorMap) { + this._workerProxy.acceptTheme(this._currentTheme, this._currentTokenColorMap); } this._modelService.getModels().forEach((model) => this._onModelAdded(model)); }); } } - protected _doUpdateTheme(grammarFactory: TMGrammarFactory, theme: IRawTheme): void { - super._doUpdateTheme(grammarFactory, theme); - if (this._currentTheme && this._workerProxy) { - this._workerProxy.acceptTheme(this._currentTheme); + protected _doUpdateTheme(grammarFactory: TMGrammarFactory, theme: IRawTheme, colorMap: string[]): void { + super._doUpdateTheme(grammarFactory, theme, colorMap); + if (this._currentTheme && this._currentTokenColorMap && this._workerProxy) { + this._workerProxy.acceptTheme(this._currentTheme, this._currentTokenColorMap); } } diff --git a/src/vs/workbench/services/textMate/electron-browser/textMateWorker.ts b/src/vs/workbench/services/textMate/electron-browser/textMateWorker.ts index 481371dc396c..098c59296820 100644 --- a/src/vs/workbench/services/textMate/electron-browser/textMateWorker.ts +++ b/src/vs/workbench/services/textMate/electron-browser/textMateWorker.ts @@ -185,9 +185,9 @@ export class TextMateWorker { return this._grammarCache[languageId]; } - public acceptTheme(theme: IRawTheme): void { + public acceptTheme(theme: IRawTheme, colorMap: string[]): void { if (this._grammarFactory) { - this._grammarFactory.setTheme(theme); + this._grammarFactory.setTheme(theme, colorMap); } } diff --git a/src/vs/workbench/services/textfile/browser/browserTextFileService.ts b/src/vs/workbench/services/textfile/browser/browserTextFileService.ts index e040466be481..b8877c162fd7 100644 --- a/src/vs/workbench/services/textfile/browser/browserTextFileService.ts +++ b/src/vs/workbench/services/textfile/browser/browserTextFileService.ts @@ -35,7 +35,7 @@ export class BrowserTextFileService extends AbstractTextFileService { return false; // no dirty: no veto } - if (!this.isHotExitEnabled) { + if (!this.filesConfigurationService.isHotExitEnabled) { return true; // dirty without backup: veto } @@ -46,7 +46,7 @@ export class BrowserTextFileService extends AbstractTextFileService { const model = this.models.get(dirtyResource); hasBackup = !!(model?.hasBackup()); } else if (dirtyResource.scheme === Schemas.untitled) { - hasBackup = this.untitledEditorService.hasBackup(dirtyResource); + hasBackup = this.untitledTextEditorService.hasBackup(dirtyResource); } if (!hasBackup) { diff --git a/src/vs/workbench/services/textfile/browser/textFileService.ts b/src/vs/workbench/services/textfile/browser/textFileService.ts index 4a75134f4e78..dfd74a862ce1 100644 --- a/src/vs/workbench/services/textfile/browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/browser/textFileService.ts @@ -5,32 +5,28 @@ import * as nls from 'vs/nls'; import { URI } from 'vs/base/common/uri'; -import * as errors from 'vs/base/common/errors'; -import * as objects from 'vs/base/common/objects'; -import { Event, Emitter } from 'vs/base/common/event'; +import { Emitter, AsyncEmitter } from 'vs/base/common/event'; import * as platform from 'vs/base/common/platform'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; -import { IResult, ITextFileOperationResult, ITextFileService, ITextFileStreamContent, IAutoSaveConfiguration, AutoSaveMode, SaveReason, ITextFileEditorModelManager, ITextFileEditorModel, ModelState, ISaveOptions, AutoSaveContext, IWillMoveEvent, ITextFileContent, IResourceEncodings, IReadTextFileOptions, IWriteTextFileOptions, toBufferOrReadable, TextFileOperationError, TextFileOperationResult } from 'vs/workbench/services/textfile/common/textfiles'; -import { ConfirmResult, IRevertOptions } from 'vs/workbench/common/editor'; +import { IResult, ITextFileOperationResult, ITextFileService, ITextFileStreamContent, ITextFileEditorModelManager, ITextFileEditorModel, ModelState, ITextFileContent, IResourceEncodings, IReadTextFileOptions, IWriteTextFileOptions, toBufferOrReadable, TextFileOperationError, TextFileOperationResult, FileOperationWillRunEvent, FileOperationDidRunEvent, ITextFileSaveOptions } from 'vs/workbench/services/textfile/common/textfiles'; +import { SaveReason, IRevertOptions } from 'vs/workbench/common/editor'; import { ILifecycleService, ShutdownReason, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { IFileService, IFilesConfiguration, FileOperationError, FileOperationResult, AutoSaveConfiguration, HotExitConfiguration, IFileStatWithMetadata, ICreateFileOptions } from 'vs/platform/files/common/files'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IFileService, FileOperationError, FileOperationResult, HotExitConfiguration, IFileStatWithMetadata, ICreateFileOptions, FileOperation } from 'vs/platform/files/common/files'; import { Disposable } from 'vs/base/common/lifecycle'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; -import { UntitledEditorModel } from 'vs/workbench/common/editor/untitledEditorModel'; +import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; +import { UntitledTextEditorModel } from 'vs/workbench/common/editor/untitledTextEditorModel'; import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ResourceMap } from 'vs/base/common/map'; import { Schemas } from 'vs/base/common/network'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; -import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { createTextBufferFactoryFromSnapshot, createTextBufferFactoryFromStream } from 'vs/editor/common/model/textModel'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; +import { INotificationService } from 'vs/platform/notification/common/notification'; import { isEqualOrParent, isEqual, joinPath, dirname, extname, basename, toLocalResource } from 'vs/base/common/resources'; -import { getConfirmMessage, IDialogService, IFileDialogService, ISaveDialogOptions, IConfirmation } from 'vs/platform/dialogs/common/dialogs'; +import { IDialogService, IFileDialogService, ISaveDialogOptions, IConfirmation, ConfirmResult } from 'vs/platform/dialogs/common/dialogs'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { coalesce } from 'vs/base/common/arrays'; @@ -39,6 +35,8 @@ import { VSBuffer } from 'vs/base/common/buffer'; import { ITextSnapshot } from 'vs/editor/common/model'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; +import { IFilesConfigurationService, AutoSaveMode } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; +import { CancellationToken } from 'vs/base/common/cancellation'; /** * The workbench file service implementation implements the raw file service spec and adds additional methods on top. @@ -47,55 +45,42 @@ export abstract class AbstractTextFileService extends Disposable implements ITex _serviceBrand: undefined; - private readonly _onAutoSaveConfigurationChange: Emitter = this._register(new Emitter()); - readonly onAutoSaveConfigurationChange: Event = this._onAutoSaveConfigurationChange.event; + //#region events - private readonly _onFilesAssociationChange: Emitter = this._register(new Emitter()); - readonly onFilesAssociationChange: Event = this._onFilesAssociationChange.event; + private _onWillRunOperation = this._register(new AsyncEmitter()); + readonly onWillRunOperation = this._onWillRunOperation.event; - private readonly _onWillMove = this._register(new Emitter()); - readonly onWillMove: Event = this._onWillMove.event; + private _onDidRunOperation = this._register(new Emitter()); + readonly onDidRunOperation = this._onDidRunOperation.event; + + //#endregion private _models: TextFileEditorModelManager; get models(): ITextFileEditorModelManager { return this._models; } abstract get encoding(): IResourceEncodings; - private currentFilesAssociationConfig: { [key: string]: string; }; - private configuredAutoSaveDelay?: number; - private configuredAutoSaveOnFocusChange: boolean | undefined; - private configuredAutoSaveOnWindowChange: boolean | undefined; - private configuredHotExit: string | undefined; - private autoSaveContext: IContextKey; - constructor( @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IFileService protected readonly fileService: IFileService, - @IUntitledEditorService protected readonly untitledEditorService: IUntitledEditorService, + @IUntitledTextEditorService protected readonly untitledTextEditorService: IUntitledTextEditorService, @ILifecycleService private readonly lifecycleService: ILifecycleService, @IInstantiationService protected readonly instantiationService: IInstantiationService, - @IConfigurationService private readonly configurationService: IConfigurationService, @IModeService private readonly modeService: IModeService, @IModelService private readonly modelService: IModelService, @IWorkbenchEnvironmentService protected readonly environmentService: IWorkbenchEnvironmentService, @INotificationService private readonly notificationService: INotificationService, @IBackupFileService private readonly backupFileService: IBackupFileService, @IHistoryService private readonly historyService: IHistoryService, - @IContextKeyService contextKeyService: IContextKeyService, @IDialogService private readonly dialogService: IDialogService, @IFileDialogService private readonly fileDialogService: IFileDialogService, @IEditorService private readonly editorService: IEditorService, - @ITextResourceConfigurationService protected readonly textResourceConfigurationService: ITextResourceConfigurationService + @ITextResourceConfigurationService protected readonly textResourceConfigurationService: ITextResourceConfigurationService, + @IFilesConfigurationService protected readonly filesConfigurationService: IFilesConfigurationService ) { super(); this._models = this._register(instantiationService.createInstance(TextFileEditorModelManager)); - this.autoSaveContext = AutoSaveContext.bindTo(contextKeyService); - - const configuration = configurationService.getValue(); - this.currentFilesAssociationConfig = configuration?.files?.associations; - - this.onFilesConfigurationChange(configuration); this.registerListeners(); } @@ -108,12 +93,16 @@ export abstract class AbstractTextFileService extends Disposable implements ITex this.lifecycleService.onBeforeShutdown(event => event.veto(this.onBeforeShutdown(event.reason))); this.lifecycleService.onShutdown(this.dispose, this); - // Files configuration changes - this._register(this.configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration('files')) { - this.onFilesConfigurationChange(this.configurationService.getValue()); - } - })); + // Auto save changes + this._register(this.filesConfigurationService.onAutoSaveConfigurationChange(() => this.onAutoSaveConfigurationChange())); + } + + private onAutoSaveConfigurationChange(): void { + + // save all dirty when enabling auto save + if (this.filesConfigurationService.getAutoSaveMode() !== AutoSaveMode.OFF) { + this.saveAll(); + } } protected onBeforeShutdown(reason: ShutdownReason): boolean | Promise { @@ -124,7 +113,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex // If auto save is enabled, save all files and then check again for dirty files // We DO NOT run any save participant if we are in the shutdown phase for performance reasons - if (this.getAutoSaveMode() !== AutoSaveMode.OFF) { + if (this.filesConfigurationService.getAutoSaveMode() !== AutoSaveMode.OFF) { return this.saveAll(false /* files only */, { skipSaveParticipants: true }).then(() => { // If we still have dirty files, we either have untitled ones or files that cannot be saved @@ -148,7 +137,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex private handleDirtyBeforeShutdown(dirty: URI[], reason: ShutdownReason): boolean | Promise { // If hot exit is enabled, backup dirty files and allow to exit without confirmation - if (this.isHotExitEnabled) { + if (this.filesConfigurationService.isHotExitEnabled) { return this.backupBeforeShutdown(dirty, reason).then(didBackup => { if (didBackup) { return this.noVeto({ cleanUpBackups: false }); // no veto and no backup cleanup (since backup was successful) @@ -176,7 +165,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex let doBackup: boolean | undefined; switch (reason) { case ShutdownReason.CLOSE: - if (this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY && this.configuredHotExit === HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) { + if (this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY && this.filesConfigurationService.hotExitConfiguration === HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) { doBackup = true; // backup if a folder is open and onExitAndWindowClose is configured } else if (await this.getWindowCount() > 1 || platform.isMacintosh) { doBackup = false; // do not backup if a window is closed that does not cause quitting of the application @@ -194,7 +183,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex break; case ShutdownReason.LOAD: - if (this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY && this.configuredHotExit === HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) { + if (this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY && this.filesConfigurationService.hotExitConfiguration === HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) { doBackup = true; // backup if a folder is open and onExitAndWindowClose is configured } else { doBackup = false; // do not backup because we are switching contexts @@ -239,18 +228,18 @@ export abstract class AbstractTextFileService extends Disposable implements ITex // Handle untitled resources await Promise.all(untitledResources - .filter(untitled => this.untitledEditorService.exists(untitled)) - .map(async untitled => (await this.untitledEditorService.loadOrCreate({ resource: untitled })).backup())); + .filter(untitled => this.untitledTextEditorService.exists(untitled)) + .map(async untitled => (await this.untitledTextEditorService.loadOrCreate({ resource: untitled })).backup())); } private async confirmBeforeShutdown(): Promise { - const confirm = await this.confirmSave(); + const confirm = await this.fileDialogService.showSaveConfirm(this.getDirty()); // Save if (confirm === ConfirmResult.SAVE) { const result = await this.saveAll(true /* includeUntitled */, { skipSaveParticipants: true }); - if (result.results.some(r => !r.success)) { + if (result.results.some(r => r.error)) { return true; // veto if some saves failed } @@ -262,7 +251,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex // Make sure to revert untitled so that they do not restore // see https://github.com/Microsoft/vscode/issues/29572 - this.untitledEditorService.revertAll(); + this.untitledTextEditorService.revertAll(); return this.noVeto({ cleanUpBackups: true }); } @@ -295,61 +284,6 @@ export abstract class AbstractTextFileService extends Disposable implements ITex await this.backupFileService.discardAllWorkspaceBackups(); } - protected onFilesConfigurationChange(configuration: IFilesConfiguration): void { - const wasAutoSaveEnabled = (this.getAutoSaveMode() !== AutoSaveMode.OFF); - - const autoSaveMode = configuration?.files?.autoSave || AutoSaveConfiguration.OFF; - this.autoSaveContext.set(autoSaveMode); - switch (autoSaveMode) { - case AutoSaveConfiguration.AFTER_DELAY: - this.configuredAutoSaveDelay = configuration?.files?.autoSaveDelay; - this.configuredAutoSaveOnFocusChange = false; - this.configuredAutoSaveOnWindowChange = false; - break; - - case AutoSaveConfiguration.ON_FOCUS_CHANGE: - this.configuredAutoSaveDelay = undefined; - this.configuredAutoSaveOnFocusChange = true; - this.configuredAutoSaveOnWindowChange = false; - break; - - case AutoSaveConfiguration.ON_WINDOW_CHANGE: - this.configuredAutoSaveDelay = undefined; - this.configuredAutoSaveOnFocusChange = false; - this.configuredAutoSaveOnWindowChange = true; - break; - - default: - this.configuredAutoSaveDelay = undefined; - this.configuredAutoSaveOnFocusChange = false; - this.configuredAutoSaveOnWindowChange = false; - break; - } - - // Emit as event - this._onAutoSaveConfigurationChange.fire(this.getAutoSaveConfiguration()); - - // save all dirty when enabling auto save - if (!wasAutoSaveEnabled && this.getAutoSaveMode() !== AutoSaveMode.OFF) { - this.saveAll(); - } - - // Check for change in files associations - const filesAssociation = configuration?.files?.associations; - if (!objects.equals(this.currentFilesAssociationConfig, filesAssociation)) { - this.currentFilesAssociationConfig = filesAssociation; - this._onFilesAssociationChange.fire(); - } - - // Hot exit - const hotExitMode = configuration?.files?.hotExit; - if (hotExitMode === HotExitConfiguration.OFF || hotExitMode === HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) { - this.configuredHotExit = hotExitMode; - } else { - this.configuredHotExit = HotExitConfiguration.ON_EXIT; - } - } - //#endregion //#region primitives (read, create, move, delete, update) @@ -409,6 +343,10 @@ export abstract class AbstractTextFileService extends Disposable implements ITex } async create(resource: URI, value?: string | ITextSnapshot, options?: ICreateFileOptions): Promise { + + // before event + await this._onWillRunOperation.fireAsync({ operation: FileOperation.CREATE, target: resource }, CancellationToken.None); + const stat = await this.doCreate(resource, value, options); // If we had an existing model for the given resource, load @@ -420,6 +358,9 @@ export abstract class AbstractTextFileService extends Disposable implements ITex await existingModel.revert(); } + // after event + this._onDidRunOperation.fire(new FileOperationDidRunEvent(FileOperation.CREATE, resource)); + return stat; } @@ -432,23 +373,37 @@ export abstract class AbstractTextFileService extends Disposable implements ITex } async delete(resource: URI, options?: { useTrash?: boolean, recursive?: boolean }): Promise { - const dirtyFiles = this.getDirty().filter(dirty => isEqualOrParent(dirty, resource)); + // before event + await this._onWillRunOperation.fireAsync({ operation: FileOperation.DELETE, target: resource }, CancellationToken.None); + + const dirtyFiles = this.getDirty().filter(dirty => isEqualOrParent(dirty, resource)); await this.revertAll(dirtyFiles, { soft: true }); - return this.fileService.del(resource, options); + await this.fileService.del(resource, options); + + // after event + this._onDidRunOperation.fire(new FileOperationDidRunEvent(FileOperation.DELETE, resource)); } async move(source: URI, target: URI, overwrite?: boolean): Promise { + return this.moveOrCopy(source, target, true, overwrite); + } - // await onWillMove event joiners - await this.notifyOnWillMove(source, target); + async copy(source: URI, target: URI, overwrite?: boolean): Promise { + return this.moveOrCopy(source, target, false, overwrite); + } + + private async moveOrCopy(source: URI, target: URI, move: boolean, overwrite?: boolean): Promise { + + // before event + await this._onWillRunOperation.fireAsync({ operation: move ? FileOperation.MOVE : FileOperation.COPY, target, source }, CancellationToken.None); // find all models that related to either source or target (can be many if resource is a folder) const sourceModels: ITextFileEditorModel[] = []; const conflictingModels: ITextFileEditorModel[] = []; for (const model of this.getFileModels()) { - const resource = model.getResource(); + const resource = model.resource; if (isEqualOrParent(resource, target, false /* do not ignorecase, see https://github.com/Microsoft/vscode/issues/56384 */)) { conflictingModels.push(model); @@ -464,7 +419,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex type ModelToRestore = { resource: URI; snapshot?: ITextSnapshot }; const modelsToRestore: ModelToRestore[] = []; for (const sourceModel of sourceModels) { - const sourceModelResource = sourceModel.getResource(); + const sourceModelResource = sourceModel.resource; // If the source is the actual model, just use target as new resource let modelToRestoreResource: URI; @@ -486,15 +441,19 @@ export abstract class AbstractTextFileService extends Disposable implements ITex modelsToRestore.push(modelToRestore); } - // in order to move, we need to soft revert all dirty models, + // in order to move and copy, we need to soft revert all dirty models, // both from the source as well as the target if any const dirtyModels = [...sourceModels, ...conflictingModels].filter(model => model.isDirty()); - await this.revertAll(dirtyModels.map(dirtyModel => dirtyModel.getResource()), { soft: true }); + await this.revertAll(dirtyModels.map(dirtyModel => dirtyModel.resource), { soft: true }); // now we can rename the source to target via file operation let stat: IFileStatWithMetadata; try { - stat = await this.fileService.move(source, target, overwrite); + if (move) { + stat = await this.fileService.move(source, target, overwrite); + } else { + stat = await this.fileService.copy(source, target, overwrite); + } } catch (error) { // in case of any error, ensure to set dirty flag back @@ -521,32 +480,17 @@ export abstract class AbstractTextFileService extends Disposable implements ITex } })); - return stat; - } - - private async notifyOnWillMove(source: URI, target: URI): Promise { - const waitForPromises: Promise[] = []; - - // fire event - this._onWillMove.fire({ - oldResource: source, - newResource: target, - waitUntil(promise: Promise) { - waitForPromises.push(promise.then(undefined, errors.onUnexpectedError)); - } - }); - - // prevent async waitUntil-calls - Object.freeze(waitForPromises); + // after event + this._onDidRunOperation.fire(new FileOperationDidRunEvent(move ? FileOperation.MOVE : FileOperation.COPY, target, source)); - await Promise.all(waitForPromises); + return stat; } //#endregion //#region save/revert - async save(resource: URI, options?: ISaveOptions): Promise { + async save(resource: URI, options?: ITextFileSaveOptions): Promise { // Run a forced save if we detect the file is not dirty so that save participants can still run if (options?.force && this.fileService.canHandleResource(resource) && !this.isDirty(resource)) { @@ -560,39 +504,12 @@ export abstract class AbstractTextFileService extends Disposable implements ITex } } - const result = await this.saveAll([resource], options); - - return result.results.length === 1 && !!result.results[0].success; - } - - async confirmSave(resources?: URI[]): Promise { - if (this.environmentService.isExtensionDevelopment) { - if (!this.environmentService.args['extension-development-confirm-save']) { - return ConfirmResult.DONT_SAVE; // no veto when we are in extension dev mode because we cannot assume we run interactive (e.g. tests) - } - } - - const resourcesToConfirm = this.getDirty(resources); - if (resourcesToConfirm.length === 0) { - return ConfirmResult.DONT_SAVE; - } - return promptSave(this.dialogService, resourcesToConfirm); - } - - async confirmOverwrite(resource: URI): Promise { - const confirm: IConfirmation = { - message: nls.localize('confirmOverwrite', "'{0}' already exists. Do you want to replace it?", basename(resource)), - detail: nls.localize('irreversible', "A file or folder with the same name already exists in the folder {0}. Replacing it will overwrite its current contents.", basename(dirname(resource))), - primaryButton: nls.localize({ key: 'replaceButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Replace"), - type: 'warning' - }; - - return (await this.dialogService.confirm(confirm)).confirmed; + return !(await this.saveAll([resource], options)).results.some(result => result.error); } - saveAll(includeUntitled?: boolean, options?: ISaveOptions): Promise; - saveAll(resources: URI[], options?: ISaveOptions): Promise; - saveAll(arg1?: boolean | URI[], options?: ISaveOptions): Promise { + saveAll(includeUntitled?: boolean, options?: ITextFileSaveOptions): Promise; + saveAll(resources: URI[], options?: ITextFileSaveOptions): Promise; + saveAll(arg1?: boolean | URI[], options?: ITextFileSaveOptions): Promise { // get all dirty let toSave: URI[] = []; @@ -616,7 +533,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex return this.doSaveAll(filesToSave, untitledToSave, options); } - private async doSaveAll(fileResources: URI[], untitledResources: URI[], options?: ISaveOptions): Promise { + private async doSaveAll(fileResources: URI[], untitledResources: URI[], options?: ITextFileSaveOptions): Promise { // Handle files first that can just be saved const result = await this.doSaveAllFiles(fileResources, options); @@ -624,11 +541,11 @@ export abstract class AbstractTextFileService extends Disposable implements ITex // Preflight for untitled to handle cancellation from the dialog const targetsForUntitled: URI[] = []; for (const untitled of untitledResources) { - if (this.untitledEditorService.exists(untitled)) { + if (this.untitledTextEditorService.exists(untitled)) { let targetUri: URI; // Untitled with associated file path don't need to prompt - if (this.untitledEditorService.hasAssociatedFilePath(untitled)) { + if (this.untitledTextEditorService.hasAssociatedFilePath(untitled)) { targetUri = toLocalResource(untitled, this.environmentService.configuration.remoteAuthority); } @@ -653,7 +570,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex result.results.push({ source: untitledResources[index], target: uri, - success: !!uri + error: !uri // the operation was canceled or failed, so mark as error }); })); @@ -721,7 +638,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex return options; } - private async doSaveAllFiles(resources?: URI[], options: ISaveOptions = Object.create(null)): Promise { + private async doSaveAllFiles(resources?: URI[], options: ITextFileSaveOptions = Object.create(null)): Promise { const dirtyFileModels = this.getDirtyFileModels(Array.isArray(resources) ? resources : undefined /* Save All */) .filter(model => { if ((model.hasState(ModelState.CONFLICT) || model.hasState(ModelState.ERROR)) && (options.reason === SaveReason.AUTO || options.reason === SaveReason.FOCUS_CHANGE || options.reason === SaveReason.WINDOW_CHANGE)) { @@ -732,19 +649,20 @@ export abstract class AbstractTextFileService extends Disposable implements ITex }); const mapResourceToResult = new ResourceMap(); - dirtyFileModels.forEach(m => { - mapResourceToResult.set(m.getResource(), { - source: m.getResource() + dirtyFileModels.forEach(dirtyModel => { + mapResourceToResult.set(dirtyModel.resource, { + source: dirtyModel.resource }); }); await Promise.all(dirtyFileModels.map(async model => { await model.save(options); - if (!model.isDirty()) { - const result = mapResourceToResult.get(model.getResource()); + // If model is still dirty, mark the resulting operation as error + if (model.isDirty()) { + const result = mapResourceToResult.get(model.resource); if (result) { - result.success = true; + result.error = true; } } })); @@ -769,7 +687,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex return this.getFileModels(resources).filter(model => model.isDirty()); } - async saveAs(resource: URI, targetResource?: URI, options?: ISaveOptions): Promise { + async saveAs(resource: URI, targetResource?: URI, options?: ITextFileSaveOptions): Promise { // Get to target resource if (!targetResource) { @@ -796,14 +714,14 @@ export abstract class AbstractTextFileService extends Disposable implements ITex return this.doSaveAs(resource, targetResource, options); } - private async doSaveAs(resource: URI, target: URI, options?: ISaveOptions): Promise { + private async doSaveAs(resource: URI, target: URI, options?: ITextFileSaveOptions): Promise { // Retrieve text model from provided resource if any - let model: ITextFileEditorModel | UntitledEditorModel | undefined; + let model: ITextFileEditorModel | UntitledTextEditorModel | undefined; if (this.fileService.canHandleResource(resource)) { model = this._models.get(resource); - } else if (resource.scheme === Schemas.untitled && this.untitledEditorService.exists(resource)) { - model = await this.untitledEditorService.loadOrCreate({ resource }); + } else if (resource.scheme === Schemas.untitled && this.untitledTextEditorService.exists(resource)) { + model = await this.untitledTextEditorService.loadOrCreate({ resource }); } // We have a model: Use it (can be null e.g. if this file is binary and not a text file or was never opened before) @@ -830,7 +748,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex return target; } - private async doSaveTextFileAs(sourceModel: ITextFileEditorModel | UntitledEditorModel, resource: URI, target: URI, options?: ISaveOptions): Promise { + private async doSaveTextFileAs(sourceModel: ITextFileEditorModel | UntitledTextEditorModel, resource: URI, target: URI, options?: ITextFileSaveOptions): Promise { // Prefer an existing model if it is already loaded for the given target resource let targetExists: boolean = false; @@ -858,7 +776,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex // path. This can happen if the file was created after the untitled file was opened. // See https://github.com/Microsoft/vscode/issues/67946 let write: boolean; - if (sourceModel instanceof UntitledEditorModel && sourceModel.hasAssociatedFilePath && targetExists && isEqual(target, toLocalResource(sourceModel.getResource(), this.environmentService.configuration.remoteAuthority))) { + if (sourceModel instanceof UntitledTextEditorModel && sourceModel.hasAssociatedFilePath && targetExists && isEqual(target, toLocalResource(sourceModel.resource, this.environmentService.configuration.remoteAuthority))) { write = await this.confirmOverwrite(target); } else { write = true; @@ -900,8 +818,19 @@ export abstract class AbstractTextFileService extends Disposable implements ITex } } + private async confirmOverwrite(resource: URI): Promise { + const confirm: IConfirmation = { + message: nls.localize('confirmOverwrite', "'{0}' already exists. Do you want to replace it?", basename(resource)), + detail: nls.localize('irreversible', "A file or folder with the name '{0}' already exists in the folder '{1}'. Replacing it will overwrite its current contents.", basename(resource), basename(dirname(resource))), + primaryButton: nls.localize({ key: 'replaceButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Replace"), + type: 'warning' + }; + + return (await this.dialogService.confirm(confirm)).confirmed; + } + private suggestFileName(untitledResource: URI): URI { - const untitledFileName = this.untitledEditorService.suggestFileName(untitledResource); + const untitledFileName = this.untitledTextEditorService.suggestFileName(untitledResource); const remoteAuthority = this.environmentService.configuration.remoteAuthority; const schemeFilter = remoteAuthority ? Schemas.vscodeRemote : Schemas.file; @@ -920,9 +849,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex } async revert(resource: URI, options?: IRevertOptions): Promise { - const result = await this.revertAll([resource], options); - - return result.results.length === 1 && !!result.results[0].success; + return !(await this.revertAll([resource], options)).results.some(result => result.error); } async revertAll(resources?: URI[], options?: IRevertOptions): Promise { @@ -931,8 +858,8 @@ export abstract class AbstractTextFileService extends Disposable implements ITex const revertOperationResult = await this.doRevertAllFiles(resources, options); // Revert untitled - const untitledReverted = this.untitledEditorService.revertAll(resources); - untitledReverted.forEach(untitled => revertOperationResult.results.push({ source: untitled, success: true })); + const untitledReverted = this.untitledTextEditorService.revertAll(resources); + untitledReverted.forEach(untitled => revertOperationResult.results.push({ source: untitled })); return revertOperationResult; } @@ -941,30 +868,28 @@ export abstract class AbstractTextFileService extends Disposable implements ITex const fileModels = options?.force ? this.getFileModels(resources) : this.getDirtyFileModels(resources); const mapResourceToResult = new ResourceMap(); - fileModels.forEach(m => { - mapResourceToResult.set(m.getResource(), { - source: m.getResource() + fileModels.forEach(fileModel => { + mapResourceToResult.set(fileModel.resource, { + source: fileModel.resource }); }); await Promise.all(fileModels.map(async model => { try { - await model.revert(options?.soft); + await model.revert(options); - if (!model.isDirty()) { - const result = mapResourceToResult.get(model.getResource()); + // If model is still dirty, mark the resulting operation as error + if (model.isDirty()) { + const result = mapResourceToResult.get(model.resource); if (result) { - result.success = true; + result.error = true; } } } catch (error) { - // FileNotFound means the file got deleted meanwhile, so still record as successful revert + // FileNotFound means the file got deleted meanwhile, so ignore it if ((error).fileOperationResult === FileOperationResult.FILE_NOT_FOUND) { - const result = mapResourceToResult.get(model.getResource()); - if (result) { - result.success = true; - } + return; } // Otherwise bubble up the error @@ -980,10 +905,10 @@ export abstract class AbstractTextFileService extends Disposable implements ITex getDirty(resources?: URI[]): URI[] { // Collect files - const dirty = this.getDirtyFileModels(resources).map(m => m.getResource()); + const dirty = this.getDirtyFileModels(resources).map(dirtyFileModel => dirtyFileModel.resource); // Add untitled ones - dirty.push(...this.untitledEditorService.getDirty(resources)); + dirty.push(...this.untitledTextEditorService.getDirty(resources)); return dirty; } @@ -996,39 +921,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex } // Check for dirty untitled - return this.untitledEditorService.getDirty().some(dirty => !resource || dirty.toString() === resource.toString()); - } - - //#endregion - - //#region config - - getAutoSaveMode(): AutoSaveMode { - if (this.configuredAutoSaveOnFocusChange) { - return AutoSaveMode.ON_FOCUS_CHANGE; - } - - if (this.configuredAutoSaveOnWindowChange) { - return AutoSaveMode.ON_WINDOW_CHANGE; - } - - if (this.configuredAutoSaveDelay && this.configuredAutoSaveDelay > 0) { - return this.configuredAutoSaveDelay <= 1000 ? AutoSaveMode.AFTER_SHORT_DELAY : AutoSaveMode.AFTER_LONG_DELAY; - } - - return AutoSaveMode.OFF; - } - - getAutoSaveConfiguration(): IAutoSaveConfiguration { - return { - autoSaveDelay: this.configuredAutoSaveDelay && this.configuredAutoSaveDelay > 0 ? this.configuredAutoSaveDelay : undefined, - autoSaveFocusChange: !!this.configuredAutoSaveOnFocusChange, - autoSaveApplicationChange: !!this.configuredAutoSaveOnWindowChange - }; - } - - get isHotExitEnabled(): boolean { - return !this.environmentService.isExtensionDevelopment && this.configuredHotExit !== HotExitConfiguration.OFF; + return this.untitledTextEditorService.getDirty().some(dirty => !resource || dirty.toString() === resource.toString()); } //#endregion @@ -1041,26 +934,3 @@ export abstract class AbstractTextFileService extends Disposable implements ITex super.dispose(); } } - -export async function promptSave(dialogService: IDialogService, resourcesToConfirm: readonly URI[]) { - const message = resourcesToConfirm.length === 1 - ? nls.localize('saveChangesMessage', "Do you want to save the changes you made to {0}?", basename(resourcesToConfirm[0])) - : getConfirmMessage(nls.localize('saveChangesMessages', "Do you want to save the changes to the following {0} files?", resourcesToConfirm.length), resourcesToConfirm); - - const buttons: string[] = [ - resourcesToConfirm.length > 1 ? nls.localize({ key: 'saveAll', comment: ['&& denotes a mnemonic'] }, "&&Save All") : nls.localize({ key: 'save', comment: ['&& denotes a mnemonic'] }, "&&Save"), - nls.localize({ key: 'dontSave', comment: ['&& denotes a mnemonic'] }, "Do&&n't Save"), - nls.localize('cancel', "Cancel") - ]; - - const { choice } = await dialogService.show(Severity.Warning, message, buttons, { - cancelId: 2, - detail: nls.localize('saveChangesDetail', "Your changes will be lost if you don't save them.") - }); - - switch (choice) { - case 0: return ConfirmResult.SAVE; - case 1: return ConfirmResult.DONT_SAVE; - default: return ConfirmResult.CANCEL; - } -} diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index c4191ed4ed39..22fedc32e0f0 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -4,18 +4,18 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import { Event, Emitter } from 'vs/base/common/event'; +import { Emitter } from 'vs/base/common/event'; import { guessMimeTypes } from 'vs/base/common/mime'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { URI } from 'vs/base/common/uri'; import { isUndefinedOrNull, assertIsDefined } from 'vs/base/common/types'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { ITextFileService, IAutoSaveConfiguration, ModelState, ITextFileEditorModel, ISaveOptions, ISaveErrorHandler, ISaveParticipant, StateChange, SaveReason, ITextFileStreamContent, ILoadOptions, LoadReason, IResolvedTextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; -import { EncodingMode } from 'vs/workbench/common/editor'; +import { ITextFileService, ModelState, ITextFileEditorModel, ISaveErrorHandler, ISaveParticipant, StateChange, ITextFileStreamContent, ILoadOptions, LoadReason, IResolvedTextFileEditorModel, ITextFileSaveOptions } from 'vs/workbench/services/textfile/common/textfiles'; +import { EncodingMode, IRevertOptions, SaveReason } from 'vs/workbench/common/editor'; import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; -import { IFileService, FileOperationError, FileOperationResult, CONTENT_CHANGE_EVENT_BUFFER_DELAY, FileChangesEvent, FileChangeType, IFileStatWithMetadata, ETAG_DISABLED } from 'vs/platform/files/common/files'; +import { IFileService, FileOperationError, FileOperationResult, CONTENT_CHANGE_EVENT_BUFFER_DELAY, FileChangesEvent, FileChangeType, IFileStatWithMetadata, ETAG_DISABLED, FileSystemProviderCapabilities } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IModelService } from 'vs/editor/common/services/modelService'; @@ -29,9 +29,12 @@ import { ILogService } from 'vs/platform/log/common/log'; import { isEqual, isEqualOrParent, extname, basename, joinPath } from 'vs/base/common/resources'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Schemas } from 'vs/base/common/network'; +import { IWorkingCopyService, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { IFilesConfigurationService, IAutoSaveConfiguration } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; export interface IBackupMetaData { mtime: number; + ctime: number; size: number; etag: string; orphaned: boolean; @@ -69,11 +72,16 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil private static saveParticipant: ISaveParticipant | null; static setSaveParticipant(handler: ISaveParticipant | null): void { TextFileEditorModel.saveParticipant = handler; } - private readonly _onDidContentChange: Emitter = this._register(new Emitter()); - readonly onDidContentChange: Event = this._onDidContentChange.event; + private readonly _onDidContentChange = this._register(new Emitter()); + readonly onDidContentChange = this._onDidContentChange.event; - private readonly _onDidStateChange: Emitter = this._register(new Emitter()); - readonly onDidStateChange: Event = this._onDidStateChange.event; + private readonly _onDidStateChange = this._register(new Emitter()); + readonly onDidStateChange = this._onDidStateChange.event; + + private readonly _onDidChangeDirty = this._register(new Emitter()); + readonly onDidChangeDirty = this._onDidChangeDirty.event; + + readonly capabilities = WorkingCopyCapabilities.AutoSave; private contentEncoding: string | undefined; // encoding as reported from disk @@ -100,7 +108,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil private disposed = false; constructor( - private resource: URI, + public readonly resource: URI, private preferredEncoding: string | undefined, // encoding as chosen by the user private preferredMode: string | undefined, // mode as chosen by the user @INotificationService private readonly notificationService: INotificationService, @@ -113,19 +121,24 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil @IBackupFileService private readonly backupFileService: IBackupFileService, @IEnvironmentService private readonly environmentService: IEnvironmentService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, - @ILogService private readonly logService: ILogService + @ILogService private readonly logService: ILogService, + @IWorkingCopyService private readonly workingCopyService: IWorkingCopyService, + @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService ) { super(modelService, modeService); - this.updateAutoSaveConfiguration(textFileService.getAutoSaveConfiguration()); + this.updateAutoSaveConfiguration(filesConfigurationService.getAutoSaveConfiguration()); + + // Make known to working copy service + this._register(this.workingCopyService.registerWorkingCopy(this)); this.registerListeners(); } private registerListeners(): void { this._register(this.fileService.onFileChanges(e => this.onFileChanges(e))); - this._register(this.textFileService.onAutoSaveConfigurationChange(config => this.updateAutoSaveConfiguration(config))); - this._register(this.textFileService.onFilesAssociationChange(e => this.onFilesAssociationChange())); + this._register(this.filesConfigurationService.onAutoSaveConfigurationChange(config => this.updateAutoSaveConfiguration(config))); + this._register(this.filesConfigurationService.onFilesAssociationChange(e => this.onFilesAssociationChange())); this._register(this.onDidStateChange(e => this.onStateChange(e))); } @@ -224,6 +237,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil if (isEqual(target, this.resource) && this.lastResolvedFileStat) { meta = { mtime: this.lastResolvedFileStat.mtime, + ctime: this.lastResolvedFileStat.ctime, size: this.lastResolvedFileStat.size, etag: this.lastResolvedFileStat.etag, orphaned: this.inOrphanMode @@ -238,19 +252,21 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil return this.backupFileService.hasBackupSync(this.resource, this.versionId); } - async revert(soft?: boolean): Promise { + async revert(options?: IRevertOptions): Promise { if (!this.isResolved()) { - return; + return false; } // Cancel any running auto-save this.autoSaveDisposable.clear(); // Unset flags + const wasDirty = this.dirty; const undo = this.setDirty(false); // Force read from disk unless reverting soft - if (!soft) { + const softUndo = options?.soft; + if (!softUndo) { try { await this.load({ forceReadFromDisk: true }); } catch (error) { @@ -264,6 +280,13 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // Emit file change event this._onDidStateChange.fire(StateChange.REVERTED); + + // Emit dirty change event + if (wasDirty) { + this._onDidChangeDirty.fire(); + } + + return true; } async load(options?: ILoadOptions): Promise { @@ -313,11 +336,11 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil resource: this.resource, name: basename(this.resource), mtime: resolvedBackup.meta ? resolvedBackup.meta.mtime : Date.now(), + ctime: resolvedBackup.meta ? resolvedBackup.meta.ctime : Date.now(), size: resolvedBackup.meta ? resolvedBackup.meta.size : 0, etag: resolvedBackup.meta ? resolvedBackup.meta.etag : ETAG_DISABLED, // etag disabled if unknown! value: resolvedBackup.value, - encoding: this.textFileService.encoding.getPreferredWriteEncoding(this.resource, this.preferredEncoding).encoding, - isReadonly: false + encoding: this.textFileService.encoding.getPreferredWriteEncoding(this.resource, this.preferredEncoding).encoding }, options, true /* from backup */); // Restore orphaned flag based on state @@ -397,11 +420,12 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil resource: this.resource, name: content.name, mtime: content.mtime, + ctime: content.ctime, size: content.size, etag: content.etag, + isFile: true, isDirectory: false, - isSymbolicLink: false, - isReadonly: content.isReadonly + isSymbolicLink: false }); // Keep the original encoding to not loose it when saving @@ -523,6 +547,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // Emit event if (wasDirty) { this._onDidStateChange.fire(StateChange.REVERTED); + this._onDidChangeDirty.fire(); } return; @@ -563,6 +588,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // Emit as Event if we turned dirty if (!wasDirty) { this._onDidStateChange.fire(StateChange.DIRTY); + this._onDidChangeDirty.fire(); } } @@ -587,9 +613,9 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil this.autoSaveDisposable.value = toDisposable(() => clearTimeout(handle)); } - async save(options: ISaveOptions = Object.create(null)): Promise { + async save(options: ITextFileSaveOptions = Object.create(null)): Promise { if (!this.isResolved()) { - return; + return false; } this.logService.trace('save() - enter', this.resource); @@ -597,10 +623,12 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // Cancel any currently running auto saves to make this the one that succeeds this.autoSaveDisposable.clear(); - return this.doSave(this.versionId, options); + await this.doSave(this.versionId, options); + + return true; } - private doSave(versionId: number, options: ISaveOptions): Promise { + private doSave(versionId: number, options: ITextFileSaveOptions): Promise { if (isUndefinedOrNull(options.reason)) { options.reason = SaveReason.EXPLICIT; } @@ -734,8 +762,9 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // Cancel any content change event promises as they are no longer valid this.contentChangeEventScheduler.cancel(); - // Emit File Saved Event + // Emit Events this._onDidStateChange.fire(StateChange.SAVED); + this._onDidChangeDirty.fire(); // Telemetry const settingsType = this.getTypeIfSettings(); @@ -1001,17 +1030,13 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } isReadonly(): boolean { - return !!(this.lastResolvedFileStat && this.lastResolvedFileStat.isReadonly); + return this.fileService.hasCapability(this.resource, FileSystemProviderCapabilities.Readonly); } isDisposed(): boolean { return this.disposed; } - getResource(): URI { - return this.resource; - } - getStat(): IFileStatWithMetadata | undefined { return this.lastResolvedFileStat; } @@ -1122,6 +1147,6 @@ class DefaultSaveErrorHandler implements ISaveErrorHandler { constructor(@INotificationService private readonly notificationService: INotificationService) { } onSaveError(error: Error, model: TextFileEditorModel): void { - this.notificationService.error(nls.localize('genericSaveError', "Failed to save '{0}': {1}", basename(model.getResource()), toErrorMessage(error, false))); + this.notificationService.error(nls.localize('genericSaveError', "Failed to save '{0}': {1}", basename(model.resource), toErrorMessage(error, false))); } } diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts index 669f85a6c7fa..6a68f44bac03 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts @@ -282,15 +282,15 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE this.mapResourceToModel.clear(); this.mapResourceToPendingModelLoaders.clear(); - // dispose dispose listeners + // dispose the dispose listeners this.mapResourceToDisposeListener.forEach(l => l.dispose()); this.mapResourceToDisposeListener.clear(); - // dispose state change listeners + // dispose the state change listeners this.mapResourceToStateChangeListener.forEach(l => l.dispose()); this.mapResourceToStateChangeListener.clear(); - // dispose model content change listeners + // dispose the model content change listeners this.mapResourceToModelContentChangeListener.forEach(l => l.dispose()); this.mapResourceToModelContentChangeListener.clear(); } @@ -304,7 +304,7 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE return; // already disposed } - if (this.mapResourceToPendingModelLoaders.has(model.getResource())) { + if (this.mapResourceToPendingModelLoaders.has(model.resource)) { return; // not yet loaded } diff --git a/src/vs/workbench/services/textfile/common/textfiles.ts b/src/vs/workbench/services/textfile/common/textfiles.ts index 348721f86817..625657db4131 100644 --- a/src/vs/workbench/services/textfile/common/textfiles.ts +++ b/src/vs/workbench/services/textfile/common/textfiles.ts @@ -4,17 +4,17 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from 'vs/base/common/uri'; -import { Event } from 'vs/base/common/event'; +import { Event, IWaitUntil } from 'vs/base/common/event'; import { IDisposable } from 'vs/base/common/lifecycle'; -import { IEncodingSupport, ConfirmResult, IRevertOptions, IModeSupport } from 'vs/workbench/common/editor'; -import { IBaseStatWithMetadata, IFileStatWithMetadata, IReadFileOptions, IWriteFileOptions, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; +import { IEncodingSupport, IModeSupport, ISaveOptions, IRevertOptions, SaveReason } from 'vs/workbench/common/editor'; +import { IBaseStatWithMetadata, IFileStatWithMetadata, IReadFileOptions, IWriteFileOptions, FileOperationError, FileOperationResult, FileOperation } from 'vs/platform/files/common/files'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ITextEditorModel } from 'vs/editor/common/services/resolverService'; import { ITextBufferFactory, ITextModel, ITextSnapshot } from 'vs/editor/common/model'; -import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { VSBuffer, VSBufferReadable } from 'vs/base/common/buffer'; import { isUndefinedOrNull } from 'vs/base/common/types'; import { isNative } from 'vs/base/common/platform'; +import { IWorkingCopy } from 'vs/workbench/services/workingCopy/common/workingCopyService'; export const ITextFileService = createDecorator('textFileService'); @@ -22,13 +22,15 @@ export interface ITextFileService extends IDisposable { _serviceBrand: undefined; - readonly onWillMove: Event; - - readonly onAutoSaveConfigurationChange: Event; - - readonly onFilesAssociationChange: Event; + /** + * An event that is fired before attempting a certain file operation. + */ + readonly onWillRunOperation: Event; - readonly isHotExitEnabled: boolean; + /** + * An event that is fired after a file operation has been performed. + */ + readonly onDidRunOperation: Event; /** * Access to the manager of text file editor models providing further methods to work with them. @@ -129,22 +131,24 @@ export interface ITextFileService extends IDisposable { move(source: URI, target: URI, overwrite?: boolean): Promise; /** - * Brings up the confirm dialog to either save, don't save or cancel. - * - * @param resources the resources of the files to ask for confirmation or null if - * confirming for all dirty resources. + * Copy a file. If the file is dirty, its contents will be preserved and restored. */ - confirmSave(resources?: URI[]): Promise; + copy(source: URI, target: URI, overwrite?: boolean): Promise; +} - /** - * Convenient fast access to the current auto save mode. - */ - getAutoSaveMode(): AutoSaveMode; +export interface FileOperationWillRunEvent extends IWaitUntil { + operation: FileOperation; + target: URI; + source?: URI; +} - /** - * Convenient fast access to the raw configured auto save settings. - */ - getAutoSaveConfiguration(): IAutoSaveConfiguration; +export class FileOperationDidRunEvent { + + constructor( + readonly operation: FileOperation, + readonly target: URI, + readonly source?: URI | undefined + ) { } } export interface IReadTextFileOptions extends IReadFileOptions { @@ -291,7 +295,7 @@ export class TextFileModelChangeEvent { private _resource: URI; constructor(model: ITextFileEditorModel, private _kind: StateChange) { - this._resource = model.getResource(); + this._resource = model.resource; } get resource(): URI { @@ -303,8 +307,6 @@ export class TextFileModelChangeEvent { } } -export const AutoSaveContext = new RawContextKey('config.files.autoSave', undefined); - export interface ITextFileOperationResult { results: IResult[]; } @@ -312,28 +314,7 @@ export interface ITextFileOperationResult { export interface IResult { source: URI; target?: URI; - success?: boolean; -} - -export interface IAutoSaveConfiguration { - autoSaveDelay?: number; - autoSaveFocusChange: boolean; - autoSaveApplicationChange: boolean; -} - -export const enum AutoSaveMode { - OFF, - AFTER_SHORT_DELAY, - AFTER_LONG_DELAY, - ON_FOCUS_CHANGE, - ON_WINDOW_CHANGE -} - -export const enum SaveReason { - EXPLICIT = 1, - AUTO = 2, - FOCUS_CHANGE = 3, - WINDOW_CHANGE = 4 + error?: boolean; } export const enum LoadReason { @@ -427,14 +408,10 @@ export interface ITextFileEditorModelManager { disposeModel(model: ITextFileEditorModel): void; } -export interface ISaveOptions { - force?: boolean; - reason?: SaveReason; +export interface ITextFileSaveOptions extends ISaveOptions { overwriteReadonly?: boolean; overwriteEncoding?: boolean; - skipSaveParticipants?: boolean; writeElevated?: boolean; - availableFileSystems?: readonly string[]; } export interface ILoadOptions { @@ -455,22 +432,20 @@ export interface ILoadOptions { reason?: LoadReason; } -export interface ITextFileEditorModel extends ITextEditorModel, IEncodingSupport, IModeSupport { +export interface ITextFileEditorModel extends ITextEditorModel, IEncodingSupport, IModeSupport, IWorkingCopy { readonly onDidContentChange: Event; readonly onDidStateChange: Event; - getResource(): URI; - hasState(state: ModelState): boolean; updatePreferredEncoding(encoding: string | undefined): void; - save(options?: ISaveOptions): Promise; + save(options?: ITextFileSaveOptions): Promise; load(options?: ILoadOptions): Promise; - revert(soft?: boolean): Promise; + revert(options?: IRevertOptions): Promise; backup(target?: URI): Promise; @@ -492,13 +467,6 @@ export interface IResolvedTextFileEditorModel extends ITextFileEditorModel { createSnapshot(): ITextSnapshot; } -export interface IWillMoveEvent { - oldResource: URI; - newResource: URI; - - waitUntil(p: Promise): void; -} - /** * Helper method to convert a snapshot into its full string form. */ diff --git a/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts b/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts index d063ddf29b51..4fa52a8681ee 100644 --- a/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts +++ b/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts @@ -14,7 +14,7 @@ import { Schemas } from 'vs/base/common/network'; import { exists, stat, chmod, rimraf, MAX_FILE_SIZE, MAX_HEAP_SIZE } from 'vs/base/node/pfs'; import { join, dirname } from 'vs/base/common/path'; import { isMacintosh } from 'vs/base/common/platform'; -import product from 'vs/platform/product/common/product'; +import { IProductService } from 'vs/platform/product/common/productService'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { UTF8, UTF8_with_bom, UTF16be, UTF16le, encodingExists, encodeStream, UTF8_BOM, toDecodeStream, IDecodeStreamResult, detectEncodingByBOMFromBuffer, isUTFEncoding } from 'vs/base/node/encoding'; @@ -22,49 +22,49 @@ import { WORKSPACE_EXTENSION } from 'vs/platform/workspaces/common/workspaces'; import { joinPath, extname, isEqualOrParent } from 'vs/base/common/resources'; import { Disposable } from 'vs/base/common/lifecycle'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { VSBufferReadable } from 'vs/base/common/buffer'; +import { VSBufferReadable, bufferToStream } from 'vs/base/common/buffer'; import { Readable } from 'stream'; import { createTextBufferFactoryFromStream } from 'vs/editor/common/model/textModel'; import { ITextSnapshot } from 'vs/editor/common/model'; import { nodeReadableToString, streamToNodeReadable, nodeStreamToVSBufferReadable } from 'vs/base/node/stream'; import { IElectronService } from 'vs/platform/electron/node/electron'; -import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; +import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IDialogService, IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { assign } from 'vs/base/common/objects'; +import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; export class NativeTextFileService extends AbstractTextFileService { constructor( @IWorkspaceContextService contextService: IWorkspaceContextService, @IFileService fileService: IFileService, - @IUntitledEditorService untitledEditorService: IUntitledEditorService, + @IUntitledTextEditorService untitledTextEditorService: IUntitledTextEditorService, @ILifecycleService lifecycleService: ILifecycleService, @IInstantiationService instantiationService: IInstantiationService, - @IConfigurationService configurationService: IConfigurationService, @IModeService modeService: IModeService, @IModelService modelService: IModelService, @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, @INotificationService notificationService: INotificationService, @IBackupFileService backupFileService: IBackupFileService, @IHistoryService historyService: IHistoryService, - @IContextKeyService contextKeyService: IContextKeyService, @IDialogService dialogService: IDialogService, @IFileDialogService fileDialogService: IFileDialogService, @IEditorService editorService: IEditorService, @ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService, - @IElectronService private readonly electronService: IElectronService + @IElectronService private readonly electronService: IElectronService, + @IProductService private readonly productService: IProductService, + @IFilesConfigurationService filesConfigurationService: IFilesConfigurationService ) { - super(contextService, fileService, untitledEditorService, lifecycleService, instantiationService, configurationService, modeService, modelService, environmentService, notificationService, backupFileService, historyService, contextKeyService, dialogService, fileDialogService, editorService, textResourceConfigurationService); + super(contextService, fileService, untitledTextEditorService, lifecycleService, instantiationService, modeService, modelService, environmentService, notificationService, backupFileService, historyService, dialogService, fileDialogService, editorService, textResourceConfigurationService, filesConfigurationService); } private _encoding: EncodingOracle | undefined; @@ -77,7 +77,16 @@ export class NativeTextFileService extends AbstractTextFileService { } async read(resource: URI, options?: IReadTextFileOptions): Promise { - const [bufferStream, decoder] = await this.doRead(resource, options); + const [bufferStream, decoder] = await this.doRead(resource, + assign({ + // optimization: since we know that the caller does not + // care about buffering, we indicate this to the reader. + // this reduces all the overhead the buffered reading + // has (open, read, close) if the provider supports + // unbuffered reading. + preferUnbuffered: true + }, options || Object.create(null)) + ); return { ...bufferStream, @@ -96,13 +105,22 @@ export class NativeTextFileService extends AbstractTextFileService { }; } - private async doRead(resource: URI, options?: IReadTextFileOptions): Promise<[IFileStreamContent, IDecodeStreamResult]> { + private async doRead(resource: URI, options?: IReadTextFileOptions & { preferUnbuffered?: boolean }): Promise<[IFileStreamContent, IDecodeStreamResult]> { // ensure limits options = this.ensureLimits(options); - // read stream raw - const bufferStream = await this.fileService.readFileStream(resource, options); + // read stream raw (either buffered or unbuffered) + let bufferStream: IFileStreamContent; + if (options.preferUnbuffered) { + const content = await this.fileService.readFile(resource, options); + bufferStream = { + ...content, + value: bufferToStream(content.value) + }; + } else { + bufferStream = await this.fileService.readFileStream(resource, options); + } // read through encoding library const decoder = await toDecodeStream(streamToNodeReadable(bufferStream.value), { @@ -272,8 +290,8 @@ export class NativeTextFileService extends AbstractTextFileService { return new Promise((resolve, reject) => { const promptOptions = { - name: this.environmentService.appNameLong.replace('-', ''), - icns: (isMacintosh && this.environmentService.isBuilt) ? join(dirname(this.environmentService.appRoot), `${product.nameShort}.icns`) : undefined + name: this.productService.nameLong.replace('-', ''), + icns: (isMacintosh && this.environmentService.isBuilt) ? join(dirname(this.environmentService.appRoot), `${this.productService.nameShort}.icns`) : undefined }; const sudoCommand: string[] = [`"${this.environmentService.cliPath}"`]; @@ -357,7 +375,7 @@ export class EncodingOracle extends Disposable implements IResourceEncodings { if (!overwriteEncoding && encoding === UTF8) { try { const buffer = (await this.fileService.readFile(resource, { length: UTF8_BOM.length })).value; - if (detectEncodingByBOMFromBuffer(buffer, buffer.byteLength) === UTF8) { + if (detectEncodingByBOMFromBuffer(buffer, buffer.byteLength) === UTF8_with_bom) { return { encoding, addBOM: true }; } } catch (error) { @@ -382,7 +400,7 @@ export class EncodingOracle extends Disposable implements IResourceEncodings { // Encoding passed in as option if (options?.encoding) { - if (detectedEncoding === UTF8 && options.encoding === UTF8) { + if (detectedEncoding === UTF8_with_bom && options.encoding === UTF8) { preferredEncoding = UTF8_with_bom; // indicate the file has BOM if we are to resolve with UTF 8 } else { preferredEncoding = options.encoding; // give passed in encoding highest priority @@ -391,11 +409,7 @@ export class EncodingOracle extends Disposable implements IResourceEncodings { // Encoding detected else if (detectedEncoding) { - if (detectedEncoding === UTF8) { - preferredEncoding = UTF8_with_bom; // if we detected UTF-8, it can only be because of a BOM - } else { - preferredEncoding = detectedEncoding; - } + preferredEncoding = detectedEncoding; } // Encoding configured diff --git a/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts b/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts index bad9a2ebca0a..03bef53a6d88 100644 --- a/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts @@ -16,9 +16,15 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { timeout } from 'vs/base/common/async'; import { ModesRegistry } from 'vs/editor/common/modes/modesRegistry'; import { assertIsDefined } from 'vs/base/common/types'; +import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; class ServiceAccessor { - constructor(@ITextFileService public textFileService: TestTextFileService, @IModelService public modelService: IModelService, @IFileService public fileService: TestFileService) { + constructor( + @ITextFileService public readonly textFileService: TestTextFileService, + @IModelService public readonly modelService: IModelService, + @IFileService public readonly fileService: TestFileService, + @IWorkingCopyService public readonly workingCopyService: IWorkingCopyService + ) { } } @@ -51,10 +57,15 @@ suite('Files - TextFileEditorModel', () => { await model.load(); + assert.equal(accessor.workingCopyService.dirtyCount, 0); + model.textEditorModel!.setValue('bar'); assert.ok(getLastModifiedTime(model) <= Date.now()); assert.ok(model.hasState(ModelState.DIRTY)); + assert.equal(accessor.workingCopyService.dirtyCount, 1); + assert.equal(accessor.workingCopyService.isDirty(model.resource), true); + let savedEvent = false; model.onDidStateChange(e => { if (e === StateChange.SAVED) { @@ -62,6 +73,13 @@ suite('Files - TextFileEditorModel', () => { } }); + let workingCopyEvent = false; + accessor.workingCopyService.onDidChangeDirty(e => { + if (e.resource.toString() === model.resource.toString()) { + workingCopyEvent = true; + } + }); + const pendingSave = model.save(); assert.ok(model.hasState(ModelState.PENDING_SAVE)); @@ -71,9 +89,13 @@ suite('Files - TextFileEditorModel', () => { assert.ok(model.hasState(ModelState.SAVED)); assert.ok(!model.isDirty()); assert.ok(savedEvent); + assert.ok(workingCopyEvent); + + assert.equal(accessor.workingCopyService.dirtyCount, 0); + assert.equal(accessor.workingCopyService.isDirty(model.resource), false); model.dispose(); - assert.ok(!accessor.modelService.getModel(model.getResource())); + assert.ok(!accessor.modelService.getModel(model.resource)); }); test('save - touching also emits saved event', async function () { @@ -88,12 +110,20 @@ suite('Files - TextFileEditorModel', () => { } }); + let workingCopyEvent = false; + accessor.workingCopyService.onDidChangeDirty(e => { + if (e.resource.toString() === model.resource.toString()) { + workingCopyEvent = true; + } + }); + await model.save({ force: true }); assert.ok(savedEvent); + assert.ok(!workingCopyEvent); model.dispose(); - assert.ok(!accessor.modelService.getModel(model.getResource())); + assert.ok(!accessor.modelService.getModel(model.resource)); }); test('setEncoding - encode', function () { @@ -132,7 +162,7 @@ suite('Files - TextFileEditorModel', () => { assert.equal(model.textEditorModel!.getModeId(), mode); model.dispose(); - assert.ok(!accessor.modelService.getModel(model.getResource())); + assert.ok(!accessor.modelService.getModel(model.resource)); }); test('disposes when underlying model is destroyed', async function () { @@ -155,7 +185,7 @@ suite('Files - TextFileEditorModel', () => { await model.load(); assert.ok(model.isResolved()); model.dispose(); - assert.ok(!accessor.modelService.getModel(model.getResource())); + assert.ok(!accessor.modelService.getModel(model.resource)); }); test('Load returns dirty model as long as model is dirty', async function () { @@ -182,14 +212,29 @@ suite('Files - TextFileEditorModel', () => { } }); + let workingCopyEvent = false; + accessor.workingCopyService.onDidChangeDirty(e => { + if (e.resource.toString() === model.resource.toString()) { + workingCopyEvent = true; + } + }); + await model.load(); model.textEditorModel!.setValue('foo'); assert.ok(model.isDirty()); + assert.equal(accessor.workingCopyService.dirtyCount, 1); + assert.equal(accessor.workingCopyService.isDirty(model.resource), true); + await model.revert(); assert.ok(!model.isDirty()); assert.equal(model.textEditorModel!.getValue(), 'Hello Html'); assert.equal(eventCounter, 1); + + assert.ok(workingCopyEvent); + assert.equal(accessor.workingCopyService.dirtyCount, 0); + assert.equal(accessor.workingCopyService.isDirty(model.resource), false); + model.dispose(); }); @@ -204,14 +249,29 @@ suite('Files - TextFileEditorModel', () => { } }); + let workingCopyEvent = false; + accessor.workingCopyService.onDidChangeDirty(e => { + if (e.resource.toString() === model.resource.toString()) { + workingCopyEvent = true; + } + }); + await model.load(); model.textEditorModel!.setValue('foo'); assert.ok(model.isDirty()); - await model.revert(true /* soft revert */); + assert.equal(accessor.workingCopyService.dirtyCount, 1); + assert.equal(accessor.workingCopyService.isDirty(model.resource), true); + + await model.revert({ soft: true }); assert.ok(!model.isDirty()); assert.equal(model.textEditorModel!.getValue(), 'foo'); assert.equal(eventCounter, 1); + + assert.ok(workingCopyEvent); + assert.equal(accessor.workingCopyService.dirtyCount, 0); + assert.equal(accessor.workingCopyService.isDirty(model.resource), false); + model.dispose(); }); @@ -223,6 +283,9 @@ suite('Files - TextFileEditorModel', () => { await model.load(); model.textEditorModel!.undo(); assert.ok(model.isDirty()); + + assert.equal(accessor.workingCopyService.dirtyCount, 1); + assert.equal(accessor.workingCopyService.isDirty(model.resource), true); }); test('Make Dirty', async function () { @@ -237,7 +300,7 @@ suite('Files - TextFileEditorModel', () => { model.textEditorModel!.setValue('foo'); assert.ok(model.isDirty()); - await model.revert(true /* soft revert */); + await model.revert({ soft: true }); assert.ok(!model.isDirty()); model.onDidStateChange(e => { @@ -246,9 +309,18 @@ suite('Files - TextFileEditorModel', () => { } }); + let workingCopyEvent = false; + accessor.workingCopyService.onDidChangeDirty(e => { + if (e.resource.toString() === model.resource.toString()) { + workingCopyEvent = true; + } + }); + model.makeDirty(); assert.ok(model.isDirty()); assert.equal(eventCounter, 1); + assert.ok(workingCopyEvent); + model.dispose(); }); @@ -343,7 +415,6 @@ suite('Files - TextFileEditorModel', () => { }); test('Save Participant, async participant', async function () { - const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); TextFileEditorModel.setSaveParticipant({ diff --git a/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts b/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts index 07461d0992d0..db3a03da9b87 100644 --- a/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts @@ -273,7 +273,7 @@ suite('Files - TextFileEditorModelManager', () => { const model = await manager.loadOrCreate(resource, { encoding: 'utf8' }); model.dispose(); assert.ok(!manager.get(resource)); - assert.ok(!accessor.modelService.getModel(model.getResource())); + assert.ok(!accessor.modelService.getModel(model.resource)); manager.dispose(); }); @@ -286,7 +286,7 @@ suite('Files - TextFileEditorModelManager', () => { model.textEditorModel!.setValue('make dirty'); manager.disposeModel((model as TextFileEditorModel)); assert.ok(!model.isDisposed()); - model.revert(true); + model.revert({ soft: true }); manager.disposeModel((model as TextFileEditorModel)); assert.ok(model.isDisposed()); manager.dispose(); diff --git a/src/vs/workbench/services/textfile/test/textFileService.io.test.ts b/src/vs/workbench/services/textfile/test/textFileService.io.test.ts index e82195a40859..dc748f0001d8 100644 --- a/src/vs/workbench/services/textfile/test/textFileService.io.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileService.io.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import { URI } from 'vs/base/common/uri'; import { workbenchInstantiationService, TestTextFileService } from 'vs/workbench/test/workbenchTestServices'; import { ITextFileService, snapshotToString, TextFileOperationResult, TextFileOperationError } from 'vs/workbench/services/textfile/common/textfiles'; -import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; +import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { IFileService } from 'vs/platform/files/common/files'; import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; import { Schemas } from 'vs/base/common/network'; @@ -32,7 +32,7 @@ import { detectEncodingByBOM } from 'vs/base/test/node/encoding/encoding.test'; class ServiceAccessor { constructor( @ITextFileService public textFileService: TestTextFileService, - @IUntitledEditorService public untitledEditorService: IUntitledEditorService + @IUntitledTextEditorService public untitledTextEditorService: IUntitledTextEditorService ) { } } @@ -96,7 +96,7 @@ suite('Files - TextFileService i/o', () => { teardown(async () => { (accessor.textFileService.models).clear(); (accessor.textFileService.models).dispose(); - accessor.untitledEditorService.revertAll(); + accessor.untitledTextEditorService.revertAll(); disposables.clear(); @@ -172,7 +172,7 @@ suite('Files - TextFileService i/o', () => { assert.equal(await exists(resource.fsPath), true); const detectedEncoding = await detectEncodingByBOM(resource.fsPath); - assert.equal(detectedEncoding, UTF8); + assert.equal(detectedEncoding, UTF8_with_bom); }); test('create - UTF 8 BOM - content provided', async () => { @@ -183,7 +183,7 @@ suite('Files - TextFileService i/o', () => { assert.equal(await exists(resource.fsPath), true); const detectedEncoding = await detectEncodingByBOM(resource.fsPath); - assert.equal(detectedEncoding, UTF8); + assert.equal(detectedEncoding, UTF8_with_bom); }); test('create - UTF 8 BOM - empty content - snapshot', async () => { @@ -194,7 +194,7 @@ suite('Files - TextFileService i/o', () => { assert.equal(await exists(resource.fsPath), true); const detectedEncoding = await detectEncodingByBOM(resource.fsPath); - assert.equal(detectedEncoding, UTF8); + assert.equal(detectedEncoding, UTF8_with_bom); }); test('create - UTF 8 BOM - content provided - snapshot', async () => { @@ -205,7 +205,7 @@ suite('Files - TextFileService i/o', () => { assert.equal(await exists(resource.fsPath), true); const detectedEncoding = await detectEncodingByBOM(resource.fsPath); - assert.equal(detectedEncoding, UTF8); + assert.equal(detectedEncoding, UTF8_with_bom); }); test('write - use encoding (UTF 16 BE) - small content as string', async () => { @@ -325,12 +325,12 @@ suite('Files - TextFileService i/o', () => { await service.write(resource, content, { encoding: UTF8_with_bom }); detectedEncoding = await detectEncodingByBOM(resource.fsPath); - assert.equal(detectedEncoding, UTF8); + assert.equal(detectedEncoding, UTF8_with_bom); // ensure BOM preserved await service.write(resource, content, { encoding: UTF8 }); detectedEncoding = await detectEncodingByBOM(resource.fsPath); - assert.equal(detectedEncoding, UTF8); + assert.equal(detectedEncoding, UTF8_with_bom); // allow to remove BOM await service.write(resource, content, { encoding: UTF8, overwriteEncoding: true }); @@ -353,12 +353,12 @@ suite('Files - TextFileService i/o', () => { await service.write(resource, model.createSnapshot(), { encoding: UTF8_with_bom }); detectedEncoding = await detectEncodingByBOM(resource.fsPath); - assert.equal(detectedEncoding, UTF8); + assert.equal(detectedEncoding, UTF8_with_bom); // ensure BOM preserved await service.write(resource, model.createSnapshot(), { encoding: UTF8 }); detectedEncoding = await detectEncodingByBOM(resource.fsPath); - assert.equal(detectedEncoding, UTF8); + assert.equal(detectedEncoding, UTF8_with_bom); // allow to remove BOM await service.write(resource, model.createSnapshot(), { encoding: UTF8, overwriteEncoding: true }); @@ -375,11 +375,11 @@ suite('Files - TextFileService i/o', () => { const resource = URI.file(join(testDir, 'some_utf8_bom.txt')); let detectedEncoding = await detectEncodingByBOM(resource.fsPath); - assert.equal(detectedEncoding, UTF8); + assert.equal(detectedEncoding, UTF8_with_bom); await service.write(resource, 'Hello World'); detectedEncoding = await detectEncodingByBOM(resource.fsPath); - assert.equal(detectedEncoding, UTF8); + assert.equal(detectedEncoding, UTF8_with_bom); }); test('write - ensure BOM in empty file - content as string', async () => { @@ -388,7 +388,7 @@ suite('Files - TextFileService i/o', () => { await service.write(resource, '', { encoding: UTF8_with_bom }); let detectedEncoding = await detectEncodingByBOM(resource.fsPath); - assert.equal(detectedEncoding, UTF8); + assert.equal(detectedEncoding, UTF8_with_bom); }); test('write - ensure BOM in empty file - content as snapshot', async () => { @@ -397,7 +397,7 @@ suite('Files - TextFileService i/o', () => { await service.write(resource, TextModel.createFromString('').createSnapshot(), { encoding: UTF8_with_bom }); let detectedEncoding = await detectEncodingByBOM(resource.fsPath); - assert.equal(detectedEncoding, UTF8); + assert.equal(detectedEncoding, UTF8_with_bom); }); test('readStream - small text', async () => { diff --git a/src/vs/workbench/services/textfile/test/textFileService.test.ts b/src/vs/workbench/services/textfile/test/textFileService.test.ts index e8f21a04d479..b6728f7a9ece 100644 --- a/src/vs/workbench/services/textfile/test/textFileService.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileService.test.ts @@ -7,13 +7,12 @@ import * as sinon from 'sinon'; import * as platform from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { ILifecycleService, BeforeShutdownEvent, ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle'; -import { workbenchInstantiationService, TestLifecycleService, TestTextFileService, TestContextService, TestFileService, TestElectronService } from 'vs/workbench/test/workbenchTestServices'; +import { workbenchInstantiationService, TestLifecycleService, TestTextFileService, TestContextService, TestFileService, TestElectronService, TestFilesConfigurationService, TestFileDialogService } from 'vs/workbench/test/workbenchTestServices'; import { toResource } from 'vs/base/test/common/utils'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { ConfirmResult } from 'vs/workbench/common/editor'; -import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; +import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { HotExitConfiguration, IFileService } from 'vs/platform/files/common/files'; import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; import { IWorkspaceContextService, Workspace } from 'vs/platform/workspace/common/workspace'; @@ -21,16 +20,20 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; import { Schemas } from 'vs/base/common/network'; import { IElectronService } from 'vs/platform/electron/node/electron'; +import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; +import { IFileDialogService, ConfirmResult } from 'vs/platform/dialogs/common/dialogs'; class ServiceAccessor { constructor( @ILifecycleService public lifecycleService: TestLifecycleService, @ITextFileService public textFileService: TestTextFileService, - @IUntitledEditorService public untitledEditorService: IUntitledEditorService, + @IFilesConfigurationService public filesConfigurationService: TestFilesConfigurationService, + @IUntitledTextEditorService public untitledTextEditorService: IUntitledTextEditorService, @IWorkspaceContextService public contextService: TestContextService, @IModelService public modelService: ModelServiceImpl, @IFileService public fileService: TestFileService, - @IElectronService public electronService: TestElectronService + @IElectronService public electronService: TestElectronService, + @IFileDialogService public fileDialogService: TestFileDialogService ) { } } @@ -62,12 +65,12 @@ suite('Files - TextFileService', () => { } (accessor.textFileService.models).clear(); (accessor.textFileService.models).dispose(); - accessor.untitledEditorService.revertAll(); + accessor.untitledTextEditorService.revertAll(); }); test('confirm onWillShutdown - no veto', async function () { model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); - (accessor.textFileService.models).add(model.getResource(), model); + (accessor.textFileService.models).add(model.resource, model); const event = new BeforeShutdownEventImpl(); accessor.lifecycleService.fireWillShutdown(event); @@ -82,10 +85,10 @@ suite('Files - TextFileService', () => { test('confirm onWillShutdown - veto if user cancels', async function () { model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); - (accessor.textFileService.models).add(model.getResource(), model); + (accessor.textFileService.models).add(model.resource, model); const service = accessor.textFileService; - service.setConfirmResult(ConfirmResult.CANCEL); + accessor.fileDialogService.setConfirmResult(ConfirmResult.CANCEL); await model.load(); model.textEditorModel!.setValue('foo'); @@ -98,11 +101,11 @@ suite('Files - TextFileService', () => { test('confirm onWillShutdown - no veto and backups cleaned up if user does not want to save (hot.exit: off)', async function () { model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); - (accessor.textFileService.models).add(model.getResource(), model); + (accessor.textFileService.models).add(model.resource, model); const service = accessor.textFileService; - service.setConfirmResult(ConfirmResult.DONT_SAVE); - service.onFilesConfigurationChange({ files: { hotExit: 'off' } }); + accessor.fileDialogService.setConfirmResult(ConfirmResult.DONT_SAVE); + accessor.filesConfigurationService.onFilesConfigurationChange({ files: { hotExit: 'off' } }); await model.load(); model.textEditorModel!.setValue('foo'); @@ -124,11 +127,11 @@ suite('Files - TextFileService', () => { test('confirm onWillShutdown - save (hot.exit: off)', async function () { model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); - (accessor.textFileService.models).add(model.getResource(), model); + (accessor.textFileService.models).add(model.resource, model); const service = accessor.textFileService; - service.setConfirmResult(ConfirmResult.SAVE); - service.onFilesConfigurationChange({ files: { hotExit: 'off' } }); + accessor.fileDialogService.setConfirmResult(ConfirmResult.SAVE); + accessor.filesConfigurationService.onFilesConfigurationChange({ files: { hotExit: 'off' } }); await model.load(); model.textEditorModel!.setValue('foo'); @@ -143,20 +146,20 @@ suite('Files - TextFileService', () => { test('isDirty/getDirty - files and untitled', async function () { model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); - (accessor.textFileService.models).add(model.getResource(), model); + (accessor.textFileService.models).add(model.resource, model); const service = accessor.textFileService; await model.load(); - assert.ok(!service.isDirty(model.getResource())); + assert.ok(!service.isDirty(model.resource)); model.textEditorModel!.setValue('foo'); - assert.ok(service.isDirty(model.getResource())); + assert.ok(service.isDirty(model.resource)); assert.equal(service.getDirty().length, 1); - assert.equal(service.getDirty([model.getResource()])[0].toString(), model.getResource().toString()); + assert.equal(service.getDirty([model.resource])[0].toString(), model.resource.toString()); - const untitled = accessor.untitledEditorService.createOrGet(); + const untitled = accessor.untitledTextEditorService.createOrGet(); const untitledModel = await untitled.resolve(); assert.ok(!service.isDirty(untitled.getResource())); @@ -170,30 +173,30 @@ suite('Files - TextFileService', () => { test('save - file', async function () { model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); - (accessor.textFileService.models).add(model.getResource(), model); + (accessor.textFileService.models).add(model.resource, model); const service = accessor.textFileService; await model.load(); model.textEditorModel!.setValue('foo'); - assert.ok(service.isDirty(model.getResource())); + assert.ok(service.isDirty(model.resource)); - const res = await service.save(model.getResource()); + const res = await service.save(model.resource); assert.ok(res); - assert.ok(!service.isDirty(model.getResource())); + assert.ok(!service.isDirty(model.resource)); }); test('save - UNC path', async function () { const untitledUncUri = URI.from({ scheme: 'untitled', authority: 'server', path: '/share/path/file.txt' }); model = instantiationService.createInstance(TextFileEditorModel, untitledUncUri, 'utf8', undefined); - (accessor.textFileService.models).add(model.getResource(), model); + (accessor.textFileService.models).add(model.resource, model); const mockedFileUri = untitledUncUri.with({ scheme: Schemas.file }); const mockedEditorInput = instantiationService.createInstance(TextFileEditorModel, mockedFileUri, 'utf8', undefined); const loadOrCreateStub = sinon.stub(accessor.textFileService.models, 'loadOrCreate', () => Promise.resolve(mockedEditorInput)); - sinon.stub(accessor.untitledEditorService, 'exists', () => true); - sinon.stub(accessor.untitledEditorService, 'hasAssociatedFilePath', () => true); + sinon.stub(accessor.untitledTextEditorService, 'exists', () => true); + sinon.stub(accessor.untitledTextEditorService, 'hasAssociatedFilePath', () => true); sinon.stub(accessor.modelService, 'updateModel', () => { }); await model.load(); @@ -202,7 +205,7 @@ suite('Files - TextFileService', () => { const res = await accessor.textFileService.saveAll(true); assert.ok(loadOrCreateStub.calledOnce); assert.equal(res.results.length, 1); - assert.ok(res.results[0].success); + assert.ok(!res.results[0].error); assert.equal(res.results[0].target!.scheme, Schemas.file); assert.equal(res.results[0].target!.authority, untitledUncUri.authority); assert.equal(res.results[0].target!.path, untitledUncUri.path); @@ -210,65 +213,65 @@ suite('Files - TextFileService', () => { test('saveAll - file', async function () { model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); - (accessor.textFileService.models).add(model.getResource(), model); + (accessor.textFileService.models).add(model.resource, model); const service = accessor.textFileService; await model.load(); model.textEditorModel!.setValue('foo'); - assert.ok(service.isDirty(model.getResource())); + assert.ok(service.isDirty(model.resource)); - const res = await service.saveAll([model.getResource()]); + const res = await service.saveAll([model.resource]); assert.ok(res); - assert.ok(!service.isDirty(model.getResource())); + assert.ok(!service.isDirty(model.resource)); assert.equal(res.results.length, 1); - assert.equal(res.results[0].source.toString(), model.getResource().toString()); + assert.equal(res.results[0].source.toString(), model.resource.toString()); }); test('saveAs - file', async function () { model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); - (accessor.textFileService.models).add(model.getResource(), model); + (accessor.textFileService.models).add(model.resource, model); const service = accessor.textFileService; - service.setPromptPath(model.getResource()); + service.setPromptPath(model.resource); await model.load(); model.textEditorModel!.setValue('foo'); - assert.ok(service.isDirty(model.getResource())); + assert.ok(service.isDirty(model.resource)); - const res = await service.saveAs(model.getResource()); - assert.equal(res!.toString(), model.getResource().toString()); - assert.ok(!service.isDirty(model.getResource())); + const res = await service.saveAs(model.resource); + assert.equal(res!.toString(), model.resource.toString()); + assert.ok(!service.isDirty(model.resource)); }); test('revert - file', async function () { model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); - (accessor.textFileService.models).add(model.getResource(), model); + (accessor.textFileService.models).add(model.resource, model); const service = accessor.textFileService; - service.setPromptPath(model.getResource()); + service.setPromptPath(model.resource); await model.load(); model!.textEditorModel!.setValue('foo'); - assert.ok(service.isDirty(model.getResource())); + assert.ok(service.isDirty(model.resource)); - const res = await service.revert(model.getResource()); + const res = await service.revert(model.resource); assert.ok(res); - assert.ok(!service.isDirty(model.getResource())); + assert.ok(!service.isDirty(model.resource)); }); test('delete - dirty file', async function () { model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); - (accessor.textFileService.models).add(model.getResource(), model); + (accessor.textFileService.models).add(model.resource, model); const service = accessor.textFileService; await model.load(); model!.textEditorModel!.setValue('foo'); - assert.ok(service.isDirty(model.getResource())); + assert.ok(service.isDirty(model.resource)); - await service.delete(model.getResource()); - assert.ok(!service.isDirty(model.getResource())); + await service.delete(model.resource); + assert.ok(!service.isDirty(model.resource)); }); test('move - dirty file', async function () { @@ -282,27 +285,27 @@ suite('Files - TextFileService', () => { async function testMove(source: URI, target: URI, targetDirty?: boolean): Promise { let sourceModel: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, source, 'utf8', undefined); let targetModel: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, target, 'utf8', undefined); - (accessor.textFileService.models).add(sourceModel.getResource(), sourceModel); - (accessor.textFileService.models).add(targetModel.getResource(), targetModel); + (accessor.textFileService.models).add(sourceModel.resource, sourceModel); + (accessor.textFileService.models).add(targetModel.resource, targetModel); const service = accessor.textFileService; await sourceModel.load(); sourceModel.textEditorModel!.setValue('foo'); - assert.ok(service.isDirty(sourceModel.getResource())); + assert.ok(service.isDirty(sourceModel.resource)); if (targetDirty) { await targetModel.load(); targetModel.textEditorModel!.setValue('bar'); - assert.ok(service.isDirty(targetModel.getResource())); + assert.ok(service.isDirty(targetModel.resource)); } - await service.move(sourceModel.getResource(), targetModel.getResource(), true); + await service.move(sourceModel.resource, targetModel.resource, true); assert.equal(targetModel.textEditorModel!.getValue(), 'foo'); - assert.ok(!service.isDirty(sourceModel.getResource())); - assert.ok(service.isDirty(targetModel.getResource())); + assert.ok(!service.isDirty(sourceModel.resource)); + assert.ok(service.isDirty(targetModel.resource)); sourceModel.dispose(); targetModel.dispose(); @@ -413,11 +416,11 @@ suite('Files - TextFileService', () => { async function hotExitTest(this: any, setting: string, shutdownReason: ShutdownReason, multipleWindows: boolean, workspace: true, shouldVeto: boolean): Promise { model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); - (accessor.textFileService.models).add(model.getResource(), model); + (accessor.textFileService.models).add(model.resource, model); const service = accessor.textFileService; // Set hot exit config - service.onFilesConfigurationChange({ files: { hotExit: setting } }); + accessor.filesConfigurationService.onFilesConfigurationChange({ files: { hotExit: setting } }); // Set empty workspace if required if (!workspace) { accessor.contextService.setWorkspace(new Workspace('empty:1508317022751')); @@ -427,7 +430,7 @@ suite('Files - TextFileService', () => { accessor.electronService.windowCount = Promise.resolve(2); } // Set cancel to force a veto if hot exit does not trigger - service.setConfirmResult(ConfirmResult.CANCEL); + accessor.fileDialogService.setConfirmResult(ConfirmResult.CANCEL); await model.load(); model.textEditorModel!.setValue('foo'); diff --git a/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts b/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts index e824bcf627bc..58906fdc6165 100644 --- a/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts +++ b/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts @@ -13,7 +13,7 @@ import { ResourceEditorModel } from 'vs/workbench/common/editor/resourceEditorMo import { ITextFileService, LoadReason } from 'vs/workbench/services/textfile/common/textfiles'; import * as network from 'vs/base/common/network'; import { ITextModelService, ITextModelContentProvider, ITextEditorModel, IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService'; -import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; +import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; import { IFileService } from 'vs/platform/files/common/files'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -126,7 +126,7 @@ export class TextModelResolverService implements ITextModelService { private resourceModelCollection: ResourceModelCollection; constructor( - @IUntitledEditorService private readonly untitledEditorService: IUntitledEditorService, + @IUntitledTextEditorService private readonly untitledTextEditorService: IUntitledTextEditorService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IModelService private readonly modelService: IModelService ) { @@ -141,7 +141,7 @@ export class TextModelResolverService implements ITextModelService { // Untitled Schema: go through cached input if (resource.scheme === network.Schemas.untitled) { - const model = await this.untitledEditorService.loadOrCreate({ resource }); + const model = await this.untitledTextEditorService.loadOrCreate({ resource }); return new ImmortalReference(model as IResolvedTextEditorModel); } @@ -179,4 +179,4 @@ export class TextModelResolverService implements ITextModelService { } } -registerSingleton(ITextModelService, TextModelResolverService, true); \ No newline at end of file +registerSingleton(ITextModelService, TextModelResolverService, true); diff --git a/src/vs/workbench/services/textmodelResolver/test/textModelResolverService.test.ts b/src/vs/workbench/services/textmodelResolver/test/textModelResolverService.test.ts index d276b52b3f67..42a8c582dfb6 100644 --- a/src/vs/workbench/services/textmodelResolver/test/textModelResolverService.test.ts +++ b/src/vs/workbench/services/textmodelResolver/test/textModelResolverService.test.ts @@ -16,7 +16,7 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { IModeService } from 'vs/editor/common/services/modeService'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; import { ITextFileService, snapshotToString } from 'vs/workbench/services/textfile/common/textfiles'; -import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; +import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; import { Event } from 'vs/base/common/event'; import { timeout } from 'vs/base/common/async'; @@ -27,7 +27,7 @@ class ServiceAccessor { @IModelService public modelService: IModelService, @IModeService public modeService: IModeService, @ITextFileService public textFileService: TestTextFileService, - @IUntitledEditorService public untitledEditorService: IUntitledEditorService + @IUntitledTextEditorService public untitledTextEditorService: IUntitledTextEditorService ) { } } @@ -50,7 +50,7 @@ suite('Workbench - TextModelResolverService', () => { } (accessor.textFileService.models).clear(); (accessor.textFileService.models).dispose(); - accessor.untitledEditorService.revertAll(); + accessor.untitledTextEditorService.revertAll(); }); test('resolve resource', async () => { @@ -88,11 +88,11 @@ suite('Workbench - TextModelResolverService', () => { test('resolve file', async function () { const textModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file_resolver.txt'), 'utf8', undefined); - (accessor.textFileService.models).add(textModel.getResource(), textModel); + (accessor.textFileService.models).add(textModel.resource, textModel); await textModel.load(); - const ref = await accessor.textModelResolverService.createModelReference(textModel.getResource()); + const ref = await accessor.textModelResolverService.createModelReference(textModel.resource); const model = ref.object; const editorModel = model.textEditorModel; @@ -111,7 +111,7 @@ suite('Workbench - TextModelResolverService', () => { }); test('resolve untitled', async () => { - const service = accessor.untitledEditorService; + const service = accessor.untitledTextEditorService; const input = service.createOrGet(); await input.resolve(); diff --git a/src/vs/workbench/services/themes/browser/fileIconThemeData.ts b/src/vs/workbench/services/themes/browser/fileIconThemeData.ts index 5137574ccd1c..8cf07a66eb99 100644 --- a/src/vs/workbench/services/themes/browser/fileIconThemeData.ts +++ b/src/vs/workbench/services/themes/browser/fileIconThemeData.ts @@ -189,8 +189,10 @@ function _loadIconThemeDocument(fileService: IFileService, location: URI): Promi return fileService.readFile(location).then((content) => { let errors: Json.ParseError[] = []; let contentValue = Json.parse(content.value.toString(), errors); - if (errors.length > 0 || !contentValue) { + if (errors.length > 0) { return Promise.reject(new Error(nls.localize('error.cannotparseicontheme', "Problems parsing file icons file: {0}", errors.map(e => getParseErrorMessage(e.error)).join(', ')))); + } else if (Json.getNodeType(contentValue) !== 'object') { + return Promise.reject(new Error(nls.localize('error.invalidformat', "Invalid format for file icons theme file: Object expected."))); } return Promise.resolve(contentValue); }); diff --git a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts index 64579fb075f8..ddb3595a3951 100644 --- a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts @@ -6,7 +6,7 @@ import * as nls from 'vs/nls'; import * as types from 'vs/base/common/types'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { IWorkbenchThemeService, IColorTheme, ITokenColorCustomizations, IFileIconTheme, ExtensionData, VS_LIGHT_THEME, VS_DARK_THEME, VS_HC_THEME, COLOR_THEME_SETTING, ICON_THEME_SETTING, CUSTOM_WORKBENCH_COLORS_SETTING, CUSTOM_EDITOR_COLORS_SETTING, DETECT_HC_SETTING, HC_THEME_ID, IColorCustomizations } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { IWorkbenchThemeService, IColorTheme, ITokenColorCustomizations, IFileIconTheme, ExtensionData, VS_LIGHT_THEME, VS_DARK_THEME, VS_HC_THEME, COLOR_THEME_SETTING, ICON_THEME_SETTING, CUSTOM_WORKBENCH_COLORS_SETTING, CUSTOM_EDITOR_COLORS_SETTING, DETECT_HC_SETTING, HC_THEME_ID, IColorCustomizations, CUSTOM_EDITOR_TOKENSTYLES_SETTING, IExperimentalTokenStyleCustomizations } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -29,9 +29,11 @@ import * as resources from 'vs/base/common/resources'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { textmateColorsSchemaId, registerColorThemeSchemas, textmateColorSettingsSchemaId } from 'vs/workbench/services/themes/common/colorThemeSchema'; import { workbenchColorsSchemaId } from 'vs/platform/theme/common/colorRegistry'; +import { tokenStylingSchemaId } from 'vs/platform/theme/common/tokenClassificationRegistry'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { getRemoteAuthority } from 'vs/platform/remote/common/remoteHosts'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; +import { IExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader'; // implementation // {{SQL CARBON EDIT}} @@ -93,6 +95,10 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { return this.configurationService.getValue(CUSTOM_EDITOR_COLORS_SETTING) || {}; } + private get tokenStylesCustomizations(): IExperimentalTokenStyleCustomizations { + return this.configurationService.getValue(CUSTOM_EDITOR_TOKENSTYLES_SETTING) || {}; + } + constructor( @IExtensionService extensionService: IExtensionService, @IStorageService private readonly storageService: IStorageService, @@ -100,6 +106,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { @ITelemetryService private readonly telemetryService: ITelemetryService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IFileService private readonly fileService: IFileService, + @IExtensionResourceLoaderService private readonly extensionResourceLoaderService: IExtensionResourceLoaderService, @IWorkbenchLayoutService readonly layoutService: IWorkbenchLayoutService ) { @@ -126,6 +133,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { } themeData.setCustomColors(this.colorCustomizations); themeData.setCustomTokenColors(this.tokenColorCustomizations); + themeData.setCustomTokenStyleRules(this.tokenStylesCustomizations); this.updateDynamicCSSRules(themeData); this.applyTheme(themeData, undefined, true); @@ -154,18 +162,22 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { const themeSpecificWorkbenchColors: IJSONSchema = { properties: {} }; const themeSpecificTokenColors: IJSONSchema = { properties: {} }; + const themeSpecificTokenStyling: IJSONSchema = { properties: {} }; const workbenchColors = { $ref: workbenchColorsSchemaId, additionalProperties: false }; const tokenColors = { properties: tokenColorSchema.properties, additionalProperties: false }; + const tokenStyling = { $ref: tokenStylingSchemaId, additionalProperties: false }; for (let t of event.themes) { // add theme specific color customization ("[Abyss]":{ ... }) const themeId = `[${t.settingsId}]`; themeSpecificWorkbenchColors.properties![themeId] = workbenchColors; themeSpecificTokenColors.properties![themeId] = tokenColors; + themeSpecificTokenStyling.properties![themeId] = tokenStyling; } colorCustomizationsSchema.allOf![1] = themeSpecificWorkbenchColors; tokenColorCustomizationSchema.allOf![1] = themeSpecificTokenColors; + experimentalTokenStylingCustomizationSchema.allOf![1] = themeSpecificTokenStyling; configurationRegistry.notifyConfigurationSchemaUpdated(themeSettingsConfiguration, tokenColorCustomizationConfiguration); @@ -308,6 +320,10 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { this.currentColorTheme.setCustomTokenColors(this.tokenColorCustomizations); hasColorChanges = true; } + if (e.affectsConfiguration(CUSTOM_EDITOR_TOKENSTYLES_SETTING)) { + this.currentColorTheme.setCustomTokenStyleRules(this.tokenStylesCustomizations); + hasColorChanges = true; + } if (hasColorChanges) { this.updateDynamicCSSRules(this.currentColorTheme); this.onColorThemeChange.fire(this.currentColorTheme); @@ -343,16 +359,19 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { return null; } const themeData = data; - return themeData.ensureLoaded(this.fileService).then(_ => { + return themeData.ensureLoaded(this.extensionResourceLoaderService).then(_ => { if (themeId === this.currentColorTheme.id && !this.currentColorTheme.isLoaded && this.currentColorTheme.hasEqualData(themeData)) { + this.currentColorTheme.clearCaches(); // the loaded theme is identical to the perisisted theme. Don't need to send an event. this.currentColorTheme = themeData; themeData.setCustomColors(this.colorCustomizations); themeData.setCustomTokenColors(this.tokenColorCustomizations); + themeData.setCustomTokenStyleRules(this.tokenStylesCustomizations); return Promise.resolve(themeData); } themeData.setCustomColors(this.colorCustomizations); themeData.setCustomTokenColors(this.tokenColorCustomizations); + themeData.setCustomTokenStyleRules(this.tokenStylesCustomizations); this.updateDynamicCSSRules(themeData); return this.applyTheme(themeData, settingsTarget); }, error => { @@ -362,9 +381,10 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { } private async reloadCurrentColorTheme() { - await this.currentColorTheme.reload(this.fileService); + await this.currentColorTheme.reload(this.extensionResourceLoaderService); this.currentColorTheme.setCustomColors(this.colorCustomizations); this.currentColorTheme.setCustomTokenColors(this.tokenColorCustomizations); + this.currentColorTheme.setCustomTokenStyleRules(this.tokenStylesCustomizations); this.updateDynamicCSSRules(this.currentColorTheme); this.applyTheme(this.currentColorTheme, undefined, false); } @@ -401,6 +421,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { } addClasses(this.container, newTheme.id); + this.currentColorTheme.clearCaches(); this.currentColorTheme = newTheme; if (!this.themingParticipantChangeListener) { this.themingParticipantChangeListener = themingRegistry.onThemingParticipantAdded(_ => this.updateDynamicCSSRules(this.currentColorTheme)); @@ -662,7 +683,7 @@ const themeSettingsConfiguration: IConfigurationNode = { }; configurationRegistry.registerConfiguration(themeSettingsConfiguration); -function tokenGroupSettings(description: string) { +function tokenGroupSettings(description: string): IJSONSchema { return { description, default: '#FF0000', @@ -698,12 +719,18 @@ const tokenColorCustomizationSchema: IConfigurationPropertySchema = { default: {}, allOf: [tokenColorSchema] }; +const experimentalTokenStylingCustomizationSchema: IConfigurationPropertySchema = { + description: nls.localize('editorColorsTokenStyles', "Overrides token color and styles from the currently selected color theme."), + default: {}, + allOf: [{ $ref: tokenStylingSchemaId }] +}; const tokenColorCustomizationConfiguration: IConfigurationNode = { id: 'editor', order: 7.2, type: 'object', properties: { - [CUSTOM_EDITOR_COLORS_SETTING]: tokenColorCustomizationSchema + [CUSTOM_EDITOR_COLORS_SETTING]: tokenColorCustomizationSchema, + [CUSTOM_EDITOR_TOKENSTYLES_SETTING]: experimentalTokenStylingCustomizationSchema } }; configurationRegistry.registerConfiguration(tokenColorCustomizationConfiguration); diff --git a/src/vs/workbench/services/themes/common/colorThemeData.ts b/src/vs/workbench/services/themes/common/colorThemeData.ts index 9c10c7e162b3..ae220248873e 100644 --- a/src/vs/workbench/services/themes/common/colorThemeData.ts +++ b/src/vs/workbench/services/themes/common/colorThemeData.ts @@ -6,26 +6,31 @@ import { basename } from 'vs/base/common/path'; import * as Json from 'vs/base/common/json'; import { Color } from 'vs/base/common/color'; -import { ExtensionData, ITokenColorCustomizations, ITokenColorizationRule, IColorTheme, IColorMap, IThemeExtensionPoint, VS_LIGHT_THEME, VS_HC_THEME, IColorCustomizations } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { ExtensionData, ITokenColorCustomizations, ITextMateThemingRule, IColorTheme, IColorMap, IThemeExtensionPoint, VS_LIGHT_THEME, VS_HC_THEME, IColorCustomizations, IExperimentalTokenStyleCustomizations, ITokenColorizationSetting } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { convertSettings } from 'vs/workbench/services/themes/common/themeCompatibility'; import * as nls from 'vs/nls'; import * as types from 'vs/base/common/types'; import * as objects from 'vs/base/common/objects'; import * as resources from 'vs/base/common/resources'; -import { Extensions, IColorRegistry, ColorIdentifier, editorBackground, editorForeground } from 'vs/platform/theme/common/colorRegistry'; +import { Extensions as ColorRegistryExtensions, IColorRegistry, ColorIdentifier, editorBackground, editorForeground } from 'vs/platform/theme/common/colorRegistry'; import { ThemeType } from 'vs/platform/theme/common/themeService'; import { Registry } from 'vs/platform/registry/common/platform'; import { getParseErrorMessage } from 'vs/base/common/jsonErrorMessages'; import { URI } from 'vs/base/common/uri'; -import { IFileService } from 'vs/platform/files/common/files'; import { parse as parsePList } from 'vs/workbench/services/themes/common/plistParser'; import { startsWith } from 'vs/base/common/strings'; +import { TokenStyle, TokenClassification, ProbeScope, TokenStylingRule, getTokenClassificationRegistry, TokenStyleValue, matchTokenStylingRule } from 'vs/platform/theme/common/tokenClassificationRegistry'; +import { MatcherWithPriority, Matcher, createMatchers } from 'vs/workbench/services/themes/common/textMateScopeMatcher'; +import { IExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader'; +import { FontStyle, ColorId, MetadataConsts } from 'vs/editor/common/modes'; -let colorRegistry = Registry.as(Extensions.ColorContribution); +let colorRegistry = Registry.as(ColorRegistryExtensions.ColorContribution); + +let tokenClassificationRegistry = getTokenClassificationRegistry(); const tokenGroupToScopesMap = { - comments: ['comment'], - strings: ['string'], + comments: ['comment', 'punctuation.definition.comment'], + strings: ['string', 'meta.embedded.assembly'], keywords: ['keyword - keyword.operator', 'keyword.control', 'storage', 'storage.type'], numbers: ['constant.numeric'], types: ['entity.name.type', 'entity.name.class', 'support.type', 'support.class'], @@ -33,6 +38,7 @@ const tokenGroupToScopesMap = { variables: ['variable', 'entity.name.variable'] }; + export class ColorThemeData implements IColorTheme { id: string; @@ -44,11 +50,20 @@ export class ColorThemeData implements IColorTheme { watch?: boolean; extensionData?: ExtensionData; - private themeTokenColors: ITokenColorizationRule[] = []; - private customTokenColors: ITokenColorizationRule[] = []; + private themeTokenColors: ITextMateThemingRule[] = []; + private customTokenColors: ITextMateThemingRule[] = []; private colorMap: IColorMap = {}; private customColorMap: IColorMap = {}; + private tokenStylingRules: TokenStylingRule[] | undefined = undefined; // undefined if the theme has no tokenStylingRules section + private customTokenStylingRules: TokenStylingRule[] = []; + + private themeTokenScopeMatchers: Matcher[] | undefined; + private customTokenScopeMatchers: Matcher[] | undefined; + + private textMateThemingRules: ITextMateThemingRule[] | undefined = undefined; // created on demand + private tokenColorIndex: TokenColorIndex | undefined = undefined; // created on demand + private constructor(id: string, label: string, settingsId: string) { this.id = id; this.label = label; @@ -56,39 +71,42 @@ export class ColorThemeData implements IColorTheme { this.isLoaded = false; } - get tokenColors(): ITokenColorizationRule[] { - const result: ITokenColorizationRule[] = []; - - // the default rule (scope empty) is always the first rule. Ignore all other default rules. - const foreground = this.getColor(editorForeground) || this.getDefault(editorForeground)!; - const background = this.getColor(editorBackground) || this.getDefault(editorBackground)!; - result.push({ - settings: { - foreground: Color.Format.CSS.formatHexA(foreground), - background: Color.Format.CSS.formatHexA(background) - } - }); + get tokenColors(): ITextMateThemingRule[] { + if (!this.textMateThemingRules) { + const result: ITextMateThemingRule[] = []; + + // the default rule (scope empty) is always the first rule. Ignore all other default rules. + const foreground = this.getColor(editorForeground) || this.getDefault(editorForeground)!; + const background = this.getColor(editorBackground) || this.getDefault(editorBackground)!; + result.push({ + settings: { + foreground: Color.Format.CSS.formatHexA(foreground, true), + background: Color.Format.CSS.formatHexA(background, true) + } + }); - let hasDefaultTokens = false; + let hasDefaultTokens = false; - function addRule(rule: ITokenColorizationRule) { - if (rule.scope && rule.settings) { - if (rule.scope === 'token.info-token') { - hasDefaultTokens = true; + function addRule(rule: ITextMateThemingRule) { + if (rule.scope && rule.settings) { + if (rule.scope === 'token.info-token') { + hasDefaultTokens = true; + } + result.push(rule); } - result.push(rule); } - } - this.themeTokenColors.forEach(addRule); - // Add the custom colors after the theme colors - // so that they will override them - this.customTokenColors.forEach(addRule); + this.themeTokenColors.forEach(addRule); + // Add the custom colors after the theme colors + // so that they will override them + this.customTokenColors.forEach(addRule); - if (!hasDefaultTokens) { - defaultThemeColors[this.type].forEach(addRule); + if (!hasDefaultTokens) { + defaultThemeColors[this.type].forEach(addRule); + } + this.textMateThemingRules = result; } - return result; + return this.textMateThemingRules; } public getColor(colorId: ColorIdentifier, useDefault?: boolean): Color | undefined { @@ -103,10 +121,180 @@ export class ColorThemeData implements IColorTheme { return color; } + public getTokenStyle(classification: TokenClassification, useDefault?: boolean): TokenStyle | undefined { + let result: any = { + foreground: undefined, + bold: undefined, + underline: undefined, + italic: undefined + }; + let score = { + foreground: -1, + bold: -1, + underline: -1, + italic: -1 + }; + + function _processStyle(matchScore: number, style: TokenStyle) { + if (style.foreground && score.foreground <= matchScore) { + score.foreground = matchScore; + result.foreground = style.foreground; + } + for (let p of ['bold', 'underline', 'italic']) { + const property = p as keyof TokenStyle; + const info = style[property]; + if (info !== undefined) { + if (score[property] <= matchScore) { + score[property] = matchScore; + result[property] = info; + } + } + } + } + if (this.tokenStylingRules === undefined) { + for (const rule of tokenClassificationRegistry.getTokenStylingDefaultRules()) { + const matchScore = matchTokenStylingRule(rule, classification); + if (matchScore >= 0) { + let style = this.resolveScopes(rule.defaults.scopesToProbe); + if (!style && useDefault !== false) { + style = this.resolveTokenStyleValue(rule.defaults[this.type]); + } + if (style) { + _processStyle(matchScore, style); + } + } + } + } else { + for (const rule of this.tokenStylingRules) { + const matchScore = matchTokenStylingRule(rule, classification); + if (matchScore >= 0) { + _processStyle(matchScore, rule.value); + } + } + } + for (const rule of this.customTokenStylingRules) { + const matchScore = matchTokenStylingRule(rule, classification); + if (matchScore >= 0) { + _processStyle(matchScore, rule.value); + } + } + return TokenStyle.fromData(result); + + } + + /** + * @param tokenStyleValue Resolve a tokenStyleValue in the context of a theme + */ + private resolveTokenStyleValue(tokenStyleValue: TokenStyleValue | null): TokenStyle | undefined { + if (tokenStyleValue === null) { + return undefined; + } else if (typeof tokenStyleValue === 'string') { + const [type, ...modifiers] = tokenStyleValue.split('.'); + const classification = tokenClassificationRegistry.getTokenClassification(type, modifiers); + if (classification) { + return this.getTokenStyle(classification); + } + } else if (typeof tokenStyleValue === 'object') { + return tokenStyleValue; + } + return undefined; + } + + private getTokenColorIndex(): TokenColorIndex { + // collect all colors that tokens can have + if (!this.tokenColorIndex) { + const index = new TokenColorIndex(); + this.tokenColors.forEach(rule => { + index.add(rule.settings.foreground); + index.add(rule.settings.background); + }); + + if (this.tokenStylingRules) { + this.tokenStylingRules.forEach(r => index.add(r.value.foreground)); + } else { + tokenClassificationRegistry.getTokenStylingDefaultRules().forEach(r => { + const defaultColor = r.defaults[this.type]; + if (defaultColor && typeof defaultColor === 'object') { + index.add(defaultColor.foreground); + } + }); + } + this.customTokenStylingRules.forEach(r => index.add(r.value.foreground)); + + this.tokenColorIndex = index; + } + return this.tokenColorIndex; + } + + public get tokenColorMap(): string[] { + return this.getTokenColorIndex().asArray(); + } + + public getTokenStyleMetadata(type: string, modifiers: string[], useDefault?: boolean): number | undefined { + const classification = tokenClassificationRegistry.getTokenClassification(type, modifiers); + if (!classification) { + return undefined; + } + const style = this.getTokenStyle(classification, useDefault); + let fontStyle = FontStyle.None; + let foreground = 0; + if (style) { + if (style.bold) { + fontStyle |= FontStyle.Bold; + } + if (style.underline) { + fontStyle |= FontStyle.Underline; + } + if (style.italic) { + fontStyle |= FontStyle.Italic; + } + foreground = this.getTokenColorIndex().get(style.foreground); + } + return toMetadata(fontStyle, foreground, 0); + } + public getDefault(colorId: ColorIdentifier): Color | undefined { return colorRegistry.resolveDefaultColor(colorId, this); } + public resolveScopes(scopes: ProbeScope[]): TokenStyle | undefined { + + if (!this.themeTokenScopeMatchers) { + this.themeTokenScopeMatchers = this.themeTokenColors.map(getScopeMatcher); + } + if (!this.customTokenScopeMatchers) { + this.customTokenScopeMatchers = this.customTokenColors.map(getScopeMatcher); + } + + for (let scope of scopes) { + let foreground: string | undefined = undefined; + let fontStyle: string | undefined = undefined; + let foregroundScore = -1; + let fontStyleScore = -1; + + function findTokenStyleForScopeInScopes(scopeMatchers: Matcher[], tokenColors: ITextMateThemingRule[]) { + for (let i = 0; i < scopeMatchers.length; i++) { + const score = scopeMatchers[i](scope); + if (score >= 0) { + const settings = tokenColors[i].settings; + if (score >= foregroundScore && settings.foreground) { + foreground = settings.foreground; + } + if (score >= fontStyleScore && types.isString(settings.fontStyle)) { + fontStyle = settings.fontStyle; + } + } + } + } + findTokenStyleForScopeInScopes(this.themeTokenScopeMatchers, this.themeTokenColors); + findTokenStyleForScopeInScopes(this.customTokenScopeMatchers, this.customTokenColors); + if (foreground !== undefined || fontStyle !== undefined) { + return getTokenStyle(foreground, fontStyle); + } + } + return undefined; + } + public defines(colorId: ColorIdentifier): boolean { return this.customColorMap.hasOwnProperty(colorId) || this.colorMap.hasOwnProperty(colorId); } @@ -119,6 +307,10 @@ export class ColorThemeData implements IColorTheme { if (types.isObject(themeSpecificColors)) { this.overwriteCustomColors(themeSpecificColors); } + + this.tokenColorIndex = undefined; + this.textMateThemingRules = undefined; + this.customTokenScopeMatchers = undefined; } private overwriteCustomColors(colors: IColorCustomizations) { @@ -132,6 +324,7 @@ export class ColorThemeData implements IColorTheme { public setCustomTokenColors(customTokenColors: ITokenColorCustomizations) { this.customTokenColors = []; + // first add the non-theme specific settings this.addCustomTokenColors(customTokenColors); @@ -140,6 +333,23 @@ export class ColorThemeData implements IColorTheme { if (types.isObject(themeSpecificTokenColors)) { this.addCustomTokenColors(themeSpecificTokenColors); } + + this.tokenColorIndex = undefined; + this.textMateThemingRules = undefined; + this.customTokenScopeMatchers = undefined; + } + + public setCustomTokenStyleRules(tokenStylingRules: IExperimentalTokenStyleCustomizations) { + this.customTokenStylingRules = []; + readCustomTokenStyleRules(tokenStylingRules, this.customTokenStylingRules); + + const themeSpecificColors = tokenStylingRules[`[${this.settingsId}]`] as IExperimentalTokenStyleCustomizations; + if (types.isObject(themeSpecificColors)) { + readCustomTokenStyleRules(themeSpecificColors, this.customTokenStylingRules); + } + + this.tokenColorIndex = undefined; + this.textMateThemingRules = undefined; } private addCustomTokenColors(customTokenColors: ITokenColorCustomizations) { @@ -167,25 +377,41 @@ export class ColorThemeData implements IColorTheme { } } - public ensureLoaded(fileService: IFileService): Promise { - return !this.isLoaded ? this.load(fileService) : Promise.resolve(undefined); + public ensureLoaded(extensionResourceLoaderService: IExtensionResourceLoaderService): Promise { + return !this.isLoaded ? this.load(extensionResourceLoaderService) : Promise.resolve(undefined); } - public reload(fileService: IFileService): Promise { - return this.load(fileService); + public reload(extensionResourceLoaderService: IExtensionResourceLoaderService): Promise { + return this.load(extensionResourceLoaderService); } - private load(fileService: IFileService): Promise { + private load(extensionResourceLoaderService: IExtensionResourceLoaderService): Promise { if (!this.location) { return Promise.resolve(undefined); } this.themeTokenColors = []; - this.colorMap = {}; - return _loadColorTheme(fileService, this.location, this.themeTokenColors, this.colorMap).then(_ => { + this.clearCaches(); + + const result = { + colors: {}, + textMateRules: [], + stylingRules: undefined + }; + return _loadColorTheme(extensionResourceLoaderService, this.location, result).then(_ => { this.isLoaded = true; + this.tokenStylingRules = result.stylingRules; + this.colorMap = result.colors; + this.themeTokenColors = result.textMateRules; }); } + public clearCaches() { + this.tokenColorIndex = undefined; + this.textMateThemingRules = undefined; + this.themeTokenScopeMatchers = undefined; + this.customTokenScopeMatchers = undefined; + } + toStorageData() { let colorMapData: { [key: string]: string } = {}; for (let key in this.colorMap) { @@ -295,21 +521,23 @@ function toCSSSelector(extensionId: string, path: string) { return str; } -function _loadColorTheme(fileService: IFileService, themeLocation: URI, resultRules: ITokenColorizationRule[], resultColors: IColorMap): Promise { +function _loadColorTheme(extensionResourceLoaderService: IExtensionResourceLoaderService, themeLocation: URI, result: { textMateRules: ITextMateThemingRule[], colors: IColorMap, stylingRules: TokenStylingRule[] | undefined }): Promise { if (resources.extname(themeLocation) === '.json') { - return fileService.readFile(themeLocation).then(content => { + return extensionResourceLoaderService.readExtensionResource(themeLocation).then(content => { let errors: Json.ParseError[] = []; - let contentValue = Json.parse(content.value.toString(), errors); + let contentValue = Json.parse(content, errors); if (errors.length > 0) { return Promise.reject(new Error(nls.localize('error.cannotparsejson', "Problems parsing JSON theme file: {0}", errors.map(e => getParseErrorMessage(e.error)).join(', ')))); + } else if (Json.getNodeType(contentValue) !== 'object') { + return Promise.reject(new Error(nls.localize('error.invalidformat', "Invalid format for JSON theme file: Object expected."))); } let includeCompletes: Promise = Promise.resolve(null); if (contentValue.include) { - includeCompletes = _loadColorTheme(fileService, resources.joinPath(resources.dirname(themeLocation), contentValue.include), resultRules, resultColors); + includeCompletes = _loadColorTheme(extensionResourceLoaderService, resources.joinPath(resources.dirname(themeLocation), contentValue.include), result); } return includeCompletes.then(_ => { if (Array.isArray(contentValue.settings)) { - convertSettings(contentValue.settings, resultRules, resultColors); + convertSettings(contentValue.settings, result); return null; } let colors = contentValue.colors; @@ -321,38 +549,42 @@ function _loadColorTheme(fileService: IFileService, themeLocation: URI, resultRu for (let colorId in colors) { let colorHex = colors[colorId]; if (typeof colorHex === 'string') { // ignore colors tht are null - resultColors[colorId] = Color.fromHex(colors[colorId]); + result.colors[colorId] = Color.fromHex(colors[colorId]); } } } let tokenColors = contentValue.tokenColors; if (tokenColors) { if (Array.isArray(tokenColors)) { - resultRules.push(...tokenColors); + result.textMateRules.push(...tokenColors); return null; } else if (typeof tokenColors === 'string') { - return _loadSyntaxTokens(fileService, resources.joinPath(resources.dirname(themeLocation), tokenColors), resultRules, {}); + return _loadSyntaxTokens(extensionResourceLoaderService, resources.joinPath(resources.dirname(themeLocation), tokenColors), result); } else { return Promise.reject(new Error(nls.localize({ key: 'error.invalidformat.tokenColors', comment: ['{0} will be replaced by a path. Values in quotes should not be translated.'] }, "Problem parsing color theme file: {0}. Property 'tokenColors' should be either an array specifying colors or a path to a TextMate theme file", themeLocation.toString()))); } } + let tokenStylingRules = contentValue.tokenStylingRules; + if (tokenStylingRules && typeof tokenStylingRules === 'object') { + result.stylingRules = readCustomTokenStyleRules(tokenStylingRules, result.stylingRules); + } return null; }); }); } else { - return _loadSyntaxTokens(fileService, themeLocation, resultRules, resultColors); + return _loadSyntaxTokens(extensionResourceLoaderService, themeLocation, result); } } -function _loadSyntaxTokens(fileService: IFileService, themeLocation: URI, resultRules: ITokenColorizationRule[], resultColors: IColorMap): Promise { - return fileService.readFile(themeLocation).then(content => { +function _loadSyntaxTokens(extensionResourceLoaderService: IExtensionResourceLoaderService, themeLocation: URI, result: { textMateRules: ITextMateThemingRule[], colors: IColorMap }): Promise { + return extensionResourceLoaderService.readExtensionResource(themeLocation).then(content => { try { - let contentValue = parsePList(content.value.toString()); - let settings: ITokenColorizationRule[] = contentValue.settings; + let contentValue = parsePList(content); + let settings: ITextMateThemingRule[] = contentValue.settings; if (!Array.isArray(settings)) { return Promise.reject(new Error(nls.localize('error.plist.invalidformat', "Problem parsing tmTheme file: {0}. 'settings' is not array."))); } - convertSettings(settings, resultRules, resultColors); + convertSettings(settings, result); return Promise.resolve(null); } catch (e) { return Promise.reject(new Error(nls.localize('error.cannotparse', "Problems parsing tmTheme file: {0}", e.message))); @@ -362,7 +594,7 @@ function _loadSyntaxTokens(fileService: IFileService, themeLocation: URI, result }); } -let defaultThemeColors: { [baseTheme: string]: ITokenColorizationRule[] } = { +let defaultThemeColors: { [baseTheme: string]: ITextMateThemingRule[] } = { 'light': [ { scope: 'token.info-token', settings: { foreground: '#316bcd' } }, { scope: 'token.warn-token', settings: { foreground: '#cd9731' } }, @@ -381,4 +613,197 @@ let defaultThemeColors: { [baseTheme: string]: ITokenColorizationRule[] } = { { scope: 'token.error-token', settings: { foreground: '#FF0000' } }, { scope: 'token.debug-token', settings: { foreground: '#b267e6' } } ], -}; \ No newline at end of file +}; + +const noMatch = (_scope: ProbeScope) => -1; + +function nameMatcher(identifers: string[], scope: ProbeScope): number { + function findInIdents(s: string, lastIndent: number): number { + for (let i = lastIndent - 1; i >= 0; i--) { + if (scopesAreMatching(s, identifers[i])) { + return i; + } + } + return -1; + } + if (scope.length < identifers.length) { + return -1; + } + let lastScopeIndex = scope.length - 1; + let lastIdentifierIndex = findInIdents(scope[lastScopeIndex--], identifers.length); + if (lastIdentifierIndex >= 0) { + const score = (lastIdentifierIndex + 1) * 0x10000 + scope.length; + while (lastScopeIndex >= 0) { + lastIdentifierIndex = findInIdents(scope[lastScopeIndex--], lastIdentifierIndex); + if (lastIdentifierIndex === -1) { + return -1; + } + } + return score; + } + return -1; +} + + +function scopesAreMatching(thisScopeName: string, scopeName: string): boolean { + if (!thisScopeName) { + return false; + } + if (thisScopeName === scopeName) { + return true; + } + const len = scopeName.length; + return thisScopeName.length > len && thisScopeName.substr(0, len) === scopeName && thisScopeName[len] === '.'; +} + +function getScopeMatcher(rule: ITextMateThemingRule): Matcher { + const ruleScope = rule.scope; + if (!ruleScope || !rule.settings) { + return noMatch; + } + const matchers: MatcherWithPriority[] = []; + if (Array.isArray(ruleScope)) { + for (let rs of ruleScope) { + createMatchers(rs, nameMatcher, matchers); + } + } else { + createMatchers(ruleScope, nameMatcher, matchers); + } + + if (matchers.length === 0) { + return noMatch; + } + return (scope: ProbeScope) => { + let max = matchers[0].matcher(scope); + for (let i = 1; i < matchers.length; i++) { + max = Math.max(max, matchers[i].matcher(scope)); + } + return max; + }; +} + +function getTokenStyle(foreground: string | undefined, fontStyle: string | undefined): TokenStyle { + let foregroundColor = undefined; + if (foreground !== undefined) { + foregroundColor = Color.fromHex(foreground); + } + let bold, underline, italic; + if (fontStyle !== undefined) { + fontStyle = fontStyle.trim(); + if (fontStyle.length === 0) { + bold = italic = underline = false; + } else { + const expression = /-?italic|-?bold|-?underline/g; + let match; + while ((match = expression.exec(fontStyle))) { + switch (match[0]) { + case 'bold': bold = true; break; + case '-bold': bold = false; break; + case 'italic': italic = true; break; + case '-italic': italic = false; break; + case 'underline': underline = true; break; + case '-underline': underline = false; break; + } + } + } + } + return new TokenStyle(foregroundColor, bold, underline, italic); + +} + +function readCustomTokenStyleRules(tokenStylingRuleSection: IExperimentalTokenStyleCustomizations, result: TokenStylingRule[] = []) { + for (let key in tokenStylingRuleSection) { + if (key[0] !== '[') { + const [type, ...modifiers] = key.split('.'); + const classification = tokenClassificationRegistry.getTokenClassification(type, modifiers); + if (classification) { + const settings = tokenStylingRuleSection[key]; + let style: TokenStyle | undefined; + if (typeof settings === 'string') { + style = getTokenStyle(settings, undefined); + } else if (isTokenColorizationSetting(settings)) { + style = getTokenStyle(settings.foreground, settings.fontStyle); + } + if (style) { + result.push(tokenClassificationRegistry.getTokenStylingRule(classification, style)); + } + } + } + } + return result; +} + +function isTokenColorizationSetting(style: any): style is ITokenColorizationSetting { + return style && (style.foreground || style.fontStyle); +} + + +class TokenColorIndex { + + private _lastColorId: number; + private _id2color: string[]; + private _color2id: { [color: string]: number; }; + + constructor() { + this._lastColorId = 0; + this._id2color = []; + this._color2id = Object.create(null); + } + + public add(color: string | Color | undefined | null): number { + if (color === null || color === undefined) { + return 0; + } + color = normalizeColorForIndex(color); + + let value = this._color2id[color]; + if (value) { + return value; + } + value = ++this._lastColorId; + this._color2id[color] = value; + this._id2color[value] = color; + return value; + } + + public get(color: string | Color | undefined): number { + if (color === undefined) { + return 0; + } + color = normalizeColorForIndex(color); + let value = this._color2id[color]; + if (value) { + return value; + } + console.log(`Color ${color} not in index.`); + return 0; + } + + public asArray(): string[] { + return this._id2color.slice(0); + } + +} + +function normalizeColorForIndex(color: string | Color): string { + if (typeof color !== 'string') { + color = Color.Format.CSS.formatHexA(color, true); + } + return color.toUpperCase(); +} + +function toMetadata(fontStyle: FontStyle, foreground: ColorId | number, background: ColorId | number) { + const fontStyleBits = fontStyle << MetadataConsts.FONT_STYLE_OFFSET; + const foregroundBits = foreground << MetadataConsts.FOREGROUND_OFFSET; + const backgroundBits = background << MetadataConsts.BACKGROUND_OFFSET; + if ((fontStyleBits & MetadataConsts.FONT_STYLE_MASK) !== fontStyleBits) { + console.log(`Can not express fontStyle ${fontStyle} in metadata`); + } + if ((backgroundBits & MetadataConsts.BACKGROUND_MASK) !== backgroundBits) { + console.log(`Can not express background ${background} in metadata`); + } + if ((foregroundBits & MetadataConsts.FOREGROUND_MASK) !== foregroundBits) { + console.log(`Can not express foreground ${foreground} in metadata`); + } + return (fontStyleBits | foregroundBits | backgroundBits) >>> 0; +} diff --git a/src/vs/workbench/services/themes/common/textMateScopeMatcher.ts b/src/vs/workbench/services/themes/common/textMateScopeMatcher.ts new file mode 100644 index 000000000000..53fb89ea5dbe --- /dev/null +++ b/src/vs/workbench/services/themes/common/textMateScopeMatcher.ts @@ -0,0 +1,134 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +export interface MatcherWithPriority { + matcher: Matcher; + priority: -1 | 0 | 1; +} + +export interface Matcher { + (matcherInput: T): number; +} + +export function createMatchers(selector: string, matchesName: (names: string[], matcherInput: T) => number, results: MatcherWithPriority[]): void { + const tokenizer = newTokenizer(selector); + let token = tokenizer.next(); + while (token !== null) { + let priority: -1 | 0 | 1 = 0; + if (token.length === 2 && token.charAt(1) === ':') { + switch (token.charAt(0)) { + case 'R': priority = 1; break; + case 'L': priority = -1; break; + default: + console.log(`Unknown priority ${token} in scope selector`); + } + token = tokenizer.next(); + } + let matcher = parseConjunction(); + if (matcher) { + results.push({ matcher, priority }); + } + if (token !== ',') { + break; + } + token = tokenizer.next(); + } + + function parseOperand(): Matcher | null { + if (token === '-') { + token = tokenizer.next(); + const expressionToNegate = parseOperand(); + if (!expressionToNegate) { + return null; + } + return matcherInput => { + const score = expressionToNegate(matcherInput); + return score < 0 ? 0 : -1; + }; + } + if (token === '(') { + token = tokenizer.next(); + const expressionInParents = parseInnerExpression(); + if (token === ')') { + token = tokenizer.next(); + } + return expressionInParents; + } + if (isIdentifier(token)) { + const identifiers: string[] = []; + do { + identifiers.push(token); + token = tokenizer.next(); + } while (isIdentifier(token)); + return matcherInput => matchesName(identifiers, matcherInput); + } + return null; + } + function parseConjunction(): Matcher | null { + let matcher = parseOperand(); + if (!matcher) { + return null; + } + + const matchers: Matcher[] = []; + while (matcher) { + matchers.push(matcher); + matcher = parseOperand(); + } + return matcherInput => { // and + let min = matchers[0](matcherInput); + for (let i = 1; min >= 0 && i < matchers.length; i++) { + min = Math.min(min, matchers[i](matcherInput)); + } + return min; + }; + } + function parseInnerExpression(): Matcher | null { + let matcher = parseConjunction(); + if (!matcher) { + return null; + } + const matchers: Matcher[] = []; + while (matcher) { + matchers.push(matcher); + if (token === '|' || token === ',') { + do { + token = tokenizer.next(); + } while (token === '|' || token === ','); // ignore subsequent commas + } else { + break; + } + matcher = parseConjunction(); + } + return matcherInput => { // or + let max = matchers[0](matcherInput); + for (let i = 1; i < matchers.length; i++) { + max = Math.max(max, matchers[i](matcherInput)); + } + return max; + }; + } +} + +function isIdentifier(token: string | null): token is string { + return !!token && !!token.match(/[\w\.:]+/); +} + +function newTokenizer(input: string): { next: () => string | null } { + let regex = /([LR]:|[\w\.:][\w\.:\-]*|[\,\|\-\(\)])/g; + let match = regex.exec(input); + return { + next: () => { + if (!match) { + return null; + } + const res = match[0]; + match = regex.exec(input); + return res; + } + }; +} diff --git a/src/vs/workbench/services/themes/common/themeCompatibility.ts b/src/vs/workbench/services/themes/common/themeCompatibility.ts index 43e406a5af29..29d88487bb64 100644 --- a/src/vs/workbench/services/themes/common/themeCompatibility.ts +++ b/src/vs/workbench/services/themes/common/themeCompatibility.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ITokenColorizationRule, IColorMap } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { ITextMateThemingRule, IColorMap } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { Color } from 'vs/base/common/color'; import * as colorRegistry from 'vs/platform/theme/common/colorRegistry'; @@ -18,9 +18,9 @@ function addSettingMapping(settingId: string, colorId: string) { colorIds.push(colorId); } -export function convertSettings(oldSettings: ITokenColorizationRule[], resultRules: ITokenColorizationRule[], resultColors: IColorMap): void { +export function convertSettings(oldSettings: ITextMateThemingRule[], result: { textMateRules: ITextMateThemingRule[], colors: IColorMap }): void { for (let rule of oldSettings) { - resultRules.push(rule); + result.textMateRules.push(rule); if (!rule.scope) { let settings = rule.settings; if (!settings) { @@ -34,7 +34,7 @@ export function convertSettings(oldSettings: ITokenColorizationRule[], resultRul if (typeof colorHex === 'string') { let color = Color.fromHex(colorHex); for (let colorId of mappings) { - resultColors[colorId] = color; + result.colors[colorId] = color; } } } diff --git a/src/vs/workbench/services/themes/common/workbenchThemeService.ts b/src/vs/workbench/services/themes/common/workbenchThemeService.ts index 00451491f44f..ef3cebff375c 100644 --- a/src/vs/workbench/services/themes/common/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/common/workbenchThemeService.ts @@ -22,6 +22,7 @@ export const DETECT_HC_SETTING = 'window.autoDetectHighContrast'; export const ICON_THEME_SETTING = 'workbench.iconTheme'; export const CUSTOM_WORKBENCH_COLORS_SETTING = 'workbench.colorCustomizations'; export const CUSTOM_EDITOR_COLORS_SETTING = 'editor.tokenColorCustomizations'; +export const CUSTOM_EDITOR_TOKENSTYLES_SETTING = 'editor.tokenColorCustomizationsExperimental'; export interface IColorTheme extends ITheme { readonly id: string; @@ -30,7 +31,7 @@ export interface IColorTheme extends ITheme { readonly extensionData?: ExtensionData; readonly description?: string; readonly isLoaded: boolean; - readonly tokenColors: ITokenColorizationRule[]; + readonly tokenColors: ITextMateThemingRule[]; } export interface IColorMap { @@ -69,7 +70,7 @@ export interface IColorCustomizations { } export interface ITokenColorCustomizations { - [groupIdOrThemeSettingsId: string]: string | ITokenColorizationSetting | ITokenColorCustomizations | undefined | ITokenColorizationRule[]; + [groupIdOrThemeSettingsId: string]: string | ITokenColorizationSetting | ITokenColorCustomizations | undefined | ITextMateThemingRule[]; comments?: string | ITokenColorizationSetting; strings?: string | ITokenColorizationSetting; numbers?: string | ITokenColorizationSetting; @@ -77,10 +78,14 @@ export interface ITokenColorCustomizations { types?: string | ITokenColorizationSetting; functions?: string | ITokenColorizationSetting; variables?: string | ITokenColorizationSetting; - textMateRules?: ITokenColorizationRule[]; + textMateRules?: ITextMateThemingRule[]; } -export interface ITokenColorizationRule { +export interface IExperimentalTokenStyleCustomizations { + [styleRuleOrThemeSettingsId: string]: string | ITokenColorizationSetting | IExperimentalTokenStyleCustomizations | undefined; +} + +export interface ITextMateThemingRule { name?: string; scope?: string | string[]; settings: ITokenColorizationSetting; @@ -89,7 +94,7 @@ export interface ITokenColorizationRule { export interface ITokenColorizationSetting { foreground?: string; background?: string; - fontStyle?: string; // italic, underline, bold + fontStyle?: string; /* [italic|underline|bold] */ } export interface ExtensionData { @@ -106,4 +111,4 @@ export interface IThemeExtensionPoint { path: string; uiTheme?: typeof VS_LIGHT_THEME | typeof VS_DARK_THEME | typeof VS_HC_THEME; _watch: boolean; // unsupported options to watch location -} \ No newline at end of file +} diff --git a/src/vs/workbench/services/themes/test/electron-browser/tokenStyleResolving.test.ts b/src/vs/workbench/services/themes/test/electron-browser/tokenStyleResolving.test.ts new file mode 100644 index 000000000000..7bb85407f054 --- /dev/null +++ b/src/vs/workbench/services/themes/test/electron-browser/tokenStyleResolving.test.ts @@ -0,0 +1,324 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ColorThemeData } from 'vs/workbench/services/themes/common/colorThemeData'; +import * as assert from 'assert'; +import { ITokenColorCustomizations } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { TokenStyle, comments, variables, types, functions, keywords, numbers, strings, getTokenClassificationRegistry } from 'vs/platform/theme/common/tokenClassificationRegistry'; +import { Color } from 'vs/base/common/color'; +import { isString } from 'vs/base/common/types'; +import { FileService } from 'vs/platform/files/common/fileService'; +import { NullLogService } from 'vs/platform/log/common/log'; +import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider'; +import { Schemas } from 'vs/base/common/network'; +import { URI } from 'vs/base/common/uri'; +import { getPathFromAmdModule } from 'vs/base/common/amd'; +import { ExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/electron-browser/extensionResourceLoaderService'; +import { TokenMetadata, FontStyle } from 'vs/editor/common/modes'; + +let tokenClassificationRegistry = getTokenClassificationRegistry(); + +const undefinedStyle = { bold: undefined, underline: undefined, italic: undefined }; +const unsetStyle = { bold: false, underline: false, italic: false }; + +function ts(foreground: string | undefined, styleFlags: { bold?: boolean; underline?: boolean; italic?: boolean } | undefined): TokenStyle { + const foregroundColor = isString(foreground) ? Color.fromHex(foreground) : undefined; + return new TokenStyle(foregroundColor, styleFlags && styleFlags.bold, styleFlags && styleFlags.underline, styleFlags && styleFlags.italic); +} + +function tokenStyleAsString(ts: TokenStyle | undefined | null) { + if (!ts) { + return 'tokenstyle-undefined'; + } + let str = ts.foreground ? ts.foreground.toString() : 'no-foreground'; + if (ts.bold !== undefined) { + str += ts.bold ? '+B' : '-B'; + } + if (ts.underline !== undefined) { + str += ts.underline ? '+U' : '-U'; + } + if (ts.italic !== undefined) { + str += ts.italic ? '+I' : '-I'; + } + return str; +} + +function assertTokenStyle(actual: TokenStyle | undefined | null, expected: TokenStyle | undefined | null, message?: string) { + assert.equal(tokenStyleAsString(actual), tokenStyleAsString(expected), message); +} + +function assertTokenStyleMetaData(colorIndex: string[], actual: number | undefined, expected: TokenStyle | undefined | null, message?: string) { + if (expected === undefined || expected === null || actual === undefined) { + assert.equal(actual, expected, message); + return; + } + const actualFontStyle = TokenMetadata.getFontStyle(actual); + assert.equal((actualFontStyle & FontStyle.Bold) === FontStyle.Bold, expected.bold === true, 'bold'); + assert.equal((actualFontStyle & FontStyle.Italic) === FontStyle.Italic, expected.italic === true, 'italic'); + assert.equal((actualFontStyle & FontStyle.Underline) === FontStyle.Underline, expected.underline === true, 'underline'); + + const actualForegroundIndex = TokenMetadata.getForeground(actual); + if (expected.foreground) { + assert.equal(actualForegroundIndex, colorIndex.indexOf(Color.Format.CSS.formatHexA(expected.foreground, true).toUpperCase()), 'foreground'); + } else { + assert.equal(actualForegroundIndex, 0, 'foreground'); + } + const actualBackgroundIndex = TokenMetadata.getBackground(actual); + assert.equal(actualBackgroundIndex, 0, 'background'); +} + + +function assertTokenStyles(themeData: ColorThemeData, expected: { [qualifiedClassifier: string]: TokenStyle }) { + const colorIndex = themeData.tokenColorMap; + + for (let qualifiedClassifier in expected) { + const [type, ...modifiers] = qualifiedClassifier.split('.'); + + const classification = tokenClassificationRegistry.getTokenClassification(type, modifiers); + assert.ok(classification, 'Classification not found'); + + const tokenStyle = themeData.getTokenStyle(classification!); + const expectedTokenStyle = expected[qualifiedClassifier]; + assertTokenStyle(tokenStyle, expectedTokenStyle, qualifiedClassifier); + + const tokenStyleMetaData = themeData.getTokenStyleMetadata(type, modifiers); + assertTokenStyleMetaData(colorIndex, tokenStyleMetaData, expectedTokenStyle); + } +} + +suite('Themes - TokenStyleResolving', () => { + + + const fileService = new FileService(new NullLogService()); + const extensionResourceLoaderService = new ExtensionResourceLoaderService(fileService); + + const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService()); + fileService.registerProvider(Schemas.file, diskFileSystemProvider); + + + test('color defaults - monokai', async () => { + const themeData = ColorThemeData.createUnloadedTheme('foo'); + const themeLocation = getPathFromAmdModule(require, '../../../../../../../extensions/theme-monokai/themes/monokai-color-theme.json'); + themeData.location = URI.file(themeLocation); + await themeData.ensureLoaded(extensionResourceLoaderService); + + assert.equal(themeData.isLoaded, true); + + assertTokenStyles(themeData, { + [comments]: ts('#88846f', undefinedStyle), + [variables]: ts('#F8F8F2', unsetStyle), + [types]: ts('#A6E22E', { underline: true }), + [functions]: ts('#A6E22E', unsetStyle), + [strings]: ts('#E6DB74', undefinedStyle), + [numbers]: ts('#AE81FF', undefinedStyle), + [keywords]: ts('#F92672', undefinedStyle) + }); + + }); + + test('color defaults - dark+', async () => { + const themeData = ColorThemeData.createUnloadedTheme('foo'); + const themeLocation = getPathFromAmdModule(require, '../../../../../../../extensions/theme-defaults/themes/dark_plus.json'); + themeData.location = URI.file(themeLocation); + await themeData.ensureLoaded(extensionResourceLoaderService); + + assert.equal(themeData.isLoaded, true); + + assertTokenStyles(themeData, { + [comments]: ts('#6A9955', undefinedStyle), + [variables]: ts('#9CDCFE', undefinedStyle), + [types]: ts('#4EC9B0', undefinedStyle), + [functions]: ts('#DCDCAA', undefinedStyle), + [strings]: ts('#CE9178', undefinedStyle), + [numbers]: ts('#B5CEA8', undefinedStyle), + [keywords]: ts('#C586C0', undefinedStyle) + }); + + }); + + test('color defaults - light vs', async () => { + const themeData = ColorThemeData.createUnloadedTheme('foo'); + const themeLocation = getPathFromAmdModule(require, '../../../../../../../extensions/theme-defaults/themes/light_vs.json'); + themeData.location = URI.file(themeLocation); + await themeData.ensureLoaded(extensionResourceLoaderService); + + assert.equal(themeData.isLoaded, true); + + assertTokenStyles(themeData, { + [comments]: ts('#008000', undefinedStyle), + [variables]: ts(undefined, undefinedStyle), + [types]: ts(undefined, undefinedStyle), + [functions]: ts(undefined, undefinedStyle), + [strings]: ts('#a31515', undefinedStyle), + [numbers]: ts('#09885a', undefinedStyle), + [keywords]: ts('#0000ff', undefinedStyle) + }); + + }); + + test('color defaults - hc', async () => { + const themeData = ColorThemeData.createUnloadedTheme('foo'); + const themeLocation = getPathFromAmdModule(require, '../../../../../../../extensions/theme-defaults/themes/hc_black.json'); + themeData.location = URI.file(themeLocation); + await themeData.ensureLoaded(extensionResourceLoaderService); + + assert.equal(themeData.isLoaded, true); + + assertTokenStyles(themeData, { + [comments]: ts('#7ca668', undefinedStyle), + [variables]: ts('#9CDCFE', undefinedStyle), + [types]: ts('#4EC9B0', undefinedStyle), + [functions]: ts('#DCDCAA', undefinedStyle), + [strings]: ts('#ce9178', undefinedStyle), + [numbers]: ts('#b5cea8', undefinedStyle), + [keywords]: ts('#C586C0', undefinedStyle) + }); + + }); + + test('color defaults - kimbie dark', async () => { + const themeData = ColorThemeData.createUnloadedTheme('foo'); + const themeLocation = getPathFromAmdModule(require, '../../../../../../../extensions/theme-kimbie-dark/themes/kimbie-dark-color-theme.json'); + themeData.location = URI.file(themeLocation); + await themeData.ensureLoaded(extensionResourceLoaderService); + + assert.equal(themeData.isLoaded, true); + + assertTokenStyles(themeData, { + [comments]: ts('#a57a4c', undefinedStyle), + [variables]: ts('#dc3958', undefinedStyle), + [types]: ts('#f06431', undefinedStyle), + [functions]: ts('#8ab1b0', undefinedStyle), + [strings]: ts('#889b4a', undefinedStyle), + [numbers]: ts('#f79a32', undefinedStyle), + [keywords]: ts('#98676a', undefinedStyle) + }); + + }); + + test('color defaults - abyss', async () => { + const themeData = ColorThemeData.createUnloadedTheme('foo'); + const themeLocation = getPathFromAmdModule(require, '../../../../../../../extensions/theme-abyss/themes/abyss-color-theme.json'); + themeData.location = URI.file(themeLocation); + await themeData.ensureLoaded(extensionResourceLoaderService); + + assert.equal(themeData.isLoaded, true); + + assertTokenStyles(themeData, { + [comments]: ts('#384887', undefinedStyle), + [variables]: ts(undefined, unsetStyle), + [types]: ts('#ffeebb', { underline: true }), + [functions]: ts('#ddbb88', unsetStyle), + [strings]: ts('#22aa44', undefinedStyle), + [numbers]: ts('#f280d0', undefinedStyle), + [keywords]: ts('#225588', undefinedStyle) + }); + + }); + + test('resolveScopes', async () => { + const themeData = ColorThemeData.createLoadedEmptyTheme('test', 'test'); + + const customTokenColors: ITokenColorCustomizations = { + textMateRules: [ + { + scope: 'variable', + settings: { + fontStyle: '', + foreground: '#F8F8F2' + } + }, + { + scope: 'keyword.operator', + settings: { + fontStyle: 'italic bold underline', + foreground: '#F92672' + } + }, + { + scope: 'storage', + settings: { + fontStyle: 'italic', + foreground: '#F92672' + } + }, + { + scope: ['storage.type', 'meta.structure.dictionary.json string.quoted.double.json'], + settings: { + foreground: '#66D9EF' + } + }, + { + scope: 'entity.name.type, entity.name.class, entity.name.namespace, entity.name.scope-resolution', + settings: { + fontStyle: 'underline', + foreground: '#A6E22E' + } + }, + ] + }; + + themeData.setCustomTokenColors(customTokenColors); + + let tokenStyle; + let defaultTokenStyle = undefined; + + tokenStyle = themeData.resolveScopes([['variable']]); + assertTokenStyle(tokenStyle, ts('#F8F8F2', unsetStyle), 'variable'); + + tokenStyle = themeData.resolveScopes([['keyword.operator']]); + assertTokenStyle(tokenStyle, ts('#F92672', { italic: true, bold: true, underline: true }), 'keyword'); + + tokenStyle = themeData.resolveScopes([['keyword']]); + assertTokenStyle(tokenStyle, defaultTokenStyle, 'keyword'); + + tokenStyle = themeData.resolveScopes([['keyword.operator']]); + assertTokenStyle(tokenStyle, ts('#F92672', { italic: true, bold: true, underline: true }), 'keyword.operator'); + + tokenStyle = themeData.resolveScopes([['keyword.operators']]); + assertTokenStyle(tokenStyle, defaultTokenStyle, 'keyword.operators'); + + tokenStyle = themeData.resolveScopes([['storage']]); + assertTokenStyle(tokenStyle, ts('#F92672', { italic: true }), 'storage'); + + tokenStyle = themeData.resolveScopes([['storage.type']]); + assertTokenStyle(tokenStyle, ts('#66D9EF', { italic: true }), 'storage.type'); + + tokenStyle = themeData.resolveScopes([['entity.name.class']]); + assertTokenStyle(tokenStyle, ts('#A6E22E', { underline: true }), 'entity.name.class'); + + tokenStyle = themeData.resolveScopes([['meta.structure.dictionary.json', 'string.quoted.double.json']]); + assertTokenStyle(tokenStyle, ts('#66D9EF', undefined), 'json property'); + + tokenStyle = themeData.resolveScopes([['keyword'], ['storage.type'], ['entity.name.class']]); + assertTokenStyle(tokenStyle, ts('#66D9EF', { italic: true }), 'storage.type'); + + }); + + test('rule matching', async () => { + const themeData = ColorThemeData.createLoadedEmptyTheme('test', 'test'); + themeData.setCustomColors({ 'editor.foreground': '#000000' }); + themeData.setCustomTokenStyleRules({ + 'types': '#ff0000', + 'classes': { foreground: '#0000ff', fontStyle: 'italic' }, + '*.static': { fontStyle: 'bold' }, + '*.declaration': { fontStyle: 'italic' }, + '*.async.static': { fontStyle: 'italic underline' }, + '*.async': { foreground: '#000fff', fontStyle: '-italic underline' } + }); + + assertTokenStyles(themeData, { + 'types': ts('#ff0000', undefinedStyle), + 'types.static': ts('#ff0000', { bold: true }), + 'types.static.declaration': ts('#ff0000', { bold: true, italic: true }), + 'classes': ts('#0000ff', { italic: true }), + 'classes.static.declaration': ts('#0000ff', { bold: true, italic: true }), + 'classes.declaration': ts('#0000ff', { italic: true }), + 'classes.declaration.async': ts('#000fff', { underline: true, italic: false }), + 'classes.declaration.async.static': ts('#000fff', { italic: true, underline: true, bold: true }), + }); + + }); +}); diff --git a/src/vs/workbench/services/untitled/common/untitledEditorService.ts b/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts similarity index 80% rename from src/vs/workbench/services/untitled/common/untitledEditorService.ts rename to src/vs/workbench/services/untitled/common/untitledTextEditorService.ts index c3a2213886a5..b235adaae36f 100644 --- a/src/vs/workbench/services/untitled/common/untitledEditorService.ts +++ b/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts @@ -6,18 +6,18 @@ import { URI } from 'vs/base/common/uri'; import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import * as arrays from 'vs/base/common/arrays'; -import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput'; +import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput'; import { IFilesConfiguration, IFileService } from 'vs/platform/files/common/files'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { Event, Emitter } from 'vs/base/common/event'; import { ResourceMap } from 'vs/base/common/map'; -import { UntitledEditorModel } from 'vs/workbench/common/editor/untitledEditorModel'; +import { UntitledTextEditorModel } from 'vs/workbench/common/editor/untitledTextEditorModel'; import { Schemas } from 'vs/base/common/network'; import { Disposable } from 'vs/base/common/lifecycle'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { basename } from 'vs/base/common/resources'; -export const IUntitledEditorService = createDecorator('untitledEditorService'); +export const IUntitledTextEditorService = createDecorator('untitledTextEditorService'); export interface IModelLoadOrCreateOptions { resource?: URI; @@ -27,27 +27,27 @@ export interface IModelLoadOrCreateOptions { useResourcePath?: boolean; } -export interface IUntitledEditorService { +export interface IUntitledTextEditorService { _serviceBrand: undefined; /** - * Events for when untitled editors content changes (e.g. any keystroke). + * Events for when untitled text editors content changes (e.g. any keystroke). */ readonly onDidChangeContent: Event; /** - * Events for when untitled editors change (e.g. getting dirty, saved or reverted). + * Events for when untitled text editors change (e.g. getting dirty, saved or reverted). */ readonly onDidChangeDirty: Event; /** - * Events for when untitled editor encodings change. + * Events for when untitled text editor encodings change. */ readonly onDidChangeEncoding: Event; /** - * Events for when untitled editors are disposed. + * Events for when untitled text editors are disposed. */ readonly onDidDisposeModel: Event; @@ -57,7 +57,7 @@ export interface IUntitledEditorService { exists(resource: URI): boolean; /** - * Returns dirty untitled editors as resource URIs. + * Returns dirty untitled text editors as resource URIs. */ getDirty(resources?: URI[]): URI[]; @@ -83,7 +83,7 @@ export interface IUntitledEditorService { * It is valid to pass in a file resource. In that case the path will be used as identifier. * The use case is to be able to create a new file with a specific path with VSCode. */ - createOrGet(resource?: URI, mode?: string, initialValue?: string, encoding?: string): UntitledEditorInput; + createOrGet(resource?: URI, mode?: string, initialValue?: string, encoding?: string): UntitledTextEditorInput; /** * Creates a new untitled model with the optional resource URI or returns an existing one @@ -92,7 +92,7 @@ export interface IUntitledEditorService { * It is valid to pass in a file resource. In that case the path will be used as identifier. * The use case is to be able to create a new file with a specific path with VSCode. */ - loadOrCreate(options: IModelLoadOrCreateOptions): Promise; + loadOrCreate(options: IModelLoadOrCreateOptions): Promise; /** * A check to find out if a untitled resource has a file path associated or not. @@ -110,24 +110,24 @@ export interface IUntitledEditorService { getEncoding(resource: URI): string | undefined; } -export class UntitledEditorService extends Disposable implements IUntitledEditorService { +export class UntitledTextEditorService extends Disposable implements IUntitledTextEditorService { _serviceBrand: undefined; - private mapResourceToInput = new ResourceMap(); + private mapResourceToInput = new ResourceMap(); private mapResourceToAssociatedFilePath = new ResourceMap(); - private readonly _onDidChangeContent: Emitter = this._register(new Emitter()); - readonly onDidChangeContent: Event = this._onDidChangeContent.event; + private readonly _onDidChangeContent = this._register(new Emitter()); + readonly onDidChangeContent = this._onDidChangeContent.event; - private readonly _onDidChangeDirty: Emitter = this._register(new Emitter()); - readonly onDidChangeDirty: Event = this._onDidChangeDirty.event; + private readonly _onDidChangeDirty = this._register(new Emitter()); + readonly onDidChangeDirty = this._onDidChangeDirty.event; - private readonly _onDidChangeEncoding: Emitter = this._register(new Emitter()); - readonly onDidChangeEncoding: Event = this._onDidChangeEncoding.event; + private readonly _onDidChangeEncoding = this._register(new Emitter()); + readonly onDidChangeEncoding = this._onDidChangeEncoding.event; - private readonly _onDidDisposeModel: Emitter = this._register(new Emitter()); - readonly onDidDisposeModel: Event = this._onDidDisposeModel.event; + private readonly _onDidDisposeModel = this._register(new Emitter()); + readonly onDidDisposeModel = this._onDidDisposeModel.event; constructor( @IInstantiationService private readonly instantiationService: IInstantiationService, @@ -137,11 +137,11 @@ export class UntitledEditorService extends Disposable implements IUntitledEditor super(); } - protected get(resource: URI): UntitledEditorInput | undefined { + protected get(resource: URI): UntitledTextEditorInput | undefined { return this.mapResourceToInput.get(resource); } - protected getAll(resources?: URI[]): UntitledEditorInput[] { + protected getAll(resources?: URI[]): UntitledTextEditorInput[] { if (resources) { return arrays.coalesce(resources.map(r => this.get(r))); } @@ -160,7 +160,6 @@ export class UntitledEditorService extends Disposable implements IUntitledEditor untitledInputs.forEach(input => { if (input) { input.revert(); - input.dispose(); reverted.push(input.getResource()); } @@ -182,7 +181,7 @@ export class UntitledEditorService extends Disposable implements IUntitledEditor } getDirty(resources?: URI[]): URI[] { - let inputs: UntitledEditorInput[]; + let inputs: UntitledTextEditorInput[]; if (resources) { inputs = arrays.coalesce(resources.map(r => this.get(r))); } else { @@ -194,11 +193,11 @@ export class UntitledEditorService extends Disposable implements IUntitledEditor .map(i => i.getResource()); } - loadOrCreate(options: IModelLoadOrCreateOptions = Object.create(null)): Promise { + loadOrCreate(options: IModelLoadOrCreateOptions = Object.create(null)): Promise { return this.createOrGet(options.resource, options.mode, options.initialValue, options.encoding, options.useResourcePath).resolve(); } - createOrGet(resource?: URI, mode?: string, initialValue?: string, encoding?: string, hasAssociatedFilePath: boolean = false): UntitledEditorInput { + createOrGet(resource?: URI, mode?: string, initialValue?: string, encoding?: string, hasAssociatedFilePath: boolean = false): UntitledTextEditorInput { if (resource) { // Massage resource if it comes with known file based resource @@ -221,7 +220,7 @@ export class UntitledEditorService extends Disposable implements IUntitledEditor return this.doCreate(resource, hasAssociatedFilePath, mode, initialValue, encoding); } - private doCreate(resource?: URI, hasAssociatedFilePath?: boolean, mode?: string, initialValue?: string, encoding?: string): UntitledEditorInput { + private doCreate(resource?: URI, hasAssociatedFilePath?: boolean, mode?: string, initialValue?: string, encoding?: string): UntitledTextEditorInput { let untitledResource: URI; if (resource) { untitledResource = resource; @@ -243,7 +242,7 @@ export class UntitledEditorService extends Disposable implements IUntitledEditor } } - const input = this.instantiationService.createInstance(UntitledEditorInput, untitledResource, !!hasAssociatedFilePath, mode, initialValue, encoding); + const input = this.instantiationService.createInstance(UntitledTextEditorInput, untitledResource, !!hasAssociatedFilePath, mode, initialValue, encoding); const contentListener = input.onDidModelChangeContent(() => this._onDidChangeContent.fire(untitledResource)); const dirtyListener = input.onDidChangeDirty(() => this._onDidChangeDirty.fire(untitledResource)); @@ -284,4 +283,4 @@ export class UntitledEditorService extends Disposable implements IUntitledEditor } } -registerSingleton(IUntitledEditorService, UntitledEditorService, true); +registerSingleton(IUntitledTextEditorService, UntitledTextEditorService, true); diff --git a/src/vs/workbench/services/url/electron-browser/urlService.ts b/src/vs/workbench/services/url/electron-browser/urlService.ts index 8cf6ecbc7647..ab882a96f8e8 100644 --- a/src/vs/workbench/services/url/electron-browser/urlService.ts +++ b/src/vs/workbench/services/url/electron-browser/urlService.ts @@ -8,7 +8,7 @@ import { URI, UriComponents } from 'vs/base/common/uri'; import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; import { URLHandlerChannel } from 'vs/platform/url/common/urlIpc'; import { URLService } from 'vs/platform/url/node/urlService'; -import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IOpenerService, IOpener, matchesScheme } from 'vs/platform/opener/common/opener'; import product from 'vs/platform/product/common/product'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService'; @@ -20,7 +20,7 @@ export interface IRelayOpenURLOptions extends IOpenURLOptions { openExternal?: boolean; } -export class RelayURLService extends URLService implements IURLHandler { +export class RelayURLService extends URLService implements IURLHandler, IOpener { private urlService: IURLService; @@ -51,11 +51,15 @@ export class RelayURLService extends URLService implements IURLHandler { return uri.with({ query }); } - async open(resource: URI, options?: IRelayOpenURLOptions): Promise { - if (resource.scheme !== product.urlProtocol) { + async open(resource: URI | string, options?: IRelayOpenURLOptions): Promise { + + if (!matchesScheme(resource, product.urlProtocol)) { return false; } + if (typeof resource === 'string') { + resource = URI.parse(resource); + } return await this.urlService.open(resource, options); } diff --git a/src/vs/workbench/services/userData/common/fileUserDataProvider.ts b/src/vs/workbench/services/userData/common/fileUserDataProvider.ts index 6066808d4e08..463d4b5e5348 100644 --- a/src/vs/workbench/services/userData/common/fileUserDataProvider.ts +++ b/src/vs/workbench/services/userData/common/fileUserDataProvider.ts @@ -5,14 +5,19 @@ import { Event, Emitter } from 'vs/base/common/event'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; -import { IFileSystemProviderWithFileReadWriteCapability, IFileChange, IWatchOptions, IStat, FileOverwriteOptions, FileType, FileWriteOptions, FileDeleteOptions, FileSystemProviderCapabilities, IFileSystemProviderWithOpenReadWriteCloseCapability, FileOpenOptions, hasReadWriteCapability, hasOpenReadWriteCloseCapability } from 'vs/platform/files/common/files'; +import { IFileSystemProviderWithFileReadWriteCapability, IFileChange, IWatchOptions, IStat, FileOverwriteOptions, FileType, FileWriteOptions, FileDeleteOptions, FileSystemProviderCapabilities, IFileSystemProviderWithOpenReadWriteCloseCapability, FileOpenOptions, hasReadWriteCapability, hasOpenReadWriteCloseCapability, IFileSystemProviderWithFileReadStreamCapability, FileReadStreamOptions, hasFileReadStreamCapability } from 'vs/platform/files/common/files'; import { URI } from 'vs/base/common/uri'; import * as resources from 'vs/base/common/resources'; import { startsWith } from 'vs/base/common/strings'; import { BACKUPS } from 'vs/platform/environment/common/environment'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { ReadableStreamEvents } from 'vs/base/common/stream'; -export class FileUserDataProvider extends Disposable implements IFileSystemProviderWithFileReadWriteCapability, IFileSystemProviderWithOpenReadWriteCloseCapability { +export class FileUserDataProvider extends Disposable implements + IFileSystemProviderWithFileReadWriteCapability, + IFileSystemProviderWithOpenReadWriteCloseCapability, + IFileSystemProviderWithFileReadStreamCapability { readonly capabilities: FileSystemProviderCapabilities = this.fileSystemProvider.capabilities; readonly onDidChangeCapabilities: Event = Event.None; @@ -60,6 +65,13 @@ export class FileUserDataProvider extends Disposable implements IFileSystemProvi throw new Error('not supported'); } + readFileStream(resource: URI, opts: FileReadStreamOptions, token?: CancellationToken): ReadableStreamEvents { + if (hasFileReadStreamCapability(this.fileSystemProvider)) { + return this.fileSystemProvider.readFileStream(this.toFileSystemResource(resource), opts, token); + } + throw new Error('not supported'); + } + readdir(resource: URI): Promise<[string, FileType][]> { return this.fileSystemProvider.readdir(this.toFileSystemResource(resource)); } diff --git a/src/vs/workbench/services/userData/common/inMemoryUserDataProvider.ts b/src/vs/workbench/services/userData/common/inMemoryUserDataProvider.ts index 30c36fe4df36..11335b84d538 100644 --- a/src/vs/workbench/services/userData/common/inMemoryUserDataProvider.ts +++ b/src/vs/workbench/services/userData/common/inMemoryUserDataProvider.ts @@ -6,7 +6,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import * as resources from 'vs/base/common/resources'; -import { FileChangeType, IFileSystemProvider, FileType, IWatchOptions, IStat, FileSystemProviderErrorCode, FileSystemProviderError, FileWriteOptions, IFileChange, FileDeleteOptions, FileSystemProviderCapabilities, FileOverwriteOptions } from 'vs/platform/files/common/files'; +import { FileChangeType, FileType, IWatchOptions, IStat, FileSystemProviderErrorCode, FileSystemProviderError, FileWriteOptions, IFileChange, FileDeleteOptions, FileSystemProviderCapabilities, FileOverwriteOptions, IFileSystemProviderWithFileReadWriteCapability } from 'vs/platform/files/common/files'; import { URI } from 'vs/base/common/uri'; class File implements IStat { @@ -50,7 +50,7 @@ class Directory implements IStat { export type Entry = File | Directory; -export class InMemoryFileSystemProvider extends Disposable implements IFileSystemProvider { +export class InMemoryFileSystemProvider extends Disposable implements IFileSystemProviderWithFileReadWriteCapability { readonly capabilities: FileSystemProviderCapabilities = FileSystemProviderCapabilities.FileReadWrite; readonly onDidChangeCapabilities: Event = Event.None; diff --git a/src/vs/workbench/services/userData/test/electron-browser/fileUserDataProvider.test.ts b/src/vs/workbench/services/userData/test/electron-browser/fileUserDataProvider.test.ts index d52f4ac8d3b8..49f6f6a80285 100644 --- a/src/vs/workbench/services/userData/test/electron-browser/fileUserDataProvider.test.ts +++ b/src/vs/workbench/services/userData/test/electron-browser/fileUserDataProvider.test.ts @@ -23,6 +23,15 @@ import { BrowserWorkbenchEnvironmentService } from 'vs/workbench/services/enviro import { Emitter, Event } from 'vs/base/common/event'; import { timeout } from 'vs/base/common/async'; +class TestBrowserWorkbenchEnvironmentService extends BrowserWorkbenchEnvironmentService { + + testUserRoamingDataHome!: URI; + + get userRoamingDataHome(): URI { + return this.testUserRoamingDataHome; + } +} + suite('FileUserDataProvider', () => { let testObject: IFileService; @@ -47,8 +56,8 @@ suite('FileUserDataProvider', () => { userDataResource = URI.file(userDataPath).with({ scheme: Schemas.userData }); await Promise.all([pfs.mkdirp(userDataPath), pfs.mkdirp(backupsPath)]); - const environmentService = new BrowserWorkbenchEnvironmentService({ remoteAuthority: 'remote', workspaceId: 'workspaceId', logsPath: URI.file('logFile') }); - environmentService.userRoamingDataHome = userDataResource; + const environmentService = new TestBrowserWorkbenchEnvironmentService({ remoteAuthority: 'remote', workspaceId: 'workspaceId', logsPath: URI.file('logFile') }); + environmentService.testUserRoamingDataHome = userDataResource; const userDataFileSystemProvider = new FileUserDataProvider(URI.file(userDataPath), URI.file(backupsPath), diskFileSystemProvider, environmentService); disposables.add(userDataFileSystemProvider); @@ -321,8 +330,8 @@ suite('FileUserDataProvider - Watching', () => { localUserDataResource = URI.file(userDataPath); userDataResource = localUserDataResource.with({ scheme: Schemas.userData }); - const environmentService = new BrowserWorkbenchEnvironmentService({ remoteAuthority: 'remote', workspaceId: 'workspaceId', logsPath: URI.file('logFile') }); - environmentService.userRoamingDataHome = userDataResource; + const environmentService = new TestBrowserWorkbenchEnvironmentService({ remoteAuthority: 'remote', workspaceId: 'workspaceId', logsPath: URI.file('logFile') }); + environmentService.testUserRoamingDataHome = userDataResource; const userDataFileSystemProvider = new FileUserDataProvider(localUserDataResource, localBackupsResource, new TestFileSystemProvider(fileEventEmitter.event), environmentService); disposables.add(userDataFileSystemProvider); diff --git a/src/vs/workbench/services/userDataSync/common/settingsMergeService.ts b/src/vs/workbench/services/userDataSync/common/settingsMergeService.ts index f589f4bff8d5..fd90c9088aaf 100644 --- a/src/vs/workbench/services/userDataSync/common/settingsMergeService.ts +++ b/src/vs/workbench/services/userDataSync/common/settingsMergeService.ts @@ -156,7 +156,7 @@ class SettingsMergeService implements ISettingsMergeService { const remote = parse(remoteContent); const remoteModel = this.modelService.createModel(localContent, this.modeService.create('jsonc')); const ignored = ignoredSettings.reduce((set, key) => { set.add(key); return set; }, new Set()); - for (const key of Object.keys(ignoredSettings)) { + for (const key of ignoredSettings) { if (ignored.has(key)) { this.editSetting(remoteModel, key, undefined); this.editSetting(remoteModel, key, remote[key]); @@ -166,8 +166,8 @@ class SettingsMergeService implements ISettingsMergeService { } private editSetting(model: ITextModel, key: string, value: any | undefined): void { - const insertSpaces = false; - const tabSize = 4; + const insertSpaces = model.getOptions().insertSpaces; + const tabSize = model.getOptions().tabSize; const eol = model.getEOL(); const edit = setProperty(model.getValue(), [key], value, { tabSize, insertSpaces, eol })[0]; if (edit) { diff --git a/src/vs/workbench/services/userDataSync/common/userDataSyncUtil.ts b/src/vs/workbench/services/userDataSync/common/userDataSyncUtil.ts new file mode 100644 index 000000000000..f7427e9896c0 --- /dev/null +++ b/src/vs/workbench/services/userDataSync/common/userDataSyncUtil.ts @@ -0,0 +1,40 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { IUserDataSyncUtilService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IStringDictionary } from 'vs/base/common/collections'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { FormattingOptions } from 'vs/base/common/jsonFormatter'; +import { URI } from 'vs/base/common/uri'; +import { ITextModelService } from 'vs/editor/common/services/resolverService'; + +class UserDataSyncUtilService implements IUserDataSyncUtilService { + + _serviceBrand: undefined; + + constructor( + @IKeybindingService private readonly keybindingsService: IKeybindingService, + @ITextModelService private readonly textModelService: ITextModelService, + ) { } + + public async resolveUserBindings(userBindings: string[]): Promise> { + const keys: IStringDictionary = {}; + for (const userbinding of userBindings) { + keys[userbinding] = this.keybindingsService.resolveUserBinding(userbinding).map(part => part.getUserSettingsLabel()).join(' '); + } + return keys; + } + + async resolveFormattingOptions(resource: URI): Promise { + const modelReference = await this.textModelService.createModelReference(resource); + const { insertSpaces, tabSize } = modelReference.object.textEditorModel.getOptions(); + const eol = modelReference.object.textEditorModel.getEOL(); + modelReference.dispose(); + return { eol, insertSpaces, tabSize }; + } +} + +registerSingleton(IUserDataSyncUtilService, UserDataSyncUtilService); diff --git a/src/vs/workbench/services/workingCopy/common/workingCopyService.ts b/src/vs/workbench/services/workingCopy/common/workingCopyService.ts new file mode 100644 index 000000000000..10a2c49b1644 --- /dev/null +++ b/src/vs/workbench/services/workingCopy/common/workingCopyService.ts @@ -0,0 +1,162 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { Event, Emitter } from 'vs/base/common/event'; +import { URI } from 'vs/base/common/uri'; +import { Disposable, IDisposable, toDisposable, DisposableStore, dispose } from 'vs/base/common/lifecycle'; +import { TernarySearchTree } from 'vs/base/common/map'; + +export const enum WorkingCopyCapabilities { + + /** + * Signals that the working copy participates + * in auto saving as configured by the user. + */ + AutoSave = 1 << 1 +} + +export interface IWorkingCopy { + + //#region Dirty Tracking + + readonly onDidChangeDirty: Event; + + isDirty(): boolean; + + //#endregion + + + readonly resource: URI; + + readonly capabilities: WorkingCopyCapabilities; +} + +export const IWorkingCopyService = createDecorator('workingCopyService'); + +export interface IWorkingCopyService { + + _serviceBrand: undefined; + + //#region Dirty Tracking + + readonly onDidChangeDirty: Event; + + readonly dirtyCount: number; + + readonly hasDirty: boolean; + + isDirty(resource: URI): boolean; + + //#endregion + + + //#region Registry + + registerWorkingCopy(workingCopy: IWorkingCopy): IDisposable; + + //#endregion +} + +export class WorkingCopyService extends Disposable implements IWorkingCopyService { + + _serviceBrand: undefined; + + //#region Dirty Tracking + + private readonly _onDidChangeDirty = this._register(new Emitter()); + readonly onDidChangeDirty = this._onDidChangeDirty.event; + + isDirty(resource: URI): boolean { + const workingCopies = this.mapResourceToWorkingCopy.get(resource.toString()); + if (workingCopies) { + for (const workingCopy of workingCopies) { + if (workingCopy.isDirty()) { + return true; + } + } + } + + return false; + } + + get hasDirty(): boolean { + for (const workingCopy of this.workingCopies) { + if (workingCopy.isDirty()) { + return true; + } + } + + return false; + } + + get dirtyCount(): number { + let totalDirtyCount = 0; + + for (const workingCopy of this.workingCopies) { + if (workingCopy.isDirty()) { + totalDirtyCount++; + } + } + + return totalDirtyCount; + } + + //#endregion + + + //#region Registry + + private mapResourceToWorkingCopy = TernarySearchTree.forPaths>(); + private workingCopies = new Set(); + + registerWorkingCopy(workingCopy: IWorkingCopy): IDisposable { + const disposables = new DisposableStore(); + + // Registry + let workingCopiesForResource = this.mapResourceToWorkingCopy.get(workingCopy.resource.toString()); + if (!workingCopiesForResource) { + workingCopiesForResource = new Set(); + this.mapResourceToWorkingCopy.set(workingCopy.resource.toString(), workingCopiesForResource); + } + + workingCopiesForResource.add(workingCopy); + + this.workingCopies.add(workingCopy); + + // Dirty Events + disposables.add(workingCopy.onDidChangeDirty(() => this._onDidChangeDirty.fire(workingCopy))); + if (workingCopy.isDirty()) { + this._onDidChangeDirty.fire(workingCopy); + } + + return toDisposable(() => { + this.unregisterWorkingCopy(workingCopy); + dispose(disposables); + }); + } + + private unregisterWorkingCopy(workingCopy: IWorkingCopy): void { + + // Remove from registry + const workingCopiesForResource = this.mapResourceToWorkingCopy.get(workingCopy.resource.toString()); + if (workingCopiesForResource && workingCopiesForResource.delete(workingCopy) && workingCopiesForResource.size === 0) { + this.mapResourceToWorkingCopy.delete(workingCopy.resource.toString()); + } + + this.workingCopies.delete(workingCopy); + + // If copy is dirty, ensure to fire an event to signal the dirty change + // (a disposed working copy cannot account for being dirty in our model) + if (workingCopy.isDirty()) { + this._onDidChangeDirty.fire(workingCopy); + } + } + + //#endregion +} + +registerSingleton(IWorkingCopyService, WorkingCopyService, true); diff --git a/src/vs/workbench/services/workingCopy/test/common/workingCopyService.test.ts b/src/vs/workbench/services/workingCopy/test/common/workingCopyService.test.ts new file mode 100644 index 000000000000..c7ecec2381ac --- /dev/null +++ b/src/vs/workbench/services/workingCopy/test/common/workingCopyService.test.ts @@ -0,0 +1,145 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { IWorkingCopy } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { URI } from 'vs/base/common/uri'; +import { Emitter } from 'vs/base/common/event'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { TestWorkingCopyService } from 'vs/workbench/test/workbenchTestServices'; + +suite('WorkingCopyService', () => { + + class TestWorkingCopy extends Disposable implements IWorkingCopy { + + private readonly _onDidChangeDirty = this._register(new Emitter()); + readonly onDidChangeDirty = this._onDidChangeDirty.event; + + private readonly _onDispose = this._register(new Emitter()); + readonly onDispose = this._onDispose.event; + + readonly capabilities = 0; + + private dirty = false; + + constructor(public readonly resource: URI, isDirty = false) { + super(); + + this.dirty = isDirty; + } + + setDirty(dirty: boolean): void { + if (this.dirty !== dirty) { + this.dirty = dirty; + this._onDidChangeDirty.fire(); + } + } + + isDirty(): boolean { + return this.dirty; + } + + dispose(): void { + this._onDispose.fire(); + + super.dispose(); + } + } + + test('registry - basics', () => { + const service = new TestWorkingCopyService(); + + const onDidChangeDirty: IWorkingCopy[] = []; + service.onDidChangeDirty(copy => onDidChangeDirty.push(copy)); + + assert.equal(service.hasDirty, false); + assert.equal(service.dirtyCount, 0); + assert.equal(service.isDirty(URI.file('/')), false); + + // resource 1 + const resource1 = URI.file('/some/folder/file.txt'); + const copy1 = new TestWorkingCopy(resource1); + const unregister1 = service.registerWorkingCopy(copy1); + + assert.equal(service.dirtyCount, 0); + assert.equal(service.isDirty(resource1), false); + assert.equal(service.hasDirty, false); + + copy1.setDirty(true); + + assert.equal(service.dirtyCount, 1); + assert.equal(service.isDirty(resource1), true); + assert.equal(service.hasDirty, true); + assert.equal(onDidChangeDirty.length, 1); + assert.equal(onDidChangeDirty[0], copy1); + + copy1.setDirty(false); + + assert.equal(service.dirtyCount, 0); + assert.equal(service.isDirty(resource1), false); + assert.equal(service.hasDirty, false); + assert.equal(onDidChangeDirty.length, 2); + assert.equal(onDidChangeDirty[1], copy1); + + unregister1.dispose(); + + // resource 2 + const resource2 = URI.file('/some/folder/file-dirty.txt'); + const copy2 = new TestWorkingCopy(resource2, true); + const unregister2 = service.registerWorkingCopy(copy2); + + assert.equal(service.dirtyCount, 1); + assert.equal(service.isDirty(resource2), true); + assert.equal(service.hasDirty, true); + + assert.equal(onDidChangeDirty.length, 3); + assert.equal(onDidChangeDirty[2], copy2); + + unregister2.dispose(); + assert.equal(service.dirtyCount, 0); + assert.equal(service.hasDirty, false); + assert.equal(onDidChangeDirty.length, 4); + assert.equal(onDidChangeDirty[3], copy2); + }); + + test('registry - multiple copies on same resource', () => { + const service = new TestWorkingCopyService(); + + const onDidChangeDirty: IWorkingCopy[] = []; + service.onDidChangeDirty(copy => onDidChangeDirty.push(copy)); + + const resource = URI.parse('custom://some/folder/custom.txt'); + + const copy1 = new TestWorkingCopy(resource); + const unregister1 = service.registerWorkingCopy(copy1); + + const copy2 = new TestWorkingCopy(resource); + const unregister2 = service.registerWorkingCopy(copy2); + + copy1.setDirty(true); + + assert.equal(service.dirtyCount, 1); + assert.equal(onDidChangeDirty.length, 1); + assert.equal(service.isDirty(resource), true); + + copy2.setDirty(true); + + assert.equal(service.dirtyCount, 2); + assert.equal(onDidChangeDirty.length, 2); + assert.equal(service.isDirty(resource), true); + + unregister1.dispose(); + + assert.equal(service.dirtyCount, 1); + assert.equal(onDidChangeDirty.length, 3); + assert.equal(service.isDirty(resource), true); + + unregister2.dispose(); + + assert.equal(service.dirtyCount, 0); + assert.equal(onDidChangeDirty.length, 4); + assert.equal(service.isDirty(resource), false); + }); +}); diff --git a/src/vs/workbench/services/workspaces/browser/abstractWorkspaceEditingService.ts b/src/vs/workbench/services/workspaces/browser/abstractWorkspaceEditingService.ts index 67935a68d676..ebda69b593f9 100644 --- a/src/vs/workbench/services/workspaces/browser/abstractWorkspaceEditingService.ts +++ b/src/vs/workbench/services/workspaces/browser/abstractWorkspaceEditingService.ts @@ -8,14 +8,10 @@ import { URI } from 'vs/base/common/uri'; import * as nls from 'vs/nls'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IJSONEditingService, JSONEditingError, JSONEditingErrorCode } from 'vs/workbench/services/configuration/common/jsonEditing'; -import { IWorkspaceIdentifier, IWorkspaceFolderCreationData, IWorkspacesService, rewriteWorkspaceFileForNewLocation, WORKSPACE_FILTER } from 'vs/platform/workspaces/common/workspaces'; +import { IWorkspaceIdentifier, IWorkspaceFolderCreationData, IWorkspacesService, rewriteWorkspaceFileForNewLocation, WORKSPACE_FILTER, IEnterWorkspaceResult, hasWorkspaceFileExtension, WORKSPACE_EXTENSION } from 'vs/platform/workspaces/common/workspaces'; import { WorkspaceService } from 'vs/workbench/services/configuration/browser/configurationService'; -import { IStorageService } from 'vs/platform/storage/common/storage'; import { ConfigurationScope, IConfigurationRegistry, Extensions as ConfigurationExtensions, IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { IBackupFileService, toBackupWorkspaceResource } from 'vs/workbench/services/backup/common/backup'; -import { BackupFileService } from 'vs/workbench/services/backup/common/backupFileService'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { distinct } from 'vs/base/common/arrays'; import { isEqual, getComparisonKey } from 'vs/base/common/resources'; @@ -36,9 +32,6 @@ export abstract class AbstractWorkspaceEditingService implements IWorkspaceEditi @IJSONEditingService private readonly jsonEditingService: IJSONEditingService, @IWorkspaceContextService private readonly contextService: WorkspaceService, @IConfigurationService private readonly configurationService: IConfigurationService, - @IStorageService private readonly storageService: IStorageService, - @IExtensionService private readonly extensionService: IExtensionService, - @IBackupFileService private readonly backupFileService: IBackupFileService, @INotificationService private readonly notificationService: INotificationService, @ICommandService private readonly commandService: ICommandService, @IFileService private readonly fileService: IFileService, @@ -50,13 +43,25 @@ export abstract class AbstractWorkspaceEditingService implements IWorkspaceEditi @IHostService protected readonly hostService: IHostService ) { } - pickNewWorkspacePath(): Promise { - return this.fileDialogService.showSaveDialog({ + async pickNewWorkspacePath(): Promise { + let workspacePath = await this.fileDialogService.showSaveDialog({ saveLabel: mnemonicButtonLabel(nls.localize('save', "Save")), title: nls.localize('saveWorkspace', "Save Workspace"), filters: WORKSPACE_FILTER, defaultUri: this.fileDialogService.defaultWorkspacePath() }); + + if (!workspacePath) { + return undefined; // canceled {{SQL CARBON EDIT}} strict-null-checks + } + + if (!hasWorkspaceFileExtension(workspacePath)) { + // Always ensure we have workspace file extension + // (see https://github.com/microsoft/vscode/issues/84818) + workspacePath = workspacePath.with({ path: `${workspacePath.path}.${WORKSPACE_EXTENSION}` }); + } + + return workspacePath; } updateFolders(index: number, deleteCount?: number, foldersToAdd?: IWorkspaceFolderCreationData[], donotNotifyError?: boolean): Promise { @@ -127,7 +132,7 @@ export abstract class AbstractWorkspaceEditingService implements IWorkspaceEditi // Do not allow workspace folders with scheme different than the current remote scheme const schemas = this.contextService.getWorkspace().folders.map(f => f.uri.scheme); if (schemas.length && foldersToAdd.some(f => schemas.indexOf(f.uri.scheme) === -1)) { - return Promise.reject(new Error(nls.localize('differentSchemeRoots', "Workspace folders from different providers are not allowed in the same workspace."))); + throw new Error(nls.localize('differentSchemeRoots', "Workspace folders from different providers are not allowed in the same workspace.")); } } @@ -267,7 +272,9 @@ export abstract class AbstractWorkspaceEditingService implements IWorkspaceEditi ); } - async enterWorkspace(path: URI): Promise { + abstract async enterWorkspace(path: URI): Promise; + + protected async doEnterWorkspace(path: URI): Promise { if (!!this.environmentService.extensionTestsLocationURI) { throw new Error('Entering a new workspace is not possible in tests.'); } @@ -282,34 +289,7 @@ export abstract class AbstractWorkspaceEditingService implements IWorkspaceEditi const workspaceImpl = this.contextService as WorkspaceService; await workspaceImpl.initialize(workspace); - const result = await this.workspacesService.enterWorkspace(path); - if (result) { - - // Migrate storage to new workspace - await this.migrateStorage(result.workspace); - - // Reinitialize backup service - this.environmentService.configuration.backupPath = result.backupPath; - this.environmentService.configuration.backupWorkspaceResource = result.backupPath ? toBackupWorkspaceResource(result.backupPath, this.environmentService) : undefined; - if (this.backupFileService instanceof BackupFileService) { - this.backupFileService.reinitialize(); - } - } - - // TODO@aeschli: workaround until restarting works - if (this.environmentService.configuration.remoteAuthority) { - this.hostService.reload(); - } - - // Restart the extension host: entering a workspace means a new location for - // storage and potentially a change in the workspace.rootPath property. - else { - this.extensionService.restartExtensionHost(); - } - } - - private migrateStorage(toWorkspace: IWorkspaceIdentifier): Promise { - return this.storageService.migrate(toWorkspace); + return this.workspacesService.enterWorkspace(path); } private migrateWorkspaceSettings(toWorkspace: IWorkspaceIdentifier): Promise { diff --git a/src/vs/workbench/services/workspaces/browser/workspaceEditingService.ts b/src/vs/workbench/services/workspaces/browser/workspaceEditingService.ts index 339c8fef6ea7..95fabe34658e 100644 --- a/src/vs/workbench/services/workspaces/browser/workspaceEditingService.ts +++ b/src/vs/workbench/services/workspaces/browser/workspaceEditingService.ts @@ -7,9 +7,6 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing'; import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; import { WorkspaceService } from 'vs/workbench/services/configuration/browser/configurationService'; -import { IStorageService } from 'vs/platform/storage/common/storage'; -import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IFileService } from 'vs/platform/files/common/files'; @@ -21,6 +18,7 @@ import { IHostService } from 'vs/workbench/services/host/browser/host'; import { AbstractWorkspaceEditingService } from 'vs/workbench/services/workspaces/browser/abstractWorkspaceEditingService'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { URI } from 'vs/base/common/uri'; export class BrowserWorkspaceEditingService extends AbstractWorkspaceEditingService { @@ -30,9 +28,6 @@ export class BrowserWorkspaceEditingService extends AbstractWorkspaceEditingServ @IJSONEditingService jsonEditingService: IJSONEditingService, @IWorkspaceContextService contextService: WorkspaceService, @IConfigurationService configurationService: IConfigurationService, - @IStorageService storageService: IStorageService, - @IExtensionService extensionService: IExtensionService, - @IBackupFileService backupFileService: IBackupFileService, @INotificationService notificationService: INotificationService, @ICommandService commandService: ICommandService, @IFileService fileService: IFileService, @@ -43,9 +38,17 @@ export class BrowserWorkspaceEditingService extends AbstractWorkspaceEditingServ @IDialogService dialogService: IDialogService, @IHostService hostService: IHostService ) { - super(jsonEditingService, contextService, configurationService, storageService, extensionService, backupFileService, notificationService, commandService, fileService, textFileService, workspacesService, environmentService, fileDialogService, dialogService, hostService); + super(jsonEditingService, contextService, configurationService, notificationService, commandService, fileService, textFileService, workspacesService, environmentService, fileDialogService, dialogService, hostService); + } + + async enterWorkspace(path: URI): Promise { + const result = await this.doEnterWorkspace(path); + if (result) { + + // Open workspace in same window + await this.hostService.openWindow([{ workspaceUri: path }], { forceReuseWindow: true }); + } } } registerSingleton(IWorkspaceEditingService, BrowserWorkspaceEditingService, true); - diff --git a/src/vs/workbench/services/workspaces/browser/workspacesService.ts b/src/vs/workbench/services/workspaces/browser/workspacesService.ts index db573c09c013..32393f351c5b 100644 --- a/src/vs/workbench/services/workspaces/browser/workspacesService.ts +++ b/src/vs/workbench/services/workspaces/browser/workspacesService.ts @@ -12,7 +12,6 @@ import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/ import { ILogService } from 'vs/platform/log/common/log'; import { Disposable } from 'vs/base/common/lifecycle'; import { getWorkspaceIdentifier } from 'vs/workbench/services/workspaces/browser/workspaces'; -import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IFileService } from 'vs/platform/files/common/files'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { joinPath } from 'vs/base/common/resources'; @@ -31,7 +30,6 @@ export class BrowserWorkspacesService extends Disposable implements IWorkspacesS @IStorageService private readonly storageService: IStorageService, @IWorkspaceContextService private readonly workspaceService: IWorkspaceContextService, @ILogService private readonly logService: ILogService, - @IHostService private readonly hostService: IHostService, @IFileService private readonly fileService: IFileService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService ) { @@ -123,18 +121,12 @@ export class BrowserWorkspacesService extends Disposable implements IWorkspacesS //#region Workspace Management async enterWorkspace(path: URI): Promise { - - // Open workspace in same window - await this.hostService.openWindow([{ workspaceUri: path }], { forceReuseWindow: true }); - - return { - workspace: await this.getWorkspaceIdentifier(path) - }; + return { workspace: await this.getWorkspaceIdentifier(path) }; } async createUntitledWorkspace(folders?: IWorkspaceFolderCreationData[], remoteAuthority?: string): Promise { const randomId = (Date.now() + Math.round(Math.random() * 1000)).toString(); - const newUntitledWorkspacePath = joinPath(this.environmentService.untitledWorkspacesHome, `${randomId}.${WORKSPACE_EXTENSION}`); + const newUntitledWorkspacePath = joinPath(this.environmentService.untitledWorkspacesHome, `Untitled-${randomId}.${WORKSPACE_EXTENSION}`); // Build array of workspace folders to store const storedWorkspaceFolder: IStoredWorkspaceFolder[] = []; diff --git a/src/vs/workbench/services/workspaces/electron-browser/workspaceEditingService.ts b/src/vs/workbench/services/workspaces/electron-browser/workspaceEditingService.ts index 1496e8c92a0a..e15a76e8da95 100644 --- a/src/vs/workbench/services/workspaces/electron-browser/workspaceEditingService.ts +++ b/src/vs/workbench/services/workspaces/electron-browser/workspaceEditingService.ts @@ -8,13 +8,14 @@ import { URI } from 'vs/base/common/uri'; import * as nls from 'vs/nls'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing'; -import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; +import { IWorkspacesService, isUntitledWorkspace, IWorkspaceIdentifier, hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces'; import { WorkspaceService } from 'vs/workbench/services/configuration/browser/configurationService'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; +import { toBackupWorkspaceResource } from 'vs/workbench/services/backup/electron-browser/backup'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { isEqual, basename, isEqualOrParent } from 'vs/base/common/resources'; +import { isEqual, basename } from 'vs/base/common/resources'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IFileService } from 'vs/platform/files/common/files'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -29,6 +30,7 @@ import { AbstractWorkspaceEditingService } from 'vs/workbench/services/workspace import { IElectronService } from 'vs/platform/electron/node/electron'; import { isMacintosh, isWindows, isLinux } from 'vs/base/common/platform'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; +import { BackupFileService } from 'vs/workbench/services/backup/common/backupFileService'; export class NativeWorkspaceEditingService extends AbstractWorkspaceEditingService { @@ -39,9 +41,9 @@ export class NativeWorkspaceEditingService extends AbstractWorkspaceEditingServi @IWorkspaceContextService contextService: WorkspaceService, @IElectronService private electronService: IElectronService, @IConfigurationService configurationService: IConfigurationService, - @IStorageService storageService: IStorageService, - @IExtensionService extensionService: IExtensionService, - @IBackupFileService backupFileService: IBackupFileService, + @IStorageService private storageService: IStorageService, + @IExtensionService private extensionService: IExtensionService, + @IBackupFileService private backupFileService: IBackupFileService, @INotificationService notificationService: INotificationService, @ICommandService commandService: ICommandService, @IFileService fileService: IFileService, @@ -54,7 +56,7 @@ export class NativeWorkspaceEditingService extends AbstractWorkspaceEditingServi @ILabelService private readonly labelService: ILabelService, @IHostService hostService: IHostService, ) { - super(jsonEditingService, contextService, configurationService, storageService, extensionService, backupFileService, notificationService, commandService, fileService, textFileService, workspacesService, environmentService, fileDialogService, dialogService, hostService); + super(jsonEditingService, contextService, configurationService, notificationService, commandService, fileService, textFileService, workspacesService, environmentService, fileDialogService, dialogService, hostService); this.registerListeners(); } @@ -74,7 +76,7 @@ export class NativeWorkspaceEditingService extends AbstractWorkspaceEditingServi } const workspaceIdentifier = this.getCurrentWorkspaceIdentifier(); - if (!workspaceIdentifier || !isEqualOrParent(workspaceIdentifier.configPath, this.environmentService.untitledWorkspacesHome)) { + if (!workspaceIdentifier || !isUntitledWorkspace(workspaceIdentifier.configPath, this.environmentService)) { return false; // only care about untitled workspaces to ask for saving } @@ -122,7 +124,7 @@ export class NativeWorkspaceEditingService extends AbstractWorkspaceEditingServi // Save: save workspace, but do not veto unload if path provided case ConfirmResult.SAVE: { const newWorkspacePath = await this.pickNewWorkspacePath(); - if (!newWorkspacePath) { + if (!newWorkspacePath || !hasWorkspaceFileExtension(newWorkspacePath)) { return true; // keep veto if no target was provided } @@ -165,6 +167,37 @@ export class NativeWorkspaceEditingService extends AbstractWorkspaceEditingServi return true; // OK } + + async enterWorkspace(path: URI): Promise { + const result = await this.doEnterWorkspace(path); + if (result) { + + // Migrate storage to new workspace + await this.migrateStorage(result.workspace); + + // Reinitialize backup service + this.environmentService.configuration.backupPath = result.backupPath; + this.environmentService.configuration.backupWorkspaceResource = result.backupPath ? toBackupWorkspaceResource(result.backupPath, this.environmentService) : undefined; + if (this.backupFileService instanceof BackupFileService) { + this.backupFileService.reinitialize(); + } + } + + // TODO@aeschli: workaround until restarting works + if (this.environmentService.configuration.remoteAuthority) { + this.hostService.reload(); + } + + // Restart the extension host: entering a workspace means a new location for + // storage and potentially a change in the workspace.rootPath property. + else { + this.extensionService.restartExtensionHost(); + } + } + + private migrateStorage(toWorkspace: IWorkspaceIdentifier): Promise { + return this.storageService.migrate(toWorkspace); + } } registerSingleton(IWorkspaceEditingService, NativeWorkspaceEditingService, true); diff --git a/src/vs/workbench/test/browser/parts/editor/baseEditor.test.ts b/src/vs/workbench/test/browser/parts/editor/baseEditor.test.ts index 3f30fa6baba7..7af8bb48b5f0 100644 --- a/src/vs/workbench/test/browser/parts/editor/baseEditor.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/baseEditor.test.ts @@ -115,14 +115,14 @@ suite('Workbench base editor', () => { }); test('EditorDescriptor', () => { - let d = new EditorDescriptor(MyEditor, 'id', 'name'); + let d = EditorDescriptor.create(MyEditor, 'id', 'name'); assert.strictEqual(d.getId(), 'id'); assert.strictEqual(d.getName(), 'name'); }); test('Editor Registration', function () { - let d1 = new EditorDescriptor(MyEditor, 'id1', 'name'); - let d2 = new EditorDescriptor(MyOtherEditor, 'id2', 'name'); + let d1 = EditorDescriptor.create(MyEditor, 'id1', 'name'); + let d2 = EditorDescriptor.create(MyOtherEditor, 'id2', 'name'); let oldEditorsCnt = EditorRegistry.getEditors().length; let oldInputCnt = (EditorRegistry).getEditorInputs().length; @@ -142,8 +142,8 @@ suite('Workbench base editor', () => { }); test('Editor Lookup favors specific class over superclass (match on specific class)', function () { - let d1 = new EditorDescriptor(MyEditor, 'id1', 'name'); - let d2 = new EditorDescriptor(MyOtherEditor, 'id2', 'name'); + let d1 = EditorDescriptor.create(MyEditor, 'id1', 'name'); + let d2 = EditorDescriptor.create(MyOtherEditor, 'id2', 'name'); let oldEditors = EditorRegistry.getEditors(); (EditorRegistry).setEditors([]); @@ -163,7 +163,7 @@ suite('Workbench base editor', () => { }); test('Editor Lookup favors specific class over superclass (match on super class)', function () { - let d1 = new EditorDescriptor(MyOtherEditor, 'id1', 'name'); + let d1 = EditorDescriptor.create(MyOtherEditor, 'id1', 'name'); let oldEditors = EditorRegistry.getEditors(); (EditorRegistry).setEditors([]); diff --git a/src/vs/workbench/test/browser/parts/editor/breadcrumbModel.test.ts b/src/vs/workbench/test/browser/parts/editor/breadcrumbModel.test.ts index 59cc7c9356df..b621b883f896 100644 --- a/src/vs/workbench/test/browser/parts/editor/breadcrumbModel.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/breadcrumbModel.test.ts @@ -29,7 +29,7 @@ suite('Breadcrumb Model', function () { test('only uri, inside workspace', function () { - let model = new EditorBreadcrumbsModel(URI.parse('foo:/bar/baz/ws/some/path/file.ts'), undefined, configService, workspaceService); + let model = new EditorBreadcrumbsModel(URI.parse('foo:/bar/baz/ws/some/path/file.ts'), undefined, configService, configService, workspaceService); let elements = model.getElements(); assert.equal(elements.length, 3); @@ -44,7 +44,7 @@ suite('Breadcrumb Model', function () { test('only uri, outside workspace', function () { - let model = new EditorBreadcrumbsModel(URI.parse('foo:/outside/file.ts'), undefined, configService, workspaceService); + let model = new EditorBreadcrumbsModel(URI.parse('foo:/outside/file.ts'), undefined, configService, configService, workspaceService); let elements = model.getElements(); assert.equal(elements.length, 2); diff --git a/src/vs/workbench/test/browser/parts/views/views.test.ts b/src/vs/workbench/test/browser/parts/views/views.test.ts index d4fc28a77904..30590a60344d 100644 --- a/src/vs/workbench/test/browser/parts/views/views.test.ts +++ b/src/vs/workbench/test/browser/parts/views/views.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { ContributableViewsModel, ViewsService } from 'vs/workbench/browser/parts/views/views'; +import { ContributableViewsModel, ViewsService, IViewState } from 'vs/workbench/browser/parts/views/views'; import { IViewsRegistry, IViewDescriptor, IViewContainersRegistry, Extensions as ViewContainerExtensions, IViewsService } from 'vs/workbench/common/views'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { move } from 'vs/base/common/arrays'; @@ -13,6 +13,7 @@ import { workbenchInstantiationService } from 'vs/workbench/test/workbenchTestSe import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { ContextKeyService } from 'vs/platform/contextkey/browser/contextKeyService'; +import sinon = require('sinon'); const container = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer('test'); const ViewsRegistry = Registry.as(ViewContainerExtensions.ViewsRegistry); @@ -244,4 +245,130 @@ suite('ContributableViewsModel', () => { assert.deepEqual(model.visibleViewDescriptors, [view1, view2, view3], 'view2 should go to the middle'); assert.deepEqual(seq.elements, [view1, view2, view3]); }); + + test('view states', async function () { + const viewStates = new Map(); + viewStates.set('view1', { visibleGlobal: false, collapsed: false, visibleWorkspace: undefined }); + const model = new ContributableViewsModel(container, viewsService, viewStates); + const seq = new ViewDescriptorSequence(model); + + assert.equal(model.visibleViewDescriptors.length, 0); + assert.equal(seq.elements.length, 0); + + const viewDescriptor: IViewDescriptor = { + id: 'view1', + ctorDescriptor: null!, + name: 'Test View 1' + }; + + ViewsRegistry.registerViews([viewDescriptor], container); + assert.equal(model.visibleViewDescriptors.length, 0, 'view should not appear since it was set not visible in view state'); + assert.equal(seq.elements.length, 0); + }); + + test('view states and when contexts', async function () { + const viewStates = new Map(); + viewStates.set('view1', { visibleGlobal: false, collapsed: false, visibleWorkspace: undefined }); + const model = new ContributableViewsModel(container, viewsService, viewStates); + const seq = new ViewDescriptorSequence(model); + + assert.equal(model.visibleViewDescriptors.length, 0); + assert.equal(seq.elements.length, 0); + + const viewDescriptor: IViewDescriptor = { + id: 'view1', + ctorDescriptor: null!, + name: 'Test View 1', + when: ContextKeyExpr.equals('showview1', true) + }; + + ViewsRegistry.registerViews([viewDescriptor], container); + assert.equal(model.visibleViewDescriptors.length, 0, 'view should not appear since context isnt in'); + assert.equal(seq.elements.length, 0); + + const key = contextKeyService.createKey('showview1', false); + assert.equal(model.visibleViewDescriptors.length, 0, 'view should still not appear since showview1 isnt true'); + assert.equal(seq.elements.length, 0); + + key.set(true); + await new Promise(c => setTimeout(c, 30)); + assert.equal(model.visibleViewDescriptors.length, 0, 'view should still not appear since it was set not visible in view state'); + assert.equal(seq.elements.length, 0); + }); + + test('view states and when contexts multiple views', async function () { + const viewStates = new Map(); + viewStates.set('view1', { visibleGlobal: false, collapsed: false, visibleWorkspace: undefined }); + const model = new ContributableViewsModel(container, viewsService, viewStates); + const seq = new ViewDescriptorSequence(model); + + assert.equal(model.visibleViewDescriptors.length, 0); + assert.equal(seq.elements.length, 0); + + const view1: IViewDescriptor = { + id: 'view1', + ctorDescriptor: null!, + name: 'Test View 1', + when: ContextKeyExpr.equals('showview', true) + }; + const view2: IViewDescriptor = { + id: 'view2', + ctorDescriptor: null!, + name: 'Test View 2', + }; + const view3: IViewDescriptor = { + id: 'view3', + ctorDescriptor: null!, + name: 'Test View 3', + when: ContextKeyExpr.equals('showview', true) + }; + + ViewsRegistry.registerViews([view1, view2, view3], container); + assert.deepEqual(model.visibleViewDescriptors, [view2], 'Only view2 should be visible'); + assert.deepEqual(seq.elements, [view2]); + + const key = contextKeyService.createKey('showview', false); + assert.deepEqual(model.visibleViewDescriptors, [view2], 'Only view2 should be visible'); + assert.deepEqual(seq.elements, [view2]); + + key.set(true); + await new Promise(c => setTimeout(c, 30)); + assert.deepEqual(model.visibleViewDescriptors, [view2, view3], 'view3 should be visible'); + assert.deepEqual(seq.elements, [view2, view3]); + + key.set(false); + await new Promise(c => setTimeout(c, 30)); + assert.deepEqual(model.visibleViewDescriptors, [view2], 'Only view2 should be visible'); + assert.deepEqual(seq.elements, [view2]); + }); + + test('remove event is not triggered if view was hidden and removed', async function () { + const model = new ContributableViewsModel(container, viewsService); + const seq = new ViewDescriptorSequence(model); + + const viewDescriptor: IViewDescriptor = { + id: 'view1', + ctorDescriptor: null!, + name: 'Test View 1', + when: ContextKeyExpr.equals('showview1', true), + canToggleVisibility: true + }; + + ViewsRegistry.registerViews([viewDescriptor], container); + + const key = contextKeyService.createKey('showview1', true); + await new Promise(c => setTimeout(c, 30)); + assert.equal(model.visibleViewDescriptors.length, 1, 'view should appear after context is set'); + assert.equal(seq.elements.length, 1); + + model.setVisible('view1', false); + assert.equal(model.visibleViewDescriptors.length, 0, 'view should disappear after setting visibility to false'); + assert.equal(seq.elements.length, 0); + + const target = sinon.spy(model.onDidRemove); + key.set(false); + await new Promise(c => setTimeout(c, 30)); + assert.ok(!target.called, 'remove event should not be called since it is already hidden'); + }); + }); diff --git a/src/vs/workbench/test/browser/quickopen.test.ts b/src/vs/workbench/test/browser/quickopen.test.ts index 5a412ad3bace..93cba1b1bce4 100644 --- a/src/vs/workbench/test/browser/quickopen.test.ts +++ b/src/vs/workbench/test/browser/quickopen.test.ts @@ -54,7 +54,7 @@ suite('QuickOpen', () => { test('QuickOpen Handler and Registry', () => { let registry = (Registry.as(QuickOpenExtensions.Quickopen)); - let handler = new QuickOpenHandlerDescriptor( + let handler = QuickOpenHandlerDescriptor.create( TestHandler, 'testhandler', ',', @@ -77,4 +77,4 @@ suite('QuickOpen', () => { defaultAction.run(); prefixAction.run(); }); -}); \ No newline at end of file +}); diff --git a/src/vs/workbench/test/browser/viewlet.test.ts b/src/vs/workbench/test/browser/viewlet.test.ts index 38c7c3e19cac..dacedd8c4de5 100644 --- a/src/vs/workbench/test/browser/viewlet.test.ts +++ b/src/vs/workbench/test/browser/viewlet.test.ts @@ -22,7 +22,7 @@ suite('Viewlets', () => { } test('ViewletDescriptor API', function () { - let d = new ViewletDescriptor(TestViewlet, 'id', 'name', 'class', 5); + let d = ViewletDescriptor.create(TestViewlet, 'id', 'name', 'class', 5); assert.strictEqual(d.id, 'id'); assert.strictEqual(d.name, 'name'); assert.strictEqual(d.cssClass, 'class'); @@ -30,11 +30,11 @@ suite('Viewlets', () => { }); test('Editor Aware ViewletDescriptor API', function () { - let d = new ViewletDescriptor(TestViewlet, 'id', 'name', 'class', 5); + let d = ViewletDescriptor.create(TestViewlet, 'id', 'name', 'class', 5); assert.strictEqual(d.id, 'id'); assert.strictEqual(d.name, 'name'); - d = new ViewletDescriptor(TestViewlet, 'id', 'name', 'class', 5); + d = ViewletDescriptor.create(TestViewlet, 'id', 'name', 'class', 5); assert.strictEqual(d.id, 'id'); assert.strictEqual(d.name, 'name'); }); @@ -45,7 +45,7 @@ suite('Viewlets', () => { assert(Types.isFunction(Platform.Registry.as(Extensions.Viewlets).getViewlets)); let oldCount = Platform.Registry.as(Extensions.Viewlets).getViewlets().length; - let d = new ViewletDescriptor(TestViewlet, 'reg-test-id', 'name'); + let d = ViewletDescriptor.create(TestViewlet, 'reg-test-id', 'name'); Platform.Registry.as(Extensions.Viewlets).registerViewlet(d); assert(d === Platform.Registry.as(Extensions.Viewlets).getViewlet('reg-test-id')); diff --git a/src/vs/workbench/test/common/editor/dataUriEditorInput.test.ts b/src/vs/workbench/test/common/editor/dataUriEditorInput.test.ts deleted file mode 100644 index aae0682f7477..000000000000 --- a/src/vs/workbench/test/common/editor/dataUriEditorInput.test.ts +++ /dev/null @@ -1,34 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as assert from 'assert'; -import { URI } from 'vs/base/common/uri'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { workbenchInstantiationService } from 'vs/workbench/test/workbenchTestServices'; -import { DataUriEditorInput } from 'vs/workbench/common/editor/dataUriEditorInput'; -import { BinaryEditorModel } from 'vs/workbench/common/editor/binaryEditorModel'; - -suite('DataUriEditorInput', () => { - - let instantiationService: IInstantiationService; - - setup(() => { - instantiationService = workbenchInstantiationService(); - }); - - test('simple', () => { - const resource = URI.parse('data:image/png;label:SomeLabel;description:SomeDescription;size:1024;base64,77+9UE5'); - const input: DataUriEditorInput = instantiationService.createInstance(DataUriEditorInput, undefined, undefined, resource); - - assert.equal(input.getName(), 'SomeLabel'); - assert.equal(input.getDescription(), 'SomeDescription'); - - return input.resolve().then((model: BinaryEditorModel) => { - assert.ok(model); - assert.equal(model.getSize(), 1024); - assert.equal(model.getMime(), 'image/png'); - }); - }); -}); \ No newline at end of file diff --git a/src/vs/workbench/test/common/editor/editor.test.ts b/src/vs/workbench/test/common/editor/editor.test.ts index 2b154890d039..9d77f92dcbc8 100644 --- a/src/vs/workbench/test/common/editor/editor.test.ts +++ b/src/vs/workbench/test/common/editor/editor.test.ts @@ -8,13 +8,13 @@ import { EditorInput, toResource, SideBySideEditor } from 'vs/workbench/common/e import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { IEditorModel } from 'vs/platform/editor/common/editor'; import { URI } from 'vs/base/common/uri'; -import { IUntitledEditorService, UntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; +import { IUntitledTextEditorService, UntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { workbenchInstantiationService } from 'vs/workbench/test/workbenchTestServices'; import { Schemas } from 'vs/base/common/network'; class ServiceAccessor { - constructor(@IUntitledEditorService public untitledEditorService: UntitledEditorService) { + constructor(@IUntitledTextEditorService public untitledTextEditorService: UntitledTextEditorService) { } } @@ -48,12 +48,12 @@ suite('Workbench editor', () => { }); teardown(() => { - accessor.untitledEditorService.revertAll(); - accessor.untitledEditorService.dispose(); + accessor.untitledTextEditorService.revertAll(); + accessor.untitledTextEditorService.dispose(); }); test('toResource', () => { - const service = accessor.untitledEditorService; + const service = accessor.untitledTextEditorService; assert.ok(!toResource(null!)); @@ -82,4 +82,4 @@ suite('Workbench editor', () => { assert.equal(toResource(file, { supportSideBySide: SideBySideEditor.MASTER, filterByScheme: Schemas.file })!.toString(), file.getResource().toString()); assert.equal(toResource(file, { supportSideBySide: SideBySideEditor.MASTER, filterByScheme: [Schemas.file, Schemas.untitled] })!.toString(), file.getResource().toString()); }); -}); \ No newline at end of file +}); diff --git a/src/vs/workbench/test/common/editor/editorGroups.test.ts b/src/vs/workbench/test/common/editor/editorGroups.test.ts index fcd5010fff6c..7697eec3e58e 100644 --- a/src/vs/workbench/test/common/editor/editorGroups.test.ts +++ b/src/vs/workbench/test/common/editor/editorGroups.test.ts @@ -187,19 +187,79 @@ suite('Workbench editor groups', () => { assert.equal(clone.isActive(input3), true); }); - test('contains() with diff editor support', function () { + test('contains()', function () { const group = createGroup(); const input1 = input(); const input2 = input(); - const diffInput = new DiffEditorInput('name', 'description', input1, input2); + const diffInput1 = new DiffEditorInput('name', 'description', input1, input2); + const diffInput2 = new DiffEditorInput('name', 'description', input2, input1); + + group.openEditor(input1, { pinned: true, active: true }); + + assert.equal(group.contains(input1), true); + assert.equal(group.contains(input1, true), true); + assert.equal(group.contains(input2), false); + assert.equal(group.contains(input2, true), false); + assert.equal(group.contains(diffInput1), false); + assert.equal(group.contains(diffInput2), false); group.openEditor(input2, { pinned: true, active: true }); + assert.equal(group.contains(input1), true); + assert.equal(group.contains(input2), true); + assert.equal(group.contains(diffInput1), false); + assert.equal(group.contains(diffInput2), false); + + group.openEditor(diffInput1, { pinned: true, active: true }); + + assert.equal(group.contains(input1), true); + assert.equal(group.contains(input2), true); + assert.equal(group.contains(diffInput1), true); + assert.equal(group.contains(diffInput2), false); + + group.openEditor(diffInput2, { pinned: true, active: true }); + + assert.equal(group.contains(input1), true); + assert.equal(group.contains(input2), true); + assert.equal(group.contains(diffInput1), true); + assert.equal(group.contains(diffInput2), true); + + group.closeEditor(input1); + + assert.equal(group.contains(input1), false); + assert.equal(group.contains(input1, true), true); assert.equal(group.contains(input2), true); - assert.equal(group.contains(diffInput), false); - assert.equal(group.contains(diffInput, true), true); + assert.equal(group.contains(diffInput1), true); + assert.equal(group.contains(diffInput2), true); + + group.closeEditor(input2); + + assert.equal(group.contains(input1), false); + assert.equal(group.contains(input1, true), true); + assert.equal(group.contains(input2), false); + assert.equal(group.contains(input2, true), true); + assert.equal(group.contains(diffInput1), true); + assert.equal(group.contains(diffInput2), true); + + group.closeEditor(diffInput1); + + assert.equal(group.contains(input1), false); + assert.equal(group.contains(input1, true), true); + assert.equal(group.contains(input2), false); + assert.equal(group.contains(input2, true), true); + assert.equal(group.contains(diffInput1), false); + assert.equal(group.contains(diffInput2), true); + + group.closeEditor(diffInput2); + + assert.equal(group.contains(input1), false); + assert.equal(group.contains(input1, true), false); + assert.equal(group.contains(input2), false); + assert.equal(group.contains(input2, true), false); + assert.equal(group.contains(diffInput1), false); + assert.equal(group.contains(diffInput2), false); }); test('group serialization', function () { @@ -1162,47 +1222,6 @@ suite('Workbench editor groups', () => { assert.equal(group1.getEditors()[1].matches(serializableInput2), true); }); - test('Multiple Editors - Resources', function () { - const group1 = createGroup(); - const group2 = createGroup(); - - const input1Resource = URI.file('/hello/world.txt'); - const input1ResourceUpper = URI.file('/hello/WORLD.txt'); - const input1 = input(undefined, false, input1Resource); - group1.openEditor(input1); - - assert.ok(group1.contains(input1Resource)); - assert.equal(group1.getEditor(input1Resource), input1); - - assert.ok(!group1.getEditor(input1ResourceUpper)); - assert.ok(!group1.contains(input1ResourceUpper)); - - group2.openEditor(input1); - group1.closeEditor(input1); - - assert.ok(!group1.contains(input1Resource)); - assert.ok(!group1.getEditor(input1Resource)); - assert.ok(!group1.getEditor(input1ResourceUpper)); - assert.ok(group2.contains(input1Resource)); - assert.equal(group2.getEditor(input1Resource), input1); - - const input1ResourceClone = URI.file('/hello/world.txt'); - const input1Clone = input(undefined, false, input1ResourceClone); - group1.openEditor(input1Clone); - - assert.ok(group1.contains(input1Resource)); - - group2.closeEditor(input1); - - assert.ok(group1.contains(input1Resource)); - assert.equal(group1.getEditor(input1Resource), input1Clone); - assert.ok(!group2.contains(input1Resource)); - - group1.closeEditor(input1Clone); - - assert.ok(!group1.contains(input1Resource)); - }); - test('Multiple Editors - Editor Dispose', function () { const group1 = createGroup(); const group2 = createGroup(); diff --git a/src/vs/workbench/test/common/editor/editorInput.test.ts b/src/vs/workbench/test/common/editor/editorInput.test.ts index 1c4a315bc0a0..c19f7b3686e3 100644 --- a/src/vs/workbench/test/common/editor/editorInput.test.ts +++ b/src/vs/workbench/test/common/editor/editorInput.test.ts @@ -22,7 +22,7 @@ suite('Workbench editor input', () => { assert(input.matches(input)); assert(!input.matches(otherInput)); assert(!input.matches(null)); - assert(!input.getName()); + assert(input.getName()); input.onDispose(() => { assert(true); @@ -84,4 +84,4 @@ suite('Workbench editor input', () => { otherInput.dispose(); assert.equal(counter, 2); }); -}); \ No newline at end of file +}); diff --git a/src/vs/workbench/test/common/editor/untitledEditor.test.ts b/src/vs/workbench/test/common/editor/untitledTextEditor.test.ts similarity index 73% rename from src/vs/workbench/test/common/editor/untitledEditor.test.ts rename to src/vs/workbench/test/common/editor/untitledTextEditor.test.ts index 1c2d513a99d9..bcee38af3758 100644 --- a/src/vs/workbench/test/common/editor/untitledEditor.test.ts +++ b/src/vs/workbench/test/common/editor/untitledTextEditor.test.ts @@ -6,32 +6,34 @@ import { URI } from 'vs/base/common/uri'; import * as assert from 'assert'; import { join } from 'vs/base/common/path'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IUntitledEditorService, UntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; +import { IUntitledTextEditorService, UntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { workbenchInstantiationService } from 'vs/workbench/test/workbenchTestServices'; -import { UntitledEditorModel } from 'vs/workbench/common/editor/untitledEditorModel'; +import { UntitledTextEditorModel } from 'vs/workbench/common/editor/untitledTextEditorModel'; import { IModeService } from 'vs/editor/common/services/modeService'; import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl'; -import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput'; +import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput'; import { timeout } from 'vs/base/common/async'; import { snapshotToString } from 'vs/workbench/services/textfile/common/textfiles'; import { ModesRegistry, PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; +import { IWorkingCopyService, IWorkingCopy } from 'vs/workbench/services/workingCopy/common/workingCopyService'; -export class TestUntitledEditorService extends UntitledEditorService { +export class TestUntitledTextEditorService extends UntitledTextEditorService { get(resource: URI) { return super.get(resource); } - getAll(resources?: URI[]): UntitledEditorInput[] { return super.getAll(resources); } + getAll(resources?: URI[]): UntitledTextEditorInput[] { return super.getAll(resources); } } class ServiceAccessor { constructor( - @IUntitledEditorService public untitledEditorService: TestUntitledEditorService, - @IModeService public modeService: ModeServiceImpl, - @IConfigurationService public testConfigurationService: TestConfigurationService) { + @IUntitledTextEditorService public readonly untitledTextEditorService: TestUntitledTextEditorService, + @IWorkingCopyService public readonly workingCopyService: IWorkingCopyService, + @IModeService public readonly modeService: ModeServiceImpl, + @IConfigurationService public readonly testConfigurationService: TestConfigurationService) { } } -suite('Workbench untitled editors', () => { +suite('Workbench untitled text editors', () => { let instantiationService: IInstantiationService; let accessor: ServiceAccessor; @@ -42,12 +44,14 @@ suite('Workbench untitled editors', () => { }); teardown(() => { - accessor.untitledEditorService.revertAll(); - accessor.untitledEditorService.dispose(); + accessor.untitledTextEditorService.revertAll(); + accessor.untitledTextEditorService.dispose(); }); - test('Untitled Editor Service', async (done) => { - const service = accessor.untitledEditorService; + test('Untitled Text Editor Service', async (done) => { + const service = accessor.untitledTextEditorService; + const workingCopyService = accessor.workingCopyService; + assert.equal(service.getAll().length, 0); const input1 = service.createOrGet(); @@ -83,14 +87,24 @@ suite('Workbench untitled editors', () => { assert.equal(service.getDirty([input2.getResource()])[0].toString(), input2.getResource().toString()); assert.equal(service.getDirty([input1.getResource()]).length, 0); + assert.ok(workingCopyService.isDirty(input2.getResource())); + assert.equal(workingCopyService.dirtyCount, 1); + service.revertAll(); assert.equal(service.getAll().length, 0); assert.ok(!input2.isDirty()); assert.ok(!model.isDirty()); - input2.dispose(); + assert.ok(!workingCopyService.isDirty(input2.getResource())); + assert.equal(workingCopyService.dirtyCount, 0); + assert.ok(input1.revert()); + assert.ok(input1.isDisposed()); + assert.ok(!service.exists(input1.getResource())); + + input2.dispose(); assert.ok(!service.exists(input2.getResource())); + done(); }); @@ -98,7 +112,7 @@ suite('Workbench untitled editors', () => { }); test('Untitled with associated resource', () => { - const service = accessor.untitledEditorService; + const service = accessor.untitledTextEditorService; const file = URI.file(join('C:\\', '/foo/file.txt')); const untitled = service.createOrGet(file); @@ -108,20 +122,23 @@ suite('Workbench untitled editors', () => { }); test('Untitled no longer dirty when content gets empty', async () => { - const service = accessor.untitledEditorService; + const service = accessor.untitledTextEditorService; + const workingCopyService = accessor.workingCopyService; const input = service.createOrGet(); // dirty const model = await input.resolve(); model.textEditorModel.setValue('foo bar'); assert.ok(model.isDirty()); + assert.ok(workingCopyService.isDirty(model.resource)); model.textEditorModel.setValue(''); assert.ok(!model.isDirty()); + assert.ok(!workingCopyService.isDirty(model.resource)); input.dispose(); }); test('Untitled via loadOrCreate', async () => { - const service = accessor.untitledEditorService; + const service = accessor.untitledTextEditorService; const model1 = await service.loadOrCreate(); @@ -138,11 +155,11 @@ suite('Workbench untitled editors', () => { const model3 = await service.loadOrCreate({ resource: input.getResource() }); - assert.equal(model3.getResource().toString(), input.getResource().toString()); + assert.equal(model3.resource.toString(), input.getResource().toString()); const file = URI.file(join('C:\\', '/foo/file44.txt')); const model4 = await service.loadOrCreate({ resource: file }); - assert.ok(service.hasAssociatedFilePath(model4.getResource())); + assert.ok(service.hasAssociatedFilePath(model4.resource)); assert.ok(model4.isDirty()); model1.dispose(); @@ -153,14 +170,14 @@ suite('Workbench untitled editors', () => { }); test('Untitled suggest name', function () { - const service = accessor.untitledEditorService; + const service = accessor.untitledTextEditorService; const input = service.createOrGet(); assert.ok(service.suggestFileName(input.getResource())); }); test('Untitled with associated path remains dirty when content gets empty', async () => { - const service = accessor.untitledEditorService; + const service = accessor.untitledTextEditorService; const file = URI.file(join('C:\\', '/foo/file.txt')); const input = service.createOrGet(file); @@ -174,13 +191,23 @@ suite('Workbench untitled editors', () => { }); test('Untitled with initial content is dirty', async () => { - const service = accessor.untitledEditorService; + const service = accessor.untitledTextEditorService; const input = service.createOrGet(undefined, undefined, 'Hello World'); + const workingCopyService = accessor.workingCopyService; + + let onDidChangeDirty: IWorkingCopy | undefined = undefined; + const listener = workingCopyService.onDidChangeDirty(copy => { + onDidChangeDirty = copy; + }); // dirty const model = await input.resolve(); assert.ok(model.isDirty()); + assert.equal(workingCopyService.dirtyCount, 1); + assert.equal(onDidChangeDirty, model); + input.dispose(); + listener.dispose(); }); test('Untitled created with files.defaultLanguage setting', () => { @@ -188,7 +215,7 @@ suite('Workbench untitled editors', () => { const config = accessor.testConfigurationService; config.setUserConfiguration('files', { 'defaultLanguage': defaultLanguage }); - const service = accessor.untitledEditorService; + const service = accessor.untitledTextEditorService; const input = service.createOrGet(); assert.equal(input.getMode(), defaultLanguage); @@ -204,7 +231,7 @@ suite('Workbench untitled editors', () => { const config = accessor.testConfigurationService; config.setUserConfiguration('files', { 'defaultLanguage': defaultLanguage }); - const service = accessor.untitledEditorService; + const service = accessor.untitledTextEditorService; const input = service.createOrGet(null!, mode); assert.equal(input.getMode(), mode); @@ -221,7 +248,7 @@ suite('Workbench untitled editors', () => { id: mode, }); - const service = accessor.untitledEditorService; + const service = accessor.untitledTextEditorService; const input = service.createOrGet(null!, mode); assert.equal(input.getMode(), mode); @@ -237,7 +264,7 @@ suite('Workbench untitled editors', () => { }); test('encoding change event', async () => { - const service = accessor.untitledEditorService; + const service = accessor.untitledTextEditorService; const input = service.createOrGet(); let counter = 0; @@ -255,10 +282,10 @@ suite('Workbench untitled editors', () => { }); test('onDidChangeContent event', async () => { - const service = accessor.untitledEditorService; + const service = accessor.untitledTextEditorService; const input = service.createOrGet(); - UntitledEditorModel.DEFAULT_CONTENT_CHANGE_BUFFER_DELAY = 0; + UntitledTextEditorModel.DEFAULT_CONTENT_CHANGE_BUFFER_DELAY = 0; let counter = 0; @@ -293,7 +320,7 @@ suite('Workbench untitled editors', () => { }); test('onDidDisposeModel event', async () => { - const service = accessor.untitledEditorService; + const service = accessor.untitledTextEditorService; const input = service.createOrGet(); let counter = 0; @@ -308,4 +335,4 @@ suite('Workbench untitled editors', () => { input.dispose(); assert.equal(counter, 1); }); -}); \ No newline at end of file +}); diff --git a/src/vs/workbench/test/contrib/linkProtection.test.ts b/src/vs/workbench/test/contrib/linkProtection.test.ts index 1595e01ccba9..b29705e97f5f 100644 --- a/src/vs/workbench/test/contrib/linkProtection.test.ts +++ b/src/vs/workbench/test/contrib/linkProtection.test.ts @@ -8,42 +8,69 @@ import * as assert from 'assert'; import { isURLDomainTrusted } from 'vs/workbench/contrib/url/common/trustedDomainsValidator'; import { URI } from 'vs/base/common/uri'; -suite('Link protection domain matching', () => { +function linkAllowedByRules(link: string, rules: string[]) { + assert.ok(isURLDomainTrusted(URI.parse(link), rules), `Link\n${link}\n should be protected by rules\n${JSON.stringify(rules)}`); +} +function linkNotAllowedByRules(link: string, rules: string[]) { + assert.ok(!isURLDomainTrusted(URI.parse(link), rules), `Link\n${link}\n should NOT be protected by rules\n${JSON.stringify(rules)}`); +} +suite('Link protection domain matching', () => { test('simple', () => { - assert.ok(!isURLDomainTrusted(URI.parse('https://x.org'), [])); - assert.ok(isURLDomainTrusted(URI.parse('https://x.org'), ['https://x.org'])); - assert.ok(isURLDomainTrusted(URI.parse('https://x.org/foo'), ['https://x.org'])); + linkNotAllowedByRules('https://x.org', []); + + linkAllowedByRules('https://x.org', ['https://x.org']); + linkAllowedByRules('https://x.org/foo', ['https://x.org']); - assert.ok(!isURLDomainTrusted(URI.parse('https://x.org'), ['http://x.org'])); - assert.ok(!isURLDomainTrusted(URI.parse('http://x.org'), ['https://x.org'])); + linkNotAllowedByRules('https://x.org', ['http://x.org']); + linkNotAllowedByRules('http://x.org', ['https://x.org']); - assert.ok(!isURLDomainTrusted(URI.parse('https://www.x.org'), ['https://x.org'])); + linkNotAllowedByRules('https://www.x.org', ['https://x.org']); - assert.ok(isURLDomainTrusted(URI.parse('https://www.x.org'), ['https://www.x.org', 'https://y.org'])); + linkAllowedByRules('https://www.x.org', ['https://www.x.org', 'https://y.org']); }); test('localhost', () => { - assert.ok(isURLDomainTrusted(URI.parse('https://127.0.0.1'), [])); - assert.ok(isURLDomainTrusted(URI.parse('https://127.0.0.1:3000'), [])); - assert.ok(isURLDomainTrusted(URI.parse('https://localhost'), [])); - assert.ok(isURLDomainTrusted(URI.parse('https://localhost:3000'), [])); + linkAllowedByRules('https://127.0.0.1', []); + linkAllowedByRules('https://127.0.0.1:3000', []); + linkAllowedByRules('https://localhost', []); + linkAllowedByRules('https://localhost:3000', []); }); test('* star', () => { - assert.ok(isURLDomainTrusted(URI.parse('https://a.x.org'), ['https://*.x.org'])); - assert.ok(isURLDomainTrusted(URI.parse('https://a.b.x.org'), ['https://*.x.org'])); - assert.ok(isURLDomainTrusted(URI.parse('https://a.x.org'), ['https://a.x.*'])); - assert.ok(isURLDomainTrusted(URI.parse('https://a.x.org'), ['https://a.*.org'])); - assert.ok(isURLDomainTrusted(URI.parse('https://a.x.org'), ['https://*.*.org'])); - assert.ok(isURLDomainTrusted(URI.parse('https://a.b.x.org'), ['https://*.b.*.org'])); - assert.ok(isURLDomainTrusted(URI.parse('https://a.a.b.x.org'), ['https://*.b.*.org'])); + linkAllowedByRules('https://a.x.org', ['https://*.x.org']); + linkAllowedByRules('https://a.b.x.org', ['https://*.x.org']); + linkAllowedByRules('https://a.x.org', ['https://a.x.*']); + linkAllowedByRules('https://a.x.org', ['https://a.*.org']); + linkAllowedByRules('https://a.x.org', ['https://*.*.org']); + linkAllowedByRules('https://a.b.x.org', ['https://*.b.*.org']); + linkAllowedByRules('https://a.a.b.x.org', ['https://*.b.*.org']); }); test('no scheme', () => { - assert.ok(isURLDomainTrusted(URI.parse('https://a.x.org'), ['a.x.org'])); - assert.ok(isURLDomainTrusted(URI.parse('https://a.x.org'), ['*.x.org'])); - assert.ok(isURLDomainTrusted(URI.parse('https://a.b.x.org'), ['*.x.org'])); - assert.ok(isURLDomainTrusted(URI.parse('https://x.org'), ['*.x.org'])); + linkAllowedByRules('https://a.x.org', ['a.x.org']); + linkAllowedByRules('https://a.x.org', ['*.x.org']); + linkAllowedByRules('https://a.b.x.org', ['*.x.org']); + linkAllowedByRules('https://x.org', ['*.x.org']); + }); + + test('sub paths', () => { + linkAllowedByRules('https://x.org/foo', ['https://x.org/foo']); + linkAllowedByRules('https://x.org/foo', ['x.org/foo']); + linkAllowedByRules('https://x.org/foo', ['*.org/foo']); + + linkNotAllowedByRules('https://x.org/bar', ['https://x.org/foo']); + linkNotAllowedByRules('https://x.org/bar', ['x.org/foo']); + linkNotAllowedByRules('https://x.org/bar', ['*.org/foo']); + + linkAllowedByRules('https://x.org/foo/bar', ['https://x.org/foo']); + linkNotAllowedByRules('https://x.org/foo2', ['https://x.org/foo']); + + linkNotAllowedByRules('https://www.x.org/foo', ['https://x.org/foo']); + + linkNotAllowedByRules('https://a.x.org/bar', ['https://*.x.org/foo']); + linkNotAllowedByRules('https://a.b.x.org/bar', ['https://*.x.org/foo']); + + linkAllowedByRules('https://github.com', ['https://github.com/foo/bar', 'https://github.com']); }); }); diff --git a/src/vs/workbench/test/electron-browser/api/extHostApiCommands.test.ts b/src/vs/workbench/test/electron-browser/api/extHostApiCommands.test.ts index 97d6a6e06735..bc3dfca0ee03 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostApiCommands.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostApiCommands.test.ts @@ -67,13 +67,13 @@ suite('ExtHostLanguageFeatureCommands', function () { rpcProtocol = new TestRPCProtocol(); instantiationService.stub(ICommandService, { _serviceBrand: undefined, - executeCommand(id: string, args: any): any { + executeCommand(id: string, ...args: any): any { const command = CommandsRegistry.getCommands().get(id); if (!command) { return Promise.reject(new Error(id + ' NOT known')); } const { handler } = command; - return Promise.resolve(instantiationService.invokeFunction(handler, args)); + return Promise.resolve(instantiationService.invokeFunction(handler, ...args)); } }); instantiationService.stub(IMarkerService, new MarkerService()); @@ -112,7 +112,7 @@ suite('ExtHostLanguageFeatureCommands', function () { rpcProtocol.set(MainContext.MainThreadCommands, inst.createInstance(MainThreadCommands, rpcProtocol)); ExtHostApiCommands.register(commands); - const diagnostics = new ExtHostDiagnostics(rpcProtocol); + const diagnostics = new ExtHostDiagnostics(rpcProtocol, new NullLogService()); rpcProtocol.set(ExtHostContext.ExtHostDiagnostics, diagnostics); extHost = new ExtHostLanguageFeatures(rpcProtocol, null, extHostDocuments, commands, diagnostics, new NullLogService()); @@ -412,11 +412,8 @@ suite('ExtHostLanguageFeatureCommands', function () { assert.equal(values.length, 4); let [first, second, third, fourth] = values; assert.equal(first.label, 'item1'); - assert.equal(first.textEdit!.newText, 'item1'); - assert.equal(first.textEdit!.range.start.line, 0); - assert.equal(first.textEdit!.range.start.character, 0); - assert.equal(first.textEdit!.range.end.line, 0); - assert.equal(first.textEdit!.range.end.character, 4); + assert.equal(first.textEdit, undefined);// no text edit, default ranges + assert.ok(!types.Range.isRange(first.range)); assert.equal(second.label, 'item2'); assert.equal(second.textEdit!.newText, 'foo'); @@ -434,10 +431,13 @@ suite('ExtHostLanguageFeatureCommands', function () { assert.equal(fourth.label, 'item4'); assert.equal(fourth.textEdit, undefined); - assert.equal(fourth.range!.start.line, 0); - assert.equal(fourth.range!.start.character, 1); - assert.equal(fourth.range!.end.line, 0); - assert.equal(fourth.range!.end.character, 4); + + const range: any = fourth.range!; + assert.ok(types.Range.isRange(range)); + assert.equal(range.start.line, 0); + assert.equal(range.start.character, 1); + assert.equal(range.end.line, 0); + assert.equal(range.end.character, 4); assert.ok(fourth.insertText instanceof types.SnippetString); assert.equal((fourth.insertText).value, 'foo$0bar'); }); @@ -859,31 +859,44 @@ suite('ExtHostLanguageFeatureCommands', function () { // --- call hierarcht - test('Call Hierarchy, back and forth', async function () { + test('CallHierarchy, back and forth', async function () { - disposables.push(extHost.registerCallHierarchyProvider(nullExtensionDescription, defaultSelector, new class implements vscode.CallHierarchyItemProvider { - provideCallHierarchyIncomingCalls(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): vscode.ProviderResult { - return [ - new types.CallHierarchyIncomingCall(new types.CallHierarchyItem(types.SymbolKind.Array, 'IN', '', document.uri, new types.Range(0, 0, 2, 0), new types.Range(0, 0, 2, 0)), [new types.Range(0, 0, 0, 0)]), - ]; + disposables.push(extHost.registerCallHierarchyProvider(nullExtensionDescription, defaultSelector, new class implements vscode.CallHierarchyProvider { + + prepareCallHierarchy(document: vscode.TextDocument, position: vscode.Position, ): vscode.ProviderResult { + return new types.CallHierarchyItem(types.SymbolKind.Constant, 'ROOT', 'ROOT', document.uri, new types.Range(0, 0, 0, 0), new types.Range(0, 0, 0, 0)); } - provideCallHierarchyOutgoingCalls(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): vscode.ProviderResult { - return [ - new types.CallHierarchyOutgoingCall(new types.CallHierarchyItem(types.SymbolKind.Array, 'OUT', '', document.uri, new types.Range(0, 0, 2, 0), new types.Range(0, 0, 2, 0)), [new types.Range(0, 0, 0, 0)]), - ]; + + provideCallHierarchyIncomingCalls(item: vscode.CallHierarchyItem, token: vscode.CancellationToken): vscode.ProviderResult { + + return [new types.CallHierarchyIncomingCall( + new types.CallHierarchyItem(types.SymbolKind.Constant, 'INCOMING', 'INCOMING', item.uri, new types.Range(0, 0, 0, 0), new types.Range(0, 0, 0, 0)), + [new types.Range(0, 0, 0, 0)] + )]; + } + + provideCallHierarchyOutgoingCalls(item: vscode.CallHierarchyItem, token: vscode.CancellationToken): vscode.ProviderResult { + return [new types.CallHierarchyOutgoingCall( + new types.CallHierarchyItem(types.SymbolKind.Constant, 'OUTGOING', 'OUTGOING', item.uri, new types.Range(0, 0, 0, 0), new types.Range(0, 0, 0, 0)), + [new types.Range(0, 0, 0, 0)] + )]; } })); await rpcProtocol.sync(); - let incoming = await commands.executeCommand('vscode.executeCallHierarchyProviderIncomingCalls', model.uri, new types.Position(0, 10)); + const root = await commands.executeCommand('vscode.prepareCallHierarchy', model.uri, new types.Position(0, 0)); + + assert.ok(Array.isArray(root)); + assert.equal(root.length, 1); + assert.equal(root[0].name, 'ROOT'); + + const incoming = await commands.executeCommand('vscode.provideIncomingCalls', root[0]); assert.equal(incoming.length, 1); - assert.ok(incoming[0].from instanceof types.CallHierarchyItem); - assert.equal(incoming[0].from.name, 'IN'); + assert.equal(incoming[0].from.name, 'INCOMING'); - let outgoing = await commands.executeCommand('vscode.executeCallHierarchyProviderOutgoingCalls', model.uri, new types.Position(0, 10)); + const outgoing = await commands.executeCommand('vscode.provideOutgoingCalls', root[0]); assert.equal(outgoing.length, 1); - assert.ok(outgoing[0].to instanceof types.CallHierarchyItem); - assert.equal(outgoing[0].to.name, 'OUT'); + assert.equal(outgoing[0].to.name, 'OUTGOING'); }); }); diff --git a/src/vs/workbench/test/electron-browser/api/extHostConfiguration.test.ts b/src/vs/workbench/test/electron-browser/api/extHostConfiguration.test.ts index 7e47efb08567..81cbc9833a0b 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostConfiguration.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostConfiguration.test.ts @@ -35,7 +35,7 @@ suite('ExtHostConfiguration', function () { if (!shape) { shape = new class extends mock() { }; } - return new ExtHostConfigProvider(shape, createExtHostWorkspace(), createConfigurationData(contents)); + return new ExtHostConfigProvider(shape, createExtHostWorkspace(), createConfigurationData(contents), new NullLogService()); } function createConfigurationData(contents: any): IConfigurationInitData { @@ -283,7 +283,8 @@ suite('ExtHostConfiguration', function () { workspace: new ConfigurationModel({}, []), folders: [], configurationScopes: [] - } + }, + new NullLogService() ); let actual = testObject.getConfiguration().inspect('editor.wordWrap')!; @@ -331,7 +332,8 @@ suite('ExtHostConfiguration', function () { workspace, folders, configurationScopes: [] - } + }, + new NullLogService() ); let actual1 = testObject.getConfiguration().inspect('editor.wordWrap')!; @@ -407,7 +409,8 @@ suite('ExtHostConfiguration', function () { workspace, folders, configurationScopes: [] - } + }, + new NullLogService() ); let actual1 = testObject.getConfiguration().inspect('editor.wordWrap')!; @@ -607,7 +610,8 @@ suite('ExtHostConfiguration', function () { 'config': false, 'updatedconfig': false } - }) + }), + new NullLogService() ); const newConfigData = createConfigurationData({ diff --git a/src/vs/workbench/test/electron-browser/api/extHostDiagnostics.test.ts b/src/vs/workbench/test/electron-browser/api/extHostDiagnostics.test.ts index e763ff57bf18..1044f429d852 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostDiagnostics.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostDiagnostics.test.ts @@ -11,6 +11,7 @@ import { MainThreadDiagnosticsShape, IMainContext } from 'vs/workbench/api/commo import { IMarkerData, MarkerSeverity } from 'vs/platform/markers/common/markers'; import { mock } from 'vs/workbench/test/electron-browser/api/mock'; import { Emitter, Event } from 'vs/base/common/event'; +import { NullLogService } from 'vs/platform/log/common/log'; suite('ExtHostDiagnostics', () => { @@ -96,10 +97,10 @@ suite('ExtHostDiagnostics', () => { assert.throws(() => array.pop()); assert.throws(() => array[0] = new Diagnostic(new Range(0, 0, 0, 0), 'evil')); - collection.forEach((uri, array: Diagnostic[]) => { - assert.throws(() => array.length = 0); - assert.throws(() => array.pop()); - assert.throws(() => array[0] = new Diagnostic(new Range(0, 0, 0, 0), 'evil')); + collection.forEach((uri, array: readonly Diagnostic[]) => { + assert.throws(() => (array as Diagnostic[]).length = 0); + assert.throws(() => (array as Diagnostic[]).pop()); + assert.throws(() => (array as Diagnostic[])[0] = new Diagnostic(new Range(0, 0, 0, 0), 'evil')); }); array = collection.get(URI.parse('foo:bar')) as Diagnostic[]; @@ -387,7 +388,7 @@ suite('ExtHostDiagnostics', () => { assertRegistered(): void { } - }); + }, new NullLogService()); let collection1 = diags.createDiagnosticCollection('foo'); let collection2 = diags.createDiagnosticCollection('foo'); // warns, uses a different owner @@ -436,7 +437,7 @@ suite('ExtHostDiagnostics', () => { assertRegistered(): void { } - }); + }, new NullLogService()); // diff --git a/src/vs/workbench/test/electron-browser/api/extHostDocumentData.test.ts b/src/vs/workbench/test/electron-browser/api/extHostDocumentData.test.ts index 052229518564..cf81f513d42f 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostDocumentData.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostDocumentData.test.ts @@ -251,11 +251,7 @@ suite('ExtHostDocumentData', () => { assert.equal(range.end.character, 4); // ignore bad regular expresson /.*/ - range = data.document.getWordRangeAtPosition(new Position(0, 2), /.*/)!; - assert.equal(range.start.line, 0); - assert.equal(range.start.character, 0); - assert.equal(range.end.line, 0); - assert.equal(range.end.character, 4); + assert.throws(() => data.document.getWordRangeAtPosition(new Position(0, 2), /.*/)!); range = data.document.getWordRangeAtPosition(new Position(0, 5), /[a-z+]+/)!; assert.equal(range.start.line, 0); diff --git a/src/vs/workbench/test/electron-browser/api/extHostDocumentSaveParticipant.test.ts b/src/vs/workbench/test/electron-browser/api/extHostDocumentSaveParticipant.test.ts index 37dfe4619439..c15515623474 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostDocumentSaveParticipant.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostDocumentSaveParticipant.test.ts @@ -10,7 +10,7 @@ import { TextDocumentSaveReason, TextEdit, Position, EndOfLine } from 'vs/workbe import { MainThreadTextEditorsShape, IWorkspaceEditDto } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostDocumentSaveParticipant } from 'vs/workbench/api/common/extHostDocumentSaveParticipant'; import { SingleProxyRPCProtocol } from './testRPCProtocol'; -import { SaveReason } from 'vs/workbench/services/textfile/common/textfiles'; +import { SaveReason } from 'vs/workbench/common/editor'; import * as vscode from 'vscode'; import { mock } from 'vs/workbench/test/electron-browser/api/mock'; import { NullLogService } from 'vs/platform/log/common/log'; diff --git a/src/vs/workbench/test/electron-browser/api/extHostFileSystemEventService.test.ts b/src/vs/workbench/test/electron-browser/api/extHostFileSystemEventService.test.ts index 4e701b56e512..094657fa5aff 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostFileSystemEventService.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostFileSystemEventService.test.ts @@ -5,6 +5,7 @@ import * as assert from 'assert'; import { ExtHostFileSystemEventService } from 'vs/workbench/api/common/extHostFileSystemEventService'; import { IMainContext } from 'vs/workbench/api/common/extHost.protocol'; +import { NullLogService } from 'vs/platform/log/common/log'; suite('ExtHostFileSystemEventService', () => { @@ -17,12 +18,12 @@ suite('ExtHostFileSystemEventService', () => { assertRegistered: undefined! }; - const watcher1 = new ExtHostFileSystemEventService(protocol, undefined!).createFileSystemWatcher('**/somethingInteresting', false, false, false); + const watcher1 = new ExtHostFileSystemEventService(protocol, new NullLogService(), undefined!).createFileSystemWatcher('**/somethingInteresting', false, false, false); assert.equal(watcher1.ignoreChangeEvents, false); assert.equal(watcher1.ignoreCreateEvents, false); assert.equal(watcher1.ignoreDeleteEvents, false); - const watcher2 = new ExtHostFileSystemEventService(protocol, undefined!).createFileSystemWatcher('**/somethingBoring', true, true, true); + const watcher2 = new ExtHostFileSystemEventService(protocol, new NullLogService(), undefined!).createFileSystemWatcher('**/somethingBoring', true, true, true); assert.equal(watcher2.ignoreChangeEvents, true); assert.equal(watcher2.ignoreCreateEvents, true); assert.equal(watcher2.ignoreDeleteEvents, true); diff --git a/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts b/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts index 84265cfe4a48..dfc5d0b0dbbb 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts @@ -23,10 +23,9 @@ import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocum import { getDocumentSymbols } from 'vs/editor/contrib/quickOpen/quickOpen'; import * as modes from 'vs/editor/common/modes'; import { getCodeLensData } from 'vs/editor/contrib/codelens/codelens'; -import { getDefinitionsAtPosition, getImplementationsAtPosition, getTypeDefinitionsAtPosition, getDeclarationsAtPosition } from 'vs/editor/contrib/goToDefinition/goToDefinition'; +import { getDefinitionsAtPosition, getImplementationsAtPosition, getTypeDefinitionsAtPosition, getDeclarationsAtPosition, getReferencesAtPosition } from 'vs/editor/contrib/gotoSymbol/goToSymbol'; import { getHover } from 'vs/editor/contrib/hover/getHover'; import { getOccurrencesAtPosition } from 'vs/editor/contrib/wordHighlighter/wordHighlighter'; -import { provideReferences } from 'vs/editor/contrib/referenceSearch/referenceSearch'; import { getCodeActions } from 'vs/editor/contrib/codeAction/codeAction'; import { getWorkspaceSymbols } from 'vs/workbench/contrib/search/common/search'; import { rename } from 'vs/editor/contrib/rename/rename'; @@ -103,7 +102,7 @@ suite('ExtHostLanguageFeatures', function () { rpcProtocol.set(ExtHostContext.ExtHostCommands, commands); rpcProtocol.set(MainContext.MainThreadCommands, inst.createInstance(MainThreadCommands, rpcProtocol)); - const diagnostics = new ExtHostDiagnostics(rpcProtocol); + const diagnostics = new ExtHostDiagnostics(rpcProtocol, new NullLogService()); rpcProtocol.set(ExtHostContext.ExtHostDiagnostics, diagnostics); extHost = new ExtHostLanguageFeatures(rpcProtocol, null, extHostDocuments, commands, diagnostics, new NullLogService()); @@ -535,7 +534,7 @@ suite('ExtHostLanguageFeatures', function () { })); await rpcProtocol.sync(); - let value = await provideReferences(model, new EditorPosition(1, 2), CancellationToken.None); + let value = await getReferencesAtPosition(model, new EditorPosition(1, 2), false, CancellationToken.None); assert.equal(value.length, 2); let [first, second] = value; assert.equal(first.uri.path, '/second'); @@ -551,7 +550,7 @@ suite('ExtHostLanguageFeatures', function () { })); await rpcProtocol.sync(); - let value = await provideReferences(model, new EditorPosition(1, 2), CancellationToken.None); + let value = await getReferencesAtPosition(model, new EditorPosition(1, 2), false, CancellationToken.None); assert.equal(value.length, 1); let [item] = value; assert.deepEqual(item.range, { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 }); @@ -572,7 +571,7 @@ suite('ExtHostLanguageFeatures', function () { })); await rpcProtocol.sync(); - const value = await provideReferences(model, new EditorPosition(1, 2), CancellationToken.None); + const value = await getReferencesAtPosition(model, new EditorPosition(1, 2), false, CancellationToken.None); assert.equal(value.length, 1); }); @@ -590,7 +589,7 @@ suite('ExtHostLanguageFeatures', function () { })); await rpcProtocol.sync(); - const { actions } = await getCodeActions(model, model.getFullModelRange(), { type: 'manual' }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, model.getFullModelRange(), { type: 'manual' }, CancellationToken.None); assert.equal(actions.length, 2); const [first, second] = actions; assert.equal(first.title, 'Testing1'); @@ -614,7 +613,7 @@ suite('ExtHostLanguageFeatures', function () { })); await rpcProtocol.sync(); - const { actions } = await getCodeActions(model, model.getFullModelRange(), { type: 'manual' }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, model.getFullModelRange(), { type: 'manual' }, CancellationToken.None); assert.equal(actions.length, 1); const [first] = actions; assert.equal(first.title, 'Testing1'); @@ -637,7 +636,7 @@ suite('ExtHostLanguageFeatures', function () { })); await rpcProtocol.sync(); - const { actions } = await getCodeActions(model, model.getFullModelRange(), { type: 'manual' }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, model.getFullModelRange(), { type: 'manual' }, CancellationToken.None); assert.equal(actions.length, 1); }); @@ -655,7 +654,7 @@ suite('ExtHostLanguageFeatures', function () { })); await rpcProtocol.sync(); - const { actions } = await getCodeActions(model, model.getFullModelRange(), { type: 'manual' }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, model.getFullModelRange(), { type: 'manual' }, CancellationToken.None); assert.equal(actions.length, 1); }); diff --git a/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts b/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts index 30dfc077297e..2a1fb66abb56 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts @@ -12,7 +12,7 @@ import { joinPath } from 'vs/base/common/resources'; import { URI, UriComponents } from 'vs/base/common/uri'; import * as pfs from 'vs/base/node/pfs'; import { MainContext, MainThreadSearchShape } from 'vs/workbench/api/common/extHost.protocol'; -import { ExtHostSearch } from 'vs/workbench/api/node/extHostSearch'; +import { NativeExtHostSearch } from 'vs/workbench/api/node/extHostSearch'; import { Range } from 'vs/workbench/api/common/extHostTypes'; import { IFileMatch, IFileQuery, IPatternInfo, IRawFileMatch2, ISearchCompleteStats, ISearchQuery, ITextQuery, QueryType, resultIsMatch } from 'vs/workbench/services/search/common/search'; import { TestRPCProtocol } from 'vs/workbench/test/electron-browser/api/testRPCProtocol'; @@ -21,9 +21,11 @@ import { NullLogService } from 'vs/platform/log/common/log'; import { URITransformerService } from 'vs/workbench/api/common/extHostUriTransformerService'; import { mock } from 'vs/workbench/test/electron-browser/api/mock'; import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; +import { TextSearchManager } from 'vs/workbench/services/search/common/textSearchManager'; +import { NativeTextSearchManager } from 'vs/workbench/services/search/node/textSearchManager'; let rpcProtocol: TestRPCProtocol; -let extHostSearch: ExtHostSearch; +let extHostSearch: NativeExtHostSearch; const disposables = new DisposableStore(); let mockMainThreadSearch: MockMainThreadSearch; @@ -138,7 +140,7 @@ suite('ExtHostSearch', () => { rpcProtocol.set(MainContext.MainThreadSearch, mockMainThreadSearch); mockPFS = {}; - extHostSearch = new class extends ExtHostSearch { + extHostSearch = new class extends NativeExtHostSearch { constructor() { super( rpcProtocol, @@ -148,6 +150,10 @@ suite('ExtHostSearch', () => { ); this._pfs = mockPFS as any; } + + protected createTextSearchManager(query: ITextQuery, provider: vscode.TextSearchProvider): TextSearchManager { + return new NativeTextSearchManager(query, provider, this._pfs); + } }; }); diff --git a/src/vs/workbench/test/electron-browser/api/extHostTypeConverter.test.ts b/src/vs/workbench/test/electron-browser/api/extHostTypeConverter.test.ts index 4a8822c3b016..90427abd09fd 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostTypeConverter.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostTypeConverter.test.ts @@ -22,13 +22,11 @@ suite('ExtHostTypeConverter', function () { data = MarkdownString.from('Hello [link](foo)'); assert.equal(data.value, 'Hello [link](foo)'); - assert.equal(size(data.uris!), 1); - assert.ok(!!data.uris!['foo']); + assert.equal(isEmptyObject(data.uris), true); // no scheme, no uri data = MarkdownString.from('Hello [link](www.noscheme.bad)'); assert.equal(data.value, 'Hello [link](www.noscheme.bad)'); - assert.equal(size(data.uris!), 1); - assert.ok(!!data.uris!['www.noscheme.bad']); + assert.equal(isEmptyObject(data.uris), true); // no scheme, no uri data = MarkdownString.from('Hello [link](foo:path)'); assert.equal(data.value, 'Hello [link](foo:path)'); diff --git a/src/vs/workbench/test/electron-browser/api/extHostTypes.test.ts b/src/vs/workbench/test/electron-browser/api/extHostTypes.test.ts index 06f8131bb8d9..14aee5886748 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostTypes.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostTypes.test.ts @@ -527,6 +527,25 @@ suite('ExtHostTypes', function () { string.appendVariable('BAR', b => { }); assert.equal(string.value, '${BAR}'); + string = new types.SnippetString(); + string.appendChoice(['b', 'a', 'r']); + assert.equal(string.value, '${1|b,a,r|}'); + + string = new types.SnippetString(); + string.appendChoice(['b', 'a', 'r'], 0); + assert.equal(string.value, '${0|b,a,r|}'); + + string = new types.SnippetString(); + string.appendText('foo').appendChoice(['far', 'boo']).appendText('bar'); + assert.equal(string.value, 'foo${1|far,boo|}bar'); + + string = new types.SnippetString(); + string.appendText('foo').appendChoice(['far', '$boo']).appendText('bar'); + assert.equal(string.value, 'foo${1|far,\\$boo|}bar'); + + string = new types.SnippetString(); + string.appendText('foo').appendPlaceholder('farboo').appendChoice(['far', 'boo']).appendText('bar'); + assert.equal(string.value, 'foo${1:farboo}${2|far,boo|}bar'); }); test('instanceof doesn\'t work for FileSystemError #49386', function () { diff --git a/src/vs/workbench/test/electron-browser/api/extHostWebview.test.ts b/src/vs/workbench/test/electron-browser/api/extHostWebview.test.ts index 3c18e894d1b3..91146c2e75d8 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostWebview.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostWebview.test.ts @@ -4,14 +4,15 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { URI } from 'vs/base/common/uri'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { NullLogService } from 'vs/platform/log/common/log'; import { MainThreadWebviews } from 'vs/workbench/api/browser/mainThreadWebview'; import { ExtHostWebviews } from 'vs/workbench/api/common/extHostWebview'; +import { EditorViewColumn } from 'vs/workbench/api/common/shared/editor'; import { mock } from 'vs/workbench/test/electron-browser/api/mock'; import * as vscode from 'vscode'; import { SingleProxyRPCProtocol } from './testRPCProtocol'; -import { EditorViewColumn } from 'vs/workbench/api/common/shared/editor'; -import { URI } from 'vs/base/common/uri'; -import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; suite('ExtHostWebview', () => { @@ -23,7 +24,7 @@ suite('ExtHostWebview', () => { webviewCspSource: '', webviewResourceRoot: '', isExtensionDevelopmentDebug: false, - }, undefined); + }, undefined, new NullLogService()); let lastInvokedDeserializer: vscode.WebviewPanelSerializer | undefined = undefined; @@ -61,7 +62,7 @@ suite('ExtHostWebview', () => { webviewCspSource: '', webviewResourceRoot: 'vscode-resource://{{resource}}', isExtensionDevelopmentDebug: false, - }, undefined); + }, undefined, new NullLogService()); const webview = extHostWebviews.createWebviewPanel({} as any, 'type', 'title', 1, {}); assert.strictEqual( @@ -102,7 +103,7 @@ suite('ExtHostWebview', () => { webviewCspSource: '', webviewResourceRoot: `https://{{uuid}}.webview.contoso.com/commit/{{resource}}`, isExtensionDevelopmentDebug: false, - }, undefined); + }, undefined, new NullLogService()); const webview = extHostWebviews.createWebviewPanel({} as any, 'type', 'title', 1, {}); function stripEndpointUuid(input: string) { diff --git a/src/vs/workbench/test/electron-browser/api/extHostWorkspace.test.ts b/src/vs/workbench/test/electron-browser/api/extHostWorkspace.test.ts index d98bba7384a1..58706c9fd509 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostWorkspace.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostWorkspace.test.ts @@ -11,13 +11,15 @@ import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensio import { ILogService, NullLogService } from 'vs/platform/log/common/log'; import { IWorkspaceFolderData } from 'vs/platform/workspace/common/workspace'; import { MainThreadWorkspace } from 'vs/workbench/api/browser/mainThreadWorkspace'; -import { IMainContext, IWorkspaceData, MainContext } from 'vs/workbench/api/common/extHost.protocol'; +import { IMainContext, IWorkspaceData, MainContext, ITextSearchComplete } from 'vs/workbench/api/common/extHost.protocol'; import { RelativePattern } from 'vs/workbench/api/common/extHostTypes'; import { ExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; import { mock } from 'vs/workbench/test/electron-browser/api/mock'; import { TestRPCProtocol } from './testRPCProtocol'; import { ExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; +import { ITextQueryBuilderOptions } from 'vs/workbench/contrib/search/common/queryBuilder'; +import { IPatternInfo } from 'vs/workbench/services/search/common/search'; function createExtHostWorkspace(mainContext: IMainContext, data: IWorkspaceData, logService: ILogService): ExtHostWorkspace { const result = new ExtHostWorkspace( @@ -674,4 +676,106 @@ suite('ExtHostWorkspace', function () { assert(mainThreadCalled, 'mainThreadCalled'); }); }); + + test('findTextInFiles - no include', async () => { + const root = '/project/foo'; + const rpcProtocol = new TestRPCProtocol(); + + let mainThreadCalled = false; + rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock() { + async $startTextSearch(query: IPatternInfo, folder: UriComponents | null, options: ITextQueryBuilderOptions, requestId: number, token: CancellationToken): Promise { + mainThreadCalled = true; + assert.equal(query.pattern, 'foo'); + assert.equal(folder, null); + assert.equal(options.includePattern, null); + assert.equal(options.excludePattern, null); + return null; + } + }); + + const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); + await ws.findTextInFiles({ pattern: 'foo' }, {}, () => { }, new ExtensionIdentifier('test')); + assert(mainThreadCalled, 'mainThreadCalled'); + }); + + test('findTextInFiles - string include', async () => { + const root = '/project/foo'; + const rpcProtocol = new TestRPCProtocol(); + + let mainThreadCalled = false; + rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock() { + async $startTextSearch(query: IPatternInfo, folder: UriComponents | null, options: ITextQueryBuilderOptions, requestId: number, token: CancellationToken): Promise { + mainThreadCalled = true; + assert.equal(query.pattern, 'foo'); + assert.equal(folder, null); + assert.equal(options.includePattern, '**/files'); + assert.equal(options.excludePattern, null); + return null; + } + }); + + const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); + await ws.findTextInFiles({ pattern: 'foo' }, { include: '**/files' }, () => { }, new ExtensionIdentifier('test')); + assert(mainThreadCalled, 'mainThreadCalled'); + }); + + test('findTextInFiles - RelativePattern include', async () => { + const root = '/project/foo'; + const rpcProtocol = new TestRPCProtocol(); + + let mainThreadCalled = false; + rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock() { + async $startTextSearch(query: IPatternInfo, folder: UriComponents | null, options: ITextQueryBuilderOptions, requestId: number, token: CancellationToken): Promise { + mainThreadCalled = true; + assert.equal(query.pattern, 'foo'); + assert.deepEqual(folder, URI.file('/other/folder').toJSON()); + assert.equal(options.includePattern, 'glob/**'); + assert.equal(options.excludePattern, null); + return null; + } + }); + + const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); + await ws.findTextInFiles({ pattern: 'foo' }, { include: new RelativePattern('/other/folder', 'glob/**') }, () => { }, new ExtensionIdentifier('test')); + assert(mainThreadCalled, 'mainThreadCalled'); + }); + + test('findTextInFiles - with cancelled token', async () => { + const root = '/project/foo'; + const rpcProtocol = new TestRPCProtocol(); + + let mainThreadCalled = false; + rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock() { + async $startTextSearch(query: IPatternInfo, folder: UriComponents | null, options: ITextQueryBuilderOptions, requestId: number, token: CancellationToken): Promise { + mainThreadCalled = true; + return null; + } + }); + + const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); + const token = CancellationToken.Cancelled; + await ws.findTextInFiles({ pattern: 'foo' }, {}, () => { }, new ExtensionIdentifier('test'), token); + assert(!mainThreadCalled, '!mainThreadCalled'); + }); + + test('findTextInFiles - RelativePattern exclude', async () => { + const root = '/project/foo'; + const rpcProtocol = new TestRPCProtocol(); + + let mainThreadCalled = false; + rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock() { + async $startTextSearch(query: IPatternInfo, folder: UriComponents | null, options: ITextQueryBuilderOptions, requestId: number, token: CancellationToken): Promise { + mainThreadCalled = true; + assert.equal(query.pattern, 'foo'); + assert.deepEqual(folder, null); + assert.equal(options.includePattern, null); + assert.equal(options.excludePattern, 'glob/**'); // exclude folder is ignored... + return null; + } + }); + + const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); + await ws.findTextInFiles({ pattern: 'foo' }, { exclude: new RelativePattern('/other/folder', 'glob/**') }, () => { }, new ExtensionIdentifier('test')); + assert(mainThreadCalled, 'mainThreadCalled'); + }); }); diff --git a/src/vs/workbench/test/electron-browser/api/mainThreadDocumentsAndEditors.test.ts b/src/vs/workbench/test/electron-browser/api/mainThreadDocumentsAndEditors.test.ts index 02e8c843472a..067af27114b8 100644 --- a/src/vs/workbench/test/electron-browser/api/mainThreadDocumentsAndEditors.test.ts +++ b/src/vs/workbench/test/electron-browser/api/mainThreadDocumentsAndEditors.test.ts @@ -20,6 +20,7 @@ import { ServiceCollection } from 'vs/platform/instantiation/common/serviceColle import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { IFileService } from 'vs/platform/files/common/files'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; +import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; suite('MainThreadDocumentsAndEditors', () => { @@ -42,7 +43,7 @@ suite('MainThreadDocumentsAndEditors', () => { deltas.length = 0; const configService = new TestConfigurationService(); configService.setUserConfiguration('editor', { 'detectIndentation': false }); - modelService = new ModelServiceImpl(configService, new TestTextResourcePropertiesService(configService)); + modelService = new ModelServiceImpl(configService, new TestTextResourcePropertiesService(configService), new TestThemeService()); codeEditorService = new TestCodeEditorService(); textFileService = new class extends mock() { isDirty() { return false; } diff --git a/src/vs/workbench/test/electron-browser/api/mainThreadEditors.test.ts b/src/vs/workbench/test/electron-browser/api/mainThreadEditors.test.ts index 264a693dd3ac..23fde4ff2e39 100644 --- a/src/vs/workbench/test/electron-browser/api/mainThreadEditors.test.ts +++ b/src/vs/workbench/test/electron-browser/api/mainThreadEditors.test.ts @@ -27,6 +27,7 @@ import { ITextModelService, IResolvedTextEditorModel } from 'vs/editor/common/se import { IReference, ImmortalReference } from 'vs/base/common/lifecycle'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { LabelService } from 'vs/workbench/services/label/common/labelService'; +import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; suite('MainThreadEditors', () => { @@ -36,15 +37,17 @@ suite('MainThreadEditors', () => { let editors: MainThreadTextEditors; const movedResources = new Map(); + const copiedResources = new Map(); const createdResources = new Set(); const deletedResources = new Set(); setup(() => { const configService = new TestConfigurationService(); - modelService = new ModelServiceImpl(configService, new TestTextResourcePropertiesService(configService)); + modelService = new ModelServiceImpl(configService, new TestTextResourcePropertiesService(configService), new TestThemeService()); const codeEditorService = new TestCodeEditorService(); movedResources.clear(); + copiedResources.clear(); createdResources.clear(); deletedResources.clear(); @@ -64,6 +67,10 @@ suite('MainThreadEditors', () => { movedResources.set(source, target); return Promise.resolve(Object.create(null)); } + copy(source: URI, target: URI) { + copiedResources.set(source, target); + return Promise.resolve(Object.create(null)); + } models = { onModelSaved: Event.None, onModelReverted: Event.None, diff --git a/src/vs/workbench/test/electron-browser/api/mainThreadSaveParticipant.test.ts b/src/vs/workbench/test/electron-browser/api/mainThreadSaveParticipant.test.ts index 2d084b99ddd4..2c4e4cef9b8e 100644 --- a/src/vs/workbench/test/electron-browser/api/mainThreadSaveParticipant.test.ts +++ b/src/vs/workbench/test/electron-browser/api/mainThreadSaveParticipant.test.ts @@ -13,7 +13,8 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; -import { ITextFileService, SaveReason, IResolvedTextFileEditorModel, snapshotToString } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService, IResolvedTextFileEditorModel, snapshotToString } from 'vs/workbench/services/textfile/common/textfiles'; +import { SaveReason } from 'vs/workbench/common/editor'; import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; class ServiceAccessor { diff --git a/src/vs/workbench/test/electron-browser/quickopen.perf.integrationTest.ts b/src/vs/workbench/test/electron-browser/quickopen.perf.integrationTest.ts index 0deb0cf40a1a..433c11fefc53 100644 --- a/src/vs/workbench/test/electron-browser/quickopen.perf.integrationTest.ts +++ b/src/vs/workbench/test/electron-browser/quickopen.perf.integrationTest.ts @@ -27,9 +27,10 @@ import 'vs/workbench/contrib/search/browser/search.contribution'; // load contri import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { LocalSearchService } from 'vs/workbench/services/search/node/searchService'; -import { IUntitledEditorService, UntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; +import { IUntitledTextEditorService, UntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { TestContextService, TestEditorGroupsService, TestEditorService, TestEnvironmentService, TestTextResourcePropertiesService } from 'vs/workbench/test/workbenchTestServices'; import { ClassifiedEvent, StrictPropertyCheck, GDPRClassification } from 'vs/platform/telemetry/common/gdprTypings'; +import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; namespace Timer { export interface ITimerEvent { @@ -73,12 +74,12 @@ suite.skip('QuickOpen performance (integration)', () => { [ITelemetryService, telemetryService], [IConfigurationService, configurationService], [ITextResourcePropertiesService, textResourcePropertiesService], - [IModelService, new ModelServiceImpl(configurationService, textResourcePropertiesService)], + [IModelService, new ModelServiceImpl(configurationService, textResourcePropertiesService, new TestThemeService())], [IWorkspaceContextService, new TestContextService(testWorkspace(URI.file(testWorkspacePath)))], [IEditorService, new TestEditorService()], [IEditorGroupsService, new TestEditorGroupsService()], [IEnvironmentService, TestEnvironmentService], - [IUntitledEditorService, createSyncDescriptor(UntitledEditorService)], + [IUntitledTextEditorService, createSyncDescriptor(UntitledTextEditorService)], [ISearchService, createSyncDescriptor(LocalSearchService)] )); diff --git a/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts b/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts index 7740878118c7..cc49483db152 100644 --- a/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts +++ b/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts @@ -11,7 +11,7 @@ import { createSyncDescriptor } from 'vs/platform/instantiation/common/descripto import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ISearchService } from 'vs/workbench/services/search/common/search'; import { ITelemetryService, ITelemetryInfo } from 'vs/platform/telemetry/common/telemetry'; -import { IUntitledEditorService, UntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; +import { IUntitledTextEditorService, UntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import * as minimist from 'vscode-minimist'; import * as path from 'vs/base/common/path'; @@ -34,6 +34,7 @@ import { testWorkspace } from 'vs/platform/workspace/test/common/testWorkspace'; import { NullLogService, ILogService } from 'vs/platform/log/common/log'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; import { ClassifiedEvent, StrictPropertyCheck, GDPRClassification } from 'vs/platform/telemetry/common/gdprTypings'; +import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; declare var __dirname: string; @@ -63,12 +64,12 @@ suite.skip('TextSearch performance (integration)', () => { [ITelemetryService, telemetryService], [IConfigurationService, configurationService], [ITextResourcePropertiesService, textResourcePropertiesService], - [IModelService, new ModelServiceImpl(configurationService, textResourcePropertiesService)], + [IModelService, new ModelServiceImpl(configurationService, textResourcePropertiesService, new TestThemeService())], [IWorkspaceContextService, new TestContextService(testWorkspace(URI.file(testWorkspacePath)))], [IEditorService, new TestEditorService()], [IEditorGroupsService, new TestEditorGroupsService()], [IEnvironmentService, TestEnvironmentService], - [IUntitledEditorService, createSyncDescriptor(UntitledEditorService)], + [IUntitledTextEditorService, createSyncDescriptor(UntitledTextEditorService)], [ISearchService, createSyncDescriptor(LocalSearchService)], [ILogService, new NullLogService()] )); diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index d537cb5a9a84..4816c7c9e813 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -8,10 +8,10 @@ import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileE import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { join } from 'vs/base/common/path'; import * as resources from 'vs/base/common/resources'; -import { URI, UriComponents } from 'vs/base/common/uri'; +import { URI } from 'vs/base/common/uri'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; -import { ConfirmResult, IEditorInputWithOptions, CloseDirection, IEditorIdentifier, IUntitledResourceInput, IResourceDiffInput, IResourceSideBySideInput, IEditorInput, IEditor, IEditorCloseEvent, IEditorPartOptions } from 'vs/workbench/common/editor'; +import { IEditorInputWithOptions, CloseDirection, IEditorIdentifier, IUntitledTextResourceInput, IResourceDiffInput, IResourceSideBySideInput, IEditorInput, IEditor, IEditorCloseEvent, IEditorPartOptions, IRevertOptions } from 'vs/workbench/common/editor'; import { IEditorOpeningEvent, EditorServiceImpl, IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor'; import { Event, Emitter } from 'vs/base/common/event'; import Severity from 'vs/base/common/severity'; @@ -21,7 +21,7 @@ import { IWorkbenchLayoutService, Parts, Position as PartPosition } from 'vs/wor import { TextModelResolverService } from 'vs/workbench/services/textmodelResolver/common/textModelResolverService'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { IEditorOptions, IResourceInput } from 'vs/platform/editor/common/editor'; -import { IUntitledEditorService, UntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; +import { IUntitledTextEditorService, UntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { IWorkspaceContextService, IWorkspace as IWorkbenchWorkspace, WorkbenchState, IWorkspaceFolder, IWorkspaceFoldersChangeEvent, Workspace } from 'vs/platform/workspace/common/workspace'; import { ILifecycleService, BeforeShutdownEvent, ShutdownReason, StartupKind, LifecyclePhase, WillShutdownEvent } from 'vs/platform/lifecycle/common/lifecycle'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; @@ -49,7 +49,7 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { MockContextKeyService, MockKeybindingService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; import { ITextBufferFactory, DefaultEndOfLine, EndOfLinePreference, IModelDecorationOptions, ITextModel, ITextSnapshot } from 'vs/editor/common/model'; import { Range } from 'vs/editor/common/core/range'; -import { IConfirmation, IConfirmationResult, IDialogService, IDialogOptions, IPickAndOpenOptions, ISaveDialogOptions, IOpenDialogOptions, IFileDialogService, IShowResult } from 'vs/platform/dialogs/common/dialogs'; +import { IConfirmation, IConfirmationResult, IDialogService, IDialogOptions, IPickAndOpenOptions, ISaveDialogOptions, IOpenDialogOptions, IFileDialogService, IShowResult, ConfirmResult } from 'vs/platform/dialogs/common/dialogs'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; import { IExtensionService, NullExtensionService } from 'vs/workbench/services/extensions/common/extensions'; @@ -57,7 +57,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IDecorationsService, IResourceDecorationChangeEvent, IDecoration, IDecorationData, IDecorationsProvider } from 'vs/workbench/services/decorations/browser/decorations'; import { IDisposable, toDisposable, Disposable } from 'vs/base/common/lifecycle'; import { IEditorGroupsService, IEditorGroup, GroupsOrder, GroupsArrangement, GroupDirection, IAddGroupOptions, IMergeGroupOptions, IMoveEditorOptions, ICopyEditorOptions, IEditorReplacement, IGroupChangeEvent, EditorsOrder, IFindGroupScope, EditorGroupLayout, ICloseEditorOptions } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { IEditorService, IOpenEditorOverrideHandler, IVisibleEditor } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorService, IOpenEditorOverrideHandler, IVisibleEditor, ISaveEditorsOptions, IRevertAllEditorsOptions } from 'vs/workbench/services/editor/common/editorService'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { ICodeEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser'; import { IDecorationRenderOptions } from 'vs/editor/common/editorCommon'; @@ -70,7 +70,7 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { ViewletDescriptor, Viewlet } from 'vs/workbench/browser/viewlet'; import { IViewlet } from 'vs/workbench/common/viewlet'; import { IStorageService, InMemoryStorageService } from 'vs/platform/storage/common/storage'; -import { isLinux, isMacintosh, IProcessEnvironment } from 'vs/base/common/platform'; +import { isLinux, isMacintosh } from 'vs/base/common/platform'; import { LabelService } from 'vs/workbench/services/label/common/labelService'; import { IDimension } from 'vs/platform/layout/browser/layoutService'; import { Part } from 'vs/workbench/browser/part'; @@ -79,7 +79,7 @@ import { IPanel } from 'vs/workbench/common/panel'; import { IBadge } from 'vs/workbench/services/activity/common/activity'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { WorkbenchEnvironmentService } from 'vs/workbench/services/environment/node/environmentService'; +import { NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; import { VSBuffer, VSBufferReadable } from 'vs/base/common/buffer'; import { NativeTextFileService } from 'vs/workbench/services/textfile/electron-browser/nativeTextFileService'; import { Schemas } from 'vs/base/common/network'; @@ -92,12 +92,14 @@ import { IBackupMainService, IWorkspaceBackupInfo } from 'vs/platform/backup/ele import { IEmptyWindowBackupInfo } from 'vs/platform/backup/node/backup'; import { IDialogMainService } from 'vs/platform/dialogs/electron-main/dialogs'; import { find } from 'vs/base/common/arrays'; +import { WorkingCopyService, IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { IFilesConfigurationService, FilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; export function createFileInput(instantiationService: IInstantiationService, resource: URI): FileEditorInput { return instantiationService.createInstance(FileEditorInput, resource, undefined, undefined); } -export const TestEnvironmentService = new WorkbenchEnvironmentService(parseArgs(process.argv, OPTIONS) as IWindowConfiguration, process.execPath, 0); +export const TestEnvironmentService = new NativeWorkbenchEnvironmentService(parseArgs(process.argv, OPTIONS) as IWindowConfiguration, process.execPath, 0); export class TestContextService implements IWorkspaceContextService { public _serviceBrand: undefined; @@ -190,48 +192,47 @@ export class TestTextFileService extends NativeTextFileService { public cleanupBackupsBeforeShutdownCalled!: boolean; private promptPath!: URI; - private confirmResult!: ConfirmResult; private resolveTextContentError!: FileOperationError | null; constructor( @IWorkspaceContextService contextService: IWorkspaceContextService, @IFileService protected fileService: IFileService, - @IUntitledEditorService untitledEditorService: IUntitledEditorService, + @IUntitledTextEditorService untitledTextEditorService: IUntitledTextEditorService, @ILifecycleService lifecycleService: ILifecycleService, @IInstantiationService instantiationService: IInstantiationService, - @IConfigurationService configurationService: IConfigurationService, @IModeService modeService: IModeService, @IModelService modelService: IModelService, @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, @INotificationService notificationService: INotificationService, @IBackupFileService backupFileService: IBackupFileService, @IHistoryService historyService: IHistoryService, - @IContextKeyService contextKeyService: IContextKeyService, @IDialogService dialogService: IDialogService, @IFileDialogService fileDialogService: IFileDialogService, @IEditorService editorService: IEditorService, @ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService, - @IElectronService electronService: IElectronService + @IElectronService electronService: IElectronService, + @IProductService productService: IProductService, + @IFilesConfigurationService filesConfigurationService: IFilesConfigurationService ) { super( contextService, fileService, - untitledEditorService, + untitledTextEditorService, lifecycleService, instantiationService, - configurationService, modeService, modelService, environmentService, notificationService, backupFileService, historyService, - contextKeyService, dialogService, fileDialogService, editorService, textResourceConfigurationService, - electronService + electronService, + productService, + filesConfigurationService ); } @@ -239,10 +240,6 @@ export class TestTextFileService extends NativeTextFileService { this.promptPath = path; } - public setConfirmResult(result: ConfirmResult): void { - this.confirmResult = result; - } - public setResolveTextContentErrorOnce(error: FileOperationError): void { this.resolveTextContentError = error; } @@ -260,6 +257,7 @@ export class TestTextFileService extends NativeTextFileService { resource: content.resource, name: content.name, mtime: content.mtime, + ctime: content.ctime, etag: content.etag, encoding: 'utf8', value: await createTextBufferFactoryFromStream(content.value), @@ -272,18 +270,6 @@ export class TestTextFileService extends NativeTextFileService { return Promise.resolve(this.promptPath); } - public confirmSave(_resources?: URI[]): Promise { - return Promise.resolve(this.confirmResult); - } - - public confirmOverwrite(_resource: URI): Promise { - return Promise.resolve(true); - } - - public onFilesConfigurationChange(configuration: any): void { - super.onFilesConfigurationChange(configuration); - } - protected cleanupBackupsBeforeShutdown(): Promise { this.cleanupBackupsBeforeShutdownCalled = true; return Promise.resolve(); @@ -293,15 +279,19 @@ export class TestTextFileService extends NativeTextFileService { export function workbenchInstantiationService(): IInstantiationService { let instantiationService = new TestInstantiationService(new ServiceCollection([ILifecycleService, new TestLifecycleService()])); instantiationService.stub(IEnvironmentService, TestEnvironmentService); - instantiationService.stub(IContextKeyService, instantiationService.createInstance(MockContextKeyService)); + const contextKeyService = instantiationService.createInstance(MockContextKeyService); + instantiationService.stub(IContextKeyService, contextKeyService); const workspaceContextService = new TestContextService(TestWorkspace); instantiationService.stub(IWorkspaceContextService, workspaceContextService); const configService = new TestConfigurationService(); instantiationService.stub(IConfigurationService, configService); + instantiationService.stub(IFilesConfigurationService, new TestFilesConfigurationService(contextKeyService, configService, TestEnvironmentService)); instantiationService.stub(ITextResourceConfigurationService, new TestTextResourceConfigurationService(configService)); - instantiationService.stub(IUntitledEditorService, instantiationService.createInstance(UntitledEditorService)); + instantiationService.stub(IUntitledTextEditorService, instantiationService.createInstance(UntitledTextEditorService)); instantiationService.stub(IStorageService, new TestStorageService()); instantiationService.stub(IWorkbenchLayoutService, new TestLayoutService()); + instantiationService.stub(IDialogService, new TestDialogService()); + instantiationService.stub(IFileDialogService, new TestFileDialogService()); instantiationService.stub(IElectronService, new TestElectronService()); instantiationService.stub(IModeService, instantiationService.createInstance(ModeServiceImpl)); instantiationService.stub(IHistoryService, new TestHistoryService()); @@ -311,7 +301,7 @@ export function workbenchInstantiationService(): IInstantiationService { instantiationService.stub(IBackupFileService, new TestBackupFileService()); instantiationService.stub(ITelemetryService, NullTelemetryService); instantiationService.stub(INotificationService, new TestNotificationService()); - instantiationService.stub(IUntitledEditorService, instantiationService.createInstance(UntitledEditorService)); + instantiationService.stub(IUntitledTextEditorService, instantiationService.createInstance(UntitledTextEditorService)); instantiationService.stub(IMenuService, new TestMenuService()); instantiationService.stub(IKeybindingService, new MockKeybindingService()); instantiationService.stub(IDecorationsService, new TestDecorationsService()); @@ -327,6 +317,7 @@ export function workbenchInstantiationService(): IInstantiationService { instantiationService.stub(IEditorService, editorService); instantiationService.stub(ICodeEditorService, new TestCodeEditorService()); instantiationService.stub(IViewletService, new TestViewletService()); + instantiationService.stub(IWorkingCopyService, new TestWorkingCopyService()); return instantiationService; } @@ -418,6 +409,8 @@ export class TestFileDialogService implements IFileDialogService { public _serviceBrand: undefined; + private confirmResult!: ConfirmResult; + public defaultFilePath(_schemeFilter?: string): URI | undefined { return undefined; } @@ -448,6 +441,12 @@ export class TestFileDialogService implements IFileDialogService { public showOpenDialog(_options: IOpenDialogOptions): Promise { return Promise.resolve(undefined); } + public setConfirmResult(result: ConfirmResult): void { + this.confirmResult = result; + } + public showSaveConfirm(fileNamesOrResources: (string | URI)[]): Promise { + return Promise.resolve(this.confirmResult); + } } export class TestLayoutService implements IWorkbenchLayoutService { @@ -461,6 +460,7 @@ export class TestLayoutService implements IWorkbenchLayoutService { onZenModeChange: Event = Event.None; onCenteredLayoutChange: Event = Event.None; onFullscreenChange: Event = Event.None; + onMaximizeChange: Event = Event.None; onPanelPositionChange: Event = Event.None; onPartVisibilityChange: Event = Event.None; onLayout = Event.None; @@ -479,6 +479,14 @@ export class TestLayoutService implements IWorkbenchLayoutService { return false; } + public hasWindowBorder(): boolean { + return false; + } + + public getWindowBorderRadius(): string | undefined { + return undefined; + } + public isVisible(_part: Parts): boolean { return true; } @@ -562,6 +570,12 @@ export class TestLayoutService implements IWorkbenchLayoutService { public resizePart(_part: Parts, _sizeChange: number): void { } public registerPart(part: Part): void { } + + isWindowMaximized() { + return false; + } + + public updateWindowMaximizedState(maximized: boolean): void { } } let activeViewlet: Viewlet = {} as any; @@ -795,7 +809,7 @@ export class TestEditorGroup implements IEditorGroupView { return []; } - getEditor(_index: number): IEditorInput { + getEditorByIndex(_index: number): IEditorInput { throw new Error('not implemented'); } @@ -888,11 +902,11 @@ export class TestEditorService implements EditorServiceImpl { throw new Error('not implemented'); } - isOpen(_editor: IEditorInput | IResourceInput | IUntitledResourceInput): boolean { + isOpen(_editor: IEditorInput | IResourceInput | IUntitledTextResourceInput): boolean { return false; } - getOpened(_editor: IEditorInput | IResourceInput | IUntitledResourceInput): IEditorInput { + getOpened(_editor: IEditorInput | IResourceInput | IUntitledTextResourceInput): IEditorInput { throw new Error('not implemented'); } @@ -904,9 +918,25 @@ export class TestEditorService implements EditorServiceImpl { throw new Error('not implemented'); } - createInput(_input: IResourceInput | IUntitledResourceInput | IResourceDiffInput | IResourceSideBySideInput): IEditorInput { + createInput(_input: IResourceInput | IUntitledTextResourceInput | IResourceDiffInput | IResourceSideBySideInput): IEditorInput { throw new Error('not implemented'); } + + save(editors: IEditorIdentifier[], options?: ISaveEditorsOptions): Promise { + throw new Error('Method not implemented.'); + } + + saveAll(options?: ISaveEditorsOptions): Promise { + throw new Error('Method not implemented.'); + } + + revert(editors: IEditorIdentifier[], options?: IRevertOptions): Promise { + throw new Error('Method not implemented.'); + } + + revertAll(options?: IRevertAllEditorsOptions): Promise { + throw new Error('Method not implemented.'); + } } export class TestFileService implements IFileService { @@ -964,7 +994,9 @@ export class TestFileService implements IFileService { encoding: 'utf8', mtime: Date.now(), size: 42, + isFile: true, isDirectory: false, + isSymbolicLink: false, name: resources.basename(resource) }); } @@ -986,6 +1018,7 @@ export class TestFileService implements IFileService { etag: 'index.txt', encoding: 'utf8', mtime: Date.now(), + ctime: Date.now(), name: resources.basename(resource), size: 1 }); @@ -1012,6 +1045,7 @@ export class TestFileService implements IFileService { etag: 'index.txt', encoding: 'utf8', mtime: Date.now(), + ctime: Date.now(), size: 1, name: resources.basename(resource) }); @@ -1023,8 +1057,11 @@ export class TestFileService implements IFileService { etag: 'index.txt', encoding: 'utf8', mtime: Date.now(), + ctime: Date.now(), size: 42, + isFile: true, isDirectory: false, + isSymbolicLink: false, name: resources.basename(resource) })); } @@ -1354,7 +1391,7 @@ export class TestElectronService implements IElectronService { async setRepresentedFilename(path: string): Promise { } async setDocumentEdited(edited: boolean): Promise { } async openExternal(url: string): Promise { return false; } - async updateTouchBar(items: { id: string; title: string | { value: string; original: string; }; category?: string | { value: string; original: string; } | undefined; iconLocation?: { dark: UriComponents; light?: { readonly scheme: string; readonly authority: string; readonly path: string; readonly query: string; readonly fragment: string; readonly fsPath: string; with: {}; toString: {}; toJSON: {}; } | undefined; } | undefined; precondition?: { getType: {}; equals: {}; evaluate: {}; serialize: {}; keys: {}; map: {}; negate: {}; } | undefined; toggled?: { getType: {}; equals: {}; evaluate: {}; serialize: {}; keys: {}; map: {}; negate: {}; } | undefined; }[][]): Promise { } + async updateTouchBar(): Promise { } async newWindowTab(): Promise { } async showPreviousWindowTab(): Promise { } async showNextWindowTab(): Promise { } @@ -1369,7 +1406,6 @@ export class TestElectronService implements IElectronService { async toggleDevTools(): Promise { } async startCrashReporter(options: Electron.CrashReporterStartOptions): Promise { } async resolveProxy(url: string): Promise { return undefined; } - async openExtensionDevelopmentHostWindow(args: string[], env: IProcessEnvironment): Promise { } } export class TestBackupMainService implements IBackupMainService { @@ -1447,3 +1483,12 @@ export class TestDialogMainService implements IDialogMainService { throw new Error('Method not implemented.'); } } + +export class TestWorkingCopyService extends WorkingCopyService { } + +export class TestFilesConfigurationService extends FilesConfigurationService { + + onFilesConfigurationChange(configuration: any): void { + super.onFilesConfigurationChange(configuration); + } +} diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index f51c8d458e3b..b690871dd014 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -71,7 +71,7 @@ import 'vs/workbench/services/editor/browser/editorService'; import 'vs/workbench/services/history/browser/history'; import 'vs/workbench/services/activity/browser/activityService'; import 'vs/workbench/services/keybinding/browser/keybindingService'; -import 'vs/workbench/services/untitled/common/untitledEditorService'; +import 'vs/workbench/services/untitled/common/untitledTextEditorService'; import 'vs/workbench/services/textfile/common/textResourcePropertiesService'; import 'vs/workbench/services/mode/common/workbenchModeService'; import 'vs/workbench/services/commands/common/commandService'; @@ -81,7 +81,11 @@ import 'vs/workbench/services/extensionManagement/common/extensionEnablementServ import 'vs/workbench/services/notification/common/notificationService'; import 'vs/workbench/services/extensions/common/staticExtensions'; import 'vs/workbench/services/userDataSync/common/settingsMergeService'; +import 'vs/workbench/services/userDataSync/common/userDataSyncUtil'; import 'vs/workbench/services/path/common/remotePathService'; +import 'vs/workbench/services/remote/common/remoteExplorerService'; +import 'vs/workbench/services/workingCopy/common/workingCopyService'; +import 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService'; @@ -357,6 +361,12 @@ import 'vs/workbench/contrib/feedback/browser/feedback.contribution'; // User Data Sync import 'vs/workbench/contrib/userDataSync/browser/userDataSync.contribution'; +// Code Actions +import 'vs/workbench/contrib/codeActions/common/codeActions.contribution'; + +// Test Custom Editors +import 'vs/workbench/contrib/testCustomEditors/browser/testCustomEditors'; + //#endregion //#region -- contributions diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts index 89cd09844783..59b9822a267c 100644 --- a/src/vs/workbench/workbench.desktop.main.ts +++ b/src/vs/workbench/workbench.desktop.main.ts @@ -62,6 +62,7 @@ import 'vs/workbench/services/clipboard/electron-browser/clipboardService'; import 'vs/workbench/services/update/electron-browser/updateService'; import 'vs/workbench/services/issue/electron-browser/issueService'; import 'vs/workbench/services/menubar/electron-browser/menubarService'; +import 'vs/workbench/services/extensionResourceLoader/electron-browser/extensionResourceLoaderService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ICredentialsService } from 'vs/platform/credentials/common/credentials'; @@ -92,9 +93,9 @@ import 'vs/workbench/contrib/localizations/browser/localizations.contribution'; // Logs import 'vs/workbench/contrib/logs/electron-browser/logs.contribution'; -// Stats -import 'vs/workbench/contrib/stats/electron-browser/workspaceStatsService'; -import 'vs/workbench/contrib/stats/electron-browser/stats.contribution'; +// Tags +import 'vs/workbench/contrib/tags/electron-browser/workspaceTagsService'; +import 'vs/workbench/contrib/tags/electron-browser/tags.contribution'; // Rapid Render Splash import 'vs/workbench/contrib/splash/electron-browser/partsSplash.contribution'; diff --git a/src/vs/workbench/workbench.web.api.ts b/src/vs/workbench/workbench.web.api.ts index f643c6e7e2da..939746a338a4 100644 --- a/src/vs/workbench/workbench.web.api.ts +++ b/src/vs/workbench/workbench.web.api.ts @@ -17,32 +17,49 @@ import { Event, Emitter } from 'vs/base/common/event'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { IWorkspaceProvider, IWorkspace } from 'vs/workbench/services/host/browser/browserHostService'; +interface IResourceUriProvider { + (uri: URI): URI; +} + +interface IStaticExtension { + packageJSON: IExtensionManifest; + extensionLocation: URI; +} + +interface ICommontTelemetryPropertiesResolver { + (): { [key: string]: any }; +} + +interface IExternalUriResolver { + (uri: URI): Promise; +} + interface IWorkbenchConstructionOptions { /** - * Experimental: the remote authority is the IP:PORT from where the workbench is served + * The remote authority is the IP:PORT from where the workbench is served * from. It is for example being used for the websocket connections as address. */ - remoteAuthority?: string; + readonly remoteAuthority?: string; /** * The connection token to send to the server. */ - connectionToken?: string; + readonly connectionToken?: string; /** - * Experimental: An endpoint to serve iframe content ("webview") from. This is required + * An endpoint to serve iframe content ("webview") from. This is required * to provide full security isolation from the workbench host. */ - webviewEndpoint?: string; + readonly webviewEndpoint?: string; /** - * Experimental: a handler for opening workspaces and providing the initial workspace. + * A handler for opening workspaces and providing the initial workspace. */ - workspaceProvider?: IWorkspaceProvider; + readonly workspaceProvider?: IWorkspaceProvider; /** - * Experimental: The userDataProvider is used to handle user specific application + * The user data provider is used to handle user specific application * state like settings, keybindings, UI state (e.g. opened editors) and snippets. */ userDataProvider?: IFileSystemProvider; @@ -50,56 +67,56 @@ interface IWorkbenchConstructionOptions { /** * A factory for web sockets. */ - webSocketFactory?: IWebSocketFactory; + readonly webSocketFactory?: IWebSocketFactory; /** * A provider for resource URIs. */ - resourceUriProvider?: (uri: URI) => URI; + readonly resourceUriProvider?: IResourceUriProvider; /** - * Experimental: Whether to enable the smoke test driver. + * The credentials provider to store and retrieve secrets. */ - driver?: boolean; + readonly credentialsProvider?: ICredentialsProvider; /** - * Experimental: The credentials provider to store and retrieve secrets. + * Add static extensions that cannot be uninstalled but only be disabled. */ - credentialsProvider?: ICredentialsProvider; + readonly staticExtensions?: ReadonlyArray; /** - * Experimental: Add static extensions that cannot be uninstalled but only be disabled. + * Support for URL callbacks. */ - staticExtensions?: { packageJSON: IExtensionManifest, extensionLocation: URI }[]; + readonly urlCallbackProvider?: IURLCallbackProvider; /** - * Experimental: Support for URL callbacks. + * Support for update reporting. */ - urlCallbackProvider?: IURLCallbackProvider; + readonly updateProvider?: IUpdateProvider; /** - * Current logging level. Default is `LogLevel.Info`. + * Support adding additional properties to telemetry. */ - logLevel?: LogLevel; + readonly resolveCommonTelemetryProperties?: ICommontTelemetryPropertiesResolver; /** - * Experimental: Support for update reporting. + * Resolves an external uri before it is opened. */ - updateProvider?: IUpdateProvider; + readonly resolveExternalUri?: IExternalUriResolver; /** - * Experimental: Support adding additional properties to telemetry. + * Current logging level. Default is `LogLevel.Info`. */ - resolveCommonTelemetryProperties?: () => { [key: string]: any }; + readonly logLevel?: LogLevel; /** - * Experimental: Resolves an external uri before it is opened. + * Whether to enable the smoke test driver. */ - readonly resolveExternalUri?: (uri: URI) => Promise; + readonly driver?: boolean; } /** - * Experimental: Creates the workbench with the provided options in the provided container. + * Creates the workbench with the provided options in the provided container. * * @param domElement the container to create the workbench in * @param options for setting up the workbench @@ -136,10 +153,14 @@ export { IWebSocketFactory, IWebSocket, + // Resources + IResourceUriProvider, + // Credentials ICredentialsProvider, // Static Extensions + IStaticExtension, IExtensionManifest, // Callbacks @@ -151,4 +172,10 @@ export { // Updates IUpdateProvider, IUpdate, + + // Telemetry + ICommontTelemetryPropertiesResolver, + + // External Uris + IExternalUriResolver }; diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts index 1aac178d8a5b..a049b4f2cdbc 100644 --- a/src/vs/workbench/workbench.web.main.ts +++ b/src/vs/workbench/workbench.web.main.ts @@ -39,7 +39,7 @@ import 'vs/workbench/services/configurationResolver/browser/configurationResolve import 'vs/workbench/services/credentials/browser/credentialsService'; import 'vs/workbench/services/url/browser/urlService'; import 'vs/workbench/services/update/browser/updateService'; -import 'vs/workbench/contrib/stats/browser/workspaceStatsService'; +import 'vs/workbench/contrib/tags/browser/workspaceTagsService'; import 'vs/workbench/services/workspaces/browser/workspacesService'; import 'vs/workbench/services/workspaces/browser/workspaceEditingService'; import 'vs/workbench/services/dialogs/browser/dialogService'; @@ -48,6 +48,7 @@ import 'vs/workbench/services/host/browser/browserHostService'; import 'vs/workbench/services/request/browser/requestService'; import 'vs/workbench/services/lifecycle/browser/lifecycleService'; import 'vs/workbench/services/clipboard/browser/clipboardService'; +import 'vs/workbench/services/extensionResourceLoader/browser/extensionResourceLoaderService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; @@ -63,7 +64,7 @@ import { NoOpTunnelService } from 'vs/platform/remote/common/tunnelService'; import { ILoggerService } from 'vs/platform/log/common/log'; import { FileLoggerService } from 'vs/platform/log/common/fileLogService'; import { IAuthTokenService } from 'vs/platform/auth/common/auth'; -import { AuthTokenService } from 'vs/platform/auth/common/authTokenService'; +import { AuthTokenService } from 'vs/workbench/services/authToken/browser/authTokenService'; import { IUserDataSyncStoreService, IUserDataSyncService, IUserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSync'; import { UserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSyncLog'; import { UserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; @@ -108,4 +109,7 @@ import 'vs/workbench/contrib/tasks/browser/taskService'; // Telemetry Opt Out import 'vs/workbench/contrib/welcome/telemetryOptOut/browser/telemetryOptOut.contribution'; +// Issues +import 'vs/workbench/contrib/issue/browser/issue.contribution'; + //#endregion diff --git a/test/automation/.gitignore b/test/automation/.gitignore index 39397c02252b..dcb272252975 100644 --- a/test/automation/.gitignore +++ b/test/automation/.gitignore @@ -5,3 +5,4 @@ node_modules/ out/ keybindings.*.json src/driver.d.ts +*.tgz diff --git a/test/automation/.npmignore b/test/automation/.npmignore new file mode 100644 index 000000000000..356ab4ded892 --- /dev/null +++ b/test/automation/.npmignore @@ -0,0 +1,6 @@ +!/out +/src +/tools +.gitignore +tsconfig.json +*.tgz diff --git a/test/automation/package.json b/test/automation/package.json index a4794be8f888..297dce969b7b 100644 --- a/test/automation/package.json +++ b/test/automation/package.json @@ -9,7 +9,7 @@ "main": "./out/index.js", "private": true, "scripts": { - "postinstall": "npm run compile", + "prepare": "npm run compile", "compile": "npm run copy-driver && npm run copy-driver-definition && tsc", "watch": "concurrently \"npm run watch-driver\" \"npm run watch-driver-definition\" \"tsc --watch\"", "copy-driver": "cpx src/driver.js out/", diff --git a/test/automation/src/debug.ts b/test/automation/src/debug.ts index 899116c94d84..9c9de81d6e27 100644 --- a/test/automation/src/debug.ts +++ b/test/automation/src/debug.ts @@ -12,14 +12,14 @@ import { IElement } from '../src/driver'; const VIEWLET = 'div[id="workbench.view.debug"]'; const DEBUG_VIEW = `${VIEWLET} .debug-view-content`; -const CONFIGURE = `div[id="workbench.parts.sidebar"] .actions-container .configure`; +const CONFIGURE = `div[id="workbench.parts.sidebar"] .actions-container .codicon-gear`; const STOP = `.debug-toolbar .action-label[title*="Stop"]`; const STEP_OVER = `.debug-toolbar .action-label[title*="Step Over"]`; const STEP_IN = `.debug-toolbar .action-label[title*="Step Into"]`; const STEP_OUT = `.debug-toolbar .action-label[title*="Step Out"]`; const CONTINUE = `.debug-toolbar .action-label[title*="Continue"]`; const GLYPH_AREA = '.margin-view-overlays>:nth-child'; -const BREAKPOINT_GLYPH = '.debug-breakpoint'; +const BREAKPOINT_GLYPH = '.codicon-debug-breakpoint'; const PAUSE = `.debug-toolbar .action-label[title*="Pause"]`; const DEBUG_STATUS_BAR = `.statusbar.debugging`; const NOT_DEBUG_STATUS_BAR = `.statusbar:not(debugging)`; diff --git a/test/automation/src/editor.ts b/test/automation/src/editor.ts index 09058cb5a7b5..efe680783ff1 100644 --- a/test/automation/src/editor.ts +++ b/test/automation/src/editor.ts @@ -40,7 +40,7 @@ export class Editor { async gotoDefinition(filename: string, term: string, line: number): Promise { await this.clickOnTerm(filename, term, line); - await this.commands.runCommand('Go to Implementation'); + await this.commands.runCommand('Go to Implementations'); } async peekDefinition(filename: string, term: string, line: number): Promise { diff --git a/test/automation/src/index.ts b/test/automation/src/index.ts index 118a757690f9..4302a9c67013 100644 --- a/test/automation/src/index.ts +++ b/test/automation/src/index.ts @@ -24,7 +24,7 @@ export * from './statusbar'; export * from './terminal'; export * from './viewlet'; export * from './workbench'; -export * from '../src/driver'; +export * from './driver'; // {{SQL CARBON EDIT}} export * from './sql/connectionDialog'; diff --git a/test/automation/yarn.lock b/test/automation/yarn.lock index 648ea306c65f..94a1350861f5 100644 --- a/test/automation/yarn.lock +++ b/test/automation/yarn.lock @@ -730,9 +730,9 @@ hosted-git-info@^2.1.4: integrity sha512-pzXIvANXEFrc5oFFXRMkbLPQ2rXRoDERwDLyrcUxGhaZhgP54BBSl9Oheh7Vv0T090cszWBxPjkQQ5Sq1PbBRQ== https-proxy-agent@^2.2.1: - version "2.2.4" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz#4ee7a737abd92678a293d9b34a1af4d0d08c787b" - integrity sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg== + version "2.2.3" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.3.tgz#fb6cd98ed5b9c35056b5a73cd01a8a721d7193d1" + integrity sha512-Ytgnz23gm2DVftnzqRRz2dOXZbGd2uiajSw/95bPp6v53zPRspQjLm/AfBgqbJ2qfeRXWIOMVLpp86+/5yX39Q== dependencies: agent-base "^4.3.0" debug "^3.1.0" diff --git a/test/electron/renderer.html b/test/electron/renderer.html index 38005b2d6094..49cfa95c6578 100644 --- a/test/electron/renderer.html +++ b/test/electron/renderer.html @@ -23,6 +23,16 @@ window.alert = function () { throw new Error('window.alert() is not supported in tests!'); } window.confirm = function () { throw new Error('window.confirm() is not supported in tests!'); } + // Ignore uncaught cancelled promise errors + window.addEventListener('unhandledrejection', e => { + const name = e && e.reason && e.reason.name; + + if (name === 'Canceled') { + e.preventDefault(); + e.stopPropagation(); + } + }); + mocha.setup({ ui: 'tdd', timeout: 5000 diff --git a/test/smoke/README.md b/test/smoke/README.md index 5f86605f15a0..7fc0ced719ff 100644 --- a/test/smoke/README.md +++ b/test/smoke/README.md @@ -43,13 +43,6 @@ yarn smoketest --build PATH_TO_NEW_RELEASE_PARENT_FOLDER --stable-build PATH_TO_ ### Develop -Start two watch tasks: - -```bash -cd test/automation -yarn watch -``` - ```bash cd test/smoke yarn watch @@ -63,6 +56,6 @@ yarn watch - Beware of **focus**. **Never** depend on DOM elements having focus using `.focused` classes or `:focus` pseudo-classes, since they will lose that state as soon as another window appears on top of the running VS Code window. A safe approach which avoids this problem is to use the `waitForActiveElement` API. Many tests use this whenever they need to wait for a specific element to _have focus_. -- Beware of **timing**. You need to read from or write to the DOM... but is it the right time to do that? Can you 100% guarantee that that `input` box will be visible at that point in time? Or are you just hoping that it will be so? Hope is your worst enemy in UI tests. Example: just because you triggered Quick Open with `F1`, it doesn't mean that it's open and you can just start typing; you must first wait for the input element to be in the DOM as well as be the current active element. +- Beware of **timing**. You need to read from or write to the DOM... but is it the right time to do that? Can you 100% guarantee that `input` box will be visible at that point in time? Or are you just hoping that it will be so? Hope is your worst enemy in UI tests. Example: just because you triggered Quick Open with `F1`, it doesn't mean that it's open and you can just start typing; you must first wait for the input element to be in the DOM as well as be the current active element. - Beware of **waiting**. **Never** wait longer than a couple of seconds for anything, unless it's justified. Think of it as a human using Code. Would a human take 10 minutes to run through the Search viewlet smoke test? Then, the computer should even be faster. **Don't** use `setTimeout` just because. Think about what you should wait for in the DOM to be ready and wait for that instead. diff --git a/test/smoke/package.json b/test/smoke/package.json index aedec3f8717f..2ae2926ada1d 100644 --- a/test/smoke/package.json +++ b/test/smoke/package.json @@ -13,7 +13,7 @@ "@types/mkdirp": "0.5.1", "@types/mocha": "2.2.41", "@types/ncp": "2.0.1", - "@types/node": "^10.14.8", + "@types/node": "^12.11.7", "@types/rimraf": "2.0.2", "concurrently": "^3.5.1", "cpx": "^1.5.0", diff --git a/test/smoke/src/areas/editor/editor.test.ts b/test/smoke/src/areas/editor/editor.test.ts index 4987db7b4118..27884dd73e7e 100644 --- a/test/smoke/src/areas/editor/editor.test.ts +++ b/test/smoke/src/areas/editor/editor.test.ts @@ -15,24 +15,6 @@ export function setup() { await app.workbench.quickopen.waitForQuickOpenElements(names => names.length >= 6); }); - it(`finds 'All References' to 'app'`, async function () { - const app = this.app as Application; - await app.workbench.quickopen.openFile('www'); - - const references = await app.workbench.editor.findReferences('www', 'app', 7); - - await references.waitForReferencesCountInTitle(3); - await references.waitForReferencesCount(3); - await references.close(); - }); - - it(`renames local 'app' variable`, async function () { - const app = this.app as Application; - await app.workbench.quickopen.openFile('www'); - await app.workbench.editor.rename('www', 7, 'app', 'newApp'); - await app.workbench.editor.waitForEditorContents('www', contents => contents.indexOf('newApp') > -1); - }); - // it('folds/unfolds the code correctly', async function () { // await app.workbench.quickopen.openFile('www'); @@ -48,23 +30,5 @@ export function setup() { // await app.workbench.editor.waitUntilShown(4); // await app.workbench.editor.waitUntilShown(5); // }); - - it(`verifies that 'Go To Definition' works`, async function () { - const app = this.app as Application; - await app.workbench.quickopen.openFile('app.js'); - - await app.workbench.editor.gotoDefinition('app.js', 'app', 14); - - await app.workbench.editor.waitForHighlightingLine('app.js', 11); - }); - - it(`verifies that 'Peek Definition' works`, async function () { - const app = this.app as Application; - await app.workbench.quickopen.openFile('app.js'); - - const peek = await app.workbench.editor.peekDefinition('app.js', 'app', 14); - - await peek.waitForFile('app.js'); - }); }); } diff --git a/test/smoke/src/areas/statusbar/statusbar.test.ts b/test/smoke/src/areas/statusbar/statusbar.test.ts index 64feccc0d7a1..3e94518631bc 100644 --- a/test/smoke/src/areas/statusbar/statusbar.test.ts +++ b/test/smoke/src/areas/statusbar/statusbar.test.ts @@ -5,7 +5,7 @@ import { Application, Quality, StatusBarElement } from '../../../../automation'; -export function setup() { +export function setup(isWeb) { describe('Statusbar', () => { it('verifies presence of all default status bar elements', async function () { const app = this.app as Application; @@ -18,7 +18,10 @@ export function setup() { await app.workbench.statusbar.waitForStatusbarElement(StatusBarElement.PROBLEMS_STATUS); await app.workbench.quickopen.openFile('app.js'); - await app.workbench.statusbar.waitForStatusbarElement(StatusBarElement.ENCODING_STATUS); + if (!isWeb) { + // Encoding picker currently hidden in web (only UTF-8 supported) + await app.workbench.statusbar.waitForStatusbarElement(StatusBarElement.ENCODING_STATUS); + } await app.workbench.statusbar.waitForStatusbarElement(StatusBarElement.EOL_STATUS); await app.workbench.statusbar.waitForStatusbarElement(StatusBarElement.INDENTATION_STATUS); await app.workbench.statusbar.waitForStatusbarElement(StatusBarElement.LANGUAGE_STATUS); @@ -36,9 +39,12 @@ export function setup() { await app.workbench.statusbar.clickOn(StatusBarElement.INDENTATION_STATUS); await app.workbench.quickinput.waitForQuickInputOpened(); await app.workbench.quickinput.closeQuickInput(); - await app.workbench.statusbar.clickOn(StatusBarElement.ENCODING_STATUS); - await app.workbench.quickinput.waitForQuickInputOpened(); - await app.workbench.quickinput.closeQuickInput(); + if (!isWeb) { + // Encoding picker currently hidden in web (only UTF-8 supported) + await app.workbench.statusbar.clickOn(StatusBarElement.ENCODING_STATUS); + await app.workbench.quickinput.waitForQuickInputOpened(); + await app.workbench.quickinput.closeQuickInput(); + } await app.workbench.statusbar.clickOn(StatusBarElement.EOL_STATUS); await app.workbench.quickinput.waitForQuickInputOpened(); await app.workbench.quickinput.closeQuickInput(); diff --git a/test/smoke/src/main.ts b/test/smoke/src/main.ts index 461fee2ebed3..baac0c4195b4 100644 --- a/test/smoke/src/main.ts +++ b/test/smoke/src/main.ts @@ -312,7 +312,7 @@ describe('Running Code', () => { setupDataEditorTests(); if (!opts.web) { setupDataDebugTests(); } setupDataGitTests(); - setupDataStatusbarTests(); + setupDataStatusbarTests(!!opts.web); setupDataExtensionTests(); setupTerminalTests(); if (!opts.web) { setupDataMultirootTests(); } diff --git a/test/smoke/yarn.lock b/test/smoke/yarn.lock index 330b02367e86..82626a55c7a8 100644 --- a/test/smoke/yarn.lock +++ b/test/smoke/yarn.lock @@ -44,10 +44,10 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.51.tgz#b31d716fb8d58eeb95c068a039b9b6292817d5fb" integrity sha512-El3+WJk2D/ppWNd2X05aiP5l2k4EwF7KwheknQZls+I26eSICoWRhRIJ56jGgw2dqNGQ5LtNajmBU2ajS28EvQ== -"@types/node@^10.14.8": - version "10.14.8" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.14.8.tgz#fe444203ecef1162348cd6deb76c62477b2cc6e9" - integrity sha512-I4+DbJEhLEg4/vIy/2gkWDvXBOOtPKV9EnLhYjMoqxcRW+TTZtUftkHktz/a8suoD5mUL7m6ReLrkPvSsCQQmw== +"@types/node@^12.11.7": + version "12.11.7" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.11.7.tgz#57682a9771a3f7b09c2497f28129a0462966524a" + integrity sha512-JNbGaHFCLwgHn/iCckiGSOZ1XYHsKFwREtzPwSGCVld1SGhOlmZw2D4ZI94HQCrBHbADzW9m4LER/8olJTRGHA== "@types/rimraf@2.0.2": version "2.0.2" diff --git a/test/splitview/public/index.html b/test/splitview/public/index.html index 2df713161463..0951af8ea524 100644 --- a/test/splitview/public/index.html +++ b/test/splitview/public/index.html @@ -18,7 +18,10 @@ color: white; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; font-weight: bold; - font-size: 30px; + font-size: 20px; + display: flex; + justify-content: center; + align-items: center; } @@ -65,9 +68,8 @@ this.setVisible(true); } - layout(width, height, orientation) { - console.log(`layout@${this.label}`); - this.element.style.lineHeight = `${height}px`; + layout(width, height, top, left) { + this.element.innerHTML = `(${top}, ${left})
(${width}, ${height})`; } setVisible(visible) { diff --git a/yarn.lock b/yarn.lock index 4a5a9cce61f2..aeecd0271361 100644 --- a/yarn.lock +++ b/yarn.lock @@ -135,11 +135,25 @@ lodash "^4.17.11" to-fast-properties "^2.0.0" +"@types/applicationinsights@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@types/applicationinsights/-/applicationinsights-0.20.0.tgz#fa7b36dc954f635fa9037cad27c378446b1048fb" + integrity sha512-dQ3Hb58ERe5YNKFVyvU9BrEvpgKeb6Ht9HkCyBvsOZxhx6yKSwF3e+xml3PJQ3JiVOvf6gM/PmE3MdWDl1L6aA== + dependencies: + applicationinsights "*" + "@types/chart.js@^2.7.31": version "2.7.48" resolved "https://registry.yarnpkg.com/@types/chart.js/-/chart.js-2.7.48.tgz#db7b6d6ed33659f97ee49181f22c980bb0790a7b" integrity sha512-U8paSPZGkW2WrHf8sgJj7s9MhfRgSz7wfU3CN73JVrcGJ13jGiqiXyr3Bg/4UFl4cbN3S8avHTsbtzYBrnWeVg== +"@types/chokidar@2.1.3": + version "2.1.3" + resolved "https://registry.yarnpkg.com/@types/chokidar/-/chokidar-2.1.3.tgz#123ab795dba6d89be04bf076e6aecaf8620db674" + integrity sha512-6qK3xoLLAhQVTucQGHTySwOVA1crHRXnJeLwqK6KIFkkKa2aoMFXh+WEi8PotxDtvN6MQJLyYN9ag9P6NLV81w== + dependencies: + chokidar "*" + "@types/commander@^2.11.0": version "2.12.2" resolved "https://registry.yarnpkg.com/@types/commander/-/commander-2.12.2.tgz#183041a23842d4281478fa5d23c5ca78e6fd08ae" @@ -162,13 +176,34 @@ resolved "https://registry.yarnpkg.com/@types/fancy-log/-/fancy-log-1.3.0.tgz#a61ab476e5e628cd07a846330df53b85e05c8ce0" integrity sha512-mQjDxyOM1Cpocd+vm1kZBP7smwKZ4TNokFeds9LV7OZibmPJFEzY3+xZMrKfUdNT71lv8GoCPD6upKwHxubClw== -"@types/htmlparser2@*", "@types/htmlparser2@^3.7.31": +"@types/graceful-fs@4.1.2": + version "4.1.2" + resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.2.tgz#fbc9575dbcc6d1d91dd768d30c5fc0c19f6c50bd" + integrity sha512-epDhsJAVxJsWfeqpzEDFhLnhHMbHie/VMFY+2Hvt5p7FemeW5ELM+6gcVYL/ZsUwdu3zrWpDE3VUTddXW+EMYg== + dependencies: + "@types/node" "*" + +"@types/htmlparser2@*": version "3.7.31" resolved "https://registry.yarnpkg.com/@types/htmlparser2/-/htmlparser2-3.7.31.tgz#ae89353691ce37fa2463c3b8b4698f20ef67a59b" integrity sha512-6Kjy02k+KfJJE2uUiCytS31SXCYnTjKA+G0ydb83DTlMFzorBlezrV2XiKazRO5HSOEvVW3cpzDFPoP9n/9rSA== dependencies: "@types/node" "*" +"@types/http-proxy-agent@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@types/http-proxy-agent/-/http-proxy-agent-2.0.1.tgz#2f95077f6bfe7adc39cc0f0042da85997ae77fc7" + integrity sha512-dgsgbsgI3t+ZkdzF9H19uBaLsurIZJJjJsVpj4mCLp8B6YghQ7jVwyqhaL0PcVtuC3nOi0ZBhAi2Dd9jCUwdFA== + dependencies: + "@types/node" "*" + +"@types/iconv-lite@0.0.1": + version "0.0.1" + resolved "https://registry.yarnpkg.com/@types/iconv-lite/-/iconv-lite-0.0.1.tgz#aa3b8bda2be512b1ae0a057b942e869c370a5569" + integrity sha1-qjuL2ivlErGuCgV7lC6GnDcKVWk= + dependencies: + "@types/node" "*" + "@types/keytar@^4.4.0": version "4.4.0" resolved "https://registry.yarnpkg.com/@types/keytar/-/keytar-4.4.0.tgz#ca24e6ee6d0df10c003aafe26e93113b8faf0d8e" @@ -230,11 +265,35 @@ "@types/uglify-js" "*" source-map "^0.6.0" +"@types/windows-foreground-love@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@types/windows-foreground-love/-/windows-foreground-love-0.3.0.tgz#26bc230b2568aa7ab7c56d35bb5653c0a6965a42" + integrity sha512-tFUVA/fiofNqOh6lZlymvQiQYPY+cZXZPR9mn9wN6/KS8uwx0zgH4Ij/jmFyRYr+x+DGZWEIeknS2BMi7FZJAQ== + +"@types/windows-process-tree@^0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@types/windows-process-tree/-/windows-process-tree-0.2.0.tgz#2fa205c838a8ef0a07697cd747c954653978d22c" + integrity sha512-vQAnkWpMX4HUPjubkxKta4Rfh2EDy2ksalnr37gFHNrmk+uxx50PRH+/fM5nTsEBCi4ESFT/7t7Za3jGqyTZ4g== + "@types/winreg@^1.2.30": version "1.2.30" resolved "https://registry.yarnpkg.com/@types/winreg/-/winreg-1.2.30.tgz#91d6710e536d345b9c9b017c574cf6a8da64c518" integrity sha1-kdZxDlNtNFucmwF8V0z2qNpkxRg= +"@types/yauzl@^2.9.1": + version "2.9.1" + resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.9.1.tgz#d10f69f9f522eef3cf98e30afb684a1e1ec923af" + integrity sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA== + dependencies: + "@types/node" "*" + +"@types/yazl@^2.4.2": + version "2.4.2" + resolved "https://registry.yarnpkg.com/@types/yazl/-/yazl-2.4.2.tgz#d5f8a4752261badbf1a36e8b49e042dc18ec84bc" + integrity sha512-T+9JH8O2guEjXNxqmybzQ92mJUh2oCwDDMSSimZSe1P+pceZiFROZLYmcbqkzV5EUwz6VwcKXCO2S2yUpra6XQ== + dependencies: + "@types/node" "*" + "@webassemblyjs/ast@1.5.13": version "1.5.13" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.5.13.tgz#81155a570bd5803a30ec31436bc2c9c0ede38f25" @@ -619,6 +678,16 @@ append-buffer@^1.0.2: dependencies: buffer-equal "^1.0.0" +applicationinsights@*: + version "1.5.0" + resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-1.5.0.tgz#074df9e525dcfd592822e7b80723b9284d2716fd" + integrity sha512-D+JyPrDx9RWVNIwukoe03ANKNdyVe/ejExbR7xMvZTm09553TzXenW2oPZmfN9jeguKSDugzIWdbILMPNSRRlg== + dependencies: + cls-hooked "^4.2.2" + continuation-local-storage "^3.2.1" + diagnostic-channel "0.2.0" + diagnostic-channel-publishers "^0.3.3" + applicationinsights@1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-1.0.8.tgz#db6e3d983cf9f9405fe1ee5ba30ac6e1914537b5" @@ -853,11 +922,26 @@ async-each@^1.0.0, async-each@^1.0.1: resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d" integrity sha1-GdOGodntxufByF04iu28xW0zYC0= +async-hook-jl@^1.7.6: + version "1.7.6" + resolved "https://registry.yarnpkg.com/async-hook-jl/-/async-hook-jl-1.7.6.tgz#4fd25c2f864dbaf279c610d73bf97b1b28595e68" + integrity sha512-gFaHkFfSxTjvoxDMYqDuGHlcRyUuamF8s+ZTtJdDzqjws4mCt7v0vuV79/E2Wr2/riMQgtG4/yUtXWs1gZ7JMg== + dependencies: + stack-chain "^1.3.7" + async-limiter@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8" integrity sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg== +async-listener@^0.6.0: + version "0.6.10" + resolved "https://registry.yarnpkg.com/async-listener/-/async-listener-0.6.10.tgz#a7c97abe570ba602d782273c0de60a51e3e17cbc" + integrity sha512-gpuo6xOyF4D5DE5WvyqZdPA3NGhiT6Qf07l7DCB0wwDEsLvDIbCr6j9S5aj5Ch96dLace5tXVzWBZkxU/c5ohw== + dependencies: + semver "^5.3.0" + shimmer "^1.1.0" + async-settle@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/async-settle/-/async-settle-1.0.0.tgz#1d0a914bb02575bec8a8f3a74e5080f72b2c0c6b" @@ -981,11 +1065,6 @@ bcrypt-pbkdf@^1.0.0: dependencies: tweetnacl "^0.14.3" -big-integer@^1.6.25: - version "1.6.25" - resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.25.tgz#1de45a9f57542ac20121c682f8d642220a34e823" - integrity sha1-HeRan1dUKsIBIcaC+NZCIgo06CM= - big.js@^3.1.3: version "3.2.0" resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.2.0.tgz#a5fc298b81b9e0dca2e458824784b65c52ba588e" @@ -1006,11 +1085,6 @@ binary-extensions@^2.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.0.0.tgz#23c0df14f6a88077f5f986c0d167ec03c3d5537c" integrity sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow== -binary-search-bounds@2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/binary-search-bounds/-/binary-search-bounds-2.0.3.tgz#5ff8616d6dd2ca5388bc85b2d6266e2b9da502dc" - integrity sha1-X/hhbW3SylOIvIWy1iZuK52lAtw= - binary@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/binary/-/binary-0.3.0.tgz#9f60553bc5ce8c3386f3b553cff47462adecaa79" @@ -1465,10 +1539,10 @@ cheerio@^1.0.0-rc.1: lodash "^4.15.0" parse5 "^3.0.1" -chokidar@3.2.2: - version "3.2.2" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.2.2.tgz#a433973350021e09f2b853a2287781022c0dc935" - integrity sha512-bw3pm7kZ2Wa6+jQWYP/c7bAZy3i4GwiIiMO2EeRjrE48l8vBqC/WvFhSF0xyM8fQiPEGvwMY/5bqDG7sSEOuhg== +chokidar@*, chokidar@3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.2.3.tgz#b9270a565d14f02f6bfdd537a6a2bbf5549b8c8c" + integrity sha512-GtrxGuRf6bzHQmXWRepvsGnXpkQkVU+D2/9a7dAe4a7v1NhrfZOZ2oKf76M3nOs46fFYL8D+Q8JYA4GYeJ8Cjw== dependencies: anymatch "~3.1.1" braces "~3.0.2" @@ -1643,6 +1717,15 @@ cloneable-readable@^1.0.0: process-nextick-args "^1.0.6" through2 "^2.0.1" +cls-hooked@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/cls-hooked/-/cls-hooked-4.2.2.tgz#ad2e9a4092680cdaffeb2d3551da0e225eae1908" + integrity sha512-J4Xj5f5wq/4jAvcdgoGsL3G103BtWpZrMo8NEinRltN+xpTZdI+M38pyQqhuFU/P792xkMFvnKSf+Lm81U1bxw== + dependencies: + async-hook-jl "^1.7.6" + emitter-listener "^1.0.1" + semver "^5.4.1" + co@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" @@ -1867,6 +1950,14 @@ content-type@~1.0.4: resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== +continuation-local-storage@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/continuation-local-storage/-/continuation-local-storage-3.2.1.tgz#11f613f74e914fe9b34c92ad2d28fe6ae1db7ffb" + integrity sha512-jx44cconVqkCEEyLSKWwkvUXwO561jXMa3LPjTPsm5QR22PA0/mhe33FT4Xb5y74JDvt/Cq+5lm8S8rskLv9ZA== + dependencies: + async-listener "^0.6.0" + emitter-listener "^1.1.1" + convert-source-map@^1.1.1: version "1.5.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.0.tgz#9acd70851c6d5dfdd93d9282e5edf94a03ff46b5" @@ -2338,6 +2429,11 @@ diagnostic-channel-publishers@0.2.1: resolved "https://registry.yarnpkg.com/diagnostic-channel-publishers/-/diagnostic-channel-publishers-0.2.1.tgz#8e2d607a8b6d79fe880b548bc58cc6beb288c4f3" integrity sha1-ji1geottef6IC1SLxYzGvrKIxPM= +diagnostic-channel-publishers@^0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/diagnostic-channel-publishers/-/diagnostic-channel-publishers-0.3.3.tgz#376b7798f4fa90f37eb4f94d2caca611b0e9c330" + integrity sha512-qIocRYU5TrGUkBlDDxaziAK1+squ8Yf2Ls4HldL3xxb/jzmWO2Enux7CvevNKYmF2kDXZ9HiRqwjPsjk8L+i2Q== + diagnostic-channel@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/diagnostic-channel/-/diagnostic-channel-0.2.0.tgz#cc99af9612c23fb1fff13612c72f2cbfaa8d5a17" @@ -2379,18 +2475,6 @@ doctrine@^2.1.0: dependencies: esutils "^2.0.2" -documentdb@^1.5.1: - version "1.14.1" - resolved "https://registry.yarnpkg.com/documentdb/-/documentdb-1.14.1.tgz#1a4716c0b38a40daf375dc9a4b2a2beb4e26294a" - integrity sha512-i5PR6/NqDBeYmv2EDEeL9nuw0/7uErFS1h6wRTYIuBMIVswJg2wYjdsGnMFH0bfCgpPk13p24NbPbmMHZNuzxw== - dependencies: - big-integer "^1.6.25" - binary-search-bounds "2.0.3" - int64-buffer "^0.1.9" - priorityqueuejs "1.0.0" - semaphore "1.0.5" - underscore "1.8.3" - dom-serializer@0, dom-serializer@~0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82" @@ -2529,6 +2613,13 @@ elliptic@^6.0.0: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.0" +emitter-listener@^1.0.1, emitter-listener@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/emitter-listener/-/emitter-listener-1.1.2.tgz#56b140e8f6992375b3d7cb2cab1cc7432d9632e8" + integrity sha512-Bt1sBAGFHY9DKY+4/2cV6izcKJUf5T7/gkdmkxzX/qv9CcGH8xSwVRW5mtX03SWJtRTWSOpzCuWN9rBFYZepZQ== + dependencies: + shimmer "^1.2.0" + emoji-regex@^7.0.1: version "7.0.3" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" @@ -4323,11 +4414,6 @@ inquirer@^6.1.0: strip-ansi "^5.0.0" through "^2.3.6" -int64-buffer@^0.1.9: - version "0.1.9" - resolved "https://registry.yarnpkg.com/int64-buffer/-/int64-buffer-0.1.9.tgz#9e039da043b24f78b196b283e04653ef5e990f61" - integrity sha1-ngOdoEOyT3ixlrKD4EZT716ZD2E= - interpret@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.2.0.tgz#d5061a6224be58e8083985f5014d844359576296" @@ -4874,10 +4960,10 @@ jsbn@~0.1.0: resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= -jschardet@1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/jschardet/-/jschardet-1.6.0.tgz#c7d1a71edcff2839db2f9ec30fc5d5ebd3c1a678" - integrity sha512-xYuhvQ7I9PDJIGBWev9xm0+SMSed3ZDBAmvVjbFR1ZRLAF+vlXcQu6cRI9uAlj81rzikElRVteehwV7DuX2ZmQ== +jschardet@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/jschardet/-/jschardet-2.1.1.tgz#af6f8fd0b3b0f5d46a8fd9614a4fce490575c184" + integrity sha512-pA5qG9Zwm8CBpGlK/lo2GE9jPxwqRgMV7Lzc/1iaPccw6v4Rhj8Zg2BTyrdmHmxlJojnbLupLeRnaPLsq03x6Q== jsdom-no-contextify@^3.1.0: version "3.1.0" @@ -5752,7 +5838,7 @@ mute-stream@0.0.7, mute-stream@~0.0.4: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s= -nan@2.14.0, nan@^2.0.0, nan@^2.13.2, nan@^2.14.0: +nan@2.14.0, nan@^2.10.0, nan@^2.13.2, nan@^2.14.0: version "2.14.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== @@ -5784,20 +5870,20 @@ napi-build-utils@^1.0.1: resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.1.tgz#1381a0f92c39d66bf19852e7873432fc2123e508" integrity sha512-boQj1WFgQH3v4clhu3mTNfP+vOBxorDlE8EKiMjUlLG3C4qAESnn9AxIOkFgTR2c9LtzNjPrjS60cT27ZKBhaA== -native-is-elevated@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/native-is-elevated/-/native-is-elevated-0.3.0.tgz#6c5d8f57daeec129abd03b5606a55e56e4337423" - integrity sha512-QJgU7vaCZ199PSEC4LAmwtGfqwGaz8a51YDeze3DPiRzcOq25LIQpxCbBWunIu+csMMHFsDuyO2OVfeSD4ioHQ== +native-is-elevated@0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/native-is-elevated/-/native-is-elevated-0.4.1.tgz#f6391aafb13441f5b949b39ae0b466b06e7f3986" + integrity sha512-2vBXCXCXYKLDjP0WzrXs/AFjDb2njPR31EbGiZ1mR2fMJg211xClK1Xm19RXve35kvAL4dBKOFGCMIyc2+pPsw== -native-keymap@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/native-keymap/-/native-keymap-2.0.0.tgz#7491ba8f9cc75bd6ada7e754dadb7716c793a3e3" - integrity sha512-KIlDZp0yKaHaGIkEVdlYN3QIaZICXwG1qh/oeBeQdM8TwAi90IAZlAD57qsNDkEvIJIzerCzb5jYYQAdHGBgYg== +native-keymap@2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/native-keymap/-/native-keymap-2.1.0.tgz#f3a92e647ac021fe552587b0020f8132efb03078" + integrity sha512-a5VYhjMqxe+HK5VzJM8yIcJOKkeuMSKYfmS0p7VEKSc7hM0F5IPsq7XO8KtwAgV8PJhfQVgAgyQmK8u/MQQ0aw== -native-watchdog@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/native-watchdog/-/native-watchdog-1.2.0.tgz#9c710093ac6e9e60b19517cb1ef4ac9d7c997395" - integrity sha512-jOOoA3PLSxt1adaeuEW7ymV9cApZiDxn4y4iFs7b4sP73EG+5Lsz+OgUNFcGMyrtznTw6ZvlLcilIN4jeAIgaQ== +native-watchdog@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/native-watchdog/-/native-watchdog-1.3.0.tgz#88cee94c9dc766b85c8506eda14c8bd8c9618e27" + integrity sha512-WOjGRNGkYZ5MXsntcvCYrKtSYMaewlbCFplbcUVo9bE80LPVt8TAVFHYWB8+a6fWCGYheq21+Wtt6CJrUaCJhw== natural-compare@^1.4.0: version "1.4.0" @@ -5897,10 +5983,10 @@ node-pre-gyp@^0.10.0: semver "^5.3.0" tar "^4" -node-pty@0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-0.9.0.tgz#8f9bcc0d1c5b970a3184ffd533d862c7eb6590a6" - integrity sha512-MBnCQl83FTYOu7B4xWw10AW77AAh7ThCE1VXEv+JeWj8mSpGo+0bwgsV+b23ljBFwEM9OmsOv3kM27iUPPm84g== +node-pty@^0.10.0-beta2: + version "0.10.0-beta2" + resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-0.10.0-beta2.tgz#6fd0d2fbbe881869e4e19795a05c557ac958da81" + integrity sha512-IU2lzlPUZ+gKG7pHJjzBHpnuwPTxWGgT3iyQicZfdL7dwLvP5cm00QxavAXCInBmRkOMhvM4aBSKvfzqQnCDBA== dependencies: nan "^2.14.0" @@ -6023,16 +6109,6 @@ npmlog@^4.0.1, npmlog@^4.0.2: gauge "~2.7.3" set-blocking "~2.0.0" -nsfw@1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/nsfw/-/nsfw-1.2.5.tgz#febe581af616f7b042f89df133abe62416c4c803" - integrity sha512-m3mwZUKXiCR69PDMLfAmKmiNzy0Oe9LhFE0DYZC5cc1htNj5Hyb1sAgglXhuaDkibFy22AVvPC5cCFB3A6mYIw== - dependencies: - fs-extra "^7.0.0" - lodash.isinteger "^4.0.4" - lodash.isundefined "^3.0.1" - nan "^2.0.0" - nth-check@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.1.tgz#9929acdf628fc2c41098deab82ac580cf149aae4" @@ -6168,10 +6244,10 @@ onetime@^2.0.0: dependencies: mimic-fn "^1.0.0" -onigasm-umd@^2.2.2: - version "2.2.2" - resolved "https://registry.yarnpkg.com/onigasm-umd/-/onigasm-umd-2.2.2.tgz#b989d762df61f899a3052ac794a50bd93fe20257" - integrity sha512-v2eMOJu7iE444L2iJN+U6s6s5S0y7oj/N0DAkrd6wokRtTVoq/v/yaDI1lIqFrTeJbNtqNzYvguDF5yNzW3Rvw== +onigasm-umd@2.2.5: + version "2.2.5" + resolved "https://registry.yarnpkg.com/onigasm-umd/-/onigasm-umd-2.2.5.tgz#f104247334a543accd3f8d641a4d99b3d908d6a1" + integrity sha512-R3qD7hq6i2bBklF+QyjqZl/G4fe7GwtukI28YLH2vuiatqx52tb9vpg2sxwemKc3nF76SgkeyOKJLchBmTm0Aw== oniguruma@^7.2.0: version "7.2.0" @@ -6938,11 +7014,6 @@ prettyjson@^1.2.1: colors "^1.1.2" minimist "^1.2.0" -priorityqueuejs@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/priorityqueuejs/-/priorityqueuejs-1.0.0.tgz#2ee4f23c2560913e08c07ce5ccdd6de3df2c5af8" - integrity sha1-LuTyPCVgkT4IwHzlzN1t498sWvg= - process-nextick-args@^1.0.6, process-nextick-args@^1.0.7, process-nextick-args@~1.0.6: version "1.0.7" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" @@ -7721,11 +7792,6 @@ schema-utils@^0.4.4, schema-utils@^0.4.5: ajv "^6.1.0" ajv-keywords "^3.1.0" -semaphore@1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/semaphore/-/semaphore-1.0.5.tgz#b492576e66af193db95d65e25ec53f5f19798d60" - integrity sha1-tJJXbmavGT25XWXiXsU/Xxl5jWA= - semver-greatest-satisfied-range@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.1.0.tgz#13e8c2658ab9691cb0cd71093240280d36f77a5b" @@ -7862,6 +7928,11 @@ shebang-regex@^1.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= +shimmer@^1.1.0, shimmer@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/shimmer/-/shimmer-1.2.1.tgz#610859f7de327b587efebf501fb43117f9aff337" + integrity sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw== + sigmund@^1.0.1, sigmund@~1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590" @@ -8029,10 +8100,10 @@ sparkles@^1.0.0: resolved "https://registry.yarnpkg.com/sparkles/-/sparkles-1.0.0.tgz#1acbbfb592436d10bbe8f785b7cc6f82815012c3" integrity sha1-Gsu/tZJDbRC76PeFt8xvgoFQEsM= -spdlog@^0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/spdlog/-/spdlog-0.9.0.tgz#c85dd9d0b9cd385f6f3f5b92dc9d2e1691092b5c" - integrity sha512-AeLWPCYjGi4w5DfpXFKb9pCdgMe4gFBMroGfgwXiNfzwmcNYGoFQkIuD1MChZBR1Iwrx0nGhsTSHFslt/qfTAQ== +spdlog@^0.11.1: + version "0.11.1" + resolved "https://registry.yarnpkg.com/spdlog/-/spdlog-0.11.1.tgz#29721b31018a5fe6a3ce2531f9d8d43e0bd6b825" + integrity sha512-M+sg9/Tnr0lrfnW2/hqgpoc4Z8Jzq7W8NUn35iiSslj+1uj1pgutI60MCpulDP2QyFzOpC8VsJmYD6Fub7wHoA== dependencies: bindings "^1.5.0" mkdirp "^0.5.1" @@ -8111,6 +8182,11 @@ ssri@^5.2.4: dependencies: safe-buffer "^5.1.1" +stack-chain@^1.3.7: + version "1.3.7" + resolved "https://registry.yarnpkg.com/stack-chain/-/stack-chain-1.3.7.tgz#d192c9ff4ea6a22c94c4dd459171e3f00cea1285" + integrity sha1-0ZLJ/06moiyUxN1FkXHj8AzqEoU= + stack-trace@0.0.10: version "0.0.10" resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" @@ -8318,10 +8394,10 @@ strip-json-comments@^2.0.1, strip-json-comments@~2.0.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= -sudo-prompt@9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/sudo-prompt/-/sudo-prompt-9.0.0.tgz#eebedeee9fcd6f661324e6bb46335e3288e8dc8a" - integrity sha512-kUn5fiOk0nhY2oKD9onIkcNCE4Zt85WTsvOfSmqCplmlEvXCcPOmp1npH5YWuf8Bmyy9wLWkIxx+D+8cThBORQ== +sudo-prompt@9.1.1: + version "9.1.1" + resolved "https://registry.yarnpkg.com/sudo-prompt/-/sudo-prompt-9.1.1.tgz#73853d729770392caec029e2470db9c221754db0" + integrity sha512-es33J1g2HjMpyAhz8lOR+ICmXXAqTuKbuXuUWLhOLew20oN9oUCgCJx615U/v7aioZg7IX5lIh9x34vwneu4pA== supports-color@1.2.0: version "1.2.0" @@ -8811,10 +8887,10 @@ typescript-formatter@7.1.0: commandpost "^1.0.0" editorconfig "^0.15.0" -typescript@3.7.0-dev.20191017: - version "3.7.0-dev.20191017" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.0-dev.20191017.tgz#e61440dd445edea6d7b9a699e7c5d5fbcd1906f2" - integrity sha512-Yi0lCPEN0cn9Gp8TEEkPpgKNR5SWAmx9Hmzzz+oEuivw6amURqRGynaLyFZkMA9iMsvYG5LLqhdlFO3uu5ZT/w== +typescript@3.7.2: + version "3.7.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.2.tgz#27e489b95fa5909445e9fef5ee48d81697ad18fb" + integrity sha512-ml7V7JfiN2Xwvcer+XAf2csGO1bPBdRbFCkYBczNZggrBZ9c7G3riSUeJmqEU5uOtXNPMhE3n+R4FA/3YOAWOQ== typescript@^2.6.2: version "2.6.2" @@ -9240,10 +9316,10 @@ vscode-debugprotocol@1.37.0: resolved "https://registry.yarnpkg.com/vscode-debugprotocol/-/vscode-debugprotocol-1.37.0.tgz#e8c4694a078d18ea1a639553a7a241b35c1e6f6d" integrity sha512-ppZLEBbFRVNsK0YpfgFi/x2CDyihx0F+UpdKmgeJcvi05UgSXYdO0n9sDVYwoGvvYQPvlpDQeWuY0nloOC4mPA== -vscode-minimist@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/vscode-minimist/-/vscode-minimist-1.2.1.tgz#e63d3f4a9bf3680dcb8f9304eed612323fd6926a" - integrity sha512-cmB72+qDoiCFJ1UKnGUBdGYfXzdpJ3bQM/D/+XhkVk5v7uZgLbYiCz5JcwVyk7NC7hSi5VGtQ4wihzmi12NeXw== +vscode-minimist@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/vscode-minimist/-/vscode-minimist-1.2.2.tgz#65403f44f0c6010d259b2271d36eb5c6f4ad8aab" + integrity sha512-DXMNG2QgrXn1jOP12LzjVfvxVkzxv/0Qa27JrMBj/XP2esj+fJ/wP2T4YUH5derj73Lc96dC8F25WyfDUbTpxQ== vscode-nls-dev@^3.3.1: version "3.3.1" @@ -9263,10 +9339,20 @@ vscode-nls-dev@^3.3.1: xml2js "^0.4.19" yargs "^13.2.4" -vscode-proxy-agent@^0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/vscode-proxy-agent/-/vscode-proxy-agent-0.5.1.tgz#7fd15e157c02176a0dca9f87840ad0991a62ca57" - integrity sha512-Nnkc7gBk9iAbbZURYZm3p/wvDvRVwDvdzEvDqF1Jh40p6przwQU/JTlV1XLrmd4cCwh6TOAWD81Z3cq+K7Xdmw== +vscode-nsfw@1.2.8: + version "1.2.8" + resolved "https://registry.yarnpkg.com/vscode-nsfw/-/vscode-nsfw-1.2.8.tgz#1bf452e72ff1304934de63692870d039a2d972af" + integrity sha512-yRLFDk2nwV0Fp+NWJkIbeXkHYIWoTuWC2siz6JfHc21FkRUjDmuI/1rVL6B+MXW15AsSbXnH5dw4Fo9kUyYclw== + dependencies: + fs-extra "^7.0.0" + lodash.isinteger "^4.0.4" + lodash.isundefined "^3.0.1" + nan "^2.10.0" + +vscode-proxy-agent@^0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/vscode-proxy-agent/-/vscode-proxy-agent-0.5.2.tgz#0c90d24d353957b841d741da7b2701e3f0a044c4" + integrity sha512-1cCNPxrWIrmUwS+1XGaXxkh3G1y7z2fpXl1sT74OZvELaryQWYb3NMxMLJJ4Q/CpPLEyuhp/bAN7nzHxxFcQ5Q== dependencies: debug "^3.1.0" http-proxy-agent "^2.1.0" @@ -9278,24 +9364,24 @@ vscode-ripgrep@^1.5.7: resolved "https://registry.yarnpkg.com/vscode-ripgrep/-/vscode-ripgrep-1.5.7.tgz#acb6b548af488a4bca5d0f1bb5faf761343289ce" integrity sha512-/Vsz/+k8kTvui0q3O74pif9FK0nKopgFTiGNVvxicZANxtSA8J8gUE9GQ/4dpi7D/2yI/YVORszwVskFbz46hQ== -vscode-sqlite3@4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/vscode-sqlite3/-/vscode-sqlite3-4.0.8.tgz#1eba415b996d2661628d80d4a191a20767713829" - integrity sha512-FsFtYSHmy0mYjtt9ibFKsJqbRzqaltDKZ5SLdpykjvORugFMr0HfGunkh+qGaz9CvAiqjM2KVO91NE9KdyTWKQ== +vscode-sqlite3@4.0.9: + version "4.0.9" + resolved "https://registry.yarnpkg.com/vscode-sqlite3/-/vscode-sqlite3-4.0.9.tgz#cad04964aff1e39121dd8c7cdb87c64a71bad9ce" + integrity sha512-g/xnAn4sgkVO+px/DfC7kpmCQ666W124tfMSVpbnQXsjMHlLhi4Urz4wPhS9YEFPz1Orl6h6MUF6wptzI6QFdg== dependencies: nan "^2.14.0" -vscode-textmate@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-4.2.2.tgz#0b4dabc69a6fba79a065cb6b615f66eac07c8f4c" - integrity sha512-1U4ih0E/KP1zNK/EbpUqyYtI7PY+Ccd2nDGTtiMR/UalLFnmaYkwoWhN1oI7B91ptBN8NdVwWuvyUnvJAulCUw== +vscode-textmate@4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-4.4.0.tgz#14032afeb50152e8f53258c95643e555f2948305" + integrity sha512-dFpm2eK0HwEjeFSD1DDh3j0q47bDSVuZt20RiJWxGqjtm73Wu2jip3C2KaZI3dQx/fSeeXCr/uEN4LNaNj7Ytw== dependencies: oniguruma "^7.2.0" -vscode-windows-ca-certs@0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/vscode-windows-ca-certs/-/vscode-windows-ca-certs-0.1.0.tgz#d58eeb40b536130918cfde2b01e6dc7e5c1bd757" - integrity sha512-ZfZbfJIE09Q0dwGqmqTj7kuAq4g6lul9WPJvo0DkKjln8/FL+dY3wUKIKbYwWQp4x56SBTLBq3tJkD72xQ9Gqw== +vscode-windows-ca-certs@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/vscode-windows-ca-certs/-/vscode-windows-ca-certs-0.2.0.tgz#086f0f4de57e2760a35ac6920831bff246237115" + integrity sha512-YBrJRT0zos+Yb1Qdn73GD8QZr7pa2IE96b5Y1hmmp6XeR8aYB7Iiq5gDAF/+/AxL+caSR9KPZQ6jiYWh5biD7w== dependencies: node-addon-api "1.6.2" @@ -9572,20 +9658,25 @@ xtend@~2.1.1: dependencies: object-keys "~0.4.0" -xterm-addon-search@0.3.0-beta5: - version "0.3.0-beta5" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.3.0-beta5.tgz#fd53d33a77a0235018479c712be8c12f7c0d083a" - integrity sha512-3GkGc4hST35/4hzgnQPLLvQ29WH7MkZ0mUrBE/Vm1IQum7TnMvWPTkGemwM+wAl4tdBmynNccHJlFeQzaQtVUg== +xterm-addon-search@0.4.0-beta4: + version "0.4.0-beta4" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.4.0-beta4.tgz#7762ea342c6b4f5e824d83466bd93793c9d7d779" + integrity sha512-TIbEBVhydGIxcyu/CfKJbD+BKHisMGbkAfaWlCPaWis2Xmw8yE7CKrCPn+lhZYl1MdjDVEmb8lQI6WetbC2OZA== xterm-addon-web-links@0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.2.1.tgz#6d1f2ce613e09870badf17615e7a1170a31542b2" integrity sha512-2KnHtiq0IG7hfwv3jw2/jQeH1RBk2d5CH4zvgwQe00rLofSJqSfgnJ7gwowxxpGHrpbPr6Lv4AmH/joaNw2+HQ== -xterm@^4.2.0-beta20: - version "4.2.0-beta20" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.2.0-beta20.tgz#74a759414511b4577159293397914ac33a2375d8" - integrity sha512-lH52ksaNQqWmLVV4OdLKWvhGkSRUXgJNvb2YYmVkBAm1PdVVS36ir8Qr4zYygnc2tBw689Wj65t4cNckmfpU1Q== +xterm-addon-webgl@0.4.0-beta.11: + version "0.4.0-beta.11" + resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.4.0-beta.11.tgz#0e4a7242e2353cf74aba55e5a5bdc0b4ec87ad10" + integrity sha512-AteDxm1RFy1WnjY9r5iJSETozLebvUkR+jextdZk/ASsK21vsYK0DuVWwRI8afgiN2hUVhxcxuHEJUOV+CJDQA== + +xterm@4.3.0-beta.28: + version "4.3.0-beta.28" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.3.0-beta.28.tgz#80f7c4ba8f6ee3c953e6f33f8ce5aef08d5a8354" + integrity sha512-WWZ4XCvce5h+klL6ObwtMauJff/n2KGGOwJJkDbJhrAjVy2a77GKgAedJTDDFGgKJ6ix1d7puHtVSSKflIVaDQ== y18n@^3.2.1: version "3.2.1" From 493e7087cf52407e7120e9597cb6ebb8e8a967ad Mon Sep 17 00:00:00 2001 From: Anthony Dresser Date: Wed, 4 Dec 2019 19:55:52 -0800 Subject: [PATCH 17/19] update distro (#8570) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 387e3d5b76fd..5d5745a5f819 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "azuredatastudio", "version": "1.14.0", - "distro": "58e879c1d775f402e72ab7bcd800fb84b5334a51", + "distro": "68a9088c1675337aaa88e0e9853ea68c32a0409f", "author": { "name": "Microsoft Corporation" }, From a898c46e74da4dbdc776a404201c58b72f9e5f6d Mon Sep 17 00:00:00 2001 From: Maddy <12754347+MaddyDev@users.noreply.github.com> Date: Thu, 5 Dec 2019 10:13:56 -0800 Subject: [PATCH 18/19] version bump (#8563) * version bump * vbump the release version as well --- build/builtInExtensions-insiders.json | 2 +- build/builtInExtensions.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build/builtInExtensions-insiders.json b/build/builtInExtensions-insiders.json index 6e88644a7791..e5aee1b9adc5 100644 --- a/build/builtInExtensions-insiders.json +++ b/build/builtInExtensions-insiders.json @@ -1,7 +1,7 @@ [ { "name": "Microsoft.sqlservernotebook", - "version": "0.3.2", + "version": "0.3.3", "repo": "https://github.com/Microsoft/azuredatastudio" } ] diff --git a/build/builtInExtensions.json b/build/builtInExtensions.json index 6e88644a7791..e5aee1b9adc5 100644 --- a/build/builtInExtensions.json +++ b/build/builtInExtensions.json @@ -1,7 +1,7 @@ [ { "name": "Microsoft.sqlservernotebook", - "version": "0.3.2", + "version": "0.3.3", "repo": "https://github.com/Microsoft/azuredatastudio" } ] From 0d9353d99e7d889328987ce7ba70b7741c81ce4d Mon Sep 17 00:00:00 2001 From: Leila Lali Date: Thu, 5 Dec 2019 10:26:50 -0800 Subject: [PATCH 19/19] Manage Package Dialog Refactor (#8473) * Refactoring Manage Packages dialog so that other extensions can contribute to it by registering package mange providers for different location and package type --- extensions/notebook/src/common/constants.ts | 7 + .../dialog/managePackages/addNewPackageTab.ts | 104 +----- .../managePackages/installedPackagesTab.ts | 49 +-- .../managePackages/managePackagesDialog.ts | 41 +- .../managePackagesDialogModel.ts | 281 ++++++++++++++ extensions/notebook/src/extension.ts | 8 +- .../notebook/src/jupyter/jupyterController.ts | 52 ++- .../src/jupyter/jupyterServerInstallation.ts | 19 +- .../localCondaPackageManageProvider.ts | 114 ++++++ .../jupyter/localPipPackageManageProvider.ts | 110 ++++++ extensions/notebook/src/jupyter/pipyClient.ts | 46 +++ .../localPackageManageProvider.test.ts | 229 ++++++++++++ .../managePackagesDialogModel.test.ts | 351 ++++++++++++++++++ extensions/notebook/src/types.d.ts | 123 +++++- .../modelComponents/dropdown.component.ts | 8 + 15 files changed, 1406 insertions(+), 136 deletions(-) create mode 100644 extensions/notebook/src/dialog/managePackages/managePackagesDialogModel.ts create mode 100644 extensions/notebook/src/jupyter/localCondaPackageManageProvider.ts create mode 100644 extensions/notebook/src/jupyter/localPipPackageManageProvider.ts create mode 100644 extensions/notebook/src/jupyter/pipyClient.ts create mode 100644 extensions/notebook/src/test/managePackages/localPackageManageProvider.test.ts create mode 100644 extensions/notebook/src/test/managePackages/managePackagesDialogModel.test.ts diff --git a/extensions/notebook/src/common/constants.ts b/extensions/notebook/src/common/constants.ts index 9ab65cb6bdd1..5d12f4530cac 100644 --- a/extensions/notebook/src/common/constants.ts +++ b/extensions/notebook/src/common/constants.ts @@ -5,6 +5,10 @@ 'use strict'; +import * as nls from 'vscode-nls'; + +const localize = nls.loadMessageBundle(); + // CONFIG VALUES /////////////////////////////////////////////////////////// export const extensionOutputChannel = 'Notebooks'; @@ -27,6 +31,9 @@ export const jupyterReinstallDependenciesCommand = 'jupyter.reinstallDependencie export const jupyterAnalyzeCommand = 'jupyter.cmd.analyzeNotebook'; export const jupyterManagePackages = 'jupyter.cmd.managePackages'; export const jupyterConfigurePython = 'jupyter.cmd.configurePython'; +export const localhostName = 'localhost'; +export const localhostTitle = localize('managePackages.localhost', "localhost"); +export const PackageNotFoundError = localize('managePackages.packageNotFound', "Could not find the specified package"); export enum BuiltInCommands { SetContext = 'setContext' diff --git a/extensions/notebook/src/dialog/managePackages/addNewPackageTab.ts b/extensions/notebook/src/dialog/managePackages/addNewPackageTab.ts index 673052e88af2..d17435e67849 100644 --- a/extensions/notebook/src/dialog/managePackages/addNewPackageTab.ts +++ b/extensions/notebook/src/dialog/managePackages/addNewPackageTab.ts @@ -5,12 +5,10 @@ import * as nls from 'vscode-nls'; import * as azdata from 'azdata'; -import * as request from 'request'; import { JupyterServerInstallation, PipPackageOverview } from '../../jupyter/jupyterServerInstallation'; import * as utils from '../../common/utils'; import { ManagePackagesDialog } from './managePackagesDialog'; -import { PythonPkgType } from '../../common/constants'; const localize = nls.loadMessageBundle(); @@ -28,7 +26,6 @@ export class AddNewPackageTab { private packageInstallButton: azdata.ButtonComponent; private readonly InvalidTextPlaceholder = localize('managePackages.invalidTextPlaceholder', "N/A"); - private readonly PackageNotFoundError = localize('managePackages.packageNotFound', "Could not find the specified package"); private readonly SearchPlaceholder = (pkgType: string) => localize('managePackages.searchBarPlaceholder', "Search {0} packages", pkgType); constructor(private dialog: ManagePackagesDialog, private jupyterInstallation: JupyterServerInstallation) { @@ -42,8 +39,8 @@ export class AddNewPackageTab { label: localize('managePackages.searchButtonLabel', "Search"), width: '200px' }).component(); - this.packagesSearchButton.onDidClick(() => { - this.loadNewPackageInfo(); + this.packagesSearchButton.onDidClick(async () => { + await this.loadNewPackageInfo(); }); this.newPackagesName = view.modelBuilder.text().withProperties({ width: '400px' }).component(); @@ -65,8 +62,8 @@ export class AddNewPackageTab { label: localize('managePackages.installButtonText', "Install"), width: '200px' }).component(); - this.packageInstallButton.onDidClick(() => { - this.doPackageInstall(); + this.packageInstallButton.onDidClick(async () => { + await this.doPackageInstall(); }); let formModel = view.modelBuilder.formContainer() @@ -107,7 +104,7 @@ export class AddNewPackageTab { await this.newPackagesSearchBar.updateProperties({ value: '', - placeHolder: this.SearchPlaceholder(this.dialog.currentPkgType) + placeHolder: this.SearchPlaceholder(this.dialog.model.currentPackageType) }); await this.setFieldsToEmpty(); } finally { @@ -145,11 +142,7 @@ export class AddNewPackageTab { } let pipPackage: PipPackageOverview; - if (this.dialog.currentPkgType === PythonPkgType.Anaconda) { - pipPackage = await this.fetchCondaPackage(packageName); - } else { - pipPackage = await this.fetchPypiPackage(packageName); - } + pipPackage = await this.dialog.model.getPackageOverview(packageName); if (!pipPackage.versions || pipPackage.versions.length === 0) { this.dialog.showErrorMessage( localize('managePackages.noVersionsFound', @@ -179,86 +172,7 @@ export class AddNewPackageTab { } } - private async fetchPypiPackage(packageName: string): Promise { - return new Promise((resolve, reject) => { - request.get(`https://pypi.org/pypi/${packageName}/json`, { timeout: 10000 }, (error, response, body) => { - if (error) { - return reject(error); - } - - if (response.statusCode === 404) { - return reject(this.PackageNotFoundError); - } - - if (response.statusCode !== 200) { - return reject( - localize('managePackages.packageRequestError', - "Package info request failed with error: {0} {1}", - response.statusCode, - response.statusMessage)); - } - - let versionNums: string[] = []; - let packageSummary = ''; - - let packagesJson = JSON.parse(body); - if (packagesJson) { - if (packagesJson.releases) { - let versionKeys = Object.keys(packagesJson.releases); - versionKeys = versionKeys.filter(versionKey => { - let releaseInfo = packagesJson.releases[versionKey]; - return Array.isArray(releaseInfo) && releaseInfo.length > 0; - }); - versionNums = utils.sortPackageVersions(versionKeys, false); - } - - if (packagesJson.info && packagesJson.info.summary) { - packageSummary = packagesJson.info.summary; - } - } - - resolve({ - name: packageName, - versions: versionNums, - summary: packageSummary - }); - }); - }); - } - - private async fetchCondaPackage(packageName: string): Promise { - let condaExe = this.jupyterInstallation.getCondaExePath(); - let cmd = `"${condaExe}" search --json ${packageName}`; - let packageResult: string; - try { - packageResult = await this.jupyterInstallation.executeBufferedCommand(cmd); - } catch (err) { - throw new Error(this.PackageNotFoundError); - } - - if (packageResult) { - let packageJson = JSON.parse(packageResult); - if (packageJson) { - if (packageJson.error) { - throw new Error(packageJson.error); - } - - let packages = packageJson[packageName]; - if (Array.isArray(packages)) { - let allVersions = packages.filter(pkg => pkg && pkg.version).map(pkg => pkg.version); - let singletonVersions = new Set(allVersions); - let sortedVersions = utils.sortPackageVersions(Array.from(singletonVersions), false); - return { - name: packageName, - versions: sortedVersions, - summary: undefined - }; - } - } - } - return undefined; - } private async doPackageInstall(): Promise { let packageName = this.newPackagesName.value; @@ -278,11 +192,7 @@ export class AddNewPackageTab { isCancelable: false, operation: op => { let installPromise: Promise; - if (this.dialog.currentPkgType === PythonPkgType.Anaconda) { - installPromise = this.jupyterInstallation.installCondaPackages([{ name: packageName, version: packageVersion }], false); - } else { - installPromise = this.jupyterInstallation.installPipPackages([{ name: packageName, version: packageVersion }], false); - } + installPromise = this.dialog.model.installPackages([{ name: packageName, version: packageVersion }]); installPromise .then(async () => { let installMsg = localize('managePackages.backgroundInstallComplete', diff --git a/extensions/notebook/src/dialog/managePackages/installedPackagesTab.ts b/extensions/notebook/src/dialog/managePackages/installedPackagesTab.ts index ba6f624bfb83..7279863c7cc6 100644 --- a/extensions/notebook/src/dialog/managePackages/installedPackagesTab.ts +++ b/extensions/notebook/src/dialog/managePackages/installedPackagesTab.ts @@ -11,7 +11,6 @@ import * as utils from '../../common/utils'; import { ManagePackagesDialog } from './managePackagesDialog'; import CodeAdapter from '../../prompts/adapter'; import { QuestionTypes, IQuestion } from '../../prompts/question'; -import { PythonPkgType } from '../../common/constants'; const localize = nls.loadMessageBundle(); @@ -21,6 +20,7 @@ export class InstalledPackagesTab { private installedPkgTab: azdata.window.DialogTab; private packageTypeDropdown: azdata.DropDownComponent; + private locationComponent: azdata.TextComponent; private installedPackageCount: azdata.TextComponent; private installedPackagesTable: azdata.TableComponent; private installedPackagesLoader: azdata.LoadingComponent; @@ -32,18 +32,28 @@ export class InstalledPackagesTab { this.installedPkgTab = azdata.window.createTab(localize('managePackages.installedTabTitle', "Installed")); this.installedPkgTab.registerContent(async view => { - let dropdownValues: string[]; - if (this.dialog.currentPkgType === PythonPkgType.Anaconda) { - dropdownValues = [PythonPkgType.Anaconda, PythonPkgType.Pip]; - } else { - dropdownValues = [PythonPkgType.Pip]; - } + + // TODO: only supporting single location for now. We should add a drop down for multi locations mode + // + let locationTitle = await this.dialog.model.getLocationTitle(); + this.locationComponent = view.modelBuilder.text().withProperties({ + value: locationTitle + }).component(); + + let dropdownValues = this.dialog.model.getPackageTypes().map(x => { + return { + name: x.providerId, + displayName: x.packageType + }; + }); + let defaultPackageType = this.dialog.model.getDefaultPackageType(); this.packageTypeDropdown = view.modelBuilder.dropDown().withProperties({ values: dropdownValues, - value: dropdownValues[0] + value: defaultPackageType }).component(); + this.dialog.changeProvider(defaultPackageType.providerId); this.packageTypeDropdown.onValueChanged(() => { - this.dialog.resetPages(this.packageTypeDropdown.value as PythonPkgType) + this.dialog.resetPages((this.packageTypeDropdown.value).name) .catch(err => { this.dialog.showErrorMessage(utils.getErrorMessage(err)); }); @@ -73,6 +83,9 @@ export class InstalledPackagesTab { let formModel = view.modelBuilder.formContainer() .withFormItems([{ + component: this.locationComponent, + title: localize('managePackages.location', "Location") + }, { component: this.packageTypeDropdown, title: localize('managePackages.packageType', "Package Type") }, { @@ -95,6 +108,7 @@ export class InstalledPackagesTab { await view.initializeModel(this.installedPackagesLoader); await this.loadInstalledPackagesInfo(); + this.packageTypeDropdown.focus(); }); } @@ -108,11 +122,7 @@ export class InstalledPackagesTab { await this.installedPackagesLoader.updateProperties({ loading: true }); await this.uninstallPackageButton.updateProperties({ enabled: false }); try { - if (this.dialog.currentPkgType === PythonPkgType.Anaconda) { - pythonPackages = await this.jupyterInstallation.getInstalledCondaPackages(); - } else { - pythonPackages = await this.jupyterInstallation.getInstalledPipPackages(); - } + pythonPackages = await this.dialog.model.listPackages(); } catch (err) { this.dialog.showErrorMessage(utils.getErrorMessage(err)); } finally { @@ -131,7 +141,7 @@ export class InstalledPackagesTab { await this.installedPackageCount.updateProperties({ value: localize('managePackages.packageCount', "{0} {1} packages found", packageCount, - this.dialog.currentPkgType) + this.dialog.model.currentPackageType) }); if (packageData && packageData.length > 0) { @@ -178,12 +188,7 @@ export class InstalledPackagesTab { description: taskName, isCancelable: false, operation: op => { - let uninstallPromise: Promise; - if (this.dialog.currentPkgType === PythonPkgType.Anaconda) { - uninstallPromise = this.jupyterInstallation.uninstallCondaPackages(packages); - } else { - uninstallPromise = this.jupyterInstallation.uninstallPipPackages(packages); - } + let uninstallPromise: Promise = this.dialog.model.uninstallPackages(packages); uninstallPromise .then(async () => { let uninstallMsg = localize('managePackages.backgroundUninstallComplete', @@ -213,4 +218,4 @@ export class InstalledPackagesTab { this.uninstallPackageButton.updateProperties({ enabled: true }); } -} \ No newline at end of file +} diff --git a/extensions/notebook/src/dialog/managePackages/managePackagesDialog.ts b/extensions/notebook/src/dialog/managePackages/managePackagesDialog.ts index 6560094753c5..644888c61d10 100644 --- a/extensions/notebook/src/dialog/managePackages/managePackagesDialog.ts +++ b/extensions/notebook/src/dialog/managePackages/managePackagesDialog.ts @@ -9,7 +9,7 @@ import * as azdata from 'azdata'; import { JupyterServerInstallation } from '../../jupyter/jupyterServerInstallation'; import { InstalledPackagesTab } from './installedPackagesTab'; import { AddNewPackageTab } from './addNewPackageTab'; -import { PythonPkgType } from '../../common/constants'; +import { ManagePackagesDialogModel } from './managePackagesDialogModel'; const localize = nls.loadMessageBundle(); @@ -18,10 +18,8 @@ export class ManagePackagesDialog { private installedPkgTab: InstalledPackagesTab; private addNewPkgTab: AddNewPackageTab; - public currentPkgType: PythonPkgType; - - constructor(private jupyterInstallation: JupyterServerInstallation) { - this.currentPkgType = this.jupyterInstallation.usingConda ? PythonPkgType.Anaconda : PythonPkgType.Pip; + constructor( + private _managePackageDialogModel: ManagePackagesDialogModel) { } /** @@ -49,8 +47,37 @@ export class ManagePackagesDialog { return this.installedPkgTab.loadInstalledPackagesInfo(); } - public async resetPages(newPkgType: PythonPkgType): Promise { - this.currentPkgType = newPkgType; + public get jupyterInstallation(): JupyterServerInstallation { + return this._managePackageDialogModel.jupyterInstallation; + } + + /** + * Dialog model instance + */ + public get model(): ManagePackagesDialogModel { + return this._managePackageDialogModel; + } + + /** + * Changes the current provider id + * @param providerId Provider Id + */ + public changeProvider(providerId: string): void { + this.model.changeProvider(providerId); + } + + /** + * Resets the tabs for given provider Id + * @param providerId Package Management Provider Id + */ + public async resetPages(providerId: string): Promise { + + // Change the provider in the model + // + this.changeProvider(providerId); + + // Load packages for given provider + // await this.installedPkgTab.loadInstalledPackagesInfo(); await this.addNewPkgTab.resetPageFields(); } diff --git a/extensions/notebook/src/dialog/managePackages/managePackagesDialogModel.ts b/extensions/notebook/src/dialog/managePackages/managePackagesDialogModel.ts new file mode 100644 index 000000000000..66bee240379f --- /dev/null +++ b/extensions/notebook/src/dialog/managePackages/managePackagesDialogModel.ts @@ -0,0 +1,281 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { JupyterServerInstallation } from '../../jupyter/jupyterServerInstallation'; +import { IPackageManageProvider, IPackageDetails, IPackageOverview } from '../../types'; + +export interface ManagePackageDialogOptions { + multiLocations: boolean; + defaultLocation?: string; + defaultProviderId?: string; +} + +export interface ProviderPackageType { + packageType: string; + providerId: string; +} + +/** + * Manage package dialog model + */ +export class ManagePackagesDialogModel { + + private _currentProvider: string; + + /** + * A set for locations + */ + private _locations: Set = new Set(); + + /** + * Map of locations to providers + */ + private _packageTypes: Map = new Map(); + + /** + * Creates new instance of the model + * @param _jupyterInstallation Jupyter installation + * @param _packageManageProviders package manage providers + * @param _options dialog options + */ + constructor( + private _jupyterInstallation: JupyterServerInstallation, + private _packageManageProviders: Map, + private _options?: ManagePackageDialogOptions) { + + if (!this._packageManageProviders || this._packageManageProviders.size === 0) { + throw Error('Invalid list of package manager providers'); + } + } + + /** + * Initialized the model + */ + public async init(): Promise { + await this.loadCaches(); + this.loadOptions(); + this.changeProvider(this.defaultProviderId); + } + + /** + * Loads the model options + */ + private loadOptions(): void { + + // Set Default Options + // + if (!this._options) { + this._options = this.defaultOptions; + } + + if (this._options.defaultLocation && !this._packageTypes.has(this._options.defaultLocation)) { + throw new Error(`Invalid default location '${this._options.defaultLocation}`); + } + + if (this._options.defaultProviderId && !this._packageManageProviders.has(this._options.defaultProviderId)) { + throw new Error(`Invalid default provider id '${this._options.defaultProviderId}`); + } + + if (!this._options.multiLocations && !this.defaultLocation) { + throw new Error('Default location not specified for single location mode'); + } + } + + private get defaultOptions(): ManagePackageDialogOptions { + return { + multiLocations: true, + defaultLocation: undefined, + defaultProviderId: undefined + }; + } + + /** + * Returns the providers map + */ + public get packageManageProviders(): Map { + return this._packageManageProviders; + } + + /** + * Returns the current provider + */ + public get currentPackageManageProvider(): IPackageManageProvider | undefined { + if (this._currentProvider) { + let provider = this._packageManageProviders.get(this._currentProvider); + return provider; + } + return undefined; + } + + /** + * Returns the current provider + */ + public get currentPackageType(): string | undefined { + if (this._currentProvider) { + let provider = this._packageManageProviders.get(this._currentProvider); + return provider.packageTarget.packageType; + } + return undefined; + } + + /** + * Returns true if multi locations mode is enabled + */ + public get multiLocationMode(): boolean { + return this.options.multiLocations; + } + + /** + * Returns options + */ + public get options(): ManagePackageDialogOptions { + return this._options || this.defaultOptions; + } + + /** + * returns the array of target locations + */ + public get targetLocations(): string[] { + return Array.from(this._locations.keys()); + } + + /** + * Returns the default location + */ + public get defaultLocation(): string { + return this.options.defaultLocation || this.targetLocations[0]; + } + + /** + * Returns the default location + */ + public get defaultProviderId(): string { + return this.options.defaultProviderId || Array.from(this.packageManageProviders.keys())[0]; + } + + /** + * Loads the provider cache + */ + private async loadCaches(): Promise { + if (this.packageManageProviders) { + let keyArray = Array.from(this.packageManageProviders.keys()); + for (let index = 0; index < keyArray.length; index++) { + const element = this.packageManageProviders.get(keyArray[index]); + if (await element.canUseProvider()) { + if (!this._locations.has(element.packageTarget.location)) { + this._locations.add(element.packageTarget.location); + } + if (!this._packageTypes.has(element.packageTarget.location)) { + this._packageTypes.set(element.packageTarget.location, []); + } + this._packageTypes.get(element.packageTarget.location).push(element); + } + } + } + } + + /** + * Returns a map of providerId to package types for given location + */ + public getPackageTypes(targetLocation?: string): ProviderPackageType[] { + targetLocation = targetLocation || this.defaultLocation; + let providers = this._packageTypes.get(targetLocation); + return providers.map(x => { + return { + providerId: x.providerId, + packageType: x.packageTarget.packageType + }; + }); + } + + /** + * Returns a map of providerId to package types for given location + */ + public getDefaultPackageType(): ProviderPackageType { + let defaultProviderId = this.defaultProviderId; + let packageTypes = this.getPackageTypes(); + return packageTypes.find(x => x.providerId === defaultProviderId); + } + + /** + * returns the list of packages for current provider + */ + public async listPackages(): Promise { + let provider = this.currentPackageManageProvider; + if (provider) { + return await provider.listPackages(); + } else { + throw new Error('Current Provider is not set'); + } + } + + /** + * Changes the current provider + */ + public changeProvider(providerId: string): void { + if (this._packageManageProviders.has(providerId)) { + this._currentProvider = providerId; + } else { + throw Error(`Invalid package type ${providerId}`); + } + } + + /** + * Installs given packages using current provider + * @param packages Packages to install + */ + public async installPackages(packages: IPackageDetails[]): Promise { + let provider = this.currentPackageManageProvider; + if (provider) { + await provider.installPackages(packages, false); + } else { + throw new Error('Current Provider is not set'); + } + } + + /** + * Returns the location title for current provider + */ + public async getLocationTitle(): Promise { + let provider = this.currentPackageManageProvider; + if (provider) { + return await provider.getLocationTitle(); + } + return Promise.resolve(undefined); + } + + /** + * UnInstalls given packages using current provider + * @param packages Packages to install + */ + public async uninstallPackages(packages: IPackageDetails[]): Promise { + let provider = this.currentPackageManageProvider; + if (provider) { + await provider.uninstallPackages(packages); + } else { + throw new Error('Current Provider is not set'); + } + } + + /** + * Returns package preview for given name + * @param packageName Package name + */ + public async getPackageOverview(packageName: string): Promise { + let provider = this.currentPackageManageProvider; + if (provider) { + return await provider.getPackageOverview(packageName); + } else { + throw new Error('Current Provider is not set'); + } + } + + /** + * Returns the jupyterInstallation instance + */ + public get jupyterInstallation(): JupyterServerInstallation { + return this._jupyterInstallation; + } +} diff --git a/extensions/notebook/src/extension.ts b/extensions/notebook/src/extension.ts index bdbfba62a111..98ae93cbc57d 100644 --- a/extensions/notebook/src/extension.ts +++ b/extensions/notebook/src/extension.ts @@ -11,7 +11,7 @@ import * as nls from 'vscode-nls'; import { JupyterController } from './jupyter/jupyterController'; import { AppContext } from './common/appContext'; import { ApiWrapper } from './common/apiWrapper'; -import { IExtensionApi } from './types'; +import { IExtensionApi, IPackageManageProvider } from './types'; import { CellType } from './contracts/content'; import { getErrorMessage, isEditorTitleFree } from './common/utils'; import { NotebookUriHandler } from './protocol/notebookUriHandler'; @@ -115,6 +115,12 @@ export async function activate(extensionContext: vscode.ExtensionContext): Promi return { getJupyterController() { return controller; + }, + registerPackageManager(providerId: string, packageManagerProvider: IPackageManageProvider): void { + controller.registerPackageManager(providerId, packageManagerProvider); + }, + getPackageManagers() { + return controller.packageManageProviders; } }; } diff --git a/extensions/notebook/src/jupyter/jupyterController.ts b/extensions/notebook/src/jupyter/jupyterController.ts index e54ad378dd5d..39211854304b 100644 --- a/extensions/notebook/src/jupyter/jupyterController.ts +++ b/extensions/notebook/src/jupyter/jupyterController.ts @@ -25,6 +25,11 @@ import { JupyterNotebookProvider } from './jupyterNotebookProvider'; import { ConfigurePythonDialog } from '../dialog/configurePythonDialog'; import CodeAdapter from '../prompts/adapter'; import { ManagePackagesDialog } from '../dialog/managePackages/managePackagesDialog'; +import { IPackageManageProvider } from '../types'; +import { LocalPipPackageManageProvider } from './localPipPackageManageProvider'; +import { LocalCondaPackageManageProvider } from './localCondaPackageManageProvider'; +import { ManagePackagesDialogModel, ManagePackageDialogOptions } from '../dialog/managePackages/managePackagesDialogModel'; +import { PiPyClient } from './pipyClient'; let untitledCounter = 0; @@ -32,6 +37,7 @@ export class JupyterController implements vscode.Disposable { private _jupyterInstallation: JupyterServerInstallation; private _notebookInstances: IServerInstance[] = []; private _serverInstanceFactory: ServerInstanceFactory = new ServerInstanceFactory(); + private _packageManageProviders = new Map(); private outputChannel: vscode.OutputChannel; private prompter: IPrompter; @@ -76,7 +82,7 @@ export class JupyterController implements vscode.Disposable { }); this.apiWrapper.registerCommand(constants.jupyterReinstallDependenciesCommand, () => { return this.handleDependenciesReinstallation(); }); - this.apiWrapper.registerCommand(constants.jupyterManagePackages, () => { return this.doManagePackages(); }); + this.apiWrapper.registerCommand(constants.jupyterManagePackages, async (args) => { return this.doManagePackages(args); }); this.apiWrapper.registerCommand(constants.jupyterConfigurePython, () => { return this.doConfigurePython(this._jupyterInstallation); }); let supportedFileFilter: vscode.DocumentFilter[] = [ @@ -85,6 +91,7 @@ export class JupyterController implements vscode.Disposable { let notebookProvider = this.registerNotebookProvider(); this.extensionContext.subscriptions.push(this.apiWrapper.registerCompletionItemProvider(supportedFileFilter, new NotebookCompletionItemProvider(notebookProvider))); + this.registerDefaultPackageManageProviders(); return true; } @@ -110,7 +117,7 @@ export class JupyterController implements vscode.Disposable { public deactivate(): void { // Shutdown any open notebooks - this._notebookInstances.forEach(instance => { instance.stop(); }); + this._notebookInstances.forEach(async (instance) => { await instance.stop(); }); } // EVENT HANDLERS ////////////////////////////////////////////////////// @@ -196,9 +203,19 @@ export class JupyterController implements vscode.Disposable { }); } - public doManagePackages(): void { + public async doManagePackages(options?: ManagePackageDialogOptions): Promise { try { - let packagesDialog = new ManagePackagesDialog(this._jupyterInstallation); + if (!options) { + options = { + multiLocations: false, + defaultLocation: constants.localhostName, + defaultProviderId: LocalPipPackageManageProvider.ProviderId + }; + } + let model = new ManagePackagesDialogModel(this._jupyterInstallation, this._packageManageProviders, options); + + await model.init(); + let packagesDialog = new ManagePackagesDialog(model); packagesDialog.showDialog(); } catch (error) { let message = utils.getErrorMessage(error); @@ -206,6 +223,33 @@ export class JupyterController implements vscode.Disposable { } } + /** + * Register a package provider + * @param providerId Provider Id + * @param packageManageProvider Provider instance + */ + public registerPackageManager(providerId: string, packageManageProvider: IPackageManageProvider): void { + if (packageManageProvider) { + if (!this._packageManageProviders.has(providerId)) { + this._packageManageProviders.set(providerId, packageManageProvider); + } else { + throw Error(`Package manager provider is already registered. provider id: ${providerId}`); + } + } + } + + /** + * Returns the list of registered providers + */ + public get packageManageProviders(): Map { + return this._packageManageProviders; + } + + private registerDefaultPackageManageProviders(): void { + this.registerPackageManager(LocalPipPackageManageProvider.ProviderId, new LocalPipPackageManageProvider(this._jupyterInstallation, new PiPyClient())); + this.registerPackageManager(LocalCondaPackageManageProvider.ProviderId, new LocalCondaPackageManageProvider(this._jupyterInstallation)); + } + public doConfigurePython(jupyterInstaller: JupyterServerInstallation): void { let pythonDialog = new ConfigurePythonDialog(this.apiWrapper, jupyterInstaller); pythonDialog.showDialog().catch((err: any) => { diff --git a/extensions/notebook/src/jupyter/jupyterServerInstallation.ts b/extensions/notebook/src/jupyter/jupyterServerInstallation.ts index 894dcc4ef0e2..3e799b03584c 100644 --- a/extensions/notebook/src/jupyter/jupyterServerInstallation.ts +++ b/extensions/notebook/src/jupyter/jupyterServerInstallation.ts @@ -38,7 +38,22 @@ function msgDependenciesInstallationFailed(errorMessage: string): string { retur function msgDownloadPython(platform: string, pythonDownloadUrl: string): string { return localize('msgDownloadPython', "Downloading local python for platform: {0} to {1}", platform, pythonDownloadUrl); } function msgPackageRetrievalFailed(errorMessage: string): string { return localize('msgPackageRetrievalFailed', "Encountered an error when trying to retrieve list of installed packages: {0}", errorMessage); } -export class JupyterServerInstallation { +export interface IJupyterServerInstallation { + installCondaPackages(packages: PythonPkgDetails[], useMinVersion: boolean): Promise; + configurePackagePaths(): Promise; + startInstallProcess(forceInstall: boolean, installSettings?: { installPath: string, existingPython: boolean }): Promise; + getInstalledPipPackages(): Promise; + getInstalledCondaPackages(): Promise; + uninstallCondaPackages(packages: PythonPkgDetails[]): Promise; + usingConda: boolean; + getCondaExePath(): string; + executeBufferedCommand(command: string): Promise; + executeStreamedCommand(command: string): Promise; + installPipPackages(packages: PythonPkgDetails[], useMinVersion: boolean): Promise; + uninstallPipPackages(packages: PythonPkgDetails[]): Promise; + pythonExecutable: string; +} +export class JupyterServerInstallation implements IJupyterServerInstallation { public apiWrapper: ApiWrapper; public extensionPath: string; public pythonBinPath: string; @@ -625,7 +640,7 @@ export class JupyterServerInstallation { } } - private async executeStreamedCommand(command: string): Promise { + public async executeStreamedCommand(command: string): Promise { await utils.executeStreamedCommand(command, { env: this.execOptions.env }, this.outputChannel); } diff --git a/extensions/notebook/src/jupyter/localCondaPackageManageProvider.ts b/extensions/notebook/src/jupyter/localCondaPackageManageProvider.ts new file mode 100644 index 000000000000..c5f4b60f4488 --- /dev/null +++ b/extensions/notebook/src/jupyter/localCondaPackageManageProvider.ts @@ -0,0 +1,114 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IPackageManageProvider, IPackageDetails, IPackageTarget, IPackageOverview } from '../types'; +import { IJupyterServerInstallation } from './jupyterServerInstallation'; +import * as constants from '../common/constants'; +import * as utils from '../common/utils'; + +export class LocalCondaPackageManageProvider implements IPackageManageProvider { + + /** + * Provider Id for Anaconda package manage provider + */ + public static ProviderId = 'localhost_Anaconda'; + + constructor(private jupyterInstallation: IJupyterServerInstallation) { + } + + /** + * Returns package target + */ + public get packageTarget(): IPackageTarget { + return { location: constants.localhostName, packageType: constants.PythonPkgType.Anaconda }; + } + + /** + * Returns provider Id + */ + public get providerId(): string { + return LocalCondaPackageManageProvider.ProviderId; + } + + /** + * Returns list of packages + */ + public async listPackages(): Promise { + return await this.jupyterInstallation.getInstalledCondaPackages(); + } + + /** + * Installs given packages + * @param packages Packages to install + * @param useMinVersion minimum version + */ + installPackages(packages: IPackageDetails[], useMinVersion: boolean): Promise { + return this.jupyterInstallation.installCondaPackages(packages, useMinVersion); + } + + /** + * Uninstalls given packages + * @param packages Packages to uninstall + */ + uninstallPackages(packages: IPackageDetails[]): Promise { + return this.jupyterInstallation.uninstallCondaPackages(packages); + } + + /** + * Returns true if the provider can be used + */ + canUseProvider(): Promise { + return Promise.resolve(this.jupyterInstallation.usingConda); + } + + /** + * Returns location title + */ + getLocationTitle(): Promise { + return Promise.resolve(constants.localhostTitle); + } + + /** + * Returns package overview for given name + * @param packageName Package Name + */ + getPackageOverview(packageName: string): Promise { + return this.fetchCondaPackage(packageName); + } + + private async fetchCondaPackage(packageName: string): Promise { + let condaExe = this.jupyterInstallation.getCondaExePath(); + let cmd = `"${condaExe}" search --json ${packageName}`; + let packageResult: string; + try { + packageResult = await this.jupyterInstallation.executeBufferedCommand(cmd); + } catch (err) { + throw new Error(constants.PackageNotFoundError); + } + + if (packageResult) { + let packageJson = JSON.parse(packageResult); + if (packageJson) { + if (packageJson.error) { + throw new Error(packageJson.error); + } + + let packages = packageJson[packageName]; + if (Array.isArray(packages)) { + let allVersions = packages.filter(pkg => pkg && pkg.version).map(pkg => pkg.version); + let singletonVersions = new Set(allVersions); + let sortedVersions = utils.sortPackageVersions(Array.from(singletonVersions), false); + return { + name: packageName, + versions: sortedVersions, + summary: undefined + }; + } + } + } + + return undefined; + } +} diff --git a/extensions/notebook/src/jupyter/localPipPackageManageProvider.ts b/extensions/notebook/src/jupyter/localPipPackageManageProvider.ts new file mode 100644 index 000000000000..1be49dcc781c --- /dev/null +++ b/extensions/notebook/src/jupyter/localPipPackageManageProvider.ts @@ -0,0 +1,110 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IPackageManageProvider, IPackageDetails, IPackageTarget, IPackageOverview } from '../types'; +import { IJupyterServerInstallation } from './jupyterServerInstallation'; +import * as constants from '../common/constants'; +import * as utils from '../common/utils'; +import { IPiPyClient } from './pipyClient'; + +export class LocalPipPackageManageProvider implements IPackageManageProvider { + + /** + * Provider Id for Pip package manage provider + */ + public static ProviderId = 'localhost_Pip'; + + constructor( + private jupyterInstallation: IJupyterServerInstallation, + private pipyClient: IPiPyClient) { + } + + /** + * Returns provider Id + */ + public get providerId(): string { + return LocalPipPackageManageProvider.ProviderId; + } + + /** + * Returns package target + */ + public get packageTarget(): IPackageTarget { + return { location: constants.localhostName, packageType: constants.PythonPkgType.Pip }; + } + + /** + * Returns list of packages + */ + public async listPackages(): Promise { + return await this.jupyterInstallation.getInstalledPipPackages(); + } + + /** + * Installs given packages + * @param packages Packages to install + * @param useMinVersion minimum version + */ + installPackages(packages: IPackageDetails[], useMinVersion: boolean): Promise { + return this.jupyterInstallation.installPipPackages(packages, useMinVersion); + } + + /** + * Uninstalls given packages + * @param packages Packages to uninstall + */ + uninstallPackages(packages: IPackageDetails[]): Promise { + return this.jupyterInstallation.uninstallPipPackages(packages); + } + + /** + * Returns true if the provider can be used + */ + canUseProvider(): Promise { + return Promise.resolve(true); + } + + /** + * Returns location title + */ + getLocationTitle(): Promise { + return Promise.resolve(constants.localhostTitle); + } + + /** + * Returns package overview for given name + * @param packageName Package Name + */ + getPackageOverview(packageName: string): Promise { + return this.fetchPypiPackage(packageName); + } + + private async fetchPypiPackage(packageName: string): Promise { + let body = await this.pipyClient.fetchPypiPackage(packageName); + let packagesJson = JSON.parse(body); + let versionNums: string[] = []; + let packageSummary = ''; + if (packagesJson) { + if (packagesJson.releases) { + let versionKeys = Object.keys(packagesJson.releases); + versionKeys = versionKeys.filter(versionKey => { + let releaseInfo = packagesJson.releases[versionKey]; + return Array.isArray(releaseInfo) && releaseInfo.length > 0; + }); + versionNums = utils.sortPackageVersions(versionKeys, false); + } + + if (packagesJson.info && packagesJson.info.summary) { + packageSummary = packagesJson.info.summary; + } + } + + return { + name: packageName, + versions: versionNums, + summary: packageSummary + }; + } +} diff --git a/extensions/notebook/src/jupyter/pipyClient.ts b/extensions/notebook/src/jupyter/pipyClient.ts new file mode 100644 index 000000000000..2a82ceb74062 --- /dev/null +++ b/extensions/notebook/src/jupyter/pipyClient.ts @@ -0,0 +1,46 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vscode-nls'; +import * as request from 'request'; +import * as constants from '../common/constants'; + +const localize = nls.loadMessageBundle(); + +export interface IPiPyClient { + fetchPypiPackage(packageName: string): Promise; +} + +export class PiPyClient implements IPiPyClient { + + private readonly RequestTimeout = 10000; + private getLink(packageName: string): string { + return `https://pypi.org/pypi/${packageName}/json`; + } + + public async fetchPypiPackage(packageName: string): Promise { + return new Promise((resolve, reject) => { + request.get(this.getLink(packageName), { timeout: this.RequestTimeout }, (error, response, body) => { + if (error) { + return reject(error); + } + + if (response.statusCode === 404) { + return reject(constants.PackageNotFoundError); + } + + if (response.statusCode !== 200) { + return reject( + localize('managePackages.packageRequestError', + "Package info request failed with error: {0} {1}", + response.statusCode, + response.statusMessage)); + } + + resolve(body); + }); + }); + } +} diff --git a/extensions/notebook/src/test/managePackages/localPackageManageProvider.test.ts b/extensions/notebook/src/test/managePackages/localPackageManageProvider.test.ts new file mode 100644 index 000000000000..0a719fb90524 --- /dev/null +++ b/extensions/notebook/src/test/managePackages/localPackageManageProvider.test.ts @@ -0,0 +1,229 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import * as should from 'should'; +import 'mocha'; +import * as TypeMoq from 'typemoq'; +import { JupyterServerInstallation, PythonPkgDetails, IJupyterServerInstallation } from '../../jupyter/jupyterServerInstallation'; +import { LocalCondaPackageManageProvider } from '../../jupyter/localCondaPackageManageProvider'; +import * as constants from '../../common/constants'; +import { LocalPipPackageManageProvider } from '../../jupyter/localPipPackageManageProvider'; +import { IPiPyClient, PiPyClient } from '../../jupyter/pipyClient'; + +interface TestContext { + serverInstallation: IJupyterServerInstallation; + piPyClient: IPiPyClient; +} + +describe('Manage Package Providers', () => { + + it('Conda should return valid package target', async function (): Promise { + let testContext = createContext(); + let serverInstallation = createJupyterServerInstallation(testContext); + let provider = new LocalCondaPackageManageProvider(serverInstallation.object); + should.deepEqual(provider.packageTarget, { location: constants.localhostName, packageType: constants.PythonPkgType.Anaconda }); + }); + + it('Pip should return valid package target', async function (): Promise { + let testContext = createContext(); + let serverInstallation = createJupyterServerInstallation(testContext); + let client = createPipyClient(testContext); + let provider = new LocalPipPackageManageProvider(serverInstallation.object, client.object); + should.deepEqual(provider.packageTarget, { location: constants.localhostName, packageType: constants.PythonPkgType.Pip }); + }); + + it('Pip listPackages should return valid packages', async function (): Promise { + let packages = [ + { + name: 'name1', + version: '1.1.1.1' + } + ]; + let testContext = createContext(); + testContext.serverInstallation.getInstalledPipPackages = () => { + return Promise.resolve(packages); + }; + let serverInstallation = createJupyterServerInstallation(testContext); + let client = createPipyClient(testContext); + let provider = new LocalPipPackageManageProvider(serverInstallation.object, client.object); + + should.deepEqual(await provider.listPackages(), packages); + }); + + it('Conda listPackages should return valid packages', async function (): Promise { + let packages = [ + { + name: 'name1', + version: '1.1.1.1' + } + ]; + let testContext = createContext(); + testContext.serverInstallation.getInstalledCondaPackages = () => { + return Promise.resolve(packages); + }; + let serverInstallation = createJupyterServerInstallation(testContext); + let provider = new LocalCondaPackageManageProvider(serverInstallation.object); + + let actual = await provider.listPackages(); + should.deepEqual(actual, packages); + }); + + it('Pip installPackages should install packages successfully', async function (): Promise { + let packages = [ + { + name: 'name1', + version: '1.1.1.1' + } + ]; + let testContext = createContext(); + let serverInstallation = createJupyterServerInstallation(testContext); + let client = createPipyClient(testContext); + let provider = new LocalPipPackageManageProvider(serverInstallation.object, client.object); + + await provider.installPackages(packages, true); + serverInstallation.verify(x => x.installPipPackages(packages, true), TypeMoq.Times.once()); + }); + + it('Conda installPackages should install packages successfully', async function (): Promise { + let packages = [ + { + name: 'name1', + version: '1.1.1.1' + } + ]; + let testContext = createContext(); + let serverInstallation = createJupyterServerInstallation(testContext); + let provider = new LocalCondaPackageManageProvider(serverInstallation.object); + + await provider.installPackages(packages, true); + serverInstallation.verify(x => x.installCondaPackages(packages, true), TypeMoq.Times.once()); + }); + + it('Pip uninstallPackages should install packages successfully', async function (): Promise { + let packages = [ + { + name: 'name1', + version: '1.1.1.1' + } + ]; + let testContext = createContext(); + let serverInstallation = createJupyterServerInstallation(testContext); + let client = createPipyClient(testContext); + let provider = new LocalPipPackageManageProvider(serverInstallation.object, client.object); + + await provider.uninstallPackages(packages); + serverInstallation.verify(x => x.uninstallPipPackages(packages), TypeMoq.Times.once()); + }); + + it('Conda uninstallPackages should install packages successfully', async function (): Promise { + let packages = [ + { + name: 'name1', + version: '1.1.1.1' + } + ]; + let testContext = createContext(); + let serverInstallation = createJupyterServerInstallation(testContext); + let provider = new LocalCondaPackageManageProvider(serverInstallation.object); + + await provider.uninstallPackages(packages); + serverInstallation.verify(x => x.uninstallCondaPackages(packages), TypeMoq.Times.once()); + }); + + it('Conda canUseProvider should return what the server is returning', async function (): Promise { + let testContext = createContext(); + let serverInstallation = createJupyterServerInstallation(testContext); + let provider = new LocalCondaPackageManageProvider(serverInstallation.object); + + should.equal(await provider.canUseProvider(), false); + }); + + it('Pip canUseProvider should return true', async function (): Promise { + let testContext = createContext(); + let serverInstallation = createJupyterServerInstallation(testContext); + let client = createPipyClient(testContext); + let provider = new LocalPipPackageManageProvider(serverInstallation.object, client.object); + + should.equal(await provider.canUseProvider(), true); + }); + + it('Pip getPackageOverview should return package info successfully', async function (): Promise { + let testContext = createContext(); + testContext.piPyClient.fetchPypiPackage = (packageName) => { + return Promise.resolve(` + "{"info":{"summary":"package summary"}, "releases":{"0.0.1":[{"comment_text":""}], "0.0.2":[{"comment_text":""}]}"`); + }; + let serverInstallation = createJupyterServerInstallation(testContext); + let client = createPipyClient(testContext); + let provider = new LocalPipPackageManageProvider(serverInstallation.object, client.object); + + should(provider.getPackageOverview('name')).resolvedWith({ + name: 'name', + versions: ['0.0.1', '0.0.2'], + summary: 'summary' + }); + }); + + it('Conda getPackageOverview should return package info successfully', async function (): Promise { + let testContext = createContext(); + testContext.serverInstallation.executeBufferedCommand = (command) => { + return Promise.resolve(` + "{"name":[{"version":"0.0.1"}, {"version":"0.0.2}]"`); + }; + let serverInstallation = createJupyterServerInstallation(testContext); + + let provider = new LocalCondaPackageManageProvider(serverInstallation.object); + + should(provider.getPackageOverview('name')).resolvedWith({ + name: 'name', + versions: ['0.0.1', '0.0.2'], + summary: undefined + }); + }); + + function createContext(): TestContext { + return { + serverInstallation: { + installCondaPackages: (packages: PythonPkgDetails[], useMinVersion: boolean) => { return Promise.resolve(); }, + configurePackagePaths: () => { return Promise.resolve(); }, + startInstallProcess: (forceInstall: boolean, installSettings?: { installPath: string, existingPython: boolean }) => { return Promise.resolve(); }, + getInstalledPipPackages: () => { return Promise.resolve([]); }, + installPipPackages: (packages: PythonPkgDetails[], useMinVersion: boolean) => { return Promise.resolve(); }, + uninstallPipPackages: (packages: PythonPkgDetails[]) => { return Promise.resolve(); }, + getInstalledCondaPackages: () => { return Promise.resolve([]); }, + uninstallCondaPackages: (packages: PythonPkgDetails[]) => { return Promise.resolve(); }, + executeBufferedCommand: (command: string) => { return Promise.resolve(''); }, + executeStreamedCommand: (command: string) => { return Promise.resolve(); }, + getCondaExePath: () => { return ''; }, + pythonExecutable: '', + usingConda: false + }, + piPyClient: { + fetchPypiPackage: (packageName) => { return Promise.resolve(); } + } + }; + } + + function createJupyterServerInstallation(testContext: TestContext): TypeMoq.IMock { + let mockInstance = TypeMoq.Mock.ofType(JupyterServerInstallation); + mockInstance.setup(x => x.installCondaPackages(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns((packages, useMinVersion) => testContext.serverInstallation.installCondaPackages(packages, useMinVersion)); + mockInstance.setup(x => x.installPipPackages(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns((packages, useMinVersion) => testContext.serverInstallation.installPipPackages(packages, useMinVersion)); + mockInstance.setup(x => x.uninstallCondaPackages(TypeMoq.It.isAny())).returns((packages, useMinVersion) => testContext.serverInstallation.uninstallCondaPackages(packages)); + mockInstance.setup(x => x.uninstallPipPackages(TypeMoq.It.isAny())).returns((packages, useMinVersion) => testContext.serverInstallation.uninstallPipPackages(packages)); + mockInstance.setup(x => x.getInstalledPipPackages()).returns(() => testContext.serverInstallation.getInstalledPipPackages()); + mockInstance.setup(x => x.getInstalledCondaPackages()).returns(() => testContext.serverInstallation.getInstalledCondaPackages()); + mockInstance.setup(x => x.usingConda).returns(() => testContext.serverInstallation.usingConda); + return mockInstance; + } + + function createPipyClient(testContext: TestContext): TypeMoq.IMock { + let mockInstance = TypeMoq.Mock.ofType(PiPyClient); + mockInstance.setup(x => x.fetchPypiPackage(TypeMoq.It.isAny())).returns((packageName) => + testContext.piPyClient.fetchPypiPackage(packageName)); + return mockInstance; + } +}); diff --git a/extensions/notebook/src/test/managePackages/managePackagesDialogModel.test.ts b/extensions/notebook/src/test/managePackages/managePackagesDialogModel.test.ts new file mode 100644 index 000000000000..f706235b4c69 --- /dev/null +++ b/extensions/notebook/src/test/managePackages/managePackagesDialogModel.test.ts @@ -0,0 +1,351 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import * as should from 'should'; +import 'mocha'; +import * as TypeMoq from 'typemoq'; +import { IPackageManageProvider, IPackageDetails } from '../../types'; +import { LocalPipPackageManageProvider } from '../../jupyter/localPipPackageManageProvider'; + +import { ManagePackagesDialogModel } from '../../dialog/managePackages/managePackagesDialogModel'; +import { JupyterServerInstallation } from '../../jupyter/jupyterServerInstallation'; + +interface TestContext { + provider: IPackageManageProvider; +} + +describe('Manage Packages', () => { + let jupyterServerInstallation: JupyterServerInstallation; + beforeEach(() => { + jupyterServerInstallation = new JupyterServerInstallation(undefined, undefined, undefined, undefined); + }); + + it('Should throw exception given undefined providers', async function (): Promise { + should.throws(() => { new ManagePackagesDialogModel(jupyterServerInstallation, undefined); }, 'Invalid list of package manager providers'); + }); + + it('Should throw exception given empty providers', async function (): Promise { + let providers = new Map(); + should.throws(() => { new ManagePackagesDialogModel(jupyterServerInstallation, providers); }, 'Invalid list of package manager providers'); + }); + + it('Should not throw exception given undefined options', async function (): Promise { + let testContext = createContext(); + testContext.provider.listPackages = () => { + return Promise.resolve(undefined); + }; + let provider = createProvider(testContext); + let providers = new Map(); + providers.set(provider.providerId, provider); + + should.doesNotThrow(() => { new ManagePackagesDialogModel(jupyterServerInstallation, providers, undefined); }); + }); + + it('Init should throw exception given invalid default location', async function (): Promise { + let testContext = createContext(); + let provider = createProvider(testContext); + let providers = new Map(); + providers.set(provider.providerId, provider); + + let options = { + multiLocations: true, + defaultLocation: 'invalid location' + }; + let model = new ManagePackagesDialogModel(jupyterServerInstallation, providers, options); + should(model.init()).rejectedWith(`Invalid default location '${options.defaultLocation}`); + }); + + it('Init should throw exception given invalid default provider', async function (): Promise { + let testContext = createContext(); + let provider = createProvider(testContext); + let providers = new Map(); + providers.set(provider.providerId, provider); + + let options = { + multiLocations: true, + defaultProviderId: 'invalid provider' + }; + let model = new ManagePackagesDialogModel(jupyterServerInstallation, providers, options); + should(model.init()).rejectedWith(`Invalid default provider id '${options.defaultProviderId}`); + }); + + it('Init should throw exception not given valid default location for single location mode', async function (): Promise { + let testContext = createContext(); + let provider = createProvider(testContext); + let providers = new Map(); + providers.set(provider.providerId, provider); + + let options = { + multiLocations: false + }; + let model = new ManagePackagesDialogModel(jupyterServerInstallation, providers, options); + should(model.init()).rejectedWith(`Default location not specified for single location mode`); + }); + + + it('Init should set default options given undefined', async function (): Promise { + let testContext = createContext(); + let provider = createProvider(testContext); + let providers = new Map(); + providers.set(provider.providerId, provider); + + let model = new ManagePackagesDialogModel(jupyterServerInstallation, providers, undefined); + + await model.init(); + should.equal(model.multiLocationMode, true); + should.equal(model.defaultLocation, provider.packageTarget.location); + should.equal(model.defaultProviderId, provider.providerId); + }); + + it('Init should set default provider Id given valid options', async function (): Promise { + let testContext1 = createContext(); + testContext1.provider.providerId = 'providerId1'; + testContext1.provider.packageTarget = { + location: 'location1', + packageType: 'package-type1' + }; + + let testContext2 = createContext(); + testContext2.provider.providerId = 'providerId2'; + testContext2.provider.packageTarget = { + location: 'location1', + packageType: 'package-type2' + }; + let providers = new Map(); + providers.set(testContext1.provider.providerId, createProvider(testContext1)); + providers.set(testContext2.provider.providerId, createProvider(testContext2)); + let options = { + multiLocations: false, + defaultLocation: testContext2.provider.packageTarget.location, + defaultProviderId: testContext2.provider.providerId + }; + let model = new ManagePackagesDialogModel(jupyterServerInstallation, providers, options); + + await model.init(); + should.equal(model.multiLocationMode, false); + should.equal(model.defaultLocation, testContext2.provider.packageTarget.location); + should.equal(model.defaultProviderId, testContext2.provider.providerId); + }); + + it('Should create a cache for multiple providers successfully', async function (): Promise { + let testContext1 = createContext(); + testContext1.provider.providerId = 'providerId1'; + testContext1.provider.packageTarget = { + location: 'location1', + packageType: 'package-type1' + }; + + let testContext2 = createContext(); + testContext2.provider.providerId = 'providerId2'; + testContext2.provider.packageTarget = { + location: 'location1', + packageType: 'package-type2' + }; + + let testContext3 = createContext(); + testContext3.provider.providerId = 'providerId3'; + testContext3.provider.packageTarget = { + location: 'location2', + packageType: 'package-type1' + }; + let providers = new Map(); + providers.set(testContext1.provider.providerId, createProvider(testContext1)); + providers.set(testContext2.provider.providerId, createProvider(testContext2)); + providers.set(testContext3.provider.providerId, createProvider(testContext3)); + + let model = new ManagePackagesDialogModel(jupyterServerInstallation, providers, undefined); + + await model.init(); + should.equal(model.defaultLocation, testContext1.provider.packageTarget.location); + should.deepEqual(model.getPackageTypes('location1'), [{ providerId: 'providerId1', packageType: 'package-type1'}, {providerId: 'providerId2', packageType: 'package-type2'}]); + should.deepEqual(model.getPackageTypes('location2'), [{providerId: 'providerId3', packageType: 'package-type1'}]); + }); + + it('Should not include a provider that can not be used in current context', async function (): Promise { + let testContext1 = createContext(); + testContext1.provider.providerId = 'providerId1'; + testContext1.provider.packageTarget = { + location: 'location1', + packageType: 'package-type1' + }; + + let testContext2 = createContext(); + testContext2.provider.providerId = 'providerId2'; + testContext2.provider.packageTarget = { + location: 'location1', + packageType: 'package-type2' + }; + testContext2.provider.canUseProvider = () => { return Promise.resolve(false); }; + + let providers = new Map(); + providers.set(testContext1.provider.providerId, createProvider(testContext1)); + providers.set(testContext2.provider.providerId, createProvider(testContext2)); + + let model = new ManagePackagesDialogModel(jupyterServerInstallation, providers, undefined); + + await model.init(); + should.equal(model.defaultLocation, testContext1.provider.packageTarget.location); + should.deepEqual(model.getPackageTypes('location1'), [{providerId: 'providerId1', packageType: 'package-type1'}]); + }); + + it('changeProvider should change current provider successfully', async function (): Promise { + let testContext1 = createContext(); + testContext1.provider.providerId = 'providerId1'; + testContext1.provider.getLocationTitle = () => Promise.resolve('location title 1'); + testContext1.provider.packageTarget = { + location: 'location1', + packageType: 'package-type1' + }; + + let testContext2 = createContext(); + testContext2.provider.providerId = 'providerId2'; + testContext2.provider.getLocationTitle = () => Promise.resolve('location title 2'); + testContext2.provider.packageTarget = { + location: 'location2', + packageType: 'package-type2' + }; + + let providers = new Map(); + providers.set(testContext1.provider.providerId, createProvider(testContext1)); + providers.set(testContext2.provider.providerId, createProvider(testContext2)); + + let model = new ManagePackagesDialogModel(jupyterServerInstallation, providers, undefined); + + await model.init(); + model.changeProvider('providerId2'); + should.deepEqual(await model.getLocationTitle(), 'location title 2'); + }); + + it('changeProvider should throw exception given invalid provider', async function (): Promise { + let testContext1 = createContext(); + testContext1.provider.providerId = 'providerId1'; + testContext1.provider.packageTarget = { + location: 'location1', + packageType: 'package-type1' + }; + + let testContext2 = createContext(); + testContext2.provider.providerId = 'providerId2'; + testContext2.provider.packageTarget = { + location: 'location2', + packageType: 'package-type2' + }; + + let providers = new Map(); + providers.set(testContext1.provider.providerId, createProvider(testContext1)); + providers.set(testContext2.provider.providerId, createProvider(testContext2)); + + let model = new ManagePackagesDialogModel(jupyterServerInstallation, providers, undefined); + + await model.init(); + should.throws(() => model.changeProvider('providerId3')); + }); + + + it('currentPackageManageProvider should return undefined if current provider is not set', async function (): Promise { + let testContext1 = createContext(); + testContext1.provider.providerId = 'providerId1'; + testContext1.provider.packageTarget = { + location: 'location1', + packageType: 'package-type1' + }; + + let testContext2 = createContext(); + testContext2.provider.providerId = 'providerId2'; + testContext2.provider.packageTarget = { + location: 'location2', + packageType: 'package-type2' + }; + + let providers = new Map(); + providers.set(testContext1.provider.providerId, createProvider(testContext1)); + providers.set(testContext2.provider.providerId, createProvider(testContext2)); + + let model = new ManagePackagesDialogModel(jupyterServerInstallation, providers, undefined); + + should.equal(model.currentPackageManageProvider, undefined); + should(model.listPackages()).rejected(); + should(model.installPackages(TypeMoq.It.isAny())).rejected(); + should(model.uninstallPackages(TypeMoq.It.isAny())).rejected(); + }); + + it('current provider should install and uninstall packages successfully', async function (): Promise { + let testContext1 = createContext(); + testContext1.provider.providerId = 'providerId1'; + testContext1.provider.packageTarget = { + location: 'location1', + packageType: 'package-type1' + }; + + let testContext2 = createContext(); + testContext2.provider.providerId = 'providerId2'; + testContext2.provider.getLocationTitle = () => Promise.resolve('location title 2'); + testContext2.provider.packageTarget = { + location: 'location2', + packageType: 'package-type2' + }; + let packages = [ + { + name: 'p1', + version: '1.1.1.1' + }, + { + name: 'p2', + version: '1.1.1.2' + } + ]; + testContext2.provider.listPackages = () => { + return Promise.resolve(packages); + }; + + let providers = new Map(); + providers.set(testContext1.provider.providerId, createProvider(testContext1)); + providers.set(testContext2.provider.providerId, createProvider(testContext2)); + + let model = new ManagePackagesDialogModel(jupyterServerInstallation, providers, undefined); + + await model.init(); + model.changeProvider('providerId2'); + should(model.listPackages()).resolvedWith(packages); + should(model.installPackages(packages)).resolved(); + should(model.uninstallPackages(packages)).resolved(); + should(model.getPackageOverview('p1')).resolved(); + should(model.getLocationTitle()).rejectedWith('location title 2'); + }); + + function createContext(): TestContext { + return { + provider: { + providerId: 'providerId', + packageTarget: { + location: 'location', + packageType: 'package-type' + }, + canUseProvider: () => { return Promise.resolve(true); }, + getLocationTitle: () => { return Promise.resolve('location-title'); }, + installPackages:() => { return Promise.resolve(); }, + uninstallPackages: (packages: IPackageDetails[]) => { return Promise.resolve(); }, + listPackages: () => { return Promise.resolve([]); }, + getPackageOverview: (name: string) => { return Promise.resolve(undefined); }, + } + }; + } + + function createProvider(testContext: TestContext): IPackageManageProvider { + let mockProvider = TypeMoq.Mock.ofType(LocalPipPackageManageProvider); + mockProvider.setup(x => x.canUseProvider()).returns(() => testContext.provider.canUseProvider()); + mockProvider.setup(x => x.getLocationTitle()).returns(() => testContext.provider.getLocationTitle()); + mockProvider.setup(x => x.installPackages(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns((packages, useMinVersion) => testContext.provider.installPackages(packages, useMinVersion)); + mockProvider.setup(x => x.uninstallPackages(TypeMoq.It.isAny())).returns((packages) => testContext.provider.uninstallPackages(packages)); + mockProvider.setup(x => x.listPackages()).returns(() => testContext.provider.listPackages()); + mockProvider.setup(x => x.getPackageOverview(TypeMoq.It.isAny())).returns((name) => testContext.provider.getPackageOverview(name)); + mockProvider.setup(x => x.packageTarget).returns(() => testContext.provider.packageTarget); + mockProvider.setup(x => x.providerId).returns(() => testContext.provider.providerId); + return mockProvider.object; + } + +}); diff --git a/extensions/notebook/src/types.d.ts b/extensions/notebook/src/types.d.ts index fdb53009cdfd..368d221a1ba1 100644 --- a/extensions/notebook/src/types.d.ts +++ b/extensions/notebook/src/types.d.ts @@ -3,13 +3,130 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { JupyterController } from './jupyter/jupyterController'; - /** * The API provided by this extension. * * @export */ export interface IExtensionApi { - getJupyterController(): JupyterController; + getJupyterController(): IJupyterController; + registerPackageManager(providerId: string, packageManagerProvider: IPackageManageProvider): void; + getPackageManagers(): Map; +} + +/** + * jupyter controller interface + */ +export interface IJupyterController { + /** + * Server installation instance + */ + jupyterInstallation: IJupyterServerInstallation; +} + +export interface IJupyterServerInstallation { + /** + * Installs packages using pip + * @param packages packages to install + * @param useMinVersion if true, minimal version will be used + */ + installPipPackages(packages: IPackageDetails[], useMinVersion: boolean): Promise; + + /** + * Uninstalls packages using pip + * @param packages packages to uninstall + */ + uninstallPipPackages(packages: IPackageDetails[]): Promise; + + /** + * Installs conda packages + * @param packages packages to install + * @param useMinVersion if true, minimal version will be used + */ + installCondaPackages(packages: IPackageDetails[], useMinVersion: boolean): Promise; + + /** + * Uninstalls packages using conda + * @param packages packages to uninstall + */ + uninstallCondaPackages(packages: IPackageDetails[]): Promise; + + /** + * Returns installed pip packages + */ + getInstalledPipPackages(): Promise; +} + +/** + * Package details interface + */ +export interface IPackageDetails { + name: string; + version: string; +} + +/** + * Package target interface + */ +export interface IPackageTarget { + location: string; + packageType: string; +} + +/** + * Package overview + */ +export interface IPackageOverview { + name: string; + versions: string[]; + summary: string; +} + +/** + * Package manage provider interface + */ +export interface IPackageManageProvider { + /** + * Provider id + */ + providerId: string; + + /** + * package target + */ + packageTarget: IPackageTarget; + + /** + * Returns list of installed packages + */ + listPackages(): Promise; + + /** + * Installs give packages + * @param package Packages to install + * @param useMinVersion if true, minimal version will be used + */ + installPackages(package: IPackageDetails[], useMinVersion: boolean): Promise; + + /** + * Uninstalls given packages + * @param package package to uninstall + */ + uninstallPackages(package: IPackageDetails[]): Promise; + + /** + * Returns true if the provider can be used in current context + */ + canUseProvider(): Promise; + + /** + * Returns location title + */ + getLocationTitle(): Promise; + + /** + * Returns Package Overview + * @param packageName package name + */ + getPackageOverview(packageName: string): Promise; } diff --git a/src/sql/workbench/browser/modelComponents/dropdown.component.ts b/src/sql/workbench/browser/modelComponents/dropdown.component.ts index 7b5219cf9236..693849d5d677 100644 --- a/src/sql/workbench/browser/modelComponents/dropdown.component.ts +++ b/src/sql/workbench/browser/modelComponents/dropdown.component.ts @@ -216,4 +216,12 @@ export default class DropDownComponent extends ComponentBase implements ICompone private setValuesProperties(properties: azdata.DropDownProperties, values: string[] | azdata.CategoryValue[]): void { properties.values = values; } + + public focus(): void { + if (this.editable && !this._isInAccessibilityMode) { + this._editableDropdown.focus(); + } else { + this._selectBox.focus(); + } + } }