From 30d9e9c141a61d0670335f80b5b77e0f2d74ad13 Mon Sep 17 00:00:00 2001 From: Anthony Dresser Date: Wed, 18 Dec 2019 00:14:28 -0800 Subject: [PATCH] Merge from vscode 4636be2b71c87bfb0bfe3c94278b447a5efcc1f1 (#8722) * Merge from vscode 4636be2b71c87bfb0bfe3c94278b447a5efcc1f1 * remove tests that aren't working --- .yarnrc | 2 +- cgmanifest.json | 4 +- extensions/configuration-editing/package.json | 4 + extensions/image-preview/src/extension.ts | 2 +- extensions/image-preview/src/preview.ts | 10 +- .../src/features/previewManager.ts | 11 +- .../src/services/platformService.ts | 4 +- extensions/search-result/README.md | 2 + package.json | 5 +- src/sql/base/common/promise.ts | 4 + .../connection/common/connectionConfig.ts | 38 +- .../test/common/connectionConfig.test.ts | 30 +- .../test/common/testConfigurationService.ts | 14 +- src/sql/platform/query/common/queryRunner.ts | 2 +- src/sql/workbench/browser/modal/modal.ts | 2 +- .../workbench/browser/modal/optionsDialog.ts | 2 +- .../modelComponents/queryTextEditor.ts | 4 +- .../contrib/accounts/browser/accountDialog.ts | 2 +- .../accounts/browser/autoOAuthDialog.ts | 2 +- .../accounts/browser/firewallRuleDialog.ts | 2 +- .../contrib/backup/browser/backupDialog.ts | 2 +- .../browser/dataExplorerViewlet.ts | 4 +- .../notebook/browser/models/notebookInput.ts | 2 +- .../browser/outputs/gridOutput.component.ts | 2 +- .../common/models/nodebookInputFactory.ts | 8 + .../test/electron-browser/cell.test.ts | 1 - .../browser/serverGroupDialog.ts | 2 +- .../browser/profilerColumnEditorDialog.ts | 2 +- .../profiler/browser/profilerCopyHandler.ts | 2 +- .../profiler/browser/profilerEditor.ts | 2 +- .../profiler/browser/profilerFilterDialog.ts | 2 +- .../browser/profilerResourceEditor.ts | 4 +- .../profiler/browser/profilerTableEditor.ts | 2 +- .../contrib/query/common/queryInputFactory.ts | 8 + .../query/common/resultsGrid.contribution.ts | 1 - .../contrib/restore/browser/restoreDialog.ts | 2 +- .../contrib/webview/browser/webViewDialog.ts | 2 +- .../browser/connectionDialogWidget.ts | 2 +- .../browser/newDashboardTabDialogImpl.ts | 2 +- .../services/dialog/browser/dialogModal.ts | 2 +- .../services/dialog/browser/wizardModal.ts | 2 +- .../browser/errorMessageDialog.ts | 2 +- .../fileBrowser/browser/fileBrowserDialog.ts | 2 +- .../insights/browser/insightsDialogView.ts | 2 +- .../notebook/browser/sql/sqlSessionManager.ts | 2 +- src/tsconfig.json | 1 + src/typings/lib.ie11_safe_es6.d.ts | 4 +- src/typings/windows-mutex.d.ts | 14 - .../ui/codiconLabel/codicon/codicon.css | 3 +- .../ui/codiconLabel/codicon/codicon.ttf | Bin 48224 -> 48356 bytes src/vs/base/browser/ui/dialog/dialog.css | 34 +- src/vs/base/browser/ui/dialog/dialog.ts | 4 +- src/vs/base/browser/ui/list/list.ts | 4 +- src/vs/base/browser/ui/menu/menu.css | 2 +- src/vs/base/browser/ui/menu/menubar.ts | 7 +- src/vs/base/common/async.ts | 2 +- src/vs/base/common/htmlContent.ts | 9 +- src/vs/base/common/map.ts | 8 + src/vs/base/node/decoder.ts | 2 +- .../test/browser/markdownRenderer.test.ts | 32 +- src/vs/base/test/common/map.test.ts | 2 + .../base/test/common/markdownString.test.ts | 48 +- src/vs/base/test/node/pfs/pfs.test.ts | 5 +- src/vs/code/electron-main/window.ts | 82 +- .../editor/browser/controller/mouseTarget.ts | 7 + src/vs/editor/browser/editorBrowser.ts | 1 - src/vs/editor/browser/view/viewImpl.ts | 6 +- .../editor/browser/view/viewOutgoingEvents.ts | 18 +- .../editor/browser/widget/codeEditorWidget.ts | 11 +- .../common/config/commonEditorConfig.ts | 3 +- src/vs/editor/common/config/editorOptions.ts | 3 + src/vs/editor/common/modes.ts | 2 +- .../services/editorWorkerServiceImpl.ts | 16 +- .../common/services/modelServiceImpl.ts | 2 +- ...ts => textResourceConfigurationService.ts} | 8 +- ...> textResourceConfigurationServiceImpl.ts} | 38 +- src/vs/editor/common/services/webWorker.ts | 7 +- .../contrib/codeAction/lightBulbWidget.ts | 6 +- .../contrib/codelens/codelensWidget.css | 10 + .../editor/contrib/codelens/codelensWidget.ts | 2 + .../documentSymbols/media/symbol-icons.css | 17 +- .../contrib/documentSymbols/outlineTree.ts | 202 +-- src/vs/editor/contrib/find/findWidget.css | 73 +- src/vs/editor/contrib/find/findWidget.ts | 12 +- .../multicursor/test/multicursor.test.ts | 3 +- .../contrib/suggest/suggestController.ts | 11 +- .../editor/contrib/suggest/suggestWidget.ts | 2 + .../suggest/test/suggestController.test.ts | 96 ++ .../browser/quickOpen/quickOutline.css | 92 -- .../browser/quickOpen/quickOutline.ts | 2 + .../browser/quickOpen/symbol-sprite.svg | 26 - .../standalone/browser/simpleServices.ts | 43 +- .../standalone/browser/standaloneServices.ts | 4 +- .../test/common/services/modelService.test.ts | 2 +- ... textResourceConfigurationService.test.ts} | 120 +- .../node/classification/typescript-test.ts | 2 +- src/vs/monaco.d.ts | 12 +- .../configuration/common/configuration.ts | 39 +- .../common/configurationModels.ts | 28 +- .../common/configurationRegistry.ts | 66 +- .../test/common/configurationModels.test.ts | 30 +- .../test/common/testConfigurationService.ts | 4 +- .../test/node/configurationService.test.ts | 20 +- src/vs/platform/files/common/fileService.ts | 67 +- .../files/test/node/diskFileService.test.ts | 10 + .../electron-main/menubarMainService.ts | 20 +- .../remote/common/remoteAuthorityResolver.ts | 5 + src/vs/platform/remote/common/tunnel.ts | 14 +- .../platform/remote/common/tunnelService.ts | 6 +- .../severityIcon/common/severityIcon.ts | 28 +- .../storage/browser/storageService.ts | 91 +- src/vs/platform/storage/common/storage.ts | 18 +- .../platform/storage/node/storageService.ts | 35 + src/vs/vscode.d.ts | 87 +- src/vs/vscode.proposed.d.ts | 394 ++++-- .../api/browser/mainThreadConfiguration.ts | 48 +- .../api/browser/mainThreadLanguageFeatures.ts | 12 +- .../workbench/api/browser/mainThreadTask.ts | 22 +- .../api/browser/mainThreadTunnelService.ts | 32 +- .../api/browser/mainThreadWebview.ts | 73 +- .../api/browser/viewsExtensionPoint.ts | 4 +- .../api/common/configurationExtensionPoint.ts | 12 +- .../workbench/api/common/extHost.api.impl.ts | 10 +- .../workbench/api/common/extHost.protocol.ts | 27 +- .../api/common/extHostConfiguration.ts | 96 +- .../api/common/extHostExtensionService.ts | 6 +- .../api/common/extHostLanguageFeatures.ts | 17 +- .../api/common/extHostTunnelService.ts | 32 +- src/vs/workbench/api/common/extHostTypes.ts | 13 +- src/vs/workbench/api/common/extHostWebview.ts | 87 +- .../api/common/menusExtensionPoint.ts | 7 +- .../api/node/extHostTunnelService.ts | 79 +- src/vs/workbench/browser/dnd.ts | 2 +- src/vs/workbench/browser/editor.ts | 23 +- src/vs/workbench/browser/layout.ts | 10 +- src/vs/workbench/browser/panecomposite.ts | 4 + src/vs/workbench/browser/panel.ts | 3 + .../browser/parts/editor/breadcrumbs.ts | 56 +- .../parts/editor/breadcrumbsControl.ts | 4 +- .../browser/parts/editor/breadcrumbsModel.ts | 4 +- .../parts/editor/editor.contribution.ts | 181 ++- .../browser/parts/editor/editorActions.ts | 153 ++- .../browser/parts/editor/editorCommands.ts | 11 +- .../browser/parts/editor/editorDropTarget.ts | 56 +- .../browser/parts/editor/editorGroupView.ts | 35 +- .../browser/parts/editor/editorPart.ts | 4 +- .../browser/parts/editor/editorPicker.ts | 100 +- .../browser/parts/editor/editorStatus.ts | 8 +- .../browser/parts/editor/textDiffEditor.ts | 4 +- .../browser/parts/editor/textEditor.ts | 13 +- .../parts/editor/textResourceEditor.ts | 6 +- .../browser/parts/quickinput/quickInput.ts | 29 +- .../parts/quickinput/quickInputActions.ts | 13 +- .../parts/quickinput/quickInputList.ts | 4 +- .../browser/parts/views/media/views.css | 4 + .../browser/parts/views/viewPaneContainer.ts | 6 +- src/vs/workbench/browser/workbench.ts | 14 +- src/vs/workbench/common/editor.ts | 24 +- src/vs/workbench/common/editor/editorGroup.ts | 141 ++- .../common/editor/untitledTextEditorModel.ts | 4 +- src/vs/workbench/common/views.ts | 13 +- .../backup/common/backupModelTracker.ts | 7 + .../contrib/backup/common/backupRestorer.ts | 2 +- .../electron-browser/backupRestorer.test.ts | 153 +++ .../callHierarchy/browser/callHierarchy.ts | 14 +- .../browser/callHierarchyTree.ts | 2 +- .../codeActions/common/configuration.ts | 4 +- .../codeEditor/browser/toggleWordWrap.ts | 8 +- .../contrib/customEditor/browser/commands.ts | 42 +- .../customEditor/browser/customEditorInput.ts | 4 +- .../customEditor/browser/customEditors.ts | 84 +- .../customEditor/common/customEditor.ts | 64 +- .../customEditor/common/customEditorModel.ts | 2 +- .../browser/breakpointEditorContribution.ts | 12 +- .../contrib/debug/browser/callStackView.ts | 20 +- .../contrib/debug/browser/debugCommands.ts | 2 +- .../browser/debugConfigurationManager.ts | 6 +- .../contrib/debug/browser/debugService.ts | 11 +- .../browser/media/debug.contribution.css | 14 +- .../workbench/contrib/debug/browser/repl.ts | 2 +- .../contrib/debug/browser/startView.ts | 20 +- .../workbench/contrib/debug/common/debug.ts | 4 +- .../contrib/debug/common/debugger.ts | 2 +- .../extensions/browser/extensionsActions.ts | 4 +- .../extensions/browser/extensionsViews.ts | 6 +- .../contrib/extensions/common/extensions.ts | 4 +- .../extensions/common/extensionsInput.ts | 6 +- .../extensions.contribution.ts | 3 + .../browser/editors/fileEditorTracker.ts | 20 +- .../files/browser/editors/textFileEditor.ts | 4 +- .../contrib/files/browser/fileActions.ts | 17 +- .../files/browser/files.contribution.ts | 28 +- .../files/browser/views/explorerView.ts | 61 +- .../files/browser/views/explorerViewer.ts | 18 +- .../contrib/files/common/explorerModel.ts | 41 +- .../contrib/files/common/explorerService.ts | 59 +- .../workbench/contrib/files/common/files.ts | 5 +- .../files/electron-browser/textFileEditor.ts | 4 +- .../electron-browser/explorerModel.test.ts | 19 +- .../contrib/markers/browser/markersPanel.ts | 30 +- .../contrib/markers/browser/media/markers.css | 1 - .../outline/browser/outline.contribution.ts | 38 +- .../outline/browser/outlineNavigation.ts | 4 +- .../contrib/outline/browser/outlinePane.ts | 7 +- .../contrib/output/browser/logViewer.ts | 4 +- .../contrib/output/browser/outputPanel.ts | 4 +- .../performance.contribution.ts | 3 + .../browser/preferences.contribution.ts | 37 +- .../preferences/browser/preferencesEditor.ts | 4 +- .../browser/preferencesRenderers.ts | 2 +- .../preferences/browser/settingsEditor2.ts | 2 +- .../preferences/browser/settingsTreeModels.ts | 20 +- .../contrib/remote/browser/remote.ts | 6 +- .../contrib/remote/browser/tunnelView.ts | 23 +- .../remote/common/remote.contribution.ts | 3 +- .../contrib/scm/browser/dirtydiffDecorator.ts | 9 +- src/vs/workbench/contrib/scm/common/scm.ts | 4 +- .../search/browser/search.contribution.ts | 21 +- .../contrib/search/browser/searchActions.ts | 86 ++ .../contrib/search/browser/searchView.ts | 88 +- .../contrib/search/common/searchModel.ts | 46 +- .../search/test/browser/searchViewlet.test.ts | 25 +- .../tasks/browser/abstractTaskService.ts | 6 +- .../terminal/browser/terminalConfigHelper.ts | 18 +- .../terminal/browser/terminalInstance.ts | 10 + .../terminal/common/terminalEnvironment.ts | 17 +- .../test/node/terminalEnvironment.test.ts | 16 +- .../themes/browser/themes.contribution.ts | 4 +- .../browser/webviewEditorInputFactory.ts | 4 + .../welcome/page/browser/welcomePage.ts | 6 +- .../browser/editor/editorWalkThrough.ts | 4 + src/vs/workbench/electron-browser/window.ts | 1 + .../backup/common/backupFileService.ts | 16 +- .../backupFileService.test.ts | 15 +- .../bulkEdit/browser/bulkEditService.ts | 57 +- .../configuration/browser/configuration.ts | 293 +++-- .../browser/configurationService.ts | 6 +- .../configuration/common/configuration.ts | 10 +- .../common/configurationEditingService.ts | 31 +- .../test/common/configurationModels.test.ts | 14 +- .../configurationEditingService.test.ts | 124 +- .../configurationService.test.ts | 101 +- .../editor/common/editorGroupsService.ts | 9 +- .../services/editor/common/editorService.ts | 12 +- .../test/browser/editorGroupsService.test.ts | 34 +- .../editor/test/browser/editorService.test.ts | 69 +- .../common/extensionDescriptionRegistry.ts | 35 + .../services/extensions/common/lazyPromise.ts | 4 + .../electron-browser/extensionService.ts | 5 +- .../extensions/worker/extHost.services.ts | 4 +- .../common/filesConfigurationService.ts | 4 +- .../services/history/browser/history.ts | 1112 +++++++++++------ .../services/history/common/history.ts | 33 +- .../services/history/test/history.test.ts | 597 +++++++++ .../keybinding/browser/keybindingService.ts | 1 - .../keybinding/browser/keymapService.ts | 1 - .../keybinding.contribution.ts | 1 - .../keybindingEditing.test.ts | 2 +- .../services/remote/node/tunnelService.ts | 57 +- .../services/search/common/search.ts | 14 +- .../services/search/common/searchService.ts | 8 +- .../services/search/node/fileSearch.ts | 4 +- .../services/search/node/rawSearchService.ts | 1 + .../search/node/ripgrepTextSearchEngine.ts | 6 +- .../test/node/ripgrepTextSearchEngine.test.ts | 2 + .../textfile/browser/textFileService.ts | 17 +- .../textfile/common/textFileEditorModel.ts | 8 + .../common/textResourcePropertiesService.ts | 2 +- .../services/textfile/common/textfiles.ts | 5 +- .../electron-browser/nativeTextFileService.ts | 6 +- .../themes/browser/workbenchThemeService.ts | 10 +- .../common/untitledTextEditorService.ts | 11 + .../common/inMemoryUserDataProvider.ts | 20 +- .../userDataSync/common/userDataSyncUtil.ts | 4 +- .../abstractWorkspaceEditingService.ts | 2 +- .../workspaces/browser/workspacesService.ts | 12 +- .../browser/parts/editor/baseEditor.test.ts | 56 +- .../test/browser/parts/views/views.test.ts | 4 +- .../test/common/editor/editorGroups.test.ts | 129 +- .../test/common/editor/editorModel.test.ts | 4 +- .../common/editor/untitledTextEditor.test.ts | 9 + .../api/extHostTreeViews.test.ts | 2 +- .../api/mainThreadConfiguration.test.ts | 38 +- .../api/mainThreadEditors.test.ts | 7 +- .../quickopen.perf.integrationTest.ts | 2 +- .../textsearch.perf.integrationTest.ts | 2 +- .../workbench/test/workbenchTestServices.ts | 376 +++--- src/vs/workbench/workbench.common.main.ts | 6 +- yarn.lock | 31 +- 289 files changed, 5537 insertions(+), 3039 deletions(-) delete mode 100644 src/typings/windows-mutex.d.ts rename src/vs/editor/common/services/{resourceConfiguration.ts => textResourceConfigurationService.ts} (90%) rename src/vs/editor/common/services/{resourceConfigurationImpl.ts => textResourceConfigurationServiceImpl.ts} (77%) create mode 100644 src/vs/editor/contrib/suggest/test/suggestController.test.ts delete mode 100644 src/vs/editor/standalone/browser/quickOpen/symbol-sprite.svg rename src/vs/editor/test/common/services/{resourceConfigurationService.test.ts => textResourceConfigurationService.test.ts} (80%) create mode 100644 src/vs/workbench/contrib/backup/test/electron-browser/backupRestorer.test.ts create mode 100644 src/vs/workbench/services/history/test/history.test.ts diff --git a/.yarnrc b/.yarnrc index 288e7393ab..85baaa63a7 100644 --- a/.yarnrc +++ b/.yarnrc @@ -1,3 +1,3 @@ disturl "https://atom.io/download/electron" -target "6.1.5" +target "6.1.6" runtime "electron" diff --git a/cgmanifest.json b/cgmanifest.json index bc3086c310..c102a04f70 100644 --- a/cgmanifest.json +++ b/cgmanifest.json @@ -60,12 +60,12 @@ "git": { "name": "electron", "repositoryUrl": "https://github.com/electron/electron", - "commitHash": "6f62f91822a80192cb711c604f1a8f1a176f328d" + "commitHash": "19c705ab80cd6fdccca3d65803ec2c4addb9540a" } }, "isOnlyProductionDependency": true, "license": "MIT", - "version": "6.1.5" + "version": "6.1.6" }, { "component": { diff --git a/extensions/configuration-editing/package.json b/extensions/configuration-editing/package.json index 5defc86cf5..be2d2e8eba 100644 --- a/extensions/configuration-editing/package.json +++ b/extensions/configuration-editing/package.json @@ -88,6 +88,10 @@ "fileMatch": "/.vscode/tasks.json", "url": "vscode://schemas/tasks" }, + { + "fileMatch": "%APP_SETTINGS_HOME%/tasks.json", + "url": "vscode://schemas/tasks" + }, { "fileMatch": "%APP_SETTINGS_HOME%/snippets/*.json", "url": "vscode://schemas/snippets" diff --git a/extensions/image-preview/src/extension.ts b/extensions/image-preview/src/extension.ts index 1804cbd561..8c66a98170 100644 --- a/extensions/image-preview/src/extension.ts +++ b/extensions/image-preview/src/extension.ts @@ -23,7 +23,7 @@ export function activate(context: vscode.ExtensionContext) { const previewManager = new PreviewManager(extensionRoot, sizeStatusBarEntry, binarySizeStatusBarEntry, zoomStatusBarEntry); - context.subscriptions.push(vscode.window.registerWebviewEditorProvider(PreviewManager.viewType, previewManager)); + context.subscriptions.push(vscode.window.registerWebviewCustomEditorProvider(PreviewManager.viewType, previewManager)); context.subscriptions.push(vscode.commands.registerCommand('imagePreview.zoomIn', () => { previewManager.activePreview?.zoomIn(); diff --git a/extensions/image-preview/src/preview.ts b/extensions/image-preview/src/preview.ts index a2a36edbb6..d66de373ed 100644 --- a/extensions/image-preview/src/preview.ts +++ b/extensions/image-preview/src/preview.ts @@ -13,7 +13,7 @@ import { BinarySizeStatusBarEntry } from './binarySizeStatusBarEntry'; const localize = nls.loadMessageBundle(); -export class PreviewManager implements vscode.WebviewEditorProvider { +export class PreviewManager implements vscode.WebviewCustomEditorProvider { public static readonly viewType = 'imagePreview.previewEditor'; @@ -28,10 +28,10 @@ export class PreviewManager implements vscode.WebviewEditorProvider { ) { } public async resolveWebviewEditor( - input: { readonly resource: vscode.Uri, }, + resource: vscode.Uri, webviewEditor: vscode.WebviewPanel, - ): Promise { - const preview = new Preview(this.extensionRoot, input.resource, webviewEditor, this.sizeStatusBarEntry, this.binarySizeStatusBarEntry, this.zoomStatusBarEntry); + ): Promise { + const preview = new Preview(this.extensionRoot, resource, webviewEditor, this.sizeStatusBarEntry, this.binarySizeStatusBarEntry, this.zoomStatusBarEntry); this._previews.add(preview); this.setActivePreview(preview); @@ -44,8 +44,6 @@ export class PreviewManager implements vscode.WebviewEditorProvider { this.setActivePreview(undefined); } }); - - return {}; } public get activePreview() { return this._activePreview; } diff --git a/extensions/markdown-language-features/src/features/previewManager.ts b/extensions/markdown-language-features/src/features/previewManager.ts index 21388327ce..52a19ca992 100644 --- a/extensions/markdown-language-features/src/features/previewManager.ts +++ b/extensions/markdown-language-features/src/features/previewManager.ts @@ -52,7 +52,7 @@ class PreviewStore extends Disposable { } } -export class MarkdownPreviewManager extends Disposable implements vscode.WebviewPanelSerializer, vscode.WebviewEditorProvider { +export class MarkdownPreviewManager extends Disposable implements vscode.WebviewPanelSerializer, vscode.WebviewCustomEditorProvider { private static readonly markdownPreviewActiveContextKey = 'markdownPreviewFocus'; private readonly _topmostLineMonitor = new TopmostLineMonitor(); @@ -70,7 +70,7 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview ) { super(); this._register(vscode.window.registerWebviewPanelSerializer(DynamicMarkdownPreview.viewType, this)); - this._register(vscode.window.registerWebviewEditorProvider('vscode.markdown.preview.editor', this)); + this._register(vscode.window.registerWebviewCustomEditorProvider('vscode.markdown.preview.editor', this)); } public refresh() { @@ -149,11 +149,11 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview } public async resolveWebviewEditor( - input: { readonly resource: vscode.Uri; }, + resource: vscode.Uri, webview: vscode.WebviewPanel - ): Promise { + ): Promise { const preview = DynamicMarkdownPreview.revive( - { resource: input.resource, locked: false, resourceColumn: vscode.ViewColumn.One }, + { resource, locked: false, resourceColumn: vscode.ViewColumn.One }, webview, this._contentProvider, this._previewConfigurations, @@ -161,7 +161,6 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview this._topmostLineMonitor, this._contributions); this.registerStaticPreview(preview); - return {}; } private createNewDynamicPreview( diff --git a/extensions/resource-deployment/src/services/platformService.ts b/extensions/resource-deployment/src/services/platformService.ts index 4522050a7e..0bfd67d552 100644 --- a/extensions/resource-deployment/src/services/platformService.ts +++ b/extensions/resource-deployment/src/services/platformService.ts @@ -252,11 +252,11 @@ export class PlatformService implements IPlatformService { outputChannel.appendLine(localize('platformService.RunStreamedCommand.ExitedWithSignal', " >>> {0} … exited with signal: {1}", command, signal)); } }); - child.stdout.on('data', (data: string | Buffer) => { + child.stdout!.on('data', (data: string | Buffer) => { stdoutData.push(data.toString()); this.outputDataChunk(data, outputChannel, localize('platformService.RunCommand.stdout', " stdout: ")); }); - child.stderr.on('data', (data: string | Buffer) => { this.outputDataChunk(data, outputChannel, localize('platformService.RunCommand.stderr', " stderr: ")); }); + child.stderr!.on('data', (data: string | Buffer) => { this.outputDataChunk(data, outputChannel, localize('platformService.RunCommand.stderr', " stderr: ")); }); await child; return stdoutData.join(''); diff --git a/extensions/search-result/README.md b/extensions/search-result/README.md index a28a54db1b..c3e1b53525 100644 --- a/extensions/search-result/README.md +++ b/extensions/search-result/README.md @@ -1,3 +1,5 @@ # Language Features for Search Result files **Notice:** This extension is bundled with Visual Studio Code. It can be disabled but not uninstalled. + +This extension provides Syntax Highlighting, Symbol Infomation, Result Highlighting, and Go to Definition capabilities for the Search Results Editor. diff --git a/package.json b/package.json index 2c07b7942a..3e79e81ad9 100644 --- a/package.json +++ b/package.json @@ -96,12 +96,13 @@ "@types/iconv-lite": "0.0.1", "@types/keytar": "^4.4.0", "@types/mocha": "2.2.39", - "@types/node": "^10.12.12", + "@types/node": "^12.11.7", "@types/plotly.js": "^1.44.9", "@types/sanitize-html": "^1.18.2", "@types/sinon": "^1.16.36", "@types/webpack": "^4.4.10", "@types/windows-foreground-love": "^0.3.0", + "@types/windows-mutex": "^0.4.0", "@types/windows-process-tree": "^0.2.0", "@types/winreg": "^1.2.30", "@types/yauzl": "^2.9.1", @@ -113,7 +114,7 @@ "coveralls": "^2.11.11", "cson-parser": "^1.3.3", "debounce": "^1.0.0", - "electron": "6.1.5", + "electron": "6.1.6", "event-stream": "3.3.4", "express": "^4.13.1", "fancy-log": "^1.3.3", diff --git a/src/sql/base/common/promise.ts b/src/sql/base/common/promise.ts index 843c0c5249..a8e03e51f2 100644 --- a/src/sql/base/common/promise.ts +++ b/src/sql/base/common/promise.ts @@ -33,4 +33,8 @@ export class Deferred implements Promise { finally(onfinally?: () => void): Promise { return this.promise.finally(onfinally); } + + get [Symbol.toStringTag](): string { + return this.toString(); + } } diff --git a/src/sql/platform/connection/common/connectionConfig.ts b/src/sql/platform/connection/common/connectionConfig.ts index e3929f3b04..0455ea2801 100644 --- a/src/sql/platform/connection/common/connectionConfig.ts +++ b/src/sql/platform/connection/common/connectionConfig.ts @@ -39,15 +39,15 @@ export class ConnectionConfig { let allGroups: IConnectionProfileGroup[] = []; const config = this.configurationService.inspect(GROUPS_CONFIG_KEY); - let { user } = config; - const { workspace } = config; + let { userValue } = config; + const { workspaceValue } = config; - if (user) { - if (workspace) { - user = user.filter(x => find(workspace, f => this.isSameGroupName(f, x)) === undefined); - allGroups = allGroups.concat(workspace); + if (userValue) { + if (workspaceValue) { + userValue = userValue.filter(x => find(workspaceValue, f => this.isSameGroupName(f, x)) === undefined); + allGroups = allGroups.concat(workspaceValue); } - allGroups = allGroups.concat(user); + allGroups = allGroups.concat(userValue); } return allGroups.map(g => { if (g.parentId === '' || !g.parentId) { @@ -63,7 +63,7 @@ export class ConnectionConfig { public addConnection(profile: IConnectionProfile): Promise { if (profile.saveProfile) { return this.addGroupFromProfile(profile).then(groupId => { - let profiles = this.configurationService.inspect(CONNECTIONS_CONFIG_KEY).user; + let profiles = this.configurationService.inspect(CONNECTIONS_CONFIG_KEY).userValue; if (!profiles) { profiles = []; } @@ -108,7 +108,7 @@ export class ConnectionConfig { if (profile.groupId && profile.groupId !== Utils.defaultGroupId) { return Promise.resolve(profile.groupId); } else { - let groups = this.configurationService.inspect(GROUPS_CONFIG_KEY).user; + let groups = this.configurationService.inspect(GROUPS_CONFIG_KEY).userValue; let result = this.saveGroup(groups!, profile.groupFullName, undefined, undefined); groups = result.groups; @@ -123,7 +123,7 @@ export class ConnectionConfig { if (profileGroup.id) { return Promise.resolve(profileGroup.id); } else { - let groups = this.configurationService.inspect(GROUPS_CONFIG_KEY).user; + let groups = this.configurationService.inspect(GROUPS_CONFIG_KEY).userValue; let sameNameGroup = groups ? find(groups, group => group.name === profileGroup.name) : undefined; if (sameNameGroup) { let errMessage: string = nls.localize('invalidServerName', "A server group with the same name already exists."); @@ -143,9 +143,9 @@ export class ConnectionConfig { if (configs) { let fromConfig: IConnectionProfileStore[] | undefined; if (configTarget === ConfigurationTarget.USER) { - fromConfig = configs.user; + fromConfig = configs.userValue; } else if (configTarget === ConfigurationTarget.WORKSPACE) { - fromConfig = configs.workspace || []; + fromConfig = configs.workspaceValue || []; } if (fromConfig) { profiles = fromConfig; @@ -218,7 +218,7 @@ export class ConnectionConfig { */ public deleteConnection(profile: ConnectionProfile): Promise { // Get all connections in the settings - let profiles = this.configurationService.inspect(CONNECTIONS_CONFIG_KEY).user; + let profiles = this.configurationService.inspect(CONNECTIONS_CONFIG_KEY).userValue; // Remove the profile from the connections profiles = profiles!.filter(value => { let providerConnectionProfile = ConnectionProfile.createFromStoredProfile(value, this._capabilitiesService); @@ -239,7 +239,7 @@ export class ConnectionConfig { // Add selected group to subgroups list subgroups.push(group); // Get all connections in the settings - let profiles = this.configurationService.inspect(CONNECTIONS_CONFIG_KEY).user; + let profiles = this.configurationService.inspect(CONNECTIONS_CONFIG_KEY).userValue; // Remove the profiles from the connections profiles = profiles!.filter(value => { let providerConnectionProfile = ConnectionProfile.createFromStoredProfile(value, this._capabilitiesService); @@ -247,7 +247,7 @@ export class ConnectionConfig { }); // Get all groups in the settings - let groups = this.configurationService.inspect(GROUPS_CONFIG_KEY).user; + let groups = this.configurationService.inspect(GROUPS_CONFIG_KEY).userValue; // Remove subgroups in the settings groups = groups!.filter((grp) => { return !subgroups.some((item) => item.id === grp.id); @@ -262,7 +262,7 @@ export class ConnectionConfig { * Moves the source group under the target group. */ public changeGroupIdForConnectionGroup(source: ConnectionProfileGroup, target: ConnectionProfileGroup): Promise { - let groups = this.configurationService.inspect(GROUPS_CONFIG_KEY).user; + let groups = this.configurationService.inspect(GROUPS_CONFIG_KEY).userValue; groups = groups!.map(g => { if (g.id === source.id) { g.parentId = target.id; @@ -286,8 +286,8 @@ export class ConnectionConfig { * Moves the connection under the target group with the new ID. */ private changeGroupIdForConnectionInSettings(profile: ConnectionProfile, newGroupID: string, target: ConfigurationTarget = ConfigurationTarget.USER): Promise { - let profiles = target === ConfigurationTarget.USER ? this.configurationService.inspect(CONNECTIONS_CONFIG_KEY).user : - this.configurationService.inspect(CONNECTIONS_CONFIG_KEY).workspace; + let profiles = target === ConfigurationTarget.USER ? this.configurationService.inspect(CONNECTIONS_CONFIG_KEY).userValue : + this.configurationService.inspect(CONNECTIONS_CONFIG_KEY).workspaceValue; if (profiles) { if (profile.parent && profile.parent.id === UNSAVED_GROUP_ID) { profile.groupId = newGroupID; @@ -328,7 +328,7 @@ export class ConnectionConfig { } public editGroup(source: ConnectionProfileGroup): Promise { - let groups = this.configurationService.inspect(GROUPS_CONFIG_KEY).user; + let groups = this.configurationService.inspect(GROUPS_CONFIG_KEY).userValue; let sameNameGroup = groups ? find(groups, group => group.name === source.name && group.id !== source.id) : undefined; if (sameNameGroup) { let errMessage: string = nls.localize('invalidServerName', "A server group with the same name already exists."); diff --git a/src/sql/platform/connection/test/common/connectionConfig.test.ts b/src/sql/platform/connection/test/common/connectionConfig.test.ts index 284e366592..d8c4e46a2f 100644 --- a/src/sql/platform/connection/test/common/connectionConfig.test.ts +++ b/src/sql/platform/connection/test/common/connectionConfig.test.ts @@ -269,7 +269,7 @@ suite('ConnectionConfig', () => { let savedConnectionProfile = await config.addConnection(connectionProfile); assert.ok(!!savedConnectionProfile.id); - assert.equal(configurationService.inspect('datasource.connections').user.length, testConnections.length + 1); + assert.equal(configurationService.inspect('datasource.connections').userValue.length, testConnections.length + 1); }); test('addConnection should not add the new profile to user settings if already exists', async () => { @@ -303,7 +303,7 @@ suite('ConnectionConfig', () => { let savedConnectionProfile = await config.addConnection(connectionProfile); assert.equal(savedConnectionProfile.id, existingConnection.id); - assert.equal(configurationService.inspect('datasource.connections').user.length, testConnections.length); + assert.equal(configurationService.inspect('datasource.connections').userValue.length, testConnections.length); }); test('addConnection should add the new group to user settings if does not exist', async () => { @@ -333,8 +333,8 @@ suite('ConnectionConfig', () => { let config = new ConnectionConfig(configurationService, capabilitiesService.object); await config.addConnection(connectionProfile); - assert.equal(configurationService.inspect('datasource.connections').user.length, testConnections.length + 1); - assert.equal(configurationService.inspect('datasource.connectionGroups').user.length, testGroups.length + 1); + assert.equal(configurationService.inspect('datasource.connections').userValue.length, testConnections.length + 1); + assert.equal(configurationService.inspect('datasource.connectionGroups').userValue.length, testGroups.length + 1); }); test('getConnections should return connections from user and workspace settings given getWorkspaceConnections set to true', () => { @@ -452,7 +452,7 @@ suite('ConnectionConfig', () => { let config = new ConnectionConfig(configurationService, capabilitiesService.object); await config.deleteConnection(connectionProfile); - assert.equal(configurationService.inspect('datasource.connections').user.length, testConnections.length - 1); + assert.equal(configurationService.inspect('datasource.connections').userValue.length, testConnections.length - 1); }); test('deleteConnectionGroup should remove the children connections and subgroups from config', async () => { @@ -488,8 +488,8 @@ suite('ConnectionConfig', () => { let config = new ConnectionConfig(configurationService, capabilitiesService.object); await config.deleteGroup(connectionProfileGroup); - assert.equal(configurationService.inspect('datasource.connections').user.length, testConnections.length - 1); - assert.equal(configurationService.inspect('datasource.connectionGroups').user.length, testGroups.length - 2); + assert.equal(configurationService.inspect('datasource.connections').userValue.length, testConnections.length - 1); + assert.equal(configurationService.inspect('datasource.connectionGroups').userValue.length, testGroups.length - 2); }); test('deleteConnection should not throw error for connection not in config', async () => { @@ -517,7 +517,7 @@ suite('ConnectionConfig', () => { let config = new ConnectionConfig(configurationService, capabilitiesService.object); await config.deleteConnection(connectionProfile); - assert.equal(configurationService.inspect('datasource.connections').user.length, testConnections.length); + assert.equal(configurationService.inspect('datasource.connections').userValue.length, testConnections.length); }); test('renameGroup should change group name', async () => { @@ -528,7 +528,7 @@ suite('ConnectionConfig', () => { let config = new ConnectionConfig(configurationService, capabilitiesService.object); await config.editGroup(connectionProfileGroup); - let editedGroups = configurationService.inspect('datasource.connectionGroups').user; + let editedGroups = configurationService.inspect('datasource.connectionGroups').userValue; assert.equal(editedGroups.length, testGroups.length); let editedGroup = find(editedGroups, group => group.id === 'g2'); @@ -547,7 +547,7 @@ suite('ConnectionConfig', () => { await config.editGroup(sameNameGroup); assert.fail(); } catch (e) { - let groups = configurationService.inspect('datasource.connectionGroups').user; + let groups = configurationService.inspect('datasource.connectionGroups').userValue; let originalGroup = find(groups, g => g.id === 'g2'); assert.ok(!!originalGroup); assert.equal(originalGroup.name, 'g2'); @@ -563,7 +563,7 @@ suite('ConnectionConfig', () => { let config = new ConnectionConfig(configurationService, capabilitiesService.object); await config.changeGroupIdForConnectionGroup(sourceProfileGroup, targetProfileGroup); - let editedGroups = configurationService.inspect('datasource.connectionGroups').user; + let editedGroups = configurationService.inspect('datasource.connectionGroups').userValue; assert.equal(editedGroups.length, testGroups.length); let editedGroup = find(editedGroups, group => group.id === 'g2'); @@ -620,7 +620,7 @@ suite('ConnectionConfig', () => { await config.changeGroupIdForConnection(connectionProfile, 'test'); assert.fail(); } catch (e) { - let editedConnections = configurationService.inspect('datasource.connections').user; + let editedConnections = configurationService.inspect('datasource.connections').userValue; // two assert.equal(editedConnections.length, _testConnections.length); let editedConnection = find(editedConnections, con => con.id === 'server3-2'); @@ -657,7 +657,7 @@ suite('ConnectionConfig', () => { let config = new ConnectionConfig(configurationService, capabilitiesService.object); await config.changeGroupIdForConnection(connectionProfile, newId); - let editedConnections = configurationService.inspect('datasource.connections').user; + let editedConnections = configurationService.inspect('datasource.connections').userValue; assert.equal(editedConnections.length, testConnections.length); let editedConnection = find(editedConnections, con => con.id === 'server3'); assert.ok(!!editedConnection); @@ -704,7 +704,7 @@ suite('ConnectionConfig', () => { await config.addGroup(newGroup); - let editGroups = configurationService.inspect('datasource.connectionGroups').user; + let editGroups = configurationService.inspect('datasource.connectionGroups').userValue; assert.equal(editGroups.length, testGroups.length + 1); }); @@ -725,7 +725,7 @@ suite('ConnectionConfig', () => { await config.addGroup(existingGroupName); assert.fail(); } catch (e) { - let editGroups = configurationService.inspect('datasource.connectionGroups').user; + let editGroups = configurationService.inspect('datasource.connectionGroups').userValue; assert.equal(editGroups.length, testGroups.length); } diff --git a/src/sql/platform/connection/test/common/testConfigurationService.ts b/src/sql/platform/connection/test/common/testConfigurationService.ts index a37a2f3d10..c3ce93d1fb 100644 --- a/src/sql/platform/connection/test/common/testConfigurationService.ts +++ b/src/sql/platform/connection/test/common/testConfigurationService.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { getConfigurationKeys, IConfigurationOverrides, IConfigurationService, getConfigurationValue, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; +import { getConfigurationKeys, IConfigurationOverrides, IConfigurationService, getConfigurationValue, ConfigurationTarget, IConfigurationValue } from 'vs/platform/configuration/common/configuration'; export class TestConfigurationService implements IConfigurationService { public _serviceBrand: undefined; @@ -42,19 +42,13 @@ export class TestConfigurationService implements IConfigurationService { return { dispose() { } }; } - public inspect(key: string, overrides?: IConfigurationOverrides): { - default: T, - user: T, - workspace?: T, - workspaceFolder?: T - value: T, - } { + public inspect(key: string, overrides?: IConfigurationOverrides): IConfigurationValue { return { value: getConfigurationValue(this.configuration.user, key), default: undefined, - user: getConfigurationValue(this.configuration.user, key), - workspace: getConfigurationValue(this.configuration.workspace, key), + userValue: getConfigurationValue(this.configuration.user, key), + workspaceValue: getConfigurationValue(this.configuration.workspace, key), workspaceFolder: undefined }; } diff --git a/src/sql/platform/query/common/queryRunner.ts b/src/sql/platform/query/common/queryRunner.ts index 4f850cc890..5644e4e49e 100644 --- a/src/sql/platform/query/common/queryRunner.ts +++ b/src/sql/platform/query/common/queryRunner.ts @@ -21,7 +21,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { Emitter, Event } from 'vs/base/common/event'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; +import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { URI } from 'vs/base/common/uri'; import { mssqlProviderName } from 'sql/platform/connection/common/constants'; import { IGridDataProvider, getResultsString } from 'sql/platform/query/common/gridDataProvider'; diff --git a/src/sql/workbench/browser/modal/modal.ts b/src/sql/workbench/browser/modal/modal.ts index 8c56eaa802..7bd2fa4071 100644 --- a/src/sql/workbench/browser/modal/modal.ts +++ b/src/sql/workbench/browser/modal/modal.ts @@ -22,7 +22,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { isUndefinedOrNull } from 'vs/base/common/types'; import { ILogService } from 'vs/platform/log/common/log'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; -import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; +import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; import { find, firstIndex } from 'vs/base/common/arrays'; diff --git a/src/sql/workbench/browser/modal/optionsDialog.ts b/src/sql/workbench/browser/modal/optionsDialog.ts index 6615e1cd70..dd0c0744ef 100644 --- a/src/sql/workbench/browser/modal/optionsDialog.ts +++ b/src/sql/workbench/browser/modal/optionsDialog.ts @@ -31,7 +31,7 @@ import { append, $ } from 'vs/base/browser/dom'; import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService'; import { ILogService } from 'vs/platform/log/common/log'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; -import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; +import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; diff --git a/src/sql/workbench/browser/modelComponents/queryTextEditor.ts b/src/sql/workbench/browser/modelComponents/queryTextEditor.ts index c63304f155..53ff98a927 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 { IResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; +import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; 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, - @IResourceConfigurationService configurationService: IResourceConfigurationService, + @ITextResourceConfigurationService configurationService: ITextResourceConfigurationService, @IThemeService themeService: IThemeService, @IEditorGroupsService editorGroupService: IEditorGroupsService, @IEditorService protected editorService: IEditorService, diff --git a/src/sql/workbench/contrib/accounts/browser/accountDialog.ts b/src/sql/workbench/contrib/accounts/browser/accountDialog.ts index c1be54cb4c..f20b90112d 100644 --- a/src/sql/workbench/contrib/accounts/browser/accountDialog.ts +++ b/src/sql/workbench/contrib/accounts/browser/accountDialog.ts @@ -33,7 +33,7 @@ import { IClipboardService } from 'sql/platform/clipboard/common/clipboardServic import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys'; import { ILogService } from 'vs/platform/log/common/log'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; -import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; +import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; diff --git a/src/sql/workbench/contrib/accounts/browser/autoOAuthDialog.ts b/src/sql/workbench/contrib/accounts/browser/autoOAuthDialog.ts index 00cdde1cd3..cda80e5c72 100644 --- a/src/sql/workbench/contrib/accounts/browser/autoOAuthDialog.ts +++ b/src/sql/workbench/contrib/accounts/browser/autoOAuthDialog.ts @@ -22,7 +22,7 @@ import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { ILogService } from 'vs/platform/log/common/log'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; -import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; +import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; export class AutoOAuthDialog extends Modal { diff --git a/src/sql/workbench/contrib/accounts/browser/firewallRuleDialog.ts b/src/sql/workbench/contrib/accounts/browser/firewallRuleDialog.ts index 8e42be46c3..fa58880328 100644 --- a/src/sql/workbench/contrib/accounts/browser/firewallRuleDialog.ts +++ b/src/sql/workbench/contrib/accounts/browser/firewallRuleDialog.ts @@ -28,7 +28,7 @@ import { IAccountPickerService } from 'sql/workbench/contrib/accounts/browser/ac import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys'; import { ILogService } from 'vs/platform/log/common/log'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; -import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; +import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; diff --git a/src/sql/workbench/contrib/backup/browser/backupDialog.ts b/src/sql/workbench/contrib/backup/browser/backupDialog.ts index 48897def6c..a37097b867 100644 --- a/src/sql/workbench/contrib/backup/browser/backupDialog.ts +++ b/src/sql/workbench/contrib/backup/browser/backupDialog.ts @@ -18,7 +18,7 @@ import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService import { append, $ } from 'vs/base/browser/dom'; import { ILogService } from 'vs/platform/log/common/log'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; -import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; +import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; export class BackupDialog extends Modal { diff --git a/src/sql/workbench/contrib/dataExplorer/browser/dataExplorerViewlet.ts b/src/sql/workbench/contrib/dataExplorer/browser/dataExplorerViewlet.ts index cbafd4b8ab..d32ee7f13b 100644 --- a/src/sql/workbench/contrib/dataExplorer/browser/dataExplorerViewlet.ts +++ b/src/sql/workbench/contrib/dataExplorer/browser/dataExplorerViewlet.ts @@ -17,7 +17,7 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IAddedViewDescriptorRef } from 'vs/workbench/browser/parts/views/views'; import { ConnectionViewletPanel } from 'sql/workbench/contrib/dataExplorer/browser/connectionViewletPanel'; -import { Extensions as ViewContainerExtensions, IViewDescriptor, IViewsRegistry, IViewContainersRegistry } from 'vs/workbench/common/views'; +import { Extensions as ViewContainerExtensions, IViewDescriptor, IViewsRegistry, IViewContainersRegistry, ViewContainerLocation } from 'vs/workbench/common/views'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -46,7 +46,7 @@ export class OpenDataExplorerViewletAction extends ShowViewletAction { } } -export const VIEW_CONTAINER = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(VIEWLET_ID); +export const VIEW_CONTAINER = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(VIEWLET_ID, ViewContainerLocation.Sidebar); export class DataExplorerViewletViewsContribution implements IWorkbenchContribution { diff --git a/src/sql/workbench/contrib/notebook/browser/models/notebookInput.ts b/src/sql/workbench/contrib/notebook/browser/models/notebookInput.ts index c95b086353..9547d19442 100644 --- a/src/sql/workbench/contrib/notebook/browser/models/notebookInput.ts +++ b/src/sql/workbench/contrib/notebook/browser/models/notebookInput.ts @@ -24,7 +24,7 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import { NotebookChangeType } from 'sql/workbench/contrib/notebook/common/models/contracts'; import { Deferred } from 'sql/base/common/promise'; import { NotebookTextFileModel } from 'sql/workbench/contrib/notebook/browser/models/notebookTextFileModel'; -import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; +import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { ResourceEditorModel } from 'vs/workbench/common/editor/resourceEditorModel'; import { UntitledTextEditorModel } from 'vs/workbench/common/editor/untitledTextEditorModel'; import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput'; diff --git a/src/sql/workbench/contrib/notebook/browser/outputs/gridOutput.component.ts b/src/sql/workbench/contrib/notebook/browser/outputs/gridOutput.component.ts index 9cbec76c5a..bd6911856e 100644 --- a/src/sql/workbench/contrib/notebook/browser/outputs/gridOutput.component.ts +++ b/src/sql/workbench/contrib/notebook/browser/outputs/gridOutput.component.ts @@ -13,7 +13,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { SaveFormat } from 'sql/workbench/contrib/grid/common/interfaces'; import { IDataResource } from 'sql/workbench/services/notebook/browser/sql/sqlSessionManager'; -import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; +import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { getEolString, shouldIncludeHeaders, shouldRemoveNewLines } from 'sql/platform/query/common/queryRunner'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; diff --git a/src/sql/workbench/contrib/notebook/common/models/nodebookInputFactory.ts b/src/sql/workbench/contrib/notebook/common/models/nodebookInputFactory.ts index a22f81ac36..638791523b 100644 --- a/src/sql/workbench/contrib/notebook/common/models/nodebookInputFactory.ts +++ b/src/sql/workbench/contrib/notebook/common/models/nodebookInputFactory.ts @@ -28,6 +28,10 @@ export class FileNoteBookEditorInputFactory implements IEditorInputFactory { const fileEditorInput = factory.deserialize(instantiationService, serializedEditorInput) as FileEditorInput; return instantiationService.createInstance(FileNotebookInput, fileEditorInput.getName(), fileEditorInput.getResource(), fileEditorInput); } + + canSerialize(): boolean { // we can always serialize notebooks + return true; + } } export class UntitledNoteBookEditorInputFactory implements IEditorInputFactory { @@ -44,4 +48,8 @@ export class UntitledNoteBookEditorInputFactory implements IEditorInputFactory { const untitledEditorInput = factory.deserialize(instantiationService, serializedEditorInput) as UntitledTextEditorInput; return instantiationService.createInstance(UntitledNotebookInput, untitledEditorInput.getName(), untitledEditorInput.getResource(), untitledEditorInput); } + + canSerialize(): boolean { // we can always serialize notebooks + return true; + } } diff --git a/src/sql/workbench/contrib/notebook/test/electron-browser/cell.test.ts b/src/sql/workbench/contrib/notebook/test/electron-browser/cell.test.ts index 8cf3563531..cd8321aea4 100644 --- a/src/sql/workbench/contrib/notebook/test/electron-browser/cell.test.ts +++ b/src/sql/workbench/contrib/notebook/test/electron-browser/cell.test.ts @@ -23,7 +23,6 @@ import { startsWith } from 'vs/base/common/strings'; import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; import { IModelContentChangedEvent } from 'vs/editor/common/model/textModelEvents'; -import { Promise } from 'es6-promise'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; diff --git a/src/sql/workbench/contrib/objectExplorer/browser/serverGroupDialog.ts b/src/sql/workbench/contrib/objectExplorer/browser/serverGroupDialog.ts index 81093d1ff7..008550f372 100644 --- a/src/sql/workbench/contrib/objectExplorer/browser/serverGroupDialog.ts +++ b/src/sql/workbench/contrib/objectExplorer/browser/serverGroupDialog.ts @@ -27,7 +27,7 @@ import { IClipboardService } from 'sql/platform/clipboard/common/clipboardServic import { ILogService } from 'vs/platform/log/common/log'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { Color } from 'vs/base/common/color'; -import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; +import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; export class ServerGroupDialog extends Modal { diff --git a/src/sql/workbench/contrib/profiler/browser/profilerColumnEditorDialog.ts b/src/sql/workbench/contrib/profiler/browser/profilerColumnEditorDialog.ts index f27914895c..8a4e9c2ddd 100644 --- a/src/sql/workbench/contrib/profiler/browser/profilerColumnEditorDialog.ts +++ b/src/sql/workbench/contrib/profiler/browser/profilerColumnEditorDialog.ts @@ -25,7 +25,7 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { ILogService } from 'vs/platform/log/common/log'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; -import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; +import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; class EventItem { diff --git a/src/sql/workbench/contrib/profiler/browser/profilerCopyHandler.ts b/src/sql/workbench/contrib/profiler/browser/profilerCopyHandler.ts index b2f467aa7b..dbdd3d939f 100644 --- a/src/sql/workbench/contrib/profiler/browser/profilerCopyHandler.ts +++ b/src/sql/workbench/contrib/profiler/browser/profilerCopyHandler.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { IClipboardService } from 'sql/platform/clipboard/common/clipboardService'; -import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; +import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; diff --git a/src/sql/workbench/contrib/profiler/browser/profilerEditor.ts b/src/sql/workbench/contrib/profiler/browser/profilerEditor.ts index 129e563a4d..7cd233b034 100644 --- a/src/sql/workbench/contrib/profiler/browser/profilerEditor.ts +++ b/src/sql/workbench/contrib/profiler/browser/profilerEditor.ts @@ -49,7 +49,7 @@ import { CopyKeybind } from 'sql/base/browser/ui/table/plugins/copyKeybind.plugi import { IClipboardService } from 'sql/platform/clipboard/common/clipboardService'; import { CellSelectionModel } from 'sql/base/browser/ui/table/plugins/cellSelectionModel.plugin'; import { handleCopyRequest } from 'sql/workbench/contrib/profiler/browser/profilerCopyHandler'; -import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; +import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { find } from 'vs/base/common/arrays'; import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput'; diff --git a/src/sql/workbench/contrib/profiler/browser/profilerFilterDialog.ts b/src/sql/workbench/contrib/profiler/browser/profilerFilterDialog.ts index 1cf2e1e957..0c066be56a 100644 --- a/src/sql/workbench/contrib/profiler/browser/profilerFilterDialog.ts +++ b/src/sql/workbench/contrib/profiler/browser/profilerFilterDialog.ts @@ -24,7 +24,7 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { ProfilerFilter, ProfilerFilterClause, ProfilerFilterClauseOperator, IProfilerService } from 'sql/workbench/services/profiler/browser/interfaces'; import { ILogService } from 'vs/platform/log/common/log'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; -import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; +import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { find, firstIndex } from 'vs/base/common/arrays'; import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; import { onUnexpectedError } from 'vs/base/common/errors'; diff --git a/src/sql/workbench/contrib/profiler/browser/profilerResourceEditor.ts b/src/sql/workbench/contrib/profiler/browser/profilerResourceEditor.ts index 790ad81421..8e246a3b6d 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 { IResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; +import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; 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, - @IResourceConfigurationService configurationService: IResourceConfigurationService, + @ITextResourceConfigurationService configurationService: ITextResourceConfigurationService, @IThemeService themeService: IThemeService, @IEditorService protected editorService: IEditorService, @IEditorGroupsService editorGroupService: IEditorGroupsService diff --git a/src/sql/workbench/contrib/profiler/browser/profilerTableEditor.ts b/src/sql/workbench/contrib/profiler/browser/profilerTableEditor.ts index e9a8465cfa..725901b76d 100644 --- a/src/sql/workbench/contrib/profiler/browser/profilerTableEditor.ts +++ b/src/sql/workbench/contrib/profiler/browser/profilerTableEditor.ts @@ -32,7 +32,7 @@ import { localize } from 'vs/nls'; import { CopyKeybind } from 'sql/base/browser/ui/table/plugins/copyKeybind.plugin'; import { IClipboardService } from 'sql/platform/clipboard/common/clipboardService'; import { handleCopyRequest } from 'sql/workbench/contrib/profiler/browser/profilerCopyHandler'; -import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; +import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; export interface ProfilerTableViewState { scrollTop: number; diff --git a/src/sql/workbench/contrib/query/common/queryInputFactory.ts b/src/sql/workbench/contrib/query/common/queryInputFactory.ts index 48fcff22d4..10df6b9827 100644 --- a/src/sql/workbench/contrib/query/common/queryInputFactory.ts +++ b/src/sql/workbench/contrib/query/common/queryInputFactory.ts @@ -30,6 +30,10 @@ export class FileQueryEditorInputFactory implements IEditorInputFactory { const queryResultsInput = instantiationService.createInstance(QueryResultsInput, fileEditorInput.getResource().toString()); return instantiationService.createInstance(FileQueryEditorInput, '', fileEditorInput, queryResultsInput); } + + canSerialize(): boolean { // we can always serialize query inputs + return true; + } } export class UntitledQueryEditorInputFactory implements IEditorInputFactory { @@ -47,4 +51,8 @@ export class UntitledQueryEditorInputFactory implements IEditorInputFactory { const queryResultsInput = instantiationService.createInstance(QueryResultsInput, untitledEditorInput.getResource().toString()); return instantiationService.createInstance(UntitledQueryEditorInput, '', untitledEditorInput, queryResultsInput); } + + canSerialize(): boolean { // we can always serialize query inputs + return true; + } } diff --git a/src/sql/workbench/contrib/query/common/resultsGrid.contribution.ts b/src/sql/workbench/contrib/query/common/resultsGrid.contribution.ts index c0b643fb81..2bebb56d28 100644 --- a/src/sql/workbench/contrib/query/common/resultsGrid.contribution.ts +++ b/src/sql/workbench/contrib/query/common/resultsGrid.contribution.ts @@ -20,7 +20,6 @@ const resultsGridConfiguration: IConfigurationNode = { id: 'resultsGrid', type: 'object', title: nls.localize('resultsGridConfigurationTitle', "Results Grid and Messages"), - overridable: true, properties: { 'resultsGrid.fontFamily': { type: 'string', diff --git a/src/sql/workbench/contrib/restore/browser/restoreDialog.ts b/src/sql/workbench/contrib/restore/browser/restoreDialog.ts index 67d55f18d6..75ce44da97 100644 --- a/src/sql/workbench/contrib/restore/browser/restoreDialog.ts +++ b/src/sql/workbench/contrib/restore/browser/restoreDialog.ts @@ -42,7 +42,7 @@ import { IClipboardService } from 'sql/platform/clipboard/common/clipboardServic import { IFileBrowserDialogController } from 'sql/workbench/services/fileBrowser/common/fileBrowserDialogController'; import { ILogService } from 'vs/platform/log/common/log'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; -import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; +import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; interface FileListElement { diff --git a/src/sql/workbench/contrib/webview/browser/webViewDialog.ts b/src/sql/workbench/contrib/webview/browser/webViewDialog.ts index 1890536964..4fcdd360a7 100644 --- a/src/sql/workbench/contrib/webview/browser/webViewDialog.ts +++ b/src/sql/workbench/contrib/webview/browser/webViewDialog.ts @@ -19,7 +19,7 @@ import * as DOM from 'vs/base/browser/dom'; import { ILogService } from 'vs/platform/log/common/log'; import { IWebviewService, WebviewElement } from 'vs/workbench/contrib/webview/browser/webview'; import { generateUuid } from 'vs/base/common/uuid'; -import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; +import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; export class WebViewDialog extends Modal { diff --git a/src/sql/workbench/services/connection/browser/connectionDialogWidget.ts b/src/sql/workbench/services/connection/browser/connectionDialogWidget.ts index 64c810e5a9..457d0ea91e 100644 --- a/src/sql/workbench/services/connection/browser/connectionDialogWidget.ts +++ b/src/sql/workbench/services/connection/browser/connectionDialogWidget.ts @@ -35,7 +35,7 @@ import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService'; import { ILogService } from 'vs/platform/log/common/log'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; -import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; +import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; import { entries } from 'sql/base/common/collections'; diff --git a/src/sql/workbench/services/dashboard/browser/newDashboardTabDialogImpl.ts b/src/sql/workbench/services/dashboard/browser/newDashboardTabDialogImpl.ts index 0a133a0b41..53f95a9704 100644 --- a/src/sql/workbench/services/dashboard/browser/newDashboardTabDialogImpl.ts +++ b/src/sql/workbench/services/dashboard/browser/newDashboardTabDialogImpl.ts @@ -26,7 +26,7 @@ import { IDashboardTab } from 'sql/workbench/contrib/dashboard/browser/dashboard import { IClipboardService } from 'sql/platform/clipboard/common/clipboardService'; import { ILogService } from 'vs/platform/log/common/log'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; -import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; +import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; class ExtensionListDelegate implements IListVirtualDelegate { diff --git a/src/sql/workbench/services/dialog/browser/dialogModal.ts b/src/sql/workbench/services/dialog/browser/dialogModal.ts index 5dbe23ac93..515f6e1f5c 100644 --- a/src/sql/workbench/services/dialog/browser/dialogModal.ts +++ b/src/sql/workbench/services/dialog/browser/dialogModal.ts @@ -22,7 +22,7 @@ import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { append, $ } from 'vs/base/browser/dom'; import { ILogService } from 'vs/platform/log/common/log'; -import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; +import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; import { onUnexpectedError } from 'vs/base/common/errors'; diff --git a/src/sql/workbench/services/dialog/browser/wizardModal.ts b/src/sql/workbench/services/dialog/browser/wizardModal.ts index c0f217c389..0dfb090447 100644 --- a/src/sql/workbench/services/dialog/browser/wizardModal.ts +++ b/src/sql/workbench/services/dialog/browser/wizardModal.ts @@ -23,7 +23,7 @@ import { append, $ } from 'vs/base/browser/dom'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ILogService } from 'vs/platform/log/common/log'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; -import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; +import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; import { onUnexpectedError } from 'vs/base/common/errors'; diff --git a/src/sql/workbench/services/errorMessage/browser/errorMessageDialog.ts b/src/sql/workbench/services/errorMessage/browser/errorMessageDialog.ts index 223b46fd7b..6a4ed103d2 100644 --- a/src/sql/workbench/services/errorMessage/browser/errorMessageDialog.ts +++ b/src/sql/workbench/services/errorMessage/browser/errorMessageDialog.ts @@ -21,7 +21,7 @@ import { IAction } from 'vs/base/common/actions'; import * as DOM from 'vs/base/browser/dom'; import { ILogService } from 'vs/platform/log/common/log'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; -import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; +import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; import { onUnexpectedError } from 'vs/base/common/errors'; diff --git a/src/sql/workbench/services/fileBrowser/browser/fileBrowserDialog.ts b/src/sql/workbench/services/fileBrowser/browser/fileBrowserDialog.ts index c0f7d99bc1..89de1700b6 100644 --- a/src/sql/workbench/services/fileBrowser/browser/fileBrowserDialog.ts +++ b/src/sql/workbench/services/fileBrowser/browser/fileBrowserDialog.ts @@ -30,7 +30,7 @@ import { IClipboardService } from 'sql/platform/clipboard/common/clipboardServic import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ILogService } from 'vs/platform/log/common/log'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; -import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; +import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; import { onUnexpectedError } from 'vs/base/common/errors'; diff --git a/src/sql/workbench/services/insights/browser/insightsDialogView.ts b/src/sql/workbench/services/insights/browser/insightsDialogView.ts index 3b2aab8478..0fe73bd50d 100644 --- a/src/sql/workbench/services/insights/browser/insightsDialogView.ts +++ b/src/sql/workbench/services/insights/browser/insightsDialogView.ts @@ -39,7 +39,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IInsightsConfigDetails } from 'sql/platform/dashboard/browser/insightRegistry'; import { TaskRegistry } from 'sql/platform/tasks/browser/tasksRegistry'; -import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; +import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; import { onUnexpectedError } from 'vs/base/common/errors'; import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; diff --git a/src/sql/workbench/services/notebook/browser/sql/sqlSessionManager.ts b/src/sql/workbench/services/notebook/browser/sql/sqlSessionManager.ts index 7ef4b2e907..e135f29f23 100644 --- a/src/sql/workbench/services/notebook/browser/sql/sqlSessionManager.ts +++ b/src/sql/workbench/services/notebook/browser/sql/sqlSessionManager.ts @@ -22,7 +22,7 @@ import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilit import { ILogService } from 'vs/platform/log/common/log'; import { isUndefinedOrNull } from 'vs/base/common/types'; import { ILanguageMagic } from 'sql/workbench/services/notebook/browser/notebookService'; -import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; +import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { URI } from 'vs/base/common/uri'; import { getUriPrefix, uriPrefixes } from 'sql/platform/connection/common/utils'; import { firstIndex } from 'vs/base/common/arrays'; diff --git a/src/tsconfig.json b/src/tsconfig.json index 788c5f3e03..e574faad00 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -25,6 +25,7 @@ "./sql" ], "exclude": [ + "./typings/es6-promise.d.ts", "./typings/require-monaco.d.ts", "./typings/xterm.d.ts", "./typings/xterm-addon-search.d.ts", diff --git a/src/typings/lib.ie11_safe_es6.d.ts b/src/typings/lib.ie11_safe_es6.d.ts index 9b86ad1383..f29d340dc0 100644 --- a/src/typings/lib.ie11_safe_es6.d.ts +++ b/src/typings/lib.ie11_safe_es6.d.ts @@ -25,7 +25,7 @@ interface Map { interface MapConstructor { new (): Map; - prototype: Map; + readonly prototype: Map; // not supported on IE11: // new (iterable: Iterable<[K, V]>): Map; @@ -51,7 +51,7 @@ interface Set { interface SetConstructor { new (): Set; - prototype: Set; + readonly prototype: Set; // not supported on IE11: // new (iterable: Iterable): Set; diff --git a/src/typings/windows-mutex.d.ts b/src/typings/windows-mutex.d.ts deleted file mode 100644 index 86a43c3ce6..0000000000 --- a/src/typings/windows-mutex.d.ts +++ /dev/null @@ -1,14 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -declare module 'windows-mutex' { - export class Mutex { - constructor(name: string); - isActive(): boolean; - release(): void; - } - - export function isActive(name: string): boolean; -} diff --git a/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css b/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css index 5972f7ec7f..914fdb6560 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?7ed98366b9684aff02478216decd2fe9") format("truetype"); + src: url("./codicon.ttf?072cd8445a025297c265f9d008123381") format("truetype"); } .codicon[class*='codicon-'] { @@ -408,4 +408,5 @@ .codicon-call-incoming:before { content: "\eb92" } .codicon-call-outgoing:before { content: "\eb93" } .codicon-menu:before { content: "\eb94" } +.codicon-expand-all:before { content: "\eb95" } .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 0d601781f23b55003c424f86adac2a83dfcfd584..fd2bf2e192d44315ac693b657049b5c0bd16a26e 100644 GIT binary patch delta 3790 zcmXxn3w+Jz83*v+dqhqoMC5wpE_b=fO@c%cL0qanhMBe2ChnIYb-$k=%+v^nZc}Tu z+fY-rBs%L%4clltQ?<^_)G|}vXVs!s)TO__=d+y8_y3&#xqY1TKJV}OyL{Jm<({i4 z&wFsirDni?ClFdNrEc-$%h%)112ILwrV~>q)lLjMw);AO%Tzp-51`Gw%KL{YW$Mg% ziy9LjKL*Sd!0&L~>_=A>?K^jit+z02nV z>u1mHH1GWJ#~%T2`T?Ck*?c&;q6Z9LOIwBv{`mRMWp@ySdOm^+T@U*Izdq(4Uf>M~ zfXjK5e|(SW(GqWp{91;$uQi2jPd*8*i)i=cUN&nW(5?-Bt^<612yg8GyUuQ~r)0P^ z$u?h`f8mVre9ndKIFG-g0jEr7e%sHnAGc9p(s334g1f=>U=1Fa4!#EW%ic+tjH#H0>6n2! z%)~6r<~N&zxtNFfSb&9Cge6#tWqi$Ytbi9Qu?me?i|=C{et;LT0YAh>G+{Hgz=y5a zhL`Xo{1`vM%h-We@j8BnpW_$UjXijS>+wsxiMMb7zd|z(dhs@XjYD_`zs2ux1n=S~ zevjigfj{7n_!CazJ-m+(@MoOHUvLH=;v<~H$M_rmjtlq)F5;iKgiqKa|3Vx7jVt&M zKE*X$hs9^OfiG|qx9}zI;4bdrKEB2yJT@>c<7fO$fax4)LQEGEYQjvoi7=7A3;W_q zGE8@qX|hbV$uYV3DPF-&yoPdAU;TwF3a3~nD|Q9!6y@v&J5{k$ zV5fyU2Y%>&*69kjeIY?nL(+LRQ?Zp`XDK!n>}L#AeS8PPs1qxZL3zZ4>?F&lwrtxg4a<-FQrr5Bs%N1J}c7#T7`$cnvPN4PCRqoO57o^FDiC0>;}bM1M7TI z!dI+K%Cu+QtPI!NZc)a~>Qmfjuv-;(9IW%Xga@oIG54H<>Cp0^M0_+{dcz|_&IAT!1IzJFGGGH094u%KpJ;fM-y{{N3uwN^FF)yt1 zs}NHL_Mu|lz&=uZ2`tmz!3=Wx^a-XBguk?dxdd@3CKQCL=U`Sr{1j6Q!qsyyzaah& zemJ8H!sT-?)F1(h@doLj7<7<8#mIvMDTW`Uqhbs~*jf$-A|zNb8X=vP(_#{$7?%)l z7aoYo2?Q8CaV-4vr8lB5{&kYvTUhomS5KO|M*AZr>Imj`edK++Y* z0whCmKtQ@HjtWSo;?RI(DUJ_Fw&EaxrlnH3wcU$0z-x=&SJ>Zic=XfTyZ`_Mkr2d$hV&0?|+SVMkUQJ2!K4JcsT%hR`J3BGR|55as2l8Sih%uwE%fe z@wx%>yy6uEq(r`dBuufAZ zly$l?PA<$)CY-fSnF!XI%0#lxQqDE7%vPo=YrQhjtS>n0&x48K$sA>zESRfI9P2z~ z;#ub_lfb$_nMBrw%5-C0q)Za)Vr85xSfWe{>r!P>o&CRzCuA~Mmn+kqb%ipStj;o# zarS(JGR~e~sZ0**D#a@|$ZExFI7p-7)f{Av!cNw;4z3Yi^Lad@Opve6Gbm;sAKa#R zd54=^T!(X^=R4|o-@85ZY*=jAh_Jn3ZQ;e?+r!%;(jq2Dych8xGBdI|vN`f}q>b{A z8XdJR>TOSkr^$1%YiQT?(SFe@q7TOmh&d8l9J?TPUF^x&8*xM98snPdZp9CeUlD&I zAs}H)!p4L{36B!DCZ6b)(QRY5%Sq8mWl1}dT9eKs_ema^JTG}yik~;7DdkFPcxs>2 zXHu7>?n!-=7M@m~wj`}N?N)k9`k3^_^w#wI8A%!IGtPG3)%|>CaptzdFxBA8RE9%$S?_AZ`sx4KoS2b51t2*01vwwa6?ftI~s2R{a;EHEp z;=pkOcMLo{@M3jzb#?Ww>SKc@585-RttBz$!<4x8Tq$5YZ|&el1Y(g0lq5$Kx_c%^ z_(tT!bPsmB-T7{JupVOF!TGWI`T6ePHWs4+ekt+lBd+))uXM2yQIt~FLsjJ2t? z*0$Oh7b-Tjt!+u#CN{A)vBp}PSZb`2dyLV<7&Ip4`#n$bdH*vr3_Qbs&;8Ez`U6*c zr>kjz=VEO~IpDJo=$$aPdgYwtF_+&3`o;h|DywEspB2;=-VZ=K4X)w~xMueA@fk{} zT3B1ZzUA>_z+4BsPggIRIo-bhh7aIw16Gv7dW~N=y74^nfSsC7`~UbEyz6lY>)iU;E%<81y|fPN&l~p`GqC? z2l&F}yvi^BjLB$?GCAI@le$bbk?l`@6I@r|_2f}Hs~_Oi4sX}-=C^%9%z&~*j9-KAZ@jKkcF?@n7lZ5|4U@->?cooyI9Y05!3B&{hnnWx@CEmeloJ6Y$ z#dlGUM&#fTW|#nchTX=6`^MY&8y|kO4fw+N@^g5>8(rXouKeWP_<8)B_jcXvnT6Sy ziz>{+d@MjU7NUlK^%5+_GSp%@>aYSU@f=q1J*%+>YtevpSdWd^gw5E3t#}^W@ICB6 z6Lw-3nz0)%U=O~Jy?7Bn#D4q)2k}$<42N(Szragem7_S06KKINams^V;T8NEzrk<0 zw7q1{0-;vcf5@Y_y^v_C0xe8a0UOydw3sL@gFwI ze{l^T;5u&LCOYsTZs8+*j61lCPjL^QqZ1GC5MSaeJT}I7nJ%WQ>Cw&fFg;B#)7u1@ zU=z|j{#fLoWRqh0nN*Wz(oKKt!;kP|96$jIF&bm=6pD=>M&W6cAq;N3j9jk2PwTpF zF5^Lf(-7TDoGV|=1Z<^py2VacTyxkNimMMhQ?U}-XztaB8*1a_`szra>0 zb`I>+dAtyt2zI_=JHakc$YQNlY%$n{ip>UFqu6$^ixe9WmYcwVInC4j!-l`gXUi1Z z61G;cL1C9Gwkm9$LK^D|WdfT|`p0_``D~S9Bg3v%Y-!jvip>qXR^aXY}SSKJt|8x*$)>_(t@Vn8p?BR<}&;4H)z#q9&TRdFN1KCjr+uuKXE zHy7*mFr%TU~*Ny|^`DZz^t5 z*bc?*3j3krri69Yh`5DeowXxwX4qSb+Zy&G#SISYGzxL6!+xx|>0$3EC-m%Hz~fw; zq_a)~5ijup9yoG{9-xCm2u=-f9P<3c?k0Fti|Eitz>Eia8i$kS>am z2H}c{kJD~!1?j4oa}Zy}#DjEG%sz;pVhTe174r}hpqPx1K*fxNbXQDENDt+-nDkUk zP>81&FT^l~^j3^jNRVQ{LV^{e780Tux{y%C_=SWi1~J5~7|D=6is1|iSBz;$U*%z9 zLn0Kj8xpCQ;*coCJcmRpXWmMTV#Y&a71JIPryyEbNeB%k;|C@YJp*YbY6BK7V zWTN7fhkR3U-a{rSPJYO@6wd&V$%>}|$hQ^G1(0VHPY96jD4rD{Q=Ijm!oR+gb*kb4 z0y0hUC;@p^@lXM&R6JflrYk&VouQ0jovDl$>nvrQ;F+yVSJpYoc)IbyTxEK&Rw>hy zb)GW4Sm!I_WW)kxf>^7S31(fWObBa@GGVNXlygj$#me+yUE-`iFD9H%mMYVib(t~| zthLHSvMyI9inUIeXx0_V#IV*Y6U(|%87CK>Qzo8ul`;v={$I@}WRh9eD3ijvR+)aR z&N7j4_J4yi&i-GgOgd|$;$aT5Uh!B5*`Rp9gKShh>OnR+ya60&j&e^he$7kW<$*5) zce#cRyq9kHc#GZ&Vk?zQf$cD(asEDYsQM;lpMJGg0 ziQW`_E@no|u9*9=Q(}+DcEtI`&5vu0+aK>69}{03UmJfRVVWo5WWu#X-^7%}QHgbl z=Mt|a1tg71YDj8J`Xo6axgxnS`E2r?l*uUv`qlP3liRFIZb}sGz-YT;bNj z^F@9|6-9MLZAG_6xJFcrI9QxiTv@!hxS_bI_&{;z$bykoBTtoNlr)r_EV=D2O)Z^Y zy0!F3X-8RbS#4QUc}{s .codicon { +.monaco-dialog-box .dialog-message-row > .codicon { flex: 0 0 48px; height: 48px; align-self: baseline; @@ -63,7 +63,7 @@ } /** Dialog: Message Container */ -.monaco-workbench .dialog-box .dialog-message-row .dialog-message-container { +.monaco-dialog-box .dialog-message-row .dialog-message-container { display: flex; flex-direction: column; overflow: hidden; @@ -77,7 +77,7 @@ } /** Dialog: Message */ -.monaco-workbench .dialog-box .dialog-message-row .dialog-message-container .dialog-message { +.monaco-dialog-box .dialog-message-row .dialog-message-container .dialog-message { line-height: 22px; font-size: 18px; flex: 1; /* let the message always grow */ @@ -90,23 +90,23 @@ } /** Dialog: Details */ -.monaco-workbench .dialog-box .dialog-message-row .dialog-message-container .dialog-message-detail { +.monaco-dialog-box .dialog-message-row .dialog-message-container .dialog-message-detail { line-height: 22px; flex: 1; /* let the message always grow */ opacity: .9; } -.monaco-workbench .dialog-box .dialog-message-row .dialog-message-container .dialog-message a:focus { +.monaco-dialog-box .dialog-message-row .dialog-message-container .dialog-message a:focus { outline-width: 1px; outline-style: solid; } -.monaco-workbench .dialog-box .dialog-message-row .dialog-message-container .dialog-checkbox-row { +.monaco-dialog-box .dialog-message-row .dialog-message-container .dialog-checkbox-row { padding: 15px 0px 0px; display: flex; } -.monaco-workbench .dialog-box .dialog-message-row .dialog-message-container .dialog-checkbox-row .dialog-checkbox-message { +.monaco-dialog-box .dialog-message-row .dialog-message-container .dialog-checkbox-row .dialog-checkbox-message { cursor: pointer; user-select: none; -webkit-user-select: none; @@ -114,7 +114,7 @@ } /** Dialog: Buttons Row */ -.monaco-workbench .dialog-box > .dialog-buttons-row { +.monaco-dialog-box > .dialog-buttons-row { display: flex; align-items: center; justify-content: flex-end; @@ -122,19 +122,19 @@ overflow: hidden; /* buttons row should never overflow */ } -.monaco-workbench .dialog-box > .dialog-buttons-row { +.monaco-dialog-box > .dialog-buttons-row { display: flex; white-space: nowrap; padding: 20px 10px 10px; } /** Dialog: Buttons */ -.monaco-workbench .dialog-box > .dialog-buttons-row > .dialog-buttons { +.monaco-dialog-box > .dialog-buttons-row > .dialog-buttons { display: flex; overflow: hidden; } -.monaco-workbench .dialog-box > .dialog-buttons-row > .dialog-buttons > .monaco-button { +.monaco-dialog-box > .dialog-buttons-row > .dialog-buttons > .monaco-button { width: fit-content; width: -moz-fit-content; padding: 5px 10px; diff --git a/src/vs/base/browser/ui/dialog/dialog.ts b/src/vs/base/browser/ui/dialog/dialog.ts index f3ad63520f..30839e9ed6 100644 --- a/src/vs/base/browser/ui/dialog/dialog.ts +++ b/src/vs/base/browser/ui/dialog/dialog.ts @@ -61,9 +61,9 @@ export class Dialog extends Disposable { constructor(private container: HTMLElement, private message: string, buttons: string[], private options: IDialogOptions) { super(); - this.modal = this.container.appendChild($(`.dialog-modal-block${options.type === 'pending' ? '.dimmed' : ''}`)); + this.modal = this.container.appendChild($(`.monaco-dialog-modal-block${options.type === 'pending' ? '.dimmed' : ''}`)); this.shadowElement = this.modal.appendChild($('.dialog-shadow')); - this.element = this.shadowElement.appendChild($('.dialog-box')); + this.element = this.shadowElement.appendChild($('.monaco-dialog-box')); hide(this.element); // If no button is provided, default to OK diff --git a/src/vs/base/browser/ui/list/list.ts b/src/vs/base/browser/ui/list/list.ts index cb0918724b..4dd4ef8c52 100644 --- a/src/vs/base/browser/ui/list/list.ts +++ b/src/vs/base/browser/ui/list/list.ts @@ -129,6 +129,8 @@ export abstract class CachedListVirtualDelegate implements ILi abstract getTemplateId(element: T): string; setDynamicHeight(element: T, height: number): void { - this.cache.set(element, height); + if (height > 0) { + this.cache.set(element, height); + } } } diff --git a/src/vs/base/browser/ui/menu/menu.css b/src/vs/base/browser/ui/menu/menu.css index 867545aafe..d06de59527 100644 --- a/src/vs/base/browser/ui/menu/menu.css +++ b/src/vs/base/browser/ui/menu/menu.css @@ -54,7 +54,7 @@ } .monaco-menu .monaco-action-bar.vertical .submenu-indicator.codicon { - font-size: 16px; + font-size: 16px !important; display: flex; align-items: center; } diff --git a/src/vs/base/browser/ui/menu/menubar.ts b/src/vs/base/browser/ui/menu/menubar.ts index 1c0a11c6fd..0331a871a7 100644 --- a/src/vs/base/browser/ui/menu/menubar.ts +++ b/src/vs/base/browser/ui/menu/menubar.ts @@ -308,8 +308,9 @@ 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 label = this.options.compactMode !== undefined ? nls.localize('mAppMenu', 'Application Menu') : nls.localize('mMore', 'More'); + const title = this.options.compactMode !== undefined ? label : undefined; + const buttonElement = $('div.menubar-menu-button', { 'role': 'menuitem', 'tabindex': -1, 'aria-label': label, 'title': title, 'aria-haspopup': true }); const titleElement = $('div.menubar-menu-title.toolbar-toggle-more.codicon.codicon-more', { 'role': 'none', 'aria-hidden': true }); buttonElement.appendChild(titleElement); @@ -910,7 +911,7 @@ export class MenuBar extends Disposable { return; } - const menuHolder = $('div.menubar-menu-items-holder'); + const menuHolder = $('div.menubar-menu-items-holder', { 'title': '' }); DOM.addClass(customMenu.buttonElement, 'open'); diff --git a/src/vs/base/common/async.ts b/src/vs/base/common/async.ts index 3c528032ac..17e8bc8ecc 100644 --- a/src/vs/base/common/async.ts +++ b/src/vs/base/common/async.ts @@ -34,7 +34,7 @@ export function createCancelablePromise(callback: (token: CancellationToken) }); }); - return new class implements CancelablePromise { + return >new class { cancel() { source.cancel(); } diff --git a/src/vs/base/common/htmlContent.ts b/src/vs/base/common/htmlContent.ts index a1727ebe3b..b479cd4fb3 100644 --- a/src/vs/base/common/htmlContent.ts +++ b/src/vs/base/common/htmlContent.ts @@ -5,7 +5,7 @@ import { equals } from 'vs/base/common/arrays'; import { UriComponents } from 'vs/base/common/uri'; -import { escapeCodicons, markdownUnescapeCodicons } from 'vs/base/common/codicons'; +import { escapeCodicons } from 'vs/base/common/codicons'; export interface IMarkdownString { readonly value: string; @@ -39,10 +39,9 @@ export class MarkdownString implements IMarkdownString { appendText(value: string): MarkdownString { // escape markdown syntax tokens: http://daringfireball.net/projects/markdown/syntax#backslash - value = value + this._value += (this._supportThemeIcons ? escapeCodicons(value) : value) .replace(/[\\`*_{}[\]()#+\-.!]/g, '\\$&') .replace('\n', '\n\n'); - this._value += this.supportThemeIcons ? markdownUnescapeCodicons(value) : value; return this; } @@ -61,10 +60,6 @@ export class MarkdownString implements IMarkdownString { this._value += '\n```\n'; return this; } - - static escapeThemeIcons(value: string): string { - return escapeCodicons(value); - } } export function isEmptyMarkdownString(oneOrMany: IMarkdownString | IMarkdownString[] | null | undefined): boolean { diff --git a/src/vs/base/common/map.ts b/src/vs/base/common/map.ts index 252e65c0b9..6290c3441b 100644 --- a/src/vs/base/common/map.ts +++ b/src/vs/base/common/map.ts @@ -526,6 +526,14 @@ export class LinkedMap { return this._size; } + get first(): V | undefined { + return this._head?.value; + } + + get last(): V | undefined { + return this._tail?.value; + } + has(key: K): boolean { return this._map.has(key); } diff --git a/src/vs/base/node/decoder.ts b/src/vs/base/node/decoder.ts index 4c910522f7..3711b739d1 100644 --- a/src/vs/base/node/decoder.ts +++ b/src/vs/base/node/decoder.ts @@ -15,7 +15,7 @@ import { CharCode } from 'vs/base/common/charCode'; * - forEach() over the result to get the lines */ export class LineDecoder { - private stringDecoder: sd.NodeStringDecoder; + private stringDecoder: sd.StringDecoder; private remaining: string | null; constructor(encoding: string = 'utf8') { diff --git a/src/vs/base/test/browser/markdownRenderer.test.ts b/src/vs/base/test/browser/markdownRenderer.test.ts index 7f42f3c185..c075ae1727 100644 --- a/src/vs/base/test/browser/markdownRenderer.test.ts +++ b/src/vs/base/test/browser/markdownRenderer.test.ts @@ -54,34 +54,26 @@ suite('MarkdownRenderer', () => { test('render appendText', () => { const mds = new MarkdownString(undefined, { supportThemeIcons: true }); - mds.appendText('$(zap) $(dont match me)'); + mds.appendText('$(zap) $(not a theme icon) $(add)'); let result: HTMLElement = renderMarkdown(mds); - assert.strictEqual(result.innerHTML, `

$(dont match me)

`); - }); - - test('render appendText escaped', () => { - const mds = new MarkdownString(undefined, { supportThemeIcons: true }); - mds.appendText(MarkdownString.escapeThemeIcons('$(zap) $(dont match me)')); - - let result: HTMLElement = renderMarkdown(mds); - assert.strictEqual(result.innerHTML, `

$(zap) $(dont match me)

`); + assert.strictEqual(result.innerHTML, `

$(zap) $(not a theme icon) $(add)

`); }); test('render appendMarkdown', () => { const mds = new MarkdownString(undefined, { supportThemeIcons: true }); - mds.appendMarkdown('$(zap) $(dont match me)'); + mds.appendMarkdown('$(zap) $(not a theme icon) $(add)'); let result: HTMLElement = renderMarkdown(mds); - assert.strictEqual(result.innerHTML, `

$(dont match me)

`); + assert.strictEqual(result.innerHTML, `

$(not a theme icon)

`); }); - test('render appendMarkdown escaped', () => { + test('render appendMarkdown with escaped icon', () => { const mds = new MarkdownString(undefined, { supportThemeIcons: true }); - mds.appendMarkdown(MarkdownString.escapeThemeIcons('$(zap) $(dont match me)')); + mds.appendMarkdown('\\$(zap) $(not a theme icon) $(add)'); let result: HTMLElement = renderMarkdown(mds); - assert.strictEqual(result.innerHTML, `

$(zap) $(dont match me)

`); + assert.strictEqual(result.innerHTML, `

$(zap) $(not a theme icon)

`); }); }); @@ -90,18 +82,18 @@ suite('MarkdownRenderer', () => { test('render appendText', () => { const mds = new MarkdownString(undefined, { supportThemeIcons: false }); - mds.appendText('$(zap) $(dont match me)'); + mds.appendText('$(zap) $(not a theme icon) $(add)'); let result: HTMLElement = renderMarkdown(mds); - assert.strictEqual(result.innerHTML, `

$(zap) $(dont match me)

`); + assert.strictEqual(result.innerHTML, `

$(zap) $(not a theme icon) $(add)

`); }); - test('render appendMarkdown', () => { + test('render appendMarkdown with escaped icon', () => { const mds = new MarkdownString(undefined, { supportThemeIcons: false }); - mds.appendMarkdown('$(zap) $(dont match me)'); + mds.appendMarkdown('\\$(zap) $(not a theme icon) $(add)'); let result: HTMLElement = renderMarkdown(mds); - assert.strictEqual(result.innerHTML, `

$(zap) $(dont match me)

`); + assert.strictEqual(result.innerHTML, `

$(zap) $(not a theme icon) $(add)

`); }); }); diff --git a/src/vs/base/test/common/map.test.ts b/src/vs/base/test/common/map.test.ts index e83961f153..7d3fff89b5 100644 --- a/src/vs/base/test/common/map.test.ts +++ b/src/vs/base/test/common/map.test.ts @@ -16,6 +16,8 @@ suite('Map', () => { map.set('bk', 'bv'); assert.deepStrictEqual(map.keys(), ['ak', 'bk']); assert.deepStrictEqual(map.values(), ['av', 'bv']); + assert.equal(map.first, 'av'); + assert.equal(map.last, 'bv'); }); test('LinkedMap - Touch Old one', () => { diff --git a/src/vs/base/test/common/markdownString.test.ts b/src/vs/base/test/common/markdownString.test.ts index a544b7043e..d0fff65452 100644 --- a/src/vs/base/test/common/markdownString.test.ts +++ b/src/vs/base/test/common/markdownString.test.ts @@ -8,10 +8,9 @@ import { MarkdownString } from 'vs/base/common/htmlContent'; suite('MarkdownString', () => { - test('escape', () => { + test('appendText', () => { const mds = new MarkdownString(); - mds.appendText('# foo\n*bar*'); assert.equal(mds.value, '\\# foo\n\n\\*bar\\*'); @@ -19,59 +18,54 @@ suite('MarkdownString', () => { suite('ThemeIcons', () => { - test('escapeThemeIcons', () => { - assert.equal( - MarkdownString.escapeThemeIcons('$(zap) $(not an icon) foo$(bar)'), - '\\$(zap) $(not an icon) foo\\$(bar)' - ); - }); - suite('Support On', () => { test('appendText', () => { const mds = new MarkdownString(undefined, { supportThemeIcons: true }); - mds.appendText('$(zap)'); + mds.appendText('$(zap) $(not a theme icon) $(add)'); - assert.equal(mds.value, '$(zap)'); - }); - - test('appendText escaped', () => { - const mds = new MarkdownString(undefined, { supportThemeIcons: true }); - mds.appendText(MarkdownString.escapeThemeIcons('$(zap)')); - - assert.equal(mds.value, '\\\\$\\(zap\\)'); + assert.equal(mds.value, '\\\\$\\(zap\\) $\\(not a theme icon\\) \\\\$\\(add\\)'); }); test('appendMarkdown', () => { const mds = new MarkdownString(undefined, { supportThemeIcons: true }); - mds.appendMarkdown('$(zap)'); + mds.appendMarkdown('$(zap) $(not a theme icon) $(add)'); - assert.equal(mds.value, '$(zap)'); + assert.equal(mds.value, '$(zap) $(not a theme icon) $(add)'); }); - test('appendMarkdown escaped', () => { + test('appendMarkdown with escaped icon', () => { const mds = new MarkdownString(undefined, { supportThemeIcons: true }); - mds.appendMarkdown(MarkdownString.escapeThemeIcons('$(zap)')); + mds.appendMarkdown('\\$(zap) $(not a theme icon) $(add)'); - assert.equal(mds.value, '\\$(zap)'); + assert.equal(mds.value, '\\$(zap) $(not a theme icon) $(add)'); }); + }); suite('Support Off', () => { test('appendText', () => { const mds = new MarkdownString(undefined, { supportThemeIcons: false }); - mds.appendText('$(zap)'); + mds.appendText('$(zap) $(not a theme icon) $(add)'); - assert.equal(mds.value, '$\\(zap\\)'); + assert.equal(mds.value, '$\\(zap\\) $\\(not a theme icon\\) $\\(add\\)'); }); test('appendMarkdown', () => { const mds = new MarkdownString(undefined, { supportThemeIcons: false }); - mds.appendMarkdown('$(zap)'); + mds.appendMarkdown('$(zap) $(not a theme icon) $(add)'); - assert.equal(mds.value, '$(zap)'); + assert.equal(mds.value, '$(zap) $(not a theme icon) $(add)'); }); + + test('appendMarkdown with escaped icon', () => { + const mds = new MarkdownString(undefined, { supportThemeIcons: true }); + mds.appendMarkdown('\\$(zap) $(not a theme icon) $(add)'); + + assert.equal(mds.value, '\\$(zap) $(not a theme icon) $(add)'); + }); + }); }); diff --git a/src/vs/base/test/node/pfs/pfs.test.ts b/src/vs/base/test/node/pfs/pfs.test.ts index 6961360841..7b62153765 100644 --- a/src/vs/base/test/node/pfs/pfs.test.ts +++ b/src/vs/base/test/node/pfs/pfs.test.ts @@ -15,7 +15,6 @@ import { getPathFromAmdModule } from 'vs/base/common/amd'; import { isWindows, isLinux } from 'vs/base/common/platform'; import { canNormalize } from 'vs/base/common/normalization'; import { VSBuffer } from 'vs/base/common/buffer'; -import { join } from 'path'; const chunkSize = 64 * 1024; const readError = 'Error while reading'; @@ -379,12 +378,12 @@ suite('PFS', function () { if (canNormalize && typeof process.versions['electron'] !== 'undefined' /* needs electron */) { const id = uuid.generateUuid(); const parentDir = path.join(os.tmpdir(), 'vsctests', id); - const testDir = join(parentDir, 'pfs', id); + const testDir = path.join(parentDir, 'pfs', id); const newDir = path.join(testDir, 'öäü'); await pfs.mkdirp(newDir, 493); - await pfs.writeFile(join(testDir, 'somefile.txt'), 'contents'); + await pfs.writeFile(path.join(testDir, 'somefile.txt'), 'contents'); assert.ok(fs.existsSync(newDir)); diff --git a/src/vs/code/electron-main/window.ts b/src/vs/code/electron-main/window.ts index 9383753557..b8929ca792 100644 --- a/src/vs/code/electron-main/window.ts +++ b/src/vs/code/electron-main/window.ts @@ -130,11 +130,6 @@ export class CodeWindow extends Disposable implements ICodeWindow { show: !isFullscreenOrMaximized, title: product.nameLong, webPreferences: { - // By default if Code is in the background, intervals and timeouts get throttled, so we - // want to enforce that Code stays in the foreground. This triggers a disable_hidden_ - // flag that Electron provides via patch: - // https://github.com/electron/libchromiumcontent/blob/master/patches/common/chromium/disable_hidden.patch - backgroundThrottling: false, nodeIntegration: true, nodeIntegrationInWorker: RUN_TEXTMATE_IN_WORKER, webviewTag: true @@ -778,6 +773,8 @@ export class CodeWindow extends Disposable implements ICodeWindow { } private validateWindowState(state: IWindowState, displays: Display[]): IWindowState | undefined { + this.logService.trace(`window#validateWindowState: validating window state on ${displays.length} display(s)`, state); + if (typeof state.x !== 'number' || typeof state.y !== 'number' || typeof state.width !== 'number' @@ -793,34 +790,62 @@ export class CodeWindow extends Disposable implements ICodeWindow { } // Single Monitor: be strict about x/y positioning + // macOS & Linux: these OS seem to be pretty good in ensuring that a window is never outside of it's bounds. + // Windows: it is possible to have a window with a size that makes it fall out of the window. our strategy + // is to try as much as possible to keep the window in the monitor bounds. we are not as strict as + // macOS and Linux and allow the window to exceed the monitor bounds as long as the window is still + // some pixels (128) visible on the screen for the user to drag it back. if (displays.length === 1) { const displayWorkingArea = this.getWorkingArea(displays[0]); if (displayWorkingArea) { - this.logService.trace('window#validateWindowState: 1 display', displayWorkingArea); + this.logService.trace('window#validateWindowState: 1 monitor working area', displayWorkingArea); - if (state.x < displayWorkingArea.x) { - state.x = displayWorkingArea.x; // prevent window from falling out of the screen to the left + function ensureStateInDisplayWorkingArea(): void { + if (!state || typeof state.x !== 'number' || typeof state.y !== 'number' || !displayWorkingArea) { + return; + } + + if (state.x < displayWorkingArea.x) { + // prevent window from falling out of the screen to the left + state.x = displayWorkingArea.x; + } + + if (state.y < displayWorkingArea.y) { + // prevent window from falling out of the screen to the top + state.y = displayWorkingArea.y; + } } - if (state.y < displayWorkingArea.y) { - state.y = displayWorkingArea.y; // prevent window from falling out of the screen to the top - } - - if (state.x > (displayWorkingArea.x + displayWorkingArea.width)) { - state.x = displayWorkingArea.x; // prevent window from falling out of the screen to the right - } - - if (state.y > (displayWorkingArea.y + displayWorkingArea.height)) { - state.y = displayWorkingArea.y; // prevent window from falling out of the screen to the bottom - } + // ensure state is not outside display working area (top, left) + ensureStateInDisplayWorkingArea(); if (state.width > displayWorkingArea.width) { - state.width = displayWorkingArea.width; // prevent window from exceeding display bounds width + // prevent window from exceeding display bounds width + state.width = displayWorkingArea.width; } if (state.height > displayWorkingArea.height) { - state.height = displayWorkingArea.height; // prevent window from exceeding display bounds height + // prevent window from exceeding display bounds height + state.height = displayWorkingArea.height; } + + if (state.x > (displayWorkingArea.x + displayWorkingArea.width - 128)) { + // prevent window from falling out of the screen to the right with + // 128px margin by positioning the window to the far right edge of + // the screen + state.x = displayWorkingArea.x + displayWorkingArea.width - state.width; + } + + if (state.y > (displayWorkingArea.y + displayWorkingArea.height - 128)) { + // prevent window from falling out of the screen to the bottom with + // 128px margin by positioning the window to the far bottom edge of + // the screen + state.y = displayWorkingArea.y + displayWorkingArea.height - state.height; + } + + // again ensure state is not outside display working area + // (it may have changed from the previous validation step) + ensureStateInDisplayWorkingArea(); } return state; @@ -840,19 +865,18 @@ export class CodeWindow extends Disposable implements ICodeWindow { } } - // Multi Monitor (non-fullscreen): be less strict because metrics can be crazy - const bounds = { x: state.x, y: state.y, width: state.width, height: state.height }; - const display = screen.getDisplayMatching(bounds); + // Multi Monitor (non-fullscreen): ensure window is within display bounds + const display = screen.getDisplayMatching({ x: state.x, y: state.y, width: state.width, height: state.height }); const displayWorkingArea = this.getWorkingArea(display); if ( display && // we have a display matching the desired bounds displayWorkingArea && // we have valid working area bounds - bounds.x < displayWorkingArea.x + displayWorkingArea.width && // prevent window from falling out of the screen to the right - bounds.y < displayWorkingArea.y + displayWorkingArea.height && // prevent window from falling out of the screen to the bottom - 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 + state.x + state.width > displayWorkingArea.x && // prevent window from falling out of the screen to the left + state.y + state.height > displayWorkingArea.y && // prevent window from falling out of the screen to the top + state.x < displayWorkingArea.x + displayWorkingArea.width && // prevent window from falling out of the screen to the right + state.y < displayWorkingArea.y + displayWorkingArea.height // prevent window from falling out of the screen to the bottom ) { - this.logService.trace('window#validateWindowState: multi display', displayWorkingArea); + this.logService.trace('window#validateWindowState: multi-monitor working area', displayWorkingArea); return state; } diff --git a/src/vs/editor/browser/controller/mouseTarget.ts b/src/vs/editor/browser/controller/mouseTarget.ts index c1e8137ed0..f32976b2f8 100644 --- a/src/vs/editor/browser/controller/mouseTarget.ts +++ b/src/vs/editor/browser/controller/mouseTarget.ts @@ -673,6 +673,13 @@ export class MouseTargetFactory { const detail = createEmptyContentDataInLines(request.mouseContentHorizontalOffset - lineWidth); return request.fulfill(MouseTargetType.CONTENT_EMPTY, new Position(lineNumber, 1), undefined, detail); } + + const lineWidth = ctx.getLineWidth(lineNumber); + if (request.mouseContentHorizontalOffset >= lineWidth) { + const detail = createEmptyContentDataInLines(request.mouseContentHorizontalOffset - lineWidth); + const pos = new Position(lineNumber, ctx.model.getLineMaxColumn(lineNumber)); + return request.fulfill(MouseTargetType.CONTENT_EMPTY, pos, undefined, detail); + } } // We have already executed hit test... diff --git a/src/vs/editor/browser/editorBrowser.ts b/src/vs/editor/browser/editorBrowser.ts index 02b5da393f..066d00b09e 100644 --- a/src/vs/editor/browser/editorBrowser.ts +++ b/src/vs/editor/browser/editorBrowser.ts @@ -422,7 +422,6 @@ export interface ICodeEditor extends editorCommon.IEditor { /** * An event emitted when users paste text in the editor. * @event - * @internal */ onDidPaste(listener: (range: Range) => void): IDisposable; /** diff --git a/src/vs/editor/browser/view/viewImpl.ts b/src/vs/editor/browser/view/viewImpl.ts index 782d382640..eaa4e9cb7c 100644 --- a/src/vs/editor/browser/view/viewImpl.ts +++ b/src/vs/editor/browser/view/viewImpl.ts @@ -460,7 +460,11 @@ export class View extends ViewEventHandler { } public getTargetAtClientPoint(clientX: number, clientY: number): IMouseTarget | null { - return this.pointerHandler.getTargetAtClientPoint(clientX, clientY); + const mouseTarget = this.pointerHandler.getTargetAtClientPoint(clientX, clientY); + if (!mouseTarget) { + return null; + } + return ViewOutgoingEvents.convertViewToModelMouseTarget(mouseTarget, this._context.model.coordinatesConverter); } public createOverviewRuler(cssClassName: string): OverviewRuler { diff --git a/src/vs/editor/browser/view/viewOutgoingEvents.ts b/src/vs/editor/browser/view/viewOutgoingEvents.ts index 16bdfbcdf7..86b8049b02 100644 --- a/src/vs/editor/browser/view/viewOutgoingEvents.ts +++ b/src/vs/editor/browser/view/viewOutgoingEvents.ts @@ -11,7 +11,7 @@ import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { IScrollEvent } from 'vs/editor/common/editorCommon'; import * as viewEvents from 'vs/editor/common/view/viewEvents'; -import { IViewModel } from 'vs/editor/common/viewModel/viewModel'; +import { IViewModel, ICoordinatesConverter } from 'vs/editor/common/viewModel/viewModel'; import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; export interface EventCallback { @@ -132,23 +132,19 @@ export class ViewOutgoingEvents extends Disposable { } private _convertViewToModelMouseTarget(target: IMouseTarget): IMouseTarget { + return ViewOutgoingEvents.convertViewToModelMouseTarget(target, this._viewModel.coordinatesConverter); + } + + public static convertViewToModelMouseTarget(target: IMouseTarget, coordinatesConverter: ICoordinatesConverter): IMouseTarget { return new ExternalMouseTarget( target.element, target.type, target.mouseColumn, - target.position ? this._convertViewToModelPosition(target.position) : null, - target.range ? this._convertViewToModelRange(target.range) : null, + target.position ? coordinatesConverter.convertViewPositionToModelPosition(target.position) : null, + target.range ? coordinatesConverter.convertViewRangeToModelRange(target.range) : null, target.detail ); } - - private _convertViewToModelPosition(viewPosition: Position): Position { - return this._viewModel.coordinatesConverter.convertViewPositionToModelPosition(viewPosition); - } - - private _convertViewToModelRange(viewRange: Range): Range { - return this._viewModel.coordinatesConverter.convertViewRangeToModelRange(viewRange); - } } class ExternalMouseTarget implements IMouseTarget { diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index ef030a6c53..a6b6ed7241 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -53,8 +53,6 @@ import { withNullAsUndefined } from 'vs/base/common/types'; let EDITOR_ID = 0; -const SHOW_UNUSED_ENABLED_CLASS = 'showUnused'; - export interface ICodeEditorWidgetOptions { /** * Is this a simple widget (not a real code editor) ? @@ -263,11 +261,6 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE const layoutInfo = options.get(EditorOption.layoutInfo); this._onDidLayoutChange.fire(layoutInfo); } - if (options.get(EditorOption.showUnused)) { - this._domElement.classList.add(SHOW_UNUSED_ENABLED_CLASS); - } else { - this._domElement.classList.remove(SHOW_UNUSED_ENABLED_CLASS); - } })); this._contextKeyService = this._register(contextKeyService.createScoped(this._domElement)); @@ -1871,12 +1864,12 @@ registerThemingParticipant((theme, collector) => { const unnecessaryForeground = theme.getColor(editorUnnecessaryCodeOpacity); if (unnecessaryForeground) { - collector.addRule(`.${SHOW_UNUSED_ENABLED_CLASS} .monaco-editor .${ClassName.EditorUnnecessaryInlineDecoration} { opacity: ${unnecessaryForeground.rgba.a}; }`); + collector.addRule(`.monaco-editor.showUnused .${ClassName.EditorUnnecessaryInlineDecoration} { opacity: ${unnecessaryForeground.rgba.a}; }`); } const unnecessaryBorder = theme.getColor(editorUnnecessaryCodeBorder); if (unnecessaryBorder) { - collector.addRule(`.${SHOW_UNUSED_ENABLED_CLASS} .monaco-editor .${ClassName.EditorUnnecessaryDecoration} { border-bottom: 2px dashed ${unnecessaryBorder}; }`); + collector.addRule(`.monaco-editor.showUnused .${ClassName.EditorUnnecessaryDecoration} { border-bottom: 2px dashed ${unnecessaryBorder}; }`); } const deprecatedForeground = theme.getColor(editorForeground) || 'inherit'; diff --git a/src/vs/editor/common/config/commonEditorConfig.ts b/src/vs/editor/common/config/commonEditorConfig.ts index 115246f130..c0cafe6a7b 100644 --- a/src/vs/editor/common/config/commonEditorConfig.ts +++ b/src/vs/editor/common/config/commonEditorConfig.ts @@ -432,8 +432,7 @@ export const editorConfigurationBaseNode = Object.freeze({ order: 5, type: 'object', title: nls.localize('editorConfigurationTitle', "Editor"), - overridable: true, - scope: ConfigurationScope.RESOURCE, + scope: ConfigurationScope.RESOURCE_LANGUAGE, }); const configurationRegistry = Registry.as(Extensions.Configuration); diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 8dfc588cb6..f79ab53304 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -1118,6 +1118,9 @@ class EditorClassName extends ComputedEditorOption { this._lastWorkerUsedTime = (new Date()).getTime(); if (!this._editorWorkerClient) { - this._editorWorkerClient = new EditorWorkerClient(this._modelService, 'editorWorkerService'); + this._editorWorkerClient = new EditorWorkerClient(this._modelService, false, 'editorWorkerService'); } return Promise.resolve(this._editorWorkerClient); } @@ -374,13 +374,15 @@ export class EditorWorkerHost { export class EditorWorkerClient extends Disposable { private readonly _modelService: IModelService; + private readonly _keepIdleModels: boolean; private _worker: IWorkerClient | null; private readonly _workerFactory: DefaultWorkerFactory; private _modelManager: EditorModelManager | null; - constructor(modelService: IModelService, label: string | undefined) { + constructor(modelService: IModelService, keepIdleModels: boolean, label: string | undefined) { super(); this._modelService = modelService; + this._keepIdleModels = keepIdleModels; this._workerFactory = new DefaultWorkerFactory(label); this._worker = null; this._modelManager = null; @@ -417,7 +419,7 @@ export class EditorWorkerClient extends Disposable { private _getOrCreateModelManager(proxy: EditorSimpleWorker): EditorModelManager { if (!this._modelManager) { - this._modelManager = this._register(new EditorModelManager(proxy, this._modelService, false)); + this._modelManager = this._register(new EditorModelManager(proxy, this._modelService, this._keepIdleModels)); } return this._modelManager; } diff --git a/src/vs/editor/common/services/modelServiceImpl.ts b/src/vs/editor/common/services/modelServiceImpl.ts index 52a89a4550..11addcf0a8 100644 --- a/src/vs/editor/common/services/modelServiceImpl.ts +++ b/src/vs/editor/common/services/modelServiceImpl.ts @@ -18,7 +18,7 @@ import { LanguageIdentifier, SemanticTokensProviderRegistry, SemanticTokensProvi import { PLAINTEXT_LANGUAGE_IDENTIFIER } from 'vs/editor/common/modes/modesRegistry'; import { ILanguageSelection } from 'vs/editor/common/services/modeService'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; +import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { RunOnceScheduler } from 'vs/base/common/async'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; diff --git a/src/vs/editor/common/services/resourceConfiguration.ts b/src/vs/editor/common/services/textResourceConfigurationService.ts similarity index 90% rename from src/vs/editor/common/services/resourceConfiguration.ts rename to src/vs/editor/common/services/textResourceConfigurationService.ts index 91eedcb312..f690c04821 100644 --- a/src/vs/editor/common/services/resourceConfiguration.ts +++ b/src/vs/editor/common/services/textResourceConfigurationService.ts @@ -9,9 +9,9 @@ import { IPosition } from 'vs/editor/common/core/position'; import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -export const IResourceConfigurationService = createDecorator('resourceConfigurationService'); +export const ITextResourceConfigurationService = createDecorator('textResourceConfigurationService'); -export interface IResourceConfigurationChangeEvent { +export interface ITextResourceConfigurationChangeEvent { /** * All affected keys. Also includes language overrides and keys changed under language overrides. @@ -29,14 +29,14 @@ export interface IResourceConfigurationChangeEvent { affectsConfiguration(resource: URI, section: string): boolean; } -export interface IResourceConfigurationService { +export interface ITextResourceConfigurationService { _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. diff --git a/src/vs/editor/common/services/resourceConfigurationImpl.ts b/src/vs/editor/common/services/textResourceConfigurationServiceImpl.ts similarity index 77% rename from src/vs/editor/common/services/resourceConfigurationImpl.ts rename to src/vs/editor/common/services/textResourceConfigurationServiceImpl.ts index c402f28cac..178b0c8160 100644 --- a/src/vs/editor/common/services/resourceConfigurationImpl.ts +++ b/src/vs/editor/common/services/textResourceConfigurationServiceImpl.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 { IResourceConfigurationService, IResourceConfigurationChangeEvent } from 'vs/editor/common/services/resourceConfiguration'; +import { ITextResourceConfigurationService, ITextResourceConfigurationChangeEvent } from 'vs/editor/common/services/textResourceConfigurationService'; import { IConfigurationService, ConfigurationTarget, IConfigurationValue, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; -export class TextResourceConfigurationService extends Disposable implements IResourceConfigurationService { +export class TextResourceConfigurationService extends Disposable implements ITextResourceConfigurationService { 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, @@ -45,15 +45,15 @@ export class TextResourceConfigurationService extends Disposable implements IRes } switch (configurationTarget) { case ConfigurationTarget.MEMORY: - return this._updateValue(key, value, configurationTarget, configurationValue.memoryTarget?.override, resource, language); + return this._updateValue(key, value, configurationTarget, configurationValue.memory?.override, resource, language); case ConfigurationTarget.WORKSPACE_FOLDER: - return this._updateValue(key, value, configurationTarget, configurationValue.workspaceFolderTarget?.override, resource, language); + return this._updateValue(key, value, configurationTarget, configurationValue.workspaceFolder?.override, resource, language); case ConfigurationTarget.WORKSPACE: - return this._updateValue(key, value, configurationTarget, configurationValue.workspaceTarget?.override, resource, language); + return this._updateValue(key, value, configurationTarget, configurationValue.workspace?.override, resource, language); case ConfigurationTarget.USER_REMOTE: - return this._updateValue(key, value, configurationTarget, configurationValue.userRemoteTarget?.override, resource, language); + return this._updateValue(key, value, configurationTarget, configurationValue.userRemote?.override, resource, language); default: - return this._updateValue(key, value, configurationTarget, configurationValue.userLocalTarget?.override, resource, language); + return this._updateValue(key, value, configurationTarget, configurationValue.userLocal?.override, resource, language); } } @@ -67,32 +67,32 @@ export class TextResourceConfigurationService extends Disposable implements IRes private deriveConfigurationTarget(configurationValue: IConfigurationValue, language: string | null): ConfigurationTarget { if (language) { - if (configurationValue.memoryTarget?.override !== undefined) { + if (configurationValue.memory?.override !== undefined) { return ConfigurationTarget.MEMORY; } - if (configurationValue.workspaceFolderTarget?.override !== undefined) { + if (configurationValue.workspaceFolder?.override !== undefined) { return ConfigurationTarget.WORKSPACE_FOLDER; } - if (configurationValue.workspaceTarget?.override !== undefined) { + if (configurationValue.workspace?.override !== undefined) { return ConfigurationTarget.WORKSPACE; } - if (configurationValue.userRemoteTarget?.override !== undefined) { + if (configurationValue.userRemote?.override !== undefined) { return ConfigurationTarget.USER_REMOTE; } - if (configurationValue.userLocalTarget?.override !== undefined) { + if (configurationValue.userLocal?.override !== undefined) { return ConfigurationTarget.USER_LOCAL; } } - if (configurationValue.memoryTarget?.value !== undefined) { + if (configurationValue.memory?.value !== undefined) { return ConfigurationTarget.MEMORY; } - if (configurationValue.workspaceFolderTarget?.value !== undefined) { + if (configurationValue.workspaceFolder?.value !== undefined) { return ConfigurationTarget.WORKSPACE_FOLDER; } - if (configurationValue.workspaceTarget?.value !== undefined) { + if (configurationValue.workspace?.value !== undefined) { return ConfigurationTarget.WORKSPACE; } - if (configurationValue.userRemoteTarget?.value !== undefined) { + if (configurationValue.userRemote?.value !== undefined) { return ConfigurationTarget.USER_REMOTE; } return ConfigurationTarget.USER_LOCAL; @@ -114,7 +114,7 @@ export class TextResourceConfigurationService extends Disposable implements IRes return this.modeService.getModeIdByFilepathOrFirstLine(resource); } - private toResourceConfigurationChangeEvent(configurationChangeEvent: IConfigurationChangeEvent): IResourceConfigurationChangeEvent { + private toResourceConfigurationChangeEvent(configurationChangeEvent: IConfigurationChangeEvent): ITextResourceConfigurationChangeEvent { return { affectedKeys: configurationChangeEvent.affectedKeys, affectsConfiguration: (resource: URI, configuration: string) => { diff --git a/src/vs/editor/common/services/webWorker.ts b/src/vs/editor/common/services/webWorker.ts index 983500b283..cef9c6ec81 100644 --- a/src/vs/editor/common/services/webWorker.ts +++ b/src/vs/editor/common/services/webWorker.ts @@ -53,6 +53,11 @@ export interface IWebWorkerOptions { * An object that can be used by the web worker to make calls back to the main thread. */ host?: any; + /** + * Keep idle models. + * Defaults to false, which means that idle models will stop syncing after a while. + */ + keepIdleModels?: boolean; } class MonacoWebWorkerImpl extends EditorWorkerClient implements MonacoWebWorker { @@ -63,7 +68,7 @@ class MonacoWebWorkerImpl extends EditorWorkerClient implements MonacoWebWork private _foreignProxy: Promise | null; constructor(modelService: IModelService, opts: IWebWorkerOptions) { - super(modelService, opts.label); + super(modelService, opts.keepIdleModels || false, opts.label); this._foreignModuleId = opts.moduleId; this._foreignModuleCreateData = opts.createData || null; this._foreignModuleHost = opts.host || null; diff --git a/src/vs/editor/contrib/codeAction/lightBulbWidget.ts b/src/vs/editor/contrib/codeAction/lightBulbWidget.ts index e68b5e45c4..ff2697dfa3 100644 --- a/src/vs/editor/contrib/codeAction/lightBulbWidget.ts +++ b/src/vs/editor/contrib/codeAction/lightBulbWidget.ts @@ -225,8 +225,7 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { const editorLightBulbForegroundColor = theme.getColor(editorLightBulbForeground); if (editorLightBulbForegroundColor) { collector.addRule(` - .monaco-workbench .contentWidgets .codicon-lightbulb, - .monaco-workbench .markers-panel-container .codicon-lightbulb { + .monaco-editor .contentWidgets .codicon-lightbulb { color: ${editorLightBulbForegroundColor}; }`); } @@ -235,8 +234,7 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { const editorLightBulbAutoFixForegroundColor = theme.getColor(editorLightBulbAutoFixForeground); if (editorLightBulbAutoFixForegroundColor) { collector.addRule(` - .monaco-workbench .contentWidgets .codicon-lightbulb-autofix, - .monaco-workbench .markers-panel-container .codicon-lightbulb-autofix { + .monaco-editor .contentWidgets .codicon-lightbulb-autofix { color: ${editorLightBulbAutoFixForegroundColor}; }`); } diff --git a/src/vs/editor/contrib/codelens/codelensWidget.css b/src/vs/editor/contrib/codelens/codelensWidget.css index d09173886d..89a07a31c1 100644 --- a/src/vs/editor/contrib/codelens/codelensWidget.css +++ b/src/vs/editor/contrib/codelens/codelensWidget.css @@ -27,6 +27,16 @@ cursor: pointer; } +.monaco-editor .codelens-decoration .codicon { + line-height: inherit; + font-size: inherit; +} + +.monaco-editor .codelens-decoration > a:hover .codicon::before { + text-decoration: underline; + cursor: pointer; +} + @keyframes fadein { 0% { opacity: 0; visibility: visible;} 100% { opacity: 1; } diff --git a/src/vs/editor/contrib/codelens/codelensWidget.ts b/src/vs/editor/contrib/codelens/codelensWidget.ts index 9851660ac6..86e1ec2d68 100644 --- a/src/vs/editor/contrib/codelens/codelensWidget.ts +++ b/src/vs/editor/contrib/codelens/codelensWidget.ts @@ -342,9 +342,11 @@ registerThemingParticipant((theme, collector) => { const codeLensForeground = theme.getColor(editorCodeLensForeground); if (codeLensForeground) { collector.addRule(`.monaco-editor .codelens-decoration { color: ${codeLensForeground}; }`); + collector.addRule(`.monaco-editor .codelens-decoration .codicon { color: ${codeLensForeground}; }`); } const activeLinkForeground = theme.getColor(editorActiveLinkForeground); if (activeLinkForeground) { collector.addRule(`.monaco-editor .codelens-decoration > a:hover { color: ${activeLinkForeground} !important; }`); + collector.addRule(`.monaco-editor .codelens-decoration > a:hover .codicon { color: ${activeLinkForeground} !important; }`); } }); diff --git a/src/vs/editor/contrib/documentSymbols/media/symbol-icons.css b/src/vs/editor/contrib/documentSymbols/media/symbol-icons.css index 28ad84c3a8..af39064849 100644 --- a/src/vs/editor/contrib/documentSymbols/media/symbol-icons.css +++ b/src/vs/editor/contrib/documentSymbols/media/symbol-icons.css @@ -3,22 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.monaco-workbench .monaco-icon-label.deprecated { +.monaco-icon-label.deprecated { text-decoration: line-through; opacity: 0.66; } - -.monaco-workbench .symbol-icon.inline { - display: flex; - align-items: center; - padding-left: 0; -} - -.monaco-workbench .symbol-icon.block { - display: inline-block; - height: 14px; - width: 16px; - min-height: 14px; - min-width: 16px; - background-position: center; -} diff --git a/src/vs/editor/contrib/documentSymbols/outlineTree.ts b/src/vs/editor/contrib/documentSymbols/outlineTree.ts index e009df6bbe..26e6c58b92 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 { IResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; +import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; 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, - @IResourceConfigurationService private readonly _textResourceConfigService: IResourceConfigurationService, + @ITextResourceConfigurationService private readonly _textResourceConfigService: ITextResourceConfigurationService, ) { } filter(element: OutlineItem): boolean { @@ -540,300 +540,168 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { const symbolIconArrayColor = theme.getColor(SYMBOL_ICON_ARRAY_FOREGROUND); if (symbolIconArrayColor) { - collector.addRule(` - .monaco-workbench .codicon-symbol-array { - color: ${symbolIconArrayColor} !important; - } - `); + collector.addRule(`.codicon-symbol-array { color: ${symbolIconArrayColor} !important; }`); } const symbolIconBooleanColor = theme.getColor(SYMBOL_ICON_BOOLEAN_FOREGROUND); if (symbolIconBooleanColor) { - collector.addRule(` - .monaco-workbench .codicon-symbol-boolean { - color: ${symbolIconBooleanColor} !important; - } - `); + collector.addRule(`.codicon-symbol-boolean { color: ${symbolIconBooleanColor} !important; }`); } const symbolIconClassColor = theme.getColor(SYMBOL_ICON_CLASS_FOREGROUND); if (symbolIconClassColor) { - collector.addRule(` - .monaco-workbench .codicon-symbol-class { - color: ${symbolIconClassColor} !important; - } - `); + collector.addRule(`.codicon-symbol-class { color: ${symbolIconClassColor} !important; }`); } const symbolIconMethodColor = theme.getColor(SYMBOL_ICON_METHOD_FOREGROUND); if (symbolIconMethodColor) { - collector.addRule(` - .monaco-workbench .codicon-symbol-method { - color: ${symbolIconMethodColor} !important; - } - `); + collector.addRule(`.codicon-symbol-method { color: ${symbolIconMethodColor} !important; }`); } const symbolIconColorColor = theme.getColor(SYMBOL_ICON_COLOR_FOREGROUND); if (symbolIconColorColor) { - collector.addRule(` - .monaco-workbench .codicon-symbol-color { - color: ${symbolIconColorColor} !important; - } - `); + collector.addRule(`.codicon-symbol-color { color: ${symbolIconColorColor} !important; }`); } const symbolIconConstantColor = theme.getColor(SYMBOL_ICON_CONSTANT_FOREGROUND); if (symbolIconConstantColor) { - collector.addRule(` - .monaco-workbench .codicon-symbol-constant { - color: ${symbolIconConstantColor} !important; - } - `); + collector.addRule(`.codicon-symbol-constant { color: ${symbolIconConstantColor} !important; }`); } const symbolIconConstructorColor = theme.getColor(SYMBOL_ICON_CONSTRUCTOR_FOREGROUND); if (symbolIconConstructorColor) { - collector.addRule(` - .monaco-workbench .codicon-symbol-constructor { - color: ${symbolIconConstructorColor} !important; - } - `); + collector.addRule(`.codicon-symbol-constructor { color: ${symbolIconConstructorColor} !important; }`); } const symbolIconEnumeratorColor = theme.getColor(SYMBOL_ICON_ENUMERATOR_FOREGROUND); if (symbolIconEnumeratorColor) { collector.addRule(` - .monaco-workbench .codicon-symbol-value, - .monaco-workbench .codicon-symbol-enum { - color: ${symbolIconEnumeratorColor} !important; - } - `); + .codicon-symbol-value,.codicon-symbol-enum { color: ${symbolIconEnumeratorColor} !important; }`); } const symbolIconEnumeratorMemberColor = theme.getColor(SYMBOL_ICON_ENUMERATOR_MEMBER_FOREGROUND); if (symbolIconEnumeratorMemberColor) { - collector.addRule(` - .monaco-workbench .codicon-symbol-enum-member { - color: ${symbolIconEnumeratorMemberColor} !important; - } - `); + collector.addRule(`.codicon-symbol-enum-member { color: ${symbolIconEnumeratorMemberColor} !important; }`); } const symbolIconEventColor = theme.getColor(SYMBOL_ICON_EVENT_FOREGROUND); if (symbolIconEventColor) { - collector.addRule(` - .monaco-workbench .codicon-symbol-event { - color: ${symbolIconEventColor} !important; - } - `); + collector.addRule(`.codicon-symbol-event { color: ${symbolIconEventColor} !important; }`); } const symbolIconFieldColor = theme.getColor(SYMBOL_ICON_FIELD_FOREGROUND); if (symbolIconFieldColor) { - collector.addRule(` - .monaco-workbench .codicon-symbol-field { - color: ${symbolIconFieldColor} !important; - } - `); + collector.addRule(`.codicon-symbol-field { color: ${symbolIconFieldColor} !important; }`); } const symbolIconFileColor = theme.getColor(SYMBOL_ICON_FILE_FOREGROUND); if (symbolIconFileColor) { - collector.addRule(` - .monaco-workbench .codicon-symbol-file { - color: ${symbolIconFileColor} !important; - } - `); + collector.addRule(`.codicon-symbol-file { color: ${symbolIconFileColor} !important; }`); } const symbolIconFolderColor = theme.getColor(SYMBOL_ICON_FOLDER_FOREGROUND); if (symbolIconFolderColor) { - collector.addRule(` - .monaco-workbench .codicon-symbol-folder { - color: ${symbolIconFolderColor} !important; - } - `); + collector.addRule(`.codicon-symbol-folder { color: ${symbolIconFolderColor} !important; }`); } const symbolIconFunctionColor = theme.getColor(SYMBOL_ICON_FUNCTION_FOREGROUND); if (symbolIconFunctionColor) { - collector.addRule(` - .monaco-workbench .codicon-symbol-function { - color: ${symbolIconFunctionColor} !important; - } - `); + collector.addRule(`.codicon-symbol-function { color: ${symbolIconFunctionColor} !important; }`); } const symbolIconInterfaceColor = theme.getColor(SYMBOL_ICON_INTERFACE_FOREGROUND); if (symbolIconInterfaceColor) { - collector.addRule(` - .monaco-workbench .codicon-symbol-interface { - color: ${symbolIconInterfaceColor} !important; - } - `); + collector.addRule(`.codicon-symbol-interface { color: ${symbolIconInterfaceColor} !important; }`); } const symbolIconKeyColor = theme.getColor(SYMBOL_ICON_KEY_FOREGROUND); if (symbolIconKeyColor) { - collector.addRule(` - .monaco-workbench .codicon-symbol-key { - color: ${symbolIconKeyColor} !important; - } - `); + collector.addRule(`.codicon-symbol-key { color: ${symbolIconKeyColor} !important; }`); } const symbolIconKeywordColor = theme.getColor(SYMBOL_ICON_KEYWORD_FOREGROUND); if (symbolIconKeywordColor) { - collector.addRule(` - .monaco-workbench .codicon-symbol-keyword { - color: ${symbolIconKeywordColor} !important; - } - `); + collector.addRule(`.codicon-symbol-keyword { color: ${symbolIconKeywordColor} !important; }`); } const symbolIconModuleColor = theme.getColor(SYMBOL_ICON_MODULE_FOREGROUND); if (symbolIconModuleColor) { - collector.addRule(` - .monaco-workbench .codicon-symbol-module { - color: ${symbolIconModuleColor} !important; - } - `); + collector.addRule(`.codicon-symbol-module { color: ${symbolIconModuleColor} !important; }`); } const outlineNamespaceColor = theme.getColor(SYMBOL_ICON_NAMESPACE_FOREGROUND); if (outlineNamespaceColor) { - collector.addRule(` - .monaco-workbench .codicon-symbol-namespace { - color: ${outlineNamespaceColor} !important; - } - `); + collector.addRule(`.codicon-symbol-namespace { color: ${outlineNamespaceColor} !important; }`); } const symbolIconNullColor = theme.getColor(SYMBOL_ICON_NULL_FOREGROUND); if (symbolIconNullColor) { - collector.addRule(` - .monaco-workbench .codicon-symbol-null { - color: ${symbolIconNullColor} !important; - } - `); + collector.addRule(`.codicon-symbol-null { color: ${symbolIconNullColor} !important; }`); } const symbolIconNumberColor = theme.getColor(SYMBOL_ICON_NUMBER_FOREGROUND); if (symbolIconNumberColor) { - collector.addRule(` - .monaco-workbench .codicon-symbol-number { - color: ${symbolIconNumberColor} !important; - } - `); + collector.addRule(`.codicon-symbol-number { color: ${symbolIconNumberColor} !important; }`); } const symbolIconObjectColor = theme.getColor(SYMBOL_ICON_OBJECT_FOREGROUND); if (symbolIconObjectColor) { - collector.addRule(` - .monaco-workbench .codicon-symbol-object { - color: ${symbolIconObjectColor} !important; - } - `); + collector.addRule(`.codicon-symbol-object { color: ${symbolIconObjectColor} !important; }`); } const symbolIconOperatorColor = theme.getColor(SYMBOL_ICON_OPERATOR_FOREGROUND); if (symbolIconOperatorColor) { - collector.addRule(` - .monaco-workbench .codicon-symbol-operator { - color: ${symbolIconOperatorColor} !important; - } - `); + collector.addRule(`.codicon-symbol-operator { color: ${symbolIconOperatorColor} !important; }`); } const symbolIconPackageColor = theme.getColor(SYMBOL_ICON_PACKAGE_FOREGROUND); if (symbolIconPackageColor) { - collector.addRule(` - .monaco-workbench .codicon-symbol-package { - color: ${symbolIconPackageColor} !important; - } - `); + collector.addRule(`.codicon-symbol-package { color: ${symbolIconPackageColor} !important; }`); } const symbolIconPropertyColor = theme.getColor(SYMBOL_ICON_PROPERTY_FOREGROUND); if (symbolIconPropertyColor) { - collector.addRule(` - .monaco-workbench .codicon-symbol-property { - color: ${symbolIconPropertyColor} !important; - } - `); + collector.addRule(`.codicon-symbol-property { color: ${symbolIconPropertyColor} !important; }`); } const symbolIconReferenceColor = theme.getColor(SYMBOL_ICON_REFERENCE_FOREGROUND); if (symbolIconReferenceColor) { - collector.addRule(` - .monaco-workbench .codicon-symbol-reference { - color: ${symbolIconReferenceColor} !important; - } - `); + collector.addRule(`.codicon-symbol-reference { color: ${symbolIconReferenceColor} !important; }`); } const symbolIconSnippetColor = theme.getColor(SYMBOL_ICON_SNIPPET_FOREGROUND); if (symbolIconSnippetColor) { - collector.addRule(` - .monaco-workbench .codicon-symbol-snippet { - color: ${symbolIconSnippetColor} !important; - } - `); + collector.addRule(`.codicon-symbol-snippet { color: ${symbolIconSnippetColor} !important; }`); } const symbolIconStringColor = theme.getColor(SYMBOL_ICON_STRING_FOREGROUND); if (symbolIconStringColor) { - collector.addRule(` - .monaco-workbench .codicon-symbol-string { - color: ${symbolIconStringColor} !important; - } - `); + collector.addRule(`.codicon-symbol-string { color: ${symbolIconStringColor} !important; }`); } const symbolIconStructColor = theme.getColor(SYMBOL_ICON_STRUCT_FOREGROUND); if (symbolIconStructColor) { - collector.addRule(` - .monaco-workbench .codicon-symbol-struct { - color: ${symbolIconStructColor} !important; - } - `); + collector.addRule(`.codicon-symbol-struct { color: ${symbolIconStructColor} !important; }`); } const symbolIconTextColor = theme.getColor(SYMBOL_ICON_TEXT_FOREGROUND); if (symbolIconTextColor) { - collector.addRule(` - .monaco-workbench .codicon-symbol-text { - color: ${symbolIconTextColor} !important; - } - `); + collector.addRule(`.codicon-symbol-text { color: ${symbolIconTextColor} !important; }`); } const symbolIconTypeParameterColor = theme.getColor(SYMBOL_ICON_TYPEPARAMETER_FOREGROUND); if (symbolIconTypeParameterColor) { - collector.addRule(` - .monaco-workbench .codicon-symbol-type-parameter { - color: ${symbolIconTypeParameterColor} !important; - } - `); + collector.addRule(`.codicon-symbol-type-parameter { color: ${symbolIconTypeParameterColor} !important; }`); } const symbolIconUnitColor = theme.getColor(SYMBOL_ICON_UNIT_FOREGROUND); if (symbolIconUnitColor) { - collector.addRule(` - .monaco-workbench .codicon-symbol-unit { - color: ${symbolIconUnitColor} !important; - } - `); + collector.addRule(`.codicon-symbol-unit { color: ${symbolIconUnitColor} !important; }`); } const symbolIconVariableColor = theme.getColor(SYMBOL_ICON_VARIABLE_FOREGROUND); if (symbolIconVariableColor) { - collector.addRule(` - .monaco-workbench .codicon-symbol-variable { - color: ${symbolIconVariableColor} !important; - } - `); + collector.addRule(`.codicon-symbol-variable { color: ${symbolIconVariableColor} !important; }`); } }); diff --git a/src/vs/editor/contrib/find/findWidget.css b/src/vs/editor/contrib/find/findWidget.css index 11ccae5150..07fb6311be 100644 --- a/src/vs/editor/contrib/find/findWidget.css +++ b/src/vs/editor/contrib/find/findWidget.css @@ -3,31 +3,6 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -/* Checkbox */ - -.monaco-checkbox .codicon-selection { - width: 12px; - height: 12px; - border: 1px solid black; - background-color: transparent; - display: inline-block; -} - -.monaco-checkbox .checkbox { - position: absolute; - overflow: hidden; - clip: rect(0 0 0 0); - height: 1px; - width: 1px; - margin: -1px; - padding: 0; - border: 0; -} - -.monaco-checkbox .checkbox:checked + .codicon-selection { - background-color: black; -} - /* Find widget */ .monaco-editor .find-widget { position: absolute; @@ -184,40 +159,6 @@ cursor: default; } -.monaco-editor .find-widget .monaco-checkbox { - width: 20px; - height: 20px; - display: inline-block; - vertical-align: middle; - margin-left: 3px; -} - -.monaco-editor .find-widget .monaco-checkbox .codicon-selection { - display: flex; - align-items: center; - justify-content: center; - width: 20px; - height: 20px; - border: none; -} - -.monaco-editor .find-widget .monaco-checkbox .checkbox:disabled + .codicon-selection { - opacity: 0.3; - cursor: default; -} - -.monaco-editor .find-widget .monaco-checkbox .checkbox:not(:disabled) + .codicon-selection { - cursor: pointer; -} - -.monaco-editor .find-widget .monaco-checkbox .checkbox:not(:disabled):hover:before + .codicon-selection { - background-color: #DDD; -} - -.monaco-editor .find-widget .monaco-checkbox .checkbox:checked + .codicon-selection { - background-color: rgba(100, 100, 100, 0.2); -} - .monaco-editor .find-widget > .replace-part { display: none; } @@ -238,8 +179,7 @@ } /* REDUCED */ -.monaco-editor .find-widget.reduced-find-widget .matchesCount, -.monaco-editor .find-widget.reduced-find-widget .monaco-checkbox { +.monaco-editor .find-widget.reduced-find-widget .matchesCount { display:none; } @@ -271,13 +211,8 @@ margin-left: -4px; } -.monaco-editor.vs-dark .find-widget .monaco-checkbox .checkbox:not(:disabled):hover:before + .codicon-selection { - background-color: rgba(255, 255, 255, 0.1); -} - .monaco-editor.hc-black .find-widget .button:not(.disabled):hover, -.monaco-editor.vs-dark .find-widget .button:not(.disabled):hover, -.monaco-editor.vs-dark .find-widget .monaco-checkbox:not(.disabled):hover { +.monaco-editor.vs-dark .find-widget .button:not(.disabled):hover { background-color: rgba(255, 255, 255, 0.1); } @@ -286,7 +221,3 @@ top: 1px; left: 2px; } - -.monaco-editor.hc-black .find-widget .monaco-checkbox .checkbox:checked + .codicon-selection { - background-color: rgba(255, 255, 255, 0.1); -} diff --git a/src/vs/editor/contrib/find/findWidget.ts b/src/vs/editor/contrib/find/findWidget.ts index 7e664963c4..6c551f2fab 100644 --- a/src/vs/editor/contrib/find/findWidget.ts +++ b/src/vs/editor/contrib/find/findWidget.ts @@ -1341,20 +1341,10 @@ registerThemingParticipant((theme, collector) => { } } - const inputActiveBorder = theme.getColor(inputActiveOptionBorder); - if (inputActiveBorder) { - collector.addRule(`.monaco-editor .find-widget .monaco-checkbox .checkbox:checked + .label { border: 1px solid ${inputActiveBorder.toString()}; }`); - } - - const inputActiveBackground = theme.getColor(inputActiveOptionBackground); - if (inputActiveBackground) { - collector.addRule(`.monaco-editor .find-widget .monaco-checkbox .checkbox:checked + .label { background-color: ${inputActiveBackground.toString()}; }`); - } - // This rule is used to override the outline color for synthetic-focus find input. const focusOutline = theme.getColor(focusBorder); if (focusOutline) { - collector.addRule(`.monaco-workbench .monaco-editor .find-widget .monaco-inputbox.synthetic-focus { outline-color: ${focusOutline}; }`); + collector.addRule(`.monaco-editor .find-widget .monaco-inputbox.synthetic-focus { outline-color: ${focusOutline}; }`); } }); diff --git a/src/vs/editor/contrib/multicursor/test/multicursor.test.ts b/src/vs/editor/contrib/multicursor/test/multicursor.test.ts index 84aea22a8e..44250944fb 100644 --- a/src/vs/editor/contrib/multicursor/test/multicursor.test.ts +++ b/src/vs/editor/contrib/multicursor/test/multicursor.test.ts @@ -69,7 +69,8 @@ suite('Multicursor selection', () => { store: (key: string, value: any) => { queryState[key] = value; return Promise.resolve(); }, remove: (key) => undefined, logStorage: () => undefined, - migrate: (toWorkspace) => Promise.resolve(undefined) + migrate: (toWorkspace) => Promise.resolve(undefined), + flush: () => undefined } as IStorageService); test('issue #8817: Cursor position changes when you cancel multicursor', () => { diff --git a/src/vs/editor/contrib/suggest/suggestController.ts b/src/vs/editor/contrib/suggest/suggestController.ts index e1e7a785ab..4b6b58e531 100644 --- a/src/vs/editor/contrib/suggest/suggestController.ts +++ b/src/vs/editor/contrib/suggest/suggestController.ts @@ -259,20 +259,21 @@ export class SuggestController implements IEditorContribution { this.editor.pushUndoStop(); } - if (Array.isArray(suggestion.additionalTextEdits)) { - this.editor.executeEdits('suggestController.additionalTextEdits', suggestion.additionalTextEdits.map(edit => EditOperation.replace(Range.lift(edit.range), edit.text))); - } + // compute overwrite[Before|After] deltas BEFORE applying extra edits + const info = this.getOverwriteInfo(item, Boolean(flags & InsertFlags.AlternativeOverwriteConfig)); // keep item in memory this._memoryService.memorize(model, this.editor.getPosition(), item); + if (Array.isArray(suggestion.additionalTextEdits)) { + this.editor.executeEdits('suggestController.additionalTextEdits', suggestion.additionalTextEdits.map(edit => EditOperation.replace(Range.lift(edit.range), edit.text))); + } + let { insertText } = suggestion; if (!(suggestion.insertTextRules! & CompletionItemInsertTextRule.InsertAsSnippet)) { insertText = SnippetParser.escape(insertText); } - const info = this.getOverwriteInfo(item, Boolean(flags & InsertFlags.AlternativeOverwriteConfig)); - SnippetController2.get(this.editor).insert(insertText, { overwriteBefore: info.overwriteBefore, overwriteAfter: info.overwriteAfter, diff --git a/src/vs/editor/contrib/suggest/suggestWidget.ts b/src/vs/editor/contrib/suggest/suggestWidget.ts index 6cf2b36d43..fcd0fd41b9 100644 --- a/src/vs/editor/contrib/suggest/suggestWidget.ts +++ b/src/vs/editor/contrib/suggest/suggestWidget.ts @@ -4,6 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/suggest'; +import 'vs/base/browser/ui/codiconLabel/codiconLabel'; // The codicon symbol styles are defined here and must be loaded +import 'vs/editor/contrib/documentSymbols/outlineTree'; // The codicon symbol colors are defined here and must be loaded import * as nls from 'vs/nls'; import { createMatches } from 'vs/base/common/filters'; import * as strings from 'vs/base/common/strings'; diff --git a/src/vs/editor/contrib/suggest/test/suggestController.test.ts b/src/vs/editor/contrib/suggest/test/suggestController.test.ts new file mode 100644 index 0000000000..b889fc75b0 --- /dev/null +++ b/src/vs/editor/contrib/suggest/test/suggestController.test.ts @@ -0,0 +1,96 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { SuggestController } from 'vs/editor/contrib/suggest/suggestController'; +import { createTestCodeEditor, TestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; +import { TextModel } from 'vs/editor/common/model/textModel'; +import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; +import { IStorageService, InMemoryStorageService } from 'vs/platform/storage/common/storage'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { MockKeybindingService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; +import { ISuggestMemoryService } from 'vs/editor/contrib/suggest/suggestMemory'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { URI } from 'vs/base/common/uri'; +import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; +import { mock } from 'vs/editor/contrib/suggest/test/suggestModel.test'; +import { Selection } from 'vs/editor/common/core/selection'; +import { CompletionProviderRegistry, CompletionItemKind, CompletionItemInsertTextRule } from 'vs/editor/common/modes'; +import { Event } from 'vs/base/common/event'; +import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2'; + +suite('SuggestController', function () { + + const disposables = new DisposableStore(); + + let controller: SuggestController; + let editor: TestCodeEditor; + let model: TextModel; + + setup(function () { + disposables.clear(); + + const serviceCollection = new ServiceCollection( + [ITelemetryService, NullTelemetryService], + [IStorageService, new InMemoryStorageService()], + [IKeybindingService, new MockKeybindingService()], + [IEditorWorkerService, new class extends mock() { + computeWordRanges() { + return Promise.resolve({}); + } + }], + [ISuggestMemoryService, new class extends mock() { + memorize(): void { } + select(): number { return 0; } + }] + ); + + model = TextModel.createFromString('', undefined, undefined, URI.from({ scheme: 'test-ctrl', path: '/path.tst' })); + editor = createTestCodeEditor({ + model, + serviceCollection, + }); + + editor.registerAndInstantiateContribution(SnippetController2.ID, SnippetController2); + controller = editor.registerAndInstantiateContribution(SuggestController.ID, SuggestController); + }); + + test('postfix completion reports incorrect position #86984', async function () { + disposables.add(CompletionProviderRegistry.register({ scheme: 'test-ctrl' }, { + provideCompletionItems(doc, pos) { + return { + suggestions: [{ + kind: CompletionItemKind.Snippet, + label: 'let', + insertText: 'let ${1:name} = foo$0', + insertTextRules: CompletionItemInsertTextRule.InsertAsSnippet, + range: { startLineNumber: 1, startColumn: 9, endLineNumber: 1, endColumn: 11 }, + additionalTextEdits: [{ + text: '', + range: { startLineNumber: 1, startColumn: 5, endLineNumber: 1, endColumn: 9 } + }] + }] + }; + } + })); + + editor.setValue(' foo.le'); + editor.setSelection(new Selection(1, 11, 1, 11)); + + // trigger + let p1 = Event.toPromise(controller.model.onDidSuggest); + controller.triggerSuggest(); + await p1; + + // + let p2 = Event.toPromise(controller.model.onDidCancel); + controller.acceptSelectedSuggestion(false, false); + await p2; + + assert.equal(editor.getValue(), ' let name = foo'); + }); +}); diff --git a/src/vs/editor/standalone/browser/quickOpen/quickOutline.css b/src/vs/editor/standalone/browser/quickOpen/quickOutline.css index fc4d341821..faa0b540c8 100644 --- a/src/vs/editor/standalone/browser/quickOpen/quickOutline.css +++ b/src/vs/editor/standalone/browser/quickOpen/quickOutline.css @@ -6,95 +6,3 @@ .monaco-quick-open-widget { font-size: 13px; } - -.monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon, -.vs-dark .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon { - background-image: url('symbol-sprite.svg'); - background-repeat: no-repeat; - background-position: -2px -22px; -} - -.monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon, -.monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.method, -.monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.function, -.monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.constructor { background-position: -2px -2px; } -.monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.field, -.monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.variable { background-position: -22px -2px; } -.monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.class { background-position: -42px -2px; } -.monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.interface { background-position: -62px -2px; } -.monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.module { background-position: -82px -2px; } -.monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.property { background-position: -102px -2px; } -.monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.enum { background-position: -122px -2px; } -.monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.string { background-position: -202px -2px; } -.monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.rule { background-position: -242px -2px; } -.monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.file { background-position: -262px -2px; } - -.vs-dark .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.method, -.vs-dark .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.function, -.vs-dark .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.constructor { background-position: -2px -22px; } -.vs-dark .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.field, -.vs-dark .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.variable { background-position: -22px -22px; } -.vs-dark .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.class { background-position: -43px -22px; } -.vs-dark .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.interface { background-position: -63px -22px; } -.vs-dark .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.module { background-position: -82px -22px; } -.vs-dark .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.property { background-position: -102px -22px; } -.vs-dark .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.enum { background-position: -122px -22px; } -.vs-dark .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.string { background-position: -202px -22px; } -.vs-dark .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.rule { background-position: -242px -22px; } -.vs-dark .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.file { background-position: -262px -22px; } - -.hc-black .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon { - background: none; - display: inline; -} - -.hc-black .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon:before { - height: 16px; - width: 16px; - display: inline-block; -} - -.hc-black .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon:before, -.hc-black .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.method:before, -.hc-black .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.function:before, -.hc-black .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.constructor:before { - content: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0yIDQuODU3NDlMMi40ODU1IDRMNy40ODU1IDFIOC41MTQ1TDEzLjUxNDUgNEwxNCA0Ljg1NzQ5VjEwLjg1NzVMMTMuNTE0NSAxMS43MTVMOC41MTQ1IDE0LjcxNUg3LjQ4NTVMMi40ODU1IDExLjcxNUwyIDEwLjg1NzVWNC44NTc0OVpNNy41IDEzLjU1NzVMMyAxMC44NTc1VjUuNjk5NzVMNy41IDguMTU0M1YxMy41NTc1Wk04LjUgMTMuNTU3NUwxMyAxMC44NTc1VjUuNjk5NzVMOC41IDguMTU0M1YxMy41NTc1Wk04IDEuODU3NDlMMy4yNTkxMyA0LjcwMjAxTDggNy4yODc5NEwxMi43NDA5IDQuNzAyMDFMOCAxLjg1NzQ5WiIgZmlsbD0iI0IxODBENyIvPgo8L3N2Zz4K"); -} - -.hc-black .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.field:before, -.hc-black .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.variable:before { - content: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xIDYuMzk0NDNMMS41NTI3OSA1LjVMOC41NTI3OSAySDkuNDQ3MjFMMTQuNDQ3MiA0LjVMMTUgNS4zOTQ0M1Y5Ljg5NDQzTDE0LjQ0NzIgMTAuNzg4OUw3LjQ0NzIxIDE0LjI4ODlINi41NTI3OUwxLjU1Mjc5IDExLjc4ODlMMSAxMC44OTQ0VjYuMzk0NDNaTTYuNSAxMy4xNDQ0TDIgMTAuODk0NFY3LjE3MDk0TDYuNSA5LjIxNjM5VjEzLjE0NDRaTTcuNSAxMy4xNDQ0TDE0IDkuODk0NDNWNi4xNzk1NEw3LjUgOS4yMTI4N1YxMy4xNDQ0Wk05IDIuODk0NDNMMi4zMzcyOCA2LjIyNTc5TDYuOTk3MjUgOC4zNDM5NkwxMy42NzA2IDUuMjI5NzNMOSAyLjg5NDQzWiIgZmlsbD0iIzc1QkVGRiIvPgo8L3N2Zz4K"); -} - -.hc-black .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.class:before { - content: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0zLjM1MzU2IDYuNjQ2NDJMMi4wNjA2NiA1LjM1MzUzTDUuMzUzNTYgMi4wNjA2NUw2LjY0NjQ1IDMuMzUzNTRMMy4zNTM1NiA2LjY0NjQyWk01IDFMMSA0Ljk5OTk4VjUuNzA3MDhMMyA3LjcwNzA3SDMuNzA3MTFMNC44NTM1NSA2LjU2MDYzVjEyLjM1MzVMNS4zNTM1NSAxMi44NTM1SDEwLjAwOTdWMTMuMzc0MUwxMS4zNDMgMTQuNzA3NEgxMi4wNTAxTDE0LjcxNjggMTIuMDQwN1YxMS4zMzM2TDEzLjM4MzUgMTAuMDAwM0gxMi42NzYzTDEwLjgyMzEgMTEuODUzNUg1Ljg1MzU1VjcuODkzNTVIMTAuMDA5N1Y4LjM3NDAxTDExLjM0MyA5LjcwNzM0SDEyLjA1MDFMMTQuNzE2OCA3LjA0MDY4VjYuMzMzNTdMMTMuMzgzNSA1LjAwMDI0SDEyLjY3NjNMMTAuODYzIDYuODEzNTZINS44NTM1NVY1LjU2MDY0TDcuNzA3MTEgMy43MDcwOVYyLjk5OTk5TDUuNzA3MTEgMUg1Wk0xMS4wNzAzIDguMDIwNDZMMTEuNjk2NiA4LjY0NjY4TDEzLjY1NjEgNi42ODcxM0wxMy4wMjk5IDYuMDYwOUwxMS4wNzAzIDguMDIwNDZaTTExLjA3MDMgMTMuMDIwNUwxMS42OTY2IDEzLjY0NjdMMTMuNjU2MSAxMS42ODcyTDEzLjAyOTkgMTEuMDYxTDExLjA3MDMgMTMuMDIwNVoiIGZpbGw9IiNFRTlEMjgiLz4KPC9zdmc+Cg=="); -} - -.hc-black .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.interface:before { - content: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xMS41IDVDMTAuMTE5MyA1IDkgNi4xMTkyOSA5IDcuNUM5IDguODgwNzEgMTAuMTE5MyAxMCAxMS41IDEwQzEyLjg4MDcgMTAgMTQgOC44ODA3MSAxNCA3LjVDMTQgNi4xMTkyOSAxMi44ODA3IDUgMTEuNSA1Wk04LjAzNTQ0IDhDOC4yNzgwNiA5LjY5NjE1IDkuNzM2NzYgMTEgMTEuNSAxMUMxMy40MzMgMTEgMTUgOS40MzMgMTUgNy41QzE1IDUuNTY3IDEzLjQzMyA0IDExLjUgNEM5LjczNjc2IDQgOC4yNzgwNiA1LjMwMzg1IDguMDM1NDQgN0g0LjkzNjk5QzQuNzE0OTcgNi4xMzczOSAzLjkzMTkyIDUuNSAzIDUuNUMxLjg5NTQzIDUuNSAxIDYuMzk1NDMgMSA3LjVDMSA4LjYwNDU3IDEuODk1NDMgOS41IDMgOS41QzMuOTMxOTIgOS41IDQuNzE0OTcgOC44NjI2MSA0LjkzNjk5IDhIOC4wMzU0NFoiIGZpbGw9IiM3NUJFRkYiLz4KPC9zdmc+Cg=="); -} - -.hc-black .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.module:before { - content: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik02IDIuOTgzNjFWMi45NzE4NFYySDUuOTEwODNDNS41OTc0MyAyIDUuMjk0MDcgMi4wNjE2MSA1LjAwMTI4IDIuMTg0NzNDNC43MDgxOCAyLjMwNzk4IDQuNDQ5NDIgMi40ODQ3NCA0LjIyNTc4IDIuNzE0OThDNC4wMDMxMSAyLjk0NDIyIDMuODM3OTIgMy4xOTQ5OCAzLjczMjgyIDMuNDY3NjZMMy43MzIzMyAzLjQ2ODk4QzMuNjMzODIgMy43MzUyIDMuNTY4MTQgNC4wMTIwMSAzLjUzNTMzIDQuMjk5MTdMMy41MzUxOSA0LjMwMDUzQzMuNTA2NzggNC41ODA1IDMuNDk4NyA0Ljg2ODQ0IDMuNTEwODQgNS4xNjQyOEMzLjUyMjcyIDUuNDUzNzkgMy41Mjg2NiA1Ljc0MzI5IDMuNTI4NjYgNi4wMzI3OUMzLjUyODY2IDYuMjM1NTYgMy40ODk3NCA2LjQyNTk0IDMuNDEyIDYuNjA1MDdMMy40MTE2IDYuNjA2MDFDMy4zMzY4NyA2Ljc4Mjk2IDMuMjM0MjMgNi45Mzg2NiAzLjEwMzE3IDcuMDczNTlDMi45NzY0NCA3LjIwNDA1IDIuODI0NjYgNy4zMTA1NSAyLjY0NjcyIDcuMzkyNUMyLjQ3MDYgNy40Njk1NCAyLjI4NDk3IDcuNTA4MiAyLjA4OTE3IDcuNTA4MkgyVjcuNlY4LjRWOC40OTE4SDIuMDg5MTdDMi4yODQ2NSA4LjQ5MTggMi40NzAwMSA4LjUzMjM4IDIuNjQ2MDEgOC42MTMzNEwyLjY0NzQyIDguNjEzOTZDMi44MjQ1NyA4LjY5MTU3IDIuOTc1NzcgOC43OTc2MiAzLjEwMjIxIDguOTMxNjFMMy4xMDQxMiA4LjkzMzUyQzMuMjM0MjggOS4wNjM3IDMuMzM2NTkgOS4yMTg3MSAzLjQxMTI5IDkuMzk5NDJMMy40MTIwMSA5LjQwMTA4QzMuNDg5ODYgOS41ODA0NyAzLjUyODY2IDkuNzY4ODMgMy41Mjg2NiA5Ljk2NzIxQzMuNTI4NjYgMTAuMjU2NyAzLjUyMjcyIDEwLjU0NjIgMy41MTA4NCAxMC44MzU3QzMuNDk4NyAxMS4xMzE2IDMuNTA2NzcgMTEuNDIxNSAzLjUzNTE2IDExLjcwNTVMMy41MzUzNSAxMS43MDcyQzMuNTY4MTkgMTEuOTkwMyAzLjYzMzg3IDEyLjI2NSAzLjczMjMyIDEyLjUzMUwzLjczMjgzIDEyLjUzMjNDMy44Mzc5MyAxMi44MDUgNC4wMDMxMSAxMy4wNTU4IDQuMjI1NzggMTMuMjg1QzQuNDQ5NDIgMTMuNTE1MyA0LjcwODE4IDEzLjY5MiA1LjAwMTI4IDEzLjgxNTNDNS4yOTQwNyAxMy45Mzg0IDUuNTk3NDMgMTQgNS45MTA4MyAxNEg2VjEzLjJWMTMuMDE2NEg1LjkxMDgzQzUuNzEwOTUgMTMuMDE2NCA1LjUyMzQ2IDEyLjk3NzcgNS4zNDc2MyAxMi45MDA4QzUuMTczOTYgMTIuODE5MSA1LjAyMTk0IDEyLjcxMjYgNC44OTA4NiAxMi41ODE4QzQuNzYzODYgMTIuNDQ2OSA0LjY2MTA0IDEyLjI5MTEgNC41ODIyMyAxMi4xMTM3QzQuNTA4MzggMTEuOTM0NiA0LjQ3MTM0IDExLjc0NCA0LjQ3MTM0IDExLjU0MUM0LjQ3MTM0IDExLjMxMjcgNC40NzUzIDExLjA4ODUgNC40ODMyMSAxMC44Njg2QzQuNDkxMjUgMTAuNjQxMSA0LjQ5MTI3IDEwLjQxOTUgNC40ODMyNCAxMC4yMDM5QzQuNDc5MTQgOS45ODI0NiA0LjQ2MDg0IDkuNzY4ODMgNC40MjgyMyA5LjU2MzEyQzQuMzk1MTMgOS4zNTAyNCA0LjMzOTIxIDkuMTQ3NTcgNC4yNjAzOSA4Ljk1NTM2QzQuMTgwOTEgOC43NjE1NyA0LjA3MjU4IDguNTc3NDYgMy45MzYxNiA4LjQwMjk4QzMuODIzNDUgOC4yNTg4MSAzLjY4NTM4IDguMTI0NjIgMy41MjI4MyA4QzMuNjg1MzggNy44NzUzOCAzLjgyMzQ1IDcuNzQxMTkgMy45MzYxNiA3LjU5NzAyQzQuMDcyNTggNy40MjI1NCA0LjE4MDkxIDcuMjM4NDMgNC4yNjAzOSA3LjA0NDY0QzQuMzM5MTMgNi44NTI2MyA0LjM5NTEzIDYuNjUxNzUgNC40MjgyNiA2LjQ0Mjg1QzQuNDYwODIgNi4yMzMzIDQuNDc5MTQgNi4wMTk3MyA0LjQ4MzI0IDUuODAyMTlDNC40OTEyNyA1LjU4MjYyIDQuNDkxMjUgNS4zNjEwNSA0LjQ4MzIxIDUuMTM3NDlDNC40NzUzIDQuOTEzNCA0LjQ3MTM0IDQuNjg3MjUgNC40NzEzNCA0LjQ1OTAyQzQuNDcxMzQgNC4yNjAxOSA0LjUwODMzIDQuMDcxNTIgNC41ODIzOCAzLjg5MjA1QzQuNjYxMzUgMy43MTAzNCA0Ljc2NDIxIDMuNTU0NzUgNC44OTA4NiAzLjQyNDM3QzUuMDIxOTMgMy4yODk0MiA1LjE3NDYxIDMuMTgyNzUgNS4zNDgwMiAzLjEwNTEzQzUuNTIzOCAzLjAyNDI3IDUuNzExMTMgMi45ODM2MSA1LjkxMDgzIDIuOTgzNjFINlpNMTAgMTMuMDE2NFYxMy4wMjgyVjE0SDEwLjA4OTJDMTAuNDAyNiAxNCAxMC43MDU5IDEzLjkzODQgMTAuOTk4NyAxMy44MTUzQzExLjI5MTggMTMuNjkyIDExLjU1MDYgMTMuNTE1MyAxMS43NzQyIDEzLjI4NUMxMS45OTY5IDEzLjA1NTggMTIuMTYyMSAxMi44MDUgMTIuMjY3MiAxMi41MzIzTDEyLjI2NzcgMTIuNTMxQzEyLjM2NjIgMTIuMjY0OCAxMi40MzE5IDExLjk4OCAxMi40NjQ3IDExLjcwMDhMMTIuNDY0OCAxMS42OTk1QzEyLjQ5MzIgMTEuNDE5NSAxMi41MDEzIDExLjEzMTYgMTIuNDg5MiAxMC44MzU3QzEyLjQ3NzMgMTAuNTQ2MiAxMi40NzEzIDEwLjI1NjcgMTIuNDcxMyA5Ljk2NzIxQzEyLjQ3MTMgOS43NjQ0NCAxMi41MTAzIDkuNTc0MDYgMTIuNTg4IDkuMzk0OTNMMTIuNTg4NCA5LjM5Mzk5QzEyLjY2MzEgOS4yMTcwNCAxMi43NjU4IDkuMDYxMzQgMTIuODk2OCA4LjkyNjQyQzEzLjAyMzYgOC43OTU5NSAxMy4xNzUzIDguNjg5NDUgMTMuMzUzMyA4LjYwNzVDMTMuNTI5NCA4LjUzMDQ2IDEzLjcxNSA4LjQ5MTggMTMuOTEwOCA4LjQ5MThIMTRWOC40VjcuNlY3LjUwODJIMTMuOTEwOEMxMy43MTUzIDcuNTA4MiAxMy41MyA3LjQ2NzYyIDEzLjM1NCA3LjM4NjY2TDEzLjM1MjYgNy4zODYwNEMxMy4xNzU0IDcuMzA4NDQgMTMuMDI0MiA3LjIwMjM4IDEyLjg5NzggNy4wNjgzOUwxMi44OTU5IDcuMDY2NDhDMTIuNzY1NyA2LjkzNjMgMTIuNjYzNCA2Ljc4MTI5IDEyLjU4ODcgNi42MDA1OEwxMi41ODggNi41OTg5MkMxMi41MTAxIDYuNDE5NTMgMTIuNDcxMyA2LjIzMTE3IDEyLjQ3MTMgNi4wMzI3OUMxMi40NzEzIDUuNzQzMjkgMTIuNDc3MyA1LjQ1Mzc5IDEyLjQ4OTIgNS4xNjQyOEMxMi41MDEzIDQuODY4NDIgMTIuNDkzMiA0LjU3ODQ4IDEyLjQ2NDggNC4yOTQ1NEwxMi40NjQ2IDQuMjkyODVDMTIuNDMxOCA0LjAwOTcxIDEyLjM2NjEgMy43MzUwMiAxMi4yNjc3IDMuNDY4OTdMMTIuMjY3MiAzLjQ2NzY2QzEyLjE2MjEgMy4xOTQ5OSAxMS45OTY5IDIuOTQ0MjIgMTEuNzc0MiAyLjcxNDk4QzExLjU1MDYgMi40ODQ3NCAxMS4yOTE4IDIuMzA3OTggMTAuOTk4NyAyLjE4NDczQzEwLjcwNTkgMi4wNjE2MSAxMC40MDI2IDIgMTAuMDg5MiAySDEwVjIuOFYyLjk4MzYxSDEwLjA4OTJDMTAuMjg5MSAyLjk4MzYxIDEwLjQ3NjUgMy4wMjIzIDEwLjY1MjQgMy4wOTkxN0MxMC44MjYgMy4xODA5MiAxMC45NzgxIDMuMjg3MzYgMTEuMTA5MSAzLjQxODIzQzExLjIzNjEgMy41NTMwNSAxMS4zMzkgMy43MDg4OSAxMS40MTc4IDMuODg2MjhDMTEuNDkxNiA0LjA2NTQgMTEuNTI4NyA0LjI1NTk2IDExLjUyODcgNC40NTkwMkMxMS41Mjg3IDQuNjg3MjcgMTEuNTI0NyA0LjkxMTQ1IDExLjUxNjggNS4xMzE0MkMxMS41MDg4IDUuMzU4OTQgMTEuNTA4NyA1LjU4MDQ5IDExLjUxNjggNS43OTYwNUMxMS41MjA5IDYuMDE3NTQgMTEuNTM5MiA2LjIzMTE3IDExLjU3MTggNi40MzY4OEMxMS42MDQ5IDYuNjQ5NzYgMTEuNjYwOCA2Ljg1MjQzIDExLjczOTYgNy4wNDQ2NEMxMS44MTkxIDcuMjM4NDMgMTEuOTI3NCA3LjQyMjU0IDEyLjA2MzggNy41OTcwMkMxMi4xNzY1IDcuNzQxMTkgMTIuMzE0NiA3Ljg3NTM4IDEyLjQ3NzIgOEMxMi4zMTQ2IDguMTI0NjIgMTIuMTc2NSA4LjI1ODgxIDEyLjA2MzggOC40MDI5OEMxMS45Mjc0IDguNTc3NDYgMTEuODE5MSA4Ljc2MTU3IDExLjczOTYgOC45NTUzNkMxMS42NjA5IDkuMTQ3MzcgMTEuNjA0OSA5LjM0ODI1IDExLjU3MTcgOS41NTcxNUMxMS41MzkyIDkuNzY2NyAxMS41MjA5IDkuOTgwMjcgMTEuNTE2OCAxMC4xOTc4QzExLjUwODcgMTAuNDE3NCAxMS41MDg3IDEwLjYzODkgMTEuNTE2OCAxMC44NjI1QzExLjUyNDcgMTEuMDg2NiAxMS41Mjg3IDExLjMxMjggMTEuNTI4NyAxMS41NDFDMTEuNTI4NyAxMS43Mzk4IDExLjQ5MTcgMTEuOTI4NSAxMS40MTc2IDEyLjEwNzlDMTEuMzM4NiAxMi4yODk3IDExLjIzNTggMTIuNDQ1MiAxMS4xMDkxIDEyLjU3NTZDMTAuOTc4MSAxMi43MTA2IDEwLjgyNTQgMTIuODE3MyAxMC42NTIgMTIuODk0OUMxMC40NzYyIDEyLjk3NTcgMTAuMjg4OSAxMy4wMTY0IDEwLjA4OTIgMTMuMDE2NEgxMFoiIGZpbGw9IiNDNUM1QzUiLz4KPC9zdmc+Cg=="); -} - -.hc-black .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.property:before { - content: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTIuODA3MjMgMTQuOTc1NEMyLjU3MTE5IDE0Ljk3MjEgMi4zMzgyNiAxNC45MjExIDIuMTIyNDcgMTQuODI1NEMxLjkwNjY3IDE0LjcyOTcgMS43MTI0OCAxNC41OTEzIDEuNTUxNTggMTQuNDE4NkMxLjIzODUgMTQuMTMzNCAxLjA0NDMzIDEzLjc0MDggMS4wMDc3NSAxMy4zMTg5QzAuOTY2MjI1IDEyLjg4MjggMS4wOTI2OSAxMi40NDczIDEuMzYxMzMgMTIuMTAxM0MyLjU2Nzc5IDEwLjgyODkgNC45NDczIDguNDQ5NCA2LjY3ODExIDYuNzU0NzlDNi4zMDk4MyA1Ljc1ODg3IDYuMzI3MDQgNC42NjEyNyA2LjcyNjM3IDMuNjc3MzlDNy4wNTQ3NCAyLjg1ODc2IDcuNjM4NjkgMi4xNjgwNSA4LjM5MTI5IDEuNzA4MDdDOC45ODE3IDEuMzE3MDYgOS42NjAzMSAxLjA3OTQ0IDEwLjM2NTcgMS4wMTY3M0MxMS4wNzExIDAuOTU0MDIyIDExLjc4MDkgMS4wNjgxOSAxMi40MzExIDEuMzQ4OTJMMTMuMDQ4MiAxLjYxNjJMMTAuMTgyNCA0LjU2NzM4TDExLjQzNzEgNS44MjU4MkwxNC4zODA5IDIuOTQ4ODdMMTQuNjQ4MiAzLjU2Nzg4QzE0Ljg3MzUgNC4wODk3NiAxNC45OTMgNC42NTExOSAxNC45OTk3IDUuMjE5NjFDMTUuMDA2NCA1Ljc4ODAyIDE0LjkwMDIgNi4zNTIxMSAxNC42ODcyIDYuODc5MTVDMTQuNDc2IDcuNDAwMjkgMTQuMTYyMyA3Ljg3MzY4IDEzLjc2NDcgOC4yNzEyMkMxMy41Mzk0IDguNDkxNjkgMTMuMjkwNCA4LjY4NjUzIDEzLjAyMjIgOC44NTIxOEMxMi40NjczIDkuMjIyNzUgMTEuODMyNCA5LjQ1NjM2IDExLjE2OTcgOS41MzM4QzEwLjUwNjkgOS42MTEyNCA5LjgzNTIxIDkuNTMwMyA5LjIwOTgyIDkuMjk3NjRDOC4xMTE5NCAxMC40MTEzIDUuMzcxNDIgMTMuMTcwNCAzLjg5MTE5IDE0LjU1MjJDMy41OTQyNiAxNC44MjE5IDMuMjA4MzIgMTQuOTcyNiAyLjgwNzIzIDE0Ljk3NTRaTTEwLjc0NDggMS45MjgwMkMxMC4wODcgMS45MjYzNyA5LjQ0MzU5IDIuMTIwMTggOC44OTYxNCAyLjQ4NDg1QzguNjgyNjUgMi42MTUyIDguNDg0MzcgMi43Njg5NyA4LjMwNDk4IDIuOTQzM0M3LjgyNzg5IDMuNDI0MjMgNy41MDkyNiA0LjAzOTUzIDcuMzkxODIgNC43MDY2OUM3LjI3NDM4IDUuMzczODUgNy4zNjM3NCA2LjA2MDk4IDcuNjQ3OTIgNi42NzU5MUw3Ljc4MzQyIDYuOTcyODhMNy41NTA0OCA3LjIwMDI1QzUuODEyMjQgOC44OTY3MiAzLjI4MTQ2IDExLjQyMDEgMi4wNjQ3OSAxMi43MDQ1QzEuOTU2NDYgMTIuODY1OCAxLjkxMDEyIDEzLjA2MDggMS45MzQzNSAxMy4yNTM1QzEuOTU4NTcgMTMuNDQ2MyAyLjA1MTcxIDEzLjYyMzggMi4xOTY1NyAxMy43NTMyQzIuMjgwMDUgMTMuODQ2MiAyLjM4MTc3IDEzLjkyMTEgMi40OTU0MSAxMy45NzMxQzIuNTk1NTcgMTQuMDE4NCAyLjcwMzgzIDE0LjA0MyAyLjgxMzczIDE0LjA0NTVDMi45ODA2NCAxNC4wNDEzIDMuMTQwNDQgMTMuOTc3IDMuMjYzODMgMTMuODY0NkM0LjgzNjg3IDEyLjM5NjQgNy44NzYyMiA5LjMyNjQxIDguNzY4MDcgOC40MjQzNUw4Ljk5NzMgOC4xOTMyNkw5LjI5MjQyIDguMzI3ODNDOS44MDYxNyA4LjU2NzMyIDEwLjM3MzEgOC42Njk4NSAxMC45MzgyIDguNjI1NDVDMTEuNTAzMyA4LjU4MTA2IDEyLjA0NzMgOC4zOTEyNSAxMi41MTc0IDguMDc0NDdDMTIuNzMxMyA3Ljk0MjYgMTIuOTI5NiA3Ljc4Njk0IDEzLjEwODUgNy42MTA0NUMxMy40MTgzIDcuMzAxNTMgMTMuNjYzMSA2LjkzMzc0IDEzLjgyODYgNi41Mjg3NEMxMy45OTQgNi4xMjM3NSAxNC4wNzY3IDUuNjg5NzQgMTQuMDcxOSA1LjI1MjI4QzE0LjA3MTkgNS4wMzY2MiAxNC4wNTA1IDQuODIxNDggMTQuMDA3OCA0LjYxMDA3TDExLjQzMDYgNy4xMjUwOEw4Ljg3OTQ0IDQuNTc3NTlMMTEuMzk0NCAxLjk4ODM0QzExLjE4MDQgMS45NDY3NCAxMC45NjI4IDEuOTI2NTMgMTAuNzQ0OCAxLjkyODAyWiIgZmlsbD0iI0M1QzVDNSIvPgo8L3N2Zz4K"); -} - -.hc-black .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.value:before, -.hc-black .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.enum:before { - content: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik04IDJMNyAzVjZIOFYzSDE0VjhIMTBWOUgxNEwxNSA4VjNMMTQgMkg4Wk05IDhMOCA3SDdIMkwxIDhWMTNMMiAxNEg4TDkgMTNWOVY4Wk04IDlWOEg3SDJWMTNIOFY5Wk05IDYuNTg1NzlMOS40MTQyMSA3SDEzVjZIOVY2LjU4NTc5Wk0xMyA0SDlWNUgxM1Y0Wk03IDlIM1YxMEg3VjlaTTMgMTFIN1YxMkgzVjExWiIgZmlsbD0iI0VFOUQyOCIvPgo8L3N2Zz4K"); -} - -.hc-black .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.rule:before { - content: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik00IDFMMyAyVjE0TDQgMTVIMTJMMTMgMTRWMkwxMiAxSDRaTTQgM1YySDEyVjE0SDRWMTNINlYxMkg0VjEwSDhWOUg0VjdINlY2SDRWNEg4VjNINFoiIGZpbGw9IiNDNUM1QzUiLz4KPC9zdmc+Cg=="); -} - -.hc-black .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.file:before { - content: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik00IDFMMyAyVjE0TDQgMTVIMTNMMTQgMTRWNUwxMy43MDcxIDQuMjkyODlMMTAuNzA3MSAxLjI5Mjg5TDEwIDFINFpNNCAxNFYyTDkgMlY2SDEzVjE0SDRaTTEzIDVMMTAgMlY1TDEzIDVaIiBmaWxsPSIjQzVDNUM1Ii8+Cjwvc3ZnPgo="); -} - -.hc-black .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.string:before { - content: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik03LjIyMjg5IDEwLjkzM0M3LjU0ODYzIDExLjEyNTQgNy45MjE2MyAxMS4yMjMxIDguMjk5ODkgMTEuMjE1QzguNjM3NzcgMTEuMjIxOCA4Ljk3MjU0IDExLjE0OTIgOS4yNzcyMSAxMS4wMDNDOS41ODE4OCAxMC44NTY3IDkuODQ3OTIgMTAuNjQwOSAxMC4wNTM5IDEwLjM3M0MxMC41MDkxIDkuNzY1MTkgMTAuNzQwMiA5LjAxODY3IDEwLjcwNzkgOC4yNTk5OEMxMC43NDEyIDcuNTg2MjIgMTAuNTM3NCA2LjkyMjEgMTAuMTMxOSA2LjM4Mjk4QzkuOTM1NzUgNi4xNDE2MSA5LjY4NTc3IDUuOTQ5NTcgOS40MDIgNS44MjIyOEM5LjExODI0IDUuNjk0OTggOC44MDg1OCA1LjYzNTk3IDguNDk3ODkgNS42NDk5N0M4LjA3NTIyIDUuNjQ2OTkgNy42NTk5NCA1Ljc2MDg1IDcuMjk3ODkgNS45Nzg5OEM3LjE4MzA0IDYuMDQ4MDcgNy4wNzQ5IDYuMTI3NzUgNi45NzQ4OSA2LjIxNjk4VjMuNDc0OThINS45ODM4OVYxMS4xSDYuOTc4ODlWMTAuNzU2QzcuMDU1MTYgMTAuODIxNyA3LjEzNjc3IDEwLjg4MDkgNy4yMjI4OSAxMC45MzNaTTcuODQ5ODEgNi43MDAwNkM4LjAzNTk4IDYuNjIxMDUgOC4yMzgwNyA2LjU4Njc3IDguNDM5ODkgNi41OTk5OEM4LjYxMjU3IDYuNTk0NTIgOC43ODQwNCA2LjYzMDU0IDguOTM5OTQgNi43MDUwMUM5LjA5NTgzIDYuNzc5NDggOS4yMzE2MSA2Ljg5MDIzIDkuMzM1ODkgNy4wMjc5OEM5LjU5MjUzIDcuMzkwNTMgOS43MTg0IDcuODI5NTEgOS42OTI4OSA4LjI3Mjk3QzkuNzE5NzIgOC43OTc0OCA5LjU3OTY5IDkuMzE3MDEgOS4yOTI4OSA5Ljc1Njk4QzkuMTg4MjIgOS45MTUyNyA5LjA0NTQ2IDEwLjA0NDcgOC44Nzc3MyAxMC4xMzM1QzguNzA5OTkgMTAuMjIyMyA4LjUyMjY0IDEwLjI2NzUgOC4zMzI4OSAxMC4yNjVDOC4xNDkzNCAxMC4yNzMyIDcuOTY2MyAxMC4yNCA3Ljc5NzM0IDEwLjE2NzhDNy42MjgzOCAxMC4wOTU2IDcuNDc3ODQgOS45ODYyOCA3LjM1Njg5IDkuODQ3OTdDNy4xMDE1MiA5LjU1OTU3IDYuOTY1MDEgOS4xODUwNiA2Ljk3NDg5IDguNzk5OThWOC4xOTk5OEM2Ljk2Mjk5IDcuNzgzMzIgNy4xMDI2MyA3LjM3NjUgNy4zNjc4OSA3LjA1NDk4QzcuNDk4NTggNi45MDA2NCA3LjY2MzY0IDYuNzc5MDggNy44NDk4MSA2LjcwMDA2Wk0zLjI4OTAyIDUuNjc0OTlDMi45NzAxMSA1LjY3OTMzIDIuNjUzODggNS43MzQgMi4zNTIwMiA1LjgzNjk5QzIuMDY0MTcgNS45MjI5MyAxLjc5MzQ3IDYuMDU4MjggMS41NTIwMiA2LjIzNjk5TDEuNDUyMDIgNi4zMTM5OVY3LjUxMzk5TDEuODc1MDIgNy4xNTQ5OUMyLjI0NTc5IDYuODA0NzggMi43MzEzMyA2LjYwMTQ2IDMuMjQxMDIgNi41ODI5OUMzLjM2NTkzIDYuNTcxNjQgMy40OTE3IDYuNTkxNDcgMy42MDcwNiA2LjY0MDY4QzMuNzIyNDMgNi42ODk5IDMuODIzNzcgNi43NjY5NyAzLjkwMjAyIDYuODY0OTlDNC4wNTIyIDcuMDk3MSA0LjEzMjM5IDcuMzY3NTQgNC4xMzMwMiA3LjY0Mzk5TDIuOTAwMDIgNy44MjQ5OUMyLjM5NDM1IDcuODc3ODEgMS45MTUyNSA4LjA3NzcyIDEuNTIyMDIgOC4zOTk5OUMxLjM2Njk3IDguNTUxODEgMS4yNDMzOSA4LjczMjcxIDEuMTU4MzUgOC45MzIzNUMxLjA3MzMxIDkuMTMxOTkgMS4wMjg0OCA5LjM0NjQ0IDEuMDI2NDQgOS41NjM0M0MxLjAyNDQgOS43ODA0MiAxLjA2NTE3IDkuOTk1NjggMS4xNDY0NCAxMC4xOTY5QzEuMjI3NyAxMC4zOTgxIDEuMzQ3ODYgMTAuNTgxMyAxLjUwMDAyIDEwLjczNkMxLjY2ODcgMTAuODkwNCAxLjg2NjIyIDExLjAxIDIuMDgxMjUgMTEuMDg3OUMyLjI5NjI3IDExLjE2NTkgMi41MjQ1NiAxMS4yMDA1IDIuNzUzMDIgMTEuMTlDMy4xNDcgMTEuMTkzMSAzLjUzMjc4IDExLjA3NzQgMy44NjAwMiAxMC44NThDMy45NjE1MyAxMC43ODk3IDQuMDU3MiAxMC43MTMxIDQuMTQ2MDIgMTAuNjI5VjExLjA3M0g1LjA4NzAyVjcuNzE0OTlDNS4xMjEzNyA3LjE3NDIyIDQuOTU0MyA2LjYzOTg4IDQuNjE4MDIgNi4yMTQ5OUM0LjQ0OTc5IDYuMDMyODUgNC4yNDM0OCA1Ljg5MDAzIDQuMDEzNzggNS43OTY3QzMuNzg0MDcgNS43MDMzNiAzLjUzNjYxIDUuNjYxODEgMy4yODkwMiA1LjY3NDk5Wk00LjE0NjAyIDguNzE1OTlDNC4xNjU2NCA5LjEzNDM1IDQuMDI1OTIgOS41NDQ1OSAzLjc1NTAyIDkuODY0QzMuNjM2ODkgMTAuMDAwNSAzLjQ4OTk4IDEwLjEwOTIgMy4zMjQ4NiAxMC4xODIxQzMuMTU5NzMgMTAuMjU1MSAyLjk4MDQ5IDEwLjI5MDYgMi44MDAwMiAxMC4yODZDMi42OTA0OSAxMC4yOTQ1IDIuNTgwMzUgMTAuMjgxMiAyLjQ3NTk5IDEwLjI0NjlDMi4zNzE2MyAxMC4yMTI1IDIuMjc1MTEgMTAuMTU3OSAyLjE5MjAyIDEwLjA4NkMyLjA2MDc5IDkuOTM0NTUgMS45ODg1NiA5Ljc0MDg4IDEuOTg4NTYgOS41NDA0OUMxLjk4ODU2IDkuMzQwMTEgMi4wNjA3OSA5LjE0NjQ0IDIuMTkyMDIgOC45OTQ5OUMyLjQ3MzIyIDguODIxMzEgMi43OTIzMyA4LjcxODM3IDMuMTIyMDIgOC42OTQ5OUw0LjE0MjAyIDguNTQ2OTlMNC4xNDYwMiA4LjcxNTk5Wk0xMi40NTg4IDExLjAzMjVDMTIuNzY2IDExLjE2MzggMTMuMDk4MyAxMS4yMjYxIDEzLjQzMjIgMTEuMjE1QzEzLjkyNyAxMS4yMjcgMTQuNDE1MyAxMS4xMDA2IDE0Ljg0MjIgMTAuODVMMTQuOTY1MiAxMC43NzVMMTQuOTc4MiAxMC43NjhWOS42MTUwNEwxNC41MzIyIDkuOTM1MDRDMTQuMjE2IDEwLjE1OTIgMTMuODM1NiAxMC4yNzQ3IDEzLjQ0ODIgMTAuMjY0QzEzLjI0OTcgMTAuMjcxOSAxMy4wNTIgMTAuMjM0MiAxMi44NzAzIDEwLjE1MzhDMTIuNjg4NiAxMC4wNzMzIDEyLjUyNzggOS45NTIzMiAxMi40MDAyIDkuODAwMDRDMTIuMTE0NCA5LjQyNDUzIDExLjk3MjUgOC45NTkxMSAxMi4wMDAyIDguNDg4MDRDMTEuOTczNyA3Ljk4NzMyIDEyLjEzNTIgNy40OTQ3NSAxMi40NTMyIDcuMTA3MDRDMTIuNTkzNCA2Ljk0MTA1IDEyLjc2OTUgNi44MDkxNCAxMi45NjgyIDYuNzIxM0MxMy4xNjcgNi42MzM0NiAxMy4zODMxIDYuNTkyIDEzLjYwMDIgNi42MDAwNEMxMy45NDM5IDYuNTk4NDQgMTQuMjgwOCA2LjY5NTI1IDE0LjU3MTIgNi44NzkwNEwxNS4wMDAyIDcuMTQ0MDRWNS45NzAwNEwxNC44MzEyIDUuODk3MDRDMTQuNDYyNiA1LjczNDMyIDE0LjA2NDEgNS42NTAyIDEzLjY2MTIgNS42NTAwNEMxMy4yOTk5IDUuNjM5OTEgMTIuOTQwNiA1LjcwNzYyIDEyLjYwNzggNS44NDg1OUMxMi4yNzQ5IDUuOTg5NTYgMTEuOTc2MyA2LjIwMDQ4IDExLjczMjIgNi40NjcwNEMxMS4yMjYxIDcuMDI2ODMgMTAuOTU4MSA3Ljc2MTg2IDEwLjk4NTIgOC41MTYwNEMxMC45NTY3IDkuMjIzNDYgMTEuMTk1NSA5LjkxNTY5IDExLjY1NDIgMTAuNDU1QzExLjg3NjkgMTAuNzA0IDEyLjE1MTYgMTAuOTAxMiAxMi40NTg4IDExLjAzMjVaIiBmaWxsPSIjQzVDNUM1Ii8+Cjwvc3ZnPgo="); -} diff --git a/src/vs/editor/standalone/browser/quickOpen/quickOutline.ts b/src/vs/editor/standalone/browser/quickOpen/quickOutline.ts index f7363952ac..5fee1d41f0 100644 --- a/src/vs/editor/standalone/browser/quickOpen/quickOutline.ts +++ b/src/vs/editor/standalone/browser/quickOpen/quickOutline.ts @@ -4,6 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./quickOutline'; +import 'vs/base/browser/ui/codiconLabel/codiconLabel'; // The codicon symbol styles are defined here and must be loaded +import 'vs/editor/contrib/documentSymbols/outlineTree'; // The codicon symbol colors are defined here and must be loaded import { CancellationToken } from 'vs/base/common/cancellation'; import { matchesFuzzy } from 'vs/base/common/filters'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; diff --git a/src/vs/editor/standalone/browser/quickOpen/symbol-sprite.svg b/src/vs/editor/standalone/browser/quickOpen/symbol-sprite.svg deleted file mode 100644 index 62449987c8..0000000000 --- a/src/vs/editor/standalone/browser/quickOpen/symbol-sprite.svg +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/vs/editor/standalone/browser/simpleServices.ts b/src/vs/editor/standalone/browser/simpleServices.ts index d0d48b0119..d2fec79f28 100644 --- a/src/vs/editor/standalone/browser/simpleServices.ts +++ b/src/vs/editor/standalone/browser/simpleServices.ts @@ -23,7 +23,7 @@ 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 { IResourceConfigurationService, ITextResourcePropertiesService, IResourceConfigurationChangeEvent } from 'vs/editor/common/services/resourceConfiguration'; +import { ITextResourceConfigurationService, ITextResourcePropertiesService, ITextResourceConfigurationChangeEvent } from 'vs/editor/common/services/textResourceConfigurationService'; import { CommandsRegistry, ICommand, ICommandEvent, ICommandHandler, ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationChangeEvent, IConfigurationData, IConfigurationOverrides, IConfigurationService, IConfigurationModel, IConfigurationValue, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { Configuration, ConfigurationModel, DefaultConfigurationModel } from 'vs/platform/configuration/common/configurationModels'; @@ -312,30 +312,29 @@ export class StandaloneKeybindingService extends AbstractKeybindingService { public addDynamicKeybinding(commandId: string, _keybinding: number, handler: ICommandHandler, when: ContextKeyExpr | undefined): IDisposable { const keybinding = createKeybinding(_keybinding, OS); - if (!keybinding) { - throw new Error(`Invalid keybinding`); - } const toDispose = new DisposableStore(); - this._dynamicKeybindings.push({ - keybinding: keybinding, - command: commandId, - when: when, - weight1: 1000, - weight2: 0 - }); + if (keybinding) { + this._dynamicKeybindings.push({ + keybinding: keybinding, + command: commandId, + when: when, + weight1: 1000, + weight2: 0 + }); - toDispose.add(toDisposable(() => { - for (let i = 0; i < this._dynamicKeybindings.length; i++) { - let kb = this._dynamicKeybindings[i]; - if (kb.command === commandId) { - this._dynamicKeybindings.splice(i, 1); - this.updateResolver({ source: KeybindingSource.Default }); - return; + toDispose.add(toDisposable(() => { + for (let i = 0; i < this._dynamicKeybindings.length; i++) { + let kb = this._dynamicKeybindings[i]; + if (kb.command === commandId) { + this._dynamicKeybindings.splice(i, 1); + this.updateResolver({ source: KeybindingSource.Default }); + return; + } } - } - })); + })); + } let commandService = this._commandService; if (commandService instanceof StandaloneCommandService) { @@ -487,11 +486,11 @@ export class SimpleConfigurationService implements IConfigurationService { } } -export class SimpleResourceConfigurationService implements IResourceConfigurationService { +export class SimpleResourceConfigurationService implements ITextResourceConfigurationService { _serviceBrand: undefined; - private readonly _onDidChangeConfiguration = new Emitter(); + private readonly _onDidChangeConfiguration = new Emitter(); public readonly onDidChangeConfiguration = this._onDidChangeConfiguration.event; constructor(private readonly configurationService: SimpleConfigurationService) { diff --git a/src/vs/editor/standalone/browser/standaloneServices.ts b/src/vs/editor/standalone/browser/standaloneServices.ts index 2653909b2a..ab3ef17c9f 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 { IResourceConfigurationService, ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; +import { ITextResourceConfigurationService, ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; 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(IResourceConfigurationService, () => new SimpleResourceConfigurationService(configurationServiceImpl)); + export const resourceConfigurationService = define(ITextResourceConfigurationService, () => new SimpleResourceConfigurationService(configurationServiceImpl)); export const resourcePropertiesService = define(ITextResourcePropertiesService, () => new SimpleResourcePropertiesService(configurationServiceImpl)); diff --git a/src/vs/editor/test/common/services/modelService.test.ts b/src/vs/editor/test/common/services/modelService.test.ts index 5d8d22e7a6..9071b8e372 100644 --- a/src/vs/editor/test/common/services/modelService.test.ts +++ b/src/vs/editor/test/common/services/modelService.test.ts @@ -13,7 +13,7 @@ import { createStringBuilder } from 'vs/editor/common/core/stringBuilder'; import { DefaultEndOfLine } from 'vs/editor/common/model'; import { TextModel, createTextBuffer } from 'vs/editor/common/model/textModel'; import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; -import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; +import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; 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'; diff --git a/src/vs/editor/test/common/services/resourceConfigurationService.test.ts b/src/vs/editor/test/common/services/textResourceConfigurationService.test.ts similarity index 80% rename from src/vs/editor/test/common/services/resourceConfigurationService.test.ts rename to src/vs/editor/test/common/services/textResourceConfigurationService.test.ts index 001ce0b9c9..0278d5e1bd 100644 --- a/src/vs/editor/test/common/services/resourceConfigurationService.test.ts +++ b/src/vs/editor/test/common/services/textResourceConfigurationService.test.ts @@ -9,7 +9,7 @@ import { TestInstantiationService } from 'vs/platform/instantiation/test/common/ 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 { TextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationServiceImpl'; import { URI } from 'vs/base/common/uri'; @@ -52,9 +52,9 @@ suite('TextResourceConfigurationService - Update', () => { test('updateValue writes into given memory target without overrides', async () => { language = 'a'; configurationValue = { - defaultTarget: { value: '1' }, - userLocalTarget: { value: '2' }, - workspaceFolderTarget: { value: '1' }, + default: { value: '1' }, + userLocal: { value: '2' }, + workspaceFolder: { value: '1' }, }; const resource = URI.file('someFile'); @@ -65,9 +65,9 @@ suite('TextResourceConfigurationService - Update', () => { test('updateValue writes into given workspace target without overrides', async () => { language = 'a'; configurationValue = { - defaultTarget: { value: '1' }, - userLocalTarget: { value: '2' }, - workspaceFolderTarget: { value: '2' }, + default: { value: '1' }, + userLocal: { value: '2' }, + workspaceFolder: { value: '2' }, }; const resource = URI.file('someFile'); @@ -78,9 +78,9 @@ suite('TextResourceConfigurationService - Update', () => { test('updateValue writes into given user target without overrides', async () => { language = 'a'; configurationValue = { - defaultTarget: { value: '1' }, - userLocalTarget: { value: '2' }, - workspaceFolderTarget: { value: '2' }, + default: { value: '1' }, + userLocal: { value: '2' }, + workspaceFolder: { value: '2' }, }; const resource = URI.file('someFile'); @@ -91,9 +91,9 @@ suite('TextResourceConfigurationService - Update', () => { 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' }, + default: { value: '1' }, + userLocal: { value: '2' }, + workspaceFolder: { value: '2', override: '1' }, }; const resource = URI.file('someFile'); @@ -104,9 +104,9 @@ suite('TextResourceConfigurationService - Update', () => { test('updateValue writes into derived workspace folder target without overrides', async () => { language = 'a'; configurationValue = { - defaultTarget: { value: '1' }, - userLocalTarget: { value: '2' }, - workspaceFolderTarget: { value: '2' }, + default: { value: '1' }, + userLocal: { value: '2' }, + workspaceFolder: { value: '2' }, }; const resource = URI.file('someFile'); @@ -117,10 +117,10 @@ suite('TextResourceConfigurationService - Update', () => { 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' }, + default: { value: '1' }, + userLocal: { value: '2' }, + workspace: { value: '2', override: '1' }, + workspaceFolder: { value: '2', override: '2' }, }; const resource = URI.file('someFile'); @@ -131,9 +131,9 @@ suite('TextResourceConfigurationService - Update', () => { test('updateValue writes into derived workspace target without overrides', async () => { language = 'a'; configurationValue = { - defaultTarget: { value: '1' }, - userLocalTarget: { value: '2' }, - workspaceTarget: { value: '2' }, + default: { value: '1' }, + userLocal: { value: '2' }, + workspace: { value: '2' }, }; const resource = URI.file('someFile'); @@ -144,9 +144,9 @@ suite('TextResourceConfigurationService - Update', () => { test('updateValue writes into derived workspace target with overrides', async () => { language = 'a'; configurationValue = { - defaultTarget: { value: '1' }, - userLocalTarget: { value: '2' }, - workspaceTarget: { value: '2', override: '2' }, + default: { value: '1' }, + userLocal: { value: '2' }, + workspace: { value: '2', override: '2' }, }; const resource = URI.file('someFile'); @@ -157,10 +157,10 @@ suite('TextResourceConfigurationService - Update', () => { 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' }, + default: { value: '1', override: '3' }, + userLocal: { value: '2' }, + workspace: { value: '2', override: '2' }, + workspaceFolder: { value: '2' }, }; const resource = URI.file('someFile'); @@ -171,9 +171,9 @@ suite('TextResourceConfigurationService - Update', () => { test('updateValue writes into derived user remote target without overrides', async () => { language = 'a'; configurationValue = { - defaultTarget: { value: '1' }, - userLocalTarget: { value: '2' }, - userRemoteTarget: { value: '2' }, + default: { value: '1' }, + userLocal: { value: '2' }, + userRemote: { value: '2' }, }; const resource = URI.file('someFile'); @@ -184,9 +184,9 @@ suite('TextResourceConfigurationService - Update', () => { 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' }, + default: { value: '1' }, + userLocal: { value: '2' }, + userRemote: { value: '2', override: '3' }, }; const resource = URI.file('someFile'); @@ -197,10 +197,10 @@ suite('TextResourceConfigurationService - Update', () => { 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' } + default: { value: '1' }, + userLocal: { value: '2' }, + userRemote: { value: '2', override: '3' }, + workspace: { value: '3' } }; const resource = URI.file('someFile'); @@ -211,11 +211,11 @@ suite('TextResourceConfigurationService - Update', () => { 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' } + default: { value: '1' }, + userLocal: { value: '2', override: '1' }, + userRemote: { value: '2', override: '3' }, + workspace: { value: '3' }, + workspaceFolder: { value: '3' } }; const resource = URI.file('someFile'); @@ -226,8 +226,8 @@ suite('TextResourceConfigurationService - Update', () => { test('updateValue writes into derived user target without overrides', async () => { language = 'a'; configurationValue = { - defaultTarget: { value: '1' }, - userLocalTarget: { value: '2' }, + default: { value: '1' }, + userLocal: { value: '2' }, }; const resource = URI.file('someFile'); @@ -238,8 +238,8 @@ suite('TextResourceConfigurationService - Update', () => { test('updateValue writes into derived user target with overrides', async () => { language = 'a'; configurationValue = { - defaultTarget: { value: '1' }, - userLocalTarget: { value: '2', override: '3' }, + default: { value: '1' }, + userLocal: { value: '2', override: '3' }, }; const resource = URI.file('someFile'); @@ -250,9 +250,9 @@ suite('TextResourceConfigurationService - Update', () => { 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' } + default: { value: '1' }, + userLocal: { value: '2', override: '3' }, + userRemote: { value: '3' } }; const resource = URI.file('someFile'); @@ -263,9 +263,9 @@ suite('TextResourceConfigurationService - Update', () => { 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' } + default: { value: '1' }, + userLocal: { value: '2', override: '3' }, + workspaceValue: { value: '3' } }; const resource = URI.file('someFile'); @@ -276,10 +276,10 @@ suite('TextResourceConfigurationService - Update', () => { 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' } + default: { value: '1', override: '3' }, + userLocal: { value: '2', override: '3' }, + userRemote: { value: '3' }, + workspaceFolderValue: { value: '3' } }; const resource = URI.file('someFile'); @@ -290,7 +290,7 @@ suite('TextResourceConfigurationService - Update', () => { test('updateValue when not changed', async () => { language = 'a'; configurationValue = { - defaultTarget: { value: '1' }, + default: { value: '1' }, }; const resource = URI.file('someFile'); diff --git a/src/vs/editor/test/node/classification/typescript-test.ts b/src/vs/editor/test/node/classification/typescript-test.ts index f8c68e4ee8..bf8856d2f7 100644 --- a/src/vs/editor/test/node/classification/typescript-test.ts +++ b/src/vs/editor/test/node/classification/typescript-test.ts @@ -57,7 +57,7 @@ const x16 = / x07; /.test('3'); /// ^^^^^^^^ regex /// ^^^ string -const x17 = `.dialog-modal-block${true ? '.dimmed' : ''}`; +const x17 = `.monaco-dialog-modal-block${true ? '.dimmed' : ''}`; /// ^^^^^^^^^^^^^^^^^^^^^^ string /// ^^^^^^^^^ string /// ^^^^ string diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 3c05800dbe..5d3ab1653e 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -996,6 +996,11 @@ declare namespace monaco.editor { * An object that can be used by the web worker to make calls back to the main thread. */ host?: any; + /** + * Keep idle models. + * Defaults to false, which means that idle models will stop syncing after a while. + */ + keepIdleModels?: boolean; } /** @@ -3802,6 +3807,11 @@ declare namespace monaco.editor { * An event emitted after composition has ended. */ onCompositionEnd(listener: () => void): IDisposable; + /** + * An event emitted when users paste text in the editor. + * @event + */ + onDidPaste(listener: (range: Range) => void): IDisposable; /** * An event emitted on a "mouseup". * @event @@ -4751,7 +4761,7 @@ declare namespace monaco.languages { preselect?: boolean; /** * A string or snippet that should be inserted in a document when selecting - * this completion. + * this completion. When `falsy` the [label](#CompletionItem.label) * is used. */ insertText: string; diff --git a/src/vs/platform/configuration/common/configuration.ts b/src/vs/platform/configuration/common/configuration.ts index 2a7b1a7685..298389a7b9 100644 --- a/src/vs/platform/configuration/common/configuration.ts +++ b/src/vs/platform/configuration/common/configuration.ts @@ -67,22 +67,22 @@ export interface IConfigurationChangeEvent { 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 defaultValue?: T; + readonly userValue?: T; + readonly userLocalValue?: T; + readonly userRemoteValue?: T; + readonly workspaceValue?: T; + readonly workspaceFolderValue?: T; + readonly memoryValue?: 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 }; + readonly default?: { value?: T, override?: T }; + readonly user?: { value?: T, override?: T }; + readonly userLocal?: { value?: T, override?: T }; + readonly userRemote?: { value?: T, override?: T }; + readonly workspace?: { value?: T, override?: T }; + readonly workspaceFolder?: { value?: T, override?: T }; + readonly memory?: { value?: T, override?: T }; } export interface IConfigurationService { @@ -216,14 +216,11 @@ export function compare(from: IConfigurationModel | undefined, to: IConfiguratio export function toOverrides(raw: any, conflictReporter: (message: string) => void): IOverrides[] { const overrides: IOverrides[] = []; - const configurationProperties = Registry.as(Extensions.Configuration).getConfigurationProperties(); for (const key of Object.keys(raw)) { if (OVERRIDE_PROPERTY_PATTERN.test(key)) { const overrideRaw: any = {}; for (const keyInOverrideRaw in raw[key]) { - if (configurationProperties[keyInOverrideRaw] && configurationProperties[keyInOverrideRaw].overridable) { - overrideRaw[keyInOverrideRaw] = raw[key][keyInOverrideRaw]; - } + overrideRaw[keyInOverrideRaw] = raw[key][keyInOverrideRaw]; } overrides.push({ identifiers: [overrideIdentifierFromKey(key).trim()], @@ -361,11 +358,11 @@ export function getMigratedSettingValue(configurationService: IConfigurationS const setting = configurationService.inspect(currentSettingName); const legacySetting = configurationService.inspect(legacySettingName); - if (typeof setting.user !== 'undefined' || typeof setting.workspace !== 'undefined' || typeof setting.workspaceFolder !== 'undefined') { + if (typeof setting.userValue !== 'undefined' || typeof setting.workspaceValue !== 'undefined' || typeof setting.workspaceFolderValue !== 'undefined') { return setting.value!; - } else if (typeof legacySetting.user !== 'undefined' || typeof legacySetting.workspace !== 'undefined' || typeof legacySetting.workspaceFolder !== 'undefined') { + } else if (typeof legacySetting.userValue !== 'undefined' || typeof legacySetting.workspaceValue !== 'undefined' || typeof legacySetting.workspaceFolderValue !== 'undefined') { return legacySetting.value!; } else { - return setting.default!; + return setting.defaultValue!; } } diff --git a/src/vs/platform/configuration/common/configurationModels.ts b/src/vs/platform/configuration/common/configurationModels.ts index 32e25fb4f6..427493a49e 100644 --- a/src/vs/platform/configuration/common/configurationModels.ts +++ b/src/vs/platform/configuration/common/configurationModels.ts @@ -394,22 +394,22 @@ export class Configuration { const value = consolidateConfigurationModel.getValue(key); return { - default: defaultValue, - user: userValue, - userLocal: userLocalValue, - userRemote: userRemoteValue, - workspace: workspaceValue, - workspaceFolder: workspaceFolderValue, - memory: memoryValue, + defaultValue: defaultValue, + userValue: userValue, + userLocalValue: userLocalValue, + userRemoteValue: userRemoteValue, + workspaceValue: workspaceValue, + workspaceFolderValue: workspaceFolderValue, + memoryValue: 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, + default: defaultValue !== undefined ? { value: this._defaultConfiguration.freeze().getValue(key), override: overrides.overrideIdentifier ? this._defaultConfiguration.freeze().getOverrideValue(key, overrides.overrideIdentifier) : undefined } : undefined, + user: userValue !== undefined ? { value: this.userConfiguration.freeze().getValue(key), override: overrides.overrideIdentifier ? this.userConfiguration.freeze().getOverrideValue(key, overrides.overrideIdentifier) : undefined } : undefined, + userLocal: userLocalValue !== undefined ? { value: this.localUserConfiguration.freeze().getValue(key), override: overrides.overrideIdentifier ? this.localUserConfiguration.freeze().getOverrideValue(key, overrides.overrideIdentifier) : undefined } : undefined, + userRemote: userRemoteValue !== undefined ? { value: this.remoteUserConfiguration.freeze().getValue(key), override: overrides.overrideIdentifier ? this.remoteUserConfiguration.freeze().getOverrideValue(key, overrides.overrideIdentifier) : undefined } : undefined, + workspace: workspaceValue !== undefined ? { value: this._workspaceConfiguration.freeze().getValue(key), override: overrides.overrideIdentifier ? this._workspaceConfiguration.freeze().getOverrideValue(key, overrides.overrideIdentifier) : undefined } : undefined, + workspaceFolder: workspaceFolderValue !== undefined ? { value: folderConfigurationModel?.freeze().getValue(key), override: overrides.overrideIdentifier ? folderConfigurationModel?.freeze().getOverrideValue(key, overrides.overrideIdentifier) : undefined } : undefined, + memory: memoryValue !== undefined ? { value: memoryConfigurationModel.getValue(key), override: overrides.overrideIdentifier ? memoryConfigurationModel.getOverrideValue(key, overrides.overrideIdentifier) : undefined } : undefined, }; } diff --git a/src/vs/platform/configuration/common/configurationRegistry.ts b/src/vs/platform/configuration/common/configurationRegistry.ts index e97f6ad0cf..37d86209d4 100644 --- a/src/vs/platform/configuration/common/configurationRegistry.ts +++ b/src/vs/platform/configuration/common/configurationRegistry.ts @@ -99,6 +99,10 @@ export const enum ConfigurationScope { * Resource specific configuration, which can be configured in the user, workspace or folder settings. */ RESOURCE, + /** + * Resource specific configuration that can also be configured in language specific settings + */ + RESOURCE_LANGUAGE, /** * Machine specific configuration that can also be configured in workspace or folder settings. */ @@ -106,7 +110,6 @@ export const enum ConfigurationScope { } export interface IConfigurationPropertySchema extends IJSONSchema { - overridable?: boolean; scope?: ConfigurationScope; included?: boolean; tags?: string[]; @@ -124,7 +127,6 @@ export interface IConfigurationNode { description?: string; properties?: { [path: string]: IConfigurationPropertySchema; }; allOf?: IConfigurationNode[]; - overridable?: boolean; scope?: ConfigurationScope; extensionInfo?: IConfigurationExtensionInfo; } @@ -144,7 +146,8 @@ export const machineOverridableSettings: { properties: SettingProperties, patter export const windowSettings: { properties: SettingProperties, patternProperties: SettingProperties } = { properties: {}, patternProperties: {} }; export const resourceSettings: { properties: SettingProperties, patternProperties: SettingProperties } = { properties: {}, patternProperties: {} }; -export const editorConfigurationSchemaId = 'vscode://schemas/settings/editor'; +export const resourceLanguageSettingsSchemaId = 'vscode://schemas/settings/resourceLanguage'; + const contributionRegistry = Registry.as(JSONExtensions.JSONContribution); class ConfigurationRegistry implements IConfigurationRegistry { @@ -153,7 +156,7 @@ class ConfigurationRegistry implements IConfigurationRegistry { private readonly configurationContributors: IConfigurationNode[]; private readonly configurationProperties: { [qualifiedKey: string]: IJSONSchema }; private readonly excludedConfigurationProperties: { [qualifiedKey: string]: IJSONSchema }; - private readonly editorConfigurationSchema: IJSONSchema; + private readonly resourceLanguageSettingsSchema: IJSONSchema; private readonly overrideIdentifiers: string[] = []; private overridePropertyPattern: string; @@ -170,12 +173,12 @@ class ConfigurationRegistry implements IConfigurationRegistry { properties: {} }; this.configurationContributors = [this.defaultOverridesConfigurationNode]; - this.editorConfigurationSchema = { properties: {}, patternProperties: {}, additionalProperties: false, errorMessage: 'Unknown editor configuration setting', allowTrailingCommas: true, allowComments: true }; + this.resourceLanguageSettingsSchema = { properties: {}, patternProperties: {}, additionalProperties: false, errorMessage: 'Unknown editor configuration setting', allowTrailingCommas: true, allowComments: true }; this.configurationProperties = {}; this.excludedConfigurationProperties = {}; this.overridePropertyPattern = this.computeOverridePropertyPattern(); - contributionRegistry.registerSchema(editorConfigurationSchemaId, this.editorConfigurationSchema); + contributionRegistry.registerSchema(resourceLanguageSettingsSchemaId, this.resourceLanguageSettingsSchema); } public registerConfiguration(configuration: IConfigurationNode, validate: boolean = true): void { @@ -188,9 +191,9 @@ class ConfigurationRegistry implements IConfigurationRegistry { properties.push(...this.validateAndRegisterProperties(configuration, validate)); // fills in defaults this.configurationContributors.push(configuration); this.registerJSONConfiguration(configuration); - this.updateSchemaForOverrideSettingsConfiguration(configuration); }); + contributionRegistry.registerSchema(resourceLanguageSettingsSchemaId, this.resourceLanguageSettingsSchema); this._onDidSchemaChange.fire(); this._onDidUpdateConfiguration.fire(properties); } @@ -203,7 +206,6 @@ class ConfigurationRegistry implements IConfigurationRegistry { properties.push(key); delete this.configurationProperties[key]; - delete this.editorConfigurationSchema.properties![key]; // Delete from schema delete allSettings.properties[key]; @@ -221,6 +223,7 @@ class ConfigurationRegistry implements IConfigurationRegistry { delete windowSettings.properties[key]; break; case ConfigurationScope.RESOURCE: + case ConfigurationScope.RESOURCE_LANGUAGE: delete resourceSettings.properties[key]; break; } @@ -238,7 +241,7 @@ class ConfigurationRegistry implements IConfigurationRegistry { } } - contributionRegistry.registerSchema(editorConfigurationSchemaId, this.editorConfigurationSchema); + contributionRegistry.registerSchema(resourceLanguageSettingsSchemaId, this.resourceLanguageSettingsSchema); this._onDidSchemaChange.fire(); this._onDidUpdateConfiguration.fire(properties); } @@ -254,7 +257,7 @@ class ConfigurationRegistry implements IConfigurationRegistry { type: 'object', default: defaultValue, description: nls.localize('overrideSettings.description', "Configure editor settings to be overridden for {0} language.", key), - $ref: editorConfigurationSchemaId + $ref: resourceLanguageSettingsSchemaId }; allSettings.properties[key] = propertySchema; this.defaultOverridesConfigurationNode.properties![key] = propertySchema; @@ -291,9 +294,8 @@ class ConfigurationRegistry implements IConfigurationRegistry { this.updateOverridePropertyPatternKey(); } - private validateAndRegisterProperties(configuration: IConfigurationNode, validate: boolean = true, scope: ConfigurationScope = ConfigurationScope.WINDOW, overridable: boolean = false): string[] { + private validateAndRegisterProperties(configuration: IConfigurationNode, validate: boolean = true, scope: ConfigurationScope = ConfigurationScope.WINDOW): string[] { scope = types.isUndefinedOrNull(configuration.scope) ? scope : configuration.scope; - overridable = configuration.overridable || overridable; let propertyKeys: string[] = []; let properties = configuration.properties; if (properties) { @@ -310,11 +312,6 @@ class ConfigurationRegistry implements IConfigurationRegistry { if (types.isUndefined(defaultValue)) { property.default = getDefaultValue(property.type); } - // Inherit overridable property from parent - if (overridable) { - property.overridable = true; - } - if (OVERRIDE_PROPERTY_PATTERN.test(key)) { property.scope = undefined; // No scope for overridable properties `[${identifier}]` } else { @@ -337,7 +334,7 @@ class ConfigurationRegistry implements IConfigurationRegistry { let subNodes = configuration.allOf; if (subNodes) { for (let node of subNodes) { - propertyKeys.push(...this.validateAndRegisterProperties(node, validate, scope, overridable)); + propertyKeys.push(...this.validateAndRegisterProperties(node, validate, scope)); } } return propertyKeys; @@ -356,7 +353,7 @@ class ConfigurationRegistry implements IConfigurationRegistry { } private registerJSONConfiguration(configuration: IConfigurationNode) { - function register(configuration: IConfigurationNode) { + const register = (configuration: IConfigurationNode) => { let properties = configuration.properties; if (properties) { for (const key in properties) { @@ -377,6 +374,10 @@ class ConfigurationRegistry implements IConfigurationRegistry { case ConfigurationScope.RESOURCE: resourceSettings.properties[key] = properties[key]; break; + case ConfigurationScope.RESOURCE_LANGUAGE: + resourceSettings.properties[key] = properties[key]; + this.resourceLanguageSettingsSchema.properties![key] = properties[key]; + break; } } } @@ -384,17 +385,10 @@ class ConfigurationRegistry implements IConfigurationRegistry { if (subNodes) { subNodes.forEach(register); } - } + }; register(configuration); } - private updateSchemaForOverrideSettingsConfiguration(configuration: IConfigurationNode): void { - if (configuration.id !== SETTINGS_OVERRRIDE_NODE_ID) { - this.update(configuration); - contributionRegistry.registerSchema(editorConfigurationSchemaId, this.editorConfigurationSchema); - } - } - private updateOverridePropertyPatternKey(): void { let patternProperties: IJSONSchema = allSettings.patternProperties[this.overridePropertyPattern]; if (!patternProperties) { @@ -402,7 +396,7 @@ class ConfigurationRegistry implements IConfigurationRegistry { type: 'object', description: nls.localize('overrideSettings.defaultDescription', "Configure editor settings to be overridden for a language."), errorMessage: 'Unknown Identifier. Use language identifiers', - $ref: editorConfigurationSchemaId + $ref: resourceLanguageSettingsSchemaId }; } @@ -425,27 +419,11 @@ class ConfigurationRegistry implements IConfigurationRegistry { this._onDidSchemaChange.fire(); } - private update(configuration: IConfigurationNode): void { - let properties = configuration.properties; - if (properties) { - for (let key in properties) { - if (properties[key].overridable) { - this.editorConfigurationSchema.properties![key] = this.getConfigurationProperties()[key]; - } - } - } - let subNodes = configuration.allOf; - if (subNodes) { - subNodes.forEach(subNode => this.update(subNode)); - } - } - private computeOverridePropertyPattern(): string { return this.overrideIdentifiers.length ? OVERRIDE_PATTERN_WITH_SUBSTITUTION.replace('${0}', this.overrideIdentifiers.map(identifier => strings.createRegExp(identifier, false).source).join('|')) : OVERRIDE_PROPERTY; } } -const SETTINGS_OVERRRIDE_NODE_ID = 'override'; const OVERRIDE_PROPERTY = '\\[.*\\]$'; const OVERRIDE_PATTERN_WITH_SUBSTITUTION = '\\[(${0})\\]$'; export const OVERRIDE_PROPERTY_PATTERN = new RegExp(OVERRIDE_PROPERTY); diff --git a/src/vs/platform/configuration/test/common/configurationModels.test.ts b/src/vs/platform/configuration/test/common/configurationModels.test.ts index bc4892d29f..f139c25102 100644 --- a/src/vs/platform/configuration/test/common/configurationModels.test.ts +++ b/src/vs/platform/configuration/test/common/configurationModels.test.ts @@ -234,23 +234,6 @@ suite('ConfigurationModel', () => { suite('CustomConfigurationModel', () => { - suiteSetup(() => { - Registry.as(Extensions.Configuration).registerConfiguration({ - 'id': 'a', - 'order': 1, - 'title': 'a', - 'type': 'object', - 'properties': { - 'a': { - 'description': 'a', - 'type': 'boolean', - 'default': true, - 'overridable': true - } - } - }); - }); - test('simple merge using models', () => { let base = new ConfigurationModelParser('base'); base.parseContent(JSON.stringify({ 'a': 1, 'b': 2 })); @@ -346,6 +329,19 @@ suite('CustomConfigurationModel', () => { }); test('Test registering the same property again', () => { + Registry.as(Extensions.Configuration).registerConfiguration({ + 'id': 'a', + 'order': 1, + 'title': 'a', + 'type': 'object', + 'properties': { + 'a': { + 'description': 'a', + 'type': 'boolean', + 'default': true, + } + } + }); Registry.as(Extensions.Configuration).registerConfiguration({ 'id': 'a', 'order': 1, diff --git a/src/vs/platform/configuration/test/common/testConfigurationService.ts b/src/vs/platform/configuration/test/common/testConfigurationService.ts index 528413c57e..6879dc8f49 100644 --- a/src/vs/platform/configuration/test/common/testConfigurationService.ts +++ b/src/vs/platform/configuration/test/common/testConfigurationService.ts @@ -60,8 +60,8 @@ export class TestConfigurationService implements IConfigurationService { return { value: getConfigurationValue(config, key), - default: getConfigurationValue(config, key), - user: getConfigurationValue(config, key) + defaultValue: getConfigurationValue(config, key), + userValue: getConfigurationValue(config, key) }; } diff --git a/src/vs/platform/configuration/test/node/configurationService.test.ts b/src/vs/platform/configuration/test/node/configurationService.test.ts index 70c333e69a..31e5b56548 100644 --- a/src/vs/platform/configuration/test/node/configurationService.test.ts +++ b/src/vs/platform/configuration/test/node/configurationService.test.ts @@ -210,20 +210,20 @@ suite('ConfigurationService - Node', () => { let res = service.inspect('something.missing'); assert.strictEqual(res.value, undefined); - assert.strictEqual(res.default, undefined); - assert.strictEqual(res.user, undefined); + assert.strictEqual(res.defaultValue, undefined); + assert.strictEqual(res.userValue, undefined); res = service.inspect('lookup.service.testSetting'); - assert.strictEqual(res.default, 'isSet'); + assert.strictEqual(res.defaultValue, 'isSet'); assert.strictEqual(res.value, 'isSet'); - assert.strictEqual(res.user, undefined); + assert.strictEqual(res.userValue, undefined); fs.writeFileSync(r.testFile, '{ "lookup.service.testSetting": "bar" }'); await service.reloadConfiguration(); res = service.inspect('lookup.service.testSetting'); - assert.strictEqual(res.default, 'isSet'); - assert.strictEqual(res.user, 'bar'); + assert.strictEqual(res.defaultValue, 'isSet'); + assert.strictEqual(res.userValue, 'bar'); assert.strictEqual(res.value, 'bar'); service.dispose(); @@ -247,18 +247,18 @@ suite('ConfigurationService - Node', () => { service.initialize(); let res = service.inspect('lookup.service.testNullSetting'); - assert.strictEqual(res.default, null); + assert.strictEqual(res.defaultValue, null); assert.strictEqual(res.value, null); - assert.strictEqual(res.user, undefined); + assert.strictEqual(res.userValue, undefined); fs.writeFileSync(r.testFile, '{ "lookup.service.testNullSetting": null }'); await service.reloadConfiguration(); res = service.inspect('lookup.service.testNullSetting'); - assert.strictEqual(res.default, null); + assert.strictEqual(res.defaultValue, null); assert.strictEqual(res.value, null); - assert.strictEqual(res.user, null); + assert.strictEqual(res.userValue, null); service.dispose(); return r.cleanUp(); diff --git a/src/vs/platform/files/common/fileService.ts b/src/vs/platform/files/common/fileService.ts index f962be772c..cc2d2144f5 100644 --- a/src/vs/platform/files/common/fileService.ts +++ b/src/vs/platform/files/common/fileService.ts @@ -113,7 +113,7 @@ export class FileService extends Disposable implements IFileService { if (!provider) { const error = new Error(); error.name = 'ENOPRO'; - error.message = localize('noProviderFound', "No file system provider found for {0}", resource.toString()); + error.message = localize('noProviderFound', "No file system provider found for resource '{0}'", resource.toString()); throw error; } @@ -128,7 +128,7 @@ export class FileService extends Disposable implements IFileService { return provider; } - throw new Error('Provider neither has FileReadWrite, FileReadStream nor FileOpenReadWriteClose capability which is needed for the read operation.'); + throw new Error(`Provider for scheme '${resource.scheme}' neither has FileReadWrite, FileReadStream nor FileOpenReadWriteClose capability which is needed for the read operation.`); } private async withWriteProvider(resource: URI): Promise { @@ -138,7 +138,7 @@ export class FileService extends Disposable implements IFileService { return provider; } - throw new Error('Provider neither has FileReadWrite nor FileOpenReadWriteClose capability which is needed for the write operation.'); + throw new Error(`Provider for scheme '${resource.scheme}' neither has FileReadWrite nor FileOpenReadWriteClose capability which is needed for the write operation.`); } //#endregion @@ -160,10 +160,7 @@ export class FileService extends Disposable implements IFileService { // Specially handle file not found case as file operation result if (toFileSystemProviderErrorCode(error) === FileSystemProviderErrorCode.FileNotFound) { - throw new FileOperationError( - localize('fileNotFoundError', "File not found ({0})", this.resourceForError(resource)), - FileOperationResult.FILE_NOT_FOUND - ); + throw new FileOperationError(localize('fileNotFoundError', "File not found ({0})", this.resourceForError(resource)), FileOperationResult.FILE_NOT_FOUND); } // Bubble up any other error as is @@ -304,7 +301,7 @@ export class FileService extends Disposable implements IFileService { } async writeFile(resource: URI, bufferOrReadableOrStream: VSBuffer | VSBufferReadable | VSBufferReadableStream, options?: IWriteFileOptions): Promise { - const provider = this.throwIfFileSystemIsReadonly(await this.withWriteProvider(resource)); + const provider = this.throwIfFileSystemIsReadonly(await this.withWriteProvider(resource), resource); try { @@ -338,7 +335,7 @@ export class FileService extends Disposable implements IFileService { await this.doWriteBuffered(provider, resource, bufferOrReadableOrStream instanceof VSBuffer ? bufferToReadable(bufferOrReadableOrStream) : bufferOrReadableOrStream); } } catch (error) { - throw new FileOperationError(localize('err.write', "Unable to write file ({0})", ensureFileSystemProviderError(error).toString()), toFileOperationResult(error), options); + throw new FileOperationError(localize('err.write', "Unable to write file '{0}' ({1})", this.resourceForError(resource), ensureFileSystemProviderError(error).toString()), toFileOperationResult(error), options); } return this.resolve(resource, { resolveMetadata: true }); @@ -354,7 +351,7 @@ export class FileService extends Disposable implements IFileService { // file cannot be directory if ((stat.type & FileType.Directory) !== 0) { - throw new FileOperationError(localize('fileIsDirectoryError', "Expected file {0} is actually a directory", this.resourceForError(resource)), FileOperationResult.FILE_IS_DIRECTORY, options); + throw new FileOperationError(localize('fileIsDirectoryError', "Expected file '{0}' is actually a directory", this.resourceForError(resource)), FileOperationResult.FILE_IS_DIRECTORY, options); } // Dirty write prevention: if the file on disk has been changed and does not match our expected @@ -453,14 +450,14 @@ export class FileService extends Disposable implements IFileService { value: fileStream }; } catch (error) { - throw new FileOperationError(localize('err.read', "Unable to read file ({0})", ensureFileSystemProviderError(error).toString()), toFileOperationResult(error), options); + throw new FileOperationError(localize('err.read', "Unable to read file '{0}' ({1})", this.resourceForError(resource), ensureFileSystemProviderError(error).toString()), toFileOperationResult(error), options); } } private readFileStreamed(provider: IFileSystemProviderWithFileReadStreamCapability, resource: URI, token: CancellationToken, options: IReadFileOptions = Object.create(null)): VSBufferReadableStream { const fileStream = provider.readFileStream(resource, options, token); - return this.transformFileReadStream(fileStream, options); + return this.transformFileReadStream(resource, fileStream, options); } private readFileBuffered(provider: IFileSystemProviderWithOpenReadWriteCloseCapability, resource: URI, token: CancellationToken, options: IReadFileOptions = Object.create(null)): VSBufferReadableStream { @@ -469,13 +466,13 @@ export class FileService extends Disposable implements IFileService { bufferSize: this.BUFFER_SIZE }, token); - return this.transformFileReadStream(fileStream, options); + return this.transformFileReadStream(resource, fileStream, options); } - private transformFileReadStream(stream: ReadableStreamEvents, options: IReadFileOptions): VSBufferReadableStream { + private transformFileReadStream(resource: URI, stream: ReadableStreamEvents, options: IReadFileOptions): VSBufferReadableStream { return transform(stream, { data: data => data instanceof VSBuffer ? data : VSBuffer.wrap(data), - error: error => new FileOperationError(localize('err.read', "Unable to read file ({0})", ensureFileSystemProviderError(error).toString()), toFileOperationResult(error), options) + error: error => new FileOperationError(localize('err.read', "Unable to read file '{0}' ({1})", this.resourceForError(resource), ensureFileSystemProviderError(error).toString()), toFileOperationResult(error), options) }, data => VSBuffer.concat(data)); } @@ -493,7 +490,7 @@ export class FileService extends Disposable implements IFileService { } // Throw if file is too large to load - this.validateReadFileLimits(buffer.byteLength, options); + this.validateReadFileLimits(resource, buffer.byteLength, options); return bufferToStream(VSBuffer.wrap(buffer)); } @@ -503,7 +500,7 @@ export class FileService extends Disposable implements IFileService { // Throw if resource is a directory if (stat.isDirectory) { - throw new FileOperationError(localize('fileIsDirectoryError', "Expected file {0} is actually a directory", this.resourceForError(resource)), FileOperationResult.FILE_IS_DIRECTORY, options); + throw new FileOperationError(localize('fileIsDirectoryError', "Expected file '{0}' is actually a directory", this.resourceForError(resource)), FileOperationResult.FILE_IS_DIRECTORY, options); } // Throw if file not modified since (unless disabled) @@ -512,12 +509,12 @@ export class FileService extends Disposable implements IFileService { } // Throw if file is too large to load - this.validateReadFileLimits(stat.size, options); + this.validateReadFileLimits(resource, stat.size, options); return stat; } - private validateReadFileLimits(size: number, options?: IReadFileOptions): void { + private validateReadFileLimits(resource: URI, size: number, options?: IReadFileOptions): void { if (options?.limits) { let tooLargeErrorResult: FileOperationResult | undefined = undefined; @@ -530,7 +527,7 @@ export class FileService extends Disposable implements IFileService { } if (typeof tooLargeErrorResult === 'number') { - throw new FileOperationError(localize('fileTooLargeError', "File is too large to open"), tooLargeErrorResult); + throw new FileOperationError(localize('fileTooLargeError', "File '{0}' is too large to open", this.resourceForError(resource)), tooLargeErrorResult); } } } @@ -540,8 +537,8 @@ export class FileService extends Disposable implements IFileService { //#region Move/Copy/Delete/Create Folder async move(source: URI, target: URI, overwrite?: boolean): Promise { - const sourceProvider = this.throwIfFileSystemIsReadonly(await this.withWriteProvider(source)); - const targetProvider = this.throwIfFileSystemIsReadonly(await this.withWriteProvider(target)); + const sourceProvider = this.throwIfFileSystemIsReadonly(await this.withWriteProvider(source), source); + const targetProvider = this.throwIfFileSystemIsReadonly(await this.withWriteProvider(target), target); // move const mode = await this.doMoveCopy(sourceProvider, source, targetProvider, target, 'move', !!overwrite); @@ -555,7 +552,7 @@ export class FileService extends Disposable implements IFileService { async copy(source: URI, target: URI, overwrite?: boolean): Promise { const sourceProvider = await this.withReadProvider(source); - const targetProvider = this.throwIfFileSystemIsReadonly(await this.withWriteProvider(target)); + const targetProvider = this.throwIfFileSystemIsReadonly(await this.withWriteProvider(target), target); // copy const mode = await this.doMoveCopy(sourceProvider, source, targetProvider, target, 'copy', !!overwrite); @@ -678,11 +675,11 @@ export class FileService extends Disposable implements IFileService { } if (isSameResourceWithDifferentPathCase && mode === 'copy') { - throw new Error(localize('unableToMoveCopyError1', "Unable to copy when source is same as target with different path case on a case insensitive file system")); + throw new Error(localize('unableToMoveCopyError1', "Unable to copy when source '{0}' is same as target '{1}' with different path case on a case insensitive file system", this.resourceForError(source), this.resourceForError(target))); } if (!isSameResourceWithDifferentPathCase && isEqualOrParent(target, source, !isPathCaseSensitive)) { - throw new Error(localize('unableToMoveCopyError2', "Unable to move/copy when source is parent of target.")); + throw new Error(localize('unableToMoveCopyError2', "Unable to move/copy when source '{0}' is parent of target '{1}'.", this.resourceForError(source), this.resourceForError(target))); } } @@ -709,7 +706,7 @@ export class FileService extends Disposable implements IFileService { } async createFolder(resource: URI): Promise { - const provider = this.throwIfFileSystemIsReadonly(await this.withProvider(resource)); + const provider = this.throwIfFileSystemIsReadonly(await this.withProvider(resource), resource); // mkdir recursively await this.mkdirp(provider, resource); @@ -729,7 +726,7 @@ export class FileService extends Disposable implements IFileService { try { const stat = await provider.stat(directory); if ((stat.type & FileType.Directory) === 0) { - throw new Error(localize('mkdirExistsError', "{0} exists, but is not a directory", this.resourceForError(directory))); + throw new Error(localize('mkdirExistsError', "Path '{0}' already exists, but is not a directory", this.resourceForError(directory))); } break; // we have hit a directory that exists -> good @@ -756,17 +753,23 @@ export class FileService extends Disposable implements IFileService { } async del(resource: URI, options?: { useTrash?: boolean; recursive?: boolean; }): Promise { - const provider = this.throwIfFileSystemIsReadonly(await this.withProvider(resource)); + const provider = this.throwIfFileSystemIsReadonly(await this.withProvider(resource), resource); // Validate trash support const useTrash = !!options?.useTrash; if (useTrash && !(provider.capabilities & FileSystemProviderCapabilities.Trash)) { - throw new Error(localize('err.trash', "Provider does not support trash.")); + throw new Error(localize('err.trash', "Provider for scheme '{0}' does not support trash.", resource.scheme)); + } + + // Validate delete + const exists = await this.exists(resource); + if (!exists) { + throw new FileOperationError(localize('fileNotFoundError', "File not found ({0})", this.resourceForError(resource)), FileOperationResult.FILE_NOT_FOUND); } // Validate recursive const recursive = !!options?.recursive; - if (!recursive && await this.exists(resource)) { + if (!recursive && exists) { const stat = await this.resolve(resource); if (stat.isDirectory && Array.isArray(stat.children) && stat.children.length > 0) { throw new Error(localize('deleteFailed', "Unable to delete non-empty folder '{0}'.", this.resourceForError(resource))); @@ -1050,9 +1053,9 @@ export class FileService extends Disposable implements IFileService { await this.doWriteUnbuffered(targetProvider, target, buffer); } - protected throwIfFileSystemIsReadonly(provider: T): T { + protected throwIfFileSystemIsReadonly(provider: T, resource: URI): T { if (provider.capabilities & FileSystemProviderCapabilities.Readonly) { - throw new FileOperationError(localize('err.readonly', "Resource can not be modified."), FileOperationResult.FILE_PERMISSION_DENIED); + throw new FileOperationError(localize('err.readonly', "Resource '{0}' can not be modified.", this.resourceForError(resource)), FileOperationResult.FILE_PERMISSION_DENIED); } return provider; diff --git a/src/vs/platform/files/test/node/diskFileService.test.ts b/src/vs/platform/files/test/node/diskFileService.test.ts index 9b0a27b91f..efb3dc195e 100644 --- a/src/vs/platform/files/test/node/diskFileService.test.ts +++ b/src/vs/platform/files/test/node/diskFileService.test.ts @@ -467,6 +467,16 @@ suite('Disk File Service', function () { assert.ok(event!); assert.equal(event!.resource.fsPath, resource.fsPath); assert.equal(event!.operation, FileOperation.DELETE); + + let error: Error | undefined = undefined; + try { + await service.del(source.resource); + } catch (e) { + error = e; + } + + assert.ok(error); + assert.equal((error).fileOperationResult, FileOperationResult.FILE_NOT_FOUND); }); test('deleteFolder (recursive)', async () => { diff --git a/src/vs/platform/menubar/electron-main/menubarMainService.ts b/src/vs/platform/menubar/electron-main/menubarMainService.ts index e227d807c1..5d9f12803e 100644 --- a/src/vs/platform/menubar/electron-main/menubarMainService.ts +++ b/src/vs/platform/menubar/electron-main/menubarMainService.ts @@ -7,28 +7,34 @@ import { IMenubarService, IMenubarData } from 'vs/platform/menubar/node/menubar' import { Menubar } from 'vs/platform/menubar/electron-main/menubar'; import { ILogService } from 'vs/platform/log/common/log'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ILifecycleMainService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; export class MenubarMainService implements IMenubarService { _serviceBrand: undefined; - private _menubar: Menubar; + private _menubar: Menubar | undefined; constructor( @IInstantiationService private readonly instantiationService: IInstantiationService, + @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, @ILogService private readonly logService: ILogService ) { // Install Menu - this._menubar = this.instantiationService.createInstance(Menubar); + this.lifecycleMainService.when(LifecycleMainPhase.AfterWindowOpen).then(() => { + this._menubar = this.instantiationService.createInstance(Menubar); + }); } updateMenubar(windowId: number, menus: IMenubarData): Promise { - this.logService.trace('menubarService#updateMenubar', windowId); + return this.lifecycleMainService.when(LifecycleMainPhase.AfterWindowOpen).then(() => { + this.logService.trace('menubarService#updateMenubar', windowId); - if (this._menubar) { - this._menubar.updateMenu(menus, windowId); - } + if (this._menubar) { + this._menubar.updateMenu(menus, windowId); + } - return Promise.resolve(undefined); + return undefined; + }); } } diff --git a/src/vs/platform/remote/common/remoteAuthorityResolver.ts b/src/vs/platform/remote/common/remoteAuthorityResolver.ts index 86a09affaf..5cb91ff36e 100644 --- a/src/vs/platform/remote/common/remoteAuthorityResolver.ts +++ b/src/vs/platform/remote/common/remoteAuthorityResolver.ts @@ -17,9 +17,14 @@ export interface ResolvedOptions { readonly extensionHostEnv?: { [key: string]: string | null }; } +export interface TunnelInformation { + detectedTunnels?: { remote: { port: number, host: string }, localAddress: string }[]; +} + export interface ResolverResult { authority: ResolvedAuthority; options?: ResolvedOptions; + tunnelInformation?: TunnelInformation; } export enum RemoteAuthorityResolverErrorCode { diff --git a/src/vs/platform/remote/common/tunnel.ts b/src/vs/platform/remote/common/tunnel.ts index 6a081c2c9e..1caba947b5 100644 --- a/src/vs/platform/remote/common/tunnel.ts +++ b/src/vs/platform/remote/common/tunnel.ts @@ -6,17 +6,28 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { URI } from 'vs/base/common/uri'; import { Event } from 'vs/base/common/event'; +import { IDisposable } from 'vs/base/common/lifecycle'; export const ITunnelService = createDecorator('tunnelService'); export interface RemoteTunnel { readonly tunnelRemotePort: number; readonly tunnelRemoteHost: string; - readonly tunnelLocalPort: number; + readonly tunnelLocalPort?: number; readonly localAddress: string; dispose(): void; } +export interface TunnelOptions { + remote: { port: number, host: string }; + localPort?: number; + name?: string; +} + +export interface ITunnelProvider { + forwardPort(tunnelOptions: TunnelOptions): Promise | undefined; +} + export interface ITunnelService { _serviceBrand: undefined; @@ -26,6 +37,7 @@ export interface ITunnelService { openTunnel(remotePort: number, localPort?: number): Promise | undefined; closeTunnel(remotePort: number): Promise; + setTunnelProvider(provider: ITunnelProvider | undefined): IDisposable; } export function extractLocalHostUriMetaDataForPortMapping(uri: URI): { address: string, port: number } | undefined { diff --git a/src/vs/platform/remote/common/tunnelService.ts b/src/vs/platform/remote/common/tunnelService.ts index a3719a715e..dd4a085fca 100644 --- a/src/vs/platform/remote/common/tunnelService.ts +++ b/src/vs/platform/remote/common/tunnelService.ts @@ -3,8 +3,9 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ITunnelService, RemoteTunnel } from 'vs/platform/remote/common/tunnel'; +import { ITunnelService, RemoteTunnel, ITunnelProvider } from 'vs/platform/remote/common/tunnel'; import { Event, Emitter } from 'vs/base/common/event'; +import { IDisposable } from 'vs/base/common/lifecycle'; export class NoOpTunnelService implements ITunnelService { _serviceBrand: undefined; @@ -19,4 +20,7 @@ export class NoOpTunnelService implements ITunnelService { } async closeTunnel(_remotePort: number): Promise { } + setTunnelProvider(provider: ITunnelProvider | undefined): IDisposable { + throw new Error('Method not implemented.'); + } } diff --git a/src/vs/platform/severityIcon/common/severityIcon.ts b/src/vs/platform/severityIcon/common/severityIcon.ts index c3374a4232..ba9a920d2a 100644 --- a/src/vs/platform/severityIcon/common/severityIcon.ts +++ b/src/vs/platform/severityIcon/common/severityIcon.ts @@ -29,10 +29,10 @@ registerThemingParticipant((theme, collector) => { const errorIconForeground = theme.getColor(problemsErrorIconForeground); if (errorIconForeground) { collector.addRule(` - .monaco-workbench .zone-widget .codicon-error, - .monaco-workbench .markers-panel .marker-icon.codicon-error, - .monaco-workbench .extensions-viewlet > .extensions .codicon-error, - .monaco-workbench .dialog-box .dialog-message-row .codicon-error { + .monaco-editor .zone-widget .codicon-error, + .markers-panel .marker-icon.codicon-error, + .extensions-viewlet > .extensions .codicon-error, + .monaco-dialog-box .dialog-message-row .codicon-error { color: ${errorIconForeground}; } `); @@ -41,11 +41,11 @@ registerThemingParticipant((theme, collector) => { const warningIconForeground = theme.getColor(problemsWarningIconForeground); if (errorIconForeground) { collector.addRule(` - .monaco-workbench .zone-widget .codicon-warning, - .monaco-workbench .markers-panel .marker-icon.codicon-warning, - .monaco-workbench .extensions-viewlet > .extensions .codicon-warning, - .monaco-workbench .extension-editor .codicon-warning, - .monaco-workbench .dialog-box .dialog-message-row .codicon-warning { + .monaco-editor .zone-widget .codicon-warning, + .markers-panel .marker-icon.codicon-warning, + .extensions-viewlet > .extensions .codicon-warning, + .extension-editor .codicon-warning, + .monaco-dialog-box .dialog-message-row .codicon-warning { color: ${warningIconForeground}; } `); @@ -54,11 +54,11 @@ registerThemingParticipant((theme, collector) => { const infoIconForeground = theme.getColor(problemsInfoIconForeground); if (errorIconForeground) { collector.addRule(` - .monaco-workbench .zone-widget .codicon-info, - .monaco-workbench .markers-panel .marker-icon.codicon-info, - .monaco-workbench .extensions-viewlet > .extensions .codicon-info, - .monaco-workbench .extension-editor .codicon-info, - .monaco-workbench .dialog-box .dialog-message-row .codicon-info { + .monaco-editor .zone-widget .codicon-info, + .markers-panel .marker-icon.codicon-info, + .extensions-viewlet > .extensions .codicon-info, + .extension-editor .codicon-info, + .monaco-dialog-box .dialog-message-row .codicon-info { color: ${infoIconForeground}; } `); diff --git a/src/vs/platform/storage/browser/storageService.ts b/src/vs/platform/storage/browser/storageService.ts index 8c1ef09778..429e63feca 100644 --- a/src/vs/platform/storage/browser/storageService.ts +++ b/src/vs/platform/storage/browser/storageService.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; import { IWorkspaceStorageChangeEvent, IStorageService, StorageScope, IWillSaveStateEvent, WillSaveStateReason, logStorage } from 'vs/platform/storage/common/storage'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @@ -21,11 +21,11 @@ export class BrowserStorageService extends Disposable implements IStorageService _serviceBrand: undefined; - private readonly _onDidChangeStorage: Emitter = this._register(new Emitter()); - readonly onDidChangeStorage: Event = this._onDidChangeStorage.event; + private readonly _onDidChangeStorage = this._register(new Emitter()); + readonly onDidChangeStorage = this._onDidChangeStorage.event; - private readonly _onWillSaveState: Emitter = this._register(new Emitter()); - readonly onWillSaveState: Event = this._onWillSaveState.event; + private readonly _onWillSaveState = this._register(new Emitter()); + readonly onWillSaveState = this._onWillSaveState.event; private globalStorage: IStorage | undefined; private workspaceStorage: IStorage | undefined; @@ -37,45 +37,15 @@ export class BrowserStorageService extends Disposable implements IStorageService private workspaceStorageFile: URI | undefined; private initializePromise: Promise | undefined; - private periodicSaveScheduler = this._register(new RunOnceScheduler(() => this.collectState(), 5000)); - get hasPendingUpdate(): boolean { - return (!!this.globalStorageDatabase && this.globalStorageDatabase.hasPendingUpdate) || (!!this.workspaceStorageDatabase && this.workspaceStorageDatabase.hasPendingUpdate); - } + private readonly periodicFlushScheduler = this._register(new RunOnceScheduler(() => this.doFlushWhenIdle(), 5000 /* every 5s */)); + private runWhenIdleDisposable: IDisposable | undefined = undefined; constructor( @IEnvironmentService private readonly environmentService: IEnvironmentService, @IFileService private readonly fileService: IFileService ) { super(); - - // In the browser we do not have support for long running unload sequences. As such, - // we cannot ask for saving state in that moment, because that would result in a - // long running operation. - // Instead, periodically ask customers to save save. The library will be clever enough - // to only save state that has actually changed. - this.periodicSaveScheduler.schedule(); - } - - private collectState(): void { - runWhenIdle(() => { - - // this event will potentially cause new state to be stored - // since new state will only be created while the document - // has focus, one optimization is to not run this when the - // document has no focus, assuming that state has not changed - // - // another optimization is to not collect more state if we - // have a pending update already running which indicates - // that the connection is either slow or disconnected and - // thus unhealthy. - if (document.hasFocus() && !this.hasPendingUpdate) { - this._onWillSaveState.fire({ reason: WillSaveStateReason.NONE }); - } - - // repeat - this.periodicSaveScheduler.schedule(); - }); } initialize(payload: IWorkspaceInitializationPayload): Promise { @@ -109,6 +79,13 @@ export class BrowserStorageService extends Disposable implements IStorageService this.workspaceStorage.init(), this.globalStorage.init() ]); + + // In the browser we do not have support for long running unload sequences. As such, + // we cannot ask for saving state in that moment, because that would result in a + // long running operation. + // Instead, periodically ask customers to save save. The library will be clever enough + // to only save state that has actually changed. + this.periodicFlushScheduler.schedule(); } get(key: string, scope: StorageScope, fallbackValue: string): string; @@ -156,6 +133,40 @@ export class BrowserStorageService extends Disposable implements IStorageService throw new Error('Migrating storage is currently unsupported in Web'); } + private doFlushWhenIdle(): void { + + // Dispose any previous idle runner + this.runWhenIdleDisposable = dispose(this.runWhenIdleDisposable); + + // Run when idle + this.runWhenIdleDisposable = runWhenIdle(() => { + + // this event will potentially cause new state to be stored + // since new state will only be created while the document + // has focus, one optimization is to not run this when the + // document has no focus, assuming that state has not changed + // + // another optimization is to not collect more state if we + // have a pending update already running which indicates + // that the connection is either slow or disconnected and + // thus unhealthy. + if (document.hasFocus() && !this.hasPendingUpdate) { + this.flush(); + } + + // repeat + this.periodicFlushScheduler.schedule(); + }); + } + + get hasPendingUpdate(): boolean { + return (!!this.globalStorageDatabase && this.globalStorageDatabase.hasPendingUpdate) || (!!this.workspaceStorageDatabase && this.workspaceStorageDatabase.hasPendingUpdate); + } + + flush(): void { + this._onWillSaveState.fire({ reason: WillSaveStateReason.NONE }); + } + close(): void { // We explicitly do not close our DBs because writing data onBeforeUnload() // can result in unexpected results. Namely, it seems that - even though this @@ -167,6 +178,12 @@ export class BrowserStorageService extends Disposable implements IStorageService // get triggered in this phase. this.dispose(); } + + dispose(): void { + this.runWhenIdleDisposable = dispose(this.runWhenIdleDisposable); + + super.dispose(); + } } export class FileStorageDatabase extends Disposable implements IStorageDatabase { diff --git a/src/vs/platform/storage/common/storage.ts b/src/vs/platform/storage/common/storage.ts index 6c74eca416..0f6a610b8a 100644 --- a/src/vs/platform/storage/common/storage.ts +++ b/src/vs/platform/storage/common/storage.ts @@ -102,6 +102,13 @@ export interface IStorageService { * Migrate the storage contents to another workspace. */ migrate(toWorkspace: IWorkspaceInitializationPayload): Promise; + + /** + * Allows to flush state, e.g. in cases where a shutdown is + * imminent. This will send out the onWillSaveState to ask + * everyone for latest state. + */ + flush(): void; } export const enum StorageScope { @@ -126,10 +133,11 @@ export class InMemoryStorageService extends Disposable implements IStorageServic _serviceBrand: undefined; - private readonly _onDidChangeStorage: Emitter = this._register(new Emitter()); - readonly onDidChangeStorage: Event = this._onDidChangeStorage.event; + private readonly _onDidChangeStorage = this._register(new Emitter()); + readonly onDidChangeStorage = this._onDidChangeStorage.event; - readonly onWillSaveState = Event.None; + protected readonly _onWillSaveState = this._register(new Emitter()); + readonly onWillSaveState = this._onWillSaveState.event; private globalCache: Map = new Map(); private workspaceCache: Map = new Map(); @@ -215,6 +223,10 @@ export class InMemoryStorageService extends Disposable implements IStorageServic async migrate(toWorkspace: IWorkspaceInitializationPayload): Promise { // not supported } + + flush(): void { + this._onWillSaveState.fire({ reason: WillSaveStateReason.NONE }); + } } export async function logStorage(global: Map, workspace: Map, globalPath: string, workspacePath: string): Promise { diff --git a/src/vs/platform/storage/node/storageService.ts b/src/vs/platform/storage/node/storageService.ts index 04b5f17921..8cf765711c 100644 --- a/src/vs/platform/storage/node/storageService.ts +++ b/src/vs/platform/storage/node/storageService.ts @@ -16,6 +16,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { IWorkspaceInitializationPayload, isWorkspaceIdentifier, isSingleFolderWorkspaceInitializationPayload } from 'vs/platform/workspaces/common/workspaces'; import { onUnexpectedError } from 'vs/base/common/errors'; import { assertIsDefined, assertAllDefined } from 'vs/base/common/types'; +import { RunOnceScheduler, runWhenIdle } from 'vs/base/common/async'; export class NativeStorageService extends Disposable implements IStorageService { @@ -38,6 +39,9 @@ export class NativeStorageService extends Disposable implements IStorageService private initializePromise: Promise | undefined; + private readonly periodicFlushScheduler = this._register(new RunOnceScheduler(() => this.doFlushWhenIdle(), 60000 /* every minute */)); + private runWhenIdleDisposable: IDisposable | undefined = undefined; + constructor( globalStorageDatabase: IStorageDatabase, @ILogService private readonly logService: ILogService, @@ -63,10 +67,17 @@ export class NativeStorageService extends Disposable implements IStorageService } private async doInitialize(payload: IWorkspaceInitializationPayload): Promise { + + // Init all storage locations await Promise.all([ this.initializeGlobalStorage(), this.initializeWorkspaceStorage(payload) ]); + + // On some OS we do not get enough time to persist state on shutdown (e.g. when + // Windows restarts after applying updates). In other cases, VSCode might crash, + // so we periodically save state to reduce the chance of loosing any state. + this.periodicFlushScheduler.schedule(); } private initializeGlobalStorage(): Promise { @@ -185,8 +196,32 @@ export class NativeStorageService extends Disposable implements IStorageService this.getStorage(scope).delete(key); } + private doFlushWhenIdle(): void { + + // Dispose any previous idle runner + this.runWhenIdleDisposable = dispose(this.runWhenIdleDisposable); + + // Run when idle + this.runWhenIdleDisposable = runWhenIdle(() => { + + // send event to collect state + this.flush(); + + // repeat + this.periodicFlushScheduler.schedule(); + }); + } + + flush(): void { + this._onWillSaveState.fire({ reason: WillSaveStateReason.NONE }); + } + async close(): Promise { + // Stop periodic scheduler and idle runner as we now collect state normally + this.periodicFlushScheduler.dispose(); + this.runWhenIdleDisposable = dispose(this.runWhenIdleDisposable); + // Signal as event so that clients can still store data this._onWillSaveState.fire({ reason: WillSaveStateReason.SHUTDOWN }); diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 6a418ec92a..e9b1876d71 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -790,7 +790,8 @@ declare module 'vscode' { } /** - * A reference to a named icon. Currently only [File](#ThemeIcon.File) and [Folder](#ThemeIcon.Folder) are supported. + * A reference to a named icon. Currently, [File](#ThemeIcon.File), [Folder](#ThemeIcon.Folder), + * and [codicons](https://microsoft.github.io/vscode-codicons/dist/codicon.html) are supported. * Using a theme icon is preferred over a custom icon as it gives theme authors the possibility to change the icons. */ export class ThemeIcon { @@ -804,7 +805,11 @@ declare module 'vscode' { */ static readonly Folder: ThemeIcon; - private constructor(id: string); + /** + * Creates a reference to a theme icon. + * @param id id of the icon. The avaiable icons are listed in https://microsoft.github.io/vscode-codicons/dist/codicon.html. + */ + constructor(id: string); } /** @@ -1574,17 +1579,17 @@ declare module 'vscode' { export interface QuickPickItem { /** - * A human readable string which is rendered prominent. + * A human-readable string which is rendered prominent. */ label: string; /** - * A human readable string which is rendered less prominent. + * A human-readable string which is rendered less prominent. */ description?: string; /** - * A human readable string which is rendered less prominent. + * A human-readable string which is rendered less prominent. */ detail?: string; @@ -1688,7 +1693,7 @@ declare module 'vscode' { canSelectMany?: boolean; /** - * A set of file filters that are used by the dialog. Each entry is a human readable label, + * A set of file filters that are used by the dialog. Each entry is a human-readable label, * like "TypeScript", and an array of extensions, e.g. * ```ts * { @@ -1715,7 +1720,7 @@ declare module 'vscode' { saveLabel?: string; /** - * A set of file filters that are used by the dialog. Each entry is a human readable label, + * A set of file filters that are used by the dialog. Each entry is a human-readable label, * like "TypeScript", and an array of extensions, e.g. * ```ts * { @@ -1810,7 +1815,7 @@ declare module 'vscode' { * to the user. * * @param value The current value of the input box. - * @return A human readable string which is presented as diagnostic message. + * @return A human-readable string which is presented as diagnostic message. * Return `undefined`, `null`, or the empty string when 'value' is valid. */ validateInput?(value: string): string | undefined | null | Thenable; @@ -2336,17 +2341,11 @@ declare module 'vscode' { } /** - * The MarkdownString represents human readable text that supports formatting via the + * The MarkdownString represents human-readable text that supports formatting via the * markdown syntax. Standard markdown is supported, also tables, but no embedded html. */ export class MarkdownString { - /** - * Escapes any [ThemeIcons](#ThemeIcon), e.g. `$(zap)`, in the string. - * @param value A string. - */ - static escapeThemeIcons(value: string): string; - /** * The markdown string. */ @@ -2362,9 +2361,9 @@ declare module 'vscode' { * Creates a new markdown string with the given value. * * @param value Optional, initial value. - * @param options Optional, options to specify whether [ThemeIcons](#ThemeIcon) are supported within the [`MarkdownString`](#MarkdownString). + * @param supportThemeIcons Optional, Specifies whether [ThemeIcons](#ThemeIcon) are supported within the [`MarkdownString`](#MarkdownString). */ - constructor(value?: string, options?: { supportThemeIcons?: boolean }); + constructor(value?: string, supportThemeIcons?: boolean); /** * Appends and escapes the given string to this markdown string. @@ -2373,7 +2372,7 @@ declare module 'vscode' { appendText(value: string): MarkdownString; /** - * Appends the given string 'as is' to this markdown string. + * Appends the given string 'as is' to this markdown string. When [`supportThemeIcons`](#MarkdownString.supportThemeIcons) is `true`, [ThemeIcons](#ThemeIcon) in the `value` will be iconified. * @param value Markdown string. */ appendMarkdown(value: string): MarkdownString; @@ -2387,7 +2386,7 @@ declare module 'vscode' { } /** - * ~~MarkedString can be used to render human readable text. It is either a markdown string + * ~~MarkedString can be used to render human-readable text. It is either a markdown string * or a code-block that provides a language and a code snippet. Note that * markdown strings will be sanitized - that means html will be escaped.~~ * @@ -2684,7 +2683,7 @@ declare module 'vscode' { */ export interface DocumentSymbolProviderMetadata { /** - * A human readable string that is shown when multiple outlines trees show for one document. + * A human-readable string that is shown when multiple outlines trees show for one document. */ label?: string; } @@ -4015,7 +4014,7 @@ declare module 'vscode' { * @returns A call hierarchy item or a thenable that resolves to such. The lack of a result can be * signaled by returning `undefined` or `null`. */ - prepareCallHierarchy(document: TextDocument, position: Position, token: CancellationToken): ProviderResult; + prepareCallHierarchy(document: TextDocument, position: Position, token: CancellationToken): ProviderResult; /** * Provide all incoming calls for an item, e.g all callers for a method. In graph terms this describes directed @@ -5717,7 +5716,7 @@ declare module 'vscode' { /** * Enumeration of file types. The types `File` and `Directory` can also be - * a symbolic links, in that use `FileType.File | FileType.SymbolicLink` and + * a symbolic links, in that case use `FileType.File | FileType.SymbolicLink` and * `FileType.Directory | FileType.SymbolicLink`. */ export enum FileType { @@ -5746,6 +5745,8 @@ declare module 'vscode' { /** * The type of the file, e.g. is a regular file, a directory, or symbolic link * to a file. + * + * *Note:* This value might be a bitmask, e.g. `FileType.File | FileType.SymbolicLink`. */ type: FileType; /** @@ -7375,7 +7376,7 @@ declare module 'vscode' { iconPath?: string | Uri | { light: string | Uri; dark: string | Uri } | ThemeIcon; /** - * A human readable string which is rendered less prominent. + * A human-readable string which is rendered less prominent. * When `true`, it is derived from [resourceUri](#TreeItem.resourceUri) and when `falsy`, it is not shown. */ description?: string | boolean; @@ -9584,7 +9585,45 @@ declare module 'vscode' { constructor(port: number, host?: string); } - export type DebugAdapterDescriptor = DebugAdapterExecutable | DebugAdapterServer; + /** + * A debug adapter that implements the Debug Adapter Protocol can be registered with VS Code if it implements the DebugAdapter interface. + */ + export interface DebugAdapter extends Disposable { + + /** + * 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 onDidSendMessage: Event; + + /** + * Handle a Debug Adapter Protocol message. + * Messages can be requests, responses, or events. + * Results or errors are returned via onSendMessage events. + * @param message A Debug Adapter Protocol message + */ + handleMessage(message: DebugProtocolMessage): void; + } + + /** + * A DebugProtocolMessage is an opaque stand-in type for the [ProtocolMessage](https://microsoft.github.io/debug-adapter-protocol/specification#Base_Protocol_ProtocolMessage) type defined in the Debug Adapter Protocol. + */ + export interface DebugProtocolMessage { + // Properties: see details [here](https://microsoft.github.io/debug-adapter-protocol/specification#Base_Protocol_ProtocolMessage). + } + + /** + * A debug adapter descriptor for an inline implementation. + */ + export class DebugAdapterInlineImplementation { + + /** + * Create a descriptor for an inline implementation of a debug adapter. + */ + constructor(implementation: DebugAdapter); + } + + export type DebugAdapterDescriptor = DebugAdapterExecutable | DebugAdapterServer | DebugAdapterInlineImplementation; export interface DebugAdapterDescriptorFactory { /** diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index b8ab6fe3d5..e83908f69e 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -39,9 +39,11 @@ declare module 'vscode' { name?: string; } - export interface Tunnel extends Disposable { + export interface Tunnel { remote: { port: number, host: string }; localAddress: string; + onDispose: Event; + dispose(): void; } /** @@ -72,7 +74,7 @@ declare module 'vscode' { * When not implemented, the core will use its default forwarding logic. * When implemented, the core will use this to forward ports. */ - forwardPort?(tunnelOptions: TunnelOptions): Thenable; + forwardPort?(tunnelOptions: TunnelOptions): Thenable | undefined; } export namespace workspace { @@ -782,45 +784,7 @@ declare module 'vscode' { //#endregion - //#region André: debug API for inline debug adapters https://github.com/microsoft/vscode/issues/85544 - - /** - * A DebugProtocolMessage is an opaque stand-in type for the [ProtocolMessage](https://microsoft.github.io/debug-adapter-protocol/specification#Base_Protocol_ProtocolMessage) type defined in the Debug Adapter Protocol. - */ - export interface DebugProtocolMessage { - // Properties: see details [here](https://microsoft.github.io/debug-adapter-protocol/specification#Base_Protocol_ProtocolMessage). - } - - /** - * A debug adapter that implements the Debug Adapter Protocol can be registered with VS Code if it implements the DebugAdapter interface. - */ - export interface DebugAdapter extends Disposable { - - /** - * 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 onDidSendMessage: Event; - - /** - * Handle a Debug Adapter Protocol message. - * Messages can be requests, responses, or events. - * Results or errors are returned via onSendMessage events. - * @param message A Debug Adapter Protocol message - */ - handleMessage(message: DebugProtocolMessage): void; - } - - /** - * A debug adapter descriptor for an inline implementation. - */ - export class DebugAdapterInlineImplementation { - - /** - * Create a descriptor for an inline implementation of a debug adapter. - */ - constructor(implementation: DebugAdapter); - } + //#region Debug // deprecated @@ -1179,22 +1143,77 @@ declare module 'vscode' { //#region Custom editors: https://github.com/microsoft/vscode/issues/77131 /** - * Defines how a webview editor interacts with VS Code. + * Defines the editing functionality of a webview editor. This allows the webview editor to hook into standard + * editor events such as `undo` or `save`. + * + * @param EditType Type of edits. Edit objects must be json serializable. */ - interface WebviewEditorCapabilities { + interface WebviewCustomEditorEditingDelegate { /** - * Invoked when the resource has been renamed in VS Code. + * Save a resource. * - * This is called when the resource's new name also matches the custom editor selector. + * @param resource Resource being saved. * - * If this is not implemented—or if the new resource name does not match the existing selector—then VS Code - * will close and reopen the editor on rename. - * - * @param newResource Full path to the resource. - * - * @return Thenable that signals the save is complete. + * @return Thenable signaling that the save has completed. */ - // rename?(newResource: Uri): Thenable; + save(resource: Uri): Thenable; + + /** + * Save an existing resource at a new path. + * + * @param resource Resource being saved. + * @param targetResource Location to save to. + * + * @return Thenable signaling that the save has completed. + */ + saveAs(resource: Uri, targetResource: Uri): Thenable; + + /** + * Event triggered by extensions to signal to VS Code that an edit has occurred. + */ + readonly onEdit: Event<{ readonly resource: Uri, readonly edit: EditType }>; + + /** + * Apply a set of edits. + * + * Note that is not invoked when `onEdit` is called as `onEdit` implies also updating the view to reflect the edit. + * + * @param resource Resource being edited. + * @param edit Array of edits. Sorted from oldest to most recent. + * + * @return Thenable signaling that the change has completed. + */ + applyEdits(resource: Uri, edits: readonly EditType[]): Thenable; + + /** + * Undo a set of edits. + * + * This is triggered when a user undoes an edit or when revert is called on a file. + * + * @param resource Resource being edited. + * @param edit Array of edits. Sorted from most recent to oldest. + * + * @return Thenable signaling that the change has completed. + */ + undoEdits(resource: Uri, edits: readonly EditType[]): Thenable; + } + + export interface WebviewCustomEditorProvider { + /** + * Resolve a webview editor for a given resource. + * + * To resolve a webview editor, a provider must fill in its initial html content and hook up all + * the event listeners it is interested it. The provider should also take ownership of the passed in `WebviewPanel`. + * + * @param resource Resource being resolved. + * @param webview Webview being resolved. The provider should take ownership of this webview. + * + * @return Thenable indicating that the webview editor has been resolved. + */ + resolveWebviewEditor( + resource: Uri, + webview: WebviewPanel, + ): Thenable; /** * Controls the editing functionality of a webview editor. This allows the webview editor to hook into standard @@ -1203,76 +1222,7 @@ declare module 'vscode' { * WebviewEditors that do not have `editingCapability` are considered to be readonly. Users can still interact * with readonly editors, but these editors will not integrate with VS Code's standard editor functionality. */ - readonly editingCapability?: WebviewEditorEditingCapability; - } - - /** - * Defines the editing functionality of a webview editor. This allows the webview editor to hook into standard - * editor events such as `undo` or `save`. - */ - interface WebviewEditorEditingCapability { - /** - * Persist the resource. - * - * Extensions should persist the resource - * - * @return Thenable signaling that the save has completed. - */ - save(): Thenable; - - /** - * - * @param resource Resource being saved. - * @param targetResource Location to save to. - */ - saveAs(resource: Uri, targetResource: Uri): Thenable; - - /** - * Event triggered by extensions to signal to VS Code that an edit has occurred. - * - * The edit must be a json serializable object. - */ - readonly onEdit: Event; - - /** - * Apply a set of edits. - * - * This is triggered on redo and when restoring a custom editor after restart. Note that is not invoked - * when `onEdit` is called as `onEdit` implies also updating the view to reflect the edit. - * - * @param edit Array of edits. Sorted from oldest to most recent. - */ - applyEdits(edits: readonly any[]): Thenable; - - /** - * Undo a set of edits. - * - * This is triggered when a user undoes an edit or when revert is called on a file. - * - * @param edit Array of edits. Sorted from most recent to oldest. - */ - undoEdits(edits: readonly any[]): Thenable; - } - - export interface WebviewEditorProvider { - /** - * Resolve a webview editor for a given resource. - * - * To resolve a webview editor, a provider must fill in its initial html content and hook up all - * the event listeners it is interested it. The provider should also take ownership of the passed in `WebviewPanel`. - * - * @param input Information about the resource being resolved. - * @param webview Webview being resolved. The provider should take ownership of this webview. - * - * @return Thenable to a `WebviewEditorCapabilities` indicating that the webview editor has been resolved. - * The `WebviewEditorCapabilities` defines how the custom editor interacts with VS Code. - */ - resolveWebviewEditor( - input: { - readonly resource: Uri - }, - webview: WebviewPanel, - ): Thenable; + readonly editingDelegate?: WebviewCustomEditorEditingDelegate; } namespace window { @@ -1283,11 +1233,11 @@ declare module 'vscode' { * @param provider Resolves webview editors. * @param options Content settings for a webview panels the provider is given. * - * @return Disposable that unregisters the `WebviewEditorProvider`. + * @return Disposable that unregisters the `WebviewCustomEditorProvider`. */ - export function registerWebviewEditorProvider( + export function registerWebviewCustomEditorProvider( viewType: string, - provider: WebviewEditorProvider, + provider: WebviewCustomEditorProvider, options?: WebviewPanelOptions, ): Disposable; } @@ -1356,4 +1306,198 @@ declare module 'vscode' { } //#endregion + + //#region Language specific settings: https://github.com/microsoft/vscode/issues/26707 + + export type ConfigurationScope = Uri | TextDocument | WorkspaceFolder | { resource: Uri, languageId: string }; + + /** + * An event describing the change in Configuration + */ + export interface ConfigurationChangeEvent { + + /** + * Returns `true` if the given section is affected in the provided scope. + * + * @param section Configuration name, supports _dotted_ names. + * @param scope A scope in which to check. + * @return `true` if the given section is affected in the provided scope. + */ + affectsConfiguration(section: string, scope?: ConfigurationScope): boolean; + } + + export namespace workspace { + + /** + * Get a workspace configuration object. + * + * When a section-identifier is provided only that part of the configuration + * is returned. Dots in the section-identifier are interpreted as child-access, + * like `{ myExt: { setting: { doIt: true }}}` and `getConfiguration('myExt.setting').get('doIt') === true`. + * + * When a scope is provided configuraiton confined to that scope is returned. Scope can be a resource or a language identifier or both. + * + * @param section A dot-separated identifier. + * @return The full configuration or a subset. + */ + export function getConfiguration(section?: string | undefined, scope?: ConfigurationScope | null): WorkspaceConfiguration; + + } + + /** + * Represents the configuration. It is a merged view of + * + * - *Default Settings* + * - *Global (User) Settings* + * - *Workspace settings* + * - *Workspace Folder settings* - From one of the [Workspace Folders](#workspace.workspaceFolders) under which requested resource belongs to. + * - *Language settings* - Settings defined under requested language. + * + * The *effective* value (returned by [`get`](#WorkspaceConfiguration.get)) is computed by overriding or merging the values in the following order. + * + * ``` + * `defaultValue` + * `globalValue` (if defined) + * `workspaceValue` (if defined) + * `workspaceFolderValue` (if defined) + * `defaultLanguageValue` (if defined) + * `globalLanguageValue` (if defined) + * `workspaceLanguageValue` (if defined) + * `workspaceLanguageValue` (if defined) + * ``` + * **Note:** Only `object` value types are merged and all other value types are overridden. + * + * Example 1: Overriding + * + * ```ts + * defaultValue = 'on'; + * globalValue = 'relative' + * workspaceFolderValue = 'off' + * value = 'off' + * ``` + * + * Example 2: Language Values + * + * ```ts + * defaultValue = 'on'; + * globalValue = 'relative' + * workspaceFolderValue = 'off' + * globalLanguageValue = 'on' + * value = 'on' + * ``` + * + * Example 3: Object Values + * + * ```ts + * defaultValue = { "a": 1, "b": 2 }; + * globalValue = { "b": 3, "c": 4 }; + * value = { "a": 1, "b": 3, "c": 4 }; + * ``` + * + * *Note:* Workspace and Workspace Folder configurations contains `launch` and `tasks` settings. Their basename will be + * part of the section identifier. The following snippets shows how to retrieve all configurations + * from `launch.json`: + * + * ```ts + * // launch.json configuration + * const config = workspace.getConfiguration('launch', vscode.workspace.workspaceFolders[0].uri); + * + * // retrieve values + * const values = config.get('configurations'); + * ``` + * + * Refer to [Settings](https://code.visualstudio.com/docs/getstarted/settings) for more information. + */ + export interface WorkspaceConfiguration { + + /** + * Return a value from this configuration. + * + * @param section Configuration name, supports _dotted_ names. + * @return The value `section` denotes or `undefined`. + */ + get(section: string): T | undefined; + + /** + * Return a value from this configuration. + * + * @param section Configuration name, supports _dotted_ names. + * @param defaultValue A value should be returned when no value could be found, is `undefined`. + * @return The value `section` denotes or the default. + */ + get(section: string, defaultValue: T): T; + + /** + * Check if this configuration has a certain value. + * + * @param section Configuration name, supports _dotted_ names. + * @return `true` if the section doesn't resolve to `undefined`. + */ + has(section: string): boolean; + + /** + * Retrieve all information about a configuration setting. A configuration value + * often consists of a *default* value, a global or installation-wide value, + * a workspace-specific value, folder-specific value + * and language-specific values (if [WorkspaceConfiguration](#WorkspaceConfiguration) is scoped to a language). + * + * *Note:* The configuration name must denote a leaf in the configuration tree + * (`editor.fontSize` vs `editor`) otherwise no result is returned. + * + * @param section Configuration name, supports _dotted_ names. + * @return Information about a configuration setting or `undefined`. + */ + inspect(section: string): { + key: string; + + defaultValue?: T; + globalValue?: T; + workspaceValue?: T, + workspaceFolderValue?: T, + + defaultLanguageValue?: T; + userLanguageValue?: T; + workspaceLanguageValue?: T; + workspaceFolderLanguageValue?: T; + + } | undefined; + + /** + * Update a configuration value. The updated configuration values are persisted. + * + * A value can be changed in + * + * - [Global settings](#ConfigurationTarget.Global): Changes the value for all instances of the editor. + * - [Workspace settings](#ConfigurationTarget.Workspace): Changes the value for current workspace, if available. + * - [Workspace folder settings](#ConfigurationTarget.WorkspaceFolder): Changes the value for settings from one of the [Workspace Folders](#workspace.workspaceFolders) under which the requested resource belongs to. + * - Language settings: Changes the value for the requested languageId. + * + * *Note:* To remove a configuration value use `undefined`, like so: `config.update('somekey', undefined)` + * + * @param section Configuration name, supports _dotted_ names. + * @param value The new value. + * @param configurationTarget The [configuration target](#ConfigurationTarget) or a boolean value. + * - If `true` updates [Global settings](#ConfigurationTarget.Global). + * - If `false` updates [Workspace settings](#ConfigurationTarget.Workspace). + * - If `undefined` or `null` updates to [Workspace folder settings](#ConfigurationTarget.WorkspaceFolder) if configuration is resource specific, + * otherwise to [Workspace settings](#ConfigurationTarget.Workspace). + * @param scopeToLanguage Whether to update the value in the scope of requested languageId or not. + * - If `true` updates the value under the requested languageId. + * - If `undefined` updates the value under the requested languageId only if the configuration is defined for the language. + * @throws error while updating + * - configuration which is not registered. + * - window configuration to workspace folder + * - configuration to workspace or workspace folder when no workspace is opened. + * - configuration to workspace folder when there is no workspace folder settings. + * - configuration to workspace folder when [WorkspaceConfiguration](#WorkspaceConfiguration) is not scoped to a resource. + */ + update(section: string, value: any, configurationTarget?: ConfigurationTarget | boolean, scopeToLanguage?: boolean): Thenable; + + /** + * Readable dictionary that backs this configuration. + */ + readonly [key: string]: any; + } + + //#endregion } diff --git a/src/vs/workbench/api/browser/mainThreadConfiguration.ts b/src/vs/workbench/api/browser/mainThreadConfiguration.ts index e4c6548248..c2920b8887 100644 --- a/src/vs/workbench/api/browser/mainThreadConfiguration.ts +++ b/src/vs/workbench/api/browser/mainThreadConfiguration.ts @@ -3,14 +3,14 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { URI, UriComponents } from 'vs/base/common/uri'; +import { URI } from 'vs/base/common/uri'; 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, IConfigurationInitData } from '../common/extHost.protocol'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; -import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ConfigurationTarget, IConfigurationService, IConfigurationOverrides } from 'vs/platform/configuration/common/configuration'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @extHostNamedCustomer(MainContext.MainThreadConfiguration) @@ -45,25 +45,45 @@ export class MainThreadConfiguration implements MainThreadConfigurationShape { this._configurationListener.dispose(); } - $updateConfigurationOption(target: ConfigurationTarget | null, key: string, value: any, resourceUriComponenets: UriComponents | undefined): Promise { - const resource = resourceUriComponenets ? URI.revive(resourceUriComponenets) : null; - return this.writeConfiguration(target, key, value, resource); + $updateConfigurationOption(target: ConfigurationTarget | null, key: string, value: any, overrides: IConfigurationOverrides | undefined, scopeToLanguage: boolean | undefined): Promise { + overrides = { resource: overrides?.resource ? URI.revive(overrides.resource) : undefined, overrideIdentifier: overrides?.overrideIdentifier }; + return this.writeConfiguration(target, key, value, overrides, scopeToLanguage); } - $removeConfigurationOption(target: ConfigurationTarget | null, key: string, resourceUriComponenets: UriComponents | undefined): Promise { - const resource = resourceUriComponenets ? URI.revive(resourceUriComponenets) : null; - return this.writeConfiguration(target, key, undefined, resource); + $removeConfigurationOption(target: ConfigurationTarget | null, key: string, overrides: IConfigurationOverrides | undefined, scopeToLanguage: boolean | undefined): Promise { + overrides = { resource: overrides?.resource ? URI.revive(overrides.resource) : undefined, overrideIdentifier: overrides?.overrideIdentifier }; + return this.writeConfiguration(target, key, undefined, overrides, scopeToLanguage); } - private writeConfiguration(target: ConfigurationTarget | null, key: string, value: any, resource: URI | null): Promise { - target = target !== null && target !== undefined ? target : this.deriveConfigurationTarget(key, resource); - return this.configurationService.updateValue(key, value, { resource }, target, true); + private writeConfiguration(target: ConfigurationTarget | null, key: string, value: any, overrides: IConfigurationOverrides, scopeToLanguage: boolean | undefined): Promise { + target = target !== null && target !== undefined ? target : this.deriveConfigurationTarget(key, overrides); + const configurationValue = this.configurationService.inspect(key, overrides); + switch (target) { + case ConfigurationTarget.MEMORY: + return this._updateValue(key, value, target, configurationValue?.memory?.override, overrides, scopeToLanguage); + case ConfigurationTarget.WORKSPACE_FOLDER: + return this._updateValue(key, value, target, configurationValue?.workspaceFolder?.override, overrides, scopeToLanguage); + case ConfigurationTarget.WORKSPACE: + return this._updateValue(key, value, target, configurationValue?.workspace?.override, overrides, scopeToLanguage); + case ConfigurationTarget.USER_REMOTE: + return this._updateValue(key, value, target, configurationValue?.userRemote?.override, overrides, scopeToLanguage); + default: + return this._updateValue(key, value, target, configurationValue?.userLocal?.override, overrides, scopeToLanguage); + } } - private deriveConfigurationTarget(key: string, resource: URI | null): ConfigurationTarget { - if (resource && this._workspaceContextService.getWorkbenchState() === WorkbenchState.WORKSPACE) { + private _updateValue(key: string, value: any, configurationTarget: ConfigurationTarget, overriddenValue: any | undefined, overrides: IConfigurationOverrides, scopeToLanguage: boolean | undefined): Promise { + overrides = scopeToLanguage === true ? overrides + : scopeToLanguage === false ? { resource: overrides.resource } + : overrides.overrideIdentifier && overriddenValue !== undefined ? overrides + : { resource: overrides.resource }; + return this.configurationService.updateValue(key, value, overrides, configurationTarget); + } + + private deriveConfigurationTarget(key: string, overrides: IConfigurationOverrides): ConfigurationTarget { + if (overrides.resource && this._workspaceContextService.getWorkbenchState() === WorkbenchState.WORKSPACE) { const configurationProperties = Registry.as(ConfigurationExtensions.Configuration).getConfigurationProperties(); - if (configurationProperties[key] && configurationProperties[key].scope === ConfigurationScope.RESOURCE) { + if (configurationProperties[key] && (configurationProperties[key].scope === ConfigurationScope.RESOURCE || configurationProperties[key].scope === ConfigurationScope.RESOURCE_LANGUAGE)) { return ConfigurationTarget.WORKSPACE_FOLDER; } } diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index 9c1c865988..c04a92c7fc 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -506,13 +506,17 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha this._registrations.set(handle, callh.CallHierarchyProviderRegistry.register(selector, { prepareCallHierarchy: async (document, position, token) => { - const item = await this._proxy.$prepareCallHierarchy(handle, document.uri, position, token); - if (!item) { + const items = await this._proxy.$prepareCallHierarchy(handle, document.uri, position, token); + if (!items) { return undefined; } return { - dispose: () => this._proxy.$releaseCallHierarchy(handle, item._sessionId), - root: MainThreadLanguageFeatures._reviveCallHierarchyItemDto(item) + dispose: () => { + for (const item of items) { + this._proxy.$releaseCallHierarchy(handle, item._sessionId); + } + }, + roots: items.map(MainThreadLanguageFeatures._reviveCallHierarchyItemDto) }; }, diff --git a/src/vs/workbench/api/browser/mainThreadTask.ts b/src/vs/workbench/api/browser/mainThreadTask.ts index 6a5bcfc4a3..1cd2760c4a 100644 --- a/src/vs/workbench/api/browser/mainThreadTask.ts +++ b/src/vs/workbench/api/browser/mainThreadTask.ts @@ -514,15 +514,19 @@ export class MainThreadTask implements MainThreadTaskShape { if (TaskHandleDTO.is(value)) { const workspaceFolder = this._workspaceContextServer.getWorkspaceFolder(URI.revive(value.workspaceFolder)); if (workspaceFolder) { - this._taskService.getTask(workspaceFolder, value.id, true).then((task: Task) => { - this._taskService.run(task).then(undefined, reason => { - // eat the error, it has already been surfaced to the user and we don't care about it here - }); - const result: TaskExecutionDTO = { - id: value.id, - task: TaskDTO.from(task) - }; - resolve(result); + this._taskService.getTask(workspaceFolder, value.id, true).then((task: Task | undefined) => { + if (!task) { + reject(new Error('Task not found')); + } else { + this._taskService.run(task).then(undefined, reason => { + // eat the error, it has already been surfaced to the user and we don't care about it here + }); + const result: TaskExecutionDTO = { + id: value.id, + task: TaskDTO.from(task) + }; + resolve(result); + } }, (_error) => { reject(new Error('Task not found')); }); diff --git a/src/vs/workbench/api/browser/mainThreadTunnelService.ts b/src/vs/workbench/api/browser/mainThreadTunnelService.ts index 270c74b1d8..02c77e11b3 100644 --- a/src/vs/workbench/api/browser/mainThreadTunnelService.ts +++ b/src/vs/workbench/api/browser/mainThreadTunnelService.ts @@ -7,6 +7,7 @@ import { MainThreadTunnelServiceShape, IExtHostContext, MainContext, ExtHostCont import { TunnelOptions, TunnelDto } from 'vs/workbench/api/common/extHostTunnelService'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { IRemoteExplorerService } from 'vs/workbench/services/remote/common/remoteExplorerService'; +import { ITunnelProvider, ITunnelService } from 'vs/platform/remote/common/tunnel'; @extHostNamedCustomer(MainContext.MainThreadTunnelService) export class MainThreadTunnelService implements MainThreadTunnelServiceShape { @@ -14,7 +15,8 @@ export class MainThreadTunnelService implements MainThreadTunnelServiceShape { constructor( extHostContext: IExtHostContext, - @IRemoteExplorerService private readonly remoteExplorerService: IRemoteExplorerService + @IRemoteExplorerService private readonly remoteExplorerService: IRemoteExplorerService, + @ITunnelService private readonly tunnelService: ITunnelService ) { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTunnelService); } @@ -22,7 +24,7 @@ export class MainThreadTunnelService implements MainThreadTunnelServiceShape { async $openTunnel(tunnelOptions: TunnelOptions): Promise { const tunnel = await this.remoteExplorerService.forward(tunnelOptions.remote.port, tunnelOptions.localPort, tunnelOptions.name); if (tunnel) { - return { remote: { host: tunnel.tunnelRemoteHost, port: tunnel.tunnelRemotePort }, localAddress: tunnel.localAddress }; + return TunnelDto.fromServiceTunnel(tunnel); } return undefined; } @@ -31,14 +33,32 @@ export class MainThreadTunnelService implements MainThreadTunnelServiceShape { return this.remoteExplorerService.close(remotePort); } - $addDetected(tunnels: { remote: { port: number, host: string }, localAddress: string }[]): Promise { - return Promise.resolve(this.remoteExplorerService.addDetected(tunnels)); - } - async $registerCandidateFinder(): Promise { this.remoteExplorerService.registerCandidateFinder(() => this._proxy.$findCandidatePorts()); } + async $setTunnelProvider(): Promise { + const tunnelProvider: ITunnelProvider = { + forwardPort: (tunnelOptions: TunnelOptions) => { + const forward = this._proxy.$forwardPort(tunnelOptions); + if (forward) { + return forward.then(tunnel => { + return { + tunnelRemotePort: tunnel.remote.port, + tunnelRemoteHost: tunnel.remote.host, + localAddress: tunnel.localAddress, + dispose: () => { + this._proxy.$closeTunnel({ host: tunnel.remote.host, port: tunnel.remote.port }); + } + }; + }); + } + return undefined; + } + }; + this.tunnelService.setTunnelProvider(tunnelProvider); + } + dispose(): void { // } diff --git a/src/vs/workbench/api/browser/mainThreadWebview.ts b/src/vs/workbench/api/browser/mainThreadWebview.ts index b93f33c3b7..75684f6708 100644 --- a/src/vs/workbench/api/browser/mainThreadWebview.ts +++ b/src/vs/workbench/api/browser/mainThreadWebview.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { onUnexpectedError } from 'vs/base/common/errors'; -import { Disposable, IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { isWeb } from 'vs/base/common/platform'; import { startsWith } from 'vs/base/common/strings'; @@ -20,7 +20,7 @@ import { editorGroupToViewColumn, EditorViewColumn, viewColumnToEditorGroup } fr import { IEditorInput } from 'vs/workbench/common/editor'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { CustomFileEditorInput } from 'vs/workbench/contrib/customEditor/browser/customEditorInput'; -import { ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; +import { ICustomEditorModel, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; import { WebviewExtensionDescription } from 'vs/workbench/contrib/webview/browser/webview'; import { WebviewInput } from 'vs/workbench/contrib/webview/browser/webviewEditorInput'; import { ICreateWebViewShowOptions, IWebviewWorkbenchService, WebviewInputOptions } from 'vs/workbench/contrib/webview/browser/webviewWorkbenchService'; @@ -95,6 +95,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma private readonly _webviewInputs = new WebviewInputStore(); private readonly _revivers = new Map(); private readonly _editorProviders = new Map(); + private readonly _customEditorModels = new Map(); constructor( context: extHostProtocol.IExtHostContext, @@ -252,7 +253,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma this._revivers.delete(viewType); } - public $registerEditorProvider(extensionData: extHostProtocol.WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions): void { + public $registerEditorProvider(extensionData: extHostProtocol.WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions, capabilities: readonly extHostProtocol.WebviewEditorCapabilities[]): void { if (this._editorProviders.has(viewType)) { throw new Error(`Provider for ${viewType} already registered`); } @@ -270,15 +271,16 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma webviewInput.webview.options = options; webviewInput.webview.extension = extension; + const resource = webviewInput.getResource(); - const model = await this.getModel(webviewInput); + const model = await this.retainCustomEditorModel(webviewInput, resource, viewType, capabilities); webviewInput.onDisposeWebview(() => { - this._customEditorService.models.disposeModel(model); + this.releaseCustomEditorModel(model); }); try { await this._proxy.$resolveWebviewEditor( - { resource: webviewInput.getResource(), edits: model.currentEdits }, + resource, handle, viewType, webviewInput.getTitle(), @@ -304,43 +306,62 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma this._editorProviders.delete(viewType); } - public async $registerCapabilities(handle: extHostProtocol.WebviewPanelHandle, capabilities: readonly extHostProtocol.WebviewEditorCapabilities[]): Promise { - const webviewInput = this.getWebviewInput(handle); - const model = await this.getModel(webviewInput); + private async retainCustomEditorModel(webviewInput: WebviewInput, resource: URI, viewType: string, capabilities: readonly extHostProtocol.WebviewEditorCapabilities[]) { + const model = await this._customEditorService.models.loadOrCreate(webviewInput.getResource(), webviewInput.viewType); + + const existingEntry = this._customEditorModels.get(model); + if (existingEntry) { + ++existingEntry.referenceCount; + // no need to hook up listeners again + return model; + } + + this._customEditorModels.set(model, { referenceCount: 1 }); const capabilitiesSet = new Set(capabilities); if (capabilitiesSet.has(extHostProtocol.WebviewEditorCapabilities.Editable)) { - model.onUndo(edits => { this._proxy.$undoEdits(handle, edits.map(x => x.data)); }); + model.onUndo(edits => { + this._proxy.$undoEdits(resource, viewType, edits.map(x => x.data)); + }); model.onApplyEdit(edits => { - const editsToApply = edits.filter(x => x.source !== webviewInput).map(x => x.data); + const editsToApply = edits.filter(x => x.source !== model).map(x => x.data); if (editsToApply.length) { - this._proxy.$applyEdits(handle, editsToApply); + this._proxy.$applyEdits(resource, viewType, editsToApply); } }); - model.onWillSave(e => { e.waitUntil(this._proxy.$onSave(handle)); }); + model.onWillSave(e => { + e.waitUntil(this._proxy.$onSave(resource.toJSON(), viewType)); + }); - model.onWillSaveAs(e => { e.waitUntil(this._proxy.$onSaveAs(handle, e.resource.toJSON(), e.targetResource.toJSON())); }); + model.onWillSaveAs(e => { + e.waitUntil(this._proxy.$onSaveAs(e.resource.toJSON(), viewType, e.targetResource.toJSON())); + }); + } + return model; + } + + private async releaseCustomEditorModel(model: ICustomEditorModel) { + const entry = this._customEditorModels.get(model); + if (!entry) { + return; + } + + --entry.referenceCount; + if (entry.referenceCount <= 0) { + this._customEditorService.models.disposeModel(model); + this._customEditorModels.delete(model); } } - private getModel(webviewInput: WebviewInput) { - return this._customEditorService.models.loadOrCreate(webviewInput.getResource(), webviewInput.viewType); - } - - public $onEdit(handle: extHostProtocol.WebviewPanelHandle, editData: any): void { - const webview = this.getWebviewInput(handle); - if (!(webview instanceof CustomFileEditorInput)) { - throw new Error('Webview is not a webview editor'); - } - - const model = this._customEditorService.models.get(webview.getResource(), webview.viewType); + public $onEdit(resource: UriComponents, viewType: string, editData: any): void { + const model = this._customEditorService.models.get(URI.revive(resource), viewType); if (!model) { throw new Error('Could not find model for webview editor'); } - model.makeEdit({ source: webview, data: editData }); + model.pushEdit({ source: model, data: editData }); } private hookupWebviewEventDelegate(handle: extHostProtocol.WebviewPanelHandle, input: WebviewInput) { diff --git a/src/vs/workbench/api/browser/viewsExtensionPoint.ts b/src/vs/workbench/api/browser/viewsExtensionPoint.ts index 6fcd443fc0..25b42bada7 100644 --- a/src/vs/workbench/api/browser/viewsExtensionPoint.ts +++ b/src/vs/workbench/api/browser/viewsExtensionPoint.ts @@ -8,7 +8,7 @@ import { forEach } from 'vs/base/common/collections'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import * as resources from 'vs/base/common/resources'; import { ExtensionMessageCollector, ExtensionsRegistry, IExtensionPoint, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -import { ViewContainer, IViewsRegistry, ITreeViewDescriptor, IViewContainersRegistry, Extensions as ViewContainerExtensions, TEST_VIEW_CONTAINER_ID, IViewDescriptor } from 'vs/workbench/common/views'; +import { ViewContainer, IViewsRegistry, ITreeViewDescriptor, IViewContainersRegistry, Extensions as ViewContainerExtensions, TEST_VIEW_CONTAINER_ID, IViewDescriptor, ViewContainerLocation } from 'vs/workbench/common/views'; import { CustomTreeViewPane, CustomTreeView } from 'vs/workbench/browser/parts/views/customView'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { coalesce, } from 'vs/base/common/arrays'; @@ -313,7 +313,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution { if (!viewContainer) { - viewContainer = this.viewContainersRegistry.registerViewContainer(id, true, extensionId); + viewContainer = this.viewContainersRegistry.registerViewContainer(id, ViewContainerLocation.Sidebar, true, extensionId); class CustomViewPaneContainer extends ViewPaneContainer { constructor( diff --git a/src/vs/workbench/api/common/configurationExtensionPoint.ts b/src/vs/workbench/api/common/configurationExtensionPoint.ts index 09b0214b05..588583cb01 100644 --- a/src/vs/workbench/api/common/configurationExtensionPoint.ts +++ b/src/vs/workbench/api/common/configurationExtensionPoint.ts @@ -8,7 +8,7 @@ import * as objects from 'vs/base/common/objects'; import { Registry } from 'vs/platform/registry/common/platform'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { ExtensionsRegistry, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -import { IConfigurationNode, IConfigurationRegistry, Extensions, editorConfigurationSchemaId, IDefaultConfigurationExtension, validateProperty, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; +import { IConfigurationNode, IConfigurationRegistry, Extensions, resourceLanguageSettingsSchemaId, IDefaultConfigurationExtension, validateProperty, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { workspaceSettingsSchemaId, launchSchemaId, tasksSchemaId } from 'vs/workbench/services/configuration/common/configuration'; import { isObject } from 'vs/base/common/types'; @@ -48,7 +48,7 @@ const configurationEntrySchema: IJSONSchema = { nls.localize('scope.resource.description', "Configuration that can be configured in the user, remote, workspace or folder settings."), nls.localize('scope.machine-overridable.description', "Machine configuration that can be configured also in workspace or folder settings.") ], - description: nls.localize('scope.description', "Scope in which the configuration is applicable. Available scopes are `application`, `machine`, `window`, `resource` and `machine-overridable`.") + description: nls.localize('scope.description', "Scope in which the configuration is applicable. Available scopes are `application`, `machine`, `window`, `resource`, and `machine-overridable`.") }, enumDescriptions: { type: 'array', @@ -57,12 +57,12 @@ const configurationEntrySchema: IJSONSchema = { }, description: nls.localize('scope.enumDescriptions', 'Descriptions for enum values') }, - markdownEnumDescription: { + markdownEnumDescriptions: { type: 'array', items: { type: 'string', }, - description: nls.localize('scope.markdownEnumDescription', 'Descriptions for enum values in the markdown format.') + description: nls.localize('scope.markdownEnumDescriptions', 'Descriptions for enum values in the markdown format.') }, markdownDescription: { type: 'string', @@ -90,7 +90,7 @@ const defaultConfigurationExtPoint = ExtensionsRegistry.registerExtensionPoint { return extHostWebviews.registerWebviewPanelSerializer(extension, viewType, serializer); }, - registerWebviewEditorProvider: (viewType: string, provider: vscode.WebviewEditorProvider, options?: vscode.WebviewPanelOptions) => { + registerWebviewCustomEditorProvider: (viewType: string, provider: vscode.WebviewCustomEditorProvider, options?: vscode.WebviewPanelOptions) => { checkProposedApiEnabled(extension); - return extHostWebviews.registerWebviewEditorProvider(extension, viewType, provider, options); + return extHostWebviews.registerWebviewCustomEditorProvider(extension, viewType, provider, options); }, registerDecorationProvider(provider: vscode.DecorationProvider) { checkProposedApiEnabled(extension); @@ -670,9 +670,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I onDidChangeConfiguration: (listener: (_: any) => any, thisArgs?: any, disposables?: extHostTypes.Disposable[]) => { return configProvider.onDidChangeConfiguration(listener, thisArgs, disposables); }, - getConfiguration(section?: string, resource?: vscode.Uri): vscode.WorkspaceConfiguration { - resource = arguments.length === 1 ? undefined : resource; - return configProvider.getConfiguration(section, resource, extension.identifier); + getConfiguration(section?: string, scope?: vscode.ConfigurationScope | null): vscode.WorkspaceConfiguration { + scope = arguments.length === 1 ? undefined : scope; + return configProvider.getConfiguration(section, scope, extension.identifier); }, registerTextDocumentContentProvider(scheme: string, provider: vscode.TextDocumentContentProvider) { return extHostDocumentContentProviders.registerTextDocumentContentProvider(scheme, provider); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index be1a2d45f8..70b54101e0 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, IConfigurationChange } from 'vs/platform/configuration/common/configuration'; +import { ConfigurationTarget, IConfigurationData, IConfigurationChange, IConfigurationOverrides } 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'; @@ -151,8 +151,8 @@ export interface MainThreadCommentsShape extends IDisposable { } export interface MainThreadConfigurationShape extends IDisposable { - $updateConfigurationOption(target: ConfigurationTarget | null, key: string, value: any, resource: UriComponents | undefined): Promise; - $removeConfigurationOption(target: ConfigurationTarget | null, key: string, resource: UriComponents | undefined): Promise; + $updateConfigurationOption(target: ConfigurationTarget | null, key: string, value: any, overrides: IConfigurationOverrides | undefined, scopeToLanguage: boolean | undefined): Promise; + $removeConfigurationOption(target: ConfigurationTarget | null, key: string, overrides: IConfigurationOverrides | undefined, scopeToLanguage: boolean | undefined): Promise; } export interface MainThreadDiagnosticsShape extends IDisposable { @@ -576,11 +576,10 @@ export interface MainThreadWebviewsShape extends IDisposable { $registerSerializer(viewType: string): void; $unregisterSerializer(viewType: string): void; - $registerEditorProvider(extension: WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions): void; + $registerEditorProvider(extension: WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions, capabilities: readonly WebviewEditorCapabilities[]): void; $unregisterEditorProvider(viewType: string): void; - $registerCapabilities(handle: WebviewPanelHandle, capabilities: readonly WebviewEditorCapabilities[]): void; - $onEdit(handle: WebviewPanelHandle, editJson: any): void; + $onEdit(resource: UriComponents, viewType: string, editJson: any): void; } export interface WebviewPanelViewStateData { @@ -598,13 +597,13 @@ export interface ExtHostWebviewsShape { $onDidDisposeWebviewPanel(handle: WebviewPanelHandle): Promise; $deserializeWebviewPanel(newWebviewHandle: WebviewPanelHandle, viewType: string, title: string, state: any, position: EditorViewColumn, options: modes.IWebviewOptions & modes.IWebviewPanelOptions): Promise; - $resolveWebviewEditor(input: { resource: UriComponents, edits: readonly any[] }, newWebviewHandle: WebviewPanelHandle, viewType: string, title: string, position: EditorViewColumn, options: modes.IWebviewOptions & modes.IWebviewPanelOptions): Promise; + $resolveWebviewEditor(resource: UriComponents, newWebviewHandle: WebviewPanelHandle, viewType: string, title: string, position: EditorViewColumn, options: modes.IWebviewOptions & modes.IWebviewPanelOptions): Promise; - $undoEdits(handle: WebviewPanelHandle, edits: readonly any[]): void; - $applyEdits(handle: WebviewPanelHandle, edits: readonly any[]): void; + $undoEdits(resource: UriComponents, viewType: string, edits: readonly any[]): void; + $applyEdits(resource: UriComponents, viewType: string, edits: readonly any[]): void; - $onSave(handle: WebviewPanelHandle): Promise; - $onSaveAs(handle: WebviewPanelHandle, resource: UriComponents, targetResource: UriComponents): Promise; + $onSave(resource: UriComponents, viewType: string): Promise; + $onSaveAs(resource: UriComponents, viewType: string, targetResource: UriComponents): Promise; } export interface MainThreadUrlsShape extends IDisposable { @@ -780,8 +779,8 @@ export interface MainThreadWindowShape extends IDisposable { 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; + $setTunnelProvider(): Promise; } // -- extension host @@ -1194,7 +1193,7 @@ export interface ExtHostLanguageFeaturesShape { $provideColorPresentations(handle: number, resource: UriComponents, colorInfo: IRawColorInfo, token: CancellationToken): Promise; $provideFoldingRanges(handle: number, resource: UriComponents, context: modes.FoldingContext, token: CancellationToken): Promise; $provideSelectionRanges(handle: number, resource: UriComponents, positions: IPosition[], token: CancellationToken): Promise; - $prepareCallHierarchy(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; + $prepareCallHierarchy(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; $provideCallHierarchyIncomingCalls(handle: number, sessionId: string, itemId: string, token: CancellationToken): Promise; $provideCallHierarchyOutgoingCalls(handle: number, sessionId: string, itemId: string, token: CancellationToken): Promise; $releaseCallHierarchy(handle: number, sessionId: string): void; @@ -1401,6 +1400,8 @@ export interface ExtHostStorageShape { export interface ExtHostTunnelServiceShape { $findCandidatePorts(): Promise<{ port: number, detail: string }[]>; + $forwardPort(tunnelOptions: TunnelOptions): Promise | undefined; + $closeTunnel(remote: { host: string, port: number }): Promise; } // --- proxy identifiers diff --git a/src/vs/workbench/api/common/extHostConfiguration.ts b/src/vs/workbench/api/common/extHostConfiguration.ts index 36d88b8dbc..30369cd17e 100644 --- a/src/vs/workbench/api/common/extHostConfiguration.ts +++ b/src/vs/workbench/api/common/extHostConfiguration.ts @@ -4,13 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import { mixin, deepClone } from 'vs/base/common/objects'; -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, IConfigurationInitData, MainContext } from './extHost.protocol'; import { ConfigurationTarget as ExtHostConfigurationTarget } from './extHostTypes'; -import { ConfigurationTarget, IConfigurationChange, IConfigurationData } from 'vs/platform/configuration/common/configuration'; +import { ConfigurationTarget, IConfigurationChange, IConfigurationData, IConfigurationOverrides } 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'; @@ -20,6 +19,7 @@ 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'; +import { URI } from 'vs/base/common/uri'; function lookUp(tree: any, key: string) { if (key) { @@ -34,12 +34,57 @@ function lookUp(tree: any, key: string) { type ConfigurationInspect = { key: string; + defaultValue?: T; globalValue?: T; - workspaceValue?: T; - workspaceFolderValue?: T; + workspaceValue?: T, + workspaceFolderValue?: T, + + defaultLanguageValue?: T; + userLanguageValue?: T; + workspaceLanguageValue?: T; + workspaceFolderLanguageValue?: T; }; +function isTextDocument(thing: any): thing is vscode.TextDocument { + return thing + && thing.uri instanceof URI + && (!thing.languageId || typeof thing.languageId === 'string'); +} + +function isWorkspaceFolder(thing: any): thing is vscode.WorkspaceFolder { + return thing + && thing.uri instanceof URI + && (!thing.name || typeof thing.name === 'string') + && (!thing.index || typeof thing.index === 'number'); +} + +function isUri(thing: any): thing is vscode.Uri { + return thing instanceof URI; +} + +function isResourceLanguage(thing: any): thing is { resource: URI, languageId: string } { + return thing + && thing.resource instanceof URI + && (!thing.languageId || typeof thing.languageId === 'string'); +} + +function scopeToOverrides(scope: vscode.ConfigurationScope | undefined | null): IConfigurationOverrides | undefined { + if (isUri(scope)) { + return { resource: scope }; + } + if (isWorkspaceFolder(scope)) { + return { resource: scope.uri }; + } + if (isTextDocument(scope)) { + return { resource: scope.uri, overrideIdentifier: scope.languageId }; + } + if (isResourceLanguage(scope)) { + return scope; + } + return undefined; +} + export class ExtHostConfiguration implements ExtHostConfigurationShape { readonly _serviceBrand: undefined; @@ -104,13 +149,14 @@ export class ExtHostConfigProvider { this._onDidChangeConfiguration.fire(this._toConfigurationChangeEvent(change, previous)); } - getConfiguration(section?: string, resource?: URI, extensionId?: ExtensionIdentifier): vscode.WorkspaceConfiguration { + getConfiguration(section?: string, scope?: vscode.ConfigurationScope | null, extensionId?: ExtensionIdentifier): vscode.WorkspaceConfiguration { + const overrides = scopeToOverrides(scope) || {}; const config = this._toReadonlyValue(section - ? lookUp(this._configuration.getValue(undefined, { resource }, this._extHostWorkspace.workspace), section) - : this._configuration.getValue(undefined, { resource }, this._extHostWorkspace.workspace)); + ? lookUp(this._configuration.getValue(undefined, overrides, this._extHostWorkspace.workspace), section) + : this._configuration.getValue(undefined, overrides, this._extHostWorkspace.workspace)); if (section) { - this._validateConfigurationAccess(section, resource, extensionId); + this._validateConfigurationAccess(section, overrides, extensionId); } function parseConfigurationTarget(arg: boolean | ExtHostConfigurationTarget): ConfigurationTarget | null { @@ -133,7 +179,7 @@ export class ExtHostConfigProvider { return typeof lookUp(config, key) !== 'undefined'; }, get: (key: string, defaultValue?: T) => { - this._validateConfigurationAccess(section ? `${section}.${key}` : key, resource, extensionId); + this._validateConfigurationAccess(section ? `${section}.${key}` : key, overrides, extensionId); let result = lookUp(config, key); if (typeof result === 'undefined') { result = defaultValue; @@ -189,25 +235,31 @@ export class ExtHostConfigProvider { } return result; }, - update: (key: string, value: any, arg: ExtHostConfigurationTarget | boolean) => { + update: (key: string, value: any, extHostConfigurationTarget: ExtHostConfigurationTarget | boolean, scopeToLanguage?: boolean) => { key = section ? `${section}.${key}` : key; - const target = parseConfigurationTarget(arg); + const target = parseConfigurationTarget(extHostConfigurationTarget); if (value !== undefined) { - return this._proxy.$updateConfigurationOption(target, key, value, resource); + return this._proxy.$updateConfigurationOption(target, key, value, overrides, scopeToLanguage); } else { - return this._proxy.$removeConfigurationOption(target, key, resource); + return this._proxy.$removeConfigurationOption(target, key, overrides, scopeToLanguage); } }, inspect: (key: string): ConfigurationInspect | undefined => { key = section ? `${section}.${key}` : key; - const config = deepClone(this._configuration.inspect(key, { resource }, this._extHostWorkspace.workspace)); + const config = deepClone(this._configuration.inspect(key, overrides, this._extHostWorkspace.workspace)); if (config) { return { key, - defaultValue: config.default, - globalValue: config.user, - workspaceValue: config.workspace, - workspaceFolderValue: config.workspaceFolder + + defaultValue: config.defaultValue, + globalValue: config.userValue, + workspaceValue: config.workspaceValue, + workspaceFolderValue: config.workspaceFolderValue, + + defaultLanguageValue: config.default?.override, + userLanguageValue: config.user?.override, + workspaceLanguageValue: config.workspace?.override, + workspaceFolderLanguageValue: config.workspaceFolder?.override, }; } return undefined; @@ -237,17 +289,17 @@ export class ExtHostConfigProvider { return readonlyProxy(result); } - private _validateConfigurationAccess(key: string, resource: URI | undefined, extensionId?: ExtensionIdentifier): void { + private _validateConfigurationAccess(key: string, overrides?: IConfigurationOverrides, extensionId?: ExtensionIdentifier): void { const scope = OVERRIDE_PROPERTY_PATTERN.test(key) ? ConfigurationScope.RESOURCE : this._configurationScopes.get(key); const extensionIdText = extensionId ? `[${extensionId.value}] ` : ''; if (ConfigurationScope.RESOURCE === scope) { - if (resource === undefined) { + if (overrides?.resource) { this._logService.warn(`${extensionIdText}Accessing a resource scoped configuration without providing a resource is not expected. To get the effective value for '${key}', provide the URI of a resource or 'null' for any resource.`); } return; } if (ConfigurationScope.WINDOW === scope) { - if (resource) { + if (overrides?.resource) { this._logService.warn(`${extensionIdText}Accessing a window scoped configuration for a resource is not expected. To associate '${key}' to a resource, define its scope to 'resource' in configuration contributions in 'package.json'.`); } return; @@ -257,7 +309,7 @@ export class ExtHostConfigProvider { 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 ? { resource } : undefined) + affectsConfiguration: (section: string, scope?: vscode.ConfigurationScope) => event.affectsConfiguration(section, scopeToOverrides(scope)) }); } diff --git a/src/vs/workbench/api/common/extHostExtensionService.ts b/src/vs/workbench/api/common/extHostExtensionService.ts index f672a68a52..0f0c8dd931 100644 --- a/src/vs/workbench/api/common/extHostExtensionService.ts +++ b/src/vs/workbench/api/common/extHostExtensionService.ts @@ -645,6 +645,7 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio try { const result = await resolver.resolve(remoteAuthority, { resolveAttempt }); + this._disposables.add(await this._extHostTunnelService.setForwardPortProvider(resolver)); // Split merged API result into separate authority/options const authority: ResolvedAuthority = { @@ -656,13 +657,12 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio extensionHostEnv: result.extensionHostEnv }; - await this._extHostTunnelService.addDetected(result.detectedTunnels); - return { type: 'ok', value: { authority, - options + options, + tunnelInformation: { detectedTunnels: result.detectedTunnels } } }; } catch (err) { diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index cb383daefc..7e6a02b4f0 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -1200,18 +1200,23 @@ class CallHierarchyAdapter { private readonly _provider: vscode.CallHierarchyProvider ) { } - async prepareSession(uri: URI, position: IPosition, token: CancellationToken): Promise { + async prepareSession(uri: URI, position: IPosition, token: CancellationToken): Promise { const doc = this._documents.getDocument(uri); const pos = typeConvert.Position.to(position); - const item = await this._provider.prepareCallHierarchy(doc, pos, token); - if (!item) { + const items = await this._provider.prepareCallHierarchy(doc, pos, token); + if (!items) { return undefined; } - const sessionId = this._idPool.nextId(); + const sessionId = this._idPool.nextId(); this._cache.set(sessionId, new Map()); - return this._cacheAndConvertItem(sessionId, item); + + if (Array.isArray(items)) { + return items.map(item => this._cacheAndConvertItem(sessionId, item)); + } else { + return [this._cacheAndConvertItem(sessionId, items)]; + } } async provideCallsTo(sessionId: string, itemId: string, token: CancellationToken): Promise { @@ -1727,7 +1732,7 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF return this._createDisposable(handle); } - $prepareCallHierarchy(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise { + $prepareCallHierarchy(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise { return this._withAdapter(handle, CallHierarchyAdapter, adapter => Promise.resolve(adapter.prepareSession(URI.revive(resource), position, token)), undefined); } diff --git a/src/vs/workbench/api/common/extHostTunnelService.ts b/src/vs/workbench/api/common/extHostTunnelService.ts index 8360d769cf..2432abfa13 100644 --- a/src/vs/workbench/api/common/extHostTunnelService.ts +++ b/src/vs/workbench/api/common/extHostTunnelService.ts @@ -6,6 +6,8 @@ import { ExtHostTunnelServiceShape } from 'vs/workbench/api/common/extHost.protocol'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import * as vscode from 'vscode'; +import { RemoteTunnel } from 'vs/platform/remote/common/tunnel'; +import { IDisposable } from 'vs/base/common/lifecycle'; export interface TunnelOptions { remote: { port: number, host: string }; @@ -19,10 +21,38 @@ export interface TunnelDto { localAddress: string; } +export namespace TunnelDto { + export function fromApiTunnel(tunnel: vscode.Tunnel): TunnelDto { + return { remote: tunnel.remote, localAddress: tunnel.localAddress }; + } + export function fromServiceTunnel(tunnel: RemoteTunnel): TunnelDto { + return { remote: { host: tunnel.tunnelRemoteHost, port: tunnel.tunnelRemotePort }, localAddress: tunnel.localAddress }; + } +} + +export interface Tunnel extends vscode.Disposable { + remote: { port: number, host: string }; + localAddress: string; +} + export interface IExtHostTunnelService extends ExtHostTunnelServiceShape { readonly _serviceBrand: undefined; makeTunnel(forward: TunnelOptions): Promise; - addDetected(tunnels: { remote: { port: number, host: string }, localAddress: string }[] | undefined): Promise; + setForwardPortProvider(provider: vscode.RemoteAuthorityResolver | undefined): Promise; } export const IExtHostTunnelService = createDecorator('IExtHostTunnelService'); + +export class ExtHostTunnelService implements IExtHostTunnelService { + _serviceBrand: undefined; + async makeTunnel(forward: TunnelOptions): Promise { + return undefined; + } + async $findCandidatePorts(): Promise<{ port: number; detail: string; }[]> { + return []; + } + async setForwardPortProvider(provider: vscode.RemoteAuthorityResolver | undefined): Promise { return { dispose: () => { } }; } + $forwardPort(tunnelOptions: TunnelOptions): Promise | undefined { return undefined; } + async $closeTunnel(remote: { host: string, port: number }): Promise { } + +} diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 6a3d96b196..5c400a1115 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -14,7 +14,7 @@ import { generateUuid } from 'vs/base/common/uuid'; import * as vscode from 'vscode'; import { FileSystemProviderErrorCode, markAsFileSystemProviderError } from 'vs/platform/files/common/files'; import { RemoteAuthorityResolverErrorCode } from 'vs/platform/remote/common/remoteAuthorityResolver'; -import { markdownUnescapeCodicons, escapeCodicons } from 'vs/base/common/codicons'; +import { escapeCodicons } from 'vs/base/common/codicons'; function es5ClassCompat(target: Function): any { ///@ts-ignore @@ -1234,17 +1234,16 @@ export class MarkdownString { isTrusted?: boolean; readonly supportThemeIcons?: boolean; - constructor(value?: string, { supportThemeIcons }: { supportThemeIcons?: boolean } = {}) { + constructor(value?: string, supportThemeIcons: boolean = false) { this.value = value ?? ''; - this.supportThemeIcons = supportThemeIcons ?? false; + this.supportThemeIcons = supportThemeIcons; } appendText(value: string): MarkdownString { // escape markdown syntax tokens: http://daringfireball.net/projects/markdown/syntax#backslash - value = value + this.value += (this.supportThemeIcons ? escapeCodicons(value) : value) .replace(/[\\`*_{}[\]()#+\-.!]/g, '\\$&') .replace('\n', '\n\n'); - this.value += this.supportThemeIcons ? markdownUnescapeCodicons(value) : value; return this; } @@ -1263,10 +1262,6 @@ export class MarkdownString { this.value += '\n```\n'; return this; } - - static escapeThemeIcons(value: string): string { - return escapeCodicons(value); - } } @es5ClassCompat diff --git a/src/vs/workbench/api/common/extHostWebview.ts b/src/vs/workbench/api/common/extHostWebview.ts index 1a12b48dc9..444d9dffae 100644 --- a/src/vs/workbench/api/common/extHostWebview.ts +++ b/src/vs/workbench/api/common/extHostWebview.ts @@ -5,7 +5,6 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; -import { assertIsDefined } from 'vs/base/common/types'; import { URI, UriComponents } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import * as modes from 'vs/editor/common/modes'; @@ -16,7 +15,7 @@ import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; import { EditorViewColumn } from 'vs/workbench/api/common/shared/editor'; import { asWebviewUri, WebviewInitData } from 'vs/workbench/api/common/shared/webview'; import * as vscode from 'vscode'; -import { ExtHostWebviewsShape, IMainContext, MainContext, MainThreadWebviewsShape, WebviewPanelHandle, WebviewPanelViewStateData, WebviewEditorCapabilities } from './extHost.protocol'; +import { ExtHostWebviewsShape, IMainContext, MainContext, MainThreadWebviewsShape, WebviewEditorCapabilities, WebviewPanelHandle, WebviewPanelViewStateData } from './extHost.protocol'; import { Disposable as VSCodeDisposable } from './extHostTypes'; type IconPath = URI | { light: URI, dark: URI }; @@ -117,8 +116,6 @@ export class ExtHostWebviewEditor extends Disposable implements vscode.WebviewPa readonly _onDidChangeViewStateEmitter = this._register(new Emitter()); public readonly onDidChangeViewState: Event = this._onDidChangeViewStateEmitter.event; - public _capabilities?: vscode.WebviewEditorCapabilities; - constructor( handle: WebviewPanelHandle, proxy: MainThreadWebviewsShape, @@ -239,31 +236,6 @@ export class ExtHostWebviewEditor extends Disposable implements vscode.WebviewPa }); } - _setCapabilities(capabilities: vscode.WebviewEditorCapabilities) { - this._capabilities = capabilities; - if (capabilities.editingCapability) { - this._register(capabilities.editingCapability.onEdit(edit => { - this._proxy.$onEdit(this._handle, edit); - })); - } - } - - _undoEdits(edits: readonly any[]): void { - assertIsDefined(this._capabilities).editingCapability?.undoEdits(edits); - } - - _redoEdits(edits: readonly any[]): void { - assertIsDefined(this._capabilities).editingCapability?.applyEdits(edits); - } - - async _onSave(): Promise { - await assertIsDefined(this._capabilities?.editingCapability)?.save(); - } - - async _onSaveAs(resource: vscode.Uri, targetResource: vscode.Uri): Promise { - await assertIsDefined(this._capabilities?.editingCapability)?.saveAs(resource, targetResource); - } - private assertNotDisposed() { if (this._isDisposed) { throw new Error('Webview is disposed'); @@ -280,7 +252,7 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { private readonly _proxy: MainThreadWebviewsShape; private readonly _webviewPanels = new Map(); private readonly _serializers = new Map(); - private readonly _editorProviders = new Map(); + private readonly _editorProviders = new Map(); constructor( mainContext: IMainContext, @@ -331,10 +303,10 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { }); } - public registerWebviewEditorProvider( + public registerWebviewCustomEditorProvider( extension: IExtensionDescription, viewType: string, - provider: vscode.WebviewEditorProvider, + provider: vscode.WebviewCustomEditorProvider, options?: vscode.WebviewPanelOptions, ): vscode.Disposable { if (this._editorProviders.has(viewType)) { @@ -342,7 +314,10 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { } this._editorProviders.set(viewType, { extension, provider, }); - this._proxy.$registerEditorProvider({ id: extension.identifier, location: extension.extensionLocation }, viewType, options || {}); + this._proxy.$registerEditorProvider({ id: extension.identifier, location: extension.extensionLocation }, viewType, options || {}, this.getCapabilites(provider)); + provider?.editingDelegate?.onEdit(({ edit, resource }) => { + this._proxy.$onEdit(resource, viewType, edit); + }); return new VSCodeDisposable(() => { this._editorProviders.delete(viewType); @@ -431,7 +406,7 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { } async $resolveWebviewEditor( - input: { resource: UriComponents, edits: readonly any[] }, + resource: UriComponents, handle: WebviewPanelHandle, viewType: string, title: string, @@ -447,46 +422,44 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { const webview = new ExtHostWebview(handle, this._proxy, options, this.initData, this.workspace, extension, this._logService); const revivedPanel = new ExtHostWebviewEditor(handle, this._proxy, viewType, title, typeof position === 'number' && position >= 0 ? typeConverters.ViewColumn.to(position) : undefined, options, webview); this._webviewPanels.set(handle, revivedPanel); - const capabilities = await provider.resolveWebviewEditor({ resource: URI.revive(input.resource) }, revivedPanel); - revivedPanel._setCapabilities(capabilities); - this.registerCapabilites(handle, capabilities); - - // TODO: the first set of edits should likely be passed when resolving - if (input.edits.length) { - revivedPanel._redoEdits(input.edits); - } + const revivedResource = URI.revive(resource); + await provider.resolveWebviewEditor(revivedResource, revivedPanel); } - $undoEdits(handle: WebviewPanelHandle, edits: readonly any[]): void { - const panel = this.getWebviewPanel(handle); - panel?._undoEdits(edits); + $undoEdits(resource: UriComponents, viewType: string, edits: readonly any[]): void { + const provider = this.getEditorProvider(viewType); + provider?.editingDelegate?.undoEdits(URI.revive(resource), edits); } - $applyEdits(handle: WebviewPanelHandle, edits: readonly any[]): void { - const panel = this.getWebviewPanel(handle); - panel?._redoEdits(edits); + $applyEdits(resource: UriComponents, viewType: string, edits: readonly any[]): void { + const provider = this.getEditorProvider(viewType); + provider?.editingDelegate?.applyEdits(URI.revive(resource), edits); } - async $onSave(handle: WebviewPanelHandle): Promise { - const panel = this.getWebviewPanel(handle); - return panel?._onSave(); + async $onSave(resource: UriComponents, viewType: string): Promise { + const provider = this.getEditorProvider(viewType); + return provider?.editingDelegate?.save(URI.revive(resource)); } - async $onSaveAs(handle: WebviewPanelHandle, resource: UriComponents, targetResource: UriComponents): Promise { - const panel = this.getWebviewPanel(handle); - return panel?._onSaveAs(URI.revive(resource), URI.revive(targetResource)); + async $onSaveAs(resource: UriComponents, viewType: string, targetResource: UriComponents): Promise { + const provider = this.getEditorProvider(viewType); + return provider?.editingDelegate?.saveAs(URI.revive(resource), URI.revive(targetResource)); } private getWebviewPanel(handle: WebviewPanelHandle): ExtHostWebviewEditor | undefined { return this._webviewPanels.get(handle); } - private registerCapabilites(handle: WebviewPanelHandle, capabilities: vscode.WebviewEditorCapabilities) { + private getEditorProvider(viewType: string): vscode.WebviewCustomEditorProvider | undefined { + return this._editorProviders.get(viewType)?.provider; + } + + private getCapabilites(capabilities: vscode.WebviewCustomEditorProvider) { const declaredCapabilites: WebviewEditorCapabilities[] = []; - if (capabilities.editingCapability) { + if (capabilities.editingDelegate) { declaredCapabilites.push(WebviewEditorCapabilities.Editable); } - this._proxy.$registerCapabilities(handle, declaredCapabilites); + return declaredCapabilites; } } diff --git a/src/vs/workbench/api/common/menusExtensionPoint.ts b/src/vs/workbench/api/common/menusExtensionPoint.ts index 4b4859ac32..4cc49a5031 100644 --- a/src/vs/workbench/api/common/menusExtensionPoint.ts +++ b/src/vs/workbench/api/common/menusExtensionPoint.ts @@ -356,11 +356,8 @@ commandsExtensionPoint.setHandler(extensions => { let absoluteIcon: { dark: URI; light?: URI; } | ThemeIcon | undefined; if (icon) { if (typeof icon === 'string') { - if (extension.description.enableProposedApi) { - absoluteIcon = ThemeIcon.fromString(icon) || { dark: resources.joinPath(extension.description.extensionLocation, icon) }; - } else { - absoluteIcon = { dark: resources.joinPath(extension.description.extensionLocation, icon) }; - } + absoluteIcon = ThemeIcon.fromString(icon) || { dark: resources.joinPath(extension.description.extensionLocation, icon) }; + } else { absoluteIcon = { dark: resources.joinPath(extension.description.extensionLocation, icon.dark), diff --git a/src/vs/workbench/api/node/extHostTunnelService.ts b/src/vs/workbench/api/node/extHostTunnelService.ts index 719b3a60e1..6a5d456567 100644 --- a/src/vs/workbench/api/node/extHostTunnelService.ts +++ b/src/vs/workbench/api/node/extHostTunnelService.ts @@ -6,18 +6,37 @@ 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 { Disposable, IDisposable, toDisposable } 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'; +import { IExtHostTunnelService, TunnelOptions, TunnelDto } from 'vs/workbench/api/common/extHostTunnelService'; +import { asPromise } from 'vs/base/common/async'; +import { Event, Emitter } from 'vs/base/common/event'; + +class ExtensionTunnel implements vscode.Tunnel { + private _onDispose: Emitter = new Emitter(); + onDispose: Event = this._onDispose.event; + + constructor( + public readonly remote: { port: number; host: string; }, + public readonly localAddress: string, + private readonly _dispose: () => void) { } + + dispose(): void { + this._onDispose.fire(); + this._dispose(); + } +} export class ExtHostTunnelService extends Disposable implements IExtHostTunnelService { readonly _serviceBrand: undefined; private readonly _proxy: MainThreadTunnelServiceShape; + private _forwardPortProvider: ((tunnelOptions: TunnelOptions) => Thenable | undefined) | undefined; + private _extensionTunnels: Map> = new Map(); constructor( @IExtHostRpcService extHostRpc: IExtHostRpcService, @@ -32,29 +51,59 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe 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); - } - }; + const disposableTunnel: vscode.Tunnel = new ExtensionTunnel(tunnel.remote, tunnel.localAddress, () => { + 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 setForwardPortProvider(provider: vscode.RemoteAuthorityResolver | undefined): Promise { + if (provider && provider.forwardPort) { + this._forwardPortProvider = provider.forwardPort; + await this._proxy.$setTunnelProvider(); + } else { + this._forwardPortProvider = undefined; + } + return toDisposable(() => { + this._forwardPortProvider = undefined; + }); + } + + async $closeTunnel(remote: { host: string, port: number }): Promise { + if (this._extensionTunnels.has(remote.host)) { + const hostMap = this._extensionTunnels.get(remote.host)!; + if (hostMap.has(remote.port)) { + hostMap.get(remote.port)!.dispose(); + hostMap.delete(remote.port); + } + } + } + + $forwardPort(tunnelOptions: TunnelOptions): Promise | undefined { + if (this._forwardPortProvider) { + const providedPort = this._forwardPortProvider!(tunnelOptions); + if (providedPort !== undefined) { + return asPromise(() => providedPort).then(tunnel => { + if (!this._extensionTunnels.has(tunnelOptions.remote.host)) { + this._extensionTunnels.set(tunnelOptions.remote.host, new Map()); + } + this._extensionTunnels.get(tunnelOptions.remote.host)!.set(tunnelOptions.remote.port, tunnel); + this._register(tunnel.onDispose(() => this._proxy.$closeTunnel(tunnel.remote.port))); + return Promise.resolve(TunnelDto.fromApiTunnel(tunnel)); + }); + } + } + return undefined; + } + + async $findCandidatePorts(): Promise<{ port: number, detail: string }[]> { if (!isLinux) { return []; diff --git a/src/vs/workbench/browser/dnd.ts b/src/vs/workbench/browser/dnd.ts index f58ad3a6ca..92adcd53d1 100644 --- a/src/vs/workbench/browser/dnd.ts +++ b/src/vs/workbench/browser/dnd.ts @@ -383,7 +383,7 @@ export function fillResourceDataTransfers(accessor: ServicesAccessor, resources: const model = textFileService.models.get(file.resource); if (model) { encoding = model.getEncoding(); - mode = model.textEditorModel?.getModeId(); + mode = model.getMode(); } } diff --git a/src/vs/workbench/browser/editor.ts b/src/vs/workbench/browser/editor.ts index 1ebad388aa..2a4315ab60 100644 --- a/src/vs/workbench/browser/editor.ts +++ b/src/vs/workbench/browser/editor.ts @@ -9,6 +9,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { IConstructorSignature0, IInstantiationService, BrandedService } from 'vs/platform/instantiation/common/instantiation'; import { find } from 'vs/base/common/arrays'; +import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; export interface IEditorDescriptor { instantiate(instantiationService: IInstantiationService): BaseEditor; @@ -30,7 +31,7 @@ export interface IEditorRegistry { * @param inputDescriptors A set of constructor functions that return an instance of EditorInput for which the * registered editor should be used for. */ - registerEditor(descriptor: IEditorDescriptor, inputDescriptors: readonly SyncDescriptor[]): void; + registerEditor(descriptor: IEditorDescriptor, inputDescriptors: readonly SyncDescriptor[]): IDisposable; /** * Returns the editor descriptor for the given input or `undefined` if none. @@ -54,7 +55,7 @@ export interface IEditorRegistry { */ export class EditorDescriptor implements IEditorDescriptor { - public static create( + static create( ctor: { new(...services: Services): BaseEditor }, id: string, name: string @@ -87,14 +88,22 @@ export class EditorDescriptor implements IEditorDescriptor { class EditorRegistry implements IEditorRegistry { - private editors: EditorDescriptor[] = []; + private readonly editors: EditorDescriptor[] = []; private readonly mapEditorToInputs = new Map[]>(); - registerEditor(descriptor: EditorDescriptor, inputDescriptors: readonly SyncDescriptor[]): void { - // Register (Support multiple Editors per Input) + registerEditor(descriptor: EditorDescriptor, inputDescriptors: readonly SyncDescriptor[]): IDisposable { this.mapEditorToInputs.set(descriptor, inputDescriptors); this.editors.push(descriptor); + + return toDisposable(() => { + this.mapEditorToInputs.delete(descriptor); + + const index = this.editors.indexOf(descriptor); + if (index !== -1) { + this.editors.splice(index, 1); + } + }); } getEditor(input: EditorInput): EditorDescriptor | undefined { @@ -156,10 +165,6 @@ class EditorRegistry implements IEditorRegistry { return this.editors.slice(0); } - setEditors(editorsToSet: EditorDescriptor[]): void { - this.editors = editorsToSet; - } - getEditorInputs(): SyncDescriptor[] { const inputClasses: SyncDescriptor[] = []; for (const editor of this.editors) { diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 1b359d0799..34c8518d48 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -178,7 +178,8 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi position: Position.BOTTOM, lastNonMaximizedWidth: 300, lastNonMaximizedHeight: 300, - panelToRestore: undefined as string | undefined + panelToRestore: undefined as string | undefined, + restored: false }, statusBar: { @@ -570,9 +571,10 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi private updatePanelPosition() { const defaultPanelPosition = this.configurationService.getValue(Settings.PANEL_POSITION); - const panelPosition = this.storageService.get(Storage.PANEL_POSITION, StorageScope.WORKSPACE, defaultPanelPosition); + const panelPosition = this.storageService.get(Storage.PANEL_POSITION, StorageScope.WORKSPACE, undefined); - this.state.panel.position = (panelPosition === 'right') ? Position.RIGHT : Position.BOTTOM; + this.state.panel.restored = panelPosition !== undefined; + this.state.panel.position = ((panelPosition || defaultPanelPosition) === 'right') ? Position.RIGHT : Position.BOTTOM; } registerPart(part: Part): void { @@ -1279,7 +1281,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi const height = this.storageService.getNumber(Storage.GRID_HEIGHT, StorageScope.GLOBAL, workbenchDimensions.height); // At some point, we will not fall back to old keys from legacy layout, but for now, let's migrate the keys const sideBarSize = this.storageService.getNumber(Storage.SIDEBAR_SIZE, StorageScope.GLOBAL, this.storageService.getNumber('workbench.sidebar.width', StorageScope.GLOBAL, Math.min(workbenchDimensions.width / 4, 300))); - const panelSize = this.storageService.getNumber(Storage.PANEL_SIZE, StorageScope.GLOBAL, this.storageService.getNumber(this.state.panel.position === Position.BOTTOM ? 'workbench.panel.height' : 'workbench.panel.width', StorageScope.GLOBAL, workbenchDimensions.height / 3)); + const panelSize = this.state.panel.restored ? this.storageService.getNumber(Storage.PANEL_SIZE, StorageScope.GLOBAL, this.storageService.getNumber(this.state.panel.position === Position.BOTTOM ? 'workbench.panel.height' : 'workbench.panel.width', StorageScope.GLOBAL, workbenchDimensions.height / 3)) : workbenchDimensions.height / 3; const titleBarHeight = this.titleBarPartView.minimumHeight; const statusBarHeight = this.statusBarPartView.minimumHeight; diff --git a/src/vs/workbench/browser/panecomposite.ts b/src/vs/workbench/browser/panecomposite.ts index 44f3572986..2f93022b9e 100644 --- a/src/vs/workbench/browser/panecomposite.ts +++ b/src/vs/workbench/browser/panecomposite.ts @@ -82,4 +82,8 @@ export class PaneComposite extends Composite implements IPaneComposite { saveState(): void { super.saveState(); } + + focus(): void { + this.viewPaneContainer.focus(); + } } diff --git a/src/vs/workbench/browser/panel.ts b/src/vs/workbench/browser/panel.ts index 1e232bc194..bf2ef65356 100644 --- a/src/vs/workbench/browser/panel.ts +++ b/src/vs/workbench/browser/panel.ts @@ -12,9 +12,12 @@ import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/bro import { IConstructorSignature0, BrandedService } from 'vs/platform/instantiation/common/instantiation'; import { isAncestor } from 'vs/base/browser/dom'; import { assertIsDefined } from 'vs/base/common/types'; +import { PaneComposite } from 'vs/workbench/browser/panecomposite'; export abstract class Panel extends Composite implements IPanel { } +export abstract class PaneCompositePanel extends PaneComposite implements IPanel { } + /** * A panel descriptor is a leightweight descriptor of a panel in the workbench. */ diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbs.ts b/src/vs/workbench/browser/parts/editor/breadcrumbs.ts index 346bb15018..7c4652598f 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbs.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbs.ts @@ -9,7 +9,7 @@ import * as glob from 'vs/base/common/glob'; import { IDisposable } from 'vs/base/common/lifecycle'; import { localize } from 'vs/nls'; import { IConfigurationOverrides, IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; +import { Extensions, IConfigurationRegistry, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -150,7 +150,7 @@ Registry.as(Extensions.Configuration).registerConfigurat description: localize('symbolSortOrder', "Controls how symbols are sorted in the breadcrumbs outline view."), type: 'string', default: 'position', - overridable: true, + scope: ConfigurationScope.RESOURCE_LANGUAGE, enum: ['position', 'name', 'type'], enumDescriptions: [ localize('symbolSortOrder.position', "Show symbol outline in file position order."), @@ -166,157 +166,157 @@ Registry.as(Extensions.Configuration).registerConfigurat 'breadcrumbs.showFiles': { type: 'boolean', default: true, - overridable: true, + scope: ConfigurationScope.RESOURCE_LANGUAGE, markdownDescription: localize('filteredTypes.file', "When enabled breadcrumbs show `file`-symbols.") }, 'breadcrumbs.showModules': { type: 'boolean', default: true, - overridable: true, + scope: ConfigurationScope.RESOURCE_LANGUAGE, markdownDescription: localize('filteredTypes.module', "When enabled breadcrumbs show `module`-symbols.") }, 'breadcrumbs.showNamespaces': { type: 'boolean', default: true, - overridable: true, + scope: ConfigurationScope.RESOURCE_LANGUAGE, markdownDescription: localize('filteredTypes.namespace', "When enabled breadcrumbs show `namespace`-symbols.") }, 'breadcrumbs.showPackages': { type: 'boolean', default: true, - overridable: true, + scope: ConfigurationScope.RESOURCE_LANGUAGE, markdownDescription: localize('filteredTypes.package', "When enabled breadcrumbs show `package`-symbols.") }, 'breadcrumbs.showClasses': { type: 'boolean', default: true, - overridable: true, + scope: ConfigurationScope.RESOURCE_LANGUAGE, markdownDescription: localize('filteredTypes.class', "When enabled breadcrumbs show `class`-symbols.") }, 'breadcrumbs.showMethods': { type: 'boolean', default: true, - overridable: true, + scope: ConfigurationScope.RESOURCE_LANGUAGE, markdownDescription: localize('filteredTypes.method', "When enabled breadcrumbs show `method`-symbols.") }, 'breadcrumbs.showProperties': { type: 'boolean', default: true, - overridable: true, + scope: ConfigurationScope.RESOURCE_LANGUAGE, markdownDescription: localize('filteredTypes.property', "When enabled breadcrumbs show `property`-symbols.") }, 'breadcrumbs.showFields': { type: 'boolean', default: true, - overridable: true, + scope: ConfigurationScope.RESOURCE_LANGUAGE, markdownDescription: localize('filteredTypes.field', "When enabled breadcrumbs show `field`-symbols.") }, 'breadcrumbs.showConstructors': { type: 'boolean', default: true, - overridable: true, + scope: ConfigurationScope.RESOURCE_LANGUAGE, markdownDescription: localize('filteredTypes.constructor', "When enabled breadcrumbs show `constructor`-symbols.") }, 'breadcrumbs.showEnums': { type: 'boolean', default: true, - overridable: true, + scope: ConfigurationScope.RESOURCE_LANGUAGE, markdownDescription: localize('filteredTypes.enum', "When enabled breadcrumbs show `enum`-symbols.") }, 'breadcrumbs.showInterfaces': { type: 'boolean', default: true, - overridable: true, + scope: ConfigurationScope.RESOURCE_LANGUAGE, markdownDescription: localize('filteredTypes.interface', "When enabled breadcrumbs show `interface`-symbols.") }, 'breadcrumbs.showFunctions': { type: 'boolean', default: true, - overridable: true, + scope: ConfigurationScope.RESOURCE_LANGUAGE, markdownDescription: localize('filteredTypes.function', "When enabled breadcrumbs show `function`-symbols.") }, 'breadcrumbs.showVariables': { type: 'boolean', default: true, - overridable: true, + scope: ConfigurationScope.RESOURCE_LANGUAGE, markdownDescription: localize('filteredTypes.variable', "When enabled breadcrumbs show `variable`-symbols.") }, 'breadcrumbs.showConstants': { type: 'boolean', default: true, - overridable: true, + scope: ConfigurationScope.RESOURCE_LANGUAGE, markdownDescription: localize('filteredTypes.constant', "When enabled breadcrumbs show `constant`-symbols.") }, 'breadcrumbs.showStrings': { type: 'boolean', default: true, - overridable: true, + scope: ConfigurationScope.RESOURCE_LANGUAGE, markdownDescription: localize('filteredTypes.string', "When enabled breadcrumbs show `string`-symbols.") }, 'breadcrumbs.showNumbers': { type: 'boolean', default: true, - overridable: true, + scope: ConfigurationScope.RESOURCE_LANGUAGE, markdownDescription: localize('filteredTypes.number', "When enabled breadcrumbs show `number`-symbols.") }, 'breadcrumbs.showBooleans': { type: 'boolean', default: true, - overridable: true, + scope: ConfigurationScope.RESOURCE_LANGUAGE, markdownDescription: localize('filteredTypes.boolean', "When enabled breadcrumbs show `boolean`-symbols.") }, 'breadcrumbs.showArrays': { type: 'boolean', default: true, - overridable: true, + scope: ConfigurationScope.RESOURCE_LANGUAGE, markdownDescription: localize('filteredTypes.array', "When enabled breadcrumbs show `array`-symbols.") }, 'breadcrumbs.showObjects': { type: 'boolean', default: true, - overridable: true, + scope: ConfigurationScope.RESOURCE_LANGUAGE, markdownDescription: localize('filteredTypes.object', "When enabled breadcrumbs show `object`-symbols.") }, 'breadcrumbs.showKeys': { type: 'boolean', default: true, - overridable: true, + scope: ConfigurationScope.RESOURCE_LANGUAGE, markdownDescription: localize('filteredTypes.key', "When enabled breadcrumbs show `key`-symbols.") }, 'breadcrumbs.showNull': { type: 'boolean', default: true, - overridable: true, + scope: ConfigurationScope.RESOURCE_LANGUAGE, markdownDescription: localize('filteredTypes.null', "When enabled breadcrumbs show `null`-symbols.") }, 'breadcrumbs.showEnumMembers': { type: 'boolean', default: true, - overridable: true, + scope: ConfigurationScope.RESOURCE_LANGUAGE, markdownDescription: localize('filteredTypes.enumMember', "When enabled breadcrumbs show `enumMember`-symbols.") }, 'breadcrumbs.showStructs': { type: 'boolean', default: true, - overridable: true, + scope: ConfigurationScope.RESOURCE_LANGUAGE, markdownDescription: localize('filteredTypes.struct', "When enabled breadcrumbs show `struct`-symbols.") }, 'breadcrumbs.showEvents': { type: 'boolean', default: true, - overridable: true, + scope: ConfigurationScope.RESOURCE_LANGUAGE, markdownDescription: localize('filteredTypes.event', "When enabled breadcrumbs show `event`-symbols.") }, 'breadcrumbs.showOperators': { type: 'boolean', default: true, - overridable: true, + scope: ConfigurationScope.RESOURCE_LANGUAGE, markdownDescription: localize('filteredTypes.operator', "When enabled breadcrumbs show `operator`-symbols.") }, 'breadcrumbs.showTypeParameters': { type: 'boolean', default: true, - overridable: true, + scope: ConfigurationScope.RESOURCE_LANGUAGE, markdownDescription: localize('filteredTypes.typeParameter', "When enabled breadcrumbs show `typeParameter`-symbols.") } } diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index 2e24ee2e91..3488510f15 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 { IResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; +import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; 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, - @IResourceConfigurationService private readonly _textResourceConfigurationService: IResourceConfigurationService, + @ITextResourceConfigurationService private readonly _textResourceConfigurationService: ITextResourceConfigurationService, @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 d849fa99d5..429b75151d 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 { IResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; +import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; 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, - @IResourceConfigurationService private readonly _textResourceConfigurationService: IResourceConfigurationService, + @ITextResourceConfigurationService private readonly _textResourceConfigurationService: ITextResourceConfigurationService, @IWorkspaceContextService workspaceService: IWorkspaceContextService, ) { this._cfgFilePath = BreadcrumbsConfig.FilePath.bindTo(_configurationService); diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index 549c3e5d1a..0a599bf65a 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -29,14 +29,14 @@ import { CloseEditorsInOtherGroupsAction, CloseAllEditorsAction, MoveGroupLeftAction, MoveGroupRightAction, SplitEditorAction, JoinTwoGroupsAction, OpenToSideFromQuickOpenAction, RevertAndCloseEditorAction, NavigateBetweenGroupsAction, FocusActiveGroupAction, FocusFirstGroupAction, ResetGroupSizesAction, MaximizeGroupAction, MinimizeOtherGroupsAction, FocusPreviousGroup, FocusNextGroup, toEditorQuickOpenEntry, CloseLeftEditorsInGroupAction, OpenNextEditor, OpenPreviousEditor, NavigateBackwardsAction, NavigateForwardAction, NavigateLastAction, ReopenClosedEditorAction, - OpenPreviousRecentlyUsedEditorInGroupAction, OpenPreviousEditorFromHistoryAction, ShowAllEditorsAction, ClearEditorHistoryAction, MoveEditorRightInGroupAction, OpenNextEditorInGroup, - OpenPreviousEditorInGroup, OpenNextRecentlyUsedEditorAction, OpenPreviousRecentlyUsedEditorAction, OpenNextRecentlyUsedEditorInGroupAction, MoveEditorToPreviousGroupAction, + QuickOpenPreviousRecentlyUsedEditorInGroupAction, QuickOpenPreviousEditorFromHistoryAction, ShowAllEditorsByAppearanceAction, ClearEditorHistoryAction, MoveEditorRightInGroupAction, OpenNextEditorInGroup, + OpenPreviousEditorInGroup, OpenNextRecentlyUsedEditorAction, OpenPreviousRecentlyUsedEditorAction, QuickOpenNextRecentlyUsedEditorInGroupAction, MoveEditorToPreviousGroupAction, MoveEditorToNextGroupAction, MoveEditorToFirstGroupAction, MoveEditorLeftInGroupAction, ClearRecentFilesAction, OpenLastEditorInGroup, - ShowEditorsInActiveGroupAction, MoveEditorToLastGroupAction, OpenFirstEditorInGroup, MoveGroupUpAction, MoveGroupDownAction, FocusLastGroupAction, SplitEditorLeftAction, SplitEditorRightAction, + ShowEditorsInActiveGroupByMostRecentlyUsedAction, MoveEditorToLastGroupAction, OpenFirstEditorInGroup, MoveGroupUpAction, MoveGroupDownAction, FocusLastGroupAction, SplitEditorLeftAction, SplitEditorRightAction, SplitEditorUpAction, SplitEditorDownAction, MoveEditorToLeftGroupAction, MoveEditorToRightGroupAction, MoveEditorToAboveGroupAction, MoveEditorToBelowGroupAction, CloseAllEditorGroupsAction, JoinAllGroupsAction, FocusLeftGroup, FocusAboveGroup, FocusRightGroup, FocusBelowGroup, EditorLayoutSingleAction, EditorLayoutTwoColumnsAction, EditorLayoutThreeColumnsAction, EditorLayoutTwoByTwoGridAction, EditorLayoutTwoRowsAction, EditorLayoutThreeRowsAction, EditorLayoutTwoColumnsBottomAction, EditorLayoutTwoRowsRightAction, NewEditorGroupLeftAction, NewEditorGroupRightAction, - NewEditorGroupAboveAction, NewEditorGroupBelowAction, SplitEditorOrthogonalAction, CloseEditorInAllGroupsAction, NavigateToLastEditLocationAction, ToggleGroupSizesAction + NewEditorGroupAboveAction, NewEditorGroupBelowAction, SplitEditorOrthogonalAction, CloseEditorInAllGroupsAction, NavigateToLastEditLocationAction, ToggleGroupSizesAction, ShowAllEditorsByMostRecentlyUsedAction, QuickOpenNextRecentlyUsedEditorAction, QuickOpenPreviousRecentlyUsedEditorAction, OpenPreviousRecentlyUsedEditorInGroupAction, OpenNextRecentlyUsedEditorInGroupAction } from 'vs/workbench/browser/parts/editor/editorActions'; import * as editorCommands from 'vs/workbench/browser/parts/editor/editorCommands'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -44,7 +44,7 @@ import { getQuickNavigateHandler, inQuickOpenContext } from 'vs/workbench/browse import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { isMacintosh } from 'vs/base/common/platform'; -import { AllEditorsPicker, ActiveEditorGroupPicker } from 'vs/workbench/browser/parts/editor/editorPicker'; +import { AllEditorsByAppearancePicker, ActiveGroupEditorsByMostRecentlyUsedPicker, AllEditorsByMostRecentlyUsedPicker } from 'vs/workbench/browser/parts/editor/editorPicker'; import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { OpenWorkspaceButtonContribution } from 'vs/workbench/browser/parts/editor/editorWidgets'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -119,6 +119,10 @@ class UntitledTextEditorInputFactory implements IEditorInputFactory { @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService ) { } + canSerialize(editorInput: EditorInput): boolean { + return this.filesConfigurationService.isHotExitEnabled; + } + serialize(editorInput: EditorInput): string | undefined { if (!this.filesConfigurationService.isHotExitEnabled) { return undefined; // never restore untitled unless hot exit is enabled @@ -169,6 +173,20 @@ interface ISerializedSideBySideEditorInput { // Register Side by Side Editor Input Factory class SideBySideEditorInputFactory implements IEditorInputFactory { + canSerialize(editorInput: EditorInput): boolean { + const input = editorInput; + + if (input.details && input.master) { + const registry = Registry.as(EditorInputExtensions.EditorInputFactories); + const detailsInputFactory = registry.getEditorInputFactory(input.details.getTypeId()); + const masterInputFactory = registry.getEditorInputFactory(input.master.getTypeId()); + + return !!(detailsInputFactory?.canSerialize(input.details) && masterInputFactory?.canSerialize(input.master)); + } + + return false; + } + serialize(editorInput: EditorInput): string | undefined { const input = editorInput; @@ -284,15 +302,15 @@ const editorPickerContext = ContextKeyExpr.and(inQuickOpenContext, ContextKeyExp Registry.as(QuickOpenExtensions.Quickopen).registerQuickOpenHandler( QuickOpenHandlerDescriptor.create( - ActiveEditorGroupPicker, - ActiveEditorGroupPicker.ID, - editorCommands.NAVIGATE_IN_ACTIVE_GROUP_PREFIX, + ActiveGroupEditorsByMostRecentlyUsedPicker, + ActiveGroupEditorsByMostRecentlyUsedPicker.ID, + editorCommands.NAVIGATE_IN_ACTIVE_GROUP_BY_MOST_RECENTLY_USED_PREFIX, editorPickerContextKey, [ { - prefix: editorCommands.NAVIGATE_IN_ACTIVE_GROUP_PREFIX, + prefix: editorCommands.NAVIGATE_IN_ACTIVE_GROUP_BY_MOST_RECENTLY_USED_PREFIX, needsEditor: false, - description: nls.localize('groupOnePicker', "Show Editors in Active Group") + description: nls.localize('groupOnePicker', "Show Editors in Active Group By Most Recently Used") } ] ) @@ -300,15 +318,31 @@ Registry.as(QuickOpenExtensions.Quickopen).registerQuickOpen Registry.as(QuickOpenExtensions.Quickopen).registerQuickOpenHandler( QuickOpenHandlerDescriptor.create( - AllEditorsPicker, - AllEditorsPicker.ID, - editorCommands.NAVIGATE_ALL_EDITORS_GROUP_PREFIX, + AllEditorsByAppearancePicker, + AllEditorsByAppearancePicker.ID, + editorCommands.NAVIGATE_ALL_EDITORS_BY_APPEARANCE_PREFIX, editorPickerContextKey, [ { - prefix: editorCommands.NAVIGATE_ALL_EDITORS_GROUP_PREFIX, + prefix: editorCommands.NAVIGATE_ALL_EDITORS_BY_APPEARANCE_PREFIX, needsEditor: false, - description: nls.localize('allEditorsPicker', "Show All Opened Editors") + description: nls.localize('allEditorsPicker', "Show All Opened Editors By Appearance") + } + ] + ) +); + +Registry.as(QuickOpenExtensions.Quickopen).registerQuickOpenHandler( + QuickOpenHandlerDescriptor.create( + AllEditorsByMostRecentlyUsedPicker, + AllEditorsByMostRecentlyUsedPicker.ID, + editorCommands.NAVIGATE_ALL_EDITORS_BY_MOST_RECENTLY_USED_PREFIX, + editorPickerContextKey, + [ + { + prefix: editorCommands.NAVIGATE_ALL_EDITORS_BY_MOST_RECENTLY_USED_PREFIX, + needsEditor: false, + description: nls.localize('allEditorsPickerByMostRecentlyUsed', "Show All Opened Editors By Most Recently Used") } ] ) @@ -316,17 +350,20 @@ Registry.as(QuickOpenExtensions.Quickopen).registerQuickOpen // Register Editor Actions const category = nls.localize('view', "View"); -registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenNextEditorInGroup, OpenNextEditorInGroup.ID, OpenNextEditorInGroup.LABEL), 'View: Open Next Editor in Group', category); -registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenPreviousEditorInGroup, OpenPreviousEditorInGroup.ID, OpenPreviousEditorInGroup.LABEL), 'View: Open Previous Editor in Group', category); -registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenLastEditorInGroup, OpenLastEditorInGroup.ID, OpenLastEditorInGroup.LABEL, { primary: KeyMod.Alt | KeyCode.KEY_0, secondary: [KeyMod.CtrlCmd | KeyCode.KEY_9], mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_0, secondary: [KeyMod.CtrlCmd | KeyCode.KEY_9] } }), 'View: Open Last Editor in Group', category); -registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenFirstEditorInGroup, OpenFirstEditorInGroup.ID, OpenFirstEditorInGroup.LABEL), 'View: Open First Editor in Group', category); -registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenNextRecentlyUsedEditorAction, OpenNextRecentlyUsedEditorAction.ID, OpenNextRecentlyUsedEditorAction.LABEL), 'View: Open Next Recently Used Editor', category); -registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenPreviousRecentlyUsedEditorAction, OpenPreviousRecentlyUsedEditorAction.ID, OpenPreviousRecentlyUsedEditorAction.LABEL), 'View: Open Previous Recently Used Editor', category); -registry.registerWorkbenchAction(SyncActionDescriptor.create(ShowAllEditorsAction, ShowAllEditorsAction.ID, ShowAllEditorsAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_P), mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Tab } }), 'View: Show All Editors', category); -registry.registerWorkbenchAction(SyncActionDescriptor.create(ShowEditorsInActiveGroupAction, ShowEditorsInActiveGroupAction.ID, ShowEditorsInActiveGroupAction.LABEL), 'View: Show Editors in Active Group', category); registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenNextEditor, OpenNextEditor.ID, OpenNextEditor.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.PageDown, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.RightArrow, secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_CLOSE_SQUARE_BRACKET] } }), 'View: Open Next Editor', category); registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenPreviousEditor, OpenPreviousEditor.ID, OpenPreviousEditor.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.PageUp, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.LeftArrow, secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_OPEN_SQUARE_BRACKET] } }), 'View: Open Previous Editor', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenNextEditorInGroup, OpenNextEditorInGroup.ID, OpenNextEditorInGroup.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.PageDown), mac: { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.RightArrow) } }), 'View: Open Next Editor in Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenPreviousEditorInGroup, OpenPreviousEditorInGroup.ID, OpenPreviousEditorInGroup.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.PageUp), mac: { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.LeftArrow) } }), 'View: Open Previous Editor in Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenNextRecentlyUsedEditorAction, OpenNextRecentlyUsedEditorAction.ID, OpenNextRecentlyUsedEditorAction.LABEL), 'View: Open Next Recently Used Editor', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenPreviousRecentlyUsedEditorAction, OpenPreviousRecentlyUsedEditorAction.ID, OpenPreviousRecentlyUsedEditorAction.LABEL), 'View: Open Previous Recently Used Editor', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenNextRecentlyUsedEditorInGroupAction, OpenNextRecentlyUsedEditorInGroupAction.ID, OpenNextRecentlyUsedEditorInGroupAction.LABEL), 'View: Open Next Recently Used Editor In Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenPreviousRecentlyUsedEditorInGroupAction, OpenPreviousRecentlyUsedEditorInGroupAction.ID, OpenPreviousRecentlyUsedEditorInGroupAction.LABEL), 'View: Open Previous Recently Used Editor In Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenFirstEditorInGroup, OpenFirstEditorInGroup.ID, OpenFirstEditorInGroup.LABEL), 'View: Open First Editor in Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenLastEditorInGroup, OpenLastEditorInGroup.ID, OpenLastEditorInGroup.LABEL, { primary: KeyMod.Alt | KeyCode.KEY_0, secondary: [KeyMod.CtrlCmd | KeyCode.KEY_9], mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_0, secondary: [KeyMod.CtrlCmd | KeyCode.KEY_9] } }), 'View: Open Last Editor in Group', category); registry.registerWorkbenchAction(SyncActionDescriptor.create(ReopenClosedEditorAction, ReopenClosedEditorAction.ID, ReopenClosedEditorAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_T }), 'View: Reopen Closed Editor', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ShowAllEditorsByAppearanceAction, ShowAllEditorsByAppearanceAction.ID, ShowAllEditorsByAppearanceAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_P), mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Tab } }), 'View: Show All Editors By Appearance', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ShowAllEditorsByMostRecentlyUsedAction, ShowAllEditorsByMostRecentlyUsedAction.ID, ShowAllEditorsByMostRecentlyUsedAction.LABEL), 'View: Show All Editors By Most Recently Used', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ShowEditorsInActiveGroupByMostRecentlyUsedAction, ShowEditorsInActiveGroupByMostRecentlyUsedAction.ID, ShowEditorsInActiveGroupByMostRecentlyUsedAction.LABEL), 'View: Show Editors in Active Group By Most Recently Used', category); registry.registerWorkbenchAction(SyncActionDescriptor.create(ClearRecentFilesAction, ClearRecentFilesAction.ID, ClearRecentFilesAction.LABEL), 'File: Clear Recently Opened', nls.localize('file', "File")); registry.registerWorkbenchAction(SyncActionDescriptor.create(CloseAllEditorsAction, CloseAllEditorsAction.ID, CloseAllEditorsAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_W) }), 'View: Close All Editors', category); registry.registerWorkbenchAction(SyncActionDescriptor.create(CloseAllEditorGroupsAction, CloseAllEditorGroupsAction.ID, CloseAllEditorGroupsAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_W) }), 'View: Close All Editor Groups', category); @@ -377,7 +414,6 @@ registry.registerWorkbenchAction(SyncActionDescriptor.create(NavigateForwardActi registry.registerWorkbenchAction(SyncActionDescriptor.create(NavigateBackwardsAction, NavigateBackwardsAction.ID, NavigateBackwardsAction.LABEL, { primary: 0, win: { primary: KeyMod.Alt | KeyCode.LeftArrow }, mac: { primary: KeyMod.WinCtrl | KeyCode.US_MINUS }, linux: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.US_MINUS } }), 'Go Back'); registry.registerWorkbenchAction(SyncActionDescriptor.create(NavigateToLastEditLocationAction, NavigateToLastEditLocationAction.ID, NavigateToLastEditLocationAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_Q) }), 'Go to Last Edit Location'); registry.registerWorkbenchAction(SyncActionDescriptor.create(NavigateLastAction, NavigateLastAction.ID, NavigateLastAction.LABEL), 'Go Last'); -registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenPreviousEditorFromHistoryAction, OpenPreviousEditorFromHistoryAction.ID, OpenPreviousEditorFromHistoryAction.LABEL), 'Open Previous Editor from History'); registry.registerWorkbenchAction(SyncActionDescriptor.create(ClearEditorHistoryAction, ClearEditorHistoryAction.ID, ClearEditorHistoryAction.LABEL), 'Clear Editor History'); registry.registerWorkbenchAction(SyncActionDescriptor.create(RevertAndCloseEditorAction, RevertAndCloseEditorAction.ID, RevertAndCloseEditorAction.LABEL), 'View: Revert and Close Editor', category); registry.registerWorkbenchAction(SyncActionDescriptor.create(EditorLayoutSingleAction, EditorLayoutSingleAction.ID, EditorLayoutSingleAction.LABEL), 'View: Single Column Editor Layout', category); @@ -389,11 +425,14 @@ registry.registerWorkbenchAction(SyncActionDescriptor.create(EditorLayoutTwoByTw registry.registerWorkbenchAction(SyncActionDescriptor.create(EditorLayoutTwoRowsRightAction, EditorLayoutTwoRowsRightAction.ID, EditorLayoutTwoRowsRightAction.LABEL), 'View: Two Rows Right Editor Layout', category); registry.registerWorkbenchAction(SyncActionDescriptor.create(EditorLayoutTwoColumnsBottomAction, EditorLayoutTwoColumnsBottomAction.ID, EditorLayoutTwoColumnsBottomAction.LABEL), 'View: Two Columns Bottom Editor Layout', category); -// Register Editor Picker Actions including quick navigate support -const openNextEditorKeybinding = { primary: KeyMod.CtrlCmd | KeyCode.Tab, mac: { primary: KeyMod.WinCtrl | KeyCode.Tab } }; -const openPreviousEditorKeybinding = { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Tab, mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.Tab } }; -registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenNextRecentlyUsedEditorInGroupAction, OpenNextRecentlyUsedEditorInGroupAction.ID, OpenNextRecentlyUsedEditorInGroupAction.LABEL, openNextEditorKeybinding), 'View: Open Next Recently Used Editor in Group', category); -registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenPreviousRecentlyUsedEditorInGroupAction, OpenPreviousRecentlyUsedEditorInGroupAction.ID, OpenPreviousRecentlyUsedEditorInGroupAction.LABEL, openPreviousEditorKeybinding), 'View: Open Previous Recently Used Editor in Group', category); +// Register Quick Editor Actions including built in quick navigate support for some +const quickOpenNextRecentlyUsedEditorInGroupKeybinding = { primary: KeyMod.CtrlCmd | KeyCode.Tab, mac: { primary: KeyMod.WinCtrl | KeyCode.Tab } }; +const quickOpenPreviousRecentlyUsedEditorInGroupKeybinding = { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Tab, mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.Tab } }; +registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickOpenNextRecentlyUsedEditorAction, QuickOpenNextRecentlyUsedEditorAction.ID, QuickOpenNextRecentlyUsedEditorAction.LABEL), 'View: Quick Open Next Recently Used Editor', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickOpenPreviousRecentlyUsedEditorAction, QuickOpenPreviousRecentlyUsedEditorAction.ID, QuickOpenPreviousRecentlyUsedEditorAction.LABEL), 'View: Quick Open Previous Recently Used Editor', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickOpenNextRecentlyUsedEditorInGroupAction, QuickOpenNextRecentlyUsedEditorInGroupAction.ID, QuickOpenNextRecentlyUsedEditorInGroupAction.LABEL, quickOpenNextRecentlyUsedEditorInGroupKeybinding), 'View: Quick Open Next Recently Used Editor in Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickOpenPreviousRecentlyUsedEditorInGroupAction, QuickOpenPreviousRecentlyUsedEditorInGroupAction.ID, QuickOpenPreviousRecentlyUsedEditorInGroupAction.LABEL, quickOpenPreviousRecentlyUsedEditorInGroupKeybinding), 'View: Quick Open Previous Recently Used Editor in Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickOpenPreviousEditorFromHistoryAction, QuickOpenPreviousEditorFromHistoryAction.ID, QuickOpenPreviousEditorFromHistoryAction.LABEL), 'Quick Open Previous Editor from History'); const quickOpenNavigateNextInEditorPickerId = 'workbench.action.quickOpenNavigateNextInEditorPicker'; KeybindingsRegistry.registerCommandAndKeybindingRule({ @@ -401,8 +440,8 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ weight: KeybindingWeight.WorkbenchContrib + 50, handler: getQuickNavigateHandler(quickOpenNavigateNextInEditorPickerId, true), when: editorPickerContext, - primary: openNextEditorKeybinding.primary, - mac: openNextEditorKeybinding.mac + primary: quickOpenNextRecentlyUsedEditorInGroupKeybinding.primary, + mac: quickOpenNextRecentlyUsedEditorInGroupKeybinding.mac }); const quickOpenNavigatePreviousInEditorPickerId = 'workbench.action.quickOpenNavigatePreviousInEditorPicker'; @@ -411,8 +450,8 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ weight: KeybindingWeight.WorkbenchContrib + 50, handler: getQuickNavigateHandler(quickOpenNavigatePreviousInEditorPickerId, false), when: editorPickerContext, - primary: openPreviousEditorKeybinding.primary, - mac: openPreviousEditorKeybinding.mac + primary: quickOpenPreviousRecentlyUsedEditorInGroupKeybinding.primary, + mac: quickOpenPreviousRecentlyUsedEditorInGroupKeybinding.mac }); // Editor Commands @@ -603,7 +642,8 @@ MenuRegistry.appendMenuItem(MenuId.MenubarRecentMenu, { group: '1_editor', command: { id: ReopenClosedEditorAction.ID, - title: nls.localize({ key: 'miReopenClosedEditor', comment: ['&& denotes a mnemonic'] }, "&&Reopen Closed Editor") + title: nls.localize({ key: 'miReopenClosedEditor', comment: ['&& denotes a mnemonic'] }, "&&Reopen Closed Editor"), + precondition: ContextKeyExpr.has('canReopenClosedEditor') }, order: 1 }); @@ -786,19 +826,55 @@ MenuRegistry.appendMenuItem(MenuId.MenubarSwitchEditorMenu, { }); MenuRegistry.appendMenuItem(MenuId.MenubarSwitchEditorMenu, { - group: '2_used', + group: '2_any_used', command: { - id: 'workbench.action.openNextRecentlyUsedEditorInGroup', - title: nls.localize({ key: 'miNextEditorInGroup', comment: ['&& denotes a mnemonic'] }, "&&Next Used Editor in Group") + id: 'workbench.action.openNextRecentlyUsedEditor', + title: nls.localize({ key: 'miNextRecentlyUsedEditor', comment: ['&& denotes a mnemonic'] }, "&&Next Used Editor") }, order: 1 }); MenuRegistry.appendMenuItem(MenuId.MenubarSwitchEditorMenu, { - group: '2_used', + group: '2_any_used', + command: { + id: 'workbench.action.openPreviousRecentlyUsedEditor', + title: nls.localize({ key: 'miPreviousRecentlyUsedEditor', comment: ['&& denotes a mnemonic'] }, "&&Previous Used Editor") + }, + order: 2 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarSwitchEditorMenu, { + group: '3_group', + command: { + id: 'workbench.action.nextEditorInGroup', + title: nls.localize({ key: 'miNextEditorInGroup', comment: ['&& denotes a mnemonic'] }, "&&Next Editor in Group") + }, + order: 1 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarSwitchEditorMenu, { + group: '3_group', + command: { + id: 'workbench.action.previousEditorInGroup', + title: nls.localize({ key: 'miPreviousEditorInGroup', comment: ['&& denotes a mnemonic'] }, "&&Previous Editor in Group") + }, + order: 2 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarSwitchEditorMenu, { + group: '4_group_used', + command: { + id: 'workbench.action.openNextRecentlyUsedEditorInGroup', + title: nls.localize({ key: 'miNextUsedEditorInGroup', comment: ['&& denotes a mnemonic'] }, "&&Next Used Editor in Group") + }, + order: 1 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarSwitchEditorMenu, { + group: '4_group_used', command: { id: 'workbench.action.openPreviousRecentlyUsedEditorInGroup', - title: nls.localize({ key: 'miPreviousEditorInGroup', comment: ['&& denotes a mnemonic'] }, "&&Previous Used Editor in Group") + title: nls.localize({ key: 'miPreviousUsedEditorInGroup', comment: ['&& denotes a mnemonic'] }, "&&Previous Used Editor in Group") }, order: 2 }); @@ -833,7 +909,8 @@ MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, { group: '1_focus_index', command: { id: 'workbench.action.focusThirdEditorGroup', - title: nls.localize({ key: 'miFocusThirdGroup', comment: ['&& denotes a mnemonic'] }, "Group &&3") + title: nls.localize({ key: 'miFocusThirdGroup', comment: ['&& denotes a mnemonic'] }, "Group &&3"), + precondition: ContextKeyExpr.has('multipleEditorGroups') }, order: 3 }); @@ -842,7 +919,8 @@ MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, { group: '1_focus_index', command: { id: 'workbench.action.focusFourthEditorGroup', - title: nls.localize({ key: 'miFocusFourthGroup', comment: ['&& denotes a mnemonic'] }, "Group &&4") + title: nls.localize({ key: 'miFocusFourthGroup', comment: ['&& denotes a mnemonic'] }, "Group &&4"), + precondition: ContextKeyExpr.has('multipleEditorGroups') }, order: 4 }); @@ -851,7 +929,8 @@ MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, { group: '1_focus_index', command: { id: 'workbench.action.focusFifthEditorGroup', - title: nls.localize({ key: 'miFocusFifthGroup', comment: ['&& denotes a mnemonic'] }, "Group &&5") + title: nls.localize({ key: 'miFocusFifthGroup', comment: ['&& denotes a mnemonic'] }, "Group &&5"), + precondition: ContextKeyExpr.has('multipleEditorGroups') }, order: 5 }); @@ -860,7 +939,8 @@ MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, { group: '2_next_prev', command: { id: 'workbench.action.focusNextGroup', - title: nls.localize({ key: 'miNextGroup', comment: ['&& denotes a mnemonic'] }, "&&Next Group") + title: nls.localize({ key: 'miNextGroup', comment: ['&& denotes a mnemonic'] }, "&&Next Group"), + precondition: ContextKeyExpr.has('multipleEditorGroups') }, order: 1 }); @@ -869,7 +949,8 @@ MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, { group: '2_next_prev', command: { id: 'workbench.action.focusPreviousGroup', - title: nls.localize({ key: 'miPreviousGroup', comment: ['&& denotes a mnemonic'] }, "&&Previous Group") + title: nls.localize({ key: 'miPreviousGroup', comment: ['&& denotes a mnemonic'] }, "&&Previous Group"), + precondition: ContextKeyExpr.has('multipleEditorGroups') }, order: 2 }); @@ -878,7 +959,8 @@ MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, { group: '3_directional', command: { id: 'workbench.action.focusLeftGroup', - title: nls.localize({ key: 'miFocusLeftGroup', comment: ['&& denotes a mnemonic'] }, "Group &&Left") + title: nls.localize({ key: 'miFocusLeftGroup', comment: ['&& denotes a mnemonic'] }, "Group &&Left"), + precondition: ContextKeyExpr.has('multipleEditorGroups') }, order: 1 }); @@ -887,7 +969,8 @@ MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, { group: '3_directional', command: { id: 'workbench.action.focusRightGroup', - title: nls.localize({ key: 'miFocusRightGroup', comment: ['&& denotes a mnemonic'] }, "Group &&Right") + title: nls.localize({ key: 'miFocusRightGroup', comment: ['&& denotes a mnemonic'] }, "Group &&Right"), + precondition: ContextKeyExpr.has('multipleEditorGroups') }, order: 2 }); @@ -896,7 +979,8 @@ MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, { group: '3_directional', command: { id: 'workbench.action.focusAboveGroup', - title: nls.localize({ key: 'miFocusAboveGroup', comment: ['&& denotes a mnemonic'] }, "Group &&Above") + title: nls.localize({ key: 'miFocusAboveGroup', comment: ['&& denotes a mnemonic'] }, "Group &&Above"), + precondition: ContextKeyExpr.has('multipleEditorGroups') }, order: 3 }); @@ -905,7 +989,8 @@ MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, { group: '3_directional', command: { id: 'workbench.action.focusBelowGroup', - title: nls.localize({ key: 'miFocusBelowGroup', comment: ['&& denotes a mnemonic'] }, "Group &&Below") + title: nls.localize({ key: 'miFocusBelowGroup', comment: ['&& denotes a mnemonic'] }, "Group &&Below"), + precondition: ContextKeyExpr.has('multipleEditorGroups') }, order: 4 }); diff --git a/src/vs/workbench/browser/parts/editor/editorActions.ts b/src/vs/workbench/browser/parts/editor/editorActions.ts index 200e87d969..2b9fb9a4ad 100644 --- a/src/vs/workbench/browser/parts/editor/editorActions.ts +++ b/src/vs/workbench/browser/parts/editor/editorActions.ts @@ -15,7 +15,7 @@ import { IResourceInput } from 'vs/platform/editor/common/editor'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { CLOSE_EDITOR_COMMAND_ID, NAVIGATE_ALL_EDITORS_GROUP_PREFIX, MOVE_ACTIVE_EDITOR_COMMAND_ID, NAVIGATE_IN_ACTIVE_GROUP_PREFIX, ActiveEditorMoveArguments, SPLIT_EDITOR_LEFT, SPLIT_EDITOR_RIGHT, SPLIT_EDITOR_UP, SPLIT_EDITOR_DOWN, splitEditor, LAYOUT_EDITOR_GROUPS_COMMAND_ID, mergeAllGroups } from 'vs/workbench/browser/parts/editor/editorCommands'; +import { CLOSE_EDITOR_COMMAND_ID, NAVIGATE_ALL_EDITORS_BY_APPEARANCE_PREFIX, MOVE_ACTIVE_EDITOR_COMMAND_ID, NAVIGATE_IN_ACTIVE_GROUP_BY_MOST_RECENTLY_USED_PREFIX, ActiveEditorMoveArguments, SPLIT_EDITOR_LEFT, SPLIT_EDITOR_RIGHT, SPLIT_EDITOR_UP, SPLIT_EDITOR_DOWN, splitEditor, LAYOUT_EDITOR_GROUPS_COMMAND_ID, mergeAllGroups, NAVIGATE_ALL_EDITORS_BY_MOST_RECENTLY_USED_PREFIX } from 'vs/workbench/browser/parts/editor/editorCommands'; import { IEditorGroupsService, IEditorGroup, GroupsArrangement, EditorsOrder, GroupLocation, GroupDirection, preferredSideBySideGroupDirection, IFindGroupScope, GroupOrientation, EditorGroupLayout, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -1258,35 +1258,54 @@ export class ClearRecentFilesAction extends Action { } } -export class ShowEditorsInActiveGroupAction extends QuickOpenAction { +export class ShowEditorsInActiveGroupByMostRecentlyUsedAction extends QuickOpenAction { static readonly ID = 'workbench.action.showEditorsInActiveGroup'; - static readonly LABEL = nls.localize('showEditorsInActiveGroup', "Show Editors in Active Group"); + static readonly LABEL = nls.localize('showEditorsInActiveGroup', "Show Editors in Active Group By Most Recently Used"); constructor( actionId: string, actionLabel: string, @IQuickOpenService quickOpenService: IQuickOpenService ) { - super(actionId, actionLabel, NAVIGATE_IN_ACTIVE_GROUP_PREFIX, quickOpenService); + super(actionId, actionLabel, NAVIGATE_IN_ACTIVE_GROUP_BY_MOST_RECENTLY_USED_PREFIX, quickOpenService); } } -export class ShowAllEditorsAction extends QuickOpenAction { +export class ShowAllEditorsByAppearanceAction extends QuickOpenAction { static readonly ID = 'workbench.action.showAllEditors'; - static readonly LABEL = nls.localize('showAllEditors', "Show All Editors"); + static readonly LABEL = nls.localize('showAllEditors', "Show All Editors By Appearance"); - constructor(actionId: string, actionLabel: string, @IQuickOpenService quickOpenService: IQuickOpenService) { - super(actionId, actionLabel, NAVIGATE_ALL_EDITORS_GROUP_PREFIX, quickOpenService); + constructor( + actionId: string, + actionLabel: string, + @IQuickOpenService quickOpenService: IQuickOpenService + ) { + super(actionId, actionLabel, NAVIGATE_ALL_EDITORS_BY_APPEARANCE_PREFIX, quickOpenService); } } -export class BaseQuickOpenEditorInGroupAction extends Action { +export class ShowAllEditorsByMostRecentlyUsedAction extends QuickOpenAction { + + static readonly ID = 'workbench.action.showAllEditorsByMostRecentlyUsed'; + static readonly LABEL = nls.localize('showAllEditorsByMostRecentlyUsed', "Show All Editors By Most Recently Used"); + + constructor( + actionId: string, + actionLabel: string, + @IQuickOpenService quickOpenService: IQuickOpenService + ) { + super(actionId, actionLabel, NAVIGATE_ALL_EDITORS_BY_MOST_RECENTLY_USED_PREFIX, quickOpenService); + } +} + +export class BaseQuickOpenEditorAction extends Action { constructor( id: string, label: string, + private prefix: string, @IQuickOpenService private readonly quickOpenService: IQuickOpenService, @IKeybindingService private readonly keybindingService: IKeybindingService ) { @@ -1294,18 +1313,18 @@ export class BaseQuickOpenEditorInGroupAction extends Action { } run(): Promise { - const keys = this.keybindingService.lookupKeybindings(this.id); + const keybindings = this.keybindingService.lookupKeybindings(this.id); - this.quickOpenService.show(NAVIGATE_IN_ACTIVE_GROUP_PREFIX, { quickNavigateConfiguration: { keybindings: keys } }); + this.quickOpenService.show(this.prefix, { quickNavigateConfiguration: { keybindings } }); return Promise.resolve(true); } } -export class OpenPreviousRecentlyUsedEditorInGroupAction extends BaseQuickOpenEditorInGroupAction { +export class QuickOpenPreviousRecentlyUsedEditorAction extends BaseQuickOpenEditorAction { - static readonly ID = 'workbench.action.openPreviousRecentlyUsedEditorInGroup'; - static readonly LABEL = nls.localize('openPreviousRecentlyUsedEditorInGroup', "Open Previous Recently Used Editor in Group"); + static readonly ID = 'workbench.action.quickOpenPreviousRecentlyUsedEditor'; + static readonly LABEL = nls.localize('quickOpenPreviousRecentlyUsedEditor', "Quick Open Previous Recently Used Editor"); constructor( id: string, @@ -1313,14 +1332,14 @@ export class OpenPreviousRecentlyUsedEditorInGroupAction extends BaseQuickOpenEd @IQuickOpenService quickOpenService: IQuickOpenService, @IKeybindingService keybindingService: IKeybindingService ) { - super(id, label, quickOpenService, keybindingService); + super(id, label, NAVIGATE_ALL_EDITORS_BY_MOST_RECENTLY_USED_PREFIX, quickOpenService, keybindingService); } } -export class OpenNextRecentlyUsedEditorInGroupAction extends BaseQuickOpenEditorInGroupAction { +export class QuickOpenNextRecentlyUsedEditorAction extends BaseQuickOpenEditorAction { - static readonly ID = 'workbench.action.openNextRecentlyUsedEditorInGroup'; - static readonly LABEL = nls.localize('openNextRecentlyUsedEditorInGroup', "Open Next Recently Used Editor in Group"); + static readonly ID = 'workbench.action.quickOpenNextRecentlyUsedEditor'; + static readonly LABEL = nls.localize('quickOpenNextRecentlyUsedEditor', "Quick Open Next Recently Used Editor"); constructor( id: string, @@ -1328,14 +1347,44 @@ export class OpenNextRecentlyUsedEditorInGroupAction extends BaseQuickOpenEditor @IQuickOpenService quickOpenService: IQuickOpenService, @IKeybindingService keybindingService: IKeybindingService ) { - super(id, label, quickOpenService, keybindingService); + super(id, label, NAVIGATE_ALL_EDITORS_BY_MOST_RECENTLY_USED_PREFIX, quickOpenService, keybindingService); } } -export class OpenPreviousEditorFromHistoryAction extends Action { +export class QuickOpenPreviousRecentlyUsedEditorInGroupAction extends BaseQuickOpenEditorAction { + + static readonly ID = 'workbench.action.quickOpenPreviousRecentlyUsedEditorInGroup'; + static readonly LABEL = nls.localize('quickOpenPreviousRecentlyUsedEditorInGroup', "Quick Open Previous Recently Used Editor in Group"); + + constructor( + id: string, + label: string, + @IQuickOpenService quickOpenService: IQuickOpenService, + @IKeybindingService keybindingService: IKeybindingService + ) { + super(id, label, NAVIGATE_IN_ACTIVE_GROUP_BY_MOST_RECENTLY_USED_PREFIX, quickOpenService, keybindingService); + } +} + +export class QuickOpenNextRecentlyUsedEditorInGroupAction extends BaseQuickOpenEditorAction { + + static readonly ID = 'workbench.action.quickOpenNextRecentlyUsedEditorInGroup'; + static readonly LABEL = nls.localize('quickOpenNextRecentlyUsedEditorInGroup', "Quick Open Next Recently Used Editor in Group"); + + constructor( + id: string, + label: string, + @IQuickOpenService quickOpenService: IQuickOpenService, + @IKeybindingService keybindingService: IKeybindingService + ) { + super(id, label, NAVIGATE_IN_ACTIVE_GROUP_BY_MOST_RECENTLY_USED_PREFIX, quickOpenService, keybindingService); + } +} + +export class QuickOpenPreviousEditorFromHistoryAction extends Action { static readonly ID = 'workbench.action.openPreviousEditorFromHistory'; - static readonly LABEL = nls.localize('navigateEditorHistoryByInput', "Open Previous Editor from History"); + static readonly LABEL = nls.localize('navigateEditorHistoryByInput', "Quick Open Previous Editor from History"); constructor( id: string, @@ -1347,9 +1396,9 @@ export class OpenPreviousEditorFromHistoryAction extends Action { } run(): Promise { - const keys = this.keybindingService.lookupKeybindings(this.id); + const keybindings = this.keybindingService.lookupKeybindings(this.id); - this.quickOpenService.show(undefined, { quickNavigateConfiguration: { keybindings: keys } }); + this.quickOpenService.show(undefined, { quickNavigateConfiguration: { keybindings } }); return Promise.resolve(true); } @@ -1360,12 +1409,16 @@ export class OpenNextRecentlyUsedEditorAction extends Action { static readonly ID = 'workbench.action.openNextRecentlyUsedEditor'; static readonly LABEL = nls.localize('openNextRecentlyUsedEditor', "Open Next Recently Used Editor"); - constructor(id: string, label: string, @IHistoryService private readonly historyService: IHistoryService) { + constructor( + id: string, + label: string, + @IHistoryService private readonly historyService: IHistoryService + ) { super(id, label); } run(): Promise { - this.historyService.forward(true); + this.historyService.openNextRecentlyUsedEditor(); return Promise.resolve(); } @@ -1376,12 +1429,58 @@ export class OpenPreviousRecentlyUsedEditorAction extends Action { static readonly ID = 'workbench.action.openPreviousRecentlyUsedEditor'; static readonly LABEL = nls.localize('openPreviousRecentlyUsedEditor', "Open Previous Recently Used Editor"); - constructor(id: string, label: string, @IHistoryService private readonly historyService: IHistoryService) { + constructor( + id: string, + label: string, + @IHistoryService private readonly historyService: IHistoryService + ) { super(id, label); } run(): Promise { - this.historyService.back(true); + this.historyService.openPreviouslyUsedEditor(); + + return Promise.resolve(); + } +} + +export class OpenNextRecentlyUsedEditorInGroupAction extends Action { + + static readonly ID = 'workbench.action.openNextRecentlyUsedEditorInGroup'; + static readonly LABEL = nls.localize('openNextRecentlyUsedEditorInGroup', "Open Next Recently Used Editor In Group"); + + constructor( + id: string, + label: string, + @IHistoryService private readonly historyService: IHistoryService, + @IEditorGroupsService private readonly editorGroupsService: IEditorGroupsService + ) { + super(id, label); + } + + run(): Promise { + this.historyService.openNextRecentlyUsedEditor(this.editorGroupsService.activeGroup.id); + + return Promise.resolve(); + } +} + +export class OpenPreviousRecentlyUsedEditorInGroupAction extends Action { + + static readonly ID = 'workbench.action.openPreviousRecentlyUsedEditorInGroup'; + static readonly LABEL = nls.localize('openPreviousRecentlyUsedEditorInGroup', "Open Previous Recently Used Editor In Group"); + + constructor( + id: string, + label: string, + @IHistoryService private readonly historyService: IHistoryService, + @IEditorGroupsService private readonly editorGroupsService: IEditorGroupsService + ) { + super(id, label); + } + + run(): Promise { + this.historyService.openPreviouslyUsedEditor(this.editorGroupsService.activeGroup.id); return Promise.resolve(); } diff --git a/src/vs/workbench/browser/parts/editor/editorCommands.ts b/src/vs/workbench/browser/parts/editor/editorCommands.ts index 7378ebe74d..e00c16897a 100644 --- a/src/vs/workbench/browser/parts/editor/editorCommands.ts +++ b/src/vs/workbench/browser/parts/editor/editorCommands.ts @@ -46,8 +46,9 @@ export const SPLIT_EDITOR_DOWN = 'workbench.action.splitEditorDown'; export const SPLIT_EDITOR_LEFT = 'workbench.action.splitEditorLeft'; export const SPLIT_EDITOR_RIGHT = 'workbench.action.splitEditorRight'; -export const NAVIGATE_ALL_EDITORS_GROUP_PREFIX = 'edt '; -export const NAVIGATE_IN_ACTIVE_GROUP_PREFIX = 'edt active '; +export const NAVIGATE_ALL_EDITORS_BY_APPEARANCE_PREFIX = 'edt '; +export const NAVIGATE_ALL_EDITORS_BY_MOST_RECENTLY_USED_PREFIX = 'edt mru '; +export const NAVIGATE_IN_ACTIVE_GROUP_BY_MOST_RECENTLY_USED_PREFIX = 'edt active '; export const OPEN_EDITOR_AT_INDEX_COMMAND_ID = 'workbench.action.openEditorAtIndex'; @@ -665,10 +666,6 @@ function registerCloseEditorCommands() { const editorGroupService = accessor.get(IEditorGroupsService); const quickOpenService = accessor.get(IQuickOpenService); - if (editorGroupService.count <= 1) { - return quickOpenService.show(NAVIGATE_ALL_EDITORS_GROUP_PREFIX); - } - const commandsContext = getCommandsContext(resourceOrContext, context); if (commandsContext && typeof commandsContext.groupId === 'number') { const group = editorGroupService.getGroup(commandsContext.groupId); @@ -677,7 +674,7 @@ function registerCloseEditorCommands() { } } - return quickOpenService.show(NAVIGATE_IN_ACTIVE_GROUP_PREFIX); + return quickOpenService.show(NAVIGATE_IN_ACTIVE_GROUP_BY_MOST_RECENTLY_USED_PREFIX); } }); diff --git a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts index 5e8782e9bb..0f11d331f4 100644 --- a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts +++ b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts @@ -18,6 +18,11 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { RunOnceScheduler } from 'vs/base/common/async'; import { find } from 'vs/base/common/arrays'; import { DataTransfers } from 'vs/base/browser/dnd'; +import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; +import { VSBuffer } from 'vs/base/common/buffer'; +import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { URI } from 'vs/base/common/uri'; +import { joinPath } from 'vs/base/common/resources'; interface IDropOperation { splitDirection?: GroupDirection; @@ -41,8 +46,10 @@ class DropOverlay extends Themable { constructor( private accessor: IEditorGroupsAccessor, private groupView: IEditorGroupView, - themeService: IThemeService, - private instantiationService: IInstantiationService + @IThemeService themeService: IThemeService, + @IInstantiationService private instantiationService: IInstantiationService, + @IUntitledTextEditorService private untitledTextEditorService: IUntitledTextEditorService, + @IFileDialogService private readonly fileDialogService: IFileDialogService ) { super(themeService); @@ -100,10 +107,6 @@ class DropOverlay extends Themable { this._register(new DragAndDropObserver(this.container, { onDragEnter: e => undefined, onDragOver: e => { - if (isWeb && containsDragType(e, DataTransfers.FILES)) { - return; // dropping files into editor is unsupported on web - } - const isDraggingGroup = this.groupTransfer.hasData(DraggedEditorGroupIdentifier.prototype); const isDraggingEditor = this.editorTransfer.hasData(DraggedEditorIdentifier.prototype); @@ -281,6 +284,45 @@ class DropOverlay extends Themable { } } + // Web: check for file transfer + else if (isWeb && containsDragType(event, DataTransfers.FILES)) { + let targetGroup: IEditorGroupView | undefined = undefined; + + const files = event.dataTransfer?.files; + if (files) { + for (let i = 0; i < files.length; i++) { + const file = files.item(i); + if (file) { + const reader = new FileReader(); + reader.readAsArrayBuffer(file); + reader.onload = async event => { + const name = file.name; + if (typeof name === 'string' && event.target?.result instanceof ArrayBuffer) { + + // Try to come up with a good file path for the untitled + // editor by asking the file dialog service for the default + let proposedFilePath: URI | undefined = undefined; + const defaultFilePath = this.fileDialogService.defaultFilePath(); + if (defaultFilePath) { + proposedFilePath = joinPath(defaultFilePath, name); + } + + // Open as untitled file with the provided contents + const contents = VSBuffer.wrap(new Uint8Array(event.target.result)).toString(); + const untitledEditor = this.untitledTextEditorService.createOrGet(proposedFilePath, undefined, contents); + + if (!targetGroup) { + targetGroup = ensureTargetGroup(); + } + + await targetGroup.openEditor(untitledEditor); + } + }; + } + } + } + } + // Check for URI transfer else { const dropHandler = this.instantiationService.createInstance(ResourcesDropHandler, { allowWorkspaceOpen: true /* open workspace instead of file if dropped */ }); @@ -532,7 +574,7 @@ export class EditorDropTarget extends Themable { if (!this.overlay) { const targetGroupView = this.findTargetGroupView(target); if (targetGroupView) { - this._overlay = new DropOverlay(this.accessor, targetGroupView, this.themeService, this.instantiationService); + this._overlay = this.instantiationService.createInstance(DropOverlay, this.accessor, targetGroupView); } } } diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index 6cccd0f0aa..108c34165e 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -49,7 +49,7 @@ import { hash } from 'vs/base/common/hash'; import { guessMimeTypes } from 'vs/base/common/mime'; import { extname } from 'vs/base/common/resources'; import { Schemas } from 'vs/base/common/network'; -import { EditorActivation, EditorOpenContext } from 'vs/platform/editor/common/editor'; +import { EditorActivation, EditorOpenContext, IResourceInput } from 'vs/platform/editor/common/editor'; import { IDialogService, IFileDialogService, ConfirmResult } from 'vs/platform/dialogs/common/dialogs'; import { ILogService } from 'vs/platform/log/common/log'; @@ -768,7 +768,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { return this._group.indexOf(editor); } - isOpened(editor: EditorInput): boolean { + isOpened(editor: EditorInput | IResourceInput): boolean { return this._group.contains(editor); } @@ -785,14 +785,16 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this._onDidFocus.fire(); } - pinEditor(editor: EditorInput | undefined = this.activeEditor || undefined): void { - if (editor && !this._group.isPinned(editor)) { + pinEditor(candidate: EditorInput | undefined = this.activeEditor || undefined): void { + if (candidate && !this._group.isPinned(candidate)) { // Update model - this._group.pin(editor); + const editor = this._group.pin(candidate); // Forward to title control - this.titleAreaControl.pinEditor(editor); + if (editor) { + this.titleAreaControl.pinEditor(editor); + } } } @@ -880,11 +882,13 @@ export class EditorGroupView extends Themable implements IEditorGroupView { } } - // Update model - this._group.openEditor(editor, openEditorOptions); + // Update model and make sure to continue to use the editor we get from + // the model. It is possible that the editor was already opened and we + // want to ensure that we use the existing instance in that case. + const openedEditor = this._group.openEditor(editor, openEditorOptions); // Show editor - return this.doShowEditor(editor, !!openEditorOptions.active, options); + return this.doShowEditor(openedEditor, !!openEditorOptions.active, options); } private async doShowEditor(editor: EditorInput, active: boolean, options?: EditorOptions): Promise { @@ -1053,17 +1057,22 @@ export class EditorGroupView extends Themable implements IEditorGroupView { } } - private doMoveEditorInsideGroup(editor: EditorInput, moveOptions?: IMoveEditorOptions): void { + private doMoveEditorInsideGroup(candidate: EditorInput, moveOptions?: IMoveEditorOptions): void { const moveToIndex = moveOptions ? moveOptions.index : undefined; if (typeof moveToIndex !== 'number') { return; // do nothing if we move into same group without index } - const currentIndex = this._group.indexOf(editor); - if (currentIndex === moveToIndex) { - return; // do nothing if editor is already at the given index + const currentIndex = this._group.indexOf(candidate); + if (currentIndex === -1 || currentIndex === moveToIndex) { + return; // do nothing if editor unknown in model or is already at the given index } + // Update model and make sure to continue to use the editor we get from + // the model. It is possible that the editor was already opened and we + // want to ensure that we use the existing instance in that case. + const editor = this.group.getEditorByIndex(currentIndex)!; + // Update model this._group.moveEditor(editor, moveToIndex); this._group.pin(editor); diff --git a/src/vs/workbench/browser/parts/editor/editorPart.ts b/src/vs/workbench/browser/parts/editor/editorPart.ts index 428ea6dac8..ec55711afd 100644 --- a/src/vs/workbench/browser/parts/editor/editorPart.ts +++ b/src/vs/workbench/browser/parts/editor/editorPart.ts @@ -240,7 +240,7 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro const mostRecentActive = coalesce(this.mostRecentActiveGroups.map(groupId => this.getGroup(groupId))); // there can be groups that got never active, even though they exist. in this case - // make sure to ust append them at the end so that all groups are returned properly + // make sure to just append them at the end so that all groups are returned properly return distinct([...mostRecentActive, ...this.groups]); case GroupsOrder.GRID_APPEARANCE: @@ -544,7 +544,7 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro this.groupViews.set(groupView.id, groupView); // Track focus - let groupDisposables = new DisposableStore(); + const groupDisposables = new DisposableStore(); groupDisposables.add(groupView.onDidFocus(() => { this.doSetGroupActive(groupView); })); diff --git a/src/vs/workbench/browser/parts/editor/editorPicker.ts b/src/vs/workbench/browser/parts/editor/editorPicker.ts index 8afb0f7343..755cca1b2b 100644 --- a/src/vs/workbench/browser/parts/editor/editorPicker.ts +++ b/src/vs/workbench/browser/parts/editor/editorPicker.ts @@ -18,6 +18,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { toResource, SideBySideEditor, IEditorInput } from 'vs/workbench/common/editor'; import { compareItemsByScore, scoreItem, ScorerCache, prepareQuery } from 'vs/base/parts/quickopen/common/quickOpenScorer'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { IHistoryService } from 'vs/workbench/services/history/common/history'; export class EditorPickerEntry extends QuickOpenEntryGroup { @@ -144,28 +145,9 @@ export abstract class BaseEditorPicker extends QuickOpenHandler { this.scorerCache = Object.create(null); } + protected abstract count(): number; + protected abstract getEditorEntries(): EditorPickerEntry[]; -} - -export class ActiveEditorGroupPicker extends BaseEditorPicker { - - static readonly ID = 'workbench.picker.activeEditors'; - - protected getEditorEntries(): EditorPickerEntry[] { - return this.group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE).map((editor, index) => this.instantiationService.createInstance(EditorPickerEntry, editor, this.group)); - } - - private get group(): IEditorGroup { - return this.editorGroupService.activeGroup; - } - - getEmptyLabel(searchString: string): string { - if (searchString) { - return nls.localize('noResultsFoundInGroup', "No matching opened editor found in group"); - } - - return nls.localize('noOpenedEditors', "List of opened editors is currently empty in group"); - } getAutoFocus(searchValue: string, context: { model: IModel, quickNavigateConfiguration?: IQuickNavigateConfiguration }): IAutoFocus { if (searchValue || !context.quickNavigateConfiguration) { @@ -189,7 +171,7 @@ export class ActiveEditorGroupPicker extends BaseEditorPicker { }; } - const editors = this.group.count; + const editors = this.count(); return { autoFocusFirstEntry: editors === 1, autoFocusSecondEntry: editors > 1 @@ -197,20 +179,44 @@ export class ActiveEditorGroupPicker extends BaseEditorPicker { } } -export class AllEditorsPicker extends BaseEditorPicker { +export class ActiveGroupEditorsByMostRecentlyUsedPicker extends BaseEditorPicker { - static readonly ID = 'workbench.picker.editors'; + static readonly ID = 'workbench.picker.activeGroupEditorsByMostRecentlyUsed'; + + protected count(): number { + return this.group.count; + } protected getEditorEntries(): EditorPickerEntry[] { - const entries: EditorPickerEntry[] = []; + return this.group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE).map(editor => this.instantiationService.createInstance(EditorPickerEntry, editor, this.group)); + } - this.editorGroupService.getGroups(GroupsOrder.GRID_APPEARANCE).forEach(group => { - group.editors.forEach(editor => { - entries.push(this.instantiationService.createInstance(EditorPickerEntry, editor, group)); - }); - }); + private get group(): IEditorGroup { + return this.editorGroupService.activeGroup; + } - return entries; + getEmptyLabel(searchString: string): string { + if (searchString) { + return nls.localize('noResultsFoundInGroup', "No matching opened editor found in active editor group"); + } + + return nls.localize('noOpenedEditors', "List of opened editors is currently empty in active editor group"); + } +} + +export abstract class BaseAllEditorsPicker extends BaseEditorPicker { + + constructor( + @IInstantiationService instantiationService: IInstantiationService, + @IEditorService editorService: IEditorService, + @IEditorGroupsService editorGroupService: IEditorGroupsService, + @IHistoryService protected historyService: IHistoryService + ) { + super(instantiationService, editorService, editorGroupService); + } + + protected count(): number { + return this.historyService.getMostRecentlyUsedOpenEditors().length; } getEmptyLabel(searchString: string): string { @@ -231,3 +237,35 @@ export class AllEditorsPicker extends BaseEditorPicker { return super.getAutoFocus(searchValue, context); } } + +export class AllEditorsByAppearancePicker extends BaseAllEditorsPicker { + + static readonly ID = 'workbench.picker.editorsByAppearance'; + + protected getEditorEntries(): EditorPickerEntry[] { + const entries: EditorPickerEntry[] = []; + + for (const group of this.editorGroupService.getGroups(GroupsOrder.GRID_APPEARANCE)) { + for (const editor of group.getEditors(EditorsOrder.SEQUENTIAL)) { + entries.push(this.instantiationService.createInstance(EditorPickerEntry, editor, group)); + } + } + + return entries; + } +} + +export class AllEditorsByMostRecentlyUsedPicker extends BaseAllEditorsPicker { + + static readonly ID = 'workbench.picker.editorsByMostRecentlyUsed'; + + protected getEditorEntries(): EditorPickerEntry[] { + const entries: EditorPickerEntry[] = []; + + for (const { editor, groupId } of this.historyService.getMostRecentlyUsedOpenEditors()) { + entries.push(this.instantiationService.createInstance(EditorPickerEntry, editor, this.editorGroupService.getGroup(groupId)!)); + } + + return entries; + } +} diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index 96befa2ce8..3edb3b308b 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 { IResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; +import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { deepClone } from 'vs/base/common/objects'; import { ICodeEditor, getCodeEditor } from 'vs/editor/browser/editorBrowser'; @@ -1161,12 +1161,12 @@ export class ChangeModeAction extends Action { // If the association is already being made in the workspace, make sure to target workspace settings let target = ConfigurationTarget.USER; - if (fileAssociationsConfig.workspace && !!(fileAssociationsConfig.workspace as any)[associationKey]) { + if (fileAssociationsConfig.workspaceValue && !!(fileAssociationsConfig.workspaceValue as any)[associationKey]) { target = ConfigurationTarget.WORKSPACE; } // Make sure to write into the value of the target and not the merged value from USER and WORKSPACE config - const currentAssociations = deepClone((target === ConfigurationTarget.WORKSPACE) ? fileAssociationsConfig.workspace : fileAssociationsConfig.user) || Object.create(null); + const currentAssociations = deepClone((target === ConfigurationTarget.WORKSPACE) ? fileAssociationsConfig.workspaceValue : fileAssociationsConfig.userValue) || Object.create(null); currentAssociations[associationKey] = language.id; this.configurationService.updateValue(FILES_ASSOCIATIONS_CONFIG, currentAssociations, target); @@ -1249,7 +1249,7 @@ export class ChangeEncodingAction extends Action { actionLabel: string, @IEditorService private readonly editorService: IEditorService, @IQuickInputService private readonly quickInputService: IQuickInputService, - @IResourceConfigurationService private readonly textResourceConfigurationService: IResourceConfigurationService, + @ITextResourceConfigurationService private readonly textResourceConfigurationService: ITextResourceConfigurationService, @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 982b4ddcba..36545ab275 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 { IResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; +import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; 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, - @IResourceConfigurationService configurationService: IResourceConfigurationService, + @ITextResourceConfigurationService configurationService: ITextResourceConfigurationService, @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 22c3d02806..94dcd920a3 100644 --- a/src/vs/workbench/browser/parts/editor/textEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textEditor.ts @@ -16,15 +16,13 @@ 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 { IResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; +import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; 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'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -const TEXT_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'textEditorViewState'; - export interface IEditorConfiguration { editor: object; diffEditor: object; @@ -35,6 +33,9 @@ export interface IEditorConfiguration { * be subclassed and not instantiated. */ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor { + + static readonly TEXT_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'textEditorViewState'; + private editorControl: IEditor | undefined; private editorContainer: HTMLElement | undefined; private hasPendingConfigurationChange: boolean | undefined; @@ -46,14 +47,14 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor { @ITelemetryService telemetryService: ITelemetryService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @IStorageService storageService: IStorageService, - @IResourceConfigurationService private readonly _configurationService: IResourceConfigurationService, + @ITextResourceConfigurationService private readonly _configurationService: ITextResourceConfigurationService, @IThemeService protected themeService: IThemeService, @IEditorService protected editorService: IEditorService, @IEditorGroupsService protected editorGroupService: IEditorGroupsService ) { super(id, telemetryService, themeService, storageService); - this.editorMemento = this.getEditorMemento(editorGroupService, TEXT_EDITOR_VIEW_STATE_PREFERENCE_KEY, 100); + this.editorMemento = this.getEditorMemento(editorGroupService, BaseTextEditor.TEXT_EDITOR_VIEW_STATE_PREFERENCE_KEY, 100); this._register(this.configurationService.onDidChangeConfiguration(e => { const resource = this.getResource(); @@ -66,7 +67,7 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor { return this._instantiationService; } - protected get configurationService(): IResourceConfigurationService { + protected get configurationService(): ITextResourceConfigurationService { return this._configurationService; } diff --git a/src/vs/workbench/browser/parts/editor/textResourceEditor.ts b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts index ecd010d578..61e02c6eb7 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 { IResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; +import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; 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, - @IResourceConfigurationService configurationService: IResourceConfigurationService, + @ITextResourceConfigurationService configurationService: ITextResourceConfigurationService, @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, - @IResourceConfigurationService configurationService: IResourceConfigurationService, + @ITextResourceConfigurationService configurationService: ITextResourceConfigurationService, @IThemeService themeService: IThemeService, @IEditorService editorService: IEditorService, @IEditorGroupsService editorGroupService: IEditorGroupsService diff --git a/src/vs/workbench/browser/parts/quickinput/quickInput.ts b/src/vs/workbench/browser/parts/quickinput/quickInput.ts index b32cdd2b99..2e11ed1e57 100644 --- a/src/vs/workbench/browser/parts/quickinput/quickInput.ts +++ b/src/vs/workbench/browser/parts/quickinput/quickInput.ts @@ -16,7 +16,7 @@ import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; import { CancellationToken } from 'vs/base/common/cancellation'; import { QuickInputList } from './quickInputList'; import { QuickInputBox } from './quickInputBox'; -import { KeyCode } from 'vs/base/common/keyCodes'; +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { localize } from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -1396,7 +1396,7 @@ export class QuickInputService extends Component implements IQuickInputService { this.setComboboxAccessibility(false); ui.inputBox.removeAttribute('aria-label'); - const keybinding = this.keybindingService.lookupKeybinding(BackAction.ID); + const keybinding = this.keybindingService.lookupKeybinding(QuickPickBack.id); backButton.tooltip = keybinding ? localize('quickInput.backWithKeybinding', "Back ({0})", keybinding.getLabel()) : localize('quickInput.back', "Back"); this.inQuickOpen('quickInput', true); @@ -1566,19 +1566,18 @@ export const QuickPickManyToggle: ICommandAndKeybindingRule = { } }; -export class BackAction extends Action { - - public static readonly ID = 'workbench.action.quickInputBack'; - public static readonly LABEL = localize('back', "Back"); - - constructor(id: string, label: string, @IQuickInputService private readonly quickInputService: IQuickInputService) { - super(id, label); +export const QuickPickBack: ICommandAndKeybindingRule = { + id: 'workbench.action.quickInputBack', + weight: KeybindingWeight.WorkbenchContrib + 50, + when: inQuickOpenContext, + primary: 0, + win: { primary: KeyMod.Alt | KeyCode.LeftArrow }, + mac: { primary: KeyMod.WinCtrl | KeyCode.US_MINUS }, + linux: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.US_MINUS }, + handler: accessor => { + const quickInputService = accessor.get(IQuickInputService); + quickInputService.back(); } - - public run(): Promise { - this.quickInputService.back(); - return Promise.resolve(); - } -} +}; registerSingleton(IQuickInputService, QuickInputService, true); diff --git a/src/vs/workbench/browser/parts/quickinput/quickInputActions.ts b/src/vs/workbench/browser/parts/quickinput/quickInputActions.ts index f071259c66..f59187737d 100644 --- a/src/vs/workbench/browser/parts/quickinput/quickInputActions.ts +++ b/src/vs/workbench/browser/parts/quickinput/quickInputActions.ts @@ -3,15 +3,8 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { QuickPickManyToggle, BackAction } from 'vs/workbench/browser/parts/quickinput/quickInput'; -import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; -import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; -import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; -import { inQuickOpenContext } from 'vs/workbench/browser/parts/quickopen/quickopen'; +import { QuickPickManyToggle, QuickPickBack } from 'vs/workbench/browser/parts/quickinput/quickInput'; +import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; KeybindingsRegistry.registerCommandAndKeybindingRule(QuickPickManyToggle); - -const registry = Registry.as(ActionExtensions.WorkbenchActions); -registry.registerWorkbenchAction(SyncActionDescriptor.create(BackAction, BackAction.ID, BackAction.LABEL, { primary: 0, win: { primary: KeyMod.Alt | KeyCode.LeftArrow }, mac: { primary: KeyMod.WinCtrl | KeyCode.US_MINUS }, linux: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.US_MINUS } }, inQuickOpenContext, KeybindingWeight.WorkbenchContrib + 50), 'Back'); +KeybindingsRegistry.registerCommandAndKeybindingRule(QuickPickBack); diff --git a/src/vs/workbench/browser/parts/quickinput/quickInputList.ts b/src/vs/workbench/browser/parts/quickinput/quickInputList.ts index 3cef2131a5..1765f4deb5 100644 --- a/src/vs/workbench/browser/parts/quickinput/quickInputList.ts +++ b/src/vs/workbench/browser/parts/quickinput/quickInputList.ts @@ -28,7 +28,7 @@ import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { Action } from 'vs/base/common/actions'; import { getIconClass } from 'vs/workbench/browser/parts/quickinput/quickInputUtils'; import { withNullAsUndefined } from 'vs/base/common/types'; -import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; +import { QUICK_INPUT_BACKGROUND } from 'vs/workbench/common/theme'; const $ = dom.$; @@ -254,7 +254,7 @@ export class QuickInputList { multipleSelectionSupport: false, horizontalScrolling: false, overrideStyles: { - listBackground: SIDE_BAR_BACKGROUND + listBackground: QUICK_INPUT_BACKGROUND } } as IWorkbenchListOptions); this.list.getHTMLElement().id = id; diff --git a/src/vs/workbench/browser/parts/views/media/views.css b/src/vs/workbench/browser/parts/views/media/views.css index 9e1d51f2af..d76397a06a 100644 --- a/src/vs/workbench/browser/parts/views/media/views.css +++ b/src/vs/workbench/browser/parts/views/media/views.css @@ -146,3 +146,7 @@ background-position: 50% 50%; background-repeat: no-repeat; } + +.customview-tree .monaco-list .custom-view-tree-node-item .actions .action-label.codicon::before { + vertical-align: middle; +} diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index 2981a681fb..654ea03e57 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -36,7 +36,8 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IViewPaneContainer } from 'vs/workbench/common/viewPaneContainer'; import { Component } from 'vs/workbench/common/component'; -import { Extensions, ViewletRegistry } from 'vs/workbench/browser/viewlet'; +import { Extensions as ViewletExtensions, ViewletRegistry } from 'vs/workbench/browser/viewlet'; +import { Extensions as PanelExtensions, PanelRegistry } from 'vs/workbench/browser/panel'; export interface IPaneColors extends IColorMapping { dropBackground?: ColorIdentifier; @@ -344,7 +345,8 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { } getTitle(): string { - let title = Registry.as(Extensions.Viewlets).getViewlet(this.getId()).name; + const composite = Registry.as(ViewletExtensions.Viewlets).getViewlet(this.getId()) || Registry.as(PanelExtensions.Panels).getPanel(this.getId()); + let title = composite.name; if (this.isSingleView()) { const paneItemTitle = this.paneItems[0].pane.title; diff --git a/src/vs/workbench/browser/workbench.ts b/src/vs/workbench/browser/workbench.ts index 67cd07b62e..6113f3b28e 100644 --- a/src/vs/workbench/browser/workbench.ts +++ b/src/vs/workbench/browser/workbench.ts @@ -44,6 +44,7 @@ import { WorkbenchContextKeysHandler } from 'vs/workbench/browser/contextkeys'; import { coalesce } from 'vs/base/common/arrays'; import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; import { Layout } from 'vs/workbench/browser/layout'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; export class Workbench extends Layout { @@ -140,6 +141,7 @@ export class Workbench extends Layout { const lifecycleService = accessor.get(ILifecycleService); const storageService = accessor.get(IStorageService); const configurationService = accessor.get(IConfigurationService); + const hostService = accessor.get(IHostService); // Layout this.initLayout(accessor); @@ -151,7 +153,7 @@ export class Workbench extends Layout { this._register(instantiationService.createInstance(WorkbenchContextKeysHandler)); // Register Listeners - this.registerListeners(lifecycleService, storageService, configurationService); + this.registerListeners(lifecycleService, storageService, configurationService, hostService); // Render Workbench this.renderWorkbench(instantiationService, accessor.get(INotificationService) as NotificationService, storageService, configurationService); @@ -224,7 +226,8 @@ export class Workbench extends Layout { private registerListeners( lifecycleService: ILifecycleService, storageService: IStorageService, - configurationService: IConfigurationService + configurationService: IConfigurationService, + hostService: IHostService ): void { // Configuration changes @@ -248,6 +251,13 @@ export class Workbench extends Layout { this._onShutdown.fire(); this.dispose(); })); + + // In some environments we do not get enough time to persist state on shutdown. + // In other cases, VSCode might crash, so we periodically save state to reduce + // the chance of loosing any state. + // The window loosing focus is a good indication that the user has stopped working + // in that window so we pick that at a time to collect state. + this._register(hostService.onDidChangeFocus(focus => { if (!focus) { storageService.flush(); } })); } private fontAliasing: 'default' | 'antialiased' | 'none' | 'auto' | undefined; diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 9669e39527..f14fe73afe 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -7,7 +7,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import { assign } from 'vs/base/common/objects'; import { isUndefinedOrNull, withNullAsUndefined, assertIsDefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; -import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; +import { IDisposable, Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { IEditor as ICodeEditor, IEditorViewState, ScrollType, IDiffEditor } from 'vs/editor/common/editorCommon'; import { IEditorModel, IEditorOptions, ITextEditorOptions, IBaseResourceInput, IResourceInput, EditorActivation, EditorOpenContext } from 'vs/platform/editor/common/editor'; import { IInstantiationService, IConstructorSignature0, ServicesAccessor, BrandedService } from 'vs/platform/instantiation/common/instantiation'; @@ -181,7 +181,7 @@ export interface IEditorInputFactoryRegistry { * @param editorInputId the identifier of the editor input * @param factory the editor input factory for serialization/deserialization */ - registerEditorInputFactory(editorInputId: string, ctor: { new(...Services: Services): IEditorInputFactory }): void; + registerEditorInputFactory(editorInputId: string, ctor: { new(...Services: Services): IEditorInputFactory }): IDisposable; /** * Returns the editor input factory for the given editor input. @@ -198,6 +198,11 @@ export interface IEditorInputFactoryRegistry { export interface IEditorInputFactory { + /** + * Determines wether the given editor input can be serialized by the factory. + */ + canSerialize(editorInput: IEditorInput): boolean; + /** * Returns a string representation of the provided editor input that contains enough information * to deserialize back to the original editor input from the deserialize() method. @@ -613,12 +618,12 @@ export const enum EncodingMode { export interface IEncodingSupport { /** - * Gets the encoding of the input if known. + * Gets the encoding of the type if known. */ getEncoding(): string | undefined; /** - * Sets the encoding for the input for saving. + * Sets the encoding for the type for saving. */ setEncoding(encoding: string, mode: EncodingMode): void; } @@ -626,7 +631,7 @@ export interface IEncodingSupport { export interface IModeSupport { /** - * Sets the language mode of the input. + * Sets the language mode of the type. */ setMode(mode: string): void; } @@ -1227,6 +1232,7 @@ export interface IEditorMemento { class EditorInputFactoryRegistry implements IEditorInputFactoryRegistry { private instantiationService: IInstantiationService | undefined; private fileInputFactory: IFileInputFactory | undefined; + private readonly editorInputFactoryConstructors: Map> = new Map(); private readonly editorInputFactoryInstances: Map = new Map(); @@ -1253,12 +1259,18 @@ class EditorInputFactoryRegistry implements IEditorInputFactoryRegistry { return assertIsDefined(this.fileInputFactory); } - registerEditorInputFactory(editorInputId: string, ctor: IConstructorSignature0): void { + registerEditorInputFactory(editorInputId: string, ctor: IConstructorSignature0): IDisposable { if (!this.instantiationService) { this.editorInputFactoryConstructors.set(editorInputId, ctor); } else { this.createEditorInputFactory(editorInputId, ctor, this.instantiationService); + } + + return toDisposable(() => { + this.editorInputFactoryConstructors.delete(editorInputId); + this.editorInputFactoryInstances.delete(editorInputId); + }); } getEditorInputFactory(editorInputId: string): IEditorInputFactory | undefined { diff --git a/src/vs/workbench/common/editor/editorGroup.ts b/src/vs/workbench/common/editor/editorGroup.ts index 478eb7a017..2c02ddfd7a 100644 --- a/src/vs/workbench/common/editor/editorGroup.ts +++ b/src/vs/workbench/common/editor/editorGroup.ts @@ -4,12 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import { Event, Emitter } from 'vs/base/common/event'; -import { Extensions, IEditorInputFactoryRegistry, EditorInput, IEditorIdentifier, IEditorCloseEvent, GroupIdentifier, CloseDirection, IEditorInput, SideBySideEditorInput } from 'vs/workbench/common/editor'; +import { Extensions, IEditorInputFactoryRegistry, EditorInput, IEditorIdentifier, IEditorCloseEvent, GroupIdentifier, CloseDirection, SideBySideEditorInput, IEditorInput } from 'vs/workbench/common/editor'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; import { dispose, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; import { coalesce, firstIndex } from 'vs/base/common/arrays'; +import { isEqual } from 'vs/base/common/resources'; +import { IResourceInput } from 'vs/platform/editor/common/editor'; // {{SQL CARBON EDIT}} import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; @@ -158,18 +160,19 @@ export class EditorGroup extends Disposable { return this.matches(this.preview, editor); } - openEditor(editor: EditorInput, options?: IEditorOpenOptions): void { - const index = this.indexOf(editor); - + openEditor(candidate: EditorInput, options?: IEditorOpenOptions): EditorInput { const makePinned = options?.pinned; const makeActive = options?.active || !this.activeEditor || (!makePinned && this.matches(this.preview, this.activeEditor)); + const existingEditor = this.findEditor(candidate); + // New editor - if (index === -1) { - let targetIndex: number; + if (!existingEditor) { + const newEditor = candidate; const indexOfActive = this.indexOf(this.active); // Insert into specific position + let targetIndex: number; if (options && typeof options.index === 'number') { targetIndex = options.index; } @@ -200,7 +203,7 @@ export class EditorGroup extends Disposable { // Insert into our list of editors if pinned or we have no preview editor if (makePinned || !this.preview) { - this.splice(targetIndex, false, editor); + this.splice(targetIndex, false, newEditor); } // Handle preview @@ -213,22 +216,24 @@ export class EditorGroup extends Disposable { targetIndex--; // accomodate for the fact that the preview editor closes } - this.replaceEditor(this.preview, editor, targetIndex, !makeActive); + this.replaceEditor(this.preview, newEditor, targetIndex, !makeActive); } - this.preview = editor; + this.preview = newEditor; } // Listeners - this.registerEditorListeners(editor); + this.registerEditorListeners(newEditor); // Event - this._onDidEditorOpen.fire(editor); + this._onDidEditorOpen.fire(newEditor); // Handle active if (makeActive) { - this.setActive(editor); + this.doSetActive(newEditor); } + + return newEditor; } // Existing editor @@ -236,18 +241,20 @@ export class EditorGroup extends Disposable { // Pin it if (makePinned) { - this.pin(editor); + this.doPin(existingEditor); } // Activate it if (makeActive) { - this.setActive(editor); + this.doSetActive(existingEditor); } // Respect index if (options && typeof options.index === 'number') { - this.moveEditor(editor, options.index); + this.moveEditor(existingEditor, options.index); } + + return existingEditor; } } @@ -293,24 +300,26 @@ export class EditorGroup extends Disposable { } } - closeEditor(editor: EditorInput, openNext = true): number | undefined { - const event = this.doCloseEditor(editor, openNext, false); + closeEditor(candidate: EditorInput, openNext = true): EditorInput | undefined { + const event = this.doCloseEditor(candidate, openNext, false); if (event) { this._onDidEditorClose.fire(event); - return event.index; + return event.editor; } return undefined; } - private doCloseEditor(editor: EditorInput, openNext: boolean, replaced: boolean): EditorCloseEvent | null { - const index = this.indexOf(editor); + private doCloseEditor(candidate: EditorInput, openNext: boolean, replaced: boolean): EditorCloseEvent | undefined { + const index = this.indexOf(candidate); if (index === -1) { - return null; // not found + return undefined; // not found } + const editor = this.editors[index]; + // Active Editor closed if (openNext && this.matches(this.active, editor)) { @@ -327,7 +336,7 @@ export class EditorGroup extends Disposable { } } - this.setActive(newActive); + this.doSetActive(newActive); } // One Editor @@ -383,26 +392,36 @@ export class EditorGroup extends Disposable { } } - moveEditor(editor: EditorInput, toIndex: number): void { - const index = this.indexOf(editor); + moveEditor(candidate: EditorInput, toIndex: number): EditorInput | undefined { + const index = this.indexOf(candidate); if (index < 0) { - return; + return undefined; // {{SQL CARBON EDIT}} strict-null-check } + const editor = this.editors[index]; + // Move this.editors.splice(index, 1); this.editors.splice(toIndex, 0, editor); // Event this._onDidEditorMove.fire(editor); + + return editor; } - setActive(editor: EditorInput): void { - const index = this.indexOf(editor); - if (index === -1) { - return; // not found + setActive(candidate: EditorInput): EditorInput | undefined { + const editor = this.findEditor(candidate); + if (!editor) { + return undefined; // not found {{SQL CARBON EDIT}} strict-null-check } + this.doSetActive(editor); + + return editor; + } + + private doSetActive(editor: EditorInput): void { if (this.matches(this.active, editor)) { return; // already active } @@ -410,18 +429,26 @@ export class EditorGroup extends Disposable { this.active = editor; // Bring to front in MRU list - this.setMostRecentlyUsed(editor); + const mruIndex = this.indexOf(editor, this.mru); + this.mru.splice(mruIndex, 1); + this.mru.unshift(editor); // Event this._onDidEditorActivate.fire(editor); } - pin(editor: EditorInput): void { - const index = this.indexOf(editor); - if (index === -1) { - return; // not found + pin(candidate: EditorInput): EditorInput | undefined { + const editor = this.findEditor(candidate); + if (!editor) { + return undefined; // not found {{SQL CARBON EDIT}} strict-null-check } + this.doPin(editor); + + return editor; + } + + private doPin(editor: EditorInput): void { if (!this.isPreview(editor)) { return; // can only pin a preview editor } @@ -433,12 +460,18 @@ export class EditorGroup extends Disposable { this._onDidEditorPin.fire(editor); } - unpin(editor: EditorInput): void { - const index = this.indexOf(editor); - if (index === -1) { - return; // not found + unpin(candidate: EditorInput): EditorInput | undefined { + const editor = this.findEditor(candidate); + if (!editor) { + return undefined; // not found {{SQL CARBON EDIT}} strict-null-check } + this.doUnpin(editor); + + return editor; + } + + private doUnpin(editor: EditorInput): void { if (!this.isPinned(editor)) { return; // can only unpin a pinned editor } @@ -537,7 +570,16 @@ export class EditorGroup extends Disposable { return -1; } - contains(candidate: EditorInput, searchInSideBySideEditors?: boolean): boolean { + private findEditor(candidate: EditorInput | null): EditorInput | undefined { + const index = this.indexOf(candidate, this.editors); + if (index === -1) { + return undefined; + } + + return this.editors[index]; + } + + contains(candidate: EditorInput | IResourceInput, searchInSideBySideEditors?: boolean): boolean { for (const editor of this.editors) { if (this.matches(editor, candidate)) { return true; @@ -553,23 +595,18 @@ export class EditorGroup extends Disposable { return false; } - private setMostRecentlyUsed(editor: EditorInput): void { - const index = this.indexOf(editor); - if (index === -1) { - return; // editor not found + private matches(editor: IEditorInput | null, candidate: IEditorInput | IResourceInput | null): boolean { + if (!editor || !candidate) { + return false; } - const mruIndex = this.indexOf(editor, this.mru); + if (candidate instanceof EditorInput) { + return editor.matches(candidate); + } - // Remove old index - this.mru.splice(mruIndex, 1); + const resource = editor.getResource(); - // Set editor as most recent one (first) - this.mru.unshift(editor); - } - - private matches(editorA: IEditorInput | null, editorB: IEditorInput | null): boolean { - return !!editorA && !!editorB && editorA.matches(editorB); + return !!(resource && isEqual(resource, (candidate as IResourceInput).resource)); } clone(): EditorGroup { diff --git a/src/vs/workbench/common/editor/untitledTextEditorModel.ts b/src/vs/workbench/common/editor/untitledTextEditorModel.ts index 1457859c0f..7a3a2194a9 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 { IResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; +import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; 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, - @IResourceConfigurationService private readonly configurationService: IResourceConfigurationService, + @ITextResourceConfigurationService private readonly configurationService: ITextResourceConfigurationService, @IWorkingCopyService private readonly workingCopyService: IWorkingCopyService, @ITextFileService private readonly textFileService: ITextFileService ) { diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index 2eb24d53d2..d139b554cd 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -26,6 +26,11 @@ export namespace Extensions { export const ViewsRegistry = 'workbench.registry.view'; } +export enum ViewContainerLocation { + Sidebar, + Panel +} + export interface IViewContainersRegistry { /** * An event that is triggerred when a view container is registered. @@ -50,7 +55,7 @@ export interface IViewContainersRegistry { * * @returns the registered ViewContainer. */ - registerViewContainer(id: string, hideIfEmpty?: boolean, extensionId?: ExtensionIdentifier, viewOrderDelegate?: ViewOrderDelegate): ViewContainer; + registerViewContainer(id: string, location: ViewContainerLocation, hideIfEmpty?: boolean, extensionId?: ExtensionIdentifier, viewOrderDelegate?: ViewOrderDelegate): ViewContainer; /** * Deregisters the given view container @@ -71,7 +76,7 @@ interface ViewOrderDelegate { } export class ViewContainer { - protected constructor(readonly id: string, readonly hideIfEmpty: boolean, readonly extensionId?: ExtensionIdentifier, readonly orderDelegate?: ViewOrderDelegate) { } + protected constructor(readonly id: string, readonly location: ViewContainerLocation, readonly hideIfEmpty: boolean, readonly extensionId?: ExtensionIdentifier, readonly orderDelegate?: ViewOrderDelegate) { } } class ViewContainersRegistryImpl extends Disposable implements IViewContainersRegistry { @@ -88,7 +93,7 @@ class ViewContainersRegistryImpl extends Disposable implements IViewContainersRe return values(this.viewContainers); } - registerViewContainer(id: string, hideIfEmpty?: boolean, extensionId?: ExtensionIdentifier, viewOrderDelegate?: ViewOrderDelegate): ViewContainer { + registerViewContainer(id: string, location: ViewContainerLocation, hideIfEmpty?: boolean, extensionId?: ExtensionIdentifier, viewOrderDelegate?: ViewOrderDelegate): ViewContainer { const existing = this.viewContainers.get(id); if (existing) { return existing; @@ -96,7 +101,7 @@ class ViewContainersRegistryImpl extends Disposable implements IViewContainersRe const viewContainer = new class extends ViewContainer { constructor() { - super(id, !!hideIfEmpty, extensionId, viewOrderDelegate); + super(id, location, !!hideIfEmpty, extensionId, viewOrderDelegate); } }; this.viewContainers.set(id, viewContainer); diff --git a/src/vs/workbench/contrib/backup/common/backupModelTracker.ts b/src/vs/workbench/contrib/backup/common/backupModelTracker.ts index 59b2e5ec07..5a966aaa10 100644 --- a/src/vs/workbench/contrib/backup/common/backupModelTracker.ts +++ b/src/vs/workbench/contrib/backup/common/backupModelTracker.ts @@ -37,6 +37,7 @@ export class BackupModelTracker extends Disposable implements IWorkbenchContribu this._register(this.textFileService.models.onModelDisposed(e => this.discardBackup(e))); // Listen for untitled model changes + this._register(this.untitledTextEditorService.onDidCreate(e => this.onUntitledModelCreated(e))); this._register(this.untitledTextEditorService.onDidChangeContent(e => this.onUntitledModelChanged(e))); this._register(this.untitledTextEditorService.onDidDisposeModel(e => this.discardBackup(e))); @@ -64,6 +65,12 @@ export class BackupModelTracker extends Disposable implements IWorkbenchContribu } } + private onUntitledModelCreated(resource: Uri): void { + if (this.untitledTextEditorService.isDirty(resource)) { + this.untitledTextEditorService.loadOrCreate({ resource }).then(model => model.backup()); + } + } + private onUntitledModelChanged(resource: Uri): void { if (this.untitledTextEditorService.isDirty(resource)) { this.untitledTextEditorService.loadOrCreate({ resource }).then(model => model.backup()); diff --git a/src/vs/workbench/contrib/backup/common/backupRestorer.ts b/src/vs/workbench/contrib/backup/common/backupRestorer.ts index 091ec7265b..6f616b3e6f 100644 --- a/src/vs/workbench/contrib/backup/common/backupRestorer.ts +++ b/src/vs/workbench/contrib/backup/common/backupRestorer.ts @@ -33,7 +33,7 @@ export class BackupRestorer implements IWorkbenchContribution { this.lifecycleService.when(LifecyclePhase.Restored).then(() => this.doRestoreBackups()); } - private async doRestoreBackups(): Promise { + protected async doRestoreBackups(): Promise { // Find all files and untitled with backups const backups = await this.backupFileService.getWorkspaceFileBackups(); diff --git a/src/vs/workbench/contrib/backup/test/electron-browser/backupRestorer.test.ts b/src/vs/workbench/contrib/backup/test/electron-browser/backupRestorer.test.ts new file mode 100644 index 0000000000..d48d8f72d9 --- /dev/null +++ b/src/vs/workbench/contrib/backup/test/electron-browser/backupRestorer.test.ts @@ -0,0 +1,153 @@ +/*--------------------------------------------------------------------------------------------- + * 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 * as platform from 'vs/base/common/platform'; +import * as os from 'os'; +import * as path from 'vs/base/common/path'; +import * as pfs from 'vs/base/node/pfs'; +import { URI } from 'vs/base/common/uri'; +import { createTextBufferFactory } from 'vs/editor/common/model/textModel'; +import { getRandomTestPath } from 'vs/base/test/node/testUtils'; +import { DefaultEndOfLine } from 'vs/editor/common/model'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { hashPath } from 'vs/workbench/services/backup/node/backupFileService'; +import { BackupModelTracker } from 'vs/workbench/contrib/backup/common/backupModelTracker'; +import { TestTextFileService, workbenchInstantiationService } from 'vs/workbench/test/workbenchTestServices'; +import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; +import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; +import { BackupRestorer } from 'vs/workbench/contrib/backup/common/backupRestorer'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { EditorService } from 'vs/workbench/services/editor/browser/editorService'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { EditorInput } from 'vs/workbench/common/editor'; +import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { IEditorRegistry, EditorDescriptor, Extensions as EditorExtensions } from 'vs/workbench/browser/editor'; +import { TextFileEditor } from 'vs/workbench/contrib/files/browser/editors/textFileEditor'; +import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; +import { NodeTestBackupFileService } from 'vs/workbench/services/backup/test/electron-browser/backupFileService.test'; +import { dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { Schemas } from 'vs/base/common/network'; +import { isEqual } from 'vs/base/common/resources'; + +const userdataDir = getRandomTestPath(os.tmpdir(), 'vsctests', 'backuprestorer'); +const backupHome = path.join(userdataDir, 'Backups'); +const workspacesJsonPath = path.join(backupHome, 'workspaces.json'); + +const workspaceResource = URI.file(platform.isWindows ? 'c:\\workspace' : '/workspace'); +const workspaceBackupPath = path.join(backupHome, hashPath(workspaceResource)); +const fooFile = URI.file(platform.isWindows ? 'c:\\Foo' : '/Foo'); +const barFile = URI.file(platform.isWindows ? 'c:\\Bar' : '/Bar'); +const untitledFile1 = URI.from({ scheme: Schemas.untitled, path: 'Untitled-1' }); +const untitledFile2 = URI.from({ scheme: Schemas.untitled, path: 'Untitled-2' }); + +class TestBackupRestorer extends BackupRestorer { + async doRestoreBackups(): Promise { + return super.doRestoreBackups(); + } +} + +class ServiceAccessor { + constructor( + @ITextFileService public textFileService: TestTextFileService, + @IUntitledTextEditorService public untitledTextEditorService: IUntitledTextEditorService + ) { + } +} + +suite.skip('BackupModelRestorer', () => { // {{SQL CARBON EDIT}} TODO @anthonydresser these tests are failing due to tabColorMode, should investigate and fix + let accessor: ServiceAccessor; + + let disposables: IDisposable[] = []; + + setup(async () => { + disposables.push(Registry.as(EditorExtensions.Editors).registerEditor( + EditorDescriptor.create( + TextFileEditor, + TextFileEditor.ID, + 'Text File Editor' + ), + [new SyncDescriptor(FileEditorInput)] + )); + + // Delete any existing backups completely and then re-create it. + await pfs.rimraf(backupHome, pfs.RimRafMode.MOVE); + await pfs.mkdirp(backupHome); + + return pfs.writeFile(workspacesJsonPath, ''); + }); + + teardown(async () => { + dispose(disposables); + disposables = []; + + (accessor.textFileService.models).clear(); + (accessor.textFileService.models).dispose(); + accessor.untitledTextEditorService.revertAll(); + + return pfs.rimraf(backupHome, pfs.RimRafMode.MOVE); + }); + + test('Restore backups', async () => { + const backupFileService = new NodeTestBackupFileService(workspaceBackupPath); + const instantiationService = workbenchInstantiationService(); + instantiationService.stub(IBackupFileService, backupFileService); + + const part = instantiationService.createInstance(EditorPart); + part.create(document.createElement('div')); + part.layout(400, 300); + + instantiationService.stub(IEditorGroupsService, part); + + const editorService: EditorService = instantiationService.createInstance(EditorService); + instantiationService.stub(IEditorService, editorService); + + accessor = instantiationService.createInstance(ServiceAccessor); + + const tracker = instantiationService.createInstance(BackupModelTracker); + const restorer = instantiationService.createInstance(TestBackupRestorer); + + // Backup 2 normal files and 2 untitled file + await backupFileService.backupResource(untitledFile1, createTextBufferFactory('untitled-1').create(DefaultEndOfLine.LF).createSnapshot(false)); + await backupFileService.backupResource(untitledFile2, createTextBufferFactory('untitled-2').create(DefaultEndOfLine.LF).createSnapshot(false)); + await backupFileService.backupResource(fooFile, createTextBufferFactory('fooFile').create(DefaultEndOfLine.LF).createSnapshot(false)); + await backupFileService.backupResource(barFile, createTextBufferFactory('barFile').create(DefaultEndOfLine.LF).createSnapshot(false)); + + // Verify backups restored and opened as dirty + await restorer.doRestoreBackups(); + assert.equal(editorService.editors.length, 4); + assert.ok(editorService.editors.every(editor => editor.isDirty())); + + let counter = 0; + for (const editor of editorService.editors) { + const resource = editor.getResource(); + if (isEqual(resource, untitledFile1)) { + const model = await accessor.untitledTextEditorService.createOrGet(resource).resolve(); + assert.equal(model.textEditorModel.getValue(), 'untitled-1'); + counter++; + } else if (isEqual(resource, untitledFile2)) { + const model = await accessor.untitledTextEditorService.createOrGet(resource).resolve(); + assert.equal(model.textEditorModel.getValue(), 'untitled-2'); + counter++; + } else if (isEqual(resource, fooFile)) { + const model = await accessor.textFileService.models.get(resource!)?.load(); + assert.equal(model?.textEditorModel?.getValue(), 'fooFile'); + counter++; + } else { + const model = await accessor.textFileService.models.get(resource!)?.load(); + assert.equal(model?.textEditorModel?.getValue(), 'barFile'); + counter++; + } + } + + assert.equal(counter, 4); + + part.dispose(); + tracker.dispose(); + }); +}); diff --git a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.ts b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.ts index c784fbe82c..ccf5cd293d 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.ts +++ b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.ts @@ -45,7 +45,7 @@ export interface OutgoingCall { } export interface CallHierarchySession { - root: CallHierarchyItem; + roots: CallHierarchyItem[]; dispose(): void; } @@ -92,15 +92,19 @@ export class CallHierarchyModel { if (!session) { return undefined; } - return new CallHierarchyModel(session.root._sessionId, provider, session.root, new RefCountedDisposabled(session)); + return new CallHierarchyModel(session.roots.reduce((p, c) => p + c._sessionId, ''), provider, session.roots, new RefCountedDisposabled(session)); } + readonly root: CallHierarchyItem; + private constructor( readonly id: string, readonly provider: CallHierarchyProvider, - readonly root: CallHierarchyItem, + readonly roots: CallHierarchyItem[], readonly ref: RefCountedDisposabled, - ) { } + ) { + this.root = roots[0]; + } dispose(): void { this.ref.release(); @@ -110,7 +114,7 @@ export class CallHierarchyModel { const that = this; return new class extends CallHierarchyModel { constructor() { - super(that.id, that.provider, item, that.ref.acquire()); + super(that.id, that.provider, [item], that.ref.acquire()); } }; } diff --git a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyTree.ts b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyTree.ts index 7c53071e32..e42f81b3e7 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyTree.ts +++ b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyTree.ts @@ -43,7 +43,7 @@ export class DataSource implements IAsyncDataSource { async getChildren(element: CallHierarchyModel | Call): Promise { if (element instanceof CallHierarchyModel) { - return [new Call(element.root, undefined, element, undefined)]; + return element.roots.map(root => new Call(root, undefined, element, undefined)); } const { model, item } = element; diff --git a/src/vs/workbench/contrib/codeActions/common/configuration.ts b/src/vs/workbench/contrib/codeActions/common/configuration.ts index bf44f6606d..663de3dee4 100644 --- a/src/vs/workbench/contrib/codeActions/common/configuration.ts +++ b/src/vs/workbench/contrib/codeActions/common/configuration.ts @@ -34,7 +34,7 @@ const codeActionsOnSaveSchema: IConfigurationPropertySchema = { }, default: {}, description: nls.localize('codeActionsOnSave', "Code action kinds to be run on save."), - scope: ConfigurationScope.RESOURCE + scope: ConfigurationScope.RESOURCE_LANGUAGE, }; export const editorConfiguration = Object.freeze({ @@ -45,7 +45,7 @@ export const editorConfiguration = Object.freeze({ type: 'number', default: 750, description: nls.localize('codeActionsOnSaveTimeout', "Timeout in milliseconds after which the code actions that are run on save are cancelled."), - scope: ConfigurationScope.RESOURCE + scope: ConfigurationScope.RESOURCE_LANGUAGE, }, } }); diff --git a/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts b/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts index e84349ef3d..5408baeed9 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 { IResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; +import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; 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: IResourceConfigurationService, codeEditorService: ICodeEditorService): IWordWrapState { +function readWordWrapState(model: ITextModel, configurationService: ITextResourceConfigurationService, 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(IResourceConfigurationService); + const textResourceConfigurationService = accessor.get(ITextResourceConfigurationService); 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, - @IResourceConfigurationService readonly configurationService: IResourceConfigurationService, + @ITextResourceConfigurationService readonly configurationService: ITextResourceConfigurationService, @ICodeEditorService readonly codeEditorService: ICodeEditorService ) { super(); diff --git a/src/vs/workbench/contrib/customEditor/browser/commands.ts b/src/vs/workbench/contrib/customEditor/browser/commands.ts index 1749132453..20981ac84c 100644 --- a/src/vs/workbench/contrib/customEditor/browser/commands.ts +++ b/src/vs/workbench/contrib/customEditor/browser/commands.ts @@ -12,17 +12,17 @@ import * as nls from 'vs/nls'; import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkeys'; -import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { IListService } from 'vs/platform/list/browser/listService'; -import { IEditorCommandsContext, IEditorInput } from 'vs/workbench/common/editor'; +import { IEditorCommandsContext } from 'vs/workbench/common/editor'; +import { defaultEditorId } from 'vs/workbench/contrib/customEditor/browser/customEditors'; import { CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE, CONTEXT_HAS_CUSTOM_EDITORS, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; import { getMultiSelectedResources } from 'vs/workbench/contrib/files/browser/files'; +import { IExplorerService } from 'vs/workbench/contrib/files/common/files'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IExplorerService } from 'vs/workbench/contrib/files/common/files'; -import { defaultEditorId } from 'vs/workbench/contrib/customEditor/browser/customEditors'; -import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; +import { CustomFileEditorInput } from 'vs/workbench/contrib/customEditor/browser/customEditorInput'; const viewCategory = nls.localize('viewCategory', "View"); @@ -194,42 +194,32 @@ MenuRegistry.appendMenuItem(MenuId.CommandPalette, { const activeGroup = activeControl.group; const activeEditor = activeControl.input; - - if (!activeEditor) { - return; - } - const targetResource = activeEditor.getResource(); + if (!targetResource) { return; } const customEditorService = accessor.get(ICustomEditorService); - const activeCustomEditor = customEditorService.activeCustomEditor; let toggleView = defaultEditorId; - if (!activeCustomEditor) { - const viewIDs = customEditorService.getContributedCustomEditors(targetResource); - if (viewIDs && viewIDs.length) { - toggleView = viewIDs[0].id; - } - else { + if (!(activeEditor instanceof CustomFileEditorInput)) { + const bestAvailableEditor = customEditorService.getContributedCustomEditors(targetResource).bestAvailableEditor; + if (bestAvailableEditor) { + toggleView = bestAvailableEditor.id; + } else { return; } } - let replInput: IEditorInput; - if (toggleView === defaultEditorId) { - const instantiationService = accessor.get(IInstantiationService); - replInput = instantiationService.createInstance(FileEditorInput, targetResource, undefined, undefined); - } - else { - replInput = customEditorService.createInput(targetResource, toggleView, activeGroup); - } + const newEditorInput = customEditorService.createInput(targetResource, toggleView, activeGroup); editorService.replaceEditors([{ editor: activeEditor, - replacement: replInput, + replacement: newEditorInput, + options: { + ignoreOverrides: true, + } }], activeGroup); } }).register(); diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts index cc27d7158a..9c48da32c9 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts @@ -143,7 +143,9 @@ export class CustomFileEditorInput extends LazilyResolvedWebviewEditorInput { public async resolve(): Promise { this._model = await this.customEditorService.models.loadOrCreate(this.getResource(), this.viewType); this._register(this._model.onDidChangeDirty(() => this._onDidChangeDirty.fire())); - this._onDidChangeDirty.fire(); + if (this.isDirty()) { + this._onDidChangeDirty.fire(); + } return await super.resolve(); } diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts index a1adcf99c9..3db48bc88d 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { coalesce, distinct, find, mergeSort } from 'vs/base/common/arrays'; +import { coalesce } from 'vs/base/common/arrays'; import { Lazy } from 'vs/base/common/lazy'; import { Disposable } from 'vs/base/common/lifecycle'; import { basename, isEqual } from 'vs/base/common/resources'; @@ -21,7 +21,7 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { EditorInput, EditorOptions, IEditor, IEditorInput } from 'vs/workbench/common/editor'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { webviewEditorsExtensionPoint } from 'vs/workbench/contrib/customEditor/browser/extensionPoint'; -import { CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE, CONTEXT_HAS_CUSTOM_EDITORS, CustomEditorInfo, CustomEditorPriority, CustomEditorSelector, ICustomEditor, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; +import { CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE, CONTEXT_HAS_CUSTOM_EDITORS, CustomEditorInfo, CustomEditorInfoCollection, CustomEditorPriority, CustomEditorSelector, ICustomEditor, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; import { CustomEditorModelManager } from 'vs/workbench/contrib/customEditor/common/customEditorModelManager'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; import { IWebviewService, webviewHasOwnEditFunctionsContext } from 'vs/workbench/contrib/webview/browser/webview'; @@ -130,15 +130,16 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ return this._editorInfoStore.get(viewType); } - public getContributedCustomEditors(resource: URI): readonly CustomEditorInfo[] { - return this._editorInfoStore.getContributedEditors(resource); + public getContributedCustomEditors(resource: URI): CustomEditorInfoCollection { + return new CustomEditorInfoCollection(this._editorInfoStore.getContributedEditors(resource)); } - public getUserConfiguredCustomEditors(resource: URI): readonly CustomEditorInfo[] { + public getUserConfiguredCustomEditors(resource: URI): CustomEditorInfoCollection { const rawAssociations = this.configurationService.getValue(customEditorsAssociationsKey) || []; - return coalesce(rawAssociations - .filter(association => CustomEditorInfo.selectorMatches(association, resource)) - .map(association => this._editorInfoStore.get(association.viewType))); + return new CustomEditorInfoCollection( + coalesce(rawAssociations + .filter(association => CustomEditorInfo.selectorMatches(association, resource)) + .map(association => this._editorInfoStore.get(association.viewType)))); } public async promptOpenWith( @@ -146,21 +147,21 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ options?: ITextEditorOptions, group?: IEditorGroup, ): Promise { - const customEditors = distinct([ + const customEditors = new CustomEditorInfoCollection([ defaultEditorInfo, - ...this.getUserConfiguredCustomEditors(resource), - ...this.getContributedCustomEditors(resource), - ], editor => editor.id); + ...this.getUserConfiguredCustomEditors(resource).allEditors, + ...this.getContributedCustomEditors(resource).allEditors, + ]); let currentlyOpenedEditorType: undefined | string; for (const editor of group ? group.editors : []) { - if (editor.getResource() && isEqual(editor.getResource()!, resource)) { + if (editor.getResource() && isEqual(editor.getResource(), resource)) { currentlyOpenedEditorType = editor instanceof CustomFileEditorInput ? editor.viewType : defaultEditorId; break; } } - const items = customEditors.map((editorDescriptor): IQuickPickItem => ({ + const items = customEditors.allEditors.map((editorDescriptor): IQuickPickItem => ({ label: editorDescriptor.displayName, id: editorDescriptor.id, description: editorDescriptor.id === currentlyOpenedEditorType @@ -201,7 +202,11 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ viewType: string, group: IEditorGroup | undefined, options?: { readonly customClasses: string; }, - ): CustomFileEditorInput { + ): EditorInput { + if (viewType === defaultEditorId) { + return this.instantiationService.createInstance(FileEditorInput, resource, undefined, undefined); + } + const id = generateUuid(); const webview = new Lazy(() => { return this.webviewService.createWebviewEditorOverlay(id, { customClasses: options?.customClasses }, {}); @@ -220,7 +225,7 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ group?: IEditorGroup ): Promise { if (group) { - const existingEditors = group.editors.filter(editor => editor.getResource() && isEqual(editor.getResource()!, resource)); + const existingEditors = group.editors.filter(editor => editor.getResource() && isEqual(editor.getResource(), resource)); if (existingEditors.length) { const existing = existingEditors[0]; if (!input.matches(existing)) { @@ -250,8 +255,8 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ } const possibleEditors = [ - ...this.getContributedCustomEditors(resource), - ...this.getUserConfiguredCustomEditors(resource), + ...this.getContributedCustomEditors(resource).allEditors, + ...this.getUserConfiguredCustomEditors(resource).allEditors, ]; this._hasCustomEditor.set(possibleEditors.length > 0); this._focusedCustomEditorIsEditable.set(activeControl?.input instanceof CustomFileEditorInput); @@ -302,7 +307,7 @@ export class CustomEditorContribution implements IWorkbenchContribution { const userConfiguredEditors = this.customEditorService.getUserConfiguredCustomEditors(resource); if (userConfiguredEditors.length) { return { - override: this.customEditorService.openWith(resource, userConfiguredEditors[0].id, options, group), + override: this.customEditorService.openWith(resource, userConfiguredEditors.allEditors[0].id, options, group), }; } @@ -311,15 +316,7 @@ export class CustomEditorContribution implements IWorkbenchContribution { return undefined; // {{SQL CARBON EDIT}} Strict-null-checks } - // Find the single default editor to use (if any) by looking at the editor's priority and the - // other contributed editors. - const defaultEditor = find(contributedEditors, editor => { - if (editor.priority !== CustomEditorPriority.default && editor.priority !== CustomEditorPriority.builtin) { - return false; - } - return contributedEditors.every(otherEditor => - otherEditor === editor || isLowerPriority(otherEditor, editor)); - }); + const defaultEditor = contributedEditors.defaultEditor; if (defaultEditor) { return { override: this.customEditorService.openWith(resource, defaultEditor.id, options, group), @@ -327,7 +324,7 @@ export class CustomEditorContribution implements IWorkbenchContribution { } // If we have all optional editors, then open VS Code's standard editor - if (contributedEditors.every(editor => editor.priority === CustomEditorPriority.option)) { + if (contributedEditors.allEditors.every(editor => editor.priority === CustomEditorPriority.option)) { return undefined; // {{SQL CARBON EDIT}} strict-null-check } @@ -367,20 +364,17 @@ export class CustomEditorContribution implements IWorkbenchContribution { } // Prefer default editors in the diff editor case but ultimatly always take the first editor - const editors = mergeSort( - distinct([ - ...this.customEditorService.getUserConfiguredCustomEditors(resource), - ...this.customEditorService.getContributedCustomEditors(resource).filter(x => x.priority !== CustomEditorPriority.option), - ], editor => editor.id), - (a, b) => { - return priorityToRank(a.priority) - priorityToRank(b.priority); - }); + const allEditors = new CustomEditorInfoCollection([ + ...this.customEditorService.getUserConfiguredCustomEditors(resource).allEditors, + ...this.customEditorService.getContributedCustomEditors(resource).allEditors.filter(x => x.priority !== CustomEditorPriority.option), + ]); - if (!editors.length) { + const bestAvailableEditor = allEditors.bestAvailableEditor; + if (!bestAvailableEditor) { return undefined; } - return this.customEditorService.createInput(resource, editors[0].id, group, { customClasses }); + return this.customEditorService.createInput(resource, bestAvailableEditor.id, group, { customClasses }); }; const modifiedOverride = getCustomEditorOverrideForSubInput(editor.modifiedInput, 'modified'); @@ -399,18 +393,6 @@ export class CustomEditorContribution implements IWorkbenchContribution { } } -function isLowerPriority(otherEditor: CustomEditorInfo, editor: CustomEditorInfo): unknown { - return priorityToRank(otherEditor.priority) < priorityToRank(editor.priority); -} - -function priorityToRank(priority: CustomEditorPriority): number { - switch (priority) { - case CustomEditorPriority.default: return 3; - case CustomEditorPriority.builtin: return 2; - case CustomEditorPriority.option: return 1; - } -} - registerThemingParticipant((theme, collector) => { const shadow = theme.getColor(colorRegistry.scrollbarShadow); if (shadow) { diff --git a/src/vs/workbench/contrib/customEditor/common/customEditor.ts b/src/vs/workbench/contrib/customEditor/common/customEditor.ts index deb8d01589..4b9b7a4c34 100644 --- a/src/vs/workbench/contrib/customEditor/common/customEditor.ts +++ b/src/vs/workbench/contrib/customEditor/common/customEditor.ts @@ -3,6 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { distinct, find, mergeSort } from 'vs/base/common/arrays'; import { Event } from 'vs/base/common/event'; import * as glob from 'vs/base/common/glob'; import { basename } from 'vs/base/common/resources'; @@ -32,8 +33,8 @@ export interface ICustomEditorService { readonly activeCustomEditor: ICustomEditor | undefined; getCustomEditor(viewType: string): CustomEditorInfo | undefined; - getContributedCustomEditors(resource: URI): readonly CustomEditorInfo[]; - getUserConfiguredCustomEditors(resource: URI): readonly CustomEditorInfo[]; + getContributedCustomEditors(resource: URI): CustomEditorInfoCollection; + getUserConfiguredCustomEditors(resource: URI): CustomEditorInfoCollection; createInput(resource: URI, viewType: string, group: IEditorGroup | undefined, options?: { readonly customClasses: string }): EditorInput; @@ -77,7 +78,7 @@ export interface ICustomEditorModel extends IWorkingCopy { save(options?: ISaveOptions): Promise; saveAs(resource: URI, targetResource: URI, currentOptions?: ISaveOptions): Promise; - makeEdit(edit: CustomEditorEdit): void; + pushEdit(edit: CustomEditorEdit): void; } export const enum CustomEditorPriority { @@ -122,3 +123,60 @@ export class CustomEditorInfo { return false; } } + +export class CustomEditorInfoCollection { + + public readonly allEditors: readonly CustomEditorInfo[]; + + constructor( + editors: readonly CustomEditorInfo[], + ) { + this.allEditors = distinct(editors, editor => editor.id); + } + + public get length(): number { return this.allEditors.length; } + + /** + * Find the single default editor to use (if any) by looking at the editor's priority and the + * other contributed editors. + */ + public get defaultEditor(): CustomEditorInfo | undefined { + return find(this.allEditors, editor => { + switch (editor.priority) { + case CustomEditorPriority.default: + case CustomEditorPriority.builtin: + // A default editor must have higher priority than all other contributed editors. + return this.allEditors.every(otherEditor => + otherEditor === editor || isLowerPriority(otherEditor, editor)); + + default: + return false; + } + }); + } + + /** + * Find the best available editor to use. + * + * Unlike the `defaultEditor`, a bestAvailableEditor can exist even if there are other editors with + * the same priority. + */ + public get bestAvailableEditor(): CustomEditorInfo | undefined { + const editors = mergeSort(Array.from(this.allEditors), (a, b) => { + return priorityToRank(a.priority) - priorityToRank(b.priority); + }); + return editors[0]; + } +} + +function isLowerPriority(otherEditor: CustomEditorInfo, editor: CustomEditorInfo): unknown { + return priorityToRank(otherEditor.priority) < priorityToRank(editor.priority); +} + +function priorityToRank(priority: CustomEditorPriority): number { + switch (priority) { + case CustomEditorPriority.default: return 3; + case CustomEditorPriority.builtin: return 2; + case CustomEditorPriority.option: return 1; + } +} diff --git a/src/vs/workbench/contrib/customEditor/common/customEditorModel.ts b/src/vs/workbench/contrib/customEditor/common/customEditorModel.ts index d4c2f2402c..a6a8256411 100644 --- a/src/vs/workbench/contrib/customEditor/common/customEditorModel.ts +++ b/src/vs/workbench/contrib/customEditor/common/customEditorModel.ts @@ -57,7 +57,7 @@ export class CustomEditorModel extends Disposable implements ICustomEditorModel return this._edits.slice(0, Math.max(0, this._currentEditIndex + 1)); } - public makeEdit(edit: CustomEditorEdit): void { + public pushEdit(edit: CustomEditorEdit): void { this._edits.splice(this._currentEditIndex + 1, this._edits.length - this._currentEditIndex, edit.data); this._currentEditIndex = this._edits.length - 1; this.updateDirty(); diff --git a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts index ca0e02091e..08e954e2f5 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts @@ -95,11 +95,12 @@ function getBreakpointDecorationOptions(model: ITextModel, breakpoint: IBreakpoi overviewRulerDecoration = null; } + const renderInline = breakpoint.column && (breakpoint.column > model.getLineFirstNonWhitespaceColumn(breakpoint.lineNumber)); return { glyphMarginClassName: `${className}`, glyphMarginHoverMessage, stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, - beforeContentClassName: breakpoint.column ? `debug-breakpoint-placeholder` : undefined, + beforeContentClassName: renderInline ? `debug-breakpoint-placeholder` : undefined, overviewRuler: overviewRulerDecoration }; } @@ -115,9 +116,10 @@ async function createCandidateDecorations(model: ITextModel, breakpointDecoratio if (positions.length > 1) { // Do not render candidates if there is only one, since it is already covered by the line breakpoint const firstColumn = model.getLineFirstNonWhitespaceColumn(lineNumber); + const lastColumn = model.getLineLastNonWhitespaceColumn(lineNumber); positions.forEach(p => { const range = new Range(p.lineNumber, p.column, p.lineNumber, p.column + 1); - if (p.column <= firstColumn) { + if (p.column <= firstColumn || p.column > lastColumn) { // Do not render candidates on the start of the line. return; } @@ -131,7 +133,7 @@ async function createCandidateDecorations(model: ITextModel, breakpointDecoratio range, options: { stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, - beforeContentClassName: `debug-breakpoint-placeholder` + beforeContentClassName: breakpointAtPosition ? undefined : `debug-breakpoint-placeholder` }, breakpoint: breakpointAtPosition ? breakpointAtPosition.breakpoint : undefined }); @@ -416,7 +418,7 @@ class BreakpointEditorContribution implements IBreakpointEditorContribution { this.breakpointDecorations = decorationIds.map((decorationId, index) => { let inlineWidget: InlineBreakpointWidget | undefined = undefined; const breakpoint = breakpoints[index]; - if (breakpoint.column) { + if (desiredBreakpointDecorations[index].options.beforeContentClassName) { const contextMenuActions = () => this.getContextMenuActions([breakpoint], activeCodeEditor.getModel().uri, breakpoint.lineNumber, breakpoint.column); inlineWidget = new InlineBreakpointWidget(activeCodeEditor, decorationId, desiredBreakpointDecorations[index].options.glyphMarginClassName, breakpoint, this.debugService, this.contextMenuService, contextMenuActions); } @@ -592,7 +594,7 @@ class InlineBreakpointWidget implements IContentWidget, IDisposable { const lineHeight = this.editor.getOption(EditorOption.lineHeight); this.domNode.style.height = `${lineHeight}px`; this.domNode.style.width = `${Math.ceil(0.8 * lineHeight)}px`; - this.domNode.style.marginLeft = `${Math.ceil(0.35 * lineHeight)}px`; + this.domNode.style.marginLeft = `4px`; }; updateSize(); diff --git a/src/vs/workbench/contrib/debug/browser/callStackView.ts b/src/vs/workbench/contrib/debug/browser/callStackView.ts index 48944a053b..26785fa8b5 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackView.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackView.ts @@ -135,7 +135,7 @@ export class CallStackView extends ViewPane { this.dataSource = new CallStackDataSource(this.debugService); this.tree = this.instantiationService.createInstance>(WorkbenchAsyncDataTree, 'CallStackView', treeContainer, new CallStackDelegate(), [ - new SessionsRenderer(this.instantiationService), + new SessionsRenderer(this.instantiationService, this.debugService), new ThreadsRenderer(this.instantiationService), this.instantiationService.createInstance(StackFramesRenderer), new ErrorsRenderer(), @@ -404,7 +404,8 @@ class SessionsRenderer implements ITreeRenderer t.stopped).pop(); + const thread = session.getAllThreads().filter(t => t.stopped).pop(); data.actionBar.clear(); const actions = getActions(this.instantiationService, element.element); data.actionBar.push(actions, { icon: true, label: false }); + data.stateLabel.hidden = false; - data.stateLabel.textContent = stoppedThread ? nls.localize('paused', "Paused") - : nls.localize({ key: 'running', comment: ['indicates state'] }, "Running"); + if (thread && thread.stoppedDetails) { + data.stateLabel.textContent = thread.stoppedDetails.description || nls.localize('debugStopped', "Paused on {0}", thread.stoppedDetails.reason || ''); + } else { + const hasChildSessions = this.debugService.getModel().getSessions().filter(s => s.parentSession === session).length > 0; + if (!hasChildSessions) { + data.stateLabel.textContent = nls.localize({ key: 'running', comment: ['indicates state'] }, "Running"); + } else { + data.stateLabel.hidden = true; + } + } } disposeTemplate(templateData: ISessionTemplateData): void { diff --git a/src/vs/workbench/contrib/debug/browser/debugCommands.ts b/src/vs/workbench/contrib/debug/browser/debugCommands.ts index d080256948..d5af7dbd48 100644 --- a/src/vs/workbench/contrib/debug/browser/debugCommands.ts +++ b/src/vs/workbench/contrib/debug/browser/debugCommands.ts @@ -25,7 +25,7 @@ import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { PanelFocusContext } from 'vs/workbench/common/panel'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { onUnexpectedError } from 'vs/base/common/errors'; -import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; +import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; diff --git a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts index ec3aee686b..bfe49898e3 100644 --- a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts +++ b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts @@ -551,7 +551,7 @@ class Launch extends AbstractLaunch implements ILaunch { } protected getConfig(): IGlobalConfig | undefined { - return this.configurationService.inspect('launch', { resource: this.workspace.uri }).workspaceFolder; + return this.configurationService.inspect('launch', { resource: this.workspace.uri }).workspaceFolderValue; } async openConfigFile(sideBySide: boolean, preserveFocus: boolean, type?: string, token?: CancellationToken): Promise<{ editor: IEditor | null, created: boolean }> { @@ -631,7 +631,7 @@ class WorkspaceLaunch extends AbstractLaunch implements ILaunch { } protected getConfig(): IGlobalConfig | undefined { - return this.configurationService.inspect('launch').workspace; + return this.configurationService.inspect('launch').workspaceValue; } async openConfigFile(sideBySide: boolean, preserveFocus: boolean): Promise<{ editor: IEditor | null, created: boolean }> { @@ -674,7 +674,7 @@ class UserLaunch extends AbstractLaunch implements ILaunch { } protected getConfig(): IGlobalConfig | undefined { - return this.configurationService.inspect('launch').user; + return this.configurationService.inspect('launch').userValue; } async openConfigFile(_: boolean, preserveFocus: boolean): Promise<{ editor: IEditor | null, created: boolean }> { diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts index 771642b544..27024734c8 100644 --- a/src/vs/workbench/contrib/debug/browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugService.ts @@ -158,13 +158,12 @@ 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)(() => { + this.toDispose.push(this.model.onDidChangeCallStack(() => { const numberOfSessions = this.model.getSessions().length; - if (numberOfSessions === 0) { - if (this.activity) { - this.activity.dispose(); - } - } else { + if (this.activity) { + this.activity.dispose(); + } + if (numberOfSessions > 0) { 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))); } })); 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 7b04097b23..9abd27738d 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css +++ b/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css @@ -16,6 +16,10 @@ align-items: center; } +.inline-breakpoint-widget.codicon-debug-breakpoint-disabled { + opacity: 0.7; +} + .codicon-debug-breakpoint.codicon-debug-stackframe-focused::after, .codicon-debug-breakpoint.codicon-debug-stackframe::after { content: '\eb8a'; @@ -32,8 +36,6 @@ width: 0.9em; 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 */ } @@ -52,14 +54,6 @@ 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: ''; - margin-right: 0px; - margin-left: 0px; -} - .monaco-editor .inline-breakpoint-widget { cursor: pointer; } diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index acd13bf6b1..f3eb2ae5dd 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -58,7 +58,7 @@ import { IContextMenuService, IContextViewService } from 'vs/platform/contextvie import { removeAnsiEscapeCodes } from 'vs/base/common/strings'; import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; +import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { RunOnceScheduler } from 'vs/base/common/async'; import { FuzzyScore, createMatches } from 'vs/base/common/filters'; import { HighlightedLabel, IHighlight } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; diff --git a/src/vs/workbench/contrib/debug/browser/startView.ts b/src/vs/workbench/contrib/debug/browser/startView.ts index 7f7b716220..f126f344d1 100644 --- a/src/vs/workbench/contrib/debug/browser/startView.ts +++ b/src/vs/workbench/contrib/debug/browser/startView.ts @@ -64,8 +64,19 @@ export class StartView extends ViewPane { this.debugButton.enabled = enabled; this.runButton.enabled = enabled; - this.debugButton.label = this.debuggerLabels.length !== 1 ? localize('debug', "Debug") : localize('debugWith', "Debug with {0}", this.debuggerLabels[0]); - this.runButton.label = this.debuggerLabels.length !== 1 ? localize('run', "Run") : localize('runWith', "Run with {0}", this.debuggerLabels[0]); + const debugKeybinding = this.keybindingService.lookupKeybinding(StartAction.ID); + let debugLabel = this.debuggerLabels.length !== 1 ? localize('debug', "Debug") : localize('debugWith', "Debug with {0}", this.debuggerLabels[0]); + if (debugKeybinding) { + debugLabel += ` (${debugKeybinding.getLabel()})`; + } + this.debugButton.label = debugLabel; + + let runLabel = this.debuggerLabels.length !== 1 ? localize('run', "Run") : localize('runWith', "Run with {0}", this.debuggerLabels[0]); + const runKeybinding = this.keybindingService.lookupKeybinding(RunAction.ID); + if (runKeybinding) { + runLabel += ` (${runKeybinding.getLabel()})`; + } + this.runButton.label = runLabel; const emptyWorkbench = this.workspaceContextService.getWorkbenchState() === WorkbenchState.EMPTY; this.firstMessageContainer.innerHTML = ''; @@ -74,12 +85,12 @@ export class StartView extends ViewPane { this.secondMessageContainer.appendChild(secondMessageElement); const setSecondMessage = () => { - secondMessageElement.textContent = localize('specifyHowToRun', "To futher configure Debug and Run"); + secondMessageElement.textContent = localize('specifyHowToRun', "To further configure Debug and Run"); this.clickElement = this.createClickElement(localize('configure', " create a launch.json file."), () => this.commandService.executeCommand(ConfigureAction.ID)); this.secondMessageContainer.appendChild(this.clickElement); }; const setSecondMessageWithFolder = () => { - secondMessageElement.textContent = localize('noLaunchConfiguration', "To futher configure Debug and Run, "); + secondMessageElement.textContent = localize('noLaunchConfiguration', "To further configure Debug and Run, "); this.clickElement = this.createClickElement(localize('openFolder', " open a folder"), () => this.dialogService.pickFolderAndOpen({ forceNewWindow: false })); this.secondMessageContainer.appendChild(this.clickElement); @@ -111,7 +122,6 @@ export class StartView extends ViewPane { this.firstMessageContainer.appendChild(firstMessageElement); firstMessageElement.textContent = localize('canBeDebuggedOrRun', " which can be debugged or run."); - setSecondMessageWithFolder(); } } diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index 2ac872b337..e57986db8a 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -24,11 +24,11 @@ import { TaskIdentifier } from 'vs/workbench/contrib/tasks/common/tasks'; import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { Extensions as ViewContainerExtensions, IViewContainersRegistry, ViewContainer } from 'vs/workbench/common/views'; +import { Extensions as ViewContainerExtensions, IViewContainersRegistry, ViewContainer, ViewContainerLocation } from 'vs/workbench/common/views'; import { Registry } from 'vs/platform/registry/common/platform'; export const VIEWLET_ID = 'workbench.view.debug'; -export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(VIEWLET_ID); +export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(VIEWLET_ID, ViewContainerLocation.Sidebar); export const VARIABLES_VIEW_ID = 'workbench.debug.variablesView'; export const WATCH_VIEW_ID = 'workbench.debug.watchExpressionsView'; diff --git a/src/vs/workbench/contrib/debug/common/debugger.ts b/src/vs/workbench/contrib/debug/common/debugger.ts index 501923a17a..8d44cf3b44 100644 --- a/src/vs/workbench/contrib/debug/common/debugger.ts +++ b/src/vs/workbench/contrib/debug/common/debugger.ts @@ -17,7 +17,7 @@ import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService' import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { memoize } from 'vs/base/common/decorators'; import { TaskDefinitionRegistry } from 'vs/workbench/contrib/tasks/common/taskDefinitionRegistry'; -import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; +import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; import { isDebuggerMainContribution } from 'vs/workbench/contrib/debug/common/debugUtils'; diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index 255ad72696..d3bdf653e3 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -1399,7 +1399,7 @@ export class SetColorThemeAction extends ExtensionAction { ignoreFocusLost }); let confValue = this.configurationService.inspect(COLOR_THEME_SETTING); - const target = typeof confValue.workspace !== 'undefined' ? ConfigurationTarget.WORKSPACE : ConfigurationTarget.USER; + const target = typeof confValue.workspaceValue !== 'undefined' ? ConfigurationTarget.WORKSPACE : ConfigurationTarget.USER; return this.workbenchThemeService.setColorTheme(pickedTheme ? pickedTheme.id : currentTheme.id, target); } } @@ -1465,7 +1465,7 @@ export class SetFileIconThemeAction extends ExtensionAction { ignoreFocusLost }); let confValue = this.configurationService.inspect(ICON_THEME_SETTING); - const target = typeof confValue.workspace !== 'undefined' ? ConfigurationTarget.WORKSPACE : ConfigurationTarget.USER; + const target = typeof confValue.workspaceValue !== 'undefined' ? ConfigurationTarget.WORKSPACE : ConfigurationTarget.USER; return this.workbenchThemeService.setFileIconTheme(pickedTheme ? pickedTheme.id : currentTheme.id, target); } } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts index 8f78e1a28a..a6cc2f38af 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts @@ -49,6 +49,8 @@ import { SeverityIcon } from 'vs/platform/severityIcon/common/severityIcon'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; +// Extensions that are automatically classified as Programming Language extensions, but should be Feature extensions +const FORCE_FEATURE_EXTENSIONS = ['vscode.git', 'vscode.search-result']; class ExtensionsViewState extends Disposable implements IExtensionsViewState { @@ -312,7 +314,7 @@ export class ExtensionsListView extends ViewPane { && e.local.manifest.contributes && Array.isArray(e.local.manifest.contributes.grammars) && e.local.manifest.contributes.grammars.length - && e.local.identifier.id !== 'vscode.git'; + && FORCE_FEATURE_EXTENSIONS.indexOf(e.local.identifier.id) === -1; }); return this.getPagedModel(this.sortExtensions(basics, options)); } @@ -321,7 +323,7 @@ export class ExtensionsListView extends ViewPane { return e.local && e.local.manifest && e.local.manifest.contributes - && (!Array.isArray(e.local.manifest.contributes.grammars) || e.local.identifier.id === 'vscode.git') + && (!Array.isArray(e.local.manifest.contributes.grammars) || FORCE_FEATURE_EXTENSIONS.indexOf(e.local.identifier.id) !== -1) && !Array.isArray(e.local.manifest.contributes.themes); }); return this.getPagedModel(this.sortExtensions(others, options)); diff --git a/src/vs/workbench/contrib/extensions/common/extensions.ts b/src/vs/workbench/contrib/extensions/common/extensions.ts index 92d360ae15..d47c79679d 100644 --- a/src/vs/workbench/contrib/extensions/common/extensions.ts +++ b/src/vs/workbench/contrib/extensions/common/extensions.ts @@ -14,11 +14,11 @@ import { areSameExtensions } from 'vs/platform/extensionManagement/common/extens import { IExtensionManifest, ExtensionType } from 'vs/platform/extensions/common/extensions'; import { URI } from 'vs/base/common/uri'; import { IViewPaneContainer } from 'vs/workbench/common/viewPaneContainer'; -import { Extensions as ViewContainerExtensions, ViewContainer, IViewContainersRegistry } from 'vs/workbench/common/views'; +import { Extensions as ViewContainerExtensions, ViewContainer, IViewContainersRegistry, ViewContainerLocation } from 'vs/workbench/common/views'; import { Registry } from 'vs/platform/registry/common/platform'; export const VIEWLET_ID = 'workbench.view.extensions'; -export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(VIEWLET_ID); +export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(VIEWLET_ID, ViewContainerLocation.Sidebar); export const EXTENSIONS_CONFIG = '.azuredatastudio/extensions.json'; diff --git a/src/vs/workbench/contrib/extensions/common/extensionsInput.ts b/src/vs/workbench/contrib/extensions/common/extensionsInput.ts index 6b29ab787f..9dedc39b68 100644 --- a/src/vs/workbench/contrib/extensions/common/extensionsInput.ts +++ b/src/vs/workbench/contrib/extensions/common/extensionsInput.ts @@ -28,6 +28,10 @@ export class ExtensionsInput extends EditorInput { } matches(other: unknown): boolean { + if (super.matches(other) === true) { + return true; + } + if (!(other instanceof ExtensionsInput)) { return false; } @@ -52,4 +56,4 @@ export class ExtensionsInput extends EditorInput { path: this.extension.identifier.id }); } -} \ No newline at end of file +} diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts index f6436341e1..0b71fdf9a4 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts @@ -44,6 +44,9 @@ Registry.as(EditorExtensions.Editors) .registerEditor(runtimeExtensionsEditorDescriptor, [new SyncDescriptor(RuntimeExtensionsInput)]); class RuntimeExtensionsInputFactory implements IEditorInputFactory { + canSerialize(editorInput: EditorInput): boolean { + return true; + } serialize(editorInput: EditorInput): string { return ''; } diff --git a/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts b/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts index dc0b2f1aab..a266eabd8b 100644 --- a/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts +++ b/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts @@ -5,7 +5,6 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { URI } from 'vs/base/common/uri'; -import * as resources from 'vs/base/common/resources'; import { IEditorViewState } from 'vs/editor/common/editorCommon'; import { toResource, SideBySideEditorInput, IWorkbenchEditorConfiguration, SideBySideEditor as SideBySideEditorChoice } from 'vs/workbench/common/editor'; import { ITextFileService, TextFileModelChangeEvent, ModelState } from 'vs/workbench/services/textfile/common/textfiles'; @@ -25,6 +24,8 @@ import { IEditorGroupsService, IEditorGroup } from 'vs/workbench/services/editor import { timeout } from 'vs/base/common/async'; import { withNullAsUndefined } from 'vs/base/common/types'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import { isEqualOrParent, joinPath } from 'vs/base/common/resources'; +import { IExplorerService } from 'vs/workbench/contrib/files/common/files'; // {{SQL CARBON EDIT}} import { QueryEditorInput } from 'sql/workbench/contrib/query/common/queryEditorInput'; @@ -45,7 +46,8 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut @IConfigurationService private readonly configurationService: IConfigurationService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IHostService private readonly hostService: IHostService, - @ICodeEditorService private readonly codeEditorService: ICodeEditorService + @ICodeEditorService private readonly codeEditorService: ICodeEditorService, + @IExplorerService private readonly explorerService: IExplorerService ) { super(); @@ -104,22 +106,19 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut // Update Editor if file (or any parent of the input) got renamed or moved const resource = editor.getResource(); - if (resources.isEqualOrParent(resource, oldResource)) { + if (isEqualOrParent(resource, oldResource)) { let reopenFileResource: URI; if (oldResource.toString() === resource.toString()) { reopenFileResource = newResource; // file got moved } else { - const index = this.getIndexOfPath(resource.path, oldResource.path, resources.hasToIgnoreCase(resource)); - reopenFileResource = resources.joinPath(newResource, resource.path.substr(index + oldResource.path.length + 1)); // parent folder got moved + const index = this.getIndexOfPath(resource.path, oldResource.path, this.explorerService.shouldIgnoreCase(resource)); + reopenFileResource = joinPath(newResource, resource.path.substr(index + oldResource.path.length + 1)); // parent folder got moved } let encoding: string | undefined = undefined; - let mode: string | undefined = undefined; - const model = this.textFileService.models.get(resource); if (model) { encoding = model.getEncoding(); - mode = model.textEditorModel?.getModeId(); } this.editorService.replaceEditors([{ @@ -127,7 +126,6 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut replacement: { resource: reopenFileResource, encoding, - mode, options: { preserveFocus: true, pinned: group.isPinned(editor), @@ -204,7 +202,7 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut // Do NOT close any opened editor that matches the resource path (either equal or being parent) of the // resource we move to (movedTo). Otherwise we would close a resource that has been renamed to the same // path but different casing. - if (movedTo && resources.isEqualOrParent(resource, movedTo)) { + if (movedTo && isEqualOrParent(resource, movedTo)) { return; } @@ -212,7 +210,7 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut if (arg1 instanceof FileChangesEvent) { matches = arg1.contains(resource, FileChangeType.DELETED); } else { - matches = resources.isEqualOrParent(resource, arg1); + matches = isEqualOrParent(resource, arg1); } if (!matches) { diff --git a/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts b/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts index 8c9942d396..3340cd04f8 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 { IResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; +import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; 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, - @IResourceConfigurationService configurationService: IResourceConfigurationService, + @ITextResourceConfigurationService configurationService: ITextResourceConfigurationService, @IEditorService editorService: IEditorService, @IThemeService themeService: IThemeService, @IEditorGroupsService editorGroupService: IEditorGroupsService, diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index 4b054af598..74df5f20dc 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -7,7 +7,7 @@ import 'vs/css!./media/fileactions'; import * as nls from 'vs/nls'; import { isWindows, isWeb } from 'vs/base/common/platform'; import * as extpath from 'vs/base/common/extpath'; -import { extname, basename } from 'vs/base/common/path'; +import { extname, basename, posix, win32 } from 'vs/base/common/path'; import * as resources from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { toErrorMessage } from 'vs/base/common/errorMessage'; @@ -45,6 +45,7 @@ import { asDomUri, triggerDownload } from 'vs/base/browser/dom'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { IWorkingCopyService, IWorkingCopy } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { ILabelService } from 'vs/platform/label/common/label'; export const NEW_FILE_COMMAND_ID = 'explorer.newFile'; export const NEW_FILE_LABEL = nls.localize('newFile', "New File"); @@ -349,12 +350,12 @@ function containsBothDirectoryAndFile(distinctElements: ExplorerItem[]): boolean } -export function findValidPasteFileTarget(targetFolder: ExplorerItem, fileToPaste: { resource: URI, isDirectory?: boolean, allowOverwrite: boolean }, incrementalNaming: 'simple' | 'smart'): URI { +export function findValidPasteFileTarget(explorerService: IExplorerService, targetFolder: ExplorerItem, fileToPaste: { resource: URI, isDirectory?: boolean, allowOverwrite: boolean }, incrementalNaming: 'simple' | 'smart'): URI { let name = resources.basenameOrAuthority(fileToPaste.resource); let candidate = resources.joinPath(targetFolder.resource, name); while (true && !fileToPaste.allowOverwrite) { - if (!targetFolder.root.find(candidate)) { + if (!explorerService.findClosest(candidate)) { break; } @@ -875,6 +876,7 @@ async function openExplorerAndCreate(accessor: ServicesAccessor, isFolder: boole const editorService = accessor.get(IEditorService); const viewletService = accessor.get(IViewletService); const notificationService = accessor.get(INotificationService); + const labelService = accessor.get(ILabelService); await viewletService.openViewlet(VIEWLET_ID, true); @@ -891,13 +893,16 @@ async function openExplorerAndCreate(accessor: ServicesAccessor, isFolder: boole throw new Error('Parent folder is readonly.'); } - const newStat = new NewExplorerItem(folder, isFolder); + const newStat = new NewExplorerItem(explorerService, folder, isFolder); await folder.fetchChildren(fileService, explorerService); folder.addChild(newStat); const onSuccess = (value: string): Promise => { - const createPromise = isFolder ? fileService.createFolder(resources.joinPath(folder.resource, value)) : textFileService.create(resources.joinPath(folder.resource, value)); + const separator = labelService.getSeparator(folder.resource.scheme); + const resource = folder.resource.with({ path: separator === '/' ? posix.join(folder.resource.path, value) : win32.join(folder.resource.path, value) }); + const createPromise = isFolder ? fileService.createFolder(resource) : textFileService.create(resource); + return createPromise.then(created => { refreshIfSeparator(value, explorerService); return isFolder ? explorerService.select(created.resource, true) @@ -1070,7 +1075,7 @@ export const pasteFileHandler = async (accessor: ServicesAccessor) => { } const incrementalNaming = configurationService.getValue().explorer.incrementalNaming; - const targetFile = findValidPasteFileTarget(target, { resource: fileToPaste, isDirectory: fileToPasteStat.isDirectory, allowOverwrite: pasteShouldMove }, incrementalNaming); + const targetFile = findValidPasteFileTarget(explorerService, target, { resource: fileToPaste, isDirectory: fileToPasteStat.isDirectory, allowOverwrite: pasteShouldMove }, incrementalNaming); // Move/Copy File if (pasteShouldMove) { diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts index 2ecd21d9a3..6486514eb1 100644 --- a/src/vs/workbench/contrib/files/browser/files.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts @@ -132,6 +132,10 @@ interface ISerializedFileInput { // Register Editor Input Factory class FileEditorInputFactory implements IEditorInputFactory { + canSerialize(editorInput: EditorInput): boolean { + return true; + } + serialize(editorInput: EditorInput): string { const fileEditorInput = editorInput; const resource = fileEditorInput.getResource(); @@ -241,25 +245,22 @@ configurationRegistry.registerConfiguration({ }, 'files.encoding': { 'type': 'string', - 'overridable': true, 'enum': Object.keys(SUPPORTED_ENCODINGS), 'default': 'utf8', 'description': nls.localize('encoding', "The default character set encoding to use when reading and writing files. This setting can also be configured per language."), - 'scope': ConfigurationScope.RESOURCE, + 'scope': ConfigurationScope.RESOURCE_LANGUAGE, 'enumDescriptions': Object.keys(SUPPORTED_ENCODINGS).map(key => SUPPORTED_ENCODINGS[key].labelLong), 'included': Object.keys(SUPPORTED_ENCODINGS).length > 1 }, 'files.autoGuessEncoding': { 'type': 'boolean', - 'overridable': true, 'default': false, 'description': nls.localize('autoGuessEncoding', "When enabled, the editor will attempt to guess the character set encoding when opening files. This setting can also be configured per language."), - 'scope': ConfigurationScope.RESOURCE, + 'scope': ConfigurationScope.RESOURCE_LANGUAGE, 'included': Object.keys(SUPPORTED_ENCODINGS).length > 1 }, 'files.eol': { 'type': 'string', - 'overridable': true, 'enum': [ '\n', '\r\n', @@ -272,7 +273,7 @@ configurationRegistry.registerConfiguration({ ], 'default': 'auto', 'description': nls.localize('eol', "The default end of line character."), - 'scope': ConfigurationScope.RESOURCE + 'scope': ConfigurationScope.RESOURCE_LANGUAGE }, 'files.enableTrash': { 'type': 'boolean', @@ -283,22 +284,19 @@ configurationRegistry.registerConfiguration({ 'type': 'boolean', 'default': false, 'description': nls.localize('trimTrailingWhitespace', "When enabled, will trim trailing whitespace when saving a file."), - 'overridable': true, - 'scope': ConfigurationScope.RESOURCE + 'scope': ConfigurationScope.RESOURCE_LANGUAGE }, 'files.insertFinalNewline': { 'type': 'boolean', 'default': false, 'description': nls.localize('insertFinalNewline', "When enabled, insert a final new line at the end of the file when saving it."), - 'overridable': true, - 'scope': ConfigurationScope.RESOURCE + 'scope': ConfigurationScope.RESOURCE_LANGUAGE }, 'files.trimFinalNewlines': { 'type': 'boolean', 'default': false, 'description': nls.localize('trimFinalNewlines', "When enabled, will trim all new lines after the final new line at the end of the file when saving it."), - 'overridable': true, - 'scope': ConfigurationScope.RESOURCE + scope: ConfigurationScope.RESOURCE_LANGUAGE, }, 'files.autoSave': { 'type': 'string', @@ -355,15 +353,13 @@ configurationRegistry.registerConfiguration({ 'type': 'boolean', 'default': false, 'description': nls.localize('formatOnSave', "Format a file on save. A formatter must be available, the file must not be saved after delay, and the editor must not be shutting down."), - 'overridable': true, - 'scope': ConfigurationScope.RESOURCE + scope: ConfigurationScope.RESOURCE_LANGUAGE, }, 'editor.formatOnSaveTimeout': { 'type': 'number', 'default': 750, 'description': nls.localize('formatOnSaveTimeout', "Timeout in milliseconds after which the formatting that is run on file save is cancelled."), - 'overridable': true, - 'scope': ConfigurationScope.RESOURCE + scope: ConfigurationScope.RESOURCE_LANGUAGE, } } }); diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index 52b6445bc7..ff75033bd0 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -83,7 +83,6 @@ export class ExplorerView extends ViewPane { private compressedFocusContext: IContextKey; private compressedFocusFirstContext: IContextKey; private compressedFocusLastContext: IContextKey; - private compressedNavigationController: ICompressedNavigationController | undefined; // Refresh is needed on the initial explorer open private shouldRefresh = true; @@ -286,18 +285,17 @@ export class ExplorerView extends ViewPane { getContext(respectMultiSelection: boolean): ExplorerItem[] { let focusedStat: ExplorerItem | undefined; - if (this.compressedNavigationController) { - focusedStat = this.compressedNavigationController.current; - } else { - const focus = this.tree.getFocus(); - focusedStat = focus.length ? focus[0] : undefined; - } + const focus = this.tree.getFocus(); + focusedStat = focus.length ? focus[0] : undefined; + + const compressedNavigationController = focusedStat && this.renderer.getCompressedNavigationController(focusedStat); + focusedStat = compressedNavigationController ? compressedNavigationController.current : focusedStat; const selectedStats: ExplorerItem[] = []; for (const stat of this.tree.getSelection()) { const controller = this.renderer.getCompressedNavigationController(stat); - if (controller && focusedStat && controller === this.compressedNavigationController) { + if (controller && focusedStat && controller === compressedNavigationController) { if (stat === focusedStat) { selectedStats.push(stat); } @@ -414,18 +412,18 @@ export class ExplorerView extends ViewPane { this._register(explorerNavigator); // Open when selecting via keyboard this._register(explorerNavigator.onDidOpenResource(async e => { - const selection = this.tree.getSelection(); + const element = e.element; // Do not react if the user is expanding selection via keyboard. // Check if the item was previously also selected, if yes the user is simply expanding / collapsing current selection #66589. const shiftDown = e.browserEvent instanceof KeyboardEvent && e.browserEvent.shiftKey; - if (selection.length === 1 && !shiftDown) { - if (selection[0].isDirectory || this.explorerService.isEditable(undefined)) { + if (element && !shiftDown) { + if (element.isDirectory || this.explorerService.isEditable(undefined)) { // Do not react if user is clicking on explorer items while some are being edited #70276 // Do not react if clicking on directories return; } this.telemetryService.publicLog2('workbenchActionExecuted', { id: 'workbench.files.openFile', from: 'explorer' }); - await this.editorService.openEditor({ resource: selection[0].resource, options: { preserveFocus: e.editorOptions.preserveFocus, pinned: e.editorOptions.pinned } }, e.sideBySide ? SIDE_GROUP : ACTIVE_GROUP); + await this.editorService.openEditor({ resource: element.resource, options: { preserveFocus: e.editorOptions.preserveFocus, pinned: e.editorOptions.pinned } }, e.sideBySide ? SIDE_GROUP : ACTIVE_GROUP); } })); @@ -532,16 +530,15 @@ export class ExplorerView extends ViewPane { this.resourceMoveableToTrash.reset(); } - this.compressedNavigationController = stat && this.renderer.getCompressedNavigationController(stat); + const compressedNavigationController = stat && this.renderer.getCompressedNavigationController(stat); - if (!this.compressedNavigationController) { + if (!compressedNavigationController) { this.compressedFocusContext.set(false); return; } this.compressedFocusContext.set(true); - // this.compressedNavigationController.last(); - this.updateCompressedNavigationContextKeys(this.compressedNavigationController); + this.updateCompressedNavigationContextKeys(compressedNavigationController); } // General methods @@ -691,39 +688,47 @@ export class ExplorerView extends ViewPane { } previousCompressedStat(): void { - if (!this.compressedNavigationController) { + const focused = this.tree.getFocus(); + if (!focused.length) { return; } - this.compressedNavigationController.previous(); - this.updateCompressedNavigationContextKeys(this.compressedNavigationController); + const compressedNavigationController = this.renderer.getCompressedNavigationController(focused[0])!; + compressedNavigationController.previous(); + this.updateCompressedNavigationContextKeys(compressedNavigationController); } nextCompressedStat(): void { - if (!this.compressedNavigationController) { + const focused = this.tree.getFocus(); + if (!focused.length) { return; } - this.compressedNavigationController.next(); - this.updateCompressedNavigationContextKeys(this.compressedNavigationController); + const compressedNavigationController = this.renderer.getCompressedNavigationController(focused[0])!; + compressedNavigationController.next(); + this.updateCompressedNavigationContextKeys(compressedNavigationController); } firstCompressedStat(): void { - if (!this.compressedNavigationController) { + const focused = this.tree.getFocus(); + if (!focused.length) { return; } - this.compressedNavigationController.first(); - this.updateCompressedNavigationContextKeys(this.compressedNavigationController); + const compressedNavigationController = this.renderer.getCompressedNavigationController(focused[0])!; + compressedNavigationController.first(); + this.updateCompressedNavigationContextKeys(compressedNavigationController); } lastCompressedStat(): void { - if (!this.compressedNavigationController) { + const focused = this.tree.getFocus(); + if (!focused.length) { return; } - this.compressedNavigationController.last(); - this.updateCompressedNavigationContextKeys(this.compressedNavigationController); + const compressedNavigationController = this.renderer.getCompressedNavigationController(focused[0])!; + compressedNavigationController.last(); + this.updateCompressedNavigationContextKeys(compressedNavigationController); } private updateCompressedNavigationContextKeys(controller: ICompressedNavigationController): void { diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index 0ce59dfb44..04fc61497b 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -20,7 +20,7 @@ import { IContextViewService } from 'vs/platform/contextview/browser/contextView import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { IFilesConfiguration, IExplorerService } from 'vs/workbench/contrib/files/common/files'; -import { dirname, joinPath, isEqualOrParent, basename, hasToIgnoreCase, distinctParents } from 'vs/base/common/resources'; +import { dirname, joinPath, isEqualOrParent, basename, distinctParents } from 'vs/base/common/resources'; import { InputBox, MessageType } from 'vs/base/browser/ui/inputbox/inputBox'; import { localize } from 'vs/nls'; import { attachInputBoxStyler } from 'vs/platform/theme/common/styler'; @@ -95,7 +95,7 @@ export class ExplorerDataSource implements IAsyncDataSource { } const resource = joinPath(target.resource, name); - await this.fileService.writeFile(resource, VSBuffer.wrap(new Uint8Array(event.target?.result))); + await this.fileService.writeFile(resource, VSBuffer.wrap(new Uint8Array(event.target.result))); if (data.files.length === 1) { await this.editorService.openEditor({ resource, options: { pinned: true } }); } @@ -920,7 +922,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop { // Check for name collisions const targetNames = new Set(); if (targetStat.children) { - const ignoreCase = hasToIgnoreCase(target.resource); + const ignoreCase = this.explorerService.shouldIgnoreCase(target.resource); targetStat.children.forEach(child => { targetNames.add(ignoreCase ? child.name.toLowerCase() : child.name); }); @@ -929,7 +931,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop { // Run add in sequence const addPromisesFactory: ITask>[] = []; await Promise.all(resources.map(async resource => { - if (targetNames.has(!hasToIgnoreCase(resource) ? basename(resource) : basename(resource).toLowerCase())) { + if (targetNames.has(this.explorerService.shouldIgnoreCase(resource) ? basename(resource).toLowerCase() : basename(resource))) { const confirmationResult = await this.dialogService.confirm(getFileOverwriteConfirm(basename(resource))); if (!confirmationResult.confirmed) { return; @@ -1031,7 +1033,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop { // Reuse duplicate action if user copies if (isCopy) { const incrementalNaming = this.configurationService.getValue().explorer.incrementalNaming; - const stat = await this.textFileService.copy(source.resource, findValidPasteFileTarget(target, { resource: source.resource, isDirectory: source.isDirectory, allowOverwrite: false }, incrementalNaming)); + const stat = await this.textFileService.copy(source.resource, findValidPasteFileTarget(this.explorerService, target, { resource: source.resource, isDirectory: source.isDirectory, allowOverwrite: false }, incrementalNaming)); if (!stat.isDirectory) { await this.editorService.openEditor({ resource: stat.resource, options: { pinned: true } }); } diff --git a/src/vs/workbench/contrib/files/common/explorerModel.ts b/src/vs/workbench/contrib/files/common/explorerModel.ts index 9705d766f6..13e3f0332d 100644 --- a/src/vs/workbench/contrib/files/common/explorerModel.ts +++ b/src/vs/workbench/contrib/files/common/explorerModel.ts @@ -6,7 +6,6 @@ import { URI } from 'vs/base/common/uri'; import { isEqual } from 'vs/base/common/extpath'; import { posix } from 'vs/base/common/path'; -import * as resources from 'vs/base/common/resources'; import { ResourceMap } from 'vs/base/common/map'; import { IFileStat, IFileService, FileSystemProviderCapabilities } from 'vs/platform/files/common/files'; import { rtrim, startsWithIgnoreCase, startsWith, equalsIgnoreCase } from 'vs/base/common/strings'; @@ -16,6 +15,7 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { memoize } from 'vs/base/common/decorators'; import { Emitter, Event } from 'vs/base/common/event'; import { IExplorerService } from 'vs/workbench/contrib/files/common/files'; +import { joinPath, isEqualOrParent, basenameOrAuthority } from 'vs/base/common/resources'; export class ExplorerModel implements IDisposable { @@ -23,9 +23,12 @@ export class ExplorerModel implements IDisposable { private _listener: IDisposable; private readonly _onDidChangeRoots = new Emitter(); - constructor(private readonly contextService: IWorkspaceContextService) { + constructor( + private readonly contextService: IWorkspaceContextService, + explorerService: IExplorerService + ) { const setRoots = () => this._roots = this.contextService.getWorkspace().folders - .map(folder => new ExplorerItem(folder.uri, undefined, true, false, false, folder.name)); + .map(folder => new ExplorerItem(folder.uri, explorerService, undefined, true, false, false, folder.name)); setRoots(); this._listener = this.contextService.onDidChangeWorkspaceFolders(() => { @@ -80,11 +83,12 @@ export class ExplorerItem { constructor( public resource: URI, + private readonly explorerService: IExplorerService, private _parent: ExplorerItem | undefined, private _isDirectory?: boolean, private _isSymbolicLink?: boolean, private _isReadonly?: boolean, - private _name: string = resources.basenameOrAuthority(resource), + private _name: string = basenameOrAuthority(resource), private _mtime?: number, ) { this._isDirectoryResolved = false; @@ -154,8 +158,8 @@ export class ExplorerItem { return this === this.root; } - static create(service: IFileService, raw: IFileStat, parent: ExplorerItem | undefined, resolveTo?: readonly URI[]): ExplorerItem { - const stat = new ExplorerItem(raw.resource, parent, raw.isDirectory, raw.isSymbolicLink, service.hasCapability(raw.resource, FileSystemProviderCapabilities.Readonly), raw.name, raw.mtime); + static create(explorerService: IExplorerService, fileService: IFileService, raw: IFileStat, parent: ExplorerItem | undefined, resolveTo?: readonly URI[]): ExplorerItem { + const stat = new ExplorerItem(raw.resource, explorerService, parent, raw.isDirectory, raw.isSymbolicLink, fileService.hasCapability(raw.resource, FileSystemProviderCapabilities.Readonly), raw.name, raw.mtime); // Recursively add children if present if (stat.isDirectory) { @@ -164,13 +168,13 @@ export class ExplorerItem { // the folder is fully resolved if either it has a list of children or the client requested this by using the resolveTo // array of resource path to resolve. stat._isDirectoryResolved = !!raw.children || (!!resolveTo && resolveTo.some((r) => { - return resources.isEqualOrParent(r, stat.resource); + return isEqualOrParent(r, stat.resource); })); // Recurse into children if (raw.children) { for (let i = 0, len = raw.children.length; i < len; i++) { - const child = ExplorerItem.create(service, raw.children[i], stat, resolveTo); + const child = ExplorerItem.create(explorerService, fileService, raw.children[i], stat, resolveTo); stat.addChild(child); } } @@ -262,7 +266,7 @@ export class ExplorerItem { const resolveMetadata = explorerService.sortOrder === 'modified'; try { const stat = await fileService.resolve(this.resource, { resolveSingleChildDescendants: true, resolveMetadata }); - const resolved = ExplorerItem.create(fileService, stat, this); + const resolved = ExplorerItem.create(explorerService, fileService, stat, this); ExplorerItem.mergeLocalWithDisk(resolved, this); } catch (e) { this.isError = true; @@ -302,7 +306,7 @@ export class ExplorerItem { } private getPlatformAwareName(name: string): string { - return (!name || !resources.hasToIgnoreCase(this.resource)) ? name : name.toLowerCase(); + return this.explorerService.shouldIgnoreCase(this.resource) ? name.toLowerCase() : name; } /** @@ -319,7 +323,7 @@ export class ExplorerItem { private updateResource(recursive: boolean): void { if (this._parent) { - this.resource = resources.joinPath(this._parent.resource, this.name); + this.resource = joinPath(this._parent.resource, this.name); } if (recursive) { @@ -352,16 +356,17 @@ export class ExplorerItem { find(resource: URI): ExplorerItem | null { // Return if path found // For performance reasons try to do the comparison as fast as possible + const ignoreCase = this.explorerService.shouldIgnoreCase(resource); if (resource && this.resource.scheme === resource.scheme && equalsIgnoreCase(this.resource.authority, resource.authority) && - (resources.hasToIgnoreCase(resource) ? startsWithIgnoreCase(resource.path, this.resource.path) : startsWith(resource.path, this.resource.path))) { - return this.findByPath(rtrim(resource.path, posix.sep), this.resource.path.length); + (ignoreCase ? startsWithIgnoreCase(resource.path, this.resource.path) : startsWith(resource.path, this.resource.path))) { + return this.findByPath(rtrim(resource.path, posix.sep), this.resource.path.length, ignoreCase); } return null; //Unable to find } - private findByPath(path: string, index: number): ExplorerItem | null { - if (isEqual(rtrim(this.resource.path, posix.sep), path, resources.hasToIgnoreCase(this.resource))) { + private findByPath(path: string, index: number, ignoreCase: boolean): ExplorerItem | null { + if (isEqual(rtrim(this.resource.path, posix.sep), path, ignoreCase)) { return this; } @@ -383,7 +388,7 @@ export class ExplorerItem { if (child) { // We found a child with the given name, search inside it - return child.findByPath(path, indexOfNextSep); + return child.findByPath(path, indexOfNextSep, ignoreCase); } } @@ -392,7 +397,7 @@ export class ExplorerItem { } export class NewExplorerItem extends ExplorerItem { - constructor(parent: ExplorerItem, isDirectory: boolean) { - super(URI.file(''), parent, isDirectory); + constructor(explorerService: IExplorerService, parent: ExplorerItem, isDirectory: boolean) { + super(URI.file(''), explorerService, parent, isDirectory); } } diff --git a/src/vs/workbench/contrib/files/common/explorerService.ts b/src/vs/workbench/contrib/files/common/explorerService.ts index fe157c809e..fe7d6d9870 100644 --- a/src/vs/workbench/contrib/files/common/explorerService.ts +++ b/src/vs/workbench/contrib/files/common/explorerService.ts @@ -9,8 +9,8 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { IExplorerService, IFilesConfiguration, SortOrder, SortOrderConfiguration, IContextProvider } from 'vs/workbench/contrib/files/common/files'; import { ExplorerItem, ExplorerModel } from 'vs/workbench/contrib/files/common/explorerModel'; import { URI } from 'vs/base/common/uri'; -import { FileOperationEvent, FileOperation, IFileStat, IFileService, FileChangesEvent, FILES_EXCLUDE_CONFIG, FileChangeType, IResolveFileOptions } from 'vs/platform/files/common/files'; -import { dirname } from 'vs/base/common/resources'; +import { FileOperationEvent, FileOperation, IFileStat, IFileService, FileChangesEvent, FILES_EXCLUDE_CONFIG, FileChangeType, IResolveFileOptions, FileSystemProviderCapabilities } from 'vs/platform/files/common/files'; +import { dirname, hasToIgnoreCase } from 'vs/base/common/resources'; import { memoize } from 'vs/base/common/decorators'; import { ResourceGlobMatcher } from 'vs/workbench/common/resources'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -41,8 +41,9 @@ export class ExplorerService implements IExplorerService { private editable: { stat: ExplorerItem, data: IEditableData } | undefined; private _sortOrder: SortOrder; private cutItems: ExplorerItem[] | undefined; - private fileSystemProviderSchemes = new Set(); private contextProvider: IContextProvider | undefined; + private fileSystemProviderCaseSensitivity = new Map(); + private model: ExplorerModel; constructor( @IFileService private fileService: IFileService, @@ -53,6 +54,29 @@ export class ExplorerService implements IExplorerService { @IEditorService private editorService: IEditorService, ) { this._sortOrder = this.configurationService.getValue('explorer.sortOrder'); + + this.model = new ExplorerModel(this.contextService, this); + this.disposables.add(this.model); + this.disposables.add(this.fileService.onAfterOperation(e => this.onFileOperation(e))); + this.disposables.add(this.fileService.onFileChanges(e => this.onFileChanges(e))); + this.disposables.add(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(this.configurationService.getValue()))); + this.disposables.add(this.fileService.onDidChangeFileSystemProviderRegistrations(e => { + const provider = e.provider; + if (e.added && provider) { + const alreadyRegistered = this.fileSystemProviderCaseSensitivity.has(e.scheme); + const readCapability = () => this.fileSystemProviderCaseSensitivity.set(e.scheme, !!(provider.capabilities & FileSystemProviderCapabilities.PathCaseSensitive)); + readCapability(); + + if (alreadyRegistered) { + // A file system provider got re-registered, we should update all file stats since they might change (got read-only) + this.model.roots.forEach(r => r.forgetChildren()); + this._onDidChangeItem.fire({ recursive: true }); + } else { + this.disposables.add(provider.onDidChangeCapabilities(() => readCapability())); + } + } + })); + this.disposables.add(this.model.onDidChangeRoots(() => this._onDidChangeRoots.fire())); } get roots(): ExplorerItem[] { @@ -107,24 +131,13 @@ export class ExplorerService implements IExplorerService { return fileEventsFilter; } - @memoize get model(): ExplorerModel { - const model = new ExplorerModel(this.contextService); - this.disposables.add(model); - this.disposables.add(this.fileService.onAfterOperation(e => this.onFileOperation(e))); - this.disposables.add(this.fileService.onFileChanges(e => this.onFileChanges(e))); - this.disposables.add(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(this.configurationService.getValue()))); - this.disposables.add(this.fileService.onDidChangeFileSystemProviderRegistrations(e => { - if (e.added && this.fileSystemProviderSchemes.has(e.scheme)) { - // A file system provider got re-registered, we should update all file stats since they might change (got read-only) - this.model.roots.forEach(r => r.forgetChildren()); - this._onDidChangeItem.fire({ recursive: true }); - } else { - this.fileSystemProviderSchemes.add(e.scheme); - } - })); - this.disposables.add(model.onDidChangeRoots(() => this._onDidChangeRoots.fire())); + shouldIgnoreCase(resource: URI): boolean { + const caseSensitive = this.fileSystemProviderCaseSensitivity.get(resource.scheme); + if (typeof caseSensitive === 'undefined') { + return hasToIgnoreCase(resource); + } - return model; + return !caseSensitive; } // IExplorerService methods @@ -187,7 +200,7 @@ export class ExplorerService implements IExplorerService { const stat = await this.fileService.resolve(rootUri, options); // Convert to model - const modelStat = ExplorerItem.create(this.fileService, stat, undefined, options.resolveTo); + const modelStat = ExplorerItem.create(this, this.fileService, stat, undefined, options.resolveTo); // Update Input with disk Stat ExplorerItem.mergeLocalWithDisk(modelStat, root); const item = root.find(resource); @@ -231,11 +244,11 @@ export class ExplorerService implements IExplorerService { const thenable: Promise = p.isDirectoryResolved ? Promise.resolve(undefined) : this.fileService.resolve(p.resource, { resolveMetadata }); thenable.then(stat => { if (stat) { - const modelStat = ExplorerItem.create(this.fileService, stat, p.parent); + const modelStat = ExplorerItem.create(this, this.fileService, stat, p.parent); ExplorerItem.mergeLocalWithDisk(modelStat, p); } - const childElement = ExplorerItem.create(this.fileService, addedElement, p.parent); + const childElement = ExplorerItem.create(this, this.fileService, addedElement, p.parent); // Make sure to remove any previous version of the file if any p.removeChild(childElement); p.addChild(childElement); diff --git a/src/vs/workbench/contrib/files/common/files.ts b/src/vs/workbench/contrib/files/common/files.ts index 71a53e381e..1fbc80a5ba 100644 --- a/src/vs/workbench/contrib/files/common/files.ts +++ b/src/vs/workbench/contrib/files/common/files.ts @@ -17,7 +17,7 @@ import { IModeService, ILanguageSelection } from 'vs/editor/common/services/mode import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkeys'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainer, IEditableData } from 'vs/workbench/common/views'; +import { IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainer, IEditableData, ViewContainerLocation } from 'vs/workbench/common/views'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel'; @@ -33,7 +33,7 @@ export const VIEWLET_ID = 'workbench.view.explorer'; /** * Explorer viewlet container. */ -export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(VIEWLET_ID); +export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(VIEWLET_ID, ViewContainerLocation.Sidebar); export interface IExplorerService { _serviceBrand: undefined; @@ -55,6 +55,7 @@ export interface IExplorerService { refresh(): void; setToCopy(stats: ExplorerItem[], cut: boolean): void; isCut(stat: ExplorerItem): boolean; + shouldIgnoreCase(resource: URI): boolean; /** * Selects and reveal the file element provided by the given resource if its found in the explorer. diff --git a/src/vs/workbench/contrib/files/electron-browser/textFileEditor.ts b/src/vs/workbench/contrib/files/electron-browser/textFileEditor.ts index 9cc026aee3..4b281d8828 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 { IResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; +import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; 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, - @IResourceConfigurationService configurationService: IResourceConfigurationService, + @ITextResourceConfigurationService configurationService: ITextResourceConfigurationService, @IEditorService editorService: IEditorService, @IThemeService themeService: IThemeService, @IEditorGroupsService editorGroupService: IEditorGroupsService, diff --git a/src/vs/workbench/contrib/files/test/electron-browser/explorerModel.test.ts b/src/vs/workbench/contrib/files/test/electron-browser/explorerModel.test.ts index ed1869f8dd..dee30a5fea 100644 --- a/src/vs/workbench/contrib/files/test/electron-browser/explorerModel.test.ts +++ b/src/vs/workbench/contrib/files/test/electron-browser/explorerModel.test.ts @@ -10,9 +10,18 @@ import { join } from 'vs/base/common/path'; import { validateFileName } from 'vs/workbench/contrib/files/browser/fileActions'; import { ExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel'; import { toResource } from 'vs/base/test/common/utils'; +import { hasToIgnoreCase } from 'vs/base/common/resources'; +import { IExplorerService } from 'vs/workbench/contrib/files/common/files'; + +class MockExplorerService { + shouldIgnoreCase(resource: URI) { + return hasToIgnoreCase(resource); + } +} +const mockExplorerService = new MockExplorerService() as IExplorerService; function createStat(this: any, path: string, name: string, isFolder: boolean, hasChildren: boolean, size: number, mtime: number): ExplorerItem { - return new ExplorerItem(toResource.call(this, path), undefined, isFolder, false, false, name, mtime); + return new ExplorerItem(toResource.call(this, path), mockExplorerService, undefined, isFolder, false, false, name, mtime); } suite('Files - View Model', function () { @@ -243,19 +252,19 @@ suite('Files - View Model', function () { }); test('Merge Local with Disk', function () { - const merge1 = new ExplorerItem(URI.file(join('C:\\', '/path/to')), undefined, true, false, false, 'to', Date.now()); - const merge2 = new ExplorerItem(URI.file(join('C:\\', '/path/to')), undefined, true, false, false, 'to', Date.now()); + const merge1 = new ExplorerItem(URI.file(join('C:\\', '/path/to')), mockExplorerService, undefined, true, false, false, 'to', Date.now()); + const merge2 = new ExplorerItem(URI.file(join('C:\\', '/path/to')), mockExplorerService, undefined, true, false, false, 'to', Date.now()); // Merge Properties ExplorerItem.mergeLocalWithDisk(merge2, merge1); assert.strictEqual(merge1.mtime, merge2.mtime); // Merge Child when isDirectoryResolved=false is a no-op - merge2.addChild(new ExplorerItem(URI.file(join('C:\\', '/path/to/foo.html')), undefined, true, false, false, 'foo.html', Date.now())); + merge2.addChild(new ExplorerItem(URI.file(join('C:\\', '/path/to/foo.html')), mockExplorerService, undefined, true, false, false, 'foo.html', Date.now())); ExplorerItem.mergeLocalWithDisk(merge2, merge1); // Merge Child with isDirectoryResolved=true - const child = new ExplorerItem(URI.file(join('C:\\', '/path/to/foo.html')), undefined, true, false, false, 'foo.html', Date.now()); + const child = new ExplorerItem(URI.file(join('C:\\', '/path/to/foo.html')), mockExplorerService, undefined, true, false, false, 'foo.html', Date.now()); merge2.removeChild(child); merge2.addChild(child); (merge2)._isDirectoryResolved = true; diff --git a/src/vs/workbench/contrib/markers/browser/markersPanel.ts b/src/vs/workbench/contrib/markers/browser/markersPanel.ts index 69ecb61714..49c53f6a22 100644 --- a/src/vs/workbench/contrib/markers/browser/markersPanel.ts +++ b/src/vs/workbench/contrib/markers/browser/markersPanel.ts @@ -18,7 +18,7 @@ import { MarkersFilterActionViewItem, MarkersFilterAction, IMarkersFilterActionC import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import Messages from 'vs/workbench/contrib/markers/browser/messages'; import { RangeHighlightDecorations } from 'vs/workbench/browser/parts/editor/rangeDecorations'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IMarkersWorkbenchService } from 'vs/workbench/contrib/markers/browser/markers'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; @@ -47,6 +47,7 @@ import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { PANEL_BACKGROUND } from 'vs/workbench/common/theme'; import { KeyCode } from 'vs/base/common/keyCodes'; +import { editorLightBulbForeground, editorLightBulbAutoFixForeground } from 'vs/platform/theme/common/colorRegistry'; function createResourceMarkersIterator(resourceMarkers: ResourceMarkers): Iterator> { const markersIt = Iterator.fromArray(resourceMarkers.markers); @@ -166,8 +167,9 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { this.updateTitleArea(); dom.toggleClass(this.filterActionBar.getContainer(), 'hide', !this.isSmallLayout); } - const treeHeight = this.isSmallLayout ? dimension.height - 44 : dimension.height; - this.tree.layout(treeHeight, dimension.width); + const height = this.isSmallLayout ? dimension.height - 44 : dimension.height; + this.tree.layout(height, dimension.width); + this.messageBoxContainer.style.height = `${height}px`; this.filterAction.layout(this.isSmallLayout ? dimension.width : dimension.width - 200); } @@ -817,3 +819,25 @@ class MarkersTree extends WorkbenchObjectTree { } } + +registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { + + // Lightbulb Icon + const editorLightBulbForegroundColor = theme.getColor(editorLightBulbForeground); + if (editorLightBulbForegroundColor) { + collector.addRule(` + .monaco-workbench .markers-panel-container .codicon-lightbulb { + color: ${editorLightBulbForegroundColor}; + }`); + } + + // Lightbulb Auto Fix Icon + const editorLightBulbAutoFixForegroundColor = theme.getColor(editorLightBulbAutoFixForeground); + if (editorLightBulbAutoFixForegroundColor) { + collector.addRule(` + .monaco-workbench .markers-panel-container .codicon-lightbulb-autofix { + color: ${editorLightBulbAutoFixForegroundColor}; + }`); + } + +}); diff --git a/src/vs/workbench/contrib/markers/browser/media/markers.css b/src/vs/workbench/contrib/markers/browser/media/markers.css index afdf80d710..fd1509def8 100644 --- a/src/vs/workbench/contrib/markers/browser/media/markers.css +++ b/src/vs/workbench/contrib/markers/browser/media/markers.css @@ -78,7 +78,6 @@ .markers-panel .markers-panel-container .message-box-container { line-height: 22px; padding-left: 20px; - height: 100%; } .markers-panel .markers-panel-container .message-box-container .messageAction { diff --git a/src/vs/workbench/contrib/outline/browser/outline.contribution.ts b/src/vs/workbench/contrib/outline/browser/outline.contribution.ts index 1c5c3db2bc..1e8f8b46a8 100644 --- a/src/vs/workbench/contrib/outline/browser/outline.contribution.ts +++ b/src/vs/workbench/contrib/outline/browser/outline.contribution.ts @@ -8,7 +8,7 @@ import { IViewsRegistry, IViewDescriptor, Extensions as ViewExtensions } from 'v import { OutlinePane } from './outlinePane'; import { VIEW_CONTAINER } from 'vs/workbench/contrib/files/common/files'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; +import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { OutlineConfigKeys, OutlineViewId } from 'vs/editor/contrib/documentSymbols/outline'; // import './outlineNavigation'; @@ -55,110 +55,110 @@ Registry.as(ConfigurationExtensions.Configuration).regis }, 'outline.showFiles': { type: 'boolean', - overridable: true, + scope: ConfigurationScope.RESOURCE_LANGUAGE, default: true, markdownDescription: localize('filteredTypes.file', "When enabled outline shows `file`-symbols.") }, 'outline.showModules': { type: 'boolean', - overridable: true, + scope: ConfigurationScope.RESOURCE_LANGUAGE, default: true, markdownDescription: localize('filteredTypes.module', "When enabled outline shows `module`-symbols.") }, 'outline.showNamespaces': { type: 'boolean', default: true, - overridable: true, + scope: ConfigurationScope.RESOURCE_LANGUAGE, markdownDescription: localize('filteredTypes.namespace', "When enabled outline shows `namespace`-symbols.") }, 'outline.showPackages': { type: 'boolean', default: true, - overridable: true, + scope: ConfigurationScope.RESOURCE_LANGUAGE, markdownDescription: localize('filteredTypes.package', "When enabled outline shows `package`-symbols.") }, 'outline.showClasses': { type: 'boolean', default: true, - overridable: true, + scope: ConfigurationScope.RESOURCE_LANGUAGE, markdownDescription: localize('filteredTypes.class', "When enabled outline shows `class`-symbols.") }, 'outline.showMethods': { type: 'boolean', default: true, - overridable: true, + scope: ConfigurationScope.RESOURCE_LANGUAGE, markdownDescription: localize('filteredTypes.method', "When enabled outline shows `method`-symbols.") }, 'outline.showProperties': { type: 'boolean', default: true, - overridable: true, + scope: ConfigurationScope.RESOURCE_LANGUAGE, markdownDescription: localize('filteredTypes.property', "When enabled outline shows `property`-symbols.") }, 'outline.showFields': { type: 'boolean', default: true, - overridable: true, + scope: ConfigurationScope.RESOURCE_LANGUAGE, markdownDescription: localize('filteredTypes.field', "When enabled outline shows `field`-symbols.") }, 'outline.showConstructors': { type: 'boolean', default: true, - overridable: true, + scope: ConfigurationScope.RESOURCE_LANGUAGE, markdownDescription: localize('filteredTypes.constructor', "When enabled outline shows `constructor`-symbols.") }, 'outline.showEnums': { type: 'boolean', default: true, - overridable: true, + scope: ConfigurationScope.RESOURCE_LANGUAGE, markdownDescription: localize('filteredTypes.enum', "When enabled outline shows `enum`-symbols.") }, 'outline.showInterfaces': { type: 'boolean', default: true, - overridable: true, + scope: ConfigurationScope.RESOURCE_LANGUAGE, markdownDescription: localize('filteredTypes.interface', "When enabled outline shows `interface`-symbols.") }, 'outline.showFunctions': { type: 'boolean', default: true, - overridable: true, + scope: ConfigurationScope.RESOURCE_LANGUAGE, markdownDescription: localize('filteredTypes.function', "When enabled outline shows `function`-symbols.") }, 'outline.showVariables': { type: 'boolean', default: true, - overridable: true, + scope: ConfigurationScope.RESOURCE_LANGUAGE, markdownDescription: localize('filteredTypes.variable', "When enabled outline shows `variable`-symbols.") }, 'outline.showConstants': { type: 'boolean', default: true, - overridable: true, + scope: ConfigurationScope.RESOURCE_LANGUAGE, markdownDescription: localize('filteredTypes.constant', "When enabled outline shows `constant`-symbols.") }, 'outline.showStrings': { type: 'boolean', default: true, - overridable: true, + scope: ConfigurationScope.RESOURCE_LANGUAGE, markdownDescription: localize('filteredTypes.string', "When enabled outline shows `string`-symbols.") }, 'outline.showNumbers': { type: 'boolean', default: true, - overridable: true, + scope: ConfigurationScope.RESOURCE_LANGUAGE, markdownDescription: localize('filteredTypes.number', "When enabled outline shows `number`-symbols.") }, 'outline.showBooleans': { type: 'boolean', - overridable: true, + scope: ConfigurationScope.RESOURCE_LANGUAGE, default: true, markdownDescription: localize('filteredTypes.boolean', "When enabled outline shows `boolean`-symbols.") }, 'outline.showArrays': { type: 'boolean', default: true, - overridable: true, + scope: ConfigurationScope.RESOURCE_LANGUAGE, markdownDescription: localize('filteredTypes.array', "When enabled outline shows `array`-symbols.") }, 'outline.showObjects': { diff --git a/src/vs/workbench/contrib/outline/browser/outlineNavigation.ts b/src/vs/workbench/contrib/outline/browser/outlineNavigation.ts index 4e8171dfad..2641fcc3a2 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 { IResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; +import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; class FlatOutline { @@ -78,7 +78,7 @@ export class OutlineNavigation implements IEditorContribution { constructor( editor: ICodeEditor, - @IResourceConfigurationService private readonly _textResourceConfigService: IResourceConfigurationService, + @ITextResourceConfigurationService private readonly _textResourceConfigService: ITextResourceConfigurationService, ) { this._editor = editor; } diff --git a/src/vs/workbench/contrib/outline/browser/outlinePane.ts b/src/vs/workbench/contrib/outline/browser/outlinePane.ts index 9dd1f8d58b..2502c88885 100644 --- a/src/vs/workbench/contrib/outline/browser/outlinePane.ts +++ b/src/vs/workbench/contrib/outline/browser/outlinePane.ts @@ -263,11 +263,10 @@ export class OutlinePane extends ViewPane { @IMarkerDecorationsService private readonly _markerDecorationService: IMarkerDecorationsService, @IConfigurationService private readonly _configurationService: IConfigurationService, @IKeybindingService keybindingService: IKeybindingService, - @IConfigurationService configurationService: IConfigurationService, @IContextKeyService contextKeyService: IContextKeyService, @IContextMenuService contextMenuService: IContextMenuService, ) { - super(options, keybindingService, contextMenuService, configurationService, contextKeyService); + super(options, keybindingService, contextMenuService, _configurationService, contextKeyService); this._outlineViewState.restore(this._storageService); this._contextKeyFocused = OutlineViewFocused.bindTo(contextKeyService); this._contextKeyFiltered = OutlineViewFiltered.bindTo(contextKeyService); @@ -372,9 +371,7 @@ export class OutlinePane extends ViewPane { if (e.affectsConfiguration(OutlineConfigKeys.icons)) { this._tree.updateChildren(); } - // This is a temporary solution to try and minimize refilters while - // ConfigurationChangeEvents only provide the first section of the config path. - if (e.affectedKeys.some(key => key.search(/(outline|\[\w+\])/) === 0)) { + if (e.affectsConfiguration('outline')) { this._tree.refilter(); } })); diff --git a/src/vs/workbench/contrib/output/browser/logViewer.ts b/src/vs/workbench/contrib/output/browser/logViewer.ts index 219c56490e..e583b06f31 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 { IResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; +import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; 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, - @IResourceConfigurationService textResourceConfigurationService: IResourceConfigurationService, + @ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService, @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 4cf5a52c84..3c23f8f2cb 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 { IResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; +import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; 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, - @IResourceConfigurationService textResourceConfigurationService: IResourceConfigurationService, + @ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService, @IThemeService themeService: IThemeService, @IOutputService private readonly outputService: IOutputService, @IContextKeyService private readonly contextKeyService: IContextKeyService, diff --git a/src/vs/workbench/contrib/performance/electron-browser/performance.contribution.ts b/src/vs/workbench/contrib/performance/electron-browser/performance.contribution.ts index f312caf258..f1f20e8063 100644 --- a/src/vs/workbench/contrib/performance/electron-browser/performance.contribution.ts +++ b/src/vs/workbench/contrib/performance/electron-browser/performance.contribution.ts @@ -26,6 +26,9 @@ Registry.as(Extensions.Workbench).registerWorkb Registry.as(Input.EditorInputFactories).registerEditorInputFactory( PerfviewInput.Id, class implements IEditorInputFactory { + canSerialize(): boolean { + return true; + } serialize(): string { return ''; } diff --git a/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts b/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts index 97832ccb3d..9937731d82 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts @@ -87,6 +87,20 @@ interface ISerializedPreferencesEditorInput { // Register Preferences Editor Input Factory class PreferencesEditorInputFactory implements IEditorInputFactory { + canSerialize(editorInput: EditorInput): boolean { + const input = editorInput; + + if (input.details && input.master) { + const registry = Registry.as(EditorInputExtensions.EditorInputFactories); + const detailsInputFactory = registry.getEditorInputFactory(input.details.getTypeId()); + const masterInputFactory = registry.getEditorInputFactory(input.master.getTypeId()); + + return !!(detailsInputFactory?.canSerialize(input.details) && masterInputFactory?.canSerialize(input.master)); + } + + return false; + } + serialize(editorInput: EditorInput): string | undefined { const input = editorInput; @@ -137,6 +151,10 @@ class PreferencesEditorInputFactory implements IEditorInputFactory { class KeybindingsEditorInputFactory implements IEditorInputFactory { + canSerialize(editorInput: EditorInput): boolean { + return true; + } + serialize(editorInput: EditorInput): string { const input = editorInput; return JSON.stringify({ @@ -150,21 +168,18 @@ class KeybindingsEditorInputFactory implements IEditorInputFactory { } } -interface ISerializedSettingsEditor2EditorInput { -} - class SettingsEditor2InputFactory implements IEditorInputFactory { - serialize(input: SettingsEditor2Input): string { - const serialized: ISerializedSettingsEditor2EditorInput = { - }; + canSerialize(editorInput: EditorInput): boolean { + return true; + } - return JSON.stringify(serialized); + serialize(input: SettingsEditor2Input): string { + return '{}'; } deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): SettingsEditor2Input { - return instantiationService.createInstance( - SettingsEditor2Input); + return instantiationService.createInstance(SettingsEditor2Input); } } @@ -175,6 +190,10 @@ interface ISerializedDefaultPreferencesEditorInput { // Register Default Preferences Editor Input Factory class DefaultPreferencesEditorInputFactory implements IEditorInputFactory { + canSerialize(editorInput: EditorInput): boolean { + return true; + } + serialize(editorInput: EditorInput): string { const input = editorInput; diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts b/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts index 3eff6773f7..3b7888014c 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 { IResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; +import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; 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, - @IResourceConfigurationService configurationService: IResourceConfigurationService, + @ITextResourceConfigurationService configurationService: ITextResourceConfigurationService, @IThemeService themeService: IThemeService, @IEditorGroupsService editorGroupService: IEditorGroupsService, @IEditorService editorService: IEditorService diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts b/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts index 3d98f2f14c..ae7ade4634 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts @@ -772,7 +772,7 @@ class EditSettingRenderer extends Disposable { if ((this.masterSettingsModel).configurationTarget !== ConfigurationTarget.WORKSPACE_FOLDER) { return true; } - if (configurationNode.scope === ConfigurationScope.RESOURCE) { + if (configurationNode.scope === ConfigurationScope.RESOURCE || configurationNode.scope === ConfigurationScope.RESOURCE_LANGUAGE) { return true; } } diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts index d71f1ee0be..d5e3bf1212 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts @@ -807,7 +807,7 @@ export class SettingsEditor2 extends BaseEditor { // If the user is changing the value back to the default, do a 'reset' instead const inspected = this.configurationService.inspect(key, overrides); - if (inspected.default === value) { + if (inspected.defaultValue === value) { value = undefined; } diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts b/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts index dae47dea2a..64cc03899d 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts @@ -155,23 +155,23 @@ export class SettingsTreeSettingElement extends SettingsTreeElement { update(inspectResult: IInspectResult): void { const { isConfigured, inspected, targetSelector } = inspectResult; - const displayValue = isConfigured ? inspected[targetSelector] : inspected.default; + const displayValue = isConfigured ? inspected[targetSelector] : inspected.defaultValue; const overriddenScopeList: string[] = []; - if (targetSelector !== 'workspace' && typeof inspected.workspace !== 'undefined') { + if (targetSelector !== 'workspaceValue' && typeof inspected.workspaceValue !== 'undefined') { overriddenScopeList.push(localize('workspace', "Workspace")); } - if (targetSelector !== 'userRemote' && typeof inspected.userRemote !== 'undefined') { + if (targetSelector !== 'userRemoteValue' && typeof inspected.userRemoteValue !== 'undefined') { overriddenScopeList.push(localize('remote', "Remote")); } - if (targetSelector !== 'userLocal' && typeof inspected.userLocal !== 'undefined') { + if (targetSelector !== 'userLocalValue' && typeof inspected.userLocalValue !== 'undefined') { overriddenScopeList.push(localize('user', "User")); } this.value = displayValue; this.scopeValue = isConfigured && inspected[targetSelector]; - this.defaultValue = inspected.default; + this.defaultValue = inspected.defaultValue; this.isConfigured = isConfigured; if (isConfigured || this.setting.tags || this.tags) { @@ -375,16 +375,16 @@ export class SettingsTreeModel { interface IInspectResult { isConfigured: boolean; inspected: IConfigurationValue; - targetSelector: 'userLocal' | 'userRemote' | 'workspace' | 'workspaceFolder'; + targetSelector: 'userLocalValue' | 'userRemoteValue' | 'workspaceValue' | 'workspaceFolderValue'; } function inspectSetting(key: string, target: SettingsTarget, configurationService: IConfigurationService): IInspectResult { const inspectOverrides = URI.isUri(target) ? { resource: target } : undefined; const inspected = configurationService.inspect(key, inspectOverrides); - const targetSelector = target === ConfigurationTarget.USER_LOCAL ? 'userLocal' : - target === ConfigurationTarget.USER_REMOTE ? 'userRemote' : - target === ConfigurationTarget.WORKSPACE ? 'workspace' : - 'workspaceFolder'; + const targetSelector = target === ConfigurationTarget.USER_LOCAL ? 'userLocalValue' : + target === ConfigurationTarget.USER_REMOTE ? 'userRemoteValue' : + target === ConfigurationTarget.WORKSPACE ? 'workspaceValue' : + 'workspaceFolderValue'; const isConfigured = typeof inspected[targetSelector] !== 'undefined'; return { isConfigured, inspected, targetSelector }; diff --git a/src/vs/workbench/contrib/remote/browser/remote.ts b/src/vs/workbench/contrib/remote/browser/remote.ts index 0829e132c1..eb1d7dcef6 100644 --- a/src/vs/workbench/contrib/remote/browser/remote.ts +++ b/src/vs/workbench/contrib/remote/browser/remote.ts @@ -44,7 +44,7 @@ import { isStringArray } from 'vs/base/common/types'; import { IRemoteExplorerService, HelpInformation } from 'vs/workbench/services/remote/common/remoteExplorerService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { startsWith } from 'vs/base/common/strings'; -import { TunnelPanelDescriptor, TunnelViewModel } from 'vs/workbench/contrib/remote/browser/tunnelView'; +import { TunnelPanelDescriptor, TunnelViewModel, forwardedPortsViewEnabled } from 'vs/workbench/contrib/remote/browser/tunnelView'; import { IAddedViewDescriptorRef } from 'vs/workbench/browser/parts/views/views'; import { ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; @@ -342,7 +342,9 @@ export class RemoteViewPaneContainer extends FilterViewPaneContainer { onDidAddViews(added: IAddedViewDescriptorRef[]): ViewPane[] { // Call to super MUST be first, since registering the additional view will cause this to be called again. const panels: ViewPane[] = super.onDidAddViews(added); - if (this.environmentService.configuration.remoteAuthority && !this.tunnelPanelDescriptor && this.configurationService.getValue('remote.forwardedPortsView.visible')) { + // This context key is set to false in the constructor, but is expected to be changed by resolver extensions to enable the forwarded ports view. + const viewEnabled: boolean = !!forwardedPortsViewEnabled.getValue(this.contextKeyService); + if (this.environmentService.configuration.remoteAuthority && !this.tunnelPanelDescriptor && viewEnabled) { this.tunnelPanelDescriptor = new TunnelPanelDescriptor(new TunnelViewModel(this.remoteExplorerService), this.environmentService); const viewsRegistry = Registry.as(Extensions.ViewsRegistry); viewsRegistry.registerViews([this.tunnelPanelDescriptor!], VIEW_CONTAINER); diff --git a/src/vs/workbench/contrib/remote/browser/tunnelView.ts b/src/vs/workbench/contrib/remote/browser/tunnelView.ts index 9de0832485..56dcc4c267 100644 --- a/src/vs/workbench/contrib/remote/browser/tunnelView.ts +++ b/src/vs/workbench/contrib/remote/browser/tunnelView.ts @@ -6,7 +6,7 @@ import 'vs/css!./media/tunnelView'; import * as nls from 'vs/nls'; import * as dom from 'vs/base/browser/dom'; -import { IViewDescriptor, IEditableData } from 'vs/workbench/common/views'; +import { IViewDescriptor, IEditableData, IViewsService } from 'vs/workbench/common/views'; import { WorkbenchAsyncDataTree, TreeResourceNavigator2 } from 'vs/platform/list/browser/listService'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; @@ -38,6 +38,8 @@ import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { URI } from 'vs/base/common/uri'; +export const forwardedPortsViewEnabled = new RawContextKey('forwardedPortsViewEnabled', false); + class TunnelTreeVirtualDelegate implements IListVirtualDelegate { getHeight(element: ITunnelItem): number { return 22; @@ -95,7 +97,7 @@ export class TunnelViewModel extends Disposable implements ITunnelViewModel { }); } groups.push({ - label: nls.localize('remote.tunnelsView.add', "Forward Port..."), + label: nls.localize('remote.tunnelsView.add', "Forward a Port..."), tunnelType: TunnelType.Add, }); return groups; @@ -377,8 +379,8 @@ export const TunnelTypeContextKey = new RawContextKey('tunnelType', export const TunnelCloseableContextKey = new RawContextKey('tunnelCloseable', false); export class TunnelPanel extends ViewPane { - static readonly ID = '~remote.tunnelPanel'; - static readonly TITLE = nls.localize('remote.tunnel', "Tunnels"); + static readonly ID = '~remote.forwardedPorts'; + static readonly TITLE = nls.localize('remote.tunnel', "Forwarded Ports"); private tree!: WorkbenchAsyncDataTree; private tunnelTypeContext: IContextKey; private tunnelCloseableContext: IContextKey; @@ -592,7 +594,7 @@ namespace LabelTunnelAction { namespace ForwardPortAction { export const ID = 'remote.tunnel.forward'; - export const LABEL = nls.localize('remote.tunnel.forward', "Forward Port"); + export const LABEL = nls.localize('remote.tunnel.forward', "Forward a Port"); export function handler(): ICommandHandler { return async (accessor, arg) => { @@ -600,6 +602,8 @@ namespace ForwardPortAction { if (arg instanceof TunnelItem) { remoteExplorerService.tunnelModel.forward(arg.remote); } else { + const viewsService = accessor.get(IViewsService); + await viewsService.openView(TunnelPanel.ID, true); remoteExplorerService.setEditable(undefined, { onFinish: (value, success) => { if (success) { @@ -679,6 +683,15 @@ CommandsRegistry.registerCommand(ClosePortAction.ID, ClosePortAction.handler()); CommandsRegistry.registerCommand(OpenPortInBrowserAction.ID, OpenPortInBrowserAction.handler()); CommandsRegistry.registerCommand(CopyAddressAction.ID, CopyAddressAction.handler()); +MenuRegistry.appendMenuItem(MenuId.CommandPalette, ({ + command: { + id: ForwardPortAction.ID, + title: ForwardPortAction.LABEL + }, + when: forwardedPortsViewEnabled +})); + + MenuRegistry.appendMenuItem(MenuId.TunnelTitle, ({ group: 'navigation', order: 0, diff --git a/src/vs/workbench/contrib/remote/common/remote.contribution.ts b/src/vs/workbench/contrib/remote/common/remote.contribution.ts index 856d39d7ba..299ecd2fdb 100644 --- a/src/vs/workbench/contrib/remote/common/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/common/remote.contribution.ts @@ -16,11 +16,12 @@ import { IOutputChannelRegistry, Extensions as OutputExt, } from 'vs/workbench/s import { localize } from 'vs/nls'; import { joinPath } from 'vs/base/common/resources'; import { Disposable } from 'vs/base/common/lifecycle'; -import { ViewContainer, IViewContainersRegistry, Extensions as ViewContainerExtensions } from 'vs/workbench/common/views'; +import { ViewContainer, IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainerLocation } from 'vs/workbench/common/views'; export const VIEWLET_ID = 'workbench.view.remote'; export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer( VIEWLET_ID, + ViewContainerLocation.Sidebar, true, undefined, { diff --git a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts index 6f4ddd3d95..bcc8bea530 100644 --- a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts +++ b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts @@ -20,7 +20,7 @@ import { URI } from 'vs/base/common/uri'; import { ISCMService, ISCMRepository, ISCMProvider } from 'vs/workbench/contrib/scm/common/scm'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { registerThemingParticipant, ITheme, ICssStyleCollector, themeColorFromId, IThemeService } from 'vs/platform/theme/common/themeService'; -import { registerColor } from 'vs/platform/theme/common/colorRegistry'; +import { registerColor, transparent } from 'vs/platform/theme/common/colorRegistry'; import { Color, RGBA } from 'vs/base/common/color'; import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; import { registerEditorAction, registerEditorContribution, ServicesAccessor, EditorAction } from 'vs/editor/browser/editorExtensions'; @@ -842,10 +842,9 @@ export const minimapGutterDeletedBackground = registerColor('minimapGutter.delet hc: new Color(new RGBA(252, 93, 109)) }, nls.localize('minimapGutterDeletedBackground', "Minimap gutter background color for lines that are deleted.")); -const overviewRulerDefault = new Color(new RGBA(0, 122, 204, 0.6)); -export const overviewRulerModifiedForeground = registerColor('editorOverviewRuler.modifiedForeground', { dark: overviewRulerDefault, light: overviewRulerDefault, hc: overviewRulerDefault }, nls.localize('overviewRulerModifiedForeground', 'Overview ruler marker color for modified content.')); -export const overviewRulerAddedForeground = registerColor('editorOverviewRuler.addedForeground', { dark: overviewRulerDefault, light: overviewRulerDefault, hc: overviewRulerDefault }, nls.localize('overviewRulerAddedForeground', 'Overview ruler marker color for added content.')); -export const overviewRulerDeletedForeground = registerColor('editorOverviewRuler.deletedForeground', { dark: overviewRulerDefault, light: overviewRulerDefault, hc: overviewRulerDefault }, nls.localize('overviewRulerDeletedForeground', 'Overview ruler marker color for deleted content.')); +export const overviewRulerModifiedForeground = registerColor('editorOverviewRuler.modifiedForeground', { dark: transparent(editorGutterModifiedBackground, 0.6), light: transparent(editorGutterModifiedBackground, 0.6), hc: transparent(editorGutterModifiedBackground, 0.6) }, nls.localize('overviewRulerModifiedForeground', 'Overview ruler marker color for modified content.')); +export const overviewRulerAddedForeground = registerColor('editorOverviewRuler.addedForeground', { dark: transparent(editorGutterAddedBackground, 0.6), light: transparent(editorGutterAddedBackground, 0.6), hc: transparent(editorGutterAddedBackground, 0.6) }, nls.localize('overviewRulerAddedForeground', 'Overview ruler marker color for added content.')); +export const overviewRulerDeletedForeground = registerColor('editorOverviewRuler.deletedForeground', { dark: transparent(editorGutterDeletedBackground, 0.6), light: transparent(editorGutterDeletedBackground, 0.6), hc: transparent(editorGutterDeletedBackground, 0.6) }, nls.localize('overviewRulerDeletedForeground', 'Overview ruler marker color for deleted content.')); class DirtyDiffDecorator extends Disposable { diff --git a/src/vs/workbench/contrib/scm/common/scm.ts b/src/vs/workbench/contrib/scm/common/scm.ts index 9fd2380ce7..615cf4fd4f 100644 --- a/src/vs/workbench/contrib/scm/common/scm.ts +++ b/src/vs/workbench/contrib/scm/common/scm.ts @@ -9,11 +9,11 @@ import { Event } from 'vs/base/common/event'; import { IDisposable } from 'vs/base/common/lifecycle'; import { Command } from 'vs/editor/common/modes'; import { ISequence } from 'vs/base/common/sequence'; -import { Extensions as ViewContainerExtensions, ViewContainer, IViewContainersRegistry } from 'vs/workbench/common/views'; +import { Extensions as ViewContainerExtensions, ViewContainer, IViewContainersRegistry, ViewContainerLocation } from 'vs/workbench/common/views'; import { Registry } from 'vs/platform/registry/common/platform'; export const VIEWLET_ID = 'workbench.view.scm'; -export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(VIEWLET_ID); +export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(VIEWLET_ID, ViewContainerLocation.Sidebar); export interface IBaselineResourceProvider { getBaselineResource(resource: URI): Promise; diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index dc17c1d8a4..a80ec500aa 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -40,7 +40,7 @@ import { ExplorerFolderContext, ExplorerRootContext, FilesExplorerFocusCondition import { OpenAnythingHandler } from 'vs/workbench/contrib/search/browser/openAnythingHandler'; import { OpenSymbolHandler } from 'vs/workbench/contrib/search/browser/openSymbolHandler'; import { registerContributions as replaceContributions } from 'vs/workbench/contrib/search/browser/replaceContributions'; -import { clearHistoryCommand, ClearSearchResultsAction, CloseReplaceAction, CollapseDeepestExpandedLevelAction, copyAllCommand, copyMatchCommand, copyPathCommand, FocusNextInputAction, FocusNextSearchResultAction, FocusPreviousInputAction, FocusPreviousSearchResultAction, focusSearchListCommand, getSearchView, openSearchView, OpenSearchViewletAction, RefreshAction, RemoveAction, ReplaceAction, ReplaceAllAction, ReplaceAllInFolderAction, ReplaceInFilesAction, toggleCaseSensitiveCommand, toggleRegexCommand, toggleWholeWordCommand, FindInFilesCommand, ToggleSearchOnTypeAction, OpenResultsInEditorAction, RerunEditorSearchAction, RerunEditorSearchWithContextAction } from 'vs/workbench/contrib/search/browser/searchActions'; +import { clearHistoryCommand, ClearSearchResultsAction, CloseReplaceAction, CollapseDeepestExpandedLevelAction, copyAllCommand, copyMatchCommand, copyPathCommand, FocusNextInputAction, FocusNextSearchResultAction, FocusPreviousInputAction, FocusPreviousSearchResultAction, focusSearchListCommand, getSearchView, openSearchView, OpenSearchViewletAction, RefreshAction, RemoveAction, ReplaceAction, ReplaceAllAction, ReplaceAllInFolderAction, ReplaceInFilesAction, toggleCaseSensitiveCommand, toggleRegexCommand, toggleWholeWordCommand, FindInFilesCommand, ToggleSearchOnTypeAction, OpenResultsInEditorAction, RerunEditorSearchAction, RerunEditorSearchWithContextAction, ExpandAllAction } from 'vs/workbench/contrib/search/browser/searchActions'; import { SearchPanel } from 'vs/workbench/contrib/search/browser/searchPanel'; import { SearchView, SearchViewPosition } from 'vs/workbench/contrib/search/browser/searchView'; import { SearchViewlet } from 'vs/workbench/contrib/search/browser/searchViewlet'; @@ -51,7 +51,7 @@ import { ISearchHistoryService, SearchHistoryService } from 'vs/workbench/contri import { FileMatchOrMatch, ISearchWorkbenchService, RenderableMatch, SearchWorkbenchService, FileMatch, Match, FolderMatch } from 'vs/workbench/contrib/search/common/searchModel'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; -import { ISearchConfiguration, ISearchConfigurationProperties, PANEL_ID, VIEWLET_ID, VIEW_ID, VIEW_CONTAINER } from 'vs/workbench/services/search/common/search'; +import { ISearchConfiguration, ISearchConfigurationProperties, PANEL_ID, VIEWLET_ID, VIEW_ID, VIEW_CONTAINER, SearchSortOrder } from 'vs/workbench/services/search/common/search'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { ExplorerViewPaneContainer } from 'vs/workbench/contrib/files/browser/explorerViewlet'; @@ -634,6 +634,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ }); registry.registerWorkbenchAction(SyncActionDescriptor.create(CollapseDeepestExpandedLevelAction, CollapseDeepestExpandedLevelAction.ID, CollapseDeepestExpandedLevelAction.LABEL), 'Search: Collapse All', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ExpandAllAction, ExpandAllAction.ID, ExpandAllAction.LABEL), 'Search: Expand All', category); registry.registerWorkbenchAction(SyncActionDescriptor.create(ShowAllSymbolsAction, ShowAllSymbolsAction.ID, ShowAllSymbolsAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_T }), 'Go to Symbol in Workspace...'); registry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleSearchOnTypeAction, ToggleSearchOnTypeAction.ID, ToggleSearchOnTypeAction.LABEL), 'Search: Toggle Search on Type', category); registry.registerWorkbenchAction(SyncActionDescriptor.create(RefreshAction, RefreshAction.ID, RefreshAction.LABEL), 'Search: Refresh', category); @@ -826,7 +827,21 @@ configurationRegistry.registerConfiguration({ type: 'boolean', default: false, description: nls.localize('search.enableSearchEditorPreview', "Experimental: When enabled, allows opening workspace search results in an editor.") - } + }, + 'search.sortOrder': { + 'type': 'string', + 'enum': [SearchSortOrder.Default, SearchSortOrder.FileNames, SearchSortOrder.Type, SearchSortOrder.Modified, SearchSortOrder.CountDescending, SearchSortOrder.CountAscending], + 'default': SearchSortOrder.Default, + 'enumDescriptions': [ + nls.localize('searchSortOrder.default', 'Results are sorted by folder and file names, in alphabetical order.'), + nls.localize('searchSortOrder.filesOnly', 'Results are sorted by file names ignoring folder order, in alphabetical order.'), + nls.localize('searchSortOrder.type', 'Results are sorted by file extensions, in alphabetical order.'), + nls.localize('searchSortOrder.modified', 'Results are sorted by file last modified date, in descending order.'), + nls.localize('searchSortOrder.countDescending', 'Results are sorted by count per file, in descending order.'), + nls.localize('searchSortOrder.countAscending', 'Results are sorted by count per file, in ascending order.') + ], + 'description': nls.localize('search.sortOrder', "Controls sorting order of search results.") + }, } }); diff --git a/src/vs/workbench/contrib/search/browser/searchActions.ts b/src/vs/workbench/contrib/search/browser/searchActions.ts index be6d85b95d..7d99f67fae 100644 --- a/src/vs/workbench/contrib/search/browser/searchActions.ts +++ b/src/vs/workbench/contrib/search/browser/searchActions.ts @@ -367,6 +367,92 @@ export class CollapseDeepestExpandedLevelAction extends Action { } } +export class ExpandAllAction extends Action { + + static readonly ID: string = 'search.action.expandSearchResults'; + static LABEL: string = nls.localize('ExpandAllAction.label', "Expand All"); + + constructor(id: string, label: string, + @IViewletService private readonly viewletService: IViewletService, + @IPanelService private readonly panelService: IPanelService + ) { + super(id, label, 'search-action codicon-expand-all'); + this.update(); + } + + update(): void { + const searchView = getSearchView(this.viewletService, this.panelService); + this.enabled = !!searchView && searchView.hasSearchResults(); + } + + run(): Promise { + const searchView = getSearchView(this.viewletService, this.panelService); + if (searchView) { + const viewer = searchView.getControl(); + viewer.expandAll(); + viewer.domFocus(); + viewer.focusFirst(); + } + return Promise.resolve(undefined); + } +} + +export class ToggleCollapseAndExpandAction extends Action { + static readonly ID: string = 'search.action.collapseOrExpandSearchResults'; + static LABEL: string = nls.localize('ToggleCollapseAndExpandAction.label', "Toggle Collapse and Expand"); + + // Cache to keep from crawling the tree too often. + private action: CollapseDeepestExpandedLevelAction | ExpandAllAction | undefined; + + constructor(id: string, label: string, + private collapseAction: CollapseDeepestExpandedLevelAction, + private expandAction: ExpandAllAction, + @IViewletService private readonly viewletService: IViewletService, + @IPanelService private readonly panelService: IPanelService + ) { + super(id, label, collapseAction.class); + this.update(); + } + + update(): void { + const searchView = getSearchView(this.viewletService, this.panelService); + this.enabled = !!searchView && searchView.hasSearchResults(); + this.onTreeCollapseStateChange(); + } + + onTreeCollapseStateChange() { + this.action = undefined; + this.determineAction(); + } + + private determineAction(): CollapseDeepestExpandedLevelAction | ExpandAllAction { + if (this.action !== undefined) { return this.action; } + this.action = this.isSomeCollapsible() ? this.collapseAction : this.expandAction; + this.class = this.action.class; + return this.action; + } + + private isSomeCollapsible(): boolean { + const searchView = getSearchView(this.viewletService, this.panelService); + if (searchView) { + const viewer = searchView.getControl(); + const navigator = viewer.navigate(); + let node = navigator.first(); + do { + if (!viewer.isCollapsed(node)) { + return true; + } + } while (node = navigator.next()); + } + return false; + } + + + async run(): Promise { + await this.determineAction().run(); + } +} + export class ClearSearchResultsAction extends Action { static readonly ID: string = 'search.action.clearSearchResults'; diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index e9ca2b671c..9b0140d322 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -34,7 +34,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { TreeResourceNavigator2, WorkbenchObjectTree, getSelectionKeyboardEvent } from 'vs/platform/list/browser/listService'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IProgressService, IProgressStep, IProgress } from 'vs/platform/progress/common/progress'; -import { IPatternInfo, ISearchComplete, ISearchConfiguration, ISearchConfigurationProperties, ITextQuery, VIEW_ID, VIEWLET_ID } from 'vs/workbench/services/search/common/search'; +import { IPatternInfo, ISearchComplete, ISearchConfiguration, ISearchConfigurationProperties, ITextQuery, VIEW_ID, VIEWLET_ID, SearchSortOrder } from 'vs/workbench/services/search/common/search'; import { ISearchHistoryService, ISearchHistoryValues } from 'vs/workbench/contrib/search/common/searchHistoryService'; import { diffInserted, diffInsertedOutline, diffRemoved, diffRemovedOutline, editorFindMatchHighlight, editorFindMatchHighlightBorder, listActiveSelectionForeground, foreground } from 'vs/platform/theme/common/colorRegistry'; import { ICssStyleCollector, ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; @@ -43,7 +43,7 @@ import { OpenFileFolderAction, OpenFolderAction } from 'vs/workbench/browser/act import { ResourceLabels } from 'vs/workbench/browser/labels'; import { IEditor } from 'vs/workbench/common/editor'; import { ExcludePatternInputWidget, PatternInputWidget } from 'vs/workbench/contrib/search/browser/patternInputWidget'; -import { CancelSearchAction, ClearSearchResultsAction, CollapseDeepestExpandedLevelAction, RefreshAction, IFindInFilesArgs, OpenResultsInEditorAction, appendKeyBindingLabel } from 'vs/workbench/contrib/search/browser/searchActions'; +import { CancelSearchAction, ClearSearchResultsAction, CollapseDeepestExpandedLevelAction, RefreshAction, IFindInFilesArgs, OpenResultsInEditorAction, appendKeyBindingLabel, ExpandAllAction, ToggleCollapseAndExpandAction } from 'vs/workbench/contrib/search/browser/searchActions'; import { FileMatchRenderer, FolderMatchRenderer, MatchRenderer, SearchAccessibilityProvider, SearchDelegate, SearchDND } from 'vs/workbench/contrib/search/browser/searchResultsView'; import { ISearchWidgetOptions, SearchWidget } from 'vs/workbench/contrib/search/browser/searchWidget'; import * as Constants from 'vs/workbench/contrib/search/common/constants'; @@ -115,6 +115,7 @@ export class SearchView extends ViewPane { private state: SearchUIState = SearchUIState.Idle; private actions: Array = []; + private toggleCollapseAction: ToggleCollapseAndExpandAction; private cancelAction: CancelSearchAction; private refreshAction: RefreshAction; private contextMenu: IMenu | null = null; @@ -144,6 +145,8 @@ export class SearchView extends ViewPane { private currentSearchQ = Promise.resolve(); private addToSearchHistoryDelayer: Delayer; + private toggleCollapseStateDelayer: Delayer; + constructor( private position: SearchViewPosition, options: IViewPaneOptions, @@ -194,6 +197,16 @@ export class SearchView extends ViewPane { this.enableSearchEditorPreview.set(this.searchConfig.enableSearchEditorPreview); } }); + this.configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('search.sortOrder')) { + if (this.searchConfig.sortOrder === SearchSortOrder.Modified) { + // If changing away from modified, remove all fileStats + // so that updated files are re-retrieved next time. + this.removeFileStats(); + } + this.refreshTree(); + } + }); this.viewModel = this._register(this.searchWorkbenchService.searchModel); this.queryBuilder = this.instantiationService.createInstance(QueryBuilder); @@ -208,10 +221,13 @@ export class SearchView extends ViewPane { this.delayedRefresh = this._register(new Delayer(250)); this.addToSearchHistoryDelayer = this._register(new Delayer(2000)); + this.toggleCollapseStateDelayer = this._register(new Delayer(100)); + + const collapseDeepestExpandedLevelAction = this.instantiationService.createInstance(CollapseDeepestExpandedLevelAction, CollapseDeepestExpandedLevelAction.ID, CollapseDeepestExpandedLevelAction.LABEL); + const expandAllAction = this.instantiationService.createInstance(ExpandAllAction, ExpandAllAction.ID, ExpandAllAction.LABEL); this.actions = [ this._register(this.instantiationService.createInstance(ClearSearchResultsAction, ClearSearchResultsAction.ID, ClearSearchResultsAction.LABEL)), - this._register(this.instantiationService.createInstance(CollapseDeepestExpandedLevelAction, CollapseDeepestExpandedLevelAction.ID, CollapseDeepestExpandedLevelAction.LABEL)) ]; if (this.searchConfig.enableSearchEditorPreview) { @@ -222,6 +238,7 @@ export class SearchView extends ViewPane { this.refreshAction = this._register(this.instantiationService.createInstance(RefreshAction, RefreshAction.ID, RefreshAction.LABEL)); this.cancelAction = this._register(this.instantiationService.createInstance(CancelSearchAction, CancelSearchAction.ID, CancelSearchAction.LABEL)); + this.toggleCollapseAction = this._register(this.instantiationService.createInstance(ToggleCollapseAndExpandAction, ToggleCollapseAndExpandAction.ID, ToggleCollapseAndExpandAction.LABEL, collapseDeepestExpandedLevelAction, expandAllAction)); } getContainer(): HTMLElement { @@ -390,6 +407,7 @@ export class SearchView extends ViewPane { this.refreshAction.update(); this.cancelAction.update(); + this.toggleCollapseAction.update(); super.updateActions(); } @@ -495,13 +513,25 @@ export class SearchView extends ViewPane { const collapseResults = this.searchConfig.collapseResults; if (!event || event.added || event.removed) { // Refresh whole tree - this.tree.setChildren(null, this.createResultIterator(collapseResults)); + if (this.searchConfig.sortOrder === SearchSortOrder.Modified) { + // Ensure all matches have retrieved their file stat + this.retrieveFileStats() + .then(() => this.tree.setChildren(null, this.createResultIterator(collapseResults))); + } else { + this.tree.setChildren(null, this.createResultIterator(collapseResults)); + } } else { - // FileMatch modified, refresh those elements - event.elements.forEach(element => { - this.tree.setChildren(element, this.createIterator(element, collapseResults)); - this.tree.rerender(element); - }); + // If updated counts affect our search order, re-sort the view. + if (this.searchConfig.sortOrder === SearchSortOrder.CountAscending || + this.searchConfig.sortOrder === SearchSortOrder.CountDescending) { + this.tree.setChildren(null, this.createResultIterator(collapseResults)); + } else { + // FileMatch modified, refresh those elements + event.elements.forEach(element => { + this.tree.setChildren(element, this.createIterator(element, collapseResults)); + this.tree.rerender(element); + }); + } } } @@ -522,9 +552,10 @@ export class SearchView extends ViewPane { } private createFolderIterator(folderMatch: FolderMatch, collapseResults: ISearchConfigurationProperties['collapseResults']): Iterator> { + const sortOrder = this.searchConfig.sortOrder; const filesIt = Iterator.fromArray( folderMatch.matches() - .sort(searchMatchComparer)); + .sort((a, b) => searchMatchComparer(a, b, sortOrder))); return Iterator.map(filesIt, fileMatch => { const children = this.createFileIterator(fileMatch); @@ -699,6 +730,9 @@ export class SearchView extends ViewPane { } })); this._register(this.tree.onContextMenu(e => this.onContextMenu(e))); + this._register(this.tree.onDidChangeCollapseState(() => + this.toggleCollapseStateDelayer.trigger(() => this.toggleCollapseAction.onTreeCollapseStateChange()) + )); const resourceNavigator = this._register(new TreeResourceNavigator2(this.tree, { openOnFocus: true, openOnSelection: false })); this._register(Event.debounce(resourceNavigator.onDidOpenResource, (last, event) => event, 75, true)(options => { @@ -1692,14 +1726,23 @@ export class SearchView extends ViewPane { } private onFilesChanged(e: FileChangesEvent): void { - if (!this.viewModel || !e.gotDeleted()) { + if (!this.viewModel || (this.searchConfig.sortOrder !== SearchSortOrder.Modified && !e.gotDeleted())) { return; } const matches = this.viewModel.searchResult.matches(); + if (e.gotDeleted()) { + const deletedMatches = matches.filter(m => e.contains(m.resource, FileChangeType.DELETED)); - const changedMatches = matches.filter(m => e.contains(m.resource, FileChangeType.DELETED)); - this.viewModel.searchResult.remove(changedMatches); + this.viewModel.searchResult.remove(deletedMatches); + } else { + // Check if the changed file contained matches + const changedMatches = matches.filter(m => e.contains(m.resource)); + if (changedMatches.length && this.searchConfig.sortOrder === SearchSortOrder.Modified) { + // No matches need to be removed, but modified files need to have their file stat updated. + this.updateFileStats(changedMatches).then(() => this.refreshTree()); + } + } } getActions(): IAction[] { @@ -1707,7 +1750,8 @@ export class SearchView extends ViewPane { this.state === SearchUIState.SlowSearch ? this.cancelAction : this.refreshAction, - ...this.actions + ...this.actions, + this.toggleCollapseAction ]; } @@ -1771,6 +1815,22 @@ export class SearchView extends ViewPane { super.saveState(); } + private async retrieveFileStats(): Promise { + const files = this.searchResult.matches().filter(f => !f.fileStat).map(f => f.resolveFileStat(this.fileService)); + await Promise.all(files); + } + + private async updateFileStats(elements: FileMatch[]): Promise { + const files = elements.map(f => f.resolveFileStat(this.fileService)); + await Promise.all(files); + } + + private removeFileStats(): void { + for (const fileMatch of this.searchResult.matches()) { + fileMatch.fileStat = undefined; + } + } + dispose(): void { this.isDisposed = true; this.saveState(); diff --git a/src/vs/workbench/contrib/search/common/searchModel.ts b/src/vs/workbench/contrib/search/common/searchModel.ts index 3d9228cda6..f1f6700ce7 100644 --- a/src/vs/workbench/contrib/search/common/searchModel.ts +++ b/src/vs/workbench/contrib/search/common/searchModel.ts @@ -20,15 +20,17 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IProgress, IProgressStep } from 'vs/platform/progress/common/progress'; import { ReplacePattern } from 'vs/workbench/services/search/common/replace'; -import { IFileMatch, IPatternInfo, ISearchComplete, ISearchProgressItem, ISearchConfigurationProperties, ISearchService, ITextQuery, ITextSearchPreviewOptions, ITextSearchMatch, ITextSearchStats, resultIsMatch, ISearchRange, OneLineRange, ITextSearchContext, ITextSearchResult } from 'vs/workbench/services/search/common/search'; +import { IFileMatch, IPatternInfo, ISearchComplete, ISearchProgressItem, ISearchConfigurationProperties, ISearchService, ITextQuery, ITextSearchPreviewOptions, ITextSearchMatch, ITextSearchStats, resultIsMatch, ISearchRange, OneLineRange, ITextSearchContext, ITextSearchResult, SearchSortOrder } from 'vs/workbench/services/search/common/search'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { overviewRulerFindMatchForeground, minimapFindMatch } from 'vs/platform/theme/common/colorRegistry'; import { themeColorFromId } from 'vs/platform/theme/common/themeService'; import { IReplaceService } from 'vs/workbench/contrib/search/common/replace'; -import { editorMatchesToTextSearchResults } from 'vs/workbench/services/search/common/searchHelpers'; +import { editorMatchesToTextSearchResults, addContextToEditorMatches } from 'vs/workbench/services/search/common/searchHelpers'; import { withNullAsUndefined } from 'vs/base/common/types'; import { memoize } from 'vs/base/common/decorators'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { compareFileNames, compareFileExtensions, comparePaths } from 'vs/base/common/comparers'; +import { IFileService, IFileStatWithMetadata } from 'vs/platform/files/common/files'; export class Match { @@ -188,6 +190,7 @@ export class FileMatch extends Disposable implements IFileMatch { readonly onDispose: Event = this._onDispose.event; private _resource: URI; + private _fileStat?: IFileStatWithMetadata; private _model: ITextModel | null = null; private _modelListener: IDisposable | null = null; private _matches: Map; @@ -306,6 +309,11 @@ export class FileMatch extends Disposable implements IFileMatch { }); }); + this.addContext( + addContextToEditorMatches(textSearchResults, this._model, this.parent().parent().query!) + .filter((result => !resultIsMatch(result)) as ((a: any) => a is ITextSearchContext)) + .map(context => ({ ...context, lineNumber: context.lineNumber + 1 }))); + this._onChange.fire(modelChange); this.updateHighlights(); } @@ -406,6 +414,18 @@ export class FileMatch extends Disposable implements IFileMatch { } } + async resolveFileStat(fileService: IFileService): Promise { + this._fileStat = await fileService.resolve(this.resource, { resolveMetadata: true }); + } + + public get fileStat(): IFileStatWithMetadata | undefined { + return this._fileStat; + } + + public set fileStat(stat: IFileStatWithMetadata | undefined) { + this._fileStat = stat; + } + dispose(): void { this.setSelectedMatch(null); this.unbindModel(); @@ -628,13 +648,31 @@ export class FolderMatchWithResource extends FolderMatch { * Compares instances of the same match type. Different match types should not be siblings * and their sort order is undefined. */ -export function searchMatchComparer(elementA: RenderableMatch, elementB: RenderableMatch): number { +export function searchMatchComparer(elementA: RenderableMatch, elementB: RenderableMatch, sortOrder: SearchSortOrder = SearchSortOrder.Default): number { if (elementA instanceof FolderMatch && elementB instanceof FolderMatch) { return elementA.index() - elementB.index(); } if (elementA instanceof FileMatch && elementB instanceof FileMatch) { - return elementA.resource.fsPath.localeCompare(elementB.resource.fsPath) || elementA.name().localeCompare(elementB.name()); + switch (sortOrder) { + case SearchSortOrder.CountDescending: + return elementB.count() - elementA.count(); + case SearchSortOrder.CountAscending: + return elementA.count() - elementB.count(); + case SearchSortOrder.Type: + return compareFileExtensions(elementA.name(), elementB.name()); + case SearchSortOrder.FileNames: + return compareFileNames(elementA.name(), elementB.name()); + case SearchSortOrder.Modified: + const fileStatA = elementA.fileStat; + const fileStatB = elementB.fileStat; + if (fileStatA && fileStatB) { + return fileStatB.mtime - fileStatA.mtime; + } + // Fall through otherwise + default: + return comparePaths(elementA.resource.fsPath, elementB.resource.fsPath) || compareFileNames(elementA.name(), elementB.name()); + } } if (elementA instanceof Match && elementB instanceof Match) { diff --git a/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts b/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts index a5ffa0da2e..dc45d5cd1b 100644 --- a/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts @@ -9,11 +9,12 @@ import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; -import { IFileMatch, ITextSearchMatch, OneLineRange, QueryType } from 'vs/workbench/services/search/common/search'; +import { IFileMatch, ITextSearchMatch, OneLineRange, QueryType, SearchSortOrder } from 'vs/workbench/services/search/common/search'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { TestWorkspace } from 'vs/platform/workspace/test/common/testWorkspace'; import { FileMatch, Match, searchMatchComparer, SearchResult } from 'vs/workbench/contrib/search/common/searchModel'; import { TestContextService } from 'vs/workbench/test/workbenchTestServices'; +import { isWindows } from 'vs/base/common/platform'; suite('Search - Viewlet', () => { let instantiation: TestInstantiationService; @@ -63,9 +64,9 @@ suite('Search - Viewlet', () => { }); test('Comparer', () => { - let fileMatch1 = aFileMatch('C:\\foo'); - let fileMatch2 = aFileMatch('C:\\with\\path'); - let fileMatch3 = aFileMatch('C:\\with\\path\\foo'); + let fileMatch1 = aFileMatch(isWindows ? 'C:\\foo' : '/c/foo'); + let fileMatch2 = aFileMatch(isWindows ? 'C:\\with\\path' : '/c/with/path'); + let fileMatch3 = aFileMatch(isWindows ? 'C:\\with\\path\\foo' : '/c/with/path/foo'); let lineMatch1 = new Match(fileMatch1, ['bar'], new OneLineRange(0, 1, 1), new OneLineRange(0, 1, 1)); let lineMatch2 = new Match(fileMatch1, ['bar'], new OneLineRange(0, 1, 1), new OneLineRange(2, 1, 1)); let lineMatch3 = new Match(fileMatch1, ['bar'], new OneLineRange(0, 1, 1), new OneLineRange(2, 1, 1)); @@ -80,9 +81,23 @@ suite('Search - Viewlet', () => { assert(searchMatchComparer(lineMatch2, lineMatch3) === 0); }); + test('Advanced Comparer', () => { + let fileMatch1 = aFileMatch(isWindows ? 'C:\\with\\path\\foo10' : '/c/with/path/foo10'); + let fileMatch2 = aFileMatch(isWindows ? 'C:\\with\\path2\\foo1' : '/c/with/path2/foo1'); + let fileMatch3 = aFileMatch(isWindows ? 'C:\\with\\path2\\bar.a' : '/c/with/path2/bar.a'); + let fileMatch4 = aFileMatch(isWindows ? 'C:\\with\\path2\\bar.b' : '/c/with/path2/bar.b'); + + // By default, path < path2 + assert(searchMatchComparer(fileMatch1, fileMatch2) < 0); + // By filenames, foo10 > foo1 + assert(searchMatchComparer(fileMatch1, fileMatch2, SearchSortOrder.FileNames) > 0); + // By type, bar.a < bar.b + assert(searchMatchComparer(fileMatch3, fileMatch4, SearchSortOrder.Type) < 0); + }); + function aFileMatch(path: string, searchResult?: SearchResult, ...lineMatches: ITextSearchMatch[]): FileMatch { let rawMatch: IFileMatch = { - resource: uri.file('C:\\' + path), + resource: uri.file(path), results: lineMatches }; return instantiation.createInstance(FileMatch, null, null, null, searchResult, rawMatch); diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index 9ab398a0d0..c4564eb4cd 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -1667,7 +1667,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer if (this.executionEngine === ExecutionEngine.Process) { return this.emptyWorkspaceTaskResults(workspaceFolder); } - const configuration = this.testParseExternalConfig(this.configurationService.inspect('tasks').workspace, nls.localize('TasksSystem.locationWorkspaceConfig', 'workspace file')); + const configuration = this.testParseExternalConfig(this.configurationService.inspect('tasks').workspaceValue, nls.localize('TasksSystem.locationWorkspaceConfig', 'workspace file')); let customizedTasks: { byIdentifier: IStringDictionary; } = { byIdentifier: Object.create(null) }; @@ -1686,7 +1686,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer if (this.executionEngine === ExecutionEngine.Process) { return this.emptyWorkspaceTaskResults(workspaceFolder); } - const configuration = this.testParseExternalConfig(this.configurationService.inspect('tasks').user, nls.localize('TasksSystem.locationUserConfig', 'user settings')); + const configuration = this.testParseExternalConfig(this.configurationService.inspect('tasks').userValue, nls.localize('TasksSystem.locationUserConfig', 'user settings')); let customizedTasks: { byIdentifier: IStringDictionary; } = { byIdentifier: Object.create(null) }; @@ -1789,7 +1789,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer protected getConfiguration(workspaceFolder: IWorkspaceFolder): { config: TaskConfig.ExternalTaskRunnerConfiguration | undefined; hasParseErrors: boolean } { let result = this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY - ? Objects.deepClone(this.configurationService.inspect('tasks', { resource: workspaceFolder.uri }).workspaceFolder) + ? Objects.deepClone(this.configurationService.inspect('tasks', { resource: workspaceFolder.uri }).workspaceFolderValue) : undefined; if (!result) { return { config: undefined, hasParseErrors: false }; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts b/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts index 35a66859a9..503fb2510f 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts @@ -196,13 +196,13 @@ export class TerminalConfigHelper implements IBrowserTerminalConfigHelper { // Check if workspace setting exists and whether it's whitelisted let isWorkspaceShellAllowed: boolean | undefined = false; - if (shellConfigValue.workspace !== undefined || shellArgsConfigValue.workspace !== undefined || envConfigValue.workspace !== undefined) { + if (shellConfigValue.workspaceValue !== undefined || shellArgsConfigValue.workspaceValue !== undefined || envConfigValue.workspaceValue !== undefined) { isWorkspaceShellAllowed = this.isWorkspaceShellAllowed(undefined); } // Always allow [] args as it would lead to an odd error message and should not be dangerous - if (shellConfigValue.workspace === undefined && envConfigValue.workspace === undefined && - shellArgsConfigValue.workspace && shellArgsConfigValue.workspace.length === 0) { + if (shellConfigValue.workspaceValue === undefined && envConfigValue.workspaceValue === undefined && + shellArgsConfigValue.workspaceValue && shellArgsConfigValue.workspaceValue.length === 0) { isWorkspaceShellAllowed = true; } @@ -210,16 +210,16 @@ export class TerminalConfigHelper implements IBrowserTerminalConfigHelper { // permission if (isWorkspaceShellAllowed === undefined) { let shellString: string | undefined; - if (shellConfigValue.workspace) { - shellString = `shell: "${shellConfigValue.workspace}"`; + if (shellConfigValue.workspaceValue) { + shellString = `shell: "${shellConfigValue.workspaceValue}"`; } let argsString: string | undefined; - if (shellArgsConfigValue.workspace) { - argsString = `shellArgs: [${shellArgsConfigValue.workspace.map(v => '"' + v + '"').join(', ')}]`; + if (shellArgsConfigValue.workspaceValue) { + argsString = `shellArgs: [${shellArgsConfigValue.workspaceValue.map(v => '"' + v + '"').join(', ')}]`; } let envString: string | undefined; - if (envConfigValue.workspace) { - envString = `env: {${Object.keys(envConfigValue.workspace).map(k => `${k}:${envConfigValue.workspace![k]}`).join(', ')}}`; + if (envConfigValue.workspaceValue) { + envString = `env: {${Object.keys(envConfigValue.workspaceValue).map(k => `${k}:${envConfigValue.workspaceValue![k]}`).join(', ')}}`; } // Should not be localized as it's json-like syntax referencing settings keys const workspaceConfigStrings: string[] = []; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index d0634ac8e0..f1cf682306 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -138,8 +138,18 @@ export const DEFAULT_COMMANDS_TO_SKIP_SHELL: string[] = [ 'workbench.action.debug.stepInto', 'workbench.action.debug.stepOut', 'workbench.action.debug.stepOver', + 'workbench.action.nextEditor', + 'workbench.action.previousEditor', + 'workbench.action.nextEditorInGroup', + 'workbench.action.previousEditorInGroup', + 'workbench.action.openNextRecentlyUsedEditor', + 'workbench.action.openPreviousRecentlyUsedEditor', 'workbench.action.openNextRecentlyUsedEditorInGroup', 'workbench.action.openPreviousRecentlyUsedEditorInGroup', + 'workbench.action.quickOpenNextRecentlyUsedEditor', + 'workbench.action.quickOpenPreviousRecentlyUsedEditor', + 'workbench.action.quickOpenNextRecentlyUsedEditorInGroup', + 'workbench.action.quickOpenPreviousRecentlyUsedEditorInGroup', 'workbench.action.focusActiveEditorGroup', 'workbench.action.focusFirstEditorGroup', 'workbench.action.focusLastEditorGroup', diff --git a/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts b/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts index 54346b42ff..c20b2be958 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[], value?: string | string[], default?: string | string[] }, + fetchSetting: (key: string) => { userValue?: string | string[], value?: string | string[], defaultValue?: string | string[] }, isWorkspaceShellAllowed: boolean, defaultShell: string, isWoW64: boolean, @@ -294,7 +294,7 @@ export function getDefaultShell( } export function getDefaultShellArgs( - fetchSetting: (key: string) => { user?: string | string[], value?: string | string[], default?: string | string[] }, + fetchSetting: (key: string) => { userValue?: string | string[], value?: string | string[], defaultValue?: string | string[] }, isWorkspaceShellAllowed: boolean, useAutomationShell: boolean, lastActiveWorkspace: IWorkspaceFolder | undefined, @@ -310,7 +310,10 @@ export function getDefaultShellArgs( const platformKey = platformOverride === platform.Platform.Windows ? 'windows' : platformOverride === platform.Platform.Mac ? 'osx' : 'linux'; const shellArgsConfigValue = fetchSetting(`terminal.integrated.shellArgs.${platformKey}`); - let args = ((isWorkspaceShellAllowed ? shellArgsConfigValue.value : shellArgsConfigValue.user) || shellArgsConfigValue.default); + let args = ((isWorkspaceShellAllowed ? shellArgsConfigValue.value : shellArgsConfigValue.userValue) || shellArgsConfigValue.defaultValue); + if (!args) { + return []; + } if (typeof args === 'string' && platformOverride === platform.Platform.Windows) { return configurationResolverService ? configurationResolverService.resolve(lastActiveWorkspace, args) : args; } @@ -330,21 +333,21 @@ export function getDefaultShellArgs( } function getShellSetting( - fetchSetting: (key: string) => { user?: string | string[], value?: string | string[], default?: string | string[] }, + fetchSetting: (key: string) => { userValue?: string | string[], value?: string | string[], defaultValue?: string | string[] }, isWorkspaceShellAllowed: boolean, type: 'automationShell' | 'shell', platformOverride: platform.Platform = platform.platform, ): string | null { const platformKey = platformOverride === platform.Platform.Windows ? 'windows' : platformOverride === platform.Platform.Mac ? 'osx' : 'linux'; const shellConfigValue = fetchSetting(`terminal.integrated.${type}.${platformKey}`); - const executable = (isWorkspaceShellAllowed ? shellConfigValue.value : shellConfigValue.user) || (shellConfigValue.default); + const executable = (isWorkspaceShellAllowed ? shellConfigValue.value : shellConfigValue.userValue) || (shellConfigValue.defaultValue); return executable; } export function createTerminalEnvironment( shellLaunchConfig: IShellLaunchConfig, lastActiveWorkspace: IWorkspaceFolder | null, - envFromConfig: { user?: ITerminalEnvironment, value?: ITerminalEnvironment, default?: ITerminalEnvironment }, + envFromConfig: { userValue?: ITerminalEnvironment, value?: ITerminalEnvironment, defaultValue?: ITerminalEnvironment }, configurationResolverService: IConfigurationResolverService | undefined, isWorkspaceShellAllowed: boolean, version: string | undefined, @@ -362,7 +365,7 @@ export function createTerminalEnvironment( // const platformKey = platform.isWindows ? 'windows' : (platform.isMacintosh ? 'osx' : 'linux'); // const envFromConfigValue = this._workspaceConfigurationService.inspect(`terminal.integrated.env.${platformKey}`); - const allowedEnvFromConfig = { ...(isWorkspaceShellAllowed ? envFromConfig.value : envFromConfig.user) }; + const allowedEnvFromConfig = { ...(isWorkspaceShellAllowed ? envFromConfig.value : envFromConfig.userValue) }; // Resolve env vars from config and shell if (configurationResolverService) { diff --git a/src/vs/workbench/contrib/terminal/test/node/terminalEnvironment.test.ts b/src/vs/workbench/contrib/terminal/test/node/terminalEnvironment.test.ts index 81863721d4..833b14e4c2 100644 --- a/src/vs/workbench/contrib/terminal/test/node/terminalEnvironment.test.ts +++ b/src/vs/workbench/contrib/terminal/test/node/terminalEnvironment.test.ts @@ -213,7 +213,7 @@ suite('Workbench - TerminalEnvironment', () => { test('should change Sysnative to System32 in non-WoW64 systems', () => { const shell = getDefaultShell(key => { return ({ - 'terminal.integrated.shell.windows': { user: 'C:\\Windows\\Sysnative\\cmd.exe', value: undefined, default: undefined } + 'terminal.integrated.shell.windows': { userValue: 'C:\\Windows\\Sysnative\\cmd.exe', value: undefined, defaultValue: undefined } } as any)[key]; }, false, 'DEFAULT', false, 'C:\\Windows', undefined, undefined, {} as any, false, platform.Platform.Windows); assert.equal(shell, 'C:\\Windows\\System32\\cmd.exe'); @@ -222,7 +222,7 @@ suite('Workbench - TerminalEnvironment', () => { test('should not change Sysnative to System32 in WoW64 systems', () => { const shell = getDefaultShell(key => { return ({ - 'terminal.integrated.shell.windows': { user: 'C:\\Windows\\Sysnative\\cmd.exe', value: undefined, default: undefined } + 'terminal.integrated.shell.windows': { userValue: 'C:\\Windows\\Sysnative\\cmd.exe', value: undefined, defaultValue: undefined } } as any)[key]; }, false, 'DEFAULT', true, 'C:\\Windows', undefined, undefined, {} as any, false, platform.Platform.Windows); assert.equal(shell, 'C:\\Windows\\Sysnative\\cmd.exe'); @@ -231,22 +231,22 @@ suite('Workbench - TerminalEnvironment', () => { test('should use automationShell when specified', () => { const shell1 = getDefaultShell(key => { return ({ - 'terminal.integrated.shell.windows': { user: 'shell', value: undefined, default: undefined }, - 'terminal.integrated.automationShell.windows': { user: undefined, value: undefined, default: undefined } + 'terminal.integrated.shell.windows': { userValue: 'shell', value: undefined, defaultValue: undefined }, + 'terminal.integrated.automationShell.windows': { userValue: undefined, value: undefined, defaultValue: undefined } } as any)[key]; }, false, 'DEFAULT', false, 'C:\\Windows', undefined, undefined, {} as any, false, platform.Platform.Windows); assert.equal(shell1, 'shell', 'automationShell was false'); const shell2 = getDefaultShell(key => { return ({ - 'terminal.integrated.shell.windows': { user: 'shell', value: undefined, default: undefined }, - 'terminal.integrated.automationShell.windows': { user: undefined, value: undefined, default: undefined } + 'terminal.integrated.shell.windows': { userValue: 'shell', value: undefined, defaultValue: undefined }, + 'terminal.integrated.automationShell.windows': { userValue: undefined, value: undefined, defaultValue: undefined } } as any)[key]; }, false, 'DEFAULT', false, 'C:\\Windows', undefined, undefined, {} as any, true, platform.Platform.Windows); assert.equal(shell2, 'shell', 'automationShell was true'); const shell3 = getDefaultShell(key => { return ({ - 'terminal.integrated.shell.windows': { user: 'shell', value: undefined, default: undefined }, - 'terminal.integrated.automationShell.windows': { user: 'automationShell', value: undefined, default: undefined } + 'terminal.integrated.shell.windows': { userValue: 'shell', value: undefined, defaultValue: undefined }, + 'terminal.integrated.automationShell.windows': { userValue: 'automationShell', value: undefined, defaultValue: undefined } } as any)[key]; }, false, 'DEFAULT', false, 'C:\\Windows', undefined, undefined, {} as any, true, platform.Platform.Windows); assert.equal(shell3, 'automationShell', 'automationShell was true and specified in settings'); diff --git a/src/vs/workbench/contrib/themes/browser/themes.contribution.ts b/src/vs/workbench/contrib/themes/browser/themes.contribution.ts index 5646055f3e..0214ca10ae 100644 --- a/src/vs/workbench/contrib/themes/browser/themes.contribution.ts +++ b/src/vs/workbench/contrib/themes/browser/themes.contribution.ts @@ -64,7 +64,7 @@ export class SelectColorThemeAction extends Action { let target: ConfigurationTarget | undefined = undefined; if (applyTheme) { const confValue = this.configurationService.inspect(COLOR_THEME_SETTING); - target = typeof confValue.workspace !== 'undefined' ? ConfigurationTarget.WORKSPACE : ConfigurationTarget.USER; + target = typeof confValue.workspaceValue !== 'undefined' ? ConfigurationTarget.WORKSPACE : ConfigurationTarget.USER; } this.themeService.setColorTheme(themeId, target).then(undefined, @@ -150,7 +150,7 @@ class SelectIconThemeAction extends Action { let target: ConfigurationTarget | undefined = undefined; if (applyTheme) { const confValue = this.configurationService.inspect(ICON_THEME_SETTING); - target = typeof confValue.workspace !== 'undefined' ? ConfigurationTarget.WORKSPACE : ConfigurationTarget.USER; + target = typeof confValue.workspaceValue !== 'undefined' ? ConfigurationTarget.WORKSPACE : ConfigurationTarget.USER; } this.themeService.setFileIconTheme(themeId, target).then(undefined, err => { diff --git a/src/vs/workbench/contrib/webview/browser/webviewEditorInputFactory.ts b/src/vs/workbench/contrib/webview/browser/webviewEditorInputFactory.ts index c0239fd121..502f59cda0 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewEditorInputFactory.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewEditorInputFactory.ts @@ -36,6 +36,10 @@ export class WebviewEditorInputFactory implements IEditorInputFactory { @IWebviewWorkbenchService private readonly _webviewWorkbenchService: IWebviewWorkbenchService ) { } + public canSerialize(input: WebviewInput): boolean { + return this._webviewWorkbenchService.shouldPersist(input); + } + public serialize(input: WebviewInput): string | undefined { if (!this._webviewWorkbenchService.shouldPersist(input)) { return undefined; diff --git a/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts b/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts index acd0ce9ac4..45c54d345b 100644 --- a/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts +++ b/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts @@ -109,7 +109,7 @@ export class WelcomePageContribution implements IWorkbenchContribution { function isWelcomePageEnabled(configurationService: IConfigurationService, contextService: IWorkspaceContextService) { const startupEditor = configurationService.inspect(configurationKey); - if (!startupEditor.user && !startupEditor.workspace) { + if (!startupEditor.userValue && !startupEditor.workspaceValue) { const welcomeEnabled = configurationService.inspect(oldConfigurationKey); if (welcomeEnabled.value !== undefined && welcomeEnabled.value !== null) { return welcomeEnabled.value; @@ -598,6 +598,10 @@ export class WelcomeInputFactory implements IEditorInputFactory { static readonly ID = welcomeInputTypeId; + public canSerialize(editorInput: EditorInput): boolean { + return true; + } + public serialize(editorInput: EditorInput): string { return '{}'; } diff --git a/src/vs/workbench/contrib/welcome/walkThrough/browser/editor/editorWalkThrough.ts b/src/vs/workbench/contrib/welcome/walkThrough/browser/editor/editorWalkThrough.ts index 191176302e..b1ec7f4caa 100644 --- a/src/vs/workbench/contrib/welcome/walkThrough/browser/editor/editorWalkThrough.ts +++ b/src/vs/workbench/contrib/welcome/walkThrough/browser/editor/editorWalkThrough.ts @@ -50,6 +50,10 @@ export class EditorWalkThroughInputFactory implements IEditorInputFactory { static readonly ID = typeId; + public canSerialize(editorInput: EditorInput): boolean { + return true; + } + public serialize(editorInput: EditorInput): string { return '{}'; } diff --git a/src/vs/workbench/electron-browser/window.ts b/src/vs/workbench/electron-browser/window.ts index 439b5e0085..13921cafb2 100644 --- a/src/vs/workbench/electron-browser/window.ts +++ b/src/vs/workbench/electron-browser/window.ts @@ -283,6 +283,7 @@ export class ElectronWindow extends Disposable { })); } + // Detect minimize / maximize this._register(Event.any( Event.map(Event.filter(this.electronService.onWindowMaximize, id => id === this.electronEnvironmentService.windowId), () => true), Event.map(Event.filter(this.electronService.onWindowUnmaximize, id => id === this.electronEnvironmentService.windowId), () => false) diff --git a/src/vs/workbench/services/backup/common/backupFileService.ts b/src/vs/workbench/services/backup/common/backupFileService.ts index c33c52589c..beb1b5eb9f 100644 --- a/src/vs/workbench/services/backup/common/backupFileService.ts +++ b/src/vs/workbench/services/backup/common/backupFileService.ts @@ -11,7 +11,7 @@ import { coalesce } from 'vs/base/common/arrays'; import { equals, deepClone } from 'vs/base/common/objects'; import { ResourceQueue } from 'vs/base/common/async'; import { IBackupFileService, IResolvedBackup } from 'vs/workbench/services/backup/common/backup'; -import { IFileService } from 'vs/platform/files/common/files'; +import { IFileService, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; import { ITextSnapshot } from 'vs/editor/common/model'; import { createTextBufferFactoryFromStream, createTextBufferFactoryFromSnapshot } from 'vs/editor/common/model/textModel'; import { keys, ResourceMap } from 'vs/base/common/map'; @@ -285,7 +285,7 @@ class BackupFileServiceImpl implements IBackupFileService { const backupResource = this.toBackupResource(resource); return this.ioOperationQueues.queueFor(backupResource).queue(async () => { - await this.fileService.del(backupResource, { recursive: true }); + await this.doDiscardResource(backupResource); model.remove(backupResource); }); @@ -296,11 +296,21 @@ class BackupFileServiceImpl implements IBackupFileService { const model = await this.ready; - await this.fileService.del(this.backupWorkspacePath, { recursive: true }); + await this.doDiscardResource(this.backupWorkspacePath); model.clear(); } + private async doDiscardResource(resource: URI): Promise { + try { + await this.fileService.del(resource, { recursive: true }); + } catch (error) { + if ((error).fileOperationResult !== FileOperationResult.FILE_NOT_FOUND) { + throw error; // re-throw any other error than file not found which is OK + } + } + } + async getWorkspaceFileBackups(): Promise { const model = await this.ready; diff --git a/src/vs/workbench/services/backup/test/electron-browser/backupFileService.test.ts b/src/vs/workbench/services/backup/test/electron-browser/backupFileService.test.ts index b52720a9e5..de7fc64efc 100644 --- a/src/vs/workbench/services/backup/test/electron-browser/backupFileService.test.ts +++ b/src/vs/workbench/services/backup/test/electron-browser/backupFileService.test.ts @@ -51,14 +51,13 @@ class TestBackupEnvironmentService extends NativeWorkbenchEnvironmentService { constructor(backupPath: string) { super({ ...parseArgs(process.argv, OPTIONS), ...{ backupPath, 'user-data-dir': userdataDir } } as IWindowConfiguration, process.execPath, 0); } - } -class TestBackupFileService extends BackupFileService { +export class NodeTestBackupFileService extends BackupFileService { readonly fileService: IFileService; - constructor(workspace: URI, backupHome: string, workspacesJsonPath: string) { + constructor(workspaceBackupPath: string) { const environmentService = new TestBackupEnvironmentService(workspaceBackupPath); const fileService = new FileService(new NullLogService()); const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService()); @@ -76,10 +75,10 @@ class TestBackupFileService extends BackupFileService { } suite('BackupFileService', () => { - let service: TestBackupFileService; + let service: NodeTestBackupFileService; setup(async () => { - service = new TestBackupFileService(workspaceResource, backupHome, workspacesJsonPath); + service = new NodeTestBackupFileService(workspaceBackupPath); // Delete any existing backups completely and then re-create it. await pfs.rimraf(backupHome, pfs.RimRafMode.MOVE); @@ -141,7 +140,7 @@ suite('BackupFileService', () => { test('should return whether a backup resource exists', async () => { await pfs.mkdirp(path.dirname(fooBackupPath)); fs.writeFileSync(fooBackupPath, 'foo'); - service = new TestBackupFileService(workspaceResource, backupHome, workspacesJsonPath); + service = new NodeTestBackupFileService(workspaceBackupPath); const resource = await service.loadBackupResource(fooFile); assert.ok(resource); assert.equal(path.basename(resource!.fsPath), path.basename(fooBackupPath)); @@ -528,10 +527,10 @@ suite('BackupFileService', () => { suite('BackupFilesModel', () => { - let service: TestBackupFileService; + let service: NodeTestBackupFileService; setup(async () => { - service = new TestBackupFileService(workspaceResource, backupHome, workspacesJsonPath); + service = new NodeTestBackupFileService(workspaceBackupPath); // Delete any existing backups completely and then re-create it. await pfs.rimraf(backupHome, pfs.RimRafMode.MOVE); diff --git a/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts b/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts index c7b6adeeec..98dbc996d6 100644 --- a/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts +++ b/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts @@ -24,6 +24,7 @@ import { ITextFileService } from 'vs/workbench/services/textfile/common/textfile import { ILabelService } from 'vs/platform/label/common/label'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; abstract class Recording { @@ -60,7 +61,7 @@ class ModelEditTask implements IDisposable { } dispose() { - dispose(this._modelReference); + this._modelReference.dispose(); } addEdit(resourceEdit: ResourceTextEdit): void { @@ -140,30 +141,26 @@ class EditorEditTask extends ModelEditTask { class BulkEditModel implements IDisposable { - private _textModelResolverService: ITextModelService; private _edits = new Map(); - private _editor: ICodeEditor | undefined; private _tasks: ModelEditTask[] | undefined; - private _progress: IProgress; constructor( - textModelResolverService: ITextModelService, - editor: ICodeEditor | undefined, + private readonly _editor: ICodeEditor | undefined, + private readonly _progress: IProgress, edits: ResourceTextEdit[], - progress: IProgress + @IEditorWorkerService private readonly _editorWorker: IEditorWorkerService, + @ITextModelService private readonly _textModelResolverService: ITextModelService, ) { - this._textModelResolverService = textModelResolverService; - this._editor = editor; - this._progress = progress; - - edits.forEach(this.addEdit, this); + edits.forEach(this._addEdit, this); } dispose(): void { - this._tasks = dispose(this._tasks!); + if (this._tasks) { + dispose(this._tasks); + } } - addEdit(edit: ResourceTextEdit): void { + private _addEdit(edit: ResourceTextEdit): void { let array = this._edits.get(edit.resource.toString()); if (!array) { array = []; @@ -182,7 +179,7 @@ class BulkEditModel implements IDisposable { const promises: Promise[] = []; this._edits.forEach((value, key) => { - const promise = this._textModelResolverService.createModelReference(URI.parse(key)).then(ref => { + const promise = this._textModelResolverService.createModelReference(URI.parse(key)).then(async ref => { const model = ref.object; if (!model || !model.textEditorModel) { @@ -190,13 +187,24 @@ class BulkEditModel implements IDisposable { } let task: ModelEditTask; + let makeMinimal = false; if (this._editor && this._editor.hasModel() && this._editor.getModel().uri.toString() === model.textEditorModel.uri.toString()) { task = new EditorEditTask(ref, this._editor); + makeMinimal = true; } else { task = new ModelEditTask(ref); } - value.forEach(edit => task.addEdit(edit)); + for (const edit of value) { + if (makeMinimal) { + const newEdits = await this._editorWorker.computeMoreMinimalEdits(edit.resource, edit.edits); + task.addEdit({ ...edit, edits: newEdits! }); + + } else { + task.addEdit(edit); + } + } + this._tasks!.push(task); this._progress.report(undefined); }); @@ -241,6 +249,7 @@ class BulkEdit { @ILogService private readonly _logService: ILogService, @ITextModelService private readonly _textModelService: ITextModelService, @IFileService private readonly _fileService: IFileService, + @IEditorWorkerService private readonly _workerService: IEditorWorkerService, @ITextFileService private readonly _textFileService: ITextFileService, @ILabelService private readonly _uriLabelServie: ILabelService, @IConfigurationService private readonly _configurationService: IConfigurationService @@ -342,7 +351,7 @@ class BulkEdit { this._logService.debug('_performTextEdits', JSON.stringify(edits)); const recording = Recording.start(this._fileService); - const model = new BulkEditModel(this._textModelService, this._editor, edits, progress); + const model = new BulkEditModel(this._editor, progress, edits, this._workerService, this._textModelService); await model.prepare(); @@ -359,10 +368,11 @@ class BulkEdit { const validationResult = model.validate(); if (validationResult.canApply === false) { + model.dispose(); throw new Error(`${validationResult.reason.toString()} has changed in the meantime`); } - await model.apply(); + model.apply(); model.dispose(); } } @@ -375,14 +385,13 @@ export class BulkEditService implements IBulkEditService { @ILogService private readonly _logService: ILogService, @IModelService private readonly _modelService: IModelService, @IEditorService private readonly _editorService: IEditorService, + @IEditorWorkerService private readonly _workerService: IEditorWorkerService, @ITextModelService private readonly _textModelService: ITextModelService, @IFileService private readonly _fileService: IFileService, @ITextFileService private readonly _textFileService: ITextFileService, @ILabelService private readonly _labelService: ILabelService, @IConfigurationService private readonly _configurationService: IConfigurationService - ) { - - } + ) { } apply(edit: WorkspaceEdit, options: IBulkEditOptions = {}): Promise { @@ -401,7 +410,7 @@ export class BulkEditService implements IBulkEditService { } // try to find code editor - // todo@joh, prefer edit that gets edited + // todo@joh, prefer editor that gets edited if (!codeEditor) { let candidate = this._editorService.activeTextEditorWidget; if (isCodeEditor(candidate)) { @@ -415,10 +424,8 @@ export class BulkEditService implements IBulkEditService { } const bulkEdit = new BulkEdit( codeEditor, options.progress, edits, - this._logService, this._textModelService, this._fileService, this._textFileService, this._labelService, this._configurationService + this._logService, this._textModelService, this._fileService, this._workerService, this._textFileService, this._labelService, this._configurationService ); - - return bulkEdit.perform().then(() => { return { ariaSummary: bulkEdit.ariaMessage() }; }).catch(err => { diff --git a/src/vs/workbench/services/configuration/browser/configuration.ts b/src/vs/workbench/services/configuration/browser/configuration.ts index 0b0e7f35aa..ead4ad7b60 100644 --- a/src/vs/workbench/services/configuration/browser/configuration.ts +++ b/src/vs/workbench/services/configuration/browser/configuration.ts @@ -7,12 +7,12 @@ import { URI } from 'vs/base/common/uri'; import * as resources from 'vs/base/common/resources'; import { Event, Emitter } from 'vs/base/common/event'; import * as errors from 'vs/base/common/errors'; -import { Disposable, IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; -import { RunOnceScheduler } from 'vs/base/common/async'; +import { Disposable, IDisposable, dispose, toDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; +import { RunOnceScheduler, runWhenIdle } from 'vs/base/common/async'; import { FileChangeType, FileChangesEvent, IFileService, whenProviderRegistered } from 'vs/platform/files/common/files'; import { ConfigurationModel, ConfigurationModelParser } from 'vs/platform/configuration/common/configurationModels'; import { WorkspaceConfigurationModelParser, StandaloneConfigurationModelParser } from 'vs/workbench/services/configuration/common/configurationModels'; -import { FOLDER_SETTINGS_PATH, TASKS_CONFIGURATION_KEY, FOLDER_SETTINGS_NAME, LAUNCH_CONFIGURATION_KEY, IConfigurationCache, ConfigurationKey, REMOTE_MACHINE_SCOPES, FOLDER_SCOPES, WORKSPACE_SCOPES } from 'vs/workbench/services/configuration/common/configuration'; +import { TASKS_CONFIGURATION_KEY, FOLDER_SETTINGS_NAME, LAUNCH_CONFIGURATION_KEY, IConfigurationCache, ConfigurationKey, REMOTE_MACHINE_SCOPES, FOLDER_SCOPES, WORKSPACE_SCOPES } from 'vs/workbench/services/configuration/common/configuration'; import { IStoredWorkspaceFolder, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { JSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditingService'; import { WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; @@ -26,28 +26,61 @@ import { hash } from 'vs/base/common/hash'; export class UserConfiguration extends Disposable { - private readonly parser: ConfigurationModelParser; - private readonly reloadConfigurationScheduler: RunOnceScheduler; - protected readonly _onDidChangeConfiguration: Emitter = this._register(new Emitter()); + private readonly _onDidInitializeCompleteConfiguration: Emitter = this._register(new Emitter()); + private readonly _onDidChangeConfiguration: Emitter = this._register(new Emitter()); readonly onDidChangeConfiguration: Event = this._onDidChangeConfiguration.event; + private readonly userConfiguration: MutableDisposable = this._register(new MutableDisposable()); + private readonly reloadConfigurationScheduler: RunOnceScheduler; + constructor( private readonly userSettingsResource: URI, private readonly scopes: ConfigurationScope[] | undefined, private readonly fileService: IFileService ) { super(); - - this.parser = new ConfigurationModelParser(this.userSettingsResource.toString(), this.scopes); + this.userConfiguration.value = new UserSettings(this.userSettingsResource, this.scopes, this.fileService); + this._register(this.userConfiguration.value.onDidChange(() => this.reloadConfigurationScheduler.schedule())); this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this.reload().then(configurationModel => this._onDidChangeConfiguration.fire(configurationModel)), 50)); - this._register(Event.filter(this.fileService.onFileChanges, e => e.contains(this.userSettingsResource))(() => this.reloadConfigurationScheduler.schedule())); + + runWhenIdle(() => this._onDidInitializeCompleteConfiguration.fire(), 5000); + this._register(Event.once(this._onDidInitializeCompleteConfiguration.event)(() => this.reloadConfigurationScheduler.schedule())); } async initialize(): Promise { - return this.reload(); + return this.userConfiguration.value!.loadConfiguration(); } async reload(): Promise { + if (!(this.userConfiguration.value instanceof FileServiceBasedConfigurationWithNames)) { + this.userConfiguration.value = new FileServiceBasedConfigurationWithNames(resources.dirname(this.userSettingsResource), [FOLDER_SETTINGS_NAME, TASKS_CONFIGURATION_KEY], this.scopes, this.fileService); + this._register(this.userConfiguration.value.onDidChange(() => this.reloadConfigurationScheduler.schedule())); + } + return this.userConfiguration.value!.loadConfiguration(); + } + + reprocess(): ConfigurationModel { + return this.userConfiguration.value!.reprocess(); + } +} + +class UserSettings extends Disposable { + + private readonly parser: ConfigurationModelParser; + protected readonly _onDidChange: Emitter = this._register(new Emitter()); + readonly onDidChange: Event = this._onDidChange.event; + + constructor( + private readonly userSettingsResource: URI, + private readonly scopes: ConfigurationScope[] | undefined, + private readonly fileService: IFileService + ) { + super(); + this.parser = new ConfigurationModelParser(this.userSettingsResource.toString(), this.scopes); + this._register(Event.filter(this.fileService.onFileChanges, e => e.contains(this.userSettingsResource))(() => this._onDidChange.fire())); + } + + async loadConfiguration(): Promise { try { const content = await this.fileService.readFile(this.userSettingsResource); this.parser.parseContent(content.value.toString() || '{}'); @@ -63,6 +96,127 @@ export class UserConfiguration extends Disposable { } } +class FileServiceBasedConfigurationWithNames extends Disposable { + + private _folderSettingsModelParser: ConfigurationModelParser; + private _standAloneConfigurations: ConfigurationModel[]; + private _cache: ConfigurationModel; + + protected readonly configurationResources: URI[]; + protected changeEventTriggerScheduler: RunOnceScheduler; + protected readonly _onDidChange: Emitter = this._register(new Emitter()); + readonly onDidChange: Event = this._onDidChange.event; + + constructor(protected readonly configurationFolder: URI, + private readonly configurationNames: string[], + private readonly scopes: ConfigurationScope[] | undefined, + private fileService: IFileService) { + super(); + this.configurationResources = this.configurationNames.map(name => resources.joinPath(this.configurationFolder, `${name}.json`)); + this._folderSettingsModelParser = new ConfigurationModelParser(this.configurationFolder.toString(), this.scopes); + this._standAloneConfigurations = []; + this._cache = new ConfigurationModel(); + + this.changeEventTriggerScheduler = this._register(new RunOnceScheduler(() => this._onDidChange.fire(), 50)); + this._register(this.fileService.onFileChanges((e) => this.handleFileEvents(e))); + } + + async loadConfiguration(): Promise { + const configurationContents = await Promise.all(this.configurationResources.map(async resource => { + try { + const content = await this.fileService.readFile(resource); + return content.value.toString(); + } catch (error) { + const exists = await this.fileService.exists(resource); + if (exists) { + errors.onUnexpectedError(error); + } + } + return undefined; + })); + + // reset + this._standAloneConfigurations = []; + this._folderSettingsModelParser.parseContent(''); + + // parse + if (configurationContents[0]) { + this._folderSettingsModelParser.parseContent(configurationContents[0]); + } + for (let index = 1; index < configurationContents.length; index++) { + const contents = configurationContents[index]; + if (contents) { + const standAloneConfigurationModelParser = new StandaloneConfigurationModelParser(this.configurationResources[index].toString(), this.configurationNames[index]); + standAloneConfigurationModelParser.parseContent(contents); + this._standAloneConfigurations.push(standAloneConfigurationModelParser.configurationModel); + } + } + + // Consolidate (support *.json files in the workspace settings folder) + this.consolidate(); + + return this._cache; + } + + reprocess(): ConfigurationModel { + const oldContents = this._folderSettingsModelParser.configurationModel.contents; + this._folderSettingsModelParser.parse(); + if (!equals(oldContents, this._folderSettingsModelParser.configurationModel.contents)) { + this.consolidate(); + } + return this._cache; + } + + private consolidate(): void { + this._cache = this._folderSettingsModelParser.configurationModel.merge(...this._standAloneConfigurations); + } + + protected async handleFileEvents(event: FileChangesEvent): Promise { + const events = event.changes; + let affectedByChanges = false; + + // Find changes that affect workspace configuration files + for (let i = 0, len = events.length; i < len; i++) { + const resource = events[i].resource; + const basename = resources.basename(resource); + const isJson = extname(basename) === '.json'; + const isConfigurationFolderDeleted = (events[i].type === FileChangeType.DELETED && resources.isEqual(resource, this.configurationFolder)); + + if (!isJson && !isConfigurationFolderDeleted) { + continue; // only JSON files or the actual settings folder + } + + const folderRelativePath = this.toFolderRelativePath(resource); + if (!folderRelativePath) { + continue; // event is not inside folder + } + + // Handle case where ".vscode" got deleted + if (isConfigurationFolderDeleted) { + affectedByChanges = true; + break; + } + + // only valid workspace config files + if (this.configurationResources.some(configurationResource => resources.isEqual(configurationResource, resource))) { + affectedByChanges = true; + break; + } + } + + if (affectedByChanges) { + this.changeEventTriggerScheduler.schedule(); + } + } + + private toFolderRelativePath(resource: URI): string | undefined { + if (resources.isEqualOrParent(resource, this.configurationFolder)) { + return resources.relativePath(this.configurationFolder, resource); + } + return undefined; + } +} + export class RemoteUserConfiguration extends Disposable { private readonly _cachedConfiguration: CachedRemoteUserConfiguration; @@ -546,125 +700,12 @@ export interface IFolderConfiguration extends IDisposable { reprocess(): ConfigurationModel; } -class FileServiceBasedFolderConfiguration extends Disposable implements IFolderConfiguration { +class FileServiceBasedFolderConfiguration extends FileServiceBasedConfigurationWithNames implements IFolderConfiguration { - private _folderSettingsModelParser: ConfigurationModelParser; - private _standAloneConfigurations: ConfigurationModel[]; - private _cache: ConfigurationModel; - - private readonly configurationNames: string[]; - protected readonly configurationResources: URI[]; - private changeEventTriggerScheduler: RunOnceScheduler; - protected readonly _onDidChange: Emitter = this._register(new Emitter()); - readonly onDidChange: Event = this._onDidChange.event; - - constructor(protected readonly configurationFolder: URI, workbenchState: WorkbenchState, private fileService: IFileService) { - super(); - - this.configurationNames = [FOLDER_SETTINGS_NAME /*First one should be settings */, TASKS_CONFIGURATION_KEY, LAUNCH_CONFIGURATION_KEY]; - this.configurationResources = this.configurationNames.map(name => resources.joinPath(this.configurationFolder, `${name}.json`)); - this._folderSettingsModelParser = new ConfigurationModelParser(FOLDER_SETTINGS_PATH, WorkbenchState.WORKSPACE === workbenchState ? FOLDER_SCOPES : WORKSPACE_SCOPES); - this._standAloneConfigurations = []; - this._cache = new ConfigurationModel(); - - this.changeEventTriggerScheduler = this._register(new RunOnceScheduler(() => this._onDidChange.fire(), 50)); - this._register(fileService.onFileChanges(e => this.handleWorkspaceFileEvents(e))); + constructor(configurationFolder: URI, workbenchState: WorkbenchState, fileService: IFileService) { + super(configurationFolder, [FOLDER_SETTINGS_NAME /*First one should be settings */, TASKS_CONFIGURATION_KEY, LAUNCH_CONFIGURATION_KEY], WorkbenchState.WORKSPACE === workbenchState ? FOLDER_SCOPES : WORKSPACE_SCOPES, fileService); } - async loadConfiguration(): Promise { - const configurationContents = await Promise.all(this.configurationResources.map(async resource => { - try { - const content = await this.fileService.readFile(resource); - return content.value.toString(); - } catch (error) { - const exists = await this.fileService.exists(resource); - if (exists) { - errors.onUnexpectedError(error); - } - } - return undefined; - })); - - // reset - this._standAloneConfigurations = []; - this._folderSettingsModelParser.parseContent(''); - - // parse - if (configurationContents[0]) { - this._folderSettingsModelParser.parseContent(configurationContents[0]); - } - for (let index = 1; index < configurationContents.length; index++) { - const contents = configurationContents[index]; - if (contents) { - const standAloneConfigurationModelParser = new StandaloneConfigurationModelParser(this.configurationResources[index].toString(), this.configurationNames[index]); - standAloneConfigurationModelParser.parseContent(contents); - this._standAloneConfigurations.push(standAloneConfigurationModelParser.configurationModel); - } - } - - // Consolidate (support *.json files in the workspace settings folder) - this.consolidate(); - - return this._cache; - } - - reprocess(): ConfigurationModel { - const oldContents = this._folderSettingsModelParser.configurationModel.contents; - this._folderSettingsModelParser.parse(); - if (!equals(oldContents, this._folderSettingsModelParser.configurationModel.contents)) { - this.consolidate(); - } - return this._cache; - } - - private consolidate(): void { - this._cache = this._folderSettingsModelParser.configurationModel.merge(...this._standAloneConfigurations); - } - - private handleWorkspaceFileEvents(event: FileChangesEvent): void { - const events = event.changes; - let affectedByChanges = false; - - // Find changes that affect workspace configuration files - for (let i = 0, len = events.length; i < len; i++) { - const resource = events[i].resource; - const basename = resources.basename(resource); - const isJson = extname(basename) === '.json'; - const isConfigurationFolderDeleted = (events[i].type === FileChangeType.DELETED && resources.isEqual(resource, this.configurationFolder)); - - if (!isJson && !isConfigurationFolderDeleted) { - continue; // only JSON files or the actual settings folder - } - - const folderRelativePath = this.toFolderRelativePath(resource); - if (!folderRelativePath) { - continue; // event is not inside folder - } - - // Handle case where ".vscode" got deleted - if (isConfigurationFolderDeleted) { - affectedByChanges = true; - break; - } - - // only valid workspace config files - if (this.configurationResources.some(configurationResource => resources.isEqual(configurationResource, resource))) { - affectedByChanges = true; - break; - } - } - - if (affectedByChanges) { - this.changeEventTriggerScheduler.schedule(); - } - } - - private toFolderRelativePath(resource: URI): string | undefined { - if (resources.isEqualOrParent(resource, this.configurationFolder)) { - return resources.relativePath(this.configurationFolder, resource); - } - return undefined; - } } class CachedFolderConfiguration extends Disposable implements IFolderConfiguration { diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index 5228a99f32..ed74581bdc 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -656,11 +656,11 @@ export class WorkspaceService extends Disposable implements IConfigurationServic return undefined; } - if (inspect.workspaceFolder !== undefined) { + if (inspect.workspaceFolderValue !== undefined) { return ConfigurationTarget.WORKSPACE_FOLDER; } - if (inspect.workspace !== undefined) { + if (inspect.workspaceValue !== undefined) { return ConfigurationTarget.WORKSPACE; } @@ -690,7 +690,7 @@ export class WorkspaceService extends Disposable implements IConfigurationServic private toEditableConfigurationTarget(target: ConfigurationTarget, key: string): EditableConfigurationTarget | null { if (target === ConfigurationTarget.USER) { - if (this.inspect(key).userRemote !== undefined) { + if (this.inspect(key).userRemoteValue !== undefined) { return EditableConfigurationTarget.USER_REMOTE; } return EditableConfigurationTarget.USER_LOCAL; diff --git a/src/vs/workbench/services/configuration/common/configuration.ts b/src/vs/workbench/services/configuration/common/configuration.ts index 3c06a68152..b91f651d7c 100644 --- a/src/vs/workbench/services/configuration/common/configuration.ts +++ b/src/vs/workbench/services/configuration/common/configuration.ts @@ -17,10 +17,10 @@ export const folderSettingsSchemaId = 'vscode://schemas/settings/folder'; export const launchSchemaId = 'vscode://schemas/launch'; export const tasksSchemaId = 'vscode://schemas/tasks'; -export const LOCAL_MACHINE_SCOPES = [ConfigurationScope.APPLICATION, ConfigurationScope.WINDOW, ConfigurationScope.RESOURCE]; -export const REMOTE_MACHINE_SCOPES = [ConfigurationScope.MACHINE, ConfigurationScope.WINDOW, ConfigurationScope.RESOURCE, ConfigurationScope.MACHINE_OVERRIDABLE]; -export const WORKSPACE_SCOPES = [ConfigurationScope.WINDOW, ConfigurationScope.RESOURCE, ConfigurationScope.MACHINE_OVERRIDABLE]; -export const FOLDER_SCOPES = [ConfigurationScope.RESOURCE, ConfigurationScope.MACHINE_OVERRIDABLE]; +export const LOCAL_MACHINE_SCOPES = [ConfigurationScope.APPLICATION, ConfigurationScope.WINDOW, ConfigurationScope.RESOURCE, ConfigurationScope.RESOURCE_LANGUAGE]; +export const REMOTE_MACHINE_SCOPES = [ConfigurationScope.MACHINE, ConfigurationScope.WINDOW, ConfigurationScope.RESOURCE, ConfigurationScope.RESOURCE_LANGUAGE, ConfigurationScope.MACHINE_OVERRIDABLE]; +export const WORKSPACE_SCOPES = [ConfigurationScope.WINDOW, ConfigurationScope.RESOURCE, ConfigurationScope.RESOURCE_LANGUAGE, ConfigurationScope.MACHINE_OVERRIDABLE]; +export const FOLDER_SCOPES = [ConfigurationScope.RESOURCE, ConfigurationScope.RESOURCE_LANGUAGE, ConfigurationScope.MACHINE_OVERRIDABLE]; export const TASKS_CONFIGURATION_KEY = 'tasks'; export const LAUNCH_CONFIGURATION_KEY = 'launch'; @@ -28,6 +28,8 @@ export const LAUNCH_CONFIGURATION_KEY = 'launch'; export const WORKSPACE_STANDALONE_CONFIGURATIONS = Object.create(null); WORKSPACE_STANDALONE_CONFIGURATIONS[TASKS_CONFIGURATION_KEY] = `${FOLDER_CONFIG_FOLDER_NAME}/${TASKS_CONFIGURATION_KEY}.json`; WORKSPACE_STANDALONE_CONFIGURATIONS[LAUNCH_CONFIGURATION_KEY] = `${FOLDER_CONFIG_FOLDER_NAME}/${LAUNCH_CONFIGURATION_KEY}.json`; +export const USER_STANDALONE_CONFIGURATIONS = Object.create(null); +USER_STANDALONE_CONFIGURATIONS[TASKS_CONFIGURATION_KEY] = `${TASKS_CONFIGURATION_KEY}.json`; export type ConfigurationKey = { type: 'user' | 'workspaces' | 'folder', key: string }; diff --git a/src/vs/workbench/services/configuration/common/configurationEditingService.ts b/src/vs/workbench/services/configuration/common/configurationEditingService.ts index 54a4b39860..284f50ed82 100644 --- a/src/vs/workbench/services/configuration/common/configurationEditingService.ts +++ b/src/vs/workbench/services/configuration/common/configurationEditingService.ts @@ -5,6 +5,7 @@ import * as nls from 'vs/nls'; import { URI } from 'vs/base/common/uri'; +import * as resources from 'vs/base/common/resources'; import * as json from 'vs/base/common/json'; import * as strings from 'vs/base/common/strings'; import { setProperty } from 'vs/base/common/jsonEdit'; @@ -19,7 +20,7 @@ import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/ import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IConfigurationService, IConfigurationOverrides, keyFromOverrideIdentifier } from 'vs/platform/configuration/common/configuration'; -import { FOLDER_SETTINGS_PATH, WORKSPACE_STANDALONE_CONFIGURATIONS, TASKS_CONFIGURATION_KEY, LAUNCH_CONFIGURATION_KEY } from 'vs/workbench/services/configuration/common/configuration'; +import { FOLDER_SETTINGS_PATH, WORKSPACE_STANDALONE_CONFIGURATIONS, TASKS_CONFIGURATION_KEY, LAUNCH_CONFIGURATION_KEY, USER_STANDALONE_CONFIGURATIONS } from 'vs/workbench/services/configuration/common/configuration'; import { IFileService } from 'vs/platform/files/common/files'; import { ITextModelService, IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService'; import { OVERRIDE_PROPERTY_PATTERN, IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; @@ -67,6 +68,11 @@ export const enum ConfigurationEditingErrorCode { */ ERROR_INVALID_FOLDER_TARGET, + /** + * Error when trying to write to language specific setting but not supported for preovided key + */ + ERROR_INVALID_RESOURCE_LANGUAGE_CONFIGURATION, + /** * Error when trying to write to the workspace configuration without having a workspace opened. */ @@ -304,6 +310,7 @@ export class ConfigurationEditingService { case ConfigurationEditingErrorCode.ERROR_INVALID_USER_TARGET: return nls.localize('errorInvalidUserTarget', "Unable to write to User Settings because {0} does not support for global scope.", operation.key); case ConfigurationEditingErrorCode.ERROR_INVALID_WORKSPACE_TARGET: return nls.localize('errorInvalidWorkspaceTarget', "Unable to write to Workspace Settings because {0} does not support for workspace scope in a multi folder workspace.", operation.key); case ConfigurationEditingErrorCode.ERROR_INVALID_FOLDER_TARGET: return nls.localize('errorInvalidFolderTarget', "Unable to write to Folder Settings because no resource is provided."); + case ConfigurationEditingErrorCode.ERROR_INVALID_RESOURCE_LANGUAGE_CONFIGURATION: return nls.localize('errorInvalidResourceLanguageConfiguraiton', "Unable to write to Language Settings because {0} is not a resource language setting.", operation.key); case ConfigurationEditingErrorCode.ERROR_NO_WORKSPACE_OPENED: return nls.localize('errorNoWorkspaceOpened', "Unable to write to {0} because no workspace is opened. Please open a workspace first and try again.", this.stringifyTarget(target)); // User issues @@ -424,8 +431,8 @@ export class ConfigurationEditingService { } if (operation.workspaceStandAloneConfigurationKey) { - // Global tasks and launches are not supported - if (target === EditableConfigurationTarget.USER_LOCAL || target === EditableConfigurationTarget.USER_REMOTE) { + // Global launches are not supported + if ((operation.workspaceStandAloneConfigurationKey !== TASKS_CONFIGURATION_KEY) && (target === EditableConfigurationTarget.USER_LOCAL || target === EditableConfigurationTarget.USER_REMOTE)) { return this.reject(ConfigurationEditingErrorCode.ERROR_INVALID_USER_TARGET, target, operation); } } @@ -460,6 +467,13 @@ export class ConfigurationEditingService { } } + if (overrides.overrideIdentifier) { + const configurationProperties = Registry.as(ConfigurationExtensions.Configuration).getConfigurationProperties(); + if (configurationProperties[operation.key].scope !== ConfigurationScope.RESOURCE_LANGUAGE) { + return this.reject(ConfigurationEditingErrorCode.ERROR_INVALID_RESOURCE_LANGUAGE_CONFIGURATION, target, operation); + } + } + if (!operation.resource) { return this.reject(ConfigurationEditingErrorCode.ERROR_INVALID_FOLDER_TARGET, target, operation); } @@ -484,9 +498,10 @@ export class ConfigurationEditingService { // Check for standalone workspace configurations if (config.key) { - const standaloneConfigurationKeys = Object.keys(WORKSPACE_STANDALONE_CONFIGURATIONS); + const standaloneConfigurationMap = target === EditableConfigurationTarget.USER_LOCAL ? USER_STANDALONE_CONFIGURATIONS : WORKSPACE_STANDALONE_CONFIGURATIONS; + const standaloneConfigurationKeys = Object.keys(standaloneConfigurationMap); for (const key of standaloneConfigurationKeys) { - const resource = this.getConfigurationFileResource(target, config, WORKSPACE_STANDALONE_CONFIGURATIONS[key], overrides.resource); + const resource = this.getConfigurationFileResource(target, config, standaloneConfigurationMap[key], overrides.resource); // Check for prefix if (config.key === key) { @@ -523,7 +538,11 @@ export class ConfigurationEditingService { private getConfigurationFileResource(target: EditableConfigurationTarget, config: IConfigurationValue, relativePath: string, resource: URI | null | undefined): URI | null { if (target === EditableConfigurationTarget.USER_LOCAL) { - return this.environmentService.settingsResource; + if (relativePath) { + return resources.joinPath(resources.dirname(this.environmentService.settingsResource), relativePath); + } else { + return this.environmentService.settingsResource; + } } if (target === EditableConfigurationTarget.USER_REMOTE) { return this.remoteSettingsResource; 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 2e0f5de203..4a5ab95a18 100644 --- a/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts +++ b/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts @@ -27,7 +27,11 @@ suite('FolderSettingsModelParser', () => { 'type': 'string', 'default': 'isSet', scope: ConfigurationScope.RESOURCE, - overridable: true + }, + 'FolderSettingsModelParser.resourceLanguage': { + 'type': 'string', + 'default': 'isSet', + scope: ConfigurationScope.RESOURCE_LANGUAGE, }, 'FolderSettingsModelParser.application': { 'type': 'string', @@ -59,12 +63,12 @@ suite('FolderSettingsModelParser', () => { assert.deepEqual(testObject.configurationModel.contents, { 'FolderSettingsModelParser': { 'resource': 'resource' } }); }); - test('parse overridable resource settings', () => { - const testObject = new ConfigurationModelParser('settings', [ConfigurationScope.RESOURCE]); + test('parse resource and resource language settings', () => { + const testObject = new ConfigurationModelParser('settings', [ConfigurationScope.RESOURCE, ConfigurationScope.RESOURCE_LANGUAGE]); - testObject.parseContent(JSON.stringify({ '[json]': { 'FolderSettingsModelParser.window': 'window', 'FolderSettingsModelParser.resource': 'resource', 'FolderSettingsModelParser.application': 'application', 'FolderSettingsModelParser.machine': 'executable' } })); + testObject.parseContent(JSON.stringify({ '[json]': { 'FolderSettingsModelParser.window': 'window', 'FolderSettingsModelParser.resource': 'resource', 'FolderSettingsModelParser.resourceLanguage': 'resourceLanguage', 'FolderSettingsModelParser.application': 'application', 'FolderSettingsModelParser.machine': 'executable' } })); - assert.deepEqual(testObject.configurationModel.overrides, [{ 'contents': { 'FolderSettingsModelParser': { 'resource': 'resource' } }, 'identifiers': ['json'], 'keys': ['FolderSettingsModelParser.resource'] }]); + assert.deepEqual(testObject.configurationModel.overrides, [{ 'contents': { 'FolderSettingsModelParser': { 'resource': 'resource', 'resourceLanguage': 'resourceLanguage' } }, 'identifiers': ['json'], 'keys': ['FolderSettingsModelParser.resource', 'FolderSettingsModelParser.resourceLanguage'] }]); }); test('reprocess folder settings excludes application and machine setting', () => { diff --git a/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts b/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts index 211c2b95bb..2ee7a7d1f6 100644 --- a/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts +++ b/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts @@ -18,7 +18,7 @@ import * as uuid from 'vs/base/common/uuid'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; import { WorkspaceService } from 'vs/workbench/services/configuration/browser/configurationService'; import { ConfigurationEditingService, ConfigurationEditingError, ConfigurationEditingErrorCode, EditableConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditingService'; -import { WORKSPACE_STANDALONE_CONFIGURATIONS, FOLDER_SETTINGS_PATH } from 'vs/workbench/services/configuration/common/configuration'; +import { WORKSPACE_STANDALONE_CONFIGURATIONS, FOLDER_SETTINGS_PATH, USER_STANDALONE_CONFIGURATIONS } from 'vs/workbench/services/configuration/common/configuration'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; @@ -60,6 +60,7 @@ suite('ConfigurationEditingService', () => { let parentDir: string; let workspaceDir: string; let globalSettingsFile: string; + let globalTasksFile: string; let workspaceSettingsDir; suiteSetup(() => { @@ -94,6 +95,7 @@ suite('ConfigurationEditingService', () => { parentDir = path.join(os.tmpdir(), 'vsctests', id); workspaceDir = path.join(parentDir, 'workspaceconfig', id); globalSettingsFile = path.join(workspaceDir, 'settings.json'); + globalTasksFile = path.join(workspaceDir, 'tasks.json'); workspaceSettingsDir = path.join(workspaceDir, '.azuredatastudio'); // {{SQL CARBON EDIT}} .vscode to .azuredatastudio return await mkdirp(workspaceSettingsDir, 493); @@ -149,12 +151,6 @@ suite('ConfigurationEditingService', () => { (error: ConfigurationEditingError) => assert.equal(error.code, ConfigurationEditingErrorCode.ERROR_UNKNOWN_KEY)); }); - test('errors cases - invalid target', () => { - return testObject.writeConfiguration(EditableConfigurationTarget.USER_LOCAL, { key: 'tasks.something', value: 'value' }) - .then(() => assert.fail('Should fail with ERROR_INVALID_TARGET'), - (error: ConfigurationEditingError) => assert.equal(error.code, ConfigurationEditingErrorCode.ERROR_INVALID_USER_TARGET)); - }); - test('errors cases - no workspace', () => { return setUpServices(true) .then(() => testObject.writeConfiguration(EditableConfigurationTarget.WORKSPACE, { key: 'configurationEditing.service.testSetting', value: 'value' })) @@ -162,11 +158,19 @@ suite('ConfigurationEditingService', () => { (error: ConfigurationEditingError) => assert.equal(error.code, ConfigurationEditingErrorCode.ERROR_NO_WORKSPACE_OPENED)); }); - test('errors cases - invalid configuration', () => { - fs.writeFileSync(globalSettingsFile, ',,,,,,,,,,,,,,'); - return testObject.writeConfiguration(EditableConfigurationTarget.USER_LOCAL, { key: 'configurationEditing.service.testSetting', value: 'value' }) + function errorCasesInvalidConfig(file: string, key: string) { + fs.writeFileSync(file, ',,,,,,,,,,,,,,'); + return testObject.writeConfiguration(EditableConfigurationTarget.USER_LOCAL, { key, value: 'value' }) .then(() => assert.fail('Should fail with ERROR_INVALID_CONFIGURATION'), (error: ConfigurationEditingError) => assert.equal(error.code, ConfigurationEditingErrorCode.ERROR_INVALID_CONFIGURATION)); + } + + test('errors cases - invalid configuration', () => { + return errorCasesInvalidConfig(globalSettingsFile, 'configurationEditing.service.testSetting'); + }); + + test('errors cases - invalid global tasks configuration', () => { + return errorCasesInvalidConfig(globalTasksFile, 'tasks.configurationEditing.service.testSetting'); }); test('errors cases - dirty', () => { @@ -271,44 +275,89 @@ suite('ConfigurationEditingService', () => { }); }); - test('write workspace standalone setting - empty file', () => { - return testObject.writeConfiguration(EditableConfigurationTarget.WORKSPACE, { key: 'tasks.service.testSetting', value: 'value' }) + function writeStandaloneSettingEmptyFile(configTarget: EditableConfigurationTarget, pathMap: any) { + return testObject.writeConfiguration(configTarget, { key: 'tasks.service.testSetting', value: 'value' }) .then(() => { - const target = path.join(workspaceDir, WORKSPACE_STANDALONE_CONFIGURATIONS['tasks']); + const target = path.join(workspaceDir, pathMap['tasks']); const contents = fs.readFileSync(target).toString('utf8'); const parsed = json.parse(contents); assert.equal(parsed['service.testSetting'], 'value'); }); + } + + test('write workspace standalone setting - empty file', () => { + return writeStandaloneSettingEmptyFile(EditableConfigurationTarget.WORKSPACE, WORKSPACE_STANDALONE_CONFIGURATIONS); }); - test('write workspace standalone setting - existing file', () => { - const target = path.join(workspaceDir, WORKSPACE_STANDALONE_CONFIGURATIONS['launch']); + test('write user standalone setting - empty file', () => { + return writeStandaloneSettingEmptyFile(EditableConfigurationTarget.USER_LOCAL, USER_STANDALONE_CONFIGURATIONS); + }); + + function writeStandaloneSettingExitingFile(configTarget: EditableConfigurationTarget, pathMap: any) { + const target = path.join(workspaceDir, pathMap['tasks']); fs.writeFileSync(target, '{ "my.super.setting": "my.super.value" }'); - return testObject.writeConfiguration(EditableConfigurationTarget.WORKSPACE, { key: 'launch.service.testSetting', value: 'value' }) + return testObject.writeConfiguration(configTarget, { key: 'tasks.service.testSetting', value: 'value' }) .then(() => { const contents = fs.readFileSync(target).toString('utf8'); const parsed = json.parse(contents); assert.equal(parsed['service.testSetting'], 'value'); assert.equal(parsed['my.super.setting'], 'my.super.value'); }); + } + + test('write workspace standalone setting - existing file', () => { + return writeStandaloneSettingExitingFile(EditableConfigurationTarget.WORKSPACE, WORKSPACE_STANDALONE_CONFIGURATIONS); }); + test('write user standalone setting - existing file', () => { + return writeStandaloneSettingExitingFile(EditableConfigurationTarget.USER_LOCAL, USER_STANDALONE_CONFIGURATIONS); + }); + + function writeStandaloneSettingEmptyFileFullJson(configTarget: EditableConfigurationTarget, pathMap: any) { + return testObject.writeConfiguration(configTarget, { key: 'tasks', value: { 'version': '1.0.0', tasks: [{ 'taskName': 'myTask' }] } }) + .then(() => { + const target = path.join(workspaceDir, pathMap['tasks']); + const contents = fs.readFileSync(target).toString('utf8'); + const parsed = json.parse(contents); + + assert.equal(parsed['version'], '1.0.0'); + assert.equal(parsed['tasks'][0]['taskName'], 'myTask'); + }); + } + test('write workspace standalone setting - empty file - full JSON', () => { - return testObject.writeConfiguration(EditableConfigurationTarget.WORKSPACE, { key: 'tasks', value: { 'version': '1.0.0', tasks: [{ 'taskName': 'myTask' }] } }) + return writeStandaloneSettingEmptyFileFullJson(EditableConfigurationTarget.WORKSPACE, WORKSPACE_STANDALONE_CONFIGURATIONS); + }); + + test('write user standalone setting - empty file - full JSON', () => { + return writeStandaloneSettingEmptyFileFullJson(EditableConfigurationTarget.USER_LOCAL, USER_STANDALONE_CONFIGURATIONS); + }); + + function writeStandaloneSettingExistingFileFullJson(configTarget: EditableConfigurationTarget, pathMap: any) { + const target = path.join(workspaceDir, pathMap['tasks']); + fs.writeFileSync(target, '{ "my.super.setting": "my.super.value" }'); + return testObject.writeConfiguration(configTarget, { key: 'tasks', value: { 'version': '1.0.0', tasks: [{ 'taskName': 'myTask' }] } }) .then(() => { - const target = path.join(workspaceDir, WORKSPACE_STANDALONE_CONFIGURATIONS['tasks']); const contents = fs.readFileSync(target).toString('utf8'); const parsed = json.parse(contents); assert.equal(parsed['version'], '1.0.0'); assert.equal(parsed['tasks'][0]['taskName'], 'myTask'); }); - }); + } test('write workspace standalone setting - existing file - full JSON', () => { - const target = path.join(workspaceDir, WORKSPACE_STANDALONE_CONFIGURATIONS['tasks']); - fs.writeFileSync(target, '{ "my.super.setting": "my.super.value" }'); - return testObject.writeConfiguration(EditableConfigurationTarget.WORKSPACE, { key: 'tasks', value: { 'version': '1.0.0', tasks: [{ 'taskName': 'myTask' }] } }) + return writeStandaloneSettingExistingFileFullJson(EditableConfigurationTarget.WORKSPACE, WORKSPACE_STANDALONE_CONFIGURATIONS); + }); + + test('write user standalone setting - existing file - full JSON', () => { + return writeStandaloneSettingExistingFileFullJson(EditableConfigurationTarget.USER_LOCAL, USER_STANDALONE_CONFIGURATIONS); + }); + + function writeStandaloneSettingExistingFileWithJsonErrorFullJson(configTarget: EditableConfigurationTarget, pathMap: any) { + const target = path.join(workspaceDir, pathMap['tasks']); + fs.writeFileSync(target, '{ "my.super.setting": '); // invalid JSON + return testObject.writeConfiguration(configTarget, { key: 'tasks', value: { 'version': '1.0.0', tasks: [{ 'taskName': 'myTask' }] } }) .then(() => { const contents = fs.readFileSync(target).toString('utf8'); const parsed = json.parse(contents); @@ -316,23 +365,18 @@ suite('ConfigurationEditingService', () => { assert.equal(parsed['version'], '1.0.0'); assert.equal(parsed['tasks'][0]['taskName'], 'myTask'); }); - }); + } test('write workspace standalone setting - existing file with JSON errors - full JSON', () => { - const target = path.join(workspaceDir, WORKSPACE_STANDALONE_CONFIGURATIONS['tasks']); - fs.writeFileSync(target, '{ "my.super.setting": '); // invalid JSON - return testObject.writeConfiguration(EditableConfigurationTarget.WORKSPACE, { key: 'tasks', value: { 'version': '1.0.0', tasks: [{ 'taskName': 'myTask' }] } }) - .then(() => { - const contents = fs.readFileSync(target).toString('utf8'); - const parsed = json.parse(contents); - - assert.equal(parsed['version'], '1.0.0'); - assert.equal(parsed['tasks'][0]['taskName'], 'myTask'); - }); + return writeStandaloneSettingExistingFileWithJsonErrorFullJson(EditableConfigurationTarget.WORKSPACE, WORKSPACE_STANDALONE_CONFIGURATIONS); }); - test('write workspace standalone setting should replace complete file', () => { - const target = path.join(workspaceDir, WORKSPACE_STANDALONE_CONFIGURATIONS['tasks']); + test('write user standalone setting - existing file with JSON errors - full JSON', () => { + return writeStandaloneSettingExistingFileWithJsonErrorFullJson(EditableConfigurationTarget.USER_LOCAL, USER_STANDALONE_CONFIGURATIONS); + }); + + function writeStandaloneSettingShouldReplace(configTarget: EditableConfigurationTarget, pathMap: any) { + const target = path.join(workspaceDir, pathMap['tasks']); fs.writeFileSync(target, `{ "version": "1.0.0", "tasks": [ @@ -344,11 +388,19 @@ suite('ConfigurationEditingService', () => { } ] }`); - return testObject.writeConfiguration(EditableConfigurationTarget.WORKSPACE, { key: 'tasks', value: { 'version': '1.0.0', tasks: [{ 'taskName': 'myTask1' }] } }) + return testObject.writeConfiguration(configTarget, { key: 'tasks', value: { 'version': '1.0.0', tasks: [{ 'taskName': 'myTask1' }] } }) .then(() => { const actual = fs.readFileSync(target).toString('utf8'); const expected = JSON.stringify({ 'version': '1.0.0', tasks: [{ 'taskName': 'myTask1' }] }, null, '\t'); assert.equal(actual, expected); }); + } + + test('write workspace standalone setting should replace complete file', () => { + return writeStandaloneSettingShouldReplace(EditableConfigurationTarget.WORKSPACE, WORKSPACE_STANDALONE_CONFIGURATIONS); + }); + + test('write user standalone setting should replace complete file', () => { + return writeStandaloneSettingShouldReplace(EditableConfigurationTarget.USER_LOCAL, USER_STANDALONE_CONFIGURATIONS); }); }); diff --git a/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts b/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts index cb428f846f..d7a01f5aab 100644 --- a/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts @@ -47,6 +47,7 @@ import { FileUserDataProvider } from 'vs/workbench/services/userData/common/file import { IKeybindingEditingService, KeybindingsEditingService } from 'vs/workbench/services/keybinding/common/keybindingEditing'; import { NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { timeout } from 'vs/base/common/async'; class TestEnvironmentService extends NativeWorkbenchEnvironmentService { @@ -717,7 +718,7 @@ suite.skip('WorkspaceService - Initialization', () => { // {{SQL CARBON EDIT}} s suite.skip('WorkspaceConfigurationService - Folder', () => { // {{SQL CARBON EDIT}} skip suite - let workspaceName = `testWorkspace${uuid.generateUuid()}`, parentResource: string, workspaceDir: string, testObject: IConfigurationService, globalSettingsFile: string; + let workspaceName = `testWorkspace${uuid.generateUuid()}`, parentResource: string, workspaceDir: string, testObject: IConfigurationService, globalSettingsFile: string, globalTasksFile: string; const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); suiteSetup(() => { @@ -756,6 +757,7 @@ suite.skip('WorkspaceConfigurationService - Folder', () => { // {{SQL CARBON EDI parentResource = parentDir; workspaceDir = folderDir; globalSettingsFile = path.join(parentDir, 'settings.json'); + globalTasksFile = path.join(parentDir, 'tasks.json'); const instantiationService = workbenchInstantiationService(); const environmentService = new TestEnvironmentService(URI.file(parentDir)); @@ -950,27 +952,27 @@ suite.skip('WorkspaceConfigurationService - Folder', () => { // {{SQL CARBON EDI test('inspect', () => { let actual = testObject.inspect('something.missing'); - assert.equal(actual.default, undefined); - assert.equal(actual.user, undefined); - assert.equal(actual.workspace, undefined); - assert.equal(actual.workspaceFolder, undefined); + assert.equal(actual.defaultValue, undefined); + assert.equal(actual.userValue, undefined); + assert.equal(actual.workspaceValue, undefined); + assert.equal(actual.workspaceFolderValue, undefined); assert.equal(actual.value, undefined); actual = testObject.inspect('configurationService.folder.testSetting'); - assert.equal(actual.default, 'isSet'); - assert.equal(actual.user, undefined); - assert.equal(actual.workspace, undefined); - assert.equal(actual.workspaceFolder, undefined); + assert.equal(actual.defaultValue, 'isSet'); + assert.equal(actual.userValue, undefined); + assert.equal(actual.workspaceValue, undefined); + assert.equal(actual.workspaceFolderValue, undefined); assert.equal(actual.value, 'isSet'); fs.writeFileSync(globalSettingsFile, '{ "configurationService.folder.testSetting": "userValue" }'); return testObject.reloadConfiguration() .then(() => { actual = testObject.inspect('configurationService.folder.testSetting'); - assert.equal(actual.default, 'isSet'); - assert.equal(actual.user, 'userValue'); - assert.equal(actual.workspace, undefined); - assert.equal(actual.workspaceFolder, undefined); + assert.equal(actual.defaultValue, 'isSet'); + assert.equal(actual.userValue, 'userValue'); + assert.equal(actual.workspaceValue, undefined); + assert.equal(actual.workspaceFolderValue, undefined); assert.equal(actual.value, 'userValue'); fs.writeFileSync(path.join(workspaceDir, '.vscode', 'settings.json'), '{ "configurationService.folder.testSetting": "workspaceValue" }'); @@ -978,10 +980,10 @@ suite.skip('WorkspaceConfigurationService - Folder', () => { // {{SQL CARBON EDI return testObject.reloadConfiguration() .then(() => { actual = testObject.inspect('configurationService.folder.testSetting'); - assert.equal(actual.default, 'isSet'); - assert.equal(actual.user, 'userValue'); - assert.equal(actual.workspace, 'workspaceValue'); - assert.equal(actual.workspaceFolder, undefined); + assert.equal(actual.defaultValue, 'isSet'); + assert.equal(actual.userValue, 'userValue'); + assert.equal(actual.workspaceValue, 'workspaceValue'); + assert.equal(actual.workspaceFolderValue, undefined); assert.equal(actual.value, 'workspaceValue'); }); }); @@ -1074,6 +1076,17 @@ suite.skip('WorkspaceConfigurationService - Folder', () => { // {{SQL CARBON EDI .then(() => assert.ok(target.called)); }); + test('no change event when there are no global tasks', async () => { + const target = sinon.spy(); + testObject.onDidChangeConfiguration(target); + await timeout(500); + assert.ok(target.notCalled); + }); + + test('change event when there are global tasks', () => { + fs.writeFileSync(globalTasksFile, '{ "version": "1.0.0", "tasks": [{ "taskName": "myTask" }'); + return new Promise((c) => testObject.onDidChangeConfiguration(() => c())); + }); }); suite.skip('WorkspaceConfigurationService-Multiroot', () => { // {{SQL CARBON EDIT}} skip suite @@ -1308,37 +1321,37 @@ suite.skip('WorkspaceConfigurationService-Multiroot', () => { // {{SQL CARBON ED test('inspect', () => { let actual = testObject.inspect('something.missing'); - assert.equal(actual.default, undefined); - assert.equal(actual.user, undefined); - assert.equal(actual.workspace, undefined); - assert.equal(actual.workspaceFolder, undefined); + assert.equal(actual.defaultValue, undefined); + assert.equal(actual.userValue, undefined); + assert.equal(actual.workspaceValue, undefined); + assert.equal(actual.workspaceFolderValue, undefined); assert.equal(actual.value, undefined); actual = testObject.inspect('configurationService.workspace.testResourceSetting'); - assert.equal(actual.default, 'isSet'); - assert.equal(actual.user, undefined); - assert.equal(actual.workspace, undefined); - assert.equal(actual.workspaceFolder, undefined); + assert.equal(actual.defaultValue, 'isSet'); + assert.equal(actual.userValue, undefined); + assert.equal(actual.workspaceValue, undefined); + assert.equal(actual.workspaceFolderValue, undefined); assert.equal(actual.value, 'isSet'); fs.writeFileSync(globalSettingsFile, '{ "configurationService.workspace.testResourceSetting": "userValue" }'); return testObject.reloadConfiguration() .then(() => { actual = testObject.inspect('configurationService.workspace.testResourceSetting'); - assert.equal(actual.default, 'isSet'); - assert.equal(actual.user, 'userValue'); - assert.equal(actual.workspace, undefined); - assert.equal(actual.workspaceFolder, undefined); + assert.equal(actual.defaultValue, 'isSet'); + assert.equal(actual.userValue, 'userValue'); + assert.equal(actual.workspaceValue, undefined); + assert.equal(actual.workspaceFolderValue, undefined); assert.equal(actual.value, 'userValue'); return jsonEditingServce.write(workspaceContextService.getWorkspace().configuration!, [{ key: 'settings', value: { 'configurationService.workspace.testResourceSetting': 'workspaceValue' } }], true) .then(() => testObject.reloadConfiguration()) .then(() => { actual = testObject.inspect('configurationService.workspace.testResourceSetting'); - assert.equal(actual.default, 'isSet'); - assert.equal(actual.user, 'userValue'); - assert.equal(actual.workspace, 'workspaceValue'); - assert.equal(actual.workspaceFolder, undefined); + assert.equal(actual.defaultValue, 'isSet'); + assert.equal(actual.userValue, 'userValue'); + assert.equal(actual.workspaceValue, 'workspaceValue'); + assert.equal(actual.workspaceFolderValue, undefined); assert.equal(actual.value, 'workspaceValue'); fs.writeFileSync(workspaceContextService.getWorkspace().folders[0].toResource('.vscode/settings.json').fsPath, '{ "configurationService.workspace.testResourceSetting": "workspaceFolderValue" }'); @@ -1346,10 +1359,10 @@ suite.skip('WorkspaceConfigurationService-Multiroot', () => { // {{SQL CARBON ED return testObject.reloadConfiguration() .then(() => { actual = testObject.inspect('configurationService.workspace.testResourceSetting', { resource: workspaceContextService.getWorkspace().folders[0].uri }); - assert.equal(actual.default, 'isSet'); - assert.equal(actual.user, 'userValue'); - assert.equal(actual.workspace, 'workspaceValue'); - assert.equal(actual.workspaceFolder, 'workspaceFolderValue'); + assert.equal(actual.defaultValue, 'isSet'); + assert.equal(actual.userValue, 'userValue'); + assert.equal(actual.workspaceValue, 'workspaceValue'); + assert.equal(actual.workspaceFolderValue, 'workspaceFolderValue'); assert.equal(actual.value, 'workspaceFolderValue'); }); }); @@ -1401,7 +1414,7 @@ suite.skip('WorkspaceConfigurationService-Multiroot', () => { // {{SQL CARBON ED return jsonEditingServce.write(workspaceContextService.getWorkspace().configuration!, [{ key: 'launch', value: expectedLaunchConfiguration }], true) .then(() => testObject.reloadConfiguration()) .then(() => { - const actual = testObject.inspect('launch').workspace; + const actual = testObject.inspect('launch').workspaceValue; assert.deepEqual(actual, expectedLaunchConfiguration); }); }); @@ -1430,7 +1443,7 @@ suite.skip('WorkspaceConfigurationService-Multiroot', () => { // {{SQL CARBON ED }); }); - test('inspect tasks configuration', () => { + test('inspect tasks configuration', async () => { const expectedTasksConfiguration = { 'version': '2.0.0', 'tasks': [ @@ -1445,12 +1458,10 @@ suite.skip('WorkspaceConfigurationService-Multiroot', () => { // {{SQL CARBON ED } ] }; - return jsonEditingServce.write(workspaceContextService.getWorkspace().configuration!, [{ key: 'tasks', value: expectedTasksConfiguration }], true) - .then(() => testObject.reloadConfiguration()) - .then(() => { - const actual = testObject.inspect('tasks').workspace; - assert.deepEqual(actual, expectedTasksConfiguration); - }); + await jsonEditingServce.write(workspaceContextService.getWorkspace().configuration!, [{ key: 'tasks', value: expectedTasksConfiguration }], true); + await testObject.reloadConfiguration(); + const actual = testObject.inspect('tasks').workspaceValue; + assert.deepEqual(actual, expectedTasksConfiguration); }); test('update user configuration', () => { diff --git a/src/vs/workbench/services/editor/common/editorGroupsService.ts b/src/vs/workbench/services/editor/common/editorGroupsService.ts index f643470938..061a27da53 100644 --- a/src/vs/workbench/services/editor/common/editorGroupsService.ts +++ b/src/vs/workbench/services/editor/common/editorGroupsService.ts @@ -6,7 +6,7 @@ import { Event } from 'vs/base/common/event'; import { createDecorator, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IEditorInput, IEditor, GroupIdentifier, IEditorInputWithOptions, CloseDirection, IEditorPartOptions } from 'vs/workbench/common/editor'; -import { IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/editor'; +import { IEditorOptions, ITextEditorOptions, IResourceInput } from 'vs/platform/editor/common/editor'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IVisibleEditor } from 'vs/workbench/services/editor/common/editorService'; import { IDimension } from 'vs/editor/common/editorCommon'; @@ -379,6 +379,11 @@ export interface IEditorGroup { */ readonly onDidGroupChange: Event; + /** + * An event that is fired when the group gets disposed. + */ + readonly onWillDispose: Event; + /** * A unique identifier of this group that remains identical even if the * group is moved to different locations. @@ -467,7 +472,7 @@ export interface IEditorGroup { * * Note: An editor can be opened but not actively visible. */ - isOpened(editor: IEditorInput): boolean; + isOpened(editor: IEditorInput | IResourceInput): boolean; /** * Find out if the provided editor is pinned in the group. diff --git a/src/vs/workbench/services/editor/common/editorService.ts b/src/vs/workbench/services/editor/common/editorService.ts index b0802af3b5..ebe14b4c29 100644 --- a/src/vs/workbench/services/editor/common/editorService.ts +++ b/src/vs/workbench/services/editor/common/editorService.ts @@ -7,7 +7,7 @@ import { createDecorator, ServicesAccessor } from 'vs/platform/instantiation/com import { IResourceInput, IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { IEditorInput, IEditor, GroupIdentifier, IEditorInputWithOptions, IUntitledTextResourceInput, IResourceDiffInput, IResourceSideBySideInput, ITextEditor, ITextDiffEditor, ITextSideBySideEditor, IEditorIdentifier, ISaveOptions, IRevertOptions } from 'vs/workbench/common/editor'; import { Event } from 'vs/base/common/event'; -import { IEditor as ICodeEditor } from 'vs/editor/common/editorCommon'; +import { IEditor as ICodeEditor, IDiffEditor } from 'vs/editor/common/editorCommon'; import { IEditorGroup, IEditorReplacement } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IDisposable } from 'vs/base/common/lifecycle'; @@ -103,7 +103,7 @@ export interface IEditorService { * * @see `IEditorService.activeEditor` */ - readonly activeTextEditorWidget: ICodeEditor | undefined; + readonly activeTextEditorWidget: ICodeEditor | IDiffEditor | undefined; /** * All editors that are currently visible. An editor is visible when it is opened in an @@ -175,19 +175,15 @@ export interface IEditorService { * a specific editor group. * * Note: An editor can be opened but not actively visible. - * - * @param group optional to specify a group to check for the editor being opened */ - isOpen(editor: IEditorInput | IResourceInput | IUntitledTextResourceInput, group?: IEditorGroup | GroupIdentifier): boolean; + isOpen(editor: IEditorInput | IResourceInput | IUntitledTextResourceInput): boolean; /** * Get the actual opened editor input in any or a specific editor group based on the resource. * * Note: An editor can be opened but not actively visible. - * - * @param group optional to specify a group to check for the editor */ - getOpened(editor: IResourceInput | IUntitledTextResourceInput, group?: IEditorGroup | GroupIdentifier): IEditorInput | undefined; + getOpened(editor: IResourceInput | IUntitledTextResourceInput): IEditorInput | undefined; /** * Allows to override the opening of editors by installing a handler that will diff --git a/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts b/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts index 990c642d2d..293dd192fc 100644 --- a/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts @@ -19,10 +19,14 @@ import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtil import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -export class TestEditorControl extends BaseEditor { +const TEST_EDITOR_ID = 'MyFileEditorForEditorGroupService'; +const TEST_EDITOR_INPUT_ID = 'testEditorInputForEditorGroupService'; - constructor(@ITelemetryService telemetryService: ITelemetryService) { super('MyFileEditorForEditorGroupService', NullTelemetryService, new TestThemeService(), new TestStorageService()); } +class TestEditorControl extends BaseEditor { + + constructor(@ITelemetryService telemetryService: ITelemetryService) { super(TEST_EDITOR_ID, NullTelemetryService, new TestThemeService(), new TestStorageService()); } async setInput(input: EditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise { super.setInput(input, options, token); @@ -30,16 +34,16 @@ export class TestEditorControl extends BaseEditor { await input.resolve(); } - getId(): string { return 'MyFileEditorForEditorGroupService'; } + getId(): string { return TEST_EDITOR_ID; } layout(): void { } createEditor(): any { } } -export class TestEditorInput extends EditorInput implements IFileEditorInput { +class TestEditorInput extends EditorInput implements IFileEditorInput { constructor(private resource: URI) { super(); } - getTypeId() { return 'testEditorInputForEditorGroupService'; } + getTypeId() { return TEST_EDITOR_INPUT_ID; } resolve(): Promise { return Promise.resolve(null); } matches(other: TestEditorInput): boolean { return other && this.resource.toString() === other.resource.toString() && other instanceof TestEditorInput; } setEncoding(encoding: string) { } @@ -53,14 +57,19 @@ export class TestEditorInput extends EditorInput implements IFileEditorInput { suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} skip suite - function registerTestEditorInput(): void { + let disposables: IDisposable[] = []; + setup(() => { interface ISerializedTestEditorInput { resource: string; } class TestEditorInputFactory implements IEditorInputFactory { + canSerialize(editorInput: EditorInput): boolean { + return true; + } + serialize(editorInput: EditorInput): string { const testEditorInput = editorInput; const testInput: ISerializedTestEditorInput = { @@ -77,11 +86,14 @@ suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} skip suite } } - (Registry.as(EditorExtensions.EditorInputFactories)).registerEditorInputFactory('testEditorInputForGroupsService', TestEditorInputFactory); - (Registry.as(Extensions.Editors)).registerEditor(EditorDescriptor.create(TestEditorControl, 'MyTestEditorForGroupsService', 'My Test File Editor'), [new SyncDescriptor(TestEditorInput)]); - } + disposables.push((Registry.as(EditorExtensions.EditorInputFactories)).registerEditorInputFactory(TEST_EDITOR_INPUT_ID, TestEditorInputFactory)); + disposables.push((Registry.as(Extensions.Editors)).registerEditor(EditorDescriptor.create(TestEditorControl, TEST_EDITOR_ID, 'My Test File Editor'), [new SyncDescriptor(TestEditorInput)])); + }); - registerTestEditorInput(); + teardown(() => { + dispose(disposables); + disposables = []; + }); function createPart(): EditorPart { const instantiationService = workbenchInstantiationService(); @@ -435,6 +447,8 @@ suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} skip suite assert.equal(group.isActive(inputInactive), false); assert.equal(group.isOpened(input), true); assert.equal(group.isOpened(inputInactive), true); + assert.equal(group.isOpened({ resource: input.getResource() }), true); + assert.equal(group.isOpened({ resource: inputInactive.getResource() }), true); assert.equal(group.isEmpty, false); assert.equal(group.count, 2); assert.equal(editorWillOpenCounter, 2); diff --git a/src/vs/workbench/services/editor/test/browser/editorService.test.ts b/src/vs/workbench/services/editor/test/browser/editorService.test.ts index 05ba6152db..318e489b15 100644 --- a/src/vs/workbench/services/editor/test/browser/editorService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorService.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { IEditorModel, EditorActivation } from 'vs/platform/editor/common/editor'; +import { EditorActivation, IEditorModel } from 'vs/platform/editor/common/editor'; import { URI } from 'vs/base/common/uri'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { EditorInput, EditorOptions, IFileEditorInput, GroupIdentifier, ISaveOptions, IRevertOptions } from 'vs/workbench/common/editor'; @@ -16,26 +16,29 @@ import { IEditorGroup, IEditorGroupsService, GroupDirection, GroupsArrangement } import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { IEditorRegistry, EditorDescriptor, Extensions } from 'vs/workbench/browser/editor'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { Registry } from 'vs/platform/registry/common/platform'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput'; import { EditorServiceImpl } from 'vs/workbench/browser/parts/editor/editor'; -import { CancellationToken } from 'vs/base/common/cancellation'; import { timeout } from 'vs/base/common/async'; import { toResource } from 'vs/base/test/common/utils'; import { IFileService } from 'vs/platform/files/common/files'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; import { ModesRegistry } from 'vs/editor/common/modes/modesRegistry'; import { UntitledTextEditorModel } from 'vs/workbench/common/editor/untitledTextEditorModel'; import { NullFileSystemProvider } from 'vs/platform/files/test/common/nullFileSystemProvider'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; +import { CancellationToken } from 'vscode'; -export class TestEditorControl extends BaseEditor { +const TEST_EDITOR_ID = 'MyTestEditorForEditorService'; +const TEST_EDITOR_INPUT_ID = 'testEditorInputForEditorService'; - constructor(@ITelemetryService telemetryService: ITelemetryService) { super('MyTestEditorForEditorService', NullTelemetryService, new TestThemeService(), new TestStorageService()); } +class TestEditorControl extends BaseEditor { + + constructor(@ITelemetryService telemetryService: ITelemetryService) { super(TEST_EDITOR_ID, NullTelemetryService, new TestThemeService(), new TestStorageService()); } async setInput(input: EditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise { super.setInput(input, options, token); @@ -43,21 +46,21 @@ export class TestEditorControl extends BaseEditor { await input.resolve(); } - getId(): string { return 'MyTestEditorForEditorService'; } + getId(): string { return TEST_EDITOR_ID; } layout(): void { } createEditor(): any { } } -export class TestEditorInput extends EditorInput implements IFileEditorInput { - public gotDisposed = false; - public gotSaved = false; - public gotSavedAs = false; - public gotReverted = false; - public dirty = false; +class TestEditorInput extends EditorInput implements IFileEditorInput { + gotDisposed = false; + gotSaved = false; + gotSavedAs = false; + gotReverted = false; + dirty = false; private fails = false; - constructor(private resource: URI) { super(); } + constructor(public resource: URI) { super(); } - getTypeId() { return 'testEditorInputForEditorService'; } + getTypeId() { return TEST_EDITOR_INPUT_ID; } resolve(): Promise { return !this.fails ? Promise.resolve(null) : Promise.reject(new Error('fails')); } matches(other: TestEditorInput): boolean { return other && other.resource && this.resource.toString() === other.resource.toString() && other instanceof TestEditorInput; } setEncoding(encoding: string) { } @@ -106,11 +109,16 @@ class FileServiceProvider extends Disposable { suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} skip suite - function registerTestEditorInput(): void { - Registry.as(Extensions.Editors).registerEditor(EditorDescriptor.create(TestEditorControl, 'MyTestEditorForEditorService', 'My Test Editor For Next Editor Service'), [new SyncDescriptor(TestEditorInput)]); - } + let disposables: IDisposable[] = []; - registerTestEditorInput(); + setup(() => { + disposables.push(Registry.as(Extensions.Editors).registerEditor(EditorDescriptor.create(TestEditorControl, TEST_EDITOR_ID, 'My Test Editor For Next Editor Service'), [new SyncDescriptor(TestEditorInput)])); + }); + + teardown(() => { + dispose(disposables); + disposables = []; + }); test('basics', async () => { const partInstantiator = workbenchInstantiationService(); @@ -137,7 +145,7 @@ suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} skip suite }); let didCloseEditorListenerCounter = 0; - const didCloseEditorListener = service.onDidCloseEditor(editor => { + const didCloseEditorListener = service.onDidCloseEditor(() => { didCloseEditorListenerCounter++; }); @@ -155,7 +163,6 @@ suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} skip suite assert.equal(service.visibleTextEditorWidgets.length, 0); assert.equal(service.isOpen(input), true); assert.equal(service.getOpened({ resource: input.getResource() }), input); - assert.equal(service.isOpen(input, part.activeGroup), true); assert.equal(activeEditorChangeEventCounter, 1); assert.equal(visibleEditorChangeEventCounter, 1); @@ -181,6 +188,8 @@ suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} skip suite activeEditorChangeListener.dispose(); visibleEditorChangeListener.dispose(); didCloseEditorListener.dispose(); + + part.dispose(); }); test('openEditors() / replaceEditors()', async () => { @@ -208,6 +217,8 @@ suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} skip suite await service.replaceEditors([{ editor: input, replacement: replaceInput }], part.activeGroup); assert.equal(part.activeGroup.count, 2); assert.equal(part.activeGroup.getIndexOfEditor(replaceInput), 0); + + part.dispose(); }); test('caching', function () { @@ -354,7 +365,7 @@ suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} skip suite const inp = instantiationService.createInstance(ResourceEditorInput, 'name', 'description', URI.parse('my://resource-delegate'), undefined); const delegate = instantiationService.createInstance(DelegatingEditorService); - delegate.setEditorOpenHandler((delegate, group, input, options?) => { + delegate.setEditorOpenHandler((delegate, group, input) => { assert.strictEqual(input, inp); done(); @@ -398,6 +409,8 @@ suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} skip suite await rightGroup.closeEditor(input); assert.equal(input.isDisposed(), true); + + part.dispose(); }); test('open to the side', async () => { @@ -430,6 +443,8 @@ suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} skip suite assert.equal(part.activeGroup, rootGroup); assert.equal(part.count, 2); assert.equal(editor!.group, part.groups[1]); + + part.dispose(); }); test('editor group activation', async () => { @@ -471,6 +486,8 @@ suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} skip suite part.arrangeGroups(GroupsArrangement.MINIMIZE_OTHERS); editor = await service.openEditor(input1, { pinned: true, preserveFocus: true, activation: EditorActivation.RESTORE }, rootGroup); assert.equal(part.activeGroup, sideGroup); + + part.dispose(); }); test('active editor change / visible editor change events', async function () { @@ -681,6 +698,8 @@ suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} skip suite // cleanup activeEditorChangeListener.dispose(); visibleEditorChangeListener.dispose(); + + part.dispose(); }); test('openEditor returns NULL when opening fails or is inactive', async function () { @@ -709,6 +728,8 @@ suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} skip suite let failingEditor = await service.openEditor(failingInput); assert.ok(!failingEditor); + + part.dispose(); }); test('save, saveAll, revertAll', async function () { @@ -750,5 +771,7 @@ suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} skip suite await service.saveAll({ saveAs: true }); assert.equal(input1.gotSavedAs, true); assert.equal(input2.gotSavedAs, true); + + part.dispose(); }); }); diff --git a/src/vs/workbench/services/extensions/common/extensionDescriptionRegistry.ts b/src/vs/workbench/services/extensions/common/extensionDescriptionRegistry.ts index 7d0e77ccad..b00611025e 100644 --- a/src/vs/workbench/services/extensions/common/extensionDescriptionRegistry.ts +++ b/src/vs/workbench/services/extensions/common/extensionDescriptionRegistry.ts @@ -5,6 +5,7 @@ import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { Emitter } from 'vs/base/common/event'; +import * as path from 'vs/base/common/path'; export class DeltaExtensionsResult { constructor( @@ -27,6 +28,9 @@ export class ExtensionDescriptionRegistry { } private _initialize(): void { + // Ensure extensions are stored in the order: builtin, user, under development + this._extensionDescriptions.sort(extensionCmp); + this._extensionsMap = new Map(); this._extensionsArr = []; this._activationMap = new Map(); @@ -193,3 +197,34 @@ export class ExtensionDescriptionRegistry { return extension ? extension : undefined; } } + +const enum SortBucket { + Builtin = 0, + User = 1, + Dev = 2 +} + +/** + * Ensure that: + * - first are builtin extensions + * - second are user extensions + * - third are extensions under development + * + * In each bucket, extensions must be sorted alphabetically by their folder name. + */ +function extensionCmp(a: IExtensionDescription, b: IExtensionDescription): number { + const aSortBucket = (a.isBuiltin ? SortBucket.Builtin : a.isUnderDevelopment ? SortBucket.Dev : SortBucket.User); + const bSortBucket = (b.isBuiltin ? SortBucket.Builtin : b.isUnderDevelopment ? SortBucket.Dev : SortBucket.User); + if (aSortBucket !== bSortBucket) { + return aSortBucket - bSortBucket; + } + const aLastSegment = path.posix.basename(a.extensionLocation.path); + const bLastSegment = path.posix.basename(b.extensionLocation.path); + if (aLastSegment < bLastSegment) { + return -1; + } + if (aLastSegment > bLastSegment) { + return 1; + } + return 0; +} diff --git a/src/vs/workbench/services/extensions/common/lazyPromise.ts b/src/vs/workbench/services/extensions/common/lazyPromise.ts index 827c5a0123..add8577258 100644 --- a/src/vs/workbench/services/extensions/common/lazyPromise.ts +++ b/src/vs/workbench/services/extensions/common/lazyPromise.ts @@ -27,6 +27,10 @@ export class LazyPromise implements Promise { this._err = null; } + get [Symbol.toStringTag](): string { + return this.toString(); + } + private _ensureActual(): Promise { if (!this._actual) { this._actual = new Promise((c, e) => { diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts index 5ad6cedc6b..1a83f08241 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts @@ -38,6 +38,7 @@ import { flatten } from 'vs/base/common/arrays'; import { IStaticExtensionsService } from 'vs/workbench/services/extensions/common/staticExtensions'; import { IElectronService } from 'vs/platform/electron/node/electron'; import { IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService'; +import { IRemoteExplorerService } from 'vs/workbench/services/remote/common/remoteExplorerService'; class DeltaExtensionsQueueItem { constructor( @@ -70,7 +71,8 @@ export class ExtensionService extends AbstractExtensionService implements IExten @IStaticExtensionsService private readonly _staticExtensions: IStaticExtensionsService, @IElectronService private readonly _electronService: IElectronService, @IHostService private readonly _hostService: IHostService, - @IElectronEnvironmentService private readonly _electronEnvironmentService: IElectronEnvironmentService + @IElectronEnvironmentService private readonly _electronEnvironmentService: IElectronEnvironmentService, + @IRemoteExplorerService private readonly _remoteExplorerService: IRemoteExplorerService ) { super( instantiationService, @@ -476,6 +478,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten // set the resolved authority this._remoteAuthorityResolverService.setResolvedAuthority(resolvedAuthority.authority, resolvedAuthority.options); + this._remoteExplorerService.addDetected(resolvedAuthority.tunnelInformation?.detectedTunnels); // monitor for breakage const connection = this._remoteAgentService.getConnection(); diff --git a/src/vs/workbench/services/extensions/worker/extHost.services.ts b/src/vs/workbench/services/extensions/worker/extHost.services.ts index ba8ade6718..d4e5e1b9cb 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 } from 'vs/workbench/api/common/extHostTunnelService'; +import { IExtHostTunnelService, ExtHostTunnelService } from 'vs/workbench/api/common/extHostTunnelService'; // register singleton services registerSingleton(ILogService, ExtHostLogService); @@ -34,6 +34,7 @@ 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,4 +55,3 @@ 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(IExtHostTunnelService, class extends NotImplementedProxy(IExtHostTunnelService) { }); diff --git a/src/vs/workbench/services/filesConfiguration/common/filesConfigurationService.ts b/src/vs/workbench/services/filesConfiguration/common/filesConfigurationService.ts index 9e3e9ab286..2235b4ca9e 100644 --- a/src/vs/workbench/services/filesConfiguration/common/filesConfigurationService.ts +++ b/src/vs/workbench/services/filesConfiguration/common/filesConfigurationService.ts @@ -184,9 +184,9 @@ export class FilesConfigurationService extends Disposable implements IFilesConfi async toggleAutoSave(): Promise { const setting = this.configurationService.inspect('files.autoSave'); - let userAutoSaveConfig = setting.user; + let userAutoSaveConfig = setting.userValue; if (isUndefinedOrNull(userAutoSaveConfig)) { - userAutoSaveConfig = setting.default; // use default if setting not defined + userAutoSaveConfig = setting.defaultValue; // use default if setting not defined } let newAutoSaveValue: string; diff --git a/src/vs/workbench/services/history/browser/history.ts b/src/vs/workbench/services/history/browser/history.ts index 85981bdaeb..c0a5d22e94 100644 --- a/src/vs/workbench/services/history/browser/history.ts +++ b/src/vs/workbench/services/history/browser/history.ts @@ -7,7 +7,7 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { URI, UriComponents } from 'vs/base/common/uri'; import { IEditor } from 'vs/editor/common/editorCommon'; import { ITextEditorOptions, IResourceInput, ITextEditorSelection } from 'vs/platform/editor/common/editor'; -import { IEditorInput, IEditor as IBaseEditor, Extensions as EditorExtensions, EditorInput, IEditorCloseEvent, IEditorInputFactoryRegistry, toResource, IEditorIdentifier } from 'vs/workbench/common/editor'; +import { IEditorInput, IEditor as IBaseEditor, Extensions as EditorExtensions, EditorInput, IEditorCloseEvent, IEditorInputFactoryRegistry, toResource, IEditorIdentifier, GroupIdentifier, Extensions } from 'vs/workbench/common/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { FileChangesEvent, IFileService, FileChangeType, FILES_EXCLUDE_CONFIG } from 'vs/platform/files/common/files'; @@ -16,9 +16,9 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace import { dispose, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { Registry } from 'vs/platform/registry/common/platform'; -import { Event } from 'vs/base/common/event'; +import { Event, Emitter } from 'vs/base/common/event'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; -import { IEditorGroupsService, IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorGroupsService, IEditorGroup, EditorsOrder, GroupChangeKind, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService'; import { getCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { getExcludes, ISearchConfiguration } from 'vs/workbench/services/search/common/search'; import { IExpression } from 'vs/base/common/glob'; @@ -27,13 +27,16 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { ResourceGlobMatcher } from 'vs/workbench/common/resources'; import { EditorServiceImpl } from 'vs/workbench/browser/parts/editor/editor'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; -import { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { coalesce } from 'vs/base/common/arrays'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { withNullAsUndefined } from 'vs/base/common/types'; import { addDisposableListener, EventType, EventHelper } from 'vs/base/browser/dom'; import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; import { Schemas } from 'vs/base/common/network'; +import { LinkedMap, Touch } from 'vs/base/common/map'; + +//#region Text Editor State helper /** * Stores the selection & view state of an editor and allows to compare it to other selection states. @@ -83,6 +86,295 @@ export class TextEditorState { } } +//#endregion + +//#region Editors History + +interface ISerializedEditorHistory { + history: ISerializedEditorIdentifier[]; +} + +interface ISerializedEditorIdentifier { + groupId: GroupIdentifier; + index: number; +} + +/** + * A history of opened editors across all editor groups by most recently used. + * Rules: + * - the last editor in the history is the one most recently activated + * - the first editor in the history is the one that was activated the longest time ago + * - an editor that opens inactive will be placed behind the currently active editor + */ +export class EditorsHistory extends Disposable { + + private static readonly STORAGE_KEY = 'history.editors'; + + private readonly keyMap = new Map>(); + private readonly mostRecentEditorsMap = new LinkedMap(); + + private readonly _onDidChange = this._register(new Emitter()); + readonly onDidChange = this._onDidChange.event; + + get editors(): IEditorIdentifier[] { + return this.mostRecentEditorsMap.values(); + } + + constructor( + @IEditorGroupsService private editorGroupsService: IEditorGroupsService, + @IStorageService private readonly storageService: IStorageService + ) { + super(); + + this.registerListeners(); + } + + private registerListeners(): void { + this._register(this.storageService.onWillSaveState(() => this.saveState())); + this._register(this.editorGroupsService.onDidAddGroup(group => this.onGroupAdded(group))); + + this.editorGroupsService.whenRestored.then(() => this.loadState()); + } + + private onGroupAdded(group: IEditorGroup): void { + + // Make sure to add any already existing editor + // of the new group into our history in LRU order + const groupEditorsMru = group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE); + for (let i = groupEditorsMru.length - 1; i >= 0; i--) { + this.addMostRecentEditor(group, groupEditorsMru[i], false /* is not active */); + } + + // Make sure that active editor is put as first if group is active + if (this.editorGroupsService.activeGroup === group && group.activeEditor) { + this.addMostRecentEditor(group, group.activeEditor, true /* is active */); + } + + // Group Listeners + this.registerGroupListeners(group); + } + + private registerGroupListeners(group: IEditorGroup): void { + const groupDisposables = new DisposableStore(); + groupDisposables.add(group.onDidGroupChange(e => { + switch (e.kind) { + + // Group gets active: put active editor as most recent + case GroupChangeKind.GROUP_ACTIVE: { + if (this.editorGroupsService.activeGroup === group && group.activeEditor) { + this.addMostRecentEditor(group, group.activeEditor, true /* is active */); + } + + break; + } + + // Editor gets active: put active editor as most recent + // if group is active, otherwise second most recent + case GroupChangeKind.EDITOR_ACTIVE: { + if (e.editor) { + this.addMostRecentEditor(group, e.editor, this.editorGroupsService.activeGroup === group); + } + + break; + } + + // Editor opens: put it as second most recent + case GroupChangeKind.EDITOR_OPEN: { + if (e.editor) { + this.addMostRecentEditor(group, e.editor, false /* is not active */); + } + + break; + } + + // Editor closes: remove from recently opened + case GroupChangeKind.EDITOR_CLOSE: { + if (e.editor) { + this.removeMostRecentEditor(group, e.editor); + } + + break; + } + } + })); + + // Make sure to cleanup on dispose + Event.once(group.onWillDispose)(() => dispose(groupDisposables)); + } + + private addMostRecentEditor(group: IEditorGroup, editor: IEditorInput, isActive: boolean): void { + const key = this.ensureKey(group, editor); + const mostRecentEditor = this.mostRecentEditorsMap.first; + + // Active or first entry: add to end of map + if (isActive || !mostRecentEditor) { + this.mostRecentEditorsMap.set(key, key, mostRecentEditor ? Touch.AsOld /* make first */ : undefined); + } + + // Otherwise: insert before most recent + else { + // we have most recent editors. as such we + // put this newly opened editor right before + // 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.mostRecentEditorsMap.set(key, key, Touch.AsOld /* make first */); + this.mostRecentEditorsMap.set(mostRecentEditor, mostRecentEditor, Touch.AsOld /* make first */); + } + + // Event + this._onDidChange.fire(); + } + + private removeMostRecentEditor(group: IEditorGroup, editor: IEditorInput): void { + const key = this.findKey(group, editor); + if (key) { + + // Remove from most recent editors + this.mostRecentEditorsMap.delete(key); + + // Remove from key map + const map = this.keyMap.get(group.id); + if (map && map.delete(key.editor) && map.size === 0) { + this.keyMap.delete(group.id); + } + + // Event + this._onDidChange.fire(); + } + } + + private findKey(group: IEditorGroup, editor: IEditorInput): IEditorIdentifier | undefined { + const groupMap = this.keyMap.get(group.id); + if (!groupMap) { + return undefined; + } + + return groupMap.get(editor); + } + + private ensureKey(group: IEditorGroup, editor: IEditorInput): IEditorIdentifier { + let groupMap = this.keyMap.get(group.id); + if (!groupMap) { + groupMap = new Map(); + + this.keyMap.set(group.id, groupMap); + } + + let key = groupMap.get(editor); + if (!key) { + key = { groupId: group.id, editor }; + groupMap.set(editor, key); + } + + return key; + } + + private saveState(): void { + if (this.mostRecentEditorsMap.isEmpty()) { + this.storageService.remove(EditorsHistory.STORAGE_KEY, StorageScope.WORKSPACE); + } else { + this.storageService.store(EditorsHistory.STORAGE_KEY, JSON.stringify(this.serialize()), StorageScope.WORKSPACE); + } + } + + private serialize(): ISerializedEditorHistory { + const registry = Registry.as(Extensions.EditorInputFactories); + + const history = this.mostRecentEditorsMap.values(); + const mapGroupToSerializableEditorsOfGroup = new Map(); + + return { + history: coalesce(history.map(({ editor, groupId }) => { + + // Find group for entry + const group = this.editorGroupsService.getGroup(groupId); + if (!group) { + return undefined; + } + + // Find serializable editors of group + let serializableEditorsOfGroup = mapGroupToSerializableEditorsOfGroup.get(group); + if (!serializableEditorsOfGroup) { + serializableEditorsOfGroup = group.getEditors(EditorsOrder.SEQUENTIAL).filter(editor => { + const factory = registry.getEditorInputFactory(editor.getTypeId()); + + return factory?.canSerialize(editor); + }); + mapGroupToSerializableEditorsOfGroup.set(group, serializableEditorsOfGroup); + } + + // Only store the index of the editor of that group + // which can be undefined if the editor is not serializable + const index = serializableEditorsOfGroup.indexOf(editor); + if (index === -1) { + return undefined; + } + + return { groupId, index }; + })) + }; + } + + private loadState(): void { + const serialized = this.storageService.get(EditorsHistory.STORAGE_KEY, StorageScope.WORKSPACE); + + // Previous state: + if (serialized) { + + // Load history map from persisted state + this.deserialize(JSON.parse(serialized)); + } + + // No previous state: best we can do is add each editor + // from oldest to most recently used editor group + else { + const groups = this.editorGroupsService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE); + for (let i = groups.length - 1; i >= 0; i--) { + const group = groups[i]; + const groupEditorsMru = group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE); + for (let i = groupEditorsMru.length - 1; i >= 0; i--) { + this.addMostRecentEditor(group, groupEditorsMru[i], true /* enforce as active to preserve order */); + } + } + } + + // Ensure we listen on group changes for those that exist on startup + for (const group of this.editorGroupsService.groups) { + this.registerGroupListeners(group); + } + } + + deserialize(serialized: ISerializedEditorHistory): void { + const mapValues: [IEditorIdentifier, IEditorIdentifier][] = []; + + for (const { groupId, index } of serialized.history) { + + // Find group for entry + const group = this.editorGroupsService.getGroup(groupId); + if (!group) { + continue; + } + + // Find editor for entry + const editor = group.getEditorByIndex(index); + if (!editor) { + continue; + } + + // Make sure key is registered as well + const editorIdentifier = this.ensureKey(group, editor); + mapValues.push([editorIdentifier, editorIdentifier]); + } + + // Fill map with deserialized values + this.mostRecentEditorsMap.fromJSON(mapValues); + } +} + +//#endregion + interface ISerializedEditorHistoryEntry { resourceJSON?: object; editorInputJSON?: { typeId: string; deserialized: string; }; @@ -102,34 +394,12 @@ export class HistoryService extends Disposable implements IHistoryService { _serviceBrand: undefined; - private static readonly STORAGE_KEY = 'history.entries'; - private static readonly MAX_HISTORY_ITEMS = 200; - private static readonly MAX_STACK_ITEMS = 50; - private static readonly MAX_RECENTLY_CLOSED_EDITORS = 20; - private readonly activeEditorListeners = this._register(new DisposableStore()); private lastActiveEditor?: IEditorIdentifier; private readonly editorHistoryListeners: Map = new Map(); private readonly editorStackListeners: Map = new Map(); - private stack: IStackEntry[]; - private index: number; - private lastIndex: number; - private navigatingInStack = false; - private currentTextEditorState: TextEditorState | null = null; - - private lastEditLocation: IStackEntry | undefined; - - private history: Array = []; - private recentlyClosedFiles: IRecentlyClosedFile[]; - private loaded: boolean; - private resourceFilter: ResourceGlobMatcher; - - private canNavigateBackContextKey: IContextKey; - private canNavigateForwardContextKey: IContextKey; - private canNavigateToLastEditLocationContextKey: IContextKey; - constructor( @IEditorService private readonly editorService: EditorServiceImpl, @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, @@ -144,37 +414,17 @@ export class HistoryService extends Disposable implements IHistoryService { ) { super(); - this.canNavigateBackContextKey = (new RawContextKey('canNavigateBack', false)).bindTo(this.contextKeyService); - this.canNavigateForwardContextKey = (new RawContextKey('canNavigateForward', false)).bindTo(this.contextKeyService); - this.canNavigateToLastEditLocationContextKey = (new RawContextKey('canNavigateToLastEditLocation', false)).bindTo(this.contextKeyService); - - this.index = -1; - this.lastIndex = -1; - this.stack = []; - this.recentlyClosedFiles = []; - this.loaded = false; - this.resourceFilter = this._register(instantiationService.createInstance( - ResourceGlobMatcher, - (root?: URI) => this.getExcludes(root), - (event: IConfigurationChangeEvent) => event.affectsConfiguration(FILES_EXCLUDE_CONFIG) || event.affectsConfiguration('search.exclude') - )); - this.registerListeners(); } - private getExcludes(root?: URI): IExpression { - const scope = root ? { resource: root } : undefined; - - return getExcludes(scope ? this.configurationService.getValue(scope) : this.configurationService.getValue())!; - } - private registerListeners(): void { this._register(this.editorService.onDidActiveEditorChange(() => this.onActiveEditorChanged())); this._register(this.editorService.onDidOpenEditorFail(event => this.remove(event.editor))); this._register(this.editorService.onDidCloseEditor(event => this.onEditorClosed(event))); this._register(this.storageService.onWillSaveState(() => this.saveState())); this._register(this.fileService.onFileChanges(event => this.onFileChanges(event))); - this._register(this.resourceFilter.onExpressionChange(() => this.handleExcludesChange())); + this._register(this.resourceFilter.onExpressionChange(() => this.removeExcludedFromHistory())); + this._register(this.mostRecentlyUsedOpenEditors.onDidChange(() => this.handleEditorEventInRecentEditorsStack())); // if the service is created late enough that an editor is already opened // make sure to trigger the onActiveEditorChanged() to track the editor @@ -255,19 +505,6 @@ export class HistoryService extends Disposable implements IHistoryService { } } - private rememberLastEditLocation(activeEditor: IEditorInput, activeTextEditorWidget: ICodeEditor): void { - this.lastEditLocation = { input: activeEditor }; - this.canNavigateToLastEditLocationContextKey.set(true); - - const position = activeTextEditorWidget.getPosition(); - if (position) { - this.lastEditLocation.selection = { - startLineNumber: position.lineNumber, - startColumn: position.column - }; - } - } - private matchesEditor(identifier: IEditorIdentifier, editor?: IBaseEditor): boolean { if (!editor || !editor.group) { return false; @@ -286,228 +523,13 @@ export class HistoryService extends Disposable implements IHistoryService { } } - private onEditorClosed(event: IEditorCloseEvent): void { - - // Track closing of editor to support to reopen closed editors (unless editor was replaced) - if (!event.replaced) { - const resource = event.editor ? event.editor.getResource() : undefined; - const supportsReopen = resource && this.fileService.canHandleResource(resource); // we only support file'ish things to reopen - if (resource && supportsReopen) { - - // Remove all inputs matching and add as last recently closed - this.removeFromRecentlyClosedFiles(event.editor); - this.recentlyClosedFiles.push({ resource, index: event.index }); - - // Bounding - if (this.recentlyClosedFiles.length > HistoryService.MAX_RECENTLY_CLOSED_EDITORS) { - this.recentlyClosedFiles.shift(); - } - } - } + private handleEditorSelectionChangeEvent(editor?: IBaseEditor, event?: ICursorPositionChangedEvent): void { + this.handleEditorEventInNavigationStack(editor, event); } - reopenLastClosedEditor(): void { - this.ensureHistoryLoaded(); - - let lastClosedFile = this.recentlyClosedFiles.pop(); - while (lastClosedFile && this.isFileOpened(lastClosedFile.resource, this.editorGroupService.activeGroup)) { - lastClosedFile = this.recentlyClosedFiles.pop(); // pop until we find a file that is not opened - } - - if (lastClosedFile) { - this.editorService.openEditor({ resource: lastClosedFile.resource, options: { pinned: true, index: lastClosedFile.index } }).then(editor => { - - // Fix for https://github.com/Microsoft/vscode/issues/67882 - // If opening of the editor fails, make sure to try the next one - // but make sure to remove this one from the list to prevent - // endless loops. - if (!editor) { - this.recentlyClosedFiles.pop(); - this.reopenLastClosedEditor(); - } - }); - } - } - - openLastEditLocation(): void { - if (this.lastEditLocation) { - this.doNavigate(this.lastEditLocation, true); - } - } - - forward(acrossEditors?: boolean): void { - if (this.stack.length > this.index + 1) { - if (acrossEditors) { - this.doForwardAcrossEditors(); - } else { - this.doForwardInEditors(); - } - } - } - - private doForwardInEditors(): void { - this.setIndex(this.index + 1); - this.navigate(); - } - - private setIndex(value: number): void { - this.lastIndex = this.index; - this.index = value; - - this.updateContextKeys(); - } - - private doForwardAcrossEditors(): void { - let currentIndex = this.index; - const currentEntry = this.stack[this.index]; - - // Find the next entry that does not match our current entry - while (this.stack.length > currentIndex + 1) { - currentIndex++; - - const previousEntry = this.stack[currentIndex]; - if (!this.matches(currentEntry.input, previousEntry.input)) { - this.setIndex(currentIndex); - this.navigate(true /* across editors */); - - break; - } - } - } - - back(acrossEditors?: boolean): void { - if (this.index > 0) { - if (acrossEditors) { - this.doBackAcrossEditors(); - } else { - this.doBackInEditors(); - } - } - } - - last(): void { - if (this.lastIndex === -1) { - this.back(); - } else { - this.setIndex(this.lastIndex); - this.navigate(); - } - } - - private doBackInEditors(): void { - this.setIndex(this.index - 1); - this.navigate(); - } - - private doBackAcrossEditors(): void { - let currentIndex = this.index; - const currentEntry = this.stack[this.index]; - - // Find the next previous entry that does not match our current entry - while (currentIndex > 0) { - currentIndex--; - - const previousEntry = this.stack[currentIndex]; - if (!this.matches(currentEntry.input, previousEntry.input)) { - this.setIndex(currentIndex); - this.navigate(true /* across editors */); - - break; - } - } - } - - clear(): void { - this.ensureHistoryLoaded(); - - // Navigation (next, previous) - this.index = -1; - this.lastIndex = -1; - this.stack.splice(0); - this.editorStackListeners.forEach(listeners => dispose(listeners)); - this.editorStackListeners.clear(); - - // Closed files - this.recentlyClosedFiles = []; - - // History - this.clearRecentlyOpened(); - - this.updateContextKeys(); - } - - clearRecentlyOpened(): void { - this.history = []; - - this.editorHistoryListeners.forEach(listeners => dispose(listeners)); - this.editorHistoryListeners.clear(); - } - - private updateContextKeys(): void { - this.canNavigateBackContextKey.set(this.stack.length > 0 && this.index > 0); - this.canNavigateForwardContextKey.set(this.stack.length > 0 && this.index < this.stack.length - 1); - } - - private navigate(acrossEditors?: boolean): void { - this.navigatingInStack = true; - - this.doNavigate(this.stack[this.index], !acrossEditors).finally(() => this.navigatingInStack = false); - } - - private doNavigate(location: IStackEntry, withSelection: boolean): Promise { - const options: ITextEditorOptions = { - revealIfOpened: true // support to navigate across editor groups - }; - - // Unless we navigate across editors, support selection and - // minimize scrolling by setting revealInCenterIfOutsideViewport - if (location.selection && withSelection) { - options.selection = location.selection; - options.revealInCenterIfOutsideViewport = true; - } - - if (location.input instanceof EditorInput) { - return this.editorService.openEditor(location.input, options); - } - - return this.editorService.openEditor({ resource: (location.input as IResourceInput).resource, options }); - } - - protected handleEditorSelectionChangeEvent(editor?: IBaseEditor, event?: ICursorPositionChangedEvent): void { - this.handleEditorEventInStack(editor, event); - } - - protected handleActiveEditorChange(editor?: IBaseEditor): void { + private handleActiveEditorChange(editor?: IBaseEditor): void { this.handleEditorEventInHistory(editor); - this.handleEditorEventInStack(editor); - } - - private handleEditorEventInHistory(editor?: IBaseEditor): void { - - // Ensure we have not configured to exclude input and don't track invalid inputs - const input = editor?.input; - if (!input || input.isDisposed() || !this.include(input)) { - return; - } - - this.ensureHistoryLoaded(); - - const historyInput = this.preferResourceInput(input); - - // Remove any existing entry and add to the beginning - this.removeFromHistory(input); - this.history.unshift(historyInput); - - // Respect max entries setting - if (this.history.length > HistoryService.MAX_HISTORY_ITEMS) { - this.clearOnEditorDispose(this.history.pop()!, this.editorHistoryListeners); - } - - // Remove this from the history unless the history input is a resource - // that can easily be restored even when the input gets disposed - if (historyInput instanceof EditorInput) { - this.onEditorDispose(historyInput, () => this.removeFromHistory(historyInput), this.editorHistoryListeners); - } + this.handleEditorEventInNavigationStack(editor); } private onEditorDispose(editor: EditorInput, listener: Function, mapEditorToDispose: Map): void { @@ -532,60 +554,115 @@ export class HistoryService extends Disposable implements IHistoryService { } } - private include(input: IEditorInput | IResourceInput): boolean { - if (input instanceof EditorInput) { - return true; // include any non files - } - - const resourceInput = input as IResourceInput; - - return !this.resourceFilter.matches(resourceInput.resource); - } - - protected handleExcludesChange(): void { - this.removeExcludedFromHistory(); - } - remove(input: IEditorInput | IResourceInput): void; remove(input: FileChangesEvent): void; remove(arg1: IEditorInput | IResourceInput | FileChangesEvent): void { this.removeFromHistory(arg1); - this.removeFromStack(arg1); + this.removeFromNavigationStack(arg1); this.removeFromRecentlyClosedFiles(arg1); this.removeFromRecentlyOpened(arg1); } - private removeExcludedFromHistory(): void { - this.ensureHistoryLoaded(); + private removeFromRecentlyOpened(arg1: IEditorInput | IResourceInput | FileChangesEvent): void { + if (arg1 instanceof EditorInput || arg1 instanceof FileChangesEvent) { + return; // for now do not delete from file events since recently open are likely out of workspace files for which there are no delete events + } - this.history = this.history.filter(e => { - const include = this.include(e); + const input = arg1 as IResourceInput; - // Cleanup any listeners associated with the input when removing from history - if (!include) { - this.clearOnEditorDispose(e, this.editorHistoryListeners); - } - - return include; - }); + this.workspacesService.removeFromRecentlyOpened([input.resource]); } - private removeFromHistory(arg1: IEditorInput | IResourceInput | FileChangesEvent): void { + clear(): void { this.ensureHistoryLoaded(); - this.history = this.history.filter(e => { - const matches = this.matches(arg1, e); + // Navigation (next, previous) + this.navigationStackIndex = -1; + this.lastNavigationStackIndex = -1; + this.navigationStack.splice(0); + this.editorStackListeners.forEach(listeners => dispose(listeners)); + this.editorStackListeners.clear(); - // Cleanup any listeners associated with the input when removing from history - if (matches) { - this.clearOnEditorDispose(arg1, this.editorHistoryListeners); - } + // Closed files + this.recentlyClosedFiles = []; - return !matches; - }); + // History + this.clearRecentlyOpened(); + + // Context Keys + this.updateContextKeys(); } - private handleEditorEventInStack(control: IBaseEditor | undefined, event?: ICursorPositionChangedEvent): void { + //#region Navigation (Go Forward, Go Backward) + + private static readonly MAX_NAVIGATION_STACK_ITEMS = 50; + + private navigationStack: IStackEntry[] = []; + private navigationStackIndex = -1; + private lastNavigationStackIndex = -1; + + private navigatingInStack = false; + + private currentTextEditorState: TextEditorState | null = null; + + forward(): void { + if (this.navigationStack.length > this.navigationStackIndex + 1) { + this.setIndex(this.navigationStackIndex + 1); + this.navigate(); + } + } + + back(): void { + if (this.navigationStackIndex > 0) { + this.setIndex(this.navigationStackIndex - 1); + this.navigate(); + } + } + + last(): void { + if (this.lastNavigationStackIndex === -1) { + this.back(); + } else { + this.setIndex(this.lastNavigationStackIndex); + this.navigate(); + } + } + + private setIndex(value: number): void { + this.lastNavigationStackIndex = this.navigationStackIndex; + this.navigationStackIndex = value; + + // Context Keys + this.updateContextKeys(); + } + + private navigate(): void { + this.navigatingInStack = true; + + const navigateToStackEntry = this.navigationStack[this.navigationStackIndex]; + + this.doNavigate(navigateToStackEntry).finally(() => this.navigatingInStack = false); + } + + private doNavigate(location: IStackEntry): Promise { + const options: ITextEditorOptions = { + revealIfOpened: true // support to navigate across editor groups + }; + + // Support selection and minimize scrolling by setting revealInCenterIfOutsideViewport + if (location.selection) { + options.selection = location.selection; + options.revealInCenterIfOutsideViewport = true; + } + + if (location.input instanceof EditorInput) { + return this.editorService.openEditor(location.input, options); + } + + return this.editorService.openEditor({ resource: (location.input as IResourceInput).resource, options }); + } + + private handleEditorEventInNavigationStack(control: IBaseEditor | undefined, event?: ICursorPositionChangedEvent): void { const codeEditor = control ? getCodeEditor(control.getControl()) : undefined; // treat editor changes that happen as part of stack navigation specially @@ -605,7 +682,7 @@ export class HistoryService extends Disposable implements IHistoryService { // navigation inside text editor if (codeEditor && control?.input && !control.input.isDisposed()) { - this.handleTextEditorEvent(control, codeEditor, event); + this.handleTextEditorEventInNavigationStack(control, codeEditor, event); } // navigation to non-text disposed editor @@ -613,13 +690,13 @@ export class HistoryService extends Disposable implements IHistoryService { this.currentTextEditorState = null; // at this time we have no active text editor view state if (control?.input && !control.input.isDisposed()) { - this.handleNonTextEditorEvent(control); + this.handleNonTextEditorEventInNavigationStack(control); } } } } - private handleTextEditorEvent(editor: IBaseEditor, editorControl: IEditor, event?: ICursorPositionChangedEvent): void { + private handleTextEditorEventInNavigationStack(editor: IBaseEditor, editorControl: IEditor, event?: ICursorPositionChangedEvent): void { if (!editor.input) { return; } @@ -628,44 +705,44 @@ export class HistoryService extends Disposable implements IHistoryService { // Add to stack if we dont have a current state or this new state justifies a push if (!this.currentTextEditorState || this.currentTextEditorState.justifiesNewPushState(stateCandidate, event)) { - this.add(editor.input, stateCandidate.selection); + this.addToNavigationStack(editor.input, stateCandidate.selection); } // Otherwise we replace the current stack entry with this one else { - this.replace(editor.input, stateCandidate.selection); + this.replaceInNavigationStack(editor.input, stateCandidate.selection); } // Update our current text editor state this.currentTextEditorState = stateCandidate; } - private handleNonTextEditorEvent(editor: IBaseEditor): void { + private handleNonTextEditorEventInNavigationStack(editor: IBaseEditor): void { if (!editor.input) { return; } - const currentStack = this.stack[this.index]; + const currentStack = this.navigationStack[this.navigationStackIndex]; if (currentStack && this.matches(editor.input, currentStack.input)) { return; // do not push same editor input again } - this.add(editor.input); + this.addToNavigationStack(editor.input); } - add(input: IEditorInput, selection?: ITextEditorSelection): void { + private addToNavigationStack(input: IEditorInput, selection?: ITextEditorSelection): void { if (!this.navigatingInStack) { - this.addOrReplaceInStack(input, selection); + this.doAddOrReplaceInNavigationStack(input, selection); } } - private replace(input: IEditorInput, selection?: ITextEditorSelection): void { + private replaceInNavigationStack(input: IEditorInput, selection?: ITextEditorSelection): void { if (!this.navigatingInStack) { - this.addOrReplaceInStack(input, selection, true /* force replace */); + this.doAddOrReplaceInNavigationStack(input, selection, true /* force replace */); } } - private addOrReplaceInStack(input: IEditorInput, selection?: ITextEditorSelection, forceReplace?: boolean): void { + private doAddOrReplaceInNavigationStack(input: IEditorInput, selection?: ITextEditorSelection, forceReplace?: boolean): void { // Overwrite an entry in the stack if we have a matching input that comes // with editor options to indicate that this entry is more specific. Also @@ -676,7 +753,7 @@ export class HistoryService extends Disposable implements IHistoryService { // on the stack. // We can also be instructed to force replace the last entry. let replace = false; - const currentEntry = this.stack[this.index]; + const currentEntry = this.navigationStack[this.navigationStackIndex]; if (currentEntry) { if (forceReplace) { replace = true; // replace if we are forced to @@ -691,33 +768,33 @@ export class HistoryService extends Disposable implements IHistoryService { // Replace at current position let removedEntries: IStackEntry[] = []; if (replace) { - removedEntries.push(this.stack[this.index]); - this.stack[this.index] = entry; + removedEntries.push(this.navigationStack[this.navigationStackIndex]); + this.navigationStack[this.navigationStackIndex] = entry; } // Add to stack at current position else { // If we are not at the end of history, we remove anything after - if (this.stack.length > this.index + 1) { - for (let i = this.index + 1; i < this.stack.length; i++) { - removedEntries.push(this.stack[i]); + if (this.navigationStack.length > this.navigationStackIndex + 1) { + for (let i = this.navigationStackIndex + 1; i < this.navigationStack.length; i++) { + removedEntries.push(this.navigationStack[i]); } - this.stack = this.stack.slice(0, this.index + 1); + this.navigationStack = this.navigationStack.slice(0, this.navigationStackIndex + 1); } // Insert entry at index - this.stack.splice(this.index + 1, 0, entry); + this.navigationStack.splice(this.navigationStackIndex + 1, 0, entry); // Check for limit - if (this.stack.length > HistoryService.MAX_STACK_ITEMS) { - removedEntries.push(this.stack.shift()!); // remove first - if (this.lastIndex >= 0) { - this.lastIndex--; + if (this.navigationStack.length > HistoryService.MAX_NAVIGATION_STACK_ITEMS) { + removedEntries.push(this.navigationStack.shift()!); // remove first + if (this.lastNavigationStackIndex >= 0) { + this.lastNavigationStackIndex--; } } else { - this.setIndex(this.index + 1); + this.setIndex(this.navigationStackIndex + 1); } } @@ -727,10 +804,10 @@ export class HistoryService extends Disposable implements IHistoryService { // Remove this from the stack unless the stack input is a resource // that can easily be restored even when the input gets disposed if (stackInput instanceof EditorInput) { - this.onEditorDispose(stackInput, () => this.removeFromStack(stackInput), this.editorStackListeners); + this.onEditorDispose(stackInput, () => this.removeFromNavigationStack(stackInput), this.editorStackListeners); } - // Context + // Context Keys this.updateContextKeys(); } @@ -757,8 +834,8 @@ export class HistoryService extends Disposable implements IHistoryService { return selectionA.startLineNumber === selectionB.startLineNumber; // we consider the history entry same if we are on the same line } - private removeFromStack(arg1: IEditorInput | IResourceInput | FileChangesEvent): void { - this.stack = this.stack.filter(e => { + private removeFromNavigationStack(arg1: IEditorInput | IResourceInput | FileChangesEvent): void { + this.navigationStack = this.navigationStack.filter(e => { const matches = this.matches(arg1, e.input); // Cleanup any listeners associated with the input when removing @@ -768,38 +845,13 @@ export class HistoryService extends Disposable implements IHistoryService { return !matches; }); - this.index = this.stack.length - 1; // reset index - this.lastIndex = -1; + this.navigationStackIndex = this.navigationStack.length - 1; // reset index + this.lastNavigationStackIndex = -1; + // Context Keys this.updateContextKeys(); } - private removeFromRecentlyClosedFiles(arg1: IEditorInput | IResourceInput | FileChangesEvent): void { - this.recentlyClosedFiles = this.recentlyClosedFiles.filter(e => !this.matchesFile(e.resource, arg1)); - } - - private removeFromRecentlyOpened(arg1: IEditorInput | IResourceInput | FileChangesEvent): void { - if (arg1 instanceof EditorInput || arg1 instanceof FileChangesEvent) { - return; // for now do not delete from file events since recently open are likely out of workspace files for which there are no delete events - } - - const input = arg1 as IResourceInput; - - this.workspacesService.removeFromRecentlyOpened([input.resource]); - } - - private isFileOpened(resource: URI, group: IEditorGroup): boolean { - if (!group) { - return false; - } - - if (!this.editorService.isOpen({ resource }, group)) { - return false; // fast check - } - - return group.editors.some(e => this.matchesFile(resource, e)); - } - private matches(arg1: IEditorInput | IResourceInput | FileChangesEvent, inputB: IEditorInput | IResourceInput): boolean { if (arg1 instanceof FileChangesEvent) { if (inputB instanceof EditorInput) { @@ -852,6 +904,204 @@ export class HistoryService extends Disposable implements IHistoryService { return resourceInput?.resource.toString() === resource.toString(); } + //#endregion + + //#region Recently Closed Files + + private static readonly MAX_RECENTLY_CLOSED_EDITORS = 20; + + private recentlyClosedFiles: IRecentlyClosedFile[] = []; + + private onEditorClosed(event: IEditorCloseEvent): void { + + // Track closing of editor to support to reopen closed editors (unless editor was replaced) + if (!event.replaced) { + const resource = event.editor ? event.editor.getResource() : undefined; + const supportsReopen = resource && this.fileService.canHandleResource(resource); // we only support file'ish things to reopen + if (resource && supportsReopen) { + + // Remove all inputs matching and add as last recently closed + this.removeFromRecentlyClosedFiles(event.editor); + this.recentlyClosedFiles.push({ resource, index: event.index }); + + // Bounding + if (this.recentlyClosedFiles.length > HistoryService.MAX_RECENTLY_CLOSED_EDITORS) { + this.recentlyClosedFiles.shift(); + } + + // Context + this.canReopenClosedEditorContextKey.set(true); + } + } + } + + reopenLastClosedEditor(): void { + let lastClosedFile = this.recentlyClosedFiles.pop(); + while (lastClosedFile && this.editorGroupService.activeGroup.isOpened({ resource: lastClosedFile.resource })) { + lastClosedFile = this.recentlyClosedFiles.pop(); // pop until we find a file that is not opened + } + + if (lastClosedFile) { + (async () => { + const editor = await this.editorService.openEditor({ resource: lastClosedFile.resource, options: { pinned: true, index: lastClosedFile.index } }); + + // Fix for https://github.com/Microsoft/vscode/issues/67882 + // If opening of the editor fails, make sure to try the next one + // but make sure to remove this one from the list to prevent + // endless loops. + if (!editor) { + this.recentlyClosedFiles.pop(); + this.reopenLastClosedEditor(); + } + })(); + } + + // Context + this.canReopenClosedEditorContextKey.set(this.recentlyClosedFiles.length > 0); + } + + private removeFromRecentlyClosedFiles(arg1: IEditorInput | IResourceInput | FileChangesEvent): void { + this.recentlyClosedFiles = this.recentlyClosedFiles.filter(e => !this.matchesFile(e.resource, arg1)); + this.canReopenClosedEditorContextKey.set(this.recentlyClosedFiles.length > 0); + } + + //#endregion + + //#region Last Edit Location + + private lastEditLocation: IStackEntry | undefined; + + private rememberLastEditLocation(activeEditor: IEditorInput, activeTextEditorWidget: ICodeEditor): void { + this.lastEditLocation = { input: activeEditor }; + this.canNavigateToLastEditLocationContextKey.set(true); + + const position = activeTextEditorWidget.getPosition(); + if (position) { + this.lastEditLocation.selection = { + startLineNumber: position.lineNumber, + startColumn: position.column + }; + } + } + + openLastEditLocation(): void { + if (this.lastEditLocation) { + this.doNavigate(this.lastEditLocation); + } + } + + //#endregion + + //#region Context Keys + + private readonly canNavigateBackContextKey = (new RawContextKey('canNavigateBack', false)).bindTo(this.contextKeyService); + private readonly canNavigateForwardContextKey = (new RawContextKey('canNavigateForward', false)).bindTo(this.contextKeyService); + private readonly canNavigateToLastEditLocationContextKey = (new RawContextKey('canNavigateToLastEditLocation', false)).bindTo(this.contextKeyService); + private readonly canReopenClosedEditorContextKey = (new RawContextKey('canReopenClosedEditor', false)).bindTo(this.contextKeyService); + + private updateContextKeys(): void { + this.canNavigateBackContextKey.set(this.navigationStack.length > 0 && this.navigationStackIndex > 0); + this.canNavigateForwardContextKey.set(this.navigationStack.length > 0 && this.navigationStackIndex < this.navigationStack.length - 1); + this.canNavigateToLastEditLocationContextKey.set(!!this.lastEditLocation); + this.canReopenClosedEditorContextKey.set(this.recentlyClosedFiles.length > 0); + } + + //#endregion + + //#region History + + private static readonly MAX_HISTORY_ITEMS = 200; + private static readonly HISTORY_STORAGE_KEY = 'history.entries'; + + private history: Array = []; + private loaded = false; + private readonly resourceFilter = this._register(this.instantiationService.createInstance( + ResourceGlobMatcher, + (root?: URI) => this.getExcludes(root), + (event: IConfigurationChangeEvent) => event.affectsConfiguration(FILES_EXCLUDE_CONFIG) || event.affectsConfiguration('search.exclude') + )); + + private getExcludes(root?: URI): IExpression { + const scope = root ? { resource: root } : undefined; + + return getExcludes(scope ? this.configurationService.getValue(scope) : this.configurationService.getValue())!; + } + + private handleEditorEventInHistory(editor?: IBaseEditor): void { + + // Ensure we have not configured to exclude input and don't track invalid inputs + const input = editor?.input; + if (!input || input.isDisposed() || !this.include(input)) { + return; + } + + this.ensureHistoryLoaded(); + + const historyInput = this.preferResourceInput(input); + + // Remove any existing entry and add to the beginning + this.removeFromHistory(input); + this.history.unshift(historyInput); + + // Respect max entries setting + if (this.history.length > HistoryService.MAX_HISTORY_ITEMS) { + this.clearOnEditorDispose(this.history.pop()!, this.editorHistoryListeners); + } + + // Remove this from the history unless the history input is a resource + // that can easily be restored even when the input gets disposed + if (historyInput instanceof EditorInput) { + this.onEditorDispose(historyInput, () => this.removeFromHistory(historyInput), this.editorHistoryListeners); + } + } + + private include(input: IEditorInput | IResourceInput): boolean { + if (input instanceof EditorInput) { + return true; // include any non files + } + + const resourceInput = input as IResourceInput; + + return !this.resourceFilter.matches(resourceInput.resource); + } + + private removeExcludedFromHistory(): void { + this.ensureHistoryLoaded(); + + this.history = this.history.filter(e => { + const include = this.include(e); + + // Cleanup any listeners associated with the input when removing from history + if (!include) { + this.clearOnEditorDispose(e, this.editorHistoryListeners); + } + + return include; + }); + } + + private removeFromHistory(arg1: IEditorInput | IResourceInput | FileChangesEvent): void { + this.ensureHistoryLoaded(); + + this.history = this.history.filter(e => { + const matches = this.matches(arg1, e); + + // Cleanup any listeners associated with the input when removing from history + if (matches) { + this.clearOnEditorDispose(arg1, this.editorHistoryListeners); + } + + return !matches; + }); + } + + clearRecentlyOpened(): void { + this.history = []; + + this.editorHistoryListeners.forEach(listeners => dispose(listeners)); + this.editorHistoryListeners.clear(); + } + getHistory(): Array { this.ensureHistoryLoaded(); @@ -894,13 +1144,13 @@ export class HistoryService extends Disposable implements IHistoryService { return undefined; })); - this.storageService.store(HistoryService.STORAGE_KEY, JSON.stringify(entries), StorageScope.WORKSPACE); + this.storageService.store(HistoryService.HISTORY_STORAGE_KEY, JSON.stringify(entries), StorageScope.WORKSPACE); } private loadHistory(): void { let entries: ISerializedEditorHistoryEntry[] = []; - const entriesRaw = this.storageService.get(HistoryService.STORAGE_KEY, StorageScope.WORKSPACE); + const entriesRaw = this.storageService.get(HistoryService.HISTORY_STORAGE_KEY, StorageScope.WORKSPACE); if (entriesRaw) { entries = coalesce(JSON.parse(entriesRaw)); } @@ -943,6 +1193,10 @@ export class HistoryService extends Disposable implements IHistoryService { return undefined; } + //#endregion + + //#region Last Active Workspace/File + getLastActiveWorkspaceRoot(schemeFilter?: string): URI | undefined { // No Folder: return early @@ -1007,6 +1261,112 @@ export class HistoryService extends Disposable implements IHistoryService { return undefined; } + + //#endregion + + //#region Editor Most Recently Used History + + private readonly mostRecentlyUsedOpenEditors = this._register(this.instantiationService.createInstance(EditorsHistory)); + + private recentlyUsedEditorsStack: IEditorIdentifier[] | undefined = undefined; + private recentlyUsedEditorsStackIndex = 0; + + private recentlyUsedEditorsInGroupStack: IEditorIdentifier[] | undefined = undefined; + private recentlyUsedEditorsInGroupStackIndex = 0; + + private navigatingInRecentlyUsedEditorsStack = false; + private navigatingInRecentlyUsedEditorsInGroupStack = false; + + openNextRecentlyUsedEditor(groupId?: GroupIdentifier): void { + const [stack, index] = this.ensureRecentlyUsedStack(index => index - 1, groupId); + + this.doNavigateInRecentlyUsedEditorsStack(stack[index], groupId); + } + + openPreviouslyUsedEditor(groupId?: GroupIdentifier): void { + const [stack, index] = this.ensureRecentlyUsedStack(index => index + 1, groupId); + + this.doNavigateInRecentlyUsedEditorsStack(stack[index], groupId); + } + + private doNavigateInRecentlyUsedEditorsStack(editorIdentifier: IEditorIdentifier | undefined, groupId?: GroupIdentifier): void { + if (editorIdentifier) { + const acrossGroups = typeof groupId !== 'number' || !this.editorGroupService.getGroup(groupId); + + if (acrossGroups) { + this.navigatingInRecentlyUsedEditorsStack = true; + } else { + this.navigatingInRecentlyUsedEditorsInGroupStack = true; + } + + this.editorService.openEditor(editorIdentifier.editor, undefined, editorIdentifier.groupId).finally(() => { + if (acrossGroups) { + this.navigatingInRecentlyUsedEditorsStack = false; + } else { + this.navigatingInRecentlyUsedEditorsInGroupStack = false; + } + }); + } + } + + private ensureRecentlyUsedStack(indexModifier: (index: number) => number, groupId?: GroupIdentifier): [IEditorIdentifier[], number] { + let editors: IEditorIdentifier[]; + let index: number; + + const group = typeof groupId === 'number' ? this.editorGroupService.getGroup(groupId) : undefined; + + // Across groups + if (!group) { + editors = this.recentlyUsedEditorsStack || this.mostRecentlyUsedOpenEditors.editors; + index = this.recentlyUsedEditorsStackIndex; + } + + // Within group + else { + editors = this.recentlyUsedEditorsInGroupStack || group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE).map(editor => ({ groupId: group.id, editor })); + index = this.recentlyUsedEditorsInGroupStackIndex; + } + + // Adjust index + let newIndex = indexModifier(index); + if (newIndex < 0) { + newIndex = 0; + } else if (newIndex > editors.length - 1) { + newIndex = editors.length - 1; + } + + // Remember index and editors + if (!group) { + this.recentlyUsedEditorsStack = editors; + this.recentlyUsedEditorsStackIndex = newIndex; + } else { + this.recentlyUsedEditorsInGroupStack = editors; + this.recentlyUsedEditorsInGroupStackIndex = newIndex; + } + + return [editors, newIndex]; + } + + private handleEditorEventInRecentEditorsStack(): void { + + // Drop all-editors stack unless navigating in all editors + if (!this.navigatingInRecentlyUsedEditorsStack) { + this.recentlyUsedEditorsStack = undefined; + this.recentlyUsedEditorsStackIndex = 0; + } + + // Drop in-group-editors stack unless navigating in group + if (!this.navigatingInRecentlyUsedEditorsInGroupStack) { + this.recentlyUsedEditorsInGroupStack = undefined; + this.recentlyUsedEditorsInGroupStackIndex = 0; + } + } + + getMostRecentlyUsedOpenEditors(): Array { + return this.mostRecentlyUsedOpenEditors.editors; + } + + //#endregion } registerSingleton(IHistoryService, HistoryService); diff --git a/src/vs/workbench/services/history/common/history.ts b/src/vs/workbench/services/history/common/history.ts index 9ac57e50ae..fc320f22ac 100644 --- a/src/vs/workbench/services/history/common/history.ts +++ b/src/vs/workbench/services/history/common/history.ts @@ -5,7 +5,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IResourceInput } from 'vs/platform/editor/common/editor'; -import { IEditorInput } from 'vs/workbench/common/editor'; +import { IEditorInput, IEditorIdentifier, GroupIdentifier } from 'vs/workbench/common/editor'; import { URI } from 'vs/base/common/uri'; export const IHistoryService = createDecorator('historyService'); @@ -26,19 +26,13 @@ export interface IHistoryService { /** * Navigate forwards in history. - * - * @param acrossEditors instructs the history to skip navigation entries that - * are only within the same document. */ - forward(acrossEditors?: boolean): void; + forward(): void; /** * Navigate backwards in history. - * - * @param acrossEditors instructs the history to skip navigation entries that - * are only within the same document. */ - back(acrossEditors?: boolean): void; + back(): void; /** * Navigate forward or backwards to previous entry in history. @@ -61,7 +55,7 @@ export interface IHistoryService { clearRecentlyOpened(): void; /** - * Get the entire history of opened editors. + * Get the entire history of editors that were opened. */ getHistory(): Array; @@ -79,4 +73,23 @@ export interface IHistoryService { * @param schemeFilter filter to restrict roots by scheme. */ getLastActiveFile(schemeFilter: string): URI | undefined; + + /** + * Opens the next used editor if any. + * + * @param group optional indicator to scope to a specific group. + */ + openNextRecentlyUsedEditor(group?: GroupIdentifier): void; + + /** + * Opens the previously used editor if any. + * + * @param group optional indicator to scope to a specific group. + */ + openPreviouslyUsedEditor(group?: GroupIdentifier): void; + + /** + * Get a list of most recently used editors that are open. + */ + getMostRecentlyUsedOpenEditors(): Array; } diff --git a/src/vs/workbench/services/history/test/history.test.ts b/src/vs/workbench/services/history/test/history.test.ts new file mode 100644 index 0000000000..e596094370 --- /dev/null +++ b/src/vs/workbench/services/history/test/history.test.ts @@ -0,0 +1,597 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { EditorOptions, EditorInput, IEditorInputFactoryRegistry, Extensions as EditorExtensions, IEditorInputFactory, IFileEditorInput } from 'vs/workbench/common/editor'; +import { URI } from 'vs/base/common/uri'; +import { workbenchInstantiationService, TestStorageService } from 'vs/workbench/test/workbenchTestServices'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart'; +import { IEditorRegistry, EditorDescriptor, Extensions } from 'vs/workbench/browser/editor'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { GroupDirection, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { EditorActivation, IEditorModel } from 'vs/platform/editor/common/editor'; +import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; +import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; +import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { EditorsHistory, HistoryService } from 'vs/workbench/services/history/browser/history'; +import { WillSaveStateReason } from 'vs/platform/storage/common/storage'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { EditorService } from 'vs/workbench/services/editor/browser/editorService'; +import { timeout } from 'vs/base/common/async'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { IHistoryService } from 'vs/workbench/services/history/common/history'; + +const TEST_EDITOR_ID = 'MyTestEditorForEditorHistory'; +const TEST_EDITOR_INPUT_ID = 'testEditorInputForHistoyService'; +const TEST_SERIALIZABLE_EDITOR_INPUT_ID = 'testSerializableEditorInputForHistoyService'; + +class TestEditorControl extends BaseEditor { + + constructor() { super(TEST_EDITOR_ID, NullTelemetryService, new TestThemeService(), new TestStorageService()); } + + async setInput(input: EditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise { + super.setInput(input, options, token); + + await input.resolve(); + } + + getId(): string { return TEST_EDITOR_ID; } + layout(): void { } + createEditor(): any { } +} + +class TestEditorInput extends EditorInput implements IFileEditorInput { + + constructor(public resource: URI) { super(); } + + getTypeId() { return TEST_EDITOR_INPUT_ID; } + resolve(): Promise { return Promise.resolve(null); } + matches(other: TestEditorInput): boolean { return other && this.resource.toString() === other.resource.toString() && other instanceof TestEditorInput; } + setEncoding(encoding: string) { } + getEncoding() { return undefined; } + setPreferredEncoding(encoding: string) { } + setMode(mode: string) { } + setPreferredMode(mode: string) { } + getResource(): URI { return this.resource; } + setForceOpenAsBinary(): void { } +} + +class HistoryTestEditorInput extends TestEditorInput { + getTypeId() { return TEST_SERIALIZABLE_EDITOR_INPUT_ID; } +} + +interface ISerializedTestInput { + resource: string; +} + +class HistoryTestEditorInputFactory implements IEditorInputFactory { + + canSerialize(editorInput: EditorInput): boolean { + return true; + } + + serialize(editorInput: EditorInput): string { + let testEditorInput = editorInput; + let testInput: ISerializedTestInput = { + resource: testEditorInput.resource.toString() + }; + + return JSON.stringify(testInput); + } + + deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): EditorInput { + let testInput: ISerializedTestInput = JSON.parse(serializedEditorInput); + + return new HistoryTestEditorInput(URI.parse(testInput.resource)); + } +} + +async function createServices(): Promise<[EditorPart, HistoryService, EditorService]> { + const instantiationService = workbenchInstantiationService(); + + const part = instantiationService.createInstance(EditorPart); + part.create(document.createElement('div')); + part.layout(400, 300); + + await part.whenRestored; + + instantiationService.stub(IEditorGroupsService, part); + + const editorService = instantiationService.createInstance(EditorService); + instantiationService.stub(IEditorService, editorService); + + const historyService = instantiationService.createInstance(HistoryService); + instantiationService.stub(IHistoryService, historyService); + + return [part, historyService, editorService]; +} + +suite.skip('HistoryService', function () { // {{SQL CARBON EDIT}} TODO @anthonydresser these tests are failing due to tabColorMode, should investigate and fix + + let disposables: IDisposable[] = []; + + setup(() => { + disposables.push(Registry.as(EditorExtensions.EditorInputFactories).registerEditorInputFactory(TEST_SERIALIZABLE_EDITOR_INPUT_ID, HistoryTestEditorInputFactory)); + disposables.push(Registry.as(Extensions.Editors).registerEditor(EditorDescriptor.create(TestEditorControl, TEST_EDITOR_ID, 'My Test Editor For History Editor Service'), [new SyncDescriptor(TestEditorInput), new SyncDescriptor(HistoryTestEditorInput)])); + }); + + teardown(() => { + dispose(disposables); + disposables = []; + }); + + test('back / forward', async () => { + const [part, historyService] = await createServices(); + + const input1 = new TestEditorInput(URI.parse('foo://bar1')); + await part.activeGroup.openEditor(input1, EditorOptions.create({ pinned: true })); + assert.equal(part.activeGroup.activeEditor, input1); + + const input2 = new TestEditorInput(URI.parse('foo://bar2')); + await part.activeGroup.openEditor(input2, EditorOptions.create({ pinned: true })); + assert.equal(part.activeGroup.activeEditor, input2); + + historyService.back(); + assert.equal(part.activeGroup.activeEditor, input1); + + historyService.forward(); + assert.equal(part.activeGroup.activeEditor, input2); + + part.dispose(); + }); + + test('getHistory', async () => { + const [part, historyService] = await createServices(); + + let history = historyService.getHistory(); + assert.equal(history.length, 0); + + const input1 = new TestEditorInput(URI.parse('foo://bar1')); + await part.activeGroup.openEditor(input1, EditorOptions.create({ pinned: true })); + + const input2 = new TestEditorInput(URI.parse('foo://bar2')); + await part.activeGroup.openEditor(input2, EditorOptions.create({ pinned: true })); + + history = historyService.getHistory(); + assert.equal(history.length, 2); + + historyService.remove(input2); + history = historyService.getHistory(); + assert.equal(history.length, 1); + assert.equal(history[0], input1); + + part.dispose(); + }); + + test('getLastActiveFile', async () => { + const [part, historyService] = await createServices(); + + assert.ok(!historyService.getLastActiveFile('foo')); + + const input1 = new TestEditorInput(URI.parse('foo://bar1')); + await part.activeGroup.openEditor(input1, EditorOptions.create({ pinned: true })); + + assert.equal(historyService.getLastActiveFile('foo')?.toString(), input1.getResource().toString()); + + part.dispose(); + }); + + suite('EditorHistory', function () { + + test('basics (single group)', async () => { + const instantiationService = workbenchInstantiationService(); + + const part = instantiationService.createInstance(EditorPart); + part.create(document.createElement('div')); + part.layout(400, 300); + + await part.whenRestored; + + const history = new EditorsHistory(part, new TestStorageService()); + + let historyChangeListenerCalled = false; + const listener = history.onDidChange(() => { + historyChangeListenerCalled = true; + }); + + let currentHistory = history.editors; + assert.equal(currentHistory.length, 0); + assert.equal(historyChangeListenerCalled, false); + + const input1 = new HistoryTestEditorInput(URI.parse('foo://bar1')); + + await part.activeGroup.openEditor(input1, EditorOptions.create({ pinned: true })); + + currentHistory = history.editors; + assert.equal(currentHistory.length, 1); + assert.equal(currentHistory[0].groupId, part.activeGroup.id); + assert.equal(currentHistory[0].editor, input1); + assert.equal(historyChangeListenerCalled, true); + + const input2 = new HistoryTestEditorInput(URI.parse('foo://bar2')); + const input3 = new HistoryTestEditorInput(URI.parse('foo://bar3')); + + await part.activeGroup.openEditor(input2, EditorOptions.create({ pinned: true })); + await part.activeGroup.openEditor(input3, EditorOptions.create({ pinned: true })); + + currentHistory = history.editors; + assert.equal(currentHistory.length, 3); + assert.equal(currentHistory[0].groupId, part.activeGroup.id); + assert.equal(currentHistory[0].editor, input3); + assert.equal(currentHistory[1].groupId, part.activeGroup.id); + assert.equal(currentHistory[1].editor, input2); + assert.equal(currentHistory[2].groupId, part.activeGroup.id); + assert.equal(currentHistory[2].editor, input1); + + await part.activeGroup.openEditor(input2, EditorOptions.create({ pinned: true })); + + currentHistory = history.editors; + assert.equal(currentHistory.length, 3); + assert.equal(currentHistory[0].groupId, part.activeGroup.id); + assert.equal(currentHistory[0].editor, input2); + assert.equal(currentHistory[1].groupId, part.activeGroup.id); + assert.equal(currentHistory[1].editor, input3); + assert.equal(currentHistory[2].groupId, part.activeGroup.id); + assert.equal(currentHistory[2].editor, input1); + + historyChangeListenerCalled = false; + await part.activeGroup.closeEditor(input1); + + currentHistory = history.editors; + assert.equal(currentHistory.length, 2); + assert.equal(currentHistory[0].groupId, part.activeGroup.id); + assert.equal(currentHistory[0].editor, input2); + assert.equal(currentHistory[1].groupId, part.activeGroup.id); + assert.equal(currentHistory[1].editor, input3); + assert.equal(historyChangeListenerCalled, true); + + await part.activeGroup.closeAllEditors(); + currentHistory = history.editors; + assert.equal(currentHistory.length, 0); + + part.dispose(); + listener.dispose(); + }); + + test('basics (multi group)', async () => { + const instantiationService = workbenchInstantiationService(); + + const part = instantiationService.createInstance(EditorPart); + part.create(document.createElement('div')); + part.layout(400, 300); + + await part.whenRestored; + + const rootGroup = part.activeGroup; + + const history = new EditorsHistory(part, new TestStorageService()); + + let currentHistory = history.editors; + assert.equal(currentHistory.length, 0); + + const sideGroup = part.addGroup(rootGroup, GroupDirection.RIGHT); + + const input1 = new HistoryTestEditorInput(URI.parse('foo://bar1')); + + await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true, activation: EditorActivation.ACTIVATE })); + await sideGroup.openEditor(input1, EditorOptions.create({ pinned: true, activation: EditorActivation.ACTIVATE })); + + currentHistory = history.editors; + assert.equal(currentHistory.length, 2); + assert.equal(currentHistory[0].groupId, sideGroup.id); + assert.equal(currentHistory[0].editor, input1); + assert.equal(currentHistory[1].groupId, rootGroup.id); + assert.equal(currentHistory[1].editor, input1); + + await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true, activation: EditorActivation.ACTIVATE })); + + currentHistory = history.editors; + assert.equal(currentHistory.length, 2); + assert.equal(currentHistory[0].groupId, rootGroup.id); + assert.equal(currentHistory[0].editor, input1); + assert.equal(currentHistory[1].groupId, sideGroup.id); + assert.equal(currentHistory[1].editor, input1); + + // Opening an editor inactive should not change + // the most recent editor, but rather put it behind + const input2 = new HistoryTestEditorInput(URI.parse('foo://bar2')); + + await rootGroup.openEditor(input2, EditorOptions.create({ inactive: true })); + + currentHistory = history.editors; + assert.equal(currentHistory.length, 3); + assert.equal(currentHistory[0].groupId, rootGroup.id); + assert.equal(currentHistory[0].editor, input1); + assert.equal(currentHistory[1].groupId, rootGroup.id); + assert.equal(currentHistory[1].editor, input2); + assert.equal(currentHistory[2].groupId, sideGroup.id); + assert.equal(currentHistory[2].editor, input1); + + await rootGroup.closeAllEditors(); + + currentHistory = history.editors; + assert.equal(currentHistory.length, 1); + assert.equal(currentHistory[0].groupId, sideGroup.id); + assert.equal(currentHistory[0].editor, input1); + + await sideGroup.closeAllEditors(); + + currentHistory = history.editors; + assert.equal(currentHistory.length, 0); + + part.dispose(); + }); + + test('copy group', async () => { + const instantiationService = workbenchInstantiationService(); + + const part = instantiationService.createInstance(EditorPart); + part.create(document.createElement('div')); + part.layout(400, 300); + + await part.whenRestored; + + const history = new EditorsHistory(part, new TestStorageService()); + + const input1 = new HistoryTestEditorInput(URI.parse('foo://bar1')); + const input2 = new HistoryTestEditorInput(URI.parse('foo://bar2')); + const input3 = new HistoryTestEditorInput(URI.parse('foo://bar3')); + + const rootGroup = part.activeGroup; + + await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true })); + await rootGroup.openEditor(input2, EditorOptions.create({ pinned: true })); + await rootGroup.openEditor(input3, EditorOptions.create({ pinned: true })); + + let currentHistory = history.editors; + assert.equal(currentHistory.length, 3); + assert.equal(currentHistory[0].groupId, rootGroup.id); + assert.equal(currentHistory[0].editor, input3); + assert.equal(currentHistory[1].groupId, rootGroup.id); + assert.equal(currentHistory[1].editor, input2); + assert.equal(currentHistory[2].groupId, rootGroup.id); + assert.equal(currentHistory[2].editor, input1); + + const copiedGroup = part.copyGroup(rootGroup, rootGroup, GroupDirection.RIGHT); + copiedGroup.setActive(true); + + currentHistory = history.editors; + assert.equal(currentHistory.length, 6); + assert.equal(currentHistory[0].groupId, copiedGroup.id); + assert.equal(currentHistory[0].editor, input3); + assert.equal(currentHistory[1].groupId, rootGroup.id); + assert.equal(currentHistory[1].editor, input3); + assert.equal(currentHistory[2].groupId, copiedGroup.id); + assert.equal(currentHistory[2].editor, input2); + assert.equal(currentHistory[3].groupId, copiedGroup.id); + assert.equal(currentHistory[3].editor, input1); + assert.equal(currentHistory[4].groupId, rootGroup.id); + assert.equal(currentHistory[4].editor, input2); + assert.equal(currentHistory[5].groupId, rootGroup.id); + assert.equal(currentHistory[5].editor, input1); + + part.dispose(); + }); + + test('initial editors are part of history and state is persisted & restored (single group)', async () => { + const instantiationService = workbenchInstantiationService(); + instantiationService.invokeFunction(accessor => Registry.as(EditorExtensions.EditorInputFactories).start(accessor)); + + const part = instantiationService.createInstance(EditorPart); + part.create(document.createElement('div')); + part.layout(400, 300); + + await part.whenRestored; + + const rootGroup = part.activeGroup; + + const input1 = new HistoryTestEditorInput(URI.parse('foo://bar1')); + const input2 = new HistoryTestEditorInput(URI.parse('foo://bar2')); + const input3 = new HistoryTestEditorInput(URI.parse('foo://bar3')); + + await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true })); + await rootGroup.openEditor(input2, EditorOptions.create({ pinned: true })); + await rootGroup.openEditor(input3, EditorOptions.create({ pinned: true })); + + const storage = new TestStorageService(); + const history = new EditorsHistory(part, storage); + await part.whenRestored; + + let currentHistory = history.editors; + assert.equal(currentHistory.length, 3); + assert.equal(currentHistory[0].groupId, rootGroup.id); + assert.equal(currentHistory[0].editor, input3); + assert.equal(currentHistory[1].groupId, rootGroup.id); + assert.equal(currentHistory[1].editor, input2); + assert.equal(currentHistory[2].groupId, rootGroup.id); + assert.equal(currentHistory[2].editor, input1); + + storage._onWillSaveState.fire({ reason: WillSaveStateReason.SHUTDOWN }); + + const restoredHistory = new EditorsHistory(part, storage); + await part.whenRestored; + + currentHistory = restoredHistory.editors; + assert.equal(currentHistory.length, 3); + assert.equal(currentHistory[0].groupId, rootGroup.id); + assert.equal(currentHistory[0].editor, input3); + assert.equal(currentHistory[1].groupId, rootGroup.id); + assert.equal(currentHistory[1].editor, input2); + assert.equal(currentHistory[2].groupId, rootGroup.id); + assert.equal(currentHistory[2].editor, input1); + + part.dispose(); + }); + + test('initial editors are part of history (multi group)', async () => { + const instantiationService = workbenchInstantiationService(); + + const part = instantiationService.createInstance(EditorPart); + part.create(document.createElement('div')); + part.layout(400, 300); + + await part.whenRestored; + + const rootGroup = part.activeGroup; + + const input1 = new HistoryTestEditorInput(URI.parse('foo://bar1')); + const input2 = new HistoryTestEditorInput(URI.parse('foo://bar2')); + const input3 = new HistoryTestEditorInput(URI.parse('foo://bar3')); + + await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true })); + await rootGroup.openEditor(input2, EditorOptions.create({ pinned: true })); + + const sideGroup = part.addGroup(rootGroup, GroupDirection.RIGHT); + await sideGroup.openEditor(input3, EditorOptions.create({ pinned: true })); + + const storage = new TestStorageService(); + const history = new EditorsHistory(part, storage); + await part.whenRestored; + + let currentHistory = history.editors; + assert.equal(currentHistory.length, 3); + assert.equal(currentHistory[0].groupId, sideGroup.id); + assert.equal(currentHistory[0].editor, input3); + assert.equal(currentHistory[1].groupId, rootGroup.id); + assert.equal(currentHistory[1].editor, input2); + assert.equal(currentHistory[2].groupId, rootGroup.id); + assert.equal(currentHistory[2].editor, input1); + + storage._onWillSaveState.fire({ reason: WillSaveStateReason.SHUTDOWN }); + + const restoredHistory = new EditorsHistory(part, storage); + await part.whenRestored; + + currentHistory = restoredHistory.editors; + assert.equal(currentHistory.length, 3); + assert.equal(currentHistory[0].groupId, sideGroup.id); + assert.equal(currentHistory[0].editor, input3); + assert.equal(currentHistory[1].groupId, rootGroup.id); + assert.equal(currentHistory[1].editor, input2); + assert.equal(currentHistory[2].groupId, rootGroup.id); + assert.equal(currentHistory[2].editor, input1); + + part.dispose(); + }); + + test('history does not restore editors that cannot be serialized', async () => { + const instantiationService = workbenchInstantiationService(); + instantiationService.invokeFunction(accessor => Registry.as(EditorExtensions.EditorInputFactories).start(accessor)); + + const part = instantiationService.createInstance(EditorPart); + part.create(document.createElement('div')); + part.layout(400, 300); + + await part.whenRestored; + + const rootGroup = part.activeGroup; + + const input1 = new TestEditorInput(URI.parse('foo://bar1')); + + await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true })); + + const storage = new TestStorageService(); + const history = new EditorsHistory(part, storage); + await part.whenRestored; + + let currentHistory = history.editors; + assert.equal(currentHistory.length, 1); + assert.equal(currentHistory[0].groupId, rootGroup.id); + assert.equal(currentHistory[0].editor, input1); + + storage._onWillSaveState.fire({ reason: WillSaveStateReason.SHUTDOWN }); + + const restoredHistory = new EditorsHistory(part, storage); + await part.whenRestored; + + currentHistory = restoredHistory.editors; + assert.equal(currentHistory.length, 0); + + part.dispose(); + }); + + test('open next/previous recently used editor (single group)', async () => { + const [part, historyService] = await createServices(); + + const input1 = new TestEditorInput(URI.parse('foo://bar1')); + const input2 = new TestEditorInput(URI.parse('foo://bar2')); + + await part.activeGroup.openEditor(input1, EditorOptions.create({ pinned: true })); + assert.equal(part.activeGroup.activeEditor, input1); + + await part.activeGroup.openEditor(input2, EditorOptions.create({ pinned: true })); + assert.equal(part.activeGroup.activeEditor, input2); + + historyService.openPreviouslyUsedEditor(); + assert.equal(part.activeGroup.activeEditor, input1); + + historyService.openNextRecentlyUsedEditor(); + assert.equal(part.activeGroup.activeEditor, input2); + + historyService.openPreviouslyUsedEditor(part.activeGroup.id); + assert.equal(part.activeGroup.activeEditor, input1); + + historyService.openNextRecentlyUsedEditor(part.activeGroup.id); + assert.equal(part.activeGroup.activeEditor, input2); + + part.dispose(); + }); + + test('open next/previous recently used editor (multi group)', async () => { + const [part, historyService] = await createServices(); + const rootGroup = part.activeGroup; + + const input1 = new TestEditorInput(URI.parse('foo://bar1')); + const input2 = new TestEditorInput(URI.parse('foo://bar2')); + + const sideGroup = part.addGroup(rootGroup, GroupDirection.RIGHT); + + await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true })); + await sideGroup.openEditor(input2, EditorOptions.create({ pinned: true })); + + historyService.openPreviouslyUsedEditor(); + assert.equal(part.activeGroup, rootGroup); + assert.equal(rootGroup.activeEditor, input1); + + historyService.openNextRecentlyUsedEditor(); + assert.equal(part.activeGroup, sideGroup); + assert.equal(sideGroup.activeEditor, input2); + + part.dispose(); + }); + + test('open next/previous recently is reset when other input opens', async () => { + const [part, historyService] = await createServices(); + + const input1 = new TestEditorInput(URI.parse('foo://bar1')); + const input2 = new TestEditorInput(URI.parse('foo://bar2')); + const input3 = new TestEditorInput(URI.parse('foo://bar3')); + const input4 = new TestEditorInput(URI.parse('foo://bar4')); + + await part.activeGroup.openEditor(input1, EditorOptions.create({ pinned: true })); + await part.activeGroup.openEditor(input2, EditorOptions.create({ pinned: true })); + await part.activeGroup.openEditor(input3, EditorOptions.create({ pinned: true })); + + historyService.openPreviouslyUsedEditor(); + assert.equal(part.activeGroup.activeEditor, input2); + + await timeout(0); + await part.activeGroup.openEditor(input4, EditorOptions.create({ pinned: true })); + + historyService.openPreviouslyUsedEditor(); + assert.equal(part.activeGroup.activeEditor, input2); + + historyService.openNextRecentlyUsedEditor(); + assert.equal(part.activeGroup.activeEditor, input4); + + part.dispose(); + }); + }); +}); + + diff --git a/src/vs/workbench/services/keybinding/browser/keybindingService.ts b/src/vs/workbench/services/keybinding/browser/keybindingService.ts index 4a4f8365de..2df0ae1984 100644 --- a/src/vs/workbench/services/keybinding/browser/keybindingService.ts +++ b/src/vs/workbench/services/keybinding/browser/keybindingService.ts @@ -745,7 +745,6 @@ const keyboardConfiguration: IConfigurationNode = { 'order': 15, 'type': 'object', 'title': nls.localize('keyboardConfigurationTitle', "Keyboard"), - 'overridable': true, 'properties': { 'keyboard.dispatch': { 'type': 'string', diff --git a/src/vs/workbench/services/keybinding/browser/keymapService.ts b/src/vs/workbench/services/keybinding/browser/keymapService.ts index 90bd9eddd7..414c9531f5 100644 --- a/src/vs/workbench/services/keybinding/browser/keymapService.ts +++ b/src/vs/workbench/services/keybinding/browser/keymapService.ts @@ -626,7 +626,6 @@ const keyboardConfiguration: IConfigurationNode = { 'order': 15, 'type': 'object', 'title': nls.localize('keyboardConfigurationTitle', "Keyboard"), - 'overridable': true, 'properties': { 'keyboard.layout': { 'type': 'string', diff --git a/src/vs/workbench/services/keybinding/electron-browser/keybinding.contribution.ts b/src/vs/workbench/services/keybinding/electron-browser/keybinding.contribution.ts index c851126a7f..e675216223 100644 --- a/src/vs/workbench/services/keybinding/electron-browser/keybinding.contribution.ts +++ b/src/vs/workbench/services/keybinding/electron-browser/keybinding.contribution.ts @@ -15,7 +15,6 @@ const keyboardConfiguration: IConfigurationNode = { 'order': 15, 'type': 'object', 'title': nls.localize('keyboardConfigurationTitle', "Keyboard"), - 'overridable': true, 'properties': { 'keyboard.touchbar.enabled': { 'type': 'boolean', diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts b/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts index 5368a2a88f..7bf742fd5e 100644 --- a/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts +++ b/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts @@ -17,7 +17,7 @@ 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 { ITextModelService } from 'vs/editor/common/services/resolverService'; -import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; +import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; diff --git a/src/vs/workbench/services/remote/node/tunnelService.ts b/src/vs/workbench/services/remote/node/tunnelService.ts index b7da3caf83..1ad9588492 100644 --- a/src/vs/workbench/services/remote/node/tunnelService.ts +++ b/src/vs/workbench/services/remote/node/tunnelService.ts @@ -5,13 +5,13 @@ import * as net from 'net'; import { Barrier } from 'vs/base/common/async'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { NodeSocket } from 'vs/base/parts/ipc/node/ipc.net'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import product from 'vs/platform/product/common/product'; import { connectRemoteAgentTunnel, IConnectionOptions } from 'vs/platform/remote/common/remoteAgentConnection'; import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; -import { ITunnelService, RemoteTunnel } from 'vs/platform/remote/common/tunnel'; +import { ITunnelService, RemoteTunnel, ITunnelProvider } from 'vs/platform/remote/common/tunnel'; import { nodeSocketFactory } from 'vs/platform/remote/node/nodeSocketFactory'; import { ISignService } from 'vs/platform/sign/common/sign'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -106,6 +106,7 @@ export class TunnelService implements ITunnelService { private _onTunnelClosed: Emitter = new Emitter(); public onTunnelClosed: Event = this._onTunnelClosed.event; private readonly _tunnels = new Map }>(); + private _tunnelProvider: ITunnelProvider | undefined; public constructor( @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @@ -114,6 +115,20 @@ export class TunnelService implements ITunnelService { @ILogService private readonly logService: ILogService, ) { } + setTunnelProvider(provider: ITunnelProvider | undefined): IDisposable { + if (!provider) { + return { + dispose: () => { } + }; + } + this._tunnelProvider = provider; + return { + dispose: () => { + this._tunnelProvider = undefined; + } + }; + } + public get tunnels(): Promise { return Promise.all(Array.from(this._tunnels.values()).map(x => x.value)); } @@ -185,22 +200,30 @@ export class TunnelService implements ITunnelService { return existing.value; } - const options: IConnectionOptions = { - commit: product.commit, - socketFactory: nodeSocketFactory, - addressProvider: { - getAddress: async () => { - const { authority } = await this.remoteAuthorityResolverService.resolveAuthority(remoteAuthority); - return { host: authority.host, port: authority.port }; - } - }, - signService: this.signService, - logService: this.logService - }; + if (this._tunnelProvider) { + const tunnel = this._tunnelProvider.forwardPort({ remote: { host: 'localhost', port: remotePort } }); + if (tunnel) { + this._tunnels.set(remotePort, { refcount: 1, value: tunnel }); + } + return tunnel; + } else { + const options: IConnectionOptions = { + commit: product.commit, + socketFactory: nodeSocketFactory, + addressProvider: { + getAddress: async () => { + const { authority } = await this.remoteAuthorityResolverService.resolveAuthority(remoteAuthority); + return { host: authority.host, port: authority.port }; + } + }, + signService: this.signService, + logService: this.logService + }; - const tunnel = createRemoteTunnel(options, remotePort, localPort); - this._tunnels.set(remotePort, { refcount: 1, value: tunnel }); - return tunnel; + const tunnel = createRemoteTunnel(options, remotePort, localPort); + this._tunnels.set(remotePort, { refcount: 1, value: tunnel }); + return tunnel; + } } } diff --git a/src/vs/workbench/services/search/common/search.ts b/src/vs/workbench/services/search/common/search.ts index 23931bc262..4a2ffdee5c 100644 --- a/src/vs/workbench/services/search/common/search.ts +++ b/src/vs/workbench/services/search/common/search.ts @@ -16,7 +16,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' import { ITelemetryData } from 'vs/platform/telemetry/common/telemetry'; import { Event } from 'vs/base/common/event'; import { relative } from 'vs/base/common/path'; -import { Extensions as ViewContainerExtensions, ViewContainer, IViewContainersRegistry } from 'vs/workbench/common/views'; +import { Extensions as ViewContainerExtensions, ViewContainer, IViewContainersRegistry, ViewContainerLocation } from 'vs/workbench/common/views'; import { Registry } from 'vs/platform/registry/common/platform'; export const VIEWLET_ID = 'workbench.view.search'; @@ -25,7 +25,7 @@ export const VIEW_ID = 'workbench.view.search'; /** * Search viewlet container. */ -export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(VIEWLET_ID, true); +export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(VIEWLET_ID, ViewContainerLocation.Sidebar, true); export const ISearchService = createDecorator('searchService'); @@ -311,6 +311,15 @@ export class OneLineRange extends SearchRange { } } +export const enum SearchSortOrder { + Default = 'default', + FileNames = 'fileNames', + Type = 'type', + Modified = 'modified', + CountDescending = 'countDescending', + CountAscending = 'countAscending' +} + export interface ISearchConfigurationProperties { exclude: glob.IExpression; useRipgrep: boolean; @@ -333,6 +342,7 @@ export interface ISearchConfigurationProperties { searchOnTypeDebouncePeriod: number; enableSearchEditorPreview: boolean; searchEditorPreviewForceAbsolutePaths: boolean; + sortOrder: SearchSortOrder; } export interface ISearchConfiguration extends IFilesConfiguration { diff --git a/src/vs/workbench/services/search/common/searchService.ts b/src/vs/workbench/services/search/common/searchService.ts index b38e6e8753..a2f8b4a777 100644 --- a/src/vs/workbench/services/search/common/searchService.ts +++ b/src/vs/workbench/services/search/common/searchService.ts @@ -59,7 +59,7 @@ export class SearchService extends Disposable implements ISearchService { }); } - textSearch(query: ITextQuery, token?: CancellationToken, onProgress?: (item: ISearchProgressItem) => void): Promise { + async textSearch(query: ITextQuery, token?: CancellationToken, onProgress?: (item: ISearchProgressItem) => void): Promise { // Get local results from dirty/untitled const localResults = this.getLocalResults(query); @@ -83,7 +83,11 @@ export class SearchService extends Disposable implements ISearchService { } }; - return this.doSearch(query, token, onProviderProgress); + const otherResults = await this.doSearch(query, token, onProviderProgress); + return { + ...otherResults, + results: [...otherResults.results, ...arrays.coalesce(localResults.values())] + }; } fileSearch(query: IFileQuery, token?: CancellationToken): Promise { diff --git a/src/vs/workbench/services/search/node/fileSearch.ts b/src/vs/workbench/services/search/node/fileSearch.ts index 0973cd61af..cde09d81b7 100644 --- a/src/vs/workbench/services/search/node/fileSearch.ts +++ b/src/vs/workbench/services/search/node/fileSearch.ts @@ -7,7 +7,7 @@ import * as childProcess from 'child_process'; import * as fs from 'fs'; import * as path from 'vs/base/common/path'; import { Readable } from 'stream'; -import { NodeStringDecoder, StringDecoder } from 'string_decoder'; +import { StringDecoder } from 'string_decoder'; import * as arrays from 'vs/base/common/arrays'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import * as glob from 'vs/base/common/glob'; @@ -360,7 +360,7 @@ export class FileWalker { }); } - private forwardData(stream: Readable, encoding: string, cb: (err: Error | null, stdout?: string) => void): NodeStringDecoder { + private forwardData(stream: Readable, encoding: string, cb: (err: Error | null, stdout?: string) => void): StringDecoder { const decoder = new StringDecoder(encoding); stream.on('data', (data: Buffer) => { cb(null, decoder.write(data)); diff --git a/src/vs/workbench/services/search/node/rawSearchService.ts b/src/vs/workbench/services/search/node/rawSearchService.ts index 082e9a3add..5b3814f287 100644 --- a/src/vs/workbench/services/search/node/rawSearchService.ts +++ b/src/vs/workbench/services/search/node/rawSearchService.ts @@ -380,6 +380,7 @@ export class SearchService implements IRawSearchService { */ private preventCancellation(promise: CancelablePromise): CancelablePromise { return new class implements CancelablePromise { + get [Symbol.toStringTag]() { return this.toString(); } cancel() { // Do nothing } diff --git a/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts b/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts index 03d396fdaf..9b9b62ce6c 100644 --- a/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts +++ b/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts @@ -6,7 +6,7 @@ import * as cp from 'child_process'; import { EventEmitter } from 'events'; import * as path from 'vs/base/common/path'; -import { NodeStringDecoder, StringDecoder } from 'string_decoder'; +import { StringDecoder } from 'string_decoder'; import { createRegExp, startsWith, startsWithUTF8BOM, stripUTF8BOM, escapeRegExpCharacters, endsWith } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; import { IExtendedExtensionSearchOptions, SearchError, SearchErrorCode, serializeSearchError } from 'vs/workbench/services/search/common/search'; @@ -169,7 +169,7 @@ export class RipgrepParser extends EventEmitter { private remainder = ''; private isDone = false; private hitLimit = false; - private stringDecoder: NodeStringDecoder; + private stringDecoder: StringDecoder; private numResults = 0; @@ -533,7 +533,7 @@ export type IRgBytesOrText = { bytes: string } | { text: string }; export function fixRegexNewline(pattern: string): string { // Replace an unescaped $ at the end of the pattern with \r?$ // Match $ preceeded by none or even number of literal \ - return pattern.replace(/([^\\]|^)(\\\\)*\\n/g, '$1$2\\r?\\n'); + return pattern.replace(/(?<=[^\\]|^)(\\\\)*\\n/g, '$1\\r?\\n'); } export function fixNewline(pattern: string): string { diff --git a/src/vs/workbench/services/search/test/node/ripgrepTextSearchEngine.test.ts b/src/vs/workbench/services/search/test/node/ripgrepTextSearchEngine.test.ts index 7dc1193808..bb345f5c14 100644 --- a/src/vs/workbench/services/search/test/node/ripgrepTextSearchEngine.test.ts +++ b/src/vs/workbench/services/search/test/node/ripgrepTextSearchEngine.test.ts @@ -38,6 +38,8 @@ suite('RipgrepTextSearchEngine', () => { ['foo', 'foo', true], ['foo\\n', 'foo\r\n', true], + ['foo\\n\\n', 'foo\n\n', true], + ['foo\\n\\n', 'foo\r\n\r\n', true], ['foo\\n', 'foo\n', true], ['foo\\nabc', 'foo\r\nabc', true], ['foo\\nabc', 'foo\nabc', true], diff --git a/src/vs/workbench/services/textfile/browser/textFileService.ts b/src/vs/workbench/services/textfile/browser/textFileService.ts index 5afbc6fe65..aab09abdf3 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 { IResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; +import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; 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, - @IResourceConfigurationService protected readonly textResourceConfigurationService: IResourceConfigurationService, + @ITextResourceConfigurationService protected readonly textResourceConfigurationService: ITextResourceConfigurationService, @IFilesConfigurationService protected readonly filesConfigurationService: IFilesConfigurationService ) { super(); @@ -433,7 +433,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex modelToRestoreResource = joinPath(target, sourceModelResource.path.substr(source.path.length + 1)); } - const modelToRestore: ModelToRestore = { resource: modelToRestoreResource, encoding: sourceModel.getEncoding(), mode: sourceModel.textEditorModel?.getModeId() }; + const modelToRestore: ModelToRestore = { resource: modelToRestoreResource, encoding: sourceModel.getEncoding() }; if (sourceModel.isDirty()) { modelToRestore.snapshot = sourceModel.createSnapshot(); } @@ -766,7 +766,16 @@ export abstract class AbstractTextFileService extends Disposable implements ITex await this.create(target, ''); } - targetModel = await this.models.loadOrCreate(target, { encoding: sourceModel.getEncoding(), mode: sourceModel.textEditorModel?.getModeId() }); + // Carry over the mode if this is an untitled file and the mode was picked by the user + let mode: string | undefined; + if (sourceModel instanceof UntitledTextEditorModel) { + mode = sourceModel.getMode(); + if (mode === PLAINTEXT_MODE_ID) { + mode = undefined; // never enforce plain text mode when moving as it is unspecific + } + } + + targetModel = await this.models.loadOrCreate(target, { encoding: sourceModel.getEncoding(), mode }); } try { diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index ebaf0a7c43..f66e99f977 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -962,6 +962,14 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } } + getMode(): string | undefined { + if (this.textEditorModel) { + return this.textEditorModel.getModeId(); + } + + return this.preferredMode; + } + getEncoding(): string | undefined { return this.preferredEncoding || this.contentEncoding; } diff --git a/src/vs/workbench/services/textfile/common/textResourcePropertiesService.ts b/src/vs/workbench/services/textfile/common/textResourcePropertiesService.ts index 141a78b898..7ff95af2f9 100644 --- a/src/vs/workbench/services/textfile/common/textResourcePropertiesService.ts +++ b/src/vs/workbench/services/textfile/common/textResourcePropertiesService.ts @@ -5,7 +5,7 @@ import { URI } from 'vs/base/common/uri'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; +import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { OperatingSystem, OS } from 'vs/base/common/platform'; import { Schemas } from 'vs/base/common/network'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; diff --git a/src/vs/workbench/services/textfile/common/textfiles.ts b/src/vs/workbench/services/textfile/common/textfiles.ts index c56406d8ad..40022a2a4f 100644 --- a/src/vs/workbench/services/textfile/common/textfiles.ts +++ b/src/vs/workbench/services/textfile/common/textfiles.ts @@ -456,6 +456,8 @@ export interface ITextFileEditorModel extends ITextEditorModel, IEncodingSupport makeDirty(): void; + getMode(): string | undefined; + isResolved(): this is IResolvedTextFileEditorModel; isDisposed(): boolean; @@ -468,9 +470,6 @@ export interface IResolvedTextFileEditorModel extends ITextFileEditorModel { createSnapshot(): ITextSnapshot; } -/** - * Helper method to convert a snapshot into its full string form. - */ export function snapshotToString(snapshot: ITextSnapshot): string { const chunks: string[] = []; diff --git a/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts b/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts index dfea180b1e..7f887a40ff 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 { IResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; +import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; 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, - @IResourceConfigurationService textResourceConfigurationService: IResourceConfigurationService, + @ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService, @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( - @IResourceConfigurationService private textResourceConfigurationService: IResourceConfigurationService, + @ITextResourceConfigurationService private textResourceConfigurationService: ITextResourceConfigurationService, @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 a11a24c770..76af15b355 100644 --- a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts @@ -582,9 +582,9 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { public writeConfiguration(key: string, value: any, settingsTarget: ConfigurationTarget | 'auto'): Promise { let settings = this.configurationService.inspect(key); if (settingsTarget === 'auto') { - if (!types.isUndefined(settings.workspaceFolder)) { + if (!types.isUndefined(settings.workspaceFolderValue)) { settingsTarget = ConfigurationTarget.WORKSPACE_FOLDER; - } else if (!types.isUndefined(settings.workspace)) { + } else if (!types.isUndefined(settings.workspaceValue)) { settingsTarget = ConfigurationTarget.WORKSPACE; } else { settingsTarget = ConfigurationTarget.USER; @@ -592,10 +592,10 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { } if (settingsTarget === ConfigurationTarget.USER) { - if (value === settings.user) { + if (value === settings.userValue) { return Promise.resolve(undefined); // nothing to do - } else if (value === settings.default) { - if (types.isUndefined(settings.user)) { + } else if (value === settings.defaultValue) { + if (types.isUndefined(settings.userValue)) { return Promise.resolve(undefined); // nothing to do } value = undefined; // remove configuration from user settings diff --git a/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts b/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts index b235adaae3..3506631ecb 100644 --- a/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts +++ b/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts @@ -31,6 +31,11 @@ export interface IUntitledTextEditorService { _serviceBrand: undefined; + /** + * Events for when untitled text editors are created. + */ + readonly onDidCreate: Event; + /** * Events for when untitled text editors content changes (e.g. any keystroke). */ @@ -117,6 +122,9 @@ export class UntitledTextEditorService extends Disposable implements IUntitledTe private mapResourceToInput = new ResourceMap(); private mapResourceToAssociatedFilePath = new ResourceMap(); + private readonly _onDidCreate = this._register(new Emitter()); + readonly onDidCreate = this._onDidCreate.event; + private readonly _onDidChangeContent = this._register(new Emitter()); readonly onDidChangeContent = this._onDidChangeContent.event; @@ -263,6 +271,9 @@ export class UntitledTextEditorService extends Disposable implements IUntitledTe // Add to cache this.mapResourceToInput.set(untitledResource, input); + // Signal new untitled as event + this._onDidCreate.fire(untitledResource); + return input; } diff --git a/src/vs/workbench/services/userData/common/inMemoryUserDataProvider.ts b/src/vs/workbench/services/userData/common/inMemoryUserDataProvider.ts index 3726c6194c..33a5fdd6c9 100644 --- a/src/vs/workbench/services/userData/common/inMemoryUserDataProvider.ts +++ b/src/vs/workbench/services/userData/common/inMemoryUserDataProvider.ts @@ -131,18 +131,14 @@ export class InMemoryFileSystemProvider extends Disposable implements IFileSyste } async delete(resource: URI, opts: FileDeleteOptions): Promise { - try { - let dirname = resources.dirname(resource); - let basename = resources.basename(resource); - let parent = this._lookupAsDirectory(dirname, false); - if (parent.entries.has(basename)) { - parent.entries.delete(basename); - parent.mtime = Date.now(); - parent.size -= 1; - this._fireSoon({ type: FileChangeType.UPDATED, resource: dirname }, { resource, type: FileChangeType.DELETED }); - } - } catch (error) { - // ignore if resource does not exist to keep parity with other file system providers + let dirname = resources.dirname(resource); + let basename = resources.basename(resource); + let parent = this._lookupAsDirectory(dirname, false); + if (parent.entries.has(basename)) { + parent.entries.delete(basename); + parent.mtime = Date.now(); + parent.size -= 1; + this._fireSoon({ type: FileChangeType.UPDATED, resource: dirname }, { resource, type: FileChangeType.DELETED }); } } diff --git a/src/vs/workbench/services/userDataSync/common/userDataSyncUtil.ts b/src/vs/workbench/services/userDataSync/common/userDataSyncUtil.ts index 3a2f525870..d1c76f94a2 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, IResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; +import { ITextResourcePropertiesService, ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; 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, - @IResourceConfigurationService private readonly textResourceConfigurationService: IResourceConfigurationService, + @ITextResourceConfigurationService private readonly textResourceConfigurationService: ITextResourceConfigurationService, ) { } public async resolveUserBindings(userBindings: string[]): Promise> { diff --git a/src/vs/workbench/services/workspaces/browser/abstractWorkspaceEditingService.ts b/src/vs/workbench/services/workspaces/browser/abstractWorkspaceEditingService.ts index ebda69b593..af6d6940fe 100644 --- a/src/vs/workbench/services/workspaces/browser/abstractWorkspaceEditingService.ts +++ b/src/vs/workbench/services/workspaces/browser/abstractWorkspaceEditingService.ts @@ -309,7 +309,7 @@ export abstract class AbstractWorkspaceEditingService implements IWorkspaceEditi continue; } - targetWorkspaceConfiguration[key] = this.configurationService.inspect(key).workspace; + targetWorkspaceConfiguration[key] = this.configurationService.inspect(key).workspaceValue; } } diff --git a/src/vs/workbench/services/workspaces/browser/workspacesService.ts b/src/vs/workbench/services/workspaces/browser/workspacesService.ts index 32393f351c..ffae9ecee3 100644 --- a/src/vs/workbench/services/workspaces/browser/workspacesService.ts +++ b/src/vs/workbench/services/workspaces/browser/workspacesService.ts @@ -12,7 +12,7 @@ import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/ import { ILogService } from 'vs/platform/log/common/log'; import { Disposable } from 'vs/base/common/lifecycle'; import { getWorkspaceIdentifier } from 'vs/workbench/services/workspaces/browser/workspaces'; -import { IFileService } from 'vs/platform/files/common/files'; +import { IFileService, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { joinPath } from 'vs/base/common/resources'; import { VSBuffer } from 'vs/base/common/buffer'; @@ -143,8 +143,14 @@ export class BrowserWorkspacesService extends Disposable implements IWorkspacesS return this.getWorkspaceIdentifier(newUntitledWorkspacePath); } - deleteUntitledWorkspace(workspace: IWorkspaceIdentifier): Promise { - return this.fileService.del(workspace.configPath); + async deleteUntitledWorkspace(workspace: IWorkspaceIdentifier): Promise { + try { + await this.fileService.del(workspace.configPath); + } catch (error) { + if ((error).fileOperationResult !== FileOperationResult.FILE_NOT_FOUND) { + throw error; // re-throw any other error than file not found which is OK + } + } } async getWorkspaceIdentifier(workspacePath: URI): Promise { diff --git a/src/vs/workbench/test/browser/parts/editor/baseEditor.test.ts b/src/vs/workbench/test/browser/parts/editor/baseEditor.test.ts index 7af8bb48b5..89667215c4 100644 --- a/src/vs/workbench/test/browser/parts/editor/baseEditor.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/baseEditor.test.ts @@ -6,19 +6,19 @@ import * as assert from 'assert'; import { BaseEditor, EditorMemento } from 'vs/workbench/browser/parts/editor/baseEditor'; import { EditorInput, EditorOptions, IEditorInputFactory, IEditorInputFactoryRegistry, Extensions as EditorExtensions } from 'vs/workbench/common/editor'; -import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import * as Platform from 'vs/platform/registry/common/platform'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; -import { workbenchInstantiationService, TestEditorGroup, TestEditorGroupsService, TestStorageService } from 'vs/workbench/test/workbenchTestServices'; +import { workbenchInstantiationService, TestEditorGroupView, TestEditorGroupsService, TestStorageService } from 'vs/workbench/test/workbenchTestServices'; import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; import { URI } from 'vs/base/common/uri'; import { IEditorRegistry, Extensions, EditorDescriptor } from 'vs/workbench/browser/editor'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IEditorModel } from 'vs/platform/editor/common/editor'; +import { dispose } from 'vs/base/common/lifecycle'; const NullThemeService = new TestThemeService(); @@ -50,6 +50,10 @@ export class MyOtherEditor extends BaseEditor { class MyInputFactory implements IEditorInputFactory { + canSerialize(editorInput: EditorInput): boolean { + return true; + } + serialize(input: EditorInput): string { return input.toString(); } @@ -98,7 +102,7 @@ suite('Workbench base editor', () => { await e.setInput(input, options, CancellationToken.None); assert.strictEqual(input, e.input); assert.strictEqual(options, e.options); - const group = new TestEditorGroup(1); + const group = new TestEditorGroupView(1); e.setVisible(true, group); assert(e.isVisible()); assert.equal(e.group, group); @@ -127,8 +131,8 @@ suite('Workbench base editor', () => { let oldEditorsCnt = EditorRegistry.getEditors().length; let oldInputCnt = (EditorRegistry).getEditorInputs().length; - EditorRegistry.registerEditor(d1, [new SyncDescriptor(MyInput)]); - EditorRegistry.registerEditor(d2, [new SyncDescriptor(MyInput), new SyncDescriptor(MyOtherInput)]); + const dispose1 = EditorRegistry.registerEditor(d1, [new SyncDescriptor(MyInput)]); + const dispose2 = EditorRegistry.registerEditor(d2, [new SyncDescriptor(MyInput), new SyncDescriptor(MyOtherInput)]); assert.equal(EditorRegistry.getEditors().length, oldEditorsCnt + 2); assert.equal((EditorRegistry).getEditorInputs().length, oldInputCnt + 3); @@ -139,62 +143,52 @@ suite('Workbench base editor', () => { assert.strictEqual(EditorRegistry.getEditorById('id1'), d1); assert.strictEqual(EditorRegistry.getEditorById('id2'), d2); assert(!EditorRegistry.getEditorById('id3')); + + dispose([dispose1, dispose2]); }); test('Editor Lookup favors specific class over superclass (match on specific class)', function () { let d1 = EditorDescriptor.create(MyEditor, 'id1', 'name'); - let d2 = EditorDescriptor.create(MyOtherEditor, 'id2', 'name'); - let oldEditors = EditorRegistry.getEditors(); - (EditorRegistry).setEditors([]); + const disposable = EditorRegistry.registerEditor(d1, [new SyncDescriptor(MyResourceInput)]); - EditorRegistry.registerEditor(d2, [new SyncDescriptor(ResourceEditorInput)]); - EditorRegistry.registerEditor(d1, [new SyncDescriptor(MyResourceInput)]); - - let inst = new TestInstantiationService(); + let inst = workbenchInstantiationService(); const editor = EditorRegistry.getEditor(inst.createInstance(MyResourceInput, 'fake', '', URI.file('/fake'), undefined))!.instantiate(inst); assert.strictEqual(editor.getId(), 'myEditor'); const otherEditor = EditorRegistry.getEditor(inst.createInstance(ResourceEditorInput, 'fake', '', URI.file('/fake'), undefined))!.instantiate(inst); - assert.strictEqual(otherEditor.getId(), 'myOtherEditor'); + assert.strictEqual(otherEditor.getId(), 'workbench.editors.textResourceEditor'); - (EditorRegistry).setEditors(oldEditors); + disposable.dispose(); }); test('Editor Lookup favors specific class over superclass (match on super class)', function () { - let d1 = EditorDescriptor.create(MyOtherEditor, 'id1', 'name'); - - let oldEditors = EditorRegistry.getEditors(); - (EditorRegistry).setEditors([]); - - EditorRegistry.registerEditor(d1, [new SyncDescriptor(ResourceEditorInput)]); - - let inst = new TestInstantiationService(); + let inst = workbenchInstantiationService(); const editor = EditorRegistry.getEditor(inst.createInstance(MyResourceInput, 'fake', '', URI.file('/fake'), undefined))!.instantiate(inst); - assert.strictEqual('myOtherEditor', editor.getId()); - - (EditorRegistry).setEditors(oldEditors); + assert.strictEqual('workbench.editors.textResourceEditor', editor.getId()); }); test('Editor Input Factory', function () { workbenchInstantiationService().invokeFunction(accessor => EditorInputRegistry.start(accessor)); - EditorInputRegistry.registerEditorInputFactory('myInputId', MyInputFactory); + const disposable = EditorInputRegistry.registerEditorInputFactory('myInputId', MyInputFactory); let factory = EditorInputRegistry.getEditorInputFactory('myInputId'); assert(factory); + + disposable.dispose(); }); test('EditorMemento - basics', function () { - const testGroup0 = new TestEditorGroup(0); - const testGroup1 = new TestEditorGroup(1); - const testGroup4 = new TestEditorGroup(4); + const testGroup0 = new TestEditorGroupView(0); + const testGroup1 = new TestEditorGroupView(1); + const testGroup4 = new TestEditorGroupView(4); const editorGroupService = new TestEditorGroupsService([ testGroup0, testGroup1, - new TestEditorGroup(2) + new TestEditorGroupView(2) ]); interface TestViewState { @@ -255,7 +249,7 @@ suite('Workbench base editor', () => { }); test('EditoMemento - use with editor input', function () { - const testGroup0 = new TestEditorGroup(0); + const testGroup0 = new TestEditorGroupView(0); interface TestViewState { line: number; diff --git a/src/vs/workbench/test/browser/parts/views/views.test.ts b/src/vs/workbench/test/browser/parts/views/views.test.ts index 30590a6034..0a325988c8 100644 --- a/src/vs/workbench/test/browser/parts/views/views.test.ts +++ b/src/vs/workbench/test/browser/parts/views/views.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import { ContributableViewsModel, ViewsService, IViewState } from 'vs/workbench/browser/parts/views/views'; -import { IViewsRegistry, IViewDescriptor, IViewContainersRegistry, Extensions as ViewContainerExtensions, IViewsService } from 'vs/workbench/common/views'; +import { IViewsRegistry, IViewDescriptor, IViewContainersRegistry, Extensions as ViewContainerExtensions, IViewsService, ViewContainerLocation } from 'vs/workbench/common/views'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { move } from 'vs/base/common/arrays'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -15,7 +15,7 @@ import { TestInstantiationService } from 'vs/platform/instantiation/test/common/ import { ContextKeyService } from 'vs/platform/contextkey/browser/contextKeyService'; import sinon = require('sinon'); -const container = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer('test'); +const container = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer('test', ViewContainerLocation.Sidebar); const ViewsRegistry = Registry.as(ViewContainerExtensions.ViewsRegistry); class ViewDescriptorSequence { diff --git a/src/vs/workbench/test/common/editor/editorGroups.test.ts b/src/vs/workbench/test/common/editor/editorGroups.test.ts index 27ec465700..bc4ee57ec5 100644 --- a/src/vs/workbench/test/common/editor/editorGroups.test.ts +++ b/src/vs/workbench/test/common/editor/editorGroups.test.ts @@ -20,6 +20,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { IStorageService } from 'vs/platform/storage/common/storage'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; function inst(): IInstantiationService { let inst = new TestInstantiationService(); @@ -138,6 +139,10 @@ interface ISerializedTestInput { class TestEditorInputFactory implements IEditorInputFactory { + canSerialize(editorInput: EditorInput): boolean { + return true; + } + serialize(editorInput: EditorInput): string { let testEditorInput = editorInput; let testInput: ISerializedTestInput = { @@ -156,13 +161,16 @@ class TestEditorInputFactory implements IEditorInputFactory { suite('Workbench editor groups', () => { - function registerEditorInputFactory() { - Registry.as(EditorExtensions.EditorInputFactories).registerEditorInputFactory('testEditorInputForGroups', TestEditorInputFactory); - } + let disposables: IDisposable[] = []; - registerEditorInputFactory(); + setup(() => { + disposables.push(Registry.as(EditorExtensions.EditorInputFactories).registerEditorInputFactory('testEditorInputForGroups', TestEditorInputFactory)); + }); teardown(() => { + dispose(disposables); + disposables = []; + index = 1; }); @@ -260,6 +268,16 @@ suite('Workbench editor groups', () => { assert.equal(group.contains(input2, true), false); assert.equal(group.contains(diffInput1), false); assert.equal(group.contains(diffInput2), false); + + const input3 = input(undefined, true, URI.parse('foo://bar')); + + group.openEditor(input3, { pinned: true, active: true }); + assert.equal(group.contains({ resource: URI.parse('foo://barsomething') }), false); + assert.equal(group.contains({ resource: URI.parse('foo://bar') }), true); + + group.closeEditor(input3); + + assert.equal(group.contains({ resource: URI.parse('foo://bar') }), false); }); test('group serialization', function () { @@ -293,7 +311,8 @@ suite('Workbench editor groups', () => { // Active && Pinned const input1 = input(); - group.openEditor(input1, { active: true, pinned: true }); + const openedEditor = group.openEditor(input1, { active: true, pinned: true }); + assert.equal(openedEditor, input1); assert.equal(group.count, 1); assert.equal(group.getEditors(true).length, 1); @@ -306,8 +325,8 @@ suite('Workbench editor groups', () => { assert.equal(events.opened[0], input1); assert.equal(events.activated[0], input1); - let index = group.closeEditor(input1); - assert.equal(index, 0); + let editor = group.closeEditor(input1); + assert.equal(editor, input1); assert.equal(group.count, 0); assert.equal(group.getEditors(true).length, 0); assert.equal(group.activeEditor, undefined); @@ -338,8 +357,8 @@ suite('Workbench editor groups', () => { assert.equal(events.closed[1].index, 0); assert.equal(events.closed[1].replaced, false); - index = group.closeEditor(input2); - assert.ok(typeof index !== 'number'); + editor = group.closeEditor(input2); + assert.ok(!editor); assert.equal(group.count, 0); assert.equal(group.getEditors(true).length, 0); assert.equal(group.activeEditor, undefined); @@ -401,12 +420,18 @@ suite('Workbench editor groups', () => { const group = createGroup(); const events = groupListener(group); - const input1 = input(); - const input2 = input(); - const input3 = input(); + const input1 = input('1'); + const input1Copy = input('1'); + const input2 = input('2'); + const input3 = input('3'); // Pinned and Active - group.openEditor(input1, { pinned: true, active: true }); + let openedEditor = group.openEditor(input1, { pinned: true, active: true }); + assert.equal(openedEditor, input1); + + openedEditor = group.openEditor(input1Copy, { pinned: true, active: true }); // opening copy of editor should still return existing one + assert.equal(openedEditor, input1); + group.openEditor(input2, { pinned: true, active: true }); group.openEditor(input3, { pinned: true, active: true }); @@ -427,11 +452,33 @@ suite('Workbench editor groups', () => { assert.equal(events.opened[1], input2); assert.equal(events.opened[2], input3); + assert.equal(events.activated[0], input1); + assert.equal(events.activated[1], input2); + assert.equal(events.activated[2], input3); + const mru = group.getEditors(true); assert.equal(mru[0], input3); assert.equal(mru[1], input2); assert.equal(mru[2], input1); + // Add some tests where a matching input is used + // and verify that events carry the original input + const sameInput1 = input('1'); + group.openEditor(sameInput1, { pinned: true, active: true }); + assert.equal(events.activated[3], input1); + + group.unpin(sameInput1); + assert.equal(events.unpinned[0], input1); + + group.pin(sameInput1); + assert.equal(events.pinned[0], input1); + + group.moveEditor(sameInput1, 1); + assert.equal(events.moved[0], input1); + + group.closeEditor(sameInput1); + assert.equal(events.closed[0].editor, input1); + group.closeAllEditors(); assert.equal(events.closed.length, 3); @@ -930,24 +977,36 @@ suite('Workbench editor groups', () => { const group = createGroup(); // [] -> /index.html/ - let indexHtml = input('index.html'); - group.openEditor(indexHtml); + const indexHtml = input('index.html'); + let openedEditor = group.openEditor(indexHtml); + assert.equal(openedEditor, indexHtml); + assert.equal(group.activeEditor, indexHtml); + assert.equal(group.previewEditor, indexHtml); + assert.equal(group.getEditors()[0], indexHtml); + assert.equal(group.count, 1); + + // /index.html/ -> /index.html/ + const sameIndexHtml = input('index.html'); + openedEditor = group.openEditor(sameIndexHtml); + assert.equal(openedEditor, indexHtml); assert.equal(group.activeEditor, indexHtml); assert.equal(group.previewEditor, indexHtml); assert.equal(group.getEditors()[0], indexHtml); assert.equal(group.count, 1); // /index.html/ -> /style.css/ - let styleCss = input('style.css'); - group.openEditor(styleCss); + const styleCss = input('style.css'); + openedEditor = group.openEditor(styleCss); + assert.equal(openedEditor, styleCss); assert.equal(group.activeEditor, styleCss); assert.equal(group.previewEditor, styleCss); assert.equal(group.getEditors()[0], styleCss); assert.equal(group.count, 1); // /style.css/ -> [/style.css/, test.js] - let testJs = input('test.js'); - group.openEditor(testJs, { active: true, pinned: true }); + const testJs = input('test.js'); + openedEditor = group.openEditor(testJs, { active: true, pinned: true }); + assert.equal(openedEditor, testJs); assert.equal(group.previewEditor, styleCss); assert.equal(group.activeEditor, testJs); assert.equal(group.isPreview(styleCss), true); @@ -957,28 +1016,28 @@ suite('Workbench editor groups', () => { assert.equal(group.count, 2); // [/style.css/, test.js] -> [test.js, /index.html/] - indexHtml = input('index.html'); - group.openEditor(indexHtml, { active: true }); - assert.equal(group.activeEditor, indexHtml); - assert.equal(group.previewEditor, indexHtml); - assert.equal(group.isPreview(indexHtml), true); + const indexHtml2 = input('index.html'); + group.openEditor(indexHtml2, { active: true }); + assert.equal(group.activeEditor, indexHtml2); + assert.equal(group.previewEditor, indexHtml2); + assert.equal(group.isPreview(indexHtml2), true); assert.equal(group.isPinned(testJs), true); assert.equal(group.getEditors()[0], testJs); - assert.equal(group.getEditors()[1], indexHtml); + assert.equal(group.getEditors()[1], indexHtml2); assert.equal(group.count, 2); // make test.js active - testJs = input('test.js'); - group.setActive(testJs); + const testJs2 = input('test.js'); + group.setActive(testJs2); assert.equal(group.activeEditor, testJs); - assert.equal(group.isActive(testJs), true); + assert.equal(group.isActive(testJs2), true); assert.equal(group.count, 2); // [test.js, /indexHtml/] -> [test.js, index.html] - indexHtml = input('index.html'); - group.pin(indexHtml); - assert.equal(group.isPinned(indexHtml), true); - assert.equal(group.isPreview(indexHtml), false); + const indexHtml3 = input('index.html'); + group.pin(indexHtml3); + assert.equal(group.isPinned(indexHtml3), true); + assert.equal(group.isPreview(indexHtml3), false); assert.equal(group.activeEditor, testJs); // [test.js, index.html] -> [test.js, file.ts, index.html] @@ -1006,9 +1065,9 @@ suite('Workbench editor groups', () => { assert.ok(group.getEditors()[2].matches(indexHtml)); // make index.html active - indexHtml = input('index.html'); - group.setActive(indexHtml); - assert.equal(group.activeEditor, indexHtml); + const indexHtml4 = input('index.html'); + group.setActive(indexHtml4); + assert.equal(group.activeEditor, indexHtml2); // [test.js, /other.ts/, index.html] -> [test.js, /other.ts/] group.closeEditor(indexHtml); diff --git a/src/vs/workbench/test/common/editor/editorModel.test.ts b/src/vs/workbench/test/common/editor/editorModel.test.ts index c758eec3e6..b6b5862fdd 100644 --- a/src/vs/workbench/test/common/editor/editorModel.test.ts +++ b/src/vs/workbench/test/common/editor/editorModel.test.ts @@ -16,7 +16,7 @@ import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; import { ITextBufferFactory } from 'vs/editor/common/model'; import { URI } from 'vs/base/common/uri'; import { createTextBufferFactory } from 'vs/editor/common/model/textModel'; -import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; +import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { TestTextResourcePropertiesService } from 'vs/workbench/test/workbenchTestServices'; class MyEditorModel extends EditorModel { } @@ -74,4 +74,4 @@ suite('Workbench editor model', () => { instantiationService.stub(ITextResourcePropertiesService, new TestTextResourcePropertiesService(instantiationService.get(IConfigurationService))); return instantiationService.createInstance(ModelServiceImpl); } -}); \ No newline at end of file +}); diff --git a/src/vs/workbench/test/common/editor/untitledTextEditor.test.ts b/src/vs/workbench/test/common/editor/untitledTextEditor.test.ts index bcee38af37..e4c373b4fc 100644 --- a/src/vs/workbench/test/common/editor/untitledTextEditor.test.ts +++ b/src/vs/workbench/test/common/editor/untitledTextEditor.test.ts @@ -54,11 +54,20 @@ suite('Workbench untitled text editors', () => { assert.equal(service.getAll().length, 0); + let createdResources: URI[] = []; + const createListener = service.onDidCreate(resource => { + createdResources.push(resource); + }); + const input1 = service.createOrGet(); assert.equal(input1, service.createOrGet(input1.getResource())); assert.ok(service.exists(input1.getResource())); assert.ok(!service.exists(URI.file('testing'))); + assert.equal(createdResources.length, 1); + assert.equal(createdResources[0].toString(), input1.getResource()); + + createListener.dispose(); const input2 = service.createOrGet(); diff --git a/src/vs/workbench/test/electron-browser/api/extHostTreeViews.test.ts b/src/vs/workbench/test/electron-browser/api/extHostTreeViews.test.ts index a1355584e3..e9e60ea408 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostTreeViews.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostTreeViews.test.ts @@ -75,7 +75,7 @@ suite('ExtHostTreeView', function () { rpcProtocol, new NullLogService() ), new NullLogService()); - onDidChangeTreeNode = new Emitter<{ key: string }>(); + onDidChangeTreeNode = new Emitter<{ key: string } | undefined>(); onDidChangeTreeNodeWithId = new Emitter<{ key: string }>(); testObject.createTreeView('testNodeTreeProvider', { treeDataProvider: aNodeTreeDataProvider() }, { enableProposedApi: true } as IExtensionDescription); testObject.createTreeView('testNodeWithIdTreeProvider', { treeDataProvider: aNodeWithIdTreeDataProvider() }, { enableProposedApi: true } as IExtensionDescription); diff --git a/src/vs/workbench/test/electron-browser/api/mainThreadConfiguration.test.ts b/src/vs/workbench/test/electron-browser/api/mainThreadConfiguration.test.ts index a0bbe0957d..b4f447dd10 100644 --- a/src/vs/workbench/test/electron-browser/api/mainThreadConfiguration.test.ts +++ b/src/vs/workbench/test/electron-browser/api/mainThreadConfiguration.test.ts @@ -63,7 +63,7 @@ suite('MainThreadConfiguration', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.WORKSPACE }); const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy)); - testObject.$updateConfigurationOption(null, 'extHostConfiguration.resource', 'value', undefined); + testObject.$updateConfigurationOption(null, 'extHostConfiguration.resource', 'value', undefined, undefined); assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]); }); @@ -72,7 +72,7 @@ suite('MainThreadConfiguration', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.FOLDER }); const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy)); - testObject.$updateConfigurationOption(null, 'extHostConfiguration.resource', 'value', URI.file('abc')); + testObject.$updateConfigurationOption(null, 'extHostConfiguration.resource', 'value', { resource: URI.file('abc') }, undefined); assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]); }); @@ -81,7 +81,7 @@ suite('MainThreadConfiguration', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.FOLDER }); const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy)); - testObject.$updateConfigurationOption(null, 'extHostConfiguration.resource', 'value', undefined); + testObject.$updateConfigurationOption(null, 'extHostConfiguration.resource', 'value', undefined, undefined); assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]); }); @@ -90,7 +90,7 @@ suite('MainThreadConfiguration', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.WORKSPACE }); const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy)); - testObject.$updateConfigurationOption(null, 'extHostConfiguration.window', 'value', undefined); + testObject.$updateConfigurationOption(null, 'extHostConfiguration.window', 'value', undefined, undefined); assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]); }); @@ -99,7 +99,7 @@ suite('MainThreadConfiguration', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.WORKSPACE }); const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy)); - testObject.$updateConfigurationOption(null, 'extHostConfiguration.window', 'value', URI.file('abc')); + testObject.$updateConfigurationOption(null, 'extHostConfiguration.window', 'value', { resource: URI.file('abc') }, undefined); assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]); }); @@ -108,7 +108,7 @@ suite('MainThreadConfiguration', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.FOLDER }); const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy)); - testObject.$updateConfigurationOption(null, 'extHostConfiguration.window', 'value', URI.file('abc')); + testObject.$updateConfigurationOption(null, 'extHostConfiguration.window', 'value', { resource: URI.file('abc') }, undefined); assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]); }); @@ -117,7 +117,7 @@ suite('MainThreadConfiguration', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.FOLDER }); const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy)); - testObject.$updateConfigurationOption(null, 'extHostConfiguration.window', 'value', undefined); + testObject.$updateConfigurationOption(null, 'extHostConfiguration.window', 'value', undefined, undefined); assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]); }); @@ -126,7 +126,7 @@ suite('MainThreadConfiguration', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.WORKSPACE }); const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy)); - testObject.$updateConfigurationOption(null, 'extHostConfiguration.resource', 'value', URI.file('abc')); + testObject.$updateConfigurationOption(null, 'extHostConfiguration.resource', 'value', { resource: URI.file('abc') }, undefined); assert.equal(ConfigurationTarget.WORKSPACE_FOLDER, target.args[0][3]); }); @@ -135,7 +135,7 @@ suite('MainThreadConfiguration', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.FOLDER }); const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy)); - testObject.$updateConfigurationOption(ConfigurationTarget.USER, 'extHostConfiguration.window', 'value', URI.file('abc')); + testObject.$updateConfigurationOption(ConfigurationTarget.USER, 'extHostConfiguration.window', 'value', { resource: URI.file('abc') }, undefined); assert.equal(ConfigurationTarget.USER, target.args[0][3]); }); @@ -144,7 +144,7 @@ suite('MainThreadConfiguration', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.FOLDER }); const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy)); - testObject.$updateConfigurationOption(ConfigurationTarget.WORKSPACE, 'extHostConfiguration.window', 'value', URI.file('abc')); + testObject.$updateConfigurationOption(ConfigurationTarget.WORKSPACE, 'extHostConfiguration.window', 'value', { resource: URI.file('abc') }, undefined); assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]); }); @@ -153,7 +153,7 @@ suite('MainThreadConfiguration', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.FOLDER }); const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy)); - testObject.$updateConfigurationOption(ConfigurationTarget.WORKSPACE_FOLDER, 'extHostConfiguration.window', 'value', URI.file('abc')); + testObject.$updateConfigurationOption(ConfigurationTarget.WORKSPACE_FOLDER, 'extHostConfiguration.window', 'value', { resource: URI.file('abc') }, undefined); assert.equal(ConfigurationTarget.WORKSPACE_FOLDER, target.args[0][3]); }); @@ -162,7 +162,7 @@ suite('MainThreadConfiguration', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.WORKSPACE }); const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy)); - testObject.$removeConfigurationOption(null, 'extHostConfiguration.resource', undefined); + testObject.$removeConfigurationOption(null, 'extHostConfiguration.resource', undefined, undefined); assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]); }); @@ -171,7 +171,7 @@ suite('MainThreadConfiguration', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.FOLDER }); const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy)); - testObject.$removeConfigurationOption(null, 'extHostConfiguration.resource', URI.file('abc')); + testObject.$removeConfigurationOption(null, 'extHostConfiguration.resource', { resource: URI.file('abc') }, undefined); assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]); }); @@ -180,7 +180,7 @@ suite('MainThreadConfiguration', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.FOLDER }); const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy)); - testObject.$removeConfigurationOption(null, 'extHostConfiguration.resource', undefined); + testObject.$removeConfigurationOption(null, 'extHostConfiguration.resource', undefined, undefined); assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]); }); @@ -189,7 +189,7 @@ suite('MainThreadConfiguration', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.WORKSPACE }); const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy)); - testObject.$removeConfigurationOption(null, 'extHostConfiguration.window', undefined); + testObject.$removeConfigurationOption(null, 'extHostConfiguration.window', undefined, undefined); assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]); }); @@ -198,7 +198,7 @@ suite('MainThreadConfiguration', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.WORKSPACE }); const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy)); - testObject.$removeConfigurationOption(null, 'extHostConfiguration.window', URI.file('abc')); + testObject.$removeConfigurationOption(null, 'extHostConfiguration.window', { resource: URI.file('abc') }, undefined); assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]); }); @@ -207,7 +207,7 @@ suite('MainThreadConfiguration', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.FOLDER }); const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy)); - testObject.$removeConfigurationOption(null, 'extHostConfiguration.window', URI.file('abc')); + testObject.$removeConfigurationOption(null, 'extHostConfiguration.window', { resource: URI.file('abc') }, undefined); assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]); }); @@ -216,7 +216,7 @@ suite('MainThreadConfiguration', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.FOLDER }); const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy)); - testObject.$removeConfigurationOption(null, 'extHostConfiguration.window', undefined); + testObject.$removeConfigurationOption(null, 'extHostConfiguration.window', undefined, undefined); assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]); }); @@ -225,7 +225,7 @@ suite('MainThreadConfiguration', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.WORKSPACE }); const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy)); - testObject.$removeConfigurationOption(null, 'extHostConfiguration.resource', URI.file('abc')); + testObject.$removeConfigurationOption(null, 'extHostConfiguration.resource', { resource: URI.file('abc') }, undefined); assert.equal(ConfigurationTarget.WORKSPACE_FOLDER, target.args[0][3]); }); 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 aa40f4a46d..5c50a8a9b7 100644 --- a/src/vs/workbench/test/electron-browser/api/mainThreadEditors.test.ts +++ b/src/vs/workbench/test/electron-browser/api/mainThreadEditors.test.ts @@ -28,6 +28,7 @@ import { IReference, ImmortalReference } from 'vs/base/common/lifecycle'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { LabelService } from 'vs/workbench/services/label/common/labelService'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; +import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; suite('MainThreadEditors', () => { @@ -89,7 +90,11 @@ suite('MainThreadEditors', () => { } }; - const bulkEditService = new BulkEditService(new NullLogService(), modelService, new TestEditorService(), textModelService, new TestFileService(), textFileService, new LabelService(TestEnvironmentService, new TestContextService()), configService); + const editorWorkerService = new class extends mock() { + + }; + + const bulkEditService = new BulkEditService(new NullLogService(), modelService, new TestEditorService(), editorWorkerService, textModelService, new TestFileService(), textFileService, new LabelService(TestEnvironmentService, new TestContextService()), configService); const rpcProtocol = new TestRPCProtocol(); rpcProtocol.set(ExtHostContext.ExtHostDocuments, new class extends mock() { 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 f586dca4e1..44c1a412e1 100644 --- a/src/vs/workbench/test/electron-browser/quickopen.perf.integrationTest.ts +++ b/src/vs/workbench/test/electron-browser/quickopen.perf.integrationTest.ts @@ -10,7 +10,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { URI } from 'vs/base/common/uri'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; -import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; +import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; 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 b91aeb279c..27e07788e0 100644 --- a/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts +++ b/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts @@ -32,7 +32,7 @@ import { QueryBuilder, ITextQueryBuilderOptions } from 'vs/workbench/contrib/sea import { Event, Emitter } from 'vs/base/common/event'; import { testWorkspace } from 'vs/platform/workspace/test/common/testWorkspace'; import { NullLogService, ILogService } from 'vs/platform/log/common/log'; -import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; +import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { ClassifiedEvent, StrictPropertyCheck, GDPRClassification } from 'vs/platform/telemetry/common/gdprTypings'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index 102f56565c..6835a2fa44 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -11,8 +11,8 @@ import * as resources from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; -import { IEditorInputWithOptions, CloseDirection, IEditorIdentifier, IUntitledTextResourceInput, IResourceDiffInput, IResourceSideBySideInput, IEditorInput, IEditor, IEditorCloseEvent, IEditorPartOptions, IRevertOptions } from 'vs/workbench/common/editor'; -import { IEditorOpeningEvent, EditorServiceImpl, IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor'; +import { IEditorInputWithOptions, CloseDirection, IEditorIdentifier, IUntitledTextResourceInput, IResourceDiffInput, IResourceSideBySideInput, IEditorInput, IEditor, IEditorCloseEvent, IEditorPartOptions, IRevertOptions, GroupIdentifier } from 'vs/workbench/common/editor'; +import { IEditorOpeningEvent, EditorServiceImpl, IEditorGroupView, IEditorGroupsAccessor } from 'vs/workbench/browser/parts/editor/editor'; 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'; @@ -33,7 +33,7 @@ import { ITextFileStreamContent, ITextFileService, IResourceEncoding, IReadTextF import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; -import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, ServicesAccessor, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { MenuBarVisibility, IWindowConfiguration, IWindowOpenable, IOpenWindowOptions, IOpenEmptyWindowOptions, IOpenedWindow } from 'vs/platform/windows/common/windows'; import { TestWorkspace } from 'vs/platform/workspace/test/common/testWorkspace'; @@ -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 { IResourceConfigurationService, ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; +import { ITextResourceConfigurationService, ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; 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'; @@ -69,7 +69,7 @@ import { timeout } from 'vs/base/common/async'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { ViewletDescriptor, Viewlet } from 'vs/workbench/browser/viewlet'; import { IViewlet } from 'vs/workbench/common/viewlet'; -import { IStorageService, InMemoryStorageService } from 'vs/platform/storage/common/storage'; +import { IStorageService, InMemoryStorageService, IWillSaveStateEvent } from 'vs/platform/storage/common/storage'; import { isLinux, isMacintosh } from 'vs/base/common/platform'; import { LabelService } from 'vs/workbench/services/label/common/labelService'; import { IDimension } from 'vs/platform/layout/browser/layoutService'; @@ -94,6 +94,7 @@ import { IDialogMainService } from 'vs/platform/dialogs/electron-main/dialogs'; import { find } from 'vs/base/common/arrays'; import { WorkingCopyService, IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { IFilesConfigurationService, FilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; +import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; export function createFileInput(instantiationService: IInstantiationService, resource: URI): FileEditorInput { return instantiationService.createInstance(FileEditorInput, resource, undefined, undefined); @@ -102,7 +103,7 @@ export function createFileInput(instantiationService: IInstantiationService, res export const TestEnvironmentService = new NativeWorkbenchEnvironmentService(parseArgs(process.argv, OPTIONS) as IWindowConfiguration, process.execPath, 0); export class TestContextService implements IWorkspaceContextService { - public _serviceBrand: undefined; + _serviceBrand: undefined; private workspace: Workspace; private options: any; @@ -119,23 +120,23 @@ export class TestContextService implements IWorkspaceContextService { this._onDidChangeWorkbenchState = new Emitter(); } - public get onDidChangeWorkspaceName(): Event { + get onDidChangeWorkspaceName(): Event { return this._onDidChangeWorkspaceName.event; } - public get onDidChangeWorkspaceFolders(): Event { + get onDidChangeWorkspaceFolders(): Event { return this._onDidChangeWorkspaceFolders.event; } - public get onDidChangeWorkbenchState(): Event { + get onDidChangeWorkbenchState(): Event { return this._onDidChangeWorkbenchState.event; } - public getFolders(): IWorkspaceFolder[] { + getFolders(): IWorkspaceFolder[] { return this.workspace ? this.workspace.folders : []; } - public getWorkbenchState(): WorkbenchState { + getWorkbenchState(): WorkbenchState { if (this.workspace.configuration) { return WorkbenchState.WORKSPACE; } @@ -151,27 +152,27 @@ export class TestContextService implements IWorkspaceContextService { return Promise.resolve(this.getWorkspace()); } - public getWorkspace(): IWorkbenchWorkspace { + getWorkspace(): IWorkbenchWorkspace { return this.workspace; } - public getWorkspaceFolder(resource: URI): IWorkspaceFolder | null { + getWorkspaceFolder(resource: URI): IWorkspaceFolder | null { return this.workspace.getFolder(resource); } - public setWorkspace(workspace: any): void { + setWorkspace(workspace: any): void { this.workspace = workspace; } - public getOptions() { + getOptions() { return this.options; } - public updateOptions() { + updateOptions() { } - public isInsideWorkspace(resource: URI): boolean { + isInsideWorkspace(resource: URI): boolean { if (resource && this.workspace) { return resources.isEqualOrParent(resource, this.workspace.folders[0].uri); } @@ -179,17 +180,17 @@ export class TestContextService implements IWorkspaceContextService { return false; } - public toResource(workspaceRelativePath: string): URI { + toResource(workspaceRelativePath: string): URI { return URI.file(join('C:\\', workspaceRelativePath)); } - public isCurrentWorkspace(workspaceIdentifier: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): boolean { + isCurrentWorkspace(workspaceIdentifier: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): boolean { return isSingleFolderWorkspaceIdentifier(workspaceIdentifier) && resources.isEqual(this.workspace.folders[0].uri, workspaceIdentifier); } } export class TestTextFileService extends NativeTextFileService { - public cleanupBackupsBeforeShutdownCalled!: boolean; + cleanupBackupsBeforeShutdownCalled!: boolean; private promptPath!: URI; private resolveTextContentError!: FileOperationError | null; @@ -209,7 +210,7 @@ export class TestTextFileService extends NativeTextFileService { @IDialogService dialogService: IDialogService, @IFileDialogService fileDialogService: IFileDialogService, @IEditorService editorService: IEditorService, - @IResourceConfigurationService textResourceConfigurationService: IResourceConfigurationService, + @ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService, @IElectronService electronService: IElectronService, @IProductService productService: IProductService, @IFilesConfigurationService filesConfigurationService: IFilesConfigurationService @@ -236,15 +237,15 @@ export class TestTextFileService extends NativeTextFileService { ); } - public setPromptPath(path: URI): void { + setPromptPath(path: URI): void { this.promptPath = path; } - public setResolveTextContentErrorOnce(error: FileOperationError): void { + setResolveTextContentErrorOnce(error: FileOperationError): void { this.resolveTextContentError = error; } - public readStream(resource: URI, options?: IReadTextFileOptions): Promise { + readStream(resource: URI, options?: IReadTextFileOptions): Promise { if (this.resolveTextContentError) { const error = this.resolveTextContentError; this.resolveTextContentError = null; @@ -266,7 +267,7 @@ export class TestTextFileService extends NativeTextFileService { }); } - public promptForPath(_resource: URI, _defaultPath: URI): Promise { + promptForPath(_resource: URI, _defaultPath: URI): Promise { return Promise.resolve(this.promptPath); } @@ -276,7 +277,11 @@ export class TestTextFileService extends NativeTextFileService { } } -export function workbenchInstantiationService(): IInstantiationService { +export interface ITestInstantiationService extends IInstantiationService { + stub(service: ServiceIdentifier, ctor: any): T; +} + +export function workbenchInstantiationService(): ITestInstantiationService { let instantiationService = new TestInstantiationService(new ServiceCollection([ILifecycleService, new TestLifecycleService()])); instantiationService.stub(IEnvironmentService, TestEnvironmentService); const contextKeyService = instantiationService.createInstance(MockContextKeyService); @@ -286,11 +291,12 @@ export function workbenchInstantiationService(): IInstantiationService { const configService = new TestConfigurationService(); instantiationService.stub(IConfigurationService, configService); instantiationService.stub(IFilesConfigurationService, new TestFilesConfigurationService(contextKeyService, configService, TestEnvironmentService)); - instantiationService.stub(IResourceConfigurationService, new TestTextResourceConfigurationService(configService)); + instantiationService.stub(ITextResourceConfigurationService, new TestTextResourceConfigurationService(configService)); instantiationService.stub(IUntitledTextEditorService, instantiationService.createInstance(UntitledTextEditorService)); instantiationService.stub(IStorageService, new TestStorageService()); instantiationService.stub(IWorkbenchLayoutService, new TestLayoutService()); instantiationService.stub(IDialogService, new TestDialogService()); + instantiationService.stub(IAccessibilityService, new TestAccessibilityService()); instantiationService.stub(IFileDialogService, new TestFileDialogService()); instantiationService.stub(IElectronService, new TestElectronService()); instantiationService.stub(IModeService, instantiationService.createInstance(ModeServiceImpl)); @@ -311,7 +317,7 @@ export function workbenchInstantiationService(): IInstantiationService { instantiationService.stub(ITextModelService, instantiationService.createInstance(TextModelResolverService)); instantiationService.stub(IThemeService, new TestThemeService()); instantiationService.stub(ILogService, new NullLogService()); - instantiationService.stub(IEditorGroupsService, new TestEditorGroupsService([new TestEditorGroup(0)])); + instantiationService.stub(IEditorGroupsService, new TestEditorGroupsService([new TestEditorGroupView(0)])); instantiationService.stub(ILabelService, instantiationService.createInstance(LabelService)); const editorService = new TestEditorService(); instantiationService.stub(IEditorService, editorService); @@ -322,6 +328,17 @@ export function workbenchInstantiationService(): IInstantiationService { return instantiationService; } +export class TestAccessibilityService implements IAccessibilityService { + + _serviceBrand: undefined; + + onDidChangeAccessibilitySupport = Event.None; + + alwaysUnderlineAccessKeys(): Promise { return Promise.resolve(false); } + getAccessibilitySupport(): AccessibilitySupport { return AccessibilitySupport.Unknown; } + setAccessibilitySupport(accessibilitySupport: AccessibilitySupport): void { } +} + export class TestDecorationsService implements IDecorationsService { _serviceBrand: undefined; onDidChangeDecorations: Event = Event.None; @@ -333,7 +350,7 @@ export class TestExtensionService extends NullExtensionService { } export class TestMenuService implements IMenuService { - public _serviceBrand: undefined; + _serviceBrand: undefined; createMenu(_id: MenuId, _scopedKeybindingService: IContextKeyService): IMenu { return { @@ -346,112 +363,90 @@ export class TestMenuService implements IMenuService { export class TestHistoryService implements IHistoryService { - public _serviceBrand: undefined; + _serviceBrand: undefined; - constructor(private root?: URI) { - } + constructor(private root?: URI) { } - public reopenLastClosedEditor(): void { - } - - public forward(_acrossEditors?: boolean): void { - } - - public back(_acrossEditors?: boolean): void { - } - - public last(): void { - } - - public remove(_input: IEditorInput | IResourceInput): void { - } - - public clear(): void { - } - - public clearRecentlyOpened(): void { - } - - public getHistory(): Array { - return []; - } - - public getLastActiveWorkspaceRoot(_schemeFilter: string): URI | undefined { - return this.root; - } - - public getLastActiveFile(_schemeFilter: string): URI | undefined { - return undefined; - } - - public openLastEditLocation(): void { - } + reopenLastClosedEditor(): void { } + forward(): void { } + back(): void { } + last(): void { } + remove(_input: IEditorInput | IResourceInput): void { } + clear(): void { } + clearRecentlyOpened(): void { } + getHistory(): Array { return []; } + openNextRecentlyUsedEditor(group?: GroupIdentifier): void { } + openPreviouslyUsedEditor(group?: GroupIdentifier): void { } + getMostRecentlyUsedOpenEditors(): Array { return []; } + getLastActiveWorkspaceRoot(_schemeFilter: string): URI | undefined { return this.root; } + getLastActiveFile(_schemeFilter: string): URI | undefined { return undefined; } + openLastEditLocation(): void { } } export class TestDialogService implements IDialogService { - public _serviceBrand: undefined; + _serviceBrand: undefined; - public confirm(_confirmation: IConfirmation): Promise { + confirm(_confirmation: IConfirmation): Promise { return Promise.resolve({ confirmed: false }); } - public show(_severity: Severity, _message: string, _buttons: string[], _options?: IDialogOptions): Promise { + show(_severity: Severity, _message: string, _buttons: string[], _options?: IDialogOptions): Promise { return Promise.resolve({ choice: 0 }); } - public about(): Promise { + about(): Promise { return Promise.resolve(); } } export class TestFileDialogService implements IFileDialogService { - public _serviceBrand: undefined; + _serviceBrand: undefined; private confirmResult!: ConfirmResult; - public defaultFilePath(_schemeFilter?: string): URI | undefined { + defaultFilePath(_schemeFilter?: string): URI | undefined { return undefined; } - public defaultFolderPath(_schemeFilter?: string): URI | undefined { + defaultFolderPath(_schemeFilter?: string): URI | undefined { return undefined; } - public defaultWorkspacePath(_schemeFilter?: string): URI | undefined { + defaultWorkspacePath(_schemeFilter?: string): URI | undefined { return undefined; } - public pickFileFolderAndOpen(_options: IPickAndOpenOptions): Promise { + pickFileFolderAndOpen(_options: IPickAndOpenOptions): Promise { return Promise.resolve(0); } - public pickFileAndOpen(_options: IPickAndOpenOptions): Promise { + pickFileAndOpen(_options: IPickAndOpenOptions): Promise { return Promise.resolve(0); } - public pickFolderAndOpen(_options: IPickAndOpenOptions): Promise { + pickFolderAndOpen(_options: IPickAndOpenOptions): Promise { return Promise.resolve(0); } - public pickWorkspaceAndOpen(_options: IPickAndOpenOptions): Promise { + pickWorkspaceAndOpen(_options: IPickAndOpenOptions): Promise { return Promise.resolve(0); } - public pickFileToSave(_options: ISaveDialogOptions): Promise { + pickFileToSave(_options: ISaveDialogOptions): Promise { return Promise.resolve(undefined); } - public showSaveDialog(_options: ISaveDialogOptions): Promise { + showSaveDialog(_options: ISaveDialogOptions): Promise { return Promise.resolve(undefined); } - public showOpenDialog(_options: IOpenDialogOptions): Promise { + showOpenDialog(_options: IOpenDialogOptions): Promise { return Promise.resolve(undefined); } - public setConfirmResult(result: ConfirmResult): void { + setConfirmResult(result: ConfirmResult): void { this.confirmResult = result; } - public showSaveConfirm(fileNamesOrResources: (string | URI)[]): Promise { + showSaveConfirm(fileNamesOrResources: (string | URI)[]): Promise { return Promise.resolve(this.confirmResult); } } export class TestLayoutService implements IWorkbenchLayoutService { - public _serviceBrand: undefined; + _serviceBrand: undefined; dimension: IDimension = { width: 800, height: 600 }; @@ -467,27 +462,27 @@ export class TestLayoutService implements IWorkbenchLayoutService { private readonly _onMenubarVisibilityChange = new Emitter(); - public get onMenubarVisibilityChange(): Event { + get onMenubarVisibilityChange(): Event { return this._onMenubarVisibilityChange.event; } - public isRestored(): boolean { + isRestored(): boolean { return true; } - public hasFocus(_part: Parts): boolean { + hasFocus(_part: Parts): boolean { return false; } - public hasWindowBorder(): boolean { + hasWindowBorder(): boolean { return false; } - public getWindowBorderRadius(): string | undefined { + getWindowBorderRadius(): string | undefined { return undefined; } - public isVisible(_part: Parts): boolean { + isVisible(_part: Parts): boolean { return true; } @@ -495,93 +490,93 @@ export class TestLayoutService implements IWorkbenchLayoutService { return new Dimension(0, 0); } - public getContainer(_part: Parts): HTMLElement { + getContainer(_part: Parts): HTMLElement { return null!; } - public isTitleBarHidden(): boolean { + isTitleBarHidden(): boolean { return false; } - public getTitleBarOffset(): number { + getTitleBarOffset(): number { return 0; } - public isStatusBarHidden(): boolean { + isStatusBarHidden(): boolean { return false; } - public isActivityBarHidden(): boolean { + isActivityBarHidden(): boolean { return false; } - public setActivityBarHidden(_hidden: boolean): void { } + setActivityBarHidden(_hidden: boolean): void { } - public isSideBarHidden(): boolean { + isSideBarHidden(): boolean { return false; } - public setEditorHidden(_hidden: boolean): Promise { return Promise.resolve(); } + setEditorHidden(_hidden: boolean): Promise { return Promise.resolve(); } - public setSideBarHidden(_hidden: boolean): Promise { return Promise.resolve(); } + setSideBarHidden(_hidden: boolean): Promise { return Promise.resolve(); } - public isPanelHidden(): boolean { + isPanelHidden(): boolean { return false; } - public setPanelHidden(_hidden: boolean): Promise { return Promise.resolve(); } + setPanelHidden(_hidden: boolean): Promise { return Promise.resolve(); } - public toggleMaximizedPanel(): void { } + toggleMaximizedPanel(): void { } - public isPanelMaximized(): boolean { + isPanelMaximized(): boolean { return false; } - public getMenubarVisibility(): MenuBarVisibility { + getMenubarVisibility(): MenuBarVisibility { throw new Error('not implemented'); } - public getSideBarPosition() { + getSideBarPosition() { return 0; } - public getPanelPosition() { + getPanelPosition() { return 0; } - public setPanelPosition(_position: PartPosition): Promise { + setPanelPosition(_position: PartPosition): Promise { return Promise.resolve(); } - public addClass(_clazz: string): void { } - public removeClass(_clazz: string): void { } + addClass(_clazz: string): void { } + removeClass(_clazz: string): void { } - public getMaximumEditorDimensions(): Dimension { throw new Error('not implemented'); } + getMaximumEditorDimensions(): Dimension { throw new Error('not implemented'); } - public getWorkbenchContainer(): HTMLElement { throw new Error('not implemented'); } - public getWorkbenchElement(): HTMLElement { throw new Error('not implemented'); } + getWorkbenchContainer(): HTMLElement { throw new Error('not implemented'); } + getWorkbenchElement(): HTMLElement { throw new Error('not implemented'); } - public toggleZenMode(): void { } + toggleZenMode(): void { } - public isEditorLayoutCentered(): boolean { return false; } - public centerEditorLayout(_active: boolean): void { } + isEditorLayoutCentered(): boolean { return false; } + centerEditorLayout(_active: boolean): void { } - public resizePart(_part: Parts, _sizeChange: number): void { } + resizePart(_part: Parts, _sizeChange: number): void { } - public registerPart(part: Part): void { } + registerPart(part: Part): void { } isWindowMaximized() { return false; } - public updateWindowMaximizedState(maximized: boolean): void { } + updateWindowMaximizedState(maximized: boolean): void { } } let activeViewlet: Viewlet = {} as any; export class TestViewletService implements IViewletService { - public _serviceBrand: undefined; + _serviceBrand: undefined; onDidViewletRegisterEmitter = new Emitter(); onDidViewletDeregisterEmitter = new Emitter(); @@ -593,97 +588,100 @@ export class TestViewletService implements IViewletService { onDidViewletOpen = this.onDidViewletOpenEmitter.event; onDidViewletClose = this.onDidViewletCloseEmitter.event; - public openViewlet(id: string, focus?: boolean): Promise { + openViewlet(id: string, focus?: boolean): Promise { return Promise.resolve(undefined); } - public getViewlets(): ViewletDescriptor[] { + getViewlets(): ViewletDescriptor[] { return []; } - public getAllViewlets(): ViewletDescriptor[] { + getAllViewlets(): ViewletDescriptor[] { return []; } - public getActiveViewlet(): IViewlet { + getActiveViewlet(): IViewlet { return activeViewlet; } - public dispose() { + dispose() { } - public getDefaultViewletId(): string { + getDefaultViewletId(): string { return 'workbench.view.explorer'; } - public getViewlet(id: string): ViewletDescriptor | undefined { + getViewlet(id: string): ViewletDescriptor | undefined { return undefined; } - public getProgressIndicator(id: string) { + getProgressIndicator(id: string) { return undefined; } - public hideActiveViewlet(): void { } + hideActiveViewlet(): void { } - public getLastActiveViewletId(): string { + getLastActiveViewletId(): string { return undefined!; } } export class TestPanelService implements IPanelService { - public _serviceBrand: undefined; + _serviceBrand: undefined; onDidPanelOpen = new Emitter<{ panel: IPanel, focus: boolean }>().event; onDidPanelClose = new Emitter().event; - public openPanel(id: string, focus?: boolean): undefined { + openPanel(id: string, focus?: boolean): undefined { return undefined; } - public getPanel(id: string): any { + getPanel(id: string): any { return activeViewlet; } - public getPanels() { + getPanels() { return []; } - public getPinnedPanels() { + getPinnedPanels() { return []; } - public getActivePanel(): IViewlet { + getActivePanel(): IViewlet { return activeViewlet; } - public setPanelEnablement(id: string, enabled: boolean): void { } + setPanelEnablement(id: string, enabled: boolean): void { } - public dispose() { + dispose() { } - public showActivity(panelId: string, badge: IBadge, clazz?: string): IDisposable { + showActivity(panelId: string, badge: IBadge, clazz?: string): IDisposable { throw new Error('Method not implemented.'); } - public getProgressIndicator(id: string) { + getProgressIndicator(id: string) { return null!; } - public hideActivePanel(): void { } + hideActivePanel(): void { } - public getLastActivePanelId(): string { + getLastActivePanelId(): string { return undefined!; } } -export class TestStorageService extends InMemoryStorageService { } +export class TestStorageService extends InMemoryStorageService { + readonly _onWillSaveState = this._register(new Emitter()); + readonly onWillSaveState = this._onWillSaveState.event; +} export class TestEditorGroupsService implements IEditorGroupsService { _serviceBrand: undefined; - constructor(public groups: TestEditorGroup[] = []) { } + constructor(public groups: TestEditorGroupView[] = []) { } onDidActiveGroupChange: Event = Event.None; onDidActivateGroup: Event = Event.None; @@ -773,7 +771,7 @@ export class TestEditorGroupsService implements IEditorGroupsService { } } -export class TestEditorGroup implements IEditorGroupView { +export class TestEditorGroupView implements IEditorGroupView { constructor(public id: number) { } @@ -825,7 +823,7 @@ export class TestEditorGroup implements IEditorGroupView { throw new Error('not implemented'); } - isOpened(_editor: IEditorInput): boolean { + isOpened(_editor: IEditorInput | IResourceInput): boolean { return false; } @@ -873,6 +871,28 @@ export class TestEditorGroup implements IEditorGroupView { relayout() { } } +export class TestEditorGroupAccessor implements IEditorGroupsAccessor { + + groups: IEditorGroupView[] = []; + activeGroup!: IEditorGroupView; + + partOptions: IEditorPartOptions = {}; + + onDidEditorPartOptionsChange = Event.None; + onDidVisibilityChange = Event.None; + + getGroup(identifier: number): IEditorGroupView | undefined { throw new Error('Method not implemented.'); } + getGroups(order: GroupsOrder): IEditorGroupView[] { throw new Error('Method not implemented.'); } + activateGroup(identifier: number | IEditorGroupView): IEditorGroupView { throw new Error('Method not implemented.'); } + restoreGroup(identifier: number | IEditorGroupView): IEditorGroupView { throw new Error('Method not implemented.'); } + addGroup(location: number | IEditorGroupView, direction: GroupDirection, options?: IAddGroupOptions | undefined): IEditorGroupView { throw new Error('Method not implemented.'); } + mergeGroup(group: number | IEditorGroupView, target: number | IEditorGroupView, options?: IMergeGroupOptions | undefined): IEditorGroupView { throw new Error('Method not implemented.'); } + moveGroup(group: number | IEditorGroupView, location: number | IEditorGroupView, direction: GroupDirection): IEditorGroupView { throw new Error('Method not implemented.'); } + copyGroup(group: number | IEditorGroupView, location: number | IEditorGroupView, direction: GroupDirection): IEditorGroupView { throw new Error('Method not implemented.'); } + removeGroup(group: number | IEditorGroupView): void { throw new Error('Method not implemented.'); } + arrangeGroups(arrangement: GroupsArrangement, target?: number | IEditorGroupView | undefined): void { throw new Error('Method not implemented.'); } +} + export class TestEditorService implements EditorServiceImpl { _serviceBrand: undefined; @@ -941,7 +961,7 @@ export class TestEditorService implements EditorServiceImpl { export class TestFileService implements IFileService { - public _serviceBrand: undefined; + _serviceBrand: undefined; private readonly _onFileChanges: Emitter; private readonly _onAfterOperation: Emitter; @@ -957,31 +977,31 @@ export class TestFileService implements IFileService { this._onAfterOperation = new Emitter(); } - public setContent(content: string): void { + setContent(content: string): void { this.content = content; } - public getContent(): string { + getContent(): string { return this.content; } - public getLastReadFileUri(): URI { + getLastReadFileUri(): URI { return this.lastReadFileUri; } - public get onFileChanges(): Event { + get onFileChanges(): Event { return this._onFileChanges.event; } - public fireFileChanges(event: FileChangesEvent): void { + fireFileChanges(event: FileChangesEvent): void { this._onFileChanges.fire(event); } - public get onAfterOperation(): Event { + get onAfterOperation(): Event { return this._onAfterOperation.event; } - public fireAfterOperation(event: FileOperationEvent): void { + fireAfterOperation(event: FileOperationEvent): void { this._onAfterOperation.fire(event); } @@ -1119,21 +1139,21 @@ export class TestFileService implements IFileService { } export class TestBackupFileService implements IBackupFileService { - public _serviceBrand: undefined; + _serviceBrand: undefined; - public hasBackups(): Promise { + hasBackups(): Promise { return Promise.resolve(false); } - public hasBackup(_resource: URI): Promise { + hasBackup(_resource: URI): Promise { return Promise.resolve(false); } - public hasBackupSync(resource: URI, versionId?: number): boolean { + hasBackupSync(resource: URI, versionId?: number): boolean { return false; } - public loadBackupResource(resource: URI): Promise { + loadBackupResource(resource: URI): Promise { return this.hasBackup(resource).then(hasBackup => { if (hasBackup) { return this.toBackupResource(resource); @@ -1143,42 +1163,42 @@ export class TestBackupFileService implements IBackupFileService { }); } - public registerResourceForBackup(_resource: URI): Promise { + registerResourceForBackup(_resource: URI): Promise { return Promise.resolve(); } - public deregisterResourceForBackup(_resource: URI): Promise { + deregisterResourceForBackup(_resource: URI): Promise { return Promise.resolve(); } - public toBackupResource(_resource: URI): URI { + toBackupResource(_resource: URI): URI { throw new Error('not implemented'); } - public backupResource(_resource: URI, _content: ITextSnapshot, versionId?: number, meta?: T): Promise { + backupResource(_resource: URI, _content: ITextSnapshot, versionId?: number, meta?: T): Promise { return Promise.resolve(); } - public getWorkspaceFileBackups(): Promise { + getWorkspaceFileBackups(): Promise { return Promise.resolve([]); } - public parseBackupContent(textBufferFactory: ITextBufferFactory): string { + parseBackupContent(textBufferFactory: ITextBufferFactory): string { const textBuffer = textBufferFactory.create(DefaultEndOfLine.LF); const lineCount = textBuffer.getLineCount(); const range = new Range(1, 1, lineCount, textBuffer.getLineLength(lineCount) + 1); return textBuffer.getValueInRange(range, EndOfLinePreference.TextDefined); } - public resolveBackupContent(_backup: URI): Promise> { + resolveBackupContent(_backup: URI): Promise> { throw new Error('not implemented'); } - public discardResourceBackup(_resource: URI): Promise { + discardResourceBackup(_resource: URI): Promise { return Promise.resolve(); } - public discardAllWorkspaceBackups(): Promise { + discardAllWorkspaceBackups(): Promise { return Promise.resolve(); } } @@ -1210,10 +1230,10 @@ export class TestCodeEditorService implements ICodeEditorService { export class TestLifecycleService implements ILifecycleService { - public _serviceBrand: undefined; + _serviceBrand: undefined; - public phase!: LifecyclePhase; - public startupKind!: StartupKind; + phase!: LifecyclePhase; + startupKind!: StartupKind; private readonly _onBeforeShutdown = new Emitter(); private readonly _onWillShutdown = new Emitter(); @@ -1223,38 +1243,38 @@ export class TestLifecycleService implements ILifecycleService { return Promise.resolve(); } - public fireShutdown(reason = ShutdownReason.QUIT): void { + fireShutdown(reason = ShutdownReason.QUIT): void { this._onWillShutdown.fire({ join: () => { }, reason }); } - public fireWillShutdown(event: BeforeShutdownEvent): void { + fireWillShutdown(event: BeforeShutdownEvent): void { this._onBeforeShutdown.fire(event); } - public get onBeforeShutdown(): Event { + get onBeforeShutdown(): Event { return this._onBeforeShutdown.event; } - public get onWillShutdown(): Event { + get onWillShutdown(): Event { return this._onWillShutdown.event; } - public get onShutdown(): Event { + get onShutdown(): Event { return this._onShutdown.event; } } -export class TestTextResourceConfigurationService implements IResourceConfigurationService { +export class TestTextResourceConfigurationService implements ITextResourceConfigurationService { _serviceBrand: undefined; constructor(private configurationService = new TestConfigurationService()) { } - public onDidChangeConfiguration() { + onDidChangeConfiguration() { return { dispose() { } }; } diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index 5fe0821717..36b9ffb29a 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -102,8 +102,8 @@ 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 { IResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; -import { TextResourceConfigurationService } from 'vs/editor/common/services/resourceConfigurationImpl'; +import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; +import { TextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationServiceImpl'; import { IMenuService } from 'vs/platform/actions/common/actions'; import { MenuService } from 'vs/platform/actions/common/menuService'; import { IDownloadService } from 'vs/platform/download/common/download'; @@ -119,7 +119,7 @@ registerSingleton(IMarkerDecorationsService, MarkerDecorationsService); registerSingleton(IMarkerService, MarkerService, true); registerSingleton(IContextKeyService, ContextKeyService); registerSingleton(IModelService, ModelServiceImpl, true); -registerSingleton(IResourceConfigurationService, TextResourceConfigurationService); +registerSingleton(ITextResourceConfigurationService, TextResourceConfigurationService); registerSingleton(IMenuService, MenuService, true); registerSingleton(IDownloadService, DownloadService, true); registerSingleton(IOpenerService, OpenerService, true); diff --git a/yarn.lock b/yarn.lock index e08a619eb4..1274034ef7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -214,16 +214,26 @@ resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-2.2.39.tgz#f68d63db8b69c38e9558b4073525cf96c4f7a829" integrity sha1-9o1j24tpw46VWLQHNSXPlsT3qCk= -"@types/node@*", "@types/node@^10.11.7", "@types/node@^10.12.12": - version "10.12.12" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.12.tgz#e15a9d034d9210f00320ef718a50c4a799417c47" - integrity sha512-Pr+6JRiKkfsFvmU/LK68oBRCQeEg36TyAbPhc2xpez24OOZZCuoIhWGTd39VZy6nGafSbxzGouFPTFD/rR1A0A== +"@types/node@*": + version "4.2.22" + resolved "https://registry.yarnpkg.com/@types/node/-/node-4.2.22.tgz#cf488a0f6b4a9c245d09927f4f757ca278b9c8ce" + integrity sha512-LXRap3bb4AjtLZ5NOFc4ssVZrQPTgdPcNm++0SEJuJZaOA+xHkojJNYqy33A5q/94BmG5tA6yaMeD4VdCv5aSA== + +"@types/node@^10.11.7": + version "10.12.21" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.21.tgz#7e8a0c34cf29f4e17a36e9bd0ea72d45ba03908e" + integrity sha512-CBgLNk4o3XMnqMc0rhb6lc77IwShMEglz05deDcn2lQxyXEZivfwgYJu7SMha9V5XcrP6qZuevTHV/QrN2vjKQ== "@types/node@^10.12.18": version "10.17.9" resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.9.tgz#4f251a1ed77ac7ef09d456247d67fc8173f6b9da" integrity sha512-+6VygF9LbG7Gaqeog2G7u1+RUcmo0q1rI+2ZxdIg2fAUngk5Vz9fOCHXdloNUOHEPd1EuuOpL5O0CdgN9Fx5UQ== +"@types/node@^12.11.7": + version "12.12.14" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.14.tgz#1c1d6e3c75dba466e0326948d56e8bd72a1903d2" + integrity sha512-u/SJDyXwuihpwjXy7hOOghagLEV1KdAST6syfnOk6QZAMzZuWZqXy5aYYZbh8Jdpd4escVFP0MvftHNDb9pruA== + "@types/plotly.js@^1.44.9": version "1.44.9" resolved "https://registry.yarnpkg.com/@types/plotly.js/-/plotly.js-1.44.9.tgz#d20bd229b409f83b5e9bc06df0c948a27b8fbc0d" @@ -275,6 +285,11 @@ resolved "https://registry.yarnpkg.com/@types/windows-foreground-love/-/windows-foreground-love-0.3.0.tgz#26bc230b2568aa7ab7c56d35bb5653c0a6965a42" integrity sha512-tFUVA/fiofNqOh6lZlymvQiQYPY+cZXZPR9mn9wN6/KS8uwx0zgH4Ij/jmFyRYr+x+DGZWEIeknS2BMi7FZJAQ== +"@types/windows-mutex@^0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@types/windows-mutex/-/windows-mutex-0.4.0.tgz#d27070418aa26047c6c860c704952ff26aeb961b" + integrity sha512-zUMH4nutIURLARU6f10Ls6XcZWhUkwmzVEALs26dt1ZbaLv/qxsGdYiePUbwhQmrQUp3EPZ1HxopWpzzC8ot5A== + "@types/windows-process-tree@^0.2.0": version "0.2.0" resolved "https://registry.yarnpkg.com/@types/windows-process-tree/-/windows-process-tree-0.2.0.tgz#2fa205c838a8ef0a07697cd747c954653978d22c" @@ -2645,10 +2660,10 @@ electron-to-chromium@^1.2.7: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.27.tgz#78ecb8a399066187bb374eede35d9c70565a803d" integrity sha1-eOy4o5kGYYe7N07t412ccFZagD0= -electron@6.1.5: - version "6.1.5" - resolved "https://registry.yarnpkg.com/electron/-/electron-6.1.5.tgz#1f1bc54042587d8368edd43ffecb0ce7c84cab87" - integrity sha512-PrdJKkAS0IaSJwu4him03VYqvAKK1qyWTE/ieb4LgcbR4F4u90b91/7xna6P1GpD/FXiHqzZQcs0SvK/o08ckQ== +electron@6.1.6: + version "6.1.6" + resolved "https://registry.yarnpkg.com/electron/-/electron-6.1.6.tgz#d63ea9c89b85b981a29eb3088bf391bf52bd8d73" + integrity sha512-4c4GiFTbWY2Mgv20HB4Bfhf1hDKb0MWgC35wkwNepNom1ioWfumocPHZrSs1xNAEe+tOmezY6lq74n3LbwTnVQ== dependencies: "@types/node" "^10.12.18" electron-download "^4.1.0"