From 8420d9f04e45ddc7fc74182db88f41408229d0fa Mon Sep 17 00:00:00 2001 From: ADS Merger Date: Wed, 6 May 2020 02:35:49 +0000 Subject: [PATCH] Merge from vscode bd0efff9e3f36d6b3e1045cee9887003af8034d7 --- .github/workflows/classifier-train.yml | 40 ++ .prettierrc.json | 6 - .yarnrc | 2 +- build/gulpfile.vscode.linux.js | 2 +- build/package.json | 2 +- build/yarn.lock | 8 +- cgmanifest.json | 4 +- .../schemas/devContainer.schema.json | 27 +- .../src/features/previewManager.ts | 1 - extensions/package.json | 2 +- extensions/yarn.lock | 8 +- package.json | 14 +- remote/package.json | 10 +- remote/web/package.json | 10 +- remote/web/yarn.lock | 40 +- remote/yarn.lock | 40 +- resources/linux/code.desktop | 0 scripts/code.sh | 2 +- .../api/common/extHostModelViewTree.ts | 2 +- .../browser/dataExplorer.contribution.ts | 4 +- .../browser/dataExplorerViewlet.ts | 2 +- .../browser/connectionTreeActions.test.ts | 19 +- .../ui/breadcrumbs/breadcrumbsWidget.ts | 8 +- src/vs/base/common/iterator.ts | 4 + src/vs/base/common/map.ts | 105 ++-- .../parts/quickinput/browser/quickInput.ts | 4 +- src/vs/base/test/common/map.test.ts | 69 ++- src/vs/code/electron-main/window.ts | 4 +- src/vs/code/node/cliProcessMain.ts | 2 +- src/vs/editor/browser/core/editorState.ts | 16 +- src/vs/editor/browser/editorExtensions.ts | 4 + .../editor/browser/widget/codeEditorWidget.ts | 2 +- src/vs/editor/common/editorContextKeys.ts | 9 +- src/vs/editor/common/model.ts | 11 +- .../pieceTreeTextBuffer.ts | 12 +- src/vs/editor/common/modes.ts | 2 +- .../services/markerDecorationsServiceImpl.ts | 12 +- .../services/semanticTokensProviderStyling.ts | 89 +-- .../editor/contrib/codelens/codelensWidget.ts | 3 +- .../contrib/documentSymbols/outlineTree.ts | 8 +- src/vs/editor/contrib/format/formatActions.ts | 2 +- src/vs/editor/contrib/gotoError/gotoError.ts | 50 +- .../contrib/gotoError/gotoErrorWidget.ts | 23 +- .../editor/contrib/gotoSymbol/goToCommands.ts | 24 +- .../contrib/gotoSymbol/peek/referencesTree.ts | 6 +- .../gotoSymbol/peek/referencesWidget.ts | 2 +- src/vs/editor/contrib/hover/hover.css | 3 +- .../editor/contrib/rename/renameInputField.ts | 3 +- .../contrib/snippet/snippetController2.ts | 3 +- .../editor/contrib/suggest/completionModel.ts | 6 +- src/vs/editor/contrib/suggest/suggest.ts | 213 +++---- .../contrib/suggest/suggestController.ts | 5 + .../editor/contrib/suggest/suggestWidget.ts | 56 +- .../contrib/suggest/test/suggestModel.test.ts | 2 +- .../editor/contrib/zoneWidget/zoneWidget.ts | 6 +- src/vs/monaco.d.ts | 2 +- src/vs/platform/actions/common/actions.ts | 119 ++-- src/vs/platform/actions/common/menuService.ts | 2 +- src/vs/platform/commands/common/commands.ts | 3 +- .../common/configurationModels.ts | 4 +- .../electron-browser/diskFileService.test.ts | 5 +- src/vs/platform/instantiation/common/graph.ts | 72 +-- .../instantiation/test/common/graph.test.ts | 2 +- src/vs/platform/list/browser/listService.ts | 7 +- .../platform/markers/common/markerService.ts | 175 +++--- .../platform/menubar/electron-main/menubar.ts | 3 +- src/vs/platform/theme/common/iconRegistry.ts | 43 +- .../common/abstractSynchronizer.ts | 8 +- .../electron-main/windowsMainService.ts | 4 +- src/vs/platform/windows/node/window.ts | 2 +- src/vs/vscode.d.ts | 47 +- src/vs/vscode.proposed.d.ts | 146 ++--- .../api/browser/mainThreadAuthentication.ts | 2 +- .../api/browser/mainThreadLanguageFeatures.ts | 4 +- .../api/browser/mainThreadNotebook.ts | 6 +- .../api/browser/viewsExtensionPoint.ts | 1 + .../workbench/api/common/extHost.api.impl.ts | 14 +- .../workbench/api/common/extHost.protocol.ts | 4 +- .../api/common/extHostDiagnostics.ts | 66 +- .../common/extHostDocumentSaveParticipant.ts | 24 +- .../api/common/extHostLanguageFeatures.ts | 98 +-- .../workbench/api/common/extHostNotebook.ts | 124 +++- .../workbench/api/common/extHostQuickOpen.ts | 21 +- .../api/common/extHostRequireInterceptor.ts | 3 +- .../workbench/api/common/extHostTreeViews.ts | 2 +- .../api/common/menusExtensionPoint.ts | 37 +- .../api/worker/extHostExtensionService.ts | 3 +- .../browser/actions/layoutActions.ts | 4 +- .../browser/actions/workspaceCommands.ts | 7 +- src/vs/workbench/browser/composite.ts | 5 +- src/vs/workbench/browser/editor.ts | 4 +- src/vs/workbench/browser/layout.ts | 13 +- src/vs/workbench/browser/media/part.css | 1 - .../parts/activitybar/activitybarActions.ts | 76 +-- .../parts/activitybar/activitybarPart.ts | 373 ++++++------ .../workbench/browser/parts/compositeBar.ts | 28 +- .../parts/editor/breadcrumbsControl.ts | 16 +- .../browser/parts/editor/breadcrumbsPicker.ts | 9 +- .../browser/parts/editor/editorControl.ts | 2 +- .../browser/parts/editor/editorDropTarget.ts | 3 +- .../browser/parts/editor/editorGroupView.ts | 2 +- .../browser/parts/editor/editorPart.ts | 20 +- .../browser/parts/editor/editorStatus.ts | 3 +- .../browser/parts/editor/editorsObserver.ts | 6 +- .../browser/parts/panel/media/panelpart.css | 16 + .../browser/parts/panel/panelPart.ts | 78 ++- .../browser/parts/sidebar/sidebarPart.ts | 8 +- .../browser/parts/statusbar/statusbarPart.ts | 4 +- .../browser/parts/titlebar/titlebarPart.ts | 10 +- .../parts/views/{views.ts => viewsService.ts} | 107 +++- src/vs/workbench/browser/viewlet.ts | 18 - src/vs/workbench/browser/workbench.ts | 6 +- src/vs/workbench/common/editor.ts | 4 +- src/vs/workbench/common/editor/editorGroup.ts | 2 +- src/vs/workbench/common/notifications.ts | 4 +- src/vs/workbench/common/views.ts | 74 ++- .../browser/callHierarchyPeek.ts | 12 +- .../browser/callHierarchyTree.ts | 3 +- .../inspectEditorTokens.ts | 14 +- .../browser/breakpointEditorContribution.ts | 2 +- .../contrib/debug/browser/breakpointsView.ts | 8 +- .../contrib/debug/browser/callStackView.ts | 8 +- .../debug/browser/debug.contribution.ts | 10 +- .../debug/browser/debugActionViewItems.ts | 12 +- .../contrib/debug/browser/debugCommands.ts | 8 +- .../browser/debugConfigurationManager.ts | 34 +- .../contrib/debug/browser/debugHover.ts | 2 +- .../contrib/debug/browser/debugService.ts | 227 +------ .../contrib/debug/browser/debugSession.ts | 4 +- .../contrib/debug/browser/debugTaskRunner.ts | 4 +- .../contrib/debug/browser/debugTitle.ts | 35 ++ .../debug/browser/media/debugViewlet.css | 7 - .../contrib/debug/browser/replViewer.ts | 6 +- .../contrib/debug/browser/variablesView.ts | 6 +- .../debug/browser/watchExpressionsView.ts | 4 +- .../contrib/debug/common/debugModel.ts | 33 +- .../contrib/debug/common/debugStorage.ts | 116 ++++ .../contrib/debug/common/debugTelemetry.ts | 80 +++ .../contrib/debug/common/debugUtils.ts | 2 +- .../debug/test/browser/baseDebugView.test.ts | 6 +- .../debug/test/browser/breakpoints.test.ts | 3 +- .../debug/test/browser/callStack.test.ts | 4 +- .../debug/test/browser/debugHover.test.ts | 5 +- .../contrib/debug/test/browser/repl.test.ts | 4 +- .../contrib/debug/test/browser/watch.test.ts | 3 +- .../contrib/debug/test/common/mockDebug.ts | 42 +- .../debugANSIHandling.test.ts | 3 +- .../extensions/browser/extensionEditor.ts | 4 +- .../extensions/browser/extensionsActions.ts | 95 ++- .../extensions/browser/extensionsList.ts | 2 +- .../extensions/browser/media/defaultIcon.png | Bin 1054 -> 0 bytes .../browser/media/extensionEditor.css | 5 - .../extensionsActions.test.ts | 562 +++++++++++++----- .../browser/externalTerminal.contribution.ts | 7 +- .../contrib/feedback/browser/feedback.ts | 2 +- .../browser/editors/textFileEditorTracker.ts | 8 +- .../contrib/files/browser/explorerViewlet.ts | 12 +- .../contrib/files/browser/fileActions.ts | 11 +- .../files/browser/files.contribution.ts | 5 +- .../contrib/files/browser/views/emptyView.ts | 10 +- .../files/browser/views/explorerView.ts | 36 +- .../files/browser/views/explorerViewer.ts | 11 +- .../files/browser/views/openEditorsView.ts | 2 +- .../contrib/files/common/explorerModel.ts | 2 +- .../contrib/files/common/explorerService.ts | 2 +- .../browser/textFileEditorTracker.test.ts | 46 +- .../contrib/markers/browser/markers.ts | 2 +- .../notebook/browser/contrib/coreActions.ts | 99 ++- .../browser/contrib/format/formatting.ts | 52 +- .../notebook/browser/media/notebook.css | 60 +- .../notebook/browser/notebook.contribution.ts | 26 +- .../notebook/browser/notebookBrowser.ts | 34 +- .../notebook/browser/notebookEditor.ts | 183 +++++- .../notebook/browser/notebookEditorInput.ts | 143 +---- ...ebookService.ts => notebookServiceImpl.ts} | 52 +- .../notebook/browser/view/notebookCellList.ts | 15 +- .../view/renderers/backLayerWebView.ts | 2 +- .../browser/view/renderers/cellRenderer.ts | 153 +++-- .../browser/view/renderers/codeCell.ts | 6 +- .../browser/view/renderers/markdownCell.ts | 26 +- .../browser/viewModel/baseCellViewModel.ts | 92 ++- .../notebook/browser/viewModel/cellEdit.ts | 3 +- .../browser/viewModel/codeCellViewModel.ts | 12 +- .../viewModel/markdownCellViewModel.ts | 12 +- .../browser/viewModel/notebookViewModel.ts | 51 +- .../common/model/notebookCellTextModel.ts | 63 +- .../common/model/notebookTextModel.ts | 10 +- .../contrib/notebook/common/notebookCommon.ts | 21 +- .../notebook/common/notebookEditorModel.ts | 208 +++++++ .../notebook/common/notebookService.ts | 51 ++ .../notebook/test/notebookTextModel.test.ts | 22 +- .../notebook/test/notebookViewModel.test.ts | 7 +- .../notebook/test/testNotebookEditor.ts | 75 ++- .../contrib/outline/browser/outlinePane.ts | 24 +- .../electron-browser/perfviewEditor.ts | 9 +- .../preferences/browser/keybindingsEditor.ts | 7 +- .../browser/preferencesRenderers.ts | 12 +- .../contrib/scm/browser/repositoryPane.ts | 4 +- .../contrib/search/browser/searchView.ts | 2 +- .../contrib/search/common/searchModel.ts | 16 +- .../browser/searchEditor.contribution.ts | 131 +++- .../browser/searchEditorActions.ts | 88 +-- .../browser/snippetCompletionProvider.ts | 2 +- .../snippets/browser/snippetsService.ts | 4 +- .../contrib/snippets/browser/tabCompletion.ts | 3 +- .../tasks/browser/abstractTaskService.ts | 10 +- .../tasks/browser/terminalTaskSystem.ts | 1 + .../workbench/contrib/tasks/common/tasks.ts | 12 + .../browser/telemetry.contribution.ts | 2 +- .../browser/addons/commandTrackerAddon.ts | 16 +- .../terminal/browser/links/terminalLink.ts | 5 + .../browser/links/terminalLinkManager.ts | 10 +- .../contrib/terminal/browser/terminalView.ts | 5 + .../terminal/browser/widgets/hoverWidget.ts | 2 +- .../browser/dynamicWebviewEditorOverlay.ts | 5 +- .../contrib/webview/browser/pre/main.js | 15 +- .../contrib/webview/browser/webviewEditor.ts | 4 +- src/vs/workbench/electron-browser/window.ts | 2 +- .../activityBar/browser/activityBarService.ts | 9 +- .../backup/common/backupFileService.ts | 2 +- .../services/bulkEdit/browser/conflicts.ts | 2 +- .../configuration/browser/configuration.ts | 4 +- .../services/editor/browser/editorService.ts | 2 +- .../environment/browser/environmentService.ts | 7 +- .../host/browser/browserHostService.ts | 18 +- .../electron-browser/desktopHostService.ts | 4 +- .../progress/browser/progressService.ts | 4 +- .../services/search/common/searchService.ts | 4 +- .../common/textFileEditorModelManager.ts | 2 +- .../common/textModelResolverService.ts | 13 +- .../themes/browser/productIconThemeData.ts | 98 ++- .../themes/browser/workbenchThemeService.ts | 8 +- .../themes/common/fileIconThemeSchema.ts | 20 +- .../themes/common/productIconThemeSchema.ts | 28 +- .../services/title/common/titleService.ts | 1 + .../services/viewlet/browser/viewlet.ts | 5 - .../views/browser/viewDescriptorService.ts | 23 +- .../browser/api/extHostDiagnostics.test.ts | 13 +- .../browser/parts/editor/editorGroups.test.ts | 44 +- .../test/browser/workbenchTestServices.ts | 13 +- src/vs/workbench/workbench.common.main.ts | 2 +- src/vs/workbench/workbench.web.api.ts | 3 + yarn.lock | 56 +- 243 files changed, 4276 insertions(+), 2478 deletions(-) create mode 100644 .github/workflows/classifier-train.yml delete mode 100644 .prettierrc.json mode change 100644 => 100755 resources/linux/code.desktop rename src/vs/workbench/browser/parts/views/{views.ts => viewsService.ts} (79%) create mode 100644 src/vs/workbench/contrib/debug/browser/debugTitle.ts create mode 100644 src/vs/workbench/contrib/debug/common/debugStorage.ts create mode 100644 src/vs/workbench/contrib/debug/common/debugTelemetry.ts delete mode 100644 src/vs/workbench/contrib/extensions/browser/media/defaultIcon.png rename src/vs/workbench/contrib/notebook/browser/{notebookService.ts => notebookServiceImpl.ts} (82%) create mode 100644 src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts create mode 100644 src/vs/workbench/contrib/notebook/common/notebookService.ts diff --git a/.github/workflows/classifier-train.yml b/.github/workflows/classifier-train.yml new file mode 100644 index 0000000000..c728e374a7 --- /dev/null +++ b/.github/workflows/classifier-train.yml @@ -0,0 +1,40 @@ +name: "Classifier: Trainer" +on: + schedule: + - cron: 0 0 12 * * + +jobs: + main: + runs-on: ubuntu-latest + steps: + - name: Checkout Actions + uses: actions/checkout@v2 + with: + repository: 'microsoft/vscode-github-triage-actions' + ref: master + lfs: true + path: ./actions + - name: Install Actions + run: npm install --production --prefix ./actions + - name: Install Additional Dependencies + # Pulls in a bunch of other packages that arent needed for the rest of the actions + run: npm install @azure/storage-blob@12 + - name: "Run Classifier: Scraper" + uses: ./actions/classifier/train/fetch-issues + with: + token: ${{secrets.ISSUE_SCRAPER_TOKEN}} # My personal token, so as to not risk going over quota on main token + - name: Set up Python 3.7 + uses: actions/setup-python@v1 + with: + python-version: 3.7 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install --upgrade numpy scipy scikit-learn joblib nltk + - name: "Run Classifier: Generator" + run: python ./actions/classifier/train/generate-models/generate.py category + - name: "Run Classifier: Upload" + uses: ./actions/classifier/train/upload-models + with: + blobContainerName: classifier-models + blobStorageKey: ${{secrets.AZURE_BLOB_STORAGE_CONNECTION_STRING}} diff --git a/.prettierrc.json b/.prettierrc.json deleted file mode 100644 index 91855cc846..0000000000 --- a/.prettierrc.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "useTabs": true, - "printWidth": 120, - "semi": true, - "singleQuote": true -} diff --git a/.yarnrc b/.yarnrc index c3c11fbeb9..d86b284e83 100644 --- a/.yarnrc +++ b/.yarnrc @@ -1,3 +1,3 @@ disturl "https://atom.io/download/electron" -target "7.2.2" +target "7.2.4" runtime "electron" diff --git a/build/gulpfile.vscode.linux.js b/build/gulpfile.vscode.linux.js index 57719d9fc9..cd9684b4ed 100644 --- a/build/gulpfile.vscode.linux.js +++ b/build/gulpfile.vscode.linux.js @@ -44,7 +44,7 @@ function prepareDebPackage(arch) { .pipe(replace('@@NAME_SHORT@@', product.nameShort)) .pipe(replace('@@NAME@@', product.applicationName)) .pipe(replace('@@EXEC@@', `/usr/share/${product.applicationName}/${product.applicationName}`)) - .pipe(replace('@@ICON@@', `/usr/share/pixmaps/${product.linuxIconName}.png`)) + .pipe(replace('@@ICON@@', product.linuxIconName)) .pipe(replace('@@URLPROTOCOL@@', product.urlProtocol)); const appdata = gulp.src('resources/linux/code.appdata.xml', { base: '.' }) diff --git a/build/package.json b/build/package.json index 3d6f7f49ae..baae3ffde5 100644 --- a/build/package.json +++ b/build/package.json @@ -48,7 +48,7 @@ "rollup-plugin-commonjs": "^10.1.0", "rollup-plugin-node-resolve": "^5.2.0", "terser": "4.3.8", - "typescript": "^3.9.0-dev.20200427", + "typescript": "^3.9.1-rc", "vsce": "1.48.0", "vscode-telemetry-extractor": "^1.5.4", "xml2js": "^0.4.17" diff --git a/build/yarn.lock b/build/yarn.lock index 1d8f6f03a7..dff6224b1a 100644 --- a/build/yarn.lock +++ b/build/yarn.lock @@ -3462,10 +3462,10 @@ typescript@^3.0.1: resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.3.tgz#c830f657f93f1ea846819e929092f5fe5983e977" integrity sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g== -typescript@^3.9.0-dev.20200427: - version "3.9.0-dev.20200427" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.0-dev.20200427.tgz#e42d606d938575dfb7b0b66f04a31b5f0eb0be57" - integrity sha512-ja/GhL7BHT+VQZiLoYMGJt2CP1Pdr0EhYefv4LLw4tVooSuCDB8SDKT/i/HwsoPgQ4ZaYfg1vPl+1RhiO3bwJg== +typescript@^3.9.1-rc: + version "3.9.1-rc" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.1-rc.tgz#81d5a5a0a597e224b6e2af8dffb46524b2eaf5f3" + integrity sha512-+cPv8L2Vd4KidCotqi2wjegBZ5n47CDRUu/QiLVu2YbeXAz78hIfcai9ziBiNI6JTGTVwUqXRug2UZxDcxhvFw== typical@^4.0.0: version "4.0.0" diff --git a/cgmanifest.json b/cgmanifest.json index 2d5e8a2d66..09478ca403 100644 --- a/cgmanifest.json +++ b/cgmanifest.json @@ -60,12 +60,12 @@ "git": { "name": "electron", "repositoryUrl": "https://github.com/electron/electron", - "commitHash": "959e80cc53cbebf8eb1d62eb2d14fa8fd86b0394" + "commitHash": "0552e0d5de46ffa3b481d741f1db5c779e201565" } }, "isOnlyProductionDependency": true, "license": "MIT", - "version": "7.2.2" + "version": "7.2.4" }, { "component": { diff --git a/extensions/configuration-editing/schemas/devContainer.schema.json b/extensions/configuration-editing/schemas/devContainer.schema.json index 9cb171915f..5a69d57057 100644 --- a/extensions/configuration-editing/schemas/devContainer.schema.json +++ b/extensions/configuration-editing/schemas/devContainer.schema.json @@ -282,33 +282,42 @@ ] } }, - "allOf": [ + "oneOf": [ { - "oneOf": [ + "allOf": [ { - "allOf": [ + "oneOf": [ { - "oneOf": [ + "allOf": [ { - "$ref": "#/definitions/dockerfileContainer" + "oneOf": [ + { + "$ref": "#/definitions/dockerfileContainer" + }, + { + "$ref": "#/definitions/imageContainer" + } + ] }, { - "$ref": "#/definitions/imageContainer" + "$ref": "#/definitions/nonComposeBase" } ] }, { - "$ref": "#/definitions/nonComposeBase" + "$ref": "#/definitions/composeContainer" } ] }, { - "$ref": "#/definitions/composeContainer" + "$ref": "#/definitions/devContainerCommon" } ] }, { - "$ref": "#/definitions/devContainerCommon" + "type": "object", + "$ref": "#/definitions/devContainerCommon", + "additionalProperties": false } ] } diff --git a/extensions/markdown-language-features/src/features/previewManager.ts b/extensions/markdown-language-features/src/features/previewManager.ts index 5a73cb7a01..c830d0ca12 100644 --- a/extensions/markdown-language-features/src/features/previewManager.ts +++ b/extensions/markdown-language-features/src/features/previewManager.ts @@ -133,7 +133,6 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview webview: vscode.WebviewPanel, state: any ): Promise { - console.log(state); const resource = vscode.Uri.parse(state.resource); const locked = state.locked; const line = state.line; diff --git a/extensions/package.json b/extensions/package.json index 7b4e8171c1..343e305375 100644 --- a/extensions/package.json +++ b/extensions/package.json @@ -3,7 +3,7 @@ "version": "0.0.1", "description": "Dependencies shared by all extensions", "dependencies": { - "typescript": "3.8.3" + "typescript": "3.9.1-rc" }, "scripts": { "postinstall": "node ./postinstall" diff --git a/extensions/yarn.lock b/extensions/yarn.lock index 83bd84cdd9..0a5236436a 100644 --- a/extensions/yarn.lock +++ b/extensions/yarn.lock @@ -2,7 +2,7 @@ # yarn lockfile v1 -typescript@3.8.3: - version "3.8.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.3.tgz#409eb8544ea0335711205869ec458ab109ee1061" - integrity sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w== +typescript@3.9.1-rc: + version "3.9.1-rc" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.1-rc.tgz#81d5a5a0a597e224b6e2af8dffb46524b2eaf5f3" + integrity sha512-+cPv8L2Vd4KidCotqi2wjegBZ5n47CDRUu/QiLVu2YbeXAz78hIfcai9ziBiNI6JTGTVwUqXRug2UZxDcxhvFw== diff --git a/package.json b/package.json index 5c22a379ce..9668eff395 100644 --- a/package.json +++ b/package.json @@ -81,11 +81,11 @@ "vscode-ripgrep": "^1.5.8", "vscode-sqlite3": "4.0.10", "vscode-textmate": "5.1.1", - "xterm": "4.6.0-beta.25", - "xterm-addon-search": "0.6.0", - "xterm-addon-unicode11": "0.2.0-beta.2", - "xterm-addon-web-links": "0.3.0", - "xterm-addon-webgl": "0.7.0-beta.8", + "xterm": "4.6.0-beta.38", + "xterm-addon-search": "0.7.0-beta.2", + "xterm-addon-unicode11": "0.2.0-beta.5", + "xterm-addon-web-links": "0.4.0-beta.5", + "xterm-addon-webgl": "0.7.0-beta.10", "yauzl": "^2.9.2", "yazl": "^2.4.3", "zone.js": "^0.8.4" @@ -126,7 +126,7 @@ "css-loader": "^3.2.0", "debounce": "^1.0.0", "deemon": "^1.4.0", - "electron": "7.2.2", + "electron": "7.2.4", "eslint": "6.8.0", "eslint-plugin-jsdoc": "^19.1.0", "event-stream": "3.3.4", @@ -183,7 +183,7 @@ "temp-write": "^3.4.0", "ts-loader": "^4.4.2", "typemoq": "^0.3.2", - "typescript": "^3.9.0-dev.20200427", + "typescript": "^3.9.1-rc", "typescript-formatter": "7.1.0", "underscore": "^1.8.2", "vinyl": "^2.0.0", diff --git a/remote/package.json b/remote/package.json index b50d72bd0d..26faf6ed76 100644 --- a/remote/package.json +++ b/remote/package.json @@ -38,11 +38,11 @@ "vscode-proxy-agent": "^0.5.2", "vscode-ripgrep": "^1.5.8", "vscode-textmate": "5.1.1", - "xterm": "4.6.0-beta.25", - "xterm-addon-search": "0.6.0", - "xterm-addon-unicode11": "0.2.0-beta.2", - "xterm-addon-web-links": "0.3.0", - "xterm-addon-webgl": "0.7.0-beta.8", + "xterm": "4.6.0-beta.38", + "xterm-addon-search": "0.7.0-beta.2", + "xterm-addon-unicode11": "0.2.0-beta.5", + "xterm-addon-web-links": "0.4.0-beta.5", + "xterm-addon-webgl": "0.7.0-beta.10", "yauzl": "^2.9.2", "yazl": "^2.4.3", "zone.js": "^0.8.4" diff --git a/remote/web/package.json b/remote/web/package.json index ca72a77c3f..0f2da3630b 100644 --- a/remote/web/package.json +++ b/remote/web/package.json @@ -23,11 +23,11 @@ "slickgrid": "github:anthonydresser/SlickGrid#2.3.33", "vscode-oniguruma": "1.3.0", "vscode-textmate": "5.1.1", - "xterm": "4.6.0-beta.25", - "xterm-addon-search": "0.6.0", - "xterm-addon-unicode11": "0.2.0-beta.2", - "xterm-addon-web-links": "0.3.0", - "xterm-addon-webgl": "0.7.0-beta.8", + "xterm": "4.6.0-beta.38", + "xterm-addon-search": "0.7.0-beta.2", + "xterm-addon-unicode11": "0.2.0-beta.5", + "xterm-addon-web-links": "0.4.0-beta.5", + "xterm-addon-webgl": "0.7.0-beta.10", "zone.js": "^0.8.4" } } diff --git a/remote/web/yarn.lock b/remote/web/yarn.lock index 41460ce065..d11c030bb4 100644 --- a/remote/web/yarn.lock +++ b/remote/web/yarn.lock @@ -353,30 +353,30 @@ xtend@^4.0.1: resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== -xterm-addon-search@0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.6.0.tgz#542cc2c35e83e7332ce1982b65ad218ee769836c" - integrity sha512-k3EsZzUptCXygHFP5rQuCBdWWkI/ZNuX3pDSOVdxPV9jB7U5Aha9guTIZoMP7FIjL8jce+ClQs6q7VINcRV1+w== +xterm-addon-search@0.7.0-beta.2: + version "0.7.0-beta.2" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.7.0-beta.2.tgz#384bda136c707f97a77eefc76cc7d9e572ce0719" + integrity sha512-A9fyiBBvG6ZNIwSJ03+sRCv9y20/uzd1wjCoaYUqp9fu3YGiHaGwyo9rAfm2M/fQM5vBmyJk4Qw/lwVq7TtlAw== -xterm-addon-unicode11@0.2.0-beta.2: - version "0.2.0-beta.2" - resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.2.0-beta.2.tgz#2a13ba5b08fdb1005be241816c4e3302674db4af" - integrity sha512-Y047mnIWrAj65TpStdyPYoPeDTX4en+XX4Y90KuQB3cW2xIyZj25NSVV9BZdqzSb7gk9M6KBvIcm8chj7S2N8Q== +xterm-addon-unicode11@0.2.0-beta.5: + version "0.2.0-beta.5" + resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.2.0-beta.5.tgz#5961850162df20b5e966166423cd6957ac2db298" + integrity sha512-IjnbBcyfS5JgJDXPO0W2nk/VBtGwx6GWE2snMC676z4DmAABUqPXfTzJKfUoWqoT6UcbxB0oIjDzykCfoRJp6Q== -xterm-addon-web-links@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.3.0.tgz#88affe9235c928b41bab660a65330f46d91c940e" - integrity sha512-vGXiIDqNMyxK5S1IzOjDqcgeQrrv7TDcSHiOeCNAoWCI2f+Rap9d18gjgnMKPyR+AbG0KoKnaKA6Dc1du1vs5A== +xterm-addon-web-links@0.4.0-beta.5: + version "0.4.0-beta.5" + resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.4.0-beta.5.tgz#523fd0a1c5668370d73e05019ed16eaf596894c8" + integrity sha512-Qe0idPpSokCNvGrthSBjdrOZrsgXwnLYbzuv0JoEec/A9HVcxKmZ+ktw7fOA2gT/zbcwtrA5FWrir3GlRHglCQ== -xterm-addon-webgl@0.7.0-beta.8: - version "0.7.0-beta.8" - resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.7.0-beta.8.tgz#546651958d740bf05d6a05555fbcacd2759b2ee7" - integrity sha512-2jxMtRR5zgAar1gPqt0iD/+GOlZ3cHyzzbIbC77EBIdZZFuhEDhJkucVPPS2KPcyqw3VROL1FgX7BSEV2rvdeA== +xterm-addon-webgl@0.7.0-beta.10: + version "0.7.0-beta.10" + resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.7.0-beta.10.tgz#39fdb96351e97a1bf15f4c4c8944ba3d05cacee4" + integrity sha512-nQl/ASk+ck11aSrBZXb2a0tu+SNDnm89owBk/sAZeZzi5MHNo6bB8y2VTKNNC6D3i3aFouTz4VorYB25LUgNFg== -xterm@4.6.0-beta.25: - version "4.6.0-beta.25" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.6.0-beta.25.tgz#2faea6cf8c677ed545792562165604ce7f314026" - integrity sha512-63FLAUdJ8Bw9SMgLU3/r353P1WAtLxupbfvfddi4nMcz1WEGRq07O1CbmJn/bKHHkJw7gQQw0n1I8xnjFlLlTA== +xterm@4.6.0-beta.38: + version "4.6.0-beta.38" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.6.0-beta.38.tgz#8472b168941500c3071aba482c2b5c6040951ec7" + integrity sha512-Q+nOalMD1MDGOqXdtkGZmOQqbSBU+71vhlX2RBwQoSpJa1QBrKDAhSlN/J+/XvouvVEtCiEFDeacF4EufMEIMg== zone.js@^0.8.4: version "0.8.29" diff --git a/remote/yarn.lock b/remote/yarn.lock index 0ca93704e1..0d63dcfbbc 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -740,30 +740,30 @@ xtend@^4.0.1: resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== -xterm-addon-search@0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.6.0.tgz#542cc2c35e83e7332ce1982b65ad218ee769836c" - integrity sha512-k3EsZzUptCXygHFP5rQuCBdWWkI/ZNuX3pDSOVdxPV9jB7U5Aha9guTIZoMP7FIjL8jce+ClQs6q7VINcRV1+w== +xterm-addon-search@0.7.0-beta.2: + version "0.7.0-beta.2" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.7.0-beta.2.tgz#384bda136c707f97a77eefc76cc7d9e572ce0719" + integrity sha512-A9fyiBBvG6ZNIwSJ03+sRCv9y20/uzd1wjCoaYUqp9fu3YGiHaGwyo9rAfm2M/fQM5vBmyJk4Qw/lwVq7TtlAw== -xterm-addon-unicode11@0.2.0-beta.2: - version "0.2.0-beta.2" - resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.2.0-beta.2.tgz#2a13ba5b08fdb1005be241816c4e3302674db4af" - integrity sha512-Y047mnIWrAj65TpStdyPYoPeDTX4en+XX4Y90KuQB3cW2xIyZj25NSVV9BZdqzSb7gk9M6KBvIcm8chj7S2N8Q== +xterm-addon-unicode11@0.2.0-beta.5: + version "0.2.0-beta.5" + resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.2.0-beta.5.tgz#5961850162df20b5e966166423cd6957ac2db298" + integrity sha512-IjnbBcyfS5JgJDXPO0W2nk/VBtGwx6GWE2snMC676z4DmAABUqPXfTzJKfUoWqoT6UcbxB0oIjDzykCfoRJp6Q== -xterm-addon-web-links@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.3.0.tgz#88affe9235c928b41bab660a65330f46d91c940e" - integrity sha512-vGXiIDqNMyxK5S1IzOjDqcgeQrrv7TDcSHiOeCNAoWCI2f+Rap9d18gjgnMKPyR+AbG0KoKnaKA6Dc1du1vs5A== +xterm-addon-web-links@0.4.0-beta.5: + version "0.4.0-beta.5" + resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.4.0-beta.5.tgz#523fd0a1c5668370d73e05019ed16eaf596894c8" + integrity sha512-Qe0idPpSokCNvGrthSBjdrOZrsgXwnLYbzuv0JoEec/A9HVcxKmZ+ktw7fOA2gT/zbcwtrA5FWrir3GlRHglCQ== -xterm-addon-webgl@0.7.0-beta.8: - version "0.7.0-beta.8" - resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.7.0-beta.8.tgz#546651958d740bf05d6a05555fbcacd2759b2ee7" - integrity sha512-2jxMtRR5zgAar1gPqt0iD/+GOlZ3cHyzzbIbC77EBIdZZFuhEDhJkucVPPS2KPcyqw3VROL1FgX7BSEV2rvdeA== +xterm-addon-webgl@0.7.0-beta.10: + version "0.7.0-beta.10" + resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.7.0-beta.10.tgz#39fdb96351e97a1bf15f4c4c8944ba3d05cacee4" + integrity sha512-nQl/ASk+ck11aSrBZXb2a0tu+SNDnm89owBk/sAZeZzi5MHNo6bB8y2VTKNNC6D3i3aFouTz4VorYB25LUgNFg== -xterm@4.6.0-beta.25: - version "4.6.0-beta.25" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.6.0-beta.25.tgz#2faea6cf8c677ed545792562165604ce7f314026" - integrity sha512-63FLAUdJ8Bw9SMgLU3/r353P1WAtLxupbfvfddi4nMcz1WEGRq07O1CbmJn/bKHHkJw7gQQw0n1I8xnjFlLlTA== +xterm@4.6.0-beta.38: + version "4.6.0-beta.38" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.6.0-beta.38.tgz#8472b168941500c3071aba482c2b5c6040951ec7" + integrity sha512-Q+nOalMD1MDGOqXdtkGZmOQqbSBU+71vhlX2RBwQoSpJa1QBrKDAhSlN/J+/XvouvVEtCiEFDeacF4EufMEIMg== yauzl@^2.9.2: version "2.10.0" diff --git a/resources/linux/code.desktop b/resources/linux/code.desktop old mode 100644 new mode 100755 diff --git a/scripts/code.sh b/scripts/code.sh index 3bc09f54f4..d0f79b53a9 100755 --- a/scripts/code.sh +++ b/scripts/code.sh @@ -55,7 +55,7 @@ function code() { function code-wsl() { - HOST_IP=$(powershell.exe -Command "& {(Get-NetIPAddress | Where-Object {\$_.InterfaceAlias -like '*WSL*' -and \$_.AddressFamily -eq 'IPv4'}).IPAddress | Write-Host -NoNewline}") + HOST_IP=$(powershell.exe –noprofile -Command "& {(Get-NetIPAddress | Where-Object {\$_.InterfaceAlias -like '*WSL*' -and \$_.AddressFamily -eq 'IPv4'}).IPAddress | Write-Host -NoNewline}") export DISPLAY="$HOST_IP:0" # in a wsl shell diff --git a/src/sql/workbench/api/common/extHostModelViewTree.ts b/src/sql/workbench/api/common/extHostModelViewTree.ts index cd7135ad6b..539c4cad7d 100644 --- a/src/sql/workbench/api/common/extHostModelViewTree.ts +++ b/src/sql/workbench/api/common/extHostModelViewTree.ts @@ -165,7 +165,7 @@ export class ExtHostTreeView extends vsTreeExt.ExtHostTreeView { }); } - protected createTreeNode(element: T, extensionTreeItem: azdata.TreeComponentItem, parent?: vsTreeExt.TreeNode): vsTreeExt.TreeNode { + protected createTreeNode(element: T, extensionTreeItem: azdata.TreeComponentItem, parent?: vsTreeExt.TreeNode | vsTreeExt.Root): vsTreeExt.TreeNode { let node = super.createTreeNode(element, extensionTreeItem, parent); if (node.item) { node.item = assign(node.item, { checked: extensionTreeItem.checked, enabled: extensionTreeItem.enabled }); diff --git a/src/sql/workbench/contrib/dataExplorer/browser/dataExplorer.contribution.ts b/src/sql/workbench/contrib/dataExplorer/browser/dataExplorer.contribution.ts index 1258cc2732..24a2c63eb3 100644 --- a/src/sql/workbench/contrib/dataExplorer/browser/dataExplorer.contribution.ts +++ b/src/sql/workbench/contrib/dataExplorer/browser/dataExplorer.contribution.ts @@ -5,9 +5,8 @@ import 'vs/css!./media/dataExplorer.contribution'; import { localize } from 'vs/nls'; -import { ViewletRegistry, Extensions as ViewletExtensions } from 'vs/workbench/browser/viewlet'; import { Registry } from 'vs/platform/registry/common/platform'; -import { DataExplorerViewletViewsContribution, OpenDataExplorerViewletAction, VIEWLET_ID } from 'sql/workbench/contrib/dataExplorer/browser/dataExplorerViewlet'; +import { DataExplorerViewletViewsContribution, OpenDataExplorerViewletAction } from 'sql/workbench/contrib/dataExplorer/browser/dataExplorerViewlet'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; @@ -16,7 +15,6 @@ import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { DataExplorerContainerExtensionHandler } from 'sql/workbench/contrib/dataExplorer/browser/dataExplorerExtensionPoint'; -Registry.as(ViewletExtensions.Viewlets).setDefaultViewletId(VIEWLET_ID); const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); workbenchRegistry.registerWorkbenchContribution(DataExplorerViewletViewsContribution, LifecyclePhase.Starting); const registry = Registry.as(ActionExtensions.WorkbenchActions); diff --git a/src/sql/workbench/contrib/dataExplorer/browser/dataExplorerViewlet.ts b/src/sql/workbench/contrib/dataExplorer/browser/dataExplorerViewlet.ts index 887ee3cdc0..aee74b6487 100644 --- a/src/sql/workbench/contrib/dataExplorer/browser/dataExplorerViewlet.ts +++ b/src/sql/workbench/contrib/dataExplorer/browser/dataExplorerViewlet.ts @@ -159,4 +159,4 @@ export const VIEW_CONTAINER = Registry.as(ViewContainer icon: 'dataExplorer', order: 0, storageId: `${VIEWLET_ID}.state` -}, ViewContainerLocation.Sidebar); +}, ViewContainerLocation.Sidebar, true); diff --git a/src/sql/workbench/contrib/objectExplorer/test/browser/connectionTreeActions.test.ts b/src/sql/workbench/contrib/objectExplorer/test/browser/connectionTreeActions.test.ts index d4e81e9d14..25f9237179 100644 --- a/src/sql/workbench/contrib/objectExplorer/test/browser/connectionTreeActions.test.ts +++ b/src/sql/workbench/contrib/objectExplorer/test/browser/connectionTreeActions.test.ts @@ -31,9 +31,10 @@ import { TestCapabilitiesService } from 'sql/platform/capabilities/test/common/t import { UNSAVED_GROUP_ID, mssqlProviderName } from 'sql/platform/connection/common/constants'; import { $ } from 'vs/base/browser/dom'; import { OEManageConnectionAction } from 'sql/workbench/contrib/dashboard/browser/dashboardActions'; -import { IViewsService, IView } from 'vs/workbench/common/views'; +import { IViewsService, IView, ViewContainerLocation, ViewContainer } from 'vs/workbench/common/views'; import { ConsoleLogService } from 'vs/platform/log/common/log'; import { IProgressIndicator } from 'vs/platform/progress/common/progress'; +import { IPaneComposite } from 'vs/workbench/common/panecomposite'; suite('SQL Connection Tree Action tests', () => { let errorMessageService: TypeMoq.Mock; @@ -110,6 +111,22 @@ suite('SQL Connection Tree Action tests', () => { }); const viewsService = new class implements IViewsService { + getViewProgressIndicator(id: string): IProgressIndicator { + throw new Error('Method not implemented.'); + } + onDidChangeViewContainerVisibility: Event<{ id: string; visible: boolean; location: ViewContainerLocation; }>; + isViewContainerVisible(id: string): boolean { + throw new Error('Method not implemented.'); + } + openViewContainer(id: string, focus?: boolean): Promise { + throw new Error('Method not implemented.'); + } + closeViewContainer(id: string): void { + throw new Error('Method not implemented.'); + } + getVisibleViewContainer(location: ViewContainerLocation): ViewContainer { + throw new Error('Method not implemented.'); + } getProgressIndicator(id: string): IProgressIndicator { throw new Error('Method not implemented.'); } diff --git a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts index 7aefef3872..90f01d31db 100644 --- a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts +++ b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts @@ -230,10 +230,10 @@ export class BreadcrumbsWidget { for (let i = 0; i < this._nodes.length; i++) { const node = this._nodes[i]; if (i !== nth) { - dom.removeClass(node, 'focused'); + node.classList.remove('focused'); } else { this._focusedItemIdx = i; - dom.addClass(node, 'focused'); + node.classList.add('focused'); node.focus(); } } @@ -274,10 +274,10 @@ export class BreadcrumbsWidget { for (let i = 0; i < this._nodes.length; i++) { const node = this._nodes[i]; if (i !== nth) { - dom.removeClass(node, 'selected'); + node.classList.remove('selected'); } else { this._selectedItemIdx = i; - dom.addClass(node, 'selected'); + node.classList.add('selected'); } } this._onDidSelectItem.fire({ type: 'select', item: this._items[this._selectedItemIdx], node: this._nodes[this._selectedItemIdx], payload }); diff --git a/src/vs/base/common/iterator.ts b/src/vs/base/common/iterator.ts index 5c80fde2e0..e1c2de039d 100644 --- a/src/vs/base/common/iterator.ts +++ b/src/vs/base/common/iterator.ts @@ -10,6 +10,10 @@ export namespace Iterable { return _empty; } + export function* single(element: T): Iterable { + yield element; + } + export function from(iterable: Iterable | undefined | null): Iterable { return iterable || _empty; } diff --git a/src/vs/base/common/map.ts b/src/vs/base/common/map.ts index d346935064..e5813e996b 100644 --- a/src/vs/base/common/map.ts +++ b/src/vs/base/common/map.ts @@ -481,7 +481,9 @@ export class TernarySearchTree { } } -export class ResourceMap { +export class ResourceMap implements Map { + + readonly [Symbol.toStringTag] = 'ResourceMap'; protected readonly map: Map; protected readonly ignoreCase?: boolean; @@ -491,8 +493,9 @@ export class ResourceMap { this.ignoreCase = false; // in the future this should be an uri-comparator } - set(resource: URI, value: T): void { + set(resource: URI, value: T): this { this.map.set(this.toKey(resource), value); + return this; } get(resource: URI): T | undefined { @@ -515,12 +518,35 @@ export class ResourceMap { return this.map.delete(this.toKey(resource)); } - forEach(clb: (value: T, key: URI) => void): void { - this.map.forEach((value, index) => clb(value, URI.parse(index))); + forEach(clb: (value: T, key: URI, map: Map) => void, thisArg?: any): void { + if (typeof thisArg !== 'undefined') { + clb = clb.bind(thisArg); + } + for (let [index, value] of this.map) { + clb(value, URI.parse(index), this); + } } - values(): T[] { - return values(this.map); + values(): IterableIterator { + return this.map.values(); + } + + *keys(): IterableIterator { + for (let key of this.map.keys()) { + yield URI.parse(key); + } + } + + *entries(): IterableIterator<[URI, T]> { + for (let tuple of this.map.entries()) { + yield [URI.parse(tuple[0]), tuple[1]]; + } + } + + *[Symbol.iterator](): IterableIterator<[URI, T]> { + for (let item of this.map) { + yield [URI.parse(item[0]), item[1]]; + } } private toKey(resource: URI): string { @@ -532,10 +558,6 @@ export class ResourceMap { return key; } - keys(): URI[] { - return keys(this.map).map(k => URI.parse(k)); - } - clone(): ResourceMap { const resourceMap = new ResourceMap(); @@ -558,7 +580,9 @@ export const enum Touch { AsNew = 2 } -export class LinkedMap { +export class LinkedMap implements Map{ + + readonly [Symbol.toStringTag] = 'LinkedMap'; private _map: Map>; private _head: Item | undefined; @@ -610,7 +634,7 @@ export class LinkedMap { return item.value; } - set(key: K, value: V, touch: Touch = Touch.None): void { + set(key: K, value: V, touch: Touch = Touch.None): this { let item = this._map.get(key); if (item) { item.value = value; @@ -636,6 +660,7 @@ export class LinkedMap { this._map.set(key, item); this._size++; } + return this; } delete(key: K): boolean { @@ -679,34 +704,13 @@ export class LinkedMap { } } - values(): V[] { - const result: V[] = []; - let current = this._head; - while (current) { - result.push(current.value); - current = current.next; - } - return result; - } - - keys(): K[] { - const result: K[] = []; - let current = this._head; - while (current) { - result.push(current.key); - current = current.next; - } - return result; - } - - /* VS Code / Monaco editor runs on es5 which has no Symbol.iterator keys(): IterableIterator { - const current = this._head; + let current = this._head; const iterator: IterableIterator = { [Symbol.iterator]() { return iterator; }, - next():IteratorResult { + next(): IteratorResult { if (current) { const result = { value: current.key, done: false }; current = current.next; @@ -720,12 +724,12 @@ export class LinkedMap { } values(): IterableIterator { - const current = this._head; + let current = this._head; const iterator: IterableIterator = { [Symbol.iterator]() { return iterator; }, - next():IteratorResult { + next(): IteratorResult { if (current) { const result = { value: current.value, done: false }; current = current.next; @@ -737,7 +741,29 @@ export class LinkedMap { }; return iterator; } - */ + + entries(): IterableIterator<[K, V]> { + let current = this._head; + const iterator: IterableIterator<[K, V]> = { + [Symbol.iterator]() { + return iterator; + }, + next(): IteratorResult<[K, V]> { + if (current) { + const result: IteratorResult<[K, V]> = { value: [current.key, current.value], done: false }; + current = current.next; + return result; + } else { + return { value: undefined, done: true }; + } + } + }; + return iterator; + } + + [Symbol.iterator](): IterableIterator<[K, V]> { + return this.entries(); + } protected trimOld(newSize: number) { if (newSize >= this.size) { @@ -939,9 +965,10 @@ export class LRUCache extends LinkedMap { return super.get(key, Touch.None); } - set(key: K, value: V): void { + set(key: K, value: V): this { super.set(key, value, Touch.AsNew); this.checkTrim(); + return this; } private checkTrim() { diff --git a/src/vs/base/parts/quickinput/browser/quickInput.ts b/src/vs/base/parts/quickinput/browser/quickInput.ts index ebf6469919..88a1d9ffc4 100644 --- a/src/vs/base/parts/quickinput/browser/quickInput.ts +++ b/src/vs/base/parts/quickinput/browser/quickInput.ts @@ -274,8 +274,10 @@ class QuickInput extends Disposable implements IQuickInput { return; } const title = this.getTitle(); - if (this.ui.title.textContent !== title) { + if (title && this.ui.title.textContent !== title) { this.ui.title.textContent = title; + } else if (!title && this.ui.title.innerHTML !== ' ') { + this.ui.title.innerHTML = ' '; } const description = this.getDescription(); if (this.ui.description.textContent !== description) { diff --git a/src/vs/base/test/common/map.test.ts b/src/vs/base/test/common/map.test.ts index f331875048..2ca1eb2aa7 100644 --- a/src/vs/base/test/common/map.test.ts +++ b/src/vs/base/test/common/map.test.ts @@ -13,8 +13,8 @@ suite('Map', () => { let map = new LinkedMap(); map.set('ak', 'av'); map.set('bk', 'bv'); - assert.deepStrictEqual(map.keys(), ['ak', 'bk']); - assert.deepStrictEqual(map.values(), ['av', 'bv']); + assert.deepStrictEqual([...map.keys()], ['ak', 'bk']); + assert.deepStrictEqual([...map.values()], ['av', 'bv']); assert.equal(map.first, 'av'); assert.equal(map.last, 'bv'); }); @@ -23,16 +23,16 @@ suite('Map', () => { let map = new LinkedMap(); map.set('ak', 'av'); map.set('ak', 'av', Touch.AsOld); - assert.deepStrictEqual(map.keys(), ['ak']); - assert.deepStrictEqual(map.values(), ['av']); + assert.deepStrictEqual([...map.keys()], ['ak']); + assert.deepStrictEqual([...map.values()], ['av']); }); test('LinkedMap - Touch New one', () => { let map = new LinkedMap(); map.set('ak', 'av'); map.set('ak', 'av', Touch.AsNew); - assert.deepStrictEqual(map.keys(), ['ak']); - assert.deepStrictEqual(map.values(), ['av']); + assert.deepStrictEqual([...map.keys()], ['ak']); + assert.deepStrictEqual([...map.values()], ['av']); }); test('LinkedMap - Touch Old two', () => { @@ -40,8 +40,8 @@ suite('Map', () => { map.set('ak', 'av'); map.set('bk', 'bv'); map.set('bk', 'bv', Touch.AsOld); - assert.deepStrictEqual(map.keys(), ['bk', 'ak']); - assert.deepStrictEqual(map.values(), ['bv', 'av']); + assert.deepStrictEqual([...map.keys()], ['bk', 'ak']); + assert.deepStrictEqual([...map.values()], ['bv', 'av']); }); test('LinkedMap - Touch New two', () => { @@ -49,8 +49,8 @@ suite('Map', () => { map.set('ak', 'av'); map.set('bk', 'bv'); map.set('ak', 'av', Touch.AsNew); - assert.deepStrictEqual(map.keys(), ['bk', 'ak']); - assert.deepStrictEqual(map.values(), ['bv', 'av']); + assert.deepStrictEqual([...map.keys()], ['bk', 'ak']); + assert.deepStrictEqual([...map.values()], ['bv', 'av']); }); test('LinkedMap - Touch Old from middle', () => { @@ -59,8 +59,8 @@ suite('Map', () => { map.set('bk', 'bv'); map.set('ck', 'cv'); map.set('bk', 'bv', Touch.AsOld); - assert.deepStrictEqual(map.keys(), ['bk', 'ak', 'ck']); - assert.deepStrictEqual(map.values(), ['bv', 'av', 'cv']); + assert.deepStrictEqual([...map.keys()], ['bk', 'ak', 'ck']); + assert.deepStrictEqual([...map.values()], ['bv', 'av', 'cv']); }); test('LinkedMap - Touch New from middle', () => { @@ -69,8 +69,8 @@ suite('Map', () => { map.set('bk', 'bv'); map.set('ck', 'cv'); map.set('bk', 'bv', Touch.AsNew); - assert.deepStrictEqual(map.keys(), ['ak', 'ck', 'bk']); - assert.deepStrictEqual(map.values(), ['av', 'cv', 'bv']); + assert.deepStrictEqual([...map.keys()], ['ak', 'ck', 'bk']); + assert.deepStrictEqual([...map.values()], ['av', 'cv', 'bv']); }); test('LinkedMap - basics', function () { @@ -136,13 +136,15 @@ suite('Map', () => { assert.strictEqual(cache.size, 5); cache.set(6, 6); assert.strictEqual(cache.size, 5); - assert.deepStrictEqual(cache.keys(), [2, 3, 4, 5, 6]); + assert.deepStrictEqual([...cache.keys()], [2, 3, 4, 5, 6]); cache.set(7, 7); assert.strictEqual(cache.size, 5); - assert.deepStrictEqual(cache.keys(), [3, 4, 5, 6, 7]); + assert.deepStrictEqual([...cache.keys()], [3, 4, 5, 6, 7]); let values: number[] = []; [3, 4, 5, 6, 7].forEach(key => values.push(cache.get(key)!)); assert.deepStrictEqual(values, [3, 4, 5, 6, 7]); + + assert.deepEqual([...cache.entries()], [[3, 3], [4, 4], [5, 5], [6, 6], [7, 7]]); }); test('LinkedMap - LRU Cache get', () => { @@ -150,11 +152,11 @@ suite('Map', () => { [1, 2, 3, 4, 5].forEach(value => cache.set(value, value)); assert.strictEqual(cache.size, 5); - assert.deepStrictEqual(cache.keys(), [1, 2, 3, 4, 5]); + assert.deepStrictEqual([...cache.keys()], [1, 2, 3, 4, 5]); cache.get(3); - assert.deepStrictEqual(cache.keys(), [1, 2, 4, 5, 3]); + assert.deepStrictEqual([...cache.keys()], [1, 2, 4, 5, 3]); cache.peek(4); - assert.deepStrictEqual(cache.keys(), [1, 2, 4, 5, 3]); + assert.deepStrictEqual([...cache.keys()], [1, 2, 4, 5, 3]); let values: number[] = []; [1, 2, 3, 4, 5].forEach(key => values.push(cache.get(key)!)); assert.deepStrictEqual(values, [1, 2, 3, 4, 5]); @@ -169,7 +171,7 @@ suite('Map', () => { assert.strictEqual(cache.size, 10); cache.limit = 5; assert.strictEqual(cache.size, 5); - assert.deepStrictEqual(cache.keys(), [6, 7, 8, 9, 10]); + assert.deepStrictEqual([...cache.keys()], [6, 7, 8, 9, 10]); cache.limit = 20; assert.strictEqual(cache.size, 5); for (let i = 11; i <= 20; i++) { @@ -181,7 +183,7 @@ suite('Map', () => { values.push(cache.get(i)!); assert.strictEqual(cache.get(i), i); } - assert.deepStrictEqual(cache.values(), values); + assert.deepStrictEqual([...cache.values()], values); }); test('LinkedMap - LRU Cache limit with ratio', () => { @@ -193,11 +195,11 @@ suite('Map', () => { assert.strictEqual(cache.size, 10); cache.set(11, 11); assert.strictEqual(cache.size, 5); - assert.deepStrictEqual(cache.keys(), [7, 8, 9, 10, 11]); + assert.deepStrictEqual([...cache.keys()], [7, 8, 9, 10, 11]); let values: number[] = []; - cache.keys().forEach(key => values.push(cache.get(key)!)); + [...cache.keys()].forEach(key => values.push(cache.get(key)!)); assert.deepStrictEqual(values, [7, 8, 9, 10, 11]); - assert.deepStrictEqual(cache.values(), values); + assert.deepStrictEqual([...cache.values()], values); }); test('LinkedMap - toJSON / fromJSON', () => { @@ -237,7 +239,7 @@ suite('Map', () => { map.delete('1'); assert.equal(map.get('1'), undefined); assert.equal(map.size, 0); - assert.equal(map.keys().length, 0); + assert.equal([...map.keys()].length, 0); }); test('LinkedMap - delete Head', function () { @@ -251,8 +253,8 @@ suite('Map', () => { map.delete('1'); assert.equal(map.get('2'), 2); assert.equal(map.size, 1); - assert.equal(map.keys().length, 1); - assert.equal(map.keys()[0], 2); + assert.equal([...map.keys()].length, 1); + assert.equal([...map.keys()][0], 2); }); test('LinkedMap - delete Tail', function () { @@ -266,8 +268,8 @@ suite('Map', () => { map.delete('2'); assert.equal(map.get('1'), 1); assert.equal(map.size, 1); - assert.equal(map.keys().length, 1); - assert.equal(map.keys()[0], 1); + assert.equal([...map.keys()].length, 1); + assert.equal([...map.keys()][0], 1); }); @@ -656,18 +658,21 @@ suite('Map', () => { assert.equal(map.size, 0); - map.set(resource1, 1); + let res = map.set(resource1, 1); + assert.ok(res === map); map.set(resource2, '2'); map.set(resource3, true); - const values = map.values(); + const values = [...map.values()]; assert.equal(values[0], 1); assert.equal(values[1], '2'); assert.equal(values[2], true); let counter = 0; - map.forEach(value => { + map.forEach((value, key, mapObj) => { assert.equal(value, values[counter++]); + assert.ok(URI.isUri(key)); + assert.ok(map === mapObj); }); const obj = Object.create(null); diff --git a/src/vs/code/electron-main/window.ts b/src/vs/code/electron-main/window.ts index 31400ad7c2..83bb2cfe75 100644 --- a/src/vs/code/electron-main/window.ts +++ b/src/vs/code/electron-main/window.ts @@ -502,7 +502,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { } private onWindowError(error: WindowError): void { - this.logService.error(error === WindowError.CRASHED ? '[VS Code]: render process crashed!' : '[VS Code]: detected unresponsive'); + this.logService.error(error === WindowError.CRASHED ? '[VS Code]: renderer process crashed!' : '[VS Code]: detected unresponsive'); // If we run extension tests from CLI, showing a dialog is not // very helpful in this case. Rather, we bring down the test run @@ -937,7 +937,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { // Multi Montior (fullscreen): try to find the previously used display if (state.display && state.mode === WindowMode.Fullscreen) { - const display = displays.filter(d => d.id === state.display)[0]; + const display = displays.find(d => d.id === state.display); if (display && typeof display.bounds?.x === 'number' && typeof display.bounds?.y === 'number') { this.logService.trace('window#validateWindowState: restoring fullscreen to previous display'); diff --git a/src/vs/code/node/cliProcessMain.ts b/src/vs/code/node/cliProcessMain.ts index 84fa97f9c0..2428e9deb4 100644 --- a/src/vs/code/node/cliProcessMain.ts +++ b/src/vs/code/node/cliProcessMain.ts @@ -216,7 +216,7 @@ export class Main { const extensionIdentifier = { id: getGalleryExtensionId(manifest.publisher, manifest.name) }; const installedExtensions = await this.extensionManagementService.getInstalled(ExtensionType.User); - const newer = installedExtensions.filter(local => areSameExtensions(extensionIdentifier, local.identifier) && semver.gt(local.manifest.version, manifest.version))[0]; + const newer = installedExtensions.find(local => areSameExtensions(extensionIdentifier, local.identifier) && semver.gt(local.manifest.version, manifest.version)); if (newer && !force) { console.log(localize('forceDowngrade', "A newer version of extension '{0}' v{1} is already installed. Use '--force' option to downgrade to older version.", newer.identifier.id, newer.manifest.version, manifest.version)); diff --git a/src/vs/editor/browser/core/editorState.ts b/src/vs/editor/browser/core/editorState.ts index 4910247c1f..7a20864f6e 100644 --- a/src/vs/editor/browser/core/editorState.ts +++ b/src/vs/editor/browser/core/editorState.ts @@ -156,12 +156,13 @@ export class StableEditorScrollState { visiblePositionScrollDelta = editor.getScrollTop() - visiblePositionScrollTop; } } - return new StableEditorScrollState(visiblePosition, visiblePositionScrollDelta); + return new StableEditorScrollState(visiblePosition, visiblePositionScrollDelta, editor.getPosition()); } constructor( private readonly _visiblePosition: Position | null, - private readonly _visiblePositionScrollDelta: number + private readonly _visiblePositionScrollDelta: number, + private readonly _cursorPosition: Position | null ) { } @@ -171,4 +172,15 @@ export class StableEditorScrollState { editor.setScrollTop(visiblePositionScrollTop + this._visiblePositionScrollDelta); } } + + public restoreRelativeVerticalPositionOfCursor(editor: ICodeEditor): void { + const currentCursorPosition = editor.getPosition(); + + if (!this._cursorPosition || !currentCursorPosition) { + return; + } + + const offset = editor.getTopForLineNumber(currentCursorPosition.lineNumber) - editor.getTopForLineNumber(this._cursorPosition.lineNumber); + editor.setScrollTop(editor.getScrollTop() + offset); + } } diff --git a/src/vs/editor/browser/editorExtensions.ts b/src/vs/editor/browser/editorExtensions.ts index 37503ccbb2..8ea1a02fce 100644 --- a/src/vs/editor/browser/editorExtensions.ts +++ b/src/vs/editor/browser/editorExtensions.ts @@ -21,6 +21,8 @@ import { IKeybindings, KeybindingsRegistry } from 'vs/platform/keybinding/common import { Registry } from 'vs/platform/registry/common/platform'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { withNullAsUndefined, assertType } from 'vs/base/common/types'; +import { ThemeIcon } from 'vs/platform/theme/common/themeService'; + export type ServicesAccessor = InstantiationServicesAccessor; export type IEditorContributionCtor = IConstructorSignature1; @@ -48,6 +50,7 @@ export interface ICommandMenuOptions { order: number; when?: ContextKeyExpression; title: string; + icon?: ThemeIcon } export interface ICommandOptions { id: string; @@ -118,6 +121,7 @@ export abstract class Command { command: { id: this.id, title: item.title, + icon: item.icon // precondition: this.precondition }, when: item.when, diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index 5006cc9eb4..29b864c080 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -1804,7 +1804,7 @@ export class EditorModeContext extends Disposable { this._hasDocumentSelectionFormattingProvider = EditorContextKeys.hasDocumentSelectionFormattingProvider.bindTo(_contextKeyService); this._hasMultipleDocumentFormattingProvider = EditorContextKeys.hasMultipleDocumentFormattingProvider.bindTo(_contextKeyService); this._hasMultipleDocumentSelectionFormattingProvider = EditorContextKeys.hasMultipleDocumentSelectionFormattingProvider.bindTo(_contextKeyService); - this._isInWalkThrough = EditorContextKeys.isInEmbeddedEditor.bindTo(_contextKeyService); + this._isInWalkThrough = EditorContextKeys.isInWalkThroughSnippet.bindTo(_contextKeyService); const update = () => this._update(); diff --git a/src/vs/editor/common/editorContextKeys.ts b/src/vs/editor/common/editorContextKeys.ts index 8d833bc65a..2f79a80c71 100644 --- a/src/vs/editor/common/editorContextKeys.ts +++ b/src/vs/editor/common/editorContextKeys.ts @@ -32,10 +32,17 @@ export namespace EditorContextKeys { export const hasSingleSelection = hasMultipleSelections.toNegated(); export const tabMovesFocus = new RawContextKey('editorTabMovesFocus', false); export const tabDoesNotMoveFocus = tabMovesFocus.toNegated(); - export const isInEmbeddedEditor = new RawContextKey('isInEmbeddedEditor', false); + export const isInWalkThroughSnippet = new RawContextKey('isInEmbeddedEditor', false); export const canUndo = new RawContextKey('canUndo', false); export const canRedo = new RawContextKey('canRedo', false); + /** + * A context key that is set when an editor is part of a larger editor, like notebooks or + * (future) a diff editor + */ + export const inCompositeEditor = new RawContextKey('inCompositeEditor', undefined); + export const notInCompositeEditor = inCompositeEditor.toNegated(); + // -- mode context keys export const languageId = new RawContextKey('editorLangId', ''); export const hasCompletionItemProvider = new RawContextKey('editorHasCompletionItemProvider', false); diff --git a/src/vs/editor/common/model.ts b/src/vs/editor/common/model.ts index 14aba0c709..c52d33a751 100644 --- a/src/vs/editor/common/model.ts +++ b/src/vs/editor/common/model.ts @@ -3,6 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { Event } from 'vs/base/common/event'; import { IMarkdownString } from 'vs/base/common/htmlContent'; import { IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; @@ -1276,7 +1277,8 @@ export class ValidAnnotatedEditOperation implements IIdentifiedSingleEditOperati /** * @internal */ -export interface ITextBuffer { +export interface IReadonlyTextBuffer { + onDidChangeContent: Event; equals(other: ITextBuffer): boolean; mightContainRTL(): boolean; mightContainNonBasicASCII(): boolean; @@ -1299,10 +1301,15 @@ export interface ITextBuffer { getLineLength(lineNumber: number): number; getLineFirstNonWhitespaceColumn(lineNumber: number): number; getLineLastNonWhitespaceColumn(lineNumber: number): number; + findMatchesLineByLine(searchRange: Range, searchData: SearchData, captureMatches: boolean, limitResultCount: number): FindMatch[]; +} +/** + * @internal + */ +export interface ITextBuffer extends IReadonlyTextBuffer { setEOL(newEOL: '\r\n' | '\n'): void; applyEdits(rawOperations: ValidAnnotatedEditOperation[], recordTrimAutoWhitespace: boolean, computeUndoEdits: boolean): ApplyEditsResult; - findMatchesLineByLine(searchRange: Range, searchData: SearchData, captureMatches: boolean, limitResultCount: number): FindMatch[]; } /** diff --git a/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer.ts b/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer.ts index 79a15b17be..4de4ee88a0 100644 --- a/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer.ts +++ b/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer.ts @@ -3,6 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { Emitter, Event } from 'vs/base/common/event'; import * as strings from 'vs/base/common/strings'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; @@ -11,6 +12,7 @@ import { PieceTreeBase, StringBuffer } from 'vs/editor/common/model/pieceTreeTex import { SearchData } from 'vs/editor/common/model/textModelSearch'; import { countEOL, StringEOL } from 'vs/editor/common/model/tokensStore'; import { TextChange } from 'vs/editor/common/model/textChange'; +import { IDisposable } from 'vs/base/common/lifecycle'; export interface IValidatedEditOperation { sortIndex: number; @@ -30,18 +32,24 @@ export interface IReverseSingleEditOperation extends IValidEditOperation { sortIndex: number; } -export class PieceTreeTextBuffer implements ITextBuffer { +export class PieceTreeTextBuffer implements ITextBuffer, IDisposable { private readonly _pieceTree: PieceTreeBase; private readonly _BOM: string; private _mightContainRTL: boolean; private _mightContainNonBasicASCII: boolean; + private readonly _onDidChangeContent: Emitter = new Emitter(); + public readonly onDidChangeContent: Event = this._onDidChangeContent.event; + constructor(chunks: StringBuffer[], BOM: string, eol: '\r\n' | '\n', containsRTL: boolean, isBasicASCII: boolean, eolNormalized: boolean) { this._BOM = BOM; this._mightContainNonBasicASCII = !isBasicASCII; this._mightContainRTL = containsRTL; this._pieceTree = new PieceTreeBase(chunks, eol, eolNormalized); } + dispose(): void { + this._onDidChangeContent.dispose(); + } // #region TextBuffer public equals(other: ITextBuffer): boolean { @@ -360,6 +368,8 @@ export class PieceTreeTextBuffer implements ITextBuffer { } } + this._onDidChangeContent.fire(); + return new ApplyEditsResult( reverseOperations, contentChanges, diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index 75c9c51e3f..87f82d1eed 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -608,7 +608,7 @@ export interface CompletionItemProvider { * * The editor will only resolve a completion item once. */ - resolveCompletionItem?(model: model.ITextModel, position: Position, item: CompletionItem, token: CancellationToken): ProviderResult; + resolveCompletionItem?(item: CompletionItem, token: CancellationToken): ProviderResult; } export interface CodeAction { diff --git a/src/vs/editor/common/services/markerDecorationsServiceImpl.ts b/src/vs/editor/common/services/markerDecorationsServiceImpl.ts index 8623983ba8..d097d7b816 100644 --- a/src/vs/editor/common/services/markerDecorationsServiceImpl.ts +++ b/src/vs/editor/common/services/markerDecorationsServiceImpl.ts @@ -18,6 +18,7 @@ import { Schemas } from 'vs/base/common/network'; import { Emitter, Event } from 'vs/base/common/event'; import { withUndefinedAsNull } from 'vs/base/common/types'; import { minimapWarning, minimapError } from 'vs/platform/theme/common/colorRegistry'; +import { Delayer } from 'vs/base/common/async'; function MODEL_ID(resource: URI): string { return resource.toString(); @@ -41,13 +42,14 @@ class MarkerDecorations extends Disposable { return super._register(t); } - public update(markers: IMarker[], newDecorations: IModelDeltaDecoration[]): void { + public update(markers: IMarker[], newDecorations: IModelDeltaDecoration[]): boolean { const oldIds = keys(this._markersData); this._markersData.clear(); const ids = this.model.deltaDecorations(oldIds, newDecorations); for (let index = 0; index < ids.length; index++) { this._markersData.set(ids[index], markers[index]); } + return oldIds.length !== 0 || ids.length !== 0; } getMarker(decoration: IModelDecoration): IMarker | undefined { @@ -114,7 +116,8 @@ export class MarkerDecorationsService extends Disposable implements IMarkerDecor private _onModelAdded(model: ITextModel): void { const markerDecorations = new MarkerDecorations(model); this._markerDecorations.set(MODEL_ID(model.uri), markerDecorations); - markerDecorations.register(model.onDidChangeContent(() => this._updateDecorations(markerDecorations))); + const delayer = markerDecorations.register(new Delayer(100)); + markerDecorations.register(model.onDidChangeContent(() => delayer.trigger(() => this._updateDecorations(markerDecorations)))); this._updateDecorations(markerDecorations); } @@ -144,8 +147,9 @@ export class MarkerDecorationsService extends Disposable implements IMarkerDecor options: this._createDecorationOption(marker) }; }); - markerDecorations.update(markers, newModelDecorations); - this._onDidChangeMarker.fire(markerDecorations.model); + if (markerDecorations.update(markers, newModelDecorations)) { + this._onDidChangeMarker.fire(markerDecorations.model); + } } private _createDecorationRange(model: ITextModel, rawMarker: IMarker): Range { diff --git a/src/vs/editor/common/services/semanticTokensProviderStyling.ts b/src/vs/editor/common/services/semanticTokensProviderStyling.ts index 7acd0dd240..a717c7ad06 100644 --- a/src/vs/editor/common/services/semanticTokensProviderStyling.ts +++ b/src/vs/editor/common/services/semanticTokensProviderStyling.ts @@ -29,50 +29,65 @@ export class SemanticTokensProviderStyling { let metadata: number; if (entry) { metadata = entry.metadata; - } else { - const tokenType = this._legend.tokenTypes[tokenTypeIndex]; - const tokenModifiers: string[] = []; - let modifierSet = tokenModifierSet; - for (let modifierIndex = 0; modifierSet > 0 && modifierIndex < this._legend.tokenModifiers.length; modifierIndex++) { - if (modifierSet & 1) { - tokenModifiers.push(this._legend.tokenModifiers[modifierIndex]); - } - modifierSet = modifierSet >> 1; + if (this._logService.getLevel() === LogLevel.Trace) { + this._logService.trace(`SemanticTokensProviderStyling [CACHED] ${tokenTypeIndex} / ${tokenModifierSet}: foreground ${TokenMetadata.getForeground(metadata)}, fontStyle ${TokenMetadata.getFontStyle(metadata).toString(2)}`); } + } else { + let tokenType = this._legend.tokenTypes[tokenTypeIndex]; + const tokenModifiers: string[] = []; + if (tokenType) { + let modifierSet = tokenModifierSet; + for (let modifierIndex = 0; modifierSet > 0 && modifierIndex < this._legend.tokenModifiers.length; modifierIndex++) { + if (modifierSet & 1) { + tokenModifiers.push(this._legend.tokenModifiers[modifierIndex]); + } + modifierSet = modifierSet >> 1; + } + if (modifierSet > 0 && this._logService.getLevel() === LogLevel.Trace) { + this._logService.trace(`SemanticTokensProviderStyling: unknown token modifier index: ${tokenModifierSet.toString(2)} for legend: ${JSON.stringify(this._legend.tokenModifiers)}`); + tokenModifiers.push('not-in-legend'); + } - const tokenStyle = this._themeService.getColorTheme().getTokenStyleMetadata(tokenType, tokenModifiers, languageId.language); - if (typeof tokenStyle === 'undefined') { - metadata = SemanticTokensProviderStylingConstants.NO_STYLING; - } else { - metadata = 0; - if (typeof tokenStyle.italic !== 'undefined') { - const italicBit = (tokenStyle.italic ? FontStyle.Italic : 0) << MetadataConsts.FONT_STYLE_OFFSET; - metadata |= italicBit | MetadataConsts.SEMANTIC_USE_ITALIC; - } - if (typeof tokenStyle.bold !== 'undefined') { - const boldBit = (tokenStyle.bold ? FontStyle.Bold : 0) << MetadataConsts.FONT_STYLE_OFFSET; - metadata |= boldBit | MetadataConsts.SEMANTIC_USE_BOLD; - } - if (typeof tokenStyle.underline !== 'undefined') { - const underlineBit = (tokenStyle.underline ? FontStyle.Underline : 0) << MetadataConsts.FONT_STYLE_OFFSET; - metadata |= underlineBit | MetadataConsts.SEMANTIC_USE_UNDERLINE; - } - if (tokenStyle.foreground) { - const foregroundBits = (tokenStyle.foreground) << MetadataConsts.FOREGROUND_OFFSET; - metadata |= foregroundBits | MetadataConsts.SEMANTIC_USE_FOREGROUND; - } - if (metadata === 0) { - // Nothing! + const tokenStyle = this._themeService.getColorTheme().getTokenStyleMetadata(tokenType, tokenModifiers, languageId.language); + if (typeof tokenStyle === 'undefined') { metadata = SemanticTokensProviderStylingConstants.NO_STYLING; + } else { + metadata = 0; + if (typeof tokenStyle.italic !== 'undefined') { + const italicBit = (tokenStyle.italic ? FontStyle.Italic : 0) << MetadataConsts.FONT_STYLE_OFFSET; + metadata |= italicBit | MetadataConsts.SEMANTIC_USE_ITALIC; + } + if (typeof tokenStyle.bold !== 'undefined') { + const boldBit = (tokenStyle.bold ? FontStyle.Bold : 0) << MetadataConsts.FONT_STYLE_OFFSET; + metadata |= boldBit | MetadataConsts.SEMANTIC_USE_BOLD; + } + if (typeof tokenStyle.underline !== 'undefined') { + const underlineBit = (tokenStyle.underline ? FontStyle.Underline : 0) << MetadataConsts.FONT_STYLE_OFFSET; + metadata |= underlineBit | MetadataConsts.SEMANTIC_USE_UNDERLINE; + } + if (tokenStyle.foreground) { + const foregroundBits = (tokenStyle.foreground) << MetadataConsts.FOREGROUND_OFFSET; + metadata |= foregroundBits | MetadataConsts.SEMANTIC_USE_FOREGROUND; + } + if (metadata === 0) { + // Nothing! + metadata = SemanticTokensProviderStylingConstants.NO_STYLING; + } } + } else { + if (this._logService.getLevel() === LogLevel.Trace) { + this._logService.trace(`SemanticTokensProviderStyling: unknown token type index: ${tokenTypeIndex} for legend: ${JSON.stringify(this._legend.tokenTypes)}`); + } + metadata = SemanticTokensProviderStylingConstants.NO_STYLING; + tokenType = 'not-in-legend'; } this._hashTable.add(tokenTypeIndex, tokenModifierSet, languageId.id, metadata); + + if (this._logService.getLevel() === LogLevel.Trace) { + this._logService.trace(`SemanticTokensProviderStyling ${tokenTypeIndex} (${tokenType}) / ${tokenModifierSet} (${tokenModifiers.join(' ')}): foreground ${TokenMetadata.getForeground(metadata)}, fontStyle ${TokenMetadata.getFontStyle(metadata).toString(2)}`); + } } - if (this._logService.getLevel() === LogLevel.Trace) { - const type = this._legend.tokenTypes[tokenTypeIndex]; - const modifiers = tokenModifierSet ? ' ' + this._legend.tokenModifiers.filter((_, i) => tokenModifierSet & (1 << i)).join(' ') : ''; - this._logService.trace(`tokenStyleMetadata ${entry ? '[CACHED] ' : ''}${type}${modifiers}: foreground ${TokenMetadata.getForeground(metadata)}, fontStyle ${TokenMetadata.getFontStyle(metadata).toString(2)}`); - } + return metadata; } } diff --git a/src/vs/editor/contrib/codelens/codelensWidget.ts b/src/vs/editor/contrib/codelens/codelensWidget.ts index 4899f14190..99fc0e72bb 100644 --- a/src/vs/editor/contrib/codelens/codelensWidget.ts +++ b/src/vs/editor/contrib/codelens/codelensWidget.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./codelensWidget'; -import * as dom from 'vs/base/browser/dom'; import { renderCodicons } from 'vs/base/common/codicons'; import { escape } from 'vs/base/common/strings'; import { IViewZone, IContentWidget, IActiveCodeEditor, IContentWidgetPosition, ContentWidgetPositionPreference, IViewZoneChangeAccessor } from 'vs/editor/browser/editorBrowser'; @@ -113,7 +112,7 @@ class CodeLensContentWidget implements IContentWidget { } this._domNode.innerHTML = innerHtml; if (this._isEmpty && animate) { - dom.addClass(this._domNode, 'fadein'); + this._domNode.classList.add('fadein'); } this._isEmpty = false; } diff --git a/src/vs/editor/contrib/documentSymbols/outlineTree.ts b/src/vs/editor/contrib/documentSymbols/outlineTree.ts index 09c1bf7660..094f08dfec 100644 --- a/src/vs/editor/contrib/documentSymbols/outlineTree.ts +++ b/src/vs/editor/contrib/documentSymbols/outlineTree.ts @@ -102,7 +102,7 @@ export class OutlineGroupRenderer implements ITreeRenderer 0) { dom.show(template.decoration); - dom.removeClass(template.decoration, 'bubble'); + template.decoration.classList.remove('bubble'); template.decoration.innerText = count < 10 ? count.toString() : '+9'; template.decoration.title = count === 1 ? localize('1.problem', "1 problem in this element") : localize('N.problem', "{0} problems in this element", count); template.decoration.style.setProperty('--outline-element-color', cssColor); } else { dom.show(template.decoration); - dom.addClass(template.decoration, 'bubble'); + template.decoration.classList.add('bubble'); template.decoration.innerText = '\uea71'; template.decoration.title = localize('deep.problem', "Contains elements with problems"); template.decoration.style.setProperty('--outline-element-color', cssColor); diff --git a/src/vs/editor/contrib/format/formatActions.ts b/src/vs/editor/contrib/format/formatActions.ts index c6d3ced423..3843f71f74 100644 --- a/src/vs/editor/contrib/format/formatActions.ts +++ b/src/vs/editor/contrib/format/formatActions.ts @@ -213,7 +213,7 @@ class FormatDocumentAction extends EditorAction { id: 'editor.action.formatDocument', label: nls.localize('formatDocument.label', "Format Document"), alias: 'Format Document', - precondition: ContextKeyExpr.and(EditorContextKeys.writable, EditorContextKeys.hasDocumentFormattingProvider), + precondition: ContextKeyExpr.and(EditorContextKeys.notInCompositeEditor, EditorContextKeys.writable, EditorContextKeys.hasDocumentFormattingProvider), kbOpts: { kbExpr: ContextKeyExpr.and(EditorContextKeys.editorTextFocus, EditorContextKeys.hasDocumentFormattingProvider), primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_F, diff --git a/src/vs/editor/contrib/gotoError/gotoError.ts b/src/vs/editor/contrib/gotoError/gotoError.ts index 479e794eba..28b9071669 100644 --- a/src/vs/editor/contrib/gotoError/gotoError.ts +++ b/src/vs/editor/contrib/gotoError/gotoError.ts @@ -15,7 +15,6 @@ import { Range } from 'vs/editor/common/core/range'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { registerEditorAction, registerEditorContribution, ServicesAccessor, IActionOptions, EditorAction, EditorCommand, registerEditorCommand } from 'vs/editor/browser/editorExtensions'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { MarkerNavigationWidget } from './gotoErrorWidget'; @@ -24,12 +23,10 @@ import { binarySearch, find } from 'vs/base/common/arrays'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { onUnexpectedError } from 'vs/base/common/errors'; import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; -import { Action } from 'vs/base/common/actions'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { isEqual } from 'vs/base/common/resources'; -import { IOpenerService } from 'vs/platform/opener/common/opener'; import { TextEditorSelectionRevealType } from 'vs/platform/editor/common/editor'; import { Codicon, registerIcon } from 'vs/base/common/codicons'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; class MarkerModel { @@ -192,9 +189,6 @@ class MarkerModel { } } -const markerNavigationNextIcon = registerIcon('marker-navigation-next', Codicon.chevronDown); -const markerNavigationPreviousIcon = registerIcon('marker-navigation-previous', Codicon.chevronUp); - export class MarkerController implements IEditorContribution { public static readonly ID = 'editor.contrib.markerController'; @@ -213,10 +207,8 @@ export class MarkerController implements IEditorContribution { editor: ICodeEditor, @IMarkerService private readonly _markerService: IMarkerService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, - @IThemeService private readonly _themeService: IThemeService, @ICodeEditorService private readonly _editorService: ICodeEditorService, - @IKeybindingService private readonly _keybindingService: IKeybindingService, - @IOpenerService private readonly _openerService: IOpenerService + @IInstantiationService private readonly _instantiationService: IInstantiationService, ) { this._editor = editor; this._widgetVisible = CONTEXT_MARKERS_NAVIGATION_VISIBLE.bindTo(this._contextKeyService); @@ -244,21 +236,13 @@ export class MarkerController implements IEditorContribution { this._model = new MarkerModel(this._editor, markers); this._markerService.onMarkerChanged(this._onMarkerChanged, this, this._disposeOnClose); - const prevMarkerKeybinding = this._keybindingService.lookupKeybinding(PrevMarkerAction.ID); - const nextMarkerKeybinding = this._keybindingService.lookupKeybinding(NextMarkerAction.ID); - const actions = [ - new Action(NextMarkerAction.ID, NextMarkerAction.LABEL + (nextMarkerKeybinding ? ` (${nextMarkerKeybinding.getLabel()})` : ''), 'show-next-problem ' + markerNavigationNextIcon.classNames, this._model.canNavigate(), async () => { if (this._model) { this._model.move(true, true); } }), - new Action(PrevMarkerAction.ID, PrevMarkerAction.LABEL + (prevMarkerKeybinding ? ` (${prevMarkerKeybinding.getLabel()})` : ''), 'show-previous-problem ' + markerNavigationPreviousIcon.classNames, this._model.canNavigate(), async () => { if (this._model) { this._model.move(false, true); } }) - ]; - this._widget = new MarkerNavigationWidget(this._editor, actions, this._themeService, this._openerService); + this._widget = this._instantiationService.createInstance(MarkerNavigationWidget, this._editor); this._widgetVisible.set(true); this._widget.onDidClose(() => this.closeMarkersNavigation(), this, this._disposeOnClose); this._disposeOnClose.add(this._model); this._disposeOnClose.add(this._widget); - for (const action of actions) { - this._disposeOnClose.add(action); - } + this._disposeOnClose.add(this._widget.onDidSelectRelatedInformation(related => { this._editorService.openCodeEditor({ resource: related.resource, @@ -431,7 +415,18 @@ export class NextMarkerAction extends MarkerNavigationAction { label: NextMarkerAction.LABEL, alias: 'Go to Next Problem (Error, Warning, Info)', precondition: undefined, - kbOpts: { kbExpr: EditorContextKeys.focus, primary: KeyMod.Alt | KeyCode.F8, weight: KeybindingWeight.EditorContrib } + kbOpts: { + kbExpr: EditorContextKeys.focus, + primary: KeyMod.Alt | KeyCode.F8, + weight: KeybindingWeight.EditorContrib + }, + menuOpts: { + menuId: MarkerNavigationWidget.TitleMenu, + title: NextMarkerAction.LABEL, + icon: registerIcon('marker-navigation-next', Codicon.chevronDown), + group: 'navigation', + order: 1 + } }); } } @@ -445,7 +440,18 @@ class PrevMarkerAction extends MarkerNavigationAction { label: PrevMarkerAction.LABEL, alias: 'Go to Previous Problem (Error, Warning, Info)', precondition: undefined, - kbOpts: { kbExpr: EditorContextKeys.focus, primary: KeyMod.Shift | KeyMod.Alt | KeyCode.F8, weight: KeybindingWeight.EditorContrib } + kbOpts: { + kbExpr: EditorContextKeys.focus, + primary: KeyMod.Shift | KeyMod.Alt | KeyCode.F8, + weight: KeybindingWeight.EditorContrib + }, + menuOpts: { + menuId: MarkerNavigationWidget.TitleMenu, + title: NextMarkerAction.LABEL, + icon: registerIcon('marker-navigation-previous', Codicon.chevronUp), + group: 'navigation', + order: 2 + } }); } } diff --git a/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts b/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts index b7684dbdb7..3910aaebaa 100644 --- a/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts +++ b/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts @@ -27,6 +27,10 @@ import { IActionBarOptions, ActionsOrientation } from 'vs/base/browser/ui/action import { SeverityIcon } from 'vs/platform/severityIcon/common/severityIcon'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { MenuId, IMenuService, MenuItemAction } from 'vs/platform/actions/common/actions'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { createAndFillInActionBarActions, MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; class MessageWidget { @@ -224,6 +228,8 @@ class MessageWidget { export class MarkerNavigationWidget extends PeekViewWidget { + static readonly TitleMenu = new MenuId('gotoErrorTitleMenu'); + private _parentContainer!: HTMLElement; private _container!: HTMLElement; private _icon!: HTMLElement; @@ -238,9 +244,11 @@ export class MarkerNavigationWidget extends PeekViewWidget { constructor( editor: ICodeEditor, - private readonly actions: ReadonlyArray, - private readonly _themeService: IThemeService, - private readonly _openerService: IOpenerService + @IThemeService private readonly _themeService: IThemeService, + @IOpenerService private readonly _openerService: IOpenerService, + @IMenuService private readonly _menuService: IMenuService, + @IContextKeyService private readonly _contextKeyService: IContextKeyService, + @IInstantiationService private readonly _instantiationService: IInstantiationService, ) { super(editor, { showArrow: true, showFrame: true, isAccessible: true }); this._severity = MarkerSeverity.Warning; @@ -288,7 +296,11 @@ export class MarkerNavigationWidget extends PeekViewWidget { protected _fillHead(container: HTMLElement): void { super._fillHead(container); - this._actionbarWidget!.push(this.actions, { label: false, icon: true, index: 0 }); + const actions: IAction[] = []; + const menu = this._menuService.createMenu(MarkerNavigationWidget.TitleMenu, this._contextKeyService); + createAndFillInActionBarActions(menu, undefined, actions); + this._actionbarWidget!.push(actions, { label: false, icon: true, index: 0 }); + menu.dispose(); } protected _fillTitleIcon(container: HTMLElement): void { @@ -297,7 +309,8 @@ export class MarkerNavigationWidget extends PeekViewWidget { protected _getActionBarOptions(): IActionBarOptions { return { - orientation: ActionsOrientation.HORIZONTAL + orientation: ActionsOrientation.HORIZONTAL, + actionViewItemProvider: action => action instanceof MenuItemAction ? this._instantiationService.createInstance(MenuEntryActionViewItem, action) : undefined }; } diff --git a/src/vs/editor/contrib/gotoSymbol/goToCommands.ts b/src/vs/editor/contrib/gotoSymbol/goToCommands.ts index 4a11d6c979..331dfbc4ca 100644 --- a/src/vs/editor/contrib/gotoSymbol/goToCommands.ts +++ b/src/vs/editor/contrib/gotoSymbol/goToCommands.ts @@ -240,7 +240,7 @@ registerEditorAction(class GoToDefinitionAction extends DefinitionAction { alias: 'Go to Definition', precondition: ContextKeyExpr.and( EditorContextKeys.hasDefinitionProvider, - EditorContextKeys.isInEmbeddedEditor.toNegated()), + EditorContextKeys.isInWalkThroughSnippet.toNegated()), kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, primary: goToDefinitionKb, @@ -276,7 +276,7 @@ registerEditorAction(class OpenDefinitionToSideAction extends DefinitionAction { alias: 'Open Definition to the Side', precondition: ContextKeyExpr.and( EditorContextKeys.hasDefinitionProvider, - EditorContextKeys.isInEmbeddedEditor.toNegated()), + EditorContextKeys.isInWalkThroughSnippet.toNegated()), kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, goToDefinitionKb), @@ -303,7 +303,7 @@ registerEditorAction(class PeekDefinitionAction extends DefinitionAction { precondition: ContextKeyExpr.and( EditorContextKeys.hasDefinitionProvider, PeekContext.notInPeekEditor, - EditorContextKeys.isInEmbeddedEditor.toNegated() + EditorContextKeys.isInWalkThroughSnippet.toNegated() ), kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, @@ -361,7 +361,7 @@ registerEditorAction(class GoToDeclarationAction extends DeclarationAction { alias: 'Go to Declaration', precondition: ContextKeyExpr.and( EditorContextKeys.hasDeclarationProvider, - EditorContextKeys.isInEmbeddedEditor.toNegated() + EditorContextKeys.isInWalkThroughSnippet.toNegated() ), contextMenuOpts: { group: 'navigation', @@ -396,7 +396,7 @@ registerEditorAction(class PeekDeclarationAction extends DeclarationAction { precondition: ContextKeyExpr.and( EditorContextKeys.hasDeclarationProvider, PeekContext.notInPeekEditor, - EditorContextKeys.isInEmbeddedEditor.toNegated() + EditorContextKeys.isInWalkThroughSnippet.toNegated() ), contextMenuOpts: { menuId: MenuId.EditorContextPeek, @@ -447,7 +447,7 @@ registerEditorAction(class GoToTypeDefinitionAction extends TypeDefinitionAction alias: 'Go to Type Definition', precondition: ContextKeyExpr.and( EditorContextKeys.hasTypeDefinitionProvider, - EditorContextKeys.isInEmbeddedEditor.toNegated()), + EditorContextKeys.isInWalkThroughSnippet.toNegated()), kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, primary: 0, @@ -483,7 +483,7 @@ registerEditorAction(class PeekTypeDefinitionAction extends TypeDefinitionAction precondition: ContextKeyExpr.and( EditorContextKeys.hasTypeDefinitionProvider, PeekContext.notInPeekEditor, - EditorContextKeys.isInEmbeddedEditor.toNegated() + EditorContextKeys.isInWalkThroughSnippet.toNegated() ), contextMenuOpts: { menuId: MenuId.EditorContextPeek, @@ -534,7 +534,7 @@ registerEditorAction(class GoToImplementationAction extends ImplementationAction alias: 'Go to Implementations', precondition: ContextKeyExpr.and( EditorContextKeys.hasImplementationProvider, - EditorContextKeys.isInEmbeddedEditor.toNegated()), + EditorContextKeys.isInWalkThroughSnippet.toNegated()), kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, primary: KeyMod.CtrlCmd | KeyCode.F12, @@ -570,7 +570,7 @@ registerEditorAction(class PeekImplementationAction extends ImplementationAction precondition: ContextKeyExpr.and( EditorContextKeys.hasImplementationProvider, PeekContext.notInPeekEditor, - EditorContextKeys.isInEmbeddedEditor.toNegated() + EditorContextKeys.isInWalkThroughSnippet.toNegated() ), kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, @@ -621,7 +621,7 @@ registerEditorAction(class GoToReferencesAction extends ReferencesAction { precondition: ContextKeyExpr.and( EditorContextKeys.hasReferenceProvider, PeekContext.notInPeekEditor, - EditorContextKeys.isInEmbeddedEditor.toNegated() + EditorContextKeys.isInWalkThroughSnippet.toNegated() ), kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, @@ -660,7 +660,7 @@ registerEditorAction(class PeekReferencesAction extends ReferencesAction { precondition: ContextKeyExpr.and( EditorContextKeys.hasReferenceProvider, PeekContext.notInPeekEditor, - EditorContextKeys.isInEmbeddedEditor.toNegated() + EditorContextKeys.isInWalkThroughSnippet.toNegated() ), contextMenuOpts: { menuId: MenuId.EditorContextPeek, @@ -693,7 +693,7 @@ class GenericGoToLocationAction extends SymbolNavigationAction { alias: 'Go To Any Symbol', precondition: ContextKeyExpr.and( PeekContext.notInPeekEditor, - EditorContextKeys.isInEmbeddedEditor.toNegated() + EditorContextKeys.isInWalkThroughSnippet.toNegated() ), }); } diff --git a/src/vs/editor/contrib/gotoSymbol/peek/referencesTree.ts b/src/vs/editor/contrib/gotoSymbol/peek/referencesTree.ts index 959bb675a9..abf3b12ec3 100644 --- a/src/vs/editor/contrib/gotoSymbol/peek/referencesTree.ts +++ b/src/vs/editor/contrib/gotoSymbol/peek/referencesTree.ts @@ -119,7 +119,7 @@ class FileReferencesTemplate extends Disposable { ) { super(); const parent = document.createElement('div'); - dom.addClass(parent, 'reference-file'); + parent.classList.add('reference-file'); this.file = this._register(new IconLabel(parent, { supportHighlights: true })); this.badge = new CountBadge(dom.append(parent, dom.$('.count'))); @@ -184,10 +184,10 @@ class OneReferenceTemplate { // we have score, then render the score const { value, highlight } = preview; if (score && !FuzzyScore.isDefault(score)) { - dom.toggleClass(this.label.element, 'referenceMatch', false); + this.label.element.classList.toggle('referenceMatch', false); this.label.set(value, createMatches(score)); } else { - dom.toggleClass(this.label.element, 'referenceMatch', true); + this.label.element.classList.toggle('referenceMatch', true); this.label.set(value, [highlight]); } } diff --git a/src/vs/editor/contrib/gotoSymbol/peek/referencesWidget.ts b/src/vs/editor/contrib/gotoSymbol/peek/referencesWidget.ts index 368a560fec..3f0ba69d27 100644 --- a/src/vs/editor/contrib/gotoSymbol/peek/referencesWidget.ts +++ b/src/vs/editor/contrib/gotoSymbol/peek/referencesWidget.ts @@ -465,7 +465,7 @@ export class ReferenceWidget extends peekView.PeekViewWidget { })); // make sure things are rendered - dom.addClass(this.container!, 'results-loaded'); + this.container!.classList.add('results-loaded'); dom.show(this._treeContainer); dom.show(this._previewContainer); this._splitView.layout(this._dim.width); diff --git a/src/vs/editor/contrib/hover/hover.css b/src/vs/editor/contrib/hover/hover.css index b5438b3a19..2e6a7e14ff 100644 --- a/src/vs/editor/contrib/hover/hover.css +++ b/src/vs/editor/contrib/hover/hover.css @@ -30,7 +30,8 @@ } .monaco-editor-hover .markdown-hover > .hover-contents:not(.code-hover-contents) hr { - min-width: 100%; + /* This is a strange rule but it avoids https://github.com/microsoft/vscode/issues/96795, just 100vw on its own caused the actual hover width to increase */ + min-width: calc(100% + 100vw); } .monaco-editor-hover p, diff --git a/src/vs/editor/contrib/rename/renameInputField.ts b/src/vs/editor/contrib/rename/renameInputField.ts index 34c678d845..33b4d0bc49 100644 --- a/src/vs/editor/contrib/rename/renameInputField.ts +++ b/src/vs/editor/contrib/rename/renameInputField.ts @@ -15,7 +15,6 @@ import { inputBackground, inputBorder, inputForeground, widgetShadow, editorWidg import { IColorTheme, IThemeService } from 'vs/platform/theme/common/themeService'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { toggleClass } from 'vs/base/browser/dom'; import { CancellationToken } from 'vs/base/common/cancellation'; export const CONTEXT_RENAME_INPUT_VISIBLE = new RawContextKey('renameInputVisible', false); @@ -152,7 +151,7 @@ export class RenameInputField implements IContentWidget { getInput(where: IRange, value: string, selectionStart: number, selectionEnd: number, supportPreview: boolean, token: CancellationToken): Promise { - toggleClass(this._domNode!, 'preview', supportPreview); + this._domNode!.classList.toggle('preview', supportPreview); this._position = new Position(where.startLineNumber, where.startColumn); this._input!.value = value; diff --git a/src/vs/editor/contrib/snippet/snippetController2.ts b/src/vs/editor/contrib/snippet/snippetController2.ts index d46c767379..1be10181c4 100644 --- a/src/vs/editor/contrib/snippet/snippetController2.ts +++ b/src/vs/editor/contrib/snippet/snippetController2.ts @@ -5,7 +5,6 @@ import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { dispose, DisposableStore } from 'vs/base/common/lifecycle'; -import { repeat } from 'vs/base/common/strings'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorCommand, registerEditorCommand, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { Range } from 'vs/editor/common/core/range'; @@ -191,7 +190,7 @@ export class SnippetController2 implements IEditorContribution { insertText: option.value, // insertText: `\${1|${after.concat(before).join(',')}|}$0`, // snippetType: 'textmate', - sortText: repeat('a', i + 1), + sortText: 'a'.repeat(i + 1), range: Range.fromPositions(this._editor.getPosition()!, this._editor.getPosition()!.delta(0, first.value.length)) }; })); diff --git a/src/vs/editor/contrib/suggest/completionModel.ts b/src/vs/editor/contrib/suggest/completionModel.ts index 1c69c86a2c..dacfcaaa75 100644 --- a/src/vs/editor/contrib/suggest/completionModel.ts +++ b/src/vs/editor/contrib/suggest/completionModel.ts @@ -101,7 +101,7 @@ export class CompletionModel { } adopt(except: Set): CompletionItem[] { - let res = new Array(); + let res: CompletionItem[] = []; for (let i = 0; i < this._items.length;) { if (!except.has(this._items[i].provider)) { res.push(this._items[i]); @@ -151,6 +151,10 @@ export class CompletionModel { const item = source[i]; + if (item.isInvalid) { + continue; // SKIP invalid items + } + // collect those supports that signaled having // an incomplete result if (item.container.incomplete) { diff --git a/src/vs/editor/contrib/suggest/suggest.ts b/src/vs/editor/contrib/suggest/suggest.ts index 6d65b8481d..cbbdeb904b 100644 --- a/src/vs/editor/contrib/suggest/suggest.ts +++ b/src/vs/editor/contrib/suggest/suggest.ts @@ -3,7 +3,6 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { first } from 'vs/base/common/async'; import { onUnexpectedExternalError, canceled, isPromiseCanceledError } from 'vs/base/common/errors'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; @@ -34,12 +33,6 @@ export class CompletionItem { _brand!: 'ISuggestionItem'; - private static readonly _defaultResolve = () => Promise.resolve(); - - readonly resolve: (token: CancellationToken) => Promise; - isResolved: boolean = false; - - // readonly editStart: IPosition; readonly editInsertEnd: IPosition; @@ -53,6 +46,9 @@ export class CompletionItem { readonly sortTextLow?: string; readonly filterTextLow?: string; + // validation + readonly isInvalid: boolean = false; + // sorting, filtering score: FuzzyScore = FuzzyScore.Default; distance: number = 0; @@ -73,6 +69,9 @@ export class CompletionItem { // ensure lower-variants (perf) this.labelLow = this.textLabel.toLowerCase(); + // validate label + this.isInvalid = !this.textLabel; + this.sortTextLow = completion.sortText && completion.sortText.toLowerCase(); this.filterTextLow = completion.filterText && completion.filterText.toLowerCase(); @@ -81,43 +80,54 @@ export class CompletionItem { this.editStart = new Position(completion.range.startLineNumber, completion.range.startColumn); this.editInsertEnd = new Position(completion.range.endLineNumber, completion.range.endColumn); this.editReplaceEnd = new Position(completion.range.endLineNumber, completion.range.endColumn); + + // validate range + this.isInvalid = this.isInvalid + || Range.spansMultipleLines(completion.range) || completion.range.startLineNumber !== position.lineNumber; + } else { this.editStart = new Position(completion.range.insert.startLineNumber, completion.range.insert.startColumn); this.editInsertEnd = new Position(completion.range.insert.endLineNumber, completion.range.insert.endColumn); this.editReplaceEnd = new Position(completion.range.replace.endLineNumber, completion.range.replace.endColumn); + + // validate ranges + this.isInvalid = this.isInvalid + || Range.spansMultipleLines(completion.range.insert) || Range.spansMultipleLines(completion.range.replace) + || completion.range.insert.startLineNumber !== position.lineNumber || completion.range.replace.startLineNumber !== position.lineNumber + || Range.compareRangesUsingStarts(completion.range.insert, completion.range.replace) !== 0; } // create the suggestion resolver - const { resolveCompletionItem } = provider; - if (typeof resolveCompletionItem !== 'function') { - this.resolve = CompletionItem._defaultResolve; - this.isResolved = true; - } else { - let cached: Promise | undefined; - this.resolve = (token) => { - if (!cached) { - cached = Promise.resolve(resolveCompletionItem.call(provider, model, Position.lift(position), completion, token)).then(value => { - Object.assign(completion, value); - this.isResolved = true; - }, err => { - if (isPromiseCanceledError(err)) { - // the IPC queue will reject the request with the - // cancellation error -> reset cached - cached = undefined; - } - }); - token.onCancellationRequested(() => { - if (!this.isResolved) { - // cancellation after the request has been - // dispatched -> reset cache - cached = undefined; - } - }); - } - return cached; - }; + if (typeof provider.resolveCompletionItem !== 'function') { + this._resolveCache = Promise.resolve(); } } + + // resolving + get isResolved() { + return Boolean(this._resolveCache); + } + + private _resolveCache?: Promise; + + async resolve(token: CancellationToken) { + if (!this._resolveCache) { + const sub = token.onCancellationRequested(() => { + this._resolveCache = undefined; + }); + this._resolveCache = Promise.resolve(this.provider.resolveCompletionItem!(this.completion, token)).then(value => { + Object.assign(this.completion, value); + sub.dispose(); + }, err => { + if (isPromiseCanceledError(err)) { + // the IPC queue will reject the request with the + // cancellation error -> reset cached + this._resolveCache = undefined; + } + }); + } + return this._resolveCache; + } } export const enum SnippetSortOrder { @@ -147,7 +157,7 @@ export function setSnippetSuggestSupport(support: modes.CompletionItemProvider): return old; } -export function provideSuggestionItems( +export async function provideSuggestionItems( model: ITextModel, position: Position, options: CompletionOptions = CompletionOptions.default, @@ -155,89 +165,86 @@ export function provideSuggestionItems( token: CancellationToken = CancellationToken.None ): Promise { - const word = model.getWordAtPosition(position); - const defaultReplaceRange = word ? new Range(position.lineNumber, word.startColumn, position.lineNumber, word.endColumn) : Range.fromPositions(position); - const defaultInsertRange = defaultReplaceRange.setEndPosition(position.lineNumber, position.column); - - // const wordUntil = model.getWordUntilPosition(position); - // const defaultRange = new Range(position.lineNumber, wordUntil.startColumn, position.lineNumber, wordUntil.endColumn); - + // const t1 = Date.now(); position = position.clone(); - // get provider groups, always add snippet suggestion provider - const supports = modes.CompletionProviderRegistry.orderedGroups(model); + const word = model.getWordAtPosition(position); + const defaultReplaceRange = word ? new Range(position.lineNumber, word.startColumn, position.lineNumber, word.endColumn) : Range.fromPositions(position); + const defaultRange = { replace: defaultReplaceRange, insert: defaultReplaceRange.setEndPosition(position.lineNumber, position.column) }; - // add snippets provider unless turned off - if (!options.kindFilter.has(modes.CompletionItemKind.Snippet) && _snippetSuggestSupport) { - supports.unshift([_snippetSuggestSupport]); - } - - const allSuggestions: CompletionItem[] = []; + const result: CompletionItem[] = []; const disposables = new DisposableStore(); - let hasResult = false; + + const onCompletionList = (provider: modes.CompletionItemProvider, container: modes.CompletionList | null | undefined) => { + if (!container) { + return; + } + for (let suggestion of container.suggestions || []) { + if (!options.kindFilter.has(suggestion.kind)) { + // fill in default range when missing + if (!suggestion.range) { + suggestion.range = defaultRange; + } + // fill in default sortText when missing + if (!suggestion.sortText) { + suggestion.sortText = typeof suggestion.label === 'string' ? suggestion.label : suggestion.label.name; + } + result.push(new CompletionItem(position, suggestion, container, provider, model)); + } + } + if (isDisposable(container)) { + disposables.add(container); + } + }; + + // ask for snippets in parallel to asking "real" providers. Only do something if configured to + // do so - no snippet filter, no special-providers-only request + const snippetCompletions = new Promise((resolve, reject) => { + if (!_snippetSuggestSupport || options.kindFilter.has(modes.CompletionItemKind.Snippet)) { + resolve(); + } + if (options.providerFilter.size > 0 && !options.providerFilter.has(_snippetSuggestSupport)) { + resolve(); + } + Promise.resolve(_snippetSuggestSupport.provideCompletionItems(model, position, context, token)).then(list => { + onCompletionList(_snippetSuggestSupport, list); + resolve(); + }, reject); + }); // add suggestions from contributed providers - providers are ordered in groups of // equal score and once a group produces a result the process stops - const factory = supports.map(supports => () => { + // get provider groups, always add snippet suggestion provider + for (let providerGroup of modes.CompletionProviderRegistry.orderedGroups(model)) { + // for each support in the group ask for suggestions - return Promise.all(supports.map(provider => { + let lenBefore = result.length; + await Promise.all(providerGroup.map(async provider => { if (options.providerFilter.size > 0 && !options.providerFilter.has(provider)) { - return undefined; + return; + } + try { + const list = await provider.provideCompletionItems(model, position, context, token); + onCompletionList(provider, list); + } catch (err) { + onUnexpectedExternalError(err); } - - return Promise.resolve(provider.provideCompletionItems(model, position, context, token)).then(container => { - - const len = allSuggestions.length; - - if (container) { - for (let suggestion of container.suggestions || []) { - if (!options.kindFilter.has(suggestion.kind)) { - - // fill in default range when missing - if (!suggestion.range) { - suggestion.range = { insert: defaultInsertRange, replace: defaultReplaceRange }; - } - // fill in default sortText when missing - if (!suggestion.sortText) { - suggestion.sortText = typeof suggestion.label === 'string' ? suggestion.label : suggestion.label.name; - } - - allSuggestions.push(new CompletionItem(position, suggestion, container, provider, model)); - } - } - if (isDisposable(container)) { - disposables.add(container); - } - } - - if (len !== allSuggestions.length && provider !== _snippetSuggestSupport) { - hasResult = true; - } - - }, onUnexpectedExternalError); })); - }); - const result = first(factory, () => { - // stop on result or cancellation - return hasResult || token.isCancellationRequested; - }).then(() => { - if (token.isCancellationRequested) { - disposables.dispose(); - return Promise.reject(canceled()); + if (lenBefore !== result.length || token.isCancellationRequested) { + break; } - return allSuggestions.sort(getSuggestionComparator(options.snippetSortOrder)); - }); + } - // result.then(items => { - // console.log(model.getWordUntilPosition(position), items.map(item => `${item.suggestion.label}, type=${item.suggestion.type}, incomplete?${item.container.incomplete}, overwriteBefore=${item.suggestion.overwriteBefore}`)); - // return items; - // }, err => { - // console.warn(model.getWordUntilPosition(position), err); - // }); + await snippetCompletions; - return result; + if (token.isCancellationRequested) { + disposables.dispose(); + return Promise.reject(canceled()); + } + // console.log(`${result.length} items AFTER ${Date.now() - t1}ms`); + return result.sort(getSuggestionComparator(options.snippetSortOrder)); } diff --git a/src/vs/editor/contrib/suggest/suggestController.ts b/src/vs/editor/contrib/suggest/suggestController.ts index ef3f61df41..0da204fcb4 100644 --- a/src/vs/editor/contrib/suggest/suggestController.ts +++ b/src/vs/editor/contrib/suggest/suggestController.ts @@ -8,6 +8,7 @@ import { isNonEmptyArray } from 'vs/base/common/arrays'; import { onUnexpectedError } from 'vs/base/common/errors'; import { KeyCode, KeyMod, SimpleKeybinding } from 'vs/base/common/keyCodes'; import { dispose, IDisposable, DisposableStore, toDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; +import { StableEditorScrollState } from 'vs/editor/browser/core/editorState'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorAction, EditorCommand, registerEditorAction, registerEditorCommand, registerEditorContribution, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { EditOperation } from 'vs/editor/common/core/editOperation'; @@ -273,6 +274,8 @@ export class SuggestController implements IEditorContribution { // keep item in memory this._memoryService.memorize(model, this.editor.getPosition(), item); + const scrollState = StableEditorScrollState.capture(this.editor); + if (Array.isArray(suggestion.additionalTextEdits)) { this.editor.executeEdits('suggestController.additionalTextEdits', suggestion.additionalTextEdits.map(edit => EditOperation.replace(Range.lift(edit.range), edit.text))); } @@ -290,6 +293,8 @@ export class SuggestController implements IEditorContribution { adjustWhitespace: !(suggestion.insertTextRules! & CompletionItemInsertTextRule.KeepWhitespace) }); + scrollState.restoreRelativeVerticalPositionOfCursor(this.editor); + if (!(flags & InsertFlags.NoAfterUndoStop)) { this.editor.pushUndoStop(); } diff --git a/src/vs/editor/contrib/suggest/suggestWidget.ts b/src/vs/editor/contrib/suggest/suggestWidget.ts index 53fc49878f..de1e17094b 100644 --- a/src/vs/editor/contrib/suggest/suggestWidget.ts +++ b/src/vs/editor/contrib/suggest/suggestWidget.ts @@ -13,7 +13,7 @@ import * as strings from 'vs/base/common/strings'; import { Event, Emitter } from 'vs/base/common/event'; import { onUnexpectedError } from 'vs/base/common/errors'; import { IDisposable, dispose, toDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle'; -import { addClass, append, $, hide, removeClass, show, toggleClass, getDomNodePagePosition, hasClass, addDisposableListener, addStandardDisposableListener, addClasses } from 'vs/base/browser/dom'; +import { append, $, hide, show, getDomNodePagePosition, addDisposableListener, addStandardDisposableListener, addClasses } from 'vs/base/browser/dom'; import { IListVirtualDelegate, IListEvent, IListRenderer, IListMouseEvent, IListGestureEvent } from 'vs/base/browser/ui/list/list'; import { List } from 'vs/base/browser/ui/list/listWidget'; import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; @@ -139,7 +139,7 @@ class ItemRenderer implements IListRenderer { e.stopPropagation(); @@ -266,7 +266,7 @@ class ItemRenderer implements IListRenderer toggleClass(this.element, 'with-status-bar', this.editor.getOption(EditorOption.suggest).statusBar.visible); + const applyStatusBarStyle = () => this.element.classList.toggle('with-status-bar', this.editor.getOption(EditorOption.suggest).statusBar.visible); applyStatusBarStyle(); this.statusBarElement = append(this.element, $('.suggest-status-bar')); @@ -598,7 +598,7 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate toggleClass(this.element, 'no-icons', !this.editor.getOption(EditorOption.suggest).showIcons); + const applyIconStyle = () => this.element.classList.toggle('no-icons', !this.editor.getOption(EditorOption.suggest).showIcons); applyIconStyle(); let renderer = instantiationService.createInstance(ItemRenderer, this, this.editor, kbToggleDetails); @@ -797,7 +797,7 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate { - addClass(this.element, 'visible'); + this.element.classList.add('visible'); this.onDidShowEmitter.fire(this); }, 100); } @@ -1140,7 +1139,7 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate widgetY && this.details.element.offsetHeight > this.listElement.offsetHeight) { @@ -1248,18 +1247,17 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate; + resolveCompletionItem?(item: CompletionItem, token: CancellationToken): ProviderResult; } export interface CodeAction { diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 4e69ce2d72..0847dbbbb8 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -9,11 +9,13 @@ import { IConstructorSignature2, createDecorator, BrandedService, ServicesAccess import { IKeybindings, KeybindingsRegistry, IKeybindingRule } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { ContextKeyExpr, IContextKeyService, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; import { ICommandService, CommandsRegistry, ICommandHandlerDescription } from 'vs/platform/commands/common/commands'; -import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { IDisposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; import { UriDto } from 'vs/base/common/types'; +import { Iterable } from 'vs/base/common/iterator'; +import { LinkedList } from 'vs/base/common/linkedList'; export interface ILocalizedString { value: string; @@ -159,33 +161,51 @@ export interface IMenuService { export type ICommandsMap = Map; +export interface IMenuRegistryChangeEvent { + has(id: MenuId): boolean; +} + export interface IMenuRegistry { + readonly onDidChangeMenu: Event; + addCommands(newCommands: Iterable): IDisposable; addCommand(userCommand: ICommandAction): IDisposable; getCommand(id: string): ICommandAction | undefined; getCommands(): ICommandsMap; + appendMenuItems(items: Iterable<{ id: MenuId, item: IMenuItem | ISubmenuItem }>): IDisposable; appendMenuItem(menu: MenuId, item: IMenuItem | ISubmenuItem): IDisposable; getMenuItems(loc: MenuId): Array; - readonly onDidChangeMenu: Event; } export const MenuRegistry: IMenuRegistry = new class implements IMenuRegistry { private readonly _commands = new Map(); - private readonly _menuItems = new Map>(); - private readonly _onDidChangeMenu = new Emitter(); + private readonly _menuItems = new Map>(); + private readonly _onDidChangeMenu = new Emitter(); - readonly onDidChangeMenu: Event = this._onDidChangeMenu.event; + readonly onDidChangeMenu: Event = this._onDidChangeMenu.event; addCommand(command: ICommandAction): IDisposable { - this._commands.set(command.id, command); - this._onDidChangeMenu.fire(MenuId.CommandPalette); - return { - dispose: () => { - if (this._commands.delete(command.id)) { - this._onDidChangeMenu.fire(MenuId.CommandPalette); - } + return this.addCommands(Iterable.single(command)); + } + + private readonly _commandPaletteChangeEvent: IMenuRegistryChangeEvent = { + has: id => id === MenuId.CommandPalette + }; + + addCommands(commands: Iterable): IDisposable { + for (const command of commands) { + this._commands.set(command.id, command); + } + this._onDidChangeMenu.fire(this._commandPaletteChangeEvent); + return toDisposable(() => { + let didChange = false; + for (const command of commands) { + didChange = this._commands.delete(command.id) || didChange; } - }; + if (didChange) { + this._onDidChangeMenu.fire(this._commandPaletteChangeEvent); + } + }); } getCommand(id: string): ICommandAction | undefined { @@ -199,28 +219,44 @@ export const MenuRegistry: IMenuRegistry = new class implements IMenuRegistry { } appendMenuItem(id: MenuId, item: IMenuItem | ISubmenuItem): IDisposable { - let array = this._menuItems.get(id); - if (!array) { - array = [item]; - this._menuItems.set(id, array); - } else { - array.push(item); - } - this._onDidChangeMenu.fire(id); - return { - dispose: () => { - const idx = array!.indexOf(item); - if (idx >= 0) { - array!.splice(idx, 1); - this._onDidChangeMenu.fire(id); - } + return this.appendMenuItems(Iterable.single({ id, item })); + } + + appendMenuItems(items: Iterable<{ id: MenuId, item: IMenuItem | ISubmenuItem }>): IDisposable { + + const changedIds = new Set(); + const toRemove = new LinkedList(); + + for (const { id, item } of items) { + let list = this._menuItems.get(id); + if (!list) { + list = new LinkedList(); + this._menuItems.set(id, list); } - }; + toRemove.push(list.push(item)); + changedIds.add(id); + } + + this._onDidChangeMenu.fire(changedIds); + + return toDisposable(() => { + if (toRemove.size > 0) { + for (let fn of toRemove) { + fn(); + } + this._onDidChangeMenu.fire(changedIds); + toRemove.clear(); + } + }); } getMenuItems(id: MenuId): Array { - const result = (this._menuItems.get(id) || []).slice(0); - + let result: Array; + if (this._menuItems.has(id)) { + result = [...this._menuItems.get(id)!]; + } else { + result = []; + } if (id === MenuId.CommandPalette) { // CommandPalette is special because it shows // all commands by default @@ -232,12 +268,12 @@ export const MenuRegistry: IMenuRegistry = new class implements IMenuRegistry { private _appendImplicitItems(result: Array) { const set = new Set(); - const temp = result.filter(item => { return isIMenuItem(item); }) as IMenuItem[]; - - for (const { command, alt } of temp) { - set.add(command.id); - if (alt) { - set.add(alt.id); + for (const item of result) { + if (isIMenuItem(item)) { + set.add(item.command.id); + if (item.alt) { + set.add(item.alt.id); + } } } this._commands.forEach((command, id) => { @@ -441,14 +477,13 @@ export function registerAction2(ctor: { new(): Action2 }): IDisposable { // menu if (Array.isArray(menu)) { - for (let item of menu) { - disposables.add(MenuRegistry.appendMenuItem(item.id, { command: { ...command }, ...item })); - } + disposables.add(MenuRegistry.appendMenuItems(menu.map(item => ({ id: item.id, item: { command, ...item } })))); + } else if (menu) { - disposables.add(MenuRegistry.appendMenuItem(menu.id, { command: { ...command }, ...menu })); + disposables.add(MenuRegistry.appendMenuItem(menu.id, { command, ...menu })); } if (f1) { - disposables.add(MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: command })); + disposables.add(MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command })); } // keybinding diff --git a/src/vs/platform/actions/common/menuService.ts b/src/vs/platform/actions/common/menuService.ts index d05d7f5486..8b08a92346 100644 --- a/src/vs/platform/actions/common/menuService.ts +++ b/src/vs/platform/actions/common/menuService.ts @@ -45,7 +45,7 @@ class Menu implements IMenu { // rebuild this menu whenever the menu registry reports an // event for this MenuId this._dispoables.add(Event.debounce( - Event.filter(MenuRegistry.onDidChangeMenu, menuId => menuId === this._id), + Event.filter(MenuRegistry.onDidChangeMenu, set => set.has(this._id)), () => { }, 50 )(this._build, this)); diff --git a/src/vs/platform/commands/common/commands.ts b/src/vs/platform/commands/common/commands.ts index 8e66d4dd26..d3c50cf447 100644 --- a/src/vs/platform/commands/common/commands.ts +++ b/src/vs/platform/commands/common/commands.ts @@ -9,7 +9,6 @@ import { ServicesAccessor, createDecorator } from 'vs/platform/instantiation/com import { Event, Emitter } from 'vs/base/common/event'; import { LinkedList } from 'vs/base/common/linkedList'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; -import { keys } from 'vs/base/common/map'; import { Iterable } from 'vs/base/common/iterator'; export const ICommandService = createDecorator('commandService'); @@ -130,7 +129,7 @@ export const CommandsRegistry: ICommandRegistry = new class implements ICommandR getCommands(): ICommandsMap { const result = new Map(); - for (const key of keys(this._commands)) { + for (const key of this._commands.keys()) { const command = this.getCommand(key); if (command) { result.set(key, command); diff --git a/src/vs/platform/configuration/common/configurationModels.ts b/src/vs/platform/configuration/common/configurationModels.ts index 5b76411290..cbd78aeae0 100644 --- a/src/vs/platform/configuration/common/configurationModels.ts +++ b/src/vs/platform/configuration/common/configurationModels.ts @@ -231,7 +231,7 @@ export class ConfigurationModelParser { } public parseContent(content: string | null | undefined): void { - if (content) { + if (!types.isUndefinedOrNull(content)) { const raw = this.doParseContent(content); this.parseRaw(raw); } @@ -677,7 +677,7 @@ export class Configuration { overrides: this._workspaceConfiguration.overrides, keys: this._workspaceConfiguration.keys }, - folders: this._folderConfigurations.keys().reduce<[UriComponents, IConfigurationModel][]>((result, folder) => { + folders: [...this._folderConfigurations.keys()].reduce<[UriComponents, IConfigurationModel][]>((result, folder) => { const { contents, overrides, keys } = this._folderConfigurations.get(folder)!; result.push([folder, { contents, overrides, keys }]); return result; diff --git a/src/vs/platform/files/test/electron-browser/diskFileService.test.ts b/src/vs/platform/files/test/electron-browser/diskFileService.test.ts index f35f62604b..43478f0f47 100644 --- a/src/vs/platform/files/test/electron-browser/diskFileService.test.ts +++ b/src/vs/platform/files/test/electron-browser/diskFileService.test.ts @@ -21,14 +21,13 @@ import { isLinux, isWindows } from 'vs/base/common/platform'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { isEqual, joinPath } from 'vs/base/common/resources'; import { VSBuffer, VSBufferReadable, streamToBufferReadableStream, VSBufferReadableStream, bufferToReadable, bufferToStream, streamToBuffer } from 'vs/base/common/buffer'; -import { find } from 'vs/base/common/arrays'; function getByName(root: IFileStat, name: string): IFileStat | undefined { if (root.children === undefined) { return undefined; } - return find(root.children, child => child.name === name); + return root.children.find(child => child.name === name); } function toLineByLineReadable(content: string): VSBufferReadable { @@ -442,7 +441,7 @@ suite('Disk File Service', function () { assert.equal(resolved.isDirectory, true); assert.equal(resolved.children!.length, 9); - const resolvedLink = resolved.children?.filter(child => child.name === 'bar' && child.isSymbolicLink)[0]; + const resolvedLink = resolved.children?.find(child => child.name === 'bar' && child.isSymbolicLink); assert.ok(resolvedLink); assert.ok(!resolvedLink?.isDirectory); diff --git a/src/vs/platform/instantiation/common/graph.ts b/src/vs/platform/instantiation/common/graph.ts index 56cd20c793..3106364207 100644 --- a/src/vs/platform/instantiation/common/graph.ts +++ b/src/vs/platform/instantiation/common/graph.ts @@ -3,86 +3,78 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { isEmptyObject } from 'vs/base/common/types'; -import { forEach } from 'vs/base/common/collections'; +export class Node { -export interface Node { - data: T; - incoming: { [key: string]: Node }; - outgoing: { [key: string]: Node }; -} + readonly data: T; + readonly incoming = new Map>(); + readonly outgoing = new Map>(); -function newNode(data: T): Node { - return { - data: data, - incoming: Object.create(null), - outgoing: Object.create(null) - }; + constructor(data: T) { + this.data = data; + } } export class Graph { - private _nodes: { [key: string]: Node } = Object.create(null); + private readonly _nodes = new Map>(); - constructor(private _hashFn: (element: T) => string) { + constructor(private readonly _hashFn: (element: T) => string) { // empty } roots(): Node[] { const ret: Node[] = []; - forEach(this._nodes, entry => { - if (isEmptyObject(entry.value.outgoing)) { - ret.push(entry.value); + for (let node of this._nodes.values()) { + if (node.outgoing.size === 0) { + ret.push(node); } - }); + } return ret; } insertEdge(from: T, to: T): void { - const fromNode = this.lookupOrInsertNode(from), - toNode = this.lookupOrInsertNode(to); + const fromNode = this.lookupOrInsertNode(from); + const toNode = this.lookupOrInsertNode(to); - fromNode.outgoing[this._hashFn(to)] = toNode; - toNode.incoming[this._hashFn(from)] = fromNode; + fromNode.outgoing.set(this._hashFn(to), toNode); + toNode.incoming.set(this._hashFn(from), fromNode); } removeNode(data: T): void { const key = this._hashFn(data); - delete this._nodes[key]; - forEach(this._nodes, (entry) => { - delete entry.value.outgoing[key]; - delete entry.value.incoming[key]; - }); + this._nodes.delete(key); + for (let node of this._nodes.values()) { + node.outgoing.delete(key); + node.incoming.delete(key); + } } lookupOrInsertNode(data: T): Node { const key = this._hashFn(data); - let node = this._nodes[key]; + let node = this._nodes.get(key); if (!node) { - node = newNode(data); - this._nodes[key] = node; + node = new Node(data); + this._nodes.set(key, node); } return node; } - lookup(data: T): Node { - return this._nodes[this._hashFn(data)]; + lookup(data: T): Node | undefined { + return this._nodes.get(this._hashFn(data)); } isEmpty(): boolean { - for (const _key in this._nodes) { - return false; - } - return true; + return this._nodes.size === 0; } toString(): string { let data: string[] = []; - forEach(this._nodes, entry => { - data.push(`${entry.key}, (incoming)[${Object.keys(entry.value.incoming).join(', ')}], (outgoing)[${Object.keys(entry.value.outgoing).join(',')}]`); - }); + for (let [key, value] of this._nodes) { + data.push(`${key}, (incoming)[${[...value.incoming.keys()].join(', ')}], (outgoing)[${[...value.outgoing.keys()].join(',')}]`); + + } return data.join('\n'); } } diff --git a/src/vs/platform/instantiation/test/common/graph.test.ts b/src/vs/platform/instantiation/test/common/graph.test.ts index bd547a0d62..b677509cc7 100644 --- a/src/vs/platform/instantiation/test/common/graph.test.ts +++ b/src/vs/platform/instantiation/test/common/graph.test.ts @@ -19,7 +19,7 @@ suite('Graph', () => { test('inserts nodes when not there yet', function () { assert.deepEqual(graph.lookup('ddd'), null); assert.deepEqual(graph.lookupOrInsertNode('ddd').data, 'ddd'); - assert.deepEqual(graph.lookup('ddd').data, 'ddd'); + assert.deepEqual(graph.lookup('ddd')!.data, 'ddd'); }); test('can remove nodes and get length', function () { diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index b7a42fb0bb..6d885405fe 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -658,6 +658,7 @@ abstract class ResourceNavigator extends Disposable { onDidChangeFocus: Event<{ browserEvent?: UIEvent }>, onDidChangeSelection: Event<{ browserEvent?: UIEvent }>, onDidOpen: Event<{ browserEvent?: UIEvent }>, + readonly openOnSingleClick?: boolean }, options?: IResourceNavigatorOptions ) { @@ -711,7 +712,7 @@ abstract class ResourceNavigator extends Disposable { !!(browserEvent).preserveFocus : !isDoubleClick; - if (this.options.openOnSingleClick || isDoubleClick || isKeyboardEvent) { + if (this.treeOrList.openOnSingleClick || isDoubleClick || isKeyboardEvent) { const sideBySide = browserEvent instanceof MouseEvent && (browserEvent.ctrlKey || browserEvent.metaKey || browserEvent.altKey); this.open(preserveFocus, isDoubleClick || isMiddleClick, sideBySide, browserEvent); } @@ -738,8 +739,8 @@ export class ListResourceNavigator extends ResourceNavigator { } export class TreeResourceNavigator extends ResourceNavigator { - constructor(tree: WorkbenchObjectTree | WorkbenchCompressibleObjectTree | WorkbenchDataTree | WorkbenchAsyncDataTree | WorkbenchCompressibleAsyncDataTree, options?: IResourceNavigatorOptions) { - super(tree, { openOnSingleClick: tree.openOnSingleClick, ...(options || {}) }); + constructor(tree: WorkbenchObjectTree | WorkbenchCompressibleObjectTree | WorkbenchDataTree | WorkbenchAsyncDataTree | WorkbenchCompressibleAsyncDataTree, options: IResourceNavigatorOptions = {}) { + super(tree, options); } } diff --git a/src/vs/platform/markers/common/markerService.ts b/src/vs/platform/markers/common/markerService.ts index 151e2bc548..577b2c7c29 100644 --- a/src/vs/platform/markers/common/markerService.ts +++ b/src/vs/platform/markers/common/markerService.ts @@ -6,40 +6,64 @@ import { isFalsyOrEmpty, isNonEmptyArray } from 'vs/base/common/arrays'; import { Schemas } from 'vs/base/common/network'; import { IDisposable } from 'vs/base/common/lifecycle'; -import { isEmptyObject } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { Event, Emitter } from 'vs/base/common/event'; import { IMarkerService, IMarkerData, IResourceMarker, IMarker, MarkerStatistics, MarkerSeverity } from './markers'; +import { ResourceMap } from 'vs/base/common/map'; +import { Iterable } from 'vs/base/common/iterator'; -interface MapMap { - [key: string]: { [key: string]: V }; -} +class DoubleResourceMap{ -namespace MapMap { + private _byResource = new ResourceMap>(); + private _byOwner = new Map>(); - export function get(map: MapMap, key1: string, key2: string): V | undefined { - if (map[key1]) { - return map[key1][key2]; + set(resource: URI, owner: string, value: V) { + let ownerMap = this._byResource.get(resource); + if (!ownerMap) { + ownerMap = new Map(); + this._byResource.set(resource, ownerMap); } - return undefined; + ownerMap.set(owner, value); + + let resourceMap = this._byOwner.get(owner); + if (!resourceMap) { + resourceMap = new ResourceMap(); + this._byOwner.set(owner, resourceMap); + } + resourceMap.set(resource, value); } - export function set(map: MapMap, key1: string, key2: string, value: V): void { - if (!map[key1]) { - map[key1] = Object.create(null); - } - map[key1][key2] = value; + get(resource: URI, owner: string): V | undefined { + let ownerMap = this._byResource.get(resource); + return ownerMap?.get(owner); } - export function remove(map: MapMap, key1: string, key2: string): boolean { - if (map[key1] && map[key1][key2]) { - delete map[key1][key2]; - if (isEmptyObject(map[key1])) { - delete map[key1]; - } - return true; + delete(resource: URI, owner: string): boolean { + let removedA = false; + let removedB = false; + let ownerMap = this._byResource.get(resource); + if (ownerMap) { + removedA = ownerMap.delete(owner); } - return false; + let resourceMap = this._byOwner.get(owner); + if (resourceMap) { + removedB = resourceMap.delete(resource); + } + if (removedA !== removedB) { + throw new Error('illegal state'); + } + return removedA && removedB; + } + + values(key?: URI | string): Iterable { + if (typeof key === 'string') { + return this._byOwner.get(key)?.values() ?? Iterable.empty(); + } + if (URI.isUri(key)) { + return this._byResource.get(key)?.values() ?? Iterable.empty(); + } + + return Iterable.map(Iterable.concat(...this._byOwner.values()), map => map[1]); } } @@ -50,9 +74,9 @@ class MarkerStats implements MarkerStatistics { warnings: number = 0; unknowns: number = 0; - private _data?: { [resource: string]: MarkerStatistics } = Object.create(null); - private _service: IMarkerService; - private _subscription: IDisposable; + private readonly _data = new ResourceMap(); + private readonly _service: IMarkerService; + private readonly _subscription: IDisposable; constructor(service: IMarkerService) { this._service = service; @@ -61,23 +85,17 @@ class MarkerStats implements MarkerStatistics { dispose(): void { this._subscription.dispose(); - this._data = undefined; } private _update(resources: readonly URI[]): void { - if (!this._data) { - return; - } - for (const resource of resources) { - const key = resource.toString(); - const oldStats = this._data[key]; + const oldStats = this._data.get(resource); if (oldStats) { this._substract(oldStats); } const newStats = this._resourceStats(resource); this._add(newStats); - this._data[key] = newStats; + this._data.set(resource, newStats); } } @@ -124,10 +142,10 @@ export class MarkerService implements IMarkerService { _serviceBrand: undefined; private readonly _onMarkerChanged = new Emitter(); - private _onMarkerChangedEvent: Event = Event.debounce(this._onMarkerChanged.event, MarkerService._debouncer, 0); - private _byResource: MapMap = Object.create(null); - private _byOwner: MapMap = Object.create(null); - private _stats: MarkerStats; + readonly onMarkerChanged: Event = Event.debounce(this._onMarkerChanged.event, MarkerService._debouncer, 0); + + private readonly _data = new DoubleResourceMap(); + private readonly _stats: MarkerStats; constructor() { this._stats = new MarkerStats(this); @@ -137,10 +155,6 @@ export class MarkerService implements IMarkerService { this._stats.dispose(); } - get onMarkerChanged(): Event { - return this._onMarkerChangedEvent; - } - getStatistics(): MarkerStatistics { return this._stats; } @@ -155,12 +169,8 @@ export class MarkerService implements IMarkerService { if (isFalsyOrEmpty(markerData)) { // remove marker for this (owner,resource)-tuple - const a = MapMap.remove(this._byResource, resource.toString(), owner); - const b = MapMap.remove(this._byOwner, owner, resource.toString()); - if (a !== b) { - throw new Error('invalid marker service state'); - } - if (a && b) { + const removed = this._data.delete(resource, owner); + if (removed) { this._onMarkerChanged.fire([resource]); } @@ -173,8 +183,7 @@ export class MarkerService implements IMarkerService { markers.push(marker); } } - MapMap.set(this._byResource, resource.toString(), owner, markers); - MapMap.set(this._byOwner, owner, resource.toString(), markers); + this._data.set(resource, owner, markers); this._onMarkerChanged.fire([resource]); } } @@ -216,21 +225,15 @@ export class MarkerService implements IMarkerService { changeAll(owner: string, data: IResourceMarker[]): void { const changes: URI[] = []; - const map = this._byOwner[owner]; // remove old marker - if (map) { - delete this._byOwner[owner]; - for (const resource in map) { - const entry = MapMap.get(this._byResource, resource, owner); - if (entry) { - // remeber what we remove - const [first] = entry; - if (first) { - changes.push(first.resource); - } - // actual remove - MapMap.remove(this._byResource, resource, owner); + const existing = this._data.values(owner); + if (existing) { + for (let data of existing) { + const first = Iterable.first(data); + if (first) { + changes.push(first.resource); + this._data.delete(first.resource, owner); } } } @@ -239,16 +242,16 @@ export class MarkerService implements IMarkerService { if (isNonEmptyArray(data)) { // group by resource - const groups: { [resource: string]: IMarker[] } = Object.create(null); + const groups = new ResourceMap(); for (const { resource, marker: markerData } of data) { const marker = MarkerService._toMarker(owner, resource, markerData); if (!marker) { // filter bad markers continue; } - const array = groups[resource.toString()]; + const array = groups.get(resource); if (!array) { - groups[resource.toString()] = [marker]; + groups.set(resource, [marker]); changes.push(resource); } else { array.push(marker); @@ -256,9 +259,8 @@ export class MarkerService implements IMarkerService { } // insert all - for (const resource in groups) { - MapMap.set(this._byResource, resource, owner, groups[resource]); - MapMap.set(this._byOwner, owner, resource, groups[resource]); + for (const [resource, value] of groups) { + this._data.set(resource, owner, value); } } @@ -277,7 +279,7 @@ export class MarkerService implements IMarkerService { if (owner && resource) { // exactly one owner AND resource - const data = MapMap.get(this._byResource, resource.toString(), owner); + const data = this._data.get(resource, owner); if (!data) { return []; } else { @@ -296,14 +298,12 @@ export class MarkerService implements IMarkerService { } else if (!owner && !resource) { // all const result: IMarker[] = []; - for (const key1 in this._byResource) { - for (const key2 in this._byResource[key1]) { - for (const data of this._byResource[key1][key2]) { - if (MarkerService._accept(data, severities)) { - const newLen = result.push(data); - if (take > 0 && newLen === take) { - return result; - } + for (let markers of this._data.values()) { + for (let data of markers) { + if (MarkerService._accept(data, severities)) { + const newLen = result.push(data); + if (take > 0 && newLen === take) { + return result; } } } @@ -312,17 +312,10 @@ export class MarkerService implements IMarkerService { } else { // of one resource OR owner - const map: { [key: string]: IMarker[] } | undefined = owner - ? this._byOwner[owner] - : resource ? this._byResource[resource.toString()] : undefined; - - if (!map) { - return []; - } - + const iterable = this._data.values(resource ?? owner!); const result: IMarker[] = []; - for (const key in map) { - for (const data of map[key]) { + for (const markers of iterable) { + for (const data of markers) { if (MarkerService._accept(data, severities)) { const newLen = result.push(data); if (take > 0 && newLen === take) { @@ -341,16 +334,16 @@ export class MarkerService implements IMarkerService { // --- event debounce logic - private static _dedupeMap: { [uri: string]: boolean }; + private static _dedupeMap: ResourceMap; private static _debouncer(last: URI[] | undefined, event: readonly URI[]): URI[] { if (!last) { - MarkerService._dedupeMap = Object.create(null); + MarkerService._dedupeMap = new ResourceMap(); last = []; } for (const uri of event) { - if (MarkerService._dedupeMap[uri.toString()] === undefined) { - MarkerService._dedupeMap[uri.toString()] = true; + if (!MarkerService._dedupeMap.has(uri)) { + MarkerService._dedupeMap.set(uri, true); last.push(uri); } } diff --git a/src/vs/platform/menubar/electron-main/menubar.ts b/src/vs/platform/menubar/electron-main/menubar.ts index 54668b3902..91f877b441 100644 --- a/src/vs/platform/menubar/electron-main/menubar.ts +++ b/src/vs/platform/menubar/electron-main/menubar.ts @@ -703,7 +703,8 @@ export class Menubar { } // DevTools focused - if (activeWindow.webContents.isDevToolsFocused()) { + if (activeWindow.webContents.isDevToolsFocused() && + activeWindow.webContents.devToolsWebContents) { return contextSpecificHandlers.inDevTools(activeWindow.webContents.devToolsWebContents); } diff --git a/src/vs/platform/theme/common/iconRegistry.ts b/src/vs/platform/theme/common/iconRegistry.ts index 893819041e..373e24dc9b 100644 --- a/src/vs/platform/theme/common/iconRegistry.ts +++ b/src/vs/platform/theme/common/iconRegistry.ts @@ -56,6 +56,11 @@ export interface IIconRegistry { */ getIcons(): IconContribution[]; + /** + * Get the icon for the given id + */ + getIcon(id: string): IconContribution | undefined; + /** * JSON schema for an object to assign icon values to one of the color contributions. */ @@ -130,6 +135,10 @@ class IconRegistry implements IIconRegistry { return Object.keys(this.iconsById).map(id => this.iconsById[id]); } + public getIcon(id: string): IconContribution | undefined { + return this.iconsById[id]; + } + public getIconSchema(): IJSONSchema { return this.iconSchema; } @@ -139,16 +148,34 @@ class IconRegistry implements IIconRegistry { } public toString() { - let sorter = (a: string, b: string) => { - let cat1 = a.indexOf('.') === -1 ? 0 : 1; - let cat2 = b.indexOf('.') === -1 ? 0 : 1; - if (cat1 !== cat2) { - return cat1 - cat2; + const sorter = (i1: IconContribution, i2: IconContribution) => { + const isThemeIcon1 = ThemeIcon.isThemeIcon(i1.defaults); + const isThemeIcon2 = ThemeIcon.isThemeIcon(i2.defaults); + if (isThemeIcon1 !== isThemeIcon2) { + return isThemeIcon1 ? -1 : 1; } - return a.localeCompare(b); + return i1.id.localeCompare(i2.id); + }; + const classNames = (i: IconContribution) => { + while (ThemeIcon.isThemeIcon(i.defaults)) { + i = this.iconsById[i.defaults.id]; + } + return `codicon codicon-${i ? i.id : ''}`; }; - return Object.keys(this.iconsById).sort(sorter).map(k => `- \`${k}\`: ${this.iconsById[k].description}`).join('\n'); + let reference = []; + let docCss = []; + + const contributions = Object.keys(this.iconsById).map(key => this.iconsById[key]); + + for (const i of contributions.sort(sorter)) { + reference.push(`||${i.id}|${ThemeIcon.isThemeIcon(i.defaults) ? i.defaults.id : ''}|`); + + if (!ThemeIcon.isThemeIcon((i.defaults))) { + docCss.push(`.codicon-${i.id}:before { content: "${i.defaults.character}" }`); + } + } + return reference.join('\n') + '\n\n' + docCss.join('\n'); } } @@ -186,4 +213,4 @@ iconRegistry.onDidChangeSchema(() => { }); -// setTimeout(_ => console.log(colorRegistry.toString()), 5000); +//setTimeout(_ => console.log(iconRegistry.toString()), 5000); diff --git a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts index 1bbd09340f..886bfa2635 100644 --- a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts +++ b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts @@ -167,10 +167,16 @@ export abstract class AbstractSynchroniser extends Disposable { if (e instanceof UserDataSyncError) { switch (e.code) { case UserDataSyncErrorCode.RemotePreconditionFailed: - // Rejected as there is a new remote version. Syncing again, + // Rejected as there is a new remote version. Syncing again... this.logService.info(`${this.syncResourceLogLabel}: Failed to synchronize as there is a new remote version available. Synchronizing again...`); + // Avoid cache and get latest remote user data - https://github.com/microsoft/vscode/issues/90624 remoteUserData = await this.getRemoteUserData(null); + + // Get the latest last sync user data. Because multiples parallel syncs (in Web) could share same last sync data + // and one of them successfully updated remote and last sync state. + lastSyncUserData = await this.getLastSyncUserData(); + return this.doSync(remoteUserData, lastSyncUserData); } } diff --git a/src/vs/platform/windows/electron-main/windowsMainService.ts b/src/vs/platform/windows/electron-main/windowsMainService.ts index 5dd19d8ae0..22110927ce 100644 --- a/src/vs/platform/windows/electron-main/windowsMainService.ts +++ b/src/vs/platform/windows/electron-main/windowsMainService.ts @@ -318,7 +318,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic if (!currentWindowsState.lastActiveWindow) { let activeWindow = this.getLastActiveWindow(); if (!activeWindow || activeWindow.isExtensionDevelopmentHost) { - activeWindow = WindowsMainService.WINDOWS.filter(window => !window.isExtensionDevelopmentHost)[0]; + activeWindow = WindowsMainService.WINDOWS.find(window => !window.isExtensionDevelopmentHost); } if (activeWindow) { @@ -327,7 +327,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic } // 2.) Find extension host window - const extensionHostWindow = WindowsMainService.WINDOWS.filter(window => window.isExtensionDevelopmentHost && !window.isExtensionTestHost)[0]; + const extensionHostWindow = WindowsMainService.WINDOWS.find(window => window.isExtensionDevelopmentHost && !window.isExtensionTestHost); if (extensionHostWindow) { currentWindowsState.lastPluginDevelopmentHostWindow = this.toWindowState(extensionHostWindow); } diff --git a/src/vs/platform/windows/node/window.ts b/src/vs/platform/windows/node/window.ts index a8b25c88cc..24dc70f54f 100644 --- a/src/vs/platform/windows/node/window.ts +++ b/src/vs/platform/windows/node/window.ts @@ -168,7 +168,7 @@ function findWindowOnFilePath(windows: W[], fileUri: U export function getLastActiveWindow(windows: W[]): W | undefined { const lastFocusedDate = Math.max.apply(Math, windows.map(window => window.lastFocusTime)); - return windows.filter(window => window.lastFocusTime === lastFocusedDate)[0]; + return windows.find(window => window.lastFocusTime === lastFocusedDate); } export function findWindowOnWorkspace(windows: W[], workspace: (IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier)): W | null { diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index d2c8a18d1d..34e5f00b0a 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -7897,7 +7897,7 @@ declare module 'vscode' { * This will trigger the view to update the changed element/root and its children recursively (if shown). * To signal that root has changed, do not pass any argument or pass `undefined` or `null`. */ - onDidChangeTreeData?: Event; + onDidChangeTreeData?: Event; /** * Get [TreeItem](#TreeItem) representation of the `element` @@ -10486,6 +10486,23 @@ declare module 'vscode' { // Properties: see details [here](https://microsoft.github.io/debug-adapter-protocol/specification#Types_Source). } + /** + * A DebugConfigurationProviderTriggerKind specifies when the `provideDebugConfigurations` method of a `DebugConfigurationProvider` is triggered. + * Currently there are two situations: to provide the initial debug configurations for a newly created launch.json or + * to provide dynamically generated debug configurations when the user asks for them through the UI (e.g. via the "Select and Start Debugging" command). + * A trigger kind is used when registering a `DebugConfigurationProvider` with #debug.registerDebugConfigurationProvider. + */ + export enum DebugConfigurationProviderTriggerKind { + /** + * `DebugConfigurationProvider.provideDebugConfigurations` is called to provide the initial debug configurations for a newly created launch.json. + */ + Initial = 1, + /** + * `DebugConfigurationProvider.provideDebugConfigurations` is called to provide dynamically generated debug configurations when the user asks for them through the UI (e.g. via the "Select and Start Debugging" command). + */ + Dynamic = 2 + } + /** * Namespace for debug functionality. */ @@ -10539,13 +10556,19 @@ declare module 'vscode' { /** * Register a [debug configuration provider](#DebugConfigurationProvider) for a specific debug type. + * The optional [triggerKind](#DebugConfigurationProviderTriggerKind) can be used to specify when the `provideDebugConfigurations` method of the provider is triggered. + * Currently two trigger kinds are possible: with the value `Initial` (or if no trigger kind argument is given) the `provideDebugConfigurations` method is used to provide the initial debug configurations to be copied into a newly created launch.json. + * With the trigger kind `Dynamic` the `provideDebugConfigurations` method is used to dynamically determine debug configurations to be presented to the user (in addition to the static configurations from the launch.json). + * Please note that the `triggerKind` argument only applies to the `provideDebugConfigurations` method: so the `resolveDebugConfiguration` methods are not affected at all. + * Registering a single provider with resolve methods for different trigger kinds, results in the same resolve methods called multiple times. * More than one provider can be registered for the same type. * * @param type The debug type for which the provider is registered. * @param provider The [debug configuration provider](#DebugConfigurationProvider) to register. + * @param triggerKind The [trigger](#DebugConfigurationProviderTrigger) for which the 'provideDebugConfiguration' method of the provider is registered. If `triggerKind` is missing, the value `DebugConfigurationProviderTriggerKind.Initial` is assumed. * @return A [disposable](#Disposable) that unregisters this provider when being disposed. */ - export function registerDebugConfigurationProvider(debugType: string, provider: DebugConfigurationProvider): Disposable; + export function registerDebugConfigurationProvider(debugType: string, provider: DebugConfigurationProvider, triggerKind?: DebugConfigurationProviderTriggerKind): Disposable; /** * Register a [debug adapter descriptor factory](#DebugAdapterDescriptorFactory) for a specific debug type. @@ -10875,6 +10898,21 @@ declare module 'vscode' { provideCommentingRanges(document: TextDocument, token: CancellationToken): ProviderResult; } + /** + * Represents a [comment controller](#CommentController)'s [options](#CommentController.options). + */ + export interface CommentOptions { + /** + * An optional string to show on the comment input box when it's collapsed. + */ + prompt?: string; + + /** + * An optional string to show as placeholder in the comment input box when it's focused. + */ + placeHolder?: string; + } + /** * A comment controller is able to provide [comments](#CommentThread) support to the editor and * provide users various ways to interact with comments. @@ -10890,6 +10928,11 @@ declare module 'vscode' { */ readonly label: string; + /** + * Comment controller options + */ + options?: CommentOptions; + /** * Optional commenting range provider. Provide a list [ranges](#Range) which support commenting to any given resource uri. * diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 4905189e58..df34dc8da7 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -25,7 +25,7 @@ declare module 'vscode' { displayName: string; id: string; }; - scopes: string[] + scopes: string[]; } /** @@ -141,12 +141,12 @@ declare module 'vscode' { * within a session has changed for a provider. Fires with the ids of the providers * that have had session data change. */ - export const onDidChangeSessions: Event<{ [providerId: string]: AuthenticationSessionsChangeEvent }>; + export const onDidChangeSessions: Event<{ [providerId: string]: AuthenticationSessionsChangeEvent; }>; } //#endregion - //#region Alex - resolvers + //#region @alexdima - resolvers export interface RemoteAuthorityResolverContext { resolveAttempt: number; @@ -160,20 +160,20 @@ declare module 'vscode' { } export interface ResolvedOptions { - extensionHostEnv?: { [key: string]: string | null }; + extensionHostEnv?: { [key: string]: string | null; }; } export interface TunnelOptions { - remoteAddress: { port: number, host: string }; + remoteAddress: { port: number, host: string; }; // The desired local port. If this port can't be used, then another will be chosen. localAddressPort?: number; label?: string; } export interface TunnelDescription { - remoteAddress: { port: number, host: string }; + remoteAddress: { port: number, host: string; }; //The complete local address(ex. localhost:1234) - localAddress: { port: number, host: string } | string; + localAddress: { port: number, host: string; } | string; } export interface Tunnel extends TunnelDescription { @@ -248,7 +248,7 @@ declare module 'vscode' { export interface ResourceLabelFormatting { label: string; // myLabel:/${path} - // TODO@isi + // TODO@isidorn // eslint-disable-next-line vscode-dts-literal-or-types separator: '/' | '\\' | ''; tildify?: boolean; @@ -284,7 +284,7 @@ declare module 'vscode' { //#region read/write in chunks: https://github.com/microsoft/vscode/issues/84515 export interface FileSystemProvider { - open?(resource: Uri, options: { create: boolean }): number | Thenable; + open?(resource: Uri, options: { create: boolean; }): number | Thenable; close?(fd: number): void | Thenable; read?(fd: number, pos: number, data: Uint8Array, offset: number, length: number): number | Thenable; write?(fd: number, pos: number, data: Uint8Array, offset: number, length: number): number | Thenable; @@ -326,7 +326,7 @@ declare module 'vscode' { /** * A file glob pattern to match file paths against. - * TODO@roblou - merge this with the GlobPattern docs/definition in vscode.d.ts. + * TODO@roblourens merge this with the GlobPattern docs/definition in vscode.d.ts. * @see [GlobPattern](#GlobPattern) */ export type GlobString = string; @@ -731,42 +731,7 @@ declare module 'vscode' { //#endregion - //#region debug: https://github.com/microsoft/vscode/issues/88230 - - /** - * A DebugConfigurationProviderTriggerKind specifies when the `provideDebugConfigurations` method of a `DebugConfigurationProvider` is triggered. - * Currently there are two situations: to provide the initial debug configurations for a newly created launch.json or - * to provide dynamically generated debug configurations when the user asks for them through the UI (e.g. via the "Select and Start Debugging" command). - * A trigger kind is used when registering a `DebugConfigurationProvider` with #debug.registerDebugConfigurationProvider. - */ - export enum DebugConfigurationProviderTriggerKind { - /** - * `DebugConfigurationProvider.provideDebugConfigurations` is called to provide the initial debug configurations for a newly created launch.json. - */ - Initial = 1, - /** - * `DebugConfigurationProvider.provideDebugConfigurations` is called to provide dynamically generated debug configurations when the user asks for them through the UI (e.g. via the "Select and Start Debugging" command). - */ - Dynamic = 2 - } - - export namespace debug { - /** - * Register a [debug configuration provider](#DebugConfigurationProvider) for a specific debug type. - * The optional [triggerKind](#DebugConfigurationProviderTriggerKind) can be used to specify when the `provideDebugConfigurations` method of the provider is triggered. - * Currently two trigger kinds are possible: with the value `Initial` (or if no trigger kind argument is given) the `provideDebugConfigurations` method is used to provide the initial debug configurations to be copied into a newly created launch.json. - * With the trigger kind `Dynamic` the `provideDebugConfigurations` method is used to dynamically determine debug configurations to be presented to the user (in addition to the static configurations from the launch.json). - * Please note that the `triggerKind` argument only applies to the `provideDebugConfigurations` method: so the `resolveDebugConfiguration` methods are not affected at all. - * Registering a single provider with resolve methods for different trigger kinds, results in the same resolve methods called multiple times. - * More than one provider can be registered for the same type. - * - * @param type The debug type for which the provider is registered. - * @param provider The [debug configuration provider](#DebugConfigurationProvider) to register. - * @param triggerKind The [trigger](#DebugConfigurationProviderTrigger) for which the 'provideDebugConfiguration' method of the provider is registered. - * @return A [disposable](#Disposable) that unregisters this provider when being disposed. - */ - export function registerDebugConfigurationProvider(debugType: string, provider: DebugConfigurationProvider, triggerKind?: DebugConfigurationProviderTriggerKind): Disposable; - } + //#region debug // deprecated debug API @@ -809,7 +774,7 @@ declare module 'vscode' { //#endregion - //#region Joao: SCM validation + //#region @joaomoreno: SCM validation /** * Represents the validation type of the Source Control input. @@ -859,7 +824,7 @@ declare module 'vscode' { //#endregion - //#region Joao: SCM selected provider + //#region @joaomoreno: SCM selected provider export interface SourceControl { @@ -1062,7 +1027,7 @@ declare module 'vscode' { //#endregion - //#region Joh -> exclusive document filters + //#region @jrieken -> exclusive document filters export interface DocumentFilter { exclusive?: boolean; @@ -1070,7 +1035,7 @@ declare module 'vscode' { //#endregion - //#region Alex - OnEnter enhancement + //#region @alexdima - OnEnter enhancement export interface OnEnterRule { /** * This rule will only execute if the text above the this line matches this regular expression. @@ -1579,7 +1544,7 @@ declare module 'vscode' { //#endregion - //#region Peng: Notebook + //#region @rebornix: Notebook export enum CellKind { Markdown = 1, @@ -1632,7 +1597,7 @@ declare module 'vscode' { * } * } */ - data: { [key: string]: any }; + data: { [key: string]: any; }; } export type CellOutput = CellStreamOutput | CellErrorOutput | CellDisplayOutput; @@ -1713,6 +1678,8 @@ declare module 'vscode' { * Defaults to true. */ hasExecutionOrder?: boolean; + + displayOrder?: GlobPattern[]; } export interface NotebookDocument { @@ -1804,13 +1771,54 @@ declare module 'vscode' { // readonly contentChanges: ReadonlyArray; } + export interface NotebookCellData { + readonly cellKind: CellKind; + readonly source: string; + language: string; + outputs: CellOutput[]; + metadata: NotebookCellMetadata; + } + + export interface NotebookData { + readonly cells: NotebookCellData[]; + readonly languages: string[]; + readonly metadata: NotebookDocumentMetadata; + } + + export interface NotebookContentProvider { + openNotebook(uri: Uri): NotebookData | Promise; + saveNotebook(document: NotebookDocument, cancellation: CancellationToken): Promise; + saveNotebookAs(targetResource: Uri, document: NotebookDocument, cancellation: CancellationToken): Promise; + readonly onDidChangeNotebook: Event; + // revert?(document: NotebookDocument, cancellation: CancellationToken): Thenable; + // backup?(document: NotebookDocument, cancellation: CancellationToken): Thenable; + + /** + * Responsible for filling in outputs for the cell + */ + executeCell(document: NotebookDocument, cell: NotebookCell | undefined, token: CancellationToken): Promise; + } + export namespace notebook { + export function registerNotebookContentProvider( + notebookType: string, + provider: NotebookContentProvider + ): Disposable; + export function registerNotebookProvider( notebookType: string, provider: NotebookProvider ): Disposable; - export function registerNotebookOutputRenderer(type: string, outputSelector: NotebookOutputSelector, renderer: NotebookOutputRenderer): Disposable; + export function registerNotebookOutputRenderer( + type: string, + outputSelector: NotebookOutputSelector, + renderer: NotebookOutputRenderer + ): Disposable; + + export const onDidOpenNotebookDocument: Event; + export const onDidCloseNotebookDocument: Event; + // export const onDidChangeVisibleNotebookEditors: Event; // remove activeNotebookDocument, now that there is activeNotebookEditor.document export let activeNotebookDocument: NotebookDocument | undefined; @@ -1865,7 +1873,7 @@ declare module 'vscode' { //#endregion - //#region eamodio - timeline: https://github.com/microsoft/vscode/issues/84297 + //#region @eamodio - timeline: https://github.com/microsoft/vscode/issues/84297 export class TimelineItem { /** @@ -1888,7 +1896,7 @@ declare module 'vscode' { /** * The icon path or [ThemeIcon](#ThemeIcon) for the timeline item. */ - iconPath?: Uri | { light: Uri; dark: Uri } | ThemeIcon; + iconPath?: Uri | { light: Uri; dark: Uri; } | ThemeIcon; /** * A human readable string describing less prominent details of the timeline item. @@ -1951,7 +1959,7 @@ declare module 'vscode' { * Use `undefined` to signal that there are no more items to be returned. */ readonly cursor: string | undefined; - } + }; /** * An array of [timeline items](#TimelineItem). @@ -1969,7 +1977,7 @@ declare module 'vscode' { * An optional maximum number timeline items or the all timeline items newer (inclusive) than the timestamp or id that should be returned. * If `undefined` all timeline items should be returned. */ - limit?: number | { timestamp: number; id?: string }; + limit?: number | { timestamp: number; id?: string; }; } export interface TimelineProvider { @@ -2033,7 +2041,7 @@ declare module 'vscode' { * * - Any code actions of `kind` are returned by the provider. */ - readonly documentation?: ReadonlyArray<{ readonly kind: CodeActionKind, readonly command: Command }>; + readonly documentation?: ReadonlyArray<{ readonly kind: CodeActionKind, readonly command: Command; }>; } //#endregion @@ -2072,26 +2080,4 @@ declare module 'vscode' { } //#endregion - - //#region Comment - export interface CommentOptions { - /** - * An optional string to show on the comment input box when it's collapsed. - */ - prompt?: string; - - /** - * An optional string to show as placeholder in the comment input box when it's focused. - */ - placeHolder?: string; - } - - export interface CommentController { - /** - * Comment controller options - */ - options?: CommentOptions; - } - - //#endregion } diff --git a/src/vs/workbench/api/browser/mainThreadAuthentication.ts b/src/vs/workbench/api/browser/mainThreadAuthentication.ts index 87ece04bd1..a4a5440565 100644 --- a/src/vs/workbench/api/browser/mainThreadAuthentication.ts +++ b/src/vs/workbench/api/browser/mainThreadAuthentication.ts @@ -324,7 +324,7 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu } const remoteConnection = this.remoteAgentService.getConnection(); - if (remoteConnection && remoteConnection.remoteAuthority === 'vsonline' && VSO_ALLOWED_EXTENSIONS.includes(extensionId)) { + if (remoteConnection && remoteConnection.remoteAuthority && remoteConnection.remoteAuthority.startsWith('vsonline') && VSO_ALLOWED_EXTENSIONS.includes(extensionId)) { return true; } diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index af6cf8c392..3a2f86482f 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -448,8 +448,8 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha } }; if (supportsResolveDetails) { - provider.resolveCompletionItem = (model, position, suggestion, token) => { - return this._proxy.$resolveCompletionItem(handle, model.uri, position, suggestion._id!, token).then(result => { + provider.resolveCompletionItem = (suggestion, token) => { + return this._proxy.$resolveCompletionItem(handle, suggestion._id!, token).then(result => { if (!result) { return suggestion; } diff --git a/src/vs/workbench/api/browser/mainThreadNotebook.ts b/src/vs/workbench/api/browser/mainThreadNotebook.ts index ff6166f668..7f658546f0 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebook.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebook.ts @@ -7,7 +7,7 @@ import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { MainContext, MainThreadNotebookShape, NotebookExtensionDescription, IExtHostContext, ExtHostNotebookShape, ExtHostContext } from '../common/extHost.protocol'; import { Disposable } from 'vs/base/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { INotebookService, IMainNotebookController } from 'vs/workbench/contrib/notebook/browser/notebookService'; +import { INotebookService, IMainNotebookController } from 'vs/workbench/contrib/notebook/common/notebookService'; import { INotebookTextModel, INotebookMimeTypeSelector, NOTEBOOK_DISPLAY_ORDER, NotebookCellOutputsSplice, CellKind, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; @@ -294,7 +294,7 @@ export class MainThreadNotebookController implements IMainNotebookController { } } - async save(uri: URI): Promise { - return this._proxy.$saveNotebook(this._viewType, uri); + async save(uri: URI, token: CancellationToken): Promise { + return this._proxy.$saveNotebook(this._viewType, uri, token); } } diff --git a/src/vs/workbench/api/browser/viewsExtensionPoint.ts b/src/vs/workbench/api/browser/viewsExtensionPoint.ts index 9b067c5082..a4385b7929 100644 --- a/src/vs/workbench/api/browser/viewsExtensionPoint.ts +++ b/src/vs/workbench/api/browser/viewsExtensionPoint.ts @@ -409,6 +409,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution { name: item.name, ctorDescriptor: new SyncDescriptor(TreeViewPane), when: ContextKeyExpr.deserialize(item.when), + containerIcon: viewContainer?.icon, canToggleVisibility: true, canMoveView: true, treeView: this.instantiationService.createInstance(CustomTreeView, item.id, item.name), diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 612bc418d6..89af44294d 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -327,7 +327,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I // namespace: languages const languages: typeof vscode.languages = { createDiagnosticCollection(name?: string): vscode.DiagnosticCollection { - return extHostDiagnostics.createDiagnosticCollection(name); + return extHostDiagnostics.createDiagnosticCollection(extension.identifier, name); }, get onDidChangeDiagnostics() { return extHostDiagnostics.onDidChangeDiagnostics; @@ -923,10 +923,22 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I // namespace: notebook const notebook: typeof vscode.notebook = { + get onDidOpenNotebookDocument(): Event { + checkProposedApiEnabled(extension); + return extHostNotebook.onDidOpenNotebookDocument; + }, + get onDidCloseNotebookDocument(): Event { + checkProposedApiEnabled(extension); + return extHostNotebook.onDidCloseNotebookDocument; + }, registerNotebookProvider: (viewType: string, provider: vscode.NotebookProvider) => { checkProposedApiEnabled(extension); return extHostNotebook.registerNotebookProvider(extension, viewType, provider); }, + registerNotebookContentProvider: (viewType: string, provider: vscode.NotebookContentProvider) => { + checkProposedApiEnabled(extension); + return extHostNotebook.registerNotebookContentProvider(extension, viewType, provider); + }, registerNotebookOutputRenderer: (type: string, outputFilter: vscode.NotebookOutputSelector, renderer: vscode.NotebookOutputRenderer) => { checkProposedApiEnabled(extension); return extHostNotebook.registerNotebookOutputRenderer(type, extension, outputFilter, renderer); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index cb34ad7236..b8487f6d5d 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1318,7 +1318,7 @@ export interface ExtHostLanguageFeaturesShape { $releaseDocumentSemanticTokens(handle: number, semanticColoringResultId: number): void; $provideDocumentRangeSemanticTokens(handle: number, resource: UriComponents, range: IRange, token: CancellationToken): Promise; $provideCompletionItems(handle: number, resource: UriComponents, position: IPosition, context: modes.CompletionContext, token: CancellationToken): Promise; - $resolveCompletionItem(handle: number, resource: UriComponents, position: IPosition, id: ChainedCacheId, token: CancellationToken): Promise; + $resolveCompletionItem(handle: number, id: ChainedCacheId, token: CancellationToken): Promise; $releaseCompletionItems(handle: number, id: number): void; $provideSignatureHelp(handle: number, resource: UriComponents, position: IPosition, context: modes.SignatureHelpContext, token: CancellationToken): Promise; $releaseSignatureHelp(handle: number, id: number): void; @@ -1545,7 +1545,7 @@ export interface INotebookEditorPropertiesChangeData { export interface ExtHostNotebookShape { $resolveNotebook(viewType: string, uri: UriComponents): Promise; $executeNotebook(viewType: string, uri: UriComponents, cellHandle: number | undefined, token: CancellationToken): Promise; - $saveNotebook(viewType: string, uri: UriComponents): Promise; + $saveNotebook(viewType: string, uri: UriComponents, token: CancellationToken): Promise; $updateActiveEditor(viewType: string, uri: UriComponents): Promise; $destoryNotebookDocument(viewType: string, uri: UriComponents): Promise; $acceptDisplayOrder(displayOrder: INotebookDisplayOrder): void; diff --git a/src/vs/workbench/api/common/extHostDiagnostics.ts b/src/vs/workbench/api/common/extHostDiagnostics.ts index 4195d5a061..3c9f98e01f 100644 --- a/src/vs/workbench/api/common/extHostDiagnostics.ts +++ b/src/vs/workbench/api/common/extHostDiagnostics.ts @@ -13,25 +13,21 @@ import * as converter from './extHostTypeConverters'; import { mergeSort } from 'vs/base/common/arrays'; import { Event, Emitter } from 'vs/base/common/event'; import { ILogService } from 'vs/platform/log/common/log'; +import { ResourceMap } from 'vs/base/common/map'; +import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; export class DiagnosticCollection implements vscode.DiagnosticCollection { - private readonly _name: string; - private readonly _owner: string; - private readonly _maxDiagnosticsPerFile: number; - private readonly _onDidChangeDiagnostics: Emitter<(vscode.Uri | string)[]>; - private readonly _proxy: MainThreadDiagnosticsShape | undefined; - private _isDisposed = false; - private _data = new Map(); + private _data = new ResourceMap(); - constructor(name: string, owner: string, maxDiagnosticsPerFile: number, proxy: MainThreadDiagnosticsShape | undefined, onDidChangeDiagnostics: Emitter<(vscode.Uri | string)[]>) { - this._name = name; - this._owner = owner; - this._maxDiagnosticsPerFile = maxDiagnosticsPerFile; - this._proxy = proxy; - this._onDidChangeDiagnostics = onDidChangeDiagnostics; - } + constructor( + private readonly _name: string, + private readonly _owner: string, + private readonly _maxDiagnosticsPerFile: number, + private readonly _proxy: MainThreadDiagnosticsShape | undefined, + private readonly _onDidChangeDiagnostics: Emitter + ) { } dispose(): void { if (!this._isDisposed) { @@ -73,7 +69,7 @@ export class DiagnosticCollection implements vscode.DiagnosticCollection { } // update single row - this._data.set(first.toString(), diagnostics.slice()); + this._data.set(first, diagnostics.slice()); toSync = [first]; } else if (Array.isArray(first)) { @@ -87,8 +83,8 @@ export class DiagnosticCollection implements vscode.DiagnosticCollection { for (const tuple of first) { const [uri, diagnostics] = tuple; if (!lastUri || uri.toString() !== lastUri.toString()) { - if (lastUri && this._data.get(lastUri.toString())!.length === 0) { - this._data.delete(lastUri.toString()); + if (lastUri && this._data.get(lastUri)!.length === 0) { + this._data.delete(lastUri); } lastUri = uri; toSync.push(uri); @@ -120,7 +116,7 @@ export class DiagnosticCollection implements vscode.DiagnosticCollection { const entries: [URI, IMarkerData[]][] = []; for (let uri of toSync) { let marker: IMarkerData[] = []; - const diagnostics = this._data.get(uri.toString()); + const diagnostics = this._data.get(uri); if (diagnostics) { // no more than N diagnostics per file @@ -160,7 +156,7 @@ export class DiagnosticCollection implements vscode.DiagnosticCollection { delete(uri: vscode.Uri): void { this._checkDisposed(); this._onDidChangeDiagnostics.fire([uri]); - this._data.delete(uri.toString()); + this._data.delete(uri); if (this._proxy) { this._proxy.$changeMany(this._owner, [[uri, undefined]]); } @@ -177,15 +173,14 @@ export class DiagnosticCollection implements vscode.DiagnosticCollection { forEach(callback: (uri: URI, diagnostics: ReadonlyArray, collection: DiagnosticCollection) => any, thisArg?: any): void { this._checkDisposed(); - this._data.forEach((value, key) => { - const uri = URI.parse(key); + this._data.forEach((value, uri) => { callback.apply(thisArg, [uri, this.get(uri), this]); }); } get(uri: URI): ReadonlyArray { this._checkDisposed(); - const result = this._data.get(uri.toString()); + const result = this._data.get(uri); if (Array.isArray(result)) { return >Object.freeze(result.slice(0)); } @@ -194,7 +189,7 @@ export class DiagnosticCollection implements vscode.DiagnosticCollection { has(uri: URI): boolean { this._checkDisposed(); - return Array.isArray(this._data.get(uri.toString())); + return Array.isArray(this._data.get(uri)); } private _checkDisposed() { @@ -221,7 +216,7 @@ export class ExtHostDiagnostics implements ExtHostDiagnosticsShape { private readonly _proxy: MainThreadDiagnosticsShape; private readonly _collections = new Map(); - private readonly _onDidChangeDiagnostics = new Emitter<(vscode.Uri | string)[]>(); + private readonly _onDidChangeDiagnostics = new Emitter(); static _debouncer(last: (vscode.Uri | string)[] | undefined, current: (vscode.Uri | string)[]): (vscode.Uri | string)[] { if (!last) { @@ -257,8 +252,25 @@ export class ExtHostDiagnostics implements ExtHostDiagnosticsShape { this._proxy = mainContext.getProxy(MainContext.MainThreadDiagnostics); } - createDiagnosticCollection(name?: string): vscode.DiagnosticCollection { - let { _collections, _proxy, _onDidChangeDiagnostics } = this; + createDiagnosticCollection(extensionId: ExtensionIdentifier, name?: string): vscode.DiagnosticCollection { + + const { _collections, _proxy, _onDidChangeDiagnostics, _logService } = this; + + const loggingProxy = new class implements MainThreadDiagnosticsShape { + $changeMany(owner: string, entries: [UriComponents, IMarkerData[] | undefined][]): void { + _proxy.$changeMany(owner, entries); + _logService.trace('[DiagnosticCollection] change many (extension, owner, uris)', extensionId.value, owner, entries.length === 0 ? 'CLEARING' : entries); + } + $clear(owner: string): void { + _proxy.$clear(owner); + _logService.trace('[DiagnosticCollection] remove all (extension, owner)', extensionId.value, owner); + } + dispose(): void { + _proxy.dispose(); + } + }; + + let owner: string; if (!name) { name = '_generated_diagnostic_collection_name_#' + ExtHostDiagnostics._idPool++; @@ -274,7 +286,7 @@ export class ExtHostDiagnostics implements ExtHostDiagnosticsShape { const result = new class extends DiagnosticCollection { constructor() { - super(name!, owner, ExtHostDiagnostics._maxDiagnosticsPerFile, _proxy, _onDidChangeDiagnostics); + super(name!, owner, ExtHostDiagnostics._maxDiagnosticsPerFile, loggingProxy, _onDidChangeDiagnostics); _collections.set(owner, this); } dispose() { diff --git a/src/vs/workbench/api/common/extHostDocumentSaveParticipant.ts b/src/vs/workbench/api/common/extHostDocumentSaveParticipant.ts index baeba3102a..c62ada5235 100644 --- a/src/vs/workbench/api/common/extHostDocumentSaveParticipant.ts +++ b/src/vs/workbench/api/common/extHostDocumentSaveParticipant.ts @@ -5,7 +5,6 @@ import { Event } from 'vs/base/common/event'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { sequence } from 'vs/base/common/async'; import { illegalState } from 'vs/base/common/errors'; import { ExtHostDocumentSaveParticipantShape, MainThreadTextEditorsShape, IWorkspaceEditDto } from 'vs/workbench/api/common/extHost.protocol'; import { TextEdit } from 'vs/workbench/api/common/extHostTypes'; @@ -48,26 +47,27 @@ export class ExtHostDocumentSaveParticipant implements ExtHostDocumentSavePartic }; } - $participateInSave(data: UriComponents, reason: SaveReason): Promise { + async $participateInSave(data: UriComponents, reason: SaveReason): Promise { const resource = URI.revive(data); - const entries = this._callbacks.toArray(); let didTimeout = false; const didTimeoutHandle = setTimeout(() => didTimeout = true, this._thresholds.timeout); - const promise = sequence(entries.map(listener => { - return () => { - + const results: boolean[] = []; + try { + for (let listener of [...this._callbacks]) { // copy to prevent concurrent modifications if (didTimeout) { // timeout - no more listeners - return Promise.resolve(); + break; } - const document = this._documents.getDocument(resource); - return this._deliverEventAsyncAndBlameBadListeners(listener, { document, reason: TextDocumentSaveReason.to(reason) }); - }; - })); - return promise.finally(() => clearTimeout(didTimeoutHandle)); + const success = await this._deliverEventAsyncAndBlameBadListeners(listener, { document, reason: TextDocumentSaveReason.to(reason) }); + results.push(success); + } + } finally { + clearTimeout(didTimeoutHandle); + } + return results; } private _deliverEventAsyncAndBlameBadListeners([listener, thisArg, extension]: Listener, stubEvent: vscode.TextDocumentWillSaveEvent): Promise { diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index 25b205117a..ea73fbb1bb 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -7,7 +7,7 @@ import { URI, UriComponents } from 'vs/base/common/uri'; import { mixin } from 'vs/base/common/objects'; import type * as vscode from 'vscode'; import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; -import { Range, Disposable, CompletionList, SnippetString, CodeActionKind, SymbolInformation, DocumentSymbol, SemanticTokensEdits } from 'vs/workbench/api/common/extHostTypes'; +import { Range, Disposable, CompletionList, SnippetString, CodeActionKind, SymbolInformation, DocumentSymbol, SemanticTokensEdits, SemanticTokens, SemanticTokensEdit } from 'vs/workbench/api/common/extHostTypes'; import { ISingleEditOperation } from 'vs/editor/common/model'; import * as modes from 'vs/editor/common/modes'; import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; @@ -676,6 +676,13 @@ class SemanticTokensPreviousResult { ) { } } +type RelaxedSemanticTokens = { readonly resultId?: string; readonly data: number[]; }; +type RelaxedSemanticTokensEdit = { readonly start: number; readonly deleteCount: number; readonly data?: number[]; }; +type RelaxedSemanticTokensEdits = { readonly resultId?: string; readonly edits: RelaxedSemanticTokensEdit[]; }; + +type ProvidedSemanticTokens = vscode.SemanticTokens | RelaxedSemanticTokens; +type ProvidedSemanticTokensEdits = vscode.SemanticTokensEdits | RelaxedSemanticTokensEdits; + export class DocumentSemanticTokensAdapter { private readonly _previousResults: Map; @@ -696,13 +703,14 @@ export class DocumentSemanticTokensAdapter { return this._provider.provideDocumentSemanticTokensEdits(doc, previousResult.resultId, token); } return this._provider.provideDocumentSemanticTokens(doc, token); - }).then(value => { + }).then((value: ProvidedSemanticTokens | ProvidedSemanticTokensEdits | null | undefined) => { if (previousResult) { this._previousResults.delete(previousResultId); } if (!value) { return null; } + value = DocumentSemanticTokensAdapter._fixProvidedSemanticTokens(value); return this._send(DocumentSemanticTokensAdapter._convertToEdits(previousResult, value), value); }); } @@ -711,12 +719,40 @@ export class DocumentSemanticTokensAdapter { this._previousResults.delete(semanticColoringResultId); } - private static _isSemanticTokens(v: vscode.SemanticTokens | vscode.SemanticTokensEdits): v is vscode.SemanticTokens { - return v && !!((v as vscode.SemanticTokens).data); + private static _fixProvidedSemanticTokens(v: ProvidedSemanticTokens | ProvidedSemanticTokensEdits): vscode.SemanticTokens | vscode.SemanticTokensEdits { + if (DocumentSemanticTokensAdapter._isSemanticTokens(v)) { + if (DocumentSemanticTokensAdapter._isCorrectSemanticTokens(v)) { + return v; + } + return new SemanticTokens(new Uint32Array(v.data), v.resultId); + } else if (DocumentSemanticTokensAdapter._isSemanticTokensEdits(v)) { + if (DocumentSemanticTokensAdapter._isCorrectSemanticTokensEdits(v)) { + return v; + } + return new SemanticTokensEdits(v.edits.map(edit => new SemanticTokensEdit(edit.start, edit.deleteCount, edit.data ? new Uint32Array(edit.data) : edit.data as undefined)), v.resultId); // {{SQL CARBON EDIT}} strict-null-checks + } + return v; } - private static _isSemanticTokensEdits(v: vscode.SemanticTokens | vscode.SemanticTokensEdits): v is vscode.SemanticTokensEdits { - return v && Array.isArray((v as vscode.SemanticTokensEdits).edits); + private static _isSemanticTokens(v: ProvidedSemanticTokens | ProvidedSemanticTokensEdits): v is ProvidedSemanticTokens { + return v && !!((v as ProvidedSemanticTokens).data); + } + + private static _isCorrectSemanticTokens(v: ProvidedSemanticTokens): v is vscode.SemanticTokens { + return (v.data instanceof Uint32Array); + } + + private static _isSemanticTokensEdits(v: ProvidedSemanticTokens | ProvidedSemanticTokensEdits): v is ProvidedSemanticTokensEdits { + return v && Array.isArray((v as ProvidedSemanticTokensEdits).edits); + } + + private static _isCorrectSemanticTokensEdits(v: ProvidedSemanticTokensEdits): v is vscode.SemanticTokensEdits { + for (const edit of v.edits) { + if (!(edit.data instanceof Uint32Array)) { + return false; + } + } + return true; } private static _convertToEdits(previousResult: SemanticTokensPreviousResult | null | undefined, newResult: vscode.SemanticTokens | vscode.SemanticTokensEdits): vscode.SemanticTokens | vscode.SemanticTokensEdits { @@ -876,33 +912,30 @@ class SuggestAdapter { for (let i = 0; i < list.items.length; i++) { const item = list.items[i]; // check for bad completion item first - if (this._validateCompletionItem(item, pos)) { - const dto = this._convertCompletionItem(item, [pid, i], insertRange, replaceRange); - completions.push(dto); - } + const dto = this._convertCompletionItem(item, [pid, i], insertRange, replaceRange); + completions.push(dto); } return result; } - async resolveCompletionItem(_resource: URI, position: IPosition, id: extHostProtocol.ChainedCacheId, token: CancellationToken): Promise { + async resolveCompletionItem(id: extHostProtocol.ChainedCacheId, token: CancellationToken): Promise { if (typeof this._provider.resolveCompletionItem !== 'function') { - return Promise.resolve(undefined); + return undefined; } const item = this._cache.get(...id); if (!item) { - return Promise.resolve(undefined); + return undefined; } - const pos = typeConvert.Position.to(position); const _mustNotChange = SuggestAdapter._mustNotChangeHash(item); const _mayNotChange = SuggestAdapter._mayNotChangeHash(item); const resolvedItem = await asPromise(() => this._provider.resolveCompletionItem!(item, token)); - if (!resolvedItem || !this._validateCompletionItem(resolvedItem, pos)) { + if (!resolvedItem) { return undefined; } @@ -952,14 +985,14 @@ class SuggestAdapter { // x: id, // - [extHostProtocol.ISuggestDataDtoField.label]: item.label, + [extHostProtocol.ISuggestDataDtoField.label]: item.label ?? '', [extHostProtocol.ISuggestDataDtoField.label2]: item.label2, [extHostProtocol.ISuggestDataDtoField.kind]: item.kind !== undefined ? typeConvert.CompletionItemKind.from(item.kind) : undefined, [extHostProtocol.ISuggestDataDtoField.kindModifier]: item.tags && item.tags.map(typeConvert.CompletionItemTag.from), [extHostProtocol.ISuggestDataDtoField.detail]: item.detail, [extHostProtocol.ISuggestDataDtoField.documentation]: typeof item.documentation === 'undefined' ? undefined : typeConvert.MarkdownString.fromStrict(item.documentation), - [extHostProtocol.ISuggestDataDtoField.sortText]: item.sortText, - [extHostProtocol.ISuggestDataDtoField.filterText]: item.filterText, + [extHostProtocol.ISuggestDataDtoField.sortText]: item.sortText !== item.label ? item.sortText : undefined, + [extHostProtocol.ISuggestDataDtoField.filterText]: item.filterText !== item.label ? item.filterText : undefined, [extHostProtocol.ISuggestDataDtoField.preselect]: item.preselect || undefined, [extHostProtocol.ISuggestDataDtoField.insertTextRules]: item.keepWhitespace ? modes.CompletionItemInsertTextRule.KeepWhitespace : 0, [extHostProtocol.ISuggestDataDtoField.commitCharacters]: item.commitCharacters, @@ -1003,31 +1036,6 @@ class SuggestAdapter { return result; } - private _validateCompletionItem(item: vscode.CompletionItem, position: vscode.Position): boolean { - if (typeof item.label !== 'string' || item.label.length === 0) { - this._logService.warn('INVALID text edit -> must have at least a label'); - return false; - } - - if (Range.isRange(item.range)) { - if (!item.range.isSingleLine || item.range.start.line !== position.line) { - this._logService.trace('INVALID range -> must be single line and on the same line'); - return false; - } - - } else if (item.range) { - if (!item.range.inserting.isSingleLine || item.range.inserting.start.line !== position.line - || !item.range.replacing.isSingleLine || item.range.replacing.start.line !== position.line - || !item.range.inserting.start.isEqual(item.range.replacing.start) - || !item.range.replacing.contains(item.range.inserting) - ) { - this._logService.trace('INVALID range -> must be single line, on the same line, insert range must be a prefix of replace range'); - return false; - } - } - return true; - } - private static _mustNotChangeHash(item: vscode.CompletionItem) { const res = JSON.stringify([item.label, item.sortText, item.filterText, item.insertText, item.range]); return res; @@ -1781,8 +1789,8 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF return this._withAdapter(handle, SuggestAdapter, adapter => adapter.provideCompletionItems(URI.revive(resource), position, context, token), undefined); } - $resolveCompletionItem(handle: number, resource: UriComponents, position: IPosition, id: extHostProtocol.ChainedCacheId, token: CancellationToken): Promise { - return this._withAdapter(handle, SuggestAdapter, adapter => adapter.resolveCompletionItem(URI.revive(resource), position, id, token), undefined); + $resolveCompletionItem(handle: number, id: extHostProtocol.ChainedCacheId, token: CancellationToken): Promise { + return this._withAdapter(handle, SuggestAdapter, adapter => adapter.resolveCompletionItem(id, token), undefined); } $releaseCompletionItems(handle: number, id: number): void { diff --git a/src/vs/workbench/api/common/extHostNotebook.ts b/src/vs/workbench/api/common/extHostNotebook.ts index ceaa33761b..7d6945ad8a 100644 --- a/src/vs/workbench/api/common/extHostNotebook.ts +++ b/src/vs/workbench/api/common/extHostNotebook.ts @@ -626,12 +626,13 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN private readonly _proxy: MainThreadNotebookShape; private readonly _notebookProviders = new Map(); + private readonly _notebookContentProviders = new Map(); private readonly _documents = new Map(); - private readonly _editors = new Map }>(); + private readonly _editors = new Map; }>(); private readonly _notebookOutputRenderers = new Map(); - private readonly _onDidChangeNotebookDocument = new Emitter<{ document: ExtHostNotebookDocument, changes: NotebookCellsChangedEvent[] }>(); - readonly onDidChangeNotebookDocument: Event<{ document: ExtHostNotebookDocument, changes: NotebookCellsChangedEvent[] }> = this._onDidChangeNotebookDocument.event; + private readonly _onDidChangeNotebookDocument = new Emitter<{ document: ExtHostNotebookDocument, changes: NotebookCellsChangedEvent[]; }>(); + readonly onDidChangeNotebookDocument: Event<{ document: ExtHostNotebookDocument, changes: NotebookCellsChangedEvent[]; }> = this._onDidChangeNotebookDocument.event; private _outputDisplayOrder: INotebookDisplayOrder | undefined; @@ -651,6 +652,11 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN return this._activeNotebookEditor; } + private _onDidOpenNotebookDocument = new Emitter(); + onDidOpenNotebookDocument: Event = this._onDidOpenNotebookDocument.event; + private _onDidCloseNotebookDocument = new Emitter(); + onDidCloseNotebookDocument: Event = this._onDidCloseNotebookDocument.event; + constructor(mainContext: IMainContext, commands: ExtHostCommands, private _documentsAndEditors: ExtHostDocumentsAndEditors) { this._proxy = mainContext.getProxy(MainContext.MainThreadNotebook); @@ -718,7 +724,82 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN }); } + registerNotebookContentProvider( + extension: IExtensionDescription, + viewType: string, + provider: vscode.NotebookContentProvider, + ): vscode.Disposable { + + if (this._notebookProviders.has(viewType)) { + throw new Error(`Notebook provider for '${viewType}' already registered`); + } + + this._notebookContentProviders.set(viewType, { extension, provider }); + this._proxy.$registerNotebookProvider({ id: extension.identifier, location: extension.extensionLocation }, viewType); + return new VSCodeDisposable(() => { + this._notebookContentProviders.delete(viewType); + this._proxy.$unregisterNotebookProvider(viewType); + }); + } + + async _resolveNotebookFromContentProvider(viewType: string, uri: UriComponents): Promise { + let provider = this._notebookContentProviders.get(viewType); + + if (provider) { + const revivedUri = URI.revive(uri); + if (!this._documents.has(revivedUri.toString())) { + let document = new ExtHostNotebookDocument(this._proxy, this._documentsAndEditors, viewType, revivedUri, this); + await this._proxy.$createNotebookDocument( + document.handle, + viewType, + uri + ); + + this._documents.set(revivedUri.toString(), document); + } + + const onDidReceiveMessage = new Emitter(); + + let editor = new ExtHostNotebookEditor( + viewType, + `${ExtHostNotebookController._handlePool++}`, + revivedUri, + this._proxy, + onDidReceiveMessage, + this._documents.get(revivedUri.toString())!, + this._documentsAndEditors + ); + + this._editors.set(revivedUri.toString(), { editor, onDidReceiveMessage }); + + const data = await provider.provider.openNotebook(revivedUri); + editor.document.languages = data.languages; + editor.document.metadata = { + ...notebookDocumentMetadataDefaults, + ...data.metadata + }; + + await editor.edit(editBuilder => { + for (let i = 0; i < data.cells.length; i++) { + const cell = data.cells[i]; + editBuilder.insert(0, cell.source, cell.language, cell.cellKind, cell.outputs, cell.metadata); + } + }); + + this._onDidOpenNotebookDocument.fire(editor.document); + return editor.document.handle; + } else { + return Promise.resolve(undefined); + } + } + async $resolveNotebook(viewType: string, uri: UriComponents): Promise { + let notebookFromNotebookContentProvider = await this._resolveNotebookFromContentProvider(viewType, uri); + + if (notebookFromNotebookContentProvider !== undefined) { + return notebookFromNotebookContentProvider; + } + let provider = this._notebookProviders.get(viewType); if (provider) { @@ -755,25 +836,45 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN } async $executeNotebook(viewType: string, uri: UriComponents, cellHandle: number | undefined, token: CancellationToken): Promise { - let provider = this._notebookProviders.get(viewType); - - if (!provider) { - return; - } - let document = this._documents.get(URI.revive(uri).toString()); if (!document) { return; } + if (this._notebookContentProviders.has(viewType)) { + let cell = cellHandle !== undefined ? document.getCell(cellHandle) : undefined; + + return this._notebookContentProviders.get(viewType)!.provider.executeCell(document, cell, token); + } + + let provider = this._notebookProviders.get(viewType); + + if (!provider) { + return; + } + let cell = cellHandle !== undefined ? document.getCell(cellHandle) : undefined; return provider.provider.executeCell(document!, cell, token); } - async $saveNotebook(viewType: string, uri: UriComponents): Promise { - let provider = this._notebookProviders.get(viewType); + async $saveNotebook(viewType: string, uri: UriComponents, token: CancellationToken): Promise { let document = this._documents.get(URI.revive(uri).toString()); + if (!document) { + return false; + } + + if (this._notebookContentProviders.has(viewType)) { + try { + await this._notebookContentProviders.get(viewType)!.provider.saveNotebook(document, token); + } catch (e) { + return false; + } + + return true; + } + + let provider = this._notebookProviders.get(viewType); if (provider && document) { return await provider.provider.save(document); @@ -799,6 +900,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN if (document) { document.dispose(); this._documents.delete(URI.revive(uri).toString()); + this._onDidCloseNotebookDocument.fire(document); } let editor = this._editors.get(URI.revive(uri).toString()); diff --git a/src/vs/workbench/api/common/extHostQuickOpen.ts b/src/vs/workbench/api/common/extHostQuickOpen.ts index ff7703bdd8..025d439830 100644 --- a/src/vs/workbench/api/common/extHostQuickOpen.ts +++ b/src/vs/workbench/api/common/extHostQuickOpen.ts @@ -159,17 +159,16 @@ export class ExtHostQuickOpen implements ExtHostQuickOpenShape { // ---- workspace folder picker - showWorkspaceFolderPick(options?: WorkspaceFolderPickOptions, token = CancellationToken.None): Promise { - return this._commands.executeCommand('_workbench.pickWorkspaceFolder', [options]).then(async (selectedFolder: WorkspaceFolder) => { - if (!selectedFolder) { - return undefined; - } - const workspaceFolders = await this._workspace.getWorkspaceFolders2(); - if (!workspaceFolders) { - return undefined; - } - return workspaceFolders.filter(folder => folder.uri.toString() === selectedFolder.uri.toString())[0]; - }); + async showWorkspaceFolderPick(options?: WorkspaceFolderPickOptions, token = CancellationToken.None): Promise { + const selectedFolder = await this._commands.executeCommand('_workbench.pickWorkspaceFolder', [options]); + if (!selectedFolder) { + return undefined; + } + const workspaceFolders = await this._workspace.getWorkspaceFolders2(); + if (!workspaceFolders) { + return undefined; + } + return workspaceFolders.find(folder => folder.uri.toString() === selectedFolder.uri.toString()); } // ---- QuickInput diff --git a/src/vs/workbench/api/common/extHostRequireInterceptor.ts b/src/vs/workbench/api/common/extHostRequireInterceptor.ts index fc598e91ab..2131aa8ea6 100644 --- a/src/vs/workbench/api/common/extHostRequireInterceptor.ts +++ b/src/vs/workbench/api/common/extHostRequireInterceptor.ts @@ -11,7 +11,6 @@ import { nullExtensionDescription } from 'vs/workbench/services/extensions/commo import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; import * as vscode from 'vscode'; import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; -import { endsWith } from 'vs/base/common/strings'; import { IExtensionApiFactory } from 'vs/workbench/api/common/extHost.api.impl'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; @@ -196,7 +195,7 @@ class KeytarNodeModuleFactory implements INodeModuleFactory { return undefined; } const sep = length - 7; - if ((name.charAt(sep) === '/' || name.charAt(sep) === '\\') && endsWith(name, 'keytar')) { + if ((name.charAt(sep) === '/' || name.charAt(sep) === '\\') && name.endsWith('keytar')) { name = name.replace(/\\/g, '/'); if (this.alternativeNames.has(name)) { return 'keytar'; diff --git a/src/vs/workbench/api/common/extHostTreeViews.ts b/src/vs/workbench/api/common/extHostTreeViews.ts index 489f257eb4..220a9d0e83 100644 --- a/src/vs/workbench/api/common/extHostTreeViews.ts +++ b/src/vs/workbench/api/common/extHostTreeViews.ts @@ -155,7 +155,7 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape { } } -type Root = null | undefined; +export type Root = null | undefined | void; // {{SQL CARBON EDIT}} export interface type TreeData = { message: boolean, element: T | Root | false }; export interface TreeNode extends IDisposable { // {{SQL CARBON EDIT}} export interface diff --git a/src/vs/workbench/api/common/menusExtensionPoint.ts b/src/vs/workbench/api/common/menusExtensionPoint.ts index 0517ba42d3..4077020a39 100644 --- a/src/vs/workbench/api/common/menusExtensionPoint.ts +++ b/src/vs/workbench/api/common/menusExtensionPoint.ts @@ -10,7 +10,7 @@ import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { forEach } from 'vs/base/common/collections'; import { IExtensionPointUser, ExtensionMessageCollector, ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { MenuId, MenuRegistry, ILocalizedString, IMenuItem } from 'vs/platform/actions/common/actions'; +import { MenuId, MenuRegistry, ILocalizedString, IMenuItem, ICommandAction } from 'vs/platform/actions/common/actions'; import { URI } from 'vs/base/common/uri'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; @@ -387,7 +387,7 @@ export const commandsExtensionPoint = ExtensionsRegistry.registerExtensionPoint< commandsExtensionPoint.setHandler(extensions => { - function handleCommand(userFriendlyCommand: schema.IUserFriendlyCommand, extension: IExtensionPointUser) { + function handleCommand(userFriendlyCommand: schema.IUserFriendlyCommand, extension: IExtensionPointUser, bucket: ICommandAction[]) { if (!schema.isValidCommand(userFriendlyCommand, extension.collector)) { return; @@ -411,29 +411,30 @@ commandsExtensionPoint.setHandler(extensions => { if (MenuRegistry.getCommand(command)) { extension.collector.info(localize('dup', "Command `{0}` appears multiple times in the `commands` section.", userFriendlyCommand.command)); } - const registration = MenuRegistry.addCommand({ + bucket.push({ id: command, title, category, precondition: ContextKeyExpr.deserialize(enablement), icon: absoluteIcon }); - _commandRegistrations.add(registration); } // remove all previous command registrations _commandRegistrations.clear(); + const newCommands: ICommandAction[] = []; for (const extension of extensions) { const { value } = extension; if (Array.isArray(value)) { for (const command of value) { - handleCommand(command, extension); + handleCommand(command, extension, newCommands); } } else { - handleCommand(value, extension); + handleCommand(value, extension, newCommands); } } + _commandRegistrations.add(MenuRegistry.addCommands(newCommands)); }); const _menuRegistrations = new DisposableStore(); @@ -446,6 +447,8 @@ ExtensionsRegistry.registerExtensionPoint<{ [loc: string]: schema.IUserFriendlyM // remove all previous menu registrations _menuRegistrations.clear(); + const items: { id: MenuId, item: IMenuItem }[] = []; + for (let extension of extensions) { const { value, collector } = extension; @@ -467,7 +470,7 @@ ExtensionsRegistry.registerExtensionPoint<{ [loc: string]: schema.IUserFriendlyM for (let item of entry.value) { let command = MenuRegistry.getCommand(item.command); - let alt = item.alt && MenuRegistry.getCommand(item.alt); + let alt = item.alt && MenuRegistry.getCommand(item.alt) || undefined; if (!command) { collector.error(localize('missing.command', "Menu item references a command `{0}` which is not defined in the 'commands' section.", item.command)); @@ -492,15 +495,19 @@ ExtensionsRegistry.registerExtensionPoint<{ [loc: string]: schema.IUserFriendlyM } } - const registration = MenuRegistry.appendMenuItem(menu, { - command, - alt, - group, - order, - when: ContextKeyExpr.deserialize(item.when) - } as IMenuItem); - _menuRegistrations.add(registration); + items.push({ + id: menu, + item: { + command, + alt, + group, + order, + when: ContextKeyExpr.deserialize(item.when) + } + }); } }); } + + _menuRegistrations.add(MenuRegistry.appendMenuItems(items)); }); diff --git a/src/vs/workbench/api/worker/extHostExtensionService.ts b/src/vs/workbench/api/worker/extHostExtensionService.ts index 39bf16efc4..11172233b5 100644 --- a/src/vs/workbench/api/worker/extHostExtensionService.ts +++ b/src/vs/workbench/api/worker/extHostExtensionService.ts @@ -6,7 +6,6 @@ import { createApiFactoryAndRegisterActors } from 'sql/workbench/api/common/sqlExtHost.api.impl'; // {{SQL CARBON EDIT}} replace with ours import { ExtensionActivationTimesBuilder } from 'vs/workbench/api/common/extHostExtensionActivator'; import { AbstractExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService'; -import { endsWith } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; import { RequireInterceptor } from 'vs/workbench/api/common/extHostRequireInterceptor'; @@ -79,5 +78,5 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService { } function ensureSuffix(path: string, suffix: string): string { - return endsWith(path, suffix) ? path : path + suffix; + return path.endsWith(suffix) ? path : path + suffix; } diff --git a/src/vs/workbench/browser/actions/layoutActions.ts b/src/vs/workbench/browser/actions/layoutActions.ts index 10d178652f..669d4ef6de 100644 --- a/src/vs/workbench/browser/actions/layoutActions.ts +++ b/src/vs/workbench/browser/actions/layoutActions.ts @@ -487,7 +487,7 @@ export class ResetViewLocationsAction extends Action { } async run(): Promise { - this.viewDescriptorService.getViewContainers().forEach(viewContainer => { + this.viewDescriptorService.viewContainers.forEach(viewContainer => { const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer); viewContainerModel.allViewDescriptors.forEach(viewDescriptor => { @@ -596,7 +596,7 @@ export class MoveFocusedViewAction extends Action { }); } - const pinnedViewlets = this.activityBarService.getPinnedViewletIds(); + const pinnedViewlets = this.activityBarService.getPinnedViewContainerIds(); items.push(...pinnedViewlets .filter(viewletId => { if (viewletId === this.viewDescriptorService.getViewContainerByViewId(focusedViewId)!.id) { diff --git a/src/vs/workbench/browser/actions/workspaceCommands.ts b/src/vs/workbench/browser/actions/workspaceCommands.ts index e46a59ad27..7259d9d1fa 100644 --- a/src/vs/workbench/browser/actions/workspaceCommands.ts +++ b/src/vs/workbench/browser/actions/workspaceCommands.ts @@ -7,7 +7,6 @@ import * as nls from 'vs/nls'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing'; import * as resources from 'vs/base/common/resources'; -import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { CancellationToken } from 'vs/base/common/cancellation'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; @@ -19,6 +18,7 @@ import { getIconClasses } from 'vs/editor/common/services/getIconClasses'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IViewDescriptorService, IViewsService, ViewContainerLocation } from 'vs/workbench/common/views'; export const ADD_ROOT_FOLDER_COMMAND_ID = 'addRootFolder'; export const ADD_ROOT_FOLDER_LABEL = nls.localize('addFolderToWorkspace', "Add Folder to Workspace..."); @@ -55,7 +55,8 @@ CommandsRegistry.registerCommand({ CommandsRegistry.registerCommand({ id: ADD_ROOT_FOLDER_COMMAND_ID, handler: async (accessor) => { - const viewletService = accessor.get(IViewletService); + const viewDescriptorService = accessor.get(IViewDescriptorService); + const viewsService = accessor.get(IViewsService); const workspaceEditingService = accessor.get(IWorkspaceEditingService); const dialogsService = accessor.get(IFileDialogService); const folders = await dialogsService.showOpenDialog({ @@ -71,7 +72,7 @@ CommandsRegistry.registerCommand({ } await workspaceEditingService.addFolders(folders.map(folder => ({ uri: resources.removeTrailingPathSeparator(folder) }))); - await viewletService.openViewlet(viewletService.getDefaultViewletId(), true); + await viewsService.openViewContainer(viewDescriptorService.getDefaultViewContainer(ViewContainerLocation.Sidebar)!.id, true); } }); diff --git a/src/vs/workbench/browser/composite.ts b/src/vs/workbench/browser/composite.ts index f96a254895..e4731814f0 100644 --- a/src/vs/workbench/browser/composite.ts +++ b/src/vs/workbench/browser/composite.ts @@ -15,7 +15,6 @@ import { trackFocus, Dimension } from 'vs/base/browser/dom'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { Disposable } from 'vs/base/common/lifecycle'; import { assertIsDefined } from 'vs/base/common/types'; -import { find } from 'vs/base/common/arrays'; /** * Composites are layed out in the sidebar and panel part of the workbench. At a time only one composite @@ -256,7 +255,7 @@ export abstract class CompositeRegistry extends Disposable private readonly _onDidDeregister = this._register(new Emitter>()); readonly onDidDeregister = this._onDidDeregister.event; - private composites: CompositeDescriptor[] = []; + private readonly composites: CompositeDescriptor[] = []; protected registerComposite(descriptor: CompositeDescriptor): void { if (this.compositeById(descriptor.id)) { @@ -286,6 +285,6 @@ export abstract class CompositeRegistry extends Disposable } private compositeById(id: string): CompositeDescriptor | undefined { - return find(this.composites, composite => composite.id === id); + return this.composites.find(composite => composite.id === id); } } diff --git a/src/vs/workbench/browser/editor.ts b/src/vs/workbench/browser/editor.ts index bcd7f2a582..3cea564a0b 100644 --- a/src/vs/workbench/browser/editor.ts +++ b/src/vs/workbench/browser/editor.ts @@ -8,7 +8,7 @@ import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { Registry } from 'vs/platform/registry/common/platform'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { IConstructorSignature0, IInstantiationService, BrandedService } from 'vs/platform/instantiation/common/instantiation'; -import { find, insert } from 'vs/base/common/arrays'; +import { insert } from 'vs/base/common/arrays'; import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; export interface IEditorDescriptor { @@ -154,7 +154,7 @@ class EditorRegistry implements IEditorRegistry { } getEditorById(editorId: string): EditorDescriptor | undefined { - return find(this.editors, editor => editor.getId() === editorId); + return this.editors.find(editor => editor.getId() === editorId); } getEditors(): readonly EditorDescriptor[] { diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 7a538d4b69..593e3371e2 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -43,6 +43,7 @@ import { WINDOW_ACTIVE_BORDER, WINDOW_INACTIVE_BORDER } from 'vs/workbench/commo import { LineNumbersType } from 'vs/editor/common/config/editorOptions'; import { ActivitybarPart } from 'vs/workbench/browser/parts/activitybar/activitybarPart'; import { URI } from 'vs/base/common/uri'; +import { IViewDescriptorService, ViewContainerLocation } from 'vs/workbench/common/views'; export enum Settings { ACTIVITYBAR_VISIBLE = 'workbench.activityBar.visible', @@ -171,6 +172,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi private panelService!: IPanelService; private titleService!: ITitleService; private viewletService!: IViewletService; + private viewDescriptorService!: IViewDescriptorService; private contextService!: IWorkspaceContextService; private backupFileService!: IBackupFileService; private notificationService!: INotificationService; @@ -255,6 +257,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.editorGroupService = accessor.get(IEditorGroupsService); this.panelService = accessor.get(IPanelService); this.viewletService = accessor.get(IViewletService); + this.viewDescriptorService = accessor.get(IViewDescriptorService); this.titleService = accessor.get(ITitleService); this.notificationService = accessor.get(INotificationService); accessor.get(IStatusbarService); // not used, but called to ensure instantiated @@ -489,11 +492,11 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi if (!this.state.sideBar.hidden) { // Only restore last viewlet if window was reloaded or we are in development mode - let viewletToRestore: string; + let viewletToRestore: string | undefined; if (!this.environmentService.isBuilt || lifecycleService.startupKind === StartupKind.ReloadedWindow || isWeb) { - viewletToRestore = this.storageService.get(SidebarPart.activeViewletSettingsKey, StorageScope.WORKSPACE, this.viewletService.getDefaultViewletId()); + viewletToRestore = this.storageService.get(SidebarPart.activeViewletSettingsKey, StorageScope.WORKSPACE, this.viewDescriptorService.getDefaultViewContainer(ViewContainerLocation.Sidebar)?.id); } else { - viewletToRestore = this.viewletService.getDefaultViewletId(); + viewletToRestore = this.viewDescriptorService.getDefaultViewContainer(ViewContainerLocation.Sidebar)?.id; } if (viewletToRestore) { @@ -639,7 +642,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } if (sidebarState.length) { - storageService.store(ActivitybarPart.PINNED_VIEWLETS, JSON.stringify(sidebarState), StorageScope.GLOBAL); + storageService.store(ActivitybarPart.PINNED_VIEW_CONTAINERS, JSON.stringify(sidebarState), StorageScope.GLOBAL); } } } @@ -1344,7 +1347,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi if (viewletToOpen) { const viewlet = this.viewletService.openViewlet(viewletToOpen, true); if (!viewlet) { - this.viewletService.openViewlet(this.viewletService.getDefaultViewletId(), true); + this.viewletService.openViewlet(this.viewDescriptorService.getDefaultViewContainer(ViewContainerLocation.Sidebar)?.id, true); } } } diff --git a/src/vs/workbench/browser/media/part.css b/src/vs/workbench/browser/media/part.css index 2cb7d75e32..389b283c40 100644 --- a/src/vs/workbench/browser/media/part.css +++ b/src/vs/workbench/browser/media/part.css @@ -19,7 +19,6 @@ width: 100%; height: 100%; backdrop-filter: brightness(97%) blur(2px); - pointer-events: none; visibility: hidden; opacity: 0; transition: opacity .5s, visibility .5s; diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts index 38d3848470..17c3680f38 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts @@ -11,7 +11,6 @@ import { EventType as TouchEventType, GestureEvent } from 'vs/base/browser/touch import { Action, IAction } from 'vs/base/common/actions'; import { KeyCode } from 'vs/base/common/keyCodes'; import { dispose } from 'vs/base/common/lifecycle'; -import { URI } from 'vs/base/common/uri'; import { SyncActionDescriptor, IMenuService, MenuId } from 'vs/platform/actions/common/actions'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -19,7 +18,6 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { activeContrastBorder, focusBorder } from 'vs/platform/theme/common/colorRegistry'; import { ICssStyleCollector, IColorTheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { ActivityAction, ActivityActionViewItem, ICompositeBar, ICompositeBarColors, ToggleCompositePinnedAction } from 'vs/workbench/browser/parts/compositeBarActions'; -import { ViewletDescriptor } from 'vs/workbench/browser/viewlet'; import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions'; import { IActivity } from 'vs/workbench/common/activity'; import { ACTIVITY_BAR_FOREGROUND, ACTIVITY_BAR_ACTIVE_BORDER, ACTIVITY_BAR_ACTIVE_FOCUS_BORDER, ACTIVITY_BAR_ACTIVE_BACKGROUND, ACTIVITY_BAR_BACKGROUND } from 'vs/workbench/common/theme'; @@ -30,9 +28,8 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { Codicon } from 'vs/base/common/codicons'; -import { isString } from 'vs/base/common/types'; -export class ViewletActivityAction extends ActivityAction { +export class ViewContainerActivityAction extends ActivityAction { private static readonly preventDoubleClickDelay = 300; @@ -48,7 +45,6 @@ export class ViewletActivityAction extends ActivityAction { @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @ITelemetryService telemetryService: ITelemetryService ) { - ViewletActivityAction.generateIconCSS(activity); super(activity); this.lastRun = 0; @@ -57,23 +53,7 @@ export class ViewletActivityAction extends ActivityAction { this.telemetryService = telemetryService; } - private static generateIconCSS(activity: IActivity): void { - if (activity.iconUrl) { - activity.cssClass = activity.cssClass || `activity-${activity.id.replace(/\./g, '-')}`; - const iconClass = `.monaco-workbench .activitybar .monaco-action-bar .action-label.${activity.cssClass}`; - DOM.createCSSRule(iconClass, ` - mask: ${DOM.asCSSUrl(activity.iconUrl)} no-repeat 50% 50%; - mask-size: 24px; - -webkit-mask: ${DOM.asCSSUrl(activity.iconUrl)} no-repeat 50% 50%; - -webkit-mask-size: 24px; - `); - } - } - - setActivity(activity: IActivity): void { - if (activity.iconUrl && this.activity.cssClass !== activity.cssClass) { - ViewletActivityAction.generateIconCSS(activity); - } + updateActivity(activity: IActivity): void { this.activity = activity; } @@ -84,7 +64,7 @@ export class ViewletActivityAction extends ActivityAction { // prevent accident trigger on a doubleclick (to help nervous people) const now = Date.now(); - if (now > this.lastRun /* https://github.com/Microsoft/vscode/issues/25830 */ && now - this.lastRun < ViewletActivityAction.preventDoubleClickDelay) { + if (now > this.lastRun /* https://github.com/Microsoft/vscode/issues/25830 */ && now - this.lastRun < ViewContainerActivityAction.preventDoubleClickDelay) { return; } this.lastRun = now; @@ -113,30 +93,6 @@ export class ViewletActivityAction extends ActivityAction { } } -export class ToggleViewletAction extends Action { - - constructor( - private _viewlet: ViewletDescriptor, - @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, - @IViewletService private readonly viewletService: IViewletService - ) { - super(_viewlet.id, _viewlet.name); - } - - async run(): Promise { - const sideBarVisible = this.layoutService.isVisible(Parts.SIDEBAR_PART); - const activeViewlet = this.viewletService.getActiveViewlet(); - - // Hide sidebar if selected viewlet already visible - if (sideBarVisible && activeViewlet?.getId() === this._viewlet.id) { - this.layoutService.setSideBarHidden(true); - return; - } - - await this.viewletService.openViewlet(this._viewlet.id, true); - } -} - export class AccountsActionViewItem extends ActivityActionViewItem { constructor( action: ActivityAction, @@ -248,23 +204,7 @@ export class GlobalActivityActionViewItem extends ActivityActionViewItem { } } -export class PlaceHolderViewletActivityAction extends ViewletActivityAction { - - constructor( - id: string, - icon: URI | string | undefined, - @IViewletService viewletService: IViewletService, - @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, - @ITelemetryService telemetryService: ITelemetryService - ) { - super({ - id, - name: id, - iconUrl: URI.isUri(icon) ? icon : undefined, - cssClass: isString(icon) ? icon : undefined - }, viewletService, layoutService, telemetryService); - } -} +export class PlaceHolderViewContainerActivityAction extends ViewContainerActivityAction { } export class PlaceHolderToggleCompositePinnedAction extends ToggleCompositePinnedAction { @@ -289,16 +229,16 @@ class SwitchSideBarViewAction extends Action { } async run(offset: number): Promise { - const pinnedViewletIds = this.activityBarService.getPinnedViewletIds(); + const visibleViewletIds = this.activityBarService.getVisibleViewContainerIds(); const activeViewlet = this.viewletService.getActiveViewlet(); if (!activeViewlet) { return; } let targetViewletId: string | undefined; - for (let i = 0; i < pinnedViewletIds.length; i++) { - if (pinnedViewletIds[i] === activeViewlet.getId()) { - targetViewletId = pinnedViewletIds[(i + pinnedViewletIds.length + offset) % pinnedViewletIds.length]; + for (let i = 0; i < visibleViewletIds.length; i++) { + if (visibleViewletIds[i] === activeViewlet.getId()) { + targetViewletId = visibleViewletIds[(i + visibleViewletIds.length + offset) % visibleViewletIds.length]; break; } } diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index b7e21e3ddb..3f72b4249a 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -8,8 +8,7 @@ import * as nls from 'vs/nls'; import { ActionsOrientation, ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { GLOBAL_ACTIVITY_ID, IActivity } from 'vs/workbench/common/activity'; import { Part } from 'vs/workbench/browser/part'; -import { GlobalActivityActionViewItem, ViewletActivityAction, ToggleViewletAction, PlaceHolderToggleCompositePinnedAction, PlaceHolderViewletActivityAction, AccountsActionViewItem, HomeAction } from 'vs/workbench/browser/parts/activitybar/activitybarActions'; -import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; +import { GlobalActivityActionViewItem, ViewContainerActivityAction, PlaceHolderToggleCompositePinnedAction, PlaceHolderViewContainerActivityAction, AccountsActionViewItem, HomeAction } from 'vs/workbench/browser/parts/activitybar/activitybarActions'; import { IBadge, NumberBadge } from 'vs/workbench/services/activity/common/activity'; import { IWorkbenchLayoutService, Parts, Position as SideBarPosition } from 'vs/workbench/services/layout/browser/layoutService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -19,15 +18,13 @@ import { IThemeService, IColorTheme } from 'vs/platform/theme/common/themeServic import { ACTIVITY_BAR_BACKGROUND, ACTIVITY_BAR_BORDER, ACTIVITY_BAR_FOREGROUND, ACTIVITY_BAR_ACTIVE_BORDER, ACTIVITY_BAR_BADGE_BACKGROUND, ACTIVITY_BAR_BADGE_FOREGROUND, ACTIVITY_BAR_DRAG_AND_DROP_BACKGROUND, ACTIVITY_BAR_INACTIVE_FOREGROUND, ACTIVITY_BAR_ACTIVE_BACKGROUND } from 'vs/workbench/common/theme'; import { contrastBorder } from 'vs/platform/theme/common/colorRegistry'; import { CompositeBar, ICompositeBarItem, CompositeDragAndDrop } from 'vs/workbench/browser/parts/compositeBar'; -import { Dimension, addClass, removeNode } from 'vs/base/browser/dom'; +import { Dimension, addClass, removeNode, createCSSRule, asCSSUrl } from 'vs/base/browser/dom'; import { IStorageService, StorageScope, IWorkspaceStorageChangeEvent } from 'vs/platform/storage/common/storage'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { URI, UriComponents } from 'vs/base/common/uri'; import { ToggleCompositePinnedAction, ICompositeBarColors, ActivityAction, ICompositeActivity } from 'vs/workbench/browser/parts/compositeBarActions'; -import { ViewletDescriptor } from 'vs/workbench/browser/viewlet'; -import { IViewDescriptorService, ViewContainer, TEST_VIEW_CONTAINER_ID, IViewContainerModel, ViewContainerLocation } from 'vs/workbench/common/views'; +import { IViewDescriptorService, ViewContainer, TEST_VIEW_CONTAINER_ID, IViewContainerModel, ViewContainerLocation, IViewsService } from 'vs/workbench/common/views'; import { IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { IViewlet } from 'vs/workbench/common/viewlet'; import { isUndefinedOrNull, assertIsDefined, isString } from 'vs/base/common/types'; import { IActivityBarService } from 'vs/workbench/services/activityBar/browser/activityBarService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -42,8 +39,10 @@ import { getUserDataSyncStore } from 'vs/platform/userDataSync/common/userDataSy import { IProductService } from 'vs/platform/product/common/productService'; import { Before2D } from 'vs/workbench/browser/dnd'; import { Codicon, iconRegistry } from 'vs/base/common/codicons'; +import { Action } from 'vs/base/common/actions'; +import { Event } from 'vs/base/common/event'; -interface IPlaceholderViewlet { +interface IPlaceholderViewContainer { id: string; name?: string; iconUrl?: UriComponents; @@ -51,14 +50,14 @@ interface IPlaceholderViewlet { views?: { when?: string }[]; } -interface IPinnedViewlet { +interface IPinnedViewContainer { id: string; pinned: boolean; order?: number; visible: boolean; } -interface ICachedViewlet { +interface ICachedViewContainer { id: string; name?: string; icon?: URI | string; @@ -73,8 +72,8 @@ export class ActivitybarPart extends Part implements IActivityBarService { _serviceBrand: undefined; private static readonly ACTION_HEIGHT = 48; - static readonly PINNED_VIEWLETS = 'workbench.activity.pinnedViewlets2'; - private static readonly PLACEHOLDER_VIEWLETS = 'workbench.activity.placeholderViewlets'; + static readonly PINNED_VIEW_CONTAINERS = 'workbench.activity.pinnedViewlets2'; + private static readonly PLACEHOLDER_VIEW_CONTAINERS = 'workbench.activity.placeholderViewlets'; //#region IView @@ -99,18 +98,20 @@ export class ActivitybarPart extends Part implements IActivityBarService { private globalActivityActionBar: ActionBar | undefined; private readonly globalActivity: ICompositeActivity[] = []; - private readonly cachedViewlets: ICachedViewlet[] = []; - private readonly compositeActions = new Map(); - private readonly viewletDisposables = new Map(); + private readonly cachedViewContainers: ICachedViewContainer[] = []; + private readonly compositeActions = new Map(); + private readonly viewContainerDisposables = new Map(); + + private readonly location = ViewContainerLocation.Sidebar; constructor( - @IViewletService private readonly viewletService: IViewletService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @IThemeService themeService: IThemeService, @IStorageService private readonly storageService: IStorageService, @IExtensionService private readonly extensionService: IExtensionService, @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService, + @IViewsService private readonly viewsService: IViewsService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IConfigurationService private readonly configurationService: IConfigurationService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @@ -119,27 +120,27 @@ export class ActivitybarPart extends Part implements IActivityBarService { ) { super(Parts.ACTIVITYBAR_PART, { hasTitle: false }, themeService, storageService, layoutService); - storageKeysSyncRegistryService.registerStorageKey({ key: ActivitybarPart.PINNED_VIEWLETS, version: 1 }); - this.migrateFromOldCachedViewletsValue(); + storageKeysSyncRegistryService.registerStorageKey({ key: ActivitybarPart.PINNED_VIEW_CONTAINERS, version: 1 }); + this.migrateFromOldCachedViewContainersValue(); - this.cachedViewlets = this.getCachedViewlets(); - for (const cachedViewlet of this.cachedViewlets) { + this.cachedViewContainers = this.getCachedViewContainers(); + for (const cachedViewContainer of this.cachedViewContainers) { if (environmentService.configuration.remoteAuthority // In remote window, hide activity bar entries until registered. - || this.shouldBeHidden(cachedViewlet.id, cachedViewlet) + || this.shouldBeHidden(cachedViewContainer.id, cachedViewContainer) ) { - cachedViewlet.visible = false; + cachedViewContainer.visible = false; } } - const cachedItems = this.cachedViewlets + const cachedItems = this.cachedViewContainers .map(v => ({ id: v.id, name: v.name, visible: v.visible, order: v.order, pinned: v.pinned })); this.compositeBar = this._register(this.instantiationService.createInstance(CompositeBar, cachedItems, { icon: true, orientation: ActionsOrientation.VERTICAL, - openComposite: (compositeId: string) => this.viewletService.openViewlet(compositeId, true), + openComposite: (compositeId: string) => this.viewsService.openViewContainer(compositeId, true), getActivityAction: (compositeId: string) => this.getCompositeActions(compositeId).activityAction, getCompositePinnedAction: (compositeId: string) => this.getCompositeActions(compositeId).pinnedAction, - getOnCompositeClickAction: (compositeId: string) => this.instantiationService.createInstance(ToggleViewletAction, assertIsDefined(this.viewletService.getViewlet(compositeId))), + getOnCompositeClickAction: (compositeId: string) => new Action(compositeId, '', '', true, () => this.viewsService.isViewContainerVisible(compositeId) ? Promise.resolve(this.viewsService.closeViewContainer(compositeId)) : this.viewsService.openViewContainer(compositeId)), getContextMenuActions: () => { const menuBarVisibility = getMenuBarVisibility(this.configurationService, this.environmentService); const actions = []; @@ -152,10 +153,10 @@ export class ActivitybarPart extends Part implements IActivityBarService { return actions; }, getContextMenuActionsForComposite: () => [], - getDefaultCompositeId: () => this.viewletService.getDefaultViewletId(), + getDefaultCompositeId: () => this.viewDescriptorService.getDefaultViewContainer(this.location)!.id, hidePart: () => this.layoutService.setSideBarHidden(true), dndHandler: new CompositeDragAndDrop(this.viewDescriptorService, ViewContainerLocation.Sidebar, - (id: string, focus?: boolean) => this.viewletService.openViewlet(id, focus), + (id: string, focus?: boolean) => this.viewsService.openViewContainer(id, focus), (from: string, to: string, before?: Before2D) => this.compositeBar.move(from, to, before?.verticallyBefore) ), compositeSize: 52, @@ -163,28 +164,25 @@ export class ActivitybarPart extends Part implements IActivityBarService { overflowActionSize: ActivitybarPart.ACTION_HEIGHT })); + this.onDidRegisterViewContainers(this.getViewContainers()); this.registerListeners(); - this.onDidRegisterViewlets(viewletService.getViewlets()); } private registerListeners(): void { - // Viewlet registration - this._register(this.viewletService.onDidViewletRegister(viewlet => this.onDidRegisterViewlets([viewlet]))); - this._register(this.viewletService.onDidViewletDeregister(({ id }) => this.onDidDeregisterViewlet(id))); + // View Container Changes + this._register(this.viewDescriptorService.onDidChangeViewContainers(({ added, removed }) => this.onDidChangeViewContainers(added, removed))); + this._register(this.viewDescriptorService.onDidChangeContainerLocation(({ viewContainer, from, to }) => this.onDidChangeViewContainerLocation(viewContainer, from, to))); - // Activate viewlet action on opening of a viewlet - this._register(this.viewletService.onDidViewletOpen(viewlet => this.onDidViewletOpen(viewlet))); - - // Deactivate viewlet action on close - this._register(this.viewletService.onDidViewletClose(viewlet => this.compositeBar.deactivateComposite(viewlet.getId()))); + // View Container Visibility Changes + this._register(Event.filter(this.viewsService.onDidChangeViewContainerVisibility, e => e.location === this.location)(({ id, visible }) => this.onDidChangeViewContainerVisibility(id, visible))); // Extension registration let disposables = this._register(new DisposableStore()); this._register(this.extensionService.onDidRegisterExtensions(() => { disposables.clear(); this.onDidRegisterExtensions(); - this.compositeBar.onDidChange(() => this.saveCachedViewlets(), this, disposables); + this.compositeBar.onDidChange(() => this.saveCachedViewContainers(), this, disposables); this.storageService.onDidChangeStorage(e => this.onDidStorageChange(e), this, disposables); })); @@ -200,39 +198,57 @@ export class ActivitybarPart extends Part implements IActivityBarService { })); } - private onDidRegisterExtensions(): void { - this.removeNotExistingComposites(); - this.saveCachedViewlets(); + private onDidChangeViewContainers(added: ReadonlyArray<{ container: ViewContainer, location: ViewContainerLocation }>, removed: ReadonlyArray<{ container: ViewContainer, location: ViewContainerLocation }>) { + removed.filter(({ location }) => location === ViewContainerLocation.Sidebar).forEach(({ container }) => this.onDidDeregisterViewContainer(container)); + this.onDidRegisterViewContainers(added.filter(({ location }) => location === ViewContainerLocation.Sidebar).map(({ container }) => container)); } - private onDidViewletOpen(viewlet: IViewlet): void { - - // Update the composite bar by adding - const foundViewlet = this.viewletService.getViewlet(viewlet.getId()); - if (foundViewlet) { - this.compositeBar.addComposite(foundViewlet); + private onDidChangeViewContainerLocation(container: ViewContainer, from: ViewContainerLocation, to: ViewContainerLocation) { + if (from === this.location) { + this.onDidDeregisterViewContainer(container); } + if (to === this.location) { + this.onDidRegisterViewContainers([container]); + } + } - this.compositeBar.activateComposite(viewlet.getId()); + private onDidChangeViewContainerVisibility(id: string, visible: boolean) { + if (visible) { + // Activate view container action on opening of a view container + this.onDidViewContainerVisible(id); + } else { + // Deactivate view container action on close + this.compositeBar.deactivateComposite(id); + } + } - const viewletDescriptor = this.viewletService.getViewlet(viewlet.getId()); - if (viewletDescriptor) { - const viewContainer = this.getViewContainer(viewletDescriptor.id); - if (viewContainer?.hideIfEmpty) { + private onDidRegisterExtensions(): void { + this.removeNotExistingComposites(); + this.saveCachedViewContainers(); + } + + private onDidViewContainerVisible(id: string): void { + const viewContainer = this.getViewContainer(id); + if (viewContainer) { + // Update the composite bar by adding + this.compositeBar.addComposite(viewContainer); + this.compositeBar.activateComposite(viewContainer.id); + + if (viewContainer.hideIfEmpty) { const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer); if (viewContainerModel.activeViewDescriptors.length === 0) { - this.hideComposite(viewletDescriptor.id); // Update the composite bar by hiding + this.hideComposite(viewContainer.id); // Update the composite bar by hiding } } } } - showActivity(viewletOrActionId: string, badge: IBadge, clazz?: string, priority?: number): IDisposable { - if (this.viewletService.getViewlet(viewletOrActionId)) { - return this.compositeBar.showActivity(viewletOrActionId, badge, clazz, priority); + showActivity(viewContainerOrActionId: string, badge: IBadge, clazz?: string, priority?: number): IDisposable { + if (this.getViewContainer(viewContainerOrActionId)) { + return this.compositeBar.showActivity(viewContainerOrActionId, badge, clazz, priority); } - if (viewletOrActionId === GLOBAL_ACTIVITY_ID) { + if (viewContainerOrActionId === GLOBAL_ACTIVITY_ID) { return this.showGlobalActivity(badge, clazz, priority); } @@ -342,7 +358,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { this.installMenubar(); } - // Viewlets action bar + // View Containers action bar this.compositeBar.create(this.content); // Global action bar @@ -444,19 +460,20 @@ export class ActivitybarPart extends Part implements IActivityBarService { this.globalActivityActionBar.push(this.globalActivityAction); } - private getCompositeActions(compositeId: string): { activityAction: ViewletActivityAction, pinnedAction: ToggleCompositePinnedAction } { + private getCompositeActions(compositeId: string): { activityAction: ViewContainerActivityAction, pinnedAction: ToggleCompositePinnedAction } { let compositeActions = this.compositeActions.get(compositeId); if (!compositeActions) { - const viewlet = this.viewletService.getViewlet(compositeId); - if (viewlet) { + const viewContainer = this.getViewContainer(compositeId); + if (viewContainer) { + const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer); compositeActions = { - activityAction: this.instantiationService.createInstance(ViewletActivityAction, viewlet), - pinnedAction: new ToggleCompositePinnedAction(viewlet, this.compositeBar) + activityAction: this.instantiationService.createInstance(ViewContainerActivityAction, this.toActivity(viewContainer, viewContainerModel)), + pinnedAction: new ToggleCompositePinnedAction(viewContainer, this.compositeBar) }; } else { - const cachedComposite = this.cachedViewlets.filter(c => c.id === compositeId)[0]; + const cachedComposite = this.cachedViewContainers.filter(c => c.id === compositeId)[0]; compositeActions = { - activityAction: this.instantiationService.createInstance(PlaceHolderViewletActivityAction, compositeId, cachedComposite?.icon), + activityAction: this.instantiationService.createInstance(PlaceHolderViewContainerActivityAction, ActivitybarPart.toActivity(compositeId, compositeId, cachedComposite?.icon, undefined)), pinnedAction: new PlaceHolderToggleCompositePinnedAction(compositeId, this.compositeBar) }; } @@ -467,28 +484,27 @@ export class ActivitybarPart extends Part implements IActivityBarService { return compositeActions; } - private onDidRegisterViewlets(viewlets: ViewletDescriptor[]): void { - for (const viewlet of viewlets) { - const cachedViewlet = this.cachedViewlets.filter(({ id }) => id === viewlet.id)[0]; - const activeViewlet = this.viewletService.getActiveViewlet(); - const isActive = activeViewlet?.getId() === viewlet.id; + private onDidRegisterViewContainers(viewContainers: ReadonlyArray): void { + for (const viewContainer of viewContainers) { + const cachedViewContainer = this.cachedViewContainers.filter(({ id }) => id === viewContainer.id)[0]; + const visibleViewContainer = this.viewsService.getVisibleViewContainer(this.location); + const isActive = visibleViewContainer?.id === viewContainer.id; - if (isActive || !this.shouldBeHidden(viewlet.id, cachedViewlet)) { - this.compositeBar.addComposite(viewlet); + if (isActive || !this.shouldBeHidden(viewContainer.id, cachedViewContainer)) { + this.compositeBar.addComposite(viewContainer); // Pin it by default if it is new - if (!cachedViewlet) { - this.compositeBar.pin(viewlet.id); + if (!cachedViewContainer) { + this.compositeBar.pin(viewContainer.id); } if (isActive) { - this.compositeBar.activateComposite(viewlet.id); + this.compositeBar.activateComposite(viewContainer.id); } } } - for (const viewlet of viewlets) { - const viewContainer = this.getViewContainer(viewlet.id)!; + for (const viewContainer of viewContainers) { const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer); this.updateActivity(viewContainer, viewContainerModel); this.onDidChangeActiveViews(viewContainer, viewContainerModel); @@ -497,38 +513,53 @@ export class ActivitybarPart extends Part implements IActivityBarService { disposables.add(viewContainerModel.onDidChangeContainerInfo(() => this.updateActivity(viewContainer, viewContainerModel))); disposables.add(viewContainerModel.onDidChangeActiveViewDescriptors(() => this.onDidChangeActiveViews(viewContainer, viewContainerModel))); - this.viewletDisposables.set(viewlet.id, disposables); + this.viewContainerDisposables.set(viewContainer.id, disposables); } } - private onDidDeregisterViewlet(viewletId: string): void { - const disposable = this.viewletDisposables.get(viewletId); + private onDidDeregisterViewContainer(viewContainer: ViewContainer): void { + const disposable = this.viewContainerDisposables.get(viewContainer.id); if (disposable) { disposable.dispose(); } - this.viewletDisposables.delete(viewletId); - this.hideComposite(viewletId); + this.viewContainerDisposables.delete(viewContainer.id); + this.hideComposite(viewContainer.id); } private updateActivity(viewContainer: ViewContainer, viewContainerModel: IViewContainerModel): void { - - const activity: IActivity = { - id: viewContainer.id, - name: viewContainerModel.title, - iconUrl: URI.isUri(viewContainerModel.icon) ? viewContainerModel.icon : undefined, - cssClass: isString(viewContainerModel.icon) ? viewContainerModel.icon : undefined, - keybindingId: viewContainer.focusCommand?.id || viewContainer.id - }; - + const activity: IActivity = this.toActivity(viewContainer, viewContainerModel); const { activityAction, pinnedAction } = this.getCompositeActions(viewContainer.id); - activityAction.setActivity(activity); + activityAction.updateActivity(activity); if (pinnedAction instanceof PlaceHolderToggleCompositePinnedAction) { pinnedAction.setActivity(activity); } - this.saveCachedViewlets(); + this.saveCachedViewContainers(); + } + + private toActivity({ id, focusCommand }: ViewContainer, { icon, title: name }: IViewContainerModel): IActivity { + return ActivitybarPart.toActivity(id, name, icon, focusCommand?.id || id); + } + + private static toActivity(id: string, name: string, icon: URI | string | undefined, keybindingId: string | undefined): IActivity { + let cssClass: string | undefined = undefined; + let iconUrl: URI | undefined = undefined; + if (URI.isUri(icon)) { + iconUrl = icon; + cssClass = `activity-${id.replace(/\./g, '-')}`; + const iconClass = `.monaco-workbench .activitybar .monaco-action-bar .action-label.${cssClass}`; + createCSSRule(iconClass, ` + mask: ${asCSSUrl(icon)} no-repeat 50% 50%; + mask-size: 24px; + -webkit-mask: ${asCSSUrl(icon)} no-repeat 50% 50%; + -webkit-mask-size: 24px; + `); + } else if (isString(icon)) { + cssClass = icon; + } + return { id, name, cssClass, iconUrl, keybindingId }; } private onDidChangeActiveViews(viewContainer: ViewContainer, viewContainerModel: IViewContainerModel): void { @@ -539,21 +570,21 @@ export class ActivitybarPart extends Part implements IActivityBarService { } } - private shouldBeHidden(viewletId: string, cachedViewlet?: ICachedViewlet): boolean { - const viewContainer = this.getViewContainer(viewletId); + private shouldBeHidden(viewContainerId: string, cachedViewContainer?: ICachedViewContainer): boolean { + const viewContainer = this.getViewContainer(viewContainerId); if (!viewContainer || !viewContainer.hideIfEmpty) { return false; } - return cachedViewlet?.views && cachedViewlet.views.length - ? cachedViewlet.views.every(({ when }) => !!when && !this.contextKeyService.contextMatchesRules(ContextKeyExpr.deserialize(when))) - : viewletId === TEST_VIEW_CONTAINER_ID /* Hide Test viewlet for the first time or it had no views registered before */; + return cachedViewContainer?.views && cachedViewContainer.views.length + ? cachedViewContainer.views.every(({ when }) => !!when && !this.contextKeyService.contextMatchesRules(ContextKeyExpr.deserialize(when))) + : viewContainerId === TEST_VIEW_CONTAINER_ID /* Hide Test view container for the first time or it had no views registered before */; } private removeNotExistingComposites(): void { - const viewlets = this.viewletService.getViewlets(); - for (const { id } of this.cachedViewlets) { - if (viewlets.every(viewlet => viewlet.id !== id)) { + const viewContainers = this.getViewContainers(); + for (const { id } of this.cachedViewContainers) { + if (viewContainers.every(viewContainer => viewContainer.id !== id)) { this.hideComposite(id); } } @@ -570,15 +601,20 @@ export class ActivitybarPart extends Part implements IActivityBarService { } } - getPinnedViewletIds(): string[] { + getPinnedViewContainerIds(): string[] { const pinnedCompositeIds = this.compositeBar.getPinnedComposites().map(v => v.id); - - return this.viewletService.getViewlets() + return this.getViewContainers() .filter(v => this.compositeBar.isPinned(v.id)) .sort((v1, v2) => pinnedCompositeIds.indexOf(v1.id) - pinnedCompositeIds.indexOf(v2.id)) .map(v => v.id); } + getVisibleViewContainerIds(): string[] { + return this.compositeBar.getVisibleComposites() + .filter(v => this.viewsService.getVisibleViewContainer(this.location)?.id === v.id || this.compositeBar.isPinned(v.id)) + .map(v => v.id); + } + layout(width: number, height: number): void { if (!this.layoutService.isVisible(Parts.ACTIVITYBAR_PART)) { return; @@ -601,27 +637,32 @@ export class ActivitybarPart extends Part implements IActivityBarService { this.compositeBar.layout(new Dimension(width, availableHeight)); } - private getViewContainer(viewletId: string): ViewContainer | undefined { - return this.viewDescriptorService.getViewContainerById(viewletId) || undefined; + private getViewContainer(id: string): ViewContainer | undefined { + const viewContainer = this.viewDescriptorService.getViewContainerById(id); + return viewContainer && this.viewDescriptorService.getViewContainerLocation(viewContainer) === this.location ? viewContainer : undefined; + } + + private getViewContainers(): ReadonlyArray { + return this.viewDescriptorService.getViewContainersByLocation(this.location); } private onDidStorageChange(e: IWorkspaceStorageChangeEvent): void { - if (e.key === ActivitybarPart.PINNED_VIEWLETS && e.scope === StorageScope.GLOBAL - && this.pinnedViewletsValue !== this.getStoredPinnedViewletsValue() /* This checks if current window changed the value or not */) { - this._pinnedViewletsValue = undefined; + if (e.key === ActivitybarPart.PINNED_VIEW_CONTAINERS && e.scope === StorageScope.GLOBAL + && this.pinnedViewContainersValue !== this.getStoredPinnedViewContainersValue() /* This checks if current window changed the value or not */) { + this._pinnedViewContainersValue = undefined; const newCompositeItems: ICompositeBarItem[] = []; const compositeItems = this.compositeBar.getCompositeBarItems(); - const cachedViewlets = this.getCachedViewlets(); + const cachedViewContainers = this.getCachedViewContainers(); - for (const cachedViewlet of cachedViewlets) { + for (const cachedViewContainer of cachedViewContainers) { // Add and update existing items - const existingItem = compositeItems.filter(({ id }) => id === cachedViewlet.id)[0]; + const existingItem = compositeItems.filter(({ id }) => id === cachedViewContainer.id)[0]; if (existingItem) { newCompositeItems.push({ id: existingItem.id, name: existingItem.name, order: existingItem.order, - pinned: cachedViewlet.pinned, + pinned: cachedViewContainer.pinned, visible: existingItem.visible }); } @@ -638,8 +679,8 @@ export class ActivitybarPart extends Part implements IActivityBarService { } } - private saveCachedViewlets(): void { - const state: ICachedViewlet[] = []; + private saveCachedViewContainers(): void { + const state: ICachedViewContainer[] = []; const compositeItems = this.compositeBar.getCompositeBarItems(); for (const compositeItem of compositeItems) { @@ -665,32 +706,32 @@ export class ActivitybarPart extends Part implements IActivityBarService { } } - this.storeCachedViewletsState(state); + this.storeCachedViewContainersState(state); } - private getCachedViewlets(): ICachedViewlet[] { - const cachedViewlets: ICachedViewlet[] = this.getPinnedViewlets(); - for (const placeholderViewlet of this.getPlaceholderViewlets()) { - const cachedViewlet = cachedViewlets.filter(cached => cached.id === placeholderViewlet.id)[0]; - if (cachedViewlet) { - cachedViewlet.name = placeholderViewlet.name; - cachedViewlet.icon = placeholderViewlet.iconCSS ? placeholderViewlet.iconCSS : - placeholderViewlet.iconUrl ? URI.revive(placeholderViewlet.iconUrl) : undefined; - cachedViewlet.views = placeholderViewlet.views; + private getCachedViewContainers(): ICachedViewContainer[] { + const cachedViewContainers: ICachedViewContainer[] = this.getPinnedViewContainers(); + for (const placeholderViewContainer of this.getPlaceholderViewContainers()) { + const cachedViewContainer = cachedViewContainers.filter(cached => cached.id === placeholderViewContainer.id)[0]; + if (cachedViewContainer) { + cachedViewContainer.name = placeholderViewContainer.name; + cachedViewContainer.icon = placeholderViewContainer.iconCSS ? placeholderViewContainer.iconCSS : + placeholderViewContainer.iconUrl ? URI.revive(placeholderViewContainer.iconUrl) : undefined; + cachedViewContainer.views = placeholderViewContainer.views; } } - return cachedViewlets; + return cachedViewContainers; } - private storeCachedViewletsState(cachedViewlets: ICachedViewlet[]): void { - this.setPinnedViewlets(cachedViewlets.map(({ id, pinned, visible, order }) => ({ + private storeCachedViewContainersState(cachedViewContainers: ICachedViewContainer[]): void { + this.setPinnedViewContainers(cachedViewContainers.map(({ id, pinned, visible, order }) => ({ id, pinned, visible, order }))); - this.setPlaceholderViewlets(cachedViewlets.map(({ id, icon, name, views }) => ({ + this.setPlaceholderViewContainers(cachedViewContainers.map(({ id, icon, name, views }) => ({ id, iconUrl: URI.isUri(icon) ? icon : undefined, iconCSS: isString(icon) ? icon : undefined, @@ -699,80 +740,80 @@ export class ActivitybarPart extends Part implements IActivityBarService { }))); } - private getPinnedViewlets(): IPinnedViewlet[] { - return JSON.parse(this.pinnedViewletsValue); + private getPinnedViewContainers(): IPinnedViewContainer[] { + return JSON.parse(this.pinnedViewContainersValue); } - private setPinnedViewlets(pinnedViewlets: IPinnedViewlet[]): void { - this.pinnedViewletsValue = JSON.stringify(pinnedViewlets); + private setPinnedViewContainers(pinnedViewContainers: IPinnedViewContainer[]): void { + this.pinnedViewContainersValue = JSON.stringify(pinnedViewContainers); } - private _pinnedViewletsValue: string | undefined; - private get pinnedViewletsValue(): string { - if (!this._pinnedViewletsValue) { - this._pinnedViewletsValue = this.getStoredPinnedViewletsValue(); + private _pinnedViewContainersValue: string | undefined; + private get pinnedViewContainersValue(): string { + if (!this._pinnedViewContainersValue) { + this._pinnedViewContainersValue = this.getStoredPinnedViewContainersValue(); } - return this._pinnedViewletsValue; + return this._pinnedViewContainersValue; } - private set pinnedViewletsValue(pinnedViewletsValue: string) { - if (this.pinnedViewletsValue !== pinnedViewletsValue) { - this._pinnedViewletsValue = pinnedViewletsValue; - this.setStoredPinnedViewletsValue(pinnedViewletsValue); + private set pinnedViewContainersValue(pinnedViewContainersValue: string) { + if (this.pinnedViewContainersValue !== pinnedViewContainersValue) { + this._pinnedViewContainersValue = pinnedViewContainersValue; + this.setStoredPinnedViewContainersValue(pinnedViewContainersValue); } } - private getStoredPinnedViewletsValue(): string { - return this.storageService.get(ActivitybarPart.PINNED_VIEWLETS, StorageScope.GLOBAL, '[]'); + private getStoredPinnedViewContainersValue(): string { + return this.storageService.get(ActivitybarPart.PINNED_VIEW_CONTAINERS, StorageScope.GLOBAL, '[]'); } - private setStoredPinnedViewletsValue(value: string): void { - this.storageService.store(ActivitybarPart.PINNED_VIEWLETS, value, StorageScope.GLOBAL); + private setStoredPinnedViewContainersValue(value: string): void { + this.storageService.store(ActivitybarPart.PINNED_VIEW_CONTAINERS, value, StorageScope.GLOBAL); } - private getPlaceholderViewlets(): IPlaceholderViewlet[] { - return JSON.parse(this.placeholderViewletsValue); + private getPlaceholderViewContainers(): IPlaceholderViewContainer[] { + return JSON.parse(this.placeholderViewContainersValue); } - private setPlaceholderViewlets(placeholderViewlets: IPlaceholderViewlet[]): void { - this.placeholderViewletsValue = JSON.stringify(placeholderViewlets); + private setPlaceholderViewContainers(placeholderViewContainers: IPlaceholderViewContainer[]): void { + this.placeholderViewContainersValue = JSON.stringify(placeholderViewContainers); } - private _placeholderViewletsValue: string | undefined; - private get placeholderViewletsValue(): string { - if (!this._placeholderViewletsValue) { - this._placeholderViewletsValue = this.getStoredPlaceholderViewletsValue(); + private _placeholderViewContainersValue: string | undefined; + private get placeholderViewContainersValue(): string { + if (!this._placeholderViewContainersValue) { + this._placeholderViewContainersValue = this.getStoredPlaceholderViewContainersValue(); } - return this._placeholderViewletsValue; + return this._placeholderViewContainersValue; } - private set placeholderViewletsValue(placeholderViewletsValue: string) { - if (this.placeholderViewletsValue !== placeholderViewletsValue) { - this._placeholderViewletsValue = placeholderViewletsValue; - this.setStoredPlaceholderViewletsValue(placeholderViewletsValue); + private set placeholderViewContainersValue(placeholderViewContainesValue: string) { + if (this.placeholderViewContainersValue !== placeholderViewContainesValue) { + this._placeholderViewContainersValue = placeholderViewContainesValue; + this.setStoredPlaceholderViewContainersValue(placeholderViewContainesValue); } } - private getStoredPlaceholderViewletsValue(): string { - return this.storageService.get(ActivitybarPart.PLACEHOLDER_VIEWLETS, StorageScope.GLOBAL, '[]'); + private getStoredPlaceholderViewContainersValue(): string { + return this.storageService.get(ActivitybarPart.PLACEHOLDER_VIEW_CONTAINERS, StorageScope.GLOBAL, '[]'); } - private setStoredPlaceholderViewletsValue(value: string): void { - this.storageService.store(ActivitybarPart.PLACEHOLDER_VIEWLETS, value, StorageScope.GLOBAL); + private setStoredPlaceholderViewContainersValue(value: string): void { + this.storageService.store(ActivitybarPart.PLACEHOLDER_VIEW_CONTAINERS, value, StorageScope.GLOBAL); } - private migrateFromOldCachedViewletsValue(): void { + private migrateFromOldCachedViewContainersValue(): void { const value = this.storageService.get('workbench.activity.pinnedViewlets', StorageScope.GLOBAL); if (value !== undefined) { - const storedStates: Array = JSON.parse(value); - const cachedViewlets = storedStates.map(c => { - const serialized: ICachedViewlet = typeof c === 'string' /* migration from pinned states to composites states */ ? { id: c, pinned: true, order: undefined, visible: true, name: undefined, icon: undefined, views: undefined } : c; + const storedStates: Array = JSON.parse(value); + const cachedViewContainers = storedStates.map(c => { + const serialized: ICachedViewContainer = typeof c === 'string' /* migration from pinned states to composites states */ ? { id: c, pinned: true, order: undefined, visible: true, name: undefined, icon: undefined, views: undefined } : c; serialized.visible = isUndefinedOrNull(serialized.visible) ? true : serialized.visible; return serialized; }); - this.storeCachedViewletsState(cachedViewlets); + this.storeCachedViewContainersState(cachedViewContainers); this.storageService.remove('workbench.activity.pinnedViewlets', StorageScope.GLOBAL); } } diff --git a/src/vs/workbench/browser/parts/compositeBar.ts b/src/vs/workbench/browser/parts/compositeBar.ts index 8564acbb80..4fcb0e24d6 100644 --- a/src/vs/workbench/browser/parts/compositeBar.ts +++ b/src/vs/workbench/browser/parts/compositeBar.ts @@ -37,11 +37,11 @@ export class CompositeDragAndDrop implements ICompositeDragAndDrop { constructor( private viewDescriptorService: IViewDescriptorService, private targetContainerLocation: ViewContainerLocation, - private openComposite: (id: string, focus?: boolean) => Promise, + private openComposite: (id: string, focus?: boolean) => Promise, private moveComposite: (from: string, to: string, before?: Before2D) => void, ) { } - drop(data: CompositeDragAndDropData, targetCompositeId: string, originalEvent: DragEvent, before?: Before2D): void { + drop(data: CompositeDragAndDropData, targetCompositeId: string | undefined, originalEvent: DragEvent, before?: Before2D): void { const dragData = data.getData(); if (dragData.type === 'composite') { @@ -50,7 +50,9 @@ export class CompositeDragAndDrop implements ICompositeDragAndDrop { // ... on the same composite bar if (currentLocation === this.targetContainerLocation) { - this.moveComposite(dragData.id, targetCompositeId, before); + if (targetCompositeId) { + this.moveComposite(dragData.id, targetCompositeId, before); + } } // ... on a different composite bar else { @@ -60,7 +62,10 @@ export class CompositeDragAndDrop implements ICompositeDragAndDrop { } this.viewDescriptorService.moveViewContainerToLocation(currentContainer, this.targetContainerLocation); - this.moveComposite(currentContainer.id, targetCompositeId, before); + + if (targetCompositeId) { + this.moveComposite(currentContainer.id, targetCompositeId, before); + } this.openComposite(currentContainer.id, true); } @@ -74,7 +79,9 @@ export class CompositeDragAndDrop implements ICompositeDragAndDrop { const newContainer = this.viewDescriptorService.getViewContainerByViewId(viewToMove.id)!; - this.moveComposite(newContainer.id, targetCompositeId, before); + if (targetCompositeId) { + this.moveComposite(newContainer.id, targetCompositeId, before); + } this.openComposite(newContainer.id, true).then(composite => { if (composite) { @@ -140,7 +147,7 @@ export interface ICompositeBarOptions { getOnCompositeClickAction: (compositeId: string) => Action; getContextMenuActions: () => Action[]; getContextMenuActionsForComposite: (compositeId: string) => Action[]; - openComposite: (compositeId: string) => Promise; + openComposite: (compositeId: string) => Promise; getDefaultCompositeId: () => string; hidePart: () => void; } @@ -188,6 +195,10 @@ export class CompositeBar extends Widget implements ICompositeBar { return this.model.pinnedItems; } + getVisibleComposites(): ICompositeBarItem[] { + return this.model.visibleItems; + } + create(parent: HTMLElement): HTMLElement { const actionBarDiv = parent.appendChild($('.composite-bar')); this.compositeSwitcherBar = this._register(new ActionBar(actionBarDiv, { @@ -264,7 +275,8 @@ export class CompositeBar extends Widget implements ICompositeBar { // Add to the model if (this.model.add(id, name, order)) { this.computeSizes([this.model.findItem(id)]); - this.updateCompositeSwitcher(); + // Set timeout helps prevent flicker + setTimeout(() => this.updateCompositeSwitcher(), 0); } } @@ -526,7 +538,7 @@ export class CompositeBar extends Widget implements ICompositeBar { }); // Add overflow action as needed - if ((visibleCompositesChange && overflows) || compositeSwitcherBar.length() === 0) { + if ((visibleCompositesChange && overflows)) { this.compositeOverflowAction = this.instantiationService.createInstance(CompositeOverflowActivityAction, () => { if (this.compositeOverflowActionViewItem) { this.compositeOverflowActionViewItem.showMenu(); diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index 7da43fcef2..8c8e607e90 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts @@ -91,7 +91,7 @@ class Item extends BreadcrumbsItem { fileKind: this.element.kind, fileDecorations: { colors: this.options.showDecorationColors, badges: false }, }); - dom.addClass(container, FileKind[this.element.kind].toLowerCase()); + container.classList.add(FileKind[this.element.kind].toLowerCase()); this._disposables.add(label); } else if (this.element instanceof OutlineModel) { @@ -113,7 +113,7 @@ class Item extends BreadcrumbsItem { let icon = document.createElement('div'); icon.className = SymbolKinds.toCssClassName(this.element.symbol.kind); container.appendChild(icon); - dom.addClass(container, 'shows-symbol-icon'); + container.classList.add('shows-symbol-icon'); } let label = new IconLabel(container); let title = this.element.symbol.name.replace(/\r|\n|\r\n/g, '\u23CE'); @@ -183,7 +183,7 @@ export class BreadcrumbsControl { @IBreadcrumbsService breadcrumbsService: IBreadcrumbsService, ) { this.domNode = document.createElement('div'); - dom.addClass(this.domNode, 'breadcrumbs-control'); + this.domNode.classList.add('breadcrumbs-control'); dom.append(container, this.domNode); this._cfUseQuickPick = BreadcrumbsConfig.UseQuickPick.bindTo(_configurationService); @@ -221,13 +221,13 @@ export class BreadcrumbsControl { } isHidden(): boolean { - return dom.hasClass(this.domNode, 'hidden'); + return this.domNode.classList.contains('hidden'); } hide(): void { this._breadcrumbsDisposables.clear(); this._ckBreadcrumbsVisible.set(false); - dom.toggleClass(this.domNode, 'hidden', true); + this.domNode.classList.toggle('hidden', true); } update(): boolean { @@ -251,7 +251,7 @@ export class BreadcrumbsControl { } } - dom.toggleClass(this.domNode, 'hidden', false); + this.domNode.classList.toggle('hidden', false); this._ckBreadcrumbsVisible.set(true); this._ckBreadcrumbsPossible.set(true); @@ -263,8 +263,8 @@ export class BreadcrumbsControl { this._textResourceConfigurationService, this._workspaceService ); - dom.toggleClass(this.domNode, 'relative-path', model.isRelative()); - dom.toggleClass(this.domNode, 'backslash-path', this._labelService.getSeparator(uri.scheme, uri.authority) === '\\'); + this.domNode.classList.toggle('relative-path', model.isRelative()); + this.domNode.classList.toggle('backslash-path', this._labelService.getSeparator(uri.scheme, uri.authority) === '\\'); const updateBreadcrumbs = () => { const showIcons = this._cfShowIcons.getValue(); diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts index 43f658c0a6..c32b9a3673 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts @@ -3,7 +3,6 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as dom from 'vs/base/browser/dom'; import { compareFileNames } from 'vs/base/common/comparers'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; @@ -375,11 +374,11 @@ export class BreadcrumbsFilePicker extends BreadcrumbsPicker { _createTree(container: HTMLElement) { // tree icon theme specials - dom.addClass(this._treeContainer, 'file-icon-themable-tree'); - dom.addClass(this._treeContainer, 'show-file-icons'); + this._treeContainer.classList.add('file-icon-themable-tree'); + this._treeContainer.classList.add('show-file-icons'); const onFileIconThemeChange = (fileIconTheme: IFileIconTheme) => { - dom.toggleClass(this._treeContainer, 'align-icons-and-twisties', fileIconTheme.hasFileIcons && !fileIconTheme.hasFolderIcons); - dom.toggleClass(this._treeContainer, 'hide-arrows', fileIconTheme.hidesExplorerArrows === true); + this._treeContainer.classList.toggle('align-icons-and-twisties', fileIconTheme.hasFileIcons && !fileIconTheme.hasFolderIcons); + this._treeContainer.classList.toggle('hide-arrows', fileIconTheme.hidesExplorerArrows === true); }; this._disposables.add(this._themeService.onDidFileIconThemeChange(onFileIconThemeChange)); onFileIconThemeChange(this._themeService.getFileIconTheme()); diff --git a/src/vs/workbench/browser/parts/editor/editorControl.ts b/src/vs/workbench/browser/parts/editor/editorControl.ts index 9dca70a80e..3a26f9ac61 100644 --- a/src/vs/workbench/browser/parts/editor/editorControl.ts +++ b/src/vs/workbench/browser/parts/editor/editorControl.ts @@ -123,7 +123,7 @@ export class EditorControl extends Disposable { private doInstantiateEditorPane(descriptor: IEditorDescriptor): BaseEditor { // Return early if already instantiated - const existingEditorPane = this.editorPanes.filter(editorPane => descriptor.describes(editorPane))[0]; + const existingEditorPane = this.editorPanes.find(editorPane => descriptor.describes(editorPane)); if (existingEditorPane) { return existingEditorPane; } diff --git a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts index fa39835349..988cf5a1ee 100644 --- a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts +++ b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts @@ -16,7 +16,6 @@ import { GroupDirection, MergeGroupMode } from 'vs/workbench/services/editor/com import { toDisposable } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { RunOnceScheduler } from 'vs/base/common/async'; -import { find } from 'vs/base/common/arrays'; import { DataTransfers } from 'vs/base/browser/dnd'; import { VSBuffer } from 'vs/base/common/buffer'; import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; @@ -600,7 +599,7 @@ export class EditorDropTarget extends Themable { private findTargetGroupView(child: HTMLElement): IEditorGroupView | undefined { const groups = this.accessor.groups; - return find(groups, groupView => isAncestor(child, groupView.element) || this.delegate.groupContainsPredicate?.(groupView)); + return groups.find(groupView => isAncestor(child, groupView.element) || this.delegate.groupContainsPredicate?.(groupView)); } private updateContainer(isDraggedOver: boolean): void { diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index 200346d0cb..368c19a337 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -1604,7 +1604,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Forward to controls this.layoutTitleAreaControl(width); - this.editorControl.layout(new Dimension(this.dimension.width, this.dimension.height - this.titleAreaControl.getPreferredHeight())); + this.editorControl.layout(new Dimension(this.dimension.width, Math.max(0, this.dimension.height - this.titleAreaControl.getPreferredHeight()))); } private layoutTitleAreaControl(width: number): void { diff --git a/src/vs/workbench/browser/parts/editor/editorPart.ts b/src/vs/workbench/browser/parts/editor/editorPart.ts index 74786d9fd3..ce2fffacbe 100644 --- a/src/vs/workbench/browser/parts/editor/editorPart.ts +++ b/src/vs/workbench/browser/parts/editor/editorPart.ts @@ -6,7 +6,7 @@ import 'vs/workbench/browser/parts/editor/editor.contribution'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { Part } from 'vs/workbench/browser/part'; -import { Dimension, isAncestor, toggleClass, addClass, $ } from 'vs/base/browser/dom'; +import { Dimension, isAncestor, toggleClass, addClass, $, EventHelper } from 'vs/base/browser/dom'; import { Event, Emitter, Relay } from 'vs/base/common/event'; import { contrastBorder, editorBackground } from 'vs/platform/theme/common/colorRegistry'; import { GroupDirection, IAddGroupOptions, GroupsArrangement, GroupOrientation, IMergeGroupOptions, MergeGroupMode, ICopyEditorOptions, GroupsOrder, GroupChangeKind, GroupLocation, IFindGroupScope, EditorGroupLayout, GroupLayoutArgument, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -830,19 +830,23 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro addClass(overlay, 'drop-block-overlay'); parent.appendChild(overlay); - CompositeDragAndDropObserver.INSTANCE.registerTarget(this.element, { - onDragOver: e => { - if (e.eventData.dataTransfer) { - e.eventData.dataTransfer.dropEffect = 'none'; - } - }, + this._register(CompositeDragAndDropObserver.INSTANCE.registerTarget(this.element, { onDragStart: e => { toggleClass(overlay, 'visible', true); }, onDragEnd: e => { toggleClass(overlay, 'visible', false); } - }); + })); + + this._register(CompositeDragAndDropObserver.INSTANCE.registerTarget(overlay, { + onDragOver: e => { + EventHelper.stop(e.eventData, true); + if (e.eventData.dataTransfer) { + e.eventData.dataTransfer.dropEffect = 'none'; + } + } + })); return this.container; } diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index 05460bd9d0..d569f2a81a 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -50,7 +50,6 @@ import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessi import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment, IStatusbarEntry } from 'vs/workbench/services/statusbar/common/statusbar'; import { IMarker, IMarkerService, MarkerSeverity, IMarkerData } from 'vs/platform/markers/common/markers'; -import { find } from 'vs/base/common/arrays'; import { STATUS_BAR_PROMINENT_ITEM_BACKGROUND, STATUS_BAR_PROMINENT_ITEM_FOREGROUND } from 'vs/workbench/common/theme'; import { themeColorFromId } from 'vs/platform/theme/common/themeService'; @@ -972,7 +971,7 @@ class ShowCurrentMarkerInStatusbarContribution extends Disposable { if (!position) { return null; } - return find(this.markers, marker => Range.containsPosition(marker, position)) || null; + return this.markers.find(marker => Range.containsPosition(marker, position)) || null; } private onMarkerChanged(changedResources: ReadonlyArray): void { diff --git a/src/vs/workbench/browser/parts/editor/editorsObserver.ts b/src/vs/workbench/browser/parts/editor/editorsObserver.ts index d186b003c7..7948dc3d5a 100644 --- a/src/vs/workbench/browser/parts/editor/editorsObserver.ts +++ b/src/vs/workbench/browser/parts/editor/editorsObserver.ts @@ -48,7 +48,7 @@ export class EditorsObserver extends Disposable { } get editors(): IEditorIdentifier[] { - return this.mostRecentEditorsMap.values(); + return [...this.mostRecentEditorsMap.values()]; } hasEditor(resource: URI): boolean { @@ -283,7 +283,7 @@ export class EditorsObserver extends Disposable { // Across all editor groups else { - await this.doEnsureOpenedEditorsLimit(limit, this.mostRecentEditorsMap.values(), exclude); + await this.doEnsureOpenedEditorsLimit(limit, [...this.mostRecentEditorsMap.values()], exclude); } } @@ -342,7 +342,7 @@ export class EditorsObserver extends Disposable { private serialize(): ISerializedEditorsList { const registry = Registry.as(Extensions.EditorInputFactories); - const entries = this.mostRecentEditorsMap.values(); + const entries = [...this.mostRecentEditorsMap.values()]; const mapGroupToSerializableEditorsOfGroup = new Map(); return { diff --git a/src/vs/workbench/browser/parts/panel/media/panelpart.css b/src/vs/workbench/browser/parts/panel/media/panelpart.css index 2602a5482b..f98575645d 100644 --- a/src/vs/workbench/browser/parts/panel/media/panelpart.css +++ b/src/vs/workbench/browser/parts/panel/media/panelpart.css @@ -62,6 +62,22 @@ } +.monaco-workbench .part.panel .empty-panel-message-area { + position: absolute; + display: none; + top: 0px; + height: 100%; + width: 100%; + z-index: 10; +} + +.monaco-workbench .part.panel .empty-panel-message-area.visible { + display: flex; + align-items: center; + align-content: center; + justify-content: center; +} + .monaco-workbench .part.panel > .composite.title > .composite-bar-excess { width: 100px; } diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts index e4bc0a25ff..4d80bd76d8 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -20,13 +20,13 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ClosePanelAction, PanelActivityAction, ToggleMaximizedPanelAction, TogglePanelAction, PlaceHolderPanelActivityAction, PlaceHolderToggleCompositePinnedAction, PositionPanelActionConfigs, SetPanelPositionAction } from 'vs/workbench/browser/parts/panel/panelActions'; import { IThemeService, registerThemingParticipant, IColorTheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; -import { PANEL_BACKGROUND, PANEL_BORDER, PANEL_ACTIVE_TITLE_FOREGROUND, PANEL_INACTIVE_TITLE_FOREGROUND, PANEL_ACTIVE_TITLE_BORDER, PANEL_DRAG_AND_DROP_BACKGROUND, PANEL_INPUT_BORDER } from 'vs/workbench/common/theme'; +import { PANEL_BACKGROUND, PANEL_BORDER, PANEL_ACTIVE_TITLE_FOREGROUND, PANEL_INACTIVE_TITLE_FOREGROUND, PANEL_ACTIVE_TITLE_BORDER, PANEL_DRAG_AND_DROP_BACKGROUND, PANEL_INPUT_BORDER, EDITOR_DRAG_AND_DROP_BACKGROUND } from 'vs/workbench/common/theme'; import { activeContrastBorder, focusBorder, contrastBorder, editorBackground, badgeBackground, badgeForeground } from 'vs/platform/theme/common/colorRegistry'; import { CompositeBar, ICompositeBarItem, CompositeDragAndDrop } from 'vs/workbench/browser/parts/compositeBar'; import { ToggleCompositePinnedAction } from 'vs/workbench/browser/parts/compositeBarActions'; import { IBadge } from 'vs/workbench/services/activity/common/activity'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { Dimension, trackFocus } from 'vs/base/browser/dom'; +import { Dimension, trackFocus, addClass, toggleClass, EventHelper } from 'vs/base/browser/dom'; import { localize } from 'vs/nls'; import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IContextKey, IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -38,7 +38,7 @@ import { MenuId } from 'vs/platform/actions/common/actions'; import { ViewMenuActions } from 'vs/workbench/browser/parts/views/viewMenuActions'; import { IPaneComposite } from 'vs/workbench/common/panecomposite'; import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; -import { Before2D } from 'vs/workbench/browser/dnd'; +import { Before2D, CompositeDragAndDropObserver, ICompositeDragAndDrop } from 'vs/workbench/browser/dnd'; import { IActivity } from 'vs/workbench/common/activity'; interface ICachedPanel { @@ -96,6 +96,8 @@ export class PanelPart extends CompositePart implements IPanelService { private panelRegistry: PanelRegistry; + private dndHandler: ICompositeDragAndDrop; + constructor( @INotificationService notificationService: INotificationService, @IStorageService storageService: IStorageService, @@ -132,10 +134,15 @@ export class PanelPart extends CompositePart implements IPanelService { this.panelRegistry = Registry.as(PanelExtensions.Panels); storageKeysSyncRegistryService.registerStorageKey({ key: PanelPart.PINNED_PANELS, version: 1 }); + this.dndHandler = new CompositeDragAndDrop(this.viewDescriptorService, ViewContainerLocation.Panel, + (id: string, focus?: boolean) => (this.openPanel(id, focus) as Promise).then(panel => panel || null), + (from: string, to: string, before?: Before2D) => this.compositeBar.move(from, to, before?.horizontallyBefore) + ); + this.compositeBar = this._register(this.instantiationService.createInstance(CompositeBar, this.getCachedPanels(), { icon: false, orientation: ActionsOrientation.HORIZONTAL, - openComposite: (compositeId: string) => this.openPanel(compositeId, true), + openComposite: (compositeId: string) => this.openPanel(compositeId, true).then(panel => panel || null), getActivityAction: (compositeId: string) => this.getCompositeActions(compositeId).activityAction, getCompositePinnedAction: (compositeId: string) => this.getCompositeActions(compositeId).pinnedAction, getOnCompositeClickAction: (compositeId: string) => this.instantiationService.createInstance(PanelActivityAction, assertIsDefined(this.getPanel(compositeId))), @@ -149,10 +156,7 @@ export class PanelPart extends CompositePart implements IPanelService { getContextMenuActionsForComposite: (compositeId: string) => this.getContextMenuActionsForComposite(compositeId) as Action[], getDefaultCompositeId: () => this.panelRegistry.getDefaultPanelId(), hidePart: () => this.layoutService.setPanelHidden(true), - dndHandler: new CompositeDragAndDrop(this.viewDescriptorService, ViewContainerLocation.Panel, - (id: string, focus?: boolean) => this.openPanel(id, focus) as Promise, // {{SQL CARBON EDIT}} strict-null-checks - (from: string, to: string, before?: Before2D) => this.compositeBar.move(from, to, before?.horizontallyBefore) - ), + dndHandler: this.dndHandler, compositeSize: 0, overflowActionSize: 44, colors: (theme: IColorTheme) => ({ @@ -192,7 +196,7 @@ export class PanelPart extends CompositePart implements IPanelService { for (const panel of panels) { const cachedPanel = this.getCachedPanels().filter(({ id }) => id === panel.id)[0]; const activePanel = this.getActivePanel(); - const isActive = activePanel?.getId() === panel.id; + const isActive = activePanel?.getId() === panel.id || (!activePanel && this.getLastActivePanelId() === panel.id); if (isActive || !this.shouldBeHidden(panel.id, cachedPanel)) { this.compositeBar.addComposite(panel); @@ -203,6 +207,10 @@ export class PanelPart extends CompositePart implements IPanelService { } if (isActive) { + if (!activePanel) { + this.doOpenPanel(panel.id); + } + this.compositeBar.activateComposite(panel.id); } } @@ -352,6 +360,7 @@ export class PanelPart extends CompositePart implements IPanelService { } this.layoutCompositeBar(); // Need to relayout composite bar since different panels have different action bar width + this.layoutEmptyMessage(); } private onPanelClose(panel: IPanel): void { @@ -362,6 +371,7 @@ export class PanelPart extends CompositePart implements IPanelService { } this.compositeBar.deactivateComposite(panel.getId()); + this.layoutEmptyMessage(); } create(parent: HTMLElement): void { @@ -369,11 +379,51 @@ export class PanelPart extends CompositePart implements IPanelService { super.create(parent); + this.createEmptyPanelMessage(); + const focusTracker = this._register(trackFocus(parent)); this._register(focusTracker.onDidFocus(() => this.panelFocusContextKey.set(true))); this._register(focusTracker.onDidBlur(() => this.panelFocusContextKey.set(false))); } + private createEmptyPanelMessage(): void { + this.emptyPanelMessageElement = document.createElement('div'); + addClass(this.emptyPanelMessageElement, 'empty-panel-message-area'); + + const messageElement = document.createElement('div'); + addClass(messageElement, 'empty-panel-message'); + messageElement.innerText = localize('panel.emptyMessage', "No panels to display. Drag a view into the panel."); + + this.emptyPanelMessageElement.appendChild(messageElement); + this.element.appendChild(this.emptyPanelMessageElement); + + this._register(CompositeDragAndDropObserver.INSTANCE.registerTarget(this.emptyPanelMessageElement, { + onDragOver: (e) => { + EventHelper.stop(e.eventData, true); + }, + onDragEnter: (e) => { + EventHelper.stop(e.eventData, true); + + const validDropTarget = this.dndHandler.onDragEnter(e.dragAndDropData, undefined, e.eventData); + this.emptyPanelMessageElement!.style.backgroundColor = validDropTarget ? this.theme.getColor(EDITOR_DRAG_AND_DROP_BACKGROUND)?.toString() || '' : ''; + }, + onDragLeave: (e) => { + EventHelper.stop(e.eventData, true); + this.emptyPanelMessageElement!.style.backgroundColor = ''; + }, + onDragEnd: (e) => { + EventHelper.stop(e.eventData, true); + this.emptyPanelMessageElement!.style.backgroundColor = ''; + }, + onDrop: (e) => { + EventHelper.stop(e.eventData, true); + this.emptyPanelMessageElement!.style.backgroundColor = ''; + + this.dndHandler.drop(e.dragAndDropData, undefined, e.eventData); + }, + })); + } + updateStyles(): void { super.updateStyles(); @@ -508,6 +558,9 @@ export class PanelPart extends CompositePart implements IPanelService { // Layout composite bar this.layoutCompositeBar(); + + // Add empty panel message + this.layoutEmptyMessage(); } private layoutCompositeBar(): void { @@ -521,6 +574,13 @@ export class PanelPart extends CompositePart implements IPanelService { } } + private emptyPanelMessageElement: HTMLElement | undefined; + private layoutEmptyMessage(): void { + if (this.emptyPanelMessageElement) { + toggleClass(this.emptyPanelMessageElement, 'visible', this.compositeBar.getVisibleComposites().length === 0); + } + } + private getCompositeActions(compositeId: string): { activityAction: PanelActivityAction, pinnedAction: ToggleCompositePinnedAction; } { let compositeActions = this.compositeActions.get(compositeId); if (!compositeActions) { diff --git a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts index 8af36d16b5..a33c7018ef 100644 --- a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts +++ b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts @@ -109,7 +109,7 @@ export class SidebarPart extends CompositePart implements IViewletServi themeService, Registry.as(ViewletExtensions.Viewlets), SidebarPart.activeViewletSettingsKey, - Registry.as(ViewletExtensions.Viewlets).getDefaultViewletId(), + viewDescriptorService.getDefaultViewContainer(ViewContainerLocation.Sidebar)!.id, 'sideBar', 'viewlet', SIDE_BAR_TITLE_FOREGROUND, @@ -142,7 +142,7 @@ export class SidebarPart extends CompositePart implements IViewletServi if (activeContainers.length) { if (this.getActiveComposite()?.getId() === viewletDescriptor.id) { - const defaultViewletId = this.getDefaultViewletId(); + const defaultViewletId = this.viewDescriptorService.getDefaultViewContainer(ViewContainerLocation.Sidebar)?.id; const containerToOpen = activeContainers.filter(c => c.id === defaultViewletId)[0] || activeContainers[0]; await this.openViewlet(containerToOpen.id); } @@ -253,10 +253,6 @@ export class SidebarPart extends CompositePart implements IViewletServi }); } - getDefaultViewletId(): string { - return this.viewletRegistry.getDefaultViewletId(); - } - getViewlet(id: string): ViewletDescriptor { return this.getViewlets().filter(viewlet => viewlet.id === id)[0]; } diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts index 09fd6ec854..ed63e150e6 100644 --- a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts +++ b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts @@ -27,7 +27,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { IStorageService, StorageScope, IWorkspaceStorageChangeEvent } from 'vs/platform/storage/common/storage'; import { Parts, IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { coalesce, find } from 'vs/base/common/arrays'; +import { coalesce } from 'vs/base/common/arrays'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { ToggleStatusbarVisibilityAction } from 'vs/workbench/browser/actions/layoutActions'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; @@ -181,7 +181,7 @@ class StatusbarViewModel extends Disposable { } findEntry(container: HTMLElement): IStatusbarViewModelEntry | undefined { - return find(this._entries, entry => entry.container === container); + return this._entries.find(entry => entry.container === container); } getEntries(alignment: StatusbarAlignment): IStatusbarViewModelEntry[] { diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index 8f55ab0868..28ed889004 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -72,7 +72,7 @@ export class TitlebarPart extends Part implements ITitleService { private isInactive: boolean = false; - private readonly properties: ITitleProperties = { isPure: true, isAdmin: false }; + private readonly properties: ITitleProperties = { isPure: true, isAdmin: false, prefix: undefined }; private readonly activeEditorListeners = this._register(new DisposableStore()); private readonly titleUpdater = this._register(new RunOnceScheduler(() => this.doUpdateTitle(), 0)); @@ -191,6 +191,10 @@ export class TitlebarPart extends Part implements ITitleService { private getWindowTitle(): string { let title = this.doGetWindowTitle(); + if (this.properties.prefix) { + title = `${this.properties.prefix} ${title || this.productService.nameLong}`; + } + if (this.properties.isAdmin) { title = `${title || this.productService.nameLong} ${TitlebarPart.NLS_USER_IS_ADMIN}`; } @@ -212,10 +216,12 @@ export class TitlebarPart extends Part implements ITitleService { updateProperties(properties: ITitleProperties): void { const isAdmin = typeof properties.isAdmin === 'boolean' ? properties.isAdmin : this.properties.isAdmin; const isPure = typeof properties.isPure === 'boolean' ? properties.isPure : this.properties.isPure; + const prefix = typeof properties.prefix === 'string' ? properties.prefix : this.properties.prefix; - if (isAdmin !== this.properties.isAdmin || isPure !== this.properties.isPure) { + if (isAdmin !== this.properties.isAdmin || isPure !== this.properties.isPure || prefix !== this.properties.prefix) { this.properties.isAdmin = isAdmin; this.properties.isPure = isPure; + this.properties.prefix = prefix; this.titleUpdater.schedule(); } diff --git a/src/vs/workbench/browser/parts/views/views.ts b/src/vs/workbench/browser/parts/views/viewsService.ts similarity index 79% rename from src/vs/workbench/browser/parts/views/views.ts rename to src/vs/workbench/browser/parts/views/viewsService.ts index b6932239a7..1745e10c6d 100644 --- a/src/vs/workbench/browser/parts/views/views.ts +++ b/src/vs/workbench/browser/parts/views/viewsService.ts @@ -5,7 +5,7 @@ import 'vs/css!./media/views'; import { Disposable, IDisposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { IViewDescriptorService, ViewContainer, IViewDescriptor, IViewContainersRegistry, Extensions as ViewExtensions, IView, ViewContainerLocation, IViewsService, IViewPaneContainer, getVisbileViewContextKey } from 'vs/workbench/common/views'; +import { IViewDescriptorService, ViewContainer, IViewDescriptor, IView, ViewContainerLocation, IViewsService, IViewPaneContainer, getVisbileViewContextKey } from 'vs/workbench/common/views'; import { Registry } from 'vs/platform/registry/common/platform'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; @@ -15,7 +15,6 @@ import { isString } from 'vs/base/common/types'; import { MenuId, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; import { localize } from 'vs/nls'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { toggleClass, addClass } from 'vs/base/browser/dom'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IPaneComposite } from 'vs/workbench/common/panecomposite'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; @@ -23,7 +22,7 @@ import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiati import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { PaneCompositePanel, PanelRegistry, PanelDescriptor, Extensions as PanelExtensions } from 'vs/workbench/browser/panel'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IThemeService, IFileIconTheme } from 'vs/platform/theme/common/themeService'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; @@ -37,15 +36,16 @@ export class ViewsService extends Disposable implements IViewsService { _serviceBrand: undefined; - private readonly viewContainersRegistry: IViewContainersRegistry; private readonly viewDisposable: Map; private readonly viewPaneContainers: Map; private readonly _onDidChangeViewVisibility: Emitter<{ id: string, visible: boolean }> = this._register(new Emitter<{ id: string, visible: boolean }>()); readonly onDidChangeViewVisibility: Event<{ id: string, visible: boolean }> = this._onDidChangeViewVisibility.event; - private readonly visibleViewContextKeys: Map>; + private readonly _onDidChangeViewContainerVisibility = this._register(new Emitter<{ id: string, visible: boolean, location: ViewContainerLocation }>()); + readonly onDidChangeViewContainerVisibility = this._onDidChangeViewContainerVisibility.event; + private readonly visibleViewContextKeys: Map>; constructor( @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService, @@ -56,7 +56,6 @@ export class ViewsService extends Disposable implements IViewsService { ) { super(); - this.viewContainersRegistry = Registry.as(ViewExtensions.ViewContainersRegistry); this.viewDisposable = new Map(); this.visibleViewContextKeys = new Map>(); this.viewPaneContainers = new Map(); @@ -66,10 +65,16 @@ export class ViewsService extends Disposable implements IViewsService { this.viewDisposable.clear(); })); - this.viewDescriptorService.getViewContainers().forEach(viewContainer => this.onDidRegisterViewContainer(viewContainer, this.viewDescriptorService.getViewContainerLocation(viewContainer)!)); - this._register(this.viewContainersRegistry.onDidRegister(({ viewContainer }) => this.onDidRegisterViewContainer(viewContainer, this.viewDescriptorService.getViewContainerLocation(viewContainer)!))); - this._register(this.viewContainersRegistry.onDidDeregister(e => this.deregisterViewPaneContainer(e.viewContainer.id))); + this.viewDescriptorService.viewContainers.forEach(viewContainer => this.onDidRegisterViewContainer(viewContainer, this.viewDescriptorService.getViewContainerLocation(viewContainer)!)); + this._register(this.viewDescriptorService.onDidChangeViewContainers(({ added, removed }) => this.onDidChangeContainers(added, removed))); this._register(this.viewDescriptorService.onDidChangeContainerLocation(({ viewContainer, from, to }) => this.onDidChangeContainerLocation(viewContainer, from, to))); + + // View Container Visibility + this._register(this.viewletService.onDidViewletOpen(viewlet => this._onDidChangeViewContainerVisibility.fire({ id: viewlet.getId(), visible: true, location: ViewContainerLocation.Sidebar }))); + this._register(this.panelService.onDidPanelOpen(e => this._onDidChangeViewContainerVisibility.fire({ id: e.panel.getId(), visible: true, location: ViewContainerLocation.Panel }))); + this._register(this.viewletService.onDidViewletClose(viewlet => this._onDidChangeViewContainerVisibility.fire({ id: viewlet.getId(), visible: false, location: ViewContainerLocation.Sidebar }))); + this._register(this.panelService.onDidPanelClose(panel => this._onDidChangeViewContainerVisibility.fire({ id: panel.getId(), visible: false, location: ViewContainerLocation.Panel }))); + } private registerViewPaneContainer(viewPaneContainer: ViewPaneContainer): void { @@ -117,6 +122,15 @@ export class ViewsService extends Disposable implements IViewsService { return contextKey; } + private onDidChangeContainers(added: ReadonlyArray<{ container: ViewContainer, location: ViewContainerLocation }>, removed: ReadonlyArray<{ container: ViewContainer, location: ViewContainerLocation }>): void { + for (const { container, location } of removed) { + this.deregisterViewletOrPanel(container, location); + } + for (const { container, location } of added) { + this.registerViewletOrPanel(container, location); + } + } + private onDidChangeContainerLocation(viewContainer: ViewContainer, from: ViewContainerLocation, to: ViewContainerLocation): void { this.deregisterViewletOrPanel(viewContainer, from); this.registerViewletOrPanel(viewContainer, to); @@ -226,6 +240,62 @@ export class ViewsService extends Disposable implements IViewsService { return undefined; } + isViewContainerVisible(id: string): boolean { + const viewContainer = this.viewDescriptorService.getViewContainerById(id); + if (viewContainer) { + const viewContainerLocation = this.viewDescriptorService.getViewContainerLocation(viewContainer); + switch (viewContainerLocation) { + case ViewContainerLocation.Panel: + return this.panelService.getActivePanel()?.getId() === id; + case ViewContainerLocation.Sidebar: + return this.viewletService.getActiveViewlet()?.getId() === id; + } + } + return false; + } + + getVisibleViewContainer(location: ViewContainerLocation): ViewContainer | null { + let viewContainerId: string | undefined = undefined; + switch (location) { + case ViewContainerLocation.Panel: + viewContainerId = this.panelService.getActivePanel()?.getId(); + break; + case ViewContainerLocation.Sidebar: + viewContainerId = this.viewletService.getActiveViewlet()?.getId(); + break; + } + return viewContainerId ? this.viewDescriptorService.getViewContainerById(viewContainerId) : null; + } + + async openViewContainer(id: string, focus?: boolean): Promise { + const viewContainer = this.viewDescriptorService.getViewContainerById(id); + if (viewContainer) { + const viewContainerLocation = this.viewDescriptorService.getViewContainerLocation(viewContainer); + switch (viewContainerLocation) { + case ViewContainerLocation.Panel: + const panel = await this.panelService.openPanel(id, focus); + return panel as IPaneComposite; + case ViewContainerLocation.Sidebar: + const viewlet = await this.viewletService.openViewlet(id, focus); + return viewlet || null; + } + } + return null; + } + + async closeViewContainer(id: string): Promise { + const viewContainer = this.viewDescriptorService.getViewContainerById(id); + if (viewContainer) { + const viewContainerLocation = this.viewDescriptorService.getViewContainerLocation(viewContainer); + switch (viewContainerLocation) { + case ViewContainerLocation.Panel: + return this.panelService.getActivePanel()?.getId() === id ? this.panelService.hideActivePanel() : undefined; + case ViewContainerLocation.Sidebar: + return this.viewletService.getActiveViewlet()?.getId() === id ? this.viewletService.hideActiveViewlet() : undefined; + } + } + } + isViewVisible(id: string): boolean { const activeView = this.getActiveViewWithId(id); return activeView?.isBodyVisible() || false; @@ -306,13 +376,13 @@ export class ViewsService extends Disposable implements IViewsService { return null; } - getProgressIndicator(id: string): IProgressIndicator | undefined { - const viewContainer = this.viewDescriptorService.getViewContainerByViewId(id); + getViewProgressIndicator(viewId: string): IProgressIndicator | undefined { + const viewContainer = this.viewDescriptorService.getViewContainerByViewId(viewId); if (viewContainer === null) { return undefined; } - const view = this.viewPaneContainers.get(viewContainer.id)?.viewPaneContainer?.getView(id); + const view = this.viewPaneContainers.get(viewContainer.id)?.viewPaneContainer?.getView(viewId); return view?.getProgressIndicator(); } @@ -411,17 +481,4 @@ export class ViewsService extends Disposable implements IViewsService { } } -export function createFileIconThemableTreeContainerScope(container: HTMLElement, themeService: IThemeService): IDisposable { - addClass(container, 'file-icon-themable-tree'); - addClass(container, 'show-file-icons'); - - const onDidChangeFileIconTheme = (theme: IFileIconTheme) => { - toggleClass(container, 'align-icons-and-twisties', theme.hasFileIcons && !theme.hasFolderIcons); - toggleClass(container, 'hide-arrows', theme.hidesExplorerArrows === true); - }; - - onDidChangeFileIconTheme(themeService.getFileIconTheme()); - return themeService.onDidFileIconThemeChange(onDidChangeFileIconTheme); -} - registerSingleton(IViewsService, ViewsService); diff --git a/src/vs/workbench/browser/viewlet.ts b/src/vs/workbench/browser/viewlet.ts index 16354be21f..4efd7308f4 100644 --- a/src/vs/workbench/browser/viewlet.ts +++ b/src/vs/workbench/browser/viewlet.ts @@ -20,7 +20,6 @@ import { URI } from 'vs/base/common/uri'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { AsyncDataTree } from 'vs/base/browser/ui/tree/asyncDataTree'; import { AbstractTree } from 'vs/base/browser/ui/tree/abstractTree'; -import { assertIsDefined } from 'vs/base/common/types'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; @@ -97,7 +96,6 @@ export const Extensions = { }; export class ViewletRegistry extends CompositeRegistry { - private defaultViewletId: string | undefined; /** * Registers a viewlet to the platform. @@ -110,9 +108,6 @@ export class ViewletRegistry extends CompositeRegistry { * Deregisters a viewlet to the platform. */ deregisterViewlet(id: string): void { - if (id === this.defaultViewletId) { - throw new Error('Cannot deregister default viewlet'); - } super.deregisterComposite(id); } @@ -130,19 +125,6 @@ export class ViewletRegistry extends CompositeRegistry { return this.getComposites() as ViewletDescriptor[]; } - /** - * Sets the id of the viewlet that should open on startup by default. - */ - setDefaultViewletId(id: string): void { - this.defaultViewletId = id; - } - - /** - * Gets the id of the viewlet that should open on startup by default. - */ - getDefaultViewletId(): string { - return assertIsDefined(this.defaultViewletId); - } } Registry.add(Extensions.Viewlets, new ViewletRegistry()); diff --git a/src/vs/workbench/browser/workbench.ts b/src/vs/workbench/browser/workbench.ts index 73e9ae1d57..e482842c0e 100644 --- a/src/vs/workbench/browser/workbench.ts +++ b/src/vs/workbench/browser/workbench.ts @@ -46,6 +46,7 @@ import { Layout } from 'vs/workbench/browser/layout'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { ILanguageAssociationRegistry, Extensions as LanguageExtensions } from 'sql/workbench/services/languageAssociation/common/languageAssociation'; import { Extensions as PanelExtensions, PanelRegistry } from 'vs/workbench/browser/panel'; +import { IViewDescriptorService, ViewContainerLocation } from 'vs/workbench/common/views'; export class Workbench extends Layout { @@ -164,7 +165,7 @@ export class Workbench extends Layout { // Restore try { - await this.restoreWorkbench(accessor.get(IEditorService), accessor.get(IEditorGroupsService), accessor.get(IViewletService), accessor.get(IPanelService), accessor.get(ILogService), lifecycleService); + await this.restoreWorkbench(accessor.get(IEditorService), accessor.get(IEditorGroupsService), accessor.get(IViewDescriptorService), accessor.get(IViewletService), accessor.get(IPanelService), accessor.get(ILogService), lifecycleService); } catch (error) { onUnexpectedError(error); } @@ -402,6 +403,7 @@ export class Workbench extends Layout { private async restoreWorkbench( editorService: IEditorService, editorGroupService: IEditorGroupsService, + viewDescriptorService: IViewDescriptorService, viewletService: IViewletService, panelService: IPanelService, logService: ILogService, @@ -438,7 +440,7 @@ export class Workbench extends Layout { const viewlet = await viewletService.openViewlet(this.state.sideBar.viewletToRestore); if (!viewlet) { - await viewletService.openViewlet(viewletService.getDefaultViewletId()); // fallback to default viewlet as needed + await viewletService.openViewlet(viewDescriptorService.getDefaultViewContainer(ViewContainerLocation.Sidebar)?.id); // fallback to default viewlet as needed } mark('didRestoreViewlet'); diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 648b57b63e..88746d0046 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -1477,12 +1477,12 @@ export async function pathsToEditors(paths: IPathData[] | undefined, fileService const editors = await Promise.all(paths.map(async path => { const resource = URI.revive(path.fileUri); if (!resource || !fileService.canHandleResource(resource)) { - return undefined; // {{SQL CARBON EDIT}} @anthonydresser revert after strictnullchecks + return undefined; // {{SQL CARBON EDIT}} @anthonydresser strict-null-checks } const exists = (typeof path.exists === 'boolean') ? path.exists : await fileService.exists(resource); if (!exists && path.openOnlyIfExists) { - return undefined; // {{SQL CARBON EDIT}} @anthonydresser revert after strictnullchecks + return undefined; // {{SQL CARBON EDIT}} @anthonydresser strict-null-checks } const options: ITextEditorOptions = (exists && typeof path.lineNumber === 'number') ? { diff --git a/src/vs/workbench/common/editor/editorGroup.ts b/src/vs/workbench/common/editor/editorGroup.ts index f2c8cecc5f..faa94f777b 100644 --- a/src/vs/workbench/common/editor/editorGroup.ts +++ b/src/vs/workbench/common/editor/editorGroup.ts @@ -663,7 +663,7 @@ export class EditorGroup extends Disposable { return null; })); - this.mru = data.mru.map(i => this.editors[i]); + this.mru = coalesce(data.mru.map(i => this.editors[i])); this.active = this.mru[0]; diff --git a/src/vs/workbench/common/notifications.ts b/src/vs/workbench/common/notifications.ts index 1cc9b58d9e..a6788e09bc 100644 --- a/src/vs/workbench/common/notifications.ts +++ b/src/vs/workbench/common/notifications.ts @@ -10,7 +10,7 @@ import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle' import { isPromiseCanceledError } from 'vs/base/common/errors'; import { Action } from 'vs/base/common/actions'; import { isErrorWithActions } from 'vs/base/common/errorsWithActions'; -import { find, equals } from 'vs/base/common/arrays'; +import { equals } from 'vs/base/common/arrays'; import { parseLinkedText, LinkedText } from 'vs/base/common/linkedText'; export interface INotificationsModel { @@ -220,7 +220,7 @@ export class NotificationsModel extends Disposable implements INotificationsMode } private findNotification(item: INotificationViewItem): INotificationViewItem | undefined { - return find(this._notifications, notification => notification.equals(item)); + return this._notifications.find(notification => notification.equals(item)); } private createViewItem(notification: INotification): INotificationViewItem | undefined { diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index e58d18e24e..dfd947e301 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -21,6 +21,7 @@ import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { SetMap } from 'vs/base/common/collections'; import { IProgressIndicator } from 'vs/platform/progress/common/progress'; import Severity from 'vs/base/common/severity'; +import { IPaneComposite } from 'vs/workbench/common/panecomposite'; export const TEST_VIEW_CONTAINER_ID = 'workbench.view.extension.test'; @@ -86,7 +87,7 @@ export interface IViewContainersRegistry { * * @returns the registered ViewContainer. */ - registerViewContainer(viewContainerDescriptor: IViewContainerDescriptor, location: ViewContainerLocation): ViewContainer; + registerViewContainer(viewContainerDescriptor: IViewContainerDescriptor, location: ViewContainerLocation, isDefault?: boolean): ViewContainer; /** * Deregisters the given view container @@ -110,6 +111,11 @@ export interface IViewContainersRegistry { * Returns the view container location */ getViewContainerLocation(container: ViewContainer): ViewContainerLocation; + + /** + * Return the default view container from the given location + */ + getDefaultViewContainer(location: ViewContainerLocation): ViewContainer | undefined; } interface ViewOrderDelegate { @@ -126,13 +132,14 @@ class ViewContainersRegistryImpl extends Disposable implements IViewContainersRe private readonly _onDidDeregister = this._register(new Emitter<{ viewContainer: ViewContainer, viewContainerLocation: ViewContainerLocation }>()); readonly onDidDeregister: Event<{ viewContainer: ViewContainer, viewContainerLocation: ViewContainerLocation }> = this._onDidDeregister.event; - private viewContainers: Map = new Map(); + private readonly viewContainers: Map = new Map(); + private readonly defaultViewContainers: ViewContainer[] = []; get all(): ViewContainer[] { return flatten(values(this.viewContainers)); } - registerViewContainer(viewContainerDescriptor: IViewContainerDescriptor, viewContainerLocation: ViewContainerLocation): ViewContainer { + registerViewContainer(viewContainerDescriptor: IViewContainerDescriptor, viewContainerLocation: ViewContainerLocation, isDefault?: boolean): ViewContainer { const existing = this.get(viewContainerDescriptor.id); if (existing) { return existing; @@ -141,6 +148,9 @@ class ViewContainersRegistryImpl extends Disposable implements IViewContainersRe const viewContainer: ViewContainer = viewContainerDescriptor; const viewContainers = getOrSet(this.viewContainers, viewContainerLocation, []); viewContainers.push(viewContainer); + if (isDefault) { + this.defaultViewContainers.push(viewContainer); + } this._onDidRegister.fire({ viewContainer, viewContainerLocation }); return viewContainer; } @@ -171,6 +181,10 @@ class ViewContainersRegistryImpl extends Disposable implements IViewContainersRe getViewContainerLocation(container: ViewContainer): ViewContainerLocation { return keys(this.viewContainers).filter(location => this.getViewContainers(location).filter(viewContainer => viewContainer.id === container.id).length > 0)[0]; } + + getDefaultViewContainer(location: ViewContainerLocation): ViewContainer | undefined { + return this.defaultViewContainers.find(viewContainer => this.getViewContainerLocation(viewContainer) === location); + } } Registry.add(Extensions.ViewContainersRegistry, new ViewContainersRegistryImpl()); @@ -443,22 +457,24 @@ export interface IView { } export const IViewsService = createDecorator('viewsService'); - export interface IViewsService { _serviceBrand: undefined; + // View Container APIs + readonly onDidChangeViewContainerVisibility: Event<{ id: string, visible: boolean, location: ViewContainerLocation }>; + isViewContainerVisible(id: string): boolean; + openViewContainer(id: string, focus?: boolean): Promise; + closeViewContainer(id: string): void; + getVisibleViewContainer(location: ViewContainerLocation): ViewContainer | null; + + // View APIs readonly onDidChangeViewVisibility: Event<{ id: string, visible: boolean }>; - isViewVisible(id: string): boolean; - - getActiveViewWithId(id: string): T | null; - openView(id: string, focus?: boolean): Promise; - closeView(id: string): void; - - getProgressIndicator(id: string): IProgressIndicator | undefined; + getActiveViewWithId(id: string): T | null; + getViewProgressIndicator(id: string): IProgressIndicator | undefined; } /** @@ -473,35 +489,31 @@ export interface IViewDescriptorService { _serviceBrand: undefined; - readonly onDidChangeContainer: Event<{ views: IViewDescriptor[], from: ViewContainer, to: ViewContainer }>; - readonly onDidChangeLocation: Event<{ views: IViewDescriptor[], from: ViewContainerLocation, to: ViewContainerLocation }>; - readonly onDidChangeContainerLocation: Event<{ viewContainer: ViewContainer, from: ViewContainerLocation, to: ViewContainerLocation }>; - - moveViewContainerToLocation(viewContainer: ViewContainer, location: ViewContainerLocation): void; - - moveViewToLocation(view: IViewDescriptor, location: ViewContainerLocation): void; - - moveViewsToContainer(views: IViewDescriptor[], viewContainer: ViewContainer): void; + // ViewContainers + readonly viewContainers: ReadonlyArray; + readonly onDidChangeViewContainers: Event<{ added: ReadonlyArray<{ container: ViewContainer, location: ViewContainerLocation }>, removed: ReadonlyArray<{ container: ViewContainer, location: ViewContainerLocation }> }>; + getDefaultViewContainer(location: ViewContainerLocation): ViewContainer | undefined; + getViewContainerById(id: string): ViewContainer | null; + getDefaultViewContainerLocation(viewContainer: ViewContainer): ViewContainerLocation | null; + getViewContainerLocation(viewContainer: ViewContainer): ViewContainerLocation | null; + getViewContainersByLocation(location: ViewContainerLocation): ViewContainer[]; getViewContainerModel(viewContainer: ViewContainer): IViewContainerModel; + readonly onDidChangeContainerLocation: Event<{ viewContainer: ViewContainer, from: ViewContainerLocation, to: ViewContainerLocation }>; + moveViewContainerToLocation(viewContainer: ViewContainer, location: ViewContainerLocation): void; + + // Views getViewDescriptorById(id: string): IViewDescriptor | null; - getViewContainerByViewId(id: string): ViewContainer | null; - - getViewContainerById(id: string): ViewContainer | null; - - getViewContainerLocation(viewContainer: ViewContainer): ViewContainerLocation | null; - - getDefaultViewContainerLocation(viewContainer: ViewContainer): ViewContainerLocation | null; - getDefaultContainerById(id: string): ViewContainer | null; - getViewLocationById(id: string): ViewContainerLocation | null; - getViewContainersByLocation(location: ViewContainerLocation): ViewContainer[]; + readonly onDidChangeContainer: Event<{ views: IViewDescriptor[], from: ViewContainer, to: ViewContainer }>; + moveViewsToContainer(views: IViewDescriptor[], viewContainer: ViewContainer): void; - getViewContainers(): ViewContainer[]; + readonly onDidChangeLocation: Event<{ views: IViewDescriptor[], from: ViewContainerLocation, to: ViewContainerLocation }>; + moveViewToLocation(view: IViewDescriptor, location: ViewContainerLocation): void; } // Custom views diff --git a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts index a296b81df3..65e7f768b6 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts +++ b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts @@ -16,7 +16,7 @@ import { localize } from 'vs/nls'; import { ScrollType } from 'vs/editor/common/editorCommon'; import { IRange, Range } from 'vs/editor/common/core/range'; import { SplitView, Orientation, Sizing } from 'vs/base/browser/ui/splitview/splitview'; -import { Dimension, addClass } from 'vs/base/browser/dom'; +import { Dimension } from 'vs/base/browser/dom'; import { Event } from 'vs/base/common/event'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; @@ -152,23 +152,23 @@ export class CallHierarchyTreePeekWidget extends peekView.PeekViewWidget { this._dim = { height: 0, width: 0 }; this._parent = parent; - addClass(parent, 'call-hierarchy'); + parent.classList.add('call-hierarchy'); const message = document.createElement('div'); - addClass(message, 'message'); + message.classList.add('message'); parent.appendChild(message); this._message = message; this._message.tabIndex = 0; const container = document.createElement('div'); - addClass(container, 'results'); + container.classList.add('results'); parent.appendChild(container); this._splitView = new SplitView(container, { orientation: Orientation.HORIZONTAL }); // editor stuff const editorContainer = document.createElement('div'); - addClass(editorContainer, 'editor'); + editorContainer.classList.add('editor'); container.appendChild(editorContainer); let editorOptions: IEditorOptions = { scrollBeyondLastLine: false, @@ -195,7 +195,7 @@ export class CallHierarchyTreePeekWidget extends peekView.PeekViewWidget { // tree stuff const treeContainer = document.createElement('div'); - addClass(treeContainer, 'tree'); + treeContainer.classList.add('tree'); container.appendChild(treeContainer); const options: IWorkbenchAsyncDataTreeOptions = { sorter: new callHTree.Sorter(), diff --git a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyTree.ts b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyTree.ts index 51544c3f11..2798bc6e96 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyTree.ts +++ b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyTree.ts @@ -10,7 +10,6 @@ import { IIdentityProvider, IListVirtualDelegate } from 'vs/base/browser/ui/list import { FuzzyScore, createMatches } from 'vs/base/common/filters'; import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; import { SymbolKinds, Location } from 'vs/editor/common/modes'; -import * as dom from 'vs/base/browser/dom'; import { compare } from 'vs/base/common/strings'; import { Range } from 'vs/editor/common/core/range'; import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; @@ -109,7 +108,7 @@ export class CallRenderer implements ITreeRenderer modSet & 1 << k); + const type = semanticTokens.legend.tokenTypes[typeIdx] || 'not in legend (ignored)'; + const modifiers = []; + let modifierSet = modSet; + for (let modifierIndex = 0; modifierSet > 0 && modifierIndex < semanticTokens.legend.tokenModifiers.length; modifierIndex++) { + if (modifierSet & 1) { + modifiers.push(semanticTokens.legend.tokenModifiers[modifierIndex]); + } + modifierSet = modifierSet >> 1; + } + if (modifierSet > 0) { + modifiers.push('not in legend (ignored)'); + } const range = new Range(line + 1, character + 1, line + 1, character + 1 + len); const definitions = {}; const colorMap = this._themeService.getColorTheme().tokenColorMap; diff --git a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts index 07aa11ac43..2b7545fb78 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts @@ -120,7 +120,7 @@ async function createCandidateDecorations(model: ITextModel, breakpointDecoratio return; } - const breakpointAtPosition = breakpointDecorations.filter(bpd => bpd.range.equalsRange(range)).pop(); + const breakpointAtPosition = breakpointDecorations.find(bpd => bpd.range.equalsRange(range)); if (breakpointAtPosition && breakpointAtPosition.inlineWidget) { // Space already occupied, do not render candidate. return; diff --git a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts index b689a49808..357c839a4e 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts @@ -236,8 +236,14 @@ export class BreakpointsView extends ViewPane { if (this.isBodyVisible()) { this.updateSize(); if (this.list) { + const lastFocusIndex = this.list.getFocus()[0]; + // Check whether focused element was removed + const needsRefocus = lastFocusIndex && !this.elements.includes(this.list.element(lastFocusIndex)); this.list.splice(0, this.list.length, this.elements); this.needsRefresh = false; + if (needsRefocus) { + this.list.focusNth(Math.min(lastFocusIndex, this.list.length - 1)); + } } } else { this.needsRefresh = true; @@ -643,7 +649,7 @@ class BreakpointsAccessibilityProvider implements IListAccessibilityProvider t.stopped).pop(); + const thread = session.getAllThreads().find(t => t.stopped); const setActionBar = () => { const actions = getActions(this.instantiationService, element.element); @@ -493,7 +493,7 @@ class SessionsRenderer implements ITreeRenderer s.parentSession === session).length > 0; + const hasChildSessions = this.debugService.getModel().getSessions().find(s => s.parentSession === session); if (!hasChildSessions) { data.stateLabel.textContent = nls.localize({ key: 'running', comment: ['indicates state'] }, "Running"); } else { @@ -751,7 +751,7 @@ class CallStackDataSource implements IAsyncDataSource 1) || (threads.length === 1 && threads[0].stopped) || (this.debugService.getModel().getSessions().filter(s => s.parentSession === element).length > 0); + return (threads.length > 1) || (threads.length === 1 && threads[0].stopped) || !!(this.debugService.getModel().getSessions().find(s => s.parentSession === element)); } return isDebugModel(element) || (element instanceof Thread && element.stopped); @@ -855,7 +855,7 @@ class CallStackAccessibilityProvider implements IListAccessibilityProviderelement).name); } if (element instanceof StackFrame) { - return nls.localize('stackFrameAriaLabel', "Stack Frame {0} line {1} {2}, callstack, debug", element.name, element.range.startLineNumber, element.getSpecificSourceName()); + return nls.localize('stackFrameAriaLabel', "Stack Frame {0}, line {1}, {2}, callstack, debug", element.name, element.range.startLineNumber, element.getSpecificSourceName()); } if (isDebugSession(element)) { return nls.localize('sessionLabel', "Debug Session {0}", element.getLabel()); diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index e181bb3b38..086f205c03 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -29,7 +29,7 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { registerCommands, ADD_CONFIGURATION_ID, TOGGLE_INLINE_BREAKPOINT_ID, COPY_STACK_TRACE_ID, REVERSE_CONTINUE_ID, STEP_BACK_ID, RESTART_SESSION_ID, TERMINATE_THREAD_ID, STEP_OVER_ID, STEP_INTO_ID, STEP_OUT_ID, PAUSE_ID, DISCONNECT_ID, STOP_ID, RESTART_FRAME_ID, CONTINUE_ID, FOCUS_REPL_ID, JUMP_TO_CURSOR_ID, RESTART_LABEL, STEP_INTO_LABEL, STEP_OVER_LABEL, STEP_OUT_LABEL, PAUSE_LABEL, DISCONNECT_LABEL, STOP_LABEL, CONTINUE_LABEL } from 'vs/workbench/contrib/debug/browser/debugCommands'; import { StatusBarColorProvider } from 'vs/workbench/contrib/debug/browser/statusbarColorProvider'; import { IViewsRegistry, Extensions as ViewExtensions, IViewContainersRegistry, ViewContainerLocation, ViewContainer } from 'vs/workbench/common/views'; -import { isMacintosh } from 'vs/base/common/platform'; +import { isMacintosh, isWeb } from 'vs/base/common/platform'; import { ContextKeyExpr, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; import { URI } from 'vs/base/common/uri'; import { DebugStatusContribution } from 'vs/workbench/contrib/debug/browser/debugStatus'; @@ -54,6 +54,7 @@ import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneCont import { IQuickAccessRegistry, Extensions as QuickAccessExtensions } from 'vs/platform/quickinput/common/quickAccess'; import { StartDebugQuickAccessProvider } from 'vs/workbench/contrib/debug/browser/debugQuickAccess'; import { DebugProgressContribution } from 'vs/workbench/contrib/debug/browser/debugProgress'; +import { DebugTitleContribution } from 'vs/workbench/contrib/debug/browser/debugTitle'; class OpenDebugViewletAction extends ShowViewletAction { public static readonly ID = VIEWLET_ID; @@ -117,7 +118,7 @@ viewsRegistry.registerViews([{ id: VARIABLES_VIEW_ID, name: nls.localize('variab viewsRegistry.registerViews([{ id: WATCH_VIEW_ID, name: nls.localize('watch', "Watch"), containerIcon: 'codicon-debug-alt-2', ctorDescriptor: new SyncDescriptor(WatchExpressionsView), order: 20, weight: 10, canToggleVisibility: true, canMoveView: true, focusCommand: { id: 'workbench.debug.action.focusWatchView' }, when: CONTEXT_DEBUG_UX.isEqualTo('default') }], viewContainer); viewsRegistry.registerViews([{ id: CALLSTACK_VIEW_ID, name: nls.localize('callStack', "Call Stack"), containerIcon: 'codicon-debug-alt-2', ctorDescriptor: new SyncDescriptor(CallStackView), order: 30, weight: 30, canToggleVisibility: true, canMoveView: true, focusCommand: { id: 'workbench.debug.action.focusCallStackView' }, when: CONTEXT_DEBUG_UX.isEqualTo('default') }], viewContainer); viewsRegistry.registerViews([{ id: BREAKPOINTS_VIEW_ID, name: nls.localize('breakpoints', "Breakpoints"), containerIcon: 'codicon-debug-alt-2', ctorDescriptor: new SyncDescriptor(BreakpointsView), order: 40, weight: 20, canToggleVisibility: true, canMoveView: true, focusCommand: { id: 'workbench.debug.action.focusBreakpointsView' }, when: ContextKeyExpr.or(CONTEXT_BREAKPOINTS_EXIST, CONTEXT_DEBUG_UX.isEqualTo('default')) }], viewContainer); -viewsRegistry.registerViews([{ id: WelcomeView.ID, name: WelcomeView.LABEL, containerIcon: 'codicon-debug-alt-2', ctorDescriptor: new SyncDescriptor(WelcomeView), order: 10, weight: 40, canToggleVisibility: true, when: CONTEXT_DEBUG_UX.isEqualTo('simple') }], viewContainer); +viewsRegistry.registerViews([{ id: WelcomeView.ID, name: WelcomeView.LABEL, containerIcon: 'codicon-debug-alt-2', ctorDescriptor: new SyncDescriptor(WelcomeView), order: 1, weight: 40, canToggleVisibility: true, when: CONTEXT_DEBUG_UX.isEqualTo('simple') }], viewContainer); viewsRegistry.registerViews([{ id: LOADED_SCRIPTS_VIEW_ID, name: nls.localize('loadedScripts', "Loaded Scripts"), containerIcon: 'codicon-debug-alt-2', ctorDescriptor: new SyncDescriptor(LoadedScriptsView), order: 35, weight: 5, canToggleVisibility: true, canMoveView: true, collapsed: true, when: ContextKeyExpr.and(CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_DEBUG_UX.isEqualTo('default')) }], viewContainer); registerCommands(); @@ -293,9 +294,12 @@ configurationRegistry.registerConfiguration({ } }); -// Register Debug Status +// Register Debug Workbench Contributions Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(DebugStatusContribution, LifecyclePhase.Eventually); Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(DebugProgressContribution, LifecyclePhase.Eventually); +if (isWeb) { + Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(DebugTitleContribution, LifecyclePhase.Eventually); +} // Debug toolbar diff --git a/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts b/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts index 7ddc791d31..c25772e4c5 100644 --- a/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts +++ b/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts @@ -15,8 +15,7 @@ import { ICommandService } from 'vs/platform/commands/common/commands'; import { IDebugService, IDebugSession, IDebugConfiguration, IConfig, ILaunch } from 'vs/workbench/contrib/debug/common/debug'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { attachSelectBoxStyler, attachStylerCallback } from 'vs/platform/theme/common/styler'; -import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; -import { selectBorder } from 'vs/platform/theme/common/colorRegistry'; +import { selectBorder, selectBackground } from 'vs/platform/theme/common/colorRegistry'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; @@ -51,9 +50,7 @@ export class StartDebugActionViewItem implements IActionViewItem { this.toDispose = []; this.selectBox = new SelectBox([], -1, contextViewService, undefined, { ariaLabel: nls.localize('debugLaunchConfigurations', 'Debug Launch Configurations') }); this.toDispose.push(this.selectBox); - this.toDispose.push(attachSelectBoxStyler(this.selectBox, themeService, { - selectBackground: SIDE_BAR_BACKGROUND - })); + this.toDispose.push(attachSelectBoxStyler(this.selectBox, themeService)); this.registerListeners(); } @@ -124,9 +121,12 @@ export class StartDebugActionViewItem implements IActionViewItem { event.stopPropagation(); } })); - this.toDispose.push(attachStylerCallback(this.themeService, { selectBorder }, colors => { + this.toDispose.push(attachStylerCallback(this.themeService, { selectBorder, selectBackground }, colors => { this.container.style.border = colors.selectBorder ? `1px solid ${colors.selectBorder}` : ''; selectBoxContainer.style.borderLeft = colors.selectBorder ? `1px solid ${colors.selectBorder}` : ''; + const selectBackgroundColor = colors.selectBackground ? `${colors.selectBackground}` : ''; + this.container.style.backgroundColor = selectBackgroundColor; + this.start.style.backgroundColor = selectBackgroundColor; })); this.debugService.getConfigurationManager().getDynamicProviders().then(providers => { this.providers = providers; diff --git a/src/vs/workbench/contrib/debug/browser/debugCommands.ts b/src/vs/workbench/contrib/debug/browser/debugCommands.ts index 627cbcd499..9858d17d70 100644 --- a/src/vs/workbench/contrib/debug/browser/debugCommands.ts +++ b/src/vs/workbench/contrib/debug/browser/debugCommands.ts @@ -74,7 +74,7 @@ async function getThreadAndRun(accessor: ServicesAccessor, sessionAndThreadId: C if (isThreadContext(sessionAndThreadId)) { const session = debugService.getModel().getSession(sessionAndThreadId.sessionId); if (session) { - thread = session.getAllThreads().filter(t => t.getId() === sessionAndThreadId.threadId).pop(); + thread = session.getAllThreads().find(t => t.getId() === sessionAndThreadId.threadId); } } else { thread = debugService.getViewModel().focusedThread; @@ -98,9 +98,9 @@ function getFrame(debugService: IDebugService, context: CallStackContext | unkno if (isStackFrameContext(context)) { const session = debugService.getModel().getSession(context.sessionId); if (session) { - const thread = session.getAllThreads().filter(t => t.getId() === context.threadId).pop(); + const thread = session.getAllThreads().find(t => t.getId() === context.threadId); if (thread) { - return thread.getCallStack().filter(sf => sf.getId() === context.frameId).pop(); + return thread.getCallStack().find(sf => sf.getId() === context.frameId); } } } @@ -498,7 +498,7 @@ export function registerCommands(): void { return; } - const launch = manager.getLaunches().filter(l => l.uri.toString() === launchUri).pop() || manager.selectedConfiguration.launch; + const launch = manager.getLaunches().find(l => l.uri.toString() === launchUri) || manager.selectedConfiguration.launch; if (launch) { const { editor, created } = await launch.openConfigFile(false, false); if (editor && !created) { diff --git a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts index b2745d2bfa..3a71d9617f 100644 --- a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts +++ b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts @@ -79,7 +79,7 @@ export class ConfigurationManager implements IConfigurationManager { this.initLaunches(); this.registerListeners(); const previousSelectedRoot = this.storageService.get(DEBUG_SELECTED_ROOT, StorageScope.WORKSPACE); - const previousSelectedLaunch = this.launches.filter(l => l.uri.toString() === previousSelectedRoot).pop(); + const previousSelectedLaunch = this.launches.find(l => l.uri.toString() === previousSelectedRoot); this.debugConfigurationTypeContext = CONTEXT_DEBUG_CONFIGURATION_TYPE.bindTo(contextKeyService); if (previousSelectedLaunch && previousSelectedLaunch.getConfigurationNames().length) { this.selectConfiguration(previousSelectedLaunch, this.storageService.get(DEBUG_SELECTED_CONFIG_NAME_KEY, StorageScope.WORKSPACE)); @@ -202,8 +202,8 @@ export class ConfigurationManager implements IConfigurationManager { triggerKind = DebugConfigurationProviderTriggerKind.Initial; } // check if there are providers for the given type that contribute a provideDebugConfigurations method - const providers = this.configProviders.filter(p => p.provideDebugConfigurations && (p.type === debugType) && (p.triggerKind === triggerKind)); - return providers.length > 0; + const provider = this.configProviders.find(p => p.provideDebugConfigurations && (p.type === debugType) && (p.triggerKind === triggerKind)); + return !!provider; } async resolveConfigurationByProviders(folderUri: uri | undefined, type: string | undefined, config: IConfig, token: CancellationToken): Promise { @@ -248,16 +248,22 @@ export class ConfigurationManager implements IConfigurationManager { async getDynamicProviders(): Promise<{ label: string, pick: () => Promise<{ launch: ILaunch, config: IConfig } | undefined> }[]> { const extensions = await this.extensionService.getExtensions(); - const debugDynamicExtensions = extensions.filter(e => { - return e.activationEvents && e.activationEvents.filter(e => e.includes('onDebugDynamicConfigurations')).length && e.contributes?.debuggers; - }); + const onDebugDynamicConfigurationsName = 'onDebugDynamicConfigurations'; + const debugDynamicExtensionsTypes = extensions.map(e => { + const activationEvent = e.activationEvents && e.activationEvents.find(e => e.includes(onDebugDynamicConfigurationsName)); + if (activationEvent) { + const type = activationEvent.substr(onDebugDynamicConfigurationsName.length); + return type || (e.contributes && e.contributes.debuggers && e.contributes.debuggers.length ? e.contributes.debuggers[0].type : undefined); + } - return debugDynamicExtensions.map(e => { - const type = e.contributes?.debuggers![0].type!; + return undefined; + }).filter(e => typeof e === 'string') as string[]; + + return debugDynamicExtensionsTypes.map(type => { return { label: this.getDebuggerLabel(type)!, pick: async () => { - await this.activateDebuggers('onDebugDynamicConfigurations', type); + await this.activateDebuggers(onDebugDynamicConfigurationsName, type); const token = new CancellationTokenSource(); const picks: Promise<{ label: string, launch: ILaunch, config: IConfig }[]>[] = []; const provider = this.configProviders.filter(p => p.type === type && p.triggerKind === DebugConfigurationProviderTriggerKind.Dynamic && p.provideDebugConfigurations)[0]; @@ -410,7 +416,7 @@ export class ConfigurationManager implements IConfigurationManager { return undefined; } - return this.launches.filter(l => l.workspace && l.workspace.uri.toString() === workspaceUri.toString()).pop(); + return this.launches.find(l => l.workspace && l.workspace.uri.toString() === workspaceUri.toString()); } get selectedConfiguration(): { launch: ILaunch | undefined, name: string | undefined } { @@ -484,11 +490,11 @@ export class ConfigurationManager implements IConfigurationManager { } getDebugger(type: string): Debugger | undefined { - return this.debuggers.filter(dbg => strings.equalsIgnoreCase(dbg.type, type)).pop(); + return this.debuggers.find(dbg => strings.equalsIgnoreCase(dbg.type, type)); } isDebuggerInterestedInLanguage(language: string): boolean { - return this.debuggers.filter(a => language && a.languages && a.languages.indexOf(language) >= 0).length > 0; + return !!this.debuggers.find(a => language && a.languages && a.languages.indexOf(language) >= 0); } async guessDebugger(type?: string): Promise { @@ -565,7 +571,7 @@ abstract class AbstractLaunch { return undefined; } - return config.compounds.filter(compound => compound.name === name).pop(); + return config.compounds.find(compound => compound.name === name); } getConfigurationNames(ignoreCompoundsAndPresentation = false): string[] { @@ -596,7 +602,7 @@ abstract class AbstractLaunch { return undefined; } - return config.configurations.filter(config => config && config.name === name).shift(); + return config.configurations.find(config => config && config.name === name); } get hidden(): boolean { diff --git a/src/vs/workbench/contrib/debug/browser/debugHover.ts b/src/vs/workbench/contrib/debug/browser/debugHover.ts index 3f95363259..606bb03e58 100644 --- a/src/vs/workbench/contrib/debug/browser/debugHover.ts +++ b/src/vs/workbench/contrib/debug/browser/debugHover.ts @@ -341,7 +341,7 @@ class DebugHoverAccessibilityProvider implements IListAccessibilityProvider; private readonly _onWillNewSession: Emitter; private readonly _onDidEndSession: Emitter; + private debugStorage: DebugStorage; private model: DebugModel; private viewModel: ViewModel; + private telemetry: DebugTelemetry; private taskRunner: DebugTaskRunner; private configurationManager: ConfigurationManager; private toDispose: IDisposable[]; @@ -78,16 +73,13 @@ export class DebugService implements IDebugService { private activity: IDisposable | undefined; constructor( - @IStorageService private readonly storageService: IStorageService, @IEditorService private readonly editorService: IEditorService, - @ITextFileService private readonly textFileService: ITextFileService, @IViewletService private readonly viewletService: IViewletService, @IPanelService private readonly panelService: IPanelService, @IViewsService private readonly viewsService: IViewsService, @INotificationService private readonly notificationService: INotificationService, @IDialogService private readonly dialogService: IDialogService, @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, - @ITelemetryService private readonly telemetryService: ITelemetryService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IContextKeyService contextKeyService: IContextKeyService, @ILifecycleService private readonly lifecycleService: ILifecycleService, @@ -116,8 +108,9 @@ export class DebugService implements IDebugService { this.debugUx = CONTEXT_DEBUG_UX.bindTo(contextKeyService); this.debugUx.set(!!this.configurationManager.selectedConfiguration.name ? 'default' : 'simple'); - this.model = new DebugModel(this.loadBreakpoints(), this.loadFunctionBreakpoints(), - this.loadExceptionBreakpoints(), this.loadDataBreakpoints(), this.loadWatchExpressions(), this.textFileService); + this.debugStorage = this.instantiationService.createInstance(DebugStorage); + this.model = this.instantiationService.createInstance(DebugModel, this.debugStorage); + this.telemetry = this.instantiationService.createInstance(DebugTelemetry, this.model); const setBreakpointsExistContext = () => this.breakpointsExist.set(!!(this.model.getBreakpoints().length || this.model.getDataBreakpoints().length || this.model.getFunctionBreakpoints().length)); this.breakpointsExist = CONTEXT_BREAKPOINTS_EXIST.bindTo(contextKeyService); setBreakpointsExistContext(); @@ -499,8 +492,6 @@ export class DebugService implements IDebugService { // since the initialized response has arrived announce the new Session (including extensions) this._onDidNewSession.fire(session); - await this.telemetryDebugSessionStart(root, session.configuration.type); - return true; } catch (error) { @@ -530,6 +521,9 @@ export class DebugService implements IDebugService { try { await session.initialize(dbgr!); await session.launchOrAttach(session.configuration); + const launchJsonExists = !!session.root && !!this.configurationService.getValue('launch', { resource: session.root.uri }); + await this.telemetry.logDebugSessionStart(dbgr!, launchJsonExists); + if (forceFocus || !this.viewModel.focusedSession || session.parentSession === this.viewModel.focusedSession) { await this.focusStackFrame(undefined, undefined, session); } @@ -569,7 +563,7 @@ export class DebugService implements IDebugService { this.extensionHostDebugService.close(extensionDebugSession.getId()); } - this.telemetryDebugSessionStop(session, adapterExitEvent); + this.telemetry.logDebugSessionStop(session, adapterExitEvent); if (session.configuration.postDebugTask) { try { @@ -785,22 +779,22 @@ export class DebugService implements IDebugService { if (!name) { this.viewModel.setSelectedExpression(we); } - this.storeWatchExpressions(); + this.debugStorage.storeWatchExpressions(this.model.getWatchExpressions()); } renameWatchExpression(id: string, newName: string): void { this.model.renameWatchExpression(id, newName); - this.storeWatchExpressions(); + this.debugStorage.storeWatchExpressions(this.model.getWatchExpressions()); } moveWatchExpression(id: string, position: number): void { this.model.moveWatchExpression(id, position); - this.storeWatchExpressions(); + this.debugStorage.storeWatchExpressions(this.model.getWatchExpressions()); } removeWatchExpressions(id?: string): void { this.model.removeWatchExpressions(id); - this.storeWatchExpressions(); + this.debugStorage.storeWatchExpressions(this.model.getWatchExpressions()); } //---- breakpoints @@ -821,16 +815,16 @@ export class DebugService implements IDebugService { this.model.enableOrDisableAllBreakpoints(enable); await this.sendAllBreakpoints(); } - this.storeBreakpoints(); + this.debugStorage.storeBreakpoints(this.model); } async addBreakpoints(uri: uri, rawBreakpoints: IBreakpointData[], context: string): Promise { const breakpoints = this.model.addBreakpoints(uri, rawBreakpoints); breakpoints.forEach(bp => aria.status(nls.localize('breakpointAdded', "Added breakpoint, line {0}, file {1}", bp.lineNumber, uri.fsPath))); - breakpoints.forEach(bp => this.telemetryDebugAddBreakpoint(bp, context)); + breakpoints.forEach(bp => this.telemetry.logDebugAddBreakpoint(bp, context)); await this.sendBreakpoints(uri); - this.storeBreakpoints(); + this.debugStorage.storeBreakpoints(this.model); return breakpoints; } @@ -841,7 +835,7 @@ export class DebugService implements IDebugService { } else { await this.sendBreakpoints(uri); } - this.storeBreakpoints(); + this.debugStorage.storeBreakpoints(this.model); } async removeBreakpoints(id?: string): Promise { @@ -852,7 +846,7 @@ export class DebugService implements IDebugService { this.model.removeBreakpoints(toRemove); await Promise.all(urisToClear.map(uri => this.sendBreakpoints(uri))); - this.storeBreakpoints(); + this.debugStorage.storeBreakpoints(this.model); } setBreakpointsActivated(activated: boolean): Promise { @@ -868,26 +862,26 @@ export class DebugService implements IDebugService { async renameFunctionBreakpoint(id: string, newFunctionName: string): Promise { this.model.renameFunctionBreakpoint(id, newFunctionName); await this.sendFunctionBreakpoints(); - this.storeBreakpoints(); + this.debugStorage.storeBreakpoints(this.model); } async removeFunctionBreakpoints(id?: string): Promise { this.model.removeFunctionBreakpoints(id); await this.sendFunctionBreakpoints(); - this.storeBreakpoints(); + this.debugStorage.storeBreakpoints(this.model); } async addDataBreakpoint(label: string, dataId: string, canPersist: boolean, accessTypes: DebugProtocol.DataBreakpointAccessType[] | undefined): Promise { this.model.addDataBreakpoint(label, dataId, canPersist, accessTypes); await this.sendDataBreakpoints(); - this.storeBreakpoints(); + this.debugStorage.storeBreakpoints(this.model); } async removeDataBreakpoints(id?: string): Promise { this.model.removeDataBreakpoints(id); await this.sendDataBreakpoints(); - this.storeBreakpoints(); + this.debugStorage.storeBreakpoints(this.model); } async sendAllBreakpoints(session?: IDebugSession): Promise { @@ -950,171 +944,6 @@ export class DebugService implements IDebugService { } }); } - - private loadBreakpoints(): Breakpoint[] { - let result: Breakpoint[] | undefined; - try { - result = JSON.parse(this.storageService.get(DEBUG_BREAKPOINTS_KEY, StorageScope.WORKSPACE, '[]')).map((breakpoint: any) => { - return new Breakpoint(uri.parse(breakpoint.uri.external || breakpoint.source.uri.external), breakpoint.lineNumber, breakpoint.column, breakpoint.enabled, breakpoint.condition, breakpoint.hitCondition, breakpoint.logMessage, breakpoint.adapterData, this.textFileService); - }); - } catch (e) { } - - return result || []; - } - - private loadFunctionBreakpoints(): FunctionBreakpoint[] { - let result: FunctionBreakpoint[] | undefined; - try { - result = JSON.parse(this.storageService.get(DEBUG_FUNCTION_BREAKPOINTS_KEY, StorageScope.WORKSPACE, '[]')).map((fb: any) => { - return new FunctionBreakpoint(fb.name, fb.enabled, fb.hitCondition, fb.condition, fb.logMessage); - }); - } catch (e) { } - - return result || []; - } - - private loadExceptionBreakpoints(): ExceptionBreakpoint[] { - let result: ExceptionBreakpoint[] | undefined; - try { - result = JSON.parse(this.storageService.get(DEBUG_EXCEPTION_BREAKPOINTS_KEY, StorageScope.WORKSPACE, '[]')).map((exBreakpoint: any) => { - return new ExceptionBreakpoint(exBreakpoint.filter, exBreakpoint.label, exBreakpoint.enabled); - }); - } catch (e) { } - - return result || []; - } - - private loadDataBreakpoints(): DataBreakpoint[] { - let result: DataBreakpoint[] | undefined; - try { - result = JSON.parse(this.storageService.get(DEBUG_DATA_BREAKPOINTS_KEY, StorageScope.WORKSPACE, '[]')).map((dbp: any) => { - return new DataBreakpoint(dbp.description, dbp.dataId, true, dbp.enabled, dbp.hitCondition, dbp.condition, dbp.logMessage, dbp.accessTypes); - }); - } catch (e) { } - - return result || []; - } - - private loadWatchExpressions(): Expression[] { - let result: Expression[] | undefined; - try { - result = JSON.parse(this.storageService.get(DEBUG_WATCH_EXPRESSIONS_KEY, StorageScope.WORKSPACE, '[]')).map((watchStoredData: { name: string, id: string }) => { - return new Expression(watchStoredData.name, watchStoredData.id); - }); - } catch (e) { } - - return result || []; - } - - private storeWatchExpressions(): void { - const watchExpressions = this.model.getWatchExpressions(); - if (watchExpressions.length) { - this.storageService.store(DEBUG_WATCH_EXPRESSIONS_KEY, JSON.stringify(watchExpressions.map(we => ({ name: we.name, id: we.getId() }))), StorageScope.WORKSPACE); - } else { - this.storageService.remove(DEBUG_WATCH_EXPRESSIONS_KEY, StorageScope.WORKSPACE); - } - } - - private storeBreakpoints(): void { - const breakpoints = this.model.getBreakpoints(); - if (breakpoints.length) { - this.storageService.store(DEBUG_BREAKPOINTS_KEY, JSON.stringify(breakpoints), StorageScope.WORKSPACE); - } else { - this.storageService.remove(DEBUG_BREAKPOINTS_KEY, StorageScope.WORKSPACE); - } - - const functionBreakpoints = this.model.getFunctionBreakpoints(); - if (functionBreakpoints.length) { - this.storageService.store(DEBUG_FUNCTION_BREAKPOINTS_KEY, JSON.stringify(functionBreakpoints), StorageScope.WORKSPACE); - } else { - this.storageService.remove(DEBUG_FUNCTION_BREAKPOINTS_KEY, StorageScope.WORKSPACE); - } - - const dataBreakpoints = this.model.getDataBreakpoints().filter(dbp => dbp.canPersist); - if (dataBreakpoints.length) { - this.storageService.store(DEBUG_DATA_BREAKPOINTS_KEY, JSON.stringify(dataBreakpoints), StorageScope.WORKSPACE); - } else { - this.storageService.remove(DEBUG_DATA_BREAKPOINTS_KEY, StorageScope.WORKSPACE); - } - - const exceptionBreakpoints = this.model.getExceptionBreakpoints(); - if (exceptionBreakpoints.length) { - this.storageService.store(DEBUG_EXCEPTION_BREAKPOINTS_KEY, JSON.stringify(exceptionBreakpoints), StorageScope.WORKSPACE); - } else { - this.storageService.remove(DEBUG_EXCEPTION_BREAKPOINTS_KEY, StorageScope.WORKSPACE); - } - } - - //---- telemetry - - private telemetryDebugSessionStart(root: IWorkspaceFolder | undefined, type: string): Promise { - const dbgr = this.configurationManager.getDebugger(type); - if (!dbgr) { - return Promise.resolve(); - } - - const extension = dbgr.getMainExtensionDescriptor(); - /* __GDPR__ - "debugSessionStart" : { - "type": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "breakpointCount": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "exceptionBreakpoints": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "watchExpressionsCount": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "extensionName": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" }, - "isBuiltin": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true}, - "launchJsonExists": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } - } - */ - return this.telemetryService.publicLog('debugSessionStart', { - type: type, - breakpointCount: this.model.getBreakpoints().length, - exceptionBreakpoints: this.model.getExceptionBreakpoints(), - watchExpressionsCount: this.model.getWatchExpressions().length, - extensionName: extension.identifier.value, - isBuiltin: extension.isBuiltin, - launchJsonExists: root && !!this.configurationService.getValue('launch', { resource: root.uri }) - }); - } - - private telemetryDebugSessionStop(session: IDebugSession, adapterExitEvent: AdapterEndEvent): Promise { - - const breakpoints = this.model.getBreakpoints(); - - /* __GDPR__ - "debugSessionStop" : { - "type" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "success": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "sessionLengthInSeconds": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "breakpointCount": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "watchExpressionsCount": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } - } - */ - return this.telemetryService.publicLog('debugSessionStop', { - type: session && session.configuration.type, - success: adapterExitEvent.emittedStopped || breakpoints.length === 0, - sessionLengthInSeconds: adapterExitEvent.sessionLengthInSeconds, - breakpointCount: breakpoints.length, - watchExpressionsCount: this.model.getWatchExpressions().length - }); - } - - private telemetryDebugAddBreakpoint(breakpoint: IBreakpoint, context: string): Promise { - /* __GDPR__ - "debugAddBreakpoint" : { - "context": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "hasCondition": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "hasHitCondition": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "hasLogMessage": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } - } - */ - - return this.telemetryService.publicLog('debugAddBreakpoint', { - context: context, - hasCondition: !!breakpoint.condition, - hasHitCondition: !!breakpoint.hitCondition, - hasLogMessage: !!breakpoint.logMessage - }); - } } export function getStackFrameThreadAndSessionToFocus(model: IDebugModel, stackFrame: IStackFrame | undefined, thread?: IThread, session?: IDebugSession): { stackFrame: IStackFrame | undefined, thread: IThread | undefined, session: IDebugSession | undefined } { @@ -1123,7 +952,7 @@ export function getStackFrameThreadAndSessionToFocus(model: IDebugModel, stackFr session = stackFrame ? stackFrame.thread.session : thread!.session; } else { const sessions = model.getSessions(); - const stoppedSession = sessions.filter(s => s.state === State.Stopped).shift(); + const stoppedSession = sessions.find(s => s.state === State.Stopped); session = stoppedSession || (sessions.length ? sessions[0] : undefined); } } @@ -1133,7 +962,7 @@ export function getStackFrameThreadAndSessionToFocus(model: IDebugModel, stackFr thread = stackFrame.thread; } else { const threads = session ? session.getAllThreads() : undefined; - const stoppedThread = threads && threads.filter(t => t.stopped).shift(); + const stoppedThread = threads && threads.find(t => t.stopped); thread = stoppedThread || (threads && threads.length ? threads[0] : undefined); } } diff --git a/src/vs/workbench/contrib/debug/browser/debugSession.ts b/src/vs/workbench/contrib/debug/browser/debugSession.ts index 563effa35f..98a81f630d 100644 --- a/src/vs/workbench/contrib/debug/browser/debugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/debugSession.ts @@ -890,8 +890,8 @@ export class DebugSession implements IDebugSession { this.rawListeners.push(this.raw.onDidBreakpoint(event => { const id = event.body && event.body.breakpoint ? event.body.breakpoint.id : undefined; - const breakpoint = this.model.getBreakpoints().filter(bp => bp.getIdFromAdapter(this.getId()) === id).pop(); - const functionBreakpoint = this.model.getFunctionBreakpoints().filter(bp => bp.getIdFromAdapter(this.getId()) === id).pop(); + const breakpoint = this.model.getBreakpoints().find(bp => bp.getIdFromAdapter(this.getId()) === id); + const functionBreakpoint = this.model.getFunctionBreakpoints().find(bp => bp.getIdFromAdapter(this.getId()) === id); if (event.body.reason === 'new' && event.body.breakpoint.source && event.body.breakpoint.line) { const source = this.getSource(event.body.breakpoint.source); diff --git a/src/vs/workbench/contrib/debug/browser/debugTaskRunner.ts b/src/vs/workbench/contrib/debug/browser/debugTaskRunner.ts index 6a4818d8b7..236937ec3a 100644 --- a/src/vs/workbench/contrib/debug/browser/debugTaskRunner.ts +++ b/src/vs/workbench/contrib/debug/browser/debugTaskRunner.ts @@ -146,10 +146,10 @@ export class DebugTaskRunner { })); const promise: Promise = this.taskService.getActiveTasks().then(async (tasks): Promise => { - if (tasks.filter(t => t._id === task._id).length) { + if (tasks.find(t => t._id === task._id)) { // Check that the task isn't busy and if it is, wait for it const busyTasks = await this.taskService.getBusyTasks(); - if (busyTasks.filter(t => t._id === task._id).length) { + if (busyTasks.find(t => t._id === task._id)) { taskStarted = true; return inactivePromise; } diff --git a/src/vs/workbench/contrib/debug/browser/debugTitle.ts b/src/vs/workbench/contrib/debug/browser/debugTitle.ts new file mode 100644 index 0000000000..d8d49d9ee3 --- /dev/null +++ b/src/vs/workbench/contrib/debug/browser/debugTitle.ts @@ -0,0 +1,35 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { IDebugService, State } from 'vs/workbench/contrib/debug/common/debug'; +import { dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; +import { ITitleService } from 'vs/workbench/services/title/common/titleService'; + +export class DebugTitleContribution implements IWorkbenchContribution { + + private toDispose: IDisposable[] = []; + + constructor( + @IDebugService readonly debugService: IDebugService, + @IHostService readonly hostService: IHostService, + @ITitleService readonly titleService: ITitleService + ) { + const updateTitle = () => { + if (debugService.state === State.Stopped && !hostService.hasFocus) { + titleService.updateProperties({ prefix: '🔴' }); + } else { + titleService.updateProperties({ prefix: '' }); + } + }; + this.toDispose.push(debugService.onDidChangeState(updateTitle)); + this.toDispose.push(hostService.onDidChangeFocus(updateTitle)); + } + + dispose(): void { + dispose(this.toDispose); + } +} diff --git a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css index 6509365fd3..a62ee921e9 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css +++ b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css @@ -42,22 +42,15 @@ .monaco-workbench .part > .title > .title-actions .start-debug-action-item .codicon { line-height: inherit; - outline-offset: 0px; flex-shrink: 0; transition: transform 50ms ease; } -.monaco-workbench .monaco-action-bar .start-debug-action-item .configuration.select-container { - margin-left: 1px; -} - .monaco-workbench .monaco-action-bar .start-debug-action-item .configuration .monaco-select-box { border: none; margin-top: 0px; cursor: pointer; - font-size: inherit; line-height: inherit; - outline-offset: 0px; } .monaco-workbench .monaco-action-bar .start-debug-action-item .configuration.disabled .monaco-select-box { diff --git a/src/vs/workbench/contrib/debug/browser/replViewer.ts b/src/vs/workbench/contrib/debug/browser/replViewer.ts index 60d10dc8ba..a5b8204a77 100644 --- a/src/vs/workbench/contrib/debug/browser/replViewer.ts +++ b/src/vs/workbench/contrib/debug/browser/replViewer.ts @@ -374,13 +374,13 @@ export class ReplAccessibilityProvider implements IListAccessibilityProvider this.tree.getNode(s).collapsed) && scopes.length > 0) { - const toExpand = scopes.filter(s => !s.expensive).shift(); + const toExpand = scopes.find(s => !s.expensive); if (toExpand) { this.tree.expand(toExpand); } @@ -356,10 +356,10 @@ class VariablesAccessibilityProvider implements IListAccessibilityProviderelement).name, (element).value); + return nls.localize('watchExpressionAriaLabel', "{0}, value {1}", (element).name, (element).value); } // Variable - return nls.localize('watchVariableAriaLabel', "{0} value {1}, watch, debug", (element).name, (element).value); + return nls.localize('watchVariableAriaLabel', "{0}, value {1}", (element).name, (element).value); } } diff --git a/src/vs/workbench/contrib/debug/common/debugModel.ts b/src/vs/workbench/contrib/debug/common/debugModel.ts index eed08b1b84..31f3a9552b 100644 --- a/src/vs/workbench/contrib/debug/common/debugModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugModel.ts @@ -23,6 +23,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { ITextEditorPane } from 'vs/workbench/common/editor'; import { mixin } from 'vs/base/common/objects'; +import { DebugStorage } from 'vs/workbench/contrib/debug/common/debugStorage'; export class ExpressionContainer implements IExpressionContainer { @@ -380,7 +381,7 @@ export class StackFrame implements IStackFrame { } equals(other: IStackFrame): boolean { - return (this.name === other.name) && (other.thread === this.thread) && (other.source === this.source) && (Range.equalsRange(this.range, other.range)); + return (this.name === other.name) && (other.thread === this.thread) && (this.frameId === other.frameId) && (other.source === this.source) && (Range.equalsRange(this.range, other.range)); } } @@ -719,7 +720,7 @@ export class Breakpoint extends BaseBreakpoint implements IBreakpoint { } toString(): string { - return resources.basenameOrAuthority(this.uri); + return `${resources.basenameOrAuthority(this.uri)} ${this.lineNumber}`; } update(data: IBreakpointUpdateData): void { @@ -848,15 +849,21 @@ export class DebugModel implements IDebugModel { private readonly _onDidChangeBreakpoints = new Emitter(); private readonly _onDidChangeCallStack = new Emitter(); private readonly _onDidChangeWatchExpressions = new Emitter(); + private breakpoints: Breakpoint[]; + private functionBreakpoints: FunctionBreakpoint[]; + private exceptionBreakpoints: ExceptionBreakpoint[]; + private dataBreakopints: DataBreakpoint[]; + private watchExpressions: Expression[]; constructor( - private breakpoints: Breakpoint[], - private functionBreakpoints: FunctionBreakpoint[], - private exceptionBreakpoints: ExceptionBreakpoint[], - private dataBreakopints: DataBreakpoint[], - private watchExpressions: Expression[], - private textFileService: ITextFileService + debugStorage: DebugStorage, + @ITextFileService private readonly textFileService: ITextFileService ) { + this.breakpoints = debugStorage.loadBreakpoints(); + this.functionBreakpoints = debugStorage.loadFunctionBreakpoints(); + this.exceptionBreakpoints = debugStorage.loadExceptionBreakpoints(); + this.dataBreakopints = debugStorage.loadDataBreakpoints(); + this.watchExpressions = debugStorage.loadWatchExpressions(); this.sessions = []; } @@ -866,7 +873,7 @@ export class DebugModel implements IDebugModel { getSession(sessionId: string | undefined, includeInactive = false): IDebugSession | undefined { if (sessionId) { - return this.getSessions(includeInactive).filter(s => s.getId() === sessionId).pop(); + return this.getSessions(includeInactive).find(s => s.getId() === sessionId); } return undefined; } @@ -917,7 +924,7 @@ export class DebugModel implements IDebugModel { } rawUpdate(data: IRawModelUpdate): void { - let session = this.sessions.filter(p => p.getId() === data.sessionId).pop(); + let session = this.sessions.find(p => p.getId() === data.sessionId); if (session) { session.rawUpdate(data); this._onDidChangeCallStack.fire(undefined); @@ -925,7 +932,7 @@ export class DebugModel implements IDebugModel { } clearThreads(id: string, removeThreads: boolean, reference: number | undefined = undefined): void { - const session = this.sessions.filter(p => p.getId() === id).pop(); + const session = this.sessions.find(p => p.getId() === id); this.schedulers.forEach(scheduler => scheduler.dispose()); this.schedulers.clear(); @@ -1169,7 +1176,7 @@ export class DebugModel implements IDebugModel { } renameFunctionBreakpoint(id: string, name: string): void { - const functionBreakpoint = this.functionBreakpoints.filter(fbp => fbp.getId() === id).pop(); + const functionBreakpoint = this.functionBreakpoints.find(fbp => fbp.getId() === id); if (functionBreakpoint) { functionBreakpoint.name = name; this._onDidChangeBreakpoints.fire({ changed: [functionBreakpoint], sessionOnly: false }); @@ -1232,7 +1239,7 @@ export class DebugModel implements IDebugModel { } moveWatchExpression(id: string, position: number): void { - const we = this.watchExpressions.filter(we => we.getId() === id).pop(); + const we = this.watchExpressions.find(we => we.getId() === id); if (we) { this.watchExpressions = this.watchExpressions.filter(we => we.getId() !== id); this.watchExpressions = this.watchExpressions.slice(0, position).concat(we, this.watchExpressions.slice(position)); diff --git a/src/vs/workbench/contrib/debug/common/debugStorage.ts b/src/vs/workbench/contrib/debug/common/debugStorage.ts new file mode 100644 index 0000000000..7ec79139d5 --- /dev/null +++ b/src/vs/workbench/contrib/debug/common/debugStorage.ts @@ -0,0 +1,116 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { URI } from 'vs/base/common/uri'; +import { StorageScope, IStorageService } from 'vs/platform/storage/common/storage'; +import { ExceptionBreakpoint, Expression, Breakpoint, FunctionBreakpoint, DataBreakpoint } from 'vs/workbench/contrib/debug/common/debugModel'; +import { IEvaluate, IExpression, IDebugModel } from 'vs/workbench/contrib/debug/common/debug'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; + +const DEBUG_BREAKPOINTS_KEY = 'debug.breakpoint'; +const DEBUG_FUNCTION_BREAKPOINTS_KEY = 'debug.functionbreakpoint'; +const DEBUG_DATA_BREAKPOINTS_KEY = 'debug.databreakpoint'; +const DEBUG_EXCEPTION_BREAKPOINTS_KEY = 'debug.exceptionbreakpoint'; +const DEBUG_WATCH_EXPRESSIONS_KEY = 'debug.watchexpressions'; + +export class DebugStorage { + constructor( + @IStorageService private readonly storageService: IStorageService, + @ITextFileService private readonly textFileService: ITextFileService + ) { } + + loadBreakpoints(): Breakpoint[] { + let result: Breakpoint[] | undefined; + try { + result = JSON.parse(this.storageService.get(DEBUG_BREAKPOINTS_KEY, StorageScope.WORKSPACE, '[]')).map((breakpoint: any) => { + return new Breakpoint(URI.parse(breakpoint.uri.external || breakpoint.source.uri.external), breakpoint.lineNumber, breakpoint.column, breakpoint.enabled, breakpoint.condition, breakpoint.hitCondition, breakpoint.logMessage, breakpoint.adapterData, this.textFileService); + }); + } catch (e) { } + + return result || []; + } + + loadFunctionBreakpoints(): FunctionBreakpoint[] { + let result: FunctionBreakpoint[] | undefined; + try { + result = JSON.parse(this.storageService.get(DEBUG_FUNCTION_BREAKPOINTS_KEY, StorageScope.WORKSPACE, '[]')).map((fb: any) => { + return new FunctionBreakpoint(fb.name, fb.enabled, fb.hitCondition, fb.condition, fb.logMessage); + }); + } catch (e) { } + + return result || []; + } + + loadExceptionBreakpoints(): ExceptionBreakpoint[] { + let result: ExceptionBreakpoint[] | undefined; + try { + result = JSON.parse(this.storageService.get(DEBUG_EXCEPTION_BREAKPOINTS_KEY, StorageScope.WORKSPACE, '[]')).map((exBreakpoint: any) => { + return new ExceptionBreakpoint(exBreakpoint.filter, exBreakpoint.label, exBreakpoint.enabled); + }); + } catch (e) { } + + return result || []; + } + + loadDataBreakpoints(): DataBreakpoint[] { + let result: DataBreakpoint[] | undefined; + try { + result = JSON.parse(this.storageService.get(DEBUG_DATA_BREAKPOINTS_KEY, StorageScope.WORKSPACE, '[]')).map((dbp: any) => { + return new DataBreakpoint(dbp.description, dbp.dataId, true, dbp.enabled, dbp.hitCondition, dbp.condition, dbp.logMessage, dbp.accessTypes); + }); + } catch (e) { } + + return result || []; + } + + loadWatchExpressions(): Expression[] { + let result: Expression[] | undefined; + try { + result = JSON.parse(this.storageService.get(DEBUG_WATCH_EXPRESSIONS_KEY, StorageScope.WORKSPACE, '[]')).map((watchStoredData: { name: string, id: string }) => { + return new Expression(watchStoredData.name, watchStoredData.id); + }); + } catch (e) { } + + return result || []; + } + + storeWatchExpressions(watchExpressions: (IExpression & IEvaluate)[]): void { + if (watchExpressions.length) { + this.storageService.store(DEBUG_WATCH_EXPRESSIONS_KEY, JSON.stringify(watchExpressions.map(we => ({ name: we.name, id: we.getId() }))), StorageScope.WORKSPACE); + } else { + this.storageService.remove(DEBUG_WATCH_EXPRESSIONS_KEY, StorageScope.WORKSPACE); + } + } + + storeBreakpoints(debugModel: IDebugModel): void { + const breakpoints = debugModel.getBreakpoints(); + if (breakpoints.length) { + this.storageService.store(DEBUG_BREAKPOINTS_KEY, JSON.stringify(breakpoints), StorageScope.WORKSPACE); + } else { + this.storageService.remove(DEBUG_BREAKPOINTS_KEY, StorageScope.WORKSPACE); + } + + const functionBreakpoints = debugModel.getFunctionBreakpoints(); + if (functionBreakpoints.length) { + this.storageService.store(DEBUG_FUNCTION_BREAKPOINTS_KEY, JSON.stringify(functionBreakpoints), StorageScope.WORKSPACE); + } else { + this.storageService.remove(DEBUG_FUNCTION_BREAKPOINTS_KEY, StorageScope.WORKSPACE); + } + + const dataBreakpoints = debugModel.getDataBreakpoints().filter(dbp => dbp.canPersist); + if (dataBreakpoints.length) { + this.storageService.store(DEBUG_DATA_BREAKPOINTS_KEY, JSON.stringify(dataBreakpoints), StorageScope.WORKSPACE); + } else { + this.storageService.remove(DEBUG_DATA_BREAKPOINTS_KEY, StorageScope.WORKSPACE); + } + + const exceptionBreakpoints = debugModel.getExceptionBreakpoints(); + if (exceptionBreakpoints.length) { + this.storageService.store(DEBUG_EXCEPTION_BREAKPOINTS_KEY, JSON.stringify(exceptionBreakpoints), StorageScope.WORKSPACE); + } else { + this.storageService.remove(DEBUG_EXCEPTION_BREAKPOINTS_KEY, StorageScope.WORKSPACE); + } + } +} diff --git a/src/vs/workbench/contrib/debug/common/debugTelemetry.ts b/src/vs/workbench/contrib/debug/common/debugTelemetry.ts new file mode 100644 index 0000000000..7e5c6662fc --- /dev/null +++ b/src/vs/workbench/contrib/debug/common/debugTelemetry.ts @@ -0,0 +1,80 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IDebugModel, IDebugSession, AdapterEndEvent, IBreakpoint } from 'vs/workbench/contrib/debug/common/debug'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { Debugger } from 'vs/workbench/contrib/debug/common/debugger'; + +export class DebugTelemetry { + + constructor( + private readonly model: IDebugModel, + @ITelemetryService private readonly telemetryService: ITelemetryService, + ) { } + + logDebugSessionStart(dbgr: Debugger, launchJsonExists: boolean): Promise { + const extension = dbgr.getMainExtensionDescriptor(); + /* __GDPR__ + "debugSessionStart" : { + "type": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "breakpointCount": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "exceptionBreakpoints": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "watchExpressionsCount": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "extensionName": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" }, + "isBuiltin": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true}, + "launchJsonExists": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } + } + */ + return this.telemetryService.publicLog('debugSessionStart', { + type: dbgr.type, + breakpointCount: this.model.getBreakpoints().length, + exceptionBreakpoints: this.model.getExceptionBreakpoints(), + watchExpressionsCount: this.model.getWatchExpressions().length, + extensionName: extension.identifier.value, + isBuiltin: extension.isBuiltin, + launchJsonExists + }); + } + + logDebugSessionStop(session: IDebugSession, adapterExitEvent: AdapterEndEvent): Promise { + + const breakpoints = this.model.getBreakpoints(); + + /* __GDPR__ + "debugSessionStop" : { + "type" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "success": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "sessionLengthInSeconds": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "breakpointCount": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "watchExpressionsCount": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } + } + */ + return this.telemetryService.publicLog('debugSessionStop', { + type: session && session.configuration.type, + success: adapterExitEvent.emittedStopped || breakpoints.length === 0, + sessionLengthInSeconds: adapterExitEvent.sessionLengthInSeconds, + breakpointCount: breakpoints.length, + watchExpressionsCount: this.model.getWatchExpressions().length + }); + } + + logDebugAddBreakpoint(breakpoint: IBreakpoint, context: string): Promise { + /* __GDPR__ + "debugAddBreakpoint" : { + "context": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "hasCondition": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "hasHitCondition": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "hasLogMessage": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } + } + */ + + return this.telemetryService.publicLog('debugAddBreakpoint', { + context: context, + hasCondition: !!breakpoint.condition, + hasHitCondition: !!breakpoint.hitCondition, + hasLogMessage: !!breakpoint.logMessage + }); + } +} diff --git a/src/vs/workbench/contrib/debug/common/debugUtils.ts b/src/vs/workbench/contrib/debug/common/debugUtils.ts index a3379c58a9..016d925b9b 100644 --- a/src/vs/workbench/contrib/debug/common/debugUtils.ts +++ b/src/vs/workbench/contrib/debug/common/debugUtils.ts @@ -219,7 +219,7 @@ function convertPaths(msg: DebugProtocol.ProtocolMessage, fixSourcePath: (toDA: break; case 'response': const response = msg; - if (response.success) { + if (response.success && response.body) { switch (response.command) { case 'stackTrace': (response).body.stackFrames.forEach(frame => fixSourcePath(false, frame.source)); diff --git a/src/vs/workbench/contrib/debug/test/browser/baseDebugView.test.ts b/src/vs/workbench/contrib/debug/test/browser/baseDebugView.test.ts index 6b99299819..f37c790c05 100644 --- a/src/vs/workbench/contrib/debug/test/browser/baseDebugView.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/baseDebugView.test.ts @@ -6,8 +6,8 @@ import * as assert from 'assert'; import { renderExpressionValue, renderVariable, renderViewTree } from 'vs/workbench/contrib/debug/browser/baseDebugView'; import * as dom from 'vs/base/browser/dom'; -import { Expression, Variable, Scope, StackFrame, Thread, DebugModel } from 'vs/workbench/contrib/debug/common/debugModel'; -import { MockSession } from 'vs/workbench/contrib/debug/test/common/mockDebug'; +import { Expression, Variable, Scope, StackFrame, Thread } from 'vs/workbench/contrib/debug/common/debugModel'; +import { MockSession, createMockDebugModel } from 'vs/workbench/contrib/debug/test/common/mockDebug'; import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; @@ -123,7 +123,7 @@ suite('Debug - Base Debug View', () => { }); test('statusbar in debug mode', () => { - const model = new DebugModel([], [], [], [], [], { isDirty: (e: any) => false }); + const model = createMockDebugModel(); const session = createMockSession(model); assert.equal(isStatusbarInDebugMode(State.Inactive, undefined), false); assert.equal(isStatusbarInDebugMode(State.Initializing, session), false); diff --git a/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts b/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts index 97c9e5f98c..0c6fd6fc9b 100644 --- a/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts @@ -19,6 +19,7 @@ import { OverviewRulerLane } from 'vs/editor/common/model'; import { MarkdownString } from 'vs/base/common/htmlContent'; import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; import { generateUuid } from 'vs/base/common/uuid'; +import { createMockDebugModel } from 'vs/workbench/contrib/debug/test/common/mockDebug'; function createMockSession(model: DebugModel, name = 'mockSession', options?: IDebugSessionOptions): DebugSession { return new DebugSession(generateUuid(), { resolved: { name, type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, options, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService, undefined!, undefined!); @@ -48,7 +49,7 @@ suite('Debug - Breakpoints', () => { let model: DebugModel; setup(() => { - model = new DebugModel([], [], [], [], [], { isDirty: (e: any) => false }); + model = createMockDebugModel(); }); // Breakpoints diff --git a/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts b/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts index 9d08a50256..75bb423d5e 100644 --- a/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import { DebugModel, StackFrame, Thread } from 'vs/workbench/contrib/debug/common/debugModel'; import * as sinon from 'sinon'; -import { MockRawSession } from 'vs/workbench/contrib/debug/test/common/mockDebug'; +import { MockRawSession, createMockDebugModel } from 'vs/workbench/contrib/debug/test/common/mockDebug'; import { Source } from 'vs/workbench/contrib/debug/common/debugSource'; import { DebugSession } from 'vs/workbench/contrib/debug/browser/debugSession'; import { Range } from 'vs/editor/common/core/range'; @@ -53,7 +53,7 @@ suite('Debug - CallStack', () => { let rawSession: MockRawSession; setup(() => { - model = new DebugModel([], [], [], [], [], { isDirty: (e: any) => false }); + model = createMockDebugModel(); rawSession = new MockRawSession(); }); diff --git a/src/vs/workbench/contrib/debug/test/browser/debugHover.test.ts b/src/vs/workbench/contrib/debug/test/browser/debugHover.test.ts index e529a04189..b84eb34146 100644 --- a/src/vs/workbench/contrib/debug/test/browser/debugHover.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/debugHover.test.ts @@ -6,13 +6,14 @@ import * as assert from 'assert'; import { findExpressionInStackFrame } from 'vs/workbench/contrib/debug/browser/debugHover'; import { createMockSession } from 'vs/workbench/contrib/debug/test/browser/callStack.test'; -import { StackFrame, Thread, DebugModel, Scope, Variable } from 'vs/workbench/contrib/debug/common/debugModel'; +import { StackFrame, Thread, Scope, Variable } from 'vs/workbench/contrib/debug/common/debugModel'; import { Source } from 'vs/workbench/contrib/debug/common/debugSource'; import type { IScope, IExpression } from 'vs/workbench/contrib/debug/common/debug'; +import { createMockDebugModel } from 'vs/workbench/contrib/debug/test/common/mockDebug'; suite('Debug - Hover', () => { test('find expression in stack frame', async () => { - const model = new DebugModel([], [], [], [], [], { isDirty: (e: any) => false }); + const model = createMockDebugModel(); const session = createMockSession(model); let stackFrame: StackFrame; diff --git a/src/vs/workbench/contrib/debug/test/browser/repl.test.ts b/src/vs/workbench/contrib/debug/test/browser/repl.test.ts index a18ba398de..0792597954 100644 --- a/src/vs/workbench/contrib/debug/test/browser/repl.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/repl.test.ts @@ -7,7 +7,7 @@ import * as assert from 'assert'; import severity from 'vs/base/common/severity'; import { DebugModel, StackFrame, Thread } from 'vs/workbench/contrib/debug/common/debugModel'; -import { MockRawSession, MockDebugAdapter } from 'vs/workbench/contrib/debug/test/common/mockDebug'; +import { MockRawSession, MockDebugAdapter, createMockDebugModel } from 'vs/workbench/contrib/debug/test/common/mockDebug'; import { SimpleReplElement, RawObjectReplElement, ReplEvaluationInput, ReplModel, ReplEvaluationResult, ReplGroup } from 'vs/workbench/contrib/debug/common/replModel'; import { RawDebugSession } from 'vs/workbench/contrib/debug/browser/rawDebugSession'; import { timeout } from 'vs/base/common/async'; @@ -18,7 +18,7 @@ suite('Debug - REPL', () => { let rawSession: MockRawSession; setup(() => { - model = new DebugModel([], [], [], [], [], { isDirty: (e: any) => false }); + model = createMockDebugModel(); rawSession = new MockRawSession(); }); diff --git a/src/vs/workbench/contrib/debug/test/browser/watch.test.ts b/src/vs/workbench/contrib/debug/test/browser/watch.test.ts index e6ecd807dc..7dd73319d1 100644 --- a/src/vs/workbench/contrib/debug/test/browser/watch.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/watch.test.ts @@ -5,6 +5,7 @@ import * as assert from 'assert'; import { Expression, DebugModel } from 'vs/workbench/contrib/debug/common/debugModel'; +import { createMockDebugModel } from 'vs/workbench/contrib/debug/test/common/mockDebug'; // Expressions @@ -22,7 +23,7 @@ suite('Debug - Watch', () => { let model: DebugModel; setup(() => { - model = new DebugModel([], [], [], [], [], { isDirty: (e: any) => false }); + model = createMockDebugModel(); }); test('watch expressions', () => { diff --git a/src/vs/workbench/contrib/debug/test/common/mockDebug.ts b/src/vs/workbench/contrib/debug/test/common/mockDebug.ts index 83ed7cd766..efeb659ecb 100644 --- a/src/vs/workbench/contrib/debug/test/common/mockDebug.ts +++ b/src/vs/workbench/contrib/debug/test/common/mockDebug.ts @@ -7,10 +7,12 @@ import { URI as uri } from 'vs/base/common/uri'; import { Event } from 'vs/base/common/event'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { Position, IPosition } from 'vs/editor/common/core/position'; -import { ILaunch, IDebugService, State, IDebugSession, IConfigurationManager, IStackFrame, IBreakpointData, IBreakpointUpdateData, IConfig, IDebugModel, IViewModel, IBreakpoint, LoadedSourceEvent, IThread, IRawModelUpdate, IFunctionBreakpoint, IExceptionBreakpoint, IDebugger, IExceptionInfo, AdapterEndEvent, IReplElement, IExpression, IReplElementSource, IDataBreakpoint, IDebugSessionOptions } from 'vs/workbench/contrib/debug/common/debug'; +import { ILaunch, IDebugService, State, IDebugSession, IConfigurationManager, IStackFrame, IBreakpointData, IBreakpointUpdateData, IConfig, IDebugModel, IViewModel, IBreakpoint, LoadedSourceEvent, IThread, IRawModelUpdate, IFunctionBreakpoint, IExceptionBreakpoint, IDebugger, IExceptionInfo, AdapterEndEvent, IReplElement, IExpression, IReplElementSource, IDataBreakpoint, IDebugSessionOptions, IEvaluate } from 'vs/workbench/contrib/debug/common/debug'; import { Source } from 'vs/workbench/contrib/debug/common/debugSource'; import Severity from 'vs/base/common/severity'; import { AbstractDebugAdapter } from 'vs/workbench/contrib/debug/common/abstractDebugAdapter'; +import { DebugStorage } from 'vs/workbench/contrib/debug/common/debugStorage'; +import { ExceptionBreakpoint, Expression, DataBreakpoint, FunctionBreakpoint, Breakpoint, DebugModel } from 'vs/workbench/contrib/debug/common/debugModel'; export class MockDebugService implements IDebugService { @@ -542,3 +544,41 @@ export class MockDebugAdapter extends AbstractDebugAdapter { } } } + +class MockDebugStorage extends DebugStorage { + + constructor() { + super(undefined as any, undefined as any); + } + + loadBreakpoints(): Breakpoint[] { + return []; + } + + loadFunctionBreakpoints(): FunctionBreakpoint[] { + return []; + } + + loadExceptionBreakpoints(): ExceptionBreakpoint[] { + return []; + + } + + loadDataBreakpoints(): DataBreakpoint[] { + return []; + + } + + loadWatchExpressions(): Expression[] { + return []; + + } + + storeWatchExpressions(_watchExpressions: (IExpression & IEvaluate)[]): void { } + + storeBreakpoints(_debugModel: IDebugModel): void { } +} + +export function createMockDebugModel(): DebugModel { + return new DebugModel(new MockDebugStorage(), { isDirty: (e: any) => false }); +} diff --git a/src/vs/workbench/contrib/debug/test/electron-browser/debugANSIHandling.test.ts b/src/vs/workbench/contrib/debug/test/electron-browser/debugANSIHandling.test.ts index 72ed9fd301..321422425a 100644 --- a/src/vs/workbench/contrib/debug/test/electron-browser/debugANSIHandling.test.ts +++ b/src/vs/workbench/contrib/debug/test/electron-browser/debugANSIHandling.test.ts @@ -17,6 +17,7 @@ import { ansiColorMap } from 'vs/workbench/contrib/terminal/common/terminalColor import { DebugModel } from 'vs/workbench/contrib/debug/common/debugModel'; import { DebugSession } from 'vs/workbench/contrib/debug/browser/debugSession'; import { NullOpenerService } from 'vs/platform/opener/common/opener'; +import { createMockDebugModel } from 'vs/workbench/contrib/debug/test/common/mockDebug'; suite.skip('Debug - ANSI Handling', () => { @@ -29,7 +30,7 @@ suite.skip('Debug - ANSI Handling', () => { * Instantiate services for use by the functions being tested. */ setup(() => { - model = new DebugModel([], [], [], [], [], { isDirty: (e: any) => false }); + model = createMockDebugModel(); session = new DebugSession(generateUuid(), { resolved: { name: 'test', type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, undefined, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService, undefined!, undefined!); const instantiationService: TestInstantiationService = workbenchInstantiationService(); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index 63a06dcd93..f412fb45cf 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -346,7 +346,7 @@ export class ExtensionEditor extends BaseEditor { template.builtin.style.display = extension.type === ExtensionType.System ? 'inherit' : 'none'; template.publisher.textContent = extension.publisherDisplayName; - template.version.textContent = extension.version; + template.version.textContent = `v${extension.version}`; template.description.textContent = extension.description; const extRecommendations = this.extensionRecommendationsService.getAllRecommendationsWithReason(); @@ -434,7 +434,7 @@ export class ExtensionEditor extends BaseEditor { this.instantiationService.createInstance(EnableDropDownAction), this.instantiationService.createInstance(DisableDropDownAction, runningExtensions), - this.instantiationService.createInstance(RemoteInstallAction), + this.instantiationService.createInstance(RemoteInstallAction, false), this.instantiationService.createInstance(LocalInstallAction), combinedInstallAction, systemDisabledWarningAction, diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index 7aaadf2720..80994a24ac 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -54,7 +54,7 @@ import { alert } from 'vs/base/browser/ui/aria/aria'; import { coalesce } from 'vs/base/common/arrays'; import { IWorkbenchThemeService, IWorkbenchTheme, IWorkbenchColorTheme, IWorkbenchFileIconTheme, IWorkbenchProductIconTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { ILabelService } from 'vs/platform/label/common/label'; -import { prefersExecuteOnUI, prefersExecuteOnWorkspace } from 'vs/workbench/services/extensions/common/extensionsUtil'; +import { prefersExecuteOnUI, prefersExecuteOnWorkspace, canExecuteOnUI, canExecuteOnWorkspace } from 'vs/workbench/services/extensions/common/extensionsUtil'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IProductService } from 'vs/platform/product/common/productService'; import { IFileDialogService, IDialogService } from 'vs/platform/dialogs/common/dialogs'; @@ -311,7 +311,11 @@ export abstract class InstallInOtherServerAction extends ExtensionAction { constructor( id: string, private readonly server: IExtensionManagementServer | null, + private readonly canInstallAnyWhere: boolean, @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, + @IExtensionManagementServerService protected readonly extensionManagementServerService: IExtensionManagementServerService, + @IProductService private readonly productService: IProductService, + @IConfigurationService private readonly configurationService: IConfigurationService, ) { super(id, InstallInOtherServerAction.INSTALL_LABEL, InstallInOtherServerAction.Class, false); this.update(); @@ -321,11 +325,7 @@ export abstract class InstallInOtherServerAction extends ExtensionAction { this.enabled = false; this.class = InstallInOtherServerAction.Class; - if ( - this.extension && this.extension.local && this.server && this.extension.state === ExtensionState.Installed && this.extension.type === ExtensionType.User - // disabled by extension kind or it is a language pack extension - && (this.extension.enablementState === EnablementState.DisabledByExtensionKind || isLanguagePackExtension(this.extension.local.manifest)) - ) { + if (this.canInstall()) { const extensionInOtherServer = this.extensionsWorkbenchService.installed.filter(e => areSameExtensions(e.identifier, this.extension!.identifier) && e.server === this.server)[0]; if (extensionInOtherServer) { // Getting installed in other server @@ -342,6 +342,48 @@ export abstract class InstallInOtherServerAction extends ExtensionAction { } } + private canInstall(): boolean { + // Disable if extension is not installed or not an user extension + if ( + !this.extension + || !this.server + || !this.extension.local + || this.extension.state !== ExtensionState.Installed + || this.extension.type !== ExtensionType.User + || this.extension.enablementState === EnablementState.DisabledByEnvironemt + ) { + return false; + } + + if (isLanguagePackExtension(this.extension.local.manifest)) { + return true; + } + + // Prefers to run on UI + if (this.server === this.extensionManagementServerService.localExtensionManagementServer && prefersExecuteOnUI(this.extension.local.manifest, this.productService, this.configurationService)) { + return true; + } + + // Prefers to run on Workspace + if (this.server === this.extensionManagementServerService.remoteExtensionManagementServer && prefersExecuteOnWorkspace(this.extension.local.manifest, this.productService, this.configurationService)) { + return true; + } + + if (this.canInstallAnyWhere) { + // Can run on UI + if (this.server === this.extensionManagementServerService.localExtensionManagementServer && canExecuteOnUI(this.extension.local.manifest, this.productService, this.configurationService)) { + return true; + } + + // Can run on Workspace + if (this.server === this.extensionManagementServerService.remoteExtensionManagementServer && canExecuteOnWorkspace(this.extension.local.manifest, this.productService, this.configurationService)) { + return true; + } + } + + return false; + } + async run(): Promise { if (!this.extension) { return; @@ -364,10 +406,13 @@ export abstract class InstallInOtherServerAction extends ExtensionAction { export class RemoteInstallAction extends InstallInOtherServerAction { constructor( + canInstallAnyWhere: boolean, @IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService, - @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService + @IExtensionManagementServerService extensionManagementServerService: IExtensionManagementServerService, + @IProductService productService: IProductService, + @IConfigurationService configurationService: IConfigurationService, ) { - super(`extensions.remoteinstall`, extensionManagementServerService.remoteExtensionManagementServer, extensionsWorkbenchService); + super(`extensions.remoteinstall`, extensionManagementServerService.remoteExtensionManagementServer, canInstallAnyWhere, extensionsWorkbenchService, extensionManagementServerService, productService, configurationService); } protected getInstallLabel(): string { @@ -380,9 +425,11 @@ export class LocalInstallAction extends InstallInOtherServerAction { constructor( @IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService, - @IExtensionManagementServerService extensionManagementServerService: IExtensionManagementServerService + @IExtensionManagementServerService extensionManagementServerService: IExtensionManagementServerService, + @IProductService productService: IProductService, + @IConfigurationService configurationService: IConfigurationService, ) { - super(`extensions.localinstall`, extensionManagementServerService.localExtensionManagementServer, extensionsWorkbenchService); + super(`extensions.localinstall`, extensionManagementServerService.localExtensionManagementServer, false, extensionsWorkbenchService, extensionManagementServerService, productService, configurationService); } protected getInstallLabel(): string { @@ -1230,6 +1277,7 @@ export class ReloadAction extends ExtensionAction { if (!this._runningExtensions || !this.extension) { return; } + const isUninstalled = this.extension.state === ExtensionState.Uninstalled; const runningExtension = this._runningExtensions.filter(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension!.identifier))[0]; const isSameExtensionRunning = runningExtension && this.extension.server === this.extensionManagementServerService.getExtensionManagementServer(runningExtension.extensionLocation); @@ -1254,15 +1302,38 @@ export class ReloadAction extends ExtensionAction { if (this.extensionService.canAddExtension(toExtensionDescription(this.extension.local))) { return; } + const runningExtensionServer = this.extensionManagementServerService.getExtensionManagementServer(runningExtension.extensionLocation); + if (isSameExtensionRunning) { // Different version of same extension is running. Requires reload to run the current version if (this.extension.version !== runningExtension.version) { this.enabled = true; this.label = localize('reloadRequired', "Reload Required"); this.tooltip = localize('postUpdateTooltip', "Please reload Azure Data Studio to enable the updated extension."); // {{SQL CARBON EDIT}} - replace Visual Studio Code with Azure Data Studio + return; } + + const extensionInOtherServer = this.extensionsWorkbenchService.installed.filter(e => areSameExtensions(e.identifier, this.extension!.identifier) && e.server !== this.extension!.server)[0]; + if (extensionInOtherServer) { + // This extension prefers to run on UI/Local side but is running in remote + if (runningExtensionServer === this.extensionManagementServerService.remoteExtensionManagementServer && prefersExecuteOnUI(this.extension.local!.manifest, this.productService, this.configurationService)) { + this.enabled = true; + this.label = localize('reloadRequired', "Reload Required"); + this.tooltip = localize('enable locally', "Please reload Visual Studio Code to enable this extension locally."); + return; + } + + // This extension prefers to run on Workspace/Remote side but is running in local + if (runningExtensionServer === this.extensionManagementServerService.localExtensionManagementServer && prefersExecuteOnWorkspace(this.extension.local!.manifest, this.productService, this.configurationService)) { + this.enabled = true; + this.label = localize('reloadRequired', "Reload Required"); + this.tooltip = localize('enable remote', "Please reload Visual Studio Code to enable this extension in {0}.", this.extensionManagementServerService.remoteExtensionManagementServer?.label); + return; + } + } + } else { - const runningExtensionServer = this.extensionManagementServerService.getExtensionManagementServer(runningExtension.extensionLocation); + if (this.extension.server === this.extensionManagementServerService.localExtensionManagementServer && runningExtensionServer === this.extensionManagementServerService.remoteExtensionManagementServer) { // This extension prefers to run on UI/Local side but is running in remote if (prefersExecuteOnUI(this.extension.local!.manifest, this.productService, this.configurationService)) { @@ -3212,7 +3283,7 @@ export class InstallLocalExtensionsInRemoteAction extends Action { private getExtensionsToInstall(local: IExtension[]): IExtension[] { return local.filter(extension => { - const action = this.instantiationService.createInstance(RemoteInstallAction); + const action = this.instantiationService.createInstance(RemoteInstallAction, true); action.extension = extension; return action.enabled; }); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsList.ts b/src/vs/workbench/contrib/extensions/browser/extensionsList.ts index 418aa2cfa1..5d4e0f88e0 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsList.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsList.ts @@ -100,7 +100,7 @@ export class Renderer implements IPagedRenderer { this.instantiationService.createInstance(UpdateAction), reloadAction, this.instantiationService.createInstance(InstallAction), - this.instantiationService.createInstance(RemoteInstallAction), + this.instantiationService.createInstance(RemoteInstallAction, false), this.instantiationService.createInstance(LocalInstallAction), this.instantiationService.createInstance(MaliciousStatusLabelAction, false), systemDisabledWarningAction, diff --git a/src/vs/workbench/contrib/extensions/browser/media/defaultIcon.png b/src/vs/workbench/contrib/extensions/browser/media/defaultIcon.png deleted file mode 100644 index adad56268218c5fce14fa579b6a9eb2cf726a557..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1054 zcmeAS@N?(olHy`uVBq!ia0vp^6(G#P3?%t>9eV(zSkfJR9T^xl_H+M9WCijSl0AZa z85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP{J?3C&U##5jUHNuA0v4RZ$EKOg)}1jv*QM-p;<= z_sD>!EjZ}AfK%bj#I*bWJ-;W3Us<%QFw0sl^M}6RrR5*(`aEAaC);=j>kCd$=@7Skb%k9g;Dx&68g?C#A19f#o9x>Dwx=b` z7L=6gm-=4fx@=whzVDI~bTziW)?Cz)^l;(s1}84pzAcO*!XkGz%DG;h+$-MdYVwlt zsHsGuXNss^6%wtGG1x%LYao3bF^r&JHYMsx`=| zeWM+AVe2NLvx#hNcKaX!*t=`KH8-(l10KRz$}IzB(|OY3^=b6+R(!|A_X$JsJJ z{5oO1OocB_wo+Z9t9iQnz0b0r9vq$cebEM+3c;Do(tEbtUoT@}^P1-b+xi$kz8-Ne ziz^KFhm^0dF{|=rH~c^T)PwE(5(XEB<9Bx~;gFkKyzcO?gg2k^D`X4i`%jBIc>m<> zFeW*pJa1l^ImH=_$Nnh$bKco8Sz|_xsQ=BD2+lXL+01!w4BQdPCyD z2`x>d1L9$VUo9*?oH};=pzZpAI*C1Rcz?`FIG@ud!@B;r{XV%H;rUDR%tLB*H!L?Z zZ8iNj$825X<6G-O^UdZYnD-@`Hzb)AC7Y+Dn1`jBd!(6LrJL(yn9F6F3uKuyWt)G= zHh-35ek0fXRG!72{O#)!b}UHTF)3+#OY)AglpPtVJEOkEEc31_*_&$h@aw_-%=15N zm(boYADC2COI#yLQW8s2t&)pUffR$0fuV)2p`osUS%{H=m5Hg9v4ysQft7)Q#)Wye zP&DM`r(~v8;?@w^tCj}TAPKS|I6tkVJh3R1!7(L2DOJHUH!(dmC^a#qvhZXoDAO`{ My85}Sb4q9e05qV^jsO4v diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css index 093f2a4547..15d110b7be 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css @@ -135,11 +135,6 @@ padding-left: 14px; } -.extension-editor > .header > .details > .subtitle .version { - font-size: 90%; - font-style: italic; -} - .extension-editor > .header > .details > .description { margin-top: 10px; white-space: nowrap; diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts index 2991f2ff2f..c1bacdc167 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts @@ -52,73 +52,75 @@ import { IExperimentService } from 'vs/workbench/contrib/experiments/common/expe import { ExtensionTipsService } from 'vs/platform/extensionManagement/node/extensionTipsService'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { TestLifecycleService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { DisposableStore } from 'vs/base/common/lifecycle'; + +let instantiationService: TestInstantiationService; +let installEvent: Emitter, + didInstallEvent: Emitter, + uninstallEvent: Emitter, + didUninstallEvent: Emitter; + +let disposables: DisposableStore; + +async function setupTest() { + disposables = new DisposableStore(); + installEvent = new Emitter(); + didInstallEvent = new Emitter(); + uninstallEvent = new Emitter(); + didUninstallEvent = new Emitter(); + + instantiationService = new TestInstantiationService(); + instantiationService.stub(ITelemetryService, NullTelemetryService); + instantiationService.stub(ILogService, NullLogService); + + instantiationService.stub(IWorkspaceContextService, new TestContextService()); + instantiationService.stub(IConfigurationService, new TestConfigurationService()); + instantiationService.stub(IProgressService, ProgressService); + instantiationService.stub(IStorageKeysSyncRegistryService, new StorageKeysSyncRegistryService()); + instantiationService.stub(IProductService, {}); + + instantiationService.stub(IExtensionGalleryService, ExtensionGalleryService); + instantiationService.stub(ISharedProcessService, TestSharedProcessService); + + instantiationService.stub(IExtensionManagementService, ExtensionManagementService); + instantiationService.stub(IExtensionManagementService, 'onInstallExtension', installEvent.event); + instantiationService.stub(IExtensionManagementService, 'onDidInstallExtension', didInstallEvent.event); + instantiationService.stub(IExtensionManagementService, 'onUninstallExtension', uninstallEvent.event); + instantiationService.stub(IExtensionManagementService, 'onDidUninstallExtension', didUninstallEvent.event); + instantiationService.stub(IRemoteAgentService, RemoteAgentService); + + instantiationService.stub(IExtensionManagementServerService, new class extends ExtensionManagementServerService { + private _localExtensionManagementServer: IExtensionManagementServer = { extensionManagementService: instantiationService.get(IExtensionManagementService), label: 'local', authority: 'vscode-local' }; + constructor() { + super(instantiationService.get(ISharedProcessService), instantiationService.get(IRemoteAgentService), instantiationService.get(IExtensionGalleryService), instantiationService.get(IConfigurationService), instantiationService.get(IProductService), instantiationService.get(ILogService), instantiationService.get(ILabelService)); + } + get localExtensionManagementServer(): IExtensionManagementServer { return this._localExtensionManagementServer; } + set localExtensionManagementServer(server: IExtensionManagementServer) { } + }()); + + instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + instantiationService.stub(ILabelService, { onDidChangeFormatters: new Emitter().event }); + + instantiationService.stub(ILifecycleService, new TestLifecycleService()); + instantiationService.stub(IExperimentService, instantiationService.createInstance(TestExperimentService)); + instantiationService.stub(IExtensionTipsService, instantiationService.createInstance(ExtensionTipsService)); + instantiationService.stub(IExtensionRecommendationsService, {}); + instantiationService.stub(IURLService, URLService); + + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', []); + instantiationService.stubPromise(IExtensionManagementService, 'getExtensionsReport', []); + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage()); + instantiationService.stub(IExtensionService, >{ getExtensions: () => Promise.resolve([]), onDidChangeExtensions: new Emitter().event, canAddExtension: (extension: IExtensionDescription) => false, canRemoveExtension: (extension: IExtensionDescription) => false }); + (instantiationService.get(IWorkbenchExtensionEnablementService)).reset(); + + instantiationService.set(IExtensionsWorkbenchService, disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService))); +} + suite('ExtensionsActions Test', () => { - let instantiationService: TestInstantiationService; - - let installEvent: Emitter, - didInstallEvent: Emitter, - uninstallEvent: Emitter, - didUninstallEvent: Emitter; - - - setup(async () => { - installEvent = new Emitter(); - didInstallEvent = new Emitter(); - uninstallEvent = new Emitter(); - didUninstallEvent = new Emitter(); - - instantiationService = new TestInstantiationService(); - instantiationService.stub(ITelemetryService, NullTelemetryService); - instantiationService.stub(ILogService, NullLogService); - - instantiationService.stub(IWorkspaceContextService, new TestContextService()); - instantiationService.stub(IConfigurationService, new TestConfigurationService()); - instantiationService.stub(IProgressService, ProgressService); - instantiationService.stub(IStorageKeysSyncRegistryService, new StorageKeysSyncRegistryService()); - instantiationService.stub(IProductService, {}); - - instantiationService.stub(IExtensionGalleryService, ExtensionGalleryService); - instantiationService.stub(ISharedProcessService, TestSharedProcessService); - - instantiationService.stub(IExtensionManagementService, ExtensionManagementService); - instantiationService.stub(IExtensionManagementService, 'onInstallExtension', installEvent.event); - instantiationService.stub(IExtensionManagementService, 'onDidInstallExtension', didInstallEvent.event); - instantiationService.stub(IExtensionManagementService, 'onUninstallExtension', uninstallEvent.event); - instantiationService.stub(IExtensionManagementService, 'onDidUninstallExtension', didUninstallEvent.event); - instantiationService.stub(IRemoteAgentService, RemoteAgentService); - - instantiationService.stub(IExtensionManagementServerService, new class extends ExtensionManagementServerService { - private _localExtensionManagementServer: IExtensionManagementServer = { extensionManagementService: instantiationService.get(IExtensionManagementService), label: 'local', authority: 'vscode-local' }; - constructor() { - super(instantiationService.get(ISharedProcessService), instantiationService.get(IRemoteAgentService), instantiationService.get(IExtensionGalleryService), instantiationService.get(IConfigurationService), instantiationService.get(IProductService), instantiationService.get(ILogService), instantiationService.get(ILabelService)); - } - get localExtensionManagementServer(): IExtensionManagementServer { return this._localExtensionManagementServer; } - set localExtensionManagementServer(server: IExtensionManagementServer) { } - }()); - - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); - instantiationService.stub(ILabelService, { onDidChangeFormatters: new Emitter().event }); - - instantiationService.stub(ILifecycleService, new TestLifecycleService()); - instantiationService.stub(IExperimentService, instantiationService.createInstance(TestExperimentService)); - instantiationService.stub(IExtensionTipsService, instantiationService.createInstance(ExtensionTipsService)); - instantiationService.stub(IExtensionRecommendationsService, {}); - instantiationService.stub(IURLService, URLService); - - instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', []); - instantiationService.stubPromise(IExtensionManagementService, 'getExtensionsReport', []); - instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage()); - instantiationService.stub(IExtensionService, >{ getExtensions: () => Promise.resolve([]), onDidChangeExtensions: new Emitter().event, canAddExtension: (extension: IExtensionDescription) => false, canRemoveExtension: (extension: IExtensionDescription) => false }); - await (instantiationService.get(IWorkbenchExtensionEnablementService)).reset(); - - instantiationService.set(IExtensionsWorkbenchService, instantiationService.createInstance(ExtensionsWorkbenchService)); - }); - - teardown(() => { - (instantiationService.get(IExtensionsWorkbenchService)).dispose(); - }); + setup(setupTest); + teardown(() => disposables.dispose()); test('Install action is disabled when there is no extension', () => { const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.InstallAction); @@ -1068,6 +1070,17 @@ suite('ExtensionsActions Test', () => { }); }); + test(`RecommendToFolderAction`, () => { + // TODO: Implement test + }); + +}); + +suite('ReloadAction', () => { + + setup(setupTest); + teardown(() => disposables.dispose()); + test('Test ReloadAction when there is no extension', () => { const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); instantiationService.createInstance(ExtensionContainers, [testObject]); @@ -1541,6 +1554,136 @@ suite('ExtensionsActions Test', () => { assert.ok(!testObject.enabled); }); + test('Test ReloadAction for remote workspace+ui extension is enabled when it is installed and enabled in local server', async () => { + // multi server setup + const gallery = aGalleryExtension('a'); + const localExtension = aLocalExtension('a', { extensionKind: ['workspace', 'ui'] }, { location: URI.file('pub.a') }); + const localExtensionManagementService = createExtensionManagementService([localExtension]); + const onDidInstallEvent = new Emitter(); + localExtensionManagementService.onDidInstallExtension = onDidInstallEvent.event; + const remoteExtension = aLocalExtension('a', { extensionKind: ['workspace', 'ui'] }, { location: URI.file('pub.a').with({ scheme: Schemas.vscodeRemote }) }); + const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, localExtensionManagementService, createExtensionManagementService([remoteExtension])); + instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); + instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.set(IExtensionsWorkbenchService, workbenchService); + + const onDidChangeExtensionsEmitter: Emitter = new Emitter(); + instantiationService.stub(IExtensionService, >{ + getExtensions: () => Promise.resolve([ExtensionsActions.toExtensionDescription(localExtension)]), + onDidChangeExtensions: onDidChangeExtensionsEmitter.event, + canAddExtension: (extension) => false + }); + const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); + instantiationService.createInstance(ExtensionContainers, [testObject]); + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); + + await workbenchService.queryGallery(CancellationToken.None); + const extensions = await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!); + testObject.extension = extensions[0]; + assert.ok(testObject.extension); + assert.ok(testObject.enabled); + }); + + test('Test ReloadAction for local ui+workspace extension is enabled when it is installed and enabled in remote server', async () => { + // multi server setup + const gallery = aGalleryExtension('a'); + const localExtension = aLocalExtension('a', { extensionKind: ['ui', 'workspace'] }, { location: URI.file('pub.a') }); + const remoteExtension = aLocalExtension('a', { extensionKind: ['ui', 'workspace'] }, { location: URI.file('pub.a').with({ scheme: Schemas.vscodeRemote }) }); + const remoteExtensionManagementService = createExtensionManagementService([remoteExtension]); + const onDidInstallEvent = new Emitter(); + remoteExtensionManagementService.onDidInstallExtension = onDidInstallEvent.event; + const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), remoteExtensionManagementService); + instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); + instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.set(IExtensionsWorkbenchService, workbenchService); + + const onDidChangeExtensionsEmitter: Emitter = new Emitter(); + instantiationService.stub(IExtensionService, >{ + getExtensions: () => Promise.resolve([ExtensionsActions.toExtensionDescription(remoteExtension)]), + onDidChangeExtensions: onDidChangeExtensionsEmitter.event, + canAddExtension: (extension) => false + }); + const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); + instantiationService.createInstance(ExtensionContainers, [testObject]); + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); + + await workbenchService.queryGallery(CancellationToken.None); + const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!); + testObject.extension = extensions[0]; + assert.ok(testObject.extension); + assert.ok(testObject.enabled); + }); + + test('Test ReloadAction for local workspace+ui extension is enabled when it is installed in both servers but running in local server', async () => { + // multi server setup + const gallery = aGalleryExtension('a'); + const localExtension = aLocalExtension('a', { extensionKind: ['workspace', 'ui'] }, { location: URI.file('pub.a') }); + const localExtensionManagementService = createExtensionManagementService([localExtension]); + const onDidInstallEvent = new Emitter(); + localExtensionManagementService.onDidInstallExtension = onDidInstallEvent.event; + const remoteExtension = aLocalExtension('a', { extensionKind: ['workspace', 'ui'] }, { location: URI.file('pub.a').with({ scheme: Schemas.vscodeRemote }) }); + const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, localExtensionManagementService, createExtensionManagementService([remoteExtension])); + instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); + instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.set(IExtensionsWorkbenchService, workbenchService); + + const onDidChangeExtensionsEmitter: Emitter = new Emitter(); + instantiationService.stub(IExtensionService, >{ + getExtensions: () => Promise.resolve([ExtensionsActions.toExtensionDescription(localExtension)]), + onDidChangeExtensions: onDidChangeExtensionsEmitter.event, + canAddExtension: (extension) => false + }); + const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); + instantiationService.createInstance(ExtensionContainers, [testObject]); + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); + + await workbenchService.queryGallery(CancellationToken.None); + const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!); + testObject.extension = extensions[0]; + assert.ok(testObject.extension); + assert.ok(testObject.enabled); + }); + + test('Test ReloadAction for remote ui+workspace extension is enabled when it is installed on both servers but running in remote server', async () => { + // multi server setup + const gallery = aGalleryExtension('a'); + const localExtension = aLocalExtension('a', { extensionKind: ['ui', 'workspace'] }, { location: URI.file('pub.a') }); + const remoteExtension = aLocalExtension('a', { extensionKind: ['ui', 'workspace'] }, { location: URI.file('pub.a').with({ scheme: Schemas.vscodeRemote }) }); + const remoteExtensionManagementService = createExtensionManagementService([remoteExtension]); + const onDidInstallEvent = new Emitter(); + remoteExtensionManagementService.onDidInstallExtension = onDidInstallEvent.event; + const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), remoteExtensionManagementService); + instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); + instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.set(IExtensionsWorkbenchService, workbenchService); + + const onDidChangeExtensionsEmitter: Emitter = new Emitter(); + instantiationService.stub(IExtensionService, >{ + getExtensions: () => Promise.resolve([ExtensionsActions.toExtensionDescription(remoteExtension)]), + onDidChangeExtensions: onDidChangeExtensionsEmitter.event, + canAddExtension: (extension) => false + }); + const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); + instantiationService.createInstance(ExtensionContainers, [testObject]); + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); + + await workbenchService.queryGallery(CancellationToken.None); + const extensions = await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!); + testObject.extension = extensions[0]; + assert.ok(testObject.extension); + assert.ok(testObject.enabled); + }); +}); + +suite('RemoteInstallAction', () => { + + setup(setupTest); + teardown(() => disposables.dispose()); + test('Test remote install action is enabled for local workspace extension', async () => { // multi server setup const localWorkspaceExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`) }); @@ -1551,7 +1694,7 @@ suite('ExtensionsActions Test', () => { instantiationService.set(IExtensionsWorkbenchService, workbenchService); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: localWorkspaceExtension.identifier }))); - const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction); + const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction, false); instantiationService.createInstance(ExtensionContainers, [testObject]); const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!); @@ -1577,7 +1720,7 @@ suite('ExtensionsActions Test', () => { const gallery = aGalleryExtension('a', { identifier: localWorkspaceExtension.identifier }); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); - const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction); + const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction, false); instantiationService.createInstance(ExtensionContainers, [testObject]); const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!); @@ -1610,7 +1753,7 @@ suite('ExtensionsActions Test', () => { const gallery = aGalleryExtension('a', { identifier: localWorkspaceExtension.identifier }); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); - const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction); + const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction, false); instantiationService.createInstance(ExtensionContainers, [testObject]); const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!); @@ -1641,7 +1784,7 @@ suite('ExtensionsActions Test', () => { await instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([localWorkspaceExtension], EnablementState.DisabledGlobally); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: localWorkspaceExtension.identifier }))); - const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction); + const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction, false); instantiationService.createInstance(ExtensionContainers, [testObject]); const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!); @@ -1652,6 +1795,70 @@ suite('ExtensionsActions Test', () => { assert.equal('extension-action label prominent install', testObject.class); }); + test('Test remote install action is enabled local workspace+ui extension', async () => { + // multi server setup + const localWorkspaceExtension = aLocalExtension('a', { extensionKind: ['workspace', 'ui'] }, { location: URI.file(`pub.a`) }); + const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localWorkspaceExtension])); + instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); + instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.set(IExtensionsWorkbenchService, workbenchService); + + await instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([localWorkspaceExtension], EnablementState.DisabledGlobally); + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: localWorkspaceExtension.identifier }))); + const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction, false); + instantiationService.createInstance(ExtensionContainers, [testObject]); + + const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!); + await workbenchService.queryGallery(CancellationToken.None); + testObject.extension = extensions[0]; + assert.ok(testObject.enabled); + assert.equal('Install in remote', testObject.label); + assert.equal('extension-action label prominent install', testObject.class); + }); + + test('Test remote install action is enabled for local ui+workapace extension if can install is true', async () => { + // multi server setup + const localWorkspaceExtension = aLocalExtension('a', { extensionKind: ['ui', 'workspace'] }, { location: URI.file(`pub.a`) }); + const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localWorkspaceExtension])); + instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); + instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.set(IExtensionsWorkbenchService, workbenchService); + + await instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([localWorkspaceExtension], EnablementState.DisabledGlobally); + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: localWorkspaceExtension.identifier }))); + const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction, true); + instantiationService.createInstance(ExtensionContainers, [testObject]); + + const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!); + await workbenchService.queryGallery(CancellationToken.None); + testObject.extension = extensions[0]; + assert.ok(testObject.enabled); + assert.equal('Install in remote', testObject.label); + assert.equal('extension-action label prominent install', testObject.class); + }); + + test('Test remote install action is disabled for local ui+workapace extension if can install is false', async () => { + // multi server setup + const localWorkspaceExtension = aLocalExtension('a', { extensionKind: ['ui', 'workspace'] }, { location: URI.file(`pub.a`) }); + const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localWorkspaceExtension])); + instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); + instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.set(IExtensionsWorkbenchService, workbenchService); + + await instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([localWorkspaceExtension], EnablementState.DisabledGlobally); + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: localWorkspaceExtension.identifier }))); + const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction, false); + instantiationService.createInstance(ExtensionContainers, [testObject]); + + const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!); + await workbenchService.queryGallery(CancellationToken.None); + testObject.extension = extensions[0]; + assert.ok(!testObject.enabled); + }); + test('Test remote install action is disabled when extension is not set', async () => { // multi server setup const localWorkspaceExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`) }); @@ -1661,7 +1868,7 @@ suite('ExtensionsActions Test', () => { instantiationService.set(IExtensionsWorkbenchService, workbenchService); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: localWorkspaceExtension.identifier }))); - const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction); + const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction, false); instantiationService.createInstance(ExtensionContainers, [testObject]); await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!); @@ -1678,7 +1885,7 @@ suite('ExtensionsActions Test', () => { instantiationService.set(IExtensionsWorkbenchService, workbenchService); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a'))); - const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction); + const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction, false); instantiationService.createInstance(ExtensionContainers, [testObject]); const pager = await workbenchService.queryGallery(CancellationToken.None); @@ -1698,7 +1905,7 @@ suite('ExtensionsActions Test', () => { instantiationService.set(IExtensionsWorkbenchService, workbenchService); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: localWorkspaceExtension.identifier }))); - const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction); + const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction, false); instantiationService.createInstance(ExtensionContainers, [testObject]); const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!); @@ -1715,7 +1922,7 @@ suite('ExtensionsActions Test', () => { const localWorkspaceExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`) }); instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [localWorkspaceExtension]); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: localWorkspaceExtension.identifier }))); - const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction); + const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction, false); instantiationService.createInstance(ExtensionContainers, [testObject]); const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!); @@ -1737,7 +1944,7 @@ suite('ExtensionsActions Test', () => { instantiationService.set(IExtensionsWorkbenchService, workbenchService); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: localWorkspaceExtension.identifier }))); - const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction); + const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction, false); instantiationService.createInstance(ExtensionContainers, [testObject]); const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!); @@ -1761,7 +1968,7 @@ suite('ExtensionsActions Test', () => { instantiationService.set(IExtensionsWorkbenchService, workbenchService); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: localWorkspaceExtension.identifier }))); - const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction); + const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction, false); instantiationService.createInstance(ExtensionContainers, [testObject]); const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!); @@ -1781,7 +1988,7 @@ suite('ExtensionsActions Test', () => { instantiationService.set(IExtensionsWorkbenchService, workbenchService); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: localWorkspaceExtension.identifier }))); - const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction); + const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction, false); instantiationService.createInstance(ExtensionContainers, [testObject]); const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!); @@ -1800,7 +2007,7 @@ suite('ExtensionsActions Test', () => { instantiationService.set(IExtensionsWorkbenchService, workbenchService); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: localWorkspaceSystemExtension.identifier }))); - const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction); + const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction, false); instantiationService.createInstance(ExtensionContainers, [testObject]); const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!); @@ -1819,7 +2026,7 @@ suite('ExtensionsActions Test', () => { instantiationService.set(IExtensionsWorkbenchService, workbenchService); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: localUIExtension.identifier }))); - const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction); + const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction, false); instantiationService.createInstance(ExtensionContainers, [testObject]); const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!); @@ -1839,7 +2046,7 @@ suite('ExtensionsActions Test', () => { instantiationService.set(IExtensionsWorkbenchService, workbenchService); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: localUIExtension.identifier }))); - const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction); + const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction, false); instantiationService.createInstance(ExtensionContainers, [testObject]); const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!); @@ -1858,7 +2065,7 @@ suite('ExtensionsActions Test', () => { instantiationService.set(IExtensionsWorkbenchService, workbenchService); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: languagePackExtension.identifier }))); - const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction); + const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction, false); instantiationService.createInstance(ExtensionContainers, [testObject]); const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!); @@ -1881,7 +2088,7 @@ suite('ExtensionsActions Test', () => { instantiationService.set(IExtensionsWorkbenchService, workbenchService); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: languagePackExtension.identifier }))); - const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction); + const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction, false); instantiationService.createInstance(ExtensionContainers, [testObject]); const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!); @@ -1893,6 +2100,12 @@ suite('ExtensionsActions Test', () => { uninstallEvent.fire(languagePackExtension.identifier); assert.ok(!testObject.enabled); }); +}); + +suite('LocalInstallAction', () => { + + setup(setupTest); + teardown(() => disposables.dispose()); test('Test local install action is enabled for remote ui extension', async () => { // multi server setup @@ -1915,6 +2128,27 @@ suite('ExtensionsActions Test', () => { assert.equal('extension-action label prominent install', testObject.class); }); + test('Test local install action is enabled for remote ui+workspace extension', async () => { + // multi server setup + const remoteUIExtension = aLocalExtension('a', { extensionKind: ['ui', 'workspace'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService(), createExtensionManagementService([remoteUIExtension])); + instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); + instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.set(IExtensionsWorkbenchService, workbenchService); + + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: remoteUIExtension.identifier }))); + const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.LocalInstallAction); + instantiationService.createInstance(ExtensionContainers, [testObject]); + + const extensions = await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!); + await workbenchService.queryGallery(CancellationToken.None); + testObject.extension = extensions[0]; + assert.ok(testObject.enabled); + assert.equal('Install Locally', testObject.label); + assert.equal('extension-action label prominent install', testObject.class); + }); + test('Test local install action when installing remote ui extension', async () => { // multi server setup const localExtensionManagementService: IExtensionManagementService = createExtensionManagementService(); @@ -2250,89 +2484,87 @@ suite('ExtensionsActions Test', () => { assert.ok(!testObject.enabled); }); - test(`RecommendToFolderAction`, () => { - // TODO: Implement test - }); - - function aLocalExtension(name: string = 'someext', manifest: any = {}, properties: any = {}): ILocalExtension { - manifest = assign({ name, publisher: 'pub', version: '1.0.0' }, manifest); - properties = assign({ - type: ExtensionType.User, - location: URI.file(`pub.${name}`), - identifier: { id: getGalleryExtensionId(manifest.publisher, manifest.name), uuid: undefined }, - metadata: { id: getGalleryExtensionId(manifest.publisher, manifest.name), publisherId: manifest.publisher, publisherDisplayName: 'somename' } - }, properties); - return Object.create({ manifest, ...properties }); - } - - function aGalleryExtension(name: string, properties: any = {}, galleryExtensionProperties: any = {}, assets: any = {}): IGalleryExtension { - const galleryExtension = Object.create({}); - assign(galleryExtension, { name, publisher: 'pub', version: '1.0.0', properties: {}, assets: {} }, properties); - assign(galleryExtension.properties, { dependencies: [] }, galleryExtensionProperties); - assign(galleryExtension.assets, assets); - galleryExtension.identifier = { id: getGalleryExtensionId(galleryExtension.publisher, galleryExtension.name), uuid: generateUuid() }; - return galleryExtension; - } - - function aPage(...objects: T[]): IPager { - return { firstPage: objects, total: objects.length, pageSize: objects.length, getPage: () => null! }; - } - - function aSingleRemoteExtensionManagementServerService(instantiationService: TestInstantiationService, remoteExtensionManagementService?: IExtensionManagementService): IExtensionManagementServerService { - const remoteExtensionManagementServer: IExtensionManagementServer = { - authority: 'vscode-remote', - label: 'remote', - extensionManagementService: remoteExtensionManagementService || createExtensionManagementService() - }; - return { - _serviceBrand: undefined, - localExtensionManagementServer: null, - remoteExtensionManagementServer, - getExtensionManagementServer: (location: URI) => { - if (location.scheme === REMOTE_HOST_SCHEME) { - return remoteExtensionManagementServer; - } - return null; - } - }; - } - - function aMultiExtensionManagementServerService(instantiationService: TestInstantiationService, localExtensionManagementService?: IExtensionManagementService, remoteExtensionManagementService?: IExtensionManagementService): IExtensionManagementServerService { - const localExtensionManagementServer: IExtensionManagementServer = { - authority: 'vscode-local', - label: 'local', - extensionManagementService: localExtensionManagementService || createExtensionManagementService() - }; - const remoteExtensionManagementServer: IExtensionManagementServer = { - authority: 'vscode-remote', - label: 'remote', - extensionManagementService: remoteExtensionManagementService || createExtensionManagementService() - }; - return { - _serviceBrand: undefined, - localExtensionManagementServer, - remoteExtensionManagementServer, - getExtensionManagementServer: (location: URI) => { - if (location.scheme === Schemas.file) { - return localExtensionManagementServer; - } - if (location.scheme === REMOTE_HOST_SCHEME) { - return remoteExtensionManagementServer; - } - return null; - } - }; - } - - function createExtensionManagementService(installed: ILocalExtension[] = []): IExtensionManagementService { - return { - onInstallExtension: Event.None, - onDidInstallExtension: Event.None, - onUninstallExtension: Event.None, - onDidUninstallExtension: Event.None, - getInstalled: () => Promise.resolve(installed), - installFromGallery: (extension: IGalleryExtension) => Promise.reject(new Error('not supported')) - }; - } - }); + +function aLocalExtension(name: string = 'someext', manifest: any = {}, properties: any = {}): ILocalExtension { + manifest = assign({ name, publisher: 'pub', version: '1.0.0' }, manifest); + properties = assign({ + type: ExtensionType.User, + location: URI.file(`pub.${name}`), + identifier: { id: getGalleryExtensionId(manifest.publisher, manifest.name), uuid: undefined }, + metadata: { id: getGalleryExtensionId(manifest.publisher, manifest.name), publisherId: manifest.publisher, publisherDisplayName: 'somename' } + }, properties); + return Object.create({ manifest, ...properties }); +} + +function aGalleryExtension(name: string, properties: any = {}, galleryExtensionProperties: any = {}, assets: any = {}): IGalleryExtension { + const galleryExtension = Object.create({}); + assign(galleryExtension, { name, publisher: 'pub', version: '1.0.0', properties: {}, assets: {} }, properties); + assign(galleryExtension.properties, { dependencies: [] }, galleryExtensionProperties); + assign(galleryExtension.assets, assets); + galleryExtension.identifier = { id: getGalleryExtensionId(galleryExtension.publisher, galleryExtension.name), uuid: generateUuid() }; + return galleryExtension; +} + +function aPage(...objects: T[]): IPager { + return { firstPage: objects, total: objects.length, pageSize: objects.length, getPage: () => null! }; +} + +function aSingleRemoteExtensionManagementServerService(instantiationService: TestInstantiationService, remoteExtensionManagementService?: IExtensionManagementService): IExtensionManagementServerService { + const remoteExtensionManagementServer: IExtensionManagementServer = { + authority: 'vscode-remote', + label: 'remote', + extensionManagementService: remoteExtensionManagementService || createExtensionManagementService() + }; + return { + _serviceBrand: undefined, + localExtensionManagementServer: null, + remoteExtensionManagementServer, + getExtensionManagementServer: (location: URI) => { + if (location.scheme === REMOTE_HOST_SCHEME) { + return remoteExtensionManagementServer; + } + return null; + } + }; +} + +function aMultiExtensionManagementServerService(instantiationService: TestInstantiationService, localExtensionManagementService?: IExtensionManagementService, remoteExtensionManagementService?: IExtensionManagementService): IExtensionManagementServerService { + const localExtensionManagementServer: IExtensionManagementServer = { + authority: 'vscode-local', + label: 'local', + extensionManagementService: localExtensionManagementService || createExtensionManagementService() + }; + const remoteExtensionManagementServer: IExtensionManagementServer = { + authority: 'vscode-remote', + label: 'remote', + extensionManagementService: remoteExtensionManagementService || createExtensionManagementService() + }; + return { + _serviceBrand: undefined, + localExtensionManagementServer, + remoteExtensionManagementServer, + getExtensionManagementServer: (location: URI) => { + if (location.scheme === Schemas.file) { + return localExtensionManagementServer; + } + if (location.scheme === REMOTE_HOST_SCHEME) { + return remoteExtensionManagementServer; + } + return null; + } + }; +} + +function createExtensionManagementService(installed: ILocalExtension[] = []): IExtensionManagementService { + return { + onInstallExtension: Event.None, + onDidInstallExtension: Event.None, + onUninstallExtension: Event.None, + onDidUninstallExtension: Event.None, + getInstalled: () => Promise.resolve(installed), + installFromGallery: (extension: IGalleryExtension) => Promise.reject(new Error('not supported')) + }; +} + + diff --git a/src/vs/workbench/contrib/externalTerminal/browser/externalTerminal.contribution.ts b/src/vs/workbench/contrib/externalTerminal/browser/externalTerminal.contribution.ts index da6b313887..01221e8254 100644 --- a/src/vs/workbench/contrib/externalTerminal/browser/externalTerminal.contribution.ts +++ b/src/vs/workbench/contrib/externalTerminal/browser/externalTerminal.contribution.ts @@ -26,6 +26,7 @@ import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteA import { optional } from 'vs/platform/instantiation/common/instantiation'; import { IExplorerService } from 'vs/workbench/contrib/files/common/files'; import { isWeb } from 'vs/base/common/platform'; +import { IPathService } from 'vs/workbench/services/path/common/pathService'; const OPEN_IN_TERMINAL_COMMAND_ID = 'openInTerminal'; @@ -90,7 +91,7 @@ if (!isWeb) { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_C, when: KEYBINDING_CONTEXT_TERMINAL_NOT_FOCUSED, weight: KeybindingWeight.WorkbenchContrib, - handler: (accessor) => { + handler: async (accessor) => { const historyService = accessor.get(IHistoryService); // Open external terminal in local workspaces const terminalService = accessor.get(IExternalTerminalService); @@ -102,6 +103,10 @@ if (!isWeb) { const activeFile = historyService.getLastActiveFile(Schemas.file); if (activeFile) { terminalService.openTerminal(paths.dirname(activeFile.fsPath)); + } else { + const pathService = accessor.get(IPathService); + const userHome = await pathService.userHome; + terminalService.openTerminal(userHome.fsPath); } } } diff --git a/src/vs/workbench/contrib/feedback/browser/feedback.ts b/src/vs/workbench/contrib/feedback/browser/feedback.ts index 4fac87bb90..c1e733dc93 100644 --- a/src/vs/workbench/contrib/feedback/browser/feedback.ts +++ b/src/vs/workbench/contrib/feedback/browser/feedback.ts @@ -271,7 +271,7 @@ export class FeedbackDropdown extends Dropdown { const hideButtonLabel = dom.append(hideButtonContainer, dom.$('label')); hideButtonLabel.setAttribute('for', 'hide-button'); - hideButtonLabel.textContent = nls.localize('showFeedback', "Show Feedback Smiley in Status Bar"); + hideButtonLabel.textContent = nls.localize('showFeedback', "Show Feedback Icon in Status Bar"); // Button: Send Feedback this.sendButton = new Button(buttonsContainer); diff --git a/src/vs/workbench/contrib/files/browser/editors/textFileEditorTracker.ts b/src/vs/workbench/contrib/files/browser/editors/textFileEditorTracker.ts index 0526303b9e..8d89166dd5 100644 --- a/src/vs/workbench/contrib/files/browser/editors/textFileEditorTracker.ts +++ b/src/vs/workbench/contrib/files/browser/editors/textFileEditorTracker.ts @@ -13,6 +13,7 @@ import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { RunOnceWorker } from 'vs/base/common/async'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import { IFilesConfigurationService, AutoSaveMode } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; export class TextFileEditorTracker extends Disposable implements IWorkbenchContribution { @@ -21,7 +22,8 @@ export class TextFileEditorTracker extends Disposable implements IWorkbenchContr @ITextFileService private readonly textFileService: ITextFileService, @ILifecycleService private readonly lifecycleService: ILifecycleService, @IHostService private readonly hostService: IHostService, - @ICodeEditorService private readonly codeEditorService: ICodeEditorService + @ICodeEditorService private readonly codeEditorService: ICodeEditorService, + @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService ) { super(); @@ -57,6 +59,10 @@ export class TextFileEditorTracker extends Disposable implements IWorkbenchContr return false; // resource must not be pending to save } + if (this.filesConfigurationService.getAutoSaveMode() === AutoSaveMode.AFTER_SHORT_DELAY) { + return false; // resource must not be pending to be auto saved + } + if (this.editorService.isOpen({ resource })) { return false; // model must not be opened already as file } diff --git a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts index b4fd280ddd..b9513dbed3 100644 --- a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts +++ b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts @@ -76,9 +76,9 @@ export class ExplorerViewletViewsContribution extends Disposable implements IWor } const explorerViewDescriptor = this.createExplorerViewDescriptor(); - const registeredExplorerViewDescriptor = viewDescriptors.filter(v => v.id === explorerViewDescriptor.id)[0]; + const registeredExplorerViewDescriptor = viewDescriptors.find(v => v.id === explorerViewDescriptor.id); const emptyViewDescriptor = this.createEmptyViewDescriptor(); - const registeredEmptyViewDescriptor = viewDescriptors.filter(v => v.id === emptyViewDescriptor.id)[0]; + const registeredEmptyViewDescriptor = viewDescriptors.find(v => v.id === emptyViewDescriptor.id); if (this.workspaceContextService.getWorkbenchState() === WorkbenchState.EMPTY || this.workspaceContextService.getWorkspace().folders.length === 0) { if (registeredExplorerViewDescriptor) { @@ -254,18 +254,20 @@ export class ExplorerViewPaneContainer extends ViewPaneContainer { } } +const viewContainerRegistry = Registry.as(Extensions.ViewContainersRegistry); + /** * Explorer viewlet container. */ -export const VIEW_CONTAINER: ViewContainer = Registry.as(Extensions.ViewContainersRegistry).registerViewContainer({ +export const VIEW_CONTAINER: ViewContainer = viewContainerRegistry.registerViewContainer({ id: VIEWLET_ID, name: localize('explore', "Explorer"), ctorDescriptor: new SyncDescriptor(ExplorerViewPaneContainer), storageId: 'workbench.explorer.views.state', icon: Codicon.files.classNames, alwaysUseContainerInfo: true, - order: 10 // {{SQL CARBON EDIT}} -}, ViewContainerLocation.Sidebar); + order: 10 // {{SQL CARBON EDIT}} change order +}, ViewContainerLocation.Sidebar); // {{SQL CARBON EDIT}} not default const viewsRegistry = Registry.as(Extensions.ViewsRegistry); viewsRegistry.registerViewWelcomeContent(EmptyView.ID, { diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index 043c286198..e4980b2acf 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -358,10 +358,10 @@ function getDeleteMessage(distinctElements: ExplorerItem[]): { message: string, } function containsBothDirectoryAndFile(distinctElements: ExplorerItem[]): boolean { - const directories = distinctElements.filter(element => element.isDirectory); - const files = distinctElements.filter(element => !element.isDirectory); + const directory = distinctElements.find(element => element.isDirectory); + const file = distinctElements.find(element => !element.isDirectory); - return directories.length > 0 && files.length > 0; + return !!directory && !!file; } @@ -764,11 +764,6 @@ export class CollapseExplorerView extends Action { const explorerView = explorerViewlet.getExplorerView(); if (explorerView) { explorerView.collapseAll(); - // If there is something being edited via input box make sure to close it #96198 - const editable = this.explorerService.getEditable(); - if (editable) { - await this.explorerService.setEditable(editable.stat, null); - } } } } diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts index a42d2a3322..26cbbc518e 100644 --- a/src/vs/workbench/contrib/files/browser/files.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { URI, UriComponents } from 'vs/base/common/uri'; -import { /*ViewletRegistry, Extensions as ViewletExtensions,*/ ShowViewletAction } from 'vs/workbench/browser/viewlet'; +import { ShowViewletAction } from 'vs/workbench/browser/viewlet'; import * as nls from 'vs/nls'; import { sep } from 'vs/base/common/path'; import { SyncActionDescriptor, MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; @@ -75,9 +75,6 @@ class FileUriLabelContribution implements IWorkbenchContribution { registerSingleton(IExplorerService, ExplorerService, true); -// {{SQL CARBON EDIT}} -// Registry.as(ViewletExtensions.Viewlets).setDefaultViewletId(VIEWLET_ID); - const openViewletKb: IKeybindings = { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_E }; diff --git a/src/vs/workbench/contrib/files/browser/views/emptyView.ts b/src/vs/workbench/contrib/files/browser/views/emptyView.ts index 07aac3e2df..491eccd0ef 100644 --- a/src/vs/workbench/contrib/files/browser/views/emptyView.ts +++ b/src/vs/workbench/contrib/files/browser/views/emptyView.ts @@ -14,7 +14,6 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { ResourcesDropHandler, DragAndDropObserver } from 'vs/workbench/browser/dnd'; import { listDropBackground } from 'vs/platform/theme/common/colorRegistry'; -import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { ILabelService } from 'vs/platform/label/common/label'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IViewDescriptorService } from 'vs/workbench/common/views'; @@ -55,8 +54,7 @@ export class EmptyView extends ViewPane { this._register(new DragAndDropObserver(container, { onDrop: e => { - const color = this.themeService.getColorTheme().getColor(SIDE_BAR_BACKGROUND); - container.style.backgroundColor = color ? color.toString() : ''; + container.style.backgroundColor = ''; const dropHandler = this.instantiationService.createInstance(ResourcesDropHandler, { allowWorkspaceOpen: true }); dropHandler.handleDrop(e, () => undefined, () => undefined); }, @@ -65,12 +63,10 @@ export class EmptyView extends ViewPane { container.style.backgroundColor = color ? color.toString() : ''; }, onDragEnd: () => { - const color = this.themeService.getColorTheme().getColor(SIDE_BAR_BACKGROUND); - container.style.backgroundColor = color ? color.toString() : ''; + container.style.backgroundColor = ''; }, onDragLeave: () => { - const color = this.themeService.getColorTheme().getColor(SIDE_BAR_BACKGROUND); - container.style.backgroundColor = color ? color.toString() : ''; + container.style.backgroundColor = ''; }, onDragOver: e => { if (e.dataTransfer) { diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index 593c6708ee..66d10d1163 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -30,7 +30,7 @@ import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/ import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { ILabelService } from 'vs/platform/label/common/label'; import { ExplorerDelegate, ExplorerDataSource, FilesRenderer, ICompressedNavigationController, FilesFilter, FileSorter, FileDragAndDrop, ExplorerCompressionDelegate, isCompressedFolderName } from 'vs/workbench/contrib/files/browser/views/explorerViewer'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IThemeService, IFileIconTheme } from 'vs/platform/theme/common/themeService'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree'; import { IMenuService, MenuId, IMenu } from 'vs/platform/actions/common/actions'; @@ -39,7 +39,6 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ExplorerItem, NewExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel'; import { onUnexpectedError } from 'vs/base/common/errors'; import { ResourceLabels } from 'vs/workbench/browser/labels'; -import { createFileIconThemableTreeContainerScope } from 'vs/workbench/browser/parts/views/views'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IAsyncDataTreeViewState } from 'vs/base/browser/ui/tree/asyncDataTree'; import { FuzzyScore } from 'vs/base/common/filters'; @@ -49,7 +48,7 @@ import { values } from 'vs/base/common/map'; import { first } from 'vs/base/common/arrays'; import { withNullAsUndefined } from 'vs/base/common/types'; import { IFileService, FileSystemProviderCapabilities } from 'vs/platform/files/common/files'; -import { DisposableStore } from 'vs/base/common/lifecycle'; +import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { Event } from 'vs/base/common/event'; import { attachStyler, IColorMapping } from 'vs/platform/theme/common/styler'; import { ColorValue, listDropBackground } from 'vs/platform/theme/common/colorRegistry'; @@ -311,19 +310,13 @@ export class ExplorerView extends ViewPane { } async setEditable(stat: ExplorerItem, isEditing: boolean): Promise { - let shouldRefresh = true; if (isEditing) { - if (stat.parent && stat.parent !== this.tree.getInput()) { - shouldRefresh = stat.parent.isDirectoryResolved; - await this.tree.expand(stat.parent); - } + await this.tree.expand(stat.parent!); } else { DOM.removeClass(this.treeContainer, 'highlight'); } - if (shouldRefresh) { - await this.refresh(false, stat.parent); - } + await this.refresh(false, stat.parent, false); if (isEditing) { DOM.addClass(this.treeContainer, 'highlight'); @@ -558,14 +551,14 @@ export class ExplorerView extends ViewPane { * Refresh the contents of the explorer to get up to date data from the disk about the file structure. * If the item is passed we refresh only that level of the tree, otherwise we do a full refresh. */ - refresh(recursive: boolean, item?: ExplorerItem): Promise { + refresh(recursive: boolean, item?: ExplorerItem, cancelEditing: boolean = true): Promise { if (!this.tree || !this.isBodyVisible() || (item && !this.tree.hasNode(item))) { // Tree node doesn't exist yet this.shouldRefresh = true; return Promise.resolve(undefined); } - if (this.explorerService.isEditable(undefined)) { + if (cancelEditing && this.explorerService.isEditable(undefined)) { this.tree.domFocus(); } @@ -709,6 +702,10 @@ export class ExplorerView extends ViewPane { } collapseAll(): void { + if (this.explorerService.isEditable(undefined)) { + this.tree.domFocus(); + } + const treeInput = this.tree.getInput(); if (Array.isArray(treeInput)) { if (hasExpandedRootChild(this.tree, treeInput)) { @@ -792,3 +789,16 @@ export class ExplorerView extends ViewPane { super.dispose(); } } + +function createFileIconThemableTreeContainerScope(container: HTMLElement, themeService: IThemeService): IDisposable { + DOM.addClass(container, 'file-icon-themable-tree'); + DOM.addClass(container, 'show-file-icons'); + + const onDidChangeFileIconTheme = (theme: IFileIconTheme) => { + DOM.toggleClass(container, 'align-icons-and-twisties', theme.hasFileIcons && !theme.hasFolderIcons); + DOM.toggleClass(container, 'hide-arrows', theme.hidesExplorerArrows === true); + }; + + onDidChangeFileIconTheme(themeService.getFileIconTheme()); + return themeService.onDidFileIconThemeChange(onDidChangeFileIconTheme); +} diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index f4e41e49f4..6318bef652 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -445,15 +445,12 @@ export class FilesRenderer implements ICompressibleTreeRenderer { showInputBoxNotification(); }), + DOM.addDisposableListener(inputBox.inputElement, DOM.EventType.BLUR, () => { + done(inputBox.isInputValid(), true); + }), label, styler ]; - setTimeout(() => { - // Do not react immediatly on blur events due to tree refresh potentially causing an early blur #96566 - toDispose.push(DOM.addDisposableListener(inputBox.inputElement, DOM.EventType.BLUR, () => { - done(inputBox.isInputValid(), true); - })); - }, 100); return toDisposable(() => { done(false, false); @@ -602,7 +599,7 @@ export class FilesFilter implements ITreeFilter { if ((cached && cached.parsed(path.relative(stat.root.resource.path, stat.resource.path), stat.name, name => !!(stat.parent && stat.parent.getChild(name)))) || stat.parent?.isExcluded) { stat.isExcluded = true; const editors = this.editorService.visibleEditors; - const editor = editors.filter(e => e.resource && isEqualOrParent(e.resource, stat.resource)).pop(); + const editor = editors.find(e => e.resource && isEqualOrParent(e.resource, stat.resource)); if (editor) { this.editorsAffectingFilter.add(editor); return true; // Show all opened files and their parents diff --git a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts index b7b4ce6dcc..36f69e6987 100644 --- a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts +++ b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts @@ -700,7 +700,7 @@ class OpenEditorsAccessibilityProvider implements IListAccessibilityProvider r.resource.toString() === folder.uri.toString()).pop(); + const root = this.roots.find(r => r.resource.toString() === folder.uri.toString()); if (root) { return root.find(resource); } diff --git a/src/vs/workbench/contrib/files/common/explorerService.ts b/src/vs/workbench/contrib/files/common/explorerService.ts index 70bcd3a0de..d4ffb37cff 100644 --- a/src/vs/workbench/contrib/files/common/explorerService.ts +++ b/src/vs/workbench/contrib/files/common/explorerService.ts @@ -168,7 +168,7 @@ export class ExplorerService implements IExplorerService { } const rootUri = workspaceFolder.uri; - const root = this.roots.filter(r => r.resource.toString() === rootUri.toString()).pop()!; + const root = this.roots.find(r => r.resource.toString() === rootUri.toString())!; try { const stat = await this.fileService.resolve(rootUri, options); diff --git a/src/vs/workbench/contrib/files/test/browser/textFileEditorTracker.test.ts b/src/vs/workbench/contrib/files/test/browser/textFileEditorTracker.test.ts index f944c3189d..5dab62b224 100644 --- a/src/vs/workbench/contrib/files/test/browser/textFileEditorTracker.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/textFileEditorTracker.test.ts @@ -8,7 +8,7 @@ import { Event } from 'vs/base/common/event'; import { TextFileEditorTracker } from 'vs/workbench/contrib/files/browser/editors/textFileEditorTracker'; import { toResource } from 'vs/base/test/common/utils'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { workbenchInstantiationService, TestServiceAccessor } from 'vs/workbench/test/browser/workbenchTestServices'; +import { workbenchInstantiationService, TestServiceAccessor, TestFilesConfigurationService } from 'vs/workbench/test/browser/workbenchTestServices'; import { IResolvedTextFileEditorModel, snapshotToString, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { FileChangesEvent, FileChangeType } from 'vs/platform/files/common/files'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -27,6 +27,11 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; import { isEqual } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; +import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; +import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; suite('Files - TextFileEditorTracker', () => { @@ -48,9 +53,21 @@ suite('Files - TextFileEditorTracker', () => { disposables = []; }); - async function createTracker(): Promise<[EditorPart, TestServiceAccessor, TextFileEditorTracker, IInstantiationService, IEditorService]> { + async function createTracker(autoSaveEnabled = false): Promise<[EditorPart, TestServiceAccessor, TextFileEditorTracker, IInstantiationService, IEditorService]> { const instantiationService = workbenchInstantiationService(); + if (autoSaveEnabled) { + const configurationService = new TestConfigurationService(); + configurationService.setUserConfiguration('files', { autoSave: 'afterDelay', autoSaveDelay: 1 }); + + instantiationService.stub(IConfigurationService, configurationService); + + instantiationService.stub(IFilesConfigurationService, new TestFilesConfigurationService( + instantiationService.createInstance(MockContextKeyService), + configurationService + )); + } + const part = instantiationService.createInstance(EditorPart); part.create(document.createElement('div')); part.layout(400, 300); @@ -93,23 +110,38 @@ suite('Files - TextFileEditorTracker', () => { }); test.skip('dirty text file model opens as editor', async function () { // {{SQL CARBON EDIT}} tabcolormode failure - const [part, accessor, tracker] = await createTracker(); - const resource = toResource.call(this, '/path/index.txt'); + await testDirtyTextFileModelOpensEditorDependingOnAutoSaveSetting(resource, false); + }); + + test('dirty text file model does not open as editor if autosave is ON', async function () { + const resource = toResource.call(this, '/path/index.txt'); + + await testDirtyTextFileModelOpensEditorDependingOnAutoSaveSetting(resource, true); + }); + + async function testDirtyTextFileModelOpensEditorDependingOnAutoSaveSetting(resource: URI, autoSave: boolean): Promise { + const [part, accessor, tracker] = await createTracker(autoSave); + assert.ok(!accessor.editorService.isOpen(accessor.editorService.createEditorInput({ resource, forceFile: true }))); const model = await accessor.textFileService.files.resolve(resource) as IResolvedTextFileEditorModel; model.textEditorModel.setValue('Super Good'); - await awaitEditorOpening(accessor.editorService); - assert.ok(accessor.editorService.isOpen(accessor.editorService.createEditorInput({ resource, forceFile: true }))); + if (autoSave) { + await timeout(100); + assert.ok(!accessor.editorService.isOpen(accessor.editorService.createEditorInput({ resource, forceFile: true }))); + } else { + await awaitEditorOpening(accessor.editorService); + assert.ok(accessor.editorService.isOpen(accessor.editorService.createEditorInput({ resource, forceFile: true }))); + } part.dispose(); tracker.dispose(); (accessor.textFileService.files).dispose(); - }); + } test.skip('dirty untitled text file model opens as editor', async function () { // {{SQL CARBON EDIT}} tabcolormode failure const [part, accessor, tracker, , editorService] = await createTracker(); diff --git a/src/vs/workbench/contrib/markers/browser/markers.ts b/src/vs/workbench/contrib/markers/browser/markers.ts index 3642a4ab67..176798d2cb 100644 --- a/src/vs/workbench/contrib/markers/browser/markers.ts +++ b/src/vs/workbench/contrib/markers/browser/markers.ts @@ -40,7 +40,7 @@ export class MarkersWorkbenchService extends Disposable implements IMarkersWorkb resourcesMap = resourcesMap ? resourcesMap : new ResourceMap(); resources.forEach(resource => resourcesMap!.set(resource, resource)); return resourcesMap; - }, 0)(resourcesMap => this.onMarkerChanged(resourcesMap.values()))); + }, 0)(resourcesMap => this.onMarkerChanged([...resourcesMap.values()]))); } private onMarkerChanged(resources: URI[]): void { diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts b/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts index d750681720..47dad8eb1f 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts @@ -16,7 +16,7 @@ import { BaseCellRenderTemplate, CellEditState, CellRunState, ICellViewModel, IN import { CellKind, NOTEBOOK_EDITOR_CURSOR_BOUNDARY } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; -import { INotebookService } from 'vs/workbench/contrib/notebook/browser/notebookService'; +import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IModelService } from 'vs/editor/common/services/modelService'; import { getIconClasses } from 'vs/editor/common/services/getIconClasses'; @@ -55,6 +55,9 @@ const PASTE_CELL_COMMAND_ID = 'notebook.cell.paste'; const PASTE_CELL_ABOVE_COMMAND_ID = 'notebook.cell.pasteAbove'; const COPY_CELL_UP_COMMAND_ID = 'notebook.cell.copyUp'; const COPY_CELL_DOWN_COMMAND_ID = 'notebook.cell.copyDown'; +const SPLIT_CELL_COMMAND_ID = 'notebook.cell.split'; +const JOIN_CELL_ABOVE_COMMAND_ID = 'notebook.cell.joinAbove'; +const JOIN_CELL_BELOW_COMMAND_ID = 'notebook.cell.joinBelow'; const EXECUTE_CELL_COMMAND_ID = 'notebook.cell.execute'; const CANCEL_CELL_COMMAND_ID = 'notebook.cell.cancelExecution'; @@ -72,6 +75,7 @@ const enum CellToolbarOrder { MoveCellUp, MoveCellDown, EditCell, + SplitCell, SaveCell, ClearCellOutput, InsertCell, @@ -1396,3 +1400,96 @@ registerAction2(class extends Action2 { editor.viewModel.notebookDocument.clearAllCellOutputs(); } }); + +async function splitCell(context: INotebookCellActionContext): Promise { + if (context.cell.cellKind === CellKind.Code) { + const newCells = await context.notebookEditor.splitNotebookCell(context.cell); + if (newCells) { + context.notebookEditor.focusNotebookCell(newCells[newCells.length - 1], true); + } + } +} + +registerAction2(class extends Action2 { + constructor() { + super( + { + id: SPLIT_CELL_COMMAND_ID, + title: localize('notebookActions.splitCell', "Split Cell"), + category: NOTEBOOK_ACTIONS_CATEGORY, + menu: { + id: MenuId.NotebookCellTitle, + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_CELL_TYPE.isEqualTo('code'), NOTEBOOK_EDITOR_EDITABLE, InputFocusedContext), + order: CellToolbarOrder.SplitCell + }, + icon: { id: 'codicon/split-vertical' }, + f1: true + }); + } + + async run(accessor: ServicesAccessor, context?: INotebookCellActionContext) { + if (!isCellActionContext(context)) { + context = getActiveCellContext(accessor); + if (!context) { + return; + } + } + + return splitCell(context); + } +}); + + +async function joinCells(context: INotebookCellActionContext, direction: 'above' | 'below'): Promise { + const cell = await context.notebookEditor.joinNotebookCells(context.cell, direction, CellKind.Code); + if (cell) { + context.notebookEditor.focusNotebookCell(cell, true); + } +} + +registerAction2(class extends Action2 { + constructor() { + super( + { + id: JOIN_CELL_ABOVE_COMMAND_ID, + title: localize('notebookActions.joinCellAbove', "Join with Previous Cell"), + category: NOTEBOOK_ACTIONS_CATEGORY, + f1: true + }); + } + + async run(accessor: ServicesAccessor, context?: INotebookCellActionContext) { + if (!isCellActionContext(context)) { + context = getActiveCellContext(accessor); + if (!context) { + return; + } + } + + return joinCells(context, 'above'); + } +}); + +registerAction2(class extends Action2 { + constructor() { + super( + { + id: JOIN_CELL_BELOW_COMMAND_ID, + title: localize('notebookActions.joinCellBelow', "Join with Next Cell"), + category: NOTEBOOK_ACTIONS_CATEGORY, + f1: true + }); + } + + async run(accessor: ServicesAccessor, context?: INotebookCellActionContext) { + if (!isCellActionContext(context)) { + context = getActiveCellContext(accessor); + if (!context) { + return; + } + } + + return joinCells(context, 'below'); + } +}); + diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/format/formatting.ts b/src/vs/workbench/contrib/notebook/browser/contrib/format/formatting.ts index e294650322..4c3350df8b 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/format/formatting.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/format/formatting.ts @@ -3,38 +3,48 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { registerAction2, Action2 } from 'vs/platform/actions/common/actions'; +import { registerAction2, Action2, MenuId } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { localize } from 'vs/nls'; -import { NOTEBOOK_IS_ACTIVE_EDITOR } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_EDITOR_EDITABLE } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { getActiveNotebookEditor, NOTEBOOK_ACTIONS_CATEGORY } from 'vs/workbench/contrib/notebook/browser/contrib/coreActions'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { getDocumentFormattingEditsUntilResult } from 'vs/editor/contrib/format/format'; +import { getDocumentFormattingEditsUntilResult, formatDocumentWithSelectedProvider, FormattingMode } from 'vs/editor/contrib/format/format'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; import { WorkspaceTextEdit } from 'vs/editor/common/modes'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { registerEditorAction, EditorAction } from 'vs/editor/browser/editorExtensions'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { Progress } from 'vs/platform/progress/common/progress'; +// format notebook registerAction2(class extends Action2 { constructor() { super({ id: 'notebook.format', title: localize('format.title', 'Format Notebook'), category: NOTEBOOK_ACTIONS_CATEGORY, - precondition: ContextKeyExpr.and(NOTEBOOK_IS_ACTIVE_EDITOR), + precondition: ContextKeyExpr.and(NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_EDITOR_EDITABLE), keybinding: { when: EditorContextKeys.editorTextFocus.toNegated(), primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_F, linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_I }, weight: KeybindingWeight.WorkbenchContrib }, - f1: true + f1: true, + menu: { + id: MenuId.EditorContext, + when: ContextKeyExpr.and(EditorContextKeys.inCompositeEditor, EditorContextKeys.hasDocumentFormattingProvider), + group: '1_modification', + order: 1.3 + } }); } @@ -84,6 +94,34 @@ registerAction2(class extends Action2 { } finally { dispoables.dispose(); } - + } +}); + +// format cell +registerEditorAction(class FormatCellAction extends EditorAction { + constructor() { + super({ + id: 'notebook.formatCell', + label: localize('formatCell.label', "Format Cell"), + alias: 'Format Cell', + precondition: ContextKeyExpr.and(NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_EDITOR_EDITABLE, EditorContextKeys.inCompositeEditor, EditorContextKeys.writable, EditorContextKeys.hasDocumentFormattingProvider), + kbOpts: { + kbExpr: ContextKeyExpr.and(EditorContextKeys.editorTextFocus), + primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_F, + linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_I }, + weight: KeybindingWeight.EditorContrib + }, + contextMenuOpts: { + group: '1_modification', + order: 1.301 + } + }); + } + + async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise { + if (editor.hasModel()) { + const instaService = accessor.get(IInstantiationService); + await instaService.invokeFunction(formatDocumentWithSelectedProvider, editor, FormattingMode.Explicit, Progress.None, CancellationToken.None); + } } }); diff --git a/src/vs/workbench/contrib/notebook/browser/media/notebook.css b/src/vs/workbench/contrib/notebook/browser/media/notebook.css index fea99e3d0f..43c25648c3 100644 --- a/src/vs/workbench/contrib/notebook/browser/media/notebook.css +++ b/src/vs/workbench/contrib/notebook/browser/media/notebook.css @@ -22,7 +22,8 @@ } */ .monaco-workbench .part.editor > .content .notebook-editor .cell-list-container .overflowingContentWidgets > div { - z-index: 600 !important; /* @rebornix: larger than the editor title bar */ + z-index: 600 !important; + /* @rebornix: larger than the editor title bar */ } .monaco-workbench .part.editor > .content .notebook-editor .cell-list-container .monaco-list-rows { @@ -34,6 +35,10 @@ position: relative; } +.monaco-workbench .part.editor > .content .notebook-editor.global-drag-active .webview { + pointer-events: none; +} + .monaco-workbench .part.editor > .content .notebook-editor .cell-list-container .webview-cover { position: absolute; top: 0; @@ -74,7 +79,20 @@ } .monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.cell-drag-image .cell-editor-part { - width: 100%; + width: calc(100% - 32px); + /* minus left gutter */ +} + +.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.cell-drag-image .cell-editor-container > div > div { + /* Rendered code content - show a single unwrapped line */ + height: 20px; + overflow: hidden; + white-space: pre-wrap; +} + +.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.cell-drag-image.markdown-cell-row .cell.markdown { + white-space: nowrap; + overflow: hidden; } .monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell { @@ -184,7 +202,8 @@ position: absolute; height: 26px; right: 36px; - top: -14px; /* this lines up the bottom toolbar border with the current line when on line 01 */ + top: -14px; + /* this lines up the bottom toolbar border with the current line when on line 01 */ z-index: 30; } @@ -373,20 +392,28 @@ pointer-events: none; } -.monaco-workbench .part.editor > .content .notebook-editor .monaco-list .monaco-list-row.cell-dragover .notebook-cell-insertion-indicator-top { +.monaco-workbench .part.editor > .content .notebook-editor .monaco-list .monaco-list-row .cell-insertion-indicator-top { + top: -15px; +} + +.monaco-workbench .part.editor > .content .notebook-editor .monaco-list .monaco-list-row .cell-insertion-indicator-bottom { + bottom: 13px; +} + +.monaco-workbench .part.editor > .content .notebook-editor .monaco-list .monaco-list-row.cell-dragover-top .cell-insertion-indicator-top, +.monaco-workbench .part.editor > .content .notebook-editor .monaco-list .monaco-list-row.cell-dragover-bottom .cell-insertion-indicator-bottom { opacity: 1; } -.monaco-workbench .part.editor > .content .notebook-editor .monaco-list .monaco-list-row.cell-dragging { - opacity: 0.5; -} - -.monaco-workbench .part.editor > .content .notebook-editor .monaco-list .monaco-list-row .notebook-cell-insertion-indicator-top { +.monaco-workbench .part.editor > .content .notebook-editor .monaco-list .monaco-list-row .cell-insertion-indicator { opacity: 0; transition: opacity 0.2s ease-in-out; position: absolute; height: 2px; - top: -15px; +} + +.monaco-workbench .part.editor > .content .notebook-editor .monaco-list .monaco-list-row.cell-dragging { + opacity: 0.5; } .monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-bottom-toolbar-container { @@ -436,13 +463,6 @@ color: inherit; } -.notebook-webview { - position: absolute; - z-index: 1000000; - left: 373px; - top: 0px; -} - /* markdown */ @@ -593,14 +613,10 @@ .monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container .notebook-folding-indicator { position: absolute; top: 8px; - left: 26px; + left: 6px; cursor: pointer; } -.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.markdown-cell-row .cell-editor-part { - width: 100%; -} - /** Theming */ /* .monaco-workbench .part.editor > .content .notebook-editor .cell.markdown pre { diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index efcdddc6b4..0b657bf78c 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -9,7 +9,7 @@ import { parse } from 'vs/base/common/marshalling'; import { basename, isEqual } from 'vs/base/common/resources'; import { assertType } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; -import { ITextModel } from 'vs/editor/common/model'; +import { ITextModel, ITextBufferFactory, DefaultEndOfLine, ITextBuffer } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IModeService } from 'vs/editor/common/services/modeService'; import { ITextModelContentProvider, ITextModelService } from 'vs/editor/common/services/resolverService'; @@ -26,7 +26,8 @@ import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchCo import { EditorInput, Extensions as EditorInputExtensions, IEditorInput, IEditorInputFactory, IEditorInputFactoryRegistry } from 'vs/workbench/common/editor'; import { NotebookEditor, NotebookEditorOptions } from 'vs/workbench/contrib/notebook/browser/notebookEditor'; import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/browser/notebookEditorInput'; -import { INotebookService, NotebookService } from 'vs/workbench/contrib/notebook/browser/notebookService'; +import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; +import { NotebookService } from 'vs/workbench/contrib/notebook/browser/notebookServiceImpl'; import { CellKind, CellUri } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider'; import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -247,14 +248,25 @@ class CellContentProvider implements ITextModelContentProvider { if (!info) { return null; } - const notebook = await this._notebookService.resolveNotebook(info.id, data.notebook); - if (!notebook) { + + const editorModel = await this._notebookService.modelManager.get(data.notebook); + if (!editorModel) { return null; } - for (let cell of notebook.cells) { + + for (let cell of editorModel.notebook.cells) { if (cell.uri.toString() === resource.toString()) { - const bufferFactory = cell.resolveTextBufferFactory(); - const language = cell.cellKind === CellKind.Markdown ? this._modeService.create('markdown') : (cell.language ? this._modeService.create(cell.language) : this._modeService.createByFilepathOrFirstLine(resource, cell.source[0])); + const bufferFactory: ITextBufferFactory = { + create: (defaultEOL) => { + const newEOL = (defaultEOL === DefaultEndOfLine.CRLF ? '\r\n' : '\n'); + (cell.textBuffer as ITextBuffer).setEOL(newEOL); + return cell.textBuffer as ITextBuffer; + }, + getFirstLineText: (limit: number) => { + return cell.textBuffer.getLineContent(1).substr(0, limit); + } + }; + const language = cell.cellKind === CellKind.Markdown ? this._modeService.create('markdown') : (cell.language ? this._modeService.create(cell.language) : this._modeService.createByFilepathOrFirstLine(resource, cell.textBuffer.getLineContent(1))); return this._modelService.createModel( bufferFactory, language, diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index 7f7f661b23..72209e7926 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -13,17 +13,18 @@ import { Event } from 'vs/base/common/event'; import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { ScrollEvent } from 'vs/base/common/scrollable'; import { URI } from 'vs/base/common/uri'; -import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { BareFontInfo } from 'vs/editor/common/config/fontInfo'; import { Range } from 'vs/editor/common/core/range'; -import { FindMatch } from 'vs/editor/common/model'; -import { RawContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { IPosition } from 'vs/editor/common/core/position'; +import { ContextKeyExpr, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { FindMatch, IReadonlyTextBuffer, ITextModel } from 'vs/editor/common/model'; import { OutputRenderer } from 'vs/workbench/contrib/notebook/browser/view/output/outputRenderer'; +import { CellLanguageStatusBarItem } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer'; import { CellViewModel, IModelDecorationsChangeAccessor, NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import { CellKind, IOutput, IRenderOutput, NotebookCellMetadata, NotebookDocumentMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { Webview } from 'vs/workbench/contrib/webview/browser/webview'; -import { CellLanguageStatusBarItem } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer'; export const KEYBINDING_CONTEXT_NOTEBOOK_FIND_WIDGET_FOCUSED = new RawContextKey('notebookFindWidgetFocused', false); @@ -92,6 +93,7 @@ export interface MarkdownCellLayoutChangeEvent { export interface ICellViewModel { readonly model: NotebookCellTextModel; readonly id: string; + readonly textBuffer: IReadonlyTextBuffer; dragging: boolean; handle: number; uri: URI; @@ -102,9 +104,17 @@ export interface ICellViewModel { currentTokenSource: CancellationTokenSource | undefined; focusMode: CellFocusMode; getText(): string; - save(): void; metadata: NotebookCellMetadata | undefined; + textModel: ITextModel | undefined; + hasModel(): this is IEditableCellViewModel; + resolveTextModel(): Promise; getEvaluatedMetadata(documentMetadata: NotebookDocumentMetadata | undefined): NotebookCellMetadata; + getSelectionsStartPosition(): IPosition[] | undefined; + getLinesContent(): string[]; +} + +export interface IEditableCellViewModel extends ICellViewModel { + textModel: ITextModel; } export interface INotebookEditorMouseEvent { @@ -168,6 +178,16 @@ export interface INotebookEditor { */ insertNotebookCell(cell: ICellViewModel | undefined, type: CellKind, direction?: 'above' | 'below', initialText?: string, ui?: boolean): CellViewModel | null; + /** + * Split a given cell into multiple cells of the same type using the selection start positions. + */ + splitNotebookCell(cell: ICellViewModel): Promise; + + /** + * Joins the given cell either with the cell above or the one below depending on the given direction. + */ + joinNotebookCells(cell: ICellViewModel, direction: 'above' | 'below', constraint?: CellKind): Promise; + /** * Delete a cell from the notebook */ @@ -387,7 +407,6 @@ export interface BaseCellRenderTemplate { cellContainer: HTMLElement; toolbar: ToolBar; focusIndicator: HTMLElement; - insertionIndicatorTop: HTMLElement; disposables: DisposableStore; elementDisposables: DisposableStore; bottomCellContainer: HTMLElement; @@ -401,6 +420,7 @@ export interface MarkdownCellRenderTemplate extends BaseCellRenderTemplate { editorPart: HTMLElement; editorContainer: HTMLElement; foldingIndicator: HTMLElement; + currentEditor?: ICodeEditor; } export interface CodeCellRenderTemplate extends BaseCellRenderTemplate { @@ -410,7 +430,7 @@ export interface CodeCellRenderTemplate extends BaseCellRenderTemplate { runButtonContainer: HTMLElement; executionOrderLabel: HTMLElement; outputContainer: HTMLElement; - editor: CodeEditorWidget; + editor: ICodeEditor; progressBar: ProgressBar; } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts index c9f418f862..a13846a6a7 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts @@ -29,9 +29,10 @@ import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor'; import { EditorOptions, IEditorCloseEvent, IEditorMemento } from 'vs/workbench/common/editor'; import { CELL_MARGIN, CELL_RUN_GUTTER, EDITOR_TOP_MARGIN, EDITOR_TOP_PADDING, EDITOR_BOTTOM_PADDING } from 'vs/workbench/contrib/notebook/browser/constants'; -import { CellEditState, CellFocusMode, ICellRange, ICellViewModel, INotebookCellList, INotebookEditor, INotebookEditorMouseEvent, NotebookLayoutInfo, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK, NOTEBOOK_EDITOR_FOCUSED, INotebookEditorContribution, NOTEBOOK_EDITOR_RUNNABLE } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -import { NotebookEditorInput, NotebookEditorModel } from 'vs/workbench/contrib/notebook/browser/notebookEditorInput'; -import { INotebookService } from 'vs/workbench/contrib/notebook/browser/notebookService'; +import { CellEditState, CellFocusMode, ICellRange, ICellViewModel, INotebookCellList, INotebookEditor, INotebookEditorMouseEvent, NotebookLayoutInfo, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK, NOTEBOOK_EDITOR_FOCUSED, INotebookEditorContribution, NOTEBOOK_EDITOR_RUNNABLE, IEditableCellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/browser/notebookEditorInput'; +import { NotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookEditorModel'; +import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { NotebookCellList } from 'vs/workbench/contrib/notebook/browser/view/notebookCellList'; import { OutputRenderer } from 'vs/workbench/contrib/notebook/browser/view/output/outputRenderer'; import { BackLayerWebView } from 'vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView'; @@ -45,6 +46,7 @@ import { getExtraColor } from 'vs/workbench/contrib/welcome/walkThrough/common/w import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { NotebookEditorExtensionsRegistry } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions'; import { onUnexpectedError } from 'vs/base/common/errors'; +import { IPosition, Position } from 'vs/editor/common/core/position'; const $ = DOM.$; const NOTEBOOK_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'NotebookEditorViewState'; @@ -252,9 +254,9 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { private createCellList(): void { DOM.addClass(this.body, 'cell-list-container'); - const dndController = new CellDragAndDropController(this); + const dndController = this._register(new CellDragAndDropController(this)); const renders = [ - this.instantiationService.createInstance(CodeCellRenderer, this, this.contextKeyService, this.renderedEditors, dndController), + this.instantiationService.createInstance(CodeCellRenderer, this, this.renderedEditors, dndController), this.instantiationService.createInstance(MarkdownCellRenderer, this.contextKeyService, this, dndController, this.renderedEditors), ]; @@ -416,7 +418,7 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { await super.setInput(input, options, token); const model = await input.resolve(); - if (this.notebookViewModel === undefined || !this.notebookViewModel.equal(model) || this.webview === null) { + if (this.notebookViewModel === undefined || !this.notebookViewModel.equal(model.notebook) || this.webview === null) { this.detachModel(); await this.attachModel(input, model); } @@ -476,7 +478,7 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { await this.webview.waitForInitialization(); this.eventDispatcher = new NotebookEventDispatcher(); - this.viewModel = this.instantiationService.createInstance(NotebookViewModel, input.viewType!, model, this.eventDispatcher, this.getLayoutInfo()); + this.viewModel = this.instantiationService.createInstance(NotebookViewModel, input.viewType!, model.notebook, this.eventDispatcher, this.getLayoutInfo()); this.eventDispatcher.emit([new NotebookLayoutChangedEvent({ width: true, fontInfo: true }, this.getLayoutInfo())]); this.updateForMetadata(); @@ -636,8 +638,6 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { state.contributionsState = contributionsState; this.editorMemento.saveEditorState(this.group, input.resource, state); - - this.notebookViewModel.viewCells.forEach(cell => cell.save()); } } @@ -775,12 +775,166 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { (direction === 'above' ? index : nextIndex) : index; const newCell = this.notebookViewModel!.createCell(insertIndex, initialText.split(/\r?\n/g), language, type, true); + return newCell; + } - if (type === CellKind.Markdown) { - newCell.editState = CellEditState.Editing; + private isAtEOL(p: IPosition, lines: string[]) { + const line = lines[p.lineNumber - 1]; + return line.length + 1 === p.column; + } + + private pushIfAbsent(positions: IPosition[], p: IPosition) { + const last = positions.length > 0 ? positions[positions.length - 1] : undefined; + if (!last || last.lineNumber !== p.lineNumber || last.column !== p.column) { + positions.push(p); + } + } + + /** + * Add split point at the beginning and the end; + * Move end of line split points to the beginning of the next line; + * Avoid duplicate split points + */ + private splitPointsToBoundaries(splitPoints: IPosition[], lines: string[]): IPosition[] | null { + const boundaries: IPosition[] = []; + + // split points need to be sorted + splitPoints = splitPoints.sort((l, r) => { + const lineDiff = l.lineNumber - r.lineNumber; + const columnDiff = l.column - r.column; + return lineDiff !== 0 ? lineDiff : columnDiff; + }); + + // eat-up any split point at the beginning, i.e. we ignore the split point at the very beginning + this.pushIfAbsent(boundaries, new Position(1, 1)); + + for (let sp of splitPoints) { + if (this.isAtEOL(sp, lines) && sp.lineNumber < lines.length) { + sp = new Position(sp.lineNumber + 1, 1); + } + this.pushIfAbsent(boundaries, sp); } - return newCell; + // eat-up any split point at the beginning, i.e. we ignore the split point at the very end + this.pushIfAbsent(boundaries, new Position(lines.length, lines[lines.length - 1].length + 1)); + + // if we only have two then they describe the whole range and nothing needs to be split + return boundaries.length > 2 ? boundaries : null; + } + + private computeCellLinesContents(cell: IEditableCellViewModel, splitPoints: IPosition[]): string[] | null { + const lines = cell.getLinesContent(); + const rangeBoundaries = this.splitPointsToBoundaries(splitPoints, lines); + if (!rangeBoundaries) { + return null; + } + const newLineModels: string[] = []; + for (let i = 1; i < rangeBoundaries.length; i++) { + const start = rangeBoundaries[i - 1]; + const end = rangeBoundaries[i]; + + newLineModels.push(cell.textModel.getValueInRange(new Range(start.lineNumber, start.column, end.lineNumber, end.column))); + } + + return newLineModels; + } + + async splitNotebookCell(cell: ICellViewModel): Promise { + if (!this.notebookViewModel!.metadata.editable) { + return null; + } + + let splitPoints = cell.getSelectionsStartPosition(); + if (splitPoints && splitPoints.length > 0) { + await cell.resolveTextModel(); + + if (!cell.hasModel()) { + return null; + } + + let newLinesContents = this.computeCellLinesContents(cell, splitPoints); + if (newLinesContents) { + + // update the contents of the first cell + cell.textModel.applyEdits([ + { range: cell.textModel.getFullModelRange(), text: newLinesContents[0] } + ], true); + + // create new cells based on the new text models + const language = cell.model.language; + const kind = cell.cellKind; + let insertIndex = this.notebookViewModel!.getCellIndex(cell) + 1; + const newCells = []; + for (let j = 1; j < newLinesContents.length; j++, insertIndex++) { + newCells.push(this.notebookViewModel!.createCell(insertIndex, newLinesContents[j], language, kind, true)); + } + return newCells; + } + } + + return null; + } + + async joinNotebookCells(cell: ICellViewModel, direction: 'above' | 'below', constraint?: CellKind): Promise { + if (!this.notebookViewModel!.metadata.editable) { + return null; + } + + if (constraint && cell.cellKind !== constraint) { + return null; + } + + const index = this.notebookViewModel!.getCellIndex(cell); + if (index === 0 && direction === 'above') { + return null; + } + + if (index === this.notebookViewModel!.length - 1 && direction === 'below') { + return null; + } + + if (direction === 'above') { + const above = this.notebookViewModel!.viewCells[index - 1]; + if (constraint && above.cellKind !== constraint) { + return null; + } + + await above.resolveTextModel(); + if (!above.hasModel()) { + return null; + } + + const insertContent = cell.getText(); + const aboveCellLineCount = above.textModel.getLineCount(); + const aboveCellLastLineEndColumn = above.textModel.getLineLength(aboveCellLineCount); + above.textModel.applyEdits([ + { range: new Range(aboveCellLineCount, aboveCellLastLineEndColumn + 1, aboveCellLineCount, aboveCellLastLineEndColumn + 1), text: insertContent } + ]); + + await this.deleteNotebookCell(cell); + return above; + } else { + const below = this.notebookViewModel!.viewCells[index + 1]; + if (constraint && below.cellKind !== constraint) { + return null; + } + + await cell.resolveTextModel(); + if (!cell.hasModel()) { + return null; + } + + const insertContent = below.getText(); + + const cellLineCount = cell.textModel.getLineCount(); + const cellLastLineEndColumn = cell.textModel.getLineLength(cellLineCount); + cell.textModel.applyEdits([ + { range: new Range(cellLineCount, cellLastLineEndColumn + 1, cellLineCount, cellLastLineEndColumn + 1), text: insertContent } + ]); + + await this.deleteNotebookCell(below); + return cell; + } } async deleteNotebookCell(cell: ICellViewModel): Promise { @@ -788,7 +942,6 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { return false; } - (cell as CellViewModel).save(); const index = this.notebookViewModel!.getCellIndex(cell); this.notebookViewModel!.deleteCell(index, true); return true; @@ -1158,7 +1311,7 @@ registerThemingParticipant((theme, collector) => { if (focusedCellIndicatorColor) { collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .monaco-list-row.focused .notebook-cell-focus-indicator { border-color: ${focusedCellIndicatorColor}; }`); collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .monaco-list-row .notebook-cell-focus-indicator { border-color: ${focusedCellIndicatorColor}; }`); - collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .monaco-list-row .notebook-cell-insertion-indicator-top { background-color: ${focusedCellIndicatorColor}; }`); + collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .monaco-list-row .cell-insertion-indicator { background-color: ${focusedCellIndicatorColor}; }`); collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .monaco-list-row.cell-editor-focus .cell-editor-part:before { outline: solid 1px ${focusedCellIndicatorColor}; }`); } @@ -1178,7 +1331,7 @@ registerThemingParticipant((theme, collector) => { collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .markdown-cell-row .cell .cell-editor-part { margin-left: ${CELL_RUN_GUTTER}px; }`); collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row > div.cell.markdown { padding-left: ${CELL_RUN_GUTTER}px; }`); collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .cell .run-button-container { width: ${CELL_RUN_GUTTER}px; }`); - collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .monaco-list .monaco-list-row .notebook-cell-insertion-indicator-top { left: ${CELL_MARGIN + CELL_RUN_GUTTER}px; right: ${CELL_MARGIN}px; }`); + collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .monaco-list .monaco-list-row .cell-insertion-indicator { left: ${CELL_MARGIN + CELL_RUN_GUTTER}px; right: ${CELL_MARGIN}px; }`); collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .cell-drag-image .cell-editor-container > div { padding: ${EDITOR_TOP_PADDING}px 16px ${EDITOR_BOTTOM_PADDING}px 16px; }`); collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .monaco-list .monaco-list-row .notebook-cell-focus-indicator { left: ${CELL_MARGIN}px; }`); }); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts index 469948422e..8b1ac9e024 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts @@ -3,94 +3,13 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { EditorInput, EditorModel, IEditorInput, GroupIdentifier, ISaveOptions, IRevertOptions } from 'vs/workbench/common/editor'; -import { Emitter, Event } from 'vs/base/common/event'; -import { INotebookService } from 'vs/workbench/contrib/notebook/browser/notebookService'; -import { ICell, NotebookCellTextModelSplice } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { EditorInput, IEditorInput, GroupIdentifier, ISaveOptions } from 'vs/workbench/common/editor'; +import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { URI } from 'vs/base/common/uri'; -import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; -import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import { isEqual } from 'vs/base/common/resources'; -import { IWorkingCopyService, IWorkingCopy, WorkingCopyCapabilities, IWorkingCopyBackup } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IFilesConfigurationService, AutoSaveMode } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; - -export class NotebookEditorModel extends EditorModel { - private _dirty = false; - - protected readonly _onDidChangeDirty = this._register(new Emitter()); - readonly onDidChangeDirty = this._onDidChangeDirty.event; - - private readonly _onDidChangeCells = new Emitter(); - get onDidChangeCells(): Event { return this._onDidChangeCells.event; } - - private readonly _onDidChangeContent = this._register(new Emitter()); - readonly onDidChangeContent: Event = this._onDidChangeContent.event; - - - get notebook() { - return this._notebook; - } - - constructor( - private _notebook: NotebookTextModel - ) { - super(); - - if (_notebook && _notebook.onDidChangeCells) { - this._register(_notebook.onDidChangeContent(() => { - this._dirty = true; - this._onDidChangeDirty.fire(); - this._onDidChangeContent.fire(); - })); - this._register(_notebook.onDidChangeCells((e) => { - this._onDidChangeCells.fire(e); - })); - } - } - - isDirty() { - return this._dirty; - } - - getNotebook(): NotebookTextModel { - return this._notebook; - } - - insertCell(cell: ICell, index: number) { - let notebook = this.getNotebook(); - - if (notebook) { - this.notebook.insertNewCell(index, [cell as NotebookCellTextModel]); - this._dirty = true; - this._onDidChangeDirty.fire(); - - } - } - - deleteCell(index: number) { - let notebook = this.getNotebook(); - - if (notebook) { - this.notebook.removeCell(index); - } - } - - moveCellToIdx(index: number, newIdx: number) { - this.notebook.moveCellToIdx(index, newIdx); - } - - async save(): Promise { - if (this._notebook) { - this._dirty = false; - this._onDidChangeDirty.fire(); - // todo, flush all states - return true; - } - - return false; - } -} +import { NotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookEditorModel'; export class NotebookEditorInput extends EditorInput { @@ -113,36 +32,16 @@ export class NotebookEditorInput extends EditorInput { } static readonly ID: string = 'workbench.input.notebook'; - private promise: Promise | null = null; private textModel: NotebookEditorModel | null = null; - private readonly _onDidChangeContent = this._register(new Emitter()); - readonly onDidChangeContent: Event = this._onDidChangeContent.event; constructor( public resource: URI, public name: string, public readonly viewType: string | undefined, @INotebookService private readonly notebookService: INotebookService, - @IWorkingCopyService private readonly workingCopyService: IWorkingCopyService, @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService ) { super(); - - const input = this; - const workingCopyAdapter = new class implements IWorkingCopy { - readonly resource = input.resource.with({ scheme: 'vscode-notebook' }); - get name() { return input.getName(); } - readonly capabilities = input.isUntitled() ? WorkingCopyCapabilities.Untitled : 0; - readonly onDidChangeDirty = input.onDidChangeDirty; - readonly onDidChangeContent = input.onDidChangeContent; - isDirty(): boolean { return input.isDirty(); } - backup(): Promise { return input.backup(); } - save(options?: ISaveOptions): Promise { return input.save(0, options).then(editor => !!editor); } - revert(options?: IRevertOptions): Promise { return input.revert(0, options); } - }; - - this._register(this.workingCopyService.registerWorkingCopy(workingCopyAdapter)); - } getTypeId(): string { @@ -179,7 +78,6 @@ export class NotebookEditorInput extends EditorInput { async save(group: GroupIdentifier, options?: ISaveOptions): Promise { if (this.textModel) { - await this.notebookService.save(this.textModel.notebook.viewType, this.textModel.notebook.uri); await this.textModel.save(); return this; } @@ -187,38 +85,18 @@ export class NotebookEditorInput extends EditorInput { return undefined; } - async revert(group: GroupIdentifier, options?: IRevertOptions): Promise { - if (this.textModel) { - // TODO@rebornix we need hashing - await this.textModel.save(); - } - } - async resolve(): Promise { - if (this.textModel) { - return this.textModel; + if (!await this.notebookService.canResolve(this.viewType!)) { + throw new Error(`Cannot open notebook of type '${this.viewType}'`); } - if (!this.promise) { - if (!await this.notebookService.canResolve(this.viewType!)) { - throw new Error(`Cannot open notebook of type '${this.viewType}'`); - } + this.textModel = await this.notebookService.modelManager.resolve(this.resource, this.viewType!); - this.promise = this.notebookService.resolveNotebook(this.viewType!, this.resource).then(notebook => { - this.textModel = new NotebookEditorModel(notebook!); - this.textModel.onDidChangeDirty(() => this._onDidChangeDirty.fire()); - this.textModel.onDidChangeContent(() => { - this._onDidChangeContent.fire(); - }); - return this.textModel; - }); - } + this._register(this.textModel.onDidChangeDirty(() => { + this._onDidChangeDirty.fire(); + })); - return this.promise; - } - - async backup(): Promise { - throw new Error(); + return this.textModel; } matches(otherInput: unknown): boolean { @@ -235,6 +113,7 @@ export class NotebookEditorInput extends EditorInput { dispose() { if (this.textModel) { this.notebookService.destoryNotebookDocument(this.textModel!.notebook.viewType, this.textModel!.notebook); + this.textModel.dispose(); } super.dispose(); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookService.ts b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts similarity index 82% rename from src/vs/workbench/contrib/notebook/browser/notebookService.ts rename to src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts index 66f9c82bca..a4200d6e2f 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookService.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts @@ -5,7 +5,7 @@ import * as nls from 'vs/nls'; import { Disposable, IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { URI } from 'vs/base/common/uri'; import { notebookProviderExtensionPoint, notebookRendererExtensionPoint } from 'vs/workbench/contrib/notebook/browser/extensionPoint'; import { NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider'; @@ -19,46 +19,13 @@ import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/no import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { IEditorService, ICustomEditorViewTypesHandler, ICustomEditorInfo } from 'vs/workbench/services/editor/common/editorService'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; +import { NotebookEditorModelManager } from 'vs/workbench/contrib/notebook/common/notebookEditorModel'; +import { INotebookService, IMainNotebookController } from 'vs/workbench/contrib/notebook/common/notebookService'; function MODEL_ID(resource: URI): string { return resource.toString(); } -export const INotebookService = createDecorator('notebookService'); - -export interface IMainNotebookController { - resolveNotebook(viewType: string, uri: URI): Promise; - executeNotebook(viewType: string, uri: URI, token: CancellationToken): Promise; - onDidReceiveMessage(uri: URI, message: any): void; - executeNotebookCell(uri: URI, handle: number, token: CancellationToken): Promise; - destoryNotebookDocument(notebook: INotebookTextModel): Promise; - save(uri: URI): Promise; -} - -export interface INotebookService { - _serviceBrand: undefined; - canResolve(viewType: string): Promise; - onDidChangeActiveEditor: Event<{ viewType: string, uri: URI }>; - registerNotebookController(viewType: string, extensionData: NotebookExtensionDescription, controller: IMainNotebookController): void; - unregisterNotebookProvider(viewType: string): void; - registerNotebookRenderer(handle: number, extensionData: NotebookExtensionDescription, type: string, selectors: INotebookMimeTypeSelector, preloads: URI[]): void; - unregisterNotebookRenderer(handle: number): void; - getRendererInfo(handle: number): INotebookRendererInfo | undefined; - resolveNotebook(viewType: string, uri: URI): Promise; - executeNotebook(viewType: string, uri: URI): Promise; - executeNotebookCell(viewType: string, uri: URI, handle: number, token: CancellationToken): Promise; - - getContributedNotebookProviders(resource: URI): readonly NotebookProviderInfo[]; - getContributedNotebookProvider(viewType: string): NotebookProviderInfo | undefined; - getNotebookProviderResourceRoots(): URI[]; - destoryNotebookDocument(viewType: string, notebook: INotebookTextModel): void; - updateActiveNotebookDocument(viewType: string, resource: URI): void; - save(viewType: string, resource: URI): Promise; - onDidReceiveMessage(viewType: string, uri: URI, message: any): void; - setToCopy(items: NotebookCellTextModel[]): void; - getToCopy(): NotebookCellTextModel[] | undefined; -} - export class NotebookProviderInfoStore { private readonly contributedEditors = new Map(); @@ -126,8 +93,6 @@ class ModelData implements IDisposable { this._modelEventListeners.dispose(); } } - - export class NotebookService extends Disposable implements INotebookService, ICustomEditorViewTypesHandler { _serviceBrand: undefined; private readonly _notebookProviders = new Map(); @@ -142,13 +107,18 @@ export class NotebookService extends Disposable implements INotebookService, ICu onDidChangeViewTypes: Event = this._onDidChangeViewTypes.event; private cutItems: NotebookCellTextModel[] | undefined; + modelManager: NotebookEditorModelManager; + constructor( @IExtensionService private readonly extensionService: IExtensionService, - @IEditorService private readonly editorService: IEditorService + @IEditorService private readonly editorService: IEditorService, + @IInstantiationService private readonly instantiationService: IInstantiationService ) { super(); this._models = {}; + this.modelManager = this.instantiationService.createInstance(NotebookEditorModelManager); + notebookProviderExtensionPoint.setHandler((extensions) => { this.notebookProviderInfoStore.clear(); @@ -311,11 +281,11 @@ export class NotebookService extends Disposable implements INotebookService, ICu return this.cutItems; } - async save(viewType: string, resource: URI): Promise { + async save(viewType: string, resource: URI, token: CancellationToken): Promise { let provider = this._notebookProviders.get(viewType); if (provider) { - return provider.controller.save(resource); + return provider.controller.save(resource, token); } return false; diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts index 3e0b0f634d..a3a309c441 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts @@ -29,7 +29,7 @@ export class NotebookCellList extends WorkbenchList implements ID get rowsContainer(): HTMLElement { return this.view.containerDomNode; } - private _previousSelectedElements: CellViewModel[] = []; + private _previousFocusedElements: CellViewModel[] = []; private _localDisposableStore = new DisposableStore(); private _viewModelStore = new DisposableStore(); private styleElement?: HTMLStyleElement; @@ -58,14 +58,14 @@ export class NotebookCellList extends WorkbenchList implements ID ) { super(listUser, container, delegate, renderers, options, contextKeyService, listService, themeService, configurationService, keybindingService); - this._previousSelectedElements = this.getSelectedElements(); - this._localDisposableStore.add(this.onDidChangeSelection((e) => { - this._previousSelectedElements.forEach(element => { + this._previousFocusedElements = this.getFocusedElements(); + this._localDisposableStore.add(this.onDidChangeFocus((e) => { + this._previousFocusedElements.forEach(element => { if (e.elements.indexOf(element) < 0) { element.onDeselect(); } }); - this._previousSelectedElements = e.elements; + this._previousFocusedElements = e.elements; })); const notebookEditorCursorAtBoundaryContext = NOTEBOOK_EDITOR_CURSOR_BOUNDARY.bindTo(contextKeyService); @@ -93,7 +93,7 @@ export class NotebookCellList extends WorkbenchList implements ID }; // Cursor Boundary context - this._localDisposableStore.add(this.onDidChangeSelection((e) => { + this._localDisposableStore.add(this.onDidChangeFocus((e) => { if (e.elements.length) { cursorSelectionListener?.dispose(); textEditorAttachListener?.dispose(); @@ -479,7 +479,7 @@ export class NotebookCellList extends WorkbenchList implements ID return; } - const focused = this.getSelection(); + const focused = this.getFocus(); this.view.updateElementHeight(index, size, focused.length ? focused[0] : null); } @@ -527,7 +527,6 @@ export class NotebookCellList extends WorkbenchList implements ID } } - // TODO@rebornix TEST & Fix potential bugs // List items have real dynamic heights, which means after we set `scrollTop` based on the `elementTop(index)`, the element at `index` might still be removed from the view once all relayouting tasks are done. // For example, we scroll item 10 into the view upwards, in the first round, items 7, 8, 9, 10 are all in the viewport. Then item 7 and 8 resize themselves to be larger and finally item 10 is removed from the view. // To ensure that item 10 is always there, we need to scroll item 10 to the top edge of the viewport. diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts index 8d67cda6bc..fcc0d22d39 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -9,7 +9,7 @@ import * as path from 'vs/base/common/path'; import { URI } from 'vs/base/common/uri'; import * as UUID from 'vs/base/common/uuid'; import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -import { INotebookService } from 'vs/workbench/contrib/notebook/browser/notebookService'; +import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { IOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { IWebviewService, WebviewElement } from 'vs/workbench/contrib/webview/browser/webview'; import { WebviewResourceScheme } from 'vs/workbench/contrib/webview/common/resourceLoader'; diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts index 7dfdff735d..bc995c7653 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts @@ -50,6 +50,8 @@ import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewMod import { MarkdownCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel'; import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; import { CellKind, NotebookCellRunState } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; const $ = DOM.$; @@ -351,19 +353,19 @@ export class MarkdownCellRenderer extends AbstractCellRenderer implements IListR const codeInnerContent = DOM.append(container, $('.cell.code')); const editorPart = DOM.append(codeInnerContent, $('.cell-editor-part')); - const editorContainer = DOM.append(editorPart, $('.markdown-editor-container')); + const editorContainer = DOM.append(editorPart, $('.cell-editor-container')); editorPart.style.display = 'none'; const innerContent = DOM.append(container, $('.cell.markdown')); - const insertionIndicatorTop = DOM.append(container, DOM.$('.notebook-cell-insertion-indicator-top')); - const foldingIndicator = DOM.append(container, DOM.$('.notebook-folding-indicator')); + DOM.append(container, DOM.$('.cell-insertion-indicator.cell-insertion-indicator-top')); + DOM.append(container, DOM.$('.cell-insertion-indicator.cell-insertion-indicator-bottom')); + const foldingIndicator = DOM.append(focusIndicator, DOM.$('.notebook-folding-indicator')); const bottomCellContainer = DOM.append(container, $('.cell-bottom-toolbar-container')); const statusBar = this.instantiationService.createInstance(CellEditorStatusBar, editorPart); const templateData: MarkdownCellRenderTemplate = { - insertionIndicatorTop, container, cellContainer: innerContent, editorPart, @@ -383,16 +385,36 @@ export class MarkdownCellRenderer extends AbstractCellRenderer implements IListR } private getDragImage(templateData: MarkdownCellRenderTemplate): HTMLElement { + if (templateData.currentRenderedCell!.editState === CellEditState.Editing) { + return this._getEditDragImage(templateData); + } else { + return this._getMarkdownDragImage(templateData); + } + } + + private _getMarkdownDragImage(templateData: MarkdownCellRenderTemplate): HTMLElement { const dragImageContainer = DOM.$('.cell-drag-image.monaco-list-row.focused.markdown-cell-row'); dragImageContainer.innerHTML = templateData.container.innerHTML; + + // Remove all rendered content nodes after the + const markdownContent = dragImageContainer.querySelector('.cell.markdown')!; + const contentNodes = markdownContent.children[0].children; + for (let i = contentNodes.length - 1; i >= 1; i--) { + contentNodes.item(i)!.remove(); + } + return dragImageContainer; } + private _getEditDragImage(templateData: MarkdownCellRenderTemplate): HTMLElement { + return new CodeCellDragImageRenderer().getDragImage(templateData, templateData.currentEditor!, 'markdown'); + } renderElement(element: MarkdownCellViewModel, index: number, templateData: MarkdownCellRenderTemplate, height: number | undefined): void { this.commonRenderElement(element, index, templateData); templateData.currentRenderedCell = element; + templateData.currentEditor = undefined; templateData.editorPart!.style.display = 'none'; templateData.cellContainer.innerHTML = ''; let renderedHTML = element.getHTML(); @@ -416,7 +438,7 @@ export class MarkdownCellRenderer extends AbstractCellRenderer implements IListR this.setupBetweenCellToolbarActions(element, templateData, elementDisposables, toolbarContext); - const markdownCell = new StatefullMarkdownCell(this.notebookEditor, element, templateData, this.editorOptions.value, this.renderedEditors, this.instantiationService); + const markdownCell = this.instantiationService.createInstance(StatefullMarkdownCell, this.notebookEditor, element, templateData, this.editorOptions.value, this.renderedEditors); elementDisposables.add(this.editorOptions.onDidChange(newValue => markdownCell.updateEditorOptions(newValue))); elementDisposables.add(markdownCell); @@ -463,18 +485,34 @@ export class MarkdownCellRenderer extends AbstractCellRenderer implements IListR } const DRAGGING_CLASS = 'cell-dragging'; -const DRAGOVER_CLASS = 'cell-dragover'; +const DRAGOVER_TOP_CLASS = 'cell-dragover-top'; +const DRAGOVER_BOTTOM_CLASS = 'cell-dragover-bottom'; + +const GLOBAL_DRAG_CLASS = 'global-drag-active'; type DragImageProvider = () => HTMLElement; -export class CellDragAndDropController { - // TODO roblou - should probably use dataTransfer here, but any dataTransfer set makes the editor think I am dropping a file, need +export class CellDragAndDropController extends Disposable { + // TODO@roblourens - should probably use dataTransfer here, but any dataTransfer set makes the editor think I am dropping a file, need // to figure out how to prevent that private currentDraggedCell: ICellViewModel | undefined; constructor( private readonly notebookEditor: INotebookEditor - ) { } + ) { + super(); + + this._register(domEvent(document.body, DOM.EventType.DRAG_START, true)(this.onGlobalDragStart.bind(this))); + this._register(domEvent(document.body, DOM.EventType.DRAG_END, true)(this.onGlobalDragEnd.bind(this))); + } + + private onGlobalDragStart() { + this.notebookEditor.getDomNode().classList.add(GLOBAL_DRAG_CLASS); + } + + private onGlobalDragEnd() { + this.notebookEditor.getDomNode().classList.remove(GLOBAL_DRAG_CLASS); + } addListeners(templateData: BaseCellRenderTemplate, dragImageProvider: DragImageProvider): void { const container = templateData.container; @@ -488,17 +526,12 @@ export class CellDragAndDropController { }; templateData.disposables.add(domEvent(dragHandle, DOM.EventType.DRAG_END)(() => { - // TODO - (this.notebookEditor.getInnerWebview() as any)!.element.style['pointer-events'] = ''; - // Note, templateData may have a different element rendered into it by now container.classList.remove(DRAGGING_CLASS); dragCleanup(); })); templateData.disposables.add(domEvent(dragHandle, DOM.EventType.DRAG_START)(event => { - (this.notebookEditor.getInnerWebview() as any)!.element.style['pointer-events'] = 'none'; - if (!event.dataTransfer) { return; } @@ -516,6 +549,10 @@ export class CellDragAndDropController { templateData.disposables.add(domEvent(container, DOM.EventType.DRAG_OVER)(event => { event.preventDefault(); + + const location = this.getDropInsertDirection(templateData, event); + DOM.toggleClass(container, DRAGOVER_TOP_CLASS, location === 'above'); + DOM.toggleClass(container, DRAGOVER_BOTTOM_CLASS, location === 'below'); })); templateData.disposables.add(domEvent(container, DOM.EventType.DROP)(event => { @@ -523,27 +560,69 @@ export class CellDragAndDropController { const draggedCell = this.currentDraggedCell!; dragCleanup(); - this.notebookEditor.moveCell(draggedCell, templateData.currentRenderedCell!, 'above'); - container.classList.remove(DRAGOVER_CLASS); - })); - templateData.disposables.add(domEvent(container, DOM.EventType.DRAG_ENTER)(event => { - event.preventDefault(); - container.classList.add(DRAGOVER_CLASS); + const isCopy = (event.ctrlKey && !platform.isMacintosh) || (event.altKey && platform.isMacintosh); + + const direction = this.getDropInsertDirection(templateData, event); + if (direction) { + const dropTarget = templateData.currentRenderedCell!; + if (isCopy) { + this.copyCell(draggedCell, dropTarget, direction); + } else { + this.moveCell(draggedCell, dropTarget, direction); + } + } + + container.classList.remove(DRAGOVER_TOP_CLASS, DRAGOVER_BOTTOM_CLASS); })); templateData.disposables.add(domEvent(container, DOM.EventType.DRAG_LEAVE)(event => { if (!event.relatedTarget || !DOM.isAncestor(event.relatedTarget as HTMLElement, container)) { - container.classList.remove(DRAGOVER_CLASS); + container.classList.remove(DRAGOVER_TOP_CLASS, DRAGOVER_BOTTOM_CLASS); } })); } + + private moveCell(draggedCell: ICellViewModel, ontoCell: ICellViewModel, direction: 'above' | 'below') { + const editState = draggedCell.editState; + this.notebookEditor.moveCell(draggedCell, ontoCell, direction); + this.notebookEditor.focusNotebookCell(draggedCell, editState === CellEditState.Editing); + } + + private copyCell(draggedCell: ICellViewModel, ontoCell: ICellViewModel, direction: 'above' | 'below') { + const editState = draggedCell.editState; + const newCell = this.notebookEditor.insertNotebookCell(ontoCell, draggedCell.cellKind, direction, draggedCell.getText()); + if (newCell) { + this.notebookEditor.focusNotebookCell(newCell, editState === CellEditState.Editing); + } + } + + private getDropInsertDirection(templateData: BaseCellRenderTemplate, event: DragEvent): 'above' | 'below' | undefined { + if (templateData.currentRenderedCell === this.currentDraggedCell) { + return undefined; // {{SQL CARBON EDIT}} strict-null-checks + } + + const dragOffset = this.getDragOffset(templateData.container, event); + if (dragOffset < 0.3) { + return 'above'; + } else if (dragOffset >= 0.5) { + return 'below'; + } else { + return undefined; // {{SQL CARBON EDIT}} strict-null-checks + } + } + + private getDragOffset(container: HTMLElement, event: DragEvent): number { + const containerRect = container.getBoundingClientRect(); + const dragoverContainerY = event.clientY - containerRect.top; + return dragoverContainerY / containerRect.height; + } } export class CellLanguageStatusBarItem extends Disposable { private labelElement: HTMLElement; - private _cell: BaseCellViewModel | undefined; + private _cell: ICellViewModel | undefined; private _editor: INotebookEditor | undefined; private cellDisposables: DisposableStore; @@ -565,7 +644,7 @@ export class CellLanguageStatusBarItem extends Disposable { this._register(this.cellDisposables = new DisposableStore()); } - update(cell: BaseCellViewModel, editor: INotebookEditor): void { + update(cell: ICellViewModel, editor: INotebookEditor): void { this.cellDisposables.clear(); this._cell = cell; this._editor = editor; @@ -643,10 +722,10 @@ class EditorTextRenderer { } class CodeCellDragImageRenderer { - getDragImage(templateData: CodeCellRenderTemplate): HTMLElement { - let dragImage = this._getDragImage(templateData); + getDragImage(templateData: BaseCellRenderTemplate, editor: ICodeEditor, type: 'code' | 'markdown'): HTMLElement { + let dragImage = this._getDragImage(templateData, editor, type); if (!dragImage) { - // TODO I don't think this can happen + // TODO@roblourens I don't think this can happen dragImage = document.createElement('div'); dragImage.textContent = '1 cell'; } @@ -654,8 +733,8 @@ class CodeCellDragImageRenderer { return dragImage; } - private _getDragImage(templateData: CodeCellRenderTemplate): HTMLElement | null { - const dragImageContainer = DOM.$('.cell-drag-image.monaco-list-row.focused.code-cell-row'); + private _getDragImage(templateData: BaseCellRenderTemplate, editor: ICodeEditor, type: 'code' | 'markdown'): HTMLElement | null { + const dragImageContainer = DOM.$(`.cell-drag-image.monaco-list-row.focused.${type}-cell-row`); dragImageContainer.innerHTML = templateData.container.innerHTML; const editorContainer = dragImageContainer.querySelector('.cell-editor-container'); @@ -668,7 +747,7 @@ class CodeCellDragImageRenderer { focusIndicator.style.height = '40px'; } - const richEditorText = new EditorTextRenderer().getRichText(templateData.editor, new Range(1, 1, 1, 1000)); + const richEditorText = new EditorTextRenderer().getRichText(editor, new Range(1, 1, 1, 1000)); if (!richEditorText) { return null; } @@ -703,9 +782,9 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende constructor( protected notebookEditor: INotebookEditor, - protected contextKeyService: IContextKeyService, private renderedEditors: Map, dndController: CellDragAndDropController, + @IContextKeyService protected contextKeyService: IContextKeyService, @IContextMenuService contextMenuService: IContextMenuService, @IConfigurationService configurationService: IConfigurationService, @IInstantiationService instantiationService: IInstantiationService, @@ -734,9 +813,14 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende const executionOrderLabel = DOM.append(runButtonContainer, $('div.execution-count-label')); + // create a special context key service that set the inCompositeEditor-contextkey + const editorContextKeyService = this.contextKeyService.createScoped(); + const editorInstaService = this.instantiationService.createChild(new ServiceCollection([IContextKeyService, editorContextKeyService])); + EditorContextKeys.inCompositeEditor.bindTo(editorContextKeyService).set(true); + const editorPart = DOM.append(cellContainer, $('.cell-editor-part')); const editorContainer = DOM.append(editorPart, $('.cell-editor-container')); - const editor = this.instantiationService.createInstance(CodeEditorWidget, editorContainer, { + const editor = editorInstaService.createInstance(CodeEditorWidget, editorContainer, { ...this.editorOptions.value, dimension: { width: 0, @@ -752,12 +836,11 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende const statusBar = this.instantiationService.createInstance(CellEditorStatusBar, editorPart); - const insertionIndicatorTop = DOM.append(container, DOM.$('.notebook-cell-insertion-indicator-top')); + DOM.append(container, DOM.$('.cell-insertion-indicator.cell-insertion-indicator-top')); const outputContainer = DOM.append(container, $('.output')); const bottomCellContainer = DOM.append(container, $('.cell-bottom-toolbar-container')); const templateData: CodeCellRenderTemplate = { - insertionIndicatorTop, container, cellContainer, statusBarContainer: statusBar.statusBarContainer, @@ -778,7 +861,7 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende toJSON: () => { return {}; } }; - this.dndController.addListeners(templateData, () => new CodeCellDragImageRenderer().getDragImage(templateData)); + this.dndController.addListeners(templateData, () => new CodeCellDragImageRenderer().getDragImage(templateData, templateData.editor, 'code')); return templateData; } @@ -812,7 +895,7 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende } else if (metadata.runState === NotebookCellRunState.Error) { templateData.cellRunStatusContainer.innerHTML = renderCodicons('$(error)'); } else if (metadata.runState === NotebookCellRunState.Running) { - // TODO should extensions be able to customize the status message while running to show progress? + // TODO@roblourens should extensions be able to customize the status message while running to show progress? templateData.cellStatusMessageContainer.textContent = nls.localize('cellRunningStatus', "Running"); templateData.cellRunStatusContainer.innerHTML = renderCodicons('$(sync~spin)'); } else { diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts index dacea99d0d..6261cc68d7 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts @@ -12,7 +12,7 @@ import * as nls from 'vs/nls'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { EDITOR_BOTTOM_PADDING, EDITOR_TOP_PADDING } from 'vs/workbench/contrib/notebook/browser/constants'; import { CellFocusMode, CodeCellRenderTemplate, INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -import { INotebookService } from 'vs/workbench/contrib/notebook/browser/notebookService'; +import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { getResizesObserver } from 'vs/workbench/contrib/notebook/browser/view/renderers/sizeObserver'; import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel'; import { CellOutputKind, IOutput, IRenderOutput, ITransformedDisplayOutputDto } from 'vs/workbench/contrib/notebook/common/notebookCommon'; @@ -220,7 +220,7 @@ export class CodeCell extends Disposable { this.templateData.outputContainer!.style.display = 'block'; // there are outputs, we need to calcualte their sizes and trigger relayout - // @todo, if there is no resizable output, we should not check their height individually, which hurts the performance + // @TODO@rebornix, if there is no resizable output, we should not check their height individually, which hurts the performance for (let index = 0; index < this.viewCell.outputs.length; index++) { const currOutput = this.viewCell.outputs[index]; @@ -379,7 +379,7 @@ export class CodeCell extends Disposable { } else { // static output - // @TODO, if we stop checking output height, we need to evaluate it later when checking the height of output container + // @TODO@rebornix, if we stop checking output height, we need to evaluate it later when checking the height of output container let clientHeight = outputItemDiv.clientHeight; this.viewCell.updateOutputHeight(index, clientHeight); } diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts index 6ca83f53e0..04f8faf627 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts @@ -18,6 +18,9 @@ import { getResizesObserver } from 'vs/workbench/contrib/notebook/browser/view/r import { CellFoldingState } from 'vs/workbench/contrib/notebook/browser/contrib/fold/foldingModel'; import { MarkdownCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; export class StatefullMarkdownCell extends Disposable { @@ -35,7 +38,8 @@ export class StatefullMarkdownCell extends Disposable { private templateData: MarkdownCellRenderTemplate, editorOptions: IEditorOptions, renderedEditors: Map, - instantiationService: IInstantiationService + @IContextKeyService contextKeyService: IContextKeyService, + @IInstantiationService instantiationService: IInstantiationService, ) { super(); @@ -70,13 +74,20 @@ export class StatefullMarkdownCell extends Disposable { editorHeight = Math.max(lineNum, 1) * lineHeight + EDITOR_TOP_PADDING + EDITOR_BOTTOM_PADDING; this.templateData.editorContainer.innerHTML = ''; - this.editor = instantiationService.createInstance(CodeEditorWidget, this.templateData.editorContainer, { + + // create a special context key service that set the inCompositeEditor-contextkey + const editorContextKeyService = contextKeyService.createScoped(); + const editorInstaService = instantiationService.createChild(new ServiceCollection([IContextKeyService, editorContextKeyService])); + EditorContextKeys.inCompositeEditor.bindTo(editorContextKeyService).set(true); + + this.editor = editorInstaService.createInstance(CodeEditorWidget, this.templateData.editorContainer, { ...this.editorOptions, dimension: { width: width, height: editorHeight } }, {}); + templateData.currentEditor = this.editor; const cts = new CancellationTokenSource(); this._register({ dispose() { cts.dispose(true); } }); @@ -98,6 +109,7 @@ export class StatefullMarkdownCell extends Disposable { height: realContentHeight } ); + editorHeight = realContentHeight; } viewCell.attachTextEditor(this.editor!); @@ -110,6 +122,12 @@ export class StatefullMarkdownCell extends Disposable { width: width, height: editorHeight }); + + + const clientHeight = this.markdownContainer.clientHeight; + const totalHeight = editorHeight + 32 + clientHeight + CELL_STATUSBAR_HEIGHT; + this.viewCell.totalHeight = totalHeight; + notebookEditor.layoutNotebookCell(viewCell, totalHeight); }); } @@ -231,7 +249,9 @@ export class StatefullMarkdownCell extends Disposable { bindEditorListeners(model: ITextModel, dimension?: IDimension) { this.localDisposables.add(model.onDidChangeContent(() => { - this.viewCell.setText(model.getLinesContent()); + // we don't need to update view cell text anymore as the textbuffer is shared + // this.viewCell.setText(model.getLinesContent()); + this.viewCell.clearHTML(); let clientHeight = this.markdownContainer.clientHeight; this.markdownContainer.innerHTML = ''; let renderedHTML = this.viewCell.getHTML(); diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts index b7a5fb20a3..23bc15e9f8 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts @@ -8,11 +8,12 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { Range } from 'vs/editor/common/core/range'; +import { IPosition } from 'vs/editor/common/core/position'; import * as editorCommon from 'vs/editor/common/editorCommon'; import * as model from 'vs/editor/common/model'; import { SearchParams } from 'vs/editor/common/model/textModelSearch'; import { EDITOR_TOOLBAR_HEIGHT, EDITOR_TOP_MARGIN } from 'vs/workbench/contrib/notebook/browser/constants'; -import { CellEditState, CellFocusMode, CellRunState, CursorAtBoundary, ICellViewModel, CellViewModelStateChangeEvent } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellEditState, CellFocusMode, CellRunState, CursorAtBoundary, CellViewModelStateChangeEvent, IEditableCellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellKind, NotebookCellMetadata, NotebookDocumentMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; @@ -21,7 +22,7 @@ export const NotebookCellMetadataDefaults = { runnable: true }; -export abstract class BaseCellViewModel extends Disposable implements ICellViewModel { +export abstract class BaseCellViewModel extends Disposable { protected readonly _onDidChangeEditorAttachState = new Emitter(); // Do not merge this event with `onDidChangeState` as we are using `Event.once(onDidChangeEditorAttachState)` elsewhere. readonly onDidChangeEditorAttachState = this._onDidChangeEditorAttachState.event; @@ -35,7 +36,7 @@ export abstract class BaseCellViewModel extends Disposable implements ICellViewM return this.model.uri; } get lineCount() { - return this.model.source.length; + return this.model.textBuffer.getLineCount(); } get metadata() { return this.model.metadata; @@ -64,7 +65,7 @@ export abstract class BaseCellViewModel extends Disposable implements ICellViewM } } - // TODO - move any "run"/"status" concept to Code-specific places + // TODO@roblourens - move any "run"/"status" concept to Code-specific places private _currentTokenSource: CancellationTokenSource | undefined; public set currentTokenSource(v: CancellationTokenSource | undefined) { this._currentTokenSource = v; @@ -105,6 +106,14 @@ export abstract class BaseCellViewModel extends Disposable implements ICellViewM private _lastDecorationId: number = 0; protected _textModel?: model.ITextModel; + get textModel(): model.ITextModel | undefined { + return this._textModel; + } + + hasModel(): this is IEditableCellViewModel { + return !!this._textModel; + } + private _dragging: boolean = false; get dragging(): boolean { return this._dragging; @@ -126,6 +135,7 @@ export abstract class BaseCellViewModel extends Disposable implements ICellViewM })); } + // abstract resolveTextModel(): Promise; abstract hasDynamicHeight(): boolean; abstract getHeight(lineHeight: number): number; abstract onDeselect(): void; @@ -192,14 +202,27 @@ export abstract class BaseCellViewModel extends Disposable implements ICellViewM } getText(): string { - if (this._textModel) { - return this._textModel.getValue(); - } - - return this.model.source.join('\n'); + return this.model.getValue(); } - abstract save(): void; + getLinesContent(): string[] { + if (this._textModel) { + return this._textModel.getLinesContent(); + } + + return this.model.textBuffer.getLinesContent(); + } + + // setLinesContent(value: string[]) { + // if (this._textModel) { + // // TODO @rebornix we should avoid creating a new string here + // return this._textModel.setValue(value.join('\n')); + // } else { + // const range = this.model.getFullModelRange(); + // this.model.textBuffer. + // this.model.source = value; + // } + // } private saveViewState(): void { if (!this._textEditor) { @@ -271,6 +294,16 @@ export abstract class BaseCellViewModel extends Disposable implements ICellViewM this._textEditor?.setSelection(range); } + getSelectionsStartPosition(): IPosition[] | undefined { + if (this._textEditor) { + const selections = this._textEditor.getSelections(); + return selections?.map(s => s.getStartPosition()); + } else { + const selections = this._editorViewStates?.cursorState; + return selections?.map(s => s.selectionStart); + } + } + getLineScrollTopOffset(line: number): number { if (!this._textEditor) { return 0; @@ -292,27 +325,28 @@ export abstract class BaseCellViewModel extends Disposable implements ICellViewM return CursorAtBoundary.None; } - // we don't allow attaching text editor without a model - const lineCnt = this._textEditor.getModel()!.getLineCount(); + const firstViewLineTop = this._textEditor.getTopForPosition(1, 1); + const lastViewLineTop = this._textEditor.getTopForPosition(this._textModel!.getLineCount(), this._textModel!.getLineLength(this._textModel!.getLineCount())); + const selectionTop = this._textEditor.getTopForPosition(selection.startLineNumber, selection.startColumn); - if (selection.startLineNumber === lineCnt) { - // bottom - if (selection.startLineNumber === 1) { + if (selectionTop === lastViewLineTop) { + if (selectionTop === firstViewLineTop) { return CursorAtBoundary.Both; - } - else { + } else { return CursorAtBoundary.Bottom; } + } else { + if (selectionTop === firstViewLineTop) { + return CursorAtBoundary.Top; + } else { + return CursorAtBoundary.None; + } } - - if (selection.startLineNumber === 1) { - return CursorAtBoundary.Top; - } - - return CursorAtBoundary.None; } - protected _buffer: model.ITextBuffer | null = null; + get textBuffer() { + return this.model.textBuffer; + } protected cellStartFind(value: string): model.FindMatch[] | null { let cellMatches: model.FindMatch[] = []; @@ -320,12 +354,8 @@ export abstract class BaseCellViewModel extends Disposable implements ICellViewM if (this.assertTextModelAttached()) { cellMatches = this._textModel!.findMatches(value, false, false, false, null, false); } else { - if (!this._buffer) { - this._buffer = this.model.resolveTextBufferFactory().create(model.DefaultEndOfLine.LF); - } - - const lineCount = this._buffer.getLineCount(); - const fullRange = new Range(1, 1, lineCount, this._buffer.getLineLength(lineCount) + 1); + const lineCount = this.textBuffer.getLineCount(); + const fullRange = new Range(1, 1, lineCount, this.textBuffer.getLineLength(lineCount) + 1); const searchParams = new SearchParams(value, false, false, null); const searchData = searchParams.parseSearchRequest(); @@ -333,7 +363,7 @@ export abstract class BaseCellViewModel extends Disposable implements ICellViewM return null; } - cellMatches = this._buffer.findMatchesLineByLine(fullRange, searchData, false, 1000); + cellMatches = this.textBuffer.findMatchesLineByLine(fullRange, searchData, false, 1000); } return cellMatches; diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/cellEdit.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/cellEdit.ts index d3629e7b4d..abcd5161e1 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/cellEdit.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/cellEdit.ts @@ -67,7 +67,8 @@ export class DeleteCellEdit implements IResourceUndoRedoElement { this._rawCell = cell.model; // save inmem text to `ICell` - this._rawCell.source = [cell.getText()]; + // no needed any more as the text buffer is transfered to `raw_cell` + // this._rawCell.source = [cell.getText()]; } undo(): void | Promise { diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts index 4b6133296b..5cfddf493a 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts @@ -81,7 +81,6 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod })); this._outputCollection = new Array(this.model.outputs.length); - this._buffer = null; this._layoutInfo = { fontInfo: initialNotebookLayoutInfo?.fontInfo || null, @@ -171,21 +170,16 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod } } - save() { - if (this._textModel && !this._textModel.isDisposed() && this.editState === CellEditState.Editing) { - this.model.source = this._textModel.getLinesContent(); - } - } - + /** + * Text model is used for editing. + */ async resolveTextModel(): Promise { if (!this._textModel) { const ref = await this._modelService.createModelReference(this.model.uri); this._textModel = ref.object.textEditorModel; - this._buffer = this._textModel.getTextBuffer(); this._register(ref); this._register(this._textModel.onDidChangeContent(() => { this.editState = CellEditState.Editing; - this.model.contentChange(); this._onDidChangeState.fire({ contentChanged: true }); })); } diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel.ts index d1e441133c..266378f564 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel.ts @@ -112,18 +112,10 @@ export class MarkdownCellViewModel extends BaseCellViewModel implements ICellVie } } - setText(strs: string[]) { - this.model.source = strs; + clearHTML() { this._html = null; } - save() { - if (this._textModel && !this._textModel.isDisposed() && this.editState === CellEditState.Editing) { - let cnt = this._textModel.getLineCount(); - this.model.source = this._textModel.getLinesContent().map((str, index) => str + (index !== cnt - 1 ? '\n' : '')); - } - } - getHTML(): HTMLElement | null { if (this.cellKind === CellKind.Markdown) { if (this._html) { @@ -140,10 +132,8 @@ export class MarkdownCellViewModel extends BaseCellViewModel implements ICellVie if (!this._textModel) { const ref = await this._modelService.createModelReference(this.model.uri); this._textModel = ref.object.textEditorModel; - this._buffer = this._textModel.getTextBuffer(); this._register(ref); this._register(this._textModel.onDidChangeContent(() => { - this.model.contentChange(); this._html = null; this._onDidChangeState.fire({ contentChanged: true }); })); diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts index fb288192e4..3246891787 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts @@ -19,7 +19,6 @@ import { WorkspaceTextEdit } from 'vs/editor/common/modes'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; import { CellEditState, CellFindMatch, ICellRange, ICellViewModel, NotebookLayoutInfo } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -import { NotebookEditorModel } from 'vs/workbench/contrib/notebook/browser/notebookEditorInput'; import { DeleteCellEdit, InsertCellEdit, MoveCellEdit, SpliceCellsEdit } from 'vs/workbench/contrib/notebook/browser/viewModel/cellEdit'; import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel'; import { NotebookEventDispatcher, NotebookMetadataChangedEvent } from 'vs/workbench/contrib/notebook/browser/viewModel/eventDispatcher'; @@ -28,6 +27,7 @@ import { MarkdownCellViewModel } from 'vs/workbench/contrib/notebook/browser/vie import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { FoldingRegions } from 'vs/editor/contrib/folding/foldingRanges'; +import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; export interface INotebookEditorViewState { editingCells: { [key: number]: boolean }; @@ -172,27 +172,27 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD } get notebookDocument() { - return this._model.notebook; + return this._notebook; } get renderers() { - return this._model.notebook!.renderers; + return this._notebook!.renderers; } get handle() { - return this._model.notebook.handle; + return this._notebook.handle; } get languages() { - return this._model.notebook.languages; + return this._notebook.languages; } get uri() { - return this._model.notebook.uri; + return this._notebook.uri; } get metadata() { - return this._model.notebook.metadata; + return this._notebook.metadata; } private readonly _onDidChangeViewCells = new Emitter(); @@ -227,7 +227,7 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD } this._selections = selections; - this._model.notebook.selections = selections; + this._notebook.selections = selections; this._onDidChangeSelection.fire(); } @@ -241,7 +241,7 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD constructor( public viewType: string, - private _model: NotebookEditorModel, + private _notebook: NotebookTextModel, readonly eventDispatcher: NotebookEventDispatcher, private _layoutInfo: NotebookLayoutInfo | null, @IInstantiationService private readonly instantiationService: IInstantiationService, @@ -254,7 +254,7 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD this.id = '$notebookViewModel' + MODEL_ID; this._instanceId = strings.singleLetterHash(MODEL_ID); - this._register(this._model.onDidChangeCells(e => { + this._register(this._notebook.onDidChangeCells(e => { const diffs = e.map(splice => { return [splice[0], splice[1], splice[2].map(cell => { return createCellViewModel(this.instantiationService, this, cell as NotebookCellTextModel); @@ -315,7 +315,7 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD this.selectionHandles = endSelectionHandles; })); - this._register(this._model.notebook.onDidChangeMetadata(e => { + this._register(this._notebook.onDidChangeMetadata(e => { this.eventDispatcher.emit([new NotebookMetadataChangedEvent(e)]); })); @@ -335,7 +335,7 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD }); })); - this._viewCells = this._model!.notebook!.cells.map(cell => { + this._viewCells = this._notebook!.cells.map(cell => { return createCellViewModel(this.instantiationService, this, cell); }); @@ -419,10 +419,6 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD return this._hiddenRanges; } - isDirty() { - return this._model.isDirty(); - } - hide() { this._viewCells.forEach(cell => { if (cell.getText() !== '') { @@ -465,7 +461,7 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD } getVersionId() { - return this._model.notebook.versionId; + return this._notebook.versionId; } getTrackedRange(id: string): ICellRange | null { @@ -580,7 +576,7 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD private _insertCellDelegate(insertIndex: number, insertCell: CellViewModel) { this._viewCells!.splice(insertIndex, 0, insertCell); this._handleToViewCellMapping.set(insertCell.handle, insertCell); - this._model.insertCell(insertCell.model, insertIndex); + this._notebook.insertNewCell(insertIndex, [insertCell.model as NotebookCellTextModel]); this._localStore.add(insertCell); this._onDidChangeViewCells.fire({ synchronous: true, splices: [[insertIndex, 0, [insertCell]]] }); } @@ -590,7 +586,7 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD this._viewCells.splice(deleteIndex, 1); this._handleToViewCellMapping.delete(deleteCell.handle); - this._model.deleteCell(deleteIndex); + this._notebook.removeCell(deleteIndex); this._onDidChangeViewCells.fire({ synchronous: true, splices: [[deleteIndex, 1, []]] }); } @@ -598,12 +594,12 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD this.selectionHandles = selections; } - createCell(index: number, source: string[], language: string, type: CellKind, synchronous: boolean) { - const cell = this._model.notebook.createCellTextModel(source, language, type, [], undefined); + createCell(index: number, source: string | string[], language: string, type: CellKind, synchronous: boolean) { + const cell = this._notebook.createCellTextModel(source, language, type, [], undefined); let newCell: CellViewModel = createCellViewModel(this.instantiationService, this, cell); this._viewCells!.splice(index, 0, newCell); this._handleToViewCellMapping.set(newCell.handle, newCell); - this._model.insertCell(cell, index); + this._notebook.insertNewCell(index, [cell]); this._localStore.add(newCell); this.undoService.pushElement(new InsertCellEdit(this.uri, index, newCell, { @@ -622,7 +618,7 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD this._viewCells!.splice(index, 0, newCell); this._handleToViewCellMapping.set(newCell.handle, newCell); - this._model.insertCell(newCell.model, index); + this._notebook.insertNewCell(index, [newCell.model]); this._localStore.add(newCell); this.undoService.pushElement(new InsertCellEdit(this.uri, index, newCell, { insertCell: this._insertCellDelegate.bind(this), @@ -642,7 +638,7 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD this._viewCells.splice(index, 1); this._handleToViewCellMapping.delete(viewCell.handle); - this._model.deleteCell(index); + this._notebook.removeCell(index); let endSelections: number[] = []; if (this.selectionHandles.length) { @@ -686,7 +682,7 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD this.viewCells.splice(index, 1); this.viewCells!.splice(newIdx, 0, viewCell); - this._model.moveCellToIdx(index, newIdx); + this._notebook.moveCellToIdx(index, newIdx); if (pushedToUndoStack) { this.undoService.pushElement(new MoveCellEdit(this.uri, index, newIdx, { @@ -869,14 +865,13 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD this.undoService.redo(this.uri); } - equal(model: NotebookEditorModel) { - return this._model === model; + equal(notebook: NotebookTextModel) { + return this._notebook === notebook; } dispose() { this._localStore.clear(); this._viewCells.forEach(cell => { - cell.save(); cell.dispose(); }); diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts index 82d073468c..a0e5b13c35 100644 --- a/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts +++ b/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts @@ -5,10 +5,13 @@ import { Emitter, Event } from 'vs/base/common/event'; import { ICell, IOutput, NotebookCellOutputsSplice, CellKind, NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { PieceTreeTextBufferFactory, PieceTreeTextBufferBuilder } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBufferBuilder'; +import { PieceTreeTextBufferBuilder } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBufferBuilder'; import { URI } from 'vs/base/common/uri'; +import * as model from 'vs/editor/common/model'; +import { Range } from 'vs/editor/common/core/range'; +import { Disposable } from 'vs/base/common/lifecycle'; -export class NotebookCellTextModel implements ICell { +export class NotebookCellTextModel extends Disposable implements ICell { private _onDidChangeOutputs = new Emitter(); onDidChangeOutputs: Event = this._onDidChangeOutputs.event; @@ -27,15 +30,6 @@ export class NotebookCellTextModel implements ICell { return this._outputs; } - get source() { - return this._source; - } - - set source(newValue: string[]) { - this._source = newValue; - this._buffer = null; - } - private _metadata: NotebookCellMetadata | undefined; get metadata() { @@ -56,24 +50,52 @@ export class NotebookCellTextModel implements ICell { this._onDidChangeLanguage.fire(newLanguage); } - private _buffer: PieceTreeTextBufferFactory | null = null; + private _textBuffer!: model.IReadonlyTextBuffer; + + get textBuffer() { + if (this._textBuffer) { + return this._textBuffer; + } + + let builder = new PieceTreeTextBufferBuilder(); + builder.acceptChunk(Array.isArray(this._source) ? this._source.join('\n') : this._source); + const bufferFactory = builder.finish(true); + this._textBuffer = bufferFactory.create(model.DefaultEndOfLine.LF); + + this._register(this._textBuffer.onDidChangeContent(() => { + this._onDidChangeContent.fire(); + })); + + return this._textBuffer; + } constructor( readonly uri: URI, public handle: number, - private _source: string[], + private _source: string | string[], private _language: string, public cellKind: CellKind, outputs: IOutput[], metadata: NotebookCellMetadata | undefined ) { + super(); this._outputs = outputs; this._metadata = metadata; } - contentChange() { - this._onDidChangeContent.fire(); + getValue(): string { + const fullRange = this.getFullModelRange(); + const eol = this.textBuffer.getEOL(); + if (eol === '\n') { + return this.textBuffer.getValueInRange(fullRange, model.EndOfLinePreference.LF); + } else { + return this.textBuffer.getValueInRange(fullRange, model.EndOfLinePreference.CRLF); + } + } + getFullModelRange() { + const lineCount = this.textBuffer.getLineCount(); + return new Range(1, 1, lineCount, this.textBuffer.getLineLength(lineCount) + 1); } spliceNotebookCellOutputs(splices: NotebookCellOutputsSplice[]): void { @@ -83,15 +105,4 @@ export class NotebookCellTextModel implements ICell { this._onDidChangeOutputs.fire(splices); } - - resolveTextBufferFactory(): PieceTreeTextBufferFactory { - if (this._buffer) { - return this._buffer; - } - - let builder = new PieceTreeTextBufferBuilder(); - builder.acceptChunk(this.source.join('\n')); - this._buffer = builder.finish(true); - return this._buffer; - } } diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts index 324084ccfc..997f9727a4 100644 --- a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts +++ b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts @@ -66,7 +66,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel } createCellTextModel( - source: string[], + source: string | string[], language: string, cellKind: CellKind, outputs: IOutput[], @@ -74,7 +74,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel ) { const cellHandle = NotebookTextModel._cellhandlePool++; const cellUri = CellUri.generate(this.uri, cellHandle); - return new NotebookCellTextModel(URI.revive(cellUri), cellHandle, source, language, cellKind, outputs || [], metadata); + return new NotebookCellTextModel(cellUri, cellHandle, source, language, cellKind, outputs || [], metadata); } applyEdit(modelVersionId: number, rawEdits: ICellEditOperation[]): boolean { @@ -122,7 +122,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel const mainCells = insertEdit.cells.map(cell => { const cellHandle = NotebookTextModel._cellhandlePool++; const cellUri = CellUri.generate(this.uri, cellHandle); - return new NotebookCellTextModel(URI.revive(cellUri), cellHandle, cell.source, cell.language, cell.cellKind, cell.outputs || [], cell.metadata); + return new NotebookCellTextModel(cellUri, cellHandle, cell.source, cell.language, cell.cellKind, cell.outputs || [], cell.metadata); }); this.insertNewCell(insertEdit.index, mainCells); break; @@ -200,7 +200,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel [{ handle: cell.handle, uri: cell.uri, - source: cell.source, + source: cell.textBuffer.getLinesContent(), language: cell.language, cellKind: cell.cellKind, outputs: cell.outputs, @@ -237,7 +237,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel cells.map(cell => ({ handle: cell.handle, uri: cell.uri, - source: cell.source, + source: cell.textBuffer.getLinesContent(), language: cell.language, cellKind: cell.cellKind, outputs: cell.outputs, diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index a91523b590..f83a16980a 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -10,9 +10,11 @@ import { isWindows } from 'vs/base/common/platform'; import { ISplice } from 'vs/base/common/sequence'; import { URI, UriComponents } from 'vs/base/common/uri'; import * as editorCommon from 'vs/editor/common/editorCommon'; -import { PieceTreeTextBufferFactory } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBufferBuilder'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { IEditorModel } from 'vs/platform/editor/common/editor'; +import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; +import { GlobPattern } from 'vs/workbench/api/common/extHost.protocol'; export enum CellKind { Markdown = 1, @@ -46,12 +48,13 @@ export const ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER = [ 'image/jpeg', ]; -export const notebookDocumentMetadataDefaults: NotebookDocumentMetadata = { +export const notebookDocumentMetadataDefaults: Required = { editable: true, runnable: true, cellEditable: true, cellRunnable: true, - hasExecutionOrder: true + hasExecutionOrder: true, + displayOrder: NOTEBOOK_DISPLAY_ORDER }; export interface NotebookDocumentMetadata { @@ -60,6 +63,7 @@ export interface NotebookDocumentMetadata { cellEditable: boolean; cellRunnable: boolean; hasExecutionOrder: boolean; + displayOrder?: GlobPattern[]; } export enum NotebookCellRunState { @@ -159,7 +163,6 @@ export type IOutput = ITransformedDisplayOutputDto | IStreamOutput | IErrorOutpu export interface ICell { readonly uri: URI; handle: number; - source: string[]; language: string; cellKind: CellKind; outputs: IOutput[]; @@ -167,9 +170,6 @@ export interface ICell { onDidChangeOutputs?: Event; onDidChangeLanguage: Event; onDidChangeMetadata: Event; - resolveTextBufferFactory(): PieceTreeTextBufferFactory; - // TODO@rebornix it should be later on replaced by moving textmodel resolution into CellTextModel - contentChange(): void; } export interface LanguageInfo { @@ -465,3 +465,10 @@ export interface ICellEditorViewState { } export const NOTEBOOK_EDITOR_CURSOR_BOUNDARY = new RawContextKey<'none' | 'top' | 'bottom' | 'both'>('notebookEditorCursorAtBoundary', 'none'); + + +export interface INotebookEditorModel extends IEditorModel { + notebook: NotebookTextModel; + isDirty(): boolean; + save(): Promise; +} diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts new file mode 100644 index 0000000000..8594f80c79 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts @@ -0,0 +1,208 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { EditorModel, IRevertOptions } from 'vs/workbench/common/editor'; +import { Emitter, Event } from 'vs/base/common/event'; +import { INotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; +import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { ResourceMap } from 'vs/base/common/map'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; +import { URI } from 'vs/base/common/uri'; +import { IWorkingCopyService, IWorkingCopy, IWorkingCopyBackup } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { basename } from 'vs/base/common/resources'; +import { CancellationTokenSource } from 'vs/base/common/cancellation'; + +export interface INotebookEditorModelManager { + models: NotebookEditorModel[]; + + resolve(resource: URI, viewType: string): Promise; + + get(resource: URI): NotebookEditorModel | undefined; +} + + +export class NotebookEditorModel extends EditorModel implements IWorkingCopy, INotebookEditorModel { + private _dirty = false; + protected readonly _onDidChangeDirty = this._register(new Emitter()); + readonly onDidChangeDirty = this._onDidChangeDirty.event; + private readonly _onDidChangeContent = this._register(new Emitter()); + readonly onDidChangeContent: Event = this._onDidChangeContent.event; + private _notebook!: NotebookTextModel; + + get notebook() { + return this._notebook; + } + + private _name!: string; + + get name() { + return this._name; + } + + constructor( + public readonly resource: URI, + public readonly viewType: string, + @INotebookService private readonly notebookService: INotebookService, + @IWorkingCopyService private readonly workingCopyService: IWorkingCopyService + ) { + super(); + this._register(this.workingCopyService.registerWorkingCopy(this)); + } + + capabilities = 0; + + async backup(): Promise { + return {}; + } + + async revert(options?: IRevertOptions | undefined): Promise { + return; + } + + async load(): Promise { + const notebook = await this.notebookService.resolveNotebook(this.viewType!, this.resource); + this._notebook = notebook!; + + this._name = basename(this._notebook!.uri); + + this._register(this._notebook.onDidChangeContent(() => { + this._dirty = true; + this._onDidChangeDirty.fire(); + this._onDidChangeContent.fire(); + })); + + return this; + } + + isDirty() { + return this._dirty; + } + + async save(): Promise { + const tokenSource = new CancellationTokenSource(); + await this.notebookService.save(this.notebook.viewType, this.notebook.uri, tokenSource.token); + this._dirty = false; + this._onDidChangeDirty.fire(); + return true; + } +} + +export class NotebookEditorModelManager extends Disposable implements INotebookEditorModelManager { + + private readonly mapResourceToModel = new ResourceMap(); + private readonly mapResourceToModelListeners = new ResourceMap(); + private readonly mapResourceToDisposeListener = new ResourceMap(); + private readonly mapResourceToPendingModelLoaders = new ResourceMap>(); + + // private readonly modelLoadQueue = this._register(new ResourceQueue()); + + get models(): NotebookEditorModel[] { + return [...this.mapResourceToModel.values()]; + } + constructor( + @IInstantiationService readonly instantiationService: IInstantiationService + ) { + super(); + } + + async resolve(resource: URI, viewType: string): Promise { + // Return early if model is currently being loaded + const pendingLoad = this.mapResourceToPendingModelLoaders.get(resource); + if (pendingLoad) { + return pendingLoad; + } + + let modelPromise: Promise; + let model = this.get(resource); + // let didCreateModel = false; + + // Model exists + if (model) { + // if (options?.reload) { + // } else { + modelPromise = Promise.resolve(model); + // } + } + + // Model does not exist + else { + // didCreateModel = true; + const newModel = model = this.instantiationService.createInstance(NotebookEditorModel, resource, viewType); + modelPromise = model.load(); + + this.registerModel(newModel); + } + + // Store pending loads to avoid race conditions + this.mapResourceToPendingModelLoaders.set(resource, modelPromise); + + // Make known to manager (if not already known) + this.add(resource, model); + + // dispose and bind new listeners + + try { + const resolvedModel = await modelPromise; + + // Remove from pending loads + this.mapResourceToPendingModelLoaders.delete(resource); + return resolvedModel; + } catch (error) { + // Free resources of this invalid model + if (model) { + model.dispose(); + } + + // Remove from pending loads + this.mapResourceToPendingModelLoaders.delete(resource); + + throw error; + } + } + + add(resource: URI, model: NotebookEditorModel): void { + const knownModel = this.mapResourceToModel.get(resource); + if (knownModel === model) { + return; // already cached + } + + // dispose any previously stored dispose listener for this resource + const disposeListener = this.mapResourceToDisposeListener.get(resource); + if (disposeListener) { + disposeListener.dispose(); + } + + // store in cache but remove when model gets disposed + this.mapResourceToModel.set(resource, model); + this.mapResourceToDisposeListener.set(resource, model.onDispose(() => this.remove(resource))); + } + + remove(resource: URI): void { + this.mapResourceToModel.delete(resource); + + const disposeListener = this.mapResourceToDisposeListener.get(resource); + if (disposeListener) { + dispose(disposeListener); + this.mapResourceToDisposeListener.delete(resource); + } + + const modelListener = this.mapResourceToModelListeners.get(resource); + if (modelListener) { + dispose(modelListener); + this.mapResourceToModelListeners.delete(resource); + } + } + + + private registerModel(model: NotebookEditorModel): void { + + } + + get(resource: URI): NotebookEditorModel | undefined { + return this.mapResourceToModel.get(resource); + } +} diff --git a/src/vs/workbench/contrib/notebook/common/notebookService.ts b/src/vs/workbench/contrib/notebook/common/notebookService.ts new file mode 100644 index 0000000000..2a3b8d5c0a --- /dev/null +++ b/src/vs/workbench/contrib/notebook/common/notebookService.ts @@ -0,0 +1,51 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { URI } from 'vs/base/common/uri'; +import { NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider'; +import { NotebookExtensionDescription } from 'vs/workbench/api/common/extHost.protocol'; +import { Event } from 'vs/base/common/event'; +import { INotebookTextModel, INotebookMimeTypeSelector, INotebookRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; +import { INotebookEditorModelManager } from 'vs/workbench/contrib/notebook/common/notebookEditorModel'; + +export const INotebookService = createDecorator('notebookService'); + +export interface IMainNotebookController { + resolveNotebook(viewType: string, uri: URI): Promise; + executeNotebook(viewType: string, uri: URI, token: CancellationToken): Promise; + onDidReceiveMessage(uri: URI, message: any): void; + executeNotebookCell(uri: URI, handle: number, token: CancellationToken): Promise; + destoryNotebookDocument(notebook: INotebookTextModel): Promise; + save(uri: URI, token: CancellationToken): Promise; +} + +export interface INotebookService { + _serviceBrand: undefined; + modelManager: INotebookEditorModelManager; + canResolve(viewType: string): Promise; + onDidChangeActiveEditor: Event<{ viewType: string, uri: URI }>; + registerNotebookController(viewType: string, extensionData: NotebookExtensionDescription, controller: IMainNotebookController): void; + unregisterNotebookProvider(viewType: string): void; + registerNotebookRenderer(handle: number, extensionData: NotebookExtensionDescription, type: string, selectors: INotebookMimeTypeSelector, preloads: URI[]): void; + unregisterNotebookRenderer(handle: number): void; + getRendererInfo(handle: number): INotebookRendererInfo | undefined; + resolveNotebook(viewType: string, uri: URI): Promise; + executeNotebook(viewType: string, uri: URI): Promise; + executeNotebookCell(viewType: string, uri: URI, handle: number, token: CancellationToken): Promise; + + getContributedNotebookProviders(resource: URI): readonly NotebookProviderInfo[]; + getContributedNotebookProvider(viewType: string): NotebookProviderInfo | undefined; + getNotebookProviderResourceRoots(): URI[]; + destoryNotebookDocument(viewType: string, notebook: INotebookTextModel): void; + updateActiveNotebookDocument(viewType: string, resource: URI): void; + save(viewType: string, resource: URI, token: CancellationToken): Promise; + onDidReceiveMessage(viewType: string, uri: URI, message: any): void; + setToCopy(items: NotebookCellTextModel[]): void; + getToCopy(): NotebookCellTextModel[] | undefined; +} diff --git a/src/vs/workbench/contrib/notebook/test/notebookTextModel.test.ts b/src/vs/workbench/contrib/notebook/test/notebookTextModel.test.ts index 2dc09c1c55..d1982fa6af 100644 --- a/src/vs/workbench/contrib/notebook/test/notebookTextModel.test.ts +++ b/src/vs/workbench/contrib/notebook/test/notebookTextModel.test.ts @@ -35,8 +35,8 @@ suite('NotebookTextModel', () => { assert.equal(textModel.cells.length, 6); - assert.equal(textModel.cells[1].source.join('\n'), 'var e = 5;'); - assert.equal(textModel.cells[4].source.join('\n'), 'var f = 6;'); + assert.equal(textModel.cells[1].getValue(), 'var e = 5;'); + assert.equal(textModel.cells[4].getValue(), 'var f = 6;'); } ); }); @@ -60,8 +60,8 @@ suite('NotebookTextModel', () => { assert.equal(textModel.cells.length, 6); - assert.equal(textModel.cells[1].source.join('\n'), 'var e = 5;'); - assert.equal(textModel.cells[2].source.join('\n'), 'var f = 6;'); + assert.equal(textModel.cells[1].getValue(), 'var e = 5;'); + assert.equal(textModel.cells[2].getValue(), 'var f = 6;'); } ); }); @@ -83,8 +83,8 @@ suite('NotebookTextModel', () => { { editType: CellEditType.Delete, index: 3, count: 1 }, ]); - assert.equal(textModel.cells[0].source.join('\n'), 'var a = 1;'); - assert.equal(textModel.cells[1].source.join('\n'), 'var c = 3;'); + assert.equal(textModel.cells[0].getValue(), 'var a = 1;'); + assert.equal(textModel.cells[1].getValue(), 'var c = 3;'); } ); }); @@ -108,8 +108,8 @@ suite('NotebookTextModel', () => { assert.equal(textModel.cells.length, 4); - assert.equal(textModel.cells[0].source.join('\n'), 'var a = 1;'); - assert.equal(textModel.cells[2].source.join('\n'), 'var e = 5;'); + assert.equal(textModel.cells[0].getValue(), 'var a = 1;'); + assert.equal(textModel.cells[2].getValue(), 'var e = 5;'); } ); }); @@ -132,9 +132,9 @@ suite('NotebookTextModel', () => { ]); assert.equal(textModel.cells.length, 4); - assert.equal(textModel.cells[0].source.join('\n'), 'var a = 1;'); - assert.equal(textModel.cells[1].source.join('\n'), 'var e = 5;'); - assert.equal(textModel.cells[2].source.join('\n'), 'var c = 3;'); + assert.equal(textModel.cells[0].getValue(), 'var a = 1;'); + assert.equal(textModel.cells[1].getValue(), 'var e = 5;'); + assert.equal(textModel.cells[2].getValue(), 'var c = 3;'); } ); }); diff --git a/src/vs/workbench/contrib/notebook/test/notebookViewModel.test.ts b/src/vs/workbench/contrib/notebook/test/notebookViewModel.test.ts index 7040516ba6..28fc461b84 100644 --- a/src/vs/workbench/contrib/notebook/test/notebookViewModel.test.ts +++ b/src/vs/workbench/contrib/notebook/test/notebookViewModel.test.ts @@ -6,10 +6,9 @@ import * as assert from 'assert'; import { URI } from 'vs/base/common/uri'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; -import { NotebookEditorModel } from 'vs/workbench/contrib/notebook/browser/notebookEditorInput'; import { NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; import { CellKind, NotebookCellMetadata, diff } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { withTestNotebook, TestCell } from 'vs/workbench/contrib/notebook/test/testNotebookEditor'; +import { withTestNotebook, TestCell, NotebookEditorTestModel } from 'vs/workbench/contrib/notebook/test/testNotebookEditor'; import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; @@ -25,9 +24,9 @@ suite('NotebookViewModel', () => { test('ctor', function () { const notebook = new NotebookTextModel(0, 'notebook', URI.parse('test')); - const model = new NotebookEditorModel(notebook); + const model = new NotebookEditorTestModel(notebook); const eventDispatcher = new NotebookEventDispatcher(); - const viewModel = new NotebookViewModel('notebook', model, eventDispatcher, null, instantiationService, blukEditService, undoRedoService); + const viewModel = new NotebookViewModel('notebook', model.notebook, eventDispatcher, null, instantiationService, blukEditService, undoRedoService); assert.equal(viewModel.viewType, 'notebook'); }); diff --git a/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts b/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts index 2dbe3670f1..033f29f72d 100644 --- a/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts @@ -4,11 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from 'vs/base/common/uri'; -import { PieceTreeTextBufferFactory } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBufferBuilder'; -import { CellKind, IOutput, CellUri, NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellKind, IOutput, CellUri, NotebookCellMetadata, INotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NotebookViewModel, IModelDecorationsChangeAccessor, CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { NotebookEditorModel } from 'vs/workbench/contrib/notebook/browser/notebookEditorInput'; import { INotebookEditor, NotebookLayoutInfo, ICellViewModel, ICellRange, INotebookEditorMouseEvent, INotebookEditorContribution } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; import { OutputRenderer } from 'vs/workbench/contrib/notebook/browser/view/output/outputRenderer'; @@ -22,25 +20,18 @@ import { NotebookEventDispatcher } from 'vs/workbench/contrib/notebook/browser/v import { Webview } from 'vs/workbench/contrib/webview/browser/webview'; import { IDisposable } from 'vs/base/common/lifecycle'; import { Emitter, Event } from 'vs/base/common/event'; - +import { EditorModel } from 'vs/workbench/common/editor'; export class TestCell extends NotebookCellTextModel { constructor( public viewType: string, handle: number, - source: string[], + public source: string[], language: string, cellKind: CellKind, outputs: IOutput[] ) { super(CellUri.generate(URI.parse('test:///fake/notebook'), handle), handle, source, language, cellKind, outputs, undefined); } - contentChange(): void { - // throw new Error('Method not implemented.'); - } - - resolveTextBufferFactory(): PieceTreeTextBufferFactory { - throw new Error('Method not implemented.'); - } } export class TestNotebookEditor implements INotebookEditor { @@ -117,6 +108,14 @@ export class TestNotebookEditor implements INotebookEditor { throw new Error('Method not implemented.'); } + splitNotebookCell(cell: ICellViewModel): Promise { + throw new Error('Method not implemented.'); + } + + joinNotebookCells(cell: ICellViewModel, direction: 'above' | 'below', constraint?: CellKind): Promise { + throw new Error('Method not implemented.'); + } + setSelection(cell: CellViewModel, selection: Range): void { throw new Error('Method not implemented.'); } @@ -211,6 +210,54 @@ export class TestNotebookEditor implements INotebookEditor { // return createCellViewModel(instantiationService, viewType, notebookHandle, mockCell); // } +export class NotebookEditorTestModel extends EditorModel implements INotebookEditorModel { + private _dirty = false; + + protected readonly _onDidChangeDirty = this._register(new Emitter()); + readonly onDidChangeDirty = this._onDidChangeDirty.event; + + private readonly _onDidChangeContent = this._register(new Emitter()); + readonly onDidChangeContent: Event = this._onDidChangeContent.event; + + + get notebook() { + return this._notebook; + } + + constructor( + private _notebook: NotebookTextModel + ) { + super(); + + if (_notebook && _notebook.onDidChangeCells) { + this._register(_notebook.onDidChangeContent(() => { + this._dirty = true; + this._onDidChangeDirty.fire(); + this._onDidChangeContent.fire(); + })); + } + } + + isDirty() { + return this._dirty; + } + + getNotebook(): NotebookTextModel { + return this._notebook; + } + + async save(): Promise { + if (this._notebook) { + this._dirty = false; + this._onDidChangeDirty.fire(); + // todo, flush all states + return true; + } + + return false; + } +} + export function withTestNotebook(instantiationService: IInstantiationService, blukEditService: IBulkEditService, undoRedoService: IUndoRedoService, cells: [string[], string, CellKind, IOutput[], NotebookCellMetadata][], callback: (editor: TestNotebookEditor, viewModel: NotebookViewModel, textModel: NotebookTextModel) => void) { const viewType = 'notebook'; const editor = new TestNotebookEditor(); @@ -218,9 +265,9 @@ export function withTestNotebook(instantiationService: IInstantiationService, bl notebook.cells = cells.map((cell, index) => { return new NotebookCellTextModel(notebook.uri, index, cell[0], cell[1], cell[2], cell[3], cell[4]); }); - const model = new NotebookEditorModel(notebook); + const model = new NotebookEditorTestModel(notebook); const eventDispatcher = new NotebookEventDispatcher(); - const viewModel = new NotebookViewModel(viewType, model, eventDispatcher, null, instantiationService, blukEditService, undoRedoService); + const viewModel = new NotebookViewModel(viewType, model.notebook, eventDispatcher, null, instantiationService, blukEditService, undoRedoService); callback(editor, viewModel, notebook); diff --git a/src/vs/workbench/contrib/outline/browser/outlinePane.ts b/src/vs/workbench/contrib/outline/browser/outlinePane.ts index c8ce1387cf..85f20fbab2 100644 --- a/src/vs/workbench/contrib/outline/browser/outlinePane.ts +++ b/src/vs/workbench/contrib/outline/browser/outlinePane.ts @@ -9,7 +9,7 @@ import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; import { Action, IAction, RadioGroup } from 'vs/base/common/actions'; import { createCancelablePromise, TimeoutTimer } from 'vs/base/common/async'; import { isPromiseCanceledError } from 'vs/base/common/errors'; -import { Emitter } from 'vs/base/common/event'; +import { Emitter, Event } from 'vs/base/common/event'; import { defaultGenerator } from 'vs/base/common/idGenerator'; import { dispose, IDisposable, toDisposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; import { LRUCache } from 'vs/base/common/map'; @@ -238,7 +238,7 @@ class OutlineViewState { export class OutlinePane extends ViewPane { - private _disposables = new Array(); + private _disposables = new DisposableStore(); private _editorDisposables = new DisposableStore(); private _outlineViewState = new OutlineViewState(); @@ -277,8 +277,8 @@ export class OutlinePane extends ViewPane { this._outlineViewState.restore(this._storageService); this._contextKeyFocused = OutlineViewFocused.bindTo(contextKeyService); this._contextKeyFiltered = OutlineViewFiltered.bindTo(contextKeyService); - this._disposables.push(this.onDidFocus(_ => this._contextKeyFocused.set(true))); - this._disposables.push(this.onDidBlur(_ => this._contextKeyFocused.set(false))); + this._disposables.add(this.onDidFocus(_ => this._contextKeyFocused.set(true))); + this._disposables.add(this.onDidBlur(_ => this._contextKeyFocused.set(false))); } dispose(): void { @@ -305,7 +305,7 @@ export class OutlinePane extends ViewPane { this._domNode = container; this._domNode.tabIndex = 0; - dom.addClass(container, 'outline-pane'); + container.classList.add('outline-pane'); let progressContainer = dom.$('.outline-progress'); this._message = dom.$('.outline-message'); @@ -349,9 +349,9 @@ export class OutlinePane extends ViewPane { ); - this._disposables.push(this._tree); - this._disposables.push(this._outlineViewState.onDidChange(this._onDidChangeUserState, this)); - this._disposables.push(this.viewDescriptorService.onDidChangeLocation(({ views }) => { + this._disposables.add(this._tree); + this._disposables.add(this._outlineViewState.onDidChange(this._onDidChangeUserState, this)); + this._disposables.add(this.viewDescriptorService.onDidChangeLocation(({ views }) => { if (views.some(v => v.id === this.id)) { this._tree.updateOptions({ overrideStyles: { listBackground: this.getBackgroundColor() } }); } @@ -452,7 +452,7 @@ export class OutlinePane extends ViewPane { } private _showMessage(message: string) { - dom.addClass(this._domNode, 'message'); + this._domNode.classList.add('message'); this._tree.setInput(undefined!); this._progressBar.stop().hide(); this._message.innerText = escape(message); @@ -508,7 +508,7 @@ export class OutlinePane extends ViewPane { return this._showMessage(localize('no-symbols', "No symbols found in document '{0}'", basename(textModel.uri))); } - dom.removeClass(this._domNode, 'message'); + this._domNode.classList.remove('message'); if (event && oldModel && textModel.getLineCount() >= 25) { // heuristic: when the symbols-to-lines ratio changes by 50% between edits @@ -526,7 +526,7 @@ export class OutlinePane extends ViewPane { handle = undefined; resolve(true); }, 2000); - this._disposables.push({ + this._disposables.add({ dispose() { clearTimeout(handle); resolve(false); @@ -614,7 +614,7 @@ export class OutlinePane extends ViewPane { } }; updateMarker(textModel, true); - this._editorDisposables.add(this._markerDecorationService.onDidChangeMarker(updateMarker)); + this._editorDisposables.add(Event.debounce(this._markerDecorationService.onDidChangeMarker, (_, e) => e, 64)(updateMarker)); this._editorDisposables.add(this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration(OutlineConfigKeys.problemsBadges) || e.affectsConfiguration(OutlineConfigKeys.problemsColors)) { diff --git a/src/vs/workbench/contrib/performance/electron-browser/perfviewEditor.ts b/src/vs/workbench/contrib/performance/electron-browser/perfviewEditor.ts index d58334bfba..1b3d9c125a 100644 --- a/src/vs/workbench/contrib/performance/electron-browser/perfviewEditor.ts +++ b/src/vs/workbench/contrib/performance/electron-browser/perfviewEditor.ts @@ -13,7 +13,6 @@ import { IModeService } from 'vs/editor/common/services/modeService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ITimerService, IStartupMetrics } from 'vs/workbench/services/timer/electron-browser/timerService'; -import { repeat } from 'vs/base/common/strings'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import * as perf from 'vs/base/common/performance'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; @@ -377,7 +376,7 @@ class MarkdownBuilder { value: string = ''; heading(level: number, value: string): this { - this.value += `${repeat('#', level)} ${value}\n\n`; + this.value += `${'#'.repeat(level)} ${value}\n\n`; return this; } @@ -407,16 +406,16 @@ class MarkdownBuilder { }); // header - header.forEach((cell, ci) => { this.value += `| ${cell + repeat(' ', lengths[ci] - cell.toString().length)} `; }); + header.forEach((cell, ci) => { this.value += `| ${cell + ' '.repeat(lengths[ci] - cell.toString().length)} `; }); this.value += '|\n'; - header.forEach((_cell, ci) => { this.value += `| ${repeat('-', lengths[ci])} `; }); + header.forEach((_cell, ci) => { this.value += `| ${'-'.repeat(lengths[ci])} `; }); this.value += '|\n'; // cells rows.forEach(row => { row.forEach((cell, ci) => { if (typeof cell !== 'undefined') { - this.value += `| ${cell + repeat(' ', lengths[ci] - cell.toString().length)} `; + this.value += `| ${cell + ' '.repeat(lengths[ci] - cell.toString().length)} `; } }); this.value += '|\n'; diff --git a/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts b/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts index d8c1856b13..98ad007a26 100644 --- a/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts +++ b/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts @@ -459,6 +459,7 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditorP setRowLineHeight: false, horizontalScrolling: false, accessibilityProvider: new AccessibilityProvider(), + keyboardNavigationLabelProvider: { getKeyboardNavigationLabel: (e: IKeybindingItemEntry) => e.keybindingItem.commandLabel || e.keybindingItem.command }, overrideStyles: { listBackground: editorBackground } @@ -1112,9 +1113,9 @@ class AccessibilityProvider implements IListAccessibilityProvider this.createDecoration(range))); } if (markerData.length) { - this.markerService.changeOne('preferencesEditor', this.workspaceSettingsEditorModel.uri, markerData); + this.markerService.changeOne('WorkspaceConfigurationRenderer', this.workspaceSettingsEditorModel.uri, markerData); } else { - this.markerService.remove('preferencesEditor', [this.workspaceSettingsEditorModel.uri]); + this.markerService.remove('WorkspaceConfigurationRenderer', [this.workspaceSettingsEditorModel.uri]); } } @@ -1149,7 +1149,7 @@ class WorkspaceConfigurationRenderer extends Disposable { } dispose(): void { - this.markerService.remove('preferencesEditor', [this.workspaceSettingsEditorModel.uri]); + this.markerService.remove('WorkspaceConfigurationRenderer', [this.workspaceSettingsEditorModel.uri]); this.decorationIds = this.editor.deltaDecorations(this.decorationIds, []); super.dispose(); } diff --git a/src/vs/workbench/contrib/scm/browser/repositoryPane.ts b/src/vs/workbench/contrib/scm/browser/repositoryPane.ts index bb31c51814..2c2d5af5c3 100644 --- a/src/vs/workbench/contrib/scm/browser/repositoryPane.ts +++ b/src/vs/workbench/contrib/scm/browser/repositoryPane.ts @@ -407,12 +407,12 @@ export class SCMAccessibilityProvider implements IListAccessibilityProvider fileMatch.dispose()); - this._unDisposedFileMatches.values().forEach((fileMatch: FileMatch) => fileMatch.dispose()); + [...this._fileMatches.values()].forEach((fileMatch: FileMatch) => fileMatch.dispose()); + [...this._unDisposedFileMatches.values()].forEach((fileMatch: FileMatch) => fileMatch.dispose()); this._fileMatches.clear(); this._unDisposedFileMatches.clear(); } @@ -702,7 +702,7 @@ export class SearchResult extends Disposable { private _rangeHighlightDecorations: RangeHighlightDecorations; private disposePastResults: () => void = () => { }; - private _hasRemovedResults = false; + private _isDirty = false; constructor( private _searchModel: SearchModel, @@ -718,13 +718,13 @@ export class SearchResult extends Disposable { this._register(this.onChange(e => { if (e.removed) { - this._hasRemovedResults = true; + this._isDirty = !this.isEmpty(); } })); } - get hasRemovedResults(): boolean { - return this._hasRemovedResults; + get isDirty(): boolean { + return this._isDirty; } get query(): ITextQuery | null { @@ -737,7 +737,7 @@ export class SearchResult extends Disposable { new Promise(resolve => this.disposePastResults = resolve) .then(() => oldFolderMatches.forEach(match => match.clear())) .then(() => oldFolderMatches.forEach(match => match.dispose())) - .then(() => this._hasRemovedResults = false); + .then(() => this._isDirty = false); this._rangeHighlightDecorations.removeHighlightRange(); this._folderMatchesMap = TernarySearchTree.forUris(); diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts index 3193772e74..c03135b085 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts @@ -10,7 +10,7 @@ import { URI } from 'vs/base/common/uri'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { ToggleCaseSensitiveKeybinding, ToggleRegexKeybinding, ToggleWholeWordKeybinding } from 'vs/editor/contrib/find/findModel'; import { localize } from 'vs/nls'; -import { MenuId, MenuRegistry, SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { MenuId, SyncActionDescriptor, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; @@ -26,12 +26,14 @@ import { Extensions as EditorInputExtensions, IEditorInputFactory, IEditorInputF import * as SearchConstants from 'vs/workbench/contrib/search/common/constants'; import * as SearchEditorConstants from 'vs/workbench/contrib/searchEditor/browser/constants'; import { SearchEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditor'; -import { modifySearchEditorContextLinesCommand, OpenResultsInEditorAction, OpenSearchEditorAction, OpenSearchEditorToSideAction, RerunSearchEditorSearchAction, selectAllSearchEditorMatchesCommand, toggleSearchEditorCaseSensitiveCommand, toggleSearchEditorContextLinesCommand, toggleSearchEditorRegexCommand, toggleSearchEditorWholeWordCommand, FocusQueryEditorWidgetAction } from 'vs/workbench/contrib/searchEditor/browser/searchEditorActions'; +import { modifySearchEditorContextLinesCommand, OpenSearchEditorAction, selectAllSearchEditorMatchesCommand, toggleSearchEditorCaseSensitiveCommand, toggleSearchEditorContextLinesCommand, toggleSearchEditorRegexCommand, toggleSearchEditorWholeWordCommand, createEditorFromSearchResult, openNewSearchEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditorActions'; import { getOrMakeSearchEditorInput, SearchEditorInput, SearchConfiguration } from 'vs/workbench/contrib/searchEditor/browser/searchEditorInput'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { parseSavedSearchEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditorSerialization'; import { Range } from 'vs/editor/common/core/range'; import { searchRefreshIcon } from 'vs/workbench/contrib/search/browser/searchIcons'; +import { IViewsService } from 'vs/workbench/common/views'; +import { getSearchView } from 'vs/workbench/contrib/search/browser/searchActions'; //#region Editor Descriptior Registry.as(EditorExtensions.Editors).registerEditor( @@ -193,36 +195,105 @@ CommandsRegistry.registerCommand( const registry = Registry.as(ActionExtensions.WorkbenchActions); const category = localize('search', "Search Editor"); -registry.registerWorkbenchAction( - SyncActionDescriptor.from(OpenResultsInEditorAction, - { mac: { primary: KeyMod.CtrlCmd | KeyCode.Enter } }, - ContextKeyExpr.and(SearchConstants.HasSearchResults, SearchConstants.SearchViewFocusedKey)), - 'Search Editor: Open Results in Editor', category); - +// TODO: Not an action2 becuase used in view pane container action bar, which uses actions registry.registerWorkbenchAction( SyncActionDescriptor.from(OpenSearchEditorAction), 'Search Editor: Open New Search Editor', category); -registry.registerWorkbenchAction( - SyncActionDescriptor.from(OpenSearchEditorToSideAction), - 'Search Editor: Open New Search Editor to Side', category); - -registry.registerWorkbenchAction(SyncActionDescriptor.from(RerunSearchEditorSearchAction, - { mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_R } }), - 'Search Editor: Search Again', category, SearchEditorConstants.InSearchEditor); - -registry.registerWorkbenchAction(SyncActionDescriptor.from(FocusQueryEditorWidgetAction, - { primary: KeyCode.Escape }), - 'Search Editor: Focus Query Editor Widget', category, SearchEditorConstants.InSearchEditor); -//#endregion - - -MenuRegistry.appendMenuItem(MenuId.EditorTitle, { - command: { - id: RerunSearchEditorSearchAction.ID, - title: RerunSearchEditorSearchAction.LABEL, - icon: searchRefreshIcon, - }, - group: 'navigation', - when: ContextKeyExpr.and(ActiveEditorContext.isEqualTo(SearchEditorConstants.SearchEditorID)) +registerAction2(class extends Action2 { + constructor() { + super({ + id: SearchEditorConstants.OpenInEditorCommandId, + title: localize('search.openResultsInEditor', "Open Results in Editor"), + category, + f1: true, + keybinding: { + primary: KeyMod.CtrlCmd | KeyCode.Enter, + when: ContextKeyExpr.and(SearchConstants.HasSearchResults, SearchConstants.SearchViewFocusedKey), + weight: KeybindingWeight.WorkbenchContrib + }, + }); + } + async run(accessor: ServicesAccessor) { + const viewsService = accessor.get(IViewsService); + const instantiationService = accessor.get(IInstantiationService); + const searchView = getSearchView(viewsService); + if (searchView) { + await instantiationService.invokeFunction(createEditorFromSearchResult, searchView.searchResult, searchView.searchIncludePattern.getValue(), searchView.searchExcludePattern.getValue()); + } + } }); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: SearchEditorConstants.OpenNewEditorToSideCommandId, + title: localize('search.openNewEditorToSide', "Open New Search Editor to Side"), + category, + f1: true, + }); + } + async run(accessor: ServicesAccessor) { + const instantiationService = accessor.get(IInstantiationService); + await instantiationService.invokeFunction(openNewSearchEditor, true); + } +}); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: SearchEditorConstants.RerunSearchEditorSearchCommandId, + title: localize('search.rerunSearchInEditor', "Search Again"), + category, + keybinding: { + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_R, + when: SearchEditorConstants.InSearchEditor, + weight: KeybindingWeight.EditorContrib + }, + icon: searchRefreshIcon, + menu: [{ + id: MenuId.EditorTitle, + group: 'navigation', + when: ActiveEditorContext.isEqualTo(SearchEditorConstants.SearchEditorID) + }, + { + id: MenuId.CommandPalette, + when: ActiveEditorContext.isEqualTo(SearchEditorConstants.SearchEditorID) + }] + }); + } + async run(accessor: ServicesAccessor) { + const editorService = accessor.get(IEditorService); + const input = editorService.activeEditor; + if (input instanceof SearchEditorInput) { + (editorService.activeEditorPane as SearchEditor).triggerSearch({ resetCursor: false }); + } + } +}); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: SearchEditorConstants.FocusQueryEditorWidgetCommandId, + title: localize('search.action.focusQueryEditorWidget', "Focus Search Editor Input"), + category, + menu: { + id: MenuId.CommandPalette, + when: ActiveEditorContext.isEqualTo(SearchEditorConstants.SearchEditorID) + }, + keybinding: { + primary: KeyCode.Escape, + when: SearchEditorConstants.InSearchEditor, + weight: KeybindingWeight.EditorContrib + } + }); + } + async run(accessor: ServicesAccessor) { + const editorService = accessor.get(IEditorService); + const input = editorService.activeEditor; + if (input instanceof SearchEditorInput) { + (editorService.activeEditorPane as SearchEditor).focusSearchInput(); + } + } +}); +//#endregion diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts index 6516b0ccd1..71ae96c9b5 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts @@ -12,8 +12,6 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ILabelService } from 'vs/platform/label/common/label'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IViewsService } from 'vs/workbench/common/views'; -import { getSearchView } from 'vs/workbench/contrib/search/browser/searchActions'; import { SearchResult } from 'vs/workbench/contrib/search/common/searchModel'; import * as Constants from 'vs/workbench/contrib/searchEditor/browser/constants'; import { SearchEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditor'; @@ -21,7 +19,7 @@ import { getOrMakeSearchEditorInput, SearchEditorInput } from 'vs/workbench/cont import { serializeSearchResultForEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditorSerialization'; import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { ISearchConfigurationProperties } from 'vs/workbench/services/search/common/search'; -import { searchRefreshIcon, searchNewEditorIcon, searchGotoFileIcon } from 'vs/workbench/contrib/search/browser/searchIcons'; +import { searchNewEditorIcon } from 'vs/workbench/contrib/search/browser/searchIcons'; export const toggleSearchEditorCaseSensitiveCommand = (accessor: ServicesAccessor) => { const editorService = accessor.get(IEditorService); @@ -71,7 +69,6 @@ export const selectAllSearchEditorMatchesCommand = (accessor: ServicesAccessor) } }; - export class OpenSearchEditorAction extends Action { static readonly ID: string = Constants.OpenNewEditorCommandId; @@ -96,88 +93,7 @@ export class OpenSearchEditorAction extends Action { } } -export class OpenSearchEditorToSideAction extends Action { - - static readonly ID: string = Constants.OpenNewEditorToSideCommandId; - static readonly LABEL = localize('search.openNewEditorToSide', "Open New Search Editor to Side"); - - constructor(id: string, label: string, - @IInstantiationService private readonly instantiationService: IInstantiationService, - ) { - super(id, label, searchNewEditorIcon.classNames); - } - - async run() { - await this.instantiationService.invokeFunction(openNewSearchEditor, true); - } -} - -export class OpenResultsInEditorAction extends Action { - - static readonly ID: string = Constants.OpenInEditorCommandId; - static readonly LABEL = localize('search.openResultsInEditor', "Open Results in Editor"); - - constructor(id: string, label: string, - @IViewsService private viewsService: IViewsService, - @IInstantiationService private readonly instantiationService: IInstantiationService, - ) { - super(id, label, searchGotoFileIcon.classNames); - } - - get enabled(): boolean { - const searchView = getSearchView(this.viewsService); - return !!searchView && searchView.hasSearchResults(); - } - - update() { - this._setEnabled(this.enabled); - } - - async run() { - const searchView = getSearchView(this.viewsService); - if (searchView) { - await this.instantiationService.invokeFunction(createEditorFromSearchResult, searchView.searchResult, searchView.searchIncludePattern.getValue(), searchView.searchExcludePattern.getValue()); - } - } -} - -export class RerunSearchEditorSearchAction extends Action { - static readonly ID: string = Constants.RerunSearchEditorSearchCommandId; - static readonly LABEL = localize('search.rerunSearchInEditor', "Search Again"); - - constructor(id: string, label: string, - @IEditorService private readonly editorService: IEditorService, - ) { - super(id, label, searchRefreshIcon.classNames); - } - - async run() { - const input = this.editorService.activeEditor; - if (input instanceof SearchEditorInput) { - (this.editorService.activeEditorPane as SearchEditor).triggerSearch({ resetCursor: false }); - } - } -} - -export class FocusQueryEditorWidgetAction extends Action { - static readonly ID: string = Constants.FocusQueryEditorWidgetCommandId; - static readonly LABEL = localize('search.action.focusQueryEditorWidget', "Focus Search Editor Input"); - - constructor(id: string, label: string, - @IEditorService private readonly editorService: IEditorService, - ) { - super(id, label); - } - - async run() { - const input = this.editorService.activeEditor; - if (input instanceof SearchEditorInput) { - (this.editorService.activeEditorPane as SearchEditor).focusSearchInput(); - } - } -} - -const openNewSearchEditor = +export const openNewSearchEditor = async (accessor: ServicesAccessor, toSide = false) => { const editorService = accessor.get(IEditorService); const telemetryService = accessor.get(ITelemetryService); diff --git a/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts b/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts index 996076b54a..8a412ac0f5 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts @@ -144,7 +144,7 @@ export class SnippetCompletionProvider implements CompletionItemProvider { return { suggestions }; } - resolveCompletionItem(_model: ITextModel, _position: Position, item: CompletionItem): CompletionItem { + resolveCompletionItem(item: CompletionItem): CompletionItem { return (item instanceof SnippetCompletion) ? item.resolve() : item; } diff --git a/src/vs/workbench/contrib/snippets/browser/snippetsService.ts b/src/vs/workbench/contrib/snippets/browser/snippetsService.ts index 8e8350aca0..5391d0c73d 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippetsService.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippetsService.ts @@ -6,7 +6,7 @@ import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { combinedDisposable, IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import * as resources from 'vs/base/common/resources'; -import { endsWith, isFalsyOrWhitespace } from 'vs/base/common/strings'; +import { isFalsyOrWhitespace } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; import { Position } from 'vs/editor/common/core/position'; import { LanguageId } from 'vs/editor/common/modes'; @@ -49,7 +49,7 @@ namespace snippetExt { return null; } - if (isFalsyOrWhitespace(snippet.language) && !endsWith(snippet.path, '.code-snippets')) { + if (isFalsyOrWhitespace(snippet.language) && !snippet.path.endsWith('.code-snippets')) { extension.collector.error(localize( 'invalid.language.0', "When omitting the language, the value of `contributes.{0}.path` must be a `.code-snippets`-file. Provided value: {1}", diff --git a/src/vs/workbench/contrib/snippets/browser/tabCompletion.ts b/src/vs/workbench/contrib/snippets/browser/tabCompletion.ts index 7333c7e110..6c8519c4c0 100644 --- a/src/vs/workbench/contrib/snippets/browser/tabCompletion.ts +++ b/src/vs/workbench/contrib/snippets/browser/tabCompletion.ts @@ -8,7 +8,6 @@ import { RawContextKey, IContextKeyService, ContextKeyExpr, IContextKey } from ' import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { ISnippetsService } from './snippets.contribution'; import { getNonWhitespacePrefix } from './snippetsService'; -import { endsWith } from 'vs/base/common/strings'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { Range } from 'vs/editor/common/core/range'; @@ -97,7 +96,7 @@ export class TabCompletionController implements IEditorContribution { const prefix = getNonWhitespacePrefix(model, selection.getPosition()); if (prefix) { for (const snippet of snippets) { - if (endsWith(prefix, snippet.prefix)) { + if (prefix.endsWith(snippet.prefix)) { this._activeSnippets.push(snippet); } } diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index 01e73822a7..f548e1145b 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -74,7 +74,7 @@ import { IPathService } from 'vs/workbench/services/path/common/pathService'; import { format } from 'vs/base/common/jsonFormatter'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { applyEdits } from 'vs/base/common/jsonEdit'; -import { ITextEditorPane } from 'vs/workbench/common/editor'; +import { ITextEditorPane, SaveReason } from 'vs/workbench/common/editor'; import { ITextEditorSelection, TextEditorSelectionRevealType } from 'vs/platform/editor/common/editor'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { find } from 'vs/base/common/arrays'; @@ -791,7 +791,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer if (quickOpenHistoryLimit === 0) { return; } - let keys = this._recentlyUsedTasks.keys(); + let keys = [...this._recentlyUsedTasks.keys()]; if (keys.length > quickOpenHistoryLimit) { keys = keys.slice(0, quickOpenHistoryLimit); } @@ -1419,7 +1419,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer }; const saveAllEditorsAndExecTask = async (task: Task, resolver: ITaskResolver): Promise => { - return this.editorService.saveAll().then(() => { + return this.editorService.saveAll({ reason: SaveReason.AUTO }).then(() => { return execTask(task, resolver); }); }; @@ -2323,7 +2323,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer taskMap[key] = task; } }); - const reversed = recentlyUsedTasks.keys().reverse(); + const reversed = [...recentlyUsedTasks.keys()].reverse(); for (const key in reversed) { let task = taskMap[key]; if (task) { @@ -2461,7 +2461,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } ProblemMatcherRegistry.onReady().then(() => { - return this.editorService.saveAll().then(() => { // make sure all dirty editors are saved + return this.editorService.saveAll({ reason: SaveReason.AUTO }).then(() => { // make sure all dirty editors are saved let executeResult = this.getTaskSystem().rerun(); if (executeResult) { return this.handleExecuteResult(executeResult); diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts index fb6a53cd55..d0562e3a6c 100644 --- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts @@ -229,6 +229,7 @@ export class TerminalTaskSystem implements ITaskSystem { } public run(task: Task, resolver: ITaskResolver, trigger: string = Triggers.command): ITaskExecuteResult { + task = task.clone(); // A small amount of task state is stored in the task (instance) and tasks passed in to run may have that set already. const recentTaskKey = task.getRecentlyUsedKey() ?? ''; let validInstance = task.runOptions && task.runOptions.instanceLimit && this.instances[recentTaskKey] && this.instances[recentTaskKey].instances < task.runOptions.instanceLimit; let instance = this.instances[recentTaskKey] ? this.instances[recentTaskKey].instances : 0; diff --git a/src/vs/workbench/contrib/tasks/common/tasks.ts b/src/vs/workbench/contrib/tasks/common/tasks.ts index 7c441d806b..55f52e8cb3 100644 --- a/src/vs/workbench/contrib/tasks/common/tasks.ts +++ b/src/vs/workbench/contrib/tasks/common/tasks.ts @@ -688,6 +688,10 @@ export class CustomTask extends CommonTask { } } + public clone(): CustomTask { + return new CustomTask(this._id, this._source, this._label, this.type, this.command, this.hasDefinedMatchers, this.runOptions, this.configurationProperties); + } + public customizes(): KeyedTaskIdentifier | undefined { if (this._source && this._source.customizes) { return this._source.customizes; @@ -874,6 +878,10 @@ export class ContributedTask extends CommonTask { this.command = command; } + public clone(): ContributedTask { + return new ContributedTask(this._id, this._source, this._label, this.type, this.defines, this.command, this.hasDefinedMatchers, this.runOptions, this.configurationProperties); + } + public getDefinition(): KeyedTaskIdentifier { return this.defines; } @@ -938,6 +946,10 @@ export class InMemoryTask extends CommonTask { this._source = source; } + public clone(): InMemoryTask { + return new InMemoryTask(this._id, this._source, this._label, this.type, this.runOptions, this.configurationProperties); + } + public static is(value: any): value is InMemoryTask { return value instanceof InMemoryTask; } diff --git a/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts b/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts index 73dd901545..c9d2ac5d8c 100644 --- a/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts +++ b/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts @@ -111,7 +111,7 @@ export class TelemetryContribution extends Disposable implements IWorkbenchContr customKeybindingsCount: keybindingsService.customKeybindingsCount(), theme: themeService.getColorTheme().id, language, - pinnedViewlets: activityBarService.getPinnedViewletIds(), + pinnedViewlets: activityBarService.getPinnedViewContainerIds(), restoredViewlet: activeViewlet ? activeViewlet.getId() : undefined, restoredEditors: editorService.visibleEditors.length, startupKind: lifecycleService.startupKind diff --git a/src/vs/workbench/contrib/terminal/browser/addons/commandTrackerAddon.ts b/src/vs/workbench/contrib/terminal/browser/addons/commandTrackerAddon.ts index 8915c62184..1fcc3e8481 100644 --- a/src/vs/workbench/contrib/terminal/browser/addons/commandTrackerAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/addons/commandTrackerAddon.ts @@ -233,13 +233,13 @@ export class CommandTrackerAddon implements ICommandTracker, ITerminalAddon { } if (this._currentMarker === Boundary.Bottom) { - this._currentMarker = xterm.registerMarker(this._getOffset(xterm) - 1); + this._currentMarker = this._addMarkerOrThrow(xterm, this._getOffset(xterm) - 1); } else { const offset = this._getOffset(xterm); if (this._isDisposable) { this._currentMarker.dispose(); } - this._currentMarker = xterm.registerMarker(offset - 1); + this._currentMarker = this._addMarkerOrThrow(xterm, offset - 1); } this._isDisposable = true; this._scrollToMarker(this._currentMarker, scrollPosition); @@ -256,18 +256,26 @@ export class CommandTrackerAddon implements ICommandTracker, ITerminalAddon { } if (this._currentMarker === Boundary.Top) { - this._currentMarker = xterm.registerMarker(this._getOffset(xterm) + 1); + this._currentMarker = this._addMarkerOrThrow(xterm, this._getOffset(xterm) + 1); } else { const offset = this._getOffset(xterm); if (this._isDisposable) { this._currentMarker.dispose(); } - this._currentMarker = xterm.registerMarker(offset + 1); + this._currentMarker = this._addMarkerOrThrow(xterm, offset + 1); } this._isDisposable = true; this._scrollToMarker(this._currentMarker, scrollPosition); } + private _addMarkerOrThrow(xterm: Terminal, cursorYOffset: number): IMarker { + const marker = xterm.addMarker(cursorYOffset); + if (!marker) { + throw new Error(`Could not create marker for ${cursorYOffset}`); + } + return marker; + } + private _getOffset(xterm: Terminal): number { if (this._currentMarker === Boundary.Bottom) { return 0; diff --git a/src/vs/workbench/contrib/terminal/browser/links/terminalLink.ts b/src/vs/workbench/contrib/terminal/browser/links/terminalLink.ts index 4194f11d4d..d30b0187d9 100644 --- a/src/vs/workbench/contrib/terminal/browser/links/terminalLink.ts +++ b/src/vs/workbench/contrib/terminal/browser/links/terminalLink.ts @@ -11,6 +11,7 @@ import { convertBufferRangeToViewport } from 'vs/workbench/contrib/terminal/brow import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { isMacintosh } from 'vs/base/common/platform'; import { localize } from 'vs/nls'; +import { Emitter, Event } from 'vs/base/common/event'; export const OPEN_FILE_LABEL = localize('openFile', 'Open file in editor'); export const FOLDER_IN_WORKSPACE_LABEL = localize('focusFolder', 'Focus folder in explorer'); @@ -19,6 +20,9 @@ export const FOLDER_NOT_IN_WORKSPACE_LABEL = localize('openFolder', 'Open folder export class TerminalLink extends DisposableStore implements ILink { decorations: ILinkDecorations; + private readonly _onLeave = new Emitter(); + public get onLeave(): Event { return this._onLeave.event; } + constructor( public readonly range: IBufferRange, public readonly text: string, @@ -85,6 +89,7 @@ export class TerminalLink extends DisposableStore implements ILink { } leave(): void { + this._onLeave.fire(); this.dispose(); } diff --git a/src/vs/workbench/contrib/terminal/browser/links/terminalLinkManager.ts b/src/vs/workbench/contrib/terminal/browser/links/terminalLinkManager.ts index 8893033632..f46157b221 100644 --- a/src/vs/workbench/contrib/terminal/browser/links/terminalLinkManager.ts +++ b/src/vs/workbench/contrib/terminal/browser/links/terminalLinkManager.ts @@ -186,17 +186,21 @@ export class TerminalLinkManager extends DisposableStore { terminalDimensions, modifierDownCallback, modifierUpCallback - }, this._getLinkHoverString(link.text, link.label), (text) => link.activate(undefined, text)); + }, this._getLinkHoverString(link.text, link.label), (text) => link.activate(undefined, text), link); } private _showHover( targetOptions: ILinkHoverTargetOptions, text: IMarkdownString, - linkHandler: (url: string) => void + linkHandler: (url: string) => void, + link?: TerminalLink ) { if (this._widgetManager) { const widget = this._instantiationService.createInstance(TerminalHover, targetOptions, text, linkHandler); - this._widgetManager.attachWidget(widget); + const attached = this._widgetManager.attachWidget(widget); + if (attached) { + link?.onLeave(() => attached.dispose()); + } } } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalView.ts b/src/vs/workbench/contrib/terminal/browser/terminalView.ts index c8fd956bc9..4e1322bd7d 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalView.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalView.ts @@ -43,6 +43,7 @@ export class TerminalViewPane extends ViewPane { private _terminalContainer: HTMLElement | undefined; private _findWidget: TerminalFindWidget | undefined; private _splitTerminalAction: IAction | undefined; + private _bodyDimensions: { width: number, height: number } = { width: 0, height: 0 }; constructor( options: IViewPaneOptions, @@ -106,6 +107,8 @@ export class TerminalViewPane extends ViewPane { this._updateTheme(); if (hadTerminals) { this._terminalService.getActiveTab()?.setVisible(visible); + } else { + this.layoutBody(this._bodyDimensions.height, this._bodyDimensions.width); } } })); @@ -116,6 +119,8 @@ export class TerminalViewPane extends ViewPane { protected layoutBody(height: number, width: number): void { super.layoutBody(height, width); + this._bodyDimensions.width = width; + this._bodyDimensions.height = height; this._terminalService.terminalTabs.forEach(t => t.layout(width, height)); // Update orientation of split button icon if (this._splitTerminalAction) { diff --git a/src/vs/workbench/contrib/terminal/browser/widgets/hoverWidget.ts b/src/vs/workbench/contrib/terminal/browser/widgets/hoverWidget.ts index 39e9a28fb8..b5d7506683 100644 --- a/src/vs/workbench/contrib/terminal/browser/widgets/hoverWidget.ts +++ b/src/vs/workbench/contrib/terminal/browser/widgets/hoverWidget.ts @@ -93,7 +93,7 @@ export class HoverWidget extends Widget { const actionsElement = $('div.actions'); this._actions.forEach(action => this._renderAction(actionsElement, action)); statusBarElement.appendChild(actionsElement); - this._domNode.appendChild(statusBarElement); + this._containerDomNode.appendChild(statusBarElement); } this._mouseTracker = new CompositeMouseTracker([this._containerDomNode, ..._target.targetElements]); diff --git a/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts b/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts index 0f2de01734..ddf353d893 100644 --- a/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts +++ b/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts @@ -97,13 +97,14 @@ export class DynamicWebviewEditorOverlay extends Disposable implements WebviewOv if (!this.container || !this.container.parentElement) { return; } + const frameRect = element.getBoundingClientRect(); const containerRect = this.container.parentElement.getBoundingClientRect(); this.container.style.position = 'absolute'; this.container.style.top = `${frameRect.top - containerRect.top}px`; this.container.style.left = `${frameRect.left - containerRect.left}px`; - this.container.style.width = `${dimension && frameRect.width > 0 ? dimension.width : frameRect.width}px`; - this.container.style.height = `${dimension && frameRect.height > 0 ? dimension.height : frameRect.height}px`; + this.container.style.width = `${dimension ? dimension.width : frameRect.width}px`; + this.container.style.height = `${dimension ? dimension.height : frameRect.height}px`; } private show() { diff --git a/src/vs/workbench/contrib/webview/browser/pre/main.js b/src/vs/workbench/contrib/webview/browser/pre/main.js index 97b681fb2b..6f8fa4891e 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/main.js +++ b/src/vs/workbench/contrib/webview/browser/pre/main.js @@ -197,8 +197,21 @@ } if (initData.styles) { + const documentStyle = document.documentElement.style; + + // Remove stale properties + for (let i = documentStyle.length - 1; i >= 0; i--) { + const property = documentStyle[i]; + + // Don't remove properties that the webview might have added separately + if (property && property.startsWith('--vscode-')) { + documentStyle.removeProperty(property); + } + } + + // Re-add new properties for (const variable of Object.keys(initData.styles)) { - document.documentElement.style.setProperty(`--${variable}`, initData.styles[variable]); + documentStyle.setProperty(`--${variable}`, initData.styles[variable]); } } }; diff --git a/src/vs/workbench/contrib/webview/browser/webviewEditor.ts b/src/vs/workbench/contrib/webview/browser/webviewEditor.ts index 76c3a861fc..98d2c8470c 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewEditor.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewEditor.ts @@ -26,6 +26,7 @@ export class WebviewEditor extends BaseEditor { private _element?: HTMLElement; private _dimension?: DOM.Dimension; + private _visible = false; private readonly _webviewVisibleDisposables = this._register(new DisposableStore()); private readonly _onFocusWindowHandler = this._register(new MutableDisposable()); @@ -65,7 +66,7 @@ export class WebviewEditor extends BaseEditor { public layout(dimension: DOM.Dimension): void { this._dimension = dimension; - if (this.webview) { + if (this.webview && this._visible) { this.synchronizeWebviewContainerDimensions(this.webview, dimension); } } @@ -84,6 +85,7 @@ export class WebviewEditor extends BaseEditor { } protected setEditorVisible(visible: boolean, group: IEditorGroup | undefined): void { + this._visible = visible; if (this.input instanceof WebviewInput && this.webview) { if (visible) { this.claimWebview(this.input); diff --git a/src/vs/workbench/electron-browser/window.ts b/src/vs/workbench/electron-browser/window.ts index ec3cf943a2..fe3957ea64 100644 --- a/src/vs/workbench/electron-browser/window.ts +++ b/src/vs/workbench/electron-browser/window.ts @@ -141,7 +141,7 @@ export class NativeWindow extends Disposable { } } } else { - args.push({ from: request.from }); // TODO@telemetry this is a bit weird to send this to every action? + args.push({ from: request.from }); } try { diff --git a/src/vs/workbench/services/activityBar/browser/activityBarService.ts b/src/vs/workbench/services/activityBar/browser/activityBarService.ts index cfb274d9a5..3079d4e2dd 100644 --- a/src/vs/workbench/services/activityBar/browser/activityBarService.ts +++ b/src/vs/workbench/services/activityBar/browser/activityBarService.ts @@ -18,7 +18,12 @@ export interface IActivityBarService { showActivity(viewletOrActionId: string, badge: IBadge, clazz?: string, priority?: number): IDisposable; /** - * Returns id of pinned viewlets following the visual order. + * Returns id of pinned view containers following the visual order. */ - getPinnedViewletIds(): string[]; + getPinnedViewContainerIds(): string[]; + + /** + * Returns id of visible viewlets following the visual order. + */ + getVisibleViewContainerIds(): string[]; } diff --git a/src/vs/workbench/services/backup/common/backupFileService.ts b/src/vs/workbench/services/backup/common/backupFileService.ts index f7b278897d..6d3f189981 100644 --- a/src/vs/workbench/services/backup/common/backupFileService.ts +++ b/src/vs/workbench/services/backup/common/backupFileService.ts @@ -95,7 +95,7 @@ export class BackupFilesModel implements IBackupFilesModel { } get(): URI[] { - return this.cache.keys(); + return [...this.cache.keys()]; } remove(resource: URI): void { diff --git a/src/vs/workbench/services/bulkEdit/browser/conflicts.ts b/src/vs/workbench/services/bulkEdit/browser/conflicts.ts index 34b87c1ddc..98cd381d16 100644 --- a/src/vs/workbench/services/bulkEdit/browser/conflicts.ts +++ b/src/vs/workbench/services/bulkEdit/browser/conflicts.ts @@ -87,7 +87,7 @@ export class ConflictDetector { } list(): URI[] { - return this._conflicts.keys(); + return [...this._conflicts.keys()]; } hasConflicts(): boolean { diff --git a/src/vs/workbench/services/configuration/browser/configuration.ts b/src/vs/workbench/services/configuration/browser/configuration.ts index 2e0e6398db..fc7603a7f1 100644 --- a/src/vs/workbench/services/configuration/browser/configuration.ts +++ b/src/vs/workbench/services/configuration/browser/configuration.ts @@ -127,12 +127,12 @@ class FileServiceBasedConfiguration extends Disposable { this._folderSettingsModelParser.parseContent(''); // parse - if (settingsContents[0]) { + if (settingsContents[0] !== undefined) { this._folderSettingsModelParser.parseContent(settingsContents[0]); } for (let index = 0; index < standAloneConfigurationContents.length; index++) { const contents = standAloneConfigurationContents[index]; - if (contents) { + if (contents !== undefined) { const standAloneConfigurationModelParser = new StandaloneConfigurationModelParser(this.standAloneConfigurationResources[index][1].toString(), this.standAloneConfigurationResources[index][0]); standAloneConfigurationModelParser.parseContent(contents); this._standAloneConfigurations.push(standAloneConfigurationModelParser.configurationModel); diff --git a/src/vs/workbench/services/editor/browser/editorService.ts b/src/vs/workbench/services/editor/browser/editorService.ts index c3dec1561e..9df2c25ed0 100644 --- a/src/vs/workbench/services/editor/browser/editorService.ts +++ b/src/vs/workbench/services/editor/browser/editorService.ts @@ -190,7 +190,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { } // Handle no longer visible out of workspace resources - this.activeOutOfWorkspaceWatchers.keys().forEach(resource => { + [...this.activeOutOfWorkspaceWatchers.keys()].forEach(resource => { if (!visibleOutOfWorkspaceResources.get(resource)) { dispose(this.activeOutOfWorkspaceWatchers.get(resource)); this.activeOutOfWorkspaceWatchers.delete(resource); diff --git a/src/vs/workbench/services/environment/browser/environmentService.ts b/src/vs/workbench/services/environment/browser/environmentService.ts index 8dc5f142ab..7edab2892b 100644 --- a/src/vs/workbench/services/environment/browser/environmentService.ts +++ b/src/vs/workbench/services/environment/browser/environmentService.ts @@ -114,8 +114,13 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment @memoize get snippetsHome(): URI { return joinPath(this.userRoamingDataHome, 'snippets'); } + /* + * In Web every workspace can potentially have scoped user-data and/or extensions and if Sync state is shared then it can make + * Sync error prone - say removing extensions from another workspace. Hence scope Sync state per workspace. + * Sync scoped to a workspace is capable of handling opening same workspace in multiple windows. + */ @memoize - get userDataSyncHome(): URI { return joinPath(this.userRoamingDataHome, 'sync'); } + get userDataSyncHome(): URI { return joinPath(this.userRoamingDataHome, 'sync', this.options.workspaceId); } @memoize get userDataSyncLogResource(): URI { return joinPath(this.options.logsPath, 'userDataSync.log'); } diff --git a/src/vs/workbench/services/host/browser/browserHostService.ts b/src/vs/workbench/services/host/browser/browserHostService.ts index 02f06bbd0f..907a88dd53 100644 --- a/src/vs/workbench/services/host/browser/browserHostService.ts +++ b/src/vs/workbench/services/host/browser/browserHostService.ts @@ -17,6 +17,8 @@ import { trackFocus } from 'vs/base/browser/dom'; import { Disposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { domEvent } from 'vs/base/browser/event'; +import { memoize } from 'vs/base/common/decorators'; /** * A workspace to open in the workbench can either be: @@ -77,17 +79,15 @@ export class BrowserHostService extends Disposable implements IHostService { } } - private _onDidChangeFocus: Event | undefined; + @memoize get onDidChangeFocus(): Event { - if (!this._onDidChangeFocus) { - const focusTracker = this._register(trackFocus(window)); - this._onDidChangeFocus = Event.any( - Event.map(focusTracker.onDidFocus, () => this.hasFocus), - Event.map(focusTracker.onDidBlur, () => this.hasFocus) - ); - } + const focusTracker = this._register(trackFocus(window)); - return this._onDidChangeFocus; + return Event.latch(Event.any( + Event.map(focusTracker.onDidFocus, () => this.hasFocus), + Event.map(focusTracker.onDidBlur, () => this.hasFocus), + Event.map(domEvent(window.document, 'visibilitychange'), () => this.hasFocus) + )); } get hasFocus(): boolean { diff --git a/src/vs/workbench/services/host/electron-browser/desktopHostService.ts b/src/vs/workbench/services/host/electron-browser/desktopHostService.ts index 20804a0d2e..2bac9f49f3 100644 --- a/src/vs/workbench/services/host/electron-browser/desktopHostService.ts +++ b/src/vs/workbench/services/host/electron-browser/desktopHostService.ts @@ -26,10 +26,10 @@ export class DesktopHostService extends Disposable implements IHostService { } get onDidChangeFocus(): Event { return this._onDidChangeFocus; } - private _onDidChangeFocus: Event = Event.any( + private _onDidChangeFocus: Event = Event.latch(Event.any( Event.map(Event.filter(this.electronService.onWindowFocus, id => id === this.environmentService.configuration.windowId), () => this.hasFocus), Event.map(Event.filter(this.electronService.onWindowBlur, id => id === this.environmentService.configuration.windowId), () => this.hasFocus) - ); + )); get hasFocus(): boolean { return document.hasFocus(); diff --git a/src/vs/workbench/services/progress/browser/progressService.ts b/src/vs/workbench/services/progress/browser/progressService.ts index f9955912b7..f22e42a197 100644 --- a/src/vs/workbench/services/progress/browser/progressService.ts +++ b/src/vs/workbench/services/progress/browser/progressService.ts @@ -57,7 +57,7 @@ export class ProgressService extends Disposable implements IProgressService { return this.withPanelProgress(location, task, { ...options, location }); } - if (this.viewsService.getProgressIndicator(location)) { + if (this.viewsService.getViewProgressIndicator(location)) { return this.withViewProgress(location, task, { ...options, location }); } @@ -418,7 +418,7 @@ export class ProgressService extends Disposable implements IProgressService { private withViewProgress

, R = unknown>(viewId: string, task: (progress: IProgress) => P, options: IProgressCompositeOptions): P { // show in viewlet - const promise = this.withCompositeProgress(this.viewsService.getProgressIndicator(viewId), task, options); + const promise = this.withCompositeProgress(this.viewsService.getViewProgressIndicator(viewId), task, options); const location = this.viewDescriptorService.getViewLocationById(viewId); if (location !== ViewContainerLocation.Sidebar) { diff --git a/src/vs/workbench/services/search/common/searchService.ts b/src/vs/workbench/services/search/common/searchService.ts index 858365df6f..d00b0ada18 100644 --- a/src/vs/workbench/services/search/common/searchService.ts +++ b/src/vs/workbench/services/search/common/searchService.ts @@ -74,7 +74,7 @@ export class SearchService extends Disposable implements ISearchService { const localResults = this.getLocalResults(query); if (onProgress) { - arrays.coalesce(localResults.results.values()).forEach(onProgress); + arrays.coalesce([...localResults.results.values()]).forEach(onProgress); } const onProviderProgress = (progress: ISearchProgressItem) => { @@ -99,7 +99,7 @@ export class SearchService extends Disposable implements ISearchService { ...{ limitHit: otherResults.limitHit || localResults.limitHit }, - results: [...otherResults.results, ...arrays.coalesce(localResults.results.values())] + results: [...otherResults.results, ...arrays.coalesce([...localResults.results.values()])] }; } diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts index 3a4905fa2b..0d03c7b585 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts @@ -68,7 +68,7 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE })(); get models(): TextFileEditorModel[] { - return this.mapResourceToModel.values(); + return [...this.mapResourceToModel.values()]; } constructor( diff --git a/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts b/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts index 83cf16e209..21519727f5 100644 --- a/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts +++ b/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from 'vs/base/common/uri'; -import { first } from 'vs/base/common/async'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ITextModel } from 'vs/editor/common/model'; import { IDisposable, toDisposable, IReference, ReferenceCollection, ImmortalReference } from 'vs/base/common/lifecycle'; @@ -110,7 +109,6 @@ class ResourceModelCollection extends ReferenceCollection { const resource = URI.parse(key); const providers = this.providers[resource.scheme] || []; - const factories = providers.map(p => () => Promise.resolve(p.provideTextContent(resource))); if (resource.query || resource.fragment) { type TextModelResolverUri = { @@ -127,12 +125,13 @@ class ResourceModelCollection extends ReferenceCollection { - return !this.isLoaded ? this.load(fileService) : Promise.resolve(this.styleSheetContent); + public ensureLoaded(fileService: IFileService, logService: ILogService): Promise { + return !this.isLoaded ? this.load(fileService, logService) : Promise.resolve(this.styleSheetContent); } - public reload(fileService: IFileService): Promise { - return this.load(fileService); + public reload(fileService: IFileService, logService: ILogService): Promise { + return this.load(fileService, logService); } - private load(fileService: IFileService): Promise { - if (!this.location) { + private load(fileService: IFileService, logService: ILogService): Promise { + const location = this.location; + if (!location) { return Promise.resolve(this.styleSheetContent); } - return _loadProductIconThemeDocument(fileService, this.location).then(iconThemeDocument => { - const result = _processIconThemeDocument(this.id, this.location!, iconThemeDocument); + return _loadProductIconThemeDocument(fileService, location).then(iconThemeDocument => { + const result = _processIconThemeDocument(this.id, location, iconThemeDocument); this.styleSheetContent = result.content; this.isLoaded = true; + if (result.warnings.length) { + logService.error(nls.localize('error.parseicondefs', "Problems processing product icons definitions in {0}:\n{1}", location.toString(), result.warnings.join('\n'))); + } return this.styleSheetContent; }); } @@ -174,9 +183,10 @@ function _loadProductIconThemeDocument(fileService: IFileService, location: URI) }); } -function _processIconThemeDocument(id: string, iconThemeDocumentLocation: URI, iconThemeDocument: ProductIconThemeDocument): { content: string; } { +function _processIconThemeDocument(id: string, iconThemeDocumentLocation: URI, iconThemeDocument: ProductIconThemeDocument): { content: string; warnings: string[] } { - const result = { content: '' }; + const warnings: string[] = []; + const result = { content: '', warnings }; if (!iconThemeDocument.iconDefinitions || !Array.isArray(iconThemeDocument.fonts) || !iconThemeDocument.fonts.length) { return result; @@ -187,22 +197,72 @@ function _processIconThemeDocument(id: string, iconThemeDocumentLocation: URI, i return resources.joinPath(iconThemeDocumentLocationDirname, path); } - let cssRules: string[] = []; + const cssRules: string[] = []; - let fonts = iconThemeDocument.fonts; + const fonts = iconThemeDocument.fonts; + const fontIdMapping: { [id: string]: string } = {}; for (const font of fonts) { const src = font.src.map(l => `${asCSSUrl(resolvePath(l.path))} format('${l.format}')`).join(', '); - cssRules.push(`@font-face { src: ${src}; font-family: '${font.id}'; font-weight: ${font.weight}; font-style: ${font.style}; }`); + if (isString(font.id) && font.id.match(fontIdRegex)) { + const fontId = `pi-` + font.id; + fontIdMapping[font.id] = fontId; + + let fontWeight = ''; + if (isString(font.weight) && font.weight.match(fontWeightRegex)) { + fontWeight = `font-weight: ${font.weight};`; + } else { + warnings.push(nls.localize('error.fontWeight', 'Invalid font weight in font \'{0}\'. Ignoring setting.', font.id)); + } + + let fontStyle = ''; + if (isString(font.style) && font.style.match(fontStyleRegex)) { + fontStyle = `font-style: ${font.style};`; + } else { + warnings.push(nls.localize('error.fontStyle', 'Invalid font style in font \'{0}\'. Ignoring setting.', font.id)); + } + + cssRules.push(`@font-face { src: ${src}; font-family: '${fontId}';${fontWeight}${fontStyle} }`); + } else { + warnings.push(nls.localize('error.fontId', 'Missing or invalid font id \'{0}\'. Skipping font definition.', font.id)); + } } - let primaryFontId = fonts[0].id; - let iconDefinitions = iconThemeDocument.iconDefinitions; - for (const iconId in iconThemeDocument.iconDefinitions) { - const definition = iconDefinitions[iconId]; - if (definition && definition.fontCharacter) { - cssRules.push(`.codicon-${iconId}:before { content: '${definition.fontCharacter}' !important; font-family: ${definition.fontId || primaryFontId} !important; }`); + const primaryFontId = fonts.length > 0 ? fontIdMapping[fonts[0].id] : ''; + + const iconDefinitions = iconThemeDocument.iconDefinitions; + const iconRegistry = getIconRegistry(); + + + for (let iconContribution of iconRegistry.getIcons()) { + const iconId = iconContribution.id; + + let definition = iconDefinitions[iconId]; + + // look if an inherited icon has a definition + while (!definition && ThemeIcon.isThemeIcon(iconContribution.defaults)) { + const ic = iconRegistry.getIcon(iconContribution.defaults.id); + if (ic) { + definition = iconDefinitions[ic.id]; + iconContribution = ic; + } else { + break; + } + } + + if (definition) { + if (isString(definition.fontCharacter)) { + const fontId = definition.fontId !== undefined ? fontIdMapping[definition.fontId] : primaryFontId; + if (fontId) { + cssRules.push(`.codicon-${iconId}:before { content: '${definition.fontCharacter}' !important; font-family: ${fontId} !important; }`); + } else { + warnings.push(nls.localize('error.icon.fontId', 'Skipping icon definition \'{0}\'. Unknown font.', iconId)); + } + } else { + warnings.push(nls.localize('error.icon.fontCharacter', 'Skipping icon definition \'{0}\'. Unknown fontCharacter.', iconId)); + } } } result.content = cssRules.join('\n'); return result; } + diff --git a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts index d5f2f72722..e732b3b375 100644 --- a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts @@ -32,6 +32,7 @@ import { ThemeRegistry, registerColorThemeExtensionPoint, registerFileIconThemeE import { updateColorThemeConfigurationSchemas, updateFileIconThemeConfigurationSchemas, ThemeConfiguration, updateProductIconThemeConfigurationSchemas } from 'vs/workbench/services/themes/common/themeConfiguration'; import { ProductIconThemeData, DEFAULT_PRODUCT_ICON_THEME_ID } from 'vs/workbench/services/themes/browser/productIconThemeData'; import { registerProductIconThemeSchemas } from 'vs/workbench/services/themes/common/productIconThemeSchema'; +import { ILogService } from 'vs/platform/log/common/log'; // implementation @@ -99,7 +100,8 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IFileService private readonly fileService: IFileService, @IExtensionResourceLoaderService private readonly extensionResourceLoaderService: IExtensionResourceLoaderService, - @IWorkbenchLayoutService readonly layoutService: IWorkbenchLayoutService + @IWorkbenchLayoutService readonly layoutService: IWorkbenchLayoutService, + @ILogService private readonly logService: ILogService ) { this.container = layoutService.getWorkbenchContainer(); this.settings = new ThemeConfiguration(configurationService); @@ -573,7 +575,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { } const newThemeData = await this.productIconThemeRegistry.findThemeById(iconTheme) || ProductIconThemeData.defaultTheme; - await newThemeData.ensureLoaded(this.fileService); + await newThemeData.ensureLoaded(this.fileService, this.logService); this.applyAndSetProductIconTheme(newThemeData); @@ -587,7 +589,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { } private async reloadCurrentProductIconTheme() { - await this.currentProductIconTheme.reload(this.fileService); + await this.currentProductIconTheme.reload(this.fileService, this.logService); this.applyAndSetProductIconTheme(this.currentProductIconTheme); } diff --git a/src/vs/workbench/services/themes/common/fileIconThemeSchema.ts b/src/vs/workbench/services/themes/common/fileIconThemeSchema.ts index da06ad9c37..a57bdedeeb 100644 --- a/src/vs/workbench/services/themes/common/fileIconThemeSchema.ts +++ b/src/vs/workbench/services/themes/common/fileIconThemeSchema.ts @@ -7,6 +7,7 @@ import * as nls from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; import { Extensions as JSONExtensions, IJSONContributionRegistry } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; +import { fontWeightRegex, fontStyleRegex, fontSizeRegex, fontIdRegex } from 'vs/workbench/services/themes/common/productIconThemeSchema'; const schemaId = 'vscode://schemas/icon-theme'; const schema: IJSONSchema = { @@ -110,7 +111,9 @@ const schema: IJSONSchema = { properties: { id: { type: 'string', - description: nls.localize('schema.id', 'The ID of the font.') + description: nls.localize('schema.id', 'The ID of the font.'), + pattern: fontIdRegex, + patternErrorMessage: nls.localize('schema.id.formatError', 'The ID must only contain letter, numbers, underscore and minus.') }, src: { type: 'array', @@ -124,7 +127,8 @@ const schema: IJSONSchema = { }, format: { type: 'string', - description: nls.localize('schema.font-format', 'The format of the font.') + description: nls.localize('schema.font-format', 'The format of the font.'), + enum: ['woff', 'woff2', 'truetype', 'opentype', 'embedded-opentype', 'svg'] } }, required: [ @@ -135,15 +139,18 @@ const schema: IJSONSchema = { }, weight: { type: 'string', - description: nls.localize('schema.font-weight', 'The weight of the font.') + description: nls.localize('schema.font-weight', 'The weight of the font. See https://developer.mozilla.org/en-US/docs/Web/CSS/font-weight for valid values.'), + pattern: fontWeightRegex }, style: { type: 'string', - description: nls.localize('schema.font-sstyle', 'The style of the font.') + description: nls.localize('schema.font-style', 'The style of the font. See https://developer.mozilla.org/en-US/docs/Web/CSS/font-style for valid values.'), + pattern: fontStyleRegex }, size: { type: 'string', - description: nls.localize('schema.font-size', 'The default size of the font.') + description: nls.localize('schema.font-size', 'The default size of the font. See https://developer.mozilla.org/en-US/docs/Web/CSS/font-size for valid values.'), + pattern: fontSizeRegex } }, required: [ @@ -174,7 +181,8 @@ const schema: IJSONSchema = { }, fontSize: { type: 'string', - description: nls.localize('schema.fontSize', 'When using a font: The font size in percentage to the text font. If not set, defaults to the size in the font definition.') + description: nls.localize('schema.fontSize', 'When using a font: The font size in percentage to the text font. If not set, defaults to the size in the font definition.'), + pattern: fontSizeRegex }, fontId: { type: 'string', diff --git a/src/vs/workbench/services/themes/common/productIconThemeSchema.ts b/src/vs/workbench/services/themes/common/productIconThemeSchema.ts index 189e6b2c60..2515d0b98f 100644 --- a/src/vs/workbench/services/themes/common/productIconThemeSchema.ts +++ b/src/vs/workbench/services/themes/common/productIconThemeSchema.ts @@ -9,6 +9,10 @@ import { Extensions as JSONExtensions, IJSONContributionRegistry } from 'vs/plat import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { iconsSchemaId } from 'vs/platform/theme/common/iconRegistry'; +export const fontIdRegex = '^([\\w-_]+)$'; +export const fontStyleRegex = '^(normal|italic|(oblique[ \\w\\s-]+))$'; +export const fontWeightRegex = '^(normal|bold|lighter|bolder|(\\d{0-1000}))$'; +export const fontSizeRegex = '^([\\w .%-_]+)$'; const schemaId = 'vscode://schemas/product-icon-theme'; const schema: IJSONSchema = { @@ -18,13 +22,14 @@ const schema: IJSONSchema = { properties: { fonts: { type: 'array', - description: nls.localize('schema.fonts', 'Fonts that are used in the icon definitions.'), items: { type: 'object', properties: { id: { type: 'string', - description: nls.localize('schema.id', 'The ID of the font.') + description: nls.localize('schema.id', 'The ID of the font.'), + pattern: fontIdRegex, + patternErrorMessage: nls.localize('schema.id.formatError', 'The ID must only contain letters, numbers, underscore and minus.') }, src: { type: 'array', @@ -38,7 +43,8 @@ const schema: IJSONSchema = { }, format: { type: 'string', - description: nls.localize('schema.font-format', 'The format of the font.') + description: nls.localize('schema.font-format', 'The format of the font.'), + enum: ['woff', 'woff2', 'truetype', 'opentype', 'embedded-opentype', 'svg'] } }, required: [ @@ -49,15 +55,19 @@ const schema: IJSONSchema = { }, weight: { type: 'string', - description: nls.localize('schema.font-weight', 'The weight of the font.') + description: nls.localize('schema.font-weight', 'The weight of the font. See https://developer.mozilla.org/en-US/docs/Web/CSS/font-weight for valid values.'), + anyOf: [ + { enum: ['normal', 'bold', 'lighter', 'bolder'] }, + { type: 'string', pattern: fontWeightRegex } + ] }, style: { type: 'string', - description: nls.localize('schema.font-sstyle', 'The style of the font.') - }, - size: { - type: 'string', - description: nls.localize('schema.font-size', 'The default size of the font.') + description: nls.localize('schema.font-style', 'The style of the font. See https://developer.mozilla.org/en-US/docs/Web/CSS/font-style for valid values.'), + anyOf: [ + { enum: ['normal', 'italic', 'oblique'] }, + { type: 'string', pattern: fontStyleRegex } + ] } }, required: [ diff --git a/src/vs/workbench/services/title/common/titleService.ts b/src/vs/workbench/services/title/common/titleService.ts index 948b179bd7..186105282b 100644 --- a/src/vs/workbench/services/title/common/titleService.ts +++ b/src/vs/workbench/services/title/common/titleService.ts @@ -11,6 +11,7 @@ export const ITitleService = createDecorator('titleService'); export interface ITitleProperties { isPure?: boolean; isAdmin?: boolean; + prefix?: string; } export interface ITitleService { diff --git a/src/vs/workbench/services/viewlet/browser/viewlet.ts b/src/vs/workbench/services/viewlet/browser/viewlet.ts index def4e49237..bfedb22547 100644 --- a/src/vs/workbench/services/viewlet/browser/viewlet.ts +++ b/src/vs/workbench/services/viewlet/browser/viewlet.ts @@ -30,11 +30,6 @@ export interface IViewletService { */ getActiveViewlet(): IViewlet | undefined; - /** - * Returns the id of the default viewlet. - */ - getDefaultViewletId(): string; - /** * Returns the viewlet by id. */ diff --git a/src/vs/workbench/services/views/browser/viewDescriptorService.ts b/src/vs/workbench/services/views/browser/viewDescriptorService.ts index 89f52a8940..f2cd986498 100644 --- a/src/vs/workbench/services/views/browser/viewDescriptorService.ts +++ b/src/vs/workbench/services/views/browser/viewDescriptorService.ts @@ -84,6 +84,9 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor } } + readonly onDidChangeViewContainers: Event<{ added: ReadonlyArray<{ container: ViewContainer, location: ViewContainerLocation }>, removed: ReadonlyArray<{ container: ViewContainer, location: ViewContainerLocation }> }>; + get viewContainers(): ReadonlyArray { return this.viewContainersRegistry.all; } + constructor( @IInstantiationService private readonly instantiationService: IInstantiationService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @@ -107,7 +110,11 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor this.cachedViewContainerInfo = this.getCachedViewContainerLocations(); // Register all containers that were registered before this ctor - this.getViewContainers().forEach(viewContainer => this.onDidRegisterViewContainer(viewContainer)); + this.viewContainers.forEach(viewContainer => this.onDidRegisterViewContainer(viewContainer)); + this.onDidChangeViewContainers = Event.any<{ added: ReadonlyArray<{ container: ViewContainer, location: ViewContainerLocation }>, removed: ReadonlyArray<{ container: ViewContainer, location: ViewContainerLocation }> }>( + Event.map(this.viewContainersRegistry.onDidRegister, e => ({ added: [{ container: e.viewContainer, location: e.viewContainerLocation }], removed: [] })), + Event.map(this.viewContainersRegistry.onDidDeregister, e => ({ added: [], removed: [{ container: e.viewContainer, location: e.viewContainerLocation }] })) + ); // Try generating all generated containers that don't need extensions this.tryGenerateContainers(); @@ -293,11 +300,13 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor getViewContainerById(id: string): ViewContainer | null { return this.viewContainersRegistry.get(id) || null; } + getViewContainersByLocation(location: ViewContainerLocation): ViewContainer[] { - return this.getViewContainers().filter(v => this.getViewContainerLocation(v) === location); + return this.viewContainers.filter(v => this.getViewContainerLocation(v) === location); } - getViewContainers(): ViewContainer[] { - return this.viewContainersRegistry.all; + + getDefaultViewContainer(location: ViewContainerLocation): ViewContainer | undefined { + return this.viewContainersRegistry.getDefaultViewContainer(location); } moveViewContainerToLocation(viewContainer: ViewContainer, location: ViewContainerLocation): void { @@ -453,7 +462,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor } // If a value is not present in the cache, it must be reset to default - this.getViewContainers().forEach(viewContainer => { + this.viewContainers.forEach(viewContainer => { const viewContainerModel = this.getViewContainerModel(viewContainer); viewContainerModel.allViewDescriptors.forEach(viewDescriptor => { if (!newCachedPositions.has(viewDescriptor.id)) { @@ -486,7 +495,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor } } - this.getViewContainers().forEach(viewContainer => { + this.viewContainers.forEach(viewContainer => { if (!newCachedLocations.has(viewContainer.id)) { const currentLocation = this.getViewContainerLocation(viewContainer); const defaultLocation = this.getDefaultViewContainerLocation(viewContainer); @@ -526,7 +535,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor } private saveViewPositionsToCache(): void { - this.getViewContainers().forEach(viewContainer => { + this.viewContainers.forEach(viewContainer => { const viewContainerModel = this.getViewContainerModel(viewContainer); viewContainerModel.allViewDescriptors.forEach(viewDescriptor => { const containerLocation = this.getViewContainerLocation(viewContainer); diff --git a/src/vs/workbench/test/browser/api/extHostDiagnostics.test.ts b/src/vs/workbench/test/browser/api/extHostDiagnostics.test.ts index dc630c7238..e3814a5110 100644 --- a/src/vs/workbench/test/browser/api/extHostDiagnostics.test.ts +++ b/src/vs/workbench/test/browser/api/extHostDiagnostics.test.ts @@ -13,6 +13,7 @@ import { mock } from 'vs/workbench/test/browser/api/mock'; import { Emitter, Event } from 'vs/base/common/event'; import { NullLogService } from 'vs/platform/log/common/log'; import type * as vscode from 'vscode'; +import { nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; suite('ExtHostDiagnostics', () => { @@ -286,7 +287,7 @@ suite('ExtHostDiagnostics', () => { }); test('diagnostic eventing', async function () { - let emitter = new Emitter>(); + let emitter = new Emitter>(); let collection = new DiagnosticCollection('ddd', 'test', 100, new DiagnosticsShape(), emitter); let diag1 = new Diagnostic(new Range(1, 1, 2, 3), 'diag1'); @@ -316,15 +317,15 @@ suite('ExtHostDiagnostics', () => { p = Event.toPromise(emitter.event).then(e => { assert.equal(e.length, 2); - assert.ok(typeof e[0] === 'string'); - assert.ok(typeof e[1] === 'string'); + assert.ok(URI.isUri(e[0])); + assert.ok(URI.isUri(e[1])); }); collection.clear(); await p; }); test('vscode.languages.onDidChangeDiagnostics Does Not Provide Document URI #49582', async function () { - let emitter = new Emitter>(); + let emitter = new Emitter>(); let collection = new DiagnosticCollection('ddd', 'test', 100, new DiagnosticsShape(), emitter); let diag1 = new Diagnostic(new Range(1, 1, 2, 3), 'diag1'); @@ -390,8 +391,8 @@ suite('ExtHostDiagnostics', () => { } }, new NullLogService()); - let collection1 = diags.createDiagnosticCollection('foo'); - let collection2 = diags.createDiagnosticCollection('foo'); // warns, uses a different owner + let collection1 = diags.createDiagnosticCollection(nullExtensionDescription.identifier, 'foo'); + let collection2 = diags.createDiagnosticCollection(nullExtensionDescription.identifier, 'foo'); // warns, uses a different owner collection1.clear(); collection2.clear(); diff --git a/src/vs/workbench/test/browser/parts/editor/editorGroups.test.ts b/src/vs/workbench/test/browser/parts/editor/editorGroups.test.ts index b211a4d279..f8c9533af0 100644 --- a/src/vs/workbench/test/browser/parts/editor/editorGroups.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/editorGroups.test.ts @@ -145,11 +145,18 @@ interface ISerializedTestInput { class TestEditorInputFactory implements IEditorInputFactory { + static disableSerialize = false; + static disableDeserialize = false; + canSerialize(editorInput: EditorInput): boolean { return true; } - serialize(editorInput: EditorInput): string { + serialize(editorInput: EditorInput): string | undefined { + if (TestEditorInputFactory.disableSerialize) { + return undefined; + } + let testEditorInput = editorInput; let testInput: ISerializedTestInput = { id: testEditorInput.id @@ -158,7 +165,11 @@ class TestEditorInputFactory implements IEditorInputFactory { return JSON.stringify(testInput); } - deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): EditorInput { + deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): EditorInput | undefined { + if (TestEditorInputFactory.disableDeserialize) { + return undefined; + } + let testInput: ISerializedTestInput = JSON.parse(serializedEditorInput); return new TestEditorInput(testInput.id); @@ -170,6 +181,9 @@ suite('Workbench editor groups', () => { let disposables: IDisposable[] = []; setup(() => { + TestEditorInputFactory.disableSerialize = false; + TestEditorInputFactory.disableDeserialize = false; + disposables.push(Registry.as(EditorExtensions.EditorInputFactories).registerEditorInputFactory('testEditorInputForGroups', TestEditorInputFactory)); }); @@ -296,18 +310,40 @@ suite('Workbench editor groups', () => { const input2 = input(); const input3 = input(); - // Pinned and Active + // Case 1: inputs can be serialized and deserialized + group.openEditor(input1, { pinned: true, active: true }); group.openEditor(input2, { pinned: true, active: true }); group.openEditor(input3, { pinned: false, active: true }); - const deserialized = createGroup(group.serialize()); + let deserialized = createGroup(group.serialize()); assert.equal(group.id, deserialized.id); assert.equal(deserialized.count, 3); + assert.equal(deserialized.getEditors(EditorsOrder.SEQUENTIAL).length, 3); + assert.equal(deserialized.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE).length, 3); assert.equal(deserialized.isPinned(input1), true); assert.equal(deserialized.isPinned(input2), true); assert.equal(deserialized.isPinned(input3), false); assert.equal(deserialized.isActive(input3), true); + + // Case 2: inputs cannot be serialized + TestEditorInputFactory.disableSerialize = true; + + deserialized = createGroup(group.serialize()); + assert.equal(group.id, deserialized.id); + assert.equal(deserialized.count, 0); + assert.equal(deserialized.getEditors(EditorsOrder.SEQUENTIAL).length, 0); + assert.equal(deserialized.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE).length, 0); + + // Case 3: inputs cannot be deserialized + TestEditorInputFactory.disableSerialize = false; + TestEditorInputFactory.disableDeserialize = true; + + deserialized = createGroup(group.serialize()); + assert.equal(group.id, deserialized.id); + assert.equal(deserialized.count, 0); + assert.equal(deserialized.getEditors(EditorsOrder.SEQUENTIAL).length, 0); + assert.equal(deserialized.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE).length, 0); }); test('One Editor', function () { diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 7ec1c795e9..640606f3cc 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -104,8 +104,9 @@ import { QuickInputService } from 'vs/workbench/services/quickinput/browser/quic import { IListService } from 'vs/platform/list/browser/listService'; import { win32, posix } from 'vs/base/common/path'; import { TestWorkingCopyService, TestContextService, TestStorageService, TestTextResourcePropertiesService, TestExtensionService } from 'vs/workbench/test/common/workbenchTestServices'; -import { IViewsService, IView } from 'vs/workbench/common/views'; +import { IViewsService, IView, ViewContainer, ViewContainerLocation } from 'vs/workbench/common/views'; import { IStorageKeysSyncRegistryService, StorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; +import { IPaneComposite } from 'vs/workbench/common/panecomposite'; export function createFileEditorInput(instantiationService: IInstantiationService, resource: URI): FileEditorInput { return instantiationService.createInstance(FileEditorInput, resource, undefined, undefined); @@ -474,14 +475,20 @@ export class TestPanelService implements IPanelService { export class TestViewsService implements IViewsService { _serviceBrand: undefined; - onDidChangeViewVisibilityEmitter = new Emitter<{ id: string; visible: boolean; }>(); + onDidChangeViewContainerVisibility = new Emitter<{ id: string; visible: boolean; location: ViewContainerLocation }>().event; + isViewContainerVisible(id: string): boolean { return true; } + getVisibleViewContainer(): ViewContainer | null { return null; } + openViewContainer(id: string, focus?: boolean): Promise { return Promise.resolve(null); } + closeViewContainer(id: string): void { } + + onDidChangeViewVisibilityEmitter = new Emitter<{ id: string; visible: boolean; }>(); onDidChangeViewVisibility = this.onDidChangeViewVisibilityEmitter.event; isViewVisible(id: string): boolean { return true; } getActiveViewWithId(id: string): T | null { return null; } openView(id: string, focus?: boolean | undefined): Promise { return Promise.resolve(null); } closeView(id: string): void { } - getProgressIndicator(id: string) { return null!; } + getViewProgressIndicator(id: string) { return null!; } } export class TestEditorGroupsService implements IEditorGroupsService { diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index c1a3f70da0..e91722266c 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -46,7 +46,7 @@ import 'vs/workbench/browser/parts/activitybar/activitybarPart'; import 'vs/workbench/browser/parts/panel/panelPart'; import 'vs/workbench/browser/parts/sidebar/sidebarPart'; import 'vs/workbench/browser/parts/statusbar/statusbarPart'; -import 'vs/workbench/browser/parts/views/views'; +import 'vs/workbench/browser/parts/views/viewsService'; //#endregion diff --git a/src/vs/workbench/workbench.web.api.ts b/src/vs/workbench/workbench.web.api.ts index 077751504e..095327b552 100644 --- a/src/vs/workbench/workbench.web.api.ts +++ b/src/vs/workbench/workbench.web.api.ts @@ -266,6 +266,9 @@ interface IWorkbenchConstructionOptions { */ readonly homeIndicator?: IHomeIndicator; + /** + * Optional default layout to apply on first time the workspace is opened. + */ readonly defaultLayout?: IDefaultLayout; //#endregion diff --git a/yarn.lock b/yarn.lock index 2058bd2692..967eb0ff8c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2810,10 +2810,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@7.2.2: - version "7.2.2" - resolved "https://registry.yarnpkg.com/electron/-/electron-7.2.2.tgz#92b159fa6e6c31dfcfe0c2818c7baacb81fa1c5f" - integrity sha512-8ppCjch2LRbi5JJ9D+gn+Q9erShNLDcN5ODRAPr/MAHqhTTlGBqCcvNMlCzRCEVV75PouKxVm7NjSKXBYlD9QA== +electron@7.2.4: + version "7.2.4" + resolved "https://registry.yarnpkg.com/electron/-/electron-7.2.4.tgz#9fc0446dae23ead897af8742470cb18da55c6ce9" + integrity sha512-Z+R692uTzXgP8AHrabE+kkrMlQJ6pnAYoINenwj9QSqaD2YbO8IuXU9DMCcUY0+VpA91ee09wFZJNUKYPMnCKg== dependencies: "@electron/get" "^1.0.1" "@types/node" "^12.0.12" @@ -9517,10 +9517,10 @@ typescript@^2.6.2: resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.2.tgz#3c5b6fd7f6de0914269027f03c0946758f7673a4" integrity sha1-PFtv1/beCRQmkCfwPAlGdY92c6Q= -typescript@^3.9.0-dev.20200427: - version "3.9.0-dev.20200427" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.0-dev.20200427.tgz#e42d606d938575dfb7b0b66f04a31b5f0eb0be57" - integrity sha512-ja/GhL7BHT+VQZiLoYMGJt2CP1Pdr0EhYefv4LLw4tVooSuCDB8SDKT/i/HwsoPgQ4ZaYfg1vPl+1RhiO3bwJg== +typescript@^3.9.1-rc: + version "3.9.1-rc" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.1-rc.tgz#81d5a5a0a597e224b6e2af8dffb46524b2eaf5f3" + integrity sha512-+cPv8L2Vd4KidCotqi2wjegBZ5n47CDRUu/QiLVu2YbeXAz78hIfcai9ziBiNI6JTGTVwUqXRug2UZxDcxhvFw== uc.micro@^1.0.1, uc.micro@^1.0.3: version "1.0.3" @@ -10294,30 +10294,30 @@ xtend@~2.1.1: dependencies: object-keys "~0.4.0" -xterm-addon-search@0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.6.0.tgz#542cc2c35e83e7332ce1982b65ad218ee769836c" - integrity sha512-k3EsZzUptCXygHFP5rQuCBdWWkI/ZNuX3pDSOVdxPV9jB7U5Aha9guTIZoMP7FIjL8jce+ClQs6q7VINcRV1+w== +xterm-addon-search@0.7.0-beta.2: + version "0.7.0-beta.2" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.7.0-beta.2.tgz#384bda136c707f97a77eefc76cc7d9e572ce0719" + integrity sha512-A9fyiBBvG6ZNIwSJ03+sRCv9y20/uzd1wjCoaYUqp9fu3YGiHaGwyo9rAfm2M/fQM5vBmyJk4Qw/lwVq7TtlAw== -xterm-addon-unicode11@0.2.0-beta.2: - version "0.2.0-beta.2" - resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.2.0-beta.2.tgz#2a13ba5b08fdb1005be241816c4e3302674db4af" - integrity sha512-Y047mnIWrAj65TpStdyPYoPeDTX4en+XX4Y90KuQB3cW2xIyZj25NSVV9BZdqzSb7gk9M6KBvIcm8chj7S2N8Q== +xterm-addon-unicode11@0.2.0-beta.5: + version "0.2.0-beta.5" + resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.2.0-beta.5.tgz#5961850162df20b5e966166423cd6957ac2db298" + integrity sha512-IjnbBcyfS5JgJDXPO0W2nk/VBtGwx6GWE2snMC676z4DmAABUqPXfTzJKfUoWqoT6UcbxB0oIjDzykCfoRJp6Q== -xterm-addon-web-links@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.3.0.tgz#88affe9235c928b41bab660a65330f46d91c940e" - integrity sha512-vGXiIDqNMyxK5S1IzOjDqcgeQrrv7TDcSHiOeCNAoWCI2f+Rap9d18gjgnMKPyR+AbG0KoKnaKA6Dc1du1vs5A== +xterm-addon-web-links@0.4.0-beta.5: + version "0.4.0-beta.5" + resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.4.0-beta.5.tgz#523fd0a1c5668370d73e05019ed16eaf596894c8" + integrity sha512-Qe0idPpSokCNvGrthSBjdrOZrsgXwnLYbzuv0JoEec/A9HVcxKmZ+ktw7fOA2gT/zbcwtrA5FWrir3GlRHglCQ== -xterm-addon-webgl@0.7.0-beta.8: - version "0.7.0-beta.8" - resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.7.0-beta.8.tgz#546651958d740bf05d6a05555fbcacd2759b2ee7" - integrity sha512-2jxMtRR5zgAar1gPqt0iD/+GOlZ3cHyzzbIbC77EBIdZZFuhEDhJkucVPPS2KPcyqw3VROL1FgX7BSEV2rvdeA== +xterm-addon-webgl@0.7.0-beta.10: + version "0.7.0-beta.10" + resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.7.0-beta.10.tgz#39fdb96351e97a1bf15f4c4c8944ba3d05cacee4" + integrity sha512-nQl/ASk+ck11aSrBZXb2a0tu+SNDnm89owBk/sAZeZzi5MHNo6bB8y2VTKNNC6D3i3aFouTz4VorYB25LUgNFg== -xterm@4.6.0-beta.25: - version "4.6.0-beta.25" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.6.0-beta.25.tgz#2faea6cf8c677ed545792562165604ce7f314026" - integrity sha512-63FLAUdJ8Bw9SMgLU3/r353P1WAtLxupbfvfddi4nMcz1WEGRq07O1CbmJn/bKHHkJw7gQQw0n1I8xnjFlLlTA== +xterm@4.6.0-beta.38: + version "4.6.0-beta.38" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.6.0-beta.38.tgz#8472b168941500c3071aba482c2b5c6040951ec7" + integrity sha512-Q+nOalMD1MDGOqXdtkGZmOQqbSBU+71vhlX2RBwQoSpJa1QBrKDAhSlN/J+/XvouvVEtCiEFDeacF4EufMEIMg== y18n@^3.2.1: version "3.2.1"