From 642920504ab52204439575e0ac23ac107361422f Mon Sep 17 00:00:00 2001 From: Anthony Dresser Date: Fri, 13 Dec 2019 00:50:37 -0800 Subject: [PATCH] Merge from vscode cbeff45f80213db0ddda2183170281ed97ed3b12 (#8670) * Merge from vscode cbeff45f80213db0ddda2183170281ed97ed3b12 * fix null strict checks --- build/monaco/monaco.d.ts.recipe | 1 - extensions/search-result/package.json | 10 + extensions/vscode-colorize-tests/package.json | 29 + .../src/colorizerTestMain.ts | 11 +- .../test/semantic-test/.vscode/settings.json | 13 + .../test/semantic-test/semantic-test.json | 9 + .../connection/common/connectionConfig.ts | 14 +- .../modelComponents/queryTextEditor.ts | 4 +- .../browser/profilerResourceEditor.ts | 4 +- src/vs/base/browser/dom.ts | 6 +- .../ui/codiconLabel/codicon/codicon.css | 6 +- .../ui/codiconLabel/codicon/codicon.ttf | Bin 47404 -> 48224 bytes src/vs/base/browser/ui/menu/check.svg | 3 - src/vs/base/browser/ui/menu/ellipsis.svg | 5 - src/vs/base/browser/ui/menu/menu.css | 36 +- src/vs/base/browser/ui/menu/menu.ts | 8 +- src/vs/base/browser/ui/menu/menubar.ts | 2 +- src/vs/base/browser/ui/menu/submenu.svg | 3 - .../browser/ui/scrollbar/scrollableElement.ts | 2 +- src/vs/base/browser/ui/selectBox/selectBox.ts | 2 + .../browser/ui/selectBox/selectBoxCustom.ts | 18 +- src/vs/base/common/async.ts | 21 +- src/vs/base/common/codicons.ts | 12 +- .../issue/issueReporterMain.ts | 12 +- .../issue/media/issueReporter.css | 11 +- src/vs/code/electron-main/window.ts | 9 + .../editor/browser/controller/mouseHandler.ts | 2 +- .../lineNumbers/flipped-cursor-2x.svg | 1 - .../lineNumbers/flipped-cursor-mac-2x.svg | 2 - .../lineNumbers/flipped-cursor-mac.svg | 1 - .../viewParts/lineNumbers/flipped-cursor.svg | 1 - .../viewParts/lineNumbers/lineNumbers.css | 14 - .../viewParts/viewCursors/viewCursors.css | 4 +- src/vs/editor/common/config/editorOptions.ts | 39 + .../services/editorWorkerServiceImpl.ts | 8 +- .../common/services/modelServiceImpl.ts | 23 +- .../common/services/resourceConfiguration.ts | 42 +- .../services/resourceConfigurationImpl.ts | 86 ++- .../common/standalone/standaloneEnums.ts | 99 --- .../contrib/documentSymbols/outlineTree.ts | 4 +- .../smartSelect/test/smartSelect.test.ts | 3 +- .../standalone/browser/simpleServices.ts | 22 +- .../standalone/browser/standaloneEditor.ts | 5 - .../standalone/browser/standaloneServices.ts | 10 +- .../test/common/services/modelService.test.ts | 3 +- .../resourceConfigurationService.test.ts | 301 ++++++++ src/vs/monaco.d.ts | 142 ---- .../configuration/common/configuration.ts | 134 +++- .../common/configurationModels.ts | 340 +++++---- .../node/configurationService.ts | 35 +- .../test/common/configuration.test.ts | 42 +- .../test/common/configurationModels.test.ts | 696 ++++++++++++++---- .../test/common/testConfigurationService.ts | 30 +- src/vs/platform/issue/node/issue.ts | 2 + .../electron-browser/telemetryService.test.ts | 26 +- .../common/tokenClassificationRegistry.ts | 160 ++-- .../electron-main/windowsMainService.ts | 4 +- src/vs/vscode.proposed.d.ts | 4 +- .../api/browser/extensionHost.contribution.ts | 2 + .../api/browser/mainThreadConfiguration.ts | 24 +- .../api/browser/mainThreadTunnelService.ts | 5 +- .../workbench/api/common/extHost.protocol.ts | 12 +- .../api/common/extHostConfiguration.ts | 49 +- .../api/common/extHostDebugService.ts | 8 +- .../api/common/extHostTunnelService.ts | 40 +- src/vs/workbench/api/node/extHost.services.ts | 3 +- .../api/node/extHostTunnelService.ts | 149 ++++ src/vs/workbench/browser/layout.ts | 2 +- .../parts/editor/breadcrumbsControl.ts | 4 +- .../browser/parts/editor/breadcrumbsModel.ts | 4 +- .../browser/parts/editor/editorStatus.ts | 4 +- .../browser/parts/editor/textDiffEditor.ts | 4 +- .../browser/parts/editor/textEditor.ts | 6 +- .../parts/editor/textResourceEditor.ts | 6 +- .../titlebar/media/chrome-close-dark.svg | 1 - .../parts/titlebar/media/chrome-close.svg | 1 - .../titlebar/media/chrome-maximize-dark.svg | 1 - .../parts/titlebar/media/chrome-maximize.svg | 1 - .../titlebar/media/chrome-minimize-dark.svg | 1 - .../parts/titlebar/media/chrome-minimize.svg | 1 - .../titlebar/media/chrome-restore-dark.svg | 1 - .../parts/titlebar/media/chrome-restore.svg | 1 - .../browser/parts/titlebar/menubarControl.ts | 35 +- src/vs/workbench/common/editor/editorGroup.ts | 16 +- .../common/editor/untitledTextEditorModel.ts | 4 +- .../codeEditor/browser/toggleWordWrap.ts | 8 +- .../browser/breakpointEditorContribution.ts | 7 +- .../browser/callStackEditorContribution.ts | 2 - .../debug/browser/debug.contribution.ts | 2 +- .../contrib/debug/browser/debugService.ts | 179 +---- .../contrib/debug/browser/debugSession.ts | 5 + .../contrib/debug/browser/debugTaskRunner.ts | 169 +++++ .../browser/media/debug.contribution.css | 96 +-- .../contrib/debug/browser/rawDebugSession.ts | 5 +- .../contrib/debug/browser/startView.ts | 1 - .../debug/common/abstractDebugAdapter.ts | 52 +- .../workbench/contrib/debug/common/debug.ts | 2 +- .../debug/test/browser/debugModel.test.ts | 28 +- .../contrib/debug/test/common/mockDebug.ts | 65 ++ .../files/browser/editors/textFileEditor.ts | 4 +- .../files/browser/views/explorerView.ts | 2 +- .../files/electron-browser/textFileEditor.ts | 4 +- .../issue/electron-browser/issueService.ts | 4 +- .../outline/browser/outlineNavigation.ts | 4 +- .../contrib/output/browser/logViewer.ts | 4 +- .../contrib/output/browser/outputPanel.ts | 4 +- .../preferences/browser/preferencesEditor.ts | 4 +- .../preferences/browser/settingsTree.ts | 9 + .../preferences/browser/settingsTreeModels.ts | 13 +- .../contrib/remote/browser/tunnelView.ts | 35 +- .../search/browser/search.contribution.ts | 5 +- .../contrib/search/browser/searchActions.ts | 4 +- .../contrib/search/browser/searchView.ts | 14 +- .../terminal/common/terminalEnvironment.ts | 8 +- .../browser/configurationService.ts | 88 ++- .../common/configurationModels.ts | 173 +---- .../test/common/configurationModels.test.ts | 211 ++---- .../configurationResolverService.test.ts | 42 +- .../extensions/worker/extHost.services.ts | 8 +- .../remote/common/remoteExplorerService.ts | 32 +- .../textfile/browser/textFileService.ts | 4 +- .../electron-browser/nativeTextFileService.ts | 6 +- .../themes/browser/workbenchThemeService.ts | 13 +- .../services/themes/common/colorThemeData.ts | 42 +- .../themes/common/colorThemeSchema.ts | 13 + .../tokenClassificationExtensionPoint.ts | 262 +++++++ .../userDataSync/common/userDataSyncUtil.ts | 4 +- .../parts/editor/breadcrumbModel.test.ts | 3 + .../test/common/editor/editorGroups.test.ts | 30 +- .../api/extHostConfiguration.test.ts | 25 +- .../api/mainThreadDocumentsAndEditors.test.ts | 3 +- .../api/mainThreadEditors.test.ts | 2 +- .../quickopen.perf.integrationTest.ts | 3 +- .../textsearch.perf.integrationTest.ts | 5 +- .../workbench/test/workbenchTestServices.ts | 14 +- src/vs/workbench/workbench.common.main.ts | 4 +- 136 files changed, 2918 insertions(+), 1729 deletions(-) create mode 100644 extensions/vscode-colorize-tests/test/semantic-test/.vscode/settings.json create mode 100644 extensions/vscode-colorize-tests/test/semantic-test/semantic-test.json delete mode 100644 src/vs/base/browser/ui/menu/check.svg delete mode 100644 src/vs/base/browser/ui/menu/ellipsis.svg delete mode 100644 src/vs/base/browser/ui/menu/submenu.svg delete mode 100644 src/vs/editor/browser/viewParts/lineNumbers/flipped-cursor-2x.svg delete mode 100644 src/vs/editor/browser/viewParts/lineNumbers/flipped-cursor-mac-2x.svg delete mode 100644 src/vs/editor/browser/viewParts/lineNumbers/flipped-cursor-mac.svg delete mode 100644 src/vs/editor/browser/viewParts/lineNumbers/flipped-cursor.svg create mode 100644 src/vs/editor/test/common/services/resourceConfigurationService.test.ts create mode 100644 src/vs/workbench/api/node/extHostTunnelService.ts delete mode 100644 src/vs/workbench/browser/parts/titlebar/media/chrome-close-dark.svg delete mode 100644 src/vs/workbench/browser/parts/titlebar/media/chrome-close.svg delete mode 100644 src/vs/workbench/browser/parts/titlebar/media/chrome-maximize-dark.svg delete mode 100644 src/vs/workbench/browser/parts/titlebar/media/chrome-maximize.svg delete mode 100644 src/vs/workbench/browser/parts/titlebar/media/chrome-minimize-dark.svg delete mode 100644 src/vs/workbench/browser/parts/titlebar/media/chrome-minimize.svg delete mode 100644 src/vs/workbench/browser/parts/titlebar/media/chrome-restore-dark.svg delete mode 100644 src/vs/workbench/browser/parts/titlebar/media/chrome-restore.svg create mode 100644 src/vs/workbench/contrib/debug/browser/debugTaskRunner.ts create mode 100644 src/vs/workbench/services/themes/common/tokenClassificationExtensionPoint.ts diff --git a/build/monaco/monaco.d.ts.recipe b/build/monaco/monaco.d.ts.recipe index 57a446cda1..bf1473e951 100644 --- a/build/monaco/monaco.d.ts.recipe +++ b/build/monaco/monaco.d.ts.recipe @@ -62,7 +62,6 @@ export interface ICommandHandler { #includeAll(vs/editor/common/editorCommon;editorOptions.=>): IScrollEvent #includeAll(vs/editor/common/model/textModelEvents): #includeAll(vs/editor/common/controller/cursorEvents): -#include(vs/platform/accessibility/common/accessibility): AccessibilitySupport #includeAll(vs/editor/common/config/editorOptions): #includeAll(vs/editor/browser/editorBrowser;editorCommon.=>;editorOptions.=>): #include(vs/editor/common/config/fontInfo): FontInfo, BareFontInfo diff --git a/extensions/search-result/package.json b/extensions/search-result/package.json index 7442c05b8e..40d59e2bcd 100644 --- a/extensions/search-result/package.json +++ b/extensions/search-result/package.json @@ -46,6 +46,16 @@ } ], "menus": { + "commandPalette": [ + { + "command": "searchResult.rerunSearch", + "when": "false" + }, + { + "command": "searchResult.rerunSearchWithContext", + "when": "false" + } + ], "editor/title": [ { "command": "searchResult.rerunSearch", diff --git a/extensions/vscode-colorize-tests/package.json b/extensions/vscode-colorize-tests/package.json index c9834b0554..152a4c9159 100644 --- a/extensions/vscode-colorize-tests/package.json +++ b/extensions/vscode-colorize-tests/package.json @@ -24,5 +24,34 @@ "mocha-junit-reporter": "^1.17.0", "mocha-multi-reporters": "^1.1.7", "vscode": "1.1.5" + }, + "contributes": { + "tokenTypes": [ + { + "id": "testToken", + "description": "A test token" + } + ], + "tokenModifiers": [ + { + "id": "testModifier", + "description": "A test modifier" + } + ], + "tokenStyleDefaults": [ + { + "selector": "testToken.testModifier", + "light": { + "fontStyle": "bold" + }, + "dark": { + "fontStyle": "bold" + }, + "highContrast": { + "fontStyle": "bold" + } + + } + ] } } diff --git a/extensions/vscode-colorize-tests/src/colorizerTestMain.ts b/extensions/vscode-colorize-tests/src/colorizerTestMain.ts index 79e15544a7..13a27c60ba 100644 --- a/extensions/vscode-colorize-tests/src/colorizerTestMain.ts +++ b/extensions/vscode-colorize-tests/src/colorizerTestMain.ts @@ -8,11 +8,13 @@ import * as jsoncParser from 'jsonc-parser'; export function activate(context: vscode.ExtensionContext): any { - const tokenTypes = ['type', 'struct', 'class', 'interface', 'enum', 'parameterType', 'function', 'variable']; - const tokenModifiers = ['static', 'abstract', 'deprecated', 'declaration', 'documentation', 'member', 'async']; + const tokenTypes = ['type', 'struct', 'class', 'interface', 'enum', 'parameterType', 'function', 'variable', 'testToken']; + const tokenModifiers = ['static', 'abstract', 'deprecated', 'declaration', 'documentation', 'member', 'async', 'testModifier']; const legend = new vscode.SemanticTokensLegend(tokenTypes, tokenModifiers); + const outputChannel = vscode.window.createOutputChannel('Semantic Tokens Test'); + const semanticHighlightProvider: vscode.SemanticTokensProvider = { provideSemanticTokens(document: vscode.TextDocument): vscode.ProviderResult { const builder = new vscode.SemanticTokensBuilder(); @@ -35,8 +37,13 @@ export function activate(context: vscode.ExtensionContext): any { builder.push(startLine, startCharacter, length, tokenType, tokenModifiers); + + const selectedModifiers = legend.tokenModifiers.filter((_val, bit) => tokenModifiers & (1 << bit)).join(' '); + outputChannel.appendLine(`line: ${startLine}, character: ${startCharacter}, length ${length}, ${legend.tokenTypes[tokenType]} (${tokenType}), ${selectedModifiers} ${tokenModifiers.toString(2)}`); } + outputChannel.appendLine('---'); + const visitor: jsoncParser.JSONVisitor = { onObjectProperty: (property: string, _offset: number, _length: number, startLine: number, startCharacter: number) => { addToken(property, startLine, startCharacter, property.length + 2); diff --git a/extensions/vscode-colorize-tests/test/semantic-test/.vscode/settings.json b/extensions/vscode-colorize-tests/test/semantic-test/.vscode/settings.json new file mode 100644 index 0000000000..c91ebc862e --- /dev/null +++ b/extensions/vscode-colorize-tests/test/semantic-test/.vscode/settings.json @@ -0,0 +1,13 @@ +{ + "editor.tokenColorCustomizationsExperimental": { + "class": "#00b0b0", + "interface": "#845faf", + "function": "#ff00ff", + "*.declaration": { + "fontStyle": "underline" + }, + "*.declaration.member": { + "fontStyle": "italic bold", + } + } +} \ No newline at end of file diff --git a/extensions/vscode-colorize-tests/test/semantic-test/semantic-test.json b/extensions/vscode-colorize-tests/test/semantic-test/semantic-test.json new file mode 100644 index 0000000000..b250b5d2bc --- /dev/null +++ b/extensions/vscode-colorize-tests/test/semantic-test/semantic-test.json @@ -0,0 +1,9 @@ +[ + "class", "function.member.declaration", + "parameterType.declaration", "type", "parameterType.declaration", "type", + "variable.declaration", "parameterNames", + "function.member.declaration", + "interface.declaration", + "function.member.declaration", "testToken.testModifier" + +] diff --git a/src/sql/platform/connection/common/connectionConfig.ts b/src/sql/platform/connection/common/connectionConfig.ts index b95f409cb6..e3929f3b04 100644 --- a/src/sql/platform/connection/common/connectionConfig.ts +++ b/src/sql/platform/connection/common/connectionConfig.ts @@ -109,7 +109,7 @@ export class ConnectionConfig { return Promise.resolve(profile.groupId); } else { let groups = this.configurationService.inspect(GROUPS_CONFIG_KEY).user; - let result = this.saveGroup(groups, profile.groupFullName, undefined, undefined); + let result = this.saveGroup(groups!, profile.groupFullName, undefined, undefined); groups = result.groups; return this.configurationService.updateValue(GROUPS_CONFIG_KEY, groups, ConfigurationTarget.USER).then(() => result.newGroupId!); @@ -129,7 +129,7 @@ export class ConnectionConfig { let errMessage: string = nls.localize('invalidServerName', "A server group with the same name already exists."); return Promise.reject(errMessage); } else { - let result = this.saveGroup(groups, profileGroup.name, profileGroup.color, profileGroup.description); + let result = this.saveGroup(groups!, profileGroup.name, profileGroup.color, profileGroup.description); groups = result.groups; return this.configurationService.updateValue(GROUPS_CONFIG_KEY, groups, ConfigurationTarget.USER).then(() => result.newGroupId!); @@ -220,7 +220,7 @@ export class ConnectionConfig { // Get all connections in the settings let profiles = this.configurationService.inspect(CONNECTIONS_CONFIG_KEY).user; // Remove the profile from the connections - profiles = profiles.filter(value => { + profiles = profiles!.filter(value => { let providerConnectionProfile = ConnectionProfile.createFromStoredProfile(value, this._capabilitiesService); return providerConnectionProfile.getOptionsKey() !== profile.getOptionsKey(); }); @@ -241,7 +241,7 @@ export class ConnectionConfig { // Get all connections in the settings let profiles = this.configurationService.inspect(CONNECTIONS_CONFIG_KEY).user; // Remove the profiles from the connections - profiles = profiles.filter(value => { + profiles = profiles!.filter(value => { let providerConnectionProfile = ConnectionProfile.createFromStoredProfile(value, this._capabilitiesService); return !connections.some((val) => val.getOptionsKey() === providerConnectionProfile.getOptionsKey()); }); @@ -249,7 +249,7 @@ export class ConnectionConfig { // Get all groups in the settings let groups = this.configurationService.inspect(GROUPS_CONFIG_KEY).user; // Remove subgroups in the settings - groups = groups.filter((grp) => { + groups = groups!.filter((grp) => { return !subgroups.some((item) => item.id === grp.id); }); return Promise.all([ @@ -263,7 +263,7 @@ export class ConnectionConfig { */ public changeGroupIdForConnectionGroup(source: ConnectionProfileGroup, target: ConnectionProfileGroup): Promise { let groups = this.configurationService.inspect(GROUPS_CONFIG_KEY).user; - groups = groups.map(g => { + groups = groups!.map(g => { if (g.id === source.id) { g.parentId = target.id; } @@ -334,7 +334,7 @@ export class ConnectionConfig { let errMessage: string = nls.localize('invalidServerName', "A server group with the same name already exists."); return Promise.reject(errMessage); } - groups = groups.map(g => { + groups = groups!.map(g => { if (g.id === source.id) { g.name = source.name; g.description = source.description; diff --git a/src/sql/workbench/browser/modelComponents/queryTextEditor.ts b/src/sql/workbench/browser/modelComponents/queryTextEditor.ts index e06c6da4af..8dd505816e 100644 --- a/src/sql/workbench/browser/modelComponents/queryTextEditor.ts +++ b/src/sql/workbench/browser/modelComponents/queryTextEditor.ts @@ -14,7 +14,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti 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 { IResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; import { EditorOptions } from 'vs/workbench/common/editor'; import { StandaloneCodeEditor } from 'vs/editor/standalone/browser/standaloneCodeEditor'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -43,7 +43,7 @@ export class QueryTextEditor extends BaseTextEditor { @ITelemetryService telemetryService: ITelemetryService, @IInstantiationService instantiationService: IInstantiationService, @IStorageService storageService: IStorageService, - @ITextResourceConfigurationService configurationService: ITextResourceConfigurationService, + @IResourceConfigurationService configurationService: IResourceConfigurationService, @IThemeService themeService: IThemeService, @IEditorGroupsService editorGroupService: IEditorGroupsService, @IEditorService protected editorService: IEditorService, diff --git a/src/sql/workbench/contrib/profiler/browser/profilerResourceEditor.ts b/src/sql/workbench/contrib/profiler/browser/profilerResourceEditor.ts index 694fab568e..790ad81421 100644 --- a/src/sql/workbench/contrib/profiler/browser/profilerResourceEditor.ts +++ b/src/sql/workbench/contrib/profiler/browser/profilerResourceEditor.ts @@ -14,7 +14,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti 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 { IResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; import { EditorOptions } from 'vs/workbench/common/editor'; import { StandaloneCodeEditor } from 'vs/editor/standalone/browser/standaloneCodeEditor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -43,7 +43,7 @@ export class ProfilerResourceEditor extends BaseTextEditor { @ITelemetryService telemetryService: ITelemetryService, @IInstantiationService instantiationService: IInstantiationService, @IStorageService storageService: IStorageService, - @ITextResourceConfigurationService configurationService: ITextResourceConfigurationService, + @IResourceConfigurationService configurationService: IResourceConfigurationService, @IThemeService themeService: IThemeService, @IEditorService protected editorService: IEditorService, @IEditorGroupsService editorGroupService: IEditorGroupsService diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index b6006139eb..f4220b4766 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -232,9 +232,9 @@ class DomListener implements IDisposable { export function addDisposableListener(node: EventTarget, type: K, handler: (event: GlobalEventHandlersEventMap[K]) => void, useCapture?: boolean): IDisposable; export function addDisposableListener(node: EventTarget, type: string, handler: (event: any) => void, useCapture?: boolean): IDisposable; -export function addDisposableListener(node: EventTarget, type: string, handler: (event: any) => void, useCapture: AddEventListenerOptions): IDisposable; -export function addDisposableListener(node: EventTarget, type: string, handler: (event: any) => void, useCapture?: boolean | AddEventListenerOptions): IDisposable { - return new DomListener(node, type, handler, useCapture); +export function addDisposableListener(node: EventTarget, type: string, handler: (event: any) => void, options: AddEventListenerOptions): IDisposable; +export function addDisposableListener(node: EventTarget, type: string, handler: (event: any) => void, useCaptureOrOptions?: boolean | AddEventListenerOptions): IDisposable { + return new DomListener(node, type, handler, useCaptureOrOptions); } export interface IAddStandardDisposableListenerSignature { diff --git a/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css b/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css index c3efcaad23..5972f7ec7f 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?c4e66586cd3ad4acc55fc456c0760dec") format("truetype"); + src: url("./codicon.ttf?7ed98366b9684aff02478216decd2fe9") format("truetype"); } .codicon[class*='codicon-'] { @@ -396,6 +396,7 @@ .codicon-debug-breakpoint-function:before { content: "\eb88" } .codicon-debug-breakpoint-function-disabled:before { content: "\eb88" } .codicon-debug-stackframe-active:before { content: "\eb89" } +.codicon-debug-stackframe-dot:before { content: "\eb8a" } .codicon-debug-stackframe:before { content: "\eb8b" } .codicon-debug-stackframe-focused:before { content: "\eb8b" } .codicon-debug-breakpoint-unsupported:before { content: "\eb8c" } @@ -404,4 +405,7 @@ .codicon-debug-step-back:before { content: "\eb8f" } .codicon-debug-restart-frame:before { content: "\eb90" } .codicon-debug-alternate:before { content: "\eb91" } +.codicon-call-incoming:before { content: "\eb92" } +.codicon-call-outgoing:before { content: "\eb93" } +.codicon-menu:before { content: "\eb94" } .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 a51c284681e086082aecd8d267d334a25b9bce8f..0d601781f23b55003c424f86adac2a83dfcfd584 100644 GIT binary patch delta 5903 zcmZYD3w%`7od@vWxp(H?nat#!%!Ck8`eFN$A#@-1LsA+U8` z`@+@*nMZPm^0{ZJzMVU8PVM63!&Kb9yld^f2d`e``@IYJ4t1<-Yt?VR>Hs-M0Bi2@ z*0r71HQ3Mp=kWQ3D_WN?9K0`g!aczLZayKYbLFb83r|KK0uJyCr5=CqaIC2kiqECZ z%c>r`Id$x(2%(dEa4Kg->EGj3-f#>5fB-CSFK>K}s_e~Iw+GWOgWIV{>)WmN*u7t< zaNiXFO)7D=|Gi|ehxjfowxC*-;&(96ScGEy z5Oc8w-$A)b#`Q>6C0NO~`7a#8Q|MC>d;@FIgIZj{d= z4!0mN8e=dP<1ii*kbz8OAsZ14KrRL%4}*}80u*8}iV#ByN)g8p3`H4+p#qht!f=d0 z4eC&j28_f+Ou}SL#dHqhKVc@mj23(aH((aNirM%YZp7Cy2RGqnw4#j%y)c2rXvY#P z#WHkYIacs|-GW=O3SC%@HMkA8;||=(=d8nBxEtNL2lwJWY{324h)s9^oAFI-#Wp;M z?dZi0d<#4A&v*zA<6p50|A9yGpZGTR;4$pQKF+5Dcmhx2AijsE@eC5r;yHXD&vR1# z059St9Knz9W4w%?;1#@z*YO5^ilg`$j^XF{1&-sFcnc?R62HPJ{2Hh6HqPKToZ`R5 zIlO~+@g9DU^Y{ba#|QW$F5n}4j8E_`!*!T~D01*PMsemkeXT)D zB@p&zRmvpqo_njjxhK4Nu3H6X7xa9=c?R7kIOCuf2+lp|g#xcJE)txN(2E6UCUm>t z9EDCS;T>_hLN67#$hb_Pnz2K0E<-ODoYl}P1m`#OO2L^9&E@OioQLL;^>Fq>-zvBc zpjU}TE(z!^!Nmc+T5y>_uMsF`yiIWFK(7_JzIRtBnkeDcoq`JrdY$0Xg1$>|kwM=r zxZHT$f(sA2TcD5e9>K*3-6Oaxq3;!3pwR0Dmn!sqK*GD@s)gPlxPGDU7w}HnM!_`= zy-9F&Lq8z60HB!IL>wD)GjbOHS>fzKIt z3Z{^A;g`mZ=GJ3^$pm_@U|NCRCzxQM_X~_?d|Y5c@0sMOi2>a5Mvs_ypuN!}rXc92 z1(OlEQ%s#)f`V zcm+_uC78vbPYC97XfI(9GduK2!5k0$E5U3JeM&IzL;qT^0zjV@tOd|-3swa#pfkK9 z76|Cy2$l-yvw}qf`nQ7R1Nxj`A%T8Ju%tl0D_C5hewr} z|6_jGDsFrtSpA?s6|95Mmjo*!^q&N4BlKm#Dhd5(!Fmb(nPBCF{#^7Q*cCn|R#WJ| z2;9Z+ob_g&Tg1rI~ zCX*Y)wgDzfuz`Td7HlP8B7#i?OjNMFfXNYTG++h@wj40Ig3SlaK*2TyCQq;-ff>|~ z69}EZ7giOcd-4VI~Roh%l1{yG58Of_)>* zRKX510W*yo#AXua8o{;_=30TXj9(ILHDRU;Hk~ln3AUdwGXxt^n3;ktDa@Azn^Tw; z!8R4A-HwkvdT>o=AGP+5%2h011JAykxcrSL9WO{Y);7;h0Om+@Ai zyu7f=JO8WLapiHNOQ=DNtA)yETq9Hg<849}GOiVBFyrk)6*1l+RE+UXp^6#T2^D9& zOQ`XQs4Q1qf@F;KncMIjM|9gZQ#@Hixi2`%4;Kd5edcn&UnEM1TU|==~>}I@Q z@S+B0BPDpp>l>JF2wv&HY!bZYfq6ji>IY`CKr`bv1+R!;wg_Gu!E6=0N`l!Yc)bL( zUGT~YrdRM93K6q|8^p^ge8frR!KmK#IU^Fg{MG)={$2j}{Fj|1C(o&Lra3n{i=6e& zLFc5K;;wb~x|agO1J?$22F?e=!I8l^!JWZFNo7e>k~)*tC7llq32h9$7&;gFESwb1 z4VQ*Tho^-*!ad=O$-d<2$?KB0CqJKjELkUCNg0;XlCm~sZ_4wj!&B#`u1`IYdNGj} zNvltrn|4Rq!)Zs;E~n?Dk5BJN-;sVkqdjAHhR)2-T$_0?%a_%jbujCb?Aq*yvfs$Q z9Lb7wM;?hBio6*uiq49zk6y}|o3lUX?10$=j^|F#-83*`;O0DE-lV))c{}qC56T(T zGN^mdk^H>;>G|99PZSgv%qiGVaI|oK;r7DBrNMIsKQZ`xQBu*;qMo8%u|TXa)*S1K z9WS0+{8aI|l0eCjl4&JtN{*JCD-D-UEA1{lQhG679B+yD#9xbF7&3dv?x9^n_mug{ zW|eIz+f{b7JgL07{KoR0@<+?B3@aPRHtXYQi<`HQQ>QuKBRGvbLpmL+z2eNZpjWgY~8LE9y@)q%=%v z*xm5n$eANMMs6H6V^sI3oudwqIz2jNbj#>7V=!j)n6+ab9`nkW^J8X;G@>6jj-^O;&wfZI!B2MX4&q%F1_YsvA|4s;`UJR#~O46Dul>m&PmY zy4rf~tuC;$_`?YH<}KF#qf6|gc2qcOLz zivQv>a%_uFvn|VK2Q8b=^gBt(;czI(x3O&>Z}&l9sD(D0E}dLjHvIw~<=P-{VGmEq9ahjw@E@Mym^~NOqJ*n_|U^DxKQ? zVXF2kyaRIDi85UFmoYUA7$uWyJqL~H6eX4U!D zAXU>4bt4RM?kX*EvfXGyUEGb)a+c+eP1@ zw$~@C%D$d`vnS}FUw&+&=l3dZ+*0p5K!mR(df5FPGwEwfSFMRNr?e{HTxMFqR KslFD!1^gA)5y7

D1Uh=>vKUPOVQ5fS=+oX3A1p5L7LhMB<3ckemxms8!gZ5M6r z$*!*e)dpZ(?X2qAGg8i81iagUpo>#xE^HXr(=!@~Isvq`Os%i33u%o##@C*rqN#iW zr`6+p{3@kPoz>j3V)FIt{CS;#=eC(mwbegP3f=%jt^{o3XH~b%whhBp{(U50pU_x6 zt3Gi{RONEu>9Kr)eRk8l<_n!R18kWL1Ri|!)#UOlD880{j+{PeQef$2_+vJo!Lh4N z*M6_pdBYt30eoSzp5=}2QCS^vYN1~mM)NojY3@yGvxmFN>u%*beBMha4qhF(ZVCbE!?9ONPoy-|oh=!+r@!B7mtaE!o6jN+H~CyYTA zZo^pIj&ZmH<8db@;4a*aNvP&Wu63av4VZ$dn1<<;U#6Iy{E;*nq#{3H%+K@DDtNf8pQQ zjHmGow%}Q8!+-D`Uce5x+}Md-co8pSH(ud#d=0N-FW$gDyotB4AMfB@yoUq$9}ePu ze1H#e7$4&k9L1;j499RBpW_6+z)5_`#r+k&Mkl_(8GHwgbNC+T@gpvv3zzW|e#RC2 zimSM$plr%hc`L_w6`-6dQ1wtjDp-Z8Fcq#MRHW*uqExhsQL!pc#j6CBs8Up_N>ja5 zy2?oT8(X*!pt*G|+!4@o1@{K@JOS54K4=!)FwpY_ zw+{3IfehAr1-BD)i{QqBUMLX2x=3)dK`$2EcF;=%Hz2h2EfKdO^isfO-Emh!Ti*n6 ze?l)4*v`6KaL+=o5Zt}c_Y3Z0XtqHMtoN-F+}qHr1$Q~L^&Y}x)>grt?}A>#17Z_^ zen@biKwIAiu`xie6}ZIOE)@HqepD!5R_i^)1_Iq7*h-++2{skz#{{}r*IPXK2Ct5* zj@Jhk^YCfGo&)`iVE2LEBG`wZw+eP7=w}7WJGujIaz*fHhhTq#b_;eY=$(Q+3)=d& ziQNl&mtY@*eo?TaLBAx}+n`?->~he%1^XTJD}tR5nuE$^-LVNmzb4oYq4x+jM(EcC zTO{;e!Db2lhG5%--Y3{Vq2Cm2rO%7wpZ@)`BE^+eN1pDKpz(z573_r4hrZKf+GX^3&G*R&2y4>#5n@}rQk$? zJ|#F?puZBFGSH_5=MD7Nf|CcjQ*Z`BeH&i^HT`Z+x45*(7y zmj%Zq^iP6=6Z&Vt5enTcI832`5ge<~R|E$v^snN^2fND0#GwoQo4{h8e|=4G5<_1X zoXJ+Jzk&Zp7$rETVQhjE8^%L$cEflIPH`A7!FdkjEjZa>d<17aj4!!($8itiCph?F z?1B*h#$PZDz&HeB0Zf2kK!9-yMg^EaVKqb3LohzT1PKNSm|($30hbBk0Wn>`gbL;i zm@vV_0TV8mJzydPQwU6?z$Vt7g2@CXN-(3qL<^=Bm>9v_0uy@!zuV1x5GNRBVB!U1 z4NQVyz=25=j5;t$f}sZ{Sup;T30s+?>d{8Ku&tUopCN-G8 zf|(7bNHD#@^b^c+FvWt24yM0gwu31VOnEQ^1oIwDsbKPh8EBn<-Z2EilnKT`m~z2j z2vZ>#5n(C?!y?Qe!Pp2hSTI1s3=xcyFhd1HCCo6vcnNcpV9<2I4CetcbHdy#m_A`f z2I|V~A%ml%B40D%YP==W(7@1-277Wka|M&2Yn4@7P2_|Zo z$%5G$rdlv%!_){|W33g6Z#Q*9*;wm^@?>og%A0kHP!86q!leTEV46@)*6Bi7jbw&U zJy>T76~sDAs9@Gcp+Z@kgbHJwEmSz`9HAmu=L+kp*UYod|2(Fto;+w4DvEW!P|>Ul zgoPUt`WQhf_X^r zVhE;9z}jvP3tk|>JR*3h1hZD~q6wy5@bU>4xJWG4h188(2_sDl)^{9?Om)?HF7Vvr z`Jv}&&n_=-uLQ3GuQIQRUael+z0P=#@m}VA$j8?w$*0k0uTQ6M58pn%jlS)EHorK( z;eKQN_S#|3v`@CLv_E0rW`EWGuKk2v`v?1{`494M@L%G8&i|TYm}7yX&GDq;kfSRg zDquvw#DK$2IMbXh&UMb0oS(S@5f~F#8rT@PF>rU_=^oA=MLpI8*@FfJwFT`BjtZ^{ z-W*~NsR~&iax}CgbY|$r&|P6kVP#w6SSR z(mH!(rz5=}y*>SOMpQ;-#`ui&8HX~?WI8i*Gg~uvWev)DD%+Obki8*$PmVJuGp8u0 zGj~XCUG9;*sJzy^v-t)2%ksCm@=y0J?!BRRS3yicMZuzi-32ELg9}F%K2>ASJ-;iBxKxkV?6y8DIpJ6`N4PAVQ=JhgaZ@yY(v`?vMqU*cJkR#H{cT(WsU z`hW=oRu4EZpsRFx>E?m^Q42>DOcbIKhsXHM$%-6jt z^_+hdI|^^pm@GZu!(T)N@%Jp;)cunC7KSB<751V$)wf?nSg){t5qz?yynNIsXJ+PT zZ|}r}$PvAJ-{mvbC)vl_mhTbi9hwy6>Eq5TXjL7z6m50CP`J+KzFc%5&utp^4OLn0 z=eCWrb=aT(+U>V}j%`v>U46~`DLFNB>#JwXZkpEEoHMVvx^_mxT>cU*r@FRz+JgG{ dKYB@BQ*-nm{C&rw9V^@=?)N?1ju)%E{tpt6fT#cf diff --git a/src/vs/base/browser/ui/menu/check.svg b/src/vs/base/browser/ui/menu/check.svg deleted file mode 100644 index cea818ef59..0000000000 --- a/src/vs/base/browser/ui/menu/check.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/base/browser/ui/menu/ellipsis.svg b/src/vs/base/browser/ui/menu/ellipsis.svg deleted file mode 100644 index 2c52e359f6..0000000000 --- a/src/vs/base/browser/ui/menu/ellipsis.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/vs/base/browser/ui/menu/menu.css b/src/vs/base/browser/ui/menu/menu.css index 8547e8b39a..867545aafe 100644 --- a/src/vs/base/browser/ui/menu/menu.css +++ b/src/vs/base/browser/ui/menu/menu.css @@ -51,8 +51,17 @@ .monaco-menu .monaco-action-bar.vertical .submenu-indicator { height: 100%; - 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 .submenu-indicator.codicon { + font-size: 16px; + display: flex; + align-items: center; +} + +.monaco-menu .monaco-action-bar.vertical .submenu-indicator.codicon::before { + margin-left: auto; + margin-right: -20px; } .monaco-menu .monaco-action-bar.vertical .action-item.disabled .keybinding, @@ -94,14 +103,15 @@ .monaco-menu .monaco-action-bar.vertical .menu-item-check { position: absolute; visibility: hidden; - 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%; } .monaco-menu .monaco-action-bar.vertical .action-menu-item.checked .menu-item-check { visibility: visible; + display: flex; + align-items: center; + justify-content: center; } /* Context Menu */ @@ -186,9 +196,6 @@ } .menubar .toolbar-toggle-more { - background-position: center; - background-repeat: no-repeat; - background-size: 14px; width: 20px; height: 100%; } @@ -197,21 +204,18 @@ position: absolute; left: 0px; top: 0px; - background-position: center; - background-repeat: no-repeat; - background-size: 16px; cursor: pointer; width: 100%; + display: flex; + align-items: center; + justify-content: center; } .menubar .toolbar-toggle-more { - display: inline-block; padding: 0; - mask: url('ellipsis.svg') no-repeat 50% 55%/14px 14px; - -webkit-mask: url('ellipsis.svg') no-repeat 50% 55%/14px 14px; + vertical-align: sub; } -.menubar.compact .toolbar-toggle-more { - mask: url('menu.svg') no-repeat 50% 55%/16px 16px; - -webkit-mask: url('menu.svg') no-repeat 50% 55%/16px 16px; +.menubar.compact .toolbar-toggle-more::before { + content: "\eb94" !important; } diff --git a/src/vs/base/browser/ui/menu/menu.ts b/src/vs/base/browser/ui/menu/menu.ts index 4838359bcf..136045c92f 100644 --- a/src/vs/base/browser/ui/menu/menu.ts +++ b/src/vs/base/browser/ui/menu/menu.ts @@ -422,7 +422,7 @@ class BaseMenuActionViewItem extends BaseActionViewItem { } } - this.check = append(this.item, $('span.menu-item-check')); + this.check = append(this.item, $('span.menu-item-check.codicon.codicon-check')); this.check.setAttribute('role', 'none'); this.label = append(this.item, $('span.action-label')); @@ -602,7 +602,7 @@ class BaseMenuActionViewItem extends BaseActionViewItem { } if (this.check) { - this.check.style.backgroundColor = fgColor ? `${fgColor}` : ''; + this.check.style.color = fgColor ? `${fgColor}` : ''; } if (this.container) { @@ -662,7 +662,7 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem { 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 = append(this.item, $('span.submenu-indicator.codicon.codicon-chevron-right')); this.submenuIndicator.setAttribute('aria-hidden', 'true'); } @@ -822,7 +822,7 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem { const fgColor = isSelected && this.menuStyle.selectionForegroundColor ? this.menuStyle.selectionForegroundColor : this.menuStyle.foregroundColor; if (this.submenuIndicator) { - this.submenuIndicator.style.backgroundColor = fgColor ? `${fgColor}` : ''; + this.submenuIndicator.style.color = fgColor ? `${fgColor}` : ''; } if (this.parentData.submenu) { diff --git a/src/vs/base/browser/ui/menu/menubar.ts b/src/vs/base/browser/ui/menu/menubar.ts index 73e591815b..1c0a11c6fd 100644 --- a/src/vs/base/browser/ui/menu/menubar.ts +++ b/src/vs/base/browser/ui/menu/menubar.ts @@ -310,7 +310,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, 'title': label, 'aria-haspopup': true }); - const titleElement = $('div.menubar-menu-title.toolbar-toggle-more', { 'role': 'none', 'aria-hidden': true }); + const titleElement = $('div.menubar-menu-title.toolbar-toggle-more.codicon.codicon-more', { 'role': 'none', 'aria-hidden': true }); buttonElement.appendChild(titleElement); this.container.appendChild(buttonElement); diff --git a/src/vs/base/browser/ui/menu/submenu.svg b/src/vs/base/browser/ui/menu/submenu.svg deleted file mode 100644 index 98a0aa5924..0000000000 --- a/src/vs/base/browser/ui/menu/submenu.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/base/browser/ui/scrollbar/scrollableElement.ts b/src/vs/base/browser/ui/scrollbar/scrollableElement.ts index c59bc26f03..5fa413dc17 100644 --- a/src/vs/base/browser/ui/scrollbar/scrollableElement.ts +++ b/src/vs/base/browser/ui/scrollbar/scrollableElement.ts @@ -317,7 +317,7 @@ export abstract class AbstractScrollableElement extends Widget { this._onMouseWheel(new StandardWheelEvent(browserEvent)); }; - this._mouseWheelToDispose.push(dom.addDisposableListener(this._listenOnDomNode, isEdgeOrIE ? 'mousewheel' : 'wheel', onMouseWheel)); + this._mouseWheelToDispose.push(dom.addDisposableListener(this._listenOnDomNode, isEdgeOrIE ? 'mousewheel' : 'wheel', onMouseWheel, { passive: false })); } } diff --git a/src/vs/base/browser/ui/selectBox/selectBox.ts b/src/vs/base/browser/ui/selectBox/selectBox.ts index 0f43a9b017..9ff40c339f 100644 --- a/src/vs/base/browser/ui/selectBox/selectBox.ts +++ b/src/vs/base/browser/ui/selectBox/selectBox.ts @@ -9,6 +9,7 @@ import { Event } from 'vs/base/common/event'; import { Widget } from 'vs/base/browser/ui/widget'; import { Color } from 'vs/base/common/color'; import { deepClone } from 'vs/base/common/objects'; +import { IContentActionHandler } from 'vs/base/browser/formattedTextRenderer'; import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; import { IListStyles } from 'vs/base/browser/ui/list/listWidget'; import { SelectBoxNative } from 'vs/base/browser/ui/selectBox/selectBoxNative'; @@ -47,6 +48,7 @@ export interface ISelectOptionItem { decoratorRight?: string; description?: string; descriptionIsMarkdown?: boolean; + descriptionMarkdownActionHandler?: IContentActionHandler; isDisabled?: boolean; } diff --git a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts index 3e834d744a..97843d2a36 100644 --- a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts +++ b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts @@ -19,6 +19,7 @@ import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import { ISelectBoxDelegate, ISelectOptionItem, ISelectBoxOptions, ISelectBoxStyles, ISelectData } from 'vs/base/browser/ui/selectBox/selectBox'; import { isMacintosh } from 'vs/base/common/platform'; import { renderMarkdown } from 'vs/base/browser/markdownRenderer'; +import { IContentActionHandler } from 'vs/base/browser/formattedTextRenderer'; const $ = dom.$; @@ -760,11 +761,16 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi .filter(() => this.selectList.length > 0) .on(e => this.onMouseUp(e), this)); - - this._register(this.selectList.onDidBlur(_ => this.onListBlur())); this._register(this.selectList.onMouseOver(e => typeof e.index !== 'undefined' && this.selectList.setFocus([e.index]))); this._register(this.selectList.onFocusChange(e => this.onListFocus(e))); + this._register(dom.addDisposableListener(this.selectDropDownContainer, dom.EventType.FOCUS_OUT, e => { + if (!this._isVisible || dom.isAncestor(e.relatedTarget as HTMLElement, this.selectDropDownContainer)) { + return; + } + this.onListBlur(); + })); + this.selectList.getHTMLElement().setAttribute('aria-label', this.selectBoxOptions.ariaLabel || ''); this.selectList.getHTMLElement().setAttribute('aria-expanded', 'true'); @@ -836,7 +842,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi } - private renderDescriptionMarkdown(text: string): HTMLElement { + private renderDescriptionMarkdown(text: string, actionHandler?: IContentActionHandler): HTMLElement { const cleanRenderedMarkdown = (element: Node) => { for (let i = 0; i < element.childNodes.length; i++) { const child = element.childNodes.item(i); @@ -850,7 +856,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi } }; - const renderedMarkdown = renderMarkdown({ value: text }); + const renderedMarkdown = renderMarkdown({ value: text }, { actionHandler }); renderedMarkdown.classList.add('select-box-description-markdown'); cleanRenderedMarkdown(renderedMarkdown); @@ -872,7 +878,8 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi if (description) { if (descriptionIsMarkdown) { - this.selectionDetailsPane.appendChild(this.renderDescriptionMarkdown(description)); + const actionHandler = this.options[selectedIndex].descriptionMarkdownActionHandler; + this.selectionDetailsPane.appendChild(this.renderDescriptionMarkdown(description, actionHandler)); } else { this.selectionDetailsPane.innerText = description; } @@ -885,7 +892,6 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi this._skipLayout = true; this.contextViewProvider.layout(); this._skipLayout = false; - } // List keyboard controller diff --git a/src/vs/base/common/async.ts b/src/vs/base/common/async.ts index 0d0c483568..3c528032ac 100644 --- a/src/vs/base/common/async.ts +++ b/src/vs/base/common/async.ts @@ -184,13 +184,14 @@ export class Delayer implements IDisposable { private timeout: any; private completionPromise: Promise | null; private doResolve: ((value?: any | Promise) => void) | null; - private doReject?: (err: any) => void; + private doReject: ((err: any) => void) | null; private task: ITask> | null; constructor(public defaultDelay: number) { this.timeout = null; this.completionPromise = null; this.doResolve = null; + this.doReject = null; this.task = null; } @@ -205,16 +206,20 @@ export class Delayer implements IDisposable { }).then(() => { this.completionPromise = null; this.doResolve = null; - const task = this.task!; - this.task = null; - - return task(); + if (this.task) { + const task = this.task; + this.task = null; + return task(); + } + return undefined; }); } this.timeout = setTimeout(() => { this.timeout = null; - this.doResolve!(null); + if (this.doResolve) { + this.doResolve(null); + } }, delay); return this.completionPromise; @@ -228,7 +233,9 @@ export class Delayer implements IDisposable { this.cancelTimeout(); if (this.completionPromise) { - this.doReject!(errors.canceled()); + if (this.doReject) { + this.doReject(errors.canceled()); + } this.completionPromise = null; } } diff --git a/src/vs/base/common/codicons.ts b/src/vs/base/common/codicons.ts index fe44e44800..f5b4437d41 100644 --- a/src/vs/base/common/codicons.ts +++ b/src/vs/base/common/codicons.ts @@ -3,9 +3,9 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -const escapeCodiconsRegex = /(? `\\${match}`); + return text.replace(escapeCodiconsRegex, (match, escaped) => escaped ? match : `\\${match}`); } const markdownEscapedCodiconsRegex = /\\\$\([a-z0-9\-]+?(?:~[a-z0-9\-]*?)?\)/gi; @@ -14,15 +14,15 @@ export function markdownEscapeEscapedCodicons(text: string): string { return text.replace(markdownEscapedCodiconsRegex, match => `\\${match}`); } -const markdownUnescapeCodiconsRegex = /(? `$(${codicon})`); + return text.replace(markdownUnescapeCodiconsRegex, (match, escaped, codicon) => escaped ? match : `$(${codicon})`); } const renderCodiconsRegex = /(\\)?\$\((([a-z0-9\-]+?)(?:~([a-z0-9\-]*?))?)\)/gi; export function renderCodicons(text: string): string { - return text.replace(renderCodiconsRegex, (_, escape, codicon, name, animation) => { - return escape + return text.replace(renderCodiconsRegex, (_, escaped, codicon, name, animation) => { + return escaped ? `$(${codicon})` : ``; }); diff --git a/src/vs/code/electron-browser/issue/issueReporterMain.ts b/src/vs/code/electron-browser/issue/issueReporterMain.ts index 08f74ee4f3..f299061f89 100644 --- a/src/vs/code/electron-browser/issue/issueReporterMain.ts +++ b/src/vs/code/electron-browser/issue/issueReporterMain.ts @@ -185,8 +185,16 @@ export class IssueReporter extends Disposable { } if (styles.inputErrorBorder) { - content.push(`.invalid-input, .invalid-input:focus { border: 1px solid ${styles.inputErrorBorder} !important; }`); - content.push(`.validation-error, .required-input { color: ${styles.inputErrorBorder}; }`); + content.push(`.invalid-input, .invalid-input:focus, .validation-error { border: 1px solid ${styles.inputErrorBorder} !important; }`); + content.push(`.required-input { color: ${styles.inputErrorBorder}; }`); + } + + if (styles.inputErrorBackground) { + content.push(`.validation-error { background: ${styles.inputErrorBackground}; }`); + } + + if (styles.inputErrorForeground) { + content.push(`.validation-error { color: ${styles.inputErrorForeground}; }`); } if (styles.inputActiveBorder) { diff --git a/src/vs/code/electron-browser/issue/media/issueReporter.css b/src/vs/code/electron-browser/issue/media/issueReporter.css index e0367ee29b..3276b242d9 100644 --- a/src/vs/code/electron-browser/issue/media/issueReporter.css +++ b/src/vs/code/electron-browser/issue/media/issueReporter.css @@ -251,16 +251,9 @@ a { text-decoration: none; } -.invalid-input { - border: 1px solid #be1100; -} - -.required-input, .validation-error { - color: #be1100; -} - .section .input-group .validation-error { - margin-left: 15%; + margin-left: calc(15% + 5px); + padding: 10px; } .section .inline-form-control, .section .inline-label { diff --git a/src/vs/code/electron-main/window.ts b/src/vs/code/electron-main/window.ts index 585d44ec69..9383753557 100644 --- a/src/vs/code/electron-main/window.ts +++ b/src/vs/code/electron-main/window.ts @@ -114,6 +114,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { // Load window state const [state, hasMultipleDisplays] = this.restoreWindowState(config.state); this.windowState = state; + this.logService.trace('window#ctor: using window state', state); // in case we are maximized or fullscreen, only show later after the call to maximize/fullscreen (see below) const isFullscreenOrMaximized = (this.windowState.mode === WindowMode.Maximized || this.windowState.mode === WindowMode.Fullscreen); @@ -782,10 +783,12 @@ export class CodeWindow extends Disposable implements ICodeWindow { || typeof state.width !== 'number' || typeof state.height !== 'number' ) { + this.logService.trace('window#validateWindowState: unexpected type of state values'); return undefined; } if (state.width <= 0 || state.height <= 0) { + this.logService.trace('window#validateWindowState: unexpected negative values'); return undefined; } @@ -793,6 +796,8 @@ export class CodeWindow extends Disposable implements ICodeWindow { if (displays.length === 1) { const displayWorkingArea = this.getWorkingArea(displays[0]); if (displayWorkingArea) { + this.logService.trace('window#validateWindowState: 1 display', displayWorkingArea); + if (state.x < displayWorkingArea.x) { state.x = displayWorkingArea.x; // prevent window from falling out of the screen to the left } @@ -825,6 +830,8 @@ export class CodeWindow extends Disposable implements ICodeWindow { if (state.display && state.mode === WindowMode.Fullscreen) { const display = displays.filter(d => d.id === state.display)[0]; if (display && typeof display.bounds?.x === 'number' && typeof display.bounds?.y === 'number') { + this.logService.trace('window#validateWindowState: restoring fullscreen to previous display'); + const defaults = defaultWindowState(WindowMode.Fullscreen); // make sure we have good values when the user restores the window defaults.x = display.bounds.x; // carefull to use displays x/y position so that the window ends up on the correct monitor defaults.y = display.bounds.y; @@ -845,6 +852,8 @@ export class CodeWindow extends Disposable implements ICodeWindow { bounds.x + bounds.width > displayWorkingArea.x && // prevent window from falling out of the screen to the left bounds.y + bounds.height > displayWorkingArea.y // prevent window from falling out of the scree nto the top ) { + this.logService.trace('window#validateWindowState: multi display', displayWorkingArea); + return state; } diff --git a/src/vs/editor/browser/controller/mouseHandler.ts b/src/vs/editor/browser/controller/mouseHandler.ts index 1373d395ea..522c951581 100644 --- a/src/vs/editor/browser/controller/mouseHandler.ts +++ b/src/vs/editor/browser/controller/mouseHandler.ts @@ -122,7 +122,7 @@ export class MouseHandler extends ViewEventHandler { e.stopPropagation(); } }; - this._register(dom.addDisposableListener(this.viewHelper.viewDomNode, browser.isEdgeOrIE ? 'mousewheel' : 'wheel', onMouseWheel, true)); + this._register(dom.addDisposableListener(this.viewHelper.viewDomNode, browser.isEdgeOrIE ? 'mousewheel' : 'wheel', onMouseWheel, { capture: true, passive: false })); this._context.addEventHandler(this); } diff --git a/src/vs/editor/browser/viewParts/lineNumbers/flipped-cursor-2x.svg b/src/vs/editor/browser/viewParts/lineNumbers/flipped-cursor-2x.svg deleted file mode 100644 index 1d1cda0e93..0000000000 --- a/src/vs/editor/browser/viewParts/lineNumbers/flipped-cursor-2x.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/vs/editor/browser/viewParts/lineNumbers/flipped-cursor-mac-2x.svg b/src/vs/editor/browser/viewParts/lineNumbers/flipped-cursor-mac-2x.svg deleted file mode 100644 index 7f64457f78..0000000000 --- a/src/vs/editor/browser/viewParts/lineNumbers/flipped-cursor-mac-2x.svg +++ /dev/null @@ -1,2 +0,0 @@ - - flipped-cursor-mac diff --git a/src/vs/editor/browser/viewParts/lineNumbers/flipped-cursor-mac.svg b/src/vs/editor/browser/viewParts/lineNumbers/flipped-cursor-mac.svg deleted file mode 100644 index da79a9547d..0000000000 --- a/src/vs/editor/browser/viewParts/lineNumbers/flipped-cursor-mac.svg +++ /dev/null @@ -1 +0,0 @@ -flipped-cursor-mac diff --git a/src/vs/editor/browser/viewParts/lineNumbers/flipped-cursor.svg b/src/vs/editor/browser/viewParts/lineNumbers/flipped-cursor.svg deleted file mode 100644 index 0add3031fb..0000000000 --- a/src/vs/editor/browser/viewParts/lineNumbers/flipped-cursor.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/vs/editor/browser/viewParts/lineNumbers/lineNumbers.css b/src/vs/editor/browser/viewParts/lineNumbers/lineNumbers.css index dfd92bf100..680cccb1fa 100644 --- a/src/vs/editor/browser/viewParts/lineNumbers/lineNumbers.css +++ b/src/vs/editor/browser/viewParts/lineNumbers/lineNumbers.css @@ -19,20 +19,6 @@ width: 100%; } -.monaco-editor .margin-view-overlays .line-numbers { - cursor: -webkit-image-set( - url('flipped-cursor.svg') 1x, - url('flipped-cursor-2x.svg') 2x - ) 30 0, default; -} - -.monaco-editor.mac .margin-view-overlays .line-numbers { - cursor: -webkit-image-set( - url('flipped-cursor-mac.svg') 1x, - url('flipped-cursor-mac-2x.svg') 2x - ) 24 3, default; -} - .monaco-editor .margin-view-overlays .line-numbers.lh-odd { margin-top: 1px; } diff --git a/src/vs/editor/browser/viewParts/viewCursors/viewCursors.css b/src/vs/editor/browser/viewParts/viewCursors/viewCursors.css index 4fb51b7855..77d16cb7bb 100644 --- a/src/vs/editor/browser/viewParts/viewCursors/viewCursors.css +++ b/src/vs/editor/browser/viewParts/viewCursors/viewCursors.css @@ -15,7 +15,7 @@ /* -- smooth-caret-animation -- */ .monaco-editor .cursors-layer.cursor-smooth-caret-animation > .cursor { - transition: 80ms; + transition: all 80ms; } /* -- block-outline-style -- */ @@ -85,4 +85,4 @@ .cursor-expand > .cursor { animation: monaco-cursor-expand 0.5s ease-in-out 0s 20 alternate; -} \ No newline at end of file +} diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index dc23029ce1..8dfc588cb6 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -996,6 +996,7 @@ class EditorAccessibilitySupport extends BaseEditorOption>; class EditorFind extends BaseEditorOption { @@ -1352,6 +1357,9 @@ export interface IGotoLocationOptions { alternativeReferenceCommand?: string; } +/** + * @internal + */ export type GoToLocationOptions = Readonly>; class EditorGoToLocation extends BaseEditorOption { @@ -1481,6 +1489,9 @@ export interface IEditorHoverOptions { sticky?: boolean; } +/** + * @internal + */ export type EditorHoverOptions = Readonly>; class EditorHover extends BaseEditorOption { @@ -1857,6 +1868,9 @@ export interface IEditorLightbulbOptions { enabled?: boolean; } +/** + * @internal + */ export type EditorLightbulbOptions = Readonly>; class EditorLightbulb extends BaseEditorOption { @@ -1948,6 +1962,9 @@ export interface IEditorMinimapOptions { scale?: number; } +/** + * @internal + */ export type EditorMinimapOptions = Readonly>; class EditorMinimap extends BaseEditorOption { @@ -2049,6 +2066,9 @@ export interface IEditorParameterHintOptions { cycle?: boolean; } +/** + * @internal + */ export type InternalParameterHintOptions = Readonly>; class EditorParameterHints extends BaseEditorOption { @@ -2115,6 +2135,9 @@ export interface IQuickSuggestionsOptions { strings: boolean; } +/** + * @internal + */ export type ValidQuickSuggestionsOptions = boolean | Readonly>; class EditorQuickSuggestions extends BaseEditorOption { @@ -2191,6 +2214,9 @@ class EditorQuickSuggestions extends BaseEditorOption string); +/** + * @internal + */ export const enum RenderLineNumbersType { Off = 0, On = 1, @@ -2199,6 +2225,9 @@ export const enum RenderLineNumbersType { Custom = 4 } +/** + * @internal + */ export interface InternalEditorRenderLineNumbersOptions { readonly renderType: RenderLineNumbersType; readonly renderFn: ((lineNumber: number) => string) | null; @@ -2354,6 +2383,9 @@ export interface IEditorScrollbarOptions { horizontalSliderSize?: number; } +/** + * @internal + */ export interface InternalEditorScrollbarOptions { readonly arrowSize: number; readonly vertical: ScrollbarVisibility; @@ -2568,6 +2600,9 @@ export interface ISuggestOptions { showSnippets?: boolean; } +/** + * @internal + */ export type InternalSuggestOptions = Readonly>; class EditorSuggest extends BaseEditorOption { @@ -2861,6 +2896,7 @@ class EditorTabFocusMode extends ComputedEditorOption this._updateModelOptions()); this._updateModelOptions(); - this._register(new SemanticColoringFeature(this, themeService)); + this._register(new SemanticColoringFeature(this, themeService, logService)); } private static _readModelOptions(config: IRawConfig, isForSimpleWidget: boolean): ITextModelCreationOptions { @@ -443,10 +445,10 @@ class SemanticColoringFeature extends Disposable { private _watchers: Record; private _semanticStyling: SemanticStyling; - constructor(modelService: IModelService, themeService: IThemeService) { + constructor(modelService: IModelService, themeService: IThemeService, logService: ILogService) { super(); this._watchers = Object.create(null); - this._semanticStyling = this._register(new SemanticStyling(themeService)); + this._semanticStyling = this._register(new SemanticStyling(themeService, logService)); this._register(modelService.onModelAdded((model) => { this._watchers[model.uri.toString()] = new ModelSemanticColoring(model, themeService, this._semanticStyling); })); @@ -462,7 +464,8 @@ class SemanticStyling extends Disposable { private _caches: WeakMap; constructor( - private readonly _themeService: IThemeService + private readonly _themeService: IThemeService, + private readonly _logService: ILogService ) { super(); this._caches = new WeakMap(); @@ -476,7 +479,7 @@ class SemanticStyling extends Disposable { public get(provider: SemanticTokensProvider): SemanticColoringProviderStyling { if (!this._caches.has(provider)) { - this._caches.set(provider, new SemanticColoringProviderStyling(provider.getLegend(), this._themeService)); + this._caches.set(provider, new SemanticColoringProviderStyling(provider.getLegend(), this._themeService, this._logService)); } return this._caches.get(provider)!; } @@ -581,7 +584,8 @@ class SemanticColoringProviderStyling { constructor( private readonly _legend: SemanticTokensLegend, - private readonly _themeService: IThemeService + private readonly _themeService: IThemeService, + private readonly _logService: ILogService ) { this._hashTable = new HashTable(); } @@ -605,6 +609,9 @@ class SemanticColoringProviderStyling { if (typeof metadata === 'undefined') { metadata = Constants.NO_STYLING; } + if (this._logService.getLevel() === LogLevel.Trace) { + this._logService.trace(`getTokenStyleMetadata(${tokenType}${tokenModifiers.length ? ', ' + tokenModifiers.join(' ') : ''}): foreground: ${TokenMetadata.getForeground(metadata)}, fontStyle ${TokenMetadata.getFontStyle(metadata).toString(2)}`); + } this._hashTable.add(tokenTypeIndex, tokenModifierSet, metadata); return metadata; diff --git a/src/vs/editor/common/services/resourceConfiguration.ts b/src/vs/editor/common/services/resourceConfiguration.ts index e44a924d89..91eedcb312 100644 --- a/src/vs/editor/common/services/resourceConfiguration.ts +++ b/src/vs/editor/common/services/resourceConfiguration.ts @@ -6,19 +6,37 @@ import { Event } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; import { IPosition } from 'vs/editor/common/core/position'; -import { IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; +import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -export const ITextResourceConfigurationService = createDecorator('textResourceConfigurationService'); +export const IResourceConfigurationService = createDecorator('resourceConfigurationService'); -export interface ITextResourceConfigurationService { +export interface IResourceConfigurationChangeEvent { + + /** + * All affected keys. Also includes language overrides and keys changed under language overrides. + */ + readonly affectedKeys: string[]; + + /** + * Returns `true` if the given section has changed for the given resource. + * + * Example: To check if the configuration section has changed for a given resource use `e.affectsConfiguration(resource, section)`. + * + * @param resource Resource for which the configuration has to be checked. + * @param section Section of the configuration + */ + affectsConfiguration(resource: URI, section: string): boolean; +} + +export interface IResourceConfigurationService { _serviceBrand: undefined; /** * Event that fires when the configuration changes. */ - onDidChangeConfiguration: Event; + onDidChangeConfiguration: Event; /** * Fetches the value of the section for the given resource by applying language overrides. @@ -32,6 +50,20 @@ export interface ITextResourceConfigurationService { getValue(resource: URI | undefined, section?: string): T; getValue(resource: URI | undefined, position?: IPosition, section?: string): T; + /** + * Update the configuration value for the given resource at the effective location. + * + * - If configurationTarget is not specified, target will be derived by checking where the configuration is defined. + * - If the language overrides for the give resource contains the configuration, then it is updated. + * + * @param resource Resource for which the configuration has to be updated + * @param key Configuration key + * @param value Configuration value + * @param configurationTarget Optional target into which the configuration has to be updated. + * If not specified, target will be derived by checking where the configuration is defined. + */ + updateValue(resource: URI, key: string, value: any, configurationTarget?: ConfigurationTarget): Promise; + } export const ITextResourcePropertiesService = createDecorator('textResourcePropertiesService'); @@ -44,4 +76,4 @@ export interface ITextResourcePropertiesService { * Returns the End of Line characters for the given resource */ getEOL(resource: URI | undefined, language?: string): string; -} \ No newline at end of file +} diff --git a/src/vs/editor/common/services/resourceConfigurationImpl.ts b/src/vs/editor/common/services/resourceConfigurationImpl.ts index 9290c947eb..c402f28cac 100644 --- a/src/vs/editor/common/services/resourceConfigurationImpl.ts +++ b/src/vs/editor/common/services/resourceConfigurationImpl.ts @@ -9,15 +9,15 @@ import { URI } from 'vs/base/common/uri'; import { IPosition, Position } from 'vs/editor/common/core/position'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; -import { IConfigurationChangeEvent, IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IResourceConfigurationService, IResourceConfigurationChangeEvent } from 'vs/editor/common/services/resourceConfiguration'; +import { IConfigurationService, ConfigurationTarget, IConfigurationValue, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; -export class TextResourceConfigurationService extends Disposable implements ITextResourceConfigurationService { +export class TextResourceConfigurationService extends Disposable implements IResourceConfigurationService { public _serviceBrand: undefined; - private readonly _onDidChangeConfiguration: Emitter = this._register(new Emitter()); - public readonly onDidChangeConfiguration: Event = this._onDidChangeConfiguration.event; + private readonly _onDidChangeConfiguration: Emitter = this._register(new Emitter()); + public readonly onDidChangeConfiguration: Event = this._onDidChangeConfiguration.event; constructor( @IConfigurationService private readonly configurationService: IConfigurationService, @@ -25,7 +25,7 @@ export class TextResourceConfigurationService extends Disposable implements ITex @IModeService private readonly modeService: IModeService, ) { super(); - this._register(this.configurationService.onDidChangeConfiguration(e => this._onDidChangeConfiguration.fire(e))); + this._register(this.configurationService.onDidChangeConfiguration(e => this._onDidChangeConfiguration.fire(this.toResourceConfigurationChangeEvent(e)))); } getValue(resource: URI, section?: string): T; @@ -37,6 +37,67 @@ export class TextResourceConfigurationService extends Disposable implements ITex return this._getValue(resource, null, typeof arg2 === 'string' ? arg2 : undefined); } + updateValue(resource: URI, key: string, value: any, configurationTarget?: ConfigurationTarget): Promise { + const language = this.getLanguage(resource, null); + const configurationValue = this.configurationService.inspect(key, { resource, overrideIdentifier: language }); + if (configurationTarget === undefined) { + configurationTarget = this.deriveConfigurationTarget(configurationValue, language); + } + switch (configurationTarget) { + case ConfigurationTarget.MEMORY: + return this._updateValue(key, value, configurationTarget, configurationValue.memoryTarget?.override, resource, language); + case ConfigurationTarget.WORKSPACE_FOLDER: + return this._updateValue(key, value, configurationTarget, configurationValue.workspaceFolderTarget?.override, resource, language); + case ConfigurationTarget.WORKSPACE: + return this._updateValue(key, value, configurationTarget, configurationValue.workspaceTarget?.override, resource, language); + case ConfigurationTarget.USER_REMOTE: + return this._updateValue(key, value, configurationTarget, configurationValue.userRemoteTarget?.override, resource, language); + default: + return this._updateValue(key, value, configurationTarget, configurationValue.userLocalTarget?.override, resource, language); + } + } + + private _updateValue(key: string, value: any, configurationTarget: ConfigurationTarget, overriddenValue: any | undefined, resource: URI, language: string | null): Promise { + if (language && overriddenValue !== undefined) { + return this.configurationService.updateValue(key, value, { resource, overrideIdentifier: language }, configurationTarget); + } else { + return this.configurationService.updateValue(key, value, { resource }, configurationTarget); + } + } + + private deriveConfigurationTarget(configurationValue: IConfigurationValue, language: string | null): ConfigurationTarget { + if (language) { + if (configurationValue.memoryTarget?.override !== undefined) { + return ConfigurationTarget.MEMORY; + } + if (configurationValue.workspaceFolderTarget?.override !== undefined) { + return ConfigurationTarget.WORKSPACE_FOLDER; + } + if (configurationValue.workspaceTarget?.override !== undefined) { + return ConfigurationTarget.WORKSPACE; + } + if (configurationValue.userRemoteTarget?.override !== undefined) { + return ConfigurationTarget.USER_REMOTE; + } + if (configurationValue.userLocalTarget?.override !== undefined) { + return ConfigurationTarget.USER_LOCAL; + } + } + if (configurationValue.memoryTarget?.value !== undefined) { + return ConfigurationTarget.MEMORY; + } + if (configurationValue.workspaceFolderTarget?.value !== undefined) { + return ConfigurationTarget.WORKSPACE_FOLDER; + } + if (configurationValue.workspaceTarget?.value !== undefined) { + return ConfigurationTarget.WORKSPACE; + } + if (configurationValue.userRemoteTarget?.value !== undefined) { + return ConfigurationTarget.USER_REMOTE; + } + return ConfigurationTarget.USER_LOCAL; + } + private _getValue(resource: URI, position: IPosition | null, section: string | undefined): T { const language = resource ? this.getLanguage(resource, position) : undefined; if (typeof section === 'undefined') { @@ -51,6 +112,15 @@ export class TextResourceConfigurationService extends Disposable implements ITex return position ? this.modeService.getLanguageIdentifier(model.getLanguageIdAtPosition(position.lineNumber, position.column))!.language : model.getLanguageIdentifier().language; } return this.modeService.getModeIdByFilepathOrFirstLine(resource); - } -} \ No newline at end of file + + private toResourceConfigurationChangeEvent(configurationChangeEvent: IConfigurationChangeEvent): IResourceConfigurationChangeEvent { + return { + affectedKeys: configurationChangeEvent.affectedKeys, + affectsConfiguration: (resource: URI, configuration: string) => { + const overrideIdentifier = this.getLanguage(resource, null); + return configurationChangeEvent.affectsConfiguration(configuration, { resource, overrideIdentifier }); + } + }; + } +} diff --git a/src/vs/editor/common/standalone/standaloneEnums.ts b/src/vs/editor/common/standalone/standaloneEnums.ts index 28c9a1302e..7f6e1b33b4 100644 --- a/src/vs/editor/common/standalone/standaloneEnums.ts +++ b/src/vs/editor/common/standalone/standaloneEnums.ts @@ -333,111 +333,12 @@ export enum CursorChangeReason { Redo = 6 } -export enum AccessibilitySupport { - /** - * This should be the browser case where it is not known if a screen reader is attached or no. - */ - Unknown = 0, - Disabled = 1, - Enabled = 2 -} - -/** - * The kind of animation in which the editor's cursor should be rendered. - */ -export enum TextEditorCursorBlinkingStyle { - /** - * Hidden - */ - Hidden = 0, - /** - * Blinking - */ - Blink = 1, - /** - * Blinking with smooth fading - */ - Smooth = 2, - /** - * Blinking with prolonged filled state and smooth fading - */ - Phase = 3, - /** - * Expand collapse animation on the y axis - */ - Expand = 4, - /** - * No-Blinking - */ - Solid = 5 -} - -/** - * The style in which the editor's cursor should be rendered. - */ -export enum TextEditorCursorStyle { - /** - * As a vertical line (sitting between two characters). - */ - Line = 1, - /** - * As a block (sitting on top of a character). - */ - Block = 2, - /** - * As a horizontal line (sitting under a character). - */ - Underline = 3, - /** - * As a thin vertical line (sitting between two characters). - */ - LineThin = 4, - /** - * As an outlined block (sitting on top of a character). - */ - BlockOutline = 5, - /** - * As a thin horizontal line (sitting under a character). - */ - UnderlineThin = 6 -} - export enum RenderMinimap { None = 0, Text = 1, Blocks = 2 } -export enum RenderLineNumbersType { - Off = 0, - On = 1, - Relative = 2, - Interval = 3, - Custom = 4 -} - -/** - * Describes how to indent wrapped lines. - */ -export enum WrappingIndent { - /** - * No indentation => wrapped lines begin at column 1. - */ - None = 0, - /** - * Same => wrapped lines get the same indentation as the parent. - */ - Same = 1, - /** - * Indent => wrapped lines get +1 indentation toward the parent. - */ - Indent = 2, - /** - * DeepIndent => wrapped lines get +2 indentation toward the parent. - */ - DeepIndent = 3 -} - /** * A positioning preference for rendering content widgets. */ diff --git a/src/vs/editor/contrib/documentSymbols/outlineTree.ts b/src/vs/editor/contrib/documentSymbols/outlineTree.ts index fc2703d143..e009df6bbe 100644 --- a/src/vs/editor/contrib/documentSymbols/outlineTree.ts +++ b/src/vs/editor/contrib/documentSymbols/outlineTree.ts @@ -22,7 +22,7 @@ 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 { IResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; import { URI } from 'vs/base/common/uri'; export type OutlineItem = OutlineGroup | OutlineElement; @@ -282,7 +282,7 @@ export class OutlineFilter implements ITreeFilter { constructor( private readonly _prefix: string, - @ITextResourceConfigurationService private readonly _textResourceConfigService: ITextResourceConfigurationService, + @IResourceConfigurationService private readonly _textResourceConfigService: IResourceConfigurationService, ) { } filter(element: OutlineItem): boolean { diff --git a/src/vs/editor/contrib/smartSelect/test/smartSelect.test.ts b/src/vs/editor/contrib/smartSelect/test/smartSelect.test.ts index 12482fa7ae..261beeb52a 100644 --- a/src/vs/editor/contrib/smartSelect/test/smartSelect.test.ts +++ b/src/vs/editor/contrib/smartSelect/test/smartSelect.test.ts @@ -18,6 +18,7 @@ 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'; +import { NullLogService } from 'vs/platform/log/common/log'; class MockJSMode extends MockMode { @@ -46,7 +47,7 @@ suite('SmartSelect', () => { setup(() => { const configurationService = new TestConfigurationService(); - modelService = new ModelServiceImpl(configurationService, new TestTextResourcePropertiesService(configurationService), new TestThemeService()); + modelService = new ModelServiceImpl(configurationService, new TestTextResourcePropertiesService(configurationService), new TestThemeService(), new NullLogService()); mode = new MockJSMode(); }); diff --git a/src/vs/editor/standalone/browser/simpleServices.ts b/src/vs/editor/standalone/browser/simpleServices.ts index 0a55d88766..d0d48b0119 100644 --- a/src/vs/editor/standalone/browser/simpleServices.ts +++ b/src/vs/editor/standalone/browser/simpleServices.ts @@ -23,9 +23,9 @@ import { ITextModel, ITextSnapshot } from 'vs/editor/common/model'; import { TextEdit, WorkspaceEdit, isResourceTextEdit } from 'vs/editor/common/modes'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IResolvedTextEditorModel, ITextModelContentProvider, ITextModelService } from 'vs/editor/common/services/resolverService'; -import { ITextResourceConfigurationService, ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; +import { IResourceConfigurationService, ITextResourcePropertiesService, IResourceConfigurationChangeEvent } from 'vs/editor/common/services/resourceConfiguration'; import { CommandsRegistry, ICommand, ICommandEvent, ICommandHandler, ICommandService } from 'vs/platform/commands/common/commands'; -import { IConfigurationChangeEvent, IConfigurationData, IConfigurationOverrides, IConfigurationService, IConfigurationModel } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationChangeEvent, IConfigurationData, IConfigurationOverrides, IConfigurationService, IConfigurationModel, IConfigurationValue, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { Configuration, ConfigurationModel, DefaultConfigurationModel } from 'vs/platform/configuration/common/configurationModels'; import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IConfirmation, IConfirmationResult, IDialogOptions, IDialogService, IShowResult } from 'vs/platform/dialogs/common/dialogs'; @@ -460,13 +460,7 @@ export class SimpleConfigurationService implements IConfigurationService { return Promise.resolve(); } - public inspect(key: string, options: IConfigurationOverrides = {}): { - default: C, - user: C, - workspace?: C, - workspaceFolder?: C - value: C, - } { + public inspect(key: string, options: IConfigurationOverrides = {}): IConfigurationValue { return this.configuration().inspect(key, options, undefined); } @@ -493,16 +487,16 @@ export class SimpleConfigurationService implements IConfigurationService { } } -export class SimpleResourceConfigurationService implements ITextResourceConfigurationService { +export class SimpleResourceConfigurationService implements IResourceConfigurationService { _serviceBrand: undefined; - private readonly _onDidChangeConfiguration = new Emitter(); + private readonly _onDidChangeConfiguration = new Emitter(); public readonly onDidChangeConfiguration = this._onDidChangeConfiguration.event; constructor(private readonly configurationService: SimpleConfigurationService) { this.configurationService.onDidChangeConfiguration((e) => { - this._onDidChangeConfiguration.fire(e); + this._onDidChangeConfiguration.fire({ affectedKeys: e.affectedKeys, affectsConfiguration: (resource: URI, configuration: string) => e.affectsConfiguration(configuration) }); }); } @@ -516,6 +510,10 @@ export class SimpleResourceConfigurationService implements ITextResourceConfigur } return this.configurationService.getValue(section); } + + updateValue(resource: URI, key: string, value: any, configurationTarget?: ConfigurationTarget): Promise { + return this.configurationService.updateValue(key, value, { resource }, configurationTarget); + } } export class SimpleResourcePropertiesService implements ITextResourcePropertiesService { diff --git a/src/vs/editor/standalone/browser/standaloneEditor.ts b/src/vs/editor/standalone/browser/standaloneEditor.ts index 34b4c74f66..ea739d2ae9 100644 --- a/src/vs/editor/standalone/browser/standaloneEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneEditor.ts @@ -346,9 +346,7 @@ export function createMonacoEditorAPI(): typeof monaco.editor { remeasureFonts: remeasureFonts, // enums - AccessibilitySupport: standaloneEnums.AccessibilitySupport, ScrollbarVisibility: standaloneEnums.ScrollbarVisibility, - WrappingIndent: standaloneEnums.WrappingIndent, OverviewRulerLane: standaloneEnums.OverviewRulerLane, MinimapPosition: standaloneEnums.MinimapPosition, EndOfLinePreference: standaloneEnums.EndOfLinePreference, @@ -357,13 +355,10 @@ export function createMonacoEditorAPI(): typeof monaco.editor { TrackedRangeStickiness: standaloneEnums.TrackedRangeStickiness, CursorChangeReason: standaloneEnums.CursorChangeReason, MouseTargetType: standaloneEnums.MouseTargetType, - TextEditorCursorStyle: standaloneEnums.TextEditorCursorStyle, - TextEditorCursorBlinkingStyle: standaloneEnums.TextEditorCursorBlinkingStyle, ContentWidgetPositionPreference: standaloneEnums.ContentWidgetPositionPreference, OverlayWidgetPositionPreference: standaloneEnums.OverlayWidgetPositionPreference, RenderMinimap: standaloneEnums.RenderMinimap, ScrollType: standaloneEnums.ScrollType, - RenderLineNumbersType: standaloneEnums.RenderLineNumbersType, // classes ConfigurationChangedEvent: ConfigurationChangedEvent, diff --git a/src/vs/editor/standalone/browser/standaloneServices.ts b/src/vs/editor/standalone/browser/standaloneServices.ts index f1767ea51f..2653909b2a 100644 --- a/src/vs/editor/standalone/browser/standaloneServices.ts +++ b/src/vs/editor/standalone/browser/standaloneServices.ts @@ -12,7 +12,7 @@ import { IModeService } from 'vs/editor/common/services/modeService'; import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; -import { ITextResourceConfigurationService, ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; +import { IResourceConfigurationService, ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; import { SimpleBulkEditService, SimpleConfigurationService, SimpleDialogService, SimpleNotificationService, SimpleEditorProgressService, SimpleResourceConfigurationService, SimpleResourcePropertiesService, SimpleUriLabelService, SimpleWorkspaceContextService, StandaloneCommandService, StandaloneKeybindingService, StandaloneTelemetryService, SimpleLayoutService } from 'vs/editor/standalone/browser/simpleServices'; import { StandaloneCodeEditorServiceImpl } from 'vs/editor/standalone/browser/standaloneCodeServiceImpl'; import { StandaloneThemeServiceImpl } from 'vs/editor/standalone/browser/standaloneThemeServiceImpl'; @@ -126,7 +126,7 @@ export module StaticServices { const configurationServiceImpl = new SimpleConfigurationService(); export const configurationService = define(IConfigurationService, () => configurationServiceImpl); - export const resourceConfigurationService = define(ITextResourceConfigurationService, () => new SimpleResourceConfigurationService(configurationServiceImpl)); + export const resourceConfigurationService = define(IResourceConfigurationService, () => new SimpleResourceConfigurationService(configurationServiceImpl)); export const resourcePropertiesService = define(ITextResourcePropertiesService, () => new SimpleResourcePropertiesService(configurationServiceImpl)); @@ -146,7 +146,9 @@ export module StaticServices { export const standaloneThemeService = define(IStandaloneThemeService, () => new StandaloneThemeServiceImpl()); - export const modelService = define(IModelService, (o) => new ModelServiceImpl(configurationService.get(o), resourcePropertiesService.get(o), standaloneThemeService.get(o))); + export const logService = define(ILogService, () => new NullLogService()); + + export const modelService = define(IModelService, (o) => new ModelServiceImpl(configurationService.get(o), resourcePropertiesService.get(o), standaloneThemeService.get(o), logService.get(o))); export const markerDecorationsService = define(IMarkerDecorationsService, (o) => new MarkerDecorationsService(modelService.get(o), markerService.get(o))); @@ -156,8 +158,6 @@ export module StaticServices { export const storageService = define(IStorageService, () => new InMemoryStorageService()); - export const logService = define(ILogService, () => new NullLogService()); - export const editorWorkerService = define(IEditorWorkerService, (o) => new EditorWorkerServiceImpl(modelService.get(o), resourceConfigurationService.get(o), logService.get(o))); } diff --git a/src/vs/editor/test/common/services/modelService.test.ts b/src/vs/editor/test/common/services/modelService.test.ts index e01ff62ff1..5d8d22e7a6 100644 --- a/src/vs/editor/test/common/services/modelService.test.ts +++ b/src/vs/editor/test/common/services/modelService.test.ts @@ -17,6 +17,7 @@ import { ITextResourcePropertiesService } from 'vs/editor/common/services/resour 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'; +import { NullLogService } from 'vs/platform/log/common/log'; const GENERATE_TESTS = false; @@ -28,7 +29,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), new TestThemeService()); + modelService = new ModelServiceImpl(configService, new TestTextResourcePropertiesService(configService), new TestThemeService(), new NullLogService()); }); teardown(() => { diff --git a/src/vs/editor/test/common/services/resourceConfigurationService.test.ts b/src/vs/editor/test/common/services/resourceConfigurationService.test.ts new file mode 100644 index 0000000000..001ce0b9c9 --- /dev/null +++ b/src/vs/editor/test/common/services/resourceConfigurationService.test.ts @@ -0,0 +1,301 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; +import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; +import { IModelService } from 'vs/editor/common/services/modelService'; +import { IModeService } from 'vs/editor/common/services/modeService'; +import { IConfigurationValue, IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; +import { TextResourceConfigurationService } from 'vs/editor/common/services/resourceConfigurationImpl'; +import { URI } from 'vs/base/common/uri'; + + +suite('TextResourceConfigurationService - Update', () => { + + let configurationValue: IConfigurationValue = {}; + let updateArgs: any[]; + let configurationService = new class extends TestConfigurationService { + inspect() { + return configurationValue; + } + updateValue() { + updateArgs = [...arguments]; + return Promise.resolve(); + } + }(); + let language: string | null = null; + let testObject: TextResourceConfigurationService; + + setup(() => { + const instantiationService = new TestInstantiationService(); + instantiationService.stub(IModelService, >{ getModel() { return null; } }); + instantiationService.stub(IModeService, >{ getModeIdByFilepathOrFirstLine() { return language; } }); + instantiationService.stub(IConfigurationService, configurationService); + testObject = instantiationService.createInstance(TextResourceConfigurationService); + }); + + test('updateValue writes without target and overrides when no language is defined', async () => { + const resource = URI.file('someFile'); + await testObject.updateValue(resource, 'a', 'b'); + assert.deepEqual(updateArgs, ['a', 'b', { resource }, ConfigurationTarget.USER_LOCAL]); + }); + + test('updateValue writes with target and without overrides when no language is defined', async () => { + const resource = URI.file('someFile'); + await testObject.updateValue(resource, 'a', 'b', ConfigurationTarget.USER_LOCAL); + assert.deepEqual(updateArgs, ['a', 'b', { resource }, ConfigurationTarget.USER_LOCAL]); + }); + + test('updateValue writes into given memory target without overrides', async () => { + language = 'a'; + configurationValue = { + defaultTarget: { value: '1' }, + userLocalTarget: { value: '2' }, + workspaceFolderTarget: { value: '1' }, + }; + const resource = URI.file('someFile'); + + await testObject.updateValue(resource, 'a', 'b', ConfigurationTarget.MEMORY); + assert.deepEqual(updateArgs, ['a', 'b', { resource }, ConfigurationTarget.MEMORY]); + }); + + test('updateValue writes into given workspace target without overrides', async () => { + language = 'a'; + configurationValue = { + defaultTarget: { value: '1' }, + userLocalTarget: { value: '2' }, + workspaceFolderTarget: { value: '2' }, + }; + const resource = URI.file('someFile'); + + await testObject.updateValue(resource, 'a', 'b', ConfigurationTarget.WORKSPACE); + assert.deepEqual(updateArgs, ['a', 'b', { resource }, ConfigurationTarget.WORKSPACE]); + }); + + test('updateValue writes into given user target without overrides', async () => { + language = 'a'; + configurationValue = { + defaultTarget: { value: '1' }, + userLocalTarget: { value: '2' }, + workspaceFolderTarget: { value: '2' }, + }; + const resource = URI.file('someFile'); + + await testObject.updateValue(resource, 'a', 'b', ConfigurationTarget.USER); + assert.deepEqual(updateArgs, ['a', 'b', { resource }, ConfigurationTarget.USER]); + }); + + test('updateValue writes into given workspace folder target with overrides', async () => { + language = 'a'; + configurationValue = { + defaultTarget: { value: '1' }, + userLocalTarget: { value: '2' }, + workspaceFolderTarget: { value: '2', override: '1' }, + }; + const resource = URI.file('someFile'); + + await testObject.updateValue(resource, 'a', 'b', ConfigurationTarget.WORKSPACE_FOLDER); + assert.deepEqual(updateArgs, ['a', 'b', { resource, overrideIdentifier: language }, ConfigurationTarget.WORKSPACE_FOLDER]); + }); + + test('updateValue writes into derived workspace folder target without overrides', async () => { + language = 'a'; + configurationValue = { + defaultTarget: { value: '1' }, + userLocalTarget: { value: '2' }, + workspaceFolderTarget: { value: '2' }, + }; + const resource = URI.file('someFile'); + + await testObject.updateValue(resource, 'a', 'b'); + assert.deepEqual(updateArgs, ['a', 'b', { resource }, ConfigurationTarget.WORKSPACE_FOLDER]); + }); + + test('updateValue writes into derived workspace folder target with overrides', async () => { + language = 'a'; + configurationValue = { + defaultTarget: { value: '1' }, + userLocalTarget: { value: '2' }, + workspaceTarget: { value: '2', override: '1' }, + workspaceFolderTarget: { value: '2', override: '2' }, + }; + const resource = URI.file('someFile'); + + await testObject.updateValue(resource, 'a', 'b'); + assert.deepEqual(updateArgs, ['a', 'b', { resource, overrideIdentifier: language }, ConfigurationTarget.WORKSPACE_FOLDER]); + }); + + test('updateValue writes into derived workspace target without overrides', async () => { + language = 'a'; + configurationValue = { + defaultTarget: { value: '1' }, + userLocalTarget: { value: '2' }, + workspaceTarget: { value: '2' }, + }; + const resource = URI.file('someFile'); + + await testObject.updateValue(resource, 'a', 'b'); + assert.deepEqual(updateArgs, ['a', 'b', { resource }, ConfigurationTarget.WORKSPACE]); + }); + + test('updateValue writes into derived workspace target with overrides', async () => { + language = 'a'; + configurationValue = { + defaultTarget: { value: '1' }, + userLocalTarget: { value: '2' }, + workspaceTarget: { value: '2', override: '2' }, + }; + const resource = URI.file('someFile'); + + await testObject.updateValue(resource, 'a', 'b'); + assert.deepEqual(updateArgs, ['a', 'b', { resource, overrideIdentifier: language }, ConfigurationTarget.WORKSPACE]); + }); + + test('updateValue writes into derived workspace target with overrides and value defined in folder', async () => { + language = 'a'; + configurationValue = { + defaultTarget: { value: '1', override: '3' }, + userLocalTarget: { value: '2' }, + workspaceTarget: { value: '2', override: '2' }, + workspaceFolderTarget: { value: '2' }, + }; + const resource = URI.file('someFile'); + + await testObject.updateValue(resource, 'a', 'b'); + assert.deepEqual(updateArgs, ['a', 'b', { resource, overrideIdentifier: language }, ConfigurationTarget.WORKSPACE]); + }); + + test('updateValue writes into derived user remote target without overrides', async () => { + language = 'a'; + configurationValue = { + defaultTarget: { value: '1' }, + userLocalTarget: { value: '2' }, + userRemoteTarget: { value: '2' }, + }; + const resource = URI.file('someFile'); + + await testObject.updateValue(resource, 'a', 'b'); + assert.deepEqual(updateArgs, ['a', 'b', { resource }, ConfigurationTarget.USER_REMOTE]); + }); + + test('updateValue writes into derived user remote target with overrides', async () => { + language = 'a'; + configurationValue = { + defaultTarget: { value: '1' }, + userLocalTarget: { value: '2' }, + userRemoteTarget: { value: '2', override: '3' }, + }; + const resource = URI.file('someFile'); + + await testObject.updateValue(resource, 'a', 'b'); + assert.deepEqual(updateArgs, ['a', 'b', { resource, overrideIdentifier: language }, ConfigurationTarget.USER_REMOTE]); + }); + + test('updateValue writes into derived user remote target with overrides and value defined in workspace', async () => { + language = 'a'; + configurationValue = { + defaultTarget: { value: '1' }, + userLocalTarget: { value: '2' }, + userRemoteTarget: { value: '2', override: '3' }, + workspaceTarget: { value: '3' } + }; + const resource = URI.file('someFile'); + + await testObject.updateValue(resource, 'a', 'b'); + assert.deepEqual(updateArgs, ['a', 'b', { resource, overrideIdentifier: language }, ConfigurationTarget.USER_REMOTE]); + }); + + test('updateValue writes into derived user remote target with overrides and value defined in workspace folder', async () => { + language = 'a'; + configurationValue = { + defaultTarget: { value: '1' }, + userLocalTarget: { value: '2', override: '1' }, + userRemoteTarget: { value: '2', override: '3' }, + workspaceTarget: { value: '3' }, + workspaceFolderTarget: { value: '3' } + }; + const resource = URI.file('someFile'); + + await testObject.updateValue(resource, 'a', 'b'); + assert.deepEqual(updateArgs, ['a', 'b', { resource, overrideIdentifier: language }, ConfigurationTarget.USER_REMOTE]); + }); + + test('updateValue writes into derived user target without overrides', async () => { + language = 'a'; + configurationValue = { + defaultTarget: { value: '1' }, + userLocalTarget: { value: '2' }, + }; + const resource = URI.file('someFile'); + + await testObject.updateValue(resource, 'a', 'b'); + assert.deepEqual(updateArgs, ['a', 'b', { resource }, ConfigurationTarget.USER_LOCAL]); + }); + + test('updateValue writes into derived user target with overrides', async () => { + language = 'a'; + configurationValue = { + defaultTarget: { value: '1' }, + userLocalTarget: { value: '2', override: '3' }, + }; + const resource = URI.file('someFile'); + + await testObject.updateValue(resource, 'a', '2'); + assert.deepEqual(updateArgs, ['a', '2', { resource, overrideIdentifier: language }, ConfigurationTarget.USER_LOCAL]); + }); + + test('updateValue writes into derived user target with overrides and value is defined in remote', async () => { + language = 'a'; + configurationValue = { + defaultTarget: { value: '1' }, + userLocalTarget: { value: '2', override: '3' }, + userRemoteTarget: { value: '3' } + }; + const resource = URI.file('someFile'); + + await testObject.updateValue(resource, 'a', '2'); + assert.deepEqual(updateArgs, ['a', '2', { resource, overrideIdentifier: language }, ConfigurationTarget.USER_LOCAL]); + }); + + test('updateValue writes into derived user target with overrides and value is defined in workspace', async () => { + language = 'a'; + configurationValue = { + defaultTarget: { value: '1' }, + userLocalTarget: { value: '2', override: '3' }, + workspace: { value: '3' } + }; + const resource = URI.file('someFile'); + + await testObject.updateValue(resource, 'a', '2'); + assert.deepEqual(updateArgs, ['a', '2', { resource, overrideIdentifier: language }, ConfigurationTarget.USER_LOCAL]); + }); + + test('updateValue writes into derived user target with overrides and value is defined in workspace folder', async () => { + language = 'a'; + configurationValue = { + defaultTarget: { value: '1', override: '3' }, + userLocalTarget: { value: '2', override: '3' }, + userRemoteTarget: { value: '3' }, + workspaceFolder: { value: '3' } + }; + const resource = URI.file('someFile'); + + await testObject.updateValue(resource, 'a', '2'); + assert.deepEqual(updateArgs, ['a', '2', { resource, overrideIdentifier: language }, ConfigurationTarget.USER_LOCAL]); + }); + + test('updateValue when not changed', async () => { + language = 'a'; + configurationValue = { + defaultTarget: { value: '1' }, + }; + const resource = URI.file('someFile'); + + await testObject.updateValue(resource, 'a', 'b'); + assert.deepEqual(updateArgs, ['a', 'b', { resource }, ConfigurationTarget.USER_LOCAL]); + }); + +}); diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index e9d6a524f7..3c05800dbe 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -2419,15 +2419,6 @@ declare namespace monaco.editor { readonly reason: CursorChangeReason; } - export enum AccessibilitySupport { - /** - * This should be the browser case where it is not known if a screen reader is attached or no. - */ - Unknown = 0, - Disabled = 1, - Enabled = 2 - } - /** * Configuration options for auto closing quotes and brackets */ @@ -2992,66 +2983,6 @@ declare namespace monaco.editor { export class ConfigurationChangedEvent { } - /** - * The kind of animation in which the editor's cursor should be rendered. - */ - export enum TextEditorCursorBlinkingStyle { - /** - * Hidden - */ - Hidden = 0, - /** - * Blinking - */ - Blink = 1, - /** - * Blinking with smooth fading - */ - Smooth = 2, - /** - * Blinking with prolonged filled state and smooth fading - */ - Phase = 3, - /** - * Expand collapse animation on the y axis - */ - Expand = 4, - /** - * No-Blinking - */ - Solid = 5 - } - - /** - * The style in which the editor's cursor should be rendered. - */ - export enum TextEditorCursorStyle { - /** - * As a vertical line (sitting between two characters). - */ - Line = 1, - /** - * As a block (sitting on top of a character). - */ - Block = 2, - /** - * As a horizontal line (sitting under a character). - */ - Underline = 3, - /** - * As a thin vertical line (sitting between two characters). - */ - LineThin = 4, - /** - * As an outlined block (sitting on top of a character). - */ - BlockOutline = 5, - /** - * As a thin horizontal line (sitting under a character). - */ - UnderlineThin = 6 - } - /** * Configuration options for editor find widget */ @@ -3067,8 +2998,6 @@ declare namespace monaco.editor { addExtraSpaceOnTop?: boolean; } - export type EditorFindOptions = Readonly>; - export type GoToLocationValues = 'peek' | 'gotoAndPeek' | 'goto'; /** @@ -3088,8 +3017,6 @@ declare namespace monaco.editor { alternativeReferenceCommand?: string; } - export type GoToLocationOptions = Readonly>; - /** * Configuration options for editor hover */ @@ -3111,8 +3038,6 @@ declare namespace monaco.editor { sticky?: boolean; } - export type EditorHoverOptions = Readonly>; - /** * A description for the overview ruler position. */ @@ -3242,8 +3167,6 @@ declare namespace monaco.editor { enabled?: boolean; } - export type EditorLightbulbOptions = Readonly>; - /** * Configuration options for editor minimap */ @@ -3279,8 +3202,6 @@ declare namespace monaco.editor { scale?: number; } - export type EditorMinimapOptions = Readonly>; - /** * Configuration options for parameter hints */ @@ -3297,8 +3218,6 @@ declare namespace monaco.editor { cycle?: boolean; } - export type InternalParameterHintOptions = Readonly>; - /** * Configuration options for quick suggestions */ @@ -3308,23 +3227,8 @@ declare namespace monaco.editor { strings: boolean; } - export type ValidQuickSuggestionsOptions = boolean | Readonly>; - export type LineNumbersType = 'on' | 'off' | 'relative' | 'interval' | ((lineNumber: number) => string); - export enum RenderLineNumbersType { - Off = 0, - On = 1, - Relative = 2, - Interval = 3, - Custom = 4 - } - - export interface InternalEditorRenderLineNumbersOptions { - readonly renderType: RenderLineNumbersType; - readonly renderFn: ((lineNumber: number) => string) | null; - } - /** * Configuration options for editor scrollbars */ @@ -3391,21 +3295,6 @@ declare namespace monaco.editor { horizontalSliderSize?: number; } - export interface InternalEditorScrollbarOptions { - readonly arrowSize: number; - readonly vertical: ScrollbarVisibility; - readonly horizontal: ScrollbarVisibility; - readonly useShadows: boolean; - readonly verticalHasArrows: boolean; - readonly horizontalHasArrows: boolean; - readonly handleMouseWheel: boolean; - readonly alwaysConsumeMouseWheel: boolean; - readonly horizontalScrollbarSize: number; - readonly horizontalSliderSize: number; - readonly verticalScrollbarSize: number; - readonly verticalSliderSize: number; - } - /** * Configuration options for editor suggest widget */ @@ -3544,37 +3433,6 @@ declare namespace monaco.editor { showSnippets?: boolean; } - export type InternalSuggestOptions = Readonly>; - - /** - * Describes how to indent wrapped lines. - */ - export enum WrappingIndent { - /** - * No indentation => wrapped lines begin at column 1. - */ - None = 0, - /** - * Same => wrapped lines get the same indentation as the parent. - */ - Same = 1, - /** - * Indent => wrapped lines get +1 indentation toward the parent. - */ - Indent = 2, - /** - * DeepIndent => wrapped lines get +2 indentation toward the parent. - */ - DeepIndent = 3 - } - - export interface EditorWrappingInfo { - readonly isDominatedByLongLines: boolean; - readonly isWordWrapMinified: boolean; - readonly isViewportWrapping: boolean; - readonly wrappingColumn: number; - } - /** * A view zone is a full horizontal rectangle that 'pushes' text down. * The editor reserves space for view zones when rendering. diff --git a/src/vs/platform/configuration/common/configuration.ts b/src/vs/platform/configuration/common/configuration.ts index 4d2dd2fad0..2a7b1a7685 100644 --- a/src/vs/platform/configuration/common/configuration.ts +++ b/src/vs/platform/configuration/common/configuration.ts @@ -11,7 +11,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IConfigurationRegistry, Extensions, OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/configurationRegistry'; -import { ResourceMap } from 'vs/base/common/map'; +import { IStringDictionary } from 'vs/base/common/collections'; export const IConfigurationService = createDecorator('configurationService'); @@ -48,18 +48,41 @@ export function ConfigurationTargetToString(configurationTarget: ConfigurationTa } } +export interface IConfigurationChange { + keys: string[]; + overrides: [string, string[]][]; +} + export interface IConfigurationChangeEvent { - source: ConfigurationTarget; - affectedKeys: string[]; - affectsConfiguration(configuration: string, resource?: URI): boolean; + readonly source: ConfigurationTarget; + readonly affectedKeys: string[]; + readonly change: IConfigurationChange; + + affectsConfiguration(configuration: string, overrides?: IConfigurationOverrides): boolean; // Following data is used for telemetry - sourceConfig: any; + readonly sourceConfig: any; +} - // Following data is used for Extension host configuration event - changedConfiguration: IConfigurationModel; - changedConfigurationByResource: ResourceMap; +export interface IConfigurationValue { + + readonly default?: T; + readonly user?: T; + readonly userLocal?: T; + readonly userRemote?: T; + readonly workspace?: T; + readonly workspaceFolder?: T; + readonly memory?: T; + readonly value?: T; + + readonly defaultTarget?: { value?: T, override?: T }; + readonly userTarget?: { value?: T, override?: T }; + readonly userLocalTarget?: { value?: T, override?: T }; + readonly userRemoteTarget?: { value?: T, override?: T }; + readonly workspaceTarget?: { value?: T, override?: T }; + readonly workspaceFolderTarget?: { value?: T, override?: T }; + readonly memoryTarget?: { value?: T, override?: T }; } export interface IConfigurationService { @@ -87,18 +110,9 @@ export interface IConfigurationService { updateValue(key: string, value: any, target: ConfigurationTarget): Promise; updateValue(key: string, value: any, overrides: IConfigurationOverrides, target: ConfigurationTarget, donotNotifyError?: boolean): Promise; - reloadConfiguration(folder?: IWorkspaceFolder): Promise; + inspect(key: string, overrides?: IConfigurationOverrides): IConfigurationValue; - inspect(key: string, overrides?: IConfigurationOverrides): { - default: T, - user: T, - userLocal?: T, - userRemote?: T, - workspace?: T, - workspaceFolder?: T, - memory?: T, - value: T, - }; + reloadConfiguration(folder?: IWorkspaceFolder): Promise; keys(): { default: string[]; @@ -116,6 +130,7 @@ export interface IConfigurationModel { } export interface IOverrides { + keys: string[]; contents: any; identifiers: string[]; } @@ -127,20 +142,76 @@ export interface IConfigurationData { folders: [UriComponents, IConfigurationModel][]; } -export function compare(from: IConfigurationModel, to: IConfigurationModel): { added: string[], removed: string[], updated: string[] } { - const added = to.keys.filter(key => from.keys.indexOf(key) === -1); - const removed = from.keys.filter(key => to.keys.indexOf(key) === -1); +export interface IConfigurationCompareResult { + added: string[]; + removed: string[]; + updated: string[]; + overrides: [string, string[]][]; +} + +export function compare(from: IConfigurationModel | undefined, to: IConfigurationModel | undefined): IConfigurationCompareResult { + const added = to + ? from ? to.keys.filter(key => from.keys.indexOf(key) === -1) : [...to.keys] + : []; + const removed = from + ? to ? from.keys.filter(key => to.keys.indexOf(key) === -1) : [...from.keys] + : []; const updated: string[] = []; - for (const key of from.keys) { - const value1 = getConfigurationValue(from.contents, key); - const value2 = getConfigurationValue(to.contents, key); - if (!objects.equals(value1, value2)) { - updated.push(key); + if (to && from) { + for (const key of from.keys) { + if (to.keys.indexOf(key) !== -1) { + const value1 = getConfigurationValue(from.contents, key); + const value2 = getConfigurationValue(to.contents, key); + if (!objects.equals(value1, value2)) { + updated.push(key); + } + } } } - return { added, removed, updated }; + const overrides: [string, string[]][] = []; + const byOverrideIdentifier = (overrides: IOverrides[]): IStringDictionary => { + const result: IStringDictionary = {}; + for (const override of overrides) { + for (const identifier of override.identifiers) { + result[keyFromOverrideIdentifier(identifier)] = override; + } + } + return result; + }; + const toOverridesByIdentifier: IStringDictionary = to ? byOverrideIdentifier(to.overrides) : {}; + const fromOverridesByIdentifier: IStringDictionary = from ? byOverrideIdentifier(from.overrides) : {}; + + if (Object.keys(toOverridesByIdentifier).length) { + for (const key of added) { + const override = toOverridesByIdentifier[key]; + if (override) { + overrides.push([overrideIdentifierFromKey(key), override.keys]); + } + } + } + if (Object.keys(fromOverridesByIdentifier).length) { + for (const key of removed) { + const override = fromOverridesByIdentifier[key]; + if (override) { + overrides.push([overrideIdentifierFromKey(key), override.keys]); + } + } + } + + if (Object.keys(toOverridesByIdentifier).length && Object.keys(fromOverridesByIdentifier).length) { + for (const key of updated) { + const fromOverride = fromOverridesByIdentifier[key]; + const toOverride = toOverridesByIdentifier[key]; + if (fromOverride && toOverride) { + const result = compare({ contents: fromOverride.contents, keys: fromOverride.keys, overrides: [] }, { contents: toOverride.contents, keys: toOverride.keys, overrides: [] }); + overrides.push([overrideIdentifierFromKey(key), [...result.added, ...result.removed, ...result.updated]]); + } + } + } + + return { added, removed, updated, overrides }; } export function toOverrides(raw: any, conflictReporter: (message: string) => void): IOverrides[] { @@ -156,6 +227,7 @@ export function toOverrides(raw: any, conflictReporter: (message: string) => voi } overrides.push({ identifiers: [overrideIdentifierFromKey(key).trim()], + keys: Object.keys(overrideRaw), contents: toValuesTree(overrideRaw, conflictReporter) }); } @@ -290,10 +362,10 @@ export function getMigratedSettingValue(configurationService: IConfigurationS const legacySetting = configurationService.inspect(legacySettingName); if (typeof setting.user !== 'undefined' || typeof setting.workspace !== 'undefined' || typeof setting.workspaceFolder !== 'undefined') { - return setting.value; + return setting.value!; } else if (typeof legacySetting.user !== 'undefined' || typeof legacySetting.workspace !== 'undefined' || typeof legacySetting.workspaceFolder !== 'undefined') { - return legacySetting.value; + return legacySetting.value!; } else { - return setting.default; + return setting.default!; } } diff --git a/src/vs/platform/configuration/common/configurationModels.ts b/src/vs/platform/configuration/common/configurationModels.ts index a2046fff15..32e25fb4f6 100644 --- a/src/vs/platform/configuration/common/configurationModels.ts +++ b/src/vs/platform/configuration/common/configurationModels.ts @@ -4,13 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import * as json from 'vs/base/common/json'; -import { ResourceMap } from 'vs/base/common/map'; +import { ResourceMap, values, getOrSet } from 'vs/base/common/map'; import * as arrays from 'vs/base/common/arrays'; import * as types from 'vs/base/common/types'; import * as objects from 'vs/base/common/objects'; import { URI, UriComponents } from 'vs/base/common/uri'; import { OVERRIDE_PROPERTY_PATTERN, ConfigurationScope, IConfigurationRegistry, Extensions, IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry'; -import { IOverrides, overrideIdentifierFromKey, addToValueTree, toValuesTree, IConfigurationModel, getConfigurationValue, IConfigurationOverrides, IConfigurationData, getDefaultValues, getConfigurationKeys, IConfigurationChangeEvent, ConfigurationTarget, removeFromValueTree, toOverrides } from 'vs/platform/configuration/common/configuration'; +import { IOverrides, overrideIdentifierFromKey, addToValueTree, toValuesTree, IConfigurationModel, getConfigurationValue, IConfigurationOverrides, IConfigurationData, getDefaultValues, getConfigurationKeys, removeFromValueTree, toOverrides, IConfigurationValue, ConfigurationTarget, compare, IConfigurationChangeEvent, IConfigurationChange } from 'vs/platform/configuration/common/configuration'; import { Workspace } from 'vs/platform/workspace/common/workspace'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -45,6 +45,22 @@ export class ConfigurationModel implements IConfigurationModel { return section ? getConfigurationValue(this.contents, section) : this.contents; } + getOverrideValue(section: string | undefined, overrideIdentifier: string): V | undefined { + const overrideContents = this.getContentsForOverrideIdentifer(overrideIdentifier); + return overrideContents + ? section ? getConfigurationValue(overrideContents, section) : overrideContents + : undefined; + } + + getKeysForOverrideIdentifier(identifier: string): string[] { + for (const override of this.overrides) { + if (override.identifiers.indexOf(identifier) !== -1) { + return override.keys; + } + } + return []; + } + override(identifier: string): ConfigurationModel { const overrideContents = this.getContentsForOverrideIdentifer(identifier); @@ -186,7 +202,8 @@ export class DefaultConfigurationModel extends ConfigurationModel { if (OVERRIDE_PROPERTY_PATTERN.test(key)) { overrides.push({ identifiers: [overrideIdentifierFromKey(key).trim()], - contents: toValuesTree(contents[key], message => console.error(`Conflict in default settings file: ${message}`)) + keys: Object.keys(contents[key]), + contents: toValuesTree(contents[key], message => console.error(`Conflict in default settings file: ${message}`)), }); } } @@ -362,28 +379,37 @@ export class Configuration { } } - inspect(key: string, overrides: IConfigurationOverrides, workspace: Workspace | undefined): { - default: C, - user: C, - userLocal?: C, - userRemote?: C, - workspace?: C, - workspaceFolder?: C - memory?: C - value: C, - } { + inspect(key: string, overrides: IConfigurationOverrides, workspace: Workspace | undefined): IConfigurationValue { const consolidateConfigurationModel = this.getConsolidateConfigurationModel(overrides, workspace); const folderConfigurationModel = this.getFolderConfigurationModelForResource(overrides.resource, workspace); const memoryConfigurationModel = overrides.resource ? this._memoryConfigurationByResource.get(overrides.resource) || this._memoryConfiguration : this._memoryConfiguration; + + const defaultValue = overrides.overrideIdentifier ? this._defaultConfiguration.freeze().override(overrides.overrideIdentifier).getValue(key) : this._defaultConfiguration.freeze().getValue(key); + const userValue = overrides.overrideIdentifier ? this.userConfiguration.freeze().override(overrides.overrideIdentifier).getValue(key) : this.userConfiguration.freeze().getValue(key); + const userLocalValue = overrides.overrideIdentifier ? this.localUserConfiguration.freeze().override(overrides.overrideIdentifier).getValue(key) : this.localUserConfiguration.freeze().getValue(key); + const userRemoteValue = overrides.overrideIdentifier ? this.remoteUserConfiguration.freeze().override(overrides.overrideIdentifier).getValue(key) : this.remoteUserConfiguration.freeze().getValue(key); + const workspaceValue = workspace ? overrides.overrideIdentifier ? this._workspaceConfiguration.freeze().override(overrides.overrideIdentifier).getValue(key) : this._workspaceConfiguration.freeze().getValue(key) : undefined; //Check on workspace exists or not because _workspaceConfiguration is never null + const workspaceFolderValue = folderConfigurationModel ? overrides.overrideIdentifier ? folderConfigurationModel.freeze().override(overrides.overrideIdentifier).getValue(key) : folderConfigurationModel.freeze().getValue(key) : undefined; + const memoryValue = overrides.overrideIdentifier ? memoryConfigurationModel.override(overrides.overrideIdentifier).getValue(key) : memoryConfigurationModel.getValue(key); + const value = consolidateConfigurationModel.getValue(key); + return { - default: overrides.overrideIdentifier ? this._defaultConfiguration.freeze().override(overrides.overrideIdentifier).getValue(key) : this._defaultConfiguration.freeze().getValue(key), - user: overrides.overrideIdentifier ? this.userConfiguration.freeze().override(overrides.overrideIdentifier).getValue(key) : this.userConfiguration.freeze().getValue(key), - userLocal: overrides.overrideIdentifier ? this.localUserConfiguration.freeze().override(overrides.overrideIdentifier).getValue(key) : this.localUserConfiguration.freeze().getValue(key), - userRemote: overrides.overrideIdentifier ? this.remoteUserConfiguration.freeze().override(overrides.overrideIdentifier).getValue(key) : this.remoteUserConfiguration.freeze().getValue(key), - workspace: workspace ? overrides.overrideIdentifier ? this._workspaceConfiguration.freeze().override(overrides.overrideIdentifier).getValue(key) : this._workspaceConfiguration.freeze().getValue(key) : undefined, //Check on workspace exists or not because _workspaceConfiguration is never null - workspaceFolder: folderConfigurationModel ? overrides.overrideIdentifier ? folderConfigurationModel.freeze().override(overrides.overrideIdentifier).getValue(key) : folderConfigurationModel.freeze().getValue(key) : undefined, - memory: overrides.overrideIdentifier ? memoryConfigurationModel.override(overrides.overrideIdentifier).getValue(key) : memoryConfigurationModel.getValue(key), - value: consolidateConfigurationModel.getValue(key) + default: defaultValue, + user: userValue, + userLocal: userLocalValue, + userRemote: userRemoteValue, + workspace: workspaceValue, + workspaceFolder: workspaceFolderValue, + memory: memoryValue, + value, + + defaultTarget: defaultValue !== undefined ? { value: this._defaultConfiguration.freeze().getValue(key), override: overrides.overrideIdentifier ? this._defaultConfiguration.freeze().getOverrideValue(key, overrides.overrideIdentifier) : undefined } : undefined, + userTarget: userValue !== undefined ? { value: this.userConfiguration.freeze().getValue(key), override: overrides.overrideIdentifier ? this.userConfiguration.freeze().getOverrideValue(key, overrides.overrideIdentifier) : undefined } : undefined, + userLocalTarget: userLocalValue !== undefined ? { value: this.localUserConfiguration.freeze().getValue(key), override: overrides.overrideIdentifier ? this.localUserConfiguration.freeze().getOverrideValue(key, overrides.overrideIdentifier) : undefined } : undefined, + userRemoteTarget: userRemoteValue !== undefined ? { value: this.remoteUserConfiguration.freeze().getValue(key), override: overrides.overrideIdentifier ? this.remoteUserConfiguration.freeze().getOverrideValue(key, overrides.overrideIdentifier) : undefined } : undefined, + workspaceTarget: workspaceValue !== undefined ? { value: this._workspaceConfiguration.freeze().getValue(key), override: overrides.overrideIdentifier ? this._workspaceConfiguration.freeze().getOverrideValue(key, overrides.overrideIdentifier) : undefined } : undefined, + workspaceFolderTarget: workspaceFolderValue !== undefined ? { value: folderConfigurationModel?.freeze().getValue(key), override: overrides.overrideIdentifier ? folderConfigurationModel?.freeze().getOverrideValue(key, overrides.overrideIdentifier) : undefined } : undefined, + memoryTarget: memoryValue !== undefined ? { value: memoryConfigurationModel.getValue(key), override: overrides.overrideIdentifier ? memoryConfigurationModel.getOverrideValue(key, overrides.overrideIdentifier) : undefined } : undefined, }; } @@ -438,6 +464,71 @@ export class Configuration { this._foldersConsolidatedConfigurations.delete(resource); } + compareAndUpdateDefaultConfiguration(defaults: ConfigurationModel, keys: string[]): IConfigurationChange { + const overrides: [string, string[]][] = keys + .filter(key => OVERRIDE_PROPERTY_PATTERN.test(key)) + .map(key => { + const overrideIdentifier = overrideIdentifierFromKey(key); + const fromKeys = this._defaultConfiguration.getKeysForOverrideIdentifier(overrideIdentifier); + const toKeys = defaults.getKeysForOverrideIdentifier(overrideIdentifier); + const keys = [ + ...toKeys.filter(key => fromKeys.indexOf(key) === -1), + ...fromKeys.filter(key => toKeys.indexOf(key) === -1), + ...fromKeys.filter(key => !objects.equals(this._defaultConfiguration.override(overrideIdentifier).getValue(key), defaults.override(overrideIdentifier).getValue(key))) + ]; + return [overrideIdentifier, keys]; + }); + this.updateDefaultConfiguration(defaults); + return { keys, overrides }; + } + + compareAndUpdateLocalUserConfiguration(user: ConfigurationModel): IConfigurationChange { + const { added, updated, removed, overrides } = compare(this.localUserConfiguration, user); + const keys = [...added, ...updated, ...removed]; + if (keys.length) { + this.updateLocalUserConfiguration(user); + } + return { keys, overrides }; + } + + compareAndUpdateRemoteUserConfiguration(user: ConfigurationModel): IConfigurationChange { + const { added, updated, removed, overrides } = compare(this.remoteUserConfiguration, user); + let keys = [...added, ...updated, ...removed]; + if (keys.length) { + this.updateRemoteUserConfiguration(user); + } + return { keys, overrides }; + } + + compareAndUpdateWorkspaceConfiguration(workspaceConfiguration: ConfigurationModel): IConfigurationChange { + const { added, updated, removed, overrides } = compare(this.workspaceConfiguration, workspaceConfiguration); + let keys = [...added, ...updated, ...removed]; + if (keys.length) { + this.updateWorkspaceConfiguration(workspaceConfiguration); + } + return { keys, overrides }; + } + + compareAndUpdateFolderConfiguration(resource: URI, folderConfiguration: ConfigurationModel): IConfigurationChange { + const currentFolderConfiguration = this.folderConfigurations.get(resource); + const { added, updated, removed, overrides } = compare(currentFolderConfiguration, folderConfiguration); + let keys = [...added, ...updated, ...removed]; + if (keys.length) { + this.updateFolderConfiguration(resource, folderConfiguration); + } + return { keys, overrides }; + } + + compareAndDeleteFolderConfiguration(folder: URI): IConfigurationChange { + const folderConfig = this.folderConfigurations.get(folder); + if (!folderConfig) { + throw new Error('Unknown folder'); + } + this.deleteFolderConfiguration(folder); + const { added, updated, removed, overrides } = compare(folderConfig, undefined); + return { keys: [...added, ...updated, ...removed], overrides }; + } + get defaults(): ConfigurationModel { return this._defaultConfiguration; } @@ -554,131 +645,118 @@ export class Configuration { }; } - allKeys(workspace: Workspace | undefined): string[] { - let keys = this.keys(workspace); - let all = [...keys.default]; - const addKeys = (keys: string[]) => { - for (const key of keys) { - if (all.indexOf(key) === -1) { - all.push(key); - } - } - }; - addKeys(keys.user); - addKeys(keys.workspace); - for (const resource of this.folderConfigurations.keys()) { - addKeys(this.folderConfigurations.get(resource)!.keys); - } - return all; + allKeys(): string[] { + const keys: Set = new Set(); + this._defaultConfiguration.freeze().keys.forEach(key => keys.add(key)); + this.userConfiguration.freeze().keys.forEach(key => keys.add(key)); + this._workspaceConfiguration.freeze().keys.forEach(key => keys.add(key)); + this._folderConfigurations.forEach(folderConfiguraiton => folderConfiguraiton.freeze().keys.forEach(key => keys.add(key))); + return values(keys); } + + protected getAllKeysForOverrideIdentifier(overrideIdentifier: string): string[] { + const keys: Set = new Set(); + this._defaultConfiguration.getKeysForOverrideIdentifier(overrideIdentifier).forEach(key => keys.add(key)); + this.userConfiguration.getKeysForOverrideIdentifier(overrideIdentifier).forEach(key => keys.add(key)); + this._workspaceConfiguration.getKeysForOverrideIdentifier(overrideIdentifier).forEach(key => keys.add(key)); + this._folderConfigurations.forEach(folderConfiguraiton => folderConfiguraiton.getKeysForOverrideIdentifier(overrideIdentifier).forEach(key => keys.add(key))); + return values(keys); + } + + static parse(data: IConfigurationData): Configuration { + const defaultConfiguration = this.parseConfigurationModel(data.defaults); + const userConfiguration = this.parseConfigurationModel(data.user); + const workspaceConfiguration = this.parseConfigurationModel(data.workspace); + const folders: ResourceMap = data.folders.reduce((result, value) => { + result.set(URI.revive(value[0]), this.parseConfigurationModel(value[1])); + return result; + }, new ResourceMap()); + return new Configuration(defaultConfiguration, userConfiguration, new ConfigurationModel(), workspaceConfiguration, folders, new ConfigurationModel(), new ResourceMap(), false); + } + + private static parseConfigurationModel(model: IConfigurationModel): ConfigurationModel { + return new ConfigurationModel(model.contents, model.keys, model.overrides).freeze(); + } + } -export class AbstractConfigurationChangeEvent { +export function mergeChanges(...changes: IConfigurationChange[]): IConfigurationChange { + if (changes.length === 0) { + return { keys: [], overrides: [] }; + } + if (changes.length === 1) { + return changes[0]; + } + const keysSet = new Set(); + const overridesMap = new Map>(); + for (const change of changes) { + change.keys.forEach(key => keysSet.add(key)); + change.overrides.forEach(([identifier, keys]) => { + const result = getOrSet(overridesMap, identifier, new Set()); + keys.forEach(key => result.add(key)); + }); + } + const overrides: [string, string[]][] = []; + overridesMap.forEach((keys, identifier) => overrides.push([identifier, values(keys)])); + return { keys: values(keysSet), overrides }; +} - protected doesConfigurationContains(configuration: ConfigurationModel, config: string): boolean { - let changedKeysTree = configuration.contents; - let requestedTree = toValuesTree({ [config]: true }, () => { }); +export class ConfigurationChangeEvent implements IConfigurationChangeEvent { + + private readonly affectedKeysTree: any; + readonly affectedKeys: string[]; + source!: ConfigurationTarget; + sourceConfig: any; + + constructor(readonly change: IConfigurationChange, private readonly previous: { workspace?: Workspace, data: IConfigurationData } | undefined, private readonly currentConfiguraiton: Configuration, private readonly currentWorkspace?: Workspace) { + const keysSet = new Set(); + change.keys.forEach(key => keysSet.add(key)); + change.overrides.forEach(([, keys]) => keys.forEach(key => keysSet.add(key))); + this.affectedKeys = values(keysSet); + + const configurationModel = new ConfigurationModel(); + this.affectedKeys.forEach(key => configurationModel.setValue(key, {})); + this.affectedKeysTree = configurationModel.contents; + } + + private _previousConfiguration: Configuration | undefined = undefined; + get previousConfiguration(): Configuration | undefined { + if (!this._previousConfiguration && this.previous) { + this._previousConfiguration = Configuration.parse(this.previous.data); + } + return this._previousConfiguration; + } + + affectsConfiguration(section: string, overrides?: IConfigurationOverrides): boolean { + if (this.doesAffectedKeysTreeContains(this.affectedKeysTree, section)) { + if (overrides) { + const value1 = this.previousConfiguration ? this.previousConfiguration.getValue(section, overrides, this.previous?.workspace) : undefined; + const value2 = this.currentConfiguraiton.getValue(section, overrides, this.currentWorkspace); + return !objects.equals(value1, value2); + } + return true; + } + return false; + } + + private doesAffectedKeysTreeContains(affectedKeysTree: any, section: string): boolean { + let requestedTree = toValuesTree({ [section]: true }, () => { }); let key; while (typeof requestedTree === 'object' && (key = Object.keys(requestedTree)[0])) { // Only one key should present, since we added only one property - changedKeysTree = changedKeysTree[key]; - if (!changedKeysTree) { + affectedKeysTree = affectedKeysTree[key]; + if (!affectedKeysTree) { return false; // Requested tree is not found } requestedTree = requestedTree[key]; } return true; } - - protected updateKeys(configuration: ConfigurationModel, keys: string[], resource?: URI): void { - for (const key of keys) { - configuration.setValue(key, {}); - } - } } -export class ConfigurationChangeEvent extends AbstractConfigurationChangeEvent implements IConfigurationChangeEvent { - - private _source: ConfigurationTarget; - private _sourceConfig: any; - - constructor( - private _changedConfiguration: ConfigurationModel = new ConfigurationModel(), - private _changedConfigurationByResource: ResourceMap = new ResourceMap()) { - super(); - this._source = ConfigurationTarget.DEFAULT; +export class AllKeysConfigurationChangeEvent extends ConfigurationChangeEvent { + constructor(configuration: Configuration, workspace: Workspace, public source: ConfigurationTarget, public sourceConfig: any) { + super({ keys: configuration.allKeys(), overrides: [] }, undefined, configuration, workspace); } - get changedConfiguration(): IConfigurationModel { - return this._changedConfiguration; - } - - get changedConfigurationByResource(): ResourceMap { - return this._changedConfigurationByResource; - } - - change(event: ConfigurationChangeEvent): ConfigurationChangeEvent; - change(keys: string[], resource?: URI): ConfigurationChangeEvent; - change(arg1: any, arg2?: any): ConfigurationChangeEvent { - if (arg1 instanceof ConfigurationChangeEvent) { - this._changedConfiguration = this._changedConfiguration.merge(arg1._changedConfiguration); - for (const resource of arg1._changedConfigurationByResource.keys()) { - let changedConfigurationByResource = this.getOrSetChangedConfigurationForResource(resource); - changedConfigurationByResource = changedConfigurationByResource.merge(arg1._changedConfigurationByResource.get(resource)!); - this._changedConfigurationByResource.set(resource, changedConfigurationByResource); - } - } else { - this.changeWithKeys(arg1, arg2); - } - return this; - } - - telemetryData(source: ConfigurationTarget, sourceConfig: any): ConfigurationChangeEvent { - this._source = source; - this._sourceConfig = sourceConfig; - return this; - } - - get affectedKeys(): string[] { - const keys = [...this._changedConfiguration.keys]; - this._changedConfigurationByResource.forEach(model => keys.push(...model.keys)); - return arrays.distinct(keys); - } - - get source(): ConfigurationTarget { - return this._source; - } - - get sourceConfig(): any { - return this._sourceConfig; - } - - affectsConfiguration(config: string, resource?: URI): boolean { - let configurationModelsToSearch: ConfigurationModel[] = [this._changedConfiguration]; - - if (resource) { - let model = this._changedConfigurationByResource.get(resource); - if (model) { - configurationModelsToSearch.push(model); - } - } else { - configurationModelsToSearch.push(...this._changedConfigurationByResource.values()); - } - - return configurationModelsToSearch.some(configuration => this.doesConfigurationContains(configuration, config)); - } - - private changeWithKeys(keys: string[], resource?: URI): void { - let changedConfiguration = resource ? this.getOrSetChangedConfigurationForResource(resource) : this._changedConfiguration; - this.updateKeys(changedConfiguration, keys); - } - - private getOrSetChangedConfigurationForResource(resource: URI): ConfigurationModel { - let changedConfigurationByResource = this._changedConfigurationByResource.get(resource); - if (!changedConfigurationByResource) { - changedConfigurationByResource = new ConfigurationModel(); - this._changedConfigurationByResource.set(resource, changedConfigurationByResource); - } - return changedConfigurationByResource; - } } diff --git a/src/vs/platform/configuration/node/configurationService.ts b/src/vs/platform/configuration/node/configurationService.ts index fbd923f467..f0e6bd5b71 100644 --- a/src/vs/platform/configuration/node/configurationService.ts +++ b/src/vs/platform/configuration/node/configurationService.ts @@ -6,8 +6,8 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry'; import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; -import { IConfigurationService, IConfigurationChangeEvent, IConfigurationOverrides, ConfigurationTarget, compare, isConfigurationOverrides, IConfigurationData } from 'vs/platform/configuration/common/configuration'; -import { DefaultConfigurationModel, Configuration, ConfigurationChangeEvent, ConfigurationModel, ConfigurationModelParser } from 'vs/platform/configuration/common/configurationModels'; +import { IConfigurationService, IConfigurationChangeEvent, IConfigurationOverrides, ConfigurationTarget, isConfigurationOverrides, IConfigurationData, IConfigurationValue, IConfigurationChange } from 'vs/platform/configuration/common/configuration'; +import { DefaultConfigurationModel, Configuration, ConfigurationModel, ConfigurationModelParser, ConfigurationChangeEvent } from 'vs/platform/configuration/common/configurationModels'; import { Event, Emitter } from 'vs/base/common/event'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { ConfigWatcher } from 'vs/base/node/config'; @@ -79,13 +79,7 @@ export class ConfigurationService extends Disposable implements IConfigurationSe return Promise.reject(new Error('not supported')); } - inspect(key: string): { - default: T, - user: T, - workspace?: T, - workspaceFolder?: T - value: T - } { + inspect(key: string): IConfigurationValue { return this.configuration.inspect(key, {}, undefined); } @@ -109,21 +103,22 @@ export class ConfigurationService extends Disposable implements IConfigurationSe } private onDidChangeUserConfiguration(userConfigurationModel: ConfigurationModel): void { - const { added, updated, removed } = compare(this.configuration.localUserConfiguration, userConfigurationModel); - const changedKeys = [...added, ...updated, ...removed]; - if (changedKeys.length) { - this.configuration.updateLocalUserConfiguration(userConfigurationModel); - this.trigger(changedKeys, ConfigurationTarget.USER); - } + const previous = this.configuration.toData(); + const change = this.configuration.compareAndUpdateLocalUserConfiguration(userConfigurationModel); + this.trigger(change, previous, ConfigurationTarget.USER); } private onDidDefaultConfigurationChange(keys: string[]): void { - this.configuration.updateDefaultConfiguration(new DefaultConfigurationModel()); - this.trigger(keys, ConfigurationTarget.DEFAULT); + const previous = this.configuration.toData(); + const change = this.configuration.compareAndUpdateDefaultConfiguration(new DefaultConfigurationModel(), keys); + this.trigger(change, previous, ConfigurationTarget.DEFAULT); } - private trigger(keys: string[], source: ConfigurationTarget): void { - this._onDidChangeConfiguration.fire(new ConfigurationChangeEvent().change(keys).telemetryData(source, this.getTargetConfiguration(source))); + private trigger(configurationChange: IConfigurationChange, previous: IConfigurationData, source: ConfigurationTarget): void { + const event = new ConfigurationChangeEvent(configurationChange, { data: previous }, this.configuration); + event.source = source; + event.sourceConfig = this.getTargetConfiguration(source); + this._onDidChangeConfiguration.fire(event); } private getTargetConfiguration(target: ConfigurationTarget): any { @@ -135,4 +130,4 @@ export class ConfigurationService extends Disposable implements IConfigurationSe } return {}; } -} \ No newline at end of file +} diff --git a/src/vs/platform/configuration/test/common/configuration.test.ts b/src/vs/platform/configuration/test/common/configuration.test.ts index 4ff1a7973f..fa8e9771f6 100644 --- a/src/vs/platform/configuration/test/common/configuration.test.ts +++ b/src/vs/platform/configuration/test/common/configuration.test.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; import { merge, removeFromValueTree } from 'vs/platform/configuration/common/configuration'; +import { mergeChanges } from 'vs/platform/configuration/common/configurationModels'; suite('Configuration', () => { @@ -104,4 +105,43 @@ suite('Configuration', () => { assert.deepEqual(target, { 'a': { 'b': { 'd': 1 } } }); }); -}); \ No newline at end of file +}); + +suite('Configuration Changes: Merge', () => { + + test('merge only keys', () => { + const actual = mergeChanges({ keys: ['a', 'b'], overrides: [] }, { keys: ['c', 'd'], overrides: [] }); + assert.deepEqual(actual, { keys: ['a', 'b', 'c', 'd'], overrides: [] }); + }); + + test('merge only keys with duplicates', () => { + const actual = mergeChanges({ keys: ['a', 'b'], overrides: [] }, { keys: ['c', 'd'], overrides: [] }, { keys: ['a', 'd', 'e'], overrides: [] }); + assert.deepEqual(actual, { keys: ['a', 'b', 'c', 'd', 'e'], overrides: [] }); + }); + + test('merge only overrides', () => { + const actual = mergeChanges({ keys: [], overrides: [['a', ['1', '2']]] }, { keys: [], overrides: [['b', ['3', '4']]] }); + assert.deepEqual(actual, { keys: [], overrides: [['a', ['1', '2']], ['b', ['3', '4']]] }); + }); + + test('merge only overrides with duplicates', () => { + const actual = mergeChanges({ keys: [], overrides: [['a', ['1', '2']], ['b', ['5', '4']]] }, { keys: [], overrides: [['b', ['3', '4']]] }, { keys: [], overrides: [['c', ['1', '4']], ['a', ['2', '3']]] }); + assert.deepEqual(actual, { keys: [], overrides: [['a', ['1', '2', '3']], ['b', ['5', '4', '3']], ['c', ['1', '4']]] }); + }); + + test('merge', () => { + const actual = mergeChanges({ keys: ['b', 'b'], overrides: [['a', ['1', '2']], ['b', ['5', '4']]] }, { keys: ['b'], overrides: [['b', ['3', '4']]] }, { keys: ['c', 'a'], overrides: [['c', ['1', '4']], ['a', ['2', '3']]] }); + assert.deepEqual(actual, { keys: ['b', 'c', 'a'], overrides: [['a', ['1', '2', '3']], ['b', ['5', '4', '3']], ['c', ['1', '4']]] }); + }); + + test('merge single change', () => { + const actual = mergeChanges({ keys: ['b', 'b'], overrides: [['a', ['1', '2']], ['b', ['5', '4']]] }); + assert.deepEqual(actual, { keys: ['b', 'b'], overrides: [['a', ['1', '2']], ['b', ['5', '4']]] }); + }); + + test('merge no changes', () => { + const actual = mergeChanges(); + assert.deepEqual(actual, { keys: [], overrides: [] }); + }); + +}); diff --git a/src/vs/platform/configuration/test/common/configurationModels.test.ts b/src/vs/platform/configuration/test/common/configurationModels.test.ts index 4d8d25f090..bc4892d29f 100644 --- a/src/vs/platform/configuration/test/common/configurationModels.test.ts +++ b/src/vs/platform/configuration/test/common/configurationModels.test.ts @@ -3,10 +3,13 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { ConfigurationModel, DefaultConfigurationModel, ConfigurationChangeEvent, ConfigurationModelParser, Configuration } from 'vs/platform/configuration/common/configurationModels'; +import { ConfigurationModel, DefaultConfigurationModel, ConfigurationChangeEvent, ConfigurationModelParser, Configuration, mergeChanges, AllKeysConfigurationChangeEvent } from 'vs/platform/configuration/common/configurationModels'; import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; import { URI } from 'vs/base/common/uri'; +import { Workspace, WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; +import { join } from 'vs/base/common/path'; +import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; suite('ConfigurationModel', () => { @@ -103,7 +106,7 @@ suite('ConfigurationModel', () => { test('get overriding configuration model for an existing identifier', () => { let testObject = new ConfigurationModel( { 'a': { 'b': 1 }, 'f': 1 }, [], - [{ identifiers: ['c'], contents: { 'a': { 'd': 1 } } }]); + [{ identifiers: ['c'], contents: { 'a': { 'd': 1 } }, keys: ['a'] }]); assert.deepEqual(testObject.override('c').contents, { 'a': { 'b': 1, 'd': 1 }, 'f': 1 }); }); @@ -111,7 +114,7 @@ suite('ConfigurationModel', () => { test('get overriding configuration model for an identifier that does not exist', () => { let testObject = new ConfigurationModel( { 'a': { 'b': 1 }, 'f': 1 }, [], - [{ identifiers: ['c'], contents: { 'a': { 'd': 1 } } }]); + [{ identifiers: ['c'], contents: { 'a': { 'd': 1 } }, keys: ['a'] }]); assert.deepEqual(testObject.override('xyz').contents, { 'a': { 'b': 1 }, 'f': 1 }); }); @@ -119,7 +122,7 @@ suite('ConfigurationModel', () => { test('get overriding configuration when one of the keys does not exist in base', () => { let testObject = new ConfigurationModel( { 'a': { 'b': 1 }, 'f': 1 }, [], - [{ identifiers: ['c'], contents: { 'a': { 'd': 1 }, 'g': 1 } }]); + [{ identifiers: ['c'], contents: { 'a': { 'd': 1 }, 'g': 1 }, keys: ['a', 'g'] }]); assert.deepEqual(testObject.override('c').contents, { 'a': { 'b': 1, 'd': 1 }, 'f': 1, 'g': 1 }); }); @@ -127,7 +130,7 @@ suite('ConfigurationModel', () => { test('get overriding configuration when one of the key in base is not of object type', () => { let testObject = new ConfigurationModel( { 'a': { 'b': 1 }, 'f': 1 }, [], - [{ identifiers: ['c'], contents: { 'a': { 'd': 1 }, 'f': { 'g': 1 } } }]); + [{ identifiers: ['c'], contents: { 'a': { 'd': 1 }, 'f': { 'g': 1 } }, keys: ['a', 'f'] }]); assert.deepEqual(testObject.override('c').contents, { 'a': { 'b': 1, 'd': 1 }, 'f': { 'g': 1 } }); }); @@ -135,7 +138,7 @@ suite('ConfigurationModel', () => { test('get overriding configuration when one of the key in overriding contents is not of object type', () => { let testObject = new ConfigurationModel( { 'a': { 'b': 1 }, 'f': { 'g': 1 } }, [], - [{ identifiers: ['c'], contents: { 'a': { 'd': 1 }, 'f': 1 } }]); + [{ identifiers: ['c'], contents: { 'a': { 'd': 1 }, 'f': 1 }, keys: ['a', 'f'] }]); assert.deepEqual(testObject.override('c').contents, { 'a': { 'b': 1, 'd': 1 }, 'f': 1 }); }); @@ -143,7 +146,7 @@ suite('ConfigurationModel', () => { test('get overriding configuration if the value of overriding identifier is not object', () => { let testObject = new ConfigurationModel( { 'a': { 'b': 1 }, 'f': { 'g': 1 } }, [], - [{ identifiers: ['c'], contents: 'abc' }]); + [{ identifiers: ['c'], contents: 'abc', keys: [] }]); assert.deepEqual(testObject.override('c').contents, { 'a': { 'b': 1 }, 'f': { 'g': 1 } }); }); @@ -151,7 +154,7 @@ suite('ConfigurationModel', () => { test('get overriding configuration if the value of overriding identifier is an empty object', () => { let testObject = new ConfigurationModel( { 'a': { 'b': 1 }, 'f': { 'g': 1 } }, [], - [{ identifiers: ['c'], contents: {} }]); + [{ identifiers: ['c'], contents: {}, keys: [] }]); assert.deepEqual(testObject.override('c').contents, { 'a': { 'b': 1 }, 'f': { 'g': 1 } }); }); @@ -176,34 +179,34 @@ suite('ConfigurationModel', () => { }); test('simple merge overrides', () => { - let base = new ConfigurationModel({ 'a': { 'b': 1 } }, ['a.b'], [{ identifiers: ['c'], contents: { 'a': 2 } }]); - let add = new ConfigurationModel({ 'a': { 'b': 2 } }, ['a.b'], [{ identifiers: ['c'], contents: { 'b': 2 } }]); + let base = new ConfigurationModel({ 'a': { 'b': 1 } }, ['a.b'], [{ identifiers: ['c'], contents: { 'a': 2 }, keys: ['a'] }]); + let add = new ConfigurationModel({ 'a': { 'b': 2 } }, ['a.b'], [{ identifiers: ['c'], contents: { 'b': 2 }, keys: ['b'] }]); let result = base.merge(add); assert.deepEqual(result.contents, { 'a': { 'b': 2 } }); - assert.deepEqual(result.overrides, [{ identifiers: ['c'], contents: { 'a': 2, 'b': 2 } }]); + assert.deepEqual(result.overrides, [{ identifiers: ['c'], contents: { 'a': 2, 'b': 2 }, keys: ['a'] }]); assert.deepEqual(result.override('c').contents, { 'a': 2, 'b': 2 }); assert.deepEqual(result.keys, ['a.b']); }); test('recursive merge overrides', () => { - let base = new ConfigurationModel({ 'a': { 'b': 1 }, 'f': 1 }, ['a.b', 'f'], [{ identifiers: ['c'], contents: { 'a': { 'd': 1 } } }]); - let add = new ConfigurationModel({ 'a': { 'b': 2 } }, ['a.b'], [{ identifiers: ['c'], contents: { 'a': { 'e': 2 } } }]); + let base = new ConfigurationModel({ 'a': { 'b': 1 }, 'f': 1 }, ['a.b', 'f'], [{ identifiers: ['c'], contents: { 'a': { 'd': 1 } }, keys: ['a'] }]); + let add = new ConfigurationModel({ 'a': { 'b': 2 } }, ['a.b'], [{ identifiers: ['c'], contents: { 'a': { 'e': 2 } }, keys: ['a'] }]); let result = base.merge(add); assert.deepEqual(result.contents, { 'a': { 'b': 2 }, 'f': 1 }); - assert.deepEqual(result.overrides, [{ identifiers: ['c'], contents: { 'a': { 'd': 1, 'e': 2 } } }]); + assert.deepEqual(result.overrides, [{ identifiers: ['c'], contents: { 'a': { 'd': 1, 'e': 2 } }, keys: ['a'] }]); assert.deepEqual(result.override('c').contents, { 'a': { 'b': 2, 'd': 1, 'e': 2 }, 'f': 1 }); assert.deepEqual(result.keys, ['a.b', 'f']); }); test('merge overrides when frozen', () => { - let model1 = new ConfigurationModel({ 'a': { 'b': 1 }, 'f': 1 }, ['a.b', 'f'], [{ identifiers: ['c'], contents: { 'a': { 'd': 1 } } }]).freeze(); - let model2 = new ConfigurationModel({ 'a': { 'b': 2 } }, ['a.b'], [{ identifiers: ['c'], contents: { 'a': { 'e': 2 } } }]).freeze(); + let model1 = new ConfigurationModel({ 'a': { 'b': 1 }, 'f': 1 }, ['a.b', 'f'], [{ identifiers: ['c'], contents: { 'a': { 'd': 1 } }, keys: ['a'] }]).freeze(); + let model2 = new ConfigurationModel({ 'a': { 'b': 2 } }, ['a.b'], [{ identifiers: ['c'], contents: { 'a': { 'e': 2 } }, keys: ['a'] }]).freeze(); let result = new ConfigurationModel().merge(model1, model2); assert.deepEqual(result.contents, { 'a': { 'b': 2 }, 'f': 1 }); - assert.deepEqual(result.overrides, [{ identifiers: ['c'], contents: { 'a': { 'd': 1, 'e': 2 } } }]); + assert.deepEqual(result.overrides, [{ identifiers: ['c'], contents: { 'a': { 'd': 1, 'e': 2 } }, keys: ['a'] }]); assert.deepEqual(result.override('c').contents, { 'a': { 'b': 2, 'd': 1, 'e': 2 }, 'f': 1 }); assert.deepEqual(result.keys, ['a.b', 'f']); }); @@ -223,7 +226,7 @@ suite('ConfigurationModel', () => { }); test('Test override gives all content merged with overrides', () => { - const testObject = new ConfigurationModel({ 'a': 1, 'c': 1 }, [], [{ identifiers: ['b'], contents: { 'a': 2 } }]); + const testObject = new ConfigurationModel({ 'a': 1, 'c': 1 }, [], [{ identifiers: ['b'], contents: { 'a': 2 }, keys: ['a'] }]); assert.deepEqual(testObject.override('b').contents, { 'a': 2, 'c': 1 }); }); @@ -360,114 +363,6 @@ suite('CustomConfigurationModel', () => { }); }); -suite('ConfigurationChangeEvent', () => { - - test('changeEvent affecting keys for all resources', () => { - let testObject = new ConfigurationChangeEvent(); - - testObject.change(['window.zoomLevel', 'workbench.editor.enablePreview', 'files', '[markdown]']); - - assert.deepEqual(testObject.affectedKeys, ['window.zoomLevel', 'workbench.editor.enablePreview', 'files', '[markdown]']); - assert.ok(testObject.affectsConfiguration('window.zoomLevel')); - assert.ok(testObject.affectsConfiguration('window')); - assert.ok(testObject.affectsConfiguration('workbench.editor.enablePreview')); - assert.ok(testObject.affectsConfiguration('workbench.editor')); - assert.ok(testObject.affectsConfiguration('workbench')); - assert.ok(testObject.affectsConfiguration('files')); - assert.ok(!testObject.affectsConfiguration('files.exclude')); - assert.ok(testObject.affectsConfiguration('[markdown]')); - }); - - test('changeEvent affecting a root key and its children', () => { - let testObject = new ConfigurationChangeEvent(); - - testObject.change(['launch', 'launch.version', 'tasks']); - - assert.deepEqual(testObject.affectedKeys, ['launch.version', 'tasks']); - assert.ok(testObject.affectsConfiguration('launch')); - assert.ok(testObject.affectsConfiguration('launch.version')); - assert.ok(testObject.affectsConfiguration('tasks')); - }); - - test('changeEvent affecting keys for resources', () => { - let testObject = new ConfigurationChangeEvent(); - - testObject.change(['window.title']); - testObject.change(['window.zoomLevel'], URI.file('file1')); - testObject.change(['workbench.editor.enablePreview'], URI.file('file2')); - testObject.change(['window.restoreFullscreen'], URI.file('file1')); - testObject.change(['window.restoreWindows'], URI.file('file2')); - - assert.deepEqual(testObject.affectedKeys, ['window.title', 'window.zoomLevel', 'window.restoreFullscreen', 'workbench.editor.enablePreview', 'window.restoreWindows']); - - assert.ok(testObject.affectsConfiguration('window.zoomLevel')); - assert.ok(testObject.affectsConfiguration('window.zoomLevel', URI.file('file1'))); - assert.ok(!testObject.affectsConfiguration('window.zoomLevel', URI.file('file2'))); - - assert.ok(testObject.affectsConfiguration('window.restoreFullscreen')); - assert.ok(testObject.affectsConfiguration('window.restoreFullscreen', URI.file('file1'))); - assert.ok(!testObject.affectsConfiguration('window.restoreFullscreen', URI.file('file2'))); - - assert.ok(testObject.affectsConfiguration('window.restoreWindows')); - assert.ok(testObject.affectsConfiguration('window.restoreWindows', URI.file('file2'))); - assert.ok(!testObject.affectsConfiguration('window.restoreWindows', URI.file('file1'))); - - assert.ok(testObject.affectsConfiguration('window.title')); - assert.ok(testObject.affectsConfiguration('window.title', URI.file('file1'))); - assert.ok(testObject.affectsConfiguration('window.title', URI.file('file2'))); - - assert.ok(testObject.affectsConfiguration('window')); - assert.ok(testObject.affectsConfiguration('window', URI.file('file1'))); - assert.ok(testObject.affectsConfiguration('window', URI.file('file2'))); - - assert.ok(testObject.affectsConfiguration('workbench.editor.enablePreview')); - assert.ok(testObject.affectsConfiguration('workbench.editor.enablePreview', URI.file('file2'))); - assert.ok(!testObject.affectsConfiguration('workbench.editor.enablePreview', URI.file('file1'))); - - assert.ok(testObject.affectsConfiguration('workbench.editor')); - assert.ok(testObject.affectsConfiguration('workbench.editor', URI.file('file2'))); - assert.ok(!testObject.affectsConfiguration('workbench.editor', URI.file('file1'))); - - assert.ok(testObject.affectsConfiguration('workbench')); - assert.ok(testObject.affectsConfiguration('workbench', URI.file('file2'))); - assert.ok(!testObject.affectsConfiguration('workbench', URI.file('file1'))); - - assert.ok(!testObject.affectsConfiguration('files')); - assert.ok(!testObject.affectsConfiguration('files', URI.file('file1'))); - assert.ok(!testObject.affectsConfiguration('files', URI.file('file2'))); - }); - - test('merging change events', () => { - let event1 = new ConfigurationChangeEvent().change(['window.zoomLevel', 'files']); - let event2 = new ConfigurationChangeEvent().change(['window.title'], URI.file('file1')).change(['[markdown]']); - - let actual = event1.change(event2); - - assert.deepEqual(actual.affectedKeys, ['window.zoomLevel', 'files', '[markdown]', 'window.title']); - - assert.ok(actual.affectsConfiguration('window.zoomLevel')); - assert.ok(actual.affectsConfiguration('window.zoomLevel', URI.file('file1'))); - assert.ok(actual.affectsConfiguration('window.zoomLevel', URI.file('file2'))); - - assert.ok(actual.affectsConfiguration('window')); - assert.ok(actual.affectsConfiguration('window', URI.file('file1'))); - assert.ok(actual.affectsConfiguration('window', URI.file('file2'))); - - assert.ok(actual.affectsConfiguration('files')); - assert.ok(actual.affectsConfiguration('files', URI.file('file1'))); - assert.ok(actual.affectsConfiguration('files', URI.file('file2'))); - - assert.ok(actual.affectsConfiguration('window.title')); - assert.ok(actual.affectsConfiguration('window.title', URI.file('file1'))); - assert.ok(!actual.affectsConfiguration('window.title', URI.file('file2'))); - - assert.ok(actual.affectsConfiguration('[markdown]')); - assert.ok(actual.affectsConfiguration('[markdown]', URI.file('file1'))); - assert.ok(actual.affectsConfiguration('[markdown]', URI.file('file2'))); - }); - -}); - suite('Configuration', () => { test('Test update value', () => { @@ -491,5 +386,552 @@ suite('Configuration', () => { assert.equal(testObject.getValue('a', {}, undefined), 2); }); + test('Test compare and update default configuration', () => { + const testObject = new Configuration(new ConfigurationModel(), new ConfigurationModel()); + testObject.updateDefaultConfiguration(toConfigurationModel({ + 'editor.lineNumbers': 'on', + })); -}); \ No newline at end of file + const actual = testObject.compareAndUpdateDefaultConfiguration(toConfigurationModel({ + 'editor.lineNumbers': 'off', + '[markdown]': { + 'editor.wordWrap': 'off' + } + }), ['editor.lineNumbers', '[markdown]']); + + assert.deepEqual(actual, { keys: ['editor.lineNumbers', '[markdown]'], overrides: [['markdown', ['editor.wordWrap']]] }); + + }); + + test('Test compare and update user configuration', () => { + const testObject = new Configuration(new ConfigurationModel(), new ConfigurationModel()); + testObject.updateLocalUserConfiguration(toConfigurationModel({ + 'editor.lineNumbers': 'off', + 'editor.fontSize': 12, + '[typescript]': { + 'editor.wordWrap': 'off' + } + })); + + const actual = testObject.compareAndUpdateLocalUserConfiguration(toConfigurationModel({ + 'editor.lineNumbers': 'on', + 'window.zoomLevel': 1, + '[typescript]': { + 'editor.wordWrap': 'on', + 'editor.insertSpaces': false + } + })); + + assert.deepEqual(actual, { keys: ['window.zoomLevel', 'editor.lineNumbers', '[typescript]', 'editor.fontSize'], overrides: [['typescript', ['editor.insertSpaces', 'editor.wordWrap']]] }); + + }); + + test('Test compare and update workspace configuration', () => { + const testObject = new Configuration(new ConfigurationModel(), new ConfigurationModel()); + testObject.updateWorkspaceConfiguration(toConfigurationModel({ + 'editor.lineNumbers': 'off', + 'editor.fontSize': 12, + '[typescript]': { + 'editor.wordWrap': 'off' + } + })); + + const actual = testObject.compareAndUpdateWorkspaceConfiguration(toConfigurationModel({ + 'editor.lineNumbers': 'on', + 'window.zoomLevel': 1, + '[typescript]': { + 'editor.wordWrap': 'on', + 'editor.insertSpaces': false + } + })); + + assert.deepEqual(actual, { keys: ['window.zoomLevel', 'editor.lineNumbers', '[typescript]', 'editor.fontSize'], overrides: [['typescript', ['editor.insertSpaces', 'editor.wordWrap']]] }); + + }); + + test('Test compare and update workspace folder configuration', () => { + const testObject = new Configuration(new ConfigurationModel(), new ConfigurationModel()); + testObject.updateFolderConfiguration(URI.file('file1'), toConfigurationModel({ + 'editor.lineNumbers': 'off', + 'editor.fontSize': 12, + '[typescript]': { + 'editor.wordWrap': 'off' + } + })); + + const actual = testObject.compareAndUpdateFolderConfiguration(URI.file('file1'), toConfigurationModel({ + 'editor.lineNumbers': 'on', + 'window.zoomLevel': 1, + '[typescript]': { + 'editor.wordWrap': 'on', + 'editor.insertSpaces': false + } + })); + + assert.deepEqual(actual, { keys: ['window.zoomLevel', 'editor.lineNumbers', '[typescript]', 'editor.fontSize'], overrides: [['typescript', ['editor.insertSpaces', 'editor.wordWrap']]] }); + + }); + + test('Test compare and deletre workspace folder configuration', () => { + const testObject = new Configuration(new ConfigurationModel(), new ConfigurationModel()); + testObject.updateFolderConfiguration(URI.file('file1'), toConfigurationModel({ + 'editor.lineNumbers': 'off', + 'editor.fontSize': 12, + '[typescript]': { + 'editor.wordWrap': 'off' + } + })); + + const actual = testObject.compareAndDeleteFolderConfiguration(URI.file('file1')); + + assert.deepEqual(actual, { keys: ['editor.lineNumbers', 'editor.fontSize', '[typescript]'], overrides: [['typescript', ['editor.wordWrap']]] }); + + }); + +}); + +suite('ConfigurationChangeEvent', () => { + + test('changeEvent affecting keys with new configuration', () => { + const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel()); + const change = configuration.compareAndUpdateLocalUserConfiguration(toConfigurationModel({ + 'window.zoomLevel': 1, + 'workbench.editor.enablePreview': false, + 'files.autoSave': 'off', + })); + let testObject = new ConfigurationChangeEvent(change, undefined, configuration); + + assert.deepEqual(testObject.affectedKeys, ['window.zoomLevel', 'workbench.editor.enablePreview', 'files.autoSave']); + + assert.ok(testObject.affectsConfiguration('window.zoomLevel')); + assert.ok(testObject.affectsConfiguration('window')); + + assert.ok(testObject.affectsConfiguration('workbench.editor.enablePreview')); + assert.ok(testObject.affectsConfiguration('workbench.editor')); + assert.ok(testObject.affectsConfiguration('workbench')); + + assert.ok(testObject.affectsConfiguration('files')); + assert.ok(testObject.affectsConfiguration('files.autoSave')); + assert.ok(!testObject.affectsConfiguration('files.exclude')); + + assert.ok(!testObject.affectsConfiguration('[markdown]')); + assert.ok(!testObject.affectsConfiguration('editor')); + }); + + test('changeEvent affecting keys when configuration changed', () => { + const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel()); + configuration.updateLocalUserConfiguration(toConfigurationModel({ + 'window.zoomLevel': 2, + 'workbench.editor.enablePreview': true, + 'files.autoSave': 'off', + })); + const data = configuration.toData(); + const change = configuration.compareAndUpdateLocalUserConfiguration(toConfigurationModel({ + 'window.zoomLevel': 1, + 'workbench.editor.enablePreview': false, + 'files.autoSave': 'off', + })); + let testObject = new ConfigurationChangeEvent(change, { data }, configuration); + + assert.deepEqual(testObject.affectedKeys, ['window.zoomLevel', 'workbench.editor.enablePreview']); + + assert.ok(testObject.affectsConfiguration('window.zoomLevel')); + assert.ok(testObject.affectsConfiguration('window')); + + assert.ok(testObject.affectsConfiguration('workbench.editor.enablePreview')); + assert.ok(testObject.affectsConfiguration('workbench.editor')); + assert.ok(testObject.affectsConfiguration('workbench')); + + assert.ok(!testObject.affectsConfiguration('files')); + assert.ok(!testObject.affectsConfiguration('[markdown]')); + assert.ok(!testObject.affectsConfiguration('editor')); + }); + + test('changeEvent affecting overrides with new configuration', () => { + const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel()); + const change = configuration.compareAndUpdateLocalUserConfiguration(toConfigurationModel({ + 'files.autoSave': 'off', + '[markdown]': { + 'editor.wordWrap': 'off' + } + })); + let testObject = new ConfigurationChangeEvent(change, undefined, configuration); + + assert.deepEqual(testObject.affectedKeys, ['files.autoSave', '[markdown]', 'editor.wordWrap']); + + assert.ok(testObject.affectsConfiguration('files')); + assert.ok(testObject.affectsConfiguration('files.autoSave')); + assert.ok(!testObject.affectsConfiguration('files.exclude')); + + assert.ok(testObject.affectsConfiguration('[markdown]')); + assert.ok(!testObject.affectsConfiguration('[markdown].editor')); + assert.ok(!testObject.affectsConfiguration('[markdown].workbench')); + + assert.ok(testObject.affectsConfiguration('editor')); + assert.ok(testObject.affectsConfiguration('editor.wordWrap')); + assert.ok(testObject.affectsConfiguration('editor', { overrideIdentifier: 'markdown' })); + assert.ok(testObject.affectsConfiguration('editor.wordWrap', { overrideIdentifier: 'markdown' })); + assert.ok(!testObject.affectsConfiguration('editor', { overrideIdentifier: 'json' })); + assert.ok(!testObject.affectsConfiguration('editor.fontSize', { overrideIdentifier: 'markdown' })); + + assert.ok(!testObject.affectsConfiguration('editor.fontSize')); + assert.ok(!testObject.affectsConfiguration('window')); + }); + + test('changeEvent affecting overrides when configuration changed', () => { + const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel()); + configuration.updateLocalUserConfiguration(toConfigurationModel({ + 'workbench.editor.enablePreview': true, + '[markdown]': { + 'editor.fontSize': 12, + 'editor.wordWrap': 'off' + }, + 'files.autoSave': 'off', + })); + const data = configuration.toData(); + const change = configuration.compareAndUpdateLocalUserConfiguration(toConfigurationModel({ + 'files.autoSave': 'off', + '[markdown]': { + 'editor.fontSize': 13, + 'editor.wordWrap': 'off' + }, + 'window.zoomLevel': 1, + })); + let testObject = new ConfigurationChangeEvent(change, { data }, configuration); + + assert.deepEqual(testObject.affectedKeys, ['window.zoomLevel', '[markdown]', 'workbench.editor.enablePreview', 'editor.fontSize']); + + assert.ok(!testObject.affectsConfiguration('files')); + + assert.ok(testObject.affectsConfiguration('[markdown]')); + assert.ok(!testObject.affectsConfiguration('[markdown].editor')); + assert.ok(!testObject.affectsConfiguration('[markdown].editor.fontSize')); + assert.ok(!testObject.affectsConfiguration('[markdown].editor.wordWrap')); + assert.ok(!testObject.affectsConfiguration('[markdown].workbench')); + + assert.ok(testObject.affectsConfiguration('editor')); + assert.ok(testObject.affectsConfiguration('editor', { overrideIdentifier: 'markdown' })); + assert.ok(testObject.affectsConfiguration('editor.fontSize', { overrideIdentifier: 'markdown' })); + assert.ok(!testObject.affectsConfiguration('editor.wordWrap')); + assert.ok(!testObject.affectsConfiguration('editor.wordWrap', { overrideIdentifier: 'markdown' })); + assert.ok(!testObject.affectsConfiguration('editor', { overrideIdentifier: 'json' })); + assert.ok(!testObject.affectsConfiguration('editor.fontSize', { overrideIdentifier: 'json' })); + + assert.ok(testObject.affectsConfiguration('window')); + assert.ok(testObject.affectsConfiguration('window.zoomLevel')); + assert.ok(testObject.affectsConfiguration('window', { overrideIdentifier: 'markdown' })); + assert.ok(testObject.affectsConfiguration('window.zoomLevel', { overrideIdentifier: 'markdown' })); + + assert.ok(testObject.affectsConfiguration('workbench')); + assert.ok(testObject.affectsConfiguration('workbench.editor')); + assert.ok(testObject.affectsConfiguration('workbench.editor.enablePreview')); + assert.ok(testObject.affectsConfiguration('workbench', { overrideIdentifier: 'markdown' })); + assert.ok(testObject.affectsConfiguration('workbench.editor', { overrideIdentifier: 'markdown' })); + }); + + test('changeEvent affecting workspace folders', () => { + const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel()); + configuration.updateWorkspaceConfiguration(toConfigurationModel({ 'window.title': 'custom' })); + configuration.updateFolderConfiguration(URI.file('folder1'), toConfigurationModel({ 'window.zoomLevel': 2, 'window.restoreFullscreen': true })); + configuration.updateFolderConfiguration(URI.file('folder2'), toConfigurationModel({ 'workbench.editor.enablePreview': true, 'window.restoreWindows': true })); + const data = configuration.toData(); + const workspace = new Workspace('a', [new WorkspaceFolder({ index: 0, name: 'a', uri: URI.file('folder1') }), new WorkspaceFolder({ index: 1, name: 'b', uri: URI.file('folder2') }), new WorkspaceFolder({ index: 2, name: 'c', uri: URI.file('folder3') })]); + const change = mergeChanges( + configuration.compareAndUpdateWorkspaceConfiguration(toConfigurationModel({ 'window.title': 'native' })), + configuration.compareAndUpdateFolderConfiguration(URI.file('folder1'), toConfigurationModel({ 'window.zoomLevel': 1, 'window.restoreFullscreen': false })), + configuration.compareAndUpdateFolderConfiguration(URI.file('folder2'), toConfigurationModel({ 'workbench.editor.enablePreview': false, 'window.restoreWindows': false })) + ); + let testObject = new ConfigurationChangeEvent(change, { data, workspace }, configuration, workspace); + + assert.deepEqual(testObject.affectedKeys, ['window.title', 'window.zoomLevel', 'window.restoreFullscreen', 'workbench.editor.enablePreview', 'window.restoreWindows']); + + assert.ok(testObject.affectsConfiguration('window.zoomLevel')); + assert.ok(testObject.affectsConfiguration('window.zoomLevel', { resource: URI.file('folder1') })); + assert.ok(testObject.affectsConfiguration('window.zoomLevel', { resource: URI.file(join('folder1', 'file1')) })); + assert.ok(!testObject.affectsConfiguration('window.zoomLevel', { resource: URI.file('file1') })); + assert.ok(!testObject.affectsConfiguration('window.zoomLevel', { resource: URI.file('file2') })); + assert.ok(!testObject.affectsConfiguration('window.zoomLevel', { resource: URI.file(join('folder2', 'file2')) })); + assert.ok(!testObject.affectsConfiguration('window.zoomLevel', { resource: URI.file(join('folder3', 'file3')) })); + + assert.ok(testObject.affectsConfiguration('window.restoreFullscreen')); + assert.ok(testObject.affectsConfiguration('window.restoreFullscreen', { resource: URI.file(join('folder1', 'file1')) })); + assert.ok(testObject.affectsConfiguration('window.restoreFullscreen', { resource: URI.file('folder1') })); + assert.ok(!testObject.affectsConfiguration('window.restoreFullscreen', { resource: URI.file('file1') })); + assert.ok(!testObject.affectsConfiguration('window.restoreFullscreen', { resource: URI.file('file2') })); + assert.ok(!testObject.affectsConfiguration('window.restoreFullscreen', { resource: URI.file(join('folder2', 'file2')) })); + assert.ok(!testObject.affectsConfiguration('window.restoreFullscreen', { resource: URI.file(join('folder3', 'file3')) })); + + assert.ok(testObject.affectsConfiguration('window.restoreWindows')); + assert.ok(testObject.affectsConfiguration('window.restoreWindows', { resource: URI.file('folder2') })); + assert.ok(testObject.affectsConfiguration('window.restoreWindows', { resource: URI.file(join('folder2', 'file2')) })); + assert.ok(!testObject.affectsConfiguration('window.restoreWindows', { resource: URI.file('file2') })); + assert.ok(!testObject.affectsConfiguration('window.restoreWindows', { resource: URI.file(join('folder1', 'file1')) })); + assert.ok(!testObject.affectsConfiguration('window.restoreWindows', { resource: URI.file(join('folder3', 'file3')) })); + + assert.ok(testObject.affectsConfiguration('window.title')); + assert.ok(testObject.affectsConfiguration('window.title', { resource: URI.file('folder1') })); + assert.ok(testObject.affectsConfiguration('window.title', { resource: URI.file(join('folder1', 'file1')) })); + assert.ok(testObject.affectsConfiguration('window.title', { resource: URI.file('folder2') })); + assert.ok(testObject.affectsConfiguration('window.title', { resource: URI.file(join('folder2', 'file2')) })); + assert.ok(testObject.affectsConfiguration('window.title', { resource: URI.file('folder3') })); + assert.ok(testObject.affectsConfiguration('window.title', { resource: URI.file(join('folder3', 'file3')) })); + assert.ok(testObject.affectsConfiguration('window.title', { resource: URI.file('file1') })); + assert.ok(testObject.affectsConfiguration('window.title', { resource: URI.file('file2') })); + assert.ok(testObject.affectsConfiguration('window.title', { resource: URI.file('file3') })); + + assert.ok(testObject.affectsConfiguration('window')); + assert.ok(testObject.affectsConfiguration('window', { resource: URI.file('folder1') })); + assert.ok(testObject.affectsConfiguration('window', { resource: URI.file(join('folder1', 'file1')) })); + assert.ok(testObject.affectsConfiguration('window', { resource: URI.file('folder2') })); + assert.ok(testObject.affectsConfiguration('window', { resource: URI.file(join('folder2', 'file2')) })); + assert.ok(testObject.affectsConfiguration('window', { resource: URI.file('folder3') })); + assert.ok(testObject.affectsConfiguration('window', { resource: URI.file(join('folder3', 'file3')) })); + assert.ok(testObject.affectsConfiguration('window', { resource: URI.file('file1') })); + assert.ok(testObject.affectsConfiguration('window', { resource: URI.file('file2') })); + assert.ok(testObject.affectsConfiguration('window', { resource: URI.file('file3') })); + + assert.ok(testObject.affectsConfiguration('workbench.editor.enablePreview')); + assert.ok(testObject.affectsConfiguration('workbench.editor.enablePreview', { resource: URI.file('folder2') })); + assert.ok(testObject.affectsConfiguration('workbench.editor.enablePreview', { resource: URI.file(join('folder2', 'file2')) })); + assert.ok(!testObject.affectsConfiguration('workbench.editor.enablePreview', { resource: URI.file('folder1') })); + assert.ok(!testObject.affectsConfiguration('workbench.editor.enablePreview', { resource: URI.file(join('folder1', 'file1')) })); + assert.ok(!testObject.affectsConfiguration('workbench.editor.enablePreview', { resource: URI.file('folder3') })); + + assert.ok(testObject.affectsConfiguration('workbench.editor')); + assert.ok(testObject.affectsConfiguration('workbench.editor', { resource: URI.file('folder2') })); + assert.ok(testObject.affectsConfiguration('workbench.editor', { resource: URI.file(join('folder2', 'file2')) })); + assert.ok(!testObject.affectsConfiguration('workbench.editor', { resource: URI.file('folder1') })); + assert.ok(!testObject.affectsConfiguration('workbench.editor', { resource: URI.file(join('folder1', 'file1')) })); + assert.ok(!testObject.affectsConfiguration('workbench.editor', { resource: URI.file('folder3') })); + + assert.ok(testObject.affectsConfiguration('workbench')); + assert.ok(testObject.affectsConfiguration('workbench', { resource: URI.file('folder2') })); + assert.ok(testObject.affectsConfiguration('workbench', { resource: URI.file(join('folder2', 'file2')) })); + assert.ok(!testObject.affectsConfiguration('workbench', { resource: URI.file('folder1') })); + assert.ok(!testObject.affectsConfiguration('workbench', { resource: URI.file('folder3') })); + + assert.ok(!testObject.affectsConfiguration('files')); + assert.ok(!testObject.affectsConfiguration('files', { resource: URI.file('folder1') })); + assert.ok(!testObject.affectsConfiguration('files', { resource: URI.file(join('folder1', 'file1')) })); + assert.ok(!testObject.affectsConfiguration('files', { resource: URI.file('folder2') })); + assert.ok(!testObject.affectsConfiguration('files', { resource: URI.file(join('folder2', 'file2')) })); + assert.ok(!testObject.affectsConfiguration('files', { resource: URI.file('folder3') })); + assert.ok(!testObject.affectsConfiguration('files', { resource: URI.file(join('folder3', 'file3')) })); + }); + + test('changeEvent - all', () => { + const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel()); + configuration.updateFolderConfiguration(URI.file('file1'), toConfigurationModel({ 'window.zoomLevel': 2, 'window.restoreFullscreen': true })); + const data = configuration.toData(); + const change = mergeChanges( + configuration.compareAndUpdateDefaultConfiguration(toConfigurationModel({ + 'editor.lineNumbers': 'off', + '[markdown]': { + 'editor.wordWrap': 'off' + } + }), ['editor.lineNumbers', '[markdown]']), + configuration.compareAndUpdateLocalUserConfiguration(toConfigurationModel({ + '[json]': { + 'editor.lineNumbers': 'relative' + } + })), + configuration.compareAndUpdateWorkspaceConfiguration(toConfigurationModel({ 'window.title': 'custom' })), + configuration.compareAndDeleteFolderConfiguration(URI.file('file1')), + configuration.compareAndUpdateFolderConfiguration(URI.file('file2'), toConfigurationModel({ 'workbench.editor.enablePreview': true, 'window.restoreWindows': true }))); + const workspace = new Workspace('a', [new WorkspaceFolder({ index: 0, name: 'a', uri: URI.file('file1') }), new WorkspaceFolder({ index: 1, name: 'b', uri: URI.file('file2') }), new WorkspaceFolder({ index: 2, name: 'c', uri: URI.file('folder3') })]); + const testObject = new ConfigurationChangeEvent(change, { data, workspace }, configuration, workspace); + + assert.deepEqual(testObject.affectedKeys, ['editor.lineNumbers', '[markdown]', '[json]', 'window.title', 'window.zoomLevel', 'window.restoreFullscreen', 'workbench.editor.enablePreview', 'window.restoreWindows', 'editor.wordWrap']); + + assert.ok(testObject.affectsConfiguration('window.title')); + assert.ok(testObject.affectsConfiguration('window.title', { resource: URI.file('file1') })); + assert.ok(testObject.affectsConfiguration('window.title', { resource: URI.file('file2') })); + + assert.ok(testObject.affectsConfiguration('window')); + assert.ok(testObject.affectsConfiguration('window', { resource: URI.file('file1') })); + assert.ok(testObject.affectsConfiguration('window', { resource: URI.file('file2') })); + + assert.ok(testObject.affectsConfiguration('window.zoomLevel')); + assert.ok(testObject.affectsConfiguration('window.zoomLevel', { resource: URI.file('file1') })); + assert.ok(!testObject.affectsConfiguration('window.zoomLevel', { resource: URI.file('file2') })); + + assert.ok(testObject.affectsConfiguration('window.restoreFullscreen')); + assert.ok(testObject.affectsConfiguration('window.restoreFullscreen', { resource: URI.file('file1') })); + assert.ok(!testObject.affectsConfiguration('window.restoreFullscreen', { resource: URI.file('file2') })); + + assert.ok(testObject.affectsConfiguration('window.restoreWindows')); + assert.ok(testObject.affectsConfiguration('window.restoreWindows', { resource: URI.file('file2') })); + assert.ok(!testObject.affectsConfiguration('window.restoreWindows', { resource: URI.file('file1') })); + + assert.ok(testObject.affectsConfiguration('workbench.editor.enablePreview')); + assert.ok(testObject.affectsConfiguration('workbench.editor.enablePreview', { resource: URI.file('file2') })); + assert.ok(!testObject.affectsConfiguration('workbench.editor.enablePreview', { resource: URI.file('file1') })); + + assert.ok(testObject.affectsConfiguration('workbench.editor')); + assert.ok(testObject.affectsConfiguration('workbench.editor', { resource: URI.file('file2') })); + assert.ok(!testObject.affectsConfiguration('workbench.editor', { resource: URI.file('file1') })); + + assert.ok(testObject.affectsConfiguration('workbench')); + assert.ok(testObject.affectsConfiguration('workbench', { resource: URI.file('file2') })); + assert.ok(!testObject.affectsConfiguration('workbench', { resource: URI.file('file1') })); + + assert.ok(!testObject.affectsConfiguration('files')); + assert.ok(!testObject.affectsConfiguration('files', { resource: URI.file('file1') })); + assert.ok(!testObject.affectsConfiguration('files', { resource: URI.file('file2') })); + + assert.ok(testObject.affectsConfiguration('editor')); + assert.ok(testObject.affectsConfiguration('editor', { resource: URI.file('file1') })); + assert.ok(testObject.affectsConfiguration('editor', { resource: URI.file('file2') })); + assert.ok(testObject.affectsConfiguration('editor', { resource: URI.file('file1'), overrideIdentifier: 'json' })); + assert.ok(testObject.affectsConfiguration('editor', { resource: URI.file('file1'), overrideIdentifier: 'markdown' })); + assert.ok(testObject.affectsConfiguration('editor', { resource: URI.file('file1'), overrideIdentifier: 'typescript' })); + assert.ok(testObject.affectsConfiguration('editor', { resource: URI.file('file2'), overrideIdentifier: 'json' })); + assert.ok(testObject.affectsConfiguration('editor', { resource: URI.file('file2'), overrideIdentifier: 'markdown' })); + assert.ok(testObject.affectsConfiguration('editor', { resource: URI.file('file2'), overrideIdentifier: 'typescript' })); + + assert.ok(testObject.affectsConfiguration('editor.lineNumbers')); + assert.ok(testObject.affectsConfiguration('editor.lineNumbers', { resource: URI.file('file1') })); + assert.ok(testObject.affectsConfiguration('editor.lineNumbers', { resource: URI.file('file2') })); + assert.ok(testObject.affectsConfiguration('editor.lineNumbers', { resource: URI.file('file1'), overrideIdentifier: 'json' })); + assert.ok(testObject.affectsConfiguration('editor.lineNumbers', { resource: URI.file('file1'), overrideIdentifier: 'markdown' })); + assert.ok(testObject.affectsConfiguration('editor.lineNumbers', { resource: URI.file('file1'), overrideIdentifier: 'typescript' })); + assert.ok(testObject.affectsConfiguration('editor.lineNumbers', { resource: URI.file('file2'), overrideIdentifier: 'json' })); + assert.ok(testObject.affectsConfiguration('editor.lineNumbers', { resource: URI.file('file2'), overrideIdentifier: 'markdown' })); + assert.ok(testObject.affectsConfiguration('editor.lineNumbers', { resource: URI.file('file2'), overrideIdentifier: 'typescript' })); + + assert.ok(testObject.affectsConfiguration('editor.wordWrap')); + assert.ok(!testObject.affectsConfiguration('editor.wordWrap', { resource: URI.file('file1') })); + assert.ok(!testObject.affectsConfiguration('editor.wordWrap', { resource: URI.file('file2') })); + assert.ok(!testObject.affectsConfiguration('editor.wordWrap', { resource: URI.file('file1'), overrideIdentifier: 'json' })); + assert.ok(testObject.affectsConfiguration('editor.wordWrap', { resource: URI.file('file1'), overrideIdentifier: 'markdown' })); + assert.ok(!testObject.affectsConfiguration('editor.wordWrap', { resource: URI.file('file1'), overrideIdentifier: 'typescript' })); + assert.ok(!testObject.affectsConfiguration('editor.wordWrap', { resource: URI.file('file2'), overrideIdentifier: 'json' })); + assert.ok(testObject.affectsConfiguration('editor.wordWrap', { resource: URI.file('file2'), overrideIdentifier: 'markdown' })); + assert.ok(!testObject.affectsConfiguration('editor.wordWrap', { resource: URI.file('file2'), overrideIdentifier: 'typescript' })); + + assert.ok(!testObject.affectsConfiguration('editor.fontSize')); + assert.ok(!testObject.affectsConfiguration('editor.fontSize', { resource: URI.file('file1') })); + assert.ok(!testObject.affectsConfiguration('editor.fontSize', { resource: URI.file('file2') })); + }); + + test('changeEvent affecting tasks and launches', () => { + const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel()); + const change = configuration.compareAndUpdateLocalUserConfiguration(toConfigurationModel({ + 'launch': { + 'configuraiton': {} + }, + 'launch.version': 1, + 'tasks': { + 'version': 2 + } + })); + let testObject = new ConfigurationChangeEvent(change, undefined, configuration); + + assert.deepEqual(testObject.affectedKeys, ['launch', 'launch.version', 'tasks']); + assert.ok(testObject.affectsConfiguration('launch')); + assert.ok(testObject.affectsConfiguration('launch.version')); + assert.ok(testObject.affectsConfiguration('tasks')); + }); + +}); + +suite('AllKeysConfigurationChangeEvent', () => { + + test('changeEvent', () => { + const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel()); + configuration.updateDefaultConfiguration(toConfigurationModel({ + 'editor.lineNumbers': 'off', + '[markdown]': { + 'editor.wordWrap': 'off' + } + })); + configuration.updateLocalUserConfiguration(toConfigurationModel({ + '[json]': { + 'editor.lineNumbers': 'relative' + } + })); + configuration.updateWorkspaceConfiguration(toConfigurationModel({ 'window.title': 'custom' })); + configuration.updateFolderConfiguration(URI.file('file1'), toConfigurationModel({ 'window.zoomLevel': 2, 'window.restoreFullscreen': true })); + configuration.updateFolderConfiguration(URI.file('file2'), toConfigurationModel({ 'workbench.editor.enablePreview': true, 'window.restoreWindows': true })); + const workspace = new Workspace('a', [new WorkspaceFolder({ index: 0, name: 'a', uri: URI.file('file1') }), new WorkspaceFolder({ index: 1, name: 'b', uri: URI.file('file2') }), new WorkspaceFolder({ index: 2, name: 'c', uri: URI.file('folder3') })]); + let testObject = new AllKeysConfigurationChangeEvent(configuration, workspace, ConfigurationTarget.USER, null); + + assert.deepEqual(testObject.affectedKeys, ['editor.lineNumbers', '[markdown]', '[json]', 'window.title', 'window.zoomLevel', 'window.restoreFullscreen', 'workbench.editor.enablePreview', 'window.restoreWindows']); + + assert.ok(testObject.affectsConfiguration('window.title')); + assert.ok(testObject.affectsConfiguration('window.title', { resource: URI.file('file1') })); + assert.ok(testObject.affectsConfiguration('window.title', { resource: URI.file('file2') })); + + assert.ok(testObject.affectsConfiguration('window')); + assert.ok(testObject.affectsConfiguration('window', { resource: URI.file('file1') })); + assert.ok(testObject.affectsConfiguration('window', { resource: URI.file('file2') })); + + assert.ok(testObject.affectsConfiguration('window.zoomLevel')); + assert.ok(testObject.affectsConfiguration('window.zoomLevel', { resource: URI.file('file1') })); + assert.ok(!testObject.affectsConfiguration('window.zoomLevel', { resource: URI.file('file2') })); + + assert.ok(testObject.affectsConfiguration('window.restoreFullscreen')); + assert.ok(testObject.affectsConfiguration('window.restoreFullscreen', { resource: URI.file('file1') })); + assert.ok(!testObject.affectsConfiguration('window.restoreFullscreen', { resource: URI.file('file2') })); + + assert.ok(testObject.affectsConfiguration('window.restoreWindows')); + assert.ok(testObject.affectsConfiguration('window.restoreWindows', { resource: URI.file('file2') })); + assert.ok(!testObject.affectsConfiguration('window.restoreWindows', { resource: URI.file('file1') })); + + assert.ok(testObject.affectsConfiguration('workbench.editor.enablePreview')); + assert.ok(testObject.affectsConfiguration('workbench.editor.enablePreview', { resource: URI.file('file2') })); + assert.ok(!testObject.affectsConfiguration('workbench.editor.enablePreview', { resource: URI.file('file1') })); + + assert.ok(testObject.affectsConfiguration('workbench.editor')); + assert.ok(testObject.affectsConfiguration('workbench.editor', { resource: URI.file('file2') })); + assert.ok(!testObject.affectsConfiguration('workbench.editor', { resource: URI.file('file1') })); + + assert.ok(testObject.affectsConfiguration('workbench')); + assert.ok(testObject.affectsConfiguration('workbench', { resource: URI.file('file2') })); + assert.ok(!testObject.affectsConfiguration('workbench', { resource: URI.file('file1') })); + + assert.ok(!testObject.affectsConfiguration('files')); + assert.ok(!testObject.affectsConfiguration('files', { resource: URI.file('file1') })); + assert.ok(!testObject.affectsConfiguration('files', { resource: URI.file('file2') })); + + assert.ok(testObject.affectsConfiguration('editor')); + assert.ok(testObject.affectsConfiguration('editor', { resource: URI.file('file1') })); + assert.ok(testObject.affectsConfiguration('editor', { resource: URI.file('file2') })); + assert.ok(testObject.affectsConfiguration('editor', { resource: URI.file('file1'), overrideIdentifier: 'json' })); + assert.ok(testObject.affectsConfiguration('editor', { resource: URI.file('file1'), overrideIdentifier: 'markdown' })); + assert.ok(testObject.affectsConfiguration('editor', { resource: URI.file('file1'), overrideIdentifier: 'typescript' })); + assert.ok(testObject.affectsConfiguration('editor', { resource: URI.file('file2'), overrideIdentifier: 'json' })); + assert.ok(testObject.affectsConfiguration('editor', { resource: URI.file('file2'), overrideIdentifier: 'markdown' })); + assert.ok(testObject.affectsConfiguration('editor', { resource: URI.file('file2'), overrideIdentifier: 'typescript' })); + + assert.ok(testObject.affectsConfiguration('editor.lineNumbers')); + assert.ok(testObject.affectsConfiguration('editor.lineNumbers', { resource: URI.file('file1') })); + assert.ok(testObject.affectsConfiguration('editor.lineNumbers', { resource: URI.file('file2') })); + assert.ok(testObject.affectsConfiguration('editor.lineNumbers', { resource: URI.file('file1'), overrideIdentifier: 'json' })); + assert.ok(testObject.affectsConfiguration('editor.lineNumbers', { resource: URI.file('file1'), overrideIdentifier: 'markdown' })); + assert.ok(testObject.affectsConfiguration('editor.lineNumbers', { resource: URI.file('file1'), overrideIdentifier: 'typescript' })); + assert.ok(testObject.affectsConfiguration('editor.lineNumbers', { resource: URI.file('file2'), overrideIdentifier: 'json' })); + assert.ok(testObject.affectsConfiguration('editor.lineNumbers', { resource: URI.file('file2'), overrideIdentifier: 'markdown' })); + assert.ok(testObject.affectsConfiguration('editor.lineNumbers', { resource: URI.file('file2'), overrideIdentifier: 'typescript' })); + + assert.ok(!testObject.affectsConfiguration('editor.wordWrap')); + assert.ok(!testObject.affectsConfiguration('editor.wordWrap', { resource: URI.file('file1') })); + assert.ok(!testObject.affectsConfiguration('editor.wordWrap', { resource: URI.file('file2') })); + assert.ok(!testObject.affectsConfiguration('editor.wordWrap', { resource: URI.file('file1'), overrideIdentifier: 'json' })); + assert.ok(!testObject.affectsConfiguration('editor.wordWrap', { resource: URI.file('file1'), overrideIdentifier: 'markdown' })); + assert.ok(!testObject.affectsConfiguration('editor.wordWrap', { resource: URI.file('file1'), overrideIdentifier: 'typescript' })); + assert.ok(!testObject.affectsConfiguration('editor.wordWrap', { resource: URI.file('file2'), overrideIdentifier: 'json' })); + assert.ok(!testObject.affectsConfiguration('editor.wordWrap', { resource: URI.file('file2'), overrideIdentifier: 'markdown' })); + assert.ok(!testObject.affectsConfiguration('editor.wordWrap', { resource: URI.file('file2'), overrideIdentifier: 'typescript' })); + + assert.ok(!testObject.affectsConfiguration('editor.fontSize')); + assert.ok(!testObject.affectsConfiguration('editor.fontSize', { resource: URI.file('file1') })); + assert.ok(!testObject.affectsConfiguration('editor.fontSize', { resource: URI.file('file2') })); + }); +}); + +function toConfigurationModel(obj: any): ConfigurationModel { + const parser = new ConfigurationModelParser('test'); + parser.parseContent(JSON.stringify(obj)); + return parser.configurationModel; +} diff --git a/src/vs/platform/configuration/test/common/testConfigurationService.ts b/src/vs/platform/configuration/test/common/testConfigurationService.ts index 507fb65aef..528413c57e 100644 --- a/src/vs/platform/configuration/test/common/testConfigurationService.ts +++ b/src/vs/platform/configuration/test/common/testConfigurationService.ts @@ -5,12 +5,18 @@ import { TernarySearchTree } from 'vs/base/common/map'; import { URI } from 'vs/base/common/uri'; -import { getConfigurationKeys, IConfigurationOverrides, IConfigurationService, getConfigurationValue, isConfigurationOverrides } from 'vs/platform/configuration/common/configuration'; +import { getConfigurationKeys, IConfigurationOverrides, IConfigurationService, getConfigurationValue, isConfigurationOverrides, IConfigurationValue } from 'vs/platform/configuration/common/configuration'; +import { Emitter } from 'vs/base/common/event'; export class TestConfigurationService implements IConfigurationService { public _serviceBrand: undefined; - private configuration = Object.create(null); + private configuration: any; + readonly onDidChangeConfiguration = new Emitter().event; + + constructor(configuration?: any) { + this.configuration = configuration || Object.create(null); + } private configurationByRoot: TernarySearchTree = TernarySearchTree.forPaths(); @@ -33,7 +39,7 @@ export class TestConfigurationService implements IConfigurationService { return configuration; } - public updateValue(key: string, overrides?: IConfigurationOverrides): Promise { + public updateValue(key: string, value: any): Promise { return Promise.resolve(undefined); } @@ -49,27 +55,13 @@ export class TestConfigurationService implements IConfigurationService { return Promise.resolve(undefined); } - public onDidChangeConfiguration() { - return { dispose() { } }; - } - - public inspect(key: string, overrides?: IConfigurationOverrides): { - default: T, - user: T, - userLocal?: T, - userRemote?: T, - workspace?: T, - workspaceFolder?: T - value: T, - } { + public inspect(key: string, overrides?: IConfigurationOverrides): IConfigurationValue { const config = this.getValue(undefined, overrides); return { value: getConfigurationValue(config, key), default: getConfigurationValue(config, key), - user: getConfigurationValue(config, key), - workspace: undefined, - workspaceFolder: undefined + user: getConfigurationValue(config, key) }; } diff --git a/src/vs/platform/issue/node/issue.ts b/src/vs/platform/issue/node/issue.ts index 70785d5174..a341ede6bc 100644 --- a/src/vs/platform/issue/node/issue.ts +++ b/src/vs/platform/issue/node/issue.ts @@ -32,6 +32,8 @@ export interface IssueReporterStyles extends WindowStyles { inputForeground?: string; inputBorder?: string; inputErrorBorder?: string; + inputErrorBackground?: string; + inputErrorForeground?: string; inputActiveBorder?: string; buttonBackground?: string; buttonForeground?: string; diff --git a/src/vs/platform/telemetry/test/electron-browser/telemetryService.test.ts b/src/vs/platform/telemetry/test/electron-browser/telemetryService.test.ts index 3d1a742306..e463fc32dd 100644 --- a/src/vs/platform/telemetry/test/electron-browser/telemetryService.test.ts +++ b/src/vs/platform/telemetry/test/electron-browser/telemetryService.test.ts @@ -9,8 +9,8 @@ import ErrorTelemetry from 'vs/platform/telemetry/browser/errorTelemetry'; import { NullAppender, ITelemetryAppender } from 'vs/platform/telemetry/common/telemetryUtils'; import * as Errors from 'vs/base/common/errors'; import * as sinon from 'sinon'; -import { getConfigurationValue } from 'vs/platform/configuration/common/configuration'; import { ITelemetryData } from 'vs/platform/telemetry/common/telemetry'; +import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; class TestTelemetryAppender implements ITelemetryAppender { @@ -729,30 +729,14 @@ suite('TelemetryService', () => { let testAppender = new TestTelemetryAppender(); let service = new TelemetryService({ appender: testAppender - }, { - _serviceBrand: undefined, + }, new class extends TestConfigurationService { + onDidChangeConfiguration = emitter.event; getValue() { return { enableTelemetry: enableTelemetry } as any; - }, - updateValue(): Promise { - return null!; - }, - inspect(key: string) { - return { - value: getConfigurationValue(this.getValue(), key), - default: getConfigurationValue(this.getValue(), key), - user: getConfigurationValue(this.getValue(), key), - workspace: null!, - workspaceFolder: null! - }; - }, - keys() { return { default: [], user: [], workspace: [], workspaceFolder: [] }; }, - onDidChangeConfiguration: emitter.event, - reloadConfiguration(): Promise { return null!; }, - getConfigurationData() { return null; } - }); + } + }()); assert.equal(service.isOptedIn, false); diff --git a/src/vs/platform/theme/common/tokenClassificationRegistry.ts b/src/vs/platform/theme/common/tokenClassificationRegistry.ts index 974d6b91f7..7fd571ff6e 100644 --- a/src/vs/platform/theme/common/tokenClassificationRegistry.ts +++ b/src/vs/platform/theme/common/tokenClassificationRegistry.ts @@ -12,14 +12,15 @@ 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 const typeAndModifierIdPattern = '^\\w+[-_\\w+]*$'; +export const fontStylePattern = '^(\\s*(-?italic|-?bold|-?underline))*\\s*$'; + export interface TokenClassification { type: number; modifiers: number; @@ -54,6 +55,34 @@ 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 function fromSettings(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); + + } } export type ProbeScope = string[]; @@ -63,10 +92,10 @@ export interface TokenStyleFunction { } export interface TokenStyleDefaults { - scopesToProbe: ProbeScope[]; - light: TokenStyleValue | null; - dark: TokenStyleValue | null; - hc: TokenStyleValue | null; + scopesToProbe?: ProbeScope[]; + light?: TokenStyleValue; + dark?: TokenStyleValue; + hc?: TokenStyleValue; } export interface TokenStylingDefaultRule { @@ -120,6 +149,12 @@ export interface ITokenClassificationRegistry { */ registerTokenStyleDefault(selector: TokenClassification, defaults: TokenStyleDefaults): void; + /** + * Deregister a TokenStyle default to the registry. + * @param selector The rule selector + */ + deregisterTokenStyleDefault(selector: TokenClassification): void; + /** * Deregister a TokenType from the registry. */ @@ -186,7 +221,7 @@ class TokenClassificationRegistry implements ITokenClassificationRegistry { 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*$', + pattern: fontStylePattern, 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' }] } @@ -205,6 +240,10 @@ class TokenClassificationRegistry implements ITokenClassificationRegistry { } public registerTokenType(id: string, description: string, deprecationMessage?: string): void { + if (!id.match(typeAndModifierIdPattern)) { + throw new Error('Invalid token type id.'); + } + const num = this.currentTypeNumber++; let tokenStyleContribution: TokenTypeOrModifierContribution = { num, id, description, deprecationMessage }; this.tokenTypeById[id] = tokenStyleContribution; @@ -213,6 +252,10 @@ class TokenClassificationRegistry implements ITokenClassificationRegistry { } public registerTokenModifier(id: string, description: string, deprecationMessage?: string): void { + if (!id.match(typeAndModifierIdPattern)) { + throw new Error('Invalid token modifier id.'); + } + const num = this.currentModifierBit; this.currentModifierBit = this.currentModifierBit * 2; let tokenStyleContribution: TokenTypeOrModifierContribution = { num, id, description, deprecationMessage }; @@ -244,6 +287,10 @@ class TokenClassificationRegistry implements ITokenClassificationRegistry { this.tokenStylingDefaultRules.push({ classification, matchScore: getTokenStylingScore(classification), defaults }); } + public deregisterTokenStyleDefault(classification: TokenClassification): void { + this.tokenStylingDefaultRules = this.tokenStylingDefaultRules.filter(r => !(r.classification.type === classification.type && r.classification.modifiers === classification.modifiers)); + } + public deregisterTokenType(id: string): void { delete this.tokenTypeById[id]; delete this.tokenStylingSchema.properties[id]; @@ -270,6 +317,7 @@ class TokenClassificationRegistry implements ITokenClassificationRegistry { return this.tokenStylingDefaultRules; } + public toString() { let sorter = (a: string, b: string) => { let cat1 = a.indexOf('.') === -1 ? 0 : 1; @@ -301,65 +349,63 @@ export function matchTokenStylingRule(themeSelector: TokenStylingRule | TokenSty 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); +registerDefaultClassifications(); - if (scopesToProbe || extendsTC) { - const classification = tokenClassificationRegistry.getTokenClassification(id, []); - tokenClassificationRegistry.registerTokenStyleDefault(classification!, { scopesToProbe, light: extendsTC, dark: extendsTC, hc: extendsTC }); +function registerDefaultClassifications(): void { + function registerTokenType(id: string, description: string, scopesToProbe: ProbeScope[] = [], extendsTC?: string, 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; } - return id; -} -export function registerTokenModifier(id: string, description: string, deprecationMessage?: string): string { - tokenClassificationRegistry.registerTokenModifier(id, description, deprecationMessage); - return id; + // default token types + + registerTokenType('comment', nls.localize('comment', "Style for comments."), [['comment']]); + registerTokenType('string', nls.localize('string', "Style for strings."), [['string']]); + registerTokenType('keyword', nls.localize('keyword', "Style for keywords."), [['keyword.control']]); + registerTokenType('number', nls.localize('number', "Style for numbers."), [['constant.numeric']]); + registerTokenType('regexp', nls.localize('regexp', "Style for expressions."), [['constant.regexp']]); + registerTokenType('operator', nls.localize('operator', "Style for operators."), [['keyword.operator']]); + + registerTokenType('namespace', nls.localize('namespace', "Style for namespaces."), [['entity.name.namespace']]); + + registerTokenType('type', nls.localize('type', "Style for types."), [['entity.name.type'], ['entity.name.class'], ['support.type'], ['support.class']]); + registerTokenType('struct', nls.localize('struct', "Style for structs."), [['storage.type.struct']], 'type'); + registerTokenType('class', nls.localize('class', "Style for classes."), [['entity.name.class']], 'type'); + registerTokenType('interface', nls.localize('interface', "Style for interfaces."), undefined, 'type'); + registerTokenType('enum', nls.localize('enum', "Style for enums."), undefined, 'type'); + registerTokenType('parameterType', nls.localize('parameterType', "Style for parameter types."), undefined, 'type'); + + registerTokenType('function', nls.localize('function', "Style for functions"), [['entity.name.function'], ['support.function']]); + registerTokenType('macro', nls.localize('macro', "Style for macros."), undefined, 'function'); + + registerTokenType('variable', nls.localize('variable', "Style for variables."), [['variable'], ['entity.name.variable']]); + registerTokenType('constant', nls.localize('constant', "Style for constants."), undefined, 'variable'); + registerTokenType('parameter', nls.localize('parameter', "Style for parameters."), undefined, 'variable'); + registerTokenType('property', nls.localize('propertie', "Style for properties."), undefined, 'variable'); + + registerTokenType('label', nls.localize('labels', "Style for labels. "), undefined); + + // default token modifiers + + tokenClassificationRegistry.registerTokenModifier('declaration', nls.localize('declaration', "Style for all symbol declarations."), undefined); + tokenClassificationRegistry.registerTokenModifier('documentation', nls.localize('documentation', "Style to use for references in documentation."), undefined); + tokenClassificationRegistry.registerTokenModifier('member', nls.localize('member', "Style to use for member functions, variables (fields) and types."), undefined); + tokenClassificationRegistry.registerTokenModifier('static', nls.localize('static', "Style to use for symbols that are static."), undefined); + tokenClassificationRegistry.registerTokenModifier('abstract', nls.localize('abstract', "Style to use for symbols that are abstract."), undefined); + tokenClassificationRegistry.registerTokenModifier('deprecated', nls.localize('deprecated', "Style to use for symbols that are deprecated."), undefined); + tokenClassificationRegistry.registerTokenModifier('modification', nls.localize('modification', "Style to use for write accesses."), undefined); + tokenClassificationRegistry.registerTokenModifier('async', nls.localize('async', "Style to use for symbols that are async."), undefined); } export function getTokenClassificationRegistry(): ITokenClassificationRegistry { return tokenClassificationRegistry; } -// default token types - -registerTokenType('comment', nls.localize('comment', "Style for comments."), [['comment']]); -registerTokenType('string', nls.localize('string', "Style for strings."), [['string']]); -registerTokenType('keyword', nls.localize('keyword', "Style for keywords."), [['keyword.control']]); -registerTokenType('number', nls.localize('number', "Style for numbers."), [['constant.numeric']]); -registerTokenType('regexp', nls.localize('regexp', "Style for expressions."), [['constant.regexp']]); -registerTokenType('operator', nls.localize('operator', "Style for operators."), [['keyword.operator']]); - -registerTokenType('namespace', nls.localize('namespace', "Style for namespaces."), [['entity.name.namespace']]); - -registerTokenType('type', nls.localize('type', "Style for types."), [['entity.name.type'], ['entity.name.class'], ['support.type'], ['support.class']]); -registerTokenType('struct', nls.localize('struct', "Style for structs."), [['storage.type.struct']], 'type'); -registerTokenType('class', nls.localize('class', "Style for classes."), [['entity.name.class']], 'type'); -registerTokenType('interface', nls.localize('interface', "Style for interfaces."), undefined, 'type'); -registerTokenType('enum', nls.localize('enum', "Style for enums."), undefined, 'type'); -registerTokenType('parameterType', nls.localize('parameterType', "Style for parameter types."), undefined, 'type'); - -registerTokenType('function', nls.localize('function', "Style for functions"), [['entity.name.function'], ['support.function']]); -registerTokenType('macro', nls.localize('macro', "Style for macros."), undefined, 'function'); - -registerTokenType('variable', nls.localize('variable', "Style for variables."), [['variable'], ['entity.name.variable']]); -registerTokenType('constant', nls.localize('constant', "Style for constants."), undefined, 'variable'); -registerTokenType('parameter', nls.localize('parameter', "Style for parameters."), undefined, 'variable'); -registerTokenType('property', nls.localize('propertie', "Style for properties."), undefined, 'variable'); - -registerTokenType('label', nls.localize('labels', "Style for labels. "), undefined); - -// default token modifiers - -registerTokenModifier('declaration', nls.localize('declaration', "Style for all symbol declarations."), undefined); -registerTokenModifier('documentation', nls.localize('documentation', "Style to use for references in documentation."), undefined); -registerTokenModifier('member', nls.localize('member', "Style to use for member functions, variables (fields) and types."), undefined); -registerTokenModifier('static', nls.localize('static', "Style to use for symbols that are static."), undefined); -registerTokenModifier('abstract', nls.localize('abstract', "Style to use for symbols that are abstract."), undefined); -registerTokenModifier('deprecated', nls.localize('deprecated', "Style to use for symbols that are deprecated."), undefined); -registerTokenModifier('modification', nls.localize('modification', "Style to use for write accesses."), undefined); -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); diff --git a/src/vs/platform/windows/electron-main/windowsMainService.ts b/src/vs/platform/windows/electron-main/windowsMainService.ts index 8c5fcbbad4..4d352a1914 100644 --- a/src/vs/platform/windows/electron-main/windowsMainService.ts +++ b/src/vs/platform/windows/electron-main/windowsMainService.ts @@ -326,7 +326,9 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic } // Persist - this.stateService.setItem(WindowsMainService.windowsStateStorageKey, getWindowsStateStoreData(currentWindowsState)); + const state = getWindowsStateStoreData(currentWindowsState); + this.logService.trace('onBeforeShutdown', state); + this.stateService.setItem(WindowsMainService.windowsStateStorageKey, state); } // See note on #onBeforeShutdown() for details how these events are flowing diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 9bf2d04326..b8ab6fe3d5 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -797,10 +797,10 @@ declare module 'vscode' { export interface DebugAdapter extends Disposable { /** - * An event which fires when the debug adapter sends a Debug Adapter Protocol message to VS Code. + * An event which fires after the debug adapter has sent a Debug Adapter Protocol message to VS Code. * Messages can be requests, responses, or events. */ - readonly onSendMessage: Event; + readonly onDidSendMessage: Event; /** * Handle a Debug Adapter Protocol message. diff --git a/src/vs/workbench/api/browser/extensionHost.contribution.ts b/src/vs/workbench/api/browser/extensionHost.contribution.ts index 9c02edb1eb..affd4338fc 100644 --- a/src/vs/workbench/api/browser/extensionHost.contribution.ts +++ b/src/vs/workbench/api/browser/extensionHost.contribution.ts @@ -11,6 +11,7 @@ import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; // --- other interested parties import { JSONValidationExtensionPoint } from 'vs/workbench/api/common/jsonValidationExtensionPoint'; import { ColorExtensionPoint } from 'vs/workbench/services/themes/common/colorExtensionPoint'; +import { TokenClassificationExtensionPoints } from 'vs/workbench/services/themes/common/tokenClassificationExtensionPoint'; import { LanguageConfigurationFileHandler } from 'vs/workbench/contrib/codeEditor/browser/languageConfigurationExtensionPoint'; // --- mainThread participants @@ -67,6 +68,7 @@ export class ExtensionPoints implements IWorkbenchContribution { // Classes that handle extension points... this.instantiationService.createInstance(JSONValidationExtensionPoint); this.instantiationService.createInstance(ColorExtensionPoint); + this.instantiationService.createInstance(TokenClassificationExtensionPoints); this.instantiationService.createInstance(LanguageConfigurationFileHandler); } } diff --git a/src/vs/workbench/api/browser/mainThreadConfiguration.ts b/src/vs/workbench/api/browser/mainThreadConfiguration.ts index c8e4bb0c85..e4c6548248 100644 --- a/src/vs/workbench/api/browser/mainThreadConfiguration.ts +++ b/src/vs/workbench/api/browser/mainThreadConfiguration.ts @@ -8,9 +8,9 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope, getScopes } from 'vs/platform/configuration/common/configurationRegistry'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { MainThreadConfigurationShape, MainContext, ExtHostContext, IExtHostContext, IWorkspaceConfigurationChangeEventData, IConfigurationInitData } from '../common/extHost.protocol'; +import { MainThreadConfigurationShape, MainContext, ExtHostContext, IExtHostContext, IConfigurationInitData } from '../common/extHost.protocol'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; -import { ConfigurationTarget, IConfigurationChangeEvent, IConfigurationModel, IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @extHostNamedCustomer(MainContext.MainThreadConfiguration) @@ -28,7 +28,7 @@ export class MainThreadConfiguration implements MainThreadConfigurationShape { proxy.$initializeConfiguration(this._getConfigurationData()); this._configurationListener = configurationService.onDidChangeConfiguration(e => { - proxy.$acceptConfigurationChanged(this._getConfigurationData(), this.toConfigurationChangeEventData(e)); + proxy.$acceptConfigurationChanged(this._getConfigurationData(), e.change); }); } @@ -69,22 +69,4 @@ export class MainThreadConfiguration implements MainThreadConfigurationShape { } return ConfigurationTarget.WORKSPACE; } - - private toConfigurationChangeEventData(event: IConfigurationChangeEvent): IWorkspaceConfigurationChangeEventData { - return { - changedConfiguration: this.toJSONConfiguration(event.changedConfiguration), - changedConfigurationByResource: event.changedConfigurationByResource.keys().reduce((result, resource) => { - result[resource.toString()] = this.toJSONConfiguration(event.changedConfigurationByResource.get(resource)); - return result; - }, Object.create({})) - }; - } - - private toJSONConfiguration({ contents, keys, overrides }: IConfigurationModel = { contents: {}, keys: [], overrides: [] }): IConfigurationModel { - return { - contents, - keys, - overrides - }; - } } diff --git a/src/vs/workbench/api/browser/mainThreadTunnelService.ts b/src/vs/workbench/api/browser/mainThreadTunnelService.ts index bf4a9de2e8..270c74b1d8 100644 --- a/src/vs/workbench/api/browser/mainThreadTunnelService.ts +++ b/src/vs/workbench/api/browser/mainThreadTunnelService.ts @@ -10,7 +10,6 @@ import { IRemoteExplorerService } from 'vs/workbench/services/remote/common/remo @extHostNamedCustomer(MainContext.MainThreadTunnelService) export class MainThreadTunnelService implements MainThreadTunnelServiceShape { - // @ts-ignore private readonly _proxy: ExtHostTunnelServiceShape; constructor( @@ -36,6 +35,10 @@ export class MainThreadTunnelService implements MainThreadTunnelServiceShape { return Promise.resolve(this.remoteExplorerService.addDetected(tunnels)); } + async $registerCandidateFinder(): Promise { + this.remoteExplorerService.registerCandidateFinder(() => this._proxy.$findCandidatePorts()); + } + dispose(): void { // } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 3e38d688ab..be1a2d45f8 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -22,7 +22,7 @@ import { IModelChangedEvent } from 'vs/editor/common/model/mirrorTextModel'; import * as modes from 'vs/editor/common/modes'; import { CharacterPair, CommentRule, EnterAction } from 'vs/editor/common/modes/languageConfiguration'; import { ICommandHandlerDescription } from 'vs/platform/commands/common/commands'; -import { ConfigurationTarget, IConfigurationData, IConfigurationModel } from 'vs/platform/configuration/common/configuration'; +import { ConfigurationTarget, IConfigurationData, IConfigurationChange } from 'vs/platform/configuration/common/configuration'; import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import * as files from 'vs/platform/files/common/files'; @@ -101,11 +101,6 @@ export interface IConfigurationInitData extends IConfigurationData { configurationScopes: [string, ConfigurationScope | undefined][]; } -export interface IWorkspaceConfigurationChangeEventData { - changedConfiguration: IConfigurationModel; - changedConfigurationByResource: { [folder: string]: IConfigurationModel; }; -} - export interface IExtHostContext extends IRPCProtocol { remoteAuthority: string; } @@ -786,6 +781,7 @@ export interface MainThreadTunnelServiceShape extends IDisposable { $openTunnel(tunnelOptions: TunnelOptions): Promise; $closeTunnel(remotePort: number): Promise; $addDetected(tunnels: { remote: { port: number, host: string }, localAddress: string }[]): Promise; + $registerCandidateFinder(): Promise; } // -- extension host @@ -797,7 +793,7 @@ export interface ExtHostCommandsShape { export interface ExtHostConfigurationShape { $initializeConfiguration(data: IConfigurationInitData): void; - $acceptConfigurationChanged(data: IConfigurationInitData, eventData: IWorkspaceConfigurationChangeEventData): void; + $acceptConfigurationChanged(data: IConfigurationInitData, change: IConfigurationChange): void; } export interface ExtHostDiagnosticsShape { @@ -1404,7 +1400,7 @@ export interface ExtHostStorageShape { export interface ExtHostTunnelServiceShape { - + $findCandidatePorts(): Promise<{ port: number, detail: string }[]>; } // --- proxy identifiers diff --git a/src/vs/workbench/api/common/extHostConfiguration.ts b/src/vs/workbench/api/common/extHostConfiguration.ts index 0d0db96080..36d88b8dbc 100644 --- a/src/vs/workbench/api/common/extHostConfiguration.ts +++ b/src/vs/workbench/api/common/extHostConfiguration.ts @@ -8,12 +8,10 @@ import { URI } from 'vs/base/common/uri'; import { Event, Emitter } from 'vs/base/common/event'; import * as vscode from 'vscode'; import { ExtHostWorkspace, IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; -import { ExtHostConfigurationShape, MainThreadConfigurationShape, IWorkspaceConfigurationChangeEventData, IConfigurationInitData, MainContext } from './extHost.protocol'; +import { ExtHostConfigurationShape, MainThreadConfigurationShape, IConfigurationInitData, MainContext } from './extHost.protocol'; import { ConfigurationTarget as ExtHostConfigurationTarget } from './extHostTypes'; -import { IConfigurationData, ConfigurationTarget, IConfigurationModel } from 'vs/platform/configuration/common/configuration'; -import { Configuration, ConfigurationChangeEvent, ConfigurationModel } from 'vs/platform/configuration/common/configurationModels'; -import { WorkspaceConfigurationChangeEvent } from 'vs/workbench/services/configuration/common/configurationModels'; -import { ResourceMap } from 'vs/base/common/map'; +import { ConfigurationTarget, IConfigurationChange, IConfigurationData } from 'vs/platform/configuration/common/configuration'; +import { Configuration, ConfigurationChangeEvent } from 'vs/platform/configuration/common/configurationModels'; import { ConfigurationScope, OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/configurationRegistry'; import { isObject } from 'vs/base/common/types'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; @@ -21,6 +19,7 @@ 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'; +import { Workspace } from 'vs/platform/workspace/common/workspace'; function lookUp(tree: any, key: string) { if (key) { @@ -72,8 +71,8 @@ export class ExtHostConfiguration implements ExtHostConfigurationShape { this._barrier.open(); } - $acceptConfigurationChanged(data: IConfigurationInitData, eventData: IWorkspaceConfigurationChangeEventData): void { - this.getConfigProvider().then(provider => provider.$acceptConfigurationChanged(data, eventData)); + $acceptConfigurationChanged(data: IConfigurationInitData, change: IConfigurationChange): void { + this.getConfigProvider().then(provider => provider.$acceptConfigurationChanged(data, change)); } } @@ -90,7 +89,7 @@ export class ExtHostConfigProvider { this._proxy = proxy; this._logService = logService; this._extHostWorkspace = extHostWorkspace; - this._configuration = ExtHostConfigProvider.parse(data); + this._configuration = Configuration.parse(data); this._configurationScopes = this._toMap(data.configurationScopes); } @@ -98,10 +97,11 @@ export class ExtHostConfigProvider { return this._onDidChangeConfiguration && this._onDidChangeConfiguration.event; } - $acceptConfigurationChanged(data: IConfigurationInitData, eventData: IWorkspaceConfigurationChangeEventData) { - this._configuration = ExtHostConfigProvider.parse(data); + $acceptConfigurationChanged(data: IConfigurationInitData, change: IConfigurationChange) { + const previous = { data: this._configuration.toData(), workspace: this._extHostWorkspace.workspace }; + this._configuration = Configuration.parse(data); this._configurationScopes = this._toMap(data.configurationScopes); - this._onDidChangeConfiguration.fire(this._toConfigurationChangeEvent(eventData)); + this._onDidChangeConfiguration.fire(this._toConfigurationChangeEvent(change, previous)); } getConfiguration(section?: string, resource?: URI, extensionId?: ExtensionIdentifier): vscode.WorkspaceConfiguration { @@ -254,17 +254,10 @@ export class ExtHostConfigProvider { } } - private _toConfigurationChangeEvent(data: IWorkspaceConfigurationChangeEventData): vscode.ConfigurationChangeEvent { - const changedConfiguration = new ConfigurationModel(data.changedConfiguration.contents, data.changedConfiguration.keys, data.changedConfiguration.overrides); - const changedConfigurationByResource: ResourceMap = new ResourceMap(); - for (const key of Object.keys(data.changedConfigurationByResource)) { - const resource = URI.parse(key); - const model = data.changedConfigurationByResource[key]; - changedConfigurationByResource.set(resource, new ConfigurationModel(model.contents, model.keys, model.overrides)); - } - const event = new WorkspaceConfigurationChangeEvent(new ConfigurationChangeEvent(changedConfiguration, changedConfigurationByResource), this._extHostWorkspace.workspace); + private _toConfigurationChangeEvent(change: IConfigurationChange, previous: { data: IConfigurationData, workspace: Workspace | undefined }): vscode.ConfigurationChangeEvent { + const event = new ConfigurationChangeEvent(change, previous, this._configuration, this._extHostWorkspace.workspace); return Object.freeze({ - affectsConfiguration: (section: string, resource?: URI) => event.affectsConfiguration(section, resource) + affectsConfiguration: (section: string, resource?: URI) => event.affectsConfiguration(section, resource ? { resource } : undefined) }); } @@ -272,20 +265,6 @@ export class ExtHostConfigProvider { return scopes.reduce((result, scope) => { result.set(scope[0], scope[1]); return result; }, new Map()); } - private static parse(data: IConfigurationData): Configuration { - const defaultConfiguration = ExtHostConfigProvider.parseConfigurationModel(data.defaults); - const userConfiguration = ExtHostConfigProvider.parseConfigurationModel(data.user); - const workspaceConfiguration = ExtHostConfigProvider.parseConfigurationModel(data.workspace); - const folders: ResourceMap = data.folders.reduce((result, value) => { - result.set(URI.revive(value[0]), ExtHostConfigProvider.parseConfigurationModel(value[1])); - return result; - }, new ResourceMap()); - return new Configuration(defaultConfiguration, userConfiguration, new ConfigurationModel(), workspaceConfiguration, folders, new ConfigurationModel(), new ResourceMap(), false); - } - - private static parseConfigurationModel(model: IConfigurationModel): ConfigurationModel { - return new ConfigurationModel(model.contents, model.keys, model.overrides).freeze(); - } } export const IExtHostConfiguration = createDecorator('IExtHostConfiguration'); diff --git a/src/vs/workbench/api/common/extHostDebugService.ts b/src/vs/workbench/api/common/extHostDebugService.ts index 77595d63a1..981c641e76 100644 --- a/src/vs/workbench/api/common/extHostDebugService.ts +++ b/src/vs/workbench/api/common/extHostDebugService.ts @@ -825,7 +825,7 @@ export class ExtHostDebugServiceBase implements IExtHostDebugService, ExtHostDeb 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 this._commandService.executeCommand(aex, rootFolder).then((ae: any) => { return new DebugAdapterExecutable(ae.command, ae.args || []); }); } @@ -1049,9 +1049,9 @@ class DirectDebugAdapter extends AbstractDebugAdapter { constructor(private implementation: vscode.DebugAdapter) { super(); - if (this.implementation.onSendMessage) { - implementation.onSendMessage((message: DebugProtocol.ProtocolMessage) => { - this.acceptMessage(message); + if (this.implementation.onDidSendMessage) { + implementation.onDidSendMessage((message: vscode.DebugProtocolMessage) => { + this.acceptMessage(message as DebugProtocol.ProtocolMessage); }); } } diff --git a/src/vs/workbench/api/common/extHostTunnelService.ts b/src/vs/workbench/api/common/extHostTunnelService.ts index ddca398ef8..8360d769cf 100644 --- a/src/vs/workbench/api/common/extHostTunnelService.ts +++ b/src/vs/workbench/api/common/extHostTunnelService.ts @@ -3,11 +3,9 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ExtHostTunnelServiceShape, MainThreadTunnelServiceShape, MainContext } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostTunnelServiceShape } from 'vs/workbench/api/common/extHost.protocol'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import * as vscode from 'vscode'; -import { Disposable } from 'vs/base/common/lifecycle'; export interface TunnelOptions { remote: { port: number, host: string }; @@ -28,39 +26,3 @@ export interface IExtHostTunnelService extends ExtHostTunnelServiceShape { } export const IExtHostTunnelService = createDecorator('IExtHostTunnelService'); - - -export class ExtHostTunnelService extends Disposable implements IExtHostTunnelService { - readonly _serviceBrand: undefined; - private readonly _proxy: MainThreadTunnelServiceShape; - - constructor( - @IExtHostRpcService extHostRpc: IExtHostRpcService - ) { - super(); - this._proxy = extHostRpc.getProxy(MainContext.MainThreadTunnelService); - } - async makeTunnel(forward: TunnelOptions): Promise { - const tunnel = await this._proxy.$openTunnel(forward); - if (tunnel) { - const disposableTunnel: vscode.Tunnel = { - remote: tunnel.remote, - localAddress: tunnel.localAddress, - dispose: () => { - return this._proxy.$closeTunnel(tunnel.remote.port); - } - }; - this._register(disposableTunnel); - return disposableTunnel; - } - return undefined; - } - - async addDetected(tunnels: { remote: { port: number, host: string }, localAddress: string }[] | undefined): Promise { - if (tunnels) { - return this._proxy.$addDetected(tunnels); - } - } - -} - diff --git a/src/vs/workbench/api/node/extHost.services.ts b/src/vs/workbench/api/node/extHost.services.ts index 86fdb68a46..e2d5fdf6c8 100644 --- a/src/vs/workbench/api/node/extHost.services.ts +++ b/src/vs/workbench/api/node/extHost.services.ts @@ -26,7 +26,8 @@ import { ExtHostExtensionService } from 'vs/workbench/api/node/extHostExtensionS import { IExtHostStorage, ExtHostStorage } from 'vs/workbench/api/common/extHostStorage'; import { ILogService } from 'vs/platform/log/common/log'; import { ExtHostLogService } from 'vs/workbench/api/node/extHostLogService'; -import { IExtHostTunnelService, ExtHostTunnelService } from 'vs/workbench/api/common/extHostTunnelService'; +import { IExtHostTunnelService } from 'vs/workbench/api/common/extHostTunnelService'; +import { ExtHostTunnelService } from 'vs/workbench/api/node/extHostTunnelService'; // register singleton services registerSingleton(ILogService, ExtHostLogService); diff --git a/src/vs/workbench/api/node/extHostTunnelService.ts b/src/vs/workbench/api/node/extHostTunnelService.ts new file mode 100644 index 0000000000..719b3a60e1 --- /dev/null +++ b/src/vs/workbench/api/node/extHostTunnelService.ts @@ -0,0 +1,149 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { MainThreadTunnelServiceShape, MainContext } from 'vs/workbench/api/common/extHost.protocol'; +import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; +import * as vscode from 'vscode'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; +import { URI } from 'vs/base/common/uri'; +import { exec } from 'child_process'; +import * as resources from 'vs/base/common/resources'; +import * as fs from 'fs'; +import { isLinux } from 'vs/base/common/platform'; +import { IExtHostTunnelService, TunnelOptions } from 'vs/workbench/api/common/extHostTunnelService'; + +export class ExtHostTunnelService extends Disposable implements IExtHostTunnelService { + readonly _serviceBrand: undefined; + private readonly _proxy: MainThreadTunnelServiceShape; + + constructor( + @IExtHostRpcService extHostRpc: IExtHostRpcService, + @IExtHostInitDataService initData: IExtHostInitDataService + ) { + super(); + this._proxy = extHostRpc.getProxy(MainContext.MainThreadTunnelService); + if (initData.remote.isRemote && initData.remote.authority) { + this.registerCandidateFinder(); + } + } + async makeTunnel(forward: TunnelOptions): Promise { + const tunnel = await this._proxy.$openTunnel(forward); + if (tunnel) { + const disposableTunnel: vscode.Tunnel = { + remote: tunnel.remote, + localAddress: tunnel.localAddress, + dispose: () => { + return this._proxy.$closeTunnel(tunnel.remote.port); + } + }; + this._register(disposableTunnel); + return disposableTunnel; + } + return undefined; + } + + async addDetected(tunnels: { remote: { port: number, host: string }, localAddress: string }[] | undefined): Promise { + if (tunnels) { + return this._proxy.$addDetected(tunnels); + } + } + + registerCandidateFinder(): Promise { + return this._proxy.$registerCandidateFinder(); + } + + async $findCandidatePorts(): Promise<{ port: number, detail: string }[]> { + if (!isLinux) { + return []; + } + + const ports: { port: number, detail: string }[] = []; + const tcp: string = fs.readFileSync('/proc/net/tcp', 'utf8'); + const tcp6: string = fs.readFileSync('/proc/net/tcp6', 'utf8'); + const procSockets: string = await (new Promise(resolve => { + exec('ls -l /proc/[0-9]*/fd/[0-9]* | grep socket:', (error, stdout, stderr) => { + resolve(stdout); + }); + })); + + const procChildren = fs.readdirSync('/proc'); + const processes: { pid: number, cwd: string, cmd: string }[] = []; + for (let childName of procChildren) { + try { + const pid: number = Number(childName); + const childUri = resources.joinPath(URI.file('/proc'), childName); + const childStat = fs.statSync(childUri.fsPath); + if (childStat.isDirectory() && !isNaN(pid)) { + const cwd = fs.readlinkSync(resources.joinPath(childUri, 'cwd').fsPath); + const cmd = fs.readFileSync(resources.joinPath(childUri, 'cmdline').fsPath, 'utf8').replace(/\0/g, ' '); + processes.push({ pid, cwd, cmd }); + } + } catch (e) { + // + } + } + + const connections: { socket: number, ip: string, port: number }[] = this.loadListeningPorts(tcp, tcp6); + const sockets = this.getSockets(procSockets); + + const socketMap = sockets.reduce((m, socket) => { + m[socket.socket] = socket; + return m; + }, {} as Record); + const processMap = processes.reduce((m, process) => { + m[process.pid] = process; + return m; + }, {} as Record); + + connections.filter((connection => socketMap[connection.socket])).forEach(({ socket, ip, port }) => { + const command = processMap[socketMap[socket].pid].cmd; + if (!command.match('.*\.vscode\-server\-[a-zA-Z]+\/bin.*') && (command.indexOf('out/vs/server/main.js') === -1)) { + ports.push({ port, detail: processMap[socketMap[socket].pid].cmd }); + } + }); + + return ports; + } + + private getSockets(stdout: string) { + const lines = stdout.trim().split('\n'); + return lines.map(line => { + const match = /\/proc\/(\d+)\/fd\/\d+ -> socket:\[(\d+)\]/.exec(line)!; + return { + pid: parseInt(match[1], 10), + socket: parseInt(match[2], 10) + }; + }); + } + + private loadListeningPorts(...stdouts: string[]): { socket: number, ip: string, port: number }[] { + const table = ([] as Record[]).concat(...stdouts.map(this.loadConnectionTable)); + return [ + ...new Map( + table.filter(row => row.st === '0A') + .map(row => { + const address = row.local_address.split(':'); + return { + socket: parseInt(row.inode, 10), + ip: address[0], + port: parseInt(address[1], 16) + }; + }).map(port => [port.port, port]) + ).values() + ]; + } + + private loadConnectionTable(stdout: string): Record[] { + const lines = stdout.trim().split('\n'); + const names = lines.shift()!.trim().split(/\s+/) + .filter(name => name !== 'rx_queue' && name !== 'tm->when'); + const table = lines.map(line => line.trim().split(/\s+/).reduce((obj, value, i) => { + obj[names[i] || i] = value; + return obj; + }, {} as Record)); + return table; + } +} diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 7f34fec989..1b359d0799 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -542,7 +542,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } // Empty workbench - else if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY && this.configurationService.inspect('workbench.startupEditor').value === 'newUntitledFile') { + else if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY && this.configurationService.getValue('workbench.startupEditor') === 'newUntitledFile') { if (this.editorGroupService.willRestoreEditors) { return []; // do not open any empty untitled file if we restored editors from previous session } diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index 8dec032b03..2e24ee2e91 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts @@ -47,7 +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'; +import { IResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; class Item extends BreadcrumbsItem { @@ -169,7 +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, + @IResourceConfigurationService private readonly _textResourceConfigurationService: IResourceConfigurationService, @IFileService private readonly _fileService: IFileService, @ITelemetryService private readonly _telemetryService: ITelemetryService, @ILabelService private readonly _labelService: ILabelService, diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts index 165dd96fd4..d849fa99d5 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts @@ -24,7 +24,7 @@ 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'; +import { IResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; export class FileElement { constructor( @@ -55,7 +55,7 @@ export class EditorBreadcrumbsModel { private readonly _uri: URI, private readonly _editor: ICodeEditor | undefined, @IConfigurationService private readonly _configurationService: IConfigurationService, - @ITextResourceConfigurationService private readonly _textResourceConfigurationService: ITextResourceConfigurationService, + @IResourceConfigurationService private readonly _textResourceConfigurationService: IResourceConfigurationService, @IWorkspaceContextService workspaceService: IWorkspaceContextService, ) { this._cfgFilePath = BreadcrumbsConfig.FilePath.bindTo(_configurationService); diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index 12923804ea..96befa2ce8 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -36,7 +36,7 @@ import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common import { ITextFileService, SUPPORTED_ENCODINGS } from 'vs/workbench/services/textfile/common/textfiles'; import { ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents'; import { ConfigurationChangedEvent, IEditorOptions, EditorOption } from 'vs/editor/common/config/editorOptions'; -import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; +import { IResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { deepClone } from 'vs/base/common/objects'; import { ICodeEditor, getCodeEditor } from 'vs/editor/browser/editorBrowser'; @@ -1249,7 +1249,7 @@ export class ChangeEncodingAction extends Action { actionLabel: string, @IEditorService private readonly editorService: IEditorService, @IQuickInputService private readonly quickInputService: IQuickInputService, - @ITextResourceConfigurationService private readonly textResourceConfigurationService: ITextResourceConfigurationService, + @IResourceConfigurationService private readonly textResourceConfigurationService: IResourceConfigurationService, @IFileService private readonly fileService: IFileService, @ITextFileService private readonly textFileService: ITextFileService ) { diff --git a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts index 2312cab6ff..982b4ddcba 100644 --- a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts @@ -16,7 +16,7 @@ import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditorWidget'; import { TextDiffEditorModel } from 'vs/workbench/common/editor/textDiffEditorModel'; 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 { IResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { TextFileOperationError, TextFileOperationResult } from 'vs/workbench/services/textfile/common/textfiles'; @@ -48,7 +48,7 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor { @ITelemetryService telemetryService: ITelemetryService, @IInstantiationService instantiationService: IInstantiationService, @IStorageService storageService: IStorageService, - @ITextResourceConfigurationService configurationService: ITextResourceConfigurationService, + @IResourceConfigurationService configurationService: IResourceConfigurationService, @IEditorService editorService: IEditorService, @IThemeService themeService: IThemeService, @IEditorGroupsService editorGroupService: IEditorGroupsService, diff --git a/src/vs/workbench/browser/parts/editor/textEditor.ts b/src/vs/workbench/browser/parts/editor/textEditor.ts index 0c9678e2a9..22c3d02806 100644 --- a/src/vs/workbench/browser/parts/editor/textEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textEditor.ts @@ -16,7 +16,7 @@ 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 { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; +import { IResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { isCodeEditor, getCodeEditor } from 'vs/editor/browser/editorBrowser'; import { IEditorGroupsService, IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -46,7 +46,7 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor { @ITelemetryService telemetryService: ITelemetryService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @IStorageService storageService: IStorageService, - @ITextResourceConfigurationService private readonly _configurationService: ITextResourceConfigurationService, + @IResourceConfigurationService private readonly _configurationService: IResourceConfigurationService, @IThemeService protected themeService: IThemeService, @IEditorService protected editorService: IEditorService, @IEditorGroupsService protected editorGroupService: IEditorGroupsService @@ -66,7 +66,7 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor { return this._instantiationService; } - protected get configurationService(): ITextResourceConfigurationService { + protected get configurationService(): IResourceConfigurationService { return this._configurationService; } diff --git a/src/vs/workbench/browser/parts/editor/textResourceEditor.ts b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts index fc973f686f..ecd010d578 100644 --- a/src/vs/workbench/browser/parts/editor/textResourceEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts @@ -13,7 +13,7 @@ import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledText 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 { IResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { Event } from 'vs/base/common/event'; @@ -33,7 +33,7 @@ export class AbstractTextResourceEditor extends BaseTextEditor { @ITelemetryService telemetryService: ITelemetryService, @IInstantiationService instantiationService: IInstantiationService, @IStorageService storageService: IStorageService, - @ITextResourceConfigurationService configurationService: ITextResourceConfigurationService, + @IResourceConfigurationService configurationService: IResourceConfigurationService, @IThemeService themeService: IThemeService, @IEditorGroupsService editorGroupService: IEditorGroupsService, @IEditorService editorService: IEditorService @@ -184,7 +184,7 @@ export class TextResourceEditor extends AbstractTextResourceEditor { @ITelemetryService telemetryService: ITelemetryService, @IInstantiationService instantiationService: IInstantiationService, @IStorageService storageService: IStorageService, - @ITextResourceConfigurationService configurationService: ITextResourceConfigurationService, + @IResourceConfigurationService configurationService: IResourceConfigurationService, @IThemeService themeService: IThemeService, @IEditorService editorService: IEditorService, @IEditorGroupsService editorGroupService: IEditorGroupsService diff --git a/src/vs/workbench/browser/parts/titlebar/media/chrome-close-dark.svg b/src/vs/workbench/browser/parts/titlebar/media/chrome-close-dark.svg deleted file mode 100644 index bb243036bb..0000000000 --- a/src/vs/workbench/browser/parts/titlebar/media/chrome-close-dark.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/titlebar/media/chrome-close.svg b/src/vs/workbench/browser/parts/titlebar/media/chrome-close.svg deleted file mode 100644 index 7abec27cd9..0000000000 --- a/src/vs/workbench/browser/parts/titlebar/media/chrome-close.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/titlebar/media/chrome-maximize-dark.svg b/src/vs/workbench/browser/parts/titlebar/media/chrome-maximize-dark.svg deleted file mode 100644 index b6645e8c82..0000000000 --- a/src/vs/workbench/browser/parts/titlebar/media/chrome-maximize-dark.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/titlebar/media/chrome-maximize.svg b/src/vs/workbench/browser/parts/titlebar/media/chrome-maximize.svg deleted file mode 100644 index 781322be05..0000000000 --- a/src/vs/workbench/browser/parts/titlebar/media/chrome-maximize.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/titlebar/media/chrome-minimize-dark.svg b/src/vs/workbench/browser/parts/titlebar/media/chrome-minimize-dark.svg deleted file mode 100644 index 1f6a7016f8..0000000000 --- a/src/vs/workbench/browser/parts/titlebar/media/chrome-minimize-dark.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/titlebar/media/chrome-minimize.svg b/src/vs/workbench/browser/parts/titlebar/media/chrome-minimize.svg deleted file mode 100644 index 80ecf45c9a..0000000000 --- a/src/vs/workbench/browser/parts/titlebar/media/chrome-minimize.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/titlebar/media/chrome-restore-dark.svg b/src/vs/workbench/browser/parts/titlebar/media/chrome-restore-dark.svg deleted file mode 100644 index d9f814370b..0000000000 --- a/src/vs/workbench/browser/parts/titlebar/media/chrome-restore-dark.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/titlebar/media/chrome-restore.svg b/src/vs/workbench/browser/parts/titlebar/media/chrome-restore.svg deleted file mode 100644 index 3ab78151c1..0000000000 --- a/src/vs/workbench/browser/parts/titlebar/media/chrome-restore.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts index 70a7bb13c3..bc3b5f7d66 100644 --- a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts @@ -329,12 +329,9 @@ export class CustomMenubarControl extends MenubarControl { const menubarActiveWindowFgColor = theme.getColor(TITLE_BAR_ACTIVE_FOREGROUND); if (menubarActiveWindowFgColor) { collector.addRule(` - .monaco-workbench .menubar > .menubar-menu-button { - color: ${menubarActiveWindowFgColor}; - } - + .monaco-workbench .menubar > .menubar-menu-button, .monaco-workbench .menubar .toolbar-toggle-more { - background-color: ${menubarActiveWindowFgColor} + color: ${menubarActiveWindowFgColor}; } `); } @@ -342,12 +339,9 @@ export class CustomMenubarControl extends MenubarControl { const activityBarInactiveFgColor = theme.getColor(ACTIVITY_BAR_INACTIVE_FOREGROUND); if (activityBarInactiveFgColor) { collector.addRule(` - .monaco-workbench .menubar.compact > .menubar-menu-button { - color: ${activityBarInactiveFgColor}; - } - + .monaco-workbench .menubar.compact > .menubar-menu-button, .monaco-workbench .menubar.compact .toolbar-toggle-more { - background-color: ${activityBarInactiveFgColor} + color: ${activityBarInactiveFgColor}; } `); @@ -358,14 +352,11 @@ export class CustomMenubarControl extends MenubarControl { collector.addRule(` .monaco-workbench .menubar.compact > .menubar-menu-button.open, .monaco-workbench .menubar.compact > .menubar-menu-button:focus, - .monaco-workbench .menubar.compact:not(:focus-within) > .menubar-menu-button:hover { - color: ${activityBarFgColor}; - } - + .monaco-workbench .menubar.compact:not(:focus-within) > .menubar-menu-button:hover, .monaco-workbench .menubar.compact > .menubar-menu-button.open .toolbar-toggle-more, .monaco-workbench .menubar.compact > .menubar-menu-button:focus .toolbar-toggle-more, .monaco-workbench .menubar.compact:not(:focus-within) > .menubar-menu-button:hover .toolbar-toggle-more { - background-color: ${activityBarFgColor} + color: ${activityBarFgColor}; } `); } @@ -373,13 +364,10 @@ export class CustomMenubarControl extends MenubarControl { const menubarInactiveWindowFgColor = theme.getColor(TITLE_BAR_INACTIVE_FOREGROUND); if (menubarInactiveWindowFgColor) { collector.addRule(` - .monaco-workbench .menubar.inactive:not(.compact) > .menubar-menu-button { + .monaco-workbench .menubar.inactive:not(.compact) > .menubar-menu-button, + .monaco-workbench .menubar.inactive:not(.compact) > .menubar-menu-button .toolbar-toggle-more { color: ${menubarInactiveWindowFgColor}; } - - .monaco-workbench .menubar.inactive:not(.compact) > .menubar-menu-button .toolbar-toggle-more { - background-color: ${menubarInactiveWindowFgColor} - } `); } @@ -389,14 +377,11 @@ export class CustomMenubarControl extends MenubarControl { collector.addRule(` .monaco-workbench .menubar:not(.compact) > .menubar-menu-button.open, .monaco-workbench .menubar:not(.compact) > .menubar-menu-button:focus, - .monaco-workbench .menubar:not(:focus-within):not(.compact) > .menubar-menu-button:hover { - color: ${menubarSelectedFgColor}; - } - + .monaco-workbench .menubar:not(:focus-within):not(.compact) > .menubar-menu-button:hover, .monaco-workbench .menubar:not(.compact) > .menubar-menu-button.open .toolbar-toggle-more, .monaco-workbench .menubar:not(.compact) > .menubar-menu-button:focus .toolbar-toggle-more, .monaco-workbench .menubar:not(:focus-within):not(.compact) > .menubar-menu-button:hover .toolbar-toggle-more { - background-color: ${menubarSelectedFgColor} + color: ${menubarSelectedFgColor}; } `); } diff --git a/src/vs/workbench/common/editor/editorGroup.ts b/src/vs/workbench/common/editor/editorGroup.ts index 329e33a729..478eb7a017 100644 --- a/src/vs/workbench/common/editor/editorGroup.ts +++ b/src/vs/workbench/common/editor/editorGroup.ts @@ -492,7 +492,19 @@ export class EditorGroup extends Disposable { // Add if (!del && editor) { - this.mru.push(editor); // make it LRU editor + if (this.mru.length === 0) { + // the list of most recent editors is empty + // so this editor can only be the most recent + this.mru.push(editor); + } else { + // we have most recent editors. as such we + // put this newly opened editor right after + // the current most recent one because it cannot + // be the most recently active one unless + // it becomes active. but it is still more + // active then any other editor in the list. + this.mru.splice(1, 0, editor); + } } // Remove / Replace @@ -552,7 +564,7 @@ export class EditorGroup extends Disposable { // Remove old index this.mru.splice(mruIndex, 1); - // Set editor to front + // Set editor as most recent one (first) this.mru.unshift(editor); } diff --git a/src/vs/workbench/common/editor/untitledTextEditorModel.ts b/src/vs/workbench/common/editor/untitledTextEditorModel.ts index e1990c1c41..1457859c0f 100644 --- a/src/vs/workbench/common/editor/untitledTextEditorModel.ts +++ b/src/vs/workbench/common/editor/untitledTextEditorModel.ts @@ -12,7 +12,7 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { Event, Emitter } from 'vs/base/common/event'; import { RunOnceScheduler } from 'vs/base/common/async'; import { IBackupFileService, IResolvedBackup } from 'vs/workbench/services/backup/common/backup'; -import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; +import { IResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; import { ITextBufferFactory } from 'vs/editor/common/model'; import { createTextBufferFactory } from 'vs/editor/common/model/textModel'; import { IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService'; @@ -48,7 +48,7 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IEnc @IModeService modeService: IModeService, @IModelService modelService: IModelService, @IBackupFileService private readonly backupFileService: IBackupFileService, - @ITextResourceConfigurationService private readonly configurationService: ITextResourceConfigurationService, + @IResourceConfigurationService private readonly configurationService: IResourceConfigurationService, @IWorkingCopyService private readonly workingCopyService: IWorkingCopyService, @ITextFileService private readonly textFileService: ITextFileService ) { diff --git a/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts b/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts index 8387e3921b..e84349ef3d 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts @@ -13,7 +13,7 @@ import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService import { EditorOption, EditorOptions } from 'vs/editor/common/config/editorOptions'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; -import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; +import { IResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; @@ -54,7 +54,7 @@ function readTransientState(model: ITextModel, codeEditorService: ICodeEditorSer return codeEditorService.getTransientModelProperty(model, transientWordWrapState); } -function readWordWrapState(model: ITextModel, configurationService: ITextResourceConfigurationService, codeEditorService: ICodeEditorService): IWordWrapState { +function readWordWrapState(model: ITextModel, configurationService: IResourceConfigurationService, codeEditorService: ICodeEditorService): IWordWrapState { const editorConfig = configurationService.getValue(model.uri, 'editor') as { wordWrap: 'on' | 'off' | 'wordWrapColumn' | 'bounded'; wordWrapMinified: boolean }; let _configuredWordWrap = editorConfig && (typeof editorConfig.wordWrap === 'string' || typeof editorConfig.wordWrap === 'boolean') ? editorConfig.wordWrap : undefined; @@ -146,7 +146,7 @@ class ToggleWordWrapAction extends EditorAction { return; } - const textResourceConfigurationService = accessor.get(ITextResourceConfigurationService); + const textResourceConfigurationService = accessor.get(IResourceConfigurationService); const codeEditorService = accessor.get(ICodeEditorService); const model = editor.getModel(); @@ -171,7 +171,7 @@ class ToggleWordWrapController extends Disposable implements IEditorContribution constructor( private readonly editor: ICodeEditor, @IContextKeyService readonly contextKeyService: IContextKeyService, - @ITextResourceConfigurationService readonly configurationService: ITextResourceConfigurationService, + @IResourceConfigurationService readonly configurationService: IResourceConfigurationService, @ICodeEditorService readonly codeEditorService: ICodeEditorService ) { super(); diff --git a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts index 213a2e6f1c..ca0e02091e 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts @@ -641,7 +641,7 @@ registerThemingParticipant((theme, collector) => { .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-hint:not([class*='codicon-debug-breakpoint']):not([class*='codicon-debug-stackframe']), .monaco-workbench .codicon-debug-breakpoint.codicon-debug-stackframe-focused::after, .monaco-workbench .codicon-debug-breakpoint.codicon-debug-stackframe::after { color: ${debugIconBreakpointColor} !important; @@ -662,7 +662,7 @@ registerThemingParticipant((theme, collector) => { if (debugIconBreakpointUnverifiedColor) { collector.addRule(` .monaco-workbench .codicon[class*='-unverified'] { - color: ${debugIconBreakpointUnverifiedColor} !important; + color: ${debugIconBreakpointUnverifiedColor}; } `); } @@ -670,7 +670,8 @@ registerThemingParticipant((theme, collector) => { const debugIconBreakpointCurrentStackframeForegroundColor = theme.getColor(debugIconBreakpointCurrentStackframeForeground); if (debugIconBreakpointCurrentStackframeForegroundColor) { collector.addRule(` - .monaco-workbench .codicon-debug-stackframe { + .monaco-workbench .codicon-debug-stackframe, + .monaco-editor .debug-top-stack-frame-column::before { color: ${debugIconBreakpointCurrentStackframeForegroundColor} !important; } `); diff --git a/src/vs/workbench/contrib/debug/browser/callStackEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/callStackEditorContribution.ts index b88f170c9d..4dd38fff06 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackEditorContribution.ts @@ -119,7 +119,6 @@ class CallStackEditorContribution implements IEditorContribution { private static TOP_STACK_FRAME_DECORATION: IModelDecorationOptions = { isWholeLine: true, - inlineClassName: 'debug-remove-token-colors', className: 'debug-top-stack-frame-line', stickiness }; @@ -130,7 +129,6 @@ class CallStackEditorContribution implements IEditorContribution { private static FOCUSED_STACK_FRAME_DECORATION: IModelDecorationOptions = { isWholeLine: true, - inlineClassName: 'debug-remove-token-colors', className: 'debug-focused-stack-frame-line', stickiness }; diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index e7b6f4a05d..a12101098d 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -84,7 +84,7 @@ Registry.as(ViewletExtensions.Viewlets).registerViewlet(Viewlet DebugViewlet, VIEWLET_ID, nls.localize('debugAndRun', "Debug and Run"), - 'codicon-debug', + 'codicon-debug-alt', 13 // {{SQL CARBON EDIT}} )); diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts index 78e10a6548..771642b544 100644 --- a/src/vs/workbench/contrib/debug/browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugService.ts @@ -11,7 +11,6 @@ import * as errors from 'vs/base/common/errors'; import severity from 'vs/base/common/severity'; import * as aria from 'vs/base/browser/ui/aria/aria'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { IMarkerService } from 'vs/platform/markers/common/markers'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -22,18 +21,15 @@ import { DebugModel, ExceptionBreakpoint, FunctionBreakpoint, Breakpoint, Expres import { ViewModel } from 'vs/workbench/contrib/debug/common/debugViewModel'; import * as debugactions from 'vs/workbench/contrib/debug/browser/debugActions'; import { ConfigurationManager } from 'vs/workbench/contrib/debug/browser/debugConfigurationManager'; -import Constants from 'vs/workbench/contrib/markers/browser/constants'; -import { ITaskService, ITaskSummary } from 'vs/workbench/contrib/tasks/common/taskService'; import { VIEWLET_ID as EXPLORER_VIEWLET_ID } from 'vs/workbench/contrib/files/common/files'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder, IWorkspace } from 'vs/platform/workspace/common/workspace'; +import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { parse, getFirstFrame } from 'vs/base/common/console'; -import { TaskEvent, TaskEventKind, TaskIdentifier } from 'vs/workbench/contrib/tasks/common/tasks'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IAction } from 'vs/base/common/actions'; @@ -42,12 +38,13 @@ 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, CONTEXT_DEBUG_UX } from 'vs/workbench/contrib/debug/common/debug'; import { getExtensionHostDebugSession } from 'vs/workbench/contrib/debug/common/debugUtils'; -import { isErrorWithActions, createErrorWithActions } from 'vs/base/common/errorsWithActions'; +import { isErrorWithActions } 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'; +import { TaskRunResult, DebugTaskRunner } from 'vs/workbench/contrib/debug/browser/debugTaskRunner'; +import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity'; const DEBUG_BREAKPOINTS_KEY = 'debug.breakpoint'; const DEBUG_FUNCTION_BREAKPOINTS_KEY = 'debug.functionbreakpoint'; @@ -55,23 +52,6 @@ const DEBUG_DATA_BREAKPOINTS_KEY = 'debug.databreakpoint'; const DEBUG_EXCEPTION_BREAKPOINTS_KEY = 'debug.exceptionbreakpoint'; const DEBUG_WATCH_EXPRESSIONS_KEY = 'debug.watchexpressions'; -function once(match: (e: TaskEvent) => boolean, event: Event): Event { - return (listener, thisArgs = null, disposables?) => { - const result = event(e => { - if (match(e)) { - result.dispose(); - return listener.call(thisArgs, e); - } - }, null, disposables); - return result; - }; -} - -const enum TaskRunResult { - Failure, - Success -} - export class DebugService implements IDebugService { _serviceBrand: undefined; @@ -81,6 +61,7 @@ export class DebugService implements IDebugService { private readonly _onDidEndSession: Emitter; private model: DebugModel; private viewModel: ViewModel; + private taskRunner: DebugTaskRunner; private configurationManager: ConfigurationManager; private toDispose: IDisposable[]; private debugType: IContextKey; @@ -91,6 +72,7 @@ export class DebugService implements IDebugService { private initializing = false; private previousState: State | undefined; private initCancellationToken: CancellationTokenSource | undefined; + private activity: IDisposable | undefined; constructor( @IStorageService private readonly storageService: IStorageService, @@ -107,11 +89,10 @@ export class DebugService implements IDebugService { @ILifecycleService private readonly lifecycleService: ILifecycleService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IExtensionService private readonly extensionService: IExtensionService, - @IMarkerService private readonly markerService: IMarkerService, - @ITaskService private readonly taskService: ITaskService, @IFileService private readonly fileService: IFileService, @IConfigurationService private readonly configurationService: IConfigurationService, - @IExtensionHostDebugService private readonly extensionHostDebugService: IExtensionHostDebugService + @IExtensionHostDebugService private readonly extensionHostDebugService: IExtensionHostDebugService, + @IActivityService private readonly activityService: IActivityService ) { this.toDispose = []; @@ -136,6 +117,7 @@ export class DebugService implements IDebugService { this.toDispose.push(this.model); this.viewModel = new ViewModel(contextKeyService); + this.taskRunner = this.instantiationService.createInstance(DebugTaskRunner); this.toDispose.push(this.fileService.onFileChanges(e => this.onFileChanges(e))); this.lifecycleService.onShutdown(this.dispose, this); @@ -176,6 +158,16 @@ export class DebugService implements IDebugService { this.toDispose.push(this.configurationManager.onDidSelectConfiguration(() => { this.debugUx.set(!!(this.state !== State.Inactive || this.configurationManager.selectedConfiguration.name) ? 'default' : 'simple'); })); + this.toDispose.push(Event.any(this.onDidNewSession, this.onDidEndSession)(() => { + const numberOfSessions = this.model.getSessions().length; + if (numberOfSessions === 0) { + if (this.activity) { + this.activity.dispose(); + } + } else { + this.activity = this.activityService.showActivity(VIEWLET_ID, new NumberBadge(numberOfSessions, n => n === 1 ? nls.localize('1activeSession', "1 active session") : nls.localize('nActiveSessions', "{0} active sessions", n))); + } + })); } getModel(): IDebugModel { @@ -299,7 +291,7 @@ export class DebugService implements IDebugService { "Compound must have \"configurations\" attribute set in order to start multiple configurations.")); } if (compound.preLaunchTask) { - const taskResult = await this.runTaskAndCheckErrors(launch?.workspace || this.contextService.getWorkspace(), compound.preLaunchTask); + const taskResult = await this.taskRunner.runTaskAndCheckErrors(launch?.workspace || this.contextService.getWorkspace(), compound.preLaunchTask, this.showError); if (taskResult === TaskRunResult.Failure) { this.endInitializingState(); return false; @@ -411,7 +403,7 @@ export class DebugService implements IDebugService { } const workspace = launch ? launch.workspace : this.contextService.getWorkspace(); - const taskResult = await this.runTaskAndCheckErrors(workspace, resolvedConfig.preLaunchTask); + const taskResult = await this.taskRunner.runTaskAndCheckErrors(workspace, resolvedConfig.preLaunchTask, this.showError); if (taskResult === TaskRunResult.Success) { return this.doCreateSession(launch?.workspace, { resolved: resolvedConfig, unresolved: unresolvedConfig }, options); } @@ -548,7 +540,7 @@ export class DebugService implements IDebugService { if (session.configuration.postDebugTask) { try { - await this.runTask(session.root, session.configuration.postDebugTask); + await this.taskRunner.runTask(session.root, session.configuration.postDebugTask); } catch (err) { this.notificationService.error(err); } @@ -587,8 +579,8 @@ export class DebugService implements IDebugService { return Promise.resolve(TaskRunResult.Success); } - await this.runTask(session.root, session.configuration.postDebugTask); - return this.runTaskAndCheckErrors(session.root, session.configuration.preLaunchTask); + await this.taskRunner.runTask(session.root, session.configuration.postDebugTask); + return this.taskRunner.runTaskAndCheckErrors(session.root, session.configuration.preLaunchTask, this.showError); }; const extensionDebugSession = getExtensionHostDebugSession(session); @@ -715,129 +707,6 @@ export class DebugService implements IDebugService { return undefined; } - //---- task management - - private async runTaskAndCheckErrors(root: IWorkspaceFolder | IWorkspace | undefined, taskId: string | TaskIdentifier | undefined): Promise { - try { - const taskSummary = await this.runTask(root, taskId); - - const errorCount = taskId ? this.markerService.getStatistics().errors : 0; - const successExitCode = taskSummary && taskSummary.exitCode === 0; - const failureExitCode = taskSummary && taskSummary.exitCode !== 0; - const onTaskErrors = this.configurationService.getValue('debug').onTaskErrors; - if (successExitCode || onTaskErrors === 'debugAnyway' || (errorCount === 0 && !failureExitCode)) { - return TaskRunResult.Success; - } - if (onTaskErrors === 'showErrors') { - this.panelService.openPanel(Constants.MARKERS_PANEL_ID); - return Promise.resolve(TaskRunResult.Failure); - } - - const taskLabel = typeof taskId === 'string' ? taskId : taskId ? taskId.name : ''; - const message = errorCount > 1 - ? nls.localize('preLaunchTaskErrors', "Errors exist after running preLaunchTask '{0}'.", taskLabel) - : errorCount === 1 - ? nls.localize('preLaunchTaskError', "Error exists after running preLaunchTask '{0}'.", taskLabel) - : nls.localize('preLaunchTaskExitCode', "The preLaunchTask '{0}' terminated with exit code {1}.", taskLabel, taskSummary ? taskSummary.exitCode : 0); - - const result = await this.dialogService.show(severity.Warning, message, [nls.localize('debugAnyway', "Debug Anyway"), nls.localize('showErrors', "Show Errors"), nls.localize('cancel', "Cancel")], { - checkbox: { - label: nls.localize('remember', "Remember my choice in user settings"), - }, - cancelId: 2 - }); - - if (result.choice === 2) { - return Promise.resolve(TaskRunResult.Failure); - } - const debugAnyway = result.choice === 0; - if (result.checkboxChecked) { - this.configurationService.updateValue('debug.onTaskErrors', debugAnyway ? 'debugAnyway' : 'showErrors'); - } - if (debugAnyway) { - return TaskRunResult.Success; - } - - this.panelService.openPanel(Constants.MARKERS_PANEL_ID); - return Promise.resolve(TaskRunResult.Failure); - } catch (err) { - await this.showError(err.message, [this.taskService.configureAction()]); - return TaskRunResult.Failure; - } - } - - private async runTask(root: IWorkspace | IWorkspaceFolder | undefined, taskId: string | TaskIdentifier | undefined): Promise { - if (!taskId) { - return Promise.resolve(null); - } - if (!root) { - return Promise.reject(new Error(nls.localize('invalidTaskReference', "Task '{0}' can not be referenced from a launch configuration that is in a different workspace folder.", typeof taskId === 'string' ? taskId : taskId.type))); - } - // run a task before starting a debug session - const task = await this.taskService.getTask(root, taskId); - if (!task) { - const errorMessage = typeof taskId === 'string' - ? nls.localize('DebugTaskNotFoundWithTaskId', "Could not find the task '{0}'.", taskId) - : nls.localize('DebugTaskNotFound', "Could not find the specified task."); - return Promise.reject(createErrorWithActions(errorMessage)); - } - - // If a task is missing the problem matcher the promise will never complete, so we need to have a workaround #35340 - let taskStarted = false; - const inactivePromise: Promise = new Promise((c, e) => once(e => { - // When a task isBackground it will go inactive when it is safe to launch. - // But when a background task is terminated by the user, it will also fire an inactive event. - // This means that we will not get to see the real exit code from running the task (undefined when terminated by the user). - // Catch the ProcessEnded event here, which occurs before inactive, and capture the exit code to prevent this. - return (e.kind === TaskEventKind.Inactive - || (e.kind === TaskEventKind.ProcessEnded && e.exitCode === undefined)) - && e.taskId === task._id; - }, this.taskService.onDidStateChange)(e => { - taskStarted = true; - c(e.kind === TaskEventKind.ProcessEnded ? { exitCode: e.exitCode } : null); - })); - - const promise: Promise = this.taskService.getActiveTasks().then(async (tasks): Promise => { - if (tasks.filter(t => t._id === task._id).length) { - // 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. - return Promise.resolve(null); - } - once(e => ((e.kind === TaskEventKind.Active) || (e.kind === TaskEventKind.DependsOnStarted)) && e.taskId === task._id, this.taskService.onDidStateChange)(() => { - // Task is active, so everything seems to be fine, no need to prompt after 10 seconds - // Use case being a slow running task should not be prompted even though it takes more than 10 seconds - taskStarted = true; - }); - const taskPromise = this.taskService.run(task); - if (task.configurationProperties.isBackground) { - return inactivePromise; - } - - return taskPromise.then(withUndefinedAsNull); - }); - - return new Promise((c, e) => { - promise.then(result => { - taskStarted = true; - c(result); - }, error => e(error)); - - setTimeout(() => { - if (!taskStarted) { - const errorMessage = typeof taskId === 'string' - ? nls.localize('taskNotTrackedWithTaskId', "The specified task cannot be tracked.") - : nls.localize('taskNotTracked', "The task '{0}' cannot be tracked.", JSON.stringify(taskId)); - e({ severity: severity.Error, message: errorMessage }); - } - }, 10000); - }); - } - //---- focus management async focusStackFrame(stackFrame: IStackFrame | undefined, thread?: IThread, session?: IDebugSession, explicit?: boolean): Promise { diff --git a/src/vs/workbench/contrib/debug/browser/debugSession.ts b/src/vs/workbench/contrib/debug/browser/debugSession.ts index 321b3e5420..190fb399b6 100644 --- a/src/vs/workbench/contrib/debug/browser/debugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/debugSession.ts @@ -691,6 +691,11 @@ export class DebugSession implements IDebugSession { } } + initializeForTest(raw: RawDebugSession): void { + this.raw = raw; + this.registerListeners(); + } + //---- private private registerListeners(): void { diff --git a/src/vs/workbench/contrib/debug/browser/debugTaskRunner.ts b/src/vs/workbench/contrib/debug/browser/debugTaskRunner.ts new file mode 100644 index 0000000000..9864baa114 --- /dev/null +++ b/src/vs/workbench/contrib/debug/browser/debugTaskRunner.ts @@ -0,0 +1,169 @@ +/*--------------------------------------------------------------------------------------------- + * 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 severity from 'vs/base/common/severity'; +import { Event } from 'vs/base/common/event'; +import Constants from 'vs/workbench/contrib/markers/browser/constants'; +import { ITaskService, ITaskSummary } from 'vs/workbench/contrib/tasks/common/taskService'; +import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IWorkspaceFolder, IWorkspace } from 'vs/platform/workspace/common/workspace'; +import { TaskEvent, TaskEventKind, TaskIdentifier } from 'vs/workbench/contrib/tasks/common/tasks'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IAction } from 'vs/base/common/actions'; +import { withUndefinedAsNull } from 'vs/base/common/types'; +import { IMarkerService } from 'vs/platform/markers/common/markers'; +import { IDebugConfiguration } from 'vs/workbench/contrib/debug/common/debug'; +import { createErrorWithActions } from 'vs/base/common/errorsWithActions'; + +function once(match: (e: TaskEvent) => boolean, event: Event): Event { + return (listener, thisArgs = null, disposables?) => { + const result = event(e => { + if (match(e)) { + result.dispose(); + return listener.call(thisArgs, e); + } + }, null, disposables); + return result; + }; +} + +export const enum TaskRunResult { + Failure, + Success +} + +export class DebugTaskRunner { + + constructor( + @ITaskService private readonly taskService: ITaskService, + @IMarkerService private readonly markerService: IMarkerService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @IPanelService private readonly panelService: IPanelService, + @IDialogService private readonly dialogService: IDialogService, + ) { } + + async runTaskAndCheckErrors(root: IWorkspaceFolder | IWorkspace | undefined, taskId: string | TaskIdentifier | undefined, onError: (msg: string, actions: IAction[]) => Promise): Promise { + try { + const taskSummary = await this.runTask(root, taskId); + + const errorCount = taskId ? this.markerService.getStatistics().errors : 0; + const successExitCode = taskSummary && taskSummary.exitCode === 0; + const failureExitCode = taskSummary && taskSummary.exitCode !== 0; + const onTaskErrors = this.configurationService.getValue('debug').onTaskErrors; + if (successExitCode || onTaskErrors === 'debugAnyway' || (errorCount === 0 && !failureExitCode)) { + return TaskRunResult.Success; + } + if (onTaskErrors === 'showErrors') { + this.panelService.openPanel(Constants.MARKERS_PANEL_ID); + return Promise.resolve(TaskRunResult.Failure); + } + + const taskLabel = typeof taskId === 'string' ? taskId : taskId ? taskId.name : ''; + const message = errorCount > 1 + ? nls.localize('preLaunchTaskErrors', "Errors exist after running preLaunchTask '{0}'.", taskLabel) + : errorCount === 1 + ? nls.localize('preLaunchTaskError', "Error exists after running preLaunchTask '{0}'.", taskLabel) + : nls.localize('preLaunchTaskExitCode', "The preLaunchTask '{0}' terminated with exit code {1}.", taskLabel, taskSummary ? taskSummary.exitCode : 0); + + const result = await this.dialogService.show(severity.Warning, message, [nls.localize('debugAnyway', "Debug Anyway"), nls.localize('showErrors', "Show Errors"), nls.localize('cancel', "Cancel")], { + checkbox: { + label: nls.localize('remember', "Remember my choice in user settings"), + }, + cancelId: 2 + }); + + if (result.choice === 2) { + return Promise.resolve(TaskRunResult.Failure); + } + const debugAnyway = result.choice === 0; + if (result.checkboxChecked) { + this.configurationService.updateValue('debug.onTaskErrors', debugAnyway ? 'debugAnyway' : 'showErrors'); + } + if (debugAnyway) { + return TaskRunResult.Success; + } + + this.panelService.openPanel(Constants.MARKERS_PANEL_ID); + return Promise.resolve(TaskRunResult.Failure); + } catch (err) { + await onError(err.message, [this.taskService.configureAction()]); + return TaskRunResult.Failure; + } + } + + async runTask(root: IWorkspace | IWorkspaceFolder | undefined, taskId: string | TaskIdentifier | undefined): Promise { + if (!taskId) { + return Promise.resolve(null); + } + if (!root) { + return Promise.reject(new Error(nls.localize('invalidTaskReference', "Task '{0}' can not be referenced from a launch configuration that is in a different workspace folder.", typeof taskId === 'string' ? taskId : taskId.type))); + } + // run a task before starting a debug session + const task = await this.taskService.getTask(root, taskId); + if (!task) { + const errorMessage = typeof taskId === 'string' + ? nls.localize('DebugTaskNotFoundWithTaskId', "Could not find the task '{0}'.", taskId) + : nls.localize('DebugTaskNotFound', "Could not find the specified task."); + return Promise.reject(createErrorWithActions(errorMessage)); + } + + // If a task is missing the problem matcher the promise will never complete, so we need to have a workaround #35340 + let taskStarted = false; + const inactivePromise: Promise = new Promise((c, e) => once(e => { + // When a task isBackground it will go inactive when it is safe to launch. + // But when a background task is terminated by the user, it will also fire an inactive event. + // This means that we will not get to see the real exit code from running the task (undefined when terminated by the user). + // Catch the ProcessEnded event here, which occurs before inactive, and capture the exit code to prevent this. + return (e.kind === TaskEventKind.Inactive + || (e.kind === TaskEventKind.ProcessEnded && e.exitCode === undefined)) + && e.taskId === task._id; + }, this.taskService.onDidStateChange)(e => { + taskStarted = true; + c(e.kind === TaskEventKind.ProcessEnded ? { exitCode: e.exitCode } : null); + })); + + const promise: Promise = this.taskService.getActiveTasks().then(async (tasks): Promise => { + if (tasks.filter(t => t._id === task._id).length) { + // 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. + return Promise.resolve(null); + } + once(e => ((e.kind === TaskEventKind.Active) || (e.kind === TaskEventKind.DependsOnStarted)) && e.taskId === task._id, this.taskService.onDidStateChange)(() => { + // Task is active, so everything seems to be fine, no need to prompt after 10 seconds + // Use case being a slow running task should not be prompted even though it takes more than 10 seconds + taskStarted = true; + }); + const taskPromise = this.taskService.run(task); + if (task.configurationProperties.isBackground) { + return inactivePromise; + } + + return taskPromise.then(withUndefinedAsNull); + }); + + return new Promise((c, e) => { + promise.then(result => { + taskStarted = true; + c(result); + }, error => e(error)); + + setTimeout(() => { + if (!taskStarted) { + const errorMessage = typeof taskId === 'string' + ? nls.localize('taskNotTrackedWithTaskId', "The specified task cannot be tracked.") + : nls.localize('taskNotTracked', "The task '{0}' cannot be tracked.", JSON.stringify(taskId)); + e({ severity: severity.Error, message: errorMessage }); + } + }, 10000); + }); + } +} 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 1577043466..7b04097b23 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css +++ b/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css @@ -7,8 +7,8 @@ cursor: pointer; } -.codicon-debug-hint:not([class*='codicon-debug-breakpoint']) { - opacity: .4 !important; +.codicon-debug-hint:not([class*='codicon-debug-breakpoint']):not([class*='codicon-debug-stackframe']) { + opacity: 0.4 !important; } .inline-breakpoint-widget.codicon { @@ -18,7 +18,7 @@ .codicon-debug-breakpoint.codicon-debug-stackframe-focused::after, .codicon-debug-breakpoint.codicon-debug-stackframe::after { - content: "\eb8a"; + content: '\eb8a'; position: absolute; } @@ -28,26 +28,38 @@ .monaco-editor .debug-breakpoint-placeholder::before, .monaco-editor .debug-top-stack-frame-column::before { - content: " "; + content: ' '; width: 0.9em; - display: inline-block; - vertical-align: text-bottom; + display: inline-flex; + vertical-align: middle; margin-right: 2px; margin-left: 2px; + margin-top: -1px; /* TODO @misolori: figure out a way to not use negative margin for alignment */ +} + +.monaco-editor .debug-top-stack-frame-column { + display: inline-flex; + vertical-align: middle; +} + +.monaco-editor .debug-top-stack-frame-column::before { + content: '\eb8b'; + font: normal normal normal 16px/1 codicon; + text-rendering: auto; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + margin-left: 0; + margin-right: 4px; } /* 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: ""; + content: ''; margin-right: 0px; margin-left: 0px; } -.monaco-editor .debug-top-stack-frame-column::before { - height: 1.3em; -} - .monaco-editor .inline-breakpoint-widget { cursor: pointer; } @@ -104,7 +116,7 @@ } .monaco-workbench .monaco-list-row .expression .name { - color: #9B46B0; + color: #9b46b0; } .monaco-workbench .monaco-list-row .expression .name.virtual { @@ -120,61 +132,61 @@ } .monaco-workbench .monaco-list-row .expression .error { - color: #E51400; + color: #e51400; } .monaco-workbench .monaco-list-row .expression .value.number { - color: #09885A; + color: #09885a; } .monaco-workbench .monaco-list-row .expression .value.boolean { - color: #0000FF; + color: #0000ff; } .monaco-workbench .monaco-list-row .expression .value.string { - color: #A31515; + color: #a31515; } -.vs-dark .monaco-workbench > .monaco-list-row .expression .value { +.vs-dark .monaco-workbench > .monaco-list-row .expression .value { color: rgba(204, 204, 204, 0.6); } -.vs-dark .monaco-workbench .monaco-list-row .expression .error { - color: #F48771; -} - -.vs-dark .monaco-workbench .monaco-list-row .expression .value.number { - color: #B5CEA8; -} - -.hc-black .monaco-workbench .monaco-list-row .expression .value.number { - color: #89d185; -} - -.hc-black .monaco-workbench .monaco-list-row .expression .value.boolean { - color: #75bdfe; -} - -.hc-black .monaco-workbench .monaco-list-row .expression .value.string { +.vs-dark .monaco-workbench .monaco-list-row .expression .error { color: #f48771; } -.vs-dark .monaco-workbench .monaco-list-row .expression .value.boolean { - color: #4E94CE; +.vs-dark .monaco-workbench .monaco-list-row .expression .value.number { + color: #b5cea8; } -.vs-dark .monaco-workbench .monaco-list-row .expression .value.string { - color: #CE9178; +.hc-black .monaco-workbench .monaco-list-row .expression .value.number { + color: #89d185; +} + +.hc-black .monaco-workbench .monaco-list-row .expression .value.boolean { + color: #75bdfe; +} + +.hc-black .monaco-workbench .monaco-list-row .expression .value.string { + color: #f48771; +} + +.vs-dark .monaco-workbench .monaco-list-row .expression .value.boolean { + color: #4e94ce; +} + +.vs-dark .monaco-workbench .monaco-list-row .expression .value.string { + color: #ce9178; } .hc-black .monaco-workbench .monaco-list-row .expression .error { - color: #F48771; + color: #f48771; } /* Dark theme */ .vs-dark .monaco-workbench .monaco-list-row .expression .name { - color: #C586C0; + color: #c586c0; } /* High Contrast Theming */ @@ -182,7 +194,3 @@ .hc-black .monaco-workbench .monaco-list-row .expression .name { color: inherit; } - -.hc-black .monaco-editor .debug-remove-token-colors { - color:black; -} diff --git a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts index 643798de80..e4702584e4 100644 --- a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts @@ -80,7 +80,6 @@ export class RawDebugSession implements IDisposable { public readonly customTelemetryService: ITelemetryService | undefined, private readonly extensionHostDebugService: IExtensionHostDebugService, private readonly openerService: IOpenerService - ) { this.debugAdapter = debugAdapter; this._capabilities = Object.create(null); @@ -598,13 +597,13 @@ export class RawDebugSession implements IDisposable { } private send(command: string, args: any, token?: CancellationToken, timeout?: number): Promise { - return new Promise((completeDispatch, errorDispatch) => { + return new Promise((completeDispatch, errorDispatch) => { if (!this.debugAdapter) { errorDispatch(new Error('no debug adapter found')); return; } let cancelationListener: IDisposable; - const requestId = this.debugAdapter.sendRequest(command, args, (response: R) => { + const requestId = this.debugAdapter.sendRequest(command, args, (response: DebugProtocol.Response) => { if (cancelationListener) { cancelationListener.dispose(); } diff --git a/src/vs/workbench/contrib/debug/browser/startView.ts b/src/vs/workbench/contrib/debug/browser/startView.ts index 9b220e3888..7f7b716220 100644 --- a/src/vs/workbench/contrib/debug/browser/startView.ts +++ b/src/vs/workbench/contrib/debug/browser/startView.ts @@ -25,7 +25,6 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; const $ = dom.$; - export class StartView extends ViewPane { static ID = 'workbench.debug.startView'; diff --git a/src/vs/workbench/contrib/debug/common/abstractDebugAdapter.ts b/src/vs/workbench/contrib/debug/common/abstractDebugAdapter.ts index be8cb3b8d2..82c40a4aae 100644 --- a/src/vs/workbench/contrib/debug/common/abstractDebugAdapter.ts +++ b/src/vs/workbench/contrib/debug/common/abstractDebugAdapter.ts @@ -5,7 +5,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { IDebugAdapter } from 'vs/workbench/contrib/debug/common/debug'; -import { timeout } from 'vs/base/common/async'; +import { timeout, Queue } from 'vs/base/common/async'; /** * Abstract implementation of the low level API for a debug adapter. @@ -18,6 +18,7 @@ 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; + private readonly queue = new Queue(); protected readonly _onError = new Emitter(); protected readonly _onExit = new Emitter(); @@ -108,26 +109,33 @@ export abstract class AbstractDebugAdapter implements IDebugAdapter { this.messageCallback(message); } else { - switch (message.type) { - case 'event': - if (this.eventCallback) { - this.eventCallback(message); - } - break; - case 'request': - if (this.requestCallback) { - this.requestCallback(message); - } - break; - case 'response': - const response = message; - const clb = this.pendingRequests.get(response.request_seq); - if (clb) { - this.pendingRequests.delete(response.request_seq); - clb(response); - } - break; - } + this.queue.queue(() => { + switch (message.type) { + case 'event': + if (this.eventCallback) { + this.eventCallback(message); + } + break; + case 'request': + if (this.requestCallback) { + this.requestCallback(message); + } + break; + case 'response': + const response = message; + const clb = this.pendingRequests.get(response.request_seq); + if (clb) { + this.pendingRequests.delete(response.request_seq); + clb(response); + } + break; + } + + // Artificially queueing protocol messages guarantees that any microtasks for + // previous message finish before next message is processed. This is essential + // to guarantee ordering when using promises anywhere along the call path. + return timeout(0); + }); } } @@ -164,6 +172,6 @@ export abstract class AbstractDebugAdapter implements IDebugAdapter { } dispose(): void { - // noop + this.queue.dispose(); } } diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index d8f0bec270..2ac872b337 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -548,7 +548,7 @@ export interface IDebugAdapterServer { } export interface IDebugAdapterInlineImpl extends IDisposable { - readonly onSendMessage: Event; + readonly onDidSendMessage: Event; handleMessage(message: DebugProtocol.Message): void; } diff --git a/src/vs/workbench/contrib/debug/test/browser/debugModel.test.ts b/src/vs/workbench/contrib/debug/test/browser/debugModel.test.ts index acb61c6980..f7d2239d1b 100644 --- a/src/vs/workbench/contrib/debug/test/browser/debugModel.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/debugModel.test.ts @@ -8,12 +8,14 @@ import { URI as uri } from 'vs/base/common/uri'; import severity from 'vs/base/common/severity'; import { DebugModel, Expression, StackFrame, Thread } from 'vs/workbench/contrib/debug/common/debugModel'; import * as sinon from 'sinon'; -import { MockRawSession } from 'vs/workbench/contrib/debug/test/common/mockDebug'; +import { MockRawSession, MockDebugAdapter } from 'vs/workbench/contrib/debug/test/common/mockDebug'; import { Source } from 'vs/workbench/contrib/debug/common/debugSource'; import { DebugSession } from 'vs/workbench/contrib/debug/browser/debugSession'; -import { SimpleReplElement, RawObjectReplElement, ReplEvaluationInput, ReplModel } from 'vs/workbench/contrib/debug/common/replModel'; +import { SimpleReplElement, RawObjectReplElement, ReplEvaluationInput, ReplModel, ReplEvaluationResult } from 'vs/workbench/contrib/debug/common/replModel'; import { IBreakpointUpdateData, IDebugSessionOptions } from 'vs/workbench/contrib/debug/common/debug'; import { NullOpenerService } from 'vs/platform/opener/common/opener'; +import { RawDebugSession } from 'vs/workbench/contrib/debug/browser/rawDebugSession'; +import { timeout } from 'vs/base/common/async'; function createMockSession(model: DebugModel, name = 'mockSession', options?: IDebugSessionOptions): DebugSession { return new DebugSession({ resolved: { name, type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, options, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService); @@ -548,4 +550,26 @@ suite('Debug - Model', () => { assert.equal(grandChild.getReplElements().length, 2); assert.equal(child3.getReplElements().length, 1); }); + + test('repl ordering', async () => { + const session = createMockSession(model); + model.addSession(session); + + const adapter = new MockDebugAdapter(); + const raw = new RawDebugSession(adapter, undefined!, undefined!, undefined!, undefined!, undefined!); + session.initializeForTest(raw); + + await session.addReplExpression(undefined, 'before.1'); + assert.equal(session.getReplElements().length, 3); + assert.equal((session.getReplElements()[0]).value, 'before.1'); + assert.equal((session.getReplElements()[1]).value, 'before.1'); + assert.equal((session.getReplElements()[2]).value, '=before.1'); + + await session.addReplExpression(undefined, 'after.2'); + await timeout(0); + assert.equal(session.getReplElements().length, 6); + assert.equal((session.getReplElements()[3]).value, 'after.2'); + assert.equal((session.getReplElements()[4]).value, '=after.2'); + assert.equal((session.getReplElements()[5]).value, 'after.2'); + }); }); diff --git a/src/vs/workbench/contrib/debug/test/common/mockDebug.ts b/src/vs/workbench/contrib/debug/test/common/mockDebug.ts index 381d311cb8..0b44d1c42d 100644 --- a/src/vs/workbench/contrib/debug/test/common/mockDebug.ts +++ b/src/vs/workbench/contrib/debug/test/common/mockDebug.ts @@ -11,6 +11,7 @@ import { ILaunch, IDebugService, State, IDebugSession, IConfigurationManager, IS import { Source } from 'vs/workbench/contrib/debug/common/debugSource'; import { CompletionItem } from 'vs/editor/common/modes'; import Severity from 'vs/base/common/severity'; +import { AbstractDebugAdapter } from 'vs/workbench/contrib/debug/common/abstractDebugAdapter'; export class MockDebugService implements IDebugService { @@ -464,3 +465,67 @@ export class MockRawSession { public readonly onDidStop: Event = null!; } + +export class MockDebugAdapter extends AbstractDebugAdapter { + private seq = 0; + + startSession(): Promise { + return Promise.resolve(); + } + + stopSession(): Promise { + return Promise.resolve(); + } + + sendMessage(message: DebugProtocol.ProtocolMessage): void { + setTimeout(() => { + if (message.type === 'request') { + const request = message as DebugProtocol.Request; + switch (request.command) { + case 'evaluate': + this.evaluate(request, request.arguments); + return; + } + this.sendResponseBody(request, {}); + return; + } + }, 0); + } + + sendResponseBody(request: DebugProtocol.Request, body: any) { + const response: DebugProtocol.Response = { + seq: ++this.seq, + type: 'response', + request_seq: request.seq, + command: request.command, + success: true, + body + }; + this.acceptMessage(response); + } + + sendEventBody(event: string, body: any) { + const response: DebugProtocol.Event = { + seq: ++this.seq, + type: 'event', + event, + body + }; + this.acceptMessage(response); + } + + evaluate(request: DebugProtocol.Request, args: DebugProtocol.EvaluateArguments) { + if (args.expression.indexOf('before.') === 0) { + this.sendEventBody('output', { output: args.expression }); + } + + this.sendResponseBody(request, { + result: '=' + args.expression, + variablesReference: 0 + }); + + if (args.expression.indexOf('after.') === 0) { + this.sendEventBody('output', { output: args.expression }); + } + } +} diff --git a/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts b/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts index 811d7d9bcf..8c9942d396 100644 --- a/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts +++ b/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts @@ -20,7 +20,7 @@ import { FileOperationError, FileOperationResult, FileChangesEvent, IFileService import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IStorageService } from 'vs/platform/storage/common/storage'; -import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; +import { IResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ScrollType } from 'vs/editor/common/editorCommon'; @@ -49,7 +49,7 @@ export class TextFileEditor extends BaseTextEditor { @IInstantiationService instantiationService: IInstantiationService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IStorageService storageService: IStorageService, - @ITextResourceConfigurationService configurationService: ITextResourceConfigurationService, + @IResourceConfigurationService configurationService: IResourceConfigurationService, @IEditorService editorService: IEditorService, @IThemeService themeService: IThemeService, @IEditorGroupsService editorGroupService: IEditorGroupsService, diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index c60d674365..52b6445bc7 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -485,7 +485,7 @@ export class ExplorerView extends ViewPane { const controller = this.renderer.getCompressedNavigationController(stat); if (controller) { - if (isCompressedFolderName(e.browserEvent.target)) { + if (e.browserEvent instanceof KeyboardEvent || isCompressedFolderName(e.browserEvent.target)) { anchor = controller.labels[controller.index]; } else { controller.last(); diff --git a/src/vs/workbench/contrib/files/electron-browser/textFileEditor.ts b/src/vs/workbench/contrib/files/electron-browser/textFileEditor.ts index ece48b108f..9cc026aee3 100644 --- a/src/vs/workbench/contrib/files/electron-browser/textFileEditor.ts +++ b/src/vs/workbench/contrib/files/electron-browser/textFileEditor.ts @@ -16,7 +16,7 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IStorageService } from 'vs/platform/storage/common/storage'; -import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; +import { IResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -37,7 +37,7 @@ export class NativeTextFileEditor extends TextFileEditor { @IInstantiationService instantiationService: IInstantiationService, @IWorkspaceContextService contextService: IWorkspaceContextService, @IStorageService storageService: IStorageService, - @ITextResourceConfigurationService configurationService: ITextResourceConfigurationService, + @IResourceConfigurationService configurationService: IResourceConfigurationService, @IEditorService editorService: IEditorService, @IThemeService themeService: IThemeService, @IEditorGroupsService editorGroupService: IEditorGroupsService, diff --git a/src/vs/workbench/contrib/issue/electron-browser/issueService.ts b/src/vs/workbench/contrib/issue/electron-browser/issueService.ts index 8edaad3362..480a606f14 100644 --- a/src/vs/workbench/contrib/issue/electron-browser/issueService.ts +++ b/src/vs/workbench/contrib/issue/electron-browser/issueService.ts @@ -5,7 +5,7 @@ import { IssueReporterStyles, IIssueService, IssueReporterData, ProcessExplorerData, IssueReporterExtensionData } from 'vs/platform/issue/node/issue'; import { ITheme, IThemeService } from 'vs/platform/theme/common/themeService'; -import { textLinkForeground, inputBackground, inputBorder, inputForeground, buttonBackground, buttonHoverBackground, buttonForeground, inputValidationErrorBorder, foreground, inputActiveOptionBorder, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, editorBackground, editorForeground, listHoverBackground, listHoverForeground, listHighlightForeground, textLinkActiveForeground } from 'vs/platform/theme/common/colorRegistry'; +import { textLinkForeground, inputBackground, inputBorder, inputForeground, buttonBackground, buttonHoverBackground, buttonForeground, inputValidationErrorBorder, foreground, inputActiveOptionBorder, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, editorBackground, editorForeground, listHoverBackground, listHoverForeground, listHighlightForeground, textLinkActiveForeground, inputValidationErrorBackground, inputValidationErrorForeground } from 'vs/platform/theme/common/colorRegistry'; import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IExtensionEnablementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; @@ -86,6 +86,8 @@ export function getIssueReporterStyles(theme: ITheme): IssueReporterStyles { inputBorder: getColor(theme, inputBorder), inputActiveBorder: getColor(theme, inputActiveOptionBorder), inputErrorBorder: getColor(theme, inputValidationErrorBorder), + inputErrorBackground: getColor(theme, inputValidationErrorBackground), + inputErrorForeground: getColor(theme, inputValidationErrorForeground), buttonBackground: getColor(theme, buttonBackground), buttonForeground: getColor(theme, buttonForeground), buttonHoverBackground: getColor(theme, buttonHoverBackground), diff --git a/src/vs/workbench/contrib/outline/browser/outlineNavigation.ts b/src/vs/workbench/contrib/outline/browser/outlineNavigation.ts index a0b2d8722d..4e8171dfad 100644 --- a/src/vs/workbench/contrib/outline/browser/outlineNavigation.ts +++ b/src/vs/workbench/contrib/outline/browser/outlineNavigation.ts @@ -19,7 +19,7 @@ import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegis 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'; +import { IResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; class FlatOutline { @@ -78,7 +78,7 @@ export class OutlineNavigation implements IEditorContribution { constructor( editor: ICodeEditor, - @ITextResourceConfigurationService private readonly _textResourceConfigService: ITextResourceConfigurationService, + @IResourceConfigurationService private readonly _textResourceConfigService: IResourceConfigurationService, ) { this._editor = editor; } diff --git a/src/vs/workbench/contrib/output/browser/logViewer.ts b/src/vs/workbench/contrib/output/browser/logViewer.ts index e9bb1585e7..219c56490e 100644 --- a/src/vs/workbench/contrib/output/browser/logViewer.ts +++ b/src/vs/workbench/contrib/output/browser/logViewer.ts @@ -7,7 +7,7 @@ import { dirname, basename } from 'vs/base/common/path'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; 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 { IResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; 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'; @@ -46,7 +46,7 @@ export class LogViewer extends AbstractTextResourceEditor { @ITelemetryService telemetryService: ITelemetryService, @IInstantiationService instantiationService: IInstantiationService, @IStorageService storageService: IStorageService, - @ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService, + @IResourceConfigurationService textResourceConfigurationService: IResourceConfigurationService, @IThemeService themeService: IThemeService, @IEditorGroupsService editorGroupService: IEditorGroupsService, @IEditorService editorService: IEditorService diff --git a/src/vs/workbench/contrib/output/browser/outputPanel.ts b/src/vs/workbench/contrib/output/browser/outputPanel.ts index 840d46c3a1..4cf5a52c84 100644 --- a/src/vs/workbench/contrib/output/browser/outputPanel.ts +++ b/src/vs/workbench/contrib/output/browser/outputPanel.ts @@ -10,7 +10,7 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; 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 { IResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -35,7 +35,7 @@ export class OutputPanel extends AbstractTextResourceEditor { @IInstantiationService instantiationService: IInstantiationService, @IStorageService storageService: IStorageService, @IConfigurationService private readonly baseConfigurationService: IConfigurationService, - @ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService, + @IResourceConfigurationService textResourceConfigurationService: IResourceConfigurationService, @IThemeService themeService: IThemeService, @IOutputService private readonly outputService: IOutputService, @IContextKeyService private readonly contextKeyService: IContextKeyService, diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts b/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts index 1cb7432078..3eff6773f7 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts @@ -21,7 +21,7 @@ import { EditorExtensionsRegistry, registerEditorContribution, IEditorContributi import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import * as editorCommon from 'vs/editor/common/editorCommon'; -import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; +import { IResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; import { FindController } from 'vs/editor/contrib/find/findController'; import { FoldingController } from 'vs/editor/contrib/folding/folding'; import { MessageController } from 'vs/editor/contrib/message/messageController'; @@ -973,7 +973,7 @@ export class DefaultPreferencesEditor extends BaseTextEditor { @ITelemetryService telemetryService: ITelemetryService, @IInstantiationService instantiationService: IInstantiationService, @IStorageService storageService: IStorageService, - @ITextResourceConfigurationService configurationService: ITextResourceConfigurationService, + @IResourceConfigurationService configurationService: IResourceConfigurationService, @IThemeService themeService: IThemeService, @IEditorGroupsService editorGroupService: IEditorGroupsService, @IEditorService editorService: IEditorService diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index 1410081828..d7f105b6aa 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -959,6 +959,9 @@ export class SettingEnumRenderer extends AbstractSettingRenderer implements ITre const enumDescriptions = dataElement.setting.enumDescriptions; const enumDescriptionsAreMarkdown = dataElement.setting.enumDescriptionsAreMarkdown; + const disposables = new DisposableStore(); + template.toDispose.push(disposables); + const displayOptions = dataElement.setting.enum! .map(String) .map(escapeInvisibleChars) @@ -966,6 +969,12 @@ export class SettingEnumRenderer extends AbstractSettingRenderer implements ITre text: data, description: (enumDescriptions && enumDescriptions[index] && (enumDescriptionsAreMarkdown ? fixSettingLinks(enumDescriptions[index], false) : enumDescriptions[index])), descriptionIsMarkdown: enumDescriptionsAreMarkdown, + descriptionMarkdownActionHandler: { + callback: (content) => { + this._openerService.open(content).catch(onUnexpectedError); + }, + disposeables: disposables + }, decoratorRight: (data === dataElement.defaultValue ? localize('settings.Default', "{0}", 'default') : '') }); diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts b/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts index 79d9dfe978..dae47dea2a 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts @@ -8,7 +8,7 @@ import { isFalsyOrWhitespace } from 'vs/base/common/strings'; import { isArray, withUndefinedAsNull } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; -import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ConfigurationTarget, IConfigurationService, IConfigurationValue } from 'vs/platform/configuration/common/configuration'; import { SettingsTarget } from 'vs/workbench/contrib/preferences/browser/preferencesWidgets'; import { ITOCEntry, knownAcronyms, knownTermMappings } from 'vs/workbench/contrib/preferences/browser/settingsLayout'; import { MODIFIED_SETTING_TAG } from 'vs/workbench/contrib/preferences/common/preferences'; @@ -374,16 +374,7 @@ export class SettingsTreeModel { interface IInspectResult { isConfigured: boolean; - inspected: { - default: any, - user: any, - userLocal?: any, - userRemote?: any, - workspace?: any, - workspaceFolder?: any, - memory?: any, - value: any, - }; + inspected: IConfigurationValue; targetSelector: 'userLocal' | 'userRemote' | 'workspace' | 'workspaceFolder'; } diff --git a/src/vs/workbench/contrib/remote/browser/tunnelView.ts b/src/vs/workbench/contrib/remote/browser/tunnelView.ts index e532cf65fb..9de0832485 100644 --- a/src/vs/workbench/contrib/remote/browser/tunnelView.ts +++ b/src/vs/workbench/contrib/remote/browser/tunnelView.ts @@ -52,8 +52,8 @@ export interface ITunnelViewModel { onForwardedPortsChanged: Event; readonly forwarded: TunnelItem[]; readonly detected: TunnelItem[]; - readonly candidates: TunnelItem[]; - readonly groups: ITunnelGroup[]; + readonly candidates: Promise; + groups(): Promise; } export class TunnelViewModel extends Disposable implements ITunnelViewModel { @@ -70,7 +70,7 @@ export class TunnelViewModel extends Disposable implements ITunnelViewModel { this._register(this.model.onPortName(() => this._onForwardedPortsChanged.fire())); } - get groups(): ITunnelGroup[] { + async groups(): Promise { const groups: ITunnelGroup[] = []; if (this.model.forwarded.size > 0) { groups.push({ @@ -86,8 +86,8 @@ export class TunnelViewModel extends Disposable implements ITunnelViewModel { items: this.detected }); } - const candidates = this.candidates; - if (this.candidates.length > 0) { + const candidates = await this.candidates; + if (candidates.length > 0) { groups.push({ label: nls.localize('remote.tunnelsView.candidates', "Candidates"), tunnelType: TunnelType.Candidate, @@ -113,17 +113,16 @@ export class TunnelViewModel extends Disposable implements ITunnelViewModel { }); } - 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.detected.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; + get candidates(): Promise { + return this.model.candidates.then(values => { + const candidates: TunnelItem[] = []; + values.forEach(value => { + if (!this.model.forwarded.has(value.port) && !this.model.detected.has(value.port)) { + candidates.push(new TunnelItem(TunnelType.Candidate, value.port, undefined, false, undefined, value.detail)); + } + }); + return candidates; + }); } dispose() { @@ -312,7 +311,7 @@ class TunnelDataSource implements IAsyncDataSourceelement).items) { @@ -332,7 +331,7 @@ enum TunnelType { interface ITunnelGroup { tunnelType: TunnelType; label: string; - items?: ITunnelItem[]; + items?: ITunnelItem[] | Promise; } interface ITunnelItem { diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index 0a3e6c25bf..dc17c1d8a4 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -646,16 +646,17 @@ registry.registerWorkbenchAction( 'Search: Open Results in Editor', category, ContextKeyExpr.and(Constants.EnableSearchEditorPreview)); +const searchEditorCategory = nls.localize({ comment: ['The name of the tabbed search view'], key: 'searcheditor' }, "Search Editor"); 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, + 'Search Editor: Search Again', searchEditorCategory, ContextKeyExpr.and(EditorContextKeys.languageId.isEqualTo('search-result'))); registry.registerWorkbenchAction( SyncActionDescriptor.create(RerunEditorSearchWithContextAction, RerunEditorSearchWithContextAction.ID, RerunEditorSearchWithContextAction.LABEL), - 'Search Editor: Search Again (With Context)', category, + 'Search Editor: Search Again (With Context)', searchEditorCategory, ContextKeyExpr.and(EditorContextKeys.languageId.isEqualTo('search-result'))); diff --git a/src/vs/workbench/contrib/search/browser/searchActions.ts b/src/vs/workbench/contrib/search/browser/searchActions.ts index a2a80f99f9..be6d85b95d 100644 --- a/src/vs/workbench/contrib/search/browser/searchActions.ts +++ b/src/vs/workbench/contrib/search/browser/searchActions.ts @@ -472,7 +472,7 @@ export class RerunEditorSearchAction extends Action { async run() { if (this.configurationService.getValue('search').enableSearchEditorPreview) { - await this.progressService.withProgress({ location: ProgressLocation.Window }, + await this.progressService.withProgress({ location: ProgressLocation.Window, title: nls.localize('searchRunning', "Running search...") }, () => refreshActiveEditorSearch(undefined, this.editorService, this.instantiationService, this.contextService, this.labelService, this.configurationService)); } } @@ -503,7 +503,7 @@ export class RerunEditorSearchWithContextAction extends Action { }); if (lines === undefined) { return; } if (this.configurationService.getValue('search').enableSearchEditorPreview) { - await this.progressService.withProgress({ location: ProgressLocation.Window }, + await this.progressService.withProgress({ location: ProgressLocation.Window, title: nls.localize('searchRunning', "Running search...") }, () => refreshActiveEditorSearch(+lines, this.editorService, this.instantiationService, this.contextService, this.labelService, this.configurationService)); } } diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index 3d408a1cb5..e9ca2b671c 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -137,6 +137,7 @@ export class SearchView extends ViewPane { private delayedRefresh: Delayer; private changedWhileHidden: boolean = false; + private updatedActionsWhileHidden = false; private searchWithoutFolderMessageElement: HTMLElement | undefined; @@ -348,6 +349,13 @@ export class SearchView extends ViewPane { this.refreshAndUpdateCount(); this.changedWhileHidden = false; } + + if (this.updatedActionsWhileHidden) { + // The actions can only run or update their enablement when the view is visible, + // because they can only access the view when it's visible + this.updateActions(); + this.updatedActionsWhileHidden = false; + } } // Enable highlights if there are searchresults @@ -372,6 +380,10 @@ export class SearchView extends ViewPane { * Warning: a bit expensive due to updating the view title */ protected updateActions(): void { + if (!this.isVisible()) { + this.updatedActionsWhileHidden = true; + } + for (const action of this.actions) { action.update(); } @@ -415,7 +427,7 @@ export class SearchView extends ViewPane { this.searchWidget.toggleReplace(true); } - this._register(this.searchWidget.onSearchSubmit(triggeredOnType => this.onQueryChanged(false, triggeredOnType))); + this._register(this.searchWidget.onSearchSubmit(triggeredOnType => this.onQueryChanged(true, triggeredOnType))); this._register(this.searchWidget.onSearchCancel(({ focus }) => this.cancelSearch(focus))); this._register(this.searchWidget.searchInput.onDidOptionChange(() => this.onQueryChanged(true))); diff --git a/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts b/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts index 70509ef973..54346b42ff 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts @@ -246,7 +246,7 @@ export function escapeNonWindowsPath(path: string): string { } export function getDefaultShell( - fetchSetting: (key: string) => { user: string | string[] | undefined, value: string | string[] | undefined, default: string | string[] | undefined }, + fetchSetting: (key: string) => { user?: string | string[], value?: string | string[], default?: string | string[] }, isWorkspaceShellAllowed: boolean, defaultShell: string, isWoW64: boolean, @@ -294,7 +294,7 @@ export function getDefaultShell( } export function getDefaultShellArgs( - fetchSetting: (key: string) => { user: string | string[] | undefined, value: string | string[] | undefined, default: string | string[] | undefined }, + fetchSetting: (key: string) => { user?: string | string[], value?: string | string[], default?: string | string[] }, isWorkspaceShellAllowed: boolean, useAutomationShell: boolean, lastActiveWorkspace: IWorkspaceFolder | undefined, @@ -330,7 +330,7 @@ export function getDefaultShellArgs( } function getShellSetting( - fetchSetting: (key: string) => { user: string | string[] | undefined, value: string | string[] | undefined, default: string | string[] | undefined }, + fetchSetting: (key: string) => { user?: string | string[], value?: string | string[], default?: string | string[] }, isWorkspaceShellAllowed: boolean, type: 'automationShell' | 'shell', platformOverride: platform.Platform = platform.platform, @@ -344,7 +344,7 @@ function getShellSetting( export function createTerminalEnvironment( shellLaunchConfig: IShellLaunchConfig, lastActiveWorkspace: IWorkspaceFolder | null, - envFromConfig: { user: ITerminalEnvironment | undefined, value: ITerminalEnvironment | undefined, default: ITerminalEnvironment | undefined }, + envFromConfig: { user?: ITerminalEnvironment, value?: ITerminalEnvironment, default?: ITerminalEnvironment }, configurationResolverService: IConfigurationResolverService | undefined, isWorkspaceShellAllowed: boolean, version: string | undefined, diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index 136ab47c72..5228a99f32 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -11,9 +11,9 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { Queue, Barrier } from 'vs/base/common/async'; import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { IWorkspaceContextService, Workspace, WorkbenchState, IWorkspaceFolder, toWorkspaceFolders, IWorkspaceFoldersChangeEvent, WorkspaceFolder, toWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; -import { ConfigurationChangeEvent, ConfigurationModel, DefaultConfigurationModel } from 'vs/platform/configuration/common/configurationModels'; -import { IConfigurationChangeEvent, ConfigurationTarget, IConfigurationOverrides, keyFromOverrideIdentifier, isConfigurationOverrides, IConfigurationData, IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { Configuration, WorkspaceConfigurationChangeEvent, AllKeysConfigurationChangeEvent } from 'vs/workbench/services/configuration/common/configurationModels'; +import { ConfigurationModel, DefaultConfigurationModel, ConfigurationChangeEvent, AllKeysConfigurationChangeEvent, mergeChanges } from 'vs/platform/configuration/common/configurationModels'; +import { IConfigurationChangeEvent, ConfigurationTarget, IConfigurationOverrides, keyFromOverrideIdentifier, isConfigurationOverrides, IConfigurationData, IConfigurationService, IConfigurationValue, IConfigurationChange } from 'vs/platform/configuration/common/configuration'; +import { Configuration } from 'vs/workbench/services/configuration/common/configurationModels'; import { FOLDER_CONFIG_FOLDER_NAME, defaultSettingsSchemaId, userSettingsSchemaId, workspaceSettingsSchemaId, folderSettingsSchemaId, IConfigurationCache, machineSettingsSchemaId, LOCAL_MACHINE_SCOPES } from 'vs/workbench/services/configuration/common/configuration'; import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationRegistry, Extensions, allSettings, windowSettings, resourceSettings, applicationSettings, machineSettings, machineOverridableSettings } from 'vs/platform/configuration/common/configurationRegistry'; @@ -271,16 +271,7 @@ export class WorkspaceService extends Disposable implements IConfigurationServic .then(() => this.loadConfiguration(local, remote))); } - inspect(key: string, overrides?: IConfigurationOverrides): { - default: T, - user: T, - userLocal?: T, - userRemote?: T, - workspace?: T, - workspaceFolder?: T, - memory?: T, - value: T - } { + inspect(key: string, overrides?: IConfigurationOverrides): IConfigurationValue { return this._configuration.inspect(key, overrides); } @@ -465,10 +456,10 @@ export class WorkspaceService extends Disposable implements IConfigurationServic this._configuration = new Configuration(this.defaultConfiguration, userConfigurationModel, remoteUserConfigurationModel, workspaceConfiguration, folderConfigurationModels, new ConfigurationModel(), new ResourceMap(), this.workspace); if (this.initialized) { - const changedKeys = this._configuration.compare(currentConfiguration); - this.triggerConfigurationChange(new ConfigurationChangeEvent().change(changedKeys), ConfigurationTarget.WORKSPACE); + const change = this._configuration.compare(currentConfiguration); + this.triggerConfigurationChange(change, { data: currentConfiguration.toData(), workspace: this.workspace }, ConfigurationTarget.WORKSPACE); } else { - this._onDidChangeConfiguration.fire(new AllKeysConfigurationChangeEvent(this._configuration, ConfigurationTarget.WORKSPACE, this.getTargetConfiguration(ConfigurationTarget.WORKSPACE))); + this._onDidChangeConfiguration.fire(new AllKeysConfigurationChangeEvent(this._configuration, this.workspace, ConfigurationTarget.WORKSPACE, this.getTargetConfiguration(ConfigurationTarget.WORKSPACE))); this.initialized = true; } }); @@ -489,7 +480,8 @@ export class WorkspaceService extends Disposable implements IConfigurationServic this.defaultConfiguration = new DefaultConfigurationModel(); this.registerConfigurationSchemas(); if (this.workspace) { - this._configuration.updateDefaultConfiguration(this.defaultConfiguration); + const previousData = this._configuration.toData(); + const change = this._configuration.compareAndUpdateDefaultConfiguration(this.defaultConfiguration, keys); if (this.remoteUserConfiguration) { this._configuration.updateLocalUserConfiguration(this.localUserConfiguration.reprocess()); this._configuration.updateRemoteUserConfiguration(this.remoteUserConfiguration.reprocess()); @@ -500,7 +492,7 @@ export class WorkspaceService extends Disposable implements IConfigurationServic this._configuration.updateWorkspaceConfiguration(this.workspaceConfiguration.reprocessWorkspaceSettings()); this.workspace.folders.forEach(folder => this._configuration.updateFolderConfiguration(folder.uri, this.cachedFolderConfigs.get(folder.uri)!.reprocess())); } - this.triggerConfigurationChange(new ConfigurationChangeEvent().change(keys), ConfigurationTarget.DEFAULT); + this.triggerConfigurationChange(change, { data: previousData, workspace: this.workspace }, ConfigurationTarget.DEFAULT); } } @@ -528,29 +520,32 @@ export class WorkspaceService extends Disposable implements IConfigurationServic } private onLocalUserConfigurationChanged(userConfiguration: ConfigurationModel): void { - const keys = this._configuration.compareAndUpdateLocalUserConfiguration(userConfiguration); - this.triggerConfigurationChange(keys, ConfigurationTarget.USER); + const previous = { data: this._configuration.toData(), workspace: this.workspace }; + const change = this._configuration.compareAndUpdateLocalUserConfiguration(userConfiguration); + this.triggerConfigurationChange(change, previous, ConfigurationTarget.USER); } private onRemoteUserConfigurationChanged(userConfiguration: ConfigurationModel): void { - const keys = this._configuration.compareAndUpdateRemoteUserConfiguration(userConfiguration); - this.triggerConfigurationChange(keys, ConfigurationTarget.USER); + const previous = { data: this._configuration.toData(), workspace: this.workspace }; + const change = this._configuration.compareAndUpdateRemoteUserConfiguration(userConfiguration); + this.triggerConfigurationChange(change, previous, ConfigurationTarget.USER); } private onWorkspaceConfigurationChanged(): Promise { if (this.workspace && this.workspace.configuration) { - const workspaceConfigurationChangeEvent = this._configuration.compareAndUpdateWorkspaceConfiguration(this.workspaceConfiguration.getConfiguration()); + const previous = { data: this._configuration.toData(), workspace: this.workspace }; + const change = this._configuration.compareAndUpdateWorkspaceConfiguration(this.workspaceConfiguration.getConfiguration()); let configuredFolders = toWorkspaceFolders(this.workspaceConfiguration.getFolders(), this.workspace.configuration); const changes = this.compareFolders(this.workspace.folders, configuredFolders); if (changes.added.length || changes.removed.length || changes.changed.length) { this.workspace.folders = configuredFolders; return this.onFoldersChanged() - .then(foldersConfigurationChangeEvent => { - this.triggerConfigurationChange(foldersConfigurationChangeEvent.change(workspaceConfigurationChangeEvent), ConfigurationTarget.WORKSPACE_FOLDER); + .then(change => { + this.triggerConfigurationChange(change, previous, ConfigurationTarget.WORKSPACE_FOLDER); this._onDidChangeWorkspaceFolders.fire(changes); }); } else { - this.triggerConfigurationChange(workspaceConfigurationChangeEvent, ConfigurationTarget.WORKSPACE); + this.triggerConfigurationChange(change, previous, ConfigurationTarget.WORKSPACE); } } return Promise.resolve(undefined); @@ -559,18 +554,19 @@ export class WorkspaceService extends Disposable implements IConfigurationServic private onWorkspaceFolderConfigurationChanged(folder: IWorkspaceFolder, key?: string): Promise { return this.loadFolderConfigurations([folder]) .then(([folderConfiguration]) => { - const folderChangedKeys = this._configuration.compareAndUpdateFolderConfiguration(folder.uri, folderConfiguration); + const previous = { data: this._configuration.toData(), workspace: this.workspace }; + const folderConfiguraitonChange = this._configuration.compareAndUpdateFolderConfiguration(folder.uri, folderConfiguration); if (this.getWorkbenchState() === WorkbenchState.FOLDER) { - const workspaceChangedKeys = this._configuration.compareAndUpdateWorkspaceConfiguration(folderConfiguration); - this.triggerConfigurationChange(workspaceChangedKeys, ConfigurationTarget.WORKSPACE); + const workspaceConfigurationChange = this._configuration.compareAndUpdateWorkspaceConfiguration(folderConfiguration); + this.triggerConfigurationChange(mergeChanges(folderConfiguraitonChange, workspaceConfigurationChange), previous, ConfigurationTarget.WORKSPACE); } else { - this.triggerConfigurationChange(folderChangedKeys, ConfigurationTarget.WORKSPACE_FOLDER); + this.triggerConfigurationChange(folderConfiguraitonChange, previous, ConfigurationTarget.WORKSPACE_FOLDER); } }); } - private onFoldersChanged(): Promise { - let changeEvent = new ConfigurationChangeEvent(); + private async onFoldersChanged(): Promise { + const changes: IConfigurationChange[] = []; // Remove the configurations of deleted folders for (const key of this.cachedFolderConfigs.keys()) { @@ -578,21 +574,18 @@ export class WorkspaceService extends Disposable implements IConfigurationServic const folderConfiguration = this.cachedFolderConfigs.get(key); folderConfiguration!.dispose(); this.cachedFolderConfigs.delete(key); - changeEvent = changeEvent.change(this._configuration.compareAndDeleteFolderConfiguration(key)); + changes.push(this._configuration.compareAndDeleteFolderConfiguration(key)); } } const toInitialize = this.workspace.folders.filter(folder => !this.cachedFolderConfigs.has(folder.uri)); if (toInitialize.length) { - return this.loadFolderConfigurations(toInitialize) - .then(folderConfigurations => { - folderConfigurations.forEach((folderConfiguration, index) => { - changeEvent = changeEvent.change(this._configuration.compareAndUpdateFolderConfiguration(toInitialize[index].uri, folderConfiguration)); - }); - return changeEvent; - }); + const folderConfigurations = await this.loadFolderConfigurations(toInitialize); + folderConfigurations.forEach((folderConfiguration, index) => { + changes.push(this._configuration.compareAndUpdateFolderConfiguration(toInitialize[index].uri, folderConfiguration)); + }); } - return Promise.resolve(changeEvent); + return mergeChanges(...changes); } private loadFolderConfigurations(folders: IWorkspaceFolder[]): Promise { @@ -613,8 +606,9 @@ export class WorkspaceService extends Disposable implements IConfigurationServic } if (target === ConfigurationTarget.MEMORY) { + const previous = { data: this._configuration.toData(), workspace: this.workspace }; this._configuration.updateValue(key, value, overrides); - this.triggerConfigurationChange(new ConfigurationChangeEvent().change(overrides && overrides.overrideIdentifier ? [keyFromOverrideIdentifier(overrides.overrideIdentifier)] : [key], overrides && overrides.resource || undefined), target); + this.triggerConfigurationChange({ keys: overrides?.overrideIdentifier ? [keyFromOverrideIdentifier(overrides.overrideIdentifier), key] : [key], overrides: overrides?.overrideIdentifier ? [[overrides?.overrideIdentifier, [key]]] : [] }, previous, target); return Promise.resolve(undefined); } @@ -673,10 +667,12 @@ export class WorkspaceService extends Disposable implements IConfigurationServic return ConfigurationTarget.USER; } - private triggerConfigurationChange(configurationEvent: ConfigurationChangeEvent, target: ConfigurationTarget): void { - if (configurationEvent.affectedKeys.length) { - configurationEvent.telemetryData(target, this.getTargetConfiguration(target)); - this._onDidChangeConfiguration.fire(new WorkspaceConfigurationChangeEvent(configurationEvent, this.workspace)); + private triggerConfigurationChange(change: IConfigurationChange, previous: { data: IConfigurationData, workspace?: Workspace } | undefined, target: ConfigurationTarget): void { + if (change.keys.length) { + const configurationChangeEvent = new ConfigurationChangeEvent(change, previous, this._configuration, this.workspace); + configurationChangeEvent.source = target; + configurationChangeEvent.sourceConfig = this.getTargetConfiguration(target); + this._onDidChangeConfiguration.fire(configurationChangeEvent); } } diff --git a/src/vs/workbench/services/configuration/common/configurationModels.ts b/src/vs/workbench/services/configuration/common/configurationModels.ts index 8a5ff31ac4..c6f0b665b2 100644 --- a/src/vs/workbench/services/configuration/common/configurationModels.ts +++ b/src/vs/workbench/services/configuration/common/configurationModels.ts @@ -4,13 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import { equals } from 'vs/base/common/objects'; -import { compare, toValuesTree, IConfigurationChangeEvent, ConfigurationTarget, IConfigurationModel, IConfigurationOverrides } from 'vs/platform/configuration/common/configuration'; -import { Configuration as BaseConfiguration, ConfigurationModelParser, ConfigurationChangeEvent, ConfigurationModel, AbstractConfigurationChangeEvent } from 'vs/platform/configuration/common/configurationModels'; +import { toValuesTree, IConfigurationModel, IConfigurationOverrides, IConfigurationValue, IConfigurationChange, overrideIdentifierFromKey } from 'vs/platform/configuration/common/configuration'; +import { Configuration as BaseConfiguration, ConfigurationModelParser, ConfigurationModel } from 'vs/platform/configuration/common/configurationModels'; import { IStoredWorkspaceFolder } from 'vs/platform/workspaces/common/workspaces'; import { Workspace } from 'vs/platform/workspace/common/workspace'; import { ResourceMap } from 'vs/base/common/map'; import { URI } from 'vs/base/common/uri'; import { WORKSPACE_SCOPES } from 'vs/workbench/services/configuration/common/configuration'; +import { OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/configurationRegistry'; export class WorkspaceConfigurationModelParser extends ConfigurationModelParser { @@ -101,16 +102,7 @@ export class Configuration extends BaseConfiguration { return super.getValue(key, overrides, this._workspace); } - inspect(key: string, overrides: IConfigurationOverrides = {}): { - default: C, - user: C, - userLocal?: C, - userRemote?: C, - workspace?: C, - workspaceFolder?: C - memory?: C - value: C, - } { + inspect(key: string, overrides: IConfigurationOverrides = {}): IConfigurationValue { return super.inspect(key, overrides, this._workspace); } @@ -123,141 +115,42 @@ export class Configuration extends BaseConfiguration { return super.keys(this._workspace); } - compareAndUpdateLocalUserConfiguration(user: ConfigurationModel): ConfigurationChangeEvent { - const { added, updated, removed } = compare(this.localUserConfiguration, user); - let changedKeys = [...added, ...updated, ...removed]; - if (changedKeys.length) { - super.updateLocalUserConfiguration(user); - } - return new ConfigurationChangeEvent().change(changedKeys); - } - - compareAndUpdateRemoteUserConfiguration(user: ConfigurationModel): ConfigurationChangeEvent { - const { added, updated, removed } = compare(this.remoteUserConfiguration, user); - let changedKeys = [...added, ...updated, ...removed]; - if (changedKeys.length) { - super.updateRemoteUserConfiguration(user); - } - return new ConfigurationChangeEvent().change(changedKeys); - } - - compareAndUpdateWorkspaceConfiguration(workspaceConfiguration: ConfigurationModel): ConfigurationChangeEvent { - const { added, updated, removed } = compare(this.workspaceConfiguration, workspaceConfiguration); - let changedKeys = [...added, ...updated, ...removed]; - if (changedKeys.length) { - super.updateWorkspaceConfiguration(workspaceConfiguration); - } - return new ConfigurationChangeEvent().change(changedKeys); - } - - compareAndUpdateFolderConfiguration(resource: URI, folderConfiguration: ConfigurationModel): ConfigurationChangeEvent { - const currentFolderConfiguration = this.folderConfigurations.get(resource); - if (currentFolderConfiguration) { - const { added, updated, removed } = compare(currentFolderConfiguration, folderConfiguration); - let changedKeys = [...added, ...updated, ...removed]; - if (changedKeys.length) { - super.updateFolderConfiguration(resource, folderConfiguration); - } - return new ConfigurationChangeEvent().change(changedKeys, resource); - } else { - super.updateFolderConfiguration(resource, folderConfiguration); - return new ConfigurationChangeEvent().change(folderConfiguration.keys, resource); - } - } - - compareAndDeleteFolderConfiguration(folder: URI): ConfigurationChangeEvent { + compareAndDeleteFolderConfiguration(folder: URI): IConfigurationChange { if (this._workspace && this._workspace.folders.length > 0 && this._workspace.folders[0].uri.toString() === folder.toString()) { // Do not remove workspace configuration - return new ConfigurationChangeEvent(); + return { keys: [], overrides: [] }; } - const folderConfig = this.folderConfigurations.get(folder); - if (!folderConfig) { - throw new Error('Unknown folder'); - } - const keys = folderConfig.keys; - super.deleteFolderConfiguration(folder); - return new ConfigurationChangeEvent().change(keys, folder); + return super.compareAndDeleteFolderConfiguration(folder); } - compare(other: Configuration): string[] { - const result: string[] = []; - for (const key of this.allKeys()) { - if (!equals(this.getValue(key), other.getValue(key)) - || (this._workspace && this._workspace.folders.some(folder => !equals(this.getValue(key, { resource: folder.uri }), other.getValue(key, { resource: folder.uri }))))) { - result.push(key); + compare(other: Configuration): IConfigurationChange { + const compare = (fromKeys: string[], toKeys: string[], overrideIdentifier?: string): string[] => { + const keys: string[] = []; + keys.push(...toKeys.filter(key => fromKeys.indexOf(key) === -1)); + keys.push(...fromKeys.filter(key => toKeys.indexOf(key) === -1)); + keys.push(...fromKeys.filter(key => { + // Ignore if the key does not exist in both models + if (toKeys.indexOf(key) === -1) { + return false; + } + // Compare workspace value + if (!equals(this.getValue(key, { overrideIdentifier }), other.getValue(key, { overrideIdentifier }))) { + return true; + } + // Compare workspace folder value + return this._workspace && this._workspace.folders.some(folder => !equals(this.getValue(key, { resource: folder.uri, overrideIdentifier }), other.getValue(key, { resource: folder.uri, overrideIdentifier }))); + })); + return keys; + }; + const keys = compare(this.allKeys(), other.allKeys()); + const overrides: [string, string[]][] = []; + for (const key of keys) { + if (OVERRIDE_PROPERTY_PATTERN.test(key)) { + const overrideIdentifier = overrideIdentifierFromKey(key); + overrides.push([overrideIdentifier, compare(this.getAllKeysForOverrideIdentifier(overrideIdentifier), other.getAllKeysForOverrideIdentifier(overrideIdentifier), overrideIdentifier)]); } } - return result; + return { keys, overrides }; } - allKeys(): string[] { - return super.allKeys(this._workspace); - } -} - -export class AllKeysConfigurationChangeEvent extends AbstractConfigurationChangeEvent implements IConfigurationChangeEvent { - - private _changedConfiguration: ConfigurationModel | null = null; - - constructor(private _configuration: Configuration, readonly source: ConfigurationTarget, readonly sourceConfig: any) { super(); } - - get changedConfiguration(): ConfigurationModel { - if (!this._changedConfiguration) { - this._changedConfiguration = new ConfigurationModel(); - this.updateKeys(this._changedConfiguration, this.affectedKeys); - } - return this._changedConfiguration; - } - - get changedConfigurationByResource(): ResourceMap { - return new ResourceMap(); - } - - get affectedKeys(): string[] { - return this._configuration.allKeys(); - } - - affectsConfiguration(config: string, resource?: URI): boolean { - return this.doesConfigurationContains(this.changedConfiguration, config); - } -} - -export class WorkspaceConfigurationChangeEvent implements IConfigurationChangeEvent { - - constructor(private configurationChangeEvent: IConfigurationChangeEvent, private workspace: Workspace | undefined) { } - - get changedConfiguration(): IConfigurationModel { - return this.configurationChangeEvent.changedConfiguration; - } - - get changedConfigurationByResource(): ResourceMap { - return this.configurationChangeEvent.changedConfigurationByResource; - } - - get affectedKeys(): string[] { - return this.configurationChangeEvent.affectedKeys; - } - - get source(): ConfigurationTarget { - return this.configurationChangeEvent.source; - } - - get sourceConfig(): any { - return this.configurationChangeEvent.sourceConfig; - } - - affectsConfiguration(config: string, resource?: URI): boolean { - if (this.configurationChangeEvent.affectsConfiguration(config, resource)) { - return true; - } - - if (resource && this.workspace) { - let workspaceFolder = this.workspace.getFolder(resource); - if (workspaceFolder) { - return this.configurationChangeEvent.affectsConfiguration(config, workspaceFolder.uri); - } - } - - return false; - } } diff --git a/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts b/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts index 8392728979..2e0f5de203 100644 --- a/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts +++ b/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts @@ -3,15 +3,13 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { join } from 'vs/base/common/path'; import { Registry } from 'vs/platform/registry/common/platform'; -import { WorkspaceConfigurationChangeEvent, StandaloneConfigurationModelParser, AllKeysConfigurationChangeEvent, Configuration } from 'vs/workbench/services/configuration/common/configurationModels'; -import { Workspace, WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; -import { URI } from 'vs/base/common/uri'; -import { ConfigurationChangeEvent, ConfigurationModel, ConfigurationModelParser } from 'vs/platform/configuration/common/configurationModels'; -import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; +import { StandaloneConfigurationModelParser, Configuration } from 'vs/workbench/services/configuration/common/configurationModels'; +import { ConfigurationModelParser, ConfigurationModel } from 'vs/platform/configuration/common/configurationModels'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { ResourceMap } from 'vs/base/common/map'; +import { Workspace, WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; +import { URI } from 'vs/base/common/uri'; suite('FolderSettingsModelParser', () => { @@ -66,7 +64,7 @@ suite('FolderSettingsModelParser', () => { testObject.parseContent(JSON.stringify({ '[json]': { 'FolderSettingsModelParser.window': 'window', 'FolderSettingsModelParser.resource': 'resource', 'FolderSettingsModelParser.application': 'application', 'FolderSettingsModelParser.machine': 'executable' } })); - assert.deepEqual(testObject.configurationModel.overrides, [{ 'contents': { 'FolderSettingsModelParser': { 'resource': 'resource' } }, 'identifiers': ['json'] }]); + assert.deepEqual(testObject.configurationModel.overrides, [{ 'contents': { 'FolderSettingsModelParser': { 'resource': 'resource' } }, 'identifiers': ['json'], 'keys': ['FolderSettingsModelParser.resource'] }]); }); test('reprocess folder settings excludes application and machine setting', () => { @@ -112,143 +110,72 @@ suite('StandaloneConfigurationModelParser', () => { }); -suite('WorkspaceConfigurationChangeEvent', () => { +suite('Workspace Configuration', () => { - test('changeEvent affecting workspace folders', () => { - let configurationChangeEvent = new ConfigurationChangeEvent(); - configurationChangeEvent.change(['window.title']); - configurationChangeEvent.change(['window.zoomLevel'], URI.file('folder1')); - configurationChangeEvent.change(['workbench.editor.enablePreview'], URI.file('folder2')); - configurationChangeEvent.change(['window.restoreFullscreen'], URI.file('folder1')); - configurationChangeEvent.change(['window.restoreWindows'], URI.file('folder2')); - configurationChangeEvent.telemetryData(ConfigurationTarget.WORKSPACE, {}); - - let testObject = new WorkspaceConfigurationChangeEvent(configurationChangeEvent, new Workspace('id', - [new WorkspaceFolder({ index: 0, name: '1', uri: URI.file('folder1') }), - new WorkspaceFolder({ index: 1, name: '2', uri: URI.file('folder2') }), - new WorkspaceFolder({ index: 2, name: '3', uri: URI.file('folder3') })])); - - assert.deepEqual(testObject.affectedKeys, ['window.title', 'window.zoomLevel', 'window.restoreFullscreen', 'workbench.editor.enablePreview', 'window.restoreWindows']); - assert.equal(testObject.source, ConfigurationTarget.WORKSPACE); - - assert.ok(testObject.affectsConfiguration('window.zoomLevel')); - assert.ok(testObject.affectsConfiguration('window.zoomLevel', URI.file('folder1'))); - assert.ok(testObject.affectsConfiguration('window.zoomLevel', URI.file(join('folder1', 'file1')))); - assert.ok(!testObject.affectsConfiguration('window.zoomLevel', URI.file('file1'))); - assert.ok(!testObject.affectsConfiguration('window.zoomLevel', URI.file('file2'))); - assert.ok(!testObject.affectsConfiguration('window.zoomLevel', URI.file(join('folder2', 'file2')))); - assert.ok(!testObject.affectsConfiguration('window.zoomLevel', URI.file(join('folder3', 'file3')))); - - assert.ok(testObject.affectsConfiguration('window.restoreFullscreen')); - assert.ok(testObject.affectsConfiguration('window.restoreFullscreen', URI.file(join('folder1', 'file1')))); - assert.ok(testObject.affectsConfiguration('window.restoreFullscreen', URI.file('folder1'))); - assert.ok(!testObject.affectsConfiguration('window.restoreFullscreen', URI.file('file1'))); - assert.ok(!testObject.affectsConfiguration('window.restoreFullscreen', URI.file('file2'))); - assert.ok(!testObject.affectsConfiguration('window.restoreFullscreen', URI.file(join('folder2', 'file2')))); - assert.ok(!testObject.affectsConfiguration('window.restoreFullscreen', URI.file(join('folder3', 'file3')))); - - assert.ok(testObject.affectsConfiguration('window.restoreWindows')); - assert.ok(testObject.affectsConfiguration('window.restoreWindows', URI.file('folder2'))); - assert.ok(testObject.affectsConfiguration('window.restoreWindows', URI.file(join('folder2', 'file2')))); - assert.ok(!testObject.affectsConfiguration('window.restoreWindows', URI.file('file2'))); - assert.ok(!testObject.affectsConfiguration('window.restoreWindows', URI.file(join('folder1', 'file1')))); - assert.ok(!testObject.affectsConfiguration('window.restoreWindows', URI.file(join('folder3', 'file3')))); - - assert.ok(testObject.affectsConfiguration('window.title')); - assert.ok(testObject.affectsConfiguration('window.title', URI.file('folder1'))); - assert.ok(testObject.affectsConfiguration('window.title', URI.file(join('folder1', 'file1')))); - assert.ok(testObject.affectsConfiguration('window.title', URI.file('folder2'))); - assert.ok(testObject.affectsConfiguration('window.title', URI.file(join('folder2', 'file2')))); - assert.ok(testObject.affectsConfiguration('window.title', URI.file('folder3'))); - assert.ok(testObject.affectsConfiguration('window.title', URI.file(join('folder3', 'file3')))); - assert.ok(testObject.affectsConfiguration('window.title', URI.file('file1'))); - assert.ok(testObject.affectsConfiguration('window.title', URI.file('file2'))); - assert.ok(testObject.affectsConfiguration('window.title', URI.file('file3'))); - - assert.ok(testObject.affectsConfiguration('window')); - assert.ok(testObject.affectsConfiguration('window', URI.file('folder1'))); - assert.ok(testObject.affectsConfiguration('window', URI.file(join('folder1', 'file1')))); - assert.ok(testObject.affectsConfiguration('window', URI.file('folder2'))); - assert.ok(testObject.affectsConfiguration('window', URI.file(join('folder2', 'file2')))); - assert.ok(testObject.affectsConfiguration('window', URI.file('folder3'))); - assert.ok(testObject.affectsConfiguration('window', URI.file(join('folder3', 'file3')))); - assert.ok(testObject.affectsConfiguration('window', URI.file('file1'))); - assert.ok(testObject.affectsConfiguration('window', URI.file('file2'))); - assert.ok(testObject.affectsConfiguration('window', URI.file('file3'))); - - assert.ok(testObject.affectsConfiguration('workbench.editor.enablePreview')); - assert.ok(testObject.affectsConfiguration('workbench.editor.enablePreview', URI.file('folder2'))); - assert.ok(testObject.affectsConfiguration('workbench.editor.enablePreview', URI.file(join('folder2', 'file2')))); - assert.ok(!testObject.affectsConfiguration('workbench.editor.enablePreview', URI.file('folder1'))); - assert.ok(!testObject.affectsConfiguration('workbench.editor.enablePreview', URI.file(join('folder1', 'file1')))); - assert.ok(!testObject.affectsConfiguration('workbench.editor.enablePreview', URI.file('folder3'))); - - assert.ok(testObject.affectsConfiguration('workbench.editor')); - assert.ok(testObject.affectsConfiguration('workbench.editor', URI.file('folder2'))); - assert.ok(testObject.affectsConfiguration('workbench.editor', URI.file(join('folder2', 'file2')))); - assert.ok(!testObject.affectsConfiguration('workbench.editor', URI.file('folder1'))); - assert.ok(!testObject.affectsConfiguration('workbench.editor', URI.file(join('folder1', 'file1')))); - assert.ok(!testObject.affectsConfiguration('workbench.editor', URI.file('folder3'))); - - assert.ok(testObject.affectsConfiguration('workbench')); - assert.ok(testObject.affectsConfiguration('workbench', URI.file('folder2'))); - assert.ok(testObject.affectsConfiguration('workbench', URI.file(join('folder2', 'file2')))); - assert.ok(!testObject.affectsConfiguration('workbench', URI.file('folder1'))); - assert.ok(!testObject.affectsConfiguration('workbench', URI.file('folder3'))); - - assert.ok(!testObject.affectsConfiguration('files')); - assert.ok(!testObject.affectsConfiguration('files', URI.file('folder1'))); - assert.ok(!testObject.affectsConfiguration('files', URI.file(join('folder1', 'file1')))); - assert.ok(!testObject.affectsConfiguration('files', URI.file('folder2'))); - assert.ok(!testObject.affectsConfiguration('files', URI.file(join('folder2', 'file2')))); - assert.ok(!testObject.affectsConfiguration('files', URI.file('folder3'))); - assert.ok(!testObject.affectsConfiguration('files', URI.file(join('folder3', 'file3')))); + const defaultConfigurationModel = toConfigurationModel({ + 'editor.lineNumbers': 'on', + 'editor.fontSize': 12, + 'window.zoomLevel': 1, + '[markdown]': { + 'editor.wordWrap': 'off' + }, + 'window.title': 'custom', + 'workbench.enableTabs': false, + 'editor.insertSpaces': true }); + test('Test compare same configurations', () => { + const workspace = new Workspace('a', [new WorkspaceFolder({ index: 0, name: 'a', uri: URI.file('folder1') }), new WorkspaceFolder({ index: 1, name: 'b', uri: URI.file('folder2') }), new WorkspaceFolder({ index: 2, name: 'c', uri: URI.file('folder3') })]); + const configuration1 = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ResourceMap(), new ConfigurationModel(), new ResourceMap(), workspace); + configuration1.updateDefaultConfiguration(defaultConfigurationModel); + configuration1.updateLocalUserConfiguration(toConfigurationModel({ 'window.title': 'native', '[typescript]': { 'editor.insertSpaces': false } })); + configuration1.updateWorkspaceConfiguration(toConfigurationModel({ 'editor.lineNumbers': 'on' })); + configuration1.updateFolderConfiguration(URI.file('folder1'), toConfigurationModel({ 'editor.fontSize': 14 })); + configuration1.updateFolderConfiguration(URI.file('folder2'), toConfigurationModel({ 'editor.wordWrap': 'on' })); + + const configuration2 = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ResourceMap(), new ConfigurationModel(), new ResourceMap(), workspace); + configuration2.updateDefaultConfiguration(defaultConfigurationModel); + configuration2.updateLocalUserConfiguration(toConfigurationModel({ 'window.title': 'native', '[typescript]': { 'editor.insertSpaces': false } })); + configuration2.updateWorkspaceConfiguration(toConfigurationModel({ 'editor.lineNumbers': 'on' })); + configuration2.updateFolderConfiguration(URI.file('folder1'), toConfigurationModel({ 'editor.fontSize': 14 })); + configuration2.updateFolderConfiguration(URI.file('folder2'), toConfigurationModel({ 'editor.wordWrap': 'on' })); + + const actual = configuration2.compare(configuration1); + + assert.deepEqual(actual, { keys: [], overrides: [] }); + }); + + test('Test compare different configurations', () => { + const workspace = new Workspace('a', [new WorkspaceFolder({ index: 0, name: 'a', uri: URI.file('folder1') }), new WorkspaceFolder({ index: 1, name: 'b', uri: URI.file('folder2') }), new WorkspaceFolder({ index: 2, name: 'c', uri: URI.file('folder3') })]); + const configuration1 = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ResourceMap(), new ConfigurationModel(), new ResourceMap(), workspace); + configuration1.updateDefaultConfiguration(defaultConfigurationModel); + configuration1.updateLocalUserConfiguration(toConfigurationModel({ 'window.title': 'native', '[typescript]': { 'editor.insertSpaces': false } })); + configuration1.updateWorkspaceConfiguration(toConfigurationModel({ 'editor.lineNumbers': 'on' })); + configuration1.updateFolderConfiguration(URI.file('folder1'), toConfigurationModel({ 'editor.fontSize': 14 })); + configuration1.updateFolderConfiguration(URI.file('folder2'), toConfigurationModel({ 'editor.wordWrap': 'on' })); + + const configuration2 = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ResourceMap(), new ConfigurationModel(), new ResourceMap(), workspace); + configuration2.updateDefaultConfiguration(defaultConfigurationModel); + configuration2.updateLocalUserConfiguration(toConfigurationModel({ 'workbench.enableTabs': true, '[typescript]': { 'editor.insertSpaces': true } })); + configuration2.updateWorkspaceConfiguration(toConfigurationModel({ 'editor.fontSize': 11 })); + configuration2.updateFolderConfiguration(URI.file('folder1'), toConfigurationModel({ 'editor.insertSpaces': true })); + configuration2.updateFolderConfiguration(URI.file('folder2'), toConfigurationModel({ + '[markdown]': { + 'editor.wordWrap': 'on', + 'editor.lineNumbers': 'relative' + }, + })); + + const actual = configuration2.compare(configuration1); + + assert.deepEqual(actual, { keys: ['editor.wordWrap', 'editor.fontSize', '[markdown]', 'window.title', 'workbench.enableTabs', '[typescript]'], overrides: [['markdown', ['editor.lineNumbers', 'editor.wordWrap']], ['typescript', ['editor.insertSpaces']]] }); + }); + + }); -suite('AllKeysConfigurationChangeEvent', () => { - - test('changeEvent affects keys for any resource', () => { - const configuraiton = new Configuration(new ConfigurationModel({}, ['window.title', 'window.zoomLevel', 'window.restoreFullscreen', 'workbench.editor.enablePreview', 'window.restoreWindows']), - new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ResourceMap(), new ConfigurationModel(), new ResourceMap(), null!); - let testObject = new AllKeysConfigurationChangeEvent(configuraiton, ConfigurationTarget.USER, null); - - assert.deepEqual(testObject.affectedKeys, ['window.title', 'window.zoomLevel', 'window.restoreFullscreen', 'workbench.editor.enablePreview', 'window.restoreWindows']); - - assert.ok(testObject.affectsConfiguration('window.zoomLevel')); - assert.ok(testObject.affectsConfiguration('window.zoomLevel', URI.file('file1'))); - assert.ok(testObject.affectsConfiguration('window.zoomLevel', URI.file('file2'))); - - assert.ok(testObject.affectsConfiguration('window.restoreFullscreen')); - assert.ok(testObject.affectsConfiguration('window.restoreFullscreen', URI.file('file1'))); - assert.ok(testObject.affectsConfiguration('window.restoreFullscreen', URI.file('file2'))); - - assert.ok(testObject.affectsConfiguration('window.restoreWindows')); - assert.ok(testObject.affectsConfiguration('window.restoreWindows', URI.file('file2'))); - assert.ok(testObject.affectsConfiguration('window.restoreWindows', URI.file('file1'))); - - assert.ok(testObject.affectsConfiguration('window.title')); - assert.ok(testObject.affectsConfiguration('window.title', URI.file('file1'))); - assert.ok(testObject.affectsConfiguration('window.title', URI.file('file2'))); - - assert.ok(testObject.affectsConfiguration('window')); - assert.ok(testObject.affectsConfiguration('window', URI.file('file1'))); - assert.ok(testObject.affectsConfiguration('window', URI.file('file2'))); - - assert.ok(testObject.affectsConfiguration('workbench.editor.enablePreview')); - assert.ok(testObject.affectsConfiguration('workbench.editor.enablePreview', URI.file('file2'))); - assert.ok(testObject.affectsConfiguration('workbench.editor.enablePreview', URI.file('file1'))); - - assert.ok(testObject.affectsConfiguration('workbench.editor')); - assert.ok(testObject.affectsConfiguration('workbench.editor', URI.file('file2'))); - assert.ok(testObject.affectsConfiguration('workbench.editor', URI.file('file1'))); - - assert.ok(testObject.affectsConfiguration('workbench')); - assert.ok(testObject.affectsConfiguration('workbench', URI.file('file2'))); - assert.ok(testObject.affectsConfiguration('workbench', URI.file('file1'))); - - assert.ok(!testObject.affectsConfiguration('files')); - assert.ok(!testObject.affectsConfiguration('files', URI.file('file1'))); - }); -}); +function toConfigurationModel(obj: any): ConfigurationModel { + const parser = new ConfigurationModelParser('test'); + parser.parseContent(JSON.stringify(obj)); + return parser.configurationModel; +} 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 5193ff073a..90f45666ee 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 @@ -6,7 +6,7 @@ import * as assert from 'assert'; import { URI as uri } from 'vs/base/common/uri'; import * as platform from 'vs/base/common/platform'; -import { IConfigurationService, getConfigurationValue, IConfigurationOverrides } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; import { ConfigurationResolverService } from 'vs/workbench/services/configurationResolver/browser/configurationResolverService'; @@ -125,7 +125,7 @@ suite('Configuration Resolver Service', () => { }); test('substitute one configuration variable', () => { - let configurationService: IConfigurationService = new MockConfigurationService({ + let configurationService: IConfigurationService = new TestConfigurationService({ editor: { fontFamily: 'foo' }, @@ -142,7 +142,7 @@ suite('Configuration Resolver Service', () => { test('substitute many configuration variables', () => { let configurationService: IConfigurationService; - configurationService = new MockConfigurationService({ + configurationService = new TestConfigurationService({ editor: { fontFamily: 'foo' }, @@ -159,7 +159,7 @@ suite('Configuration Resolver Service', () => { test('substitute one env variable and a configuration variable', () => { let configurationService: IConfigurationService; - configurationService = new MockConfigurationService({ + configurationService = new TestConfigurationService({ editor: { fontFamily: 'foo' }, @@ -180,7 +180,7 @@ suite('Configuration Resolver Service', () => { test('substitute many env variable and a configuration variable', () => { let configurationService: IConfigurationService; - configurationService = new MockConfigurationService({ + configurationService = new TestConfigurationService({ editor: { fontFamily: 'foo' }, @@ -201,7 +201,7 @@ suite('Configuration Resolver Service', () => { test('mixed types of configuration variables', () => { let configurationService: IConfigurationService; - configurationService = new MockConfigurationService({ + configurationService = new TestConfigurationService({ editor: { fontFamily: 'foo', lineNumbers: 123, @@ -231,7 +231,7 @@ suite('Configuration Resolver Service', () => { test('uses original variable as fallback', () => { let configurationService: IConfigurationService; - configurationService = new MockConfigurationService({ + configurationService = new TestConfigurationService({ editor: {} }); @@ -242,7 +242,7 @@ suite('Configuration Resolver Service', () => { test('configuration variables with invalid accessor', () => { let configurationService: IConfigurationService; - configurationService = new MockConfigurationService({ + configurationService = new TestConfigurationService({ editor: { fontFamily: 'foo' } @@ -503,32 +503,6 @@ suite('Configuration Resolver Service', () => { }); -class MockConfigurationService implements IConfigurationService { - public _serviceBrand: undefined; - public serviceId = IConfigurationService; - public constructor(private configuration: any = {}) { } - public inspect(key: string, overrides?: IConfigurationOverrides): any { return { value: getConfigurationValue(this.getValue(), key), default: getConfigurationValue(this.getValue(), key), user: getConfigurationValue(this.getValue(), key), workspaceFolder: undefined, folder: undefined }; } - public keys() { return { default: [], user: [], workspace: [], workspaceFolder: [] }; } - public getValue(): any; - public getValue(value: string): any; - public getValue(value?: any): any { - if (!value) { - return this.configuration; - } - const valuePath = (value).split('.'); - let object = this.configuration; - while (valuePath.length && object) { - object = object[valuePath.shift()!]; - } - - return object; - } - public updateValue(): Promise { return Promise.resolve(); } - public getConfigurationData(): any { return null; } - public onDidChangeConfiguration() { return { dispose() { } }; } - public reloadConfiguration() { return Promise.resolve(); } -} - class MockCommandService implements ICommandService { public _serviceBrand: undefined; diff --git a/src/vs/workbench/services/extensions/worker/extHost.services.ts b/src/vs/workbench/services/extensions/worker/extHost.services.ts index e273ea0a03..ba8ade6718 100644 --- a/src/vs/workbench/services/extensions/worker/extHost.services.ts +++ b/src/vs/workbench/services/extensions/worker/extHost.services.ts @@ -21,7 +21,7 @@ import { ExtHostExtensionService } from 'vs/workbench/api/worker/extHostExtensio import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; import { ExtHostLogService } from 'vs/workbench/api/worker/extHostLogService'; -import { IExtHostTunnelService, ExtHostTunnelService } from 'vs/workbench/api/common/extHostTunnelService'; +import { IExtHostTunnelService } from 'vs/workbench/api/common/extHostTunnelService'; // register singleton services registerSingleton(ILogService, ExtHostLogService); @@ -34,7 +34,6 @@ registerSingleton(IExtHostDocumentsAndEditors, ExtHostDocumentsAndEditors); registerSingleton(IExtHostStorage, ExtHostStorage); registerSingleton(IExtHostExtensionService, ExtHostExtensionService); registerSingleton(IExtHostSearch, ExtHostSearch); -registerSingleton(IExtHostTunnelService, ExtHostTunnelService); // register services that only throw errors function NotImplementedProxy(name: ServiceIdentifier): { new(): T } { @@ -54,6 +53,5 @@ function NotImplementedProxy(name: ServiceIdentifier): { new(): T } { registerSingleton(IExtHostTerminalService, WorkerExtHostTerminalService); // registerSingleton(IExtHostTask, WorkerExtHostTask); {{SQL CARBON EDIT}} disable // registerSingleton(IExtHostDebugService, WorkerExtHostDebugService); {{SQL CARBON EDIT}} disable -registerSingleton(IExtensionStoragePaths, class extends NotImplementedProxy(IExtensionStoragePaths) { - whenReady = Promise.resolve(); -}); +registerSingleton(IExtensionStoragePaths, class extends NotImplementedProxy(IExtensionStoragePaths) { whenReady = Promise.resolve(); }); +registerSingleton(IExtHostTunnelService, class extends NotImplementedProxy(IExtHostTunnelService) { }); diff --git a/src/vs/workbench/services/remote/common/remoteExplorerService.ts b/src/vs/workbench/services/remote/common/remoteExplorerService.ts index 1d59e6fa17..da5c672aa5 100644 --- a/src/vs/workbench/services/remote/common/remoteExplorerService.ts +++ b/src/vs/workbench/services/remote/common/remoteExplorerService.ts @@ -29,13 +29,14 @@ export interface Tunnel { export class TunnelModel extends Disposable { readonly forwarded: Map; readonly detected: 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; + private _candidateFinder: (() => Promise<{ port: number, detail: string }[]>) | undefined; + constructor( @ITunnelService private readonly tunnelService: ITunnelService ) { @@ -54,11 +55,7 @@ export class TunnelModel extends Disposable { }); this.detected = 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, @@ -122,6 +119,17 @@ export class TunnelModel extends Disposable { }); }); } + + registerCandidateFinder(finder: () => Promise<{ port: number, detail: string }[]>): void { + this._candidateFinder = finder; + } + + get candidates(): Promise<{ port: number, detail: string }[]> { + if (this._candidateFinder) { + return this._candidateFinder(); + } + return Promise.resolve([]); + } } export interface IRemoteExplorerService { @@ -136,6 +144,7 @@ export interface IRemoteExplorerService { forward(remote: number, local?: number, name?: string): Promise; close(remote: number): Promise; addDetected(tunnels: { remote: { port: number, host: string }, localAddress: string }[] | undefined): void; + registerCandidateFinder(finder: () => Promise<{ port: number, detail: string }[]>): void; } export interface HelpInformation { @@ -180,7 +189,7 @@ class RemoteExplorerService implements IRemoteExplorerService { public readonly onDidChangeTargetType: Event = this._onDidChangeTargetType.event; private _helpInformation: HelpInformation[] = []; private _tunnelModel: TunnelModel; - private editable: { remote: number | undefined, data: IEditableData } | undefined; + private _editable: { remote: number | undefined, data: IEditableData } | undefined; private readonly _onDidChangeEditable: Emitter = new Emitter(); public readonly onDidChangeEditable: Event = this._onDidChangeEditable.event; @@ -253,16 +262,21 @@ class RemoteExplorerService implements IRemoteExplorerService { setEditable(remote: number | undefined, data: IEditableData | null): void { if (!data) { - this.editable = undefined; + this._editable = undefined; } else { - this.editable = { remote, data }; + 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; + return this._editable && this._editable.remote === remote ? this._editable.data : undefined; } + + registerCandidateFinder(finder: () => Promise<{ port: number, detail: string }[]>): void { + this.tunnelModel.registerCandidateFinder(finder); + } + } registerSingleton(IRemoteExplorerService, RemoteExplorerService, true); diff --git a/src/vs/workbench/services/textfile/browser/textFileService.ts b/src/vs/workbench/services/textfile/browser/textFileService.ts index 2eab9c8ca0..5afbc6fe65 100644 --- a/src/vs/workbench/services/textfile/browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/browser/textFileService.ts @@ -33,7 +33,7 @@ import { coalesce } from 'vs/base/common/arrays'; import { trim } from 'vs/base/common/strings'; import { VSBuffer } from 'vs/base/common/buffer'; import { ITextSnapshot } from 'vs/editor/common/model'; -import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; +import { IResourceConfigurationService } 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'; @@ -75,7 +75,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex @IDialogService private readonly dialogService: IDialogService, @IFileDialogService private readonly fileDialogService: IFileDialogService, @IEditorService private readonly editorService: IEditorService, - @ITextResourceConfigurationService protected readonly textResourceConfigurationService: ITextResourceConfigurationService, + @IResourceConfigurationService protected readonly textResourceConfigurationService: IResourceConfigurationService, @IFilesConfigurationService protected readonly filesConfigurationService: IFilesConfigurationService ) { super(); diff --git a/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts b/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts index 3e86a16f7b..dfea180b1e 100644 --- a/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts +++ b/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts @@ -15,7 +15,7 @@ import { exists, stat, chmod, rimraf, MAX_FILE_SIZE, MAX_HEAP_SIZE } from 'vs/ba import { join, dirname } from 'vs/base/common/path'; import { isMacintosh } from 'vs/base/common/platform'; import { IProductService } from 'vs/platform/product/common/productService'; -import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; +import { IResourceConfigurationService } 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'; import { WORKSPACE_EXTENSION } from 'vs/platform/workspaces/common/workspaces'; @@ -59,7 +59,7 @@ export class NativeTextFileService extends AbstractTextFileService { @IDialogService dialogService: IDialogService, @IFileDialogService fileDialogService: IFileDialogService, @IEditorService editorService: IEditorService, - @ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService, + @IResourceConfigurationService textResourceConfigurationService: IResourceConfigurationService, @IElectronService private readonly electronService: IElectronService, @IProductService private readonly productService: IProductService, @IFilesConfigurationService filesConfigurationService: IFilesConfigurationService @@ -326,7 +326,7 @@ export class EncodingOracle extends Disposable implements IResourceEncodings { protected encodingOverrides: IEncodingOverride[]; constructor( - @ITextResourceConfigurationService private textResourceConfigurationService: ITextResourceConfigurationService, + @IResourceConfigurationService private textResourceConfigurationService: IResourceConfigurationService, @IEnvironmentService private environmentService: IEnvironmentService, @IWorkspaceContextService private contextService: IWorkspaceContextService, @IFileService private fileService: IFileService diff --git a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts index ddb3595a39..a11a24c770 100644 --- a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts @@ -27,7 +27,7 @@ import { IFileService, FileChangeType } from 'vs/platform/files/common/files'; import { URI } from 'vs/base/common/uri'; 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 { textmateColorsSchemaId, registerColorThemeSchemas, textmateColorGroupSchemaId } 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'; @@ -686,16 +686,7 @@ configurationRegistry.registerConfiguration(themeSettingsConfiguration); function tokenGroupSettings(description: string): IJSONSchema { return { description, - default: '#FF0000', - anyOf: [ - { - type: 'string', - format: 'color-hex' - }, - { - $ref: textmateColorSettingsSchemaId - } - ] + $ref: textmateColorGroupSchemaId }; } diff --git a/src/vs/workbench/services/themes/common/colorThemeData.ts b/src/vs/workbench/services/themes/common/colorThemeData.ts index ae22024887..2c9c2bc943 100644 --- a/src/vs/workbench/services/themes/common/colorThemeData.ts +++ b/src/vs/workbench/services/themes/common/colorThemeData.ts @@ -155,7 +155,10 @@ export class ColorThemeData implements IColorTheme { for (const rule of tokenClassificationRegistry.getTokenStylingDefaultRules()) { const matchScore = matchTokenStylingRule(rule, classification); if (matchScore >= 0) { - let style = this.resolveScopes(rule.defaults.scopesToProbe); + let style: TokenStyle | undefined; + if (rule.defaults.scopesToProbe) { + style = this.resolveScopes(rule.defaults.scopesToProbe); + } if (!style && useDefault !== false) { style = this.resolveTokenStyleValue(rule.defaults[this.type]); } @@ -185,8 +188,8 @@ export class ColorThemeData implements IColorTheme { /** * @param tokenStyleValue Resolve a tokenStyleValue in the context of a theme */ - private resolveTokenStyleValue(tokenStyleValue: TokenStyleValue | null): TokenStyle | undefined { - if (tokenStyleValue === null) { + private resolveTokenStyleValue(tokenStyleValue: TokenStyleValue | undefined): TokenStyle | undefined { + if (tokenStyleValue === undefined) { return undefined; } else if (typeof tokenStyleValue === 'string') { const [type, ...modifiers] = tokenStyleValue.split('.'); @@ -289,7 +292,7 @@ export class ColorThemeData implements IColorTheme { findTokenStyleForScopeInScopes(this.themeTokenScopeMatchers, this.themeTokenColors); findTokenStyleForScopeInScopes(this.customTokenScopeMatchers, this.customTokenColors); if (foreground !== undefined || fontStyle !== undefined) { - return getTokenStyle(foreground, fontStyle); + return TokenStyle.fromSettings(foreground, fontStyle); } } return undefined; @@ -682,34 +685,7 @@ function getScopeMatcher(rule: ITextMateThemingRule): Matcher { }; } -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) { @@ -720,9 +696,9 @@ function readCustomTokenStyleRules(tokenStylingRuleSection: IExperimentalTokenSt const settings = tokenStylingRuleSection[key]; let style: TokenStyle | undefined; if (typeof settings === 'string') { - style = getTokenStyle(settings, undefined); + style = TokenStyle.fromSettings(settings, undefined); } else if (isTokenColorizationSetting(settings)) { - style = getTokenStyle(settings.foreground, settings.fontStyle); + style = TokenStyle.fromSettings(settings.foreground, settings.fontStyle); } if (style) { result.push(tokenClassificationRegistry.getTokenStylingRule(classification, style)); diff --git a/src/vs/workbench/services/themes/common/colorThemeSchema.ts b/src/vs/workbench/services/themes/common/colorThemeSchema.ts index ab5b3db0ae..8ca9ce18ed 100644 --- a/src/vs/workbench/services/themes/common/colorThemeSchema.ts +++ b/src/vs/workbench/services/themes/common/colorThemeSchema.ts @@ -115,10 +115,23 @@ let textMateScopes = [ export const textmateColorsSchemaId = 'vscode://schemas/textmate-colors'; export const textmateColorSettingsSchemaId = `${textmateColorsSchemaId}#definitions/settings`; +export const textmateColorGroupSchemaId = `${textmateColorsSchemaId}#definitions/colorGroup`; const textmateColorSchema: IJSONSchema = { type: 'array', definitions: { + colorGroup: { + default: '#FF0000', + anyOf: [ + { + type: 'string', + format: 'color-hex' + }, + { + $ref: '#definitions/settings' + } + ] + }, settings: { type: 'object', description: nls.localize('schema.token.settings', 'Colors and styles for the token.'), diff --git a/src/vs/workbench/services/themes/common/tokenClassificationExtensionPoint.ts b/src/vs/workbench/services/themes/common/tokenClassificationExtensionPoint.ts new file mode 100644 index 0000000000..2c0d9a58f8 --- /dev/null +++ b/src/vs/workbench/services/themes/common/tokenClassificationExtensionPoint.ts @@ -0,0 +1,262 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { ExtensionsRegistry, ExtensionMessageCollector } from 'vs/workbench/services/extensions/common/extensionsRegistry'; +import { getTokenClassificationRegistry, ITokenClassificationRegistry, typeAndModifierIdPattern, TokenStyleDefaults, TokenStyle, fontStylePattern } from 'vs/platform/theme/common/tokenClassificationRegistry'; +import { textmateColorSettingsSchemaId } from 'vs/workbench/services/themes/common/colorThemeSchema'; + +interface ITokenTypeExtensionPoint { + id: string; + description: string; +} + +interface ITokenModifierExtensionPoint { + id: string; + description: string; +} + +interface ITokenStyleDefaultExtensionPoint { + selector: string; + scopes?: string[]; + light?: { + foreground?: string; + fontStyle?: string; + }; + dark?: { + foreground?: string; + fontStyle?: string; + }; + highContrast?: { + foreground?: string; + fontStyle?: string; + }; +} + +const selectorPattern = '^([-_\\w]+|\\*)(\\.[-_\\w+]+)*$'; +const colorPattern = '^#([0-9A-Fa-f]{6})([0-9A-Fa-f]{2})?$'; + +const tokenClassificationRegistry: ITokenClassificationRegistry = getTokenClassificationRegistry(); + +const tokenTypeExtPoint = ExtensionsRegistry.registerExtensionPoint({ + extensionPoint: 'tokenTypes', + jsonSchema: { + description: nls.localize('contributes.tokenTypes', 'Contributes semantic token types.'), + type: 'array', + items: { + type: 'object', + properties: { + id: { + type: 'string', + description: nls.localize('contributes.tokenTypes.id', 'The identifier of the token type'), + pattern: typeAndModifierIdPattern, + patternErrorMessage: nls.localize('contributes.tokenTypes.id.format', 'Identifiers should be in the form letterOrDigit[_-letterOrDigit]*'), + }, + description: { + type: 'string', + description: nls.localize('contributes.color.description', 'The description of the token type'), + } + } + } + } +}); + +const tokenModifierExtPoint = ExtensionsRegistry.registerExtensionPoint({ + extensionPoint: 'tokenModifiers', + jsonSchema: { + description: nls.localize('contributes.tokenModifiers', 'Contributes semantic token modifiers.'), + type: 'array', + items: { + type: 'object', + properties: { + id: { + type: 'string', + description: nls.localize('contributes.tokenModifiers.id', 'The identifier of the token modifier'), + pattern: typeAndModifierIdPattern, + patternErrorMessage: nls.localize('contributes.tokenModifiers.id.format', 'Identifiers should be in the form letterOrDigit[_-letterOrDigit]*') + }, + description: { + description: nls.localize('contributes.tokenModifiers.description', 'The description of the token modifier') + } + } + } + } +}); + +const tokenStyleDefaultsExtPoint = ExtensionsRegistry.registerExtensionPoint({ + extensionPoint: 'tokenStyleDefaults', + jsonSchema: { + description: nls.localize('contributes.tokenStyleDefaults', 'Contributes semantic token style default.'), + type: 'array', + items: { + type: 'object', + properties: { + selector: { + type: 'string', + description: nls.localize('contributes.tokenStyleDefaults.selector', 'The selector matching token types and modifiers.'), + pattern: selectorPattern, + patternErrorMessage: nls.localize('contributes.tokenStyleDefaults.selector.format', 'Selectors should be in the form (type|*)(.modifier)*'), + }, + scopes: { + type: 'array', + description: nls.localize('contributes.scopes.light', 'A list of textmate scopes that are matched against the current color theme to find a default style'), + items: { + type: 'string' + } + }, + light: { + description: nls.localize('contributes.tokenStyleDefaults.light', 'The default style used for light themes'), + $ref: textmateColorSettingsSchemaId + }, + dark: { + description: nls.localize('contributes.tokenStyleDefaults.dark', 'The default style used for dark themes'), + $ref: textmateColorSettingsSchemaId + }, + highContrast: { + description: nls.localize('contributes.tokenStyleDefaults.hc', 'The default style used for high contrast themes'), + $ref: textmateColorSettingsSchemaId + } + } + } + } +}); + + +export class TokenClassificationExtensionPoints { + + constructor() { + function validateTypeOrModifier(contribution: ITokenTypeExtensionPoint | ITokenModifierExtensionPoint, extensionPoint: string, collector: ExtensionMessageCollector): boolean { + if (typeof contribution.id !== 'string' || contribution.id.length === 0) { + collector.error(nls.localize('invalid.id', "'configuration.{0}.id' must be defined and can not be empty", extensionPoint)); + return false; + } + if (!contribution.id.match(typeAndModifierIdPattern)) { + collector.error(nls.localize('invalid.id.format', "'configuration.{0}.id' must follow the pattern letterOrDigit[-_letterOrDigit]*", extensionPoint)); + return false; + } + if (typeof contribution.description !== 'string' || contribution.id.length === 0) { + collector.error(nls.localize('invalid.description', "'configuration.{0}.description' must be defined and can not be empty", extensionPoint)); + return false; + } + return true; + } + function validateStyle(style: { foreground?: string; fontStyle?: string; } | undefined, extensionPoint: string, collector: ExtensionMessageCollector): TokenStyle | undefined { + if (!style) { + return undefined; + } + if (style.foreground) { + if (typeof style.foreground !== 'string' || !style.foreground.match(colorPattern)) { + collector.error(nls.localize('invalid.color', "'configuration.{0}.foreground' must follow the pattern #RRGGBB[AA]", extensionPoint)); + return undefined; + } + } + if (style.fontStyle) { + if (typeof style.fontStyle !== 'string' || !style.fontStyle.match(fontStylePattern)) { + collector.error(nls.localize('invalid.fontStyle', "'configuration.{0}.fontStyle' must be a one or a compination of \'italic\', \'bold\' or \'underline\' or the empty string", extensionPoint)); + return undefined; + } + } + return TokenStyle.fromSettings(style.foreground, style.fontStyle); + } + + tokenTypeExtPoint.setHandler((extensions, delta) => { + for (const extension of delta.added) { + const extensionValue = extension.value; + const collector = extension.collector; + + if (!extensionValue || !Array.isArray(extensionValue)) { + collector.error(nls.localize('invalid.tokenTypeConfiguration', "'configuration.tokenType' must be a array")); + return; + } + for (const contribution of extensionValue) { + if (validateTypeOrModifier(contribution, 'tokenType', collector)) { + tokenClassificationRegistry.registerTokenType(contribution.id, contribution.description); + } + } + } + for (const extension of delta.removed) { + const extensionValue = extension.value; + for (const contribution of extensionValue) { + tokenClassificationRegistry.deregisterTokenType(contribution.id); + } + } + }); + tokenModifierExtPoint.setHandler((extensions, delta) => { + for (const extension of delta.added) { + const extensionValue = extension.value; + const collector = extension.collector; + + if (!extensionValue || !Array.isArray(extensionValue)) { + collector.error(nls.localize('invalid.tokenModifierConfiguration', "'configuration.tokenModifier' must be a array")); + return; + } + for (const contribution of extensionValue) { + if (validateTypeOrModifier(contribution, 'tokenModifier', collector)) { + tokenClassificationRegistry.registerTokenModifier(contribution.id, contribution.description); + } + } + } + for (const extension of delta.removed) { + const extensionValue = extension.value; + for (const contribution of extensionValue) { + tokenClassificationRegistry.deregisterTokenModifier(contribution.id); + } + } + }); + tokenStyleDefaultsExtPoint.setHandler((extensions, delta) => { + for (const extension of delta.added) { + const extensionValue = extension.value; + const collector = extension.collector; + + if (!extensionValue || !Array.isArray(extensionValue)) { + collector.error(nls.localize('invalid.tokenStyleDefaultConfiguration', "'configuration.tokenStyleDefaults' must be a array")); + return; + } + for (const contribution of extensionValue) { + if (typeof contribution.selector !== 'string' || contribution.selector.length === 0) { + collector.error(nls.localize('invalid.selector', "'configuration.tokenStyleDefaults.selector' must be defined and can not be empty")); + continue; + } + if (!contribution.selector.match(selectorPattern)) { + collector.error(nls.localize('invalid.selector.format', "'configuration.tokenStyleDefaults.selector' must be in the form (type|*)(.modifier)*")); + continue; + } + + const tokenStyleDefault: TokenStyleDefaults = {}; + + if (contribution.scopes) { + if ((!Array.isArray(contribution.scopes) || contribution.scopes.some(s => typeof s !== 'string'))) { + collector.error(nls.localize('invalid.scopes', "If defined, 'configuration.tokenStyleDefaults.scopes' must must be an array or strings")); + continue; + } + tokenStyleDefault.scopesToProbe = [contribution.scopes]; + } + tokenStyleDefault.light = validateStyle(contribution.light, 'tokenStyleDefaults.light', collector); + tokenStyleDefault.dark = validateStyle(contribution.dark, 'tokenStyleDefaults.dark', collector); + tokenStyleDefault.hc = validateStyle(contribution.highContrast, 'tokenStyleDefaults.highContrast', collector); + + const [type, ...modifiers] = contribution.selector.split('.'); + const classification = tokenClassificationRegistry.getTokenClassification(type, modifiers); + if (classification) { + tokenClassificationRegistry.registerTokenStyleDefault(classification, tokenStyleDefault); + } + } + } + for (const extension of delta.removed) { + const extensionValue = extension.value; + for (const contribution of extensionValue) { + const [type, ...modifiers] = contribution.selector.split('.'); + const classification = tokenClassificationRegistry.getTokenClassification(type, modifiers); + if (classification) { + tokenClassificationRegistry.deregisterTokenStyleDefault(classification); + } + } + } + }); + } +} + + + diff --git a/src/vs/workbench/services/userDataSync/common/userDataSyncUtil.ts b/src/vs/workbench/services/userDataSync/common/userDataSyncUtil.ts index 4d936be51f..3a2f525870 100644 --- a/src/vs/workbench/services/userDataSync/common/userDataSyncUtil.ts +++ b/src/vs/workbench/services/userDataSync/common/userDataSyncUtil.ts @@ -10,7 +10,7 @@ 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'; -import { ITextResourcePropertiesService, ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; +import { ITextResourcePropertiesService, IResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; class UserDataSyncUtilService implements IUserDataSyncUtilService { @@ -20,7 +20,7 @@ class UserDataSyncUtilService implements IUserDataSyncUtilService { @IKeybindingService private readonly keybindingsService: IKeybindingService, @ITextModelService private readonly textModelService: ITextModelService, @ITextResourcePropertiesService private readonly textResourcePropertiesService: ITextResourcePropertiesService, - @ITextResourceConfigurationService private readonly textResourceConfigurationService: ITextResourceConfigurationService, + @IResourceConfigurationService private readonly textResourceConfigurationService: IResourceConfigurationService, ) { } public async resolveUserBindings(userBindings: string[]): Promise> { 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 b621b883f8..ea20d79973 100644 --- a/src/vs/workbench/test/browser/parts/editor/breadcrumbModel.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/breadcrumbModel.test.ts @@ -25,6 +25,9 @@ suite('Breadcrumb Model', function () { } return super.getValue(...args); } + updateValue() { + return Promise.resolve(); + } }; test('only uri, inside workspace', function () { diff --git a/src/vs/workbench/test/common/editor/editorGroups.test.ts b/src/vs/workbench/test/common/editor/editorGroups.test.ts index 7697eec3e5..27ec465700 100644 --- a/src/vs/workbench/test/common/editor/editorGroups.test.ts +++ b/src/vs/workbench/test/common/editor/editorGroups.test.ts @@ -521,8 +521,8 @@ suite('Workbench editor groups', () => { const mru = group.getEditors(true); assert.equal(mru[0], input1); - assert.equal(mru[1], input2); - assert.equal(mru[2], input3); + assert.equal(mru[1], input3); + assert.equal(mru[2], input2); }); test('Multiple Editors - Preview gets overwritten', function () { @@ -1115,12 +1115,12 @@ suite('Workbench editor groups', () => { assert.equal(group2.previewEditor!.matches(g2_input2), true); assert.equal(group1.getEditors(true)[0].matches(g1_input2), true); - assert.equal(group1.getEditors(true)[1].matches(g1_input1), true); - assert.equal(group1.getEditors(true)[2].matches(g1_input3), true); + assert.equal(group1.getEditors(true)[1].matches(g1_input3), true); + assert.equal(group1.getEditors(true)[2].matches(g1_input1), true); assert.equal(group2.getEditors(true)[0].matches(g2_input1), true); - assert.equal(group2.getEditors(true)[1].matches(g2_input2), true); - assert.equal(group2.getEditors(true)[2].matches(g2_input3), true); + assert.equal(group2.getEditors(true)[1].matches(g2_input3), true); + assert.equal(group2.getEditors(true)[2].matches(g2_input2), true); // Create model again - should load from storage group1 = inst.createInstance(EditorGroup, group1.serialize()); @@ -1134,12 +1134,12 @@ suite('Workbench editor groups', () => { assert.equal(group2.previewEditor!.matches(g2_input2), true); assert.equal(group1.getEditors(true)[0].matches(g1_input2), true); - assert.equal(group1.getEditors(true)[1].matches(g1_input1), true); - assert.equal(group1.getEditors(true)[2].matches(g1_input3), true); + assert.equal(group1.getEditors(true)[1].matches(g1_input3), true); + assert.equal(group1.getEditors(true)[2].matches(g1_input1), true); assert.equal(group2.getEditors(true)[0].matches(g2_input1), true); - assert.equal(group2.getEditors(true)[1].matches(g2_input2), true); - assert.equal(group2.getEditors(true)[2].matches(g2_input3), true); + assert.equal(group2.getEditors(true)[1].matches(g2_input3), true); + assert.equal(group2.getEditors(true)[2].matches(g2_input2), true); }); test('Single group, multiple editors - persist (some not persistable)', function () { @@ -1172,18 +1172,18 @@ suite('Workbench editor groups', () => { assert.equal(group.previewEditor!.matches(nonSerializableInput2), true); assert.equal(group.getEditors(true)[0].matches(nonSerializableInput2), true); - assert.equal(group.getEditors(true)[1].matches(serializableInput1), true); - assert.equal(group.getEditors(true)[2].matches(serializableInput2), true); + assert.equal(group.getEditors(true)[1].matches(serializableInput2), true); + assert.equal(group.getEditors(true)[2].matches(serializableInput1), true); // Create model again - should load from storage group = inst.createInstance(EditorGroup, group.serialize()); assert.equal(group.count, 2); - assert.equal(group.activeEditor!.matches(serializableInput1), true); + assert.equal(group.activeEditor!.matches(serializableInput2), true); assert.equal(group.previewEditor, null); - assert.equal(group.getEditors(true)[0].matches(serializableInput1), true); - assert.equal(group.getEditors(true)[1].matches(serializableInput2), true); + assert.equal(group.getEditors(true)[0].matches(serializableInput2), true); + assert.equal(group.getEditors(true)[1].matches(serializableInput1), true); }); test('Multiple groups, multiple editors - persist (some not persistable, causes empty group)', function () { 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 81cbc9833a..576fe39304 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostConfiguration.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostConfiguration.test.ts @@ -12,7 +12,7 @@ import { ConfigurationModel } from 'vs/platform/configuration/common/configurati import { TestRPCProtocol } from './testRPCProtocol'; import { mock } from 'vs/workbench/test/electron-browser/api/mock'; import { IWorkspaceFolder, WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; -import { ConfigurationTarget, IConfigurationModel } from 'vs/platform/configuration/common/configuration'; +import { ConfigurationTarget, IConfigurationModel, IConfigurationChange } from 'vs/platform/configuration/common/configuration'; import { NullLogService } from 'vs/platform/log/common/log'; import { assign } from 'vs/base/common/objects'; import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; @@ -608,7 +608,7 @@ suite('ExtHostConfiguration', function () { createConfigurationData({ 'farboo': { 'config': false, - 'updatedconfig': false + 'updatedConfig': false } }), new NullLogService() @@ -617,29 +617,16 @@ suite('ExtHostConfiguration', function () { const newConfigData = createConfigurationData({ 'farboo': { 'config': false, - 'updatedconfig': true, + 'updatedConfig': true, 'newConfig': true, } }); - const changedConfigurationByResource = Object.create({}); - changedConfigurationByResource[workspaceFolder.uri.toString()] = new ConfigurationModel({ - 'farboo': { - 'newConfig': true, - } - }, ['farboo.newConfig']); - const configEventData = { - changedConfiguration: new ConfigurationModel({ - 'farboo': { - 'updatedConfig': true, - } - }, ['farboo.updatedConfig']), - changedConfigurationByResource - }; + const configEventData: IConfigurationChange = { keys: ['farboo.updatedConfig', 'farboo.newConfig'], overrides: [] }; testObject.onDidChangeConfiguration(e => { assert.deepEqual(testObject.getConfiguration().get('farboo'), { 'config': false, - 'updatedconfig': true, + 'updatedConfig': true, 'newConfig': true, }); @@ -653,7 +640,7 @@ suite('ExtHostConfiguration', function () { assert.ok(e.affectsConfiguration('farboo.newConfig')); assert.ok(e.affectsConfiguration('farboo.newConfig', workspaceFolder.uri)); - assert.ok(!e.affectsConfiguration('farboo.newConfig', URI.file('any'))); + assert.ok(e.affectsConfiguration('farboo.newConfig', URI.file('any'))); assert.ok(!e.affectsConfiguration('farboo.config')); assert.ok(!e.affectsConfiguration('farboo.config', workspaceFolder.uri)); 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 067af27114..29d1f039b5 100644 --- a/src/vs/workbench/test/electron-browser/api/mainThreadDocumentsAndEditors.test.ts +++ b/src/vs/workbench/test/electron-browser/api/mainThreadDocumentsAndEditors.test.ts @@ -21,6 +21,7 @@ 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'; +import { NullLogService } from 'vs/platform/log/common/log'; suite('MainThreadDocumentsAndEditors', () => { @@ -43,7 +44,7 @@ suite('MainThreadDocumentsAndEditors', () => { deltas.length = 0; const configService = new TestConfigurationService(); configService.setUserConfiguration('editor', { 'detectIndentation': false }); - modelService = new ModelServiceImpl(configService, new TestTextResourcePropertiesService(configService), new TestThemeService()); + modelService = new ModelServiceImpl(configService, new TestTextResourcePropertiesService(configService), new TestThemeService(), new NullLogService()); 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 23fde4ff2e..aa40f4a46d 100644 --- a/src/vs/workbench/test/electron-browser/api/mainThreadEditors.test.ts +++ b/src/vs/workbench/test/electron-browser/api/mainThreadEditors.test.ts @@ -43,7 +43,7 @@ suite('MainThreadEditors', () => { setup(() => { const configService = new TestConfigurationService(); - modelService = new ModelServiceImpl(configService, new TestTextResourcePropertiesService(configService), new TestThemeService()); + modelService = new ModelServiceImpl(configService, new TestTextResourcePropertiesService(configService), new TestThemeService(), new NullLogService()); const codeEditorService = new TestCodeEditorService(); movedResources.clear(); 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 433c11fefc..f586dca4e1 100644 --- a/src/vs/workbench/test/electron-browser/quickopen.perf.integrationTest.ts +++ b/src/vs/workbench/test/electron-browser/quickopen.perf.integrationTest.ts @@ -31,6 +31,7 @@ import { IUntitledTextEditorService, UntitledTextEditorService } from 'vs/workbe 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'; +import { NullLogService } from 'vs/platform/log/common/log'; namespace Timer { export interface ITimerEvent { @@ -74,7 +75,7 @@ suite.skip('QuickOpen performance (integration)', () => { [ITelemetryService, telemetryService], [IConfigurationService, configurationService], [ITextResourcePropertiesService, textResourcePropertiesService], - [IModelService, new ModelServiceImpl(configurationService, textResourcePropertiesService, new TestThemeService())], + [IModelService, new ModelServiceImpl(configurationService, textResourcePropertiesService, new TestThemeService(), new NullLogService())], [IWorkspaceContextService, new TestContextService(testWorkspace(URI.file(testWorkspacePath)))], [IEditorService, new TestEditorService()], [IEditorGroupsService, new TestEditorGroupsService()], 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 cc49483db1..b91aeb279c 100644 --- a/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts +++ b/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts @@ -60,18 +60,19 @@ suite.skip('TextSearch performance (integration)', () => { const telemetryService = new TestTelemetryService(); const configurationService = new TestConfigurationService(); const textResourcePropertiesService = new TestTextResourcePropertiesService(configurationService); + const logService = new NullLogService(); const instantiationService = new InstantiationService(new ServiceCollection( [ITelemetryService, telemetryService], [IConfigurationService, configurationService], [ITextResourcePropertiesService, textResourcePropertiesService], - [IModelService, new ModelServiceImpl(configurationService, textResourcePropertiesService, new TestThemeService())], + [IModelService, new ModelServiceImpl(configurationService, textResourcePropertiesService, new TestThemeService(), logService)], [IWorkspaceContextService, new TestContextService(testWorkspace(URI.file(testWorkspacePath)))], [IEditorService, new TestEditorService()], [IEditorGroupsService, new TestEditorGroupsService()], [IEnvironmentService, TestEnvironmentService], [IUntitledTextEditorService, createSyncDescriptor(UntitledTextEditorService)], [ISearchService, createSyncDescriptor(LocalSearchService)], - [ILogService, new NullLogService()] + [ILogService, logService] )); const queryOptions: ITextQueryBuilderOptions = { diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index 4816c7c9e8..102f56565c 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -16,7 +16,7 @@ import { IEditorOpeningEvent, EditorServiceImpl, IEditorGroupView } from 'vs/wor import { Event, Emitter } from 'vs/base/common/event'; import Severity from 'vs/base/common/severity'; import { IBackupFileService, IResolvedBackup } from 'vs/workbench/services/backup/common/backup'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { IWorkbenchLayoutService, Parts, Position as PartPosition } from 'vs/workbench/services/layout/browser/layoutService'; import { TextModelResolverService } from 'vs/workbench/services/textmodelResolver/common/textModelResolverService'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; @@ -42,7 +42,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { IThemeService } from 'vs/platform/theme/common/themeService'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; -import { ITextResourceConfigurationService, ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; +import { IResourceConfigurationService, ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; import { IPosition, Position as EditorPosition } from 'vs/editor/common/core/position'; import { IMenuService, MenuId, IMenu } from 'vs/platform/actions/common/actions'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -209,7 +209,7 @@ export class TestTextFileService extends NativeTextFileService { @IDialogService dialogService: IDialogService, @IFileDialogService fileDialogService: IFileDialogService, @IEditorService editorService: IEditorService, - @ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService, + @IResourceConfigurationService textResourceConfigurationService: IResourceConfigurationService, @IElectronService electronService: IElectronService, @IProductService productService: IProductService, @IFilesConfigurationService filesConfigurationService: IFilesConfigurationService @@ -286,7 +286,7 @@ export function workbenchInstantiationService(): IInstantiationService { const configService = new TestConfigurationService(); instantiationService.stub(IConfigurationService, configService); instantiationService.stub(IFilesConfigurationService, new TestFilesConfigurationService(contextKeyService, configService, TestEnvironmentService)); - instantiationService.stub(ITextResourceConfigurationService, new TestTextResourceConfigurationService(configService)); + instantiationService.stub(IResourceConfigurationService, new TestTextResourceConfigurationService(configService)); instantiationService.stub(IUntitledTextEditorService, instantiationService.createInstance(UntitledTextEditorService)); instantiationService.stub(IStorageService, new TestStorageService()); instantiationService.stub(IWorkbenchLayoutService, new TestLayoutService()); @@ -1247,7 +1247,7 @@ export class TestLifecycleService implements ILifecycleService { } } -export class TestTextResourceConfigurationService implements ITextResourceConfigurationService { +export class TestTextResourceConfigurationService implements IResourceConfigurationService { _serviceBrand: undefined; @@ -1263,6 +1263,10 @@ export class TestTextResourceConfigurationService implements ITextResourceConfig const section: string | undefined = position ? (typeof arg3 === 'string' ? arg3 : undefined) : (typeof arg2 === 'string' ? arg2 : undefined); return this.configurationService.getValue(section, { resource }); } + + updateValue(resource: URI, key: string, value: any, configurationTarget?: ConfigurationTarget): Promise { + return this.configurationService.updateValue(key, value); + } } export class TestTextResourcePropertiesService implements ITextResourcePropertiesService { diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index f96bd1937e..5fe0821717 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -102,7 +102,7 @@ import { ContextKeyService } from 'vs/platform/contextkey/browser/contextKeyServ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; -import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; +import { IResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; import { TextResourceConfigurationService } from 'vs/editor/common/services/resourceConfigurationImpl'; import { IMenuService } from 'vs/platform/actions/common/actions'; import { MenuService } from 'vs/platform/actions/common/menuService'; @@ -119,7 +119,7 @@ registerSingleton(IMarkerDecorationsService, MarkerDecorationsService); registerSingleton(IMarkerService, MarkerService, true); registerSingleton(IContextKeyService, ContextKeyService); registerSingleton(IModelService, ModelServiceImpl, true); -registerSingleton(ITextResourceConfigurationService, TextResourceConfigurationService); +registerSingleton(IResourceConfigurationService, TextResourceConfigurationService); registerSingleton(IMenuService, MenuService, true); registerSingleton(IDownloadService, DownloadService, true); registerSingleton(IOpenerService, OpenerService, true);